diff --git a/.autotest b/.autotest index 541dc4873..6ded48585 100644 --- a/.autotest +++ b/.autotest @@ -1,13 +1,29 @@ +require 'autotest/bundler' +require 'autotest/notify' + Autotest.add_hook(:initialize) {|at| + + at.options[:quiet] = true + %w{.git .swp .DS_Store ._* tmp rakefile .txt}.each do |exception| at.add_exception(exception) end - # at.clear_mappings # take out the default (test/test*rb) + at.add_mapping(%r{^lib/.*\.rb$}) {|f, _| Dir['spec/**/*.rb'] } - at.add_mapping(%r{^config/authorization\_rules\.rb$}) {|f, _| - Dir['spec/**/*.rb'] + + at.add_mapping(%r%^spec/acceptance/.*_spec.rb$%, true) { |filename, _| + filename + } + + at.add_mapping(%r%^app/(models|controllers|helpers|lib)/.*rb$%, true) { + at.files_matching %r%^spec/acceptance/.*_spec.rb$% + } + + at.add_mapping(%r%^app/views/(.*)$%, true) { + at.files_matching %r%^spec/acceptance/.*_spec.rb$% } + nil -} \ No newline at end of file +} diff --git a/.bundle/config b/.bundle/config deleted file mode 100644 index 741327a18..000000000 --- a/.bundle/config +++ /dev/null @@ -1,3 +0,0 @@ ---- -BUNDLE_DISABLE_SHARED_GEMS: "1" -BUNDLE_PATH: development diff --git a/.gems b/.gems deleted file mode 100644 index 412c24233..000000000 --- a/.gems +++ /dev/null @@ -1,11 +0,0 @@ -rails --version '>= 2.3.8' -authlogic -formtastic -paperclip -acts-as-taggable-on -declarative_authorization -validatable -haml -RedCloth -acts-as-list -glebm-geokit \ No newline at end of file diff --git a/.gitignore b/.gitignore index 77213088c..01150680f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,12 @@ -log/cucumber.log -log/development.log -log/test.log -log/production.log +.vimrc +log/*.log +log/*.lck tmp/**/* tmp/* doc/api doc/app config/database.yml +config/crypto.yml **/**/#* **/**/.#* *~x @@ -20,5 +20,17 @@ coverage.data rerun.txt config/config.yml public/stylesheets +public/assets development development/* +log/exceptional.log +log/sendmail.log +config/exceptional.yml +.sass-cache +*.swp +.idea* +.bundle/* +log/*.log +.rvmrc +log/sunspot-solr-development.log.1 +sunspot-solr.pid diff --git a/.gitignore.swp b/.gitignore.swp deleted file mode 100644 index b81840480..000000000 Binary files a/.gitignore.swp and /dev/null differ diff --git a/.rspec b/.rspec new file mode 100644 index 000000000..7dd4b932e --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--colour --drb --format Fuubar diff --git a/.slugignore b/.slugignore new file mode 100644 index 000000000..0bc612b7a --- /dev/null +++ b/.slugignore @@ -0,0 +1,3 @@ +selenium +autotest +spec \ No newline at end of file diff --git a/Capfile b/Capfile deleted file mode 100644 index 85817f026..000000000 --- a/Capfile +++ /dev/null @@ -1,3 +0,0 @@ -load 'deploy' if respond_to?(:namespace) # cap2 differentiator -Dir['vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) } -load 'config/deploy' diff --git a/Gemfile b/Gemfile index 094056999..dca1f254b 100644 --- a/Gemfile +++ b/Gemfile @@ -1,73 +1,127 @@ source :gemcutter -gem "rails", "2.3.9" -gem "pg" -gem 'subdomain-fu' -gem 'nested_layouts' -gem 'aasm' -gem 'authlogic' -gem 'compass' -gem 'haml' -gem 'formtastic' -gem 'paperclip' -gem "acts-as-taggable-on" -gem "BlueCloth", :require => "bluecloth" -gem "acts-as-list", :require =>"acts_as_list" -gem "glebm-geokit", :require => "geokit" -gem "cancan" -gem "friendly_id" -gem "tlsmail" -gem "resque" -gem 'resque_mailer' -gem "SystemTimer", :require => "system_timer" -gem 'exceptional' -gem 'aasm' -gem 'tmail' - -gem 'httparty' -gem 'json' -gem 'simple_uuid' -gem 'outside-in' -gem 'mcbean' -gem 'eventfulapi' - -gem 'redis' -gem 'redis-namespace' -gem 'yajl-ruby' -gem 'json' -gem 'resque' -gem 'resque-scheduler' - -gem 'twitter' - -gem 'god' - -group 'cli' do - gem 'text-reform' - gem 'thor' -end +gem 'rails', "~> 3.1.0" +gem 'rack' + +# API +gem 'sinatra' # Powers the api +#gem 'sinatra-reloader' +gem 'rack-contrib' +gem 'sham_rack' # For using the api in-process +gem 'faraday' # For using the api in-process +gem 'rack-cache' # For caching +gem 'dalli' # memcache client, for caching +gem 'acts_as_api' + +# ActiveRecord +gem 'sunspot_rails', :git => 'git://github.com/alindeman/sunspot.git' # database search +gem 'sunspot_solr' +gem 'pg' # for postgres +gem 'permanent_records' # adds soft-delete if a model has a deleted_at column +gem 'paperclip', "~> 2.4.4" # we use this to store avatars +gem 'rmagick' # we use this to crop avatars +gem 'geocoder' # we use geocoder to find user latlngs from addresses +gem 'glebm-geokit', :require => 'geokit' # use this to find latlngs from address again. try to remove in favor of geocoder + +# Deployment +gem 'thin' # lighter than mongrel, faster than webrick + +# Authentication +gem 'devise' # used for authentication +gem 'omniauth', "0.3.0" # used for authentication with facebook +gem 'uuid' # used in app/controllers/admin_controller.rb, could be refactored/removed? + +# Authorization +gem 'cancan' # Authorization, see app/models/ability.rb, should be refactored/redone + +# Assets +gem 'aws-s3', :require => 'aws/s3' # storing avatars and stuff + +# Worker Jobs +gem 'redis' # for queueing with resque +gem 'redis-namespace', :require => false # resque wants it, we don't need to require it +gem 'mcbean' # We use this to pull data from rss feeds for import +gem 'redcarpet' # We use this to format user messages in emails + + +# Jobs +gem 'resque', "~> 1.19.0" # use this to queue worker processes +gem 'resque-exceptional' # we use this to send notify of exceptions with worker processes +gem 'resque-scheduler' # we use this to queue jobs at specific times +gem 'hirefireapp' # auto-scale web and worker processes +gem 'delayed_job' # we use this to run jobs to index our data -group :production do -gem 'unicorn' +# Mail +gem 'mail' # Used for mail +gem 'mustache' # used for mail +gem 'premailer', :git => "git://github.com/Jberlinsky/premailer.git" # we use this to inline css in our emails + +# ActionView +gem 'sanitize' # used in app/controllers/posts_controller.rb (which is dead code) ! remove +gem 'haml', '~> 3.1' # used for view templates +gem 'formtastic' # used for view templates +gem 'sass', '~> 3.1' # used for stylesheets + +# Admin +gem 'activeadmin' # use as an easy admin tool +gem 'googlecharts' # used for admin/overview +gem 'garb' # used to access the Google Analytics API + +# Monitoring +gem 'exceptional' # we use this to notify on exceptions +gem 'rpm_contrib' # we use this to monitor the app +gem 'newrelic_rpm' # we use this to monitor the app + +# Features +gem 'rollout' # we use this to control features + +# Misc +gem 'json', "~> 1.6.0" # isn't json built-in? +gem 'system_timer', :platforms => [:ruby_18] # this is annoying +gem 'heroku' # access heroku api +gem 'rack-timeout' # Timeout requests that take too long + +group :assets do + gem 'sass-rails', " ~> 3.1.0" + gem 'coffee-rails', "~> 3.1.0" + gem 'uglifier' + gem 'compass', '0.12.alpha.0' end group :development, :test do - gem "factory_girl" - gem "ZenTest" - gem "forgery" - gem "rspec-rails", "~> 1.3" - gem "rspec", "~> 1.3" - gem "autotest-rails" + gem 'guard-jslint-on-rails' + gem 'rails-dev-tweaks', '~> 0.5.0' # Don't reload the code when serving assets + gem 'factory_girl' # we use factory_girl to generate models for tests + gem 'forgery' # we use forgery to generate data for tests + gem 'foreman' # we use foreman to start all the processes we need for development + gem 'pry' # for when IRB is not enough + gem 'guard' # because doing things manually is for suckers + gem 'guard-rspec' + gem 'guard-spork' + gem 'guard-bundler' + gem 'therubyracer' # because something was yelling at us for not having a javascript runtime + gem 'test_track' # jasmine doesn't support the assset pipeline yet, this helps + gem 'jasmine' end -group :development do - gem "capistrano" - gem "capistrano-ext"#, :lib => false - gem 'mongrel' +group :linux do + gem 'libnotify' + gem 'rb-inotify' end -group :test do - gem "rr" - gem "database_cleaner" +group :osx do + end +group :test do + gem 'rspec-rails' # we use rspec-rails for tests + gem 'fuubar' # we use fuubar for pretty rspec output + gem 'spork' # we use spork to speed up tests + gem 'rr' # we use rr for mocking + gem 'rspec-rr' # we use rspec-rr for integration between rspec and rr + gem 'webmock' # we use webmock to mock google maps and other apis + gem 'capybara', :git => 'git://github.com/jnicklas/capybara.git' # we use capybara for integration testing + gem 'launchy' # we use launchy to launch a browser during integration testing + gem 'database_cleaner' # we use database_cleaner to clean the database between tests + gem 'jasmine' # we use jasmine for javascript tests +end diff --git a/Gemfile.lock b/Gemfile.lock index b8b515098..20dbcc10b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,213 +1,511 @@ +GIT + remote: git://github.com/Jberlinsky/premailer.git + revision: cb80fdd5edfb091d5f1341db6c654b284265e79c + specs: + premailer (1.7.3) + css_parser (>= 1.1.9) + htmlentities (>= 4.0.0) + +GIT + remote: git://github.com/alindeman/sunspot.git + revision: f69629ccf5921b9c90f08121ed9a76cc05c8cc79 + specs: + sunspot (1.3.0.rc3) + escape (= 0.0.4) + pr_geohash (~> 1.0) + rsolr (= 1.0.2) + sunspot_rails (1.3.0.rc3) + nokogiri + sunspot (= 1.3.0.rc3) + sunspot_solr (1.3.0.rc3) + +GIT + remote: git://github.com/jnicklas/capybara.git + revision: c466a2bc8b61d5879b488d607350072019034224 + specs: + capybara (1.1.1) + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + selenium-webdriver (~> 2.0) + xpath (~> 0.1.4) + GEM remote: http://rubygems.org/ specs: - BlueCloth (1.0.1) - RedCloth (4.2.3) - SystemTimer (1.2.1) - ZenTest (4.4.2) - aasm (2.2.0) - actionmailer (2.3.9) - actionpack (= 2.3.9) - actionpack (2.3.9) - activesupport (= 2.3.9) - rack (~> 1.1.0) - activerecord (2.3.9) - activesupport (= 2.3.9) - activeresource (2.3.9) - activesupport (= 2.3.9) - activesupport (2.3.9) - acts-as-list (0.1.2) - acts-as-taggable-on (2.0.6) - addressable (2.2.2) - authlogic (2.1.6) - activesupport - autotest-rails (4.1.0) - ZenTest - babosa (0.2.0) - cancan (1.4.1) - capistrano (2.5.19) - highline - net-scp (>= 1.0.0) - net-sftp (>= 2.0.0) - net-ssh (>= 2.0.14) - net-ssh-gateway (>= 1.0.0) - capistrano-ext (1.2.1) - capistrano (>= 1.0.0) - cgi_multipart_eof_fix (2.5.0) - compass (0.10.6) - haml (>= 3.0.4) - crack (0.1.8) - creole (0.3.8) - daemons (1.1.0) - database_cleaner (0.6.0) - eventfulapi (2.2.1) - mime-types (> 0.0.0) + RedCloth (4.2.8) + XMLCanonicalizer (1.0.1) + log4r (>= 1.0.4) + actionmailer (3.1.0) + actionpack (= 3.1.0) + mail (~> 2.3.0) + actionpack (3.1.0) + activemodel (= 3.1.0) + activesupport (= 3.1.0) + builder (~> 3.0.0) + erubis (~> 2.7.0) + i18n (~> 0.6) + rack (~> 1.3.2) + rack-cache (~> 1.0.3) + rack-mount (~> 0.8.2) + rack-test (~> 0.6.1) + sprockets (~> 2.0.0) + activeadmin (0.3.1) + devise (>= 1.1.2) + fastercsv + formtastic (>= 1.1.0) + inherited_resources (< 1.3.0) + kaminari (>= 0.12.4) + meta_search (>= 0.9.2) + rails (>= 3.0.0) + sass (>= 3.1.0) + activemodel (3.1.0) + activesupport (= 3.1.0) + bcrypt-ruby (~> 3.0.0) + builder (~> 3.0.0) + i18n (~> 0.6) + activerecord (3.1.0) + activemodel (= 3.1.0) + activesupport (= 3.1.0) + arel (~> 2.2.1) + tzinfo (~> 0.3.29) + activeresource (3.1.0) + activemodel (= 3.1.0) + activesupport (= 3.1.0) + activesupport (3.1.0) + multi_json (~> 1.0) + acts_as_api (0.3.10) + activemodel (>= 3.0.0) + activesupport (>= 3.0.0) + rack (>= 1.1.0) + addressable (2.2.6) + arel (2.2.1) + aws-s3 (0.6.2) + builder + mime-types + xml-simple + bcrypt-ruby (3.0.1) + builder (3.0.0) + cancan (1.6.5) + childprocess (0.2.2) + ffi (~> 1.0.6) + chunky_png (1.2.4) + cocaine (0.2.0) + coderay (0.9.8) + coffee-rails (3.1.1) + coffee-script (>= 2.2.0) + railties (~> 3.1.0) + coffee-script (2.2.0) + coffee-script-source + execjs + coffee-script-source (1.1.2) + compass (0.12.alpha.0) + chunky_png (~> 1.2) + fssm (>= 0.2.7) + sass (~> 3.1) + crack (0.3.0) + creole (0.4.2) + css_parser (1.2.5) + addressable + daemons (1.1.4) + dalli (1.1.2) + database_cleaner (0.6.7) + delayed_job (2.1.4) + activesupport (~> 3.0) + daemons + devise (1.4.5) + bcrypt-ruby (~> 3.0) + orm_adapter (~> 0.0.3) + warden (~> 1.0.3) + diff-lcs (1.1.3) + erubis (2.7.0) + escape (0.0.4) + eventmachine (0.12.10) exceptional (2.0.32) rack - factory_girl (1.3.2) - faraday (0.5.4) - addressable (~> 2.2.2) + execjs (1.2.6) + multi_json (~> 1.0) + factory_girl (2.1.0) + faraday (0.7.4) + addressable (~> 2.2.6) multipart-post (~> 1.1.0) rack (>= 1.1.0, < 2) - faraday_middleware (0.3.1) - faraday (~> 0.5.3) - fastthread (1.0.7) - forgery (0.3.6) - formtastic (1.2.2) + fastercsv (1.5.4) + ffi (1.0.9) + foreman (0.22.0) + term-ansicolor (~> 1.0.5) + thor (>= 0.13.6) + forgery (0.5.0) + formtastic (1.2.4) actionpack (>= 2.3.7) activesupport (>= 2.3.7) - i18n (>= 0.4.0) - friendly_id (3.1.8) - babosa (~> 0.2.0) - gem_plugin (0.2.3) + i18n (~> 0.4) + fssm (0.2.7) + fuubar (0.0.6) + rspec (~> 2.0) + rspec-instafail (~> 0.1.8) + ruby-progressbar (~> 0.0.10) + garb (0.9.1) + activesupport (>= 2.2.0) + crack (>= 0.1.6) + geocoder (1.0.4) glebm-geokit (1.5.2) - god (0.11.0) - haml (3.0.25) - hashie (0.4.0) - highline (1.6.1) - httparty (0.6.1) - crack (= 0.1.8) - i18n (0.5.0) - json (1.4.6) - kgio (2.0.0) - loofah (1.0.0) - nokogiri (>= 1.3.3) + googlecharts (1.6.7) + guard (0.7.0) + thor (~> 0.14.6) + guard-bundler (0.1.3) + bundler (>= 1.0.0) + guard (>= 0.2.2) + guard-jslint-on-rails (0.0.7) + guard (>= 0.4.0) + jslint_on_rails (>= 1.0.6) + guard-rspec (0.4.5) + guard (>= 0.4.0) + guard-spork (0.2.1) + guard (>= 0.2.2) + spork (>= 0.8.4) + haml (3.1.3) + has_scope (0.5.1) + heroku (2.8.0) + launchy (>= 0.3.2) + rest-client (~> 1.6.1) + rubyzip + term-ansicolor (~> 1.0.5) + hike (1.2.1) + hirefireapp (0.0.5) + htmlentities (4.3.0) + i18n (0.6.0) + inherited_resources (1.2.2) + has_scope (~> 0.5.0) + responders (~> 0.6.0) + jasmine (1.1.0) + jasmine-core (>= 1.1.0) + rack (>= 1.1) + rspec (>= 1.3.1) + selenium-webdriver (>= 0.1.3) + jasmine-core (1.1.0) + jslint_on_rails (1.0.7) + json (1.6.1) + json_pure (1.6.1) + kaminari (0.12.4) + rails (>= 3.0.0) + launchy (2.0.5) + addressable (~> 2.2.6) + libnotify (0.5.7) + libv8 (3.3.10.2) + log4r (1.1.9) + loofah (1.2.0) + nokogiri (>= 1.4.4) + macaddr (1.4.0) + systemu (~> 2.2.0) + mail (2.3.0) + i18n (>= 0.4.0) + mime-types (~> 1.16) + treetop (~> 1.4.8) mcbean (0.4.0) RedCloth (>= 4.2.0) creole (>= 0.3.7) loofah (>= 0.4.7) rdiscount (>= 1.6.0) + meta_search (1.1.1) + actionpack (~> 3.1.0) + activerecord (~> 3.1.0) + activesupport (~> 3.1.0) + polyamorous (~> 0.5.0) + method_source (0.6.5) + ruby_parser (>= 2.0.5) mime-types (1.16) - mongrel (1.1.5) - cgi_multipart_eof_fix (>= 2.4) - daemons (>= 1.0.3) - fastthread (>= 1.0.1) - gem_plugin (>= 0.2.3) - multi_json (0.0.5) - multi_xml (0.2.0) - multipart-post (1.1.0) - nested_layouts (0.1.3) - actionpack (>= 2.3) - net-scp (1.0.4) - net-ssh (>= 1.99.1) - net-sftp (2.0.5) - net-ssh (>= 2.0.9) - net-ssh (2.0.23) - net-ssh-gateway (1.0.1) - net-ssh (>= 1.99.1) - nokogiri (1.4.4) - outside-in (1.0.0) - paperclip (2.3.8) + multi_json (1.0.3) + multi_xml (0.4.1) + multipart-post (1.1.3) + mustache (0.99.4) + net-ldap (0.2.2) + newrelic_rpm (3.1.2) + nokogiri (1.5.0) + oa-basic (0.3.0) + oa-core (= 0.3.0) + rest-client (~> 1.6.0) + oa-core (0.3.0) + oa-enterprise (0.3.0) + XMLCanonicalizer (~> 1.0.1) + addressable (~> 2.2.6) + net-ldap (~> 0.2.2) + nokogiri (~> 1.5.0) + oa-core (= 0.3.0) + pyu-ruby-sasl (~> 0.0.3.1) + rubyntlm (~> 0.1.1) + uuid + oa-more (0.3.0) + multi_json (~> 1.0.0) + oa-core (= 0.3.0) + rest-client (~> 1.6.0) + oa-oauth (0.3.0) + faraday (~> 0.7.3) + multi_json (~> 1.0.0) + multi_xml (~> 0.4.0) + oa-core (= 0.3.0) + oauth (~> 0.4.0) + oauth2 (~> 0.5.0) + oa-openid (0.3.0) + oa-core (= 0.3.0) + rack-openid (~> 1.3.1) + ruby-openid-apps-discovery (~> 1.2.0) + oauth (0.4.5) + oauth2 (0.5.1) + faraday (~> 0.7.4) + multi_json (~> 1.0.3) + omniauth (0.3.0) + oa-basic (= 0.3.0) + oa-core (= 0.3.0) + oa-enterprise (= 0.3.0) + oa-more (= 0.3.0) + oa-oauth (= 0.3.0) + oa-openid (= 0.3.0) + orm_adapter (0.0.5) + paperclip (2.4.4) + activerecord (>= 2.3.0) + activesupport (>= 2.3.2) + cocaine (>= 0.0.2) + mime-types + permanent_records (2.1.2) activerecord - activesupport - pg (0.10.0) - rack (1.1.0) - rails (2.3.9) - actionmailer (= 2.3.9) - actionpack (= 2.3.9) - activerecord (= 2.3.9) - activeresource (= 2.3.9) - activesupport (= 2.3.9) - rake (>= 0.8.3) - rake (0.8.7) - rdiscount (1.6.5) - redis (2.1.1) - redis-namespace (0.8.0) + pg (0.11.0) + polyamorous (0.5.0) + activerecord (~> 3.0) + polyglot (0.3.2) + pr_geohash (1.0.0) + pry (0.9.6) + coderay (>= 0.9.8) + method_source (>= 0.6.5) + ruby_parser (>= 2.0.5) + slop (~> 2.1.0) + pyu-ruby-sasl (0.0.3.3) + rack (1.3.3) + rack-cache (1.0.3) + rack (>= 0.4) + rack-contrib (1.1.0) + rack (>= 0.9.1) + rack-mount (0.8.3) + rack (>= 1.0.0) + rack-openid (1.3.1) + rack (>= 1.1.0) + ruby-openid (>= 2.1.8) + rack-ssl (1.3.2) + rack + rack-test (0.6.1) + rack (>= 1.0) + rack-timeout (0.0.3) + rails (3.1.0) + actionmailer (= 3.1.0) + actionpack (= 3.1.0) + activerecord (= 3.1.0) + activeresource (= 3.1.0) + activesupport (= 3.1.0) + bundler (~> 1.0) + railties (= 3.1.0) + rails-dev-tweaks (0.5.0) + rails (~> 3.1.0) + railties (3.1.0) + actionpack (= 3.1.0) + activesupport (= 3.1.0) + rack-ssl (~> 1.3.2) + rake (>= 0.8.7) + rdoc (~> 3.4) + thor (~> 0.14.6) + rake (0.9.2) + rb-inotify (0.8.6) + ffi (>= 0.5.0) + rdiscount (1.6.8) + rdoc (3.9.4) + redcarpet (1.17.2) + redis (2.2.2) + redis-namespace (1.0.3) redis (< 3.0.0) - resque (1.10.0) - json (~> 1.4.6) - redis-namespace (~> 0.8.0) + responders (0.6.4) + resque (1.19.0) + multi_json (~> 1.0) + redis-namespace (~> 1.0.2) sinatra (>= 0.9.2) vegas (~> 0.1.2) - resque-scheduler (1.9.7) + resque-exceptional (0.1.0) + resque (>= 1.8.0) + resque-scheduler (1.9.9) redis (>= 2.0.1) resque (>= 1.8.0) rufus-scheduler - resque_mailer (1.0.1) - rr (1.0.2) - rspec (1.3.1) - rspec-rails (1.3.3) - rack (>= 1.0.0) - rspec (= 1.3.1) - rufus-scheduler (2.0.7) - tzinfo - simple_oauth (0.1.3) - simple_uuid (0.1.1) - sinatra (1.1.0) + rest-client (1.6.7) + mime-types (>= 1.16) + rmagick (2.13.1) + rollout (0.3.0) + rpm_contrib (2.1.4) + newrelic_rpm (>= 3.1.1) + rr (1.0.4) + rsolr (1.0.2) + builder (>= 2.1.2) + rspec (2.6.0) + rspec-core (~> 2.6.0) + rspec-expectations (~> 2.6.0) + rspec-mocks (~> 2.6.0) + rspec-core (2.6.4) + rspec-expectations (2.6.0) + diff-lcs (~> 1.1.2) + rspec-instafail (0.1.8) + rspec-mocks (2.6.0) + rspec-rails (2.6.1) + actionpack (~> 3.0) + activesupport (~> 3.0) + railties (~> 3.0) + rspec (~> 2.6.0) + rspec-rr (0.1.0) + ruby-openid (2.1.8) + ruby-openid-apps-discovery (1.2.0) + ruby-openid (>= 2.1.7) + ruby-progressbar (0.0.10) + ruby_parser (2.3.0) + sexp_processor (~> 3.0) + rubyntlm (0.1.1) + rubyzip (0.9.4) + rufus-scheduler (2.0.10) + tzinfo (>= 0.3.23) + sanitize (2.0.3) + nokogiri (>= 1.4.4, < 1.6) + sass (3.1.7) + sass-rails (3.1.2) + actionpack (~> 3.1.0) + railties (~> 3.1.0) + sass (>= 3.1.4) + sprockets (~> 2.0.0) + tilt (~> 1.3.2) + selenium-webdriver (2.6.0) + childprocess (>= 0.2.1) + ffi (>= 1.0.7) + json_pure + rubyzip + sexp_processor (3.0.6) + sham_rack (1.3.3) + rack + sinatra (1.2.6) rack (~> 1.1) - tilt (~> 1.1) - subdomain-fu (0.5.4) - text-hyphen (1.0.0) - text-reform (0.2.0) - text-hyphen (~> 1.0.0) + tilt (>= 1.2.2, < 2.0) + slop (2.1.0) + spork (0.8.5) + sprockets (2.0.0) + hike (~> 1.2) + rack (~> 1.0) + tilt (~> 1.1, != 1.3.0) + system_timer (1.2.4) + systemu (2.2.0) + term-ansicolor (1.0.6) + test_track (0.0.4) + rails (~> 3.1.0) + therubyracer (0.9.4) + libv8 (~> 3.3.10) + thin (1.2.11) + daemons (>= 1.0.9) + eventmachine (>= 0.12.6) + rack (>= 1.0.0) thor (0.14.6) - tilt (1.1) - tlsmail (0.0.1) - tmail (1.2.7.1) - twitter (1.1.0) - faraday (~> 0.5.3) - faraday_middleware (~> 0.3.1) - hashie (~> 0.4.0) - multi_json (~> 0.0.5) - multi_xml (~> 0.2.0) - simple_oauth (~> 0.1.3) - tzinfo (0.3.23) - unicorn (3.1.0) - kgio (~> 2.0.0) - rack + tilt (1.3.3) + treetop (1.4.10) + polyglot + polyglot (>= 0.3.1) + tzinfo (0.3.29) + uglifier (1.0.3) + execjs (>= 0.3.0) + multi_json (>= 1.0.2) + uuid (2.3.4) + macaddr (~> 1.0) vegas (0.1.8) rack (>= 1.0.0) - yajl-ruby (0.7.8) + warden (1.0.5) + rack (>= 1.0) + webmock (1.7.6) + addressable (~> 2.2, > 2.2.5) + crack (>= 0.1.7) + xml-simple (1.1.0) + xpath (0.1.4) + nokogiri (~> 1.3) PLATFORMS ruby DEPENDENCIES - BlueCloth - SystemTimer - ZenTest - aasm - acts-as-list - acts-as-taggable-on - authlogic - autotest-rails + activeadmin + acts_as_api + aws-s3 cancan - capistrano - capistrano-ext - compass + capybara! + coffee-rails (~> 3.1.0) + compass (= 0.12.alpha.0) + dalli database_cleaner - eventfulapi + delayed_job + devise exceptional factory_girl + faraday + foreman forgery formtastic - friendly_id + fuubar + garb + geocoder glebm-geokit - god - haml - httparty - json + googlecharts + guard + guard-bundler + guard-jslint-on-rails + guard-rspec + guard-spork + haml (~> 3.1) + heroku + hirefireapp + jasmine + json (~> 1.6.0) + launchy + libnotify + mail mcbean - mongrel - nested_layouts - outside-in - paperclip + mustache + newrelic_rpm + omniauth (= 0.3.0) + paperclip (~> 2.4.4) + permanent_records pg - rails (= 2.3.9) + premailer! + pry + rack + rack-cache + rack-contrib + rack-timeout + rails (~> 3.1.0) + rails-dev-tweaks (~> 0.5.0) + rb-inotify + redcarpet redis redis-namespace - resque + resque (~> 1.19.0) + resque-exceptional resque-scheduler - resque_mailer + rmagick + rollout + rpm_contrib rr - rspec (~> 1.3) - rspec-rails (~> 1.3) - simple_uuid - subdomain-fu - text-reform - thor - tlsmail - tmail - twitter - unicorn - yajl-ruby + rspec-rails + rspec-rr + sanitize + sass (~> 3.1) + sass-rails (~> 3.1.0) + sham_rack + sinatra + spork + sunspot_rails! + sunspot_solr + system_timer + test_track + therubyracer + thin + uglifier + uuid + webmock diff --git a/Guardfile b/Guardfile new file mode 100644 index 000000000..57bbea7bf --- /dev/null +++ b/Guardfile @@ -0,0 +1,15 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +guard 'bundler' do + watch('Gemfile') + # Uncomment next line if Gemfile contain `gemspec' command + # watch(/^.+\.gemspec/) +end + +guard 'jslint-on-rails' do + # watch for changes to application javascript files + watch(%r{^app/javascripts/.*\.js$}) + # watch for changes to the JSLint configuration + watch('config/jslint.yml') +end diff --git a/Procfile b/Procfile new file mode 100644 index 000000000..2931e658a --- /dev/null +++ b/Procfile @@ -0,0 +1,5 @@ +web: bundle exec rails server thin -p $PORT +worker: QUEUE=* bundle exec rake resque:work --trace +delayed_job_worker: bundle exec rake jobs:work +clock: bundle exec rake resque:scheduler +sunspot: bundle exec rake sunspot:solr:run diff --git a/README b/README deleted file mode 100644 index b11e30f16..000000000 --- a/README +++ /dev/null @@ -1,9 +0,0 @@ - -Subdomains - - Everything happens on a subdomain; no part of the app runs without either admin.host or #{community.slug}.host. - - A community of name "West Roxbury" and slug "westroxbury" is setup by rake db:setup. To get this working at westroxbury.localhost, you'll have to add westroxbury.localhost to your /etc/hosts. When you're done, /etc/localhosts shoould have a line like: - - 127.0.0.1 localhost admin.localhost westroxbury.localhost - diff --git a/README.md b/README.md new file mode 100644 index 000000000..b13e6d548 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +Welcome to CommonPlace +==== + +CommonPlace is a Social Networking Site that includes everything needed to create +database-backed civic infrastructure according to the Model-View-Control pattern. + +This pattern splits the view (also called the presentation) into "dumb" +templates that are primarily responsible for inserting pre-built data in between +HTML tags. The model contains the "smart" domain objects (such as Account, +Product, Person, Post) that holds all the business logic and knows how to +persist themselves to a database. The controller handles the incoming requests +(such as Save New Account, Update Product, Show Post) by manipulating the model +and directing data to the view. + +In Rails, the model is handled by what's called an object-relational mapping +layer entitled Active Record. This layer allows you to present the data from +database rows as objects and embellish these data objects with business logic +methods. You can read more about Active Record in +link:files/vendor/rails/activerecord/README.html. + +The controller and view are handled by the Action Pack, which handles both +layers by its two parts: Action View and Action Controller. These two layers +are bundled in a single package due to their heavy interdependence. This is +unlike the relationship between the Active Record and Action Pack that is much +more separate. Each of these packages can be used independently outside of +Rails. You can read more about Action Pack in +link:files/vendor/rails/actionpack/README.html. + +Getting Started +---- + +1. Install and setup Git +2. Install RVM, and setup an installation of Ruby (1.9.2, or 1.9.3 if you want the tests to pass) +3. Follow the steps in `rvm notes` +4. Install Gem +5. `git clone git@github.com:commonplaceusa/commonplace.git` +6. `cd commonplace` +7. `gem install bundler` +8. Install Redis + * `sudo apt-get install redis-server` + +9. Install ImageMagick + * `sudo apt-get install imagemagick libmagick9-dev` + +10. Install Postgres + * `sudo apt-get install postgresql libpq-dev` + +11. `bundle install` +12. `cp config/database.yml.example config/database.yml` +13. Authenticate for the database + * `sudo su postgres` + * `createuser `username +14. `bundle exec rake db:setup` + +Run the server with `bundle exec foreman start` or `bundle exec rails s thin` + +Run sunspot with `bundle exec sunspot-solr run` + +Go to [http://localhost:5000/test](http://localhost:5000/test) and login with test@example.com:password + +TDD +---- + +It's nice to have reasonable assurrance that you didn't bork something by making a simple change or git merge. That's why we <3 tests. Write them to conform to the spec before you start writing the feature, and write the feature to conform to the tests. Anything going into master should pass all test cases, and all new code should be tested to the hilt. + diff --git a/Rakefile b/Rakefile index ecffcd6fa..4d98000a3 100644 --- a/Rakefile +++ b/Rakefile @@ -1,10 +1,9 @@ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. -require(File.join(File.dirname(__FILE__), 'config', 'boot')) - +require File.expand_path('../config/application', __FILE__) require 'rake' -require 'rake/testtask' -require 'rake/rdoctask' -require 'tasks/rails' \ No newline at end of file +# JSLint configuration + +Commonplace::Application.load_tasks diff --git a/TODO b/TODO deleted file mode 100644 index 6e7891c0c..000000000 --- a/TODO +++ /dev/null @@ -1,24 +0,0 @@ - -* Code Deletion - -_Stylesheets_ - - styled_box.sass - - merge _form.sass and _modal_form.sass -_Views_ - - - -* Small tasks - - -* Medium tasks - -** Merge the controllers in the management namespace with the un-namespaced controllers. There isn't significant overlap as far as controller actions go, and having the management namespace isn't as clean as originally thought - -** Update to the latest version of sammy. Which has breaking changes to our codebase, but introduces some cool stuff. See http://www.quirkey.com/blog/2010/09/02/sammy-0-6-california-suite/ - -** Hand-in-hand with updating sammy, get rid of those *.json.erb files, and give up all pretense of supporting non-javascript enabled browsers. (Facebook doesn't!) - -* Large tasks - -** Rails 3 diff --git a/app/admin/announcements.rb b/app/admin/announcements.rb new file mode 100644 index 000000000..659f3b61c --- /dev/null +++ b/app/admin/announcements.rb @@ -0,0 +1,3 @@ +ActiveAdmin.register Announcement do + menu :parent => "Posts" +end diff --git a/app/admin/communities.rb b/app/admin/communities.rb new file mode 100644 index 000000000..eec7a5b99 --- /dev/null +++ b/app/admin/communities.rb @@ -0,0 +1,19 @@ +ActiveAdmin.register Community do + index do + column :name do |name| + link_to name.name, admin_community_path(name) + end + column :slug + column :zip_code + column :signup_message + column :organizer_email + column :organizer_name + column :organizer_about + column :households + end + filter :name + filter :slug + filter :zip_code + filter :organizer_name + filter :households +end diff --git a/app/admin/dashboards.rb b/app/admin/dashboards.rb new file mode 100644 index 000000000..ca1b59ead --- /dev/null +++ b/app/admin/dashboards.rb @@ -0,0 +1,36 @@ +ActiveAdmin::Dashboards.build do + + # Define your dashboard sections here. Each block will be + # rendered on the dashboard in the context of the view. So just + # return the content which you would like to display. + + # == Simple Dashboard Section + # Here is an example of a simple dashboard section + # + # section "Recent Posts" do + # ul do + # Post.recent(5).collect do |post| + # li link_to(post.title, admin_post_path(post)) + # end + # end + # end + + # == Render Partial Section + # The block is rendererd within the context of the view, so you can + # easily render a partial rather than build content in ruby. + # + # section "Recent Posts" do + # render 'recent_posts' # => this will render /app/views/admin/dashboard/_recent_posts.html.erb + # end + + # == Section Ordering + # The dashboard sections are ordered by a given priority from top left to + # bottom right. The default priority is 10. By giving a section numerically lower + # priority it will be sorted higher. For example: + # + # section "Recent Posts", :priority => 10 + # section "Recent User", :priority => 1 + # + # Will render the "Recent Users" then the "Recent Posts" sections on the dashboard. + +end diff --git a/app/admin/events.rb b/app/admin/events.rb new file mode 100644 index 000000000..1bfe9d147 --- /dev/null +++ b/app/admin/events.rb @@ -0,0 +1,3 @@ +ActiveAdmin.register Event do + menu :parent => "Posts" +end diff --git a/app/admin/feeds.rb b/app/admin/feeds.rb new file mode 100644 index 000000000..8ac277b20 --- /dev/null +++ b/app/admin/feeds.rb @@ -0,0 +1,20 @@ +ActiveAdmin.register Feed do + index do + column :name do |feed| + link_to feed.name, admin_feed_path(feed) + end + column :created_at + column :about + column :phone + column :website + column :category + column :feed_url + column :address + column :slug + column :twitter_name + end + + filter :name + filter :feed_url + filter :twitter_name +end diff --git a/app/admin/group_posts.rb b/app/admin/group_posts.rb new file mode 100644 index 000000000..89a923b69 --- /dev/null +++ b/app/admin/group_posts.rb @@ -0,0 +1,3 @@ +ActiveAdmin.register GroupPost do + menu :parent => "Posts" +end diff --git a/app/admin/groups.rb b/app/admin/groups.rb new file mode 100644 index 000000000..716bb7788 --- /dev/null +++ b/app/admin/groups.rb @@ -0,0 +1,14 @@ +ActiveAdmin.register Group do + index do + column :name do |group| + link_to group.name, admin_group_path(group) + end + column :slug + column :about + column :created_at + end + + filter :name + filter :slug + filter :created_at +end diff --git a/app/admin/neighborhoods.rb b/app/admin/neighborhoods.rb new file mode 100644 index 000000000..aeb15e79b --- /dev/null +++ b/app/admin/neighborhoods.rb @@ -0,0 +1,3 @@ +ActiveAdmin.register Neighborhood do + +end diff --git a/app/admin/posts.rb b/app/admin/posts.rb new file mode 100644 index 000000000..d9999882d --- /dev/null +++ b/app/admin/posts.rb @@ -0,0 +1,3 @@ +ActiveAdmin.register Post do + +end diff --git a/app/admin/requests.rb b/app/admin/requests.rb new file mode 100644 index 000000000..d61afb25c --- /dev/null +++ b/app/admin/requests.rb @@ -0,0 +1,9 @@ +ActiveAdmin.register Request do + index do + column :community_name + column :name + column :email + column :sponsor_organization + column :created_at + end +end diff --git a/app/admin/users.rb b/app/admin/users.rb new file mode 100644 index 000000000..eebd3ca7e --- /dev/null +++ b/app/admin/users.rb @@ -0,0 +1,9 @@ +ActiveAdmin.register User do + index do + column :email do |user| + link_to user.email, admin_user_path(user) + end + column :first_name + column :last_name + end +end diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 3a354bbe1..4c6292708 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -1,121 +1,87 @@ -class AccountsController < CommunitiesController +class AccountsController < ApplicationController layout 'application' protect_from_forgery :except => :update - - def new - if can? :create, User - @user = User.new - if params[:short] - params[:action] = "new short" - render :short, :layout => 'application' - else - render :layout => 'application' - end - else - redirect_to root_url - end - end - - def create - authorize! :create, User - @user = User.new(params[:user]) + before_filter :authenticate_user!, :except => :learn_more - position = LatLng.from_address(@user.address, - current_community.zip_code) - if position - @user.neighborhood = current_community.neighborhoods.to_a. - find(lambda{current_community.neighborhoods.first}) do |n| - position.within?(n.bounds) if n.bounds - end - else - @user.neighborhood = current_community.neighborhoods.first - end - - if @user.save - if params[:short] - redirect_to new_feed_url - else - redirect_to edit_new_account_url - end - else - render params[:short] ? :short : :new - end - end - - def create_from_facebook - authorize! :create, User - puts params.inspect + def delete end + def destroy + current_user.destroy + redirect_to root_url + end + def edit if can? :edit, current_user render :layout => 'application' else - redirect_to root_url + redirect_to '/users/sign_in' end end def settings if current_user.update_attributes(params[:user]) + sign_in(current_user, :bypass => true) redirect_to root_url else render :edit end end - def avatar + def update current_user.update_attributes(params[:user]) - render :nothing => true + sign_in(current_user, :bypass => true) + redirect_to root_url end - - def edit_new - if current_user.facebook_uid - current_user.avatar = Avatar.create(:avatar_remote_url => "http://graph.facebook.com/" + current_user.facebook_uid.to_s + "/picture/") - current_user.save! - end + + def learn_more + render :layout => false end - def update_new - authorize! :update, User - if current_user.facebook_uid.present? - current_user.password = "FACEBOOK_OVERRIDE" + def facebook_invite + # Twitter doesn't like https... + unless logged_in? + raise CanCan::AccessDenied end - if current_user.update_attributes(params[:user]) && current_user.password.present? - redirect_to root_url - else - current_user.errors.add("password", "Please create a password") - @user = current_user - render :edit_new + if request.ssl? + redirect_to :protocol => "http://" end + @invitation = Invite.new end - def update - current_user.update_attributes(params[:user]) - end + def make_focp + user = User.find_by_email(params[:email]) + slug = user.community.slug - def edit_avatar - @avatar = current_user.avatar - end - - def update_avatar - @avatar = current_user.avatar - @avatar.update_attributes(params[:avatar]) - @avatar.save - redirect_to new_first_post_url - end + # TODO: Get an auth token + url = URI.parse('https://www.google.com/accounts/ClientLogin') + req = Net::HTTP::Post.new(url.path) + req.add_field('Content-type', 'application/x-www-form-urlencoded') + data = "&Email=jason%40commonplaceusa%2Ecom&Passwd=601845jrB&accountType=HOSTED&service=apps" + req.form_data = data + con = Net::HTTP.new(url.host, url.port) + con.use_ssl = true + res = con.start { |http| http.request(req) } + logger.info res.body - def learn_more - end + url = URI.parse('https://apps-apis.google.com/a/feeds/group/2.0/commonplace.in/friendsofcommonplace' + slug + '@commonplace.in') + req = Net::HTTP::Post.new(url.path) + req.add_field('Content-type', 'application/atom+xml') + req.add_field('Authorization', 'GoogleLogin auth=' + auth_token) + data = '' + req.form_data = data + con = Net::HTTP.new(url.host, url.port) + con.use_ssl = true + con.start { |http| http.request(req) } - def edit_interests + render :nothing => true end - def update_interests - current_user.interest_list = params[:user][:interest_list] - current_user.save - redirect_to root_url + def profile + authorize! :update, User end + end diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb new file mode 100644 index 000000000..25ecca6d0 --- /dev/null +++ b/app/controllers/admin_controller.rb @@ -0,0 +1,60 @@ +class AdminController < ApplicationController + + before_filter :verify_admin + def verify_admin + unless current_user.admin + redirect_to root_url + end + end + + def view_messages + @messages = Message.find(:all, :order => "id desc", :limit => 50).sort { |x, y| y.created_at <=> x.created_at } + render :layout => false + end + + + def overview + @communities = ActiveSupport::JSON.decode(Resque.redis.get "statistics:community") + @overall_statistics = ActiveSupport::JSON.decode(Resque.redis.get "statistics:overall") + @historical_statistics = StatisticsAggregator.historical_statistics + render :layout => nil + end + + def clipboard + require 'uuid' + UUID.state_file = false + uuid = UUID.new + if params[:registrants].present? + entries = params[:registrants].split("\n") + email_addresses_registered = [] + entries.each do |e| + entry = e.split(';') + name = entry[0] + email = entry[1] + address = entry[2] + half_user = HalfUser.new(:full_name => name, :email => email, :street_address => address, :community => Community.find(params[:clipboard_community]), :single_access_token => uuid.generate) + if half_user.save + email_addresses_registered << email + kickoff.deliver_clipboard_welcome(half_user) + end + end + flash[:notice] = "Registered #{email_addresses_registered.count} users: #{email_addresses_registered.join(', ')}" + end + end + + def export_csv + csv = "Date,Users,Posts,Events,Announcements,Private Messages,Group Posts" + today = DateTime.now + slug = params[:community] + community = Community.find_by_slug(slug) + launch = community.users.sort{ |a,b| a.created_at <=> b.created_at }.first.created_at.to_date + launch.upto(today).each do |day| + csv = "#{csv}\n#{day},#{community.users.between(launch.to_datetime,day.to_datetime).count},#{community.posts.between(launch.to_datetime,day.to_datetime).count},#{community.events.between(launch.to_datetime,day.to_datetime).count},#{community.announcements.between(launch.to_datetime,day.to_datetime).count},#{community.private_messages.select { |m| m.between?(launch.to_datetime, day.to_datetime)}.count},#{community.group_posts.select { |p| p.between?(launch.to_datetime, day.to_datetime)}.count}" + end + + send_data csv, :type => 'text/csv; charset=iso-8859-1; header=present', :disposition => "attachment; filename=#{slug}.csv" + end + + def show_referrers ; end + def map ; end +end diff --git a/app/controllers/administration/addresses_controller.rb b/app/controllers/administration/addresses_controller.rb deleted file mode 100644 index f685da19f..000000000 --- a/app/controllers/administration/addresses_controller.rb +++ /dev/null @@ -1,16 +0,0 @@ -class Administration::AddressesController < AdministrationController - - def index - @addresses = Address.all - end - - def create - @address = Address.new(params[:address]) - @address.save - respond_to do |format| - format.json { render :json => @address } - end - end - - -end diff --git a/app/controllers/administration/communities_controller.rb b/app/controllers/administration/communities_controller.rb deleted file mode 100644 index f8d8bcd09..000000000 --- a/app/controllers/administration/communities_controller.rb +++ /dev/null @@ -1,39 +0,0 @@ -class Administration::CommunitiesController < ApplicationController - layout 'administration' - - def index - @communities = Community.all - end - - def new - @community = Community.new - @community.neighborhoods = [Neighborhood.new] - render :layout => "administration" - end - - def create - params[:community][:neighborhoods_attributes].each do |i,n| - n[:bounds] = YAML::load(n[:bounds]) - end - @community = Community.new(params[:community]) - if @community.save - redirect_to communities_url - else - render :new - end - end - - def edit - @community = Community.find params[:id] - end - - def update - @community = Community.find params[:id] - if @community.update_attributes(params[:community]) - logger.info @community.inspect - redirect_to communities_url - else - render :edit - end - end -end diff --git a/app/controllers/administration/feeds_controller.rb b/app/controllers/administration/feeds_controller.rb deleted file mode 100644 index 3b5a28c44..000000000 --- a/app/controllers/administration/feeds_controller.rb +++ /dev/null @@ -1,37 +0,0 @@ -class Administration::FeedsController < AdministrationController - - def index - @feeds = Feed.all - end - - def show - @feed = Feed.find(params[:id]) - end - - def new - @feed = Feed.new - end - - def create - @feed = Feed.new(params[:feed]) - if @feed.save - redirect_to :index - else - render :new - end - end - - def edit - @feed = Feed.find(params[:id]) - end - - def update - @feed = Feed.find(params[:id]) - if @feed.update_attributes(params[:feed]) - redirect_to feeds_path - else - render :edit - end - end - -end diff --git a/app/controllers/administration_controller.rb b/app/controllers/administration_controller.rb deleted file mode 100644 index c56e329c1..000000000 --- a/app/controllers/administration_controller.rb +++ /dev/null @@ -1,3 +0,0 @@ -class AdministrationController < ApplicationController - -end diff --git a/app/controllers/announcements_controller.rb b/app/controllers/announcements_controller.rb deleted file mode 100644 index d15253d8c..000000000 --- a/app/controllers/announcements_controller.rb +++ /dev/null @@ -1,27 +0,0 @@ -class AnnouncementsController < CommunitiesController - load_and_authorize_resource - - def index - @items = current_community.announcements.all(:order => "created_at DESC") - end - - def subscribed - @items = current_user.subscribed_announcements - render :index - end - - def show - end - - def new - end - - def create - @announcement = Announcement.new(params[:announcement]) - if @announcement.save - redirect_to announcements_path - else - render :new - end - end -end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 60ebdb564..93f36c9fb 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,112 +1,111 @@ -# Filters added to this controller apply to all controllers in the application. -# Likewise, all the methods added will be available for all controllers. - class ApplicationController < ActionController::Base - helper :all # include all helpers, all the time - include FeedsHelper + protect_from_forgery + helper :all helper_method :current_community - helper_method :current_neighborhood - helper_method 'xhr?' - #Temporarily removed the below line to test e-mail parsing. - protect_from_forgery :except => :parse # See ActionController::RequestForgeryProtection for details + helper_method 'logged_in?' - # Scrub sensitive parameters from your log - filter_parameter_logging :password + helper_method :api, :serialize + + before_filter :domain_redirect, :set_locale, :set_api_token - before_filter :set_template_format, :set_facebook_session - - filter_parameter_logging :password, :password_confirmation - helper_method :current_user_session, :current_user, :facebook_session - rescue_from CanCan::AccessDenied do |exception| store_location - redirect_to root_url + redirect_to "/users/sign_in" end - - def set_neighborhood - if current_user.admin? - session[:neighborhood_id] = params[:neighborhood_id] - end - redirect_to root_url + + def kickoff + @kickoff ||= KickOff.new end - + + def after_sign_out_path_for(resource_or_scope) + "/users/sign_in" + end + protected + + def set_api_token + if logged_in? + cookies['authentication_token'] = current_user.authentication_token + end + end + + def serialize(thing) + Serializer::serialize(thing).to_json.html_safe + end + + def cp_client + @_cp_client ||= CPClient.new(:host => "http://commonplace.api", :api_key => current_user.authentication_token) + end def translate_with(options = {}) @default_translate_options ||= {} @default_translate_options.merge!(options) end - def set_template_format - if xhr? - response.template.template_format = :json - end - end + def domain_redirect + return unless Rails.env.production? + return if request.host == "commonplace.herokuapp.com" + case request.host - def current_community - @current_community ||= Community.find_by_slug(current_subdomain) - translate_with :community => @current_community.name - @current_community - end + when %r{^www\.ourcommonplace\.com$} + return - def current_neighborhood - @current_neighborhood ||= - (current_user.admin? && session[:neighborhood_id]) ? Neighborhood.find(session[:neighborhood_id]) : - current_user.neighborhood - end + when %r{^assets\.} + return + + when %r{^ourcommonplace\.com$} + redirect_to "https://www.ourcommonplace.com#{request.fullpath}", :status => 301 + return + + when %r{^(?:www\.)?([a-zA-Z]+)\.ourcommonplace\.com$} + if request.path == "/" || request.path == "" + redirect_to "https://www.ourcommonplace.com/#{$1}", :status => 301 + else + redirect_to "https://www.ourcommonplace.com#{request.fullpath}", :status => 301 + end - def authorize_current_community - if @current_community - authorize! :read, @current_community - else - raise CanCan::AccessDenied + when %r{^(?:www\.)?commonplaceusa.com$} + case request.path + when %r{^/$} + redirect_to "https://www.ourcommonplace.com/about", :status => 301 + else + redirect_to "https://www.ourcommonplace.com#{request.fullpath}", :status => 301 + end end + end - def store_location - session[:return_to] = request.request_uri - end - - def redirect_back_or_default(default) - redirect_to(session[:return_to] || default) - session[:return_to] = nil - end + def current_community + @_community ||= if params[:community] + Community.find_by_slug(params[:community]) + elsif logged_in? + current_user.community + else + nil + end + + if @_community + params[:community] = @_community.slug + translate_with :community => @_community.name + Time.zone = @_community.time_zone + end - def current_user_session - return @current_user_session if defined?(@current_user_session) - @current_user_session = UserSession.find + @_community end - def current_user - return @current_user if defined?(@current_user) - @current_user = current_user_session && current_user_session.user || User.new(:neighborhood_id => 1, :avatar => Avatar.new) - end - - def reload_current_user! - @current_user_session = UserSession.find - @current_user = current_user_session.user + def store_location + session["user_return_to"] = request.fullpath end - def xhr? - request.env['HTTP_X_REQUESTED_WITH'].present? || params[:xhr] + def logged_in? + user_signed_in? end - def redirect_to(options = {}, response_status = {}) - if xhr? - render :json => {"redirect_to" => options} - else - super(options, response_status) + def set_locale + if current_community.present? + I18n.default_locale = :en + I18n.locale = current_community.locale end end - - #def facebook_session - # puts CGI.parse(cookies['fbs_179741908724938']).keys - # nil - #end - - #def set_facebook_session - # #puts cookies - #end - - + end diff --git a/app/controllers/attendances_controller.rb b/app/controllers/attendances_controller.rb deleted file mode 100644 index d2b7fedbd..000000000 --- a/app/controllers/attendances_controller.rb +++ /dev/null @@ -1,19 +0,0 @@ -class AttendancesController < CommunitiesController - - def create - @event = Event.find(params[:event_id]) - unless @event.attendees.exists?(current_user) - @event.attendees << current_user - end - flash[:message] = "You are attending #{@event.name}" - redirect_to event_path(@event) - end - - def destroy - @event = Event.find(params[:event_id]) - @event.attendees.delete(current_user) - flash[:message] = "You are no longer attending #{@event.name}" - redirect_to event_path(@event) - end - -end diff --git a/app/controllers/avatars_controller.rb b/app/controllers/avatars_controller.rb deleted file mode 100644 index 31d39d4f4..000000000 --- a/app/controllers/avatars_controller.rb +++ /dev/null @@ -1,14 +0,0 @@ -class AvatarsController < CommunitiesController - - def edit - @avatar = Avatar.find(params[:id]) - end - - def update - @avatar = Avatar.find(params[:id]) - @avatar.update_attributes(params[:avatar]) - @avatar.save - redirect_to management_url - end - -end diff --git a/app/controllers/bootstraps_controller.rb b/app/controllers/bootstraps_controller.rb new file mode 100644 index 000000000..b5f3855dd --- /dev/null +++ b/app/controllers/bootstraps_controller.rb @@ -0,0 +1,16 @@ +class BootstrapsController < ApplicationController + + # these actions give just a little bit of information to the browser before + # handing things over to Backbone.js + + layout false + + before_filter :authenticate_user! + + def community ; end + + def feed ; end + + def group ; end + +end diff --git a/app/controllers/claims_controller.rb b/app/controllers/claims_controller.rb deleted file mode 100644 index 712b5b392..000000000 --- a/app/controllers/claims_controller.rb +++ /dev/null @@ -1,48 +0,0 @@ -class ClaimsController < CommunitiesController - before_filter :feed - - def new - render :layout => false - end - - def create - if params[:code] == @feed.code - @feed.admins << current_user - @feed.claimed = true - @feed.save - redirect_to edit_feed_url(@feed) - else - flash.now[:error] = "Sorry, that claim code is not valid." - render :new - end - end - - def edit - respond_to do |format| - format.json - end - end - - def update - respond_to do |format| - if @feed.update_attributes(params[:feed]) - format.json - else - format.json - end - end - end - - def edit_fields - respond_to do |format| - format.json - end - end - - protected - - def feed - @feed = Feed.find(params[:feed_id]) - end - -end diff --git a/app/controllers/communities_controller.rb b/app/controllers/communities_controller.rb index e74bb89ac..cb601a8cb 100644 --- a/app/controllers/communities_controller.rb +++ b/app/controllers/communities_controller.rb @@ -1,17 +1,14 @@ class CommunitiesController < ApplicationController - before_filter :current_community - before_filter :authorize_current_community - layout 'communities' + def good_neighbor_discount + render :layout => "application" + end + + def faq ; end - def show - if current_user_session - @posts = current_neighborhood.posts.sort_by(&:created_at).reverse.take(3) - @announcements = current_community.announcements.all(:order => 'created_at DESC').take(3) - @events = current_community.events.take(3) - else - redirect_to new_account_url - end + def send_faq + kickoff.deliver_admin_question(params[:email_address], params[:message], params[:name]) + redirect_to faq_url end end diff --git a/app/controllers/deliveries_controller.rb b/app/controllers/deliveries_controller.rb deleted file mode 100644 index 615c9e6f1..000000000 --- a/app/controllers/deliveries_controller.rb +++ /dev/null @@ -1,8 +0,0 @@ -class DeliveriesController < AdministrationController - - def index - @deliveries = ActionMailer::Base.deliveries.sort_by(&:date).reverse - end - - -end diff --git a/app/controllers/email_parse_controller.rb b/app/controllers/email_parse_controller.rb index fec9a72e8..7798ae6b6 100644 --- a/app/controllers/email_parse_controller.rb +++ b/app/controllers/email_parse_controller.rb @@ -1,45 +1,128 @@ -class EmailParseController < ApplicationController +class EmailParseController < ActionController::Base - def EmailParseController.strip(text,to) - # Strip any replies from the text - - # Check for key phrases - phrases = ['-- \n','--\n','-----Original Message-----','________________________________','From: ','Sent from my ',TMail::Address.parse(to).spec,TMail::Address.parse(to).spec.match(/[A-Za-z0-9]*/)[0]] - - index = text.length + 1 - - phrases.each { |phrase| - newIndex = text.index(phrase) - if newIndex && newIndex < index - index = newIndex + protect_from_forgery :only => [] + before_filter :check_user, :filter_out_of_office + + def kickoff + @kickoff ||= KickOff.new + end + + def parse + case to + + when /reply\+([a-zA-Z_0-9]+)/ + if reply = Reply.create(:body => body_text, :repliable => Repliable.find($1), :user => user) + kickoff.deliver_reply(reply) end - } - - # Erase everything before the key phrase - text = text[0,index] - - # Find the last \n character in the remaining text and erase everything after it - - index = text.rindex("\n") - if index - text[0,text.rindex("\n")] + when 'notifications' + logger.info(< body_text, :user => user, :subject => params[:subject], :community => user.community) + + kickoff.deliver_post_confirmation(post) + kickoff.deliver_post(post) + end + + elsif feed = user.community.feeds.find_by_slug(to) + if feed.user_id == user.id + announcement = Announcement.create(:body => body_text, :owner => feed, :subject => params[:subject], :community => feed.community) + kickoff.deliver_announcement(announcement) if announcement + kickoff.deliver_announcement_confirmation(announcement) if announcement + else + kickoff.deliver_feed_permission_warning(user, feed) + end + + else + kickoff.deliver_unknown_address_warning(user) + end + end + + ensure + render :nothing => true + end + + def self.strip_email_body(text) + text.split(%r{(^-- \n) # match standard signature + |(^--\n) # match non-stantard signature + |(^-----Original\ Message-----) # Outlook + |(^----- Original\ Message -----) # Outlook + |(^________________________________) # Outlook + |(-*\ ?Original\ Message\ ?-*) # Generic + |(On.*wrote:) # OS X Mail.app + |(From:\ ) # Outlook and some others + |(Sent\ from) # iPhone, Blackberry + |(In\ a\ message\ dated.*,) + }x).first end - def parse - user = User.find_by_email(TMail::Address.parse(params[:from]).spec) - post = Post.find_by_long_id(TMail::Address.parse(params[:to]).spec.match(/[A-Za-z0-9]*/)[0]) - if user && post - text = EmailParseController.strip(params[:text],params[:to]) + protected + + def out_of_office_regexp + %r{(out\ of\ office) + |(out\ of\ the\ office)}xi + end + def filter_out_of_office + if params['stripped-text'].match(out_of_office_regexp) + render :nothing => true + return false + end + end - Reply.create(:body => text, - :repliable => post, - :user => user) + def current_community + user.try(:community) + end + + def check_user + if user.nil? + kickoff.deliver_unknown_user_warning(from) + render :nothing => true + false + else + true end - - render :nothing => true + end + + def user + @user ||= User.find_by_email(from) + end + + def body_text + @body_text ||= + if personalized_filters.has_key?(from) + personalized_filters[from].call(params['body-html']) + else + EmailParseController.strip_email_body(params['stripped-text']) + end + end + + def to + @to ||= Mail::Address.new(params[:recipient]).address.slice(/^[^@]*/) + end + + def from + @from ||= Mail::Address.new(params[:from]).address + end + + def personalized_filters + { + "dwayne.patterson@raleighnc.gov" => lambda do |text| + text.match(/
(.*?)
/m)[1]. + gsub(/<.*?>/m,""). + gsub(" ",""). + gsub("’", "'"). + gsub(/\n\n\n*/,"\n\n") + end + } + end end diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb deleted file mode 100644 index 1aabda5e9..000000000 --- a/app/controllers/events_controller.rb +++ /dev/null @@ -1,46 +0,0 @@ -class EventsController < CommunitiesController - before_filter :owner - load_and_authorize_resource - - def index - @items = current_community.events - end - - def your - @items = current_user.events - render :index - end - - def suggested - @items = current_user.suggested_events - render :index - end - - def new - end - - def create - @event = Event.new(params[:event].merge(:owner => @owner)) - if @event.save - redirect_to events_path - else - render :new - end - end - - def update - end - def show - if current_user.events.include?(@event) && !flash.now[:message] - flash.now[:message] = "You are attending #{@event.name}" - end - end - - def owner - if params[:event] && params[:event][:owner] - owner_class, owner_id = params[:event].delete(:owner).try(:split, "_") - @owner = owner_class.capitalize.constantize.find(owner_id.to_i) - end - end -end - diff --git a/app/controllers/facebook_canvas_controller.rb b/app/controllers/facebook_canvas_controller.rb new file mode 100644 index 000000000..02fe50b70 --- /dev/null +++ b/app/controllers/facebook_canvas_controller.rb @@ -0,0 +1,12 @@ +require 'open-uri' + +class FacebookCanvasController < ApplicationController + + def index + @request_id = params[:request_ids] + url = "https://graph.facebook.com/oauth/access_token?&client_id=#{$FacebookConfig['app_id']}&client_secret=#{$FacebookConfig['app_secret']}&grant_type=client_credentials" + @application_token = open(url).read.gsub("access_token=","") + render :layout => nil + end + +end diff --git a/app/controllers/feed_registrations_controller.rb b/app/controllers/feed_registrations_controller.rb new file mode 100644 index 000000000..d157c26a3 --- /dev/null +++ b/app/controllers/feed_registrations_controller.rb @@ -0,0 +1,56 @@ +class FeedRegistrationsController < ApplicationController + + helper_method :registration + + layout 'feed_registration' + + before_filter :authenticate_user! + + def new ; end + + def create + registration.attributes = params[:feed] + if registration.save + if registration.has_avatar? + redirect_to avatar_feed_registration_url(registration) + else + redirect_to profile_feed_registration_url(registration) + end + else + render :new + end + end + + def avatar ; end + + def crop_avatar + registration.update_attributes(params[:feed]) + redirect_to profile_feed_registration_url(registration) + end + + def profile ; end + + def add_profile + if registration.update_attributes params[:feed] + redirect_to subscribers_feed_registration_url(registration) + else + render :profile + end + end + + def subscribers ; end + + def invite_subscribers + registration.invite_subscribers(params[:feed_subscribers]) + redirect_to "/pages/#{registration.feed.slug.blank? ? registration.feed.id : registration.feed.slug}" + end + + protected + + def registration + @registration ||= + FeedRegistration.find_or_create(:id => params[:id], + :community => current_community, + :user => current_user) + end +end diff --git a/app/controllers/feeds/announcements_controller.rb b/app/controllers/feeds/announcements_controller.rb deleted file mode 100644 index 757d3374e..000000000 --- a/app/controllers/feeds/announcements_controller.rb +++ /dev/null @@ -1,24 +0,0 @@ -class Feeds::AnnouncementsController < ApplicationController - - layout :choose_layout - - def new - @feed = Feed.find(params[:feed_id]) - @announcement = Announcement.new - end - - def create - @feed = Feed.find(params[:feed_id]) - @announcement = @feed.announcements.build(params[:announcement]) - if @announcement.save - render :create - else - render :new - end - end - - private - def choose_layout - xhr? ? 'application' : '/feeds/profile.haml' - end -end diff --git a/app/controllers/feeds/events_controller.rb b/app/controllers/feeds/events_controller.rb deleted file mode 100644 index 556de6c58..000000000 --- a/app/controllers/feeds/events_controller.rb +++ /dev/null @@ -1,25 +0,0 @@ -class Feeds::EventsController < ApplicationController - - layout :choose_layout - - def new - @feed = Feed.find(params[:feed_id]) - @event = Event.new - end - - def create - @feed = Feed.find(params[:feed_id]) - @event = @feed.events.build(params[:event]) - if @event.save - render :create - else - render :new - end - end - - private - def choose_layout - xhr? ? 'application' : '/feeds/profile.haml' - end - -end diff --git a/app/controllers/feeds/invites_controller.rb b/app/controllers/feeds/invites_controller.rb deleted file mode 100644 index bd850c794..000000000 --- a/app/controllers/feeds/invites_controller.rb +++ /dev/null @@ -1,17 +0,0 @@ -class Feeds::InvitesController < ApplicationController - - def new - @feed = Feed.find(params[:feed_id]) - end - - def create - @feed = Feed.find(params[:feed_id]) - params[:emails].split(",").each do |email| - unless User.exists?(:email => email) - InviteMailer.deliver_feed_invite(@feed.id,email) - end - end - redirect_to feed_profile_path(@feed) - end - -end diff --git a/app/controllers/feeds_controller.rb b/app/controllers/feeds_controller.rb index b2e83357b..a84b83b16 100644 --- a/app/controllers/feeds_controller.rb +++ b/app/controllers/feeds_controller.rb @@ -1,71 +1,71 @@ class FeedsController < CommunitiesController - before_filter :load, :except => [:index,:show] - authorize_resource - - def index - @items = current_community.feeds.all(:order => "name ASC") - end + before_filter :load - def municipal - @items = current_community.feeds - render :index - end - - def show - case params[:id] - when /\d+/ - @feed = Feed.find(params[:id]) - if current_user.feeds.include?(@feed) && !flash.now[:message] - flash.now[:message] = "You are subscribed to #{@feed.name}" - end - else - params[:action] = "profile" - @feed = Feed.find_by_slug_and_community_id(params[:id],current_community.id) - render :profile, :layout => false - end - end - - def import - render :layout => xhr? ? 'application' : "/feeds/profile" + def delete + render :layout => 'application' end - def profile - render :layout => false + def destroy + @feed.destroy + redirect_to root_url end - def new + def edit_owner render :layout => 'application' end - def create - @feed = current_community.feeds.new(params[:feed]) - @feed.user = current_user - if @feed.save - redirect_to new_feed_invites_url(@feed) + def update_owner + if user = User.find_by_email(params[:email]) + @feed.user = user + @feed.owners << user + @feed.save + redirect_to root_url else - render :new, :layout => 'application' + @error = true + render :edit_owner, :layout => 'application' end end def edit - render :layout => 'application' + render :layout => 'feed_registration' + end + + def avatar + render :layout => 'feed_registration' + end + + def crop_avatar + @feed.update_attributes(params[:feed]) + redirect_to feed_profile_path(@feed) end def update if @feed.update_attributes(params[:feed]) - redirect_to profile_feed_url(@feed) + if params[:feed].has_key?(:avatar) + redirect_to :action => :avatar + else + redirect_to feed_profile_path(@feed) + end else - render :edit, :layout => 'application' + render :edit, :layout => 'feed_registration' end end protected def load @feed = - if params[:id] - Feed.find(params[:id], :scope => current_community) + case params[:id] + when /^\d+$/ + Feed.find(params[:id]) + when /[^\d]/ + params[:action] = "profile" + Feed.find_by_slug_and_community_id(params[:id], current_community.id) else Feed.new end end + + def feed_profile_path(feed) + "/pages/#{feed.slug.blank? ? feed.id : feed.slug}" + end end diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb deleted file mode 100644 index 27f53e44c..000000000 --- a/app/controllers/invites_controller.rb +++ /dev/null @@ -1,16 +0,0 @@ -class InvitesController < CommunitiesController - - def new - end - - def create - params[:emails].split(",").each do |email| - unless User.exists?(:email => email) - InviteMailer.deliver_user_invite(current_user.id,email,params[:message]) - end - end - redirect_to new_post_path - end - - -end diff --git a/app/controllers/management_controller.rb b/app/controllers/management_controller.rb deleted file mode 100644 index 0b4827f1b..000000000 --- a/app/controllers/management_controller.rb +++ /dev/null @@ -1,13 +0,0 @@ -class ManagementController < ApplicationController - - def show - authorize!(:read, :management) - end - - - protected - - def current_ability - @current_ability ||= ManagementAbility.new(current_user) - end -end diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb deleted file mode 100644 index fc50f885d..000000000 --- a/app/controllers/messages_controller.rb +++ /dev/null @@ -1,25 +0,0 @@ -class MessagesController < CommunitiesController - helper_method :parent - - - def new - authorize! :create, Reply - @user = parent - @message = Message.new - end - - def create - @message = parent.messages.build(params[:message].merge(:user => current_user)) - if @message.save - parent.notifications.create(:notifiable => @message) - flash.now[:message] = "Message sent to #{parent.name}" - else - render :new - end - end - - protected - def parent - @parent ||= params[:messagable].constantize.find(params[(params[:messagable].downcase + "_id").intern]) - end -end diff --git a/app/controllers/mets_controller.rb b/app/controllers/mets_controller.rb deleted file mode 100644 index 99572d1ac..000000000 --- a/app/controllers/mets_controller.rb +++ /dev/null @@ -1,14 +0,0 @@ -class MetsController < ApplicationController - - layout false - - def create - @user = User.find(params[:user_id]) - unless current_user.people.exists? @user - current_user.people << @user - end - flash[:message] = "You have met #{@user.name}" - redirect_to user_path(@user) - end - -end diff --git a/app/controllers/neighborhood/people_controller.rb b/app/controllers/neighborhood/people_controller.rb deleted file mode 100644 index 986758d20..000000000 --- a/app/controllers/neighborhood/people_controller.rb +++ /dev/null @@ -1,8 +0,0 @@ -class Neighborhood::PeopleController < CommunitiesController - - def index - authorize! :read, User - @items = current_neighborhood.users.all(:order => "last_name ASC") - @render_args = Proc.new { |u| ['users/user', {:user => u}] } - end -end diff --git a/app/controllers/organizer_controller.rb b/app/controllers/organizer_controller.rb new file mode 100644 index 000000000..6b48edbe8 --- /dev/null +++ b/app/controllers/organizer_controller.rb @@ -0,0 +1,15 @@ +class OrganizerController < ApplicationController + def app + @center_zip_code = current_community.zip_code.to_s + @community_id = current_community.id + end + + def add + puts "(1..#{params[:entry_count]})" + (1..(params[:entry_count].to_i)).each do |e| + cp_client.add_data_point(current_community, {:number => params["number_#{e}"], :address => params["address_#{e}"], :status => params[:status]}) + end + redirect_to :action => :app + end + +end diff --git a/app/controllers/password_resets_controller.rb b/app/controllers/password_resets_controller.rb index 11fa45433..aef620232 100644 --- a/app/controllers/password_resets_controller.rb +++ b/app/controllers/password_resets_controller.rb @@ -1,40 +1,3 @@ -class PasswordResetsController < CommunitiesController - layout 'application' - - def new - @email = "" - end - - def create - if @user = User.find_by_email(params[:email]) - @user.reset_perishable_token! - PasswordsMailer.deliver_reset(@user) - render :show - else - flash.now[:error] = "That email address is not in our system" - render :new - end - end - - def edit - if @user = User.find_using_perishable_token(params[:id]) - render - else - redirect_to new_password_reset_url - end - end - - def update - if @user = User.find_using_perishable_token(params[:id]) - @user.password = params[:user][:password] - if @user.save - redirect_to root_url - else - render :edit - end - else - redirect_to new_password_reset_url - end - end - +class PasswordResetsController < Devise::PasswordsController + layout 'sign_in' end diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb deleted file mode 100644 index a6f0d1fe9..000000000 --- a/app/controllers/posts_controller.rb +++ /dev/null @@ -1,45 +0,0 @@ -class PostsController < CommunitiesController - helper_method :post - - load_and_authorize_resource - - def index - @items = current_neighborhood.posts.sort_by(&:created_at).reverse - end - - def new - end - - def create - @post.user = current_user - @post.neighborhood_id = current_neighborhood.id - if @post.save - current_neighborhood.notifications.create(:notifiable => @post) - flash[:message] = "Post Created!" - redirect_to posts_path - else - render :new - end - end - - def destroy - if can? :destroy, @post - # if (@post.user_may_delete(current_user)) - if @post.destroy - flash[:message] = "Post Deleted!" - redirect_to posts_path - else - render :new - end - end - end - - def show - end - - protected - def post - @post - end - -end diff --git a/app/controllers/profile_fields_controller.rb b/app/controllers/profile_fields_controller.rb deleted file mode 100644 index c4b034be5..000000000 --- a/app/controllers/profile_fields_controller.rb +++ /dev/null @@ -1,46 +0,0 @@ -class ProfileFieldsController < CommunitiesController - - layout false - - def index - @feed = Feed.find(params[:feed_id]) - end - - def order - params[:fields].each_with_index do |id, index| - ProfileField.update_all(["position=?", index+1], ["id=?", id]) - end - render :nothing => true - end - - - def new - @feed = Feed.find(params[:feed_id]) - @profile_field = ProfileField.new - end - - def create - @feed = Feed.find(params[:feed_id]) - @profile_field = @feed.profile_fields.build(params[:profile_field]) - if @profile_field.save - render :show - else - render :new - end - end - - def edit - @profile_field = ProfileField.find(params[:id]) - end - - def update - @profile_field = ProfileField.find(params[:id]) - if @profile_field.update_attributes(params[:profile_field]) - @feed = @profile_field.feed - render :show - else - render :edit - end - end - -end diff --git a/app/controllers/referrals_controller.rb b/app/controllers/referrals_controller.rb deleted file mode 100644 index 43612525a..000000000 --- a/app/controllers/referrals_controller.rb +++ /dev/null @@ -1,13 +0,0 @@ -class ReferralsController < ApplicationController - - def create - @event = Event.find(params[:event_id]) - @referral = @event.referrals.build(params[:referral].merge(:referrer => current_user)) - if @referral.save - render 'show' - else - render 'new' - end - end - -end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb new file mode 100644 index 000000000..f2ddc8521 --- /dev/null +++ b/app/controllers/registrations_controller.rb @@ -0,0 +1,91 @@ +class RegistrationsController < ApplicationController + helper_method :registration + layout 'registration' + + before_filter :authenticate_user!, :except => [:new, :create, :facebook_new, :mobile_new] + + def new + session["devise.community"] = current_community.id + end + + def mobile_new + render :layout => 'mobile_registration' + end + + def facebook_new + # this action is actually rendered in + # UsersOmniAuthCallbacksController#facebook + end + + def create + registration.attributes = params[:user] + if registration.save + sign_in(registration.user, :bypass => true) + kickoff.deliver_welcome_email(registration.user) + redirect_to profile_registration_url + else + render :new + end + end + + def profile ; end + + def mobile_profile + render :layout => 'mobile_registration' + end + + def add_profile + if registration.update_attributes params[:user] + sign_in(registration.user, :bypass => true) + if registration.has_avatar? + redirect_to avatar_registration_url + else + redirect_to feeds_registration_url + end + else + render :profile + end + end + + def avatar ; end + + def crop_avatar + registration.update_attributes params[:user] + redirect_to feeds_registration_url + end + + def feeds + if current_community.feeds.present? + render + else + redirect_to groups_registration_url + end + end + + def add_feeds + registration.add_feeds(params[:feed_ids]) + redirect_to groups_registration_url + end + + def groups + if current_community.feeds.present? + render + else + redirect_to root_url + end + end + + def add_groups + registration.add_groups(params[:group_ids]) + redirect_to(root_url + "#/tour") + end + + protected + + def registration + @registration ||= + Registration.new(current_user || User.new(:community => current_community)) + end + + +end diff --git a/app/controllers/replies_controller.rb b/app/controllers/replies_controller.rb deleted file mode 100644 index c9bdc9760..000000000 --- a/app/controllers/replies_controller.rb +++ /dev/null @@ -1,14 +0,0 @@ -class RepliesController < CommunitiesController - - def create - authorize! :create, Reply - @reply = current_user.replies.build(params[:reply]) - if @reply.save - @reply.repliable.notifications.create(:notifiable => @reply) if @reply.repliable.is_a?(Post) - render :show - else - render :new - end - end - -end diff --git a/app/controllers/requests_controller.rb b/app/controllers/requests_controller.rb new file mode 100644 index 000000000..be7bffc7d --- /dev/null +++ b/app/controllers/requests_controller.rb @@ -0,0 +1,9 @@ +class RequestsController < ApplicationController + + def create + @request = Request.new(params[:request]) + flash[:notice] = 'Thank you for requesting CommonPlace!' + @request.save + redirect_to('/') + end +end diff --git a/app/controllers/site_controller.rb b/app/controllers/site_controller.rb index 20d50120e..22f3cfef1 100644 --- a/app/controllers/site_controller.rb +++ b/app/controllers/site_controller.rb @@ -1,7 +1,14 @@ -class SiteController < CommunitiesController - +class SiteController < ApplicationController + + layout 'application' + + def index + @request = Request.new + render :layout => 'starter_site' + end + def privacy ; end def terms ; end - + end diff --git a/app/controllers/subscriptions_controller.rb b/app/controllers/subscriptions_controller.rb deleted file mode 100644 index 3553f83f5..000000000 --- a/app/controllers/subscriptions_controller.rb +++ /dev/null @@ -1,26 +0,0 @@ -class SubscriptionsController < CommunitiesController - - def index - @items = current_community.feeds.all(:order => "name ASC") - end - - def recommended - @items = Feed.all - render :index - end - - def create - @feed = Feed.find params[:feed_id] - current_user.feeds << @feed - flash[:message] = "You've subscribed to #{ @feed.name }." - redirect_to feed_path(@feed) - end - - def destroy - @feed = Feed.find params[:feed_id] - current_user.feeds.delete @feed - current_user.save - flash[:message] = "You've unsubscribed from #{ @feed.name }." - redirect_to feed_path(@feed) - end -end diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb deleted file mode 100644 index 8da1ab230..000000000 --- a/app/controllers/tags_controller.rb +++ /dev/null @@ -1,17 +0,0 @@ -class TagsController < CommunitiesController - - def index - @goods = ActsAsTaggableOn::Tagging.all(:conditions => {:context => "goods"}).map(&:tag).map(&:canonical_tag).uniq - @skills = ActsAsTaggableOn::Tagging.all(:conditions => {:context => "skills"}).map(&:tag).map(&:canonical_tag).uniq - respond_to do |format| - format.json - end - end - - def new - respond_to do |format| - format.json - end - end - -end diff --git a/app/controllers/user_sessions_controller.rb b/app/controllers/user_sessions_controller.rb index 28789555f..a1ad396b1 100644 --- a/app/controllers/user_sessions_controller.rb +++ b/app/controllers/user_sessions_controller.rb @@ -1,62 +1,3 @@ -class UserSessionsController < ApplicationController - - def new - authorize! :new, UserSession - @user = User.new - end - - def create_from_facebook - authorize! :create, UserSession - if facebook_session - puts facebook_session - @user = User.find_by_facebook_uid(facebook_session.user.id) - puts @user.inspect - @user_session = UserSession.new(@user) - if @user_session.save - reload_current_user! - redirect_back_or_default root_url - else - @user = User.new - @user_session_errors = true - params[:controller] = "accounts" - params[:action] = "new" - render 'accounts/new' - end - else - @user = User.new - @user_session_errors = true - params[:controller] = "accounts" - params[:action] = "new" - render 'accounts/new' - puts "No session" - end - end - - def create - authorize! :create, UserSession - @user_session = UserSession.new(params[:user_session]) - @user_session.remember_me = true - if @user_session.save - reload_current_user! - redirect_back_or_default root_url - else - @user = User.new - @user_session_errors = true - params[:controller] = "accounts" - params[:action] = "new" - render 'accounts/new' - end - end - - def show - redirect_to root_url - end - - def destroy - authorize! :destroy, UserSession - current_user_session.destroy - redirect_to root_url - end - - +class UserSessionsController < Devise::SessionsController + layout "sign_in" end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb deleted file mode 100644 index 48fa81a04..000000000 --- a/app/controllers/users_controller.rb +++ /dev/null @@ -1,13 +0,0 @@ -class UsersController < CommunitiesController - load_and_authorize_resource - - def index - @neighbors = current_neighborhood.users.sort_by(&:last_name) - @users = current_community.users.sort_by(&:last_name) - @neighbors - end - - def show - end - - -end diff --git a/app/controllers/users_omniauth_callbacks_controller.rb b/app/controllers/users_omniauth_callbacks_controller.rb new file mode 100644 index 000000000..96a204b25 --- /dev/null +++ b/app/controllers/users_omniauth_callbacks_controller.rb @@ -0,0 +1,27 @@ +class UsersOmniauthCallbacksController < Devise::OmniauthCallbacksController + helper_method :registration + def facebook + + if @user = User.find_for_facebook_oauth(env["omniauth.auth"]) + sign_in_and_redirect @user, :event => :authentication + else + @_community = Community.find(session["devise.community"]) + @registration = + Registration.new(User.new_from_facebook({:community_id => session["devise.community"]}, env["omniauth.auth"])) + render "registrations/facebook_new", :layout => "registration" + end + end + + def passthru + render(:file => "#{Rails.root}/public/404.html", + :status => 404, + :layout => false) + end + + protected + + def registration + @registration ||= + Registration.new(current_user || User.new(:community => current_community)) + end +end diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb new file mode 100644 index 000000000..7a4cd343c --- /dev/null +++ b/app/helpers/accounts_helper.rb @@ -0,0 +1,27 @@ +module AccountsHelper + def college_dorms_for_school(community) + # HACK + if community.is_college + options_for_select(['Select your residence hall', '-----', community.neighborhoods.map(&:name)].flatten, 'Select your residence hall') + else + [] + end + end + + def community_registration_url(community) + return "#{root_url}#{community.slug}" + end + + def translate_collection(namespace, collection, kv_store=false) + translations = {} + collection.each do |c| + translation = I18n.t("#{namespace}.#{c.downcase.gsub(' ', '_')}") + if kv_store + translations[translation] = c + else + translations << translation + end + end + translations + end +end diff --git a/app/helpers/announcement_importer_helper.rb b/app/helpers/announcement_importer_helper.rb deleted file mode 100644 index 05ac222e0..000000000 --- a/app/helpers/announcement_importer_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module AnnouncementImporterHelper -end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 028dae4f3..62614a706 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,58 +1,120 @@ - module ApplicationHelper - - def creation_feeds_for(user) - ActiveSupport::OrderedHash.new.tap do |collection| - user.managable_feeds.to_a.unshift(user).each do |f| - collection[f.name] = dom_id(f) - end - end - end - - def html_to_json(&block) - @template_format = :html - result = block.call - @template_format = :json - return result.to_json - end - - alias_method :h2j, :html_to_json - - def include_javascript_folder(folder) - files = Dir.glob("#{ RAILS_ROOT }/public/javascripts/#{ folder }/*.js") - files.map!{ |f| folder + "/" + File.basename(f) } - javascript_include_tag files - end - - def tab_to(name, options = {}, html_options = {}, &block) - options, html_options = name, options if block - - html_options[:class] ||= "" - if current_page?(options) || current_page?(url_for(options) + ".json") - html_options[:class] += " selected_nav" - end - if block - link_to(options, html_options, &block) - else - link_to(name, options, html_options) - end - end - - def link_to_add(text, options, html_options = {}) - html_options[:id] ||= "" - html_options[:id] += "add" - link_to(text, options, html_options) - end - - def winnow_button(text, target, html_options = {}) - html_options['data-remote'] = true - tab_to target, html_options do - content_tag(:span, text) - end - end - - def content_for?(name) - raise "Delete content_for? in application_helper.rb" if RAILS_GEM_VERSION.to_i > 2 - ivar = "@content_for_#{name}" - instance_variable_get(ivar).present? - end -end +module ApplicationHelper + + def tab_to(name, options = {}, html_options = {}, &block) + options, html_options = name, options if block + + html_options[:class] ||= "" + if current_page?(options) || current_page?(url_for(options) + ".json") + html_options[:class] += " selected_nav" + end + if block + link_to(options, html_options, &block) + else + link_to(name, options, html_options) + end + end + + def include_dummy_console + raw < +script + end + + def include_mixpanel + key = Rails.env.production? ? $MixpanelAPIToken : 'staging/testing key' + raw < +script + end + + def include_ga + key = Rails.env.production? ? 'UA-12807510-2' : 'staging/testing key' + raw < +script + end + + def include_exceptional + key = Rails.env.production? ? '0556a141945715c3deb50a0288ec3bea5417f6bf' : 'staging/testing key' + raw < + +script + end + + def include_commonplace(title = '') + raw < +script + end + + def populate_commonplace + community = account = '' + + if current_user + account << "CommonPlace.account = new Account(#{serialize(Account.new(current_user))});" + end + + if current_community + community << "CommonPlace.community = new Community(#{serialize(current_community)});" + end + + raw < +script + end + +end \ No newline at end of file diff --git a/app/helpers/email_parse_helper.rb b/app/helpers/email_parse_helper.rb deleted file mode 100644 index 926a7928d..000000000 --- a/app/helpers/email_parse_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module EmailParseHelper -end diff --git a/app/helpers/feeds_helper.rb b/app/helpers/feeds_helper.rb deleted file mode 100644 index 93c7f753c..000000000 --- a/app/helpers/feeds_helper.rb +++ /dev/null @@ -1,19 +0,0 @@ -module FeedsHelper - - def feed_profile_path(feed) - if feed.slug - feed_path(feed.slug) - else - profile_feed_path(feed) - end - end - - def feed_profile_url(feed) - if feed.slug - feed_url(feed.slug) - else - profile_feed_url(feed) - end - end - -end diff --git a/app/helpers/images_helper.rb b/app/helpers/images_helper.rb deleted file mode 100644 index 7b3a8bc8e..000000000 --- a/app/helpers/images_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module ImagesHelper -end diff --git a/app/helpers/items_helper.rb b/app/helpers/items_helper.rb deleted file mode 100644 index 87ff5bddd..000000000 --- a/app/helpers/items_helper.rb +++ /dev/null @@ -1,9 +0,0 @@ -module ItemsHelper - - def item_tag(item, html_options = {}, &block) - html_options[:class] = [html_options[:class], dom_class(item)].join(" ") - html_options[:id] = dom_id(item) - content_tag(:li, html_options, &block) - end - -end diff --git a/app/helpers/management_helper.rb b/app/helpers/management_helper.rb deleted file mode 100644 index 2f7c6114b..000000000 --- a/app/helpers/management_helper.rb +++ /dev/null @@ -1,16 +0,0 @@ -module ManagementHelper - - def management_options_for(user) - options = { 'Account' => management_path } - - user.managable_feeds.each do |feed| - options[feed.name] = url_for([:management, feed]) - end - - user.managable_feeds.map(&:events).flatten.each do |event| - options[event.name] = url_for([:management, event]) - end - options_for_select(options) - end - -end diff --git a/app/helpers/map_helper.rb b/app/helpers/map_helper.rb deleted file mode 100644 index 96348d458..000000000 --- a/app/helpers/map_helper.rb +++ /dev/null @@ -1,6 +0,0 @@ -module MapHelper - - def render_map(&block) - content_tag(:div, "", 'data-map' => Mapifier.new(&block).to_json) - end -end diff --git a/app/helpers/modal_helper.rb b/app/helpers/modal_helper.rb deleted file mode 100644 index 2ce2efbb4..000000000 --- a/app/helpers/modal_helper.rb +++ /dev/null @@ -1,24 +0,0 @@ -module ModalHelper - def as_modal(options = {}, &block) - options = { - :class => "", - :close => true - }.merge(options) - - update_content("#modal") do - content_tag(:div, :class => "not_empty", :id => "modal") do - content_tag(:div, :id => "modal-overlay"){} + - content_tag(:div, :id => "modal-content", :class => options[:class]) do - if options[:close] - link_to("close", :id => "modal-close", 'data-remote' => true) do - image_tag 'modal-close.png' - end + - capture(&block) - else - capture(&block) - end - end - end - end - end -end diff --git a/app/helpers/organizer_helper.rb b/app/helpers/organizer_helper.rb new file mode 100644 index 000000000..0c3eb75b1 --- /dev/null +++ b/app/helpers/organizer_helper.rb @@ -0,0 +1,11 @@ +module OrganizerHelper + def google_api_key() + if ENV['HEROKU_APP'] == 'commonplace' + ['ABQIAAAAgdPfd74v_0Ttup6W0tJHtxQB4B98GOFQoxUPdFn0mOBvUSbzOxSwgIAcuwJ7Sux0NCxmE-bx8bLihQ','ABQIAAAAzr2EBOXUKnm_jVnk0OJI7xSosDVG8KKPE1-m51RBrvYughuyMxQ-i1QfUnH94QxWIa6N4U6MouMmBA','ABQIAAAAgdPfd74v_0Ttup6W0tJHtxQB4B98GOFQoxUPdFn0mOBvUSbzOxSwgIAcuwJ7Sux0NCxmE-bx8bLihQ'].shuffle.first + elsif ENV['HEROKU_APP'] == 'commonplace-staging' + 'ABQIAAAAgdPfd74v_0Ttup6W0tJHtxSeiCNuZ_fCHJEwH48648-0ULan5RRVuO5xxDLGQXIjDZFCu8_6tlc8HQ' + else + 'unknown_env_key' + end + end +end diff --git a/app/helpers/text_helper.rb b/app/helpers/text_helper.rb deleted file mode 100644 index 72202547f..000000000 --- a/app/helpers/text_helper.rb +++ /dev/null @@ -1,22 +0,0 @@ -module TextHelper - - def vowel?(char) - !!char.match(/[aeiou]/i) - end - - def reply_count(item) - if item.replies.length == 0 - "no replies yet" - else - "(#{self.replies.length}) replies" - end - end - - def with_article(noun) - [vowel?(noun.slice(0,1)) ? 'an' : 'a', noun].join(" ") - end - - def markdown(text) - BlueCloth.new(text || "").to_html - end -end diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb deleted file mode 100644 index 5f1085aad..000000000 --- a/app/helpers/time_helper.rb +++ /dev/null @@ -1,55 +0,0 @@ -module TimeHelper - - def full_date(date,time) - date.strftime("%A, %B %d") + " at " + hours_minutes(time) - end - - def hours_minutes time - return time.strftime("%I:%M %p").sub(/^0/,'') # strip leading zero - end - - def post_date time - unless time - return "" - end - - diff = ( DateTime.now.to_i - time.to_i ) / 86400 - - if diff < 7 - return time_ago_in_words(time, include_seconds = true) + " ago" - elsif diff < 365 - return time.strftime("%B %d").sub(/(\s)(0)(\d*)/, '\1\3') - else - return time.strftime("%B %d %Y").sub(/(\s)(0)(\d*)/, '\1\3') - end - end - - - def event_date time - unless time - return "" - end - - time_string = time_ago_in_words(time) - diff = ( time.to_i - DateTime.now.to_i ) / 86400 - if diff < 0 - return time_string + " ago" - else - return "in " + time_string - end - end - - def drop_down_times - ((5..23).to_a + (0..4).to_a).map{ |h| - 0.step(59, 30).map{ |m| Time.parse("#{h}:#{m}").to_s } - }.flatten - end - - def event_select_time(edge) - content_tag(:li, :class => 'select required', :id => "event_#{edge}_input") do - content_tag(:label, :for => "event_#{edge}") { edge.humanize } + - select('event', edge, options_for_select(drop_down_times, @event.read_attribute(edge).to_s)) - end - end - -end diff --git a/app/helpers/update_helper.rb b/app/helpers/update_helper.rb deleted file mode 100644 index 4eba81dfa..000000000 --- a/app/helpers/update_helper.rb +++ /dev/null @@ -1,16 +0,0 @@ -module UpdateHelper - - def update_content(name, content = nil, &block) - @_updated_content ||= Hash.new - content = block_given? ? capture(&block) : "none" - @_updated_content[name] = content || "" - end - - def updated_content - @_updated_content || Hash.new - end - - def updated_content_or(key, &default) - updated_content.has_key?(key) ? updated_content[key] : capture(&default) - end -end diff --git a/public/images/Jcrop.gif b/app/images/Jcrop.gif similarity index 100% rename from public/images/Jcrop.gif rename to app/images/Jcrop.gif diff --git a/public/images/about.jpg b/app/images/about.jpg similarity index 100% rename from public/images/about.jpg rename to app/images/about.jpg diff --git a/app/images/active_admin/admin_notes_icon.png b/app/images/active_admin/admin_notes_icon.png new file mode 100644 index 000000000..1d240a1a1 Binary files /dev/null and b/app/images/active_admin/admin_notes_icon.png differ diff --git a/app/images/active_admin/loading.gif b/app/images/active_admin/loading.gif new file mode 100644 index 000000000..da33c3e03 Binary files /dev/null and b/app/images/active_admin/loading.gif differ diff --git a/app/images/active_admin/nested_menu_arrow.gif b/app/images/active_admin/nested_menu_arrow.gif new file mode 100644 index 000000000..878357fe4 Binary files /dev/null and b/app/images/active_admin/nested_menu_arrow.gif differ diff --git a/app/images/active_admin/nested_menu_arrow_dark.gif b/app/images/active_admin/nested_menu_arrow_dark.gif new file mode 100644 index 000000000..006372c82 Binary files /dev/null and b/app/images/active_admin/nested_menu_arrow_dark.gif differ diff --git a/app/images/active_admin/orderable.png b/app/images/active_admin/orderable.png new file mode 100644 index 000000000..427009e72 Binary files /dev/null and b/app/images/active_admin/orderable.png differ diff --git a/public/images/announcement-zone.png b/app/images/announcement-zone.png similarity index 100% rename from public/images/announcement-zone.png rename to app/images/announcement-zone.png diff --git a/public/images/announcements.png b/app/images/announcements.png similarity index 100% rename from public/images/announcements.png rename to app/images/announcements.png diff --git a/app/images/blue-arrow-down.png b/app/images/blue-arrow-down.png new file mode 100644 index 000000000..f956ca3bb Binary files /dev/null and b/app/images/blue-arrow-down.png differ diff --git a/app/images/blue-arrow-up.png b/app/images/blue-arrow-up.png new file mode 100644 index 000000000..c38b330fa Binary files /dev/null and b/app/images/blue-arrow-up.png differ diff --git a/public/images/bullet-pin.png b/app/images/bullet-pin.png similarity index 100% rename from public/images/bullet-pin.png rename to app/images/bullet-pin.png diff --git a/app/images/bullet-star.png b/app/images/bullet-star.png new file mode 100644 index 000000000..61fd3a990 Binary files /dev/null and b/app/images/bullet-star.png differ diff --git a/app/images/bullet-star2.png b/app/images/bullet-star2.png new file mode 100644 index 000000000..3ee762ea5 Binary files /dev/null and b/app/images/bullet-star2.png differ diff --git a/public/images/buttons/continue.png b/app/images/buttons/continue.png similarity index 100% rename from public/images/buttons/continue.png rename to app/images/buttons/continue.png diff --git a/app/images/buttons/continue2.png b/app/images/buttons/continue2.png new file mode 100644 index 000000000..71900ed72 Binary files /dev/null and b/app/images/buttons/continue2.png differ diff --git a/public/images/buttons/down-arrow.png b/app/images/buttons/down-arrow.png similarity index 100% rename from public/images/buttons/down-arrow.png rename to app/images/buttons/down-arrow.png diff --git a/app/images/buttons/facebook-login.png b/app/images/buttons/facebook-login.png new file mode 100644 index 000000000..e88a3cf0c Binary files /dev/null and b/app/images/buttons/facebook-login.png differ diff --git a/app/images/buttons/facebook-sign-up.png b/app/images/buttons/facebook-sign-up.png new file mode 100644 index 000000000..e97f48715 Binary files /dev/null and b/app/images/buttons/facebook-sign-up.png differ diff --git a/app/images/buttons/learnmore.png b/app/images/buttons/learnmore.png new file mode 100644 index 000000000..2c6ed456e Binary files /dev/null and b/app/images/buttons/learnmore.png differ diff --git a/public/images/buttons/login.png b/app/images/buttons/login.png similarity index 100% rename from public/images/buttons/login.png rename to app/images/buttons/login.png diff --git a/app/images/buttons/login2.png b/app/images/buttons/login2.png new file mode 100644 index 000000000..4043207eb Binary files /dev/null and b/app/images/buttons/login2.png differ diff --git a/app/images/buttons/post-now-btn.png b/app/images/buttons/post-now-btn.png new file mode 100644 index 000000000..f28e5ca9f Binary files /dev/null and b/app/images/buttons/post-now-btn.png differ diff --git a/public/images/buttons/post-now.png b/app/images/buttons/post-now.png similarity index 100% rename from public/images/buttons/post-now.png rename to app/images/buttons/post-now.png diff --git a/app/images/buttons/post-now2.png b/app/images/buttons/post-now2.png new file mode 100644 index 000000000..eb9ce36fe Binary files /dev/null and b/app/images/buttons/post-now2.png differ diff --git a/public/images/buttons/red-post-now-button.png b/app/images/buttons/red-post-now-button.png similarity index 100% rename from public/images/buttons/red-post-now-button.png rename to app/images/buttons/red-post-now-button.png diff --git a/public/images/buttons/sign-up.png b/app/images/buttons/sign-up.png similarity index 100% rename from public/images/buttons/sign-up.png rename to app/images/buttons/sign-up.png diff --git a/app/images/buttons/signup.png b/app/images/buttons/signup.png new file mode 100644 index 000000000..203809904 Binary files /dev/null and b/app/images/buttons/signup.png differ diff --git a/app/images/cards/test.png b/app/images/cards/test.png new file mode 100644 index 000000000..1104717f5 Binary files /dev/null and b/app/images/cards/test.png differ diff --git a/app/images/cards/vienna.png b/app/images/cards/vienna.png new file mode 100644 index 000000000..5a8bd0b24 Binary files /dev/null and b/app/images/cards/vienna.png differ diff --git a/app/images/check-off.png b/app/images/check-off.png new file mode 100644 index 000000000..b8013634b Binary files /dev/null and b/app/images/check-off.png differ diff --git a/app/images/check-on.png b/app/images/check-on.png new file mode 100644 index 000000000..da7382ee5 Binary files /dev/null and b/app/images/check-on.png differ diff --git a/public/images/clouds.png b/app/images/clouds.png similarity index 100% rename from public/images/clouds.png rename to app/images/clouds.png diff --git a/app/images/contacts-from-email-btn.jpg b/app/images/contacts-from-email-btn.jpg new file mode 100644 index 000000000..eef7e680c Binary files /dev/null and b/app/images/contacts-from-email-btn.jpg differ diff --git a/public/images/create-a-feed.png b/app/images/create-a-feed.png similarity index 100% rename from public/images/create-a-feed.png rename to app/images/create-a-feed.png diff --git a/public/images/delete.png b/app/images/delete.png similarity index 100% rename from public/images/delete.png rename to app/images/delete.png diff --git a/app/images/delete_grey.png b/app/images/delete_grey.png new file mode 100644 index 000000000..7711d4e54 Binary files /dev/null and b/app/images/delete_grey.png differ diff --git a/public/images/directory-zone.png b/app/images/directory-zone.png similarity index 100% rename from public/images/directory-zone.png rename to app/images/directory-zone.png diff --git a/public/images/directory.png b/app/images/directory.png similarity index 100% rename from public/images/directory.png rename to app/images/directory.png diff --git a/app/images/doc-icon.png b/app/images/doc-icon.png new file mode 100644 index 000000000..9674bfd2c Binary files /dev/null and b/app/images/doc-icon.png differ diff --git a/app/images/download-btn.png b/app/images/download-btn.png new file mode 100644 index 000000000..73ccf6f7c Binary files /dev/null and b/app/images/download-btn.png differ diff --git a/public/images/event-zone.png b/app/images/event-zone.png similarity index 100% rename from public/images/event-zone.png rename to app/images/event-zone.png diff --git a/public/images/events.png b/app/images/events.png similarity index 100% rename from public/images/events.png rename to app/images/events.png diff --git a/app/images/example-email.png b/app/images/example-email.png new file mode 100644 index 000000000..c1b9a93cf Binary files /dev/null and b/app/images/example-email.png differ diff --git a/app/images/example-platform.png b/app/images/example-platform.png new file mode 100644 index 000000000..b235d6583 Binary files /dev/null and b/app/images/example-platform.png differ diff --git a/public/images/fake_map.png b/app/images/fake_map.png similarity index 100% rename from public/images/fake_map.png rename to app/images/fake_map.png diff --git a/public/images/fallschurch_notification_header.png b/app/images/fallschurch_notification_header.png similarity index 100% rename from public/images/fallschurch_notification_header.png rename to app/images/fallschurch_notification_header.png diff --git a/public/images/favicon.png b/app/images/favicon.png similarity index 100% rename from public/images/favicon.png rename to app/images/favicon.png diff --git a/app/images/fb-btn.png b/app/images/fb-btn.png new file mode 100644 index 000000000..51322952e Binary files /dev/null and b/app/images/fb-btn.png differ diff --git a/public/images/feed-post-dropdown-arrow.png b/app/images/feed-post-dropdown-arrow.png similarity index 100% rename from public/images/feed-post-dropdown-arrow.png rename to app/images/feed-post-dropdown-arrow.png diff --git a/app/images/feed_page/announcement-tab-icon.png b/app/images/feed_page/announcement-tab-icon.png new file mode 100644 index 000000000..46fbb935f Binary files /dev/null and b/app/images/feed_page/announcement-tab-icon.png differ diff --git a/app/images/feed_page/check-off.png b/app/images/feed_page/check-off.png new file mode 100644 index 000000000..b8013634b Binary files /dev/null and b/app/images/feed_page/check-off.png differ diff --git a/app/images/feed_page/check-on.png b/app/images/feed_page/check-on.png new file mode 100644 index 000000000..da7382ee5 Binary files /dev/null and b/app/images/feed_page/check-on.png differ diff --git a/app/images/feed_page/doc-icon.png b/app/images/feed_page/doc-icon.png new file mode 100644 index 000000000..9674bfd2c Binary files /dev/null and b/app/images/feed_page/doc-icon.png differ diff --git a/app/images/feed_page/event-tab-icon.png b/app/images/feed_page/event-tab-icon.png new file mode 100644 index 000000000..dc370fa59 Binary files /dev/null and b/app/images/feed_page/event-tab-icon.png differ diff --git a/app/images/feed_page/invite-tab-icon.png b/app/images/feed_page/invite-tab-icon.png new file mode 100644 index 000000000..2857c53cc Binary files /dev/null and b/app/images/feed_page/invite-tab-icon.png differ diff --git a/public/images/feeds.png b/app/images/feeds.png similarity index 100% rename from public/images/feeds.png rename to app/images/feeds.png diff --git a/public/images/goods-and-skills.png b/app/images/goods-and-skills.png similarity index 100% rename from public/images/goods-and-skills.png rename to app/images/goods-and-skills.png diff --git a/public/images/grid.png b/app/images/grid.png similarity index 100% rename from public/images/grid.png rename to app/images/grid.png diff --git a/app/images/group-icons/academic-life.png b/app/images/group-icons/academic-life.png new file mode 100644 index 000000000..7e5776752 Binary files /dev/null and b/app/images/group-icons/academic-life.png differ diff --git a/app/images/group-icons/city-problem-solving.png b/app/images/group-icons/city-problem-solving.png new file mode 100644 index 000000000..87297eec1 Binary files /dev/null and b/app/images/group-icons/city-problem-solving.png differ diff --git a/app/images/group-icons/dogs.png b/app/images/group-icons/dogs.png new file mode 100644 index 000000000..ac4e3fa60 Binary files /dev/null and b/app/images/group-icons/dogs.png differ diff --git a/app/images/group-icons/environment.png b/app/images/group-icons/environment.png new file mode 100644 index 000000000..2fdeeff9e Binary files /dev/null and b/app/images/group-icons/environment.png differ diff --git a/app/images/group-icons/fredericksburg-happenings.png b/app/images/group-icons/fredericksburg-happenings.png new file mode 100644 index 000000000..89af8fb2c Binary files /dev/null and b/app/images/group-icons/fredericksburg-happenings.png differ diff --git a/app/images/group-icons/gardening-home-improvement.png b/app/images/group-icons/gardening-home-improvement.png new file mode 100644 index 000000000..5d9bd4930 Binary files /dev/null and b/app/images/group-icons/gardening-home-improvement.png differ diff --git a/app/images/group-icons/link-sharing.png b/app/images/group-icons/link-sharing.png new file mode 100644 index 000000000..e061f2a34 Binary files /dev/null and b/app/images/group-icons/link-sharing.png differ diff --git a/app/images/group-icons/local-dining.png b/app/images/group-icons/local-dining.png new file mode 100644 index 000000000..51c4a707c Binary files /dev/null and b/app/images/group-icons/local-dining.png differ diff --git a/app/images/group-icons/music-arts.png b/app/images/group-icons/music-arts.png new file mode 100644 index 000000000..9dad4116c Binary files /dev/null and b/app/images/group-icons/music-arts.png differ diff --git a/app/images/group-icons/new-mothers.png b/app/images/group-icons/new-mothers.png new file mode 100644 index 000000000..0ba2b295b Binary files /dev/null and b/app/images/group-icons/new-mothers.png differ diff --git a/app/images/group-icons/parents-families.png b/app/images/group-icons/parents-families.png new file mode 100644 index 000000000..3b644ea04 Binary files /dev/null and b/app/images/group-icons/parents-families.png differ diff --git a/app/images/group-icons/recipes.png b/app/images/group-icons/recipes.png new file mode 100644 index 000000000..a748a85d4 Binary files /dev/null and b/app/images/group-icons/recipes.png differ diff --git a/app/images/group-icons/schools.png b/app/images/group-icons/schools.png new file mode 100644 index 000000000..5ca1451e0 Binary files /dev/null and b/app/images/group-icons/schools.png differ diff --git a/app/images/group-icons/social-opportunities.png b/app/images/group-icons/social-opportunities.png new file mode 100644 index 000000000..b7039f3b7 Binary files /dev/null and b/app/images/group-icons/social-opportunities.png differ diff --git a/app/images/group-icons/sports-recreation.png b/app/images/group-icons/sports-recreation.png new file mode 100644 index 000000000..0b2592481 Binary files /dev/null and b/app/images/group-icons/sports-recreation.png differ diff --git a/app/images/group-icons/volunteer-opportunities.png b/app/images/group-icons/volunteer-opportunities.png new file mode 100644 index 000000000..260d1e299 Binary files /dev/null and b/app/images/group-icons/volunteer-opportunities.png differ diff --git a/app/images/harrisonburg_notification_header.png b/app/images/harrisonburg_notification_header.png new file mode 100644 index 000000000..cf8b17e25 Binary files /dev/null and b/app/images/harrisonburg_notification_header.png differ diff --git a/public/images/houses.png b/app/images/houses.png similarity index 100% rename from public/images/houses.png rename to app/images/houses.png diff --git a/app/images/icons/announcements-icon-selected.png b/app/images/icons/announcements-icon-selected.png new file mode 100644 index 000000000..ff3791e49 Binary files /dev/null and b/app/images/icons/announcements-icon-selected.png differ diff --git a/app/images/icons/announcements-icon.png b/app/images/icons/announcements-icon.png new file mode 100644 index 000000000..03f6bd4e5 Binary files /dev/null and b/app/images/icons/announcements-icon.png differ diff --git a/public/images/icons/arrow-l-hov.png b/app/images/icons/arrow-l-hov.png similarity index 100% rename from public/images/icons/arrow-l-hov.png rename to app/images/icons/arrow-l-hov.png diff --git a/public/images/icons/arrow-l.png b/app/images/icons/arrow-l.png similarity index 100% rename from public/images/icons/arrow-l.png rename to app/images/icons/arrow-l.png diff --git a/public/images/icons/arrow-r-hov.png b/app/images/icons/arrow-r-hov.png similarity index 100% rename from public/images/icons/arrow-r-hov.png rename to app/images/icons/arrow-r-hov.png diff --git a/public/images/icons/arrow-r.png b/app/images/icons/arrow-r.png similarity index 100% rename from public/images/icons/arrow-r.png rename to app/images/icons/arrow-r.png diff --git a/app/images/icons/discuss-icon-selected.png b/app/images/icons/discuss-icon-selected.png new file mode 100644 index 000000000..f50cb67d9 Binary files /dev/null and b/app/images/icons/discuss-icon-selected.png differ diff --git a/app/images/icons/discuss-icon.png b/app/images/icons/discuss-icon.png new file mode 100644 index 000000000..3bb4cf839 Binary files /dev/null and b/app/images/icons/discuss-icon.png differ diff --git a/app/images/icons/events-icon-selected.png b/app/images/icons/events-icon-selected.png new file mode 100644 index 000000000..b77693b52 Binary files /dev/null and b/app/images/icons/events-icon-selected.png differ diff --git a/app/images/icons/events-icon.png b/app/images/icons/events-icon.png new file mode 100644 index 000000000..26493cc65 Binary files /dev/null and b/app/images/icons/events-icon.png differ diff --git a/app/images/icons/neighborhood-post-selected.png b/app/images/icons/neighborhood-post-selected.png new file mode 100644 index 000000000..7fe58892c Binary files /dev/null and b/app/images/icons/neighborhood-post-selected.png differ diff --git a/app/images/icons/neighborhood-post.png b/app/images/icons/neighborhood-post.png new file mode 100644 index 000000000..0a5ac22e0 Binary files /dev/null and b/app/images/icons/neighborhood-post.png differ diff --git a/app/images/icons/new-announcement.png b/app/images/icons/new-announcement.png new file mode 100644 index 000000000..1f57557b1 Binary files /dev/null and b/app/images/icons/new-announcement.png differ diff --git a/app/images/icons/new-event.png b/app/images/icons/new-event.png new file mode 100644 index 000000000..db71192aa Binary files /dev/null and b/app/images/icons/new-event.png differ diff --git a/app/images/icons/new-post.png b/app/images/icons/new-post.png new file mode 100644 index 000000000..58f143387 Binary files /dev/null and b/app/images/icons/new-post.png differ diff --git a/public/images/intro_slideshow/neighborhood1.jpg b/app/images/intro_slideshow/neighborhood1.jpg similarity index 100% rename from public/images/intro_slideshow/neighborhood1.jpg rename to app/images/intro_slideshow/neighborhood1.jpg diff --git a/public/images/intro_slideshow/neighborhood2.jpg b/app/images/intro_slideshow/neighborhood2.jpg similarity index 100% rename from public/images/intro_slideshow/neighborhood2.jpg rename to app/images/intro_slideshow/neighborhood2.jpg diff --git a/app/images/invite/PDFLogo.png b/app/images/invite/PDFLogo.png new file mode 100644 index 000000000..ede5d3a6c Binary files /dev/null and b/app/images/invite/PDFLogo.png differ diff --git a/app/images/invite/downloadzip.png b/app/images/invite/downloadzip.png new file mode 100644 index 000000000..a73fe66aa Binary files /dev/null and b/app/images/invite/downloadzip.png differ diff --git a/app/images/invite/facebookinvite.png b/app/images/invite/facebookinvite.png new file mode 100644 index 000000000..b08bc949c Binary files /dev/null and b/app/images/invite/facebookinvite.png differ diff --git a/app/images/invite/findneighbors.png b/app/images/invite/findneighbors.png new file mode 100644 index 000000000..46bf2c4d8 Binary files /dev/null and b/app/images/invite/findneighbors.png differ diff --git a/app/images/invite/joinfriends.png b/app/images/invite/joinfriends.png new file mode 100644 index 000000000..78b725f0b Binary files /dev/null and b/app/images/invite/joinfriends.png differ diff --git a/app/images/invite/neighborinvite.png b/app/images/invite/neighborinvite.png new file mode 100644 index 000000000..9a5e5ae79 Binary files /dev/null and b/app/images/invite/neighborinvite.png differ diff --git a/app/images/invite/plane.png b/app/images/invite/plane.png new file mode 100644 index 000000000..356227e2e Binary files /dev/null and b/app/images/invite/plane.png differ diff --git a/app/images/invite/twitterinv.png b/app/images/invite/twitterinv.png new file mode 100644 index 000000000..7d7cd2642 Binary files /dev/null and b/app/images/invite/twitterinv.png differ diff --git a/public/images/item_hover.png b/app/images/item_hover.png similarity index 100% rename from public/images/item_hover.png rename to app/images/item_hover.png diff --git a/app/images/learn_more/bulletin_screen_shot.png b/app/images/learn_more/bulletin_screen_shot.png new file mode 100644 index 000000000..e5c14b85c Binary files /dev/null and b/app/images/learn_more/bulletin_screen_shot.png differ diff --git a/app/images/learn_more/community_screen_shot.png b/app/images/learn_more/community_screen_shot.png new file mode 100644 index 000000000..aafa0bd61 Binary files /dev/null and b/app/images/learn_more/community_screen_shot.png differ diff --git a/app/images/learn_more/post_screen_shot.png b/app/images/learn_more/post_screen_shot.png new file mode 100644 index 000000000..793642bd5 Binary files /dev/null and b/app/images/learn_more/post_screen_shot.png differ diff --git a/app/images/learn_more/sign_up_screen_shot.png b/app/images/learn_more/sign_up_screen_shot.png new file mode 100644 index 000000000..faa88b4c1 Binary files /dev/null and b/app/images/learn_more/sign_up_screen_shot.png differ diff --git a/app/images/left-arrow.png b/app/images/left-arrow.png new file mode 100644 index 000000000..7e5b53c50 Binary files /dev/null and b/app/images/left-arrow.png differ diff --git a/public/images/loading.gif b/app/images/loading.gif similarity index 100% rename from public/images/loading.gif rename to app/images/loading.gif diff --git a/public/images/logo-pin.png b/app/images/logo-pin.png similarity index 100% rename from public/images/logo-pin.png rename to app/images/logo-pin.png diff --git a/public/images/logo.png b/app/images/logo.png similarity index 100% rename from public/images/logo.png rename to app/images/logo.png diff --git a/app/images/logo2.png b/app/images/logo2.png new file mode 100644 index 000000000..247ff8791 Binary files /dev/null and b/app/images/logo2.png differ diff --git a/app/images/mail/invite-them-now-button.png b/app/images/mail/invite-them-now-button.png new file mode 100644 index 000000000..601a7848c Binary files /dev/null and b/app/images/mail/invite-them-now-button.png differ diff --git a/app/images/mail/reply-button.png b/app/images/mail/reply-button.png new file mode 100644 index 000000000..ca1a09097 Binary files /dev/null and b/app/images/mail/reply-button.png differ diff --git a/app/images/mail/reply-now-button.png b/app/images/mail/reply-now-button.png new file mode 100644 index 000000000..fe52b41db Binary files /dev/null and b/app/images/mail/reply-now-button.png differ diff --git a/app/images/main_page/announcement-tab-icon.png b/app/images/main_page/announcement-tab-icon.png new file mode 100644 index 000000000..2857c53cc Binary files /dev/null and b/app/images/main_page/announcement-tab-icon.png differ diff --git a/app/images/main_page/event-tab-icon.png b/app/images/main_page/event-tab-icon.png new file mode 100644 index 000000000..dc370fa59 Binary files /dev/null and b/app/images/main_page/event-tab-icon.png differ diff --git a/app/images/main_page/group-post-tab-icon.png b/app/images/main_page/group-post-tab-icon.png new file mode 100644 index 000000000..46051f185 Binary files /dev/null and b/app/images/main_page/group-post-tab-icon.png differ diff --git a/app/images/main_page/neighborhood-post-tab-icon.png b/app/images/main_page/neighborhood-post-tab-icon.png new file mode 100644 index 000000000..46fbb935f Binary files /dev/null and b/app/images/main_page/neighborhood-post-tab-icon.png differ diff --git a/app/images/mobile/0stars.png b/app/images/mobile/0stars.png new file mode 100644 index 000000000..81947f7bc Binary files /dev/null and b/app/images/mobile/0stars.png differ diff --git a/app/images/mobile/0starsborder.png b/app/images/mobile/0starsborder.png new file mode 100644 index 000000000..f4d231499 Binary files /dev/null and b/app/images/mobile/0starsborder.png differ diff --git a/app/images/mobile/1stars.png b/app/images/mobile/1stars.png new file mode 100644 index 000000000..12dd9c6a9 Binary files /dev/null and b/app/images/mobile/1stars.png differ diff --git a/app/images/mobile/1starsborder.png b/app/images/mobile/1starsborder.png new file mode 100644 index 000000000..ad21998f1 Binary files /dev/null and b/app/images/mobile/1starsborder.png differ diff --git a/app/images/mobile/2stars.png b/app/images/mobile/2stars.png new file mode 100644 index 000000000..fa7e2b95c Binary files /dev/null and b/app/images/mobile/2stars.png differ diff --git a/app/images/mobile/2starsborder.png b/app/images/mobile/2starsborder.png new file mode 100644 index 000000000..10a2b912b Binary files /dev/null and b/app/images/mobile/2starsborder.png differ diff --git a/app/images/mobile/3stars.png b/app/images/mobile/3stars.png new file mode 100644 index 000000000..7136c3f24 Binary files /dev/null and b/app/images/mobile/3stars.png differ diff --git a/app/images/mobile/3starsborder.png b/app/images/mobile/3starsborder.png new file mode 100644 index 000000000..22c5a9252 Binary files /dev/null and b/app/images/mobile/3starsborder.png differ diff --git a/app/images/mobile/4stars.png b/app/images/mobile/4stars.png new file mode 100644 index 000000000..071415689 Binary files /dev/null and b/app/images/mobile/4stars.png differ diff --git a/app/images/mobile/4starsborder.png b/app/images/mobile/4starsborder.png new file mode 100644 index 000000000..4a17ceef0 Binary files /dev/null and b/app/images/mobile/4starsborder.png differ diff --git a/app/images/mobile/5stars.png b/app/images/mobile/5stars.png new file mode 100644 index 000000000..370c60be5 Binary files /dev/null and b/app/images/mobile/5stars.png differ diff --git a/app/images/mobile/arrow.png b/app/images/mobile/arrow.png new file mode 100644 index 000000000..9993a06bf Binary files /dev/null and b/app/images/mobile/arrow.png differ diff --git a/app/images/mobile/checkbox.png b/app/images/mobile/checkbox.png new file mode 100644 index 000000000..75f0ede29 Binary files /dev/null and b/app/images/mobile/checkbox.png differ diff --git a/app/images/mobile/home.png b/app/images/mobile/home.png new file mode 100644 index 000000000..0fc49fcb8 Binary files /dev/null and b/app/images/mobile/home.png differ diff --git a/app/images/mobile/logo.gif b/app/images/mobile/logo.gif new file mode 100644 index 000000000..0f0550b23 Binary files /dev/null and b/app/images/mobile/logo.gif differ diff --git a/app/images/mobile/navbutton.png b/app/images/mobile/navbutton.png new file mode 100644 index 000000000..c786de4ea Binary files /dev/null and b/app/images/mobile/navbutton.png differ diff --git a/app/images/mobile/navbuttonblack.png b/app/images/mobile/navbuttonblack.png new file mode 100644 index 000000000..b7a9bab49 Binary files /dev/null and b/app/images/mobile/navbuttonblack.png differ diff --git a/app/images/mobile/navbuttonblue.png b/app/images/mobile/navbuttonblue.png new file mode 100644 index 000000000..97a2c983b Binary files /dev/null and b/app/images/mobile/navbuttonblue.png differ diff --git a/app/images/mobile/navlinkright.png b/app/images/mobile/navlinkright.png new file mode 100644 index 000000000..df5131f87 Binary files /dev/null and b/app/images/mobile/navlinkright.png differ diff --git a/app/images/mobile/navlinkrightblack.png b/app/images/mobile/navlinkrightblack.png new file mode 100644 index 000000000..ef5508491 Binary files /dev/null and b/app/images/mobile/navlinkrightblack.png differ diff --git a/app/images/mobile/navright.png b/app/images/mobile/navright.png new file mode 100644 index 000000000..afa269d08 Binary files /dev/null and b/app/images/mobile/navright.png differ diff --git a/app/images/mobile/navrightblack.png b/app/images/mobile/navrightblack.png new file mode 100644 index 000000000..80c5b8334 Binary files /dev/null and b/app/images/mobile/navrightblack.png differ diff --git a/app/images/mobile/play.gif b/app/images/mobile/play.gif new file mode 100644 index 000000000..7447d0541 Binary files /dev/null and b/app/images/mobile/play.gif differ diff --git a/app/images/mobile/searchfield.png b/app/images/mobile/searchfield.png new file mode 100644 index 000000000..05eb98a07 Binary files /dev/null and b/app/images/mobile/searchfield.png differ diff --git a/app/images/mobile/tributton.png b/app/images/mobile/tributton.png new file mode 100644 index 000000000..f5c8f55f3 Binary files /dev/null and b/app/images/mobile/tributton.png differ diff --git a/public/images/modal-close.png b/app/images/modal-close.png similarity index 100% rename from public/images/modal-close.png rename to app/images/modal-close.png diff --git a/app/images/nav-down-arrow.png b/app/images/nav-down-arrow.png new file mode 100644 index 000000000..14b0f2d69 Binary files /dev/null and b/app/images/nav-down-arrow.png differ diff --git a/public/images/nav-down-arrow.png b/app/images/nav-down-arrow.png.bak similarity index 100% rename from public/images/nav-down-arrow.png rename to app/images/nav-down-arrow.png.bak diff --git a/app/images/neighborhood-bg.png b/app/images/neighborhood-bg.png new file mode 100644 index 000000000..2fc7d6b52 Binary files /dev/null and b/app/images/neighborhood-bg.png differ diff --git a/public/images/neighborhood-zone.png b/app/images/neighborhood-zone.png similarity index 100% rename from public/images/neighborhood-zone.png rename to app/images/neighborhood-zone.png diff --git a/public/images/notification_border.png b/app/images/notification_border.png similarity index 100% rename from public/images/notification_border.png rename to app/images/notification_border.png diff --git a/public/images/notification_header.png b/app/images/notification_header.png similarity index 100% rename from public/images/notification_header.png rename to app/images/notification_header.png diff --git a/app/images/ourcommonplace-logo.png b/app/images/ourcommonplace-logo.png new file mode 100644 index 000000000..7e8a660ab Binary files /dev/null and b/app/images/ourcommonplace-logo.png differ diff --git a/public/images/people.png b/app/images/people.png similarity index 100% rename from public/images/people.png rename to app/images/people.png diff --git a/app/images/pete-davis.png b/app/images/pete-davis.png new file mode 100644 index 000000000..0654b3970 Binary files /dev/null and b/app/images/pete-davis.png differ diff --git a/public/images/pin-icon.png b/app/images/pin-icon.png similarity index 100% rename from public/images/pin-icon.png rename to app/images/pin-icon.png diff --git a/public/images/plus_sign.png b/app/images/plus_sign.png similarity index 100% rename from public/images/plus_sign.png rename to app/images/plus_sign.png diff --git a/public/images/plus_sign.psd b/app/images/plus_sign.psd similarity index 100% rename from public/images/plus_sign.psd rename to app/images/plus_sign.psd diff --git a/app/images/post-box-button-sprite.png b/app/images/post-box-button-sprite.png new file mode 100644 index 000000000..40d1eb109 Binary files /dev/null and b/app/images/post-box-button-sprite.png differ diff --git a/app/images/post-box-button-sprite2.png b/app/images/post-box-button-sprite2.png new file mode 100644 index 000000000..4f89aa05a Binary files /dev/null and b/app/images/post-box-button-sprite2.png differ diff --git a/app/images/post-hall-btn-selected.png b/app/images/post-hall-btn-selected.png new file mode 100644 index 000000000..d83d59270 Binary files /dev/null and b/app/images/post-hall-btn-selected.png differ diff --git a/app/images/post-hall-btn.png b/app/images/post-hall-btn.png new file mode 100644 index 000000000..c371649c3 Binary files /dev/null and b/app/images/post-hall-btn.png differ diff --git a/app/images/post-nav-hover-arrow.png b/app/images/post-nav-hover-arrow.png new file mode 100644 index 000000000..9b6d438f2 Binary files /dev/null and b/app/images/post-nav-hover-arrow.png differ diff --git a/public/images/posts.png b/app/images/posts.png similarity index 100% rename from public/images/posts.png rename to app/images/posts.png diff --git a/public/images/privacy.jpg b/app/images/privacy.jpg similarity index 100% rename from public/images/privacy.jpg rename to app/images/privacy.jpg diff --git a/public/images/reply-arrow-top.png b/app/images/reply-arrow-top.png similarity index 100% rename from public/images/reply-arrow-top.png rename to app/images/reply-arrow-top.png diff --git a/public/images/say-something-selected.png b/app/images/say-something-selected.png similarity index 100% rename from public/images/say-something-selected.png rename to app/images/say-something-selected.png diff --git a/public/images/say-something-unselected.png b/app/images/say-something-unselected.png similarity index 100% rename from public/images/say-something-unselected.png rename to app/images/say-something-unselected.png diff --git a/app/images/search-blue.png b/app/images/search-blue.png new file mode 100644 index 000000000..a03c7282f Binary files /dev/null and b/app/images/search-blue.png differ diff --git a/app/images/shared/icons/add_person.png b/app/images/shared/icons/add_person.png new file mode 100644 index 000000000..5e665a48f Binary files /dev/null and b/app/images/shared/icons/add_person.png differ diff --git a/app/images/shared/icons/search.png b/app/images/shared/icons/search.png new file mode 100644 index 000000000..36a9a49ed Binary files /dev/null and b/app/images/shared/icons/search.png differ diff --git a/public/images/sign-up-now-btn.png b/app/images/sign-up-now-btn.png similarity index 100% rename from public/images/sign-up-now-btn.png rename to app/images/sign-up-now-btn.png diff --git a/public/images/sign-up-now.png b/app/images/sign-up-now.png similarity index 100% rename from public/images/sign-up-now.png rename to app/images/sign-up-now.png diff --git a/public/images/speech-bubble.png b/app/images/speech-bubble.png similarity index 100% rename from public/images/speech-bubble.png rename to app/images/speech-bubble.png diff --git a/public/images/staging.png b/app/images/staging.png similarity index 100% rename from public/images/staging.png rename to app/images/staging.png diff --git a/public/images/staging_notification_header.png b/app/images/staging_notification_header.png similarity index 100% rename from public/images/staging_notification_header.png rename to app/images/staging_notification_header.png diff --git a/public/images/staging_signup.png b/app/images/staging_signup.png similarity index 100% rename from public/images/staging_signup.png rename to app/images/staging_signup.png diff --git a/app/images/starter_site/border.jpg b/app/images/starter_site/border.jpg new file mode 100644 index 000000000..16d1eb45a Binary files /dev/null and b/app/images/starter_site/border.jpg differ diff --git a/app/images/starter_site/bubble.png b/app/images/starter_site/bubble.png new file mode 100644 index 000000000..d3b549429 Binary files /dev/null and b/app/images/starter_site/bubble.png differ diff --git a/app/images/starter_site/bubble2.png b/app/images/starter_site/bubble2.png new file mode 100644 index 000000000..2c82e3a40 Binary files /dev/null and b/app/images/starter_site/bubble2.png differ diff --git a/app/images/starter_site/by.png b/app/images/starter_site/by.png new file mode 100644 index 000000000..0893d5555 Binary files /dev/null and b/app/images/starter_site/by.png differ diff --git a/app/images/starter_site/cp_public.png b/app/images/starter_site/cp_public.png new file mode 100644 index 000000000..9a0f9d05a Binary files /dev/null and b/app/images/starter_site/cp_public.png differ diff --git a/app/images/starter_site/events.png b/app/images/starter_site/events.png new file mode 100644 index 000000000..5ac56927b Binary files /dev/null and b/app/images/starter_site/events.png differ diff --git a/app/images/starter_site/goods_and_skills.png b/app/images/starter_site/goods_and_skills.png new file mode 100644 index 000000000..72132a381 Binary files /dev/null and b/app/images/starter_site/goods_and_skills.png differ diff --git a/app/images/starter_site/grid.png b/app/images/starter_site/grid.png new file mode 100644 index 000000000..129d4a29f Binary files /dev/null and b/app/images/starter_site/grid.png differ diff --git a/app/images/starter_site/heart.png b/app/images/starter_site/heart.png new file mode 100644 index 000000000..8f06e19b5 Binary files /dev/null and b/app/images/starter_site/heart.png differ diff --git a/app/images/starter_site/icons/facebook.png b/app/images/starter_site/icons/facebook.png new file mode 100644 index 000000000..a6cdb701e Binary files /dev/null and b/app/images/starter_site/icons/facebook.png differ diff --git a/app/images/starter_site/icons/newsletter.png b/app/images/starter_site/icons/newsletter.png new file mode 100644 index 000000000..12b849846 Binary files /dev/null and b/app/images/starter_site/icons/newsletter.png differ diff --git a/app/images/starter_site/icons/press-kit.png b/app/images/starter_site/icons/press-kit.png new file mode 100644 index 000000000..1cd5906d1 Binary files /dev/null and b/app/images/starter_site/icons/press-kit.png differ diff --git a/app/images/starter_site/icons/rox-home.png b/app/images/starter_site/icons/rox-home.png new file mode 100644 index 000000000..54dcffb38 Binary files /dev/null and b/app/images/starter_site/icons/rox-home.png differ diff --git a/app/images/starter_site/icons/twitter.png b/app/images/starter_site/icons/twitter.png new file mode 100644 index 000000000..5b7f93b8e Binary files /dev/null and b/app/images/starter_site/icons/twitter.png differ diff --git a/app/images/starter_site/johns.png b/app/images/starter_site/johns.png new file mode 100644 index 000000000..b7e866523 Binary files /dev/null and b/app/images/starter_site/johns.png differ diff --git a/app/images/starter_site/logo.jpg b/app/images/starter_site/logo.jpg new file mode 100644 index 000000000..22fee0b2b Binary files /dev/null and b/app/images/starter_site/logo.jpg differ diff --git a/app/images/starter_site/logo.png b/app/images/starter_site/logo.png new file mode 100644 index 000000000..f5a7adeee Binary files /dev/null and b/app/images/starter_site/logo.png differ diff --git a/app/images/starter_site/logo1.png b/app/images/starter_site/logo1.png new file mode 100644 index 000000000..affb38728 Binary files /dev/null and b/app/images/starter_site/logo1.png differ diff --git a/app/images/starter_site/nd.png b/app/images/starter_site/nd.png new file mode 100644 index 000000000..fd6486714 Binary files /dev/null and b/app/images/starter_site/nd.png differ diff --git a/app/images/starter_site/person1.jpg b/app/images/starter_site/person1.jpg new file mode 100644 index 000000000..a7e40b18e Binary files /dev/null and b/app/images/starter_site/person1.jpg differ diff --git a/app/images/starter_site/person2.png b/app/images/starter_site/person2.png new file mode 100644 index 000000000..85031c80f Binary files /dev/null and b/app/images/starter_site/person2.png differ diff --git a/app/images/starter_site/person3.png b/app/images/starter_site/person3.png new file mode 100644 index 000000000..de3edfcaa Binary files /dev/null and b/app/images/starter_site/person3.png differ diff --git a/app/images/starter_site/person5.png b/app/images/starter_site/person5.png new file mode 100644 index 000000000..f4b1f6286 Binary files /dev/null and b/app/images/starter_site/person5.png differ diff --git a/app/images/starter_site/person6.png b/app/images/starter_site/person6.png new file mode 100644 index 000000000..333b2834b Binary files /dev/null and b/app/images/starter_site/person6.png differ diff --git a/app/images/starter_site/posts.png b/app/images/starter_site/posts.png new file mode 100644 index 000000000..eeca860d0 Binary files /dev/null and b/app/images/starter_site/posts.png differ diff --git a/app/images/starter_site/rails.png b/app/images/starter_site/rails.png new file mode 100644 index 000000000..d5edc04e6 Binary files /dev/null and b/app/images/starter_site/rails.png differ diff --git a/app/images/starter_site/screenshot1.png b/app/images/starter_site/screenshot1.png new file mode 100644 index 000000000..83050ac08 Binary files /dev/null and b/app/images/starter_site/screenshot1.png differ diff --git a/app/images/starter_site/screenshot2.png b/app/images/starter_site/screenshot2.png new file mode 100644 index 000000000..a283bd520 Binary files /dev/null and b/app/images/starter_site/screenshot2.png differ diff --git a/app/images/starter_site/screenshot3.png b/app/images/starter_site/screenshot3.png new file mode 100644 index 000000000..532dadd65 Binary files /dev/null and b/app/images/starter_site/screenshot3.png differ diff --git a/app/images/starter_site/screenshot4.png b/app/images/starter_site/screenshot4.png new file mode 100644 index 000000000..72caa9a4a Binary files /dev/null and b/app/images/starter_site/screenshot4.png differ diff --git a/app/images/starter_site/share.png b/app/images/starter_site/share.png new file mode 100644 index 000000000..70a3540c3 Binary files /dev/null and b/app/images/starter_site/share.png differ diff --git a/app/images/starter_site/skyline-bg.png b/app/images/starter_site/skyline-bg.png new file mode 100644 index 000000000..688282070 Binary files /dev/null and b/app/images/starter_site/skyline-bg.png differ diff --git a/app/images/starter_site/user1.png b/app/images/starter_site/user1.png new file mode 100644 index 000000000..ce06f954b Binary files /dev/null and b/app/images/starter_site/user1.png differ diff --git a/app/images/starter_site/users.png b/app/images/starter_site/users.png new file mode 100644 index 000000000..da3a3fecd Binary files /dev/null and b/app/images/starter_site/users.png differ diff --git a/app/images/starter_site/users1.png b/app/images/starter_site/users1.png new file mode 100644 index 000000000..46e5d5b40 Binary files /dev/null and b/app/images/starter_site/users1.png differ diff --git a/app/images/step-1-icon-small.png b/app/images/step-1-icon-small.png new file mode 100644 index 000000000..b8daaec47 Binary files /dev/null and b/app/images/step-1-icon-small.png differ diff --git a/public/images/step-1-icon.png b/app/images/step-1-icon.png similarity index 100% rename from public/images/step-1-icon.png rename to app/images/step-1-icon.png diff --git a/app/images/step-2-icon-small.png b/app/images/step-2-icon-small.png new file mode 100644 index 000000000..86cbfbb97 Binary files /dev/null and b/app/images/step-2-icon-small.png differ diff --git a/public/images/step-2-icon.png b/app/images/step-2-icon.png similarity index 100% rename from public/images/step-2-icon.png rename to app/images/step-2-icon.png diff --git a/app/images/step-3-icon-small.png b/app/images/step-3-icon-small.png new file mode 100644 index 000000000..9dca05472 Binary files /dev/null and b/app/images/step-3-icon-small.png differ diff --git a/public/images/step-3-icon.png b/app/images/step-3-icon.png similarity index 100% rename from public/images/step-3-icon.png rename to app/images/step-3-icon.png diff --git a/public/images/step-4-icon-large.png b/app/images/step-4-icon-large.png similarity index 100% rename from public/images/step-4-icon-large.png rename to app/images/step-4-icon-large.png diff --git a/public/images/step-4-icon.png b/app/images/step-4-icon.png similarity index 100% rename from public/images/step-4-icon.png rename to app/images/step-4-icon.png diff --git a/public/images/step-5-icon.png b/app/images/step-5-icon.png similarity index 100% rename from public/images/step-5-icon.png rename to app/images/step-5-icon.png diff --git a/public/images/terms.jpg b/app/images/terms.jpg similarity index 100% rename from public/images/terms.jpg rename to app/images/terms.jpg diff --git a/public/images/test.png b/app/images/test.png similarity index 100% rename from public/images/test.png rename to app/images/test.png diff --git a/public/images/test_notification_header.png b/app/images/test_notification_header.png similarity index 100% rename from public/images/test_notification_header.png rename to app/images/test_notification_header.png diff --git a/public/images/test_signup.png b/app/images/test_signup.png similarity index 100% rename from public/images/test_signup.png rename to app/images/test_signup.png diff --git a/public/images/tipsy.gif b/app/images/tipsy.gif similarity index 100% rename from public/images/tipsy.gif rename to app/images/tipsy.gif diff --git a/app/images/tooltip-bg-sm.png b/app/images/tooltip-bg-sm.png new file mode 100644 index 000000000..074f3af13 Binary files /dev/null and b/app/images/tooltip-bg-sm.png differ diff --git a/app/images/topbubble.png b/app/images/topbubble.png new file mode 100644 index 000000000..137f7bef2 Binary files /dev/null and b/app/images/topbubble.png differ diff --git a/app/images/tour/announcement-icon.png b/app/images/tour/announcement-icon.png new file mode 100644 index 000000000..7fb67ed6c Binary files /dev/null and b/app/images/tour/announcement-icon.png differ diff --git a/app/images/tour/discussion-icon.png b/app/images/tour/discussion-icon.png new file mode 100644 index 000000000..e6805f2d0 Binary files /dev/null and b/app/images/tour/discussion-icon.png differ diff --git a/app/images/tour/events-icon.png b/app/images/tour/events-icon.png new file mode 100644 index 000000000..12ab9056f Binary files /dev/null and b/app/images/tour/events-icon.png differ diff --git a/app/images/tour/large-tour-bg.png b/app/images/tour/large-tour-bg.png new file mode 100644 index 000000000..a5de515dc Binary files /dev/null and b/app/images/tour/large-tour-bg.png differ diff --git a/app/images/tour/neighborhood-post-icon.png b/app/images/tour/neighborhood-post-icon.png new file mode 100644 index 000000000..fae0a9e4b Binary files /dev/null and b/app/images/tour/neighborhood-post-icon.png differ diff --git a/app/images/tour/next-button.png b/app/images/tour/next-button.png new file mode 100644 index 000000000..8881977d1 Binary files /dev/null and b/app/images/tour/next-button.png differ diff --git a/app/images/tour/reverse-small-tour-bg.png b/app/images/tour/reverse-small-tour-bg.png new file mode 100644 index 000000000..8668179fe Binary files /dev/null and b/app/images/tour/reverse-small-tour-bg.png differ diff --git a/app/images/tour/small-tour-bg.png b/app/images/tour/small-tour-bg.png new file mode 100644 index 000000000..074f3af13 Binary files /dev/null and b/app/images/tour/small-tour-bg.png differ diff --git a/app/images/tour/star-icon.png b/app/images/tour/star-icon.png new file mode 100644 index 000000000..1a6450905 Binary files /dev/null and b/app/images/tour/star-icon.png differ diff --git a/app/images/tour/tour-button.png b/app/images/tour/tour-button.png new file mode 100644 index 000000000..fb3b915d3 Binary files /dev/null and b/app/images/tour/tour-button.png differ diff --git a/app/images/tour/up-tour-bg.png b/app/images/tour/up-tour-bg.png new file mode 100644 index 000000000..459004de5 Binary files /dev/null and b/app/images/tour/up-tour-bg.png differ diff --git a/app/images/tour/welcome-bg.png b/app/images/tour/welcome-bg.png new file mode 100644 index 000000000..846ac628d Binary files /dev/null and b/app/images/tour/welcome-bg.png differ diff --git a/app/images/tour/welcome-to.png b/app/images/tour/welcome-to.png new file mode 100644 index 000000000..7c7dd480c Binary files /dev/null and b/app/images/tour/welcome-to.png differ diff --git a/public/images/transparent_big.png b/app/images/transparent_big.png similarity index 100% rename from public/images/transparent_big.png rename to app/images/transparent_big.png diff --git a/public/images/transparent_old.png b/app/images/transparent_old.png similarity index 100% rename from public/images/transparent_old.png rename to app/images/transparent_old.png diff --git a/public/images/transparent_small.png b/app/images/transparent_small.png similarity index 100% rename from public/images/transparent_small.png rename to app/images/transparent_small.png diff --git a/public/images/upload.gif b/app/images/upload.gif similarity index 100% rename from public/images/upload.gif rename to app/images/upload.gif diff --git a/app/images/upperuws_notification_header.png b/app/images/upperuws_notification_header.png new file mode 100644 index 000000000..51844105b Binary files /dev/null and b/app/images/upperuws_notification_header.png differ diff --git a/public/images/waiting.gif b/app/images/waiting.gif similarity index 100% rename from public/images/waiting.gif rename to app/images/waiting.gif diff --git a/public/images/westroxbury.png b/app/images/westroxbury.png similarity index 100% rename from public/images/westroxbury.png rename to app/images/westroxbury.png diff --git a/public/images/westroxbury/slideshow/1.jpg b/app/images/westroxbury/slideshow/1.jpg similarity index 100% rename from public/images/westroxbury/slideshow/1.jpg rename to app/images/westroxbury/slideshow/1.jpg diff --git a/public/images/westroxbury/slideshow/2.jpg b/app/images/westroxbury/slideshow/2.jpg similarity index 100% rename from public/images/westroxbury/slideshow/2.jpg rename to app/images/westroxbury/slideshow/2.jpg diff --git a/public/images/westroxbury/slideshow/3.jpg b/app/images/westroxbury/slideshow/3.jpg similarity index 100% rename from public/images/westroxbury/slideshow/3.jpg rename to app/images/westroxbury/slideshow/3.jpg diff --git a/public/images/westroxbury_notification_header.png b/app/images/westroxbury_notification_header.png similarity index 100% rename from public/images/westroxbury_notification_header.png rename to app/images/westroxbury_notification_header.png diff --git a/public/images/westroxbury_signup.png b/app/images/westroxbury_signup.png similarity index 100% rename from public/images/westroxbury_signup.png rename to app/images/westroxbury_signup.png diff --git a/public/images/yourtown.png b/app/images/yourtown.png similarity index 100% rename from public/images/yourtown.png rename to app/images/yourtown.png diff --git a/app/javascripts/accounts.js b/app/javascripts/accounts.js new file mode 100644 index 000000000..0414802ff --- /dev/null +++ b/app/javascripts/accounts.js @@ -0,0 +1,32 @@ + +function load_modal(){ + var photo_form = '
' + + '
Take a photo
' + + '
'; + $.modal(photo_form, { + opacity:70, + overlayCss: {backgroundColor:"#000"}, + containerCss:{ + backgroundColor:"#fff", + borderColor:"#fff", + height:300, + padding:0, + width:340 + }, + overlayClose:true + + }); + $('#webcam').webcam({ + width: 320, + height: 240, + mode: "save", + swffile: "/swf/jscam.swf", + onTick: function() {}, + onSave: function(data) {}, + onCapture: function() { + webcam.save('/account/take_photo'); + }, + debug: function() {}, + onLoad: function() {} + }); +} diff --git a/app/javascripts/active_admin.js b/app/javascripts/active_admin.js new file mode 100644 index 000000000..5b3073cf6 --- /dev/null +++ b/app/javascripts/active_admin.js @@ -0,0 +1,51 @@ +/* Active Admin JS */ + +$(function(){ + $(".datepicker").datepicker({dateFormat: 'yy-mm-dd'}); + + $(".clear_filters_btn").click(function(){ + window.location.search = ""; + return false; + }); + + // AJAX Comments + $('form#admin_note_new').submit(function() { + + if ($(this).find('#admin_note_body').val() != "") { + $(this).fadeOut('slow', function() { + $('.loading_indicator').fadeIn(); + $.ajax({ + url: $(this).attr('action'), + type: 'POST', + dataType: 'json', + data: $(this).serialize(), + success: function(data, textStatus, xhr) { + $('.loading_indicator').fadeOut('slow', function(){ + + // Hide the empty message + $('.admin_notes_list li.empty').fadeOut().remove(); + + // Add the note + $('.admin_notes_list').append(data['note']); + + // Update the number of notes + $('.admin_notes h3 span.admin_notes_count').html("(" + data['number_of_notes'] + ")"); + + // Reset the form + $('form#new_active_admin_admin_note').find('#active_admin_admin_note_body').val(""); + + // Show the form + $('form#new_active_admin_admin_note').fadeIn('slow'); + }) + }, + error: function(xhr, textStatus, errorThrown) { + //called when there is an error + } + }); + }); + + }; + + return false; + }); +}); diff --git a/app/javascripts/active_admin_vendor.js b/app/javascripts/active_admin_vendor.js new file mode 100644 index 000000000..f1a88eeb0 --- /dev/null +++ b/app/javascripts/active_admin_vendor.js @@ -0,0 +1,382 @@ +/*! + * jQuery JavaScript Library v1.4.2 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Sat Feb 13 22:33:48 2010 -0500 + */ +(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, +Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& +(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, +a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== +"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, +function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
a"; +var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected, +parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent= +false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n= +s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true, +applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando]; +else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this, +a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b=== +w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i, +cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected= +c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); +a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g, +function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split("."); +k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a), +C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B=0){a.type= +e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&& +f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive; +if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data", +e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a, +"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a, +d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, +e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift(); +t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D|| +g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, +CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, +g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, +text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, +setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= +h[3];l=0;for(m=h.length;l=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== +"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, +h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& +q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; +if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="

";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); +(function(){var g=s.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: +function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= +{},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== +"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", +d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? +a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== +1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"},F={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= +c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, +wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, +prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, +this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); +return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, +""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); +return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", +""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= +c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? +c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= +function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= +Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, +"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= +a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= +a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=//gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!== +"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("
").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this}, +serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), +function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href, +global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&& +e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)? +"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache=== +false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B= +false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since", +c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E|| +d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x); +g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status=== +1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b=== +"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional; +if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration=== +"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]|| +c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start; +this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now= +this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem, +e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b
"; +a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b); +c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a, +d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top- +f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset": +"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in +e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window); + +/** + * Rails.js + */ +jQuery(function ($) { + var csrf_token = $('meta[name=csrf-token]').attr('content'), + csrf_param = $('meta[name=csrf-param]').attr('content'); + + $.fn.extend({ + /** + * Triggers a custom event on an element and returns the event result + * this is used to get around not being able to ensure callbacks are placed + * at the end of the chain. + * + * TODO: deprecate with jQuery 1.4.2 release, in favor of subscribing to our + * own events and placing ourselves at the end of the chain. + */ + triggerAndReturn: function (name, data) { + var event = new $.Event(name); + this.trigger(event, data); + + return event.result !== false; + }, + + /** + * Handles execution of remote calls firing overridable events along the way + */ + callRemote: function () { + var el = this, + method = el.attr('method') || el.attr('data-method') || 'GET', + url = el.attr('action') || el.attr('href'), + dataType = el.attr('data-type') || 'script'; + + if (url === undefined) { + throw "No URL specified for remote call (action or href must be present)."; + } else { + if (el.triggerAndReturn('ajax:before')) { + var data = el.is('form') ? el.serializeArray() : []; + $.ajax({ + url: url, + data: data, + dataType: dataType, + type: method.toUpperCase(), + beforeSend: function (xhr) { + el.trigger('ajax:loading', xhr); + }, + success: function (data, status, xhr) { + el.trigger('ajax:success', [data, status, xhr]); + }, + complete: function (xhr) { + el.trigger('ajax:complete', xhr); + }, + error: function (xhr, status, error) { + el.trigger('ajax:failure', [xhr, status, error]); + } + }); + } + + el.trigger('ajax:after'); + } + } + }); + + /** + * confirmation handler + */ + $('a[data-confirm],input[data-confirm]').live('click', function () { + var el = $(this); + if (el.triggerAndReturn('confirm')) { + if (!confirm(el.attr('data-confirm'))) { + return false; + } + } + }); + + + /** + * remote handlers + */ + $('form[data-remote]').live('submit', function (e) { + $(this).callRemote(); + e.preventDefault(); + }); + + $('a[data-remote],input[data-remote]').live('click', function (e) { + $(this).callRemote(); + e.preventDefault(); + }); + + $('a[data-method]:not([data-remote])').live('click', function (e){ + var link = $(this), + href = link.attr('href'), + method = link.attr('data-method'), + form = $('
'), + metadata_input = ''; + + if (csrf_param != null && csrf_token != null) { + metadata_input += ''; + } + + form.hide() + .append(metadata_input) + .appendTo('body'); + + e.preventDefault(); + form.submit(); + }); + + /** + * disable-with handlers + */ + var disable_with_input_selector = 'input[data-disable-with]'; + var disable_with_form_selector = 'form[data-remote]:has(' + disable_with_input_selector + ')'; + + $(disable_with_form_selector).live('ajax:before', function () { + $(this).find(disable_with_input_selector).each(function () { + var input = $(this); + input.data('enable-with', input.val()) + .attr('value', input.attr('data-disable-with')) + .attr('disabled', 'disabled'); + }); + }); + + $(disable_with_form_selector).live('ajax:complete', function () { + $(this).find(disable_with_input_selector).each(function () { + var input = $(this); + input.removeAttr('disabled') + .val(input.data('enable-with')); + }); + }); +}); + +/*! + * jQuery UI 1.8.2 + * + * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI + */ +(function(c){c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.2",plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=0;e0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a=0)&&c(a).is(":focusable")}})}})(jQuery); +;/* + * jQuery UI Datepicker 1.8.2 + * + * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Datepicker + * + * Depends: + * jquery.ui.core.js + */ +(function(d){function J(){this.debug=false;this._curInst=null;this._keyEvent=false;this._disabledInputs=[];this._inDialog=this._datepickerShowing=false;this._mainDivId="ui-datepicker-div";this._inlineClass="ui-datepicker-inline";this._appendClass="ui-datepicker-append";this._triggerClass="ui-datepicker-trigger";this._dialogClass="ui-datepicker-dialog";this._disableClass="ui-datepicker-disabled";this._unselectableClass="ui-datepicker-unselectable";this._currentClass="ui-datepicker-current-day";this._dayOverClass= +"ui-datepicker-days-cell-over";this.regional=[];this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su", +"Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:false,showMonthAfterYear:false,yearSuffix:""};this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:false,hideIfNoPrevNext:false,navigationAsDateFormat:false,gotoCurrent:false,changeMonth:false,changeYear:false,yearRange:"c-10:c+10",showOtherMonths:false,selectOtherMonths:false,showWeek:false,calculateWeek:this.iso8601Week,shortYearCutoff:"+10", +minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:true,showButtonPanel:false,autoSize:false};d.extend(this._defaults,this.regional[""]);this.dpDiv=d('
')}function E(a,b){d.extend(a, +b);for(var c in b)if(b[c]==null||b[c]==undefined)a[c]=b[c];return a}d.extend(d.ui,{datepicker:{version:"1.8.2"}});var y=(new Date).getTime();d.extend(J.prototype,{markerClassName:"hasDatepicker",log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){E(this._defaults,a||{});return this},_attachDatepicker:function(a,b){var c=null;for(var e in this._defaults){var f=a.getAttribute("date:"+e);if(f){c=c||{};try{c[e]=eval(f)}catch(h){c[e]= +f}}}e=a.nodeName.toLowerCase();f=e=="div"||e=="span";if(!a.id){this.uuid+=1;a.id="dp"+this.uuid}var i=this._newInst(d(a),f);i.settings=d.extend({},b||{},c||{});if(e=="input")this._connectDatepicker(a,i);else f&&this._inlineDatepicker(a,i)},_newInst:function(a,b){return{id:a[0].id.replace(/([^A-Za-z0-9_])/g,"\\\\$1"),input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:!b?this.dpDiv:d('
')}}, +_connectDatepicker:function(a,b){var c=d(a);b.append=d([]);b.trigger=d([]);if(!c.hasClass(this.markerClassName)){this._attachments(c,b);c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});this._autoSize(b);d.data(a,"datepicker",b)}},_attachments:function(a,b){var c=this._get(b,"appendText"),e=this._get(b,"isRTL");b.append&& +b.append.remove();if(c){b.append=d(''+c+"");a[e?"before":"after"](b.append)}a.unbind("focus",this._showDatepicker);b.trigger&&b.trigger.remove();c=this._get(b,"showOn");if(c=="focus"||c=="both")a.focus(this._showDatepicker);if(c=="button"||c=="both"){c=this._get(b,"buttonText");var f=this._get(b,"buttonImage");b.trigger=d(this._get(b,"buttonImageOnly")?d("").addClass(this._triggerClass).attr({src:f,alt:c,title:c}):d('').addClass(this._triggerClass).html(f== +""?c:d("").attr({src:f,alt:c,title:c})));a[e?"before":"after"](b.trigger);b.trigger.click(function(){d.datepicker._datepickerShowing&&d.datepicker._lastInput==a[0]?d.datepicker._hideDatepicker():d.datepicker._showDatepicker(a[0]);return false})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var e=function(f){for(var h=0,i=0,g=0;gh){h=f[g].length;i=g}return i};b.setMonth(e(this._get(a, +c.match(/MM/)?"monthNames":"monthNamesShort")));b.setDate(e(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=d(a);if(!c.hasClass(this.markerClassName)){c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});d.data(a,"datepicker",b);this._setDate(b,this._getDefaultDate(b), +true);this._updateDatepicker(b);this._updateAlternate(b)}},_dialogDatepicker:function(a,b,c,e,f){a=this._dialogInst;if(!a){this.uuid+=1;this._dialogInput=d('');this._dialogInput.keydown(this._doKeyDown);d("body").append(this._dialogInput);a=this._dialogInst=this._newInst(this._dialogInput,false);a.settings={};d.data(this._dialogInput[0],"datepicker",a)}E(a.settings,e||{});b=b&&b.constructor== +Date?this._formatDate(a,b):b;this._dialogInput.val(b);this._pos=f?f.length?f:[f.pageX,f.pageY]:null;if(!this._pos)this._pos=[document.documentElement.clientWidth/2-100+(document.documentElement.scrollLeft||document.body.scrollLeft),document.documentElement.clientHeight/2-150+(document.documentElement.scrollTop||document.body.scrollTop)];this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px");a.settings.onSelect=c;this._inDialog=true;this.dpDiv.addClass(this._dialogClass);this._showDatepicker(this._dialogInput[0]); +d.blockUI&&d.blockUI(this.dpDiv);d.data(this._dialogInput[0],"datepicker",a);return this},_destroyDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();d.removeData(a,"datepicker");if(e=="input"){c.append.remove();c.trigger.remove();b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)}else if(e=="div"||e=="span")b.removeClass(this.markerClassName).empty()}}, +_enableDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if(e=="input"){a.disabled=false;c.trigger.filter("button").each(function(){this.disabled=false}).end().filter("img").css({opacity:"1.0",cursor:""})}else if(e=="div"||e=="span")b.children("."+this._inlineClass).children().removeClass("ui-state-disabled");this._disabledInputs=d.map(this._disabledInputs,function(f){return f==a?null:f})}},_disableDatepicker:function(a){var b= +d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if(e=="input"){a.disabled=true;c.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5",cursor:"default"})}else if(e=="div"||e=="span")b.children("."+this._inlineClass).children().addClass("ui-state-disabled");this._disabledInputs=d.map(this._disabledInputs,function(f){return f==a?null:f});this._disabledInputs[this._disabledInputs.length]=a}},_isDisabledDatepicker:function(a){if(!a)return false; +for(var b=0;b-1}},_doKeyUp:function(a){a=d.datepicker._getInst(a.target);if(a.input.val()!=a.lastVal)try{if(d.datepicker.parseDate(d.datepicker._get(a,"dateFormat"),a.input?a.input.val():null,d.datepicker._getFormatConfig(a))){d.datepicker._setDateFromField(a);d.datepicker._updateAlternate(a);d.datepicker._updateDatepicker(a)}}catch(b){d.datepicker.log(b)}return true},_showDatepicker:function(a){a=a.target|| +a;if(a.nodeName.toLowerCase()!="input")a=d("input",a.parentNode)[0];if(!(d.datepicker._isDisabledDatepicker(a)||d.datepicker._lastInput==a)){var b=d.datepicker._getInst(a);d.datepicker._curInst&&d.datepicker._curInst!=b&&d.datepicker._curInst.dpDiv.stop(true,true);var c=d.datepicker._get(b,"beforeShow");E(b.settings,c?c.apply(a,[a,b]):{});b.lastVal=null;d.datepicker._lastInput=a;d.datepicker._setDateFromField(b);if(d.datepicker._inDialog)a.value="";if(!d.datepicker._pos){d.datepicker._pos=d.datepicker._findPos(a); +d.datepicker._pos[1]+=a.offsetHeight}var e=false;d(a).parents().each(function(){e|=d(this).css("position")=="fixed";return!e});if(e&&d.browser.opera){d.datepicker._pos[0]-=document.documentElement.scrollLeft;d.datepicker._pos[1]-=document.documentElement.scrollTop}c={left:d.datepicker._pos[0],top:d.datepicker._pos[1]};d.datepicker._pos=null;b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});d.datepicker._updateDatepicker(b);c=d.datepicker._checkOffset(b,c,e);b.dpDiv.css({position:d.datepicker._inDialog&& +d.blockUI?"static":e?"fixed":"absolute",display:"none",left:c.left+"px",top:c.top+"px"});if(!b.inline){c=d.datepicker._get(b,"showAnim");var f=d.datepicker._get(b,"duration"),h=function(){d.datepicker._datepickerShowing=true;var i=d.datepicker._getBorders(b.dpDiv);b.dpDiv.find("iframe.ui-datepicker-cover").css({left:-i[0],top:-i[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})};b.dpDiv.zIndex(d(a).zIndex()+1);d.effects&&d.effects[c]?b.dpDiv.show(c,d.datepicker._get(b,"showOptions"),f, +h):b.dpDiv[c||"show"](c?f:null,h);if(!c||!f)h();b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus();d.datepicker._curInst=b}}},_updateDatepicker:function(a){var b=this,c=d.datepicker._getBorders(a.dpDiv);a.dpDiv.empty().append(this._generateHTML(a)).find("iframe.ui-datepicker-cover").css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()}).end().find("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a").bind("mouseout",function(){d(this).removeClass("ui-state-hover"); +this.className.indexOf("ui-datepicker-prev")!=-1&&d(this).removeClass("ui-datepicker-prev-hover");this.className.indexOf("ui-datepicker-next")!=-1&&d(this).removeClass("ui-datepicker-next-hover")}).bind("mouseover",function(){if(!b._isDisabledDatepicker(a.inline?a.dpDiv.parent()[0]:a.input[0])){d(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");d(this).addClass("ui-state-hover");this.className.indexOf("ui-datepicker-prev")!=-1&&d(this).addClass("ui-datepicker-prev-hover"); +this.className.indexOf("ui-datepicker-next")!=-1&&d(this).addClass("ui-datepicker-next-hover")}}).end().find("."+this._dayOverClass+" a").trigger("mouseover").end();c=this._getNumberOfMonths(a);var e=c[1];e>1?a.dpDiv.addClass("ui-datepicker-multi-"+e).css("width",17*e+"em"):a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");a.dpDiv[(c[0]!=1||c[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi");a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"); +a==d.datepicker._curInst&&d.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input.focus()},_getBorders:function(a){var b=function(c){return{thin:1,medium:2,thick:3}[c]||c};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var e=a.dpDiv.outerWidth(),f=a.dpDiv.outerHeight(),h=a.input?a.input.outerWidth():0,i=a.input?a.input.outerHeight():0,g=document.documentElement.clientWidth+d(document).scrollLeft(), +k=document.documentElement.clientHeight+d(document).scrollTop();b.left-=this._get(a,"isRTL")?e-h:0;b.left-=c&&b.left==a.input.offset().left?d(document).scrollLeft():0;b.top-=c&&b.top==a.input.offset().top+i?d(document).scrollTop():0;b.left-=Math.min(b.left,b.left+e>g&&g>e?Math.abs(b.left+e-g):0);b.top-=Math.min(b.top,b.top+f>k&&k>f?Math.abs(f+i):0);return b},_findPos:function(a){for(var b=this._get(this._getInst(a),"isRTL");a&&(a.type=="hidden"||a.nodeType!=1);)a=a[b?"previousSibling":"nextSibling"]; +a=d(a).offset();return[a.left,a.top]},_hideDatepicker:function(a){var b=this._curInst;if(!(!b||a&&b!=d.data(a,"datepicker")))if(this._datepickerShowing){a=this._get(b,"showAnim");var c=this._get(b,"duration"),e=function(){d.datepicker._tidyDialog(b);this._curInst=null};d.effects&&d.effects[a]?b.dpDiv.hide(a,d.datepicker._get(b,"showOptions"),c,e):b.dpDiv[a=="slideDown"?"slideUp":a=="fadeIn"?"fadeOut":"hide"](a?c:null,e);a||e();if(a=this._get(b,"onClose"))a.apply(b.input?b.input[0]:null,[b.input?b.input.val(): +"",b]);this._datepickerShowing=false;this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute",left:"0",top:"-100px"});if(d.blockUI){d.unblockUI();d("body").append(this.dpDiv)}}this._inDialog=false}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(a){if(d.datepicker._curInst){a=d(a.target);a[0].id!=d.datepicker._mainDivId&&a.parents("#"+d.datepicker._mainDivId).length==0&&!a.hasClass(d.datepicker.markerClassName)&& +!a.hasClass(d.datepicker._triggerClass)&&d.datepicker._datepickerShowing&&!(d.datepicker._inDialog&&d.blockUI)&&d.datepicker._hideDatepicker()}},_adjustDate:function(a,b,c){a=d(a);var e=this._getInst(a[0]);if(!this._isDisabledDatepicker(a[0])){this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"):0),c);this._updateDatepicker(e)}},_gotoToday:function(a){a=d(a);var b=this._getInst(a[0]);if(this._get(b,"gotoCurrent")&&b.currentDay){b.selectedDay=b.currentDay;b.drawMonth=b.selectedMonth=b.currentMonth; +b.drawYear=b.selectedYear=b.currentYear}else{var c=new Date;b.selectedDay=c.getDate();b.drawMonth=b.selectedMonth=c.getMonth();b.drawYear=b.selectedYear=c.getFullYear()}this._notifyChange(b);this._adjustDate(a)},_selectMonthYear:function(a,b,c){a=d(a);var e=this._getInst(a[0]);e._selectingMonthYear=false;e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10);this._notifyChange(e);this._adjustDate(a)},_clickMonthYear:function(a){a=this._getInst(d(a)[0]); +a.input&&a._selectingMonthYear&&!d.browser.msie&&a.input.focus();a._selectingMonthYear=!a._selectingMonthYear},_selectDay:function(a,b,c,e){var f=d(a);if(!(d(e).hasClass(this._unselectableClass)||this._isDisabledDatepicker(f[0]))){f=this._getInst(f[0]);f.selectedDay=f.currentDay=d("a",e).html();f.selectedMonth=f.currentMonth=b;f.selectedYear=f.currentYear=c;this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))}},_clearDate:function(a){a=d(a);this._getInst(a[0]);this._selectDate(a, +"")},_selectDate:function(a,b){a=this._getInst(d(a)[0]);b=b!=null?b:this._formatDate(a);a.input&&a.input.val(b);this._updateAlternate(a);var c=this._get(a,"onSelect");if(c)c.apply(a.input?a.input[0]:null,[b,a]);else a.input&&a.input.trigger("change");if(a.inline)this._updateDatepicker(a);else{this._hideDatepicker();this._lastInput=a.input[0];typeof a.input[0]!="object"&&a.input.focus();this._lastInput=null}},_updateAlternate:function(a){var b=this._get(a,"altField");if(b){var c=this._get(a,"altFormat")|| +this._get(a,"dateFormat"),e=this._getDate(a),f=this.formatDate(c,e,this._getFormatConfig(a));d(b).each(function(){d(this).val(f)})}},noWeekends:function(a){a=a.getDay();return[a>0&&a<6,""]},iso8601Week:function(a){a=new Date(a.getTime());a.setDate(a.getDate()+4-(a.getDay()||7));var b=a.getTime();a.setMonth(0);a.setDate(1);return Math.floor(Math.round((b-a)/864E5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"?b.toString():b+"";if(b=="")return null; +for(var e=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff,f=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,h=(c?c.dayNames:null)||this._defaults.dayNames,i=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,k=c=-1,l=-1,u=-1,j=false,o=function(p){(p=z+1-1){k=1;l=u;do{e=this._getDaysInMonth(c,k-1);if(l<=e)break;k++;l-=e}while(1)}v=this._daylightSavingAdjust(new Date(c, +k-1,l));if(v.getFullYear()!=c||v.getMonth()+1!=k||v.getDate()!=l)throw"Invalid date";return v},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1E7,formatDate:function(a,b,c){if(!b)return"";var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c? +c.dayNames:null)||this._defaults.dayNames,h=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort;c=(c?c.monthNames:null)||this._defaults.monthNames;var i=function(o){(o=j+112?a.getHours()+2:0);return a},_setDate:function(a,b,c){var e=!b,f=a.selectedMonth,h=a.selectedYear;b=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=a.currentDay=b.getDate();a.drawMonth=a.selectedMonth=a.currentMonth=b.getMonth();a.drawYear=a.selectedYear=a.currentYear=b.getFullYear();if((f!=a.selectedMonth||h!=a.selectedYear)&&!c)this._notifyChange(a);this._adjustInstDate(a);if(a.input)a.input.val(e?"":this._formatDate(a))},_getDate:function(a){return!a.currentYear|| +a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay))},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),e=this._get(a,"showButtonPanel"),f=this._get(a,"hideIfNoPrevNext"),h=this._get(a,"navigationAsDateFormat"),i=this._getNumberOfMonths(a),g=this._get(a,"showCurrentAtPos"),k=this._get(a,"stepMonths"),l=i[0]!=1||i[1]!=1,u=this._daylightSavingAdjust(!a.currentDay? +new Date(9999,9,9):new Date(a.currentYear,a.currentMonth,a.currentDay)),j=this._getMinMaxDate(a,"min"),o=this._getMinMaxDate(a,"max");g=a.drawMonth-g;var m=a.drawYear;if(g<0){g+=12;m--}if(o){var n=this._daylightSavingAdjust(new Date(o.getFullYear(),o.getMonth()-i[0]*i[1]+1,o.getDate()));for(n=j&&nn;){g--;if(g<0){g=11;m--}}}a.drawMonth=g;a.drawYear=m;n=this._get(a,"prevText");n=!h?n:this.formatDate(n,this._daylightSavingAdjust(new Date(m,g-k,1)),this._getFormatConfig(a)); +n=this._canAdjustMonth(a,-1,m,g)?''+n+"":f?"":''+n+"";var r=this._get(a,"nextText");r=!h?r:this.formatDate(r,this._daylightSavingAdjust(new Date(m, +g+k,1)),this._getFormatConfig(a));f=this._canAdjustMonth(a,+1,m,g)?''+r+"":f?"":''+r+"";k=this._get(a,"currentText");r=this._get(a,"gotoCurrent")&& +a.currentDay?u:b;k=!h?k:this.formatDate(k,r,this._getFormatConfig(a));h=!a.inline?'":"";e=e?'
'+(c?h:"")+(this._isInRange(a,r)?'":"")+(c?"":h)+"
":"";h=parseInt(this._get(a,"firstDay"),10);h=isNaN(h)?0:h;k=this._get(a,"showWeek");r=this._get(a,"dayNames");this._get(a,"dayNamesShort");var s=this._get(a,"dayNamesMin"),z=this._get(a,"monthNames"),v=this._get(a,"monthNamesShort"),p=this._get(a,"beforeShowDay"),w=this._get(a,"showOtherMonths"),G=this._get(a,"selectOtherMonths");this._get(a,"calculateWeek");for(var K=this._getDefaultDate(a),H="",C=0;C1)switch(D){case 0:x+=" ui-datepicker-group-first";t=" ui-corner-"+(c?"right":"left");break;case i[1]-1:x+=" ui-datepicker-group-last";t=" ui-corner-"+(c?"left":"right");break;default:x+=" ui-datepicker-group-middle";t="";break}x+='">'}x+='
'+(/all|left/.test(t)&&C==0?c? +f:n:"")+(/all|right/.test(t)&&C==0?c?n:f:"")+this._generateMonthYearHeader(a,g,m,j,o,C>0||D>0,z,v)+'
';var A=k?'":"";for(t=0;t<7;t++){var q=(t+h)%7;A+="=5?' class="ui-datepicker-week-end"':"")+'>'+s[q]+""}x+=A+"";A=this._getDaysInMonth(m,g);if(m==a.selectedYear&&g==a.selectedMonth)a.selectedDay=Math.min(a.selectedDay, +A);t=(this._getFirstDayOfMonth(m,g)-h+7)%7;A=l?6:Math.ceil((t+A)/7);q=this._daylightSavingAdjust(new Date(m,g,1-t));for(var N=0;N";var O=!k?"":'";for(t=0;t<7;t++){var F=p?p.apply(a.input?a.input[0]:null,[q]):[true,""],B=q.getMonth()!=g,I=B&&!G||!F[0]||j&&qo;O+='";q.setDate(q.getDate()+1);q=this._daylightSavingAdjust(q)}x+=O+""}g++;if(g>11){g=0;m++}x+="
'+this._get(a,"weekHeader")+"
'+this._get(a,"calculateWeek")(q)+""+(B&&!w?" ":I?''+q.getDate()+ +"":''+q.getDate()+"")+"
"+(l?"
"+(i[0]>0&&D==i[1]-1?'
':""):"");L+=x}H+=L}H+=e+(d.browser.msie&&parseInt(d.browser.version,10)<7&&!a.inline?'': +"");a._keyEvent=false;return H},_generateMonthYearHeader:function(a,b,c,e,f,h,i,g){var k=this._get(a,"changeMonth"),l=this._get(a,"changeYear"),u=this._get(a,"showMonthAfterYear"),j='
',o="";if(h||!k)o+=''+i[b]+"";else{i=e&&e.getFullYear()==c;var m=f&&f.getFullYear()==c;o+='"}u||(j+=o+(h||!(k&&l)?" ":""));if(h||!l)j+=''+c+"";else{g=this._get(a,"yearRange").split(":");var r=(new Date).getFullYear();i=function(s){s=s.match(/c[+-].*/)?c+parseInt(s.substring(1),10):s.match(/[+-].*/)?r+parseInt(s,10):parseInt(s,10);return isNaN(s)?r:s};b=i(g[0]);g=Math.max(b, +i(g[1]||""));b=e?Math.max(b,e.getFullYear()):b;g=f?Math.min(g,f.getFullYear()):g;for(j+='"}j+=this._get(a,"yearSuffix");if(u)j+=(h||!(k&&l)?" ":"")+o;j+="
";return j},_adjustInstDate:function(a,b,c){var e= +a.drawYear+(c=="Y"?b:0),f=a.drawMonth+(c=="M"?b:0);b=Math.min(a.selectedDay,this._getDaysInMonth(e,f))+(c=="D"?b:0);e=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(e,f,b)));a.selectedDay=e.getDate();a.drawMonth=a.selectedMonth=e.getMonth();a.drawYear=a.selectedYear=e.getFullYear();if(c=="M"||c=="Y")this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");b=c&&ba?a:b},_notifyChange:function(a){var b=this._get(a, +"onChangeMonthYear");if(b)b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){a=this._get(a,"numberOfMonths");return a==null?[1,1]:typeof a=="number"?[1,a]:a},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,e){var f=this._getNumberOfMonths(a); +c=this._daylightSavingAdjust(new Date(c,e+(b<0?b:f[0]*f[1]),1));b<0&&c.setDate(this._getDaysInMonth(c.getFullYear(),c.getMonth()));return this._isInRange(a,c)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!a||b.getTime()<=a.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10);return{shortYearCutoff:b,dayNamesShort:this._get(a, +"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,e){if(!b){a.currentDay=a.selectedDay;a.currentMonth=a.selectedMonth;a.currentYear=a.selectedYear}b=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(e,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),b,this._getFormatConfig(a))}});d.fn.datepicker= +function(a){if(!d.datepicker.initialized){d(document).mousedown(d.datepicker._checkExternalClick).find("body").append(d.datepicker.dpDiv);d.datepicker.initialized=true}var b=Array.prototype.slice.call(arguments,1);if(typeof a=="string"&&(a=="isDisabled"||a=="getDate"||a=="widget"))return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));if(a=="option"&&arguments.length==2&&typeof arguments[1]=="string")return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b)); +return this.each(function(){typeof a=="string"?d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this].concat(b)):d.datepicker._attachDatepicker(this,a)})};d.datepicker=new J;d.datepicker.initialized=false;d.datepicker.uuid=(new Date).getTime();d.datepicker.version="1.8.2";window["DP_jQuery_"+y]=d})(jQuery); +; diff --git a/app/javascripts/administration.js b/app/javascripts/administration.js new file mode 100644 index 000000000..3c59cd654 --- /dev/null +++ b/app/javascripts/administration.js @@ -0,0 +1,4 @@ +//= require jquery +//= require jquery-ui +//= require jquery/jquery.polygon-input.js +//= require maps \ No newline at end of file diff --git a/app/javascripts/app.js b/app/javascripts/app.js new file mode 100644 index 000000000..9a60f43d6 --- /dev/null +++ b/app/javascripts/app.js @@ -0,0 +1,76 @@ +var CommonPlace = CommonPlace || {}; + +function _ajax_request(url, data, callback, type, method) { + if (jQuery.isFunction(data)) { + callback = data; + data = {}; + } + return jQuery.ajax({ + type: method, + url: url, + data: data, + success: callback, + dataType: type + }); +} + +jQuery.extend({ + put: function(url, data, callback, type) { + return _ajax_request(url, data, callback, type, 'PUT'); + }, + del: function(url, data, callback, type) { + return _ajax_request(url, data, callback, type, 'DELETE'); + } +}); + +Mustache.template = function(templateString) { + return templateString; +}; + +CommonPlace.say_something_blocked = false; +CommonPlace.timeAgoInWords = function(date_str) { + var time = CommonPlace.parseDate(date_str); + var diff_in_seconds = (time - (new Date)) / 1000; + var diff_in_minutes = Math.abs(Math.floor((diff_in_seconds / 60))); + var add_token = function (in_words) { return diff_in_seconds > 0 ? "in " + in_words : in_words + " ago"; }; + if (diff_in_minutes === 0) { return add_token('less than a minute'); } + if (diff_in_minutes == 1) { return add_token('a minute'); } + if (diff_in_minutes < 45) { return add_token(diff_in_minutes + ' minutes'); } + if (diff_in_minutes < 90) { return add_token('about 1 hour'); } + if (diff_in_minutes < 1440) { return add_token('about ' + Math.floor(diff_in_minutes / 60) + ' hours'); } + if (diff_in_minutes < 2880) { return add_token('1 day'); } + if (diff_in_minutes < 43200) { return add_token(Math.floor(diff_in_minutes / 1440) + ' days'); } + if (diff_in_minutes < 86400) { return add_token('about 1 month'); } + if (diff_in_minutes < 525960) { return add_token(Math.floor(diff_in_minutes / 43200) + ' months'); } + if (diff_in_minutes < 1051199) { return add_token('about 1 year'); } + + return add_token('over ' + Math.floor(diff_in_minutes / 525960) + ' years'); +}; + +CommonPlace.parseDate = function(date_str) { + var m = date_str.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z/); + return Date.UTC(m[1],m[2] - 1,m[3],m[4],m[5],m[6]); +}; + + +$(function() { + $.preLoadImages("/assets/loading.gif"); + + //todo: this code block is duplicated in lib.js + $('form.formtastic.feed input:text, form.formtastic.feed textarea').keydown(function(e) { + var $input = $(e.currentTarget); + setTimeout(function() { + $("#preview").find("[data-track='" + $input.attr('name') + "']").html($input.val()); + }, 10); + }); + + + // Feed Profile + $('#post-to-feed h2 nav li:last-child').hide(); + $('#post-to-feed h2 nav ul').hover(function(){ + $('#post-to-feed h2 nav li:last-child').show(); + }, function(){ + $('#post-to-feed h2 nav li:last-child').hide(); + }); + +}); diff --git a/app/javascripts/application.js b/app/javascripts/application.js new file mode 100644 index 000000000..8d2c5bea6 --- /dev/null +++ b/app/javascripts/application.js @@ -0,0 +1,15 @@ +//= require showdown +//= require jquery-1.6.1 +//= require jquery-ui +//= require jquery/underscore +//= require jquery/mustache +//= require jquery/json2 +//= require jquery/backbone +//= require_tree ./jquery/ +//= require facebook +//= require chosen + +$(function() { + // todo: explain what this code does and put it somewhere relevent. + $("#user_interest_list, #user_good_list, #user_skill_list").chosen(); +}); \ No newline at end of file diff --git a/app/javascripts/config.js b/app/javascripts/config.js new file mode 100644 index 000000000..00b65855e --- /dev/null +++ b/app/javascripts/config.js @@ -0,0 +1,5 @@ +if (!window.CommonPlace) { window.CommonPlace = {}; } + +_.extend(CommonPlace, { + autoActionTimeout: 500 +}); diff --git a/app/javascripts/date.js b/app/javascripts/date.js new file mode 100644 index 000000000..77f498645 --- /dev/null +++ b/app/javascripts/date.js @@ -0,0 +1,104 @@ +/** + * Version: 1.0 Alpha-1 + * Build Date: 13-Nov-2007 + * Copyright (c) 2006-2007, Coolite Inc. (http://www.coolite.com/). All rights reserved. + * License: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/. + * Website: http://www.datejs.com/ or http://www.coolite.com/datejs/ + */ +Date.CultureInfo={name:"en-US",englishName:"English (United States)",nativeName:"English (United States)",dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbreviatedDayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],shortestDayNames:["Su","Mo","Tu","We","Th","Fr","Sa"],firstLetterDayNames:["S","M","T","W","T","F","S"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],abbreviatedMonthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],amDesignator:"AM",pmDesignator:"PM",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"mdy",formatPatterns:{shortDate:"M/d/yyyy",longDate:"dddd, MMMM dd, yyyy",shortTime:"h:mm tt",longTime:"h:mm:ss tt",fullDateTime:"dddd, MMMM dd, yyyy h:mm:ss tt",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"MMMM dd",yearMonth:"MMMM, yyyy"},regexPatterns:{jan:/^jan(uary)?/i,feb:/^feb(ruary)?/i,mar:/^mar(ch)?/i,apr:/^apr(il)?/i,may:/^may/i,jun:/^jun(e)?/i,jul:/^jul(y)?/i,aug:/^aug(ust)?/i,sep:/^sep(t(ember)?)?/i,oct:/^oct(ober)?/i,nov:/^nov(ember)?/i,dec:/^dec(ember)?/i,sun:/^su(n(day)?)?/i,mon:/^mo(n(day)?)?/i,tue:/^tu(e(s(day)?)?)?/i,wed:/^we(d(nesday)?)?/i,thu:/^th(u(r(s(day)?)?)?)?/i,fri:/^fr(i(day)?)?/i,sat:/^sa(t(urday)?)?/i,future:/^next/i,past:/^last|past|prev(ious)?/i,add:/^(\+|after|from)/i,subtract:/^(\-|before|ago)/i,yesterday:/^yesterday/i,today:/^t(oday)?/i,tomorrow:/^tomorrow/i,now:/^n(ow)?/i,millisecond:/^ms|milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^min(ute)?s?/i,hour:/^h(ou)?rs?/i,week:/^w(ee)?k/i,month:/^m(o(nth)?s?)?/i,day:/^d(ays?)?/i,year:/^y((ea)?rs?)?/i,shortMeridian:/^(a|p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a|p)/i},abbreviatedTimeZoneStandard:{GMT:"-000",EST:"-0400",CST:"-0500",MST:"-0600",PST:"-0700"},abbreviatedTimeZoneDST:{GMT:"-000",EDT:"-0500",CDT:"-0600",MDT:"-0700",PDT:"-0800"}}; +Date.getMonthNumberFromName=function(name){var n=Date.CultureInfo.monthNames,m=Date.CultureInfo.abbreviatedMonthNames,s=name.toLowerCase();for(var i=0;idate)?1:(this=start.getTime()&&t<=end.getTime();};Date.prototype.addMilliseconds=function(value){this.setMilliseconds(this.getMilliseconds()+value);return this;};Date.prototype.addSeconds=function(value){return this.addMilliseconds(value*1000);};Date.prototype.addMinutes=function(value){return this.addMilliseconds(value*60000);};Date.prototype.addHours=function(value){return this.addMilliseconds(value*3600000);};Date.prototype.addDays=function(value){return this.addMilliseconds(value*86400000);};Date.prototype.addWeeks=function(value){return this.addMilliseconds(value*604800000);};Date.prototype.addMonths=function(value){var n=this.getDate();this.setDate(1);this.setMonth(this.getMonth()+value);this.setDate(Math.min(n,this.getDaysInMonth()));return this;};Date.prototype.addYears=function(value){return this.addMonths(value*12);};Date.prototype.add=function(config){if(typeof config=="number"){this._orient=config;return this;} +var x=config;if(x.millisecond||x.milliseconds){this.addMilliseconds(x.millisecond||x.milliseconds);} +if(x.second||x.seconds){this.addSeconds(x.second||x.seconds);} +if(x.minute||x.minutes){this.addMinutes(x.minute||x.minutes);} +if(x.hour||x.hours){this.addHours(x.hour||x.hours);} +if(x.month||x.months){this.addMonths(x.month||x.months);} +if(x.year||x.years){this.addYears(x.year||x.years);} +if(x.day||x.days){this.addDays(x.day||x.days);} +return this;};Date._validate=function(value,min,max,name){if(typeof value!="number"){throw new TypeError(value+" is not a Number.");}else if(valuemax){throw new RangeError(value+" is not a valid value for "+name+".");} +return true;};Date.validateMillisecond=function(n){return Date._validate(n,0,999,"milliseconds");};Date.validateSecond=function(n){return Date._validate(n,0,59,"seconds");};Date.validateMinute=function(n){return Date._validate(n,0,59,"minutes");};Date.validateHour=function(n){return Date._validate(n,0,23,"hours");};Date.validateDay=function(n,year,month){return Date._validate(n,1,Date.getDaysInMonth(year,month),"days");};Date.validateMonth=function(n){return Date._validate(n,0,11,"months");};Date.validateYear=function(n){return Date._validate(n,1,9999,"seconds");};Date.prototype.set=function(config){var x=config;if(!x.millisecond&&x.millisecond!==0){x.millisecond=-1;} +if(!x.second&&x.second!==0){x.second=-1;} +if(!x.minute&&x.minute!==0){x.minute=-1;} +if(!x.hour&&x.hour!==0){x.hour=-1;} +if(!x.day&&x.day!==0){x.day=-1;} +if(!x.month&&x.month!==0){x.month=-1;} +if(!x.year&&x.year!==0){x.year=-1;} +if(x.millisecond!=-1&&Date.validateMillisecond(x.millisecond)){this.addMilliseconds(x.millisecond-this.getMilliseconds());} +if(x.second!=-1&&Date.validateSecond(x.second)){this.addSeconds(x.second-this.getSeconds());} +if(x.minute!=-1&&Date.validateMinute(x.minute)){this.addMinutes(x.minute-this.getMinutes());} +if(x.hour!=-1&&Date.validateHour(x.hour)){this.addHours(x.hour-this.getHours());} +if(x.month!==-1&&Date.validateMonth(x.month)){this.addMonths(x.month-this.getMonth());} +if(x.year!=-1&&Date.validateYear(x.year)){this.addYears(x.year-this.getFullYear());} +if(x.day!=-1&&Date.validateDay(x.day,this.getFullYear(),this.getMonth())){this.addDays(x.day-this.getDate());} +if(x.timezone){this.setTimezone(x.timezone);} +if(x.timezoneOffset){this.setTimezoneOffset(x.timezoneOffset);} +return this;};Date.prototype.clearTime=function(){this.setHours(0);this.setMinutes(0);this.setSeconds(0);this.setMilliseconds(0);return this;};Date.prototype.isLeapYear=function(){var y=this.getFullYear();return(((y%4===0)&&(y%100!==0))||(y%400===0));};Date.prototype.isWeekday=function(){return!(this.is().sat()||this.is().sun());};Date.prototype.getDaysInMonth=function(){return Date.getDaysInMonth(this.getFullYear(),this.getMonth());};Date.prototype.moveToFirstDayOfMonth=function(){return this.set({day:1});};Date.prototype.moveToLastDayOfMonth=function(){return this.set({day:this.getDaysInMonth()});};Date.prototype.moveToDayOfWeek=function(day,orient){var diff=(day-this.getDay()+7*(orient||+1))%7;return this.addDays((diff===0)?diff+=7*(orient||+1):diff);};Date.prototype.moveToMonth=function(month,orient){var diff=(month-this.getMonth()+12*(orient||+1))%12;return this.addMonths((diff===0)?diff+=12*(orient||+1):diff);};Date.prototype.getDayOfYear=function(){return Math.floor((this-new Date(this.getFullYear(),0,1))/86400000);};Date.prototype.getWeekOfYear=function(firstDayOfWeek){var y=this.getFullYear(),m=this.getMonth(),d=this.getDate();var dow=firstDayOfWeek||Date.CultureInfo.firstDayOfWeek;var offset=7+1-new Date(y,0,1).getDay();if(offset==8){offset=1;} +var daynum=((Date.UTC(y,m,d,0,0,0)-Date.UTC(y,0,1,0,0,0))/86400000)+1;var w=Math.floor((daynum-offset+7)/7);if(w===dow){y--;var prevOffset=7+1-new Date(y,0,1).getDay();if(prevOffset==2||prevOffset==8){w=53;}else{w=52;}} +return w;};Date.prototype.isDST=function(){console.log('isDST');return this.toString().match(/(E|C|M|P)(S|D)T/)[2]=="D";};Date.prototype.getTimezone=function(){return Date.getTimezoneAbbreviation(this.getUTCOffset,this.isDST());};Date.prototype.setTimezoneOffset=function(s){var here=this.getTimezoneOffset(),there=Number(s)*-6/10;this.addMinutes(there-here);return this;};Date.prototype.setTimezone=function(s){return this.setTimezoneOffset(Date.getTimezoneOffset(s));};Date.prototype.getUTCOffset=function(){var n=this.getTimezoneOffset()*-10/6,r;if(n<0){r=(n-10000).toString();return r[0]+r.substr(2);}else{r=(n+10000).toString();return"+"+r.substr(1);}};Date.prototype.getDayName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedDayNames[this.getDay()]:Date.CultureInfo.dayNames[this.getDay()];};Date.prototype.getMonthName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedMonthNames[this.getMonth()]:Date.CultureInfo.monthNames[this.getMonth()];};Date.prototype._toString=Date.prototype.toString;Date.prototype.toString=function(format){var self=this;var p=function p(s){return(s.toString().length==1)?"0"+s:s;};return format?format.replace(/dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?/g,function(format){switch(format){case"hh":return p(self.getHours()<13?self.getHours():(self.getHours()-12));case"h":return self.getHours()<13?self.getHours():(self.getHours()-12);case"HH":return p(self.getHours());case"H":return self.getHours();case"mm":return p(self.getMinutes());case"m":return self.getMinutes();case"ss":return p(self.getSeconds());case"s":return self.getSeconds();case"yyyy":return self.getFullYear();case"yy":return self.getFullYear().toString().substring(2,4);case"dddd":return self.getDayName();case"ddd":return self.getDayName(true);case"dd":return p(self.getDate());case"d":return self.getDate().toString();case"MMMM":return self.getMonthName();case"MMM":return self.getMonthName(true);case"MM":return p((self.getMonth()+1));case"M":return self.getMonth()+1;case"t":return self.getHours()<12?Date.CultureInfo.amDesignator.substring(0,1):Date.CultureInfo.pmDesignator.substring(0,1);case"tt":return self.getHours()<12?Date.CultureInfo.amDesignator:Date.CultureInfo.pmDesignator;case"zzz":case"zz":case"z":return"";}}):this._toString();}; +Date.now=function(){return new Date();};Date.today=function(){return Date.now().clearTime();};Date.prototype._orient=+1;Date.prototype.next=function(){this._orient=+1;return this;};Date.prototype.last=Date.prototype.prev=Date.prototype.previous=function(){this._orient=-1;return this;};Date.prototype._is=false;Date.prototype.is=function(){this._is=true;return this;};Number.prototype._dateElement="day";Number.prototype.fromNow=function(){var c={};c[this._dateElement]=this;return Date.now().add(c);};Number.prototype.ago=function(){var c={};c[this._dateElement]=this*-1;return Date.now().add(c);};(function(){var $D=Date.prototype,$N=Number.prototype;var dx=("sunday monday tuesday wednesday thursday friday saturday").split(/\s/),mx=("january february march april may june july august september october november december").split(/\s/),px=("Millisecond Second Minute Hour Day Week Month Year").split(/\s/),de;var df=function(n){return function(){if(this._is){this._is=false;return this.getDay()==n;} +return this.moveToDayOfWeek(n,this._orient);};};for(var i=0;i0&&!last){try{q=d.call(this,r[1]);}catch(ex){last=true;}}else{last=true;} +if(!last&&q[1].length===0){last=true;} +if(!last){var qx=[];for(var j=0;j0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}} +if(rx[1].length1){args=Array.prototype.slice.call(arguments);}else if(arguments[0]instanceof Array){args=arguments[0];} +if(args){for(var i=0,px=args.shift();i2)?n:(n+(((n+2000)Date.getDaysInMonth(this.year,this.month)){throw new RangeError(this.day+" is not a valid value for days.");} +var r=new Date(this.year,this.month,this.day,this.hour,this.minute,this.second);if(this.timezone){r.set({timezone:this.timezone});}else if(this.timezoneOffset){r.set({timezoneOffset:this.timezoneOffset});} +return r;},finish:function(x){x=(x instanceof Array)?flattenAndCompact(x):[x];if(x.length===0){return null;} +for(var i=0;i= top) { + sidebar.css({position: "fixed", left: main, top: "30px"}); + } + else{ + sidebar.css({position: "absolute", left: "", top: "30px"}); + } + } + else{ + sidebar.css({position: "absolute", top: "3637px", left:""}); + } + }); + $(window).scroll(function (event) { + var ypos = $(this).scrollTop(); + main = $('#main').offset().left; + main += $('#main').width(); + main -= sidebar.width(); + main += 80; + if(ypos <= 3772){ + if (ypos+40 >= top) { + sidebar.css({position: "fixed", left: main, top: "30px"}); + } + else{ + sidebar.css({position: "absolute", left: "", top: "30px"}); + } + } + else{ + sidebar.css({position: "absolute", top: "3637px", left:""}); + } + }); + + $.localScroll(); + + +}); \ No newline at end of file diff --git a/app/javascripts/feature_switches.js b/app/javascripts/feature_switches.js new file mode 100644 index 000000000..00562af96 --- /dev/null +++ b/app/javascripts/feature_switches.js @@ -0,0 +1,51 @@ + +FeatureSwitcher = function(features, backend) { + + this._features = _(features).keys(); + + this._backend = backend; + + this.features = function() { return this._features; }; + + this.activate = function(feature) { + this.set(feature, true); + }; + + this.deactivate = function(feature) { + this.set(feature, false); + }; + + this.toggle = function(feature) { + this.set(feature, + !this.isActive(feature)); + }; + + this.isActive = function(feature) { + return this.get(feature); + }; + + this.set = function(feature, bool) { + this._backend.setItem(this.keyFor(feature), JSON.stringify(bool)); + }; + + this.get = function(feature) { + return JSON.parse(this._backend.getItem(this.keyFor(feature))); + }; + + this.keyFor = function(feature) { return "features:" + feature; }; + + var self = this; + _(features).each(function(bool, feature) { + self.set(feature, bool || self.isActive(feature)); + }); + + return this; +}; + +Features = new FeatureSwitcher({ + wireSearch: false, + fixedLayout: false +}, window.sessionStorage || { + setItem: function(name, value) {}, + getItem: function(name) { return false; } +}); diff --git a/app/javascripts/feed_page.js b/app/javascripts/feed_page.js new file mode 100644 index 000000000..fc809928c --- /dev/null +++ b/app/javascripts/feed_page.js @@ -0,0 +1,20 @@ +//= require showdown +//= require jquery +//= require jquery-ui +//= require placeholder +//= require time_ago_in_words +//= require underscore +//= require mustache +//= require json2 +//= require backbone +//= require autoresize +//= require dropkick +//= require truncator +//= require views +//= require models +//= require_tree ../templates/shared +//= require_tree ../templates/feed_page +//= require wires +//= require wire_items +//= require feed_page/app +//= require_tree ./feed_page diff --git a/app/javascripts/feed_page/app.js b/app/javascripts/feed_page/app.js new file mode 100644 index 000000000..a6bcb94bc --- /dev/null +++ b/app/javascripts/feed_page/app.js @@ -0,0 +1,38 @@ + +var FeedPageRouter = Backbone.Router.extend({ + + routes: { + "/feeds/:slug": "show", + "/pages/:slug": "show" + }, + + initialize: function(options) { + var self = this; + this.account = options.account; + this.community = options.community; + this.feedsList = new FeedsListView({ collection: options.feeds, el: $("#feeds-list") }); + this.feedsList.render(); + this.show(options.feed); + }, + + show: function(slug) { + var self = this; + $.getJSON("/api" + this.community.links.groups, function(groups) { + self.feedsList.select(slug); + $.getJSON("/api/feeds/" + slug, function(response) { + var feed = new Feed(response); + + document.title = feed.get('name'); + + var feedView = new FeedView({ model: feed, community: self.community, account: self.account, groups: groups }); + window.currentFeedView = feedView; + feedView.render(); + + $("#feed").replaceWith(feedView.el); + }); + }); + } +}); + + + diff --git a/app/javascripts/feed_page/feed_about_view.js b/app/javascripts/feed_page/feed_about_view.js new file mode 100644 index 000000000..ef3f3c8db --- /dev/null +++ b/app/javascripts/feed_page/feed_about_view.js @@ -0,0 +1,8 @@ + + +var FeedAboutView = CommonPlace.View.extend({ + template: "feed_page/feed-about", + id: "feed-about", + about: function() { return this.model.get('about'); } +}); + diff --git a/app/javascripts/feed_page/feed_actions_view.js b/app/javascripts/feed_page/feed_actions_view.js new file mode 100644 index 000000000..c10bfb2a5 --- /dev/null +++ b/app/javascripts/feed_page/feed_actions_view.js @@ -0,0 +1,115 @@ + + +var FeedActionsView = CommonPlace.View.extend({ + id: "feed-actions", + template: "feed_page/feed-actions", + events: { + "click #feed-action-nav a": "navigate", + "click .post-announcement button": "postAnnouncement", + "click .post-event button": "postEvent", + "click .invite-subscribers form.invite-by-email button": "inviteByEmail", + "change .post-label-selector input": "toggleCheckboxLIClass" + }, + + initialize: function(options) { + this.feed = options.feed; + this.groups = options.groups; + this.account = options.account; + this.community = options.community; + this.postAnnouncementClass = "current"; + }, + + afterRender: function() { + this.$("input.date").datepicker({dateFormat: 'yy-mm-dd'}); + this.$('input[placeholder], textarea[placeholder]').placeholder(); + this.$("textarea").autoResize(); + this.$("select.time").dropkick(); + }, + + navigate: function(e) { + var $target = $(e.target); + $target.addClass("current").siblings().removeClass("current"); + $(this.el).children(".tab") + .removeClass("current") + .filter("." + $target.attr('href').split("#")[1].slice(1)) + .addClass("current"); + this.$(".error").hide(); + e.preventDefault(); + }, + + toggleCheckboxLIClass: function(e) { + $(e.target).closest("li").toggleClass("checked"); + }, + + showError: function(response) { + this.$(".error").text(response.responseText); + this.$(".error").show(); + }, + + postAnnouncement: function(e) { + var $form = this.$(".post-announcement form"); + var self = this; + this.cleanUpPlaceholders(); + e.preventDefault(); + this.feed.announcements.create( + { title: $("[name=title]", $form).val(), + body: $("[name=body]", $form).val(), + groups: $("[name=groups]:checked", $form).map(function() { return $(this).val(); }).toArray() + }, { + success: function() { self.render(); }, + error: function(attribs, response) { self.showError(response); } + }); + }, + + postEvent: function(e) { + var self = this; + var $form = this.$(".post-event form"); + e.preventDefault(); + this.cleanUpPlaceholders(); + this.feed.events.create( + { title: $("[name=title]", $form).val(), + about: $("[name=about]", $form).val(), + date: $("[name=date]", $form).val(), + start: $("[name=start]", $form).val(), + end: $("[name=end]", $form).val(), + venue: $("[name=venue]", $form).val(), + address: $("[name=address]", $form).val(), + tags: $("[name=tags]", $form).val(), + groups: $("[name=groups]:checked", $form).map(function() { return $(this).val(); }).toArray() + }, { + success: function() { self.render(); }, + error: function(attribs, response) { self.showError(response); } + }); + }, + + avatarUrl: function() { return this.feed.get('links').avatar.thumb; }, + + + time_values: _.flatten(_.map(["AM", "PM"], + function(half) { + return _.map([12,1,2,3,4,5,6,7,8,9,10,11], + function(hour) { + return _.map(["00", "30"], + function(minute) { + return String(hour) + ":" + minute + " " + half; + }); + }); + })), + + inviteByEmail: function(e) { + var self = this; + var $form = this.$(".invite-subscribers form"); + e.preventDefault(); + $.ajax({ + contentType: "application/json", + url: "/api" + this.feed.link('invites'), + data: JSON.stringify({ emails: _.map($("[name=emails]", $form).val().split(/,|;/), + function(s) { return s.replace(/\s*/,""); }), + message: $("[name=message]", $form).val() + }), + type: "post", + dataType: "json", + success: function() { self.render(); }}); + } + +}); diff --git a/app/javascripts/feed_page/feed_admin_bar.js b/app/javascripts/feed_page/feed_admin_bar.js new file mode 100644 index 000000000..59778b18b --- /dev/null +++ b/app/javascripts/feed_page/feed_admin_bar.js @@ -0,0 +1,14 @@ + +var FeedAdminBar = CommonPlace.View.extend({ + template: "feed_page/feed-admin-bar", + id: "feed-admin-bar", + + feeds: function() { + var self = this; + var feeds = _(this.options.account.get('feeds')).map(function(f) { + return {name: f.name, slug: f.slug, current: f.id == self.model.id}; + }); + return feeds; + } + +}); diff --git a/app/javascripts/feed_page/feed_header_view.js b/app/javascripts/feed_page/feed_header_view.js new file mode 100644 index 000000000..e637dcee7 --- /dev/null +++ b/app/javascripts/feed_page/feed_header_view.js @@ -0,0 +1,49 @@ + +var FeedHeaderView = CommonPlace.View.extend({ + template: "feed_page/feed-header", + id: "feed-header", + initialize: function(options) { + this.account = options.account; + }, + + events: { + "click a.subscribe": "subscribe", + "click a.unsubscribe": "unsubscribe", + "click .feed-edit": "openEditModal" + }, + + isSubscribed: function() { + return this.account.isSubscribedToFeed(this.model); + }, + + isOwner: function() { + return this.account.isFeedOwner(this.model); + }, + + editURL: function() { + return this.model.link("edit"); + }, + + subscribe: function(e) { + var self = this; + e.preventDefault(); + this.account.subscribeToFeed(this.model, function() { self.render(); }); + }, + + unsubscribe: function(e) { + var self = this; + e.preventDefault(); + this.account.unsubscribeFromFeed(this.model, function() { self.render(); }); + }, + + feedName: function() { return this.model.get('name'); }, + + openEditModal: function(e) { + e.preventDefault(); + var formview = new FeedEditFormView({ + model: this.model + }); + formview.render(); + } + +}); diff --git a/app/javascripts/feed_page/feed_nav_view.js b/app/javascripts/feed_page/feed_nav_view.js new file mode 100644 index 000000000..caf18add1 --- /dev/null +++ b/app/javascripts/feed_page/feed_nav_view.js @@ -0,0 +1,26 @@ + +var FeedNavView = CommonPlace.View.extend({ + template: "feed_page/feed-nav", + id: "feed-nav", + events: { + "click a": "navigate" + }, + + initialize: function(options) { + this.current = options.current || "showAnnouncements"; + }, + + navigate: function(e) { + e.preventDefault(); + this.current = $(e.target).attr('data-tab'); + this.trigger('switchTab', this.current); + this.render(); + }, + + classIfCurrent: function() { + var self = this; + return function(text) { + return this.current == text ? "current" : ""; + }; + } +}); \ No newline at end of file diff --git a/app/javascripts/feed_page/feed_profile_view.js b/app/javascripts/feed_page/feed_profile_view.js new file mode 100644 index 000000000..43c5f4d0c --- /dev/null +++ b/app/javascripts/feed_page/feed_profile_view.js @@ -0,0 +1,49 @@ + +var FeedProfileView = CommonPlace.View.extend({ + template: "feed_page/feed-profile", + id: "feed-profile", + initialize: function(options) { + this.account = options.account; + }, + + events: { + "click .send-message": "openMessageModal", + "click .feed-owners": "openPermissionsModal" + }, + + openMessageModal: function(e) { + e.preventDefault(); + var formview = new MessageFormView({ + model: new Message({messagable: this.model}) + }); + formview.render(); + }, + + openPermissionsModal: function(e) { + e.preventDefault(); + var formview = new FeedOwnersFormView({ + model: this.model + }); + formview.render(); + }, + + avatarSrc: function() { return this.model.get("links").avatar.large; }, + address: function() { return this.model.get("address"); }, + phone: function() { return this.model.get("phone"); }, + website: function() { + if (!this.model.get("website")) { return false; } + + if (this.model.get("website").length <= 35) { + return this.model.get("website"); + } else { + return this.model.get("website").substring(0,32) + "..."; + } + }, + + websiteURL: function() { return this.model.get("website"); }, + + isOwner: function() { + return this.account.isFeedOwner(this.model); + } + +}); diff --git a/app/javascripts/feed_page/feed_sub_resources_view.js b/app/javascripts/feed_page/feed_sub_resources_view.js new file mode 100644 index 000000000..a147bc363 --- /dev/null +++ b/app/javascripts/feed_page/feed_sub_resources_view.js @@ -0,0 +1,87 @@ + + +var FeedSubResourcesView = CommonPlace.View.extend({ + template: "feed_page/feed-subresources", + id: "feed-subresources", + initialize: function(options) { + this.account = options.account; + this.feed = options.feed; + this.announcementsCollection = this.feed.announcements; + this.eventsCollection = this.feed.events; + this.subscribersCollection = this.feed.subscribers; + this.currentTab = options.current || "showAnnouncements"; + this.feed.events.bind("add", function() { this.switchTab("showEvents"); }, this); + this.feed.announcements.bind("add", function() { this.switchTab("showAnnouncements"); }, this); + this.feed.subscribers.bind("add", function() { this.switchTab("showSubscribers"); }, this); + }, + + afterRender: function() { + this[this.currentTab](); + }, + + showAnnouncements: function() { + var account = this.account; + var wireView = new PaginatingWire({ + collection: this.announcementsCollection, + account: this.account, + el: this.$(".feed-announcements .wire"), + emptyMessage: "No announcements here yet", + modelToView: function(model) { + return new AnnouncementWireItem({ model: model, account: account }); + } + }); + wireView.render(); + }, + + showEvents: function() { + var account = this.account; + var wireView = new PaginatingWire({ + collection: this.eventsCollection, + account: this.account, + el: this.$(".feed-events .wire"), + emptyMessage: "No events here yet", + modelToView: function(model) { + return new EventWireItem({ model: model, account: account }); + } + }); + wireView.render(); + }, + + showSubscribers: function() { + var account = this.account; + var wireView = new PaginatingWire({ + collection: this.subscribersCollection, + account: this.account, + el: this.$(".feed-subscribers .wire"), + emptyMessage: "No subscribers yet", + modelToView: function(model) { + return new UserWireItem({ model: model, account: account }); + } + }); + wireView.render(); + }, + + tabs: function() { + return { + showAnnouncements: this.$(".feed-announcements"), + showEvents: this.$(".feed-events"), + showSubscribers: this.$(".feed-subscribers") + }; + }, + + classIfCurrent: function() { + var self = this; + return function(text) { + return text == self.currentTab ? "current" : ""; + }; + }, + + switchTab: function(newTab) { + this.tabs()[this.currentTab].hide(); + this.currentTab = newTab; + this.tabs()[this.currentTab].show(); + this.render(); + }, + + feedName: function() { return this.feed.get('name'); } +}); diff --git a/app/javascripts/feed_page/feed_view.js b/app/javascripts/feed_page/feed_view.js new file mode 100644 index 000000000..9fa286ea6 --- /dev/null +++ b/app/javascripts/feed_page/feed_view.js @@ -0,0 +1,42 @@ +var FeedView = CommonPlace.View.extend({ + template: "feed_page/feed", + id: "feed", + initialize: function(options) { + var self = this; + this.community = options.community; + this.account = options.account; + this.groups = options.groups; + var feed = this.model; + var resourceNav, resource, actions, profile, about, header, feedAdminBar; + + + adminBar = new FeedAdminBar({ model: feed, collection: this.account.feeds, account: this.account }); + profile = new FeedProfileView({ model: feed, account: this.account }); + about = new FeedAboutView({ model: feed }); + header = new FeedHeaderView({ model: feed, account: self.account }); + resource = new FeedSubResourcesView({ feed: feed, account: self.account }); + resourceNav = resourceNav = new FeedNavView({ model: feed }); + actions = feedActionsView = new FeedActionsView({ feed: feed, + groups: this.groups, + account: self.account, + community: self.community + }); + + resourceNav.bind("switchTab", function(tab) { resource.switchTab(tab);}); + + this.subViews = [profile, about, header, resource, resourceNav, actions, adminBar]; + }, + + afterRender: function() { + var self = this; + _(this.subViews).each(function(view) { + view.render(); + self.$("#" + view.id).replaceWith(view.el); + }); + }, + + isOwner: function() { + return this.account.isFeedOwner(this.model); + } + +}); diff --git a/app/javascripts/feed_page/feeds_list_view.js b/app/javascripts/feed_page/feeds_list_view.js new file mode 100644 index 000000000..8e9caf48a --- /dev/null +++ b/app/javascripts/feed_page/feeds_list_view.js @@ -0,0 +1,23 @@ + +var FeedsListView = CommonPlace.View.extend({ + template: "feed_page/feeds-list", + + feeds: function() { + return this.collection; + }, + + afterRender: function() { + var height = 0; + $("#feeds-list li").each(function(index) { + if (index == 9) { return false; } + height = height + $(this).outerHeight(true); + }); + $("#feeds-list ul").height(height); + }, + + select: function(slug) { + this.$("li").removeClass("current"); + this.$("li[data-feed-slug='" + slug + "']").addClass("current"); + } + +}); diff --git a/app/javascripts/feed_registration.js b/app/javascripts/feed_registration.js new file mode 100644 index 000000000..b985b2886 --- /dev/null +++ b/app/javascripts/feed_registration.js @@ -0,0 +1,86 @@ +//= require slugify.js +//= require jquery.js +//= require jcrop.js + +$(function() { + $("
", { id: "file_input_fix" }) + .append($("", { type: "text", name: "file_fix", id: "file_style_fix" })) + .append($("
", { id: "browse_button", text: "Browse..." })) + .appendTo("#feed_avatar_input"); + + + $('#feed_avatar').change(function() { + $("#file_input_fix input").val($(this).val().replace(/^.*\\/,"")); + }); + + $("#feed_slug_input").data("original-hint", + $("#feed_slug_input p.inline-hints").html()); + + $("#feed_name").keyup(function() { + if (!$("#feed_slug").data("manual")) { + $("#feed_slug").val(slugify($(this).val())); + $("#feed_slug_input p.inline-hints").html( + $("#feed_slug_input").data("original-hint").replace(/____/g, + $("#feed_slug").val())); + } + }); + + $("#feed_slug").keydown(function() { + $(this).data("manual", true); + }); + + $("#feed_slug").focusin(function() { + $("#feed_slug_input p.inline-hints").show(); + }); + + $("#feed_slug").keyup(function() { + $("#feed_slug_input p.inline-hints").html( + $("#feed_slug_input").data("original-hint").replace(/____/g, + $("#feed_slug").val())); + }); + + $("#feed_slug").focusout(function() { + $("#feed_slug_input p.inline-hints").hide(); + }); + + // Avatar crop + var updateCrop = function(coords) { + $("#crop_x").val(coords.x); + $("#crop_y").val(coords.y); + $("#crop_w").val(coords.w); + $("#crop_h").val(coords.h); + }; + + $("form.crop").load(function() { + $("form.crop").css({ width: Math.max($("#cropbox").width(), 420) }); + }); + + $("#cropbox").Jcrop({ + onChange: updateCrop, + onSelect: updateCrop, + aspectRatio: 1.0 + }); + + $("form.new_subscribers td").focusin(function() { + if ($(this).text() == "...") { $(this).text(""); } + }); + + $("form.new_subscribers td").focusout(function() { + if ($(this).text() == "") { $(this).text("..."); } + }); + + $("form.new_subscribers").submit(function(e) { + var subscribers = []; + $("form.new_subscribers .input-row").each(function() { + var name = $(".name", this).text(); + var email = $(".email", this).text(); + if ((name != "") && (name != "...") && (email != "") && (email != "...")) { + var val = name + "<" + email + ">"; + $('').val(val).appendTo($("form.new_subscribers")); + } + }); + + }); + + +}); diff --git a/app/javascripts/gaAddons.js b/app/javascripts/gaAddons.js new file mode 100644 index 000000000..90c775ff0 --- /dev/null +++ b/app/javascripts/gaAddons.js @@ -0,0 +1,4 @@ +// gaAddons v2.1.2, Copyright 2010-2011, Stephane Hamel - gaAddons.com +// Licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License +eval((function(x){var d="";var p=0;while(p4)d+=d.substr(d.length-x.charCodeAt(p+1)*96-x.charCodeAt(p+2)+3104-l,l);else d+="`";p+=4}}return d})("var _GA_SCOPE_VISITOR=1,` *&SESSION=2` )'PAGE=3,gaAddons;if(!window.console){` \"*={};}` %#.log=` 1#.log||function(){};` 2$info` >%info||` J'` >%dir` =%dir` /2group` >%` )!` .7End` >*End`!V+(` %%$){var copyright=\"`\"o$ v2.1.2, C` 4$ 2010-2011, Stephane Hamel - ` O$.com. Licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported` j$\";String.prototype.mreplace=`\",%hash,options){if(!` $%` \"#=\"g\";}var str=this,key;for(key in hash` W!Object`!%'hasOwnProperty.call`!,\"key)){str=str.`!O#(new RegExp(key`!N%,hash[key]);}}return str;};`#C$={_:{_4Q:{account:\"\",loc:\"4Q-WEB2\",lID:1,src:\"http://4qinvite.4q.iperce`!+\".com/1.aspx\",allowLinker:true,domainName:\"none\"},_formAnalysis:{onBounce` J\"minFields:3,formLis`!X\"astForm:null` (\"ield:\"Empty\",selector:\"form\"},_`\"i#:{debug:false,delay:100},_setDayOfWeek:{index:1,n`!f!` 0%\",value`!1\"scope:`*S-,days:[\"Sunday\",\"Mo` \"#Tues` $\"Wedn` $$Thur` $#Fri` #\"Saturday\"]`!S#ualMode:{path:\"//\"+location.host+\"/__utm.gif\"` N\"XD`#h!:{`#j'`!s!a`'9!:\"click\",includ` 5#ex` '\"/^$/,url`\"C#`#F%a\"},_trackDownload`$B-ethod:\"page\",category:\"d` I#\"`!0,`#L\"0`!?%/\\.(docx*|xlsx*|pptx*|exe|rar|zip|pdf|xpi|mp3|mp4|flv|wmv)$/i`!s*format:\"/%`!@$%/%`!9\"%/%hostname%%path` %!`%n(`\"A&Error:{`\"2$event`\"/(error\",` \"!:/(404|500)/`\"3$` `$LoadTime`##,` `&l` ;#\"`#>%`!&#bucket:[100,500,1500,25` (\"0],time:`*_!Date).getTime()`#T'`'L\"`'&,PAGE`!Y$MailTo`$Z4`\"G-mailto`$O>./`$)*`#P0Outboun`%v5`!,-o` J#` mN`$ocPageview:{title:document.` *!`(X!` *%href`+a)` ]!Real`\"E#{`\"/&No` .\"\"` J#30`%%},_get`*D!b:`3_'var kv=`!&%cookie.match(\"(^|;) ?` Q\"=([^;]*)(;|$)\");`0+#kv?parseInt(kv[2].split(/\\./)[1]):1;},_4Q`!',a=arg`!1!s[1];window.pageTracker4Q=_gaq._getAsync` 1#();` 2\"push([a.`\"n!er+\"_setA`0P&\",a.`0^'],` <,`-\"\"Name\",a.`-$&]);$.getScript(a.src+\"?sdfc=\"+a.`2G#+\"&lID` -!lID+\"&lo` :\"loc);`1Q,`\"T'$(`#i$).bind(\"ready\",`\"f(,` H%e`#)$e.data;if(!a.`'f$&&`\"h\"firstGACall){`$7\";}` E\"`2Y$){$(` ^\".`&p$).each(`$-+n=$(this).attr(\"name\")`!4!gaAddons._.`\"3).`3R$&&n&&$(\":input\",` b\"not(\":button,:hidden,:submit\").length>a.`4O%){` S-focusout`!b(`!.=`\"!%parents(\"form\"`\"++` I:ield`\"\\2});` 0$`\"&\"`!R&e){$(`'P\").un`%\"\"u`2%!.`!'$\");`$t\"`!(4;if(a`\"#%){`%&!`'f.`1'\"`-#\"`\";! a`&O$`&k!` Y#,e.type,` +#ield]);}});}})`%h!`%^*`!v$`!f2`&n/`!%~`!d?,_option`)%*.extend(`!W(` A#`)*)` \\!setDayOfWeek` Z(if(`(z/`,[/a.`.U!=a` \"\"||a.days[`3[*Day()]`,R8CustomVa`,g!`4$!,a.name,` s#,a.`43!`\"\\!`!s\"ualMode`.1;` z7LocalGifPath\",a.path+\"#\"+`07,replace(/;\\s*/g,\"&\")`.4.` j!RemoteServerMode\"`#`%X`.`\"`!Q:,url=location`2p!`&-\"url){url=` &!;}var cd=null` ;\"`/K&&&a.in`4i!&&(`$$!RegExp` 9),\"i\")).test(url)||(cd=url`2m#` [%)))`%K!!/\\\\\\//` L\"` 3'`'^\"`#K2`1,(cd?cd[0]:`14)`#4-`2*)tru` +4Hash\",fals`%N!else{if(cd`!(E\"none\"` ~@` x$`3fD`!a2`3l5`!|A}`,P\"`${(!`$|(`$`C`\"UE`!G7addIgnoredRef` ;+`0t*`'l&`!b$=a=/^$/`4I$`&3/^`&7/`\"5,` 1#`!uIC`*+!`*H$` [#]);}$(`*L$).ready`3p(`/b.`\"/&`)8\"`!\\${$(a.selector).each` Y,h`3R+href\")` Z)`\"'\"h)`$~!ex` $,/(mailto\\:|__utma)`#%#h)){` u$mouseup`4:)if(/` L\"` J#this`+`!)){return`+T\"pageT`#2\"=`#9%?_gat._get` 4#ByName(` 8%`-]&\\./,\"\")):` <3),n=` y'` =!`(W\"Url`!K';if`1t0.debug){console.info(\"` X): \"+` _%+\" > \"+n);}` +%=n;`3@$`3F\"`\" !Download`3E)`%P'bind(\"`%\\!\"`3A),`#\\($(e.dat`$pV` X$onBounce||!`3}.&&` ?#`%d.` 2#`%l+`%S&`\"!\"`%\\#\",` I\"`!v)if`!<$a` /!===\"click\"&&e.which!==1`%i*v=`#V%`0_#/\\/\\/(.+)$/)[1],t` <\"target.length?` (':\"_self\",ftype` G\"pathname` j%.([^\\.]+)/);`')!=`!d#format.m`&`${'%category%':` >#` *$,'%`\"5\"` 6!`\"-!===1?`\"@#:`\"u&'%`!W\"%':t,'%value` f&` *!,'%pagename%':`%h$.title,'%` #!%':`#x$text(),'%hash%':`#1\"ash,'%hos`!%!` .!ost` ,#` t#` .%name,'%href` .%ref`!D!`#*\"` /$` ($,'%por` s%port,'%protocol` .%` )#,'%searc`!V%` (\",'%filetype%':`$A!?`$G![1]:\"n/a\",'%location`\".!`\"d(` ,)` <)`\"O\"` 6/`\"\\\"` #)`#V,` ,-` D)`\"{\"` :/`#(\"` '%`#,'` ;.`#9'` *&`#G\"` :/`#T\"` &&`#Y&` :/`#f&` +%`#p%` =.`#}%` *$` 20}`+@!`&y#method===\"event\"`2(#r=p.split(/\\//);`+[!`3.\"` R#`3/&` %!E` Y!,ar.slice(1,2).toString()` /&2,3` #23).join(\"/\")`+m#`(X\"]);}else`4T\"`!)8Pageview\",p]);}`\"A!`)r%`-4!metaKey&&t!==\"_blank\"){setTimeout(\"window.open(\\\"`0;)\\\",` -!+\"\\\")\",`1&2lay);e.pr`#L!Default();`-E\" false`0p&`0s&Error`0n(`$$!=`0[(;$.ajax({type:\"HEAD\",url`*6'complete` _&XMLHttpRequest,textStatus`/B!a.error`/\"` =*.s` A\")`#s\"_`#u!`%`.?[`%45`.$&` e1,`&r%referrer,`%+$:` c.`$s&\"/\"+` r&+\"/\"+` k1` 5!` s-]);}`#v!`#q#LoadTim`#''`#f1`%!(` K).now=`3o:;$(`!?$).ready(` |-` g5;`$A!now` D\"diffTime=(new Date).g`'9\"()-a.time,i,bucket`) \";if(typeof a.` 3\"===\"object\"){for(i=0;i<` 7$`3u#;i++`%a!`!-$` 8%[i]){` x(=i?` 2&-1]+\"-\"+(` D'-1):\"0` ()0]-1);break;}}if(!` f(` q+` M%`!U+`!'!+\";}`*~\"i=parseInt`!l%/`!)%*1000));`!k**` 7$`!n\"(i+1)` +%-1);}`+o&`'HS`!%(,\"\"`'_$?`'h#:`!c$`'i+setCustomVar\",a.index` _5_GA_SCOPE_PAGE`'b!`'\\&MailTo`'X(`&h(bind(\"`&u!\",`'k(,` H%e){$`0M$selector).each`'<,h=$(this).attr(\"href\"`1/!!/^mailto:/i`+l\"h)){`-S\"`/*!` y$`(Q8)&&` ?#include` b$`/\\!` 4!ex` ,)){`!I$`\"B\"mouseup\"`0#`\"7)`2p&a` /!===\"click\"&&`0f#!==1`!l&var v=`.X%.match(/`\"A#([^?]+)/)[1]`3:'`3f3?`3?:` 6#`%5%`!\\),v`3'*:`2c=\"/\"+` d+`-m!v]);`2e(location`\";!='`2e)'`2I9` @%=\"javascript:void()\"`2b>`2r-Outbound`&E~`'':ar.slice(1,2).toString()` /&2,3` #23).join`!+!`2v*);}else{`!$=`3+&p]);}`\"@!`)q%`.V!metaKey&&t!==\"_blank\"){`3<(window.open(\\\"`3:)\\\",` -!+\"\\\")`369`2t=`3$.`!u$`3*(if(typeof `2{(===\"objec`$E%=` 3(`-0!oTitle=`+]*;`+l*=a` 0#`2&\"`#T\"` `&0],a.url]);`\"t(` V* = '\"+`!(\"+\"'\",a`\"[$`$I'` f1`!g']);}`\"_$Real`3_\"`\"](`\"3/if(`3r.`$])`%e''\"+`&}2',` 6!`/n$+\"','over \"+`\"##+\" seconds', '',\"+`&q#+\"])`\"G%*1000);}}};Array`)s\"type.`%_$=`\"%'`&0!`\"r!=` ?,push;` :\"`\"/'=`&C&get__utmb()===1` F\"` f!` v'for(v=0;v<`#,%`3I#;v++`#?.v`%q\"`\"|#=a[0]`*X$.\"`3B\"`+##` <#` g#&2?` *#[1]:` #$0`$:!`!k$[` T\"]&&` #\"`)6!`)c*||`+~&` ,+&&`'{$`'k,if(a`!@#>1){` ;!$.extend({}`)U'`!:$,a[1`+F%` K!` 2.;}a[1]`%e$`\"560]+\".\":\"\"`\"?(`*]*bug){console.group(\"` A%\"+`!+\");` :$dir(`![\"` G)End();}`#>,.apply(this,a`\",$` |K.\"+a[0]);a[1]?`!7-:\"\"`!61`'4&.call`!:&}`,~#`&C-};};})(jQuery`&)\"_gaq=[]`+w\"`!m$();")) + diff --git a/app/javascripts/group_page.js b/app/javascripts/group_page.js new file mode 100644 index 000000000..f199ca75c --- /dev/null +++ b/app/javascripts/group_page.js @@ -0,0 +1,19 @@ +//= require showdown +//= require jquery +//= require jquery-ui +//= require underscore +//= require mustache +//= require json2 +//= require time_ago_in_words +//= require backbone +//= require placeholder +//= require autoresize +//= require dropkick +//= require truncator +//= require views +//= require models +//= require_tree ../templates/shared +//= require_tree ../templates/group_page +//= require wires +//= require wire_items +//= require_tree ./group_page diff --git a/app/javascripts/group_page/group_header.js b/app/javascripts/group_page/group_header.js new file mode 100644 index 000000000..5c128fa90 --- /dev/null +++ b/app/javascripts/group_page/group_header.js @@ -0,0 +1,31 @@ +var GroupHeaderView = CommonPlace.View.extend({ + template: "group_page/header", + id: "group-header", + + initialize: function(options) { this.account = options.account; }, + + name: function() { + return this.model.get("name"); + }, + + isSubscribed: function() { + return this.account.isSubscribedToGroup(this.model); + }, + + events: { + "click a.subscribe": "subscribe", + "click a.unsubscribe": "unsubscribe" + }, + + subscribe: function(e) { + var self = this; + e.preventDefault(); + this.account.subscribeToGroup(this.model, function() { self.render(); }); + }, + + unsubscribe: function(e) { + var self = this; + e.preventDefault(); + this.account.unsubscribeFromGroup(this.model, function() { self.render(); }); + } +}); diff --git a/app/javascripts/group_page/group_list.js b/app/javascripts/group_page/group_list.js new file mode 100644 index 000000000..e137df537 --- /dev/null +++ b/app/javascripts/group_page/group_list.js @@ -0,0 +1,30 @@ +var GroupsListView = CommonPlace.View.extend({ + template: "group_page/groups-list", + + initialize: function(options) { + this.community = options.community; + }, + + afterRender: function() { + var height = 0; + $("#groups-list li").each(function(index) { + if (index == 9) { return false; } + height = height + $(this).outerHeight(true); + }); + $("#groups-list ul").height(height); + }, + + groups: function() { + return this.collection; + }, + + select: function(slug) { + this.$("li").removeClass("current"); + this.$("li[data-group-slug=" + slug + "]").addClass("current"); + }, + + community_name: function() { + return this.community.name; + } + +}); diff --git a/app/javascripts/group_page/group_nav.js b/app/javascripts/group_page/group_nav.js new file mode 100644 index 000000000..b6111b6a6 --- /dev/null +++ b/app/javascripts/group_page/group_nav.js @@ -0,0 +1,27 @@ +var GroupNavView = CommonPlace.View.extend({ + template: "group_page/nav", + id: "group-nav", + + events: { + "click a": "navigate" + }, + + initialize: function(options) { + this.current = options.current || "showGroupPosts"; + }, + + navigate: function(e) { + e.preventDefault(); + this.current = $(e.target).attr('data-tab'); + this.trigger('switchTab', this.current); + this.render(); + }, + + classIfCurrent: function() { + var self = this; + return function(text) { + return this.current == text ? "current" : ""; + }; + } + +}); diff --git a/app/javascripts/group_page/group_profile.js b/app/javascripts/group_page/group_profile.js new file mode 100644 index 000000000..b57422761 --- /dev/null +++ b/app/javascripts/group_page/group_profile.js @@ -0,0 +1,12 @@ +var GroupProfileView = CommonPlace.View.extend({ + template: "group_page/profile", + id: "group-profile", + + avatar_url: function() { + return this.model.get("avatar_url"); + }, + + about: function() { + return this.model.get("about"); + } +}); diff --git a/app/javascripts/group_page/group_subresources.js b/app/javascripts/group_page/group_subresources.js new file mode 100644 index 000000000..24cb73917 --- /dev/null +++ b/app/javascripts/group_page/group_subresources.js @@ -0,0 +1,103 @@ +var GroupSubresourcesView = CommonPlace.View.extend({ + template: "group_page/subresources", + id: "group-subresources", + + initialize: function(options) { + var self = this; + this.account = options.account; + this.group = options.model; + this.groupPostsCollection = this.group.posts; + this.groupMembersCollection = this.group.members; + this.groupAnnouncementsCollection = this.group.announcements; + this.groupEventsCollection = this.group.events; + this.currentTab = options.current || "showGroupPosts"; + this.group.posts.bind("add", function() { self.switchTab("showGroupPosts"); }, this); + this.group.members.bind("add", function() { self.switchTab("showGroupMembers"); }, this); + this.group.announcements.bind("add", function() { self.switchTab("showAnnouncements"); }, this); + this.group.events.bind("add", function() { self.switchTab("showEvents"); }, this); + }, + + afterRender: function() { + this[this.currentTab](); + }, + + showGroupPosts: function() { + var account = this.account; + var wireView = new PaginatingWire({ + collection: this.groupPostsCollection, + account: this.account, + el: this.$(".group-posts .wire"), + emptyMessage: "No posts here yet", + modelToView: function(model) { + return new GroupPostWireItem({ model: model, account: account }); + } + }); + wireView.render(); + }, + + showGroupMembers: function() { + var account = this.account; + var wireView = new PaginatingWire({ + collection: this.groupMembersCollection, + account: this.account, + el: this.$(".group-members .wire"), + emptyMessage: "No members yet", + modelToView: function(model) { + return new UserWireItem({ model: model, account: account }); + } + }); + wireView.render(); + }, + + showAnnouncements: function() { + var account = this.account; + var wireView = new PaginatingWire({ + collection: this.groupAnnouncementsCollection, + account: this.account, + el: this.$(".group-announcements .wire"), + emptyMessage: "No announcements here yet", + modelToView: function(model) { + return new AnnouncementWireItem({ model: model, account: account }); + } + }); + wireView.render(); + }, + + showEvents: function() { + var account = this.account; + var wireView = new PaginatingWire({ + collection: this.groupEventsCollection, + account: this.account, + el: this.$(".group-events .wire"), + emptyMessage: "No events here yet", + modelToView: function(model) { + return new EventWireItem({ model: model, account: account }); + } + }); + wireView.render(); + }, + + tabs: function() { + return { + showGroupPosts: this.$(".group-posts"), + showGroupMembers: this.$(".group-members"), + showAnnouncements: this.$(".group-announcements"), + showEvents: this.$(".group-events") + }; + }, + + classIfCurrent: function() { + var self = this; + return function(text) { + return text == self.currentTab ? "current" : ""; + }; + }, + + switchTab: function(newTab) { + this.tabs()[this.currentTab].hide(); + this.currentTab = newTab; + this.tabs()[this.currentTab].show(); + this.render(); + } + +}); diff --git a/app/javascripts/group_page/group_view.js b/app/javascripts/group_page/group_view.js new file mode 100644 index 000000000..5ecc48217 --- /dev/null +++ b/app/javascripts/group_page/group_view.js @@ -0,0 +1,32 @@ +var GroupView = CommonPlace.View.extend({ + template: "group_page/group", + id: "group", + + initialize: function(options) { + var self = this; + this.community = options.community; + this.account = options.account; + var group = this.model; + var profile, header, newpost, nav, subresources, list; + + profile = new GroupProfileView({model: group}); + header = new GroupHeaderView({model: group, account: this.account}); + newpost = new NewPostView({model: group, account: this.account}); + nav = new GroupNavView({model: group}); + subresources = new GroupSubresourcesView({model: group, account: this.account}); + + nav.bind("switchTab", function(tab) { subresources.switchTab(tab); }); + + this.subviews = [profile, header, newpost, nav, subresources]; + + }, + + afterRender: function() { + var self = this; + _(this.subviews).each(function(view) { + view.render(); + self.$("#" + view.id).replaceWith(view.el); + }); + } + +}); diff --git a/app/javascripts/group_page/new_post.js b/app/javascripts/group_page/new_post.js new file mode 100644 index 000000000..04e381329 --- /dev/null +++ b/app/javascripts/group_page/new_post.js @@ -0,0 +1,40 @@ +var NewPostView = CommonPlace.View.extend({ + template: "group_page/new-post", + id: "new-post", + + initialize: function(options) { + this.account = options.account; + }, + + afterRender: function() { + this.$('input[placeholder], textarea[placeholder]').placeholder(); + this.$("textarea").autoResize(); + }, + + account_avatar: function() { + return this.account.get("avatar_url"); + }, + + events: { + "click button": "postMessage" + }, + + showError: function(response) { + this.$(".error").text(response.responseText); + this.$(".error").show(); + }, + + postMessage: function(e) { + e.preventDefault(); + var $form = this.$("form"); + var self = this; + this.cleanUpPlaceholders(); + this.model.posts.create({ + title: $("[name=title]", $form).val(), + body: $("[name=body]", $form).val() + }, { + success: function() { self.render(); }, + error: function(attribs, response) { self.showError(response); } + }); + } +}); diff --git a/app/javascripts/group_page/router.js b/app/javascripts/group_page/router.js new file mode 100644 index 000000000..6b567dd35 --- /dev/null +++ b/app/javascripts/group_page/router.js @@ -0,0 +1,36 @@ +var GroupPageRouter = Backbone.Router.extend({ + + routes: {}, + + initialize: function(options) { + this.account = new Account(options.account); + this.community = options.community; + this.group = options.group; + this.groupsList = new GroupsListView({ + collection: options.groups, + el: $("#groups-list"), + community: this.community + }); + this.groupsList.render(); + this.show(options.group); + }, + + show: function(slug) { + var self = this; + $.getJSON("/api" + this.community.links.groups, function(groups) { + self.groupsList.select(slug); + $.getJSON("/api/groups/" + slug, function(response) { + var group = new Group(response); + + document.title = group.get('name'); + + var groupView = new GroupView({ model: group, community: self.community, account: self.account }); + window.currentGroupView = groupView; + groupView.render(); + + $("#group").replaceWith(groupView.el); + }); + }); + } + +}); diff --git a/app/javascripts/head.min.js b/app/javascripts/head.min.js new file mode 100644 index 000000000..7ad9af7fb --- /dev/null +++ b/app/javascripts/head.min.js @@ -0,0 +1,8 @@ +/** + Head JS The only script in your + Copyright Tero Piirainen (tipiirai) + License MIT / http://bit.ly/mit-license + Version 0.9 + + http://headjs.com +*/(function(a){var b=a.documentElement,c={screens:[320,480,640,768,1024,1280,1440,1680,1920],section:"-section",page:"-page",head:"head"},d=[];if(window.head_conf)for(var e in head_conf)head_conf[e]!==undefined&&(c[e]=head_conf[e]);function f(a){d.push(a)}function g(a){var c=new RegExp("\\b"+a+"\\b");b.className=b.className.replace(c,"")}function h(a,b){for(var c=0;c2&&this[d+1]!==undefined)d&&f(this.slice(1,d+1).join("-")+c.section);else{var e=a||"index",g=e.indexOf(".");g>0&&(e=e.substring(0,g)),b.id=e+c.page,d||f("root"+c.section)}});function l(){var a=window.outerWidth||b.clientWidth;b.className=b.className.replace(/ (w|lt)-\d+/g,""),f("w-"+Math.round(a/100)*100),h(c.screens,function(b){a<=b&&f("lt-"+b)}),i.feature()}l(),window.onresize=l,i.feature("js",true).feature()})(document),function(){var a=document.createElement("i"),b=a.style,c=" -o- -moz- -ms- -webkit- -khtml- ".split(" "),d="Webkit Moz O ms Khtml".split(" "),e=window.head_conf&&head_conf.head||"head",f=window[e];function g(a){for(var c in a)if(b[a[c]]!==undefined)return true}function h(a){var b=a.charAt(0).toUpperCase()+a.substr(1),c=(a+" "+d.join(b+" ")+b).split(" ");return!!g(c)}var i={gradient:function(){var a="background-image:",d="gradient(linear,left top,right bottom,from(#9f9),to(#fff));",e="linear-gradient(left top,#eee,#fff);";b.cssText=(a+c.join(d+a)+c.join(e+a)).slice(0,-a.length);return!!b.backgroundImage},rgba:function(){b.cssText="background-color:rgba(0,0,0,0.5)";return!!b.backgroundColor},opacity:function(){return a.style.opacity===""},textshadow:function(){return b.textShadow===""},multiplebgs:function(){b.cssText="background:url(//:),url(//:),red url(//:)";return(new RegExp("(url\\s*\\(.*?){3}")).test(b.background)},boxshadow:function(){return h("boxShadow")},borderimage:function(){return h("borderImage")},borderradius:function(){return h("borderRadius")},cssreflections:function(){return h("boxReflect")},csstransforms:function(){return h("transform")},csstransitions:function(){return h("transition")},fontface:function(){var a=navigator.userAgent,b;if(0)return true;if(b=a.match(/Chrome\/(\d+\.\d+\.\d+\.\d+)/))return b[1]>="4.0.249.4";if((b=a.match(/Safari\/(\d+\.\d+)/))&&!/iPhone/.test(a))return b[1]>="525.13";if(/Opera/.test(({}).toString.call(window.opera)))return opera.version()>="10.00";if(b=a.match(/rv:(\d+\.\d+\.\d+)[^b].*Gecko\//))return b[1]>="1.9.1";return false}};for(var j in i)i[j]&&f.feature(j,i[j].call(),true);f.feature()}(),function(a){var b=a.documentElement,c,d,e=[],f=[],g={},h={},i=a.createElement("script").async===true||"MozAppearance"in a.documentElement.style||window.opera;var j=window.head_conf&&head_conf.head||"head",k=window[j]=window[j]||function(){k.ready.apply(null,arguments)};var l=0,m=1,n=2,o=3;i?k.js=function(){var a=arguments,b=a[a.length-1],c=[];t(b)||(b=null),s(a,function(d,e){d!=b&&(d=r(d),c.push(d),x(d,b&&e==a.length-2?function(){u(c)&&p(b)}:null))});return k}:k.js=function(){var a=arguments,b=[].slice.call(a,1),d=b[0];if(!c){f.push(function(){k.js.apply(null,a)});return k}d?(s(b,function(a){t(a)||w(r(a))}),x(r(a[0]),t(d)?d:function(){k.js.apply(null,b)})):x(r(a[0]));return k},k.ready=function(a,b){if(a=="dom"){d?p(b):e.push(b);return k}t(a)&&(b=a,a="ALL");var c=h[a];if(c&&c.state==o||a=="ALL"&&u()&&d){p(b);return k}var f=g[a];f?f.push(b):f=g[a]=[b];return k},k.ready("dom",function(){c&&u()&&s(g.ALL,function(a){p(a)}),k.feature&&k.feature("domloaded",true)});function p(a){a._done||(a(),a._done=1)}function q(a){var b=a.split("/"),c=b[b.length-1],d=c.indexOf("?");return d!=-1?c.substring(0,d):c}function r(a){var b;if(typeof a=="object")for(var c in a)a[c]&&(b={name:c,url:a[c]});else b={name:q(a),url:a};var d=h[b.name];if(d&&d.url===b.url)return d;h[b.name]=b;return b}function s(a,b){if(a){typeof a=="object"&&(a=[].slice.call(a));for(var c=0;c3?g%3:0;return e+(g?c.substr(0,g)+d:"")+c.substr(g).replace(/(\d{3})(?=\d)/g,"$1"+d)+(f?b+cb(a-c).toFixed(f).slice(2):"")}function Hc(){}function Hd(a,b){function c(m,h){function x(l, +p){this.pos=l;this.minor=p;this.isNew=true;p||this.addLabel()}function w(l){if(l){this.options=l;this.id=l.id}return this}function O(){var l=[],p=[],r;Ta=u=null;Z=[];t(Ba,function(o){r=false;t(["xAxis","yAxis"],function(la){if(o.isCartesian&&(la=="xAxis"&&ma||la=="yAxis"&&!ma)&&(o.options[la]==h.index||o.options[la]===Ra&&h.index===0)){o[la]=s;Z.push(o);r=true}});if(!o.visible&&v.ignoreHiddenSeries)r=false;if(r){var T,Y,G,B,ia;if(!ma){T=o.options.stacking;Ic=T=="percent";if(T){B=o.type+y(o.options.stack, +"");ia="-"+B;o.stackKey=B;Y=l[B]||[];l[B]=Y;G=p[ia]||[];p[ia]=G}if(Ic){Ta=0;u=99}}if(o.isCartesian){t(o.data,function(la){var C=la.x,na=la.y,S=na<0,$=S?G:Y;S=S?ia:B;if(Ta===null)Ta=u=la[H];if(ma)if(C>u)u=C;else{if(Cu)u=na;else if(la=0){Ta=0;Id=true}else if(u<0){u=0;Jd=true}}}})}function ja(l,p){var r; +Fb=p?1:Ua.pow(10,ob(Ua.log(l)/Ua.LN10));r=l/Fb;if(!p){p=[1,2,2.5,5,10];if(h.allowDecimals===false)if(Fb==1)p=[1,2,5,10];else if(Fb<=0.1)p=[1/Fb]}for(var o=0;o0||!Jd))P+=l*Kd}Wa=K==P?1:Mb&&!T&&Y==r.options.tickPixelInterval?r.tickInterval:y(T,Va?1:(P-K)*Y/A);if(!N&&!J(h.tickInterval))Wa=ja(Wa);s.tickInterval=Wa;Jc=h.minorTickInterval==="auto"&&Wa?Wa/5:h.minorTickInterval;if(N){ra=[];T=Sa.global.useUTC; +var G=1E3/qb,B=6E4/qb,ia=36E5/qb;Y=864E5/qb;l=6048E5/qb;o=2592E6/qb;var la=31556952E3/qb,C=[["second",G,[1,2,5,10,15,30]],["minute",B,[1,2,5,10,15,30]],["hour",ia,[1,2,3,4,6,8,12]],["day",Y,[1,2]],["week",l,[1,2]],["month",o,[1,2,3,4,6]],["year",la,null]],na=C[6],S=na[1],$=na[2];for(r=0;r=G)$.setSeconds(S>=B?0:C*ob($.getSeconds()/ +C));if(S>=B)$[Cd](S>=ia?0:C*ob($[bd]()/C));if(S>=ia)$[Dd](S>=Y?0:C*ob($[cd]()/C));if(S>=Y)$[ed](S>=o?1:C*ob($[oc]()/C));if(S>=o){$[Ed](S>=la?0:C*ob($[Dc]()/C));p=$[Ec]()}if(S>=la){p-=p%C;$[Fd](p)}S==l&&$[ed]($[oc]()-$[dd]()+h.startOfWeek);r=1;p=$[Ec]();G=$.getTime()/qb;B=$[Dc]();for(ia=$[oc]();Gp&&ra.shift();if(h.endOnTick)P=r;else PGb[H])Gb[H]=ra.length}}function Ea(){var l,p;gb=K;cc=P;O();ga();ha=D;D=A/(P-K||1);if(!ma)for(l in ea)for(p in ea[l])ea[l][p].cum=ea[l][p].total;if(!s.isDirty)s.isDirty=K!=gb||P!=cc}function ua(l){l= +(new w(l)).render();Nb.push(l);return l}function bb(){var l=h.title,p=h.alternateGridColor,r=h.lineWidth,o,T,Y=m.hasRendered,G=Y&&J(gb)&&!isNaN(gb);o=Z.length&&J(K)&&J(P);A=M?wa:sa;D=A/(P-K||1);xa=M?V:rb;if(o||Mb){if(Jc&&!Va)for(o=K+(ra[0]-K)%Jc;o<=P;o+=Jc){Wb[o]||(Wb[o]=new x(o,true));G&&Wb[o].isNew&&Wb[o].render(null,true);Wb[o].isActive=true;Wb[o].render()}t(ra,function(B,ia){if(!Mb||B>=K&&B<=P){G&&sb[B].isNew&&sb[B].render(ia,true);sb[B].isActive=true;sb[B].render(ia)}});p&&t(ra,function(B,ia){if(ia% +2===0&&B=1E3?Gd(l,0):l},Nc=M&&h.labels.staggerLines,Xb=h.reversed,Yb=Va&&h.tickmarkPlacement=="between"?0.5:0;x.prototype={addLabel:function(){var l=this.pos,p=h.labels,r=!(l== +K&&!y(h.showFirstLabel,1)||l==P&&!y(h.showLastLabel,0)),o=Va&&M&&Va.length&&!p.step&&!p.staggerLines&&!p.rotation&&wa/Va.length||!M&&wa/2,T=this.label;l=$d.call({isFirst:l==ra[0],isLast:l==ra[ra.length-1],dateTimeLabelFormat:Kc,value:Va&&Va[l]?Va[l]:l});o=o&&{width:o-2*(p.padding||10)+$a};o=qa(o,p.style);if(T===Ra)this.label=J(l)&&r&&p.enabled?aa.text(l,0,0).attr({align:p.align,rotation:p.rotation}).css(o).add(tb):null;else T&&T.attr({text:l}).css(o)},getLabelSize:function(){var l=this.label;return l? +(this.labelBBox=l.getBBox())[M?"height":"width"]:0},render:function(l,p){var r=!this.minor,o=this.label,T=this.pos,Y=h.labels,G=this.gridLine,B=r?h.gridLineWidth:h.minorGridLineWidth,ia=r?h.gridLineColor:h.minorGridLineColor,la=r?h.gridLineDashStyle:h.minorGridLineDashStyle,C=this.mark,na=r?h.tickLength:h.minorTickLength,S=r?h.tickWidth:h.minorTickWidth||0,$=r?h.tickColor:h.minorTickColor,pc=r?h.tickPosition:h.minorTickPosition;r=Y.step;var hb=p&&Oc||Pa,Ob;Ob=M?va(T+Yb,null,null,p)+xa:V+Q+(Oa?(p&& +jd||Xa)-Ab-V:0);hb=M?hb-rb+Q-(Oa?sa:0):hb-va(T+Yb,null,null,p)-xa;if(B){T=Ka(T+Yb,B,p);if(G===Ra){G={stroke:ia,"stroke-width":B};if(la)G.dashstyle=la;this.gridLine=G=B?aa.path(T).attr(G).add(Hb):null}G&&T&&G.animate({d:T})}if(S){if(pc=="inside")na=-na;if(Oa)na=-na;B=aa.crispLine([Za,Ob,hb,Da,Ob+(M?0:-na),hb+(M?na:0)],S);if(C)C.animate({d:B});else this.mark=aa.path(B).attr({stroke:$,"stroke-width":S}).add(tb)}if(o){Ob=Ob+Y.x-(Yb&&M?Yb*D*(Xb?-1:1):0);hb=hb+Y.y-(Yb&&!M?Yb*D*(Xb?1:-1):0);J(Y.y)||(hb+= +parseInt(o.styles.lineHeight)*0.9-o.getBBox().height/2);if(Nc)hb+=l%Nc*16;if(r)o[l%r?"hide":"show"]();o[this.isNew?"attr":"animate"]({x:Ob,y:hb})}this.isNew=false},destroy:function(){for(var l in this)this[l]&&this[l].destroy&&this[l].destroy()}};w.prototype={render:function(){var l=this,p=l.options,r=p.label,o=l.label,T=p.width,Y=p.to,G,B=p.from,ia=p.dashStyle,la=l.svgElem,C=[],na,S,$=p.color;S=p.zIndex;var pc=p.events;if(T){C=Ka(p.value,T);p={stroke:$,"stroke-width":T};if(ia)p.dashstyle=ia}else if(J(B)&& +J(Y)){B=Ca(B,K);Y=pb(Y,P);G=Ka(Y);if((C=Ka(B))&&G)C.push(G[4],G[5],G[1],G[2]);else C=null;p={fill:$}}else return;if(J(S))p.zIndex=S;if(la)if(C)la.animate({d:C},null,la.onGetPath);else{la.hide();la.onGetPath=function(){la.show()}}else if(C&&C.length){l.svgElem=la=aa.path(C).attr(p).add();if(pc){ia=function(hb){la.on(hb,function(Ob){pc[hb].apply(l,[Ob])})};for(na in pc)ia(na)}}if(r&&J(r.text)&&C&&C.length&&wa>0&&sa>0){r=ya({align:M&&G&&"center",x:M?!G&&4:10,verticalAlign:!M&&G&&"middle",y:M?G?16:10: +G?6:-4,rotation:M&&!G&&90},r);if(!o)l.label=o=aa.text(r.text,0,0).attr({align:r.textAlign||r.align,rotation:r.rotation,zIndex:S}).css(r.style).add();G=[C[1],C[4],C[6]||C[1]];C=[C[2],C[5],C[7]||C[2]];na=pb.apply(Ua,G);S=pb.apply(Ua,C);o.align(r,false,{x:na,y:S,width:Ca.apply(Ua,G)-na,height:Ca.apply(Ua,C)-S});o.show()}else o&&o.hide();return l},destroy:function(){for(var l in this){this[l]&&this[l].destroy&&this[l].destroy();delete this[l]}mc(Nb,this)}};va=function(l,p,r,o){var T=1,Y=0,G=o?ha:D;o= +o?gb:K;G||(G=D);if(r){T*=-1;Y=A}if(Xb){T*=-1;Y-=T*A}if(p){if(Xb)l=A-l;l=l/G+o}else l=T*(l-o)*G+Y;return l};Ka=function(l,p,r){var o,T,Y;l=va(l,null,null,r);var G=r&&Oc||Pa,B=r&&jd||Xa,ia;r=T=fa(l+xa);o=Y=fa(G-l-xa);if(isNaN(l))ia=true;else if(M){o=ba;Y=G-rb;if(rV+wa)ia=true}else{r=V;T=B-Ab;if(oba+sa)ia=true}return ia?null:aa.crispLine([Za,r,o,Da,T,Y],p||0)};if(Ga&&ma&&Xb===Ra)Xb=true;qa(s,{addPlotBand:ua,addPlotLine:ua,adjustTickAmount:function(){if(Gb&&!N&&!Va&&!Mb){var l=ec,p=ra.length; +ec=Gb[H];if(pl)l=K;else if(P', +A?Mc("%A, %b %e, %Y",D):D,"
"]:[];t(H,function(va){xa.push(va.point.tooltipFormatter(ha))});return xa.join("")}function x(H,A){E=ma?H:(2*E+H)/3;ea=ma?A:(ea+A)/2;s.translate(E,ea);kd=cb(H-E)>1||cb(A-ea)>1?function(){x(H,A)}:null}function w(){if(!ma){var H=q.hoverPoints;s.hide();t(ga,function(A){A&&A.hide()});H&&t(H,function(A){A.setState()});q.hoverPoints=null;ma=true}}var O,ja=m.borderWidth,L=m.crosshairs,ga=[],Ea=m.style,ua=m.shared,bb=oa(Ea.padding),Ja=ja+bb,ma=true,Oa,M,E=0,ea=0;Ea.padding= +0;var s=aa.g("tooltip").attr({zIndex:8}).add(),N=aa.rect(Ja,Ja,0,0,m.borderRadius,ja).attr({fill:m.backgroundColor,"stroke-width":ja}).add(s).shadow(m.shadow),Q=aa.text("",bb+Ja,oa(Ea.fontSize)+bb+Ja).attr({zIndex:1}).css(Ea).add(s);s.hide();return{shared:ua,refresh:function(H){var A,D,ha,xa=0,va={},Ka=[];ha=H.tooltipPos;A=m.formatter||h;va=q.hoverPoints;var tb=function(Fa){return{series:Fa.series,point:Fa,x:Fa.category,y:Fa.y,percentage:Fa.percentage,total:Fa.total||Fa.stackTotal}};if(ua){va&&t(va, +function(Fa){Fa.setState()});q.hoverPoints=H;t(H,function(Fa){Fa.setState(yb);xa+=Fa.plotY;Ka.push(tb(Fa))});D=H[0].plotX;xa=fa(xa)/H.length;va={x:H[0].category};va.points=Ka;H=H[0]}else va=tb(H);va=A.call(va);O=H.series;D=ua?D:H.plotX;xa=ua?xa:H.plotY;A=fa(ha?ha[0]:Ga?wa-xa:D);D=fa(ha?ha[1]:Ga?sa-D:xa);ha=ua||!H.series.isCartesian||hc(A,D);if(va===false||!ha)w();else{if(ma){s.show();ma=false}Q.attr({text:va});ha=Q.getBBox();Oa=ha.width+2*bb;M=ha.height+2*bb;N.attr({width:Oa,height:M,stroke:m.borderColor|| +H.color||O.color||"#606060"});A=A-Oa+V-25;D=D-M+ba+10;if(A<7){A=7;D-=30}if(D<5)D=5;else if(D+M>Pa)D=Pa-M-5;x(fa(A-Ja),fa(D-Ja))}if(L){L=nc(L);D=L.length;for(var Hb;D--;)if(L[D]&&(Hb=H.series[D?"yAxis":"xAxis"])){A=Hb.getPlotLinePath(H[D?"y":"x"],1);if(ga[D])ga[D].attr({d:A,visibility:Bb});else{ha={"stroke-width":L[D].width||1,stroke:L[D].color||"#C0C0C0",zIndex:2};if(L[D].dashStyle)ha.dashstyle=L[D].dashStyle;ga[D]=aa.path(A).attr(ha).add()}}}},hide:w}}function f(m,h){function x(E){var ea;E=E||ib.event; +if(!E.target)E.target=E.srcElement;ea=E.touches?E.touches.item(0):E;if(E.type!="mousemove"||ib.opera){for(var s=ta,N={left:s.offsetLeft,top:s.offsetTop};s=s.offsetParent;){N.left+=s.offsetLeft;N.top+=s.offsetTop;if(s!=Aa.body&&s!=Aa.documentElement){N.left-=s.scrollLeft;N.top-=s.scrollTop}}qc=N}if(Ac){E.chartX=E.x;E.chartY=E.y}else if(ea.layerX===Ra){E.chartX=ea.pageX-qc.left;E.chartY=ea.pageY-qc.top}else{E.chartX=E.layerX;E.chartY=E.layerY}return E}function w(E){var ea={xAxis:[],yAxis:[]};t(ab,function(s){var N= +s.translate,Q=s.isXAxis;ea[Q?"xAxis":"yAxis"].push({axis:s,value:N((Ga?!Q:Q)?E.chartX-V:sa-E.chartY+ba,true)})});return ea}function O(){var E=m.hoverSeries,ea=m.hoverPoint;ea&&ea.onMouseOut();E&&E.onMouseOut();rc&&rc.hide();ld=null}function ja(){if(ua){var E={xAxis:[],yAxis:[]},ea=ua.getBBox(),s=ea.x-V,N=ea.y-ba;if(Ea){t(ab,function(Q){var H=Q.translate,A=Q.isXAxis,D=Ga?!A:A,ha=H(D?s:sa-N-ea.height,true);H=H(D?s+ea.width:sa-N,true);E[A?"xAxis":"yAxis"].push({axis:Q,min:pb(ha,H),max:Ca(ha,H)})});La(m, +"selection",E,md)}ua=ua.destroy()}m.mouseIsDown=nd=Ea=false;Cb(Aa,Ib?"touchend":"mouseup",ja)}var L,ga,Ea,ua,bb=v.zoomType,Ja=/x/.test(bb),ma=/y/.test(bb),Oa=Ja&&!Ga||ma&&Ga,M=ma&&!Ga||Ja&&Ga;Pc=function(){if(Qc){Qc.translate(V,ba);Ga&&Qc.attr({width:m.plotWidth,height:m.plotHeight}).invert()}else m.trackerGroup=Qc=aa.g("tracker").attr({zIndex:9}).add()};Pc();if(h.enabled)m.tooltip=rc=e(h);(function(){var E=true;ta.onmousedown=function(s){s=x(s);m.mouseIsDown=nd=true;L=s.chartX;ga=s.chartY;Qa(Aa, +Ib?"touchend":"mouseup",ja)};var ea=function(s){if(!(s&&s.touches&&s.touches.length>1)){s=x(s);if(!Ib)s.returnValue=false;var N=s.chartX,Q=s.chartY,H=!hc(N-V,Q-ba);if(Ib&&s.type=="touchstart")if(za(s.target,"isTracker"))m.runTrackerClick||s.preventDefault();else!ae&&!H&&s.preventDefault();if(H){E||O();if(NV+wa)N=V+wa;if(Qba+sa)Q=ba+sa}if(nd&&s.type!="touchstart"){if(Ea=Math.sqrt(Math.pow(L-N,2)+Math.pow(ga-Q,2))>10){if(ic&&(Ja||ma)&&hc(L-V,ga-ba))ua||(ua=aa.rect(V, +ba,Oa?1:wa,M?1:sa,0).attr({fill:"rgba(69,114,167,0.25)",zIndex:7}).add());if(ua&&Oa){N=N-L;ua.attr({width:cb(N),x:(N>0?0:N)+L})}if(ua&&M){Q=Q-ga;ua.attr({height:cb(Q),y:(Q>0?0:Q)+ga})}}}else if(!H){var A;Q=m.hoverPoint;N=m.hoverSeries;var D,ha,xa=Xa,va=Ga?s.chartY:s.chartX-V;if(rc&&h.shared){A=[];D=Ba.length;for(ha=0;haxa&&A.splice(D, +1);if(A.length&&A[0].plotX!=ld){rc.refresh(A);ld=A[0].plotX}}if(N&&N.tracker)(s=N.tooltipPoints[va])&&s!=Q&&s.onMouseOver()}return(E=H)||!ic}};ta.onmousemove=ea;Qa(ta,"mouseleave",O);ta.ontouchstart=function(s){if(Ja||ma)ta.onmousedown(s);ea(s)};ta.ontouchmove=ea;ta.ontouchend=function(){Ea&&O()};ta.onclick=function(s){var N=m.hoverPoint;s=x(s);s.cancelBubble=true;if(!Ea)if(N&&za(s.target,"isTracker")){var Q=N.plotX,H=N.plotY;qa(N,{pageX:qc.left+V+(Ga?wa-H:Q),pageY:qc.top+ba+(Ga?sa-Q:H)});La(N.series, +"click",qa(s,{point:N}));N.firePointEvent("click",s)}else{qa(s,w(s));hc(s.chartX-V,s.chartY-ba)&&La(m,"click",s)}Ea=false}})();Nd=setInterval(function(){kd&&kd()},32);qa(this,{zoomX:Ja,zoomY:ma,resetTracker:O})}function g(m){var h=m.type||v.type||v.defaultSeriesType,x=ub[h],w=q.hasRendered;if(w)if(Ga&&h=="column")x=ub.bar;else if(!Ga&&h=="bar")x=ub.column;h=new x;h.init(q,m);if(!w&&h.inverted)Ga=true;if(h.isCartesian)ic=h.isCartesian;Ba.push(h);return h}function i(){v.alignTicks!==false&&t(ab,function(m){m.adjustTickAmount()}); +Gb=null}function k(m){var h=q.isDirtyLegend,x,w=q.isDirtyBox,O=Ba.length,ja=O,L=q.clipRect;for(bc(m,q);ja--;){m=Ba[ja];if(m.isDirty&&m.options.stacking){x=true;break}}if(x)for(ja=O;ja--;){m=Ba[ja];if(m.options.stacking)m.isDirty=true}t(Ba,function(ga){if(ga.isDirty){ga.cleanData();ga.getSegments();if(ga.options.legendType=="point")h=true}});if(h&&od.renderLegend){od.renderLegend();q.isDirtyLegend=false}if(ic){if(!Rc){Gb=null;t(ab,function(ga){ga.setScale()})}i();sc();t(ab,function(ga){if(ga.isDirty|| +w){ga.redraw();w=true}})}if(w){pd();Pc();if(L){Sc(L);L.animate({width:q.plotSizeX,height:q.plotSizeY})}}t(Ba,function(ga){if(ga.isDirty&&ga.visible&&(!ga.isCartesian||ga.xAxis))ga.redraw()});gc&&gc.resetTracker&&gc.resetTracker();La(q,"redraw")}function j(){var m=a.xAxis||{},h=a.yAxis||{},x;m=nc(m);t(m,function(w,O){w.index=O;w.isX=true});h=nc(h);t(h,function(w,O){w.index=O});ab=m.concat(h);q.xAxis=[];q.yAxis=[];ab=jc(ab,function(w){x=new c(q,w);q[x.isXAxis?"xAxis":"yAxis"].push(x);return x});i()} +function n(m,h){kc=ya(a.title,m);tc=ya(a.subtitle,h);t([["title",m,kc],["subtitle",h,tc]],function(x){var w=x[0],O=q[w],ja=x[1];x=x[2];if(O&&ja){O.destroy();O=null}if(x&&x.text&&!O)q[w]=aa.text(x.text,0,0).attr({align:x.align,"class":"highcharts-"+w,zIndex:1}).css(x.style).add().align(x,false,uc)})}function z(){jb=v.renderTo;Od=Zb+qd++;if(Kb(jb))jb=Aa.getElementById(jb);jb.innerHTML="";if(!jb.offsetWidth){Qb=jb.cloneNode(0);Ia(Qb,{position:lc,top:"-9999px",display:""});Aa.body.appendChild(Qb)}Tc= +(Qb||jb).offsetWidth;vc=(Qb||jb).offsetHeight;q.chartWidth=Xa=v.width||Tc||600;q.chartHeight=Pa=v.height||(vc>19?vc:400);q.container=ta=fb(Lb,{className:"highcharts-container"+(v.className?" "+v.className:""),id:Od},qa({position:Pd,overflow:vb,width:Xa+$a,height:Pa+$a,textAlign:"left"},v.style),Qb||jb);q.renderer=aa=v.forExport?new Uc(ta,Xa,Pa,true):new Qd(ta,Xa,Pa);var m,h;if(Rd&&ta.getBoundingClientRect){m=function(){Ia(ta,{left:0,top:0});h=ta.getBoundingClientRect();Ia(ta,{left:-h.left%1+$a,top:-h.top% +1+$a})};m();Qa(ib,"resize",m);Qa(q,"destroy",function(){Cb(ib,"resize",m)})}}function F(){function m(){var x=v.width||jb.offsetWidth,w=v.height||jb.offsetHeight;if(x&&w){if(x!=Tc||w!=vc){clearTimeout(h);h=setTimeout(function(){rd(x,w,false)},100)}Tc=x;vc=w}}var h;Qa(window,"resize",m);Qa(q,"destroy",function(){Cb(window,"resize",m)})}function W(){var m=a.labels,h=a.credits,x;n();od=q.legend=new be(q);sc();t(ab,function(w){w.setTickPositions(true)});i();sc();pd();ic&&t(ab,function(w){w.render()}); +if(!q.seriesGroup)q.seriesGroup=aa.g("series-group").attr({zIndex:3}).add();t(Ba,function(w){w.translate();w.setTooltipPoints();w.render()});m.items&&t(m.items,function(){var w=qa(m.style,this.style),O=oa(w.left)+V,ja=oa(w.top)+ba+12;delete w.left;delete w.top;aa.text(this.html,O,ja).attr({zIndex:2}).css(w).add()});if(!q.toolbar)q.toolbar=d(q);if(h.enabled&&!q.credits){x=h.href;aa.text(h.text,0,0).on("click",function(){if(x)location.href=x}).attr({align:h.position.align,zIndex:8}).css(h.style).add().align(h.position)}Pc(); +q.hasRendered=true;if(Qb){jb.appendChild(ta);Fc(Qb)}}function ca(){var m=Ba.length,h=ta&&ta.parentNode;La(q,"destroy");Cb(ib,"unload",ca);Cb(q);for(t(ab,function(x){Cb(x)});m--;)Ba[m].destroy();if(ta){ta.innerHTML="";Cb(ta);h&&h.removeChild(ta);ta=null}if(aa)aa.alignedObjects=null;clearInterval(Nd);for(m in q)delete q[m]}function ka(){if(!wc&&ib==ib.top&&Aa.readyState!="complete")Aa.attachEvent("onreadystatechange",function(){Aa.detachEvent("onreadystatechange",ka);ka()});else{z();sd();td();t(a.series|| +[],function(m){g(m)});q.inverted=Ga=y(Ga,a.chart.inverted);j();q.render=W;q.tracker=gc=new f(q,a.tooltip);W();La(q,"load");b&&b.apply(q,[q]);t(q.callbacks,function(m){m.apply(q,[q])})}}Lc=ya(Lc,Sa.xAxis);hd=ya(hd,Sa.yAxis);Sa.xAxis=Sa.yAxis=null;a=ya(Sa,a);var v=a.chart,I=v.margin;I=Eb(I)?I:[I,I,I,I];var da=y(v.marginTop,I[0]),X=y(v.marginRight,I[1]),U=y(v.marginBottom,I[2]),R=y(v.marginLeft,I[3]),Ha=v.spacingTop,Ya=v.spacingRight,ud=v.spacingBottom,Vc=v.spacingLeft,uc,kc,tc,ba,Ab,rb,V,Pb,jb,Qb,ta, +Od,Tc,vc,Xa,Pa,jd,Oc,Wc,vd,wd,Xc,q=this,ae=(I=v.events)&&!!I.click,xd,hc,rc,nd,$b,Sd,yd,sa,wa,gc,Qc,Pc,od,Rb,Sb,qc,ic=v.showAxes,Rc=0,ab=[],Gb,Ba=[],Ga,aa,kd,Nd,ld,pd,sc,sd,td,rd,md,Td,be=function(m){function h(u,Z){var pa=u.legendItem,Na=u.legendLine,P=u.legendSymbol,K=M.color,gb=Z?L.itemStyle.color:K;K=Z?u.color:K;pa&&pa.css({fill:gb});Na&&Na.attr({stroke:K});P&&P.attr({stroke:K,fill:K})}function x(u,Z,pa){var Na=u.legendItem,P=u.legendLine,K=u.legendSymbol;u=u.checkbox;Na&&Na.attr({x:Z,y:pa}); +P&&P.translate(Z,pa-4);K&&K.attr({x:Z+K.xOff,y:pa+K.yOff});if(u){u.x=Z;u.y=pa}}function w(){t(bb,function(u){var Z=u.checkbox;Z&&Ia(Z,{left:Ka.attr("translateX")+u.legendItemWidth+Z.x-40+$a,top:Ka.attr("translateY")+Z.y-11+$a})})}function O(u){var Z,pa,Na,P,K,gb=u.legendItem;P=u.series||u;if(!gb){K=/^(bar|pie|area|column)$/.test(P.type);u.legendItem=gb=aa.text(L.labelFormatter.call(u),0,0).css(u.visible?ma:M).on("mouseover",function(){u.setState(yb);gb.css(Oa)}).on("mouseout",function(){gb.css(u.visible? +ma:M);u.setState()}).on("click",function(){var Vb=function(){u.setVisible()};u.firePointEvent?u.firePointEvent("legendItemClick",null,Vb):La(u,"legendItemClick",null,Vb)}).attr({zIndex:2}).add(Ka);if(!K&&u.options&&u.options.lineWidth){var cc=u.options;P={"stroke-width":cc.lineWidth,zIndex:2};if(cc.dashStyle)P.dashstyle=cc.dashStyle;u.legendLine=aa.path([Za,-Ea-ua,0,Da,-ua,0]).attr(P).add(Ka)}if(K)Z=aa.rect(pa=-Ea-ua,Na=-11,Ea,12,2).attr({"stroke-width":0,zIndex:3}).add(Ka);else if(u.options&&u.options.marker&& +u.options.marker.enabled)Z=aa.symbol(u.symbol,pa=-Ea/2-ua,Na=-4,u.options.marker.radius).attr(u.pointAttr[db]).attr({zIndex:3}).add(Ka);if(Z){Z.xOff=pa;Z.yOff=Na}u.legendSymbol=Z;h(u,u.visible);if(u.options&&u.options.showCheckbox){u.checkbox=fb("input",{type:"checkbox",checked:u.selected,defaultChecked:u.selected},L.itemCheckboxStyle,ta);Qa(u.checkbox,"click",function(Vb){La(u,"checkboxClick",{checked:Vb.target.checked},function(){u.select()})})}}Z=gb.getBBox();pa=u.legendItemWidth=L.itemWidth|| +Ea+ua+Z.width+ea;D=Z.height;if(ga&&Q-N+pa>(Hb||Xa-2*E-N)){Q=N;H+=D}A=H;x(u,Q,H);if(ga)Q+=pa;else H+=D;tb=Hb||Ca(ga?Q-N:pa,tb);bb.push(u)}function ja(){Q=N;H=s;A=tb=0;bb=[];Ka||(Ka=aa.g("legend").attr({zIndex:7}).add());Ta&&Fa.reverse();t(Fa,function(Na){if(Na.options.showInLegend)t(Na.options.legendType=="point"?Na.data:[Na],O)});Ta&&Fa.reverse();Rb=Hb||tb;Sb=A-s+D;if(xa||va){Rb+=2*E;Sb+=2*E;if(ha)Rb>0&&Sb>0&&ha.animate(ha.crisp(null,null,null,Rb,Sb));else ha=aa.rect(0,0,Rb,Sb,L.borderRadius,xa|| +0).attr({stroke:L.borderColor,"stroke-width":xa||0,fill:va||nb}).add(Ka).shadow(L.shadow);ha[bb.length?"show":"hide"]()}for(var u=["left","right","top","bottom"],Z,pa=4;pa--;){Z=u[pa];if(Ja[Z]&&Ja[Z]!="auto"){L[pa<2?"align":"verticalAlign"]=Z;L[pa<2?"x":"y"]=oa(Ja[Z])*(pa%2?-1:1)}}Ka.align(qa(L,{width:Rb,height:Sb}),true,uc);Rc||w()}var L=m.options.legend;if(L.enabled){var ga=L.layout=="horizontal",Ea=L.symbolWidth,ua=L.symbolPadding,bb,Ja=L.style,ma=L.itemStyle,Oa=L.itemHoverStyle,M=L.itemHiddenStyle, +E=oa(Ja.padding),ea=20,s=18,N=4+E+Ea+ua,Q,H,A,D=0,ha,xa=L.borderWidth,va=L.backgroundColor,Ka,tb,Hb=L.width,Fa=m.series,Ta=L.reversed;ja();Qa(m,"endResize",w);return{colorizeItem:h,destroyItem:function(u){var Z=u.checkbox;t(["legendItem","legendLine","legendSymbol"],function(pa){u[pa]&&u[pa].destroy()});Z&&Fc(u.checkbox)},renderLegend:ja}}};hc=function(m,h){return m>=0&&m<=wa&&h>=0&&h<=sa};Td=function(){La(q,"selection",{resetSelection:true},md);q.toolbar.remove("zoom")};md=function(m){var h=Sa.lang, +x=q.pointCount<100;q.toolbar.add("zoom",h.resetZoom,h.resetZoomTitle,Td);!m||m.resetSelection?t(ab,function(w){w.setExtremes(null,null,false,x)}):t(m.xAxis.concat(m.yAxis),function(w){var O=w.axis;if(q.tracker[O.isXAxis?"zoomX":"zoomY"])O.setExtremes(w.min,w.max,false,x)});k()};sc=function(){var m=a.legend,h=y(m.margin,10),x=m.x,w=m.y,O=m.align,ja=m.verticalAlign,L;sd();if((q.title||q.subtitle)&&!J(da))if(L=Ca(q.title&&!kc.floating&&!kc.verticalAlign&&kc.y||0,q.subtitle&&!tc.floating&&!tc.verticalAlign&& +tc.y||0))ba=Ca(ba,L+y(kc.margin,15)+Ha);if(m.enabled&&!m.floating)if(O=="right")J(X)||(Ab=Ca(Ab,Rb-x+h+Ya));else if(O=="left")J(R)||(V=Ca(V,Rb+x+h+Vc));else if(ja=="top")J(da)||(ba=Ca(ba,Sb+w+h+Ha));else if(ja=="bottom")J(U)||(rb=Ca(rb,Sb-w+h+ud));ic&&t(ab,function(ga){ga.getOffset()});J(R)||(V+=Pb[3]);J(da)||(ba+=Pb[0]);J(U)||(rb+=Pb[2]);J(X)||(Ab+=Pb[1]);td()};rd=function(m,h,x){var w=q.title,O=q.subtitle;Rc+=1;bc(x,q);Oc=Pa;jd=Xa;Xa=fa(m);Pa=fa(h);Ia(ta,{width:Xa+$a,height:Pa+$a});aa.setSize(Xa, +Pa,x);wa=Xa-V-Ab;sa=Pa-ba-rb;Gb=null;t(ab,function(ja){ja.isDirty=true;ja.setScale()});t(Ba,function(ja){ja.isDirty=true});q.isDirtyLegend=true;q.isDirtyBox=true;sc();w&&w.align(null,null,uc);O&&O.align(null,null,uc);k(x);Oc=null;La(q,"resize");setTimeout(function(){La(q,"endResize",null,function(){Rc-=1})},Bc&&Bc.duration||500)};td=function(){q.plotLeft=V=fa(V);q.plotTop=ba=fa(ba);q.plotWidth=wa=fa(Xa-V-Ab);q.plotHeight=sa=fa(Pa-ba-rb);q.plotSizeX=Ga?sa:wa;q.plotSizeY=Ga?wa:sa;uc={x:Vc,y:Ha,width:Xa- +Vc-Ya,height:Pa-Ha-ud}};sd=function(){ba=y(da,Ha);Ab=y(X,Ya);rb=y(U,ud);V=y(R,Vc);Pb=[0,0,0,0]};pd=function(){var m=v.borderWidth||0,h=v.backgroundColor,x=v.plotBackgroundColor,w=v.plotBackgroundImage,O,ja={x:V,y:ba,width:wa,height:sa};O=m+(v.shadow?8:0);if(m||h)if(Wc)Wc.animate(Wc.crisp(null,null,null,Xa-O,Pa-O));else Wc=aa.rect(O/2,O/2,Xa-O,Pa-O,v.borderRadius,m).attr({stroke:v.borderColor,"stroke-width":m,fill:h||nb}).add().shadow(v.shadow);if(x)if(vd)vd.animate(ja);else vd=aa.rect(V,ba,wa,sa, +0).attr({fill:x}).add().shadow(v.plotShadow);if(w)if(wd)wd.animate(ja);else wd=aa.image(w,V,ba,wa,sa).add();if(v.plotBorderWidth)if(Xc)Xc.animate(Xc.crisp(null,V,ba,wa,sa));else Xc=aa.rect(V,ba,wa,sa,0,v.plotBorderWidth).attr({stroke:v.plotBorderColor,"stroke-width":v.plotBorderWidth,zIndex:4}).add();q.isDirtyBox=false};Yc=Jb=0;Qa(ib,"unload",ca);v.reflow!==false&&Qa(q,"load",F);if(I)for(xd in I)Qa(q,xd,I[xd]);q.options=a;q.series=Ba;q.addSeries=function(m,h,x){var w;if(m){bc(x,q);h=y(h,true);La(q, +"addSeries",{options:m},function(){w=g(m);w.isDirty=true;q.isDirtyLegend=true;h&&q.redraw()})}return w};q.animation=y(v.animation,true);q.destroy=ca;q.get=function(m){var h,x,w;for(h=0;h-1,f=e?7:3,g;b=b.split(" ");c=[].concat(c);var i,k,j=function(n){for(g=n.length;g--;)n[g]==Za&&n.splice(g+1,0,n[g+1],n[g+2],n[g+1],n[g+2])};if(e){j(b);j(c)}if(a.isArea){i= +b.splice(b.length-6,6);k=c.splice(c.length-6,6)}if(d){c=[].concat(c).splice(0,f).concat(c);a.shift=false}if(b.length)for(a=c.length;b.length255)b[e]=255}}return this},setOpacity:function(d){b[3]=d;return this}}};Mc=function(a,b,c){function d(F){return F.toString().replace(/^([0-9])$/,"0$1")}if(!J(b)||isNaN(b))return"Invalid date";a=y(a,"%Y-%m-%d %H:%M:%S");b=new Date(b* +qb);var e=b[cd](),f=b[dd](),g=b[oc](),i=b[Dc](),k=b[Ec](),j=Sa.lang,n=j.weekdays;j=j.months;b={a:n[f].substr(0,3),A:n[f],d:d(g),e:g,b:j[i].substr(0,3),B:j[i],m:d(i+1),y:k.toString().substr(2,2),Y:k,H:d(e),I:d(e%12||12),l:e%12||12,M:d(b[bd]()),p:e<12?"AM":"PM",P:e<12?"am":"pm",S:d(b.getSeconds())};for(var z in b)a=a.replace("%"+z,b[z]);return c?a.substr(0,1).toUpperCase()+a.substr(1):a};Hc.prototype={init:function(a,b){this.element=Aa.createElementNS("http://www.w3.org/2000/svg",b);this.renderer=a}, +animate:function(a,b,c){if(b=y(b,Bc,true)){b=ya(b);if(c)b.complete=c;Zc(this,a,b)}else{this.attr(a);c&&c()}},attr:function(a,b){var c,d,e,f,g=this.element,i=g.nodeName,k=this.renderer,j,n=this.shadows,z,F=this;if(Kb(a)&&J(b)){c=a;a={};a[c]=b}if(Kb(a)){c=a;if(i=="circle")c={x:"cx",y:"cy"}[c]||c;else if(c=="strokeWidth")c="stroke-width";F=za(g,c)||this[c]||0;if(c!="d"&&c!="visibility")F=parseFloat(F)}else for(c in a){j=false;d=a[c];if(c=="d"){if(d&&d.join)d=d.join(" ");if(/(NaN| {2}|^$)/.test(d))d= +"M 0 0";this.d=d}else if(c=="x"&&i=="text"){for(e=0;eg||!J(g)&& +J(b))){d.insertBefore(f,a);return this}}d.appendChild(f);this.added=true;return this},destroy:function(){var a=this.element||{},b=this.shadows,c=a.parentNode,d;a.onclick=a.onmouseout=a.onmouseover=a.onmousemove=null;Sc(this);c&&c.removeChild(a);b&&t(b,function(e){(c=e.parentNode)&&c.removeChild(e)});mc(this.renderer.alignedObjects,this);for(d in this)delete this[d];return null},empty:function(){for(var a=this.element,b=a.childNodes,c=b.length;c--;)a.removeChild(b[c])},shadow:function(a){var b=[], +c,d=this.element,e=this.parentInverted?"(-1,-1)":"(1,1)";if(a){for(a=1;a<=3;a++){c=d.cloneNode(0);za(c,{isShadow:"true",stroke:"rgb(0, 0, 0)","stroke-opacity":0.05*a,"stroke-width":7-2*a,transform:"translate"+e,fill:nb});d.parentNode.insertBefore(c,d);b.push(c)}this.shadows=b}return this}};var Uc=function(){this.init.apply(this,arguments)};Uc.prototype={init:function(a,b,c,d){var e=location,f;this.Element=Hc;f=this.createElement("svg").attr({xmlns:"http://www.w3.org/2000/svg",version:"1.1"});a.appendChild(f.element); +this.box=f.element;this.boxWrapper=f;this.alignedObjects=[];this.url=Ac?"":e.href.replace(/#.*?$/,"");this.defs=this.createElement("defs").add();this.forExport=d;this.setSize(b,c,false)},createElement:function(a){var b=new this.Element;b.init(this,a);return b},buildText:function(a){for(var b=a.element,c=y(a.textStr,"").toString().replace(/<(b|strong)>/g,'').replace(/<(i|em)>/g,'').replace(//g, +"").split(/]?>/g),d=b.childNodes,e=/style="([^"]+)"/,f=/href="([^"]+)"/,g=za(b,"x"),i=a.styles,k=Rd&&i&&i.HcDirection=="rtl"&&!this.forExport,j,n=i&&oa(i.width),z=i&&i.lineHeight,F,W=d.length;W--;)b.removeChild(d[W]);n&&!a.added&&this.box.appendChild(b);t(c,function(ca,ka){var v,I=0,da;ca=ca.replace(//g,"|||");v=ca.split("|||");t(v,function(X){if(X!==""||v.length==1){var U={},R=Aa.createElementNS("http://www.w3.org/2000/svg","tspan");e.test(X)&& +za(R,"style",X.match(e)[1].replace(/(;| |^)color([ :])/,"$1fill$2"));if(f.test(X)){za(R,"onclick",'location.href="'+X.match(f)[1]+'"');Ia(R,{cursor:"pointer"})}X=X.replace(/<(.|\n)*?>/g,"")||" ";if(k){j=[];for(W=X.length;W--;)j.push(X.charAt(W));X=j.join("")}R.appendChild(Aa.createTextNode(X));if(I)U.dx=3;else U.x=g;if(!I){if(ka){da=oa(window.getComputedStyle(F,null).getPropertyValue("line-height"));if(isNaN(da))da=z||F.offsetHeight||18;za(R,"dy",da)}F=R}za(R,U);b.appendChild(R);I++;if(n){X=X.replace(/-/g, +"- ").split(" ");for(var Ha,Ya=[];X.length||Ya.length;){Ha=b.getBBox().width;U=Ha>n;if(!U||X.length==1){X=Ya;Ya=[];if(X.length){R=Aa.createElementNS("http://www.w3.org/2000/svg","tspan");za(R,{x:g,dy:z||16});b.appendChild(R);if(Ha>n)n=Ha}}else{R.removeChild(R.firstChild);Ya.unshift(X.pop())}R.appendChild(Aa.createTextNode(X.join(" ").replace(/- /g,"-")))}}}})})},crispLine:function(a,b){if(a[1]==a[4])a[1]=a[4]=fa(a[1])+b%2/2;if(a[2]==a[5])a[2]=a[5]=fa(a[2])+b%2/2;return a},path:function(a){return this.createElement("path").attr({d:a, +fill:nb})},circle:function(a,b,c){a=Eb(a)?a:{x:a,y:b,r:c};return this.createElement("circle").attr(a)},arc:function(a,b,c,d,e,f){if(Eb(a)){b=a.y;c=a.r;d=a.innerR;e=a.start;f=a.end;a=a.x}return this.symbol("arc",a||0,b||0,c||0,{innerR:d||0,start:e||0,end:f||0})},rect:function(a,b,c,d,e,f){if(Eb(a)){b=a.y;c=a.width;d=a.height;e=a.r;a=a.x}e=this.createElement("rect").attr({rx:e,ry:e,fill:nb});return e.attr(e.crisp(f,a,b,Ca(c,0),Ca(d,0)))},setSize:function(a,b,c){var d=this.alignedObjects,e=d.length; +this.width=a;this.height=b;for(this.boxWrapper[y(c,true)?"animate":"attr"]({width:a,height:b});e--;)d[e].align()},g:function(a){return this.createElement("g").attr(J(a)&&{"class":Zb+a})},image:function(a,b,c,d,e){var f={preserveAspectRatio:nb};arguments.length>1&&qa(f,{x:b,y:c,width:d,height:e});f=this.createElement("image").attr(f);f.element.setAttributeNS("http://www.w3.org/1999/xlink","href",a);return f},symbol:function(a,b,c,d,e){var f,g=this.symbols[a];g=g&&g(b,c,d,e);var i=/^url\((.*?)\)$/; +if(g){f=this.path(g);qa(f,{symbolName:a,x:b,y:c,r:d});e&&qa(f,e)}else if(i.test(a)){a=a.match(i)[1];f=this.image(a).attr({x:b,y:c});fb("img",{onload:function(){var k=de[this.src]||[this.width,this.height];f.attr({width:k[0],height:k[1]}).translate(-fa(k[0]/2),-fa(k[1]/2))},src:a})}else f=this.circle(b,c,d);return f},symbols:{square:function(a,b,c){c=0.707*c;return[Za,a-c,b-c,Da,a+c,b-c,a+c,b+c,a-c,b+c,"Z"]},triangle:function(a,b,c){return[Za,a,b-1.33*c,Da,a+c,b+0.67*c,a-c,b+0.67*c,"Z"]},"triangle-down":function(a, +b,c){return[Za,a,b+1.33*c,Da,a-c,b-0.67*c,a+c,b-0.67*c,"Z"]},diamond:function(a,b,c){return[Za,a,b-c,Da,a+c,b,a,b+c,a-c,b,"Z"]},arc:function(a,b,c,d){var e=d.start,f=d.end-1.0E-6,g=d.innerR,i=kb(e),k=zb(e),j=kb(f);f=zb(f);d=d.end-e');if(b){c=b==Lb||b=="span"||b=="img"?c.join(""): +a.prepVML(c);this.element=fb(c)}this.renderer=a},add:function(a){var b=this.renderer,c=this.element,d=b.box;d=a?a.element||a:d;a&&a.inverted&&b.invertChild(c,d);yc&&d.gVis==vb&&Ia(c,{visibility:vb});d.appendChild(c);this.added=true;this.alignOnAdd&&this.updateTransform();return this},attr:function(a,b){var c,d,e,f=this.element||{},g=f.style,i=f.nodeName,k=this.renderer,j=this.symbolName,n,z,F=this.shadows,W=this;if(Kb(a)&&J(b)){c=a;a={};a[c]=b}if(Kb(a)){c=a;W=c=="strokeWidth"||c=="stroke-width"?this.strokeweight: +this[c]}else for(c in a){d=a[c];n=false;if(j&&/^(x|y|r|start|end|width|height|innerR)/.test(c)){if(!z){this.symbolAttr(a);z=true}n=true}else if(c=="d"){d=d||[];this.d=d.join(" ");e=d.length;for(n=[];e--;)n[e]=ac(d[e])?fa(d[e]*10)-5:d[e]=="Z"?"x":d[e];d=n.join(" ")||"x";f.path=d;if(F)for(e=F.length;e--;)F[e].path=d;n=true}else if(c=="zIndex"||c=="visibility"){if(yc&&c=="visibility"&&i=="DIV"){f.gVis=d;n=f.childNodes;for(e=n.length;e--;)Ia(n[e],{visibility:d});if(d==Bb)d=null}if(d)g[c]=d;n=true}else if(/^(width|height)$/.test(c)){if(this.updateClipping){this[c]= +d;this.updateClipping()}else g[c]=d;n=true}else if(/^(x|y)$/.test(c)){this[c]=d;if(f.tagName=="SPAN")this.updateTransform();else g[{x:"left",y:"top"}[c]]=d}else if(c=="class")f.className=d;else if(c=="stroke"){d=k.color(d,f,c);c="strokecolor"}else if(c=="stroke-width"||c=="strokeWidth"){f.stroked=d?true:false;c="strokeweight";this[c]=d;if(ac(d))d+=$a}else if(c=="dashstyle"){(f.getElementsByTagName("stroke")[0]||fb(k.prepVML([""]),null,null,f))[c]=d||"solid";this.dashstyle=d;n=true}else if(c== +"fill")if(i=="SPAN")g.color=d;else{f.filled=d!=nb?true:false;d=k.color(d,f,c);c="fillcolor"}else if(c=="translateX"||c=="translateY"||c=="rotation"||c=="align"){if(c=="align")c="textAlign";this[c]=d;this.updateTransform();n=true}else if(c=="text"){f.innerHTML=d;n=true}if(F&&c=="visibility")for(e=F.length;e--;)F[e].style[c]=d;if(!n)if(yc)f[c]=d;else za(f,c,d)}return W},clip:function(a){var b=this,c=a.members;c.push(b);b.destroyClip=function(){mc(c,b)};return b.css(a.getCSS(b.inverted))},css:function(a){var b= +this.element;if(b=a&&b.tagName=="SPAN"&&a.width){delete a.width;this.textWidth=b;this.updateTransform()}this.styles=qa(this.styles,a);Ia(this.element,a);return this},destroy:function(){this.destroyClip&&this.destroyClip();Hc.prototype.destroy.apply(this)},empty:function(){for(var a=this.element.childNodes,b=a.length,c;b--;){c=a[b];c.parentNode.removeChild(c)}},getBBox:function(){var a=this.element;if(a.nodeName=="text")a.style.position=lc;return{x:a.offsetLeft,y:a.offsetTop,width:a.offsetWidth,height:a.offsetHeight}}, +on:function(a,b){this.element["on"+a]=function(){var c=ib.event;c.target=c.srcElement;b(c)};return this},updateTransform:function(){if(this.added){var a=this,b=a.element,c=a.translateX||0,d=a.translateY||0,e=a.x||0,f=a.y||0,g=a.textAlign||"left",i={left:0,center:0.5,right:1}[g],k=g&&g!="left";if(c||d)a.css({marginLeft:c,marginTop:d});a.inverted&&t(b.childNodes,function(I){a.renderer.invertChild(I,b)});if(b.tagName=="SPAN"){var j,n;c=a.rotation;var z;j=0;d=1;var F=0,W;z=oa(a.textWidth);var ca=a.xCorr|| +0,ka=a.yCorr||0,v=[c,g,b.innerHTML,a.textWidth].join(",");if(v!=a.cTT){if(J(c)){j=c*Ud;d=kb(j);F=zb(j);Ia(b,{filter:c?["progid:DXImageTransform.Microsoft.Matrix(M11=",d,", M12=",-F,", M21=",F,", M22=",d,", sizingMethod='auto expand')"].join(""):nb})}j=b.offsetWidth;n=b.offsetHeight;if(j>z){Ia(b,{width:z+$a,display:"block",whiteSpace:"normal"});j=z}z=fa(oa(b.style.fontSize||12)*1.2);ca=d<0&&-j;ka=F<0&&-n;W=d*F<0;ca+=F*z*(W?1-i:i);ka-=d*z*(c?W?i:1-i:1);if(k){ca-=j*i*(d<0?-1:1);if(c)ka-=n*i*(F<0?-1: +1);Ia(b,{textAlign:g})}a.xCorr=ca;a.yCorr=ka}Ia(b,{left:e+ca,top:f+ka});a.cTT=v}}else this.alignOnAdd=true},shadow:function(a){var b=[],c=this.element,d=this.renderer,e,f=c.style,g,i=c.path;if(""+c.path==="")i="x";if(a){for(a=1;a<=3;a++){g=[''];e=fb(d.prepVML(g),null,{left:oa(f.left)+1,top:oa(f.top)+1});g=[''];fb(d.prepVML(g),null, +null,e);c.parentNode.insertBefore(e,c);b.push(e)}this.shadows=b}return this}});Ma=function(){this.init.apply(this,arguments)};Ma.prototype=ya(Uc.prototype,{isIE8:xc.indexOf("MSIE 8.0")>-1,init:function(a,b,c){var d;this.Element=ge;this.alignedObjects=[];d=this.createElement(Lb);a.appendChild(d.element);this.box=d.element;this.boxWrapper=d;this.setSize(b,c,false);if(!Aa.namespaces.hcv){Aa.namespaces.add("hcv","urn:schemas-microsoft-com:vml");Aa.createStyleSheet().cssText="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}}, +clipRect:function(a,b,c,d){var e=this.createElement();return qa(e,{members:[],left:a,top:b,width:c,height:d,getCSS:function(f){var g=this.top,i=this.left,k=i+this.width,j=g+this.height;g={clip:"rect("+fa(f?i:g)+"px,"+fa(f?j:k)+"px,"+fa(f?k:j)+"px,"+fa(f?g:i)+"px)"};!f&&yc&&qa(g,{width:k+$a,height:j+$a});return g},updateClipping:function(){t(e.members,function(f){f.css(e.getCSS(f.inverted))})}})},color:function(a,b,c){var d,e=/^rgba/;if(a&&a.linearGradient){var f,g,i=a.linearGradient,k,j,n,z;t(a.stops, +function(F,W){if(e.test(F[1])){d=Ub(F[1]);f=d.get("rgb");g=d.get("a")}else{f=F[1];g=1}if(W){n=f;z=g}else{k=f;j=g}});a=90-Ua.atan((i[3]-i[1])/(i[2]-i[0]))*180/Tb;c=["<",c,' colors="0% ',k,",100% ",n,'" angle="',a,'" opacity="',z,'" o:opacity2="',j,'" type="gradient" focus="100%" />'];fb(this.prepVML(c),null,null,b)}else if(e.test(a)&&b.tagName!="IMG"){d=Ub(a);c=["<",c,' opacity="',d.get("a"),'"/>'];fb(this.prepVML(c),null,null,b);return d.get("rgb")}else return a},prepVML:function(a){var b=this.isIE8; +a=a.join("");if(b){a=a.replace("/>",' xmlns="urn:schemas-microsoft-com:vml" />');a=a.indexOf('style="')==-1?a.replace("/>",' style="display:inline-block;behavior:url(#default#VML);" />'):a.replace('style="','style="display:inline-block;behavior:url(#default#VML);')}else a=a.replace("<","1&&f.css({left:b,top:c,width:d,height:e});return f},rect:function(a,b,c,d,e,f){if(Eb(a)){b=a.y;c=a.width;d=a.height;e=a.r;a=a.x}var g=this.symbol("rect");g.r=e;return g.attr(g.crisp(f,a,b,Ca(c,0),Ca(d,0)))},invertChild:function(a,b){var c=b.style; +Ia(a,{flip:"x",left:oa(c.width)-10,top:oa(c.height)-10,rotation:-90})},symbols:{arc:function(a,b,c,d){var e=d.start,f=d.end,g=kb(e),i=zb(e),k=kb(f),j=zb(f);d=d.innerR;var n=0.07/c,z=d&&0.1/d||0;if(f-e===0)return["x"];else if(2*Tb-f+e=c.length)Jb=0}a.chart.pointCount++;return this},applyOptions:function(a){var b=this.series;this.config=a;if(ac(a)||a===null)this.y=a;else if(Eb(a)&&!ac(a.length)){qa(this,a);this.options=a}else if(Kb(a[0])){this.name=a[0];this.y=a[1]}else if(ac(a[0])){this.x=a[0];this.y=a[1]}if(this.x===Ra)this.x=b.autoIncrement()},destroy:function(){var a=this,b=a.series,c;b.chart.pointCount--;a==b.chart.hoverPoint&&a.onMouseOut();b.chart.hoverPoints=null;Cb(a);t(["graphic","tracker","group","dataLabel", +"connector"],function(d){a[d]&&a[d].destroy()});a.legendItem&&a.series.chart.legend.destroyItem(a);for(c in a)a[c]=null},select:function(a,b){var c=this,d=c.series.chart;c.selected=a=y(a,!c.selected);c.firePointEvent(a?"select":"unselect");c.setState(a&&"select");b||t(d.getSelectedPoints(),function(e){if(e.selected&&e!=c){e.selected=false;e.setState(db);e.firePointEvent("unselect")}})},onMouseOver:function(){var a=this.series.chart,b=a.tooltip,c=a.hoverPoint;c&&c!=this&&c.onMouseOut();this.firePointEvent("mouseOver"); +b&&!b.shared&&b.refresh(this);this.setState(yb);a.hoverPoint=this},onMouseOut:function(){this.firePointEvent("mouseOut");this.setState();this.series.chart.hoverPoint=null},tooltipFormatter:function(a){var b=this.series;return['',this.name||b.name,": ",!a?"x = "+(this.name||this.x)+", ":"","",!a?"y = ":"",this.y,"
"].join("")},getDataLabelText:function(){return this.series.options.dataLabels.formatter.call({x:this.x,y:this.y,series:this.series, +point:this,percentage:this.percentage,total:this.total||this.stackTotal})},update:function(a,b,c){var d=this,e=d.series,f=d.dataLabel,g=d.graphic,i=e.chart;b=y(b,true);d.firePointEvent("update",{options:a},function(){d.applyOptions(a);f&&f.attr({text:d.getDataLabelText()});if(Eb(a)){e.getAttribs();g&&g.attr(d.pointAttr[e.state])}e.isDirty=true;b&&i.redraw(c)})},remove:function(a,b){var c=this,d=c.series,e=d.chart,f=d.data;bc(b,e);a=y(a,true);c.firePointEvent("remove",null,function(){mc(f,c);c.destroy(); +d.isDirty=true;a&&e.redraw()})},firePointEvent:function(a,b,c){var d=this,e=this.series.options;if(e.point.events[a]||d.options&&d.options.events&&d.options.events[a])this.importEvents();if(a=="click"&&e.allowPointSelect)c=function(f){d.select(null,f.ctrlKey||f.metaKey||f.shiftKey)};La(this,a,b,c)},importEvents:function(){if(!this.hasImportedEvents){var a=ya(this.series.options.point,this.options).events,b;this.events=a;for(b in a)Qa(this,b,a[b]);this.hasImportedEvents=true}},setState:function(a){var b= +this.series,c=b.options.states,d=wb[b.type].marker&&b.options.marker,e=d&&!d.enabled,f=(d=d&&d.states[a])&&d.enabled===false,g=b.stateMarkerGraphic,i=b.chart,k=this.pointAttr;a||(a=db);if(!(a==this.state||this.selected&&a!="select"||c[a]&&c[a].enabled===false||a&&(f||e&&!d.enabled))){if(this.graphic)this.graphic.attr(k[a]);else{if(a){if(!g)b.stateMarkerGraphic=g=i.renderer.circle(0,0,k[a].r).attr(k[a]).add(b.group);g.translate(this.plotX,this.plotY)}if(g)g[a?"show":"hide"]()}this.state=a}}};var mb= +function(){};mb.prototype={isCartesian:true,type:"line",pointClass:zc,pointAttrToOptions:{stroke:"lineColor","stroke-width":"lineWidth",fill:"fillColor",r:"radius"},init:function(a,b){var c,d;d=a.series.length;this.chart=a;b=this.setOptions(b);qa(this,{index:d,options:b,name:b.name||"Series "+(d+1),state:db,pointAttr:{},visible:b.visible!==false,selected:b.selected===true});d=b.events;for(c in d)Qa(this,c,d[c]);if(d&&d.click||b.point&&b.point.events&&b.point.events.click||b.allowPointSelect)a.runTrackerClick= +true;this.getColor();this.getSymbol();this.setData(b.data,false)},autoIncrement:function(){var a=this.options,b=this.xIncrement;b=y(b,a.pointStart,0);this.pointInterval=y(this.pointInterval,a.pointInterval,1);this.xIncrement=b+this.pointInterval;return b},cleanData:function(){var a=this.chart,b=this.data,c,d,e=a.smallestInterval,f,g;b.sort(function(i,k){return i.x-k.x});for(g=b.length-1;g>=0;g--)b[g-1]&&b[g-1].x==b[g].x&&b.splice(g-1,1);for(g=b.length-1;g>=0;g--)if(b[g-1]){f=b[g].x-b[g-1].x;if(d=== +Ra||fa+1&&b.push(c.slice(a+1,e));a=e}else e==c.length-1&&b.push(c.slice(a+1,e+1))});this.segments=b},setOptions:function(a){var b=this.chart.options.plotOptions;return ya(b[this.type],b.series,a)},getColor:function(){var a=this.chart.options.colors;this.color=this.options.color||a[Jb++]||"#0000ff";if(Jb>=a.length)Jb=0},getSymbol:function(){var a= +this.chart.options.symbols;this.symbol=this.options.marker.symbol||a[Yc++];if(Yc>=a.length)Yc=0},addPoint:function(a,b,c,d){var e=this.data,f=this.graph,g=this.area,i=this.chart;a=(new this.pointClass).init(this,a);bc(d,i);if(f&&c)f.shift=c;if(g){g.shift=c;g.isArea=true}b=y(b,true);e.push(a);c&&e[0].remove(false);this.isDirty=true;b&&i.redraw()},setData:function(a,b){var c=this,d=c.data,e=c.initialColor,f=c.chart,g=d&&d.length||0;c.xIncrement=null;if(J(e))Jb=e;for(a=jc(nc(a||[]),function(i){return(new c.pointClass).init(c, +i)});g--;)d[g].destroy();c.data=a;c.cleanData();c.getSegments();c.isDirty=true;f.isDirtyBox=true;y(b,true)&&f.redraw(false)},remove:function(a,b){var c=this,d=c.chart;a=y(a,true);if(!c.isRemoving){c.isRemoving=true;La(c,"remove",null,function(){c.destroy();d.isDirtyLegend=d.isDirtyBox=true;a&&d.redraw(b)})}c.isRemoving=false},translate:function(){for(var a=this.chart,b=this.options.stacking,c=this.xAxis.categories,d=this.yAxis,e=this.data,f=e.length;f--;){var g=e[f],i=g.x,k=g.y,j=g.low,n=d.stacks[(k< +0?"-":"")+this.stackKey];g.plotX=this.xAxis.translate(i);if(b&&this.visible&&n&&n[i]){j=n[i];i=j.total;j.cum=j=j.cum-k;k=j+k;if(b=="percent"){j=i?j*100/i:0;k=i?k*100/i:0}g.percentage=i?g.y*100/i:0;g.stackTotal=i}if(J(j))g.yBottom=d.translate(j,0,1);if(k!==null)g.plotY=d.translate(k,0,1);g.clientX=a.inverted?a.plotHeight-g.plotX:g.plotX;g.category=c&&c[g.x]!==Ra?c[g.x]:g.x}},setTooltipPoints:function(a){var b=this.chart,c=b.inverted,d=[],e=fa((c?b.plotTop:b.plotLeft)+b.plotSizeX),f,g,i=[];if(a)this.tooltipPoints= +null;t(this.segments,function(k){d=d.concat(k)});if(this.xAxis&&this.xAxis.reversed)d=d.reverse();t(d,function(k,j){f=d[j-1]?d[j-1].high+1:0;for(g=k.high=d[j+1]?ob((k.plotX+(d[j+1]?d[j+1].plotX:e))/2):e;f<=g;)i[c?e-f++:f++]=k});this.tooltipPoints=i},onMouseOver:function(){var a=this.chart,b=a.hoverSeries;if(!(!Ib&&a.mouseIsDown)){b&&b!=this&&b.onMouseOut();this.options.events.mouseOver&&La(this,"mouseOver");this.tracker&&this.tracker.toFront();this.setState(yb);a.hoverSeries=this}},onMouseOut:function(){var a= +this.options,b=this.chart,c=b.tooltip,d=b.hoverPoint;d&&d.onMouseOut();this&&a.events.mouseOut&&La(this,"mouseOut");c&&!a.stickyTracking&&c.hide();this.setState();b.hoverSeries=null},animate:function(a){var b=this.chart,c=this.clipRect,d=this.options.animation;if(d&&!Eb(d))d={};if(a){if(!c.isAnimating){c.attr("width",0);c.isAnimating=true}}else{c.animate({width:b.plotSizeX},d);this.animate=null}},drawPoints:function(){var a,b=this.data,c=this.chart,d,e,f,g,i,k;if(this.options.marker.enabled)for(f= +b.length;f--;){g=b[f];d=g.plotX;e=g.plotY;k=g.graphic;if(e!==Ra&&!isNaN(e)){a=g.pointAttr[g.selected?"select":db];i=a.r;if(k)k.animate({x:d,y:e,r:i});else g.graphic=c.renderer.symbol(y(g.marker&&g.marker.symbol,this.symbol),d,e,i).attr(a).add(this.group)}}},convertAttribs:function(a,b,c,d){var e=this.pointAttrToOptions,f,g,i={};a=a||{};b=b||{};c=c||{};d=d||{};for(f in e){g=e[f];i[f]=y(a[g],b[f],c[f],d[f])}return i},getAttribs:function(){var a=this,b=wb[a.type].marker?a.options.marker:a.options,c= +b.states,d=c[yb],e,f=a.color,g={stroke:f,fill:f},i=a.data,k=[],j,n=a.pointAttrToOptions;if(a.options.marker){d.radius=d.radius||b.radius+2;d.lineWidth=d.lineWidth||b.lineWidth+1}else d.color=d.color||Ub(d.color||f).brighten(d.brightness).get();k[db]=a.convertAttribs(b,g);t([yb,"select"],function(F){k[F]=a.convertAttribs(c[F],k[db])});a.pointAttr=k;for(f=i.length;f--;){g=i[f];if((b=g.options&&g.options.marker||g.options)&&b.enabled===false)b.radius=0;e=false;if(g.options)for(var z in n)if(J(b[n[z]]))e= +true;if(e){j=[];c=b.states||{};e=c[yb]=c[yb]||{};if(!a.options.marker)e.color=Ub(e.color||g.options.color).brighten(e.brightness||d.brightness).get();j[db]=a.convertAttribs(b,k[db]);j[yb]=a.convertAttribs(c[yb],k[yb],j[db]);j.select=a.convertAttribs(c.select,k.select,j[db])}else j=k;g.pointAttr=j}},destroy:function(){var a=this,b=a.chart,c=/\/5[0-9\.]+ (Safari|Mobile)\//.test(xc),d,e;Cb(a);a.legendItem&&a.chart.legend.destroyItem(a);t(a.data,function(f){f.destroy()});t(["area","graph","dataLabelsGroup", +"group","tracker"],function(f){if(a[f]){d=c&&f=="group"?"hide":"destroy";a[f][d]()}});if(b.hoverSeries==a)b.hoverSeries=null;mc(b.series,a);for(e in a)delete a[e]},drawDataLabels:function(){if(this.options.dataLabels.enabled){var a,b,c=this.data,d=this.options.dataLabels,e,f=this.dataLabelsGroup,g=this.chart,i=g.inverted,k=this.type,j;if(!f)f=this.dataLabelsGroup=g.renderer.g(Zb+"data-labels").attr({visibility:this.visible?Bb:vb,zIndex:5}).translate(g.plotLeft,g.plotTop).add();j=d.color;if(j=="auto")j= +null;d.style.color=y(j,this.color);t(c,function(n){var z=n.barX;z=z&&z+n.barW/2||n.plotX||-999;var F=y(n.plotY,-999),W=n.dataLabel,ca=d.align;e=n.getDataLabelText();a=(i?g.plotWidth-F:z)+d.x;b=(i?g.plotHeight-z:F)+d.y;if(k=="column")a+={left:-1,right:1}[ca]*n.barW/2||0;if(W)W.animate({x:a,y:b});else if(J(e))W=n.dataLabel=g.renderer.text(e,a,b).attr({align:ca,rotation:d.rotation,zIndex:1}).css(d.style).add(f);i&&!d.y&&W.attr({y:b+parseInt(W.styles.lineHeight)*0.9-W.getBBox().height/2})})}},drawGraph:function(){var a= +this,b=a.options,c=a.graph,d=[],e,f=a.area,g=a.group,i=b.lineColor||a.color,k=b.lineWidth,j=b.dashStyle,n,z=a.chart.renderer,F=a.yAxis.getThreshold(b.threshold||0),W=/^area/.test(a.type),ca=[],ka=[];t(a.segments,function(v){n=[];t(v,function(U,R){if(a.getPointSpline)n.push.apply(n,a.getPointSpline(v,U,R));else{n.push(R?Da:Za);R&&b.step&&n.push(U.plotX,v[R-1].plotY);n.push(U.plotX,U.plotY)}});if(v.length>1)d=d.concat(n);else ca.push(v[0]);if(W){var I=[],da,X=n.length;for(da=0;da=0;da--)I.push(v[da].plotX,v[da].yBottom);else I.push(Da,v[v.length-1].plotX,F,Da,v[0].plotX,F);ka=ka.concat(I)}});a.graphPath=d;a.singlePoints=ca;if(W){e=y(b.fillColor,Ub(a.color).setOpacity(b.fillOpacity||0.75).get());if(f)f.animate({d:ka});else a.area=a.chart.renderer.path(ka).attr({fill:e}).add(g)}if(c)c.animate({d:d});else if(k){c={stroke:i,"stroke-width":k};if(j)c.dashstyle=j;a.graph=z.path(d).attr(c).add(g).shadow(b.shadow)}}, +render:function(){var a=this,b=a.chart,c,d,e=a.options,f=e.animation,g=f&&a.animate;f=g?f&&f.duration||500:0;var i=a.clipRect;d=b.renderer;if(!i){i=a.clipRect=!b.hasRendered&&b.clipRect?b.clipRect:d.clipRect(0,0,b.plotSizeX,b.plotSizeY);if(!b.clipRect)b.clipRect=i}if(!a.group){c=a.group=d.g("series");if(b.inverted){d=function(){c.attr({width:b.plotWidth,height:b.plotHeight}).invert()};d();Qa(b,"resize",d)}c.clip(a.clipRect).attr({visibility:a.visible?Bb:vb,zIndex:e.zIndex}).translate(b.plotLeft,b.plotTop).add(b.seriesGroup)}a.drawDataLabels(); +g&&a.animate(true);a.getAttribs();a.drawGraph&&a.drawGraph();a.drawPoints();a.options.enableMouseTracking!==false&&a.drawTracker();g&&a.animate();setTimeout(function(){i.isAnimating=false;if((c=a.group)&&i!=b.clipRect&&i.renderer){c.clip(a.clipRect=b.clipRect);i.destroy()}},f);a.isDirty=false},redraw:function(){var a=this.chart,b=this.group;if(b){a.inverted&&b.attr({width:a.plotWidth,height:a.plotHeight});b.animate({translateX:a.plotLeft,translateY:a.plotTop})}this.translate();this.setTooltipPoints(true); +this.render()},setState:function(a){var b=this.options,c=this.graph,d=b.states;b=b.lineWidth;a=a||db;if(this.state!=a){this.state=a;if(!(d[a]&&d[a].enabled===false)){if(a)b=d[a].lineWidth||b+1;if(c&&!c.dashstyle)c.attr({"stroke-width":b},a?0:500)}}},setVisible:function(a,b){var c=this.chart,d=this.legendItem,e=this.group,f=this.tracker,g=this.dataLabelsGroup,i,k=this.data,j=c.options.chart.ignoreHiddenSeries;i=this.visible;i=(this.visible=a=a===Ra?!i:a)?"show":"hide";e&&e[i]();if(f)f[i]();else for(e= +k.length;e--;){f=k[e];f.tracker&&f.tracker[i]()}g&&g[i]();d&&c.legend.colorizeItem(this,a);this.isDirty=true;this.options.stacking&&t(c.series,function(n){if(n.options.stacking&&n.visible)n.isDirty=true});if(j)c.isDirtyBox=true;b!==false&&c.redraw();La(this,i)},show:function(){this.setVisible(true)},hide:function(){this.setVisible(false)},select:function(a){this.selected=a=a===Ra?!this.selected:a;if(this.checkbox)this.checkbox.checked=a;La(this,a?"select":"unselect")},drawTracker:function(){var a= +this,b=a.options,c=[].concat(a.graphPath),d=c.length,e=a.chart,f=e.options.tooltip.snap,g=a.tracker,i=b.cursor;i=i&&{cursor:i};var k=a.singlePoints,j;if(d)for(j=d+1;j--;){c[j]==Za&&c.splice(j+1,0,c[j+1]-f,c[j+2],Da);if(j&&c[j]==Za||j==d)c.splice(j,0,Da,c[j-2]+f,c[j-1])}for(j=0;j +a&&k>e){k=Ca(a,e);n=2*e-k}else if(kg&&n>e){n=Ca(g,e);k=2*e-n}else if(nv?X-v:ka-(da<=ka?v:0)}Ya=R-3}qa(I,{barX:U,barY:R,barW:W,barH:Ha});I.shapeType="rect";I.shapeArgs={x:U,y:R,width:W,height:Ha,r:k.borderRadius}; +I.trackerArgs=J(Ya)&&ya(I.shapeArgs,{height:Ca(6,Ha+3),y:Ya})})},getSymbol:function(){},drawGraph:function(){},drawPoints:function(){var a=this,b=a.options,c=a.chart.renderer,d,e;t(a.data,function(f){var g=f.plotY;if(g!==Ra&&!isNaN(g)){d=f.graphic;e=f.shapeArgs;if(d){Sc(d);d.animate(e)}else f.graphic=c[f.shapeType](e).attr(f.pointAttr[f.selected?"select":db]).add(a.group).shadow(b.shadow)}})},drawTracker:function(){var a=this,b=a.chart,c=b.renderer,d,e,f=+new Date,g=a.options.cursor,i=g&&{cursor:g}, +k;t(a.data,function(j){e=j.tracker;d=j.trackerArgs||j.shapeArgs;if(j.y!==null)if(e)e.attr(d);else j.tracker=c[j.shapeType](d).attr({isTracker:f,fill:Vd,visibility:a.visible?Bb:vb,zIndex:1}).on(Ib?"touchstart":"mouseover",function(n){k=n.relatedTarget||n.fromElement;b.hoverSeries!=a&&za(k,"isTracker")!=f&&a.onMouseOver();j.onMouseOver()}).on("mouseout",function(n){if(!a.options.stickyTracking){k=n.relatedTarget||n.toElement;za(k,"isTracker")!=f&&a.onMouseOut()}}).css(i).add(b.trackerGroup)})},animate:function(a){var b= +this,c=b.data;if(!a){t(c,function(d){var e=d.graphic;if(e){e.attr({height:0,y:b.yAxis.translate(0,0,1)});e.animate({height:d.barH,y:d.barY},b.options.animation)}});b.animate=null}},remove:function(){var a=this,b=a.chart;b.hasRendered&&t(b.series,function(c){if(c.type==a.type)c.isDirty=true});mb.prototype.remove.apply(a,arguments)}});ub.column=ad;Ma=xb(ad,{type:"bar",init:function(a){a.inverted=this.inverted=true;ad.prototype.init.apply(this,arguments)}});ub.bar=Ma;Ma=xb(mb,{type:"scatter",translate:function(){var a= +this;mb.prototype.translate.apply(a);t(a.data,function(b){b.shapeType="circle";b.shapeArgs={x:b.plotX,y:b.plotY,r:a.chart.options.tooltip.snap}})},drawTracker:function(){var a=this,b=a.options.cursor,c=b&&{cursor:b},d;t(a.data,function(e){(d=e.graphic)&&d.attr({isTracker:true}).on("mouseover",function(){a.onMouseOver();e.onMouseOver()}).on("mouseout",function(){a.options.stickyTracking||a.onMouseOut()}).css(c)})},cleanData:function(){}});ub.scatter=Ma;Ma=xb(zc,{init:function(){zc.prototype.init.apply(this, +arguments);var a=this,b;qa(a,{visible:a.visible!==false,name:y(a.name,"Slice")});b=function(){a.slice()};Qa(a,"select",b);Qa(a,"unselect",b);return a},setVisible:function(a){var b=this.series.chart,c=this.tracker,d=this.dataLabel,e=this.connector,f;f=(this.visible=a=a===Ra?!this.visible:a)?"show":"hide";this.group[f]();c&&c[f]();d&&d[f]();e&&e[f]();this.legendItem&&b.legend.colorizeItem(this,a)},slice:function(a,b,c){var d=this.series.chart,e=this.slicedTranslation;bc(c,d);y(b,true);a=this.sliced= +J(a)?a:!this.sliced;this.group.animate({translateX:a?e[0]:d.plotLeft,translateY:a?e[1]:d.plotTop})}});Ma=xb(mb,{type:"pie",isCartesian:false,pointClass:Ma,pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color"},getColor:function(){this.initialColor=Jb},animate:function(){var a=this;t(a.data,function(b){var c=b.graphic;b=b.shapeArgs;var d=-Tb/2;if(c){c.attr({r:0,start:d,end:d});c.animate({r:b.r,start:b.start,end:b.end},a.options.animation)}});a.animate=null},translate:function(){var a= +0,b=-0.25,c=this.options,d=c.slicedOffset,e=d+c.borderWidth,f=c.center,g=this.chart,i=g.plotWidth,k=g.plotHeight,j,n,z,F=this.data,W=2*Tb,ca,ka=pb(i,k),v,I,da,X=c.dataLabels.distance;f.push(c.size,c.innerSize||0);f=jc(f,function(U,R){return(v=/%$/.test(U))?[i,k,ka,ka][R]*oa(U)/100:U});this.getX=function(U,R){z=Ua.asin((U-f[1])/(f[2]/2+X));return f[0]+(R?-1:1)*kb(z)*(f[2]/2+X)};this.center=f;t(F,function(U){a+=U.y});t(F,function(U){ca=a?U.y/a:0;j=fa(b*W*1E3)/1E3;b+=ca;n=fa(b*W*1E3)/1E3;U.shapeType= +"arc";U.shapeArgs={x:f[0],y:f[1],r:f[2]/2,innerR:f[3]/2,start:j,end:n};z=(n+j)/2;U.slicedTranslation=jc([kb(z)*d+g.plotLeft,zb(z)*d+g.plotTop],fa);I=kb(z)*f[2]/2;da=zb(z)*f[2]/2;U.tooltipPos=[f[0]+I*0.7,f[1]+da*0.7];U.labelPos=[f[0]+I+kb(z)*X,f[1]+da+zb(z)*X,f[0]+I+kb(z)*e,f[1]+da+zb(z)*e,f[0]+I,f[1]+da,X<0?"center":z0,j,n,z=this.center[1],F=[[],[],[],[]],W,ca,ka,v,I,da,X,U=4,R;mb.prototype.drawDataLabels.apply(this);t(a,function(Ha){var Ya=Ha.labelPos[7];F[Ya<0?0:YaYa.y};U--;){a=0;b=[].concat(F[U]);b.sort(X);for(R=b.length;R--;)b[R].rank=R;for(v=0;v<2;v++){n=(da=U%3)?9999:-9999;I=da?-1:1;for(R=0;Rn-j){ca=n+I*j;W=this.getX(ca,U>1);if(!da&&ca+j>z||da&&ca-j 0); + } +}); + diff --git a/app/javascripts/info_boxes.js b/app/javascripts/info_boxes.js new file mode 100644 index 000000000..ffcd46239 --- /dev/null +++ b/app/javascripts/info_boxes.js @@ -0,0 +1,3 @@ +//= require_tree ../templates/main_page/profiles +//= require info_boxes/base +//= require_tree ./info_boxes diff --git a/app/javascripts/info_boxes/account_profile.js b/app/javascripts/info_boxes/account_profile.js new file mode 100644 index 000000000..5de839bc2 --- /dev/null +++ b/app/javascripts/info_boxes/account_profile.js @@ -0,0 +1,37 @@ +var AccountProfileBox = Profile.extend({ + template: "main_page/profiles/account-profile", + className: "profile", + + comma: function(item) { + return " " + item; + }, + + avatarUrl: function() { return this.model.get('avatar_url'); }, + + editLink: function() { return this.options.account.link("edit"); }, + + fullName: function() { return this.model.get("name"); }, + + shortName: function() { return this.model.get("first_name"); }, + + about: function() { return this.model.get('about'); }, + + interests: function() { return _.map(this.model.get('interests'), this.comma); }, + + skills: function() { return _.map(this.model.get("skills"), this.comma); }, + + goods: function() { return _.map(this.model.get("goods"), this.comma); }, + + subscriptions: function() { return this.model.get('subscriptions'); }, + + groups: function() { return ""; }, + + hasInterests: function() { return this.model.get("interests").length > 0; }, + + hasSkills: function() { return this.model.get("skills").length > 0; }, + + hasGoods: function() { return this.model.get("goods").length > 0; }, + + hasAbout: function() { return this.model.get("about") != undefined; } + +}); diff --git a/app/javascripts/info_boxes/base.js b/app/javascripts/info_boxes/base.js new file mode 100644 index 000000000..d38ad8111 --- /dev/null +++ b/app/javascripts/info_boxes/base.js @@ -0,0 +1,339 @@ +var InfoListItem = CommonPlace.View.extend({ + template: "main_page/info-list", + tagName: "li", + events: { + "click": "switchProfile" + }, + + avatarUrl: function() { return this.model.get('avatar_url'); }, + + title: function() { return this.model.get("name"); }, + + about: function() { + var longText = this.model.get("about"); + if (longText) { + var shortText = longText.match(/\b([\w]+[\W]+){6}/); + return (shortText) ? shortText[0] : longText; + } + return ""; + }, + + switchProfile: function(e) { + $(this.el).siblings().removeClass("current"); + $(this.el).addClass("current"); + e.preventDefault(); + window.infoBox.showList(window.infoBox.getSchema(), this.model); + } + +}); + +var InfoBox = CommonPlace.View.extend({ + id: "info-box", + template: "main_page/info-box", + $profile: function() { return this.$("#profile"); }, + $list: function() { return this.$("#info-list"); }, + $profile_none: function() { return this.$("#profile-area > .none"); }, + $list_none: function() { return this.$("#info-list-area > .none"); }, + + events: { + "click .filter-tab": "switchTab", + "click .remove-search": "removeSearch", + "submit form": "searchFormSubmit", + "keyup input.search": "filterBySearch" + }, + + searchFormSubmit: function(e) { e.preventDefault(); }, + + afterRender: function() { + var self = this; + this.currentCollection = {}; + this.currentQuery = ""; + this.page = 0; + this.showProfile(this.options.account); + + if (this.isActive("fixedLayout")) { + setPostBoxTop(); + setProfileBoxBottom(); + setProfileBoxTop(); + setProfileBoxInfoUpperHeight(); + } else { + $(window).scroll(function() { + $(self.el).css({ width: $(self.el).width() }); + self.setPosition(); + }); + } + + this.$("#info-list-area > ul").scroll(function() { + if (this.offsetHeight + $(this).scrollTop() >= this.scrollHeight) { + self.nextPage(); + } + }); + + }, + + setPosition: function() { + var $el = $(this.el); + if ($el.css("position") == "fixed") { $el.css({ top: 0, bottom: "auto" }); } + var marginTop = parseInt($el.css("margin-top"), 10); + var $parent = $el.parent(); + var $footerTop = $("#footer").offset().top; + var topScroll = $(window).scrollTop(); + var distanceFromTop = $el.offset().top; + var parentBottomDistanceToTop = $parent.offset().top + $parent.height(); + + if ($el.css("position") == "relative") { + if (distanceFromTop < topScroll) { + $el.css({ position: "fixed", top: 0, bottom: "auto" }); + } + } else { + if (distanceFromTop < parentBottomDistanceToTop + marginTop) { + $el.css({ position: "relative" }); + } else if (distanceFromTop + $el.height() > $footerTop) { + var bottom = (topScroll + $(window).height() + 20) - $footerTop; + $el.css({ top: "auto", bottom: bottom }); + } + } + }, + + isAccount: function(model) { + return model && + (model.get("schema") == "users" || model.get("schema") == "account") && + model.id === this.options.account.id; + }, + + showProfile: function(model) { + var self = this; + + this.$(".remove-search").hide(); + this.currentQuery = ""; + + model.fetch({ + success: function() { + if (self.isAccount(model)) { model = self.options.account; } + + self.renderProfile(model); + + self.currentQuery = ""; + self.$("form > input").val(self.currentQuery); + + self.currentCollection = self.collectionFor(model); + self.currentCollection.fetch({ + success: function() { + self.renderList(self.currentCollection); + } + }); + } + }); + }, + + showList: function(schema, model) { + var self = this; + + var partial = this.currentQuery ? this.options.community.search : this.options.community; + var collection = (schema == "account") ? partial.users : partial[schema]; + + if (schema == "account") { + if ( (model && this.isAccount(model)) || !model) { + model = this.options.account; + } + } + + this.page = 0; + + collection.fetch({ + data: { query: this.currentQuery }, + success: function() { + if (collection.length === 0) { + self.$(".remove-search").removeClass("not-empty"); + self.$(".remove-search").addClass("empty"); + self.changeSchema(schema); + self.renderNone(); + if (self.currentQuery) { + self.$list().empty(); + } else { + collection = self.options.community; + collection = (schema == "account") ? collection.users : collection[schema]; + + collection.fetch({ + success: function() { + self.showFetchedList(collection, model); + } + }); + } + } else { + self.$(".remove-search").addClass("not-empty"); + self.$(".remove-search").removeClass("empty"); + self.$(".none").hide(); + self.showFetchedList(collection, model); + } + } + }); + }, + + showFetchedList: function(collection, model) { + this.renderList(collection); + this.currentCollection = collection; + + var firstIsAccount = this.isAccount(collection.first()); + var self = this; + + if ((firstIsAccount && collection.length == 1)) { + model = this.options.account; + } else { + if (model) { + model = this.isAccount(model) ? this.options.account : model; + } else { + model = firstIsAccount ? collection.at(1) : collection.first(); + } + } + model.fetch({ + success: function() { + self.renderProfile(model); + } + }); + }, + + nextPage: function() { + var collection = this.currentCollection; + if (collection.length < 25) { return; } + this.page = this.page + 1; + var self = this; + collection.fetch({ + data: { + query: this.currentQuery, + page: this.page + }, + success: function() { + self.renderList(collection, "append"); + } + }); + }, + + renderProfile: function(model) { + if (model == this.currentModel) { return; } + this.$profile().show(); + this.$profile_none().hide(); + var profile = this.profileBoxFor(model); + profile.render(); + this.$profile().replaceWith(profile.el); + this.changeSchema(model.get("schema")); + this.currentModel = model; + }, + + renderNone: function() { + this.$profile().hide(); + this.$profile().hide(); + this.page = 0; + var schema = (this.getSchema() == "account" ? "users" : this.getSchema()); + var box = new { + "users" : UserNoneBox, + "feeds" : FeedNoneBox, + "groups" : GroupNoneBox, + "account" : UserNoneBox + }[schema]({ community: this.options.community }); + box.render(); + this.$profile_none().replaceWith(box.el); + this.$profile_none().show(); + this.$list_none().show(); + }, + + renderList: function(collection, options) { + var self = this; + this.$list_none().hide(); + if (collection != this.currentCollection) { + this.$("#info-list-area > ul").scrollTop(0); + } + if (options != "append") { this.$list().empty(); } + collection.each(function (model) { + var item = new InfoListItem({ + model: model, + account: self.options.account + }); + item.render(); + self.$list().append(item.el); + }); + }, + + changeSchema: function(schema) { + this.$(".filter-tab").removeClass("current"); + this.$("." + schema + "-filter").addClass("current"); + }, + + profileBoxFor: function(model) { + return new (this.config(model.get('schema')).profileBox)({ + model: model, account: + this.options.account + }); + }, + + collectionFor: function(model) { + return this.config(model.get('schema')).collection; + }, + + searchFor: function(model) { + return this.config(model.get("schema")).search; + }, + + config: function(type) { + return { + "account": { profileBox: AccountProfileBox, + collection: this.options.community.users, + search: this.options.community.search.users + }, + "users": { profileBox: UserProfileBox, + collection: this.options.community.users, + search: this.options.community.search.users + }, + "groups": { profileBox: GroupProfileBox, + collection: this.options.community.groups, + search: this.options.community.search.groups + }, + "feeds": { profileBox: FeedProfileBox, + collection: this.options.community.feeds, + search: this.options.community.search.feeds + } + }[type]; + }, + + switchTab: function(e) { + e && e.preventDefault(); + this.showList(this.getSchema($(e.target))); + }, + + filterBySearch: _.debounce(function(e) { + e && e.preventDefault(); + query = this.$("input.search").val(); + if (query) { + this.$("#info-list-area").addClass("searching"); + this.$(".remove-search").show(); + this.$(".remove-search").text(query); + this.currentQuery = query; + this.showList(this.getSchema()); + } else { this.removeSearch(); } + }, 500), + + removeSearch: function(e) { + e && e.preventDefault(); + this.$("#info-list-area").removeClass("searching"); + this.$(".remove-search").hide(); + this.currentQuery = ""; + this.$("form > input").val(""); + this.showList(this.getSchema()); + }, + + getSchema: function(el) { + el = el ? el : $(".filter-tab.current"); + return el.attr("href").split("#")[1]; + } + +}); + +var Profile = CommonPlace.View.extend({ + id: "profile", + + aroundRender: function(render) { + this.model.fetch({ + success: render + }); + } +}); diff --git a/app/javascripts/info_boxes/feed_profile.js b/app/javascripts/info_boxes/feed_profile.js new file mode 100644 index 000000000..52abd8eeb --- /dev/null +++ b/app/javascripts/info_boxes/feed_profile.js @@ -0,0 +1,62 @@ +var FeedProfileBox = Profile.extend({ + template: "main_page/profiles/feed-profile", + className: "profile", + + events: { + "click .message": "showMessageForm", + "click .subscribe": "subscribe", + "click .unsubscribe": "unsubscribe", + "click .edit-feed": "showFeedEditForm" + }, + + avatarUrl: function() { return this.model.get('avatar_url'); }, + + fullName: function() { return this.model.get("name"); }, + + shortName: function() { return this.model.get("first_name"); }, + + about: function() { return this.model.get('about'); }, + + groups: function() { return ""; }, + + showMessageForm: function(e) { + e.preventDefault(); + var formView = new MessageFormView({ + model: new Message({ messagable: this.model }) + }); + formView.render(); + }, + + showFeedEditForm: function(e) { + e.preventDefault(); + var formView = new FeedEditFormView({ + model: this.model + }); + formView.render(); + }, + + subscribe: function(e) { + e.preventDefault(); + this.options.account.subscribeToFeed(this.model); + this.render(); + }, + + unsubscribe: function(e) { + e.preventDefault(); + this.options.account.unsubscribeFromFeed(this.model); + this.render(); + }, + + isSubscribed: function() { return this.options.account.isSubscribedToFeed(this.model); }, + + isOwner: function() { return this.options.account.isFeedOwner(this.model); }, + + url: function() { return this.model.get("url"); } + +}); + +var FeedNoneBox = CommonPlace.View.extend({ + template: "main_page/profiles/feed-none", + className: "none", + query: function() { return window.infoBox.currentQuery; } +}); diff --git a/app/javascripts/info_boxes/group_profile.js b/app/javascripts/info_boxes/group_profile.js new file mode 100644 index 000000000..416a98cab --- /dev/null +++ b/app/javascripts/info_boxes/group_profile.js @@ -0,0 +1,45 @@ +var GroupProfileBox = Profile.extend({ + template: "main_page/profiles/group-profile", + className: "profile", + + events: { + "click .subscribe": "subscribe", + "click .unsubscribe": "unsubscribe" + }, + + avatarUrl: function() { return this.model.get('avatar_url'); }, + + fullName: function() { return this.model.get("name"); }, + + about: function() { return this.model.get('about'); }, + + subscribe: function(e) { + e.preventDefault(); + this.options.account.subscribeToGroup(this.model); + this.render(); + }, + + unsubscribe: function(e) { + e.preventDefault(); + this.options.account.unsubscribeFromGroup(this.model); + this.render(); + }, + + isSubscribed: function() { return this.options.account.isSubscribedToGroup(this.model); }, + + url: function() { return this.model.get("url"); } + +}); + +var GroupNoneBox = CommonPlace.View.extend({ + template: "main_page/profiles/group-none", + className: "none", + + initialize: function(options) { + this.community = options.community; + }, + + query: function() { return window.infoBox.currentQuery; }, + + email: function() { return this.community.get("admin_email"); } +}); diff --git a/app/javascripts/info_boxes/user_profile.js b/app/javascripts/info_boxes/user_profile.js new file mode 100644 index 000000000..88a9dfaa5 --- /dev/null +++ b/app/javascripts/info_boxes/user_profile.js @@ -0,0 +1,69 @@ +var UserProfileBox = Profile.extend({ + template: "main_page/profiles/user-profile", + className: "profile", + + events: { + "click .message": "showMessageForm", + "click .meet": "meet", + "click .unmeet": "unmeet" + }, + + comma: function(item) { + return " " + item; + }, + + avatarUrl: function() { return this.model.get('avatar_url'); }, + + fullName: function() { return this.model.get("name"); }, + + shortName: function() { return this.model.get("first_name"); }, + + about: function() { return this.model.get('about'); }, + + interests: function() { return _.map(this.model.get('interests'), this.comma); }, + + skills: function() { return _.map(this.model.get("skills"), this.comma); }, + + goods: function() { return _.map(this.model.get("goods"), this.comma); }, + + subscriptions: function() { return this.model.get('subscriptions'); }, + + groups: function() { return ""; }, + + hasAbout: function() { return this.model.get("about") ; }, + + hasInterests: function() { return this.model.get("interests").length > 0; }, + + hasSkills: function() { return this.model.get("skills").length > 0; }, + + hasGoods: function() { return this.model.get("goods").length > 0; }, + + showMessageForm: function(e) { + e.preventDefault(); + var formView = new MessageFormView({ + model: new Message({ messagable: this.model }) + }); + formView.render(); + }, + + meet: function(e) { + e.preventDefault(); + this.options.account.meetUser(this.model); + this.render(); + }, + + unmeet: function(e) { + e.preventDefault(); + this.options.account.unmeetUser(this.model); + this.render(); + }, + + hasMet: function() { return this.options.account.hasMetUser(this.model); } + +}); + +var UserNoneBox = CommonPlace.View.extend({ + template: "main_page/profiles/user-none", + className: "none", + query: function() { return window.infoBox.currentQuery; } +}); diff --git a/public/javascripts/inlineform.js b/app/javascripts/inlineform.js similarity index 79% rename from public/javascripts/inlineform.js rename to app/javascripts/inlineform.js index 84ed8d86b..c36c04b88 100644 --- a/public/javascripts/inlineform.js +++ b/app/javascripts/inlineform.js @@ -14,12 +14,8 @@ function initInlineForm() { url: $(this).attr('data-form-url'), data: data, success: function(response) { - $('#information').replaceWith(response.content); - initInlineForm(); - renderMaps(); - setInfoBoxPosition(); - }, - dataType: "json" + $("#information").replaceWith(window.innerShiv(response,false)); + } }); }); @@ -29,23 +25,17 @@ function initInlineForm() { }); $('.inline-form').bind('image.inline-form', function() { - var $this = $(this) + var $this = $(this); $("#avatar-form", $this).ajaxSubmit({ success: function(response){ $('img.avatar', $this).attr('src', - $('img.avatar', $this).attr('src') + "1234"); + $('img.avatar', $this).attr('src') + "a"); } }); }); - $("#avatar-form #file_uploader").change(function() { - $(this).trigger("image.inline-form"); - }); - - $('.inline-form .inline-edit').click(function(e) { $(this).trigger('edit.inline-form'); - e.stopPropagation(); }); $('.inline-form .inline-save').click(function(e) { diff --git a/public/javascripts/innershiv.js b/app/javascripts/innershiv.js similarity index 100% rename from public/javascripts/innershiv.js rename to app/javascripts/innershiv.js diff --git a/app/javascripts/invite_page.js b/app/javascripts/invite_page.js new file mode 100644 index 000000000..ab2a4483b --- /dev/null +++ b/app/javascripts/invite_page.js @@ -0,0 +1,46 @@ +function onABCommComplete() { + text = ""; + $($("textarea#invite_email").val().split(", ")).each(function(index, value) { text += value.replace(/(.*)<(.*)>/, "$2") + ", "; }); + $("textarea#invite_email").val(text.substring(0, text.length-2)); +} + + + +function add_to_friends_of_commonplace(email) { + $.post('/account/make_focp', 'email=' + email); +} + + +$(function(){ + + $('input[placeholder], textarea[placeholder]').placeholder(); + + + $("#invite_email_input").removeClass("optional") + $("#invite_body_input").removeClass("optional") + $("#invite_email_input").addClass("required") + $("#invite_body_input").removeClass("required") + + $("form#new_invite").submit(function(e) { + e.preventDefault(); + $.ajax({ + type: "POST", + dataType: "json", + url: $(this).attr("action"), + data: JSON.stringify({ + emails: $("#invite_email", this).val().split(/,\s*/), + message: $("#invite_body", this).val() + }), + success: function() { + $("#new_invite #invite_email").val(""); + $("#new_invite #invite_body").val(""); + $("form#new_invite").append($('

Invites sent. Send some more!

')); + } + + }); + + + + }); + +}); diff --git a/app/javascripts/jquery-1.6.1.js b/app/javascripts/jquery-1.6.1.js new file mode 100644 index 000000000..5d5a1d58e --- /dev/null +++ b/app/javascripts/jquery-1.6.1.js @@ -0,0 +1,8936 @@ +/*! + * jQuery JavaScript Library v1.6.1 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Thu May 12 15:04:36 2011 -0400 + */ +(function( window, undefined ) { + +// Use the correct document accordingly with window argument (sandbox) +var document = window.document, + navigator = window.navigator, + location = window.location; +var jQuery = (function() { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // (both of which we optimize for) + quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Check for digits + rdigit = /\d/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // The deferred used on DOM ready + readyList, + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; + +jQuery.fn = jQuery.prototype = { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context && document.body ) { + this.context = document; + this[0] = document.body; + this.selector = selector; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = quickExpr.exec( selector ); + } + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + doc = (context ? context.ownerDocument || context : document); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); + selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return (context || rootjQuery).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if (selector.selector !== undefined) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.6.1", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = this.constructor(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + (this.selector ? " " : "") + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // Add the callback + readyList.done( fn ); + + return this; + }, + + eq: function( i ) { + return i === -1 ? + this.slice( i ) : + this.slice( i, +i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + // Either a released hold or an DOMready/load event and not yet ready + if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).unbind( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyList ) { + return; + } + + readyList = jQuery._Deferred(); + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + return setTimeout( jQuery.ready, 1 ); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", DOMContentLoaded ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + // A crude way of determining if an object is a window + isWindow: function( obj ) { + return obj && typeof obj === "object" && "setInterval" in obj; + }, + + isNaN: function( obj ) { + return obj == null || !rdigit.test( obj ) || isNaN( obj ); + }, + + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ toString.call(obj) ] || "object"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw msg; + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return (new Function( "return " + data ))(); + + } + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + // (xml & tmp used internally) + parseXML: function( data , xml , tmp ) { + + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + + tmp = xml.documentElement; + + if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) { + jQuery.error( "Invalid XML: " + data ); + } + + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && rnotwhite.test( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction( object ); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { + break; + } + } + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim: trim ? + function( text ) { + return text == null ? + "" : + trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // The extra typeof function check is to prevent crashes + // in Safari 2 (See: #3039) + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type( array ); + + if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array ) { + + if ( indexOf ) { + return indexOf.call( array, elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, + j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, key, ret = [], + i = 0, + length = elems.length, + // jquery objects are treated as arrays + isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( key in elems ) { + value = callback( elems[ key ], key, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + if ( typeof context === "string" ) { + var tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + var args = slice.call( arguments, 2 ), + proxy = function() { + return fn.apply( context, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can be optionally by executed if its a function + access: function( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + jQuery.access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; + }, + + now: function() { + return (new Date()).getTime(); + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = rwebkit.exec( ua ) || + ropera.exec( ua ) || + rmsie.exec( ua ) || + ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + sub: function() { + function jQuerySub( selector, context ) { + return new jQuerySub.fn.init( selector, context ); + } + jQuery.extend( true, jQuerySub, this ); + jQuerySub.superclass = this; + jQuerySub.fn = jQuerySub.prototype = this(); + jQuerySub.fn.constructor = jQuerySub; + jQuerySub.sub = this.sub; + jQuerySub.fn.init = function init( selector, context ) { + if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { + context = jQuerySub( context ); + } + + return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); + }; + jQuerySub.fn.init.prototype = jQuerySub.fn; + var rootjQuerySub = jQuerySub(document); + return jQuerySub; + }, + + browser: {} +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +// IE doesn't match non-breaking spaces with \s +if ( rnotwhite.test( "\xA0" ) ) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch(e) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +// Expose jQuery to the global object +return jQuery; + +})(); + + +var // Promise methods + promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ), + // Static reference to slice + sliceDeferred = [].slice; + +jQuery.extend({ + // Create a simple deferred (one callbacks list) + _Deferred: function() { + var // callbacks list + callbacks = [], + // stored [ context , args ] + fired, + // to avoid firing when already doing so + firing, + // flag to know if the deferred has been cancelled + cancelled, + // the deferred itself + deferred = { + + // done( f1, f2, ...) + done: function() { + if ( !cancelled ) { + var args = arguments, + i, + length, + elem, + type, + _fired; + if ( fired ) { + _fired = fired; + fired = 0; + } + for ( i = 0, length = args.length; i < length; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + deferred.done.apply( deferred, elem ); + } else if ( type === "function" ) { + callbacks.push( elem ); + } + } + if ( _fired ) { + deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] ); + } + } + return this; + }, + + // resolve with given context and args + resolveWith: function( context, args ) { + if ( !cancelled && !fired && !firing ) { + // make sure args are available (#8421) + args = args || []; + firing = 1; + try { + while( callbacks[ 0 ] ) { + callbacks.shift().apply( context, args ); + } + } + finally { + fired = [ context, args ]; + firing = 0; + } + } + return this; + }, + + // resolve with this as context and given arguments + resolve: function() { + deferred.resolveWith( this, arguments ); + return this; + }, + + // Has this deferred been resolved? + isResolved: function() { + return !!( firing || fired ); + }, + + // Cancel + cancel: function() { + cancelled = 1; + callbacks = []; + return this; + } + }; + + return deferred; + }, + + // Full fledged deferred (two callbacks list) + Deferred: function( func ) { + var deferred = jQuery._Deferred(), + failDeferred = jQuery._Deferred(), + promise; + // Add errorDeferred methods, then and promise + jQuery.extend( deferred, { + then: function( doneCallbacks, failCallbacks ) { + deferred.done( doneCallbacks ).fail( failCallbacks ); + return this; + }, + always: function() { + return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments ); + }, + fail: failDeferred.done, + rejectWith: failDeferred.resolveWith, + reject: failDeferred.resolve, + isRejected: failDeferred.isResolved, + pipe: function( fnDone, fnFail ) { + return jQuery.Deferred(function( newDefer ) { + jQuery.each( { + done: [ fnDone, "resolve" ], + fail: [ fnFail, "reject" ] + }, function( handler, data ) { + var fn = data[ 0 ], + action = data[ 1 ], + returned; + if ( jQuery.isFunction( fn ) ) { + deferred[ handler ](function() { + returned = fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise().then( newDefer.resolve, newDefer.reject ); + } else { + newDefer[ action ]( returned ); + } + }); + } else { + deferred[ handler ]( newDefer[ action ] ); + } + }); + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + if ( obj == null ) { + if ( promise ) { + return promise; + } + promise = obj = {}; + } + var i = promiseMethods.length; + while( i-- ) { + obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ]; + } + return obj; + } + }); + // Make sure only one callback list will be used + deferred.done( failDeferred.cancel ).fail( deferred.cancel ); + // Unexpose cancel + delete deferred.cancel; + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + return deferred; + }, + + // Deferred helper + when: function( firstParam ) { + var args = arguments, + i = 0, + length = args.length, + count = length, + deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? + firstParam : + jQuery.Deferred(); + function resolveFunc( i ) { + return function( value ) { + args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + if ( !( --count ) ) { + // Strange bug in FF4: + // Values changed onto the arguments object sometimes end up as undefined values + // outside the $.when method. Cloning the object into a fresh array solves the issue + deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) ); + } + }; + } + if ( length > 1 ) { + for( ; i < length; i++ ) { + if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) { + args[ i ].promise().then( resolveFunc(i), deferred.reject ); + } else { + --count; + } + } + if ( !count ) { + deferred.resolveWith( deferred, args ); + } + } else if ( deferred !== firstParam ) { + deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); + } + return deferred.promise(); + } +}); + + + +jQuery.support = (function() { + + var div = document.createElement( "div" ), + documentElement = document.documentElement, + all, + a, + select, + opt, + input, + marginDiv, + support, + fragment, + body, + bodyStyle, + tds, + events, + eventName, + i, + isSupported; + + // Preliminary tests + div.setAttribute("className", "t"); + div.innerHTML = "
a"; + + all = div.getElementsByTagName( "*" ); + a = div.getElementsByTagName( "a" )[ 0 ]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return {}; + } + + // First batch of supports tests + select = document.createElement( "select" ); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName( "input" )[ 0 ]; + + support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: ( div.firstChild.nodeType === 3 ), + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName( "tbody" ).length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName( "link" ).length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: ( a.getAttribute( "href" ) === "/a" ), + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55$/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: ( input.value === "on" ), + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // Will be defined later + submitBubbles: true, + changeBubbles: true, + focusinBubbles: false, + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true + }; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { + div.attachEvent( "onclick", function click() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + support.noCloneEvent = false; + div.detachEvent( "onclick", click ); + }); + div.cloneNode( true ).fireEvent( "onclick" ); + } + + // Check if a radio maintains it's value + // after being appended to the DOM + input = document.createElement("input"); + input.value = "t"; + input.setAttribute("type", "radio"); + support.radioValue = input.value === "t"; + + input.setAttribute("checked", "checked"); + div.appendChild( input ); + fragment = document.createDocumentFragment(); + fragment.appendChild( div.firstChild ); + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + div.innerHTML = ""; + + // Figure out if the W3C box model works as expected + div.style.width = div.style.paddingLeft = "1px"; + + // We use our own, invisible, body + body = document.createElement( "body" ); + bodyStyle = { + visibility: "hidden", + width: 0, + height: 0, + border: 0, + margin: 0, + // Set background to avoid IE crashes when removing (#9028) + background: "none" + }; + for ( i in bodyStyle ) { + body.style[ i ] = bodyStyle[ i ]; + } + body.appendChild( div ); + documentElement.insertBefore( body, documentElement.firstChild ); + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + support.boxModel = div.offsetWidth === 2; + + if ( "zoom" in div.style ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.style.display = "inline"; + div.style.zoom = 1; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 ); + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = ""; + div.innerHTML = "
"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); + } + + div.innerHTML = "
t
"; + tds = div.getElementsByTagName( "td" ); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE < 8 fail this test) + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + div.innerHTML = ""; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + if ( document.defaultView && document.defaultView.getComputedStyle ) { + marginDiv = document.createElement( "div" ); + marginDiv.style.width = "0"; + marginDiv.style.marginRight = "0"; + div.appendChild( marginDiv ); + support.reliableMarginRight = + ( parseInt( ( document.defaultView.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; + } + + // Remove the body element we added + body.innerHTML = ""; + documentElement.removeChild( body ); + + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if ( div.attachEvent ) { + for( i in { + submit: 1, + change: 1, + focusin: 1 + } ) { + eventName = "on" + i; + isSupported = ( eventName in div ); + if ( !isSupported ) { + div.setAttribute( eventName, "return;" ); + isSupported = ( typeof div[ eventName ] === "function" ); + } + support[ i + "Bubbles" ] = isSupported; + } + } + + return support; +})(); + +// Keep track of boxModel +jQuery.boxModel = jQuery.support.boxModel; + + + + +var rbrace = /^(?:\{.*\}|\[.*\])$/, + rmultiDash = /([a-z])([A-Z])/g; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache, + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ jQuery.expando ] = id = ++jQuery.uuid; + } else { + id = jQuery.expando; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery + // metadata on plain JS objects when the object is serialized using + // JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name); + } else { + cache[ id ] = jQuery.extend(cache[ id ], name); + } + } + + thisCache = cache[ id ]; + + // Internal jQuery data is stored in a separate object inside the object's data + // cache in order to avoid key collisions between internal data and user-defined + // data + if ( pvt ) { + if ( !thisCache[ internalKey ] ) { + thisCache[ internalKey ] = {}; + } + + thisCache = thisCache[ internalKey ]; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should + // not attempt to inspect the internal events object using jQuery.data, as this + // internal data object is undocumented and subject to change. + if ( name === "events" && !thisCache[name] ) { + return thisCache[ internalKey ] && thisCache[ internalKey ].events; + } + + return getByName ? thisCache[ jQuery.camelCase( name ) ] : thisCache; + }, + + removeData: function( elem, name, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var internalKey = jQuery.expando, isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ]; + + if ( thisCache ) { + delete thisCache[ name ]; + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !isEmptyDataObject(thisCache) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( pvt ) { + delete cache[ id ][ internalKey ]; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject(cache[ id ]) ) { + return; + } + } + + var internalCache = cache[ id ][ internalKey ]; + + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + if ( jQuery.support.deleteExpando || cache != window ) { + delete cache[ id ]; + } else { + cache[ id ] = null; + } + + // We destroyed the entire user cache at once because it's faster than + // iterating through each key, but we need to continue to persist internal + // data if it existed + if ( internalCache ) { + cache[ id ] = {}; + // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery + // metadata on plain JS objects when the object is serialized using + // JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + + cache[ id ][ internalKey ] = internalCache; + + // Otherwise, we need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + } else if ( isNode ) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( jQuery.support.deleteExpando ) { + delete elem[ jQuery.expando ]; + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + } else { + elem[ jQuery.expando ] = null; + } + } + }, + + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + if ( elem.nodeName ) { + var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + + if ( match ) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var data = null; + + if ( typeof key === "undefined" ) { + if ( this.length ) { + data = jQuery.data( this[0] ); + + if ( this[0].nodeType === 1 ) { + var attr = this[0].attributes, name; + for ( var i = 0, l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.substring(5) ); + + dataAttr( this[0], name, data[ name ] ); + } + } + } + } + + return data; + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + // Try to fetch any internally stored data first + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + data = dataAttr( this[0], key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + + } else { + return this.each(function() { + var $this = jQuery( this ), + args = [ parts[0], value ]; + + $this.triggerHandler( "setData" + parts[1] + "!", args ); + jQuery.data( this, key, value ); + $this.triggerHandler( "changeData" + parts[1] + "!", args ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + var name = "data-" + key.replace( rmultiDash, "$1-$2" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + !jQuery.isNaN( data ) ? parseFloat( data ) : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// TODO: This is a hack for 1.5 ONLY to allow objects with a single toJSON +// property to be considered empty objects; this property always exists in +// order to make sure JSON.stringify does not expose internal metadata +function isEmptyDataObject( obj ) { + for ( var name in obj ) { + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + + + + +function handleQueueMarkDefer( elem, type, src ) { + var deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + defer = jQuery.data( elem, deferDataKey, undefined, true ); + if ( defer && + ( src === "queue" || !jQuery.data( elem, queueDataKey, undefined, true ) ) && + ( src === "mark" || !jQuery.data( elem, markDataKey, undefined, true ) ) ) { + // Give room for hard-coded callbacks to fire first + // and eventually mark/queue something else on the element + setTimeout( function() { + if ( !jQuery.data( elem, queueDataKey, undefined, true ) && + !jQuery.data( elem, markDataKey, undefined, true ) ) { + jQuery.removeData( elem, deferDataKey, true ); + defer.resolve(); + } + }, 0 ); + } +} + +jQuery.extend({ + + _mark: function( elem, type ) { + if ( elem ) { + type = (type || "fx") + "mark"; + jQuery.data( elem, type, (jQuery.data(elem,type,undefined,true) || 0) + 1, true ); + } + }, + + _unmark: function( force, elem, type ) { + if ( force !== true ) { + type = elem; + elem = force; + force = false; + } + if ( elem ) { + type = type || "fx"; + var key = type + "mark", + count = force ? 0 : ( (jQuery.data( elem, key, undefined, true) || 1 ) - 1 ); + if ( count ) { + jQuery.data( elem, key, count, true ); + } else { + jQuery.removeData( elem, key, true ); + handleQueueMarkDefer( elem, type, "mark" ); + } + } + }, + + queue: function( elem, type, data ) { + if ( elem ) { + type = (type || "fx") + "queue"; + var q = jQuery.data( elem, type, undefined, true ); + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !q || jQuery.isArray(data) ) { + q = jQuery.data( elem, type, jQuery.makeArray(data), true ); + } else { + q.push( data ); + } + } + return q || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + fn = queue.shift(), + defer; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift("inprogress"); + } + + fn.call(elem, function() { + jQuery.dequeue(elem, type); + }); + } + + if ( !queue.length ) { + jQuery.removeData( elem, type + "queue", true ); + handleQueueMarkDefer( elem, type, "queue" ); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) { + return jQuery.queue( this[0], type ); + } + return this.each(function() { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; + type = type || "fx"; + + return this.queue( type, function() { + var elem = this; + setTimeout(function() { + jQuery.dequeue( elem, type ); + }, time ); + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, object ) { + if ( typeof type !== "string" ) { + object = type; + type = undefined; + } + type = type || "fx"; + var defer = jQuery.Deferred(), + elements = this, + i = elements.length, + count = 1, + deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + tmp; + function resolve() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + } + while( i-- ) { + if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || + ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || + jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && + jQuery.data( elements[ i ], deferDataKey, jQuery._Deferred(), true ) )) { + count++; + tmp.done( resolve ); + } + } + resolve(); + return defer.promise(); + } +}); + + + + +var rclass = /[\n\t\r]/g, + rspace = /\s+/, + rreturn = /\r/g, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, + rinvalidChar = /\:/, + formHook, boolHook; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.attr ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.prop ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this); + self.addClass( value.call(this, i, self.attr("class") || "") ); + }); + } + + if ( value && typeof value === "string" ) { + var classNames = (value || "").split( rspace ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className ) { + elem.className = value; + + } else { + var className = " " + elem.className + " ", + setClass = elem.className; + + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { + setClass += " " + classNames[c]; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.removeClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + var classNames = (value || "").split( rspace ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + var className = (" " + elem.className + " ").replace(rclass, " "); + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[c] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this); + self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.split( rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " "; + for ( var i = 0, l = this.length; i < l; i++ ) { + if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var hooks, ret, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + return (elem.value || "").replace(rreturn, ""); + } + + return undefined; + } + + var isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var self = jQuery(this), val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, + index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + // Fixes Bug #2551 -- select.val() broken in IE after form.reset() + if ( one && !values.length && options.length ) { + return jQuery( options[ index ] ).val(); + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attrFix: { + // Always normalize to ensure hook usage + tabindex: "tabIndex" + }, + + attr: function( elem, name, value, pass ) { + var nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return undefined; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery( elem )[ name ]( value ); + } + + // Fallback to prop when attributes are not supported + if ( !("getAttribute" in elem) ) { + return jQuery.prop( elem, name, value ); + } + + var ret, hooks, + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // Normalize the name if needed + name = notxml && jQuery.attrFix[ name ] || name; + + hooks = jQuery.attrHooks[ name ]; + + if ( !hooks ) { + // Use boolHook for boolean attributes + if ( rboolean.test( name ) && + (typeof value === "boolean" || value === undefined || value.toLowerCase() === name.toLowerCase()) ) { + + hooks = boolHook; + + // Use formHook for forms and if the name contains certain characters + } else if ( formHook && (jQuery.nodeName( elem, "form" ) || rinvalidChar.test( name )) ) { + hooks = formHook; + } + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return undefined; + + } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, "" + value ); + return value; + } + + } else if ( hooks && "get" in hooks && notxml ) { + return hooks.get( elem, name ); + + } else { + + ret = elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return ret === null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, name ) { + var propName; + if ( elem.nodeType === 1 ) { + name = jQuery.attrFix[ name ] || name; + + if ( jQuery.support.getSetAttribute ) { + // Use removeAttribute in browsers that support it + elem.removeAttribute( name ); + } else { + jQuery.attr( elem, name, "" ); + elem.removeAttributeNode( elem.getAttributeNode( name ) ); + } + + // Set corresponding property to false for boolean attributes + if ( rboolean.test( name ) && (propName = jQuery.propFix[ name ] || name) in elem ) { + elem[ propName ] = false; + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to it's default in case type is set after value + // This is for element creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + }, + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabIndex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return undefined; + } + + var ret, hooks, + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // Try to normalize/fix the name + name = notxml && jQuery.propFix[ name ] || name; + + hooks = jQuery.propHooks[ name ]; + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return (elem[ name ] = value); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== undefined ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: {} +}); + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + // Align boolean attributes with corresponding properties + return elem[ jQuery.propFix[ name ] || name ] ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + var propName; + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + // value is true since we know at this point it's type boolean and not false + // Set boolean attributes to the same name and set the DOM property + propName = jQuery.propFix[ name ] || name; + if ( propName in elem ) { + // Only set the IDL specifically if it already exists on the element + elem[ propName ] = value; + } + + elem.setAttribute( name, name.toLowerCase() ); + } + return name; + } +}; + +// Use the value property for back compat +// Use the formHook for button elements in IE6/7 (#1954) +jQuery.attrHooks.value = { + get: function( elem, name ) { + if ( formHook && jQuery.nodeName( elem, "button" ) ) { + return formHook.get( elem, name ); + } + return elem.value; + }, + set: function( elem, value, name ) { + if ( formHook && jQuery.nodeName( elem, "button" ) ) { + return formHook.set( elem, value, name ); + } + // Does not return so that setAttribute is also used + elem.value = value; + } +}; + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !jQuery.support.getSetAttribute ) { + + // propFix is more comprehensive and contains all fixes + jQuery.attrFix = jQuery.propFix; + + // Use this for any attribute on a form in IE6/7 + formHook = jQuery.attrHooks.name = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret; + ret = elem.getAttributeNode( name ); + // Return undefined if nodeValue is empty string + return ret && ret.nodeValue !== "" ? + ret.nodeValue : + undefined; + }, + set: function( elem, value, name ) { + // Check form objects in IE (multiple bugs related) + // Only use nodeValue if the attribute node exists on the form + var ret = elem.getAttributeNode( name ); + if ( ret ) { + ret.nodeValue = value; + return value; + } + } + }; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); +} + + +// Some attributes require a special call on IE +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret === null ? undefined : ret; + } + }); + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Normalize to lowercase since IE uppercases css property names + return elem.style.cssText.toLowerCase() || undefined; + }, + set: function( elem, value ) { + return (elem.style.cssText = "" + value); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }); +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return (elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0); + } + } + }); +}); + + + + +var hasOwn = Object.prototype.hasOwnProperty, + rnamespaces = /\.(.*)$/, + rformElems = /^(?:textarea|input|select)$/i, + rperiod = /\./g, + rspaces = / /g, + rescape = /[^\w\s.|`]/g, + fcleanup = function( nm ) { + return nm.replace(rescape, "\\$&"); + }; + +/* + * A number of helper functions used for managing events. + * Many of the ideas behind this code originated from + * Dean Edwards' addEvent library. + */ +jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function( elem, types, handler, data ) { + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + if ( handler === false ) { + handler = returnFalse; + } else if ( !handler ) { + // Fixes bug #7229. Fix recommended by jdalton + return; + } + + var handleObjIn, handleObj; + + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure + var elemData = jQuery._data( elem ); + + // If no elemData is found then we must be trying to bind to one of the + // banned noData elements + if ( !elemData ) { + return; + } + + var events = elemData.events, + eventHandle = elemData.handle; + + if ( !events ) { + elemData.events = events = {}; + } + + if ( !eventHandle ) { + elemData.handle = eventHandle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.handle.apply( eventHandle.elem, arguments ) : + undefined; + }; + } + + // Add elem as a property of the handle function + // This is to prevent a memory leak with non-native events in IE. + eventHandle.elem = elem; + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = types.split(" "); + + var type, i = 0, namespaces; + + while ( (type = types[ i++ ]) ) { + handleObj = handleObjIn ? + jQuery.extend({}, handleObjIn) : + { handler: handler, data: data }; + + // Namespaced event handlers + if ( type.indexOf(".") > -1 ) { + namespaces = type.split("."); + type = namespaces.shift(); + handleObj.namespace = namespaces.slice(0).sort().join("."); + + } else { + namespaces = []; + handleObj.namespace = ""; + } + + handleObj.type = type; + if ( !handleObj.guid ) { + handleObj.guid = handler.guid; + } + + // Get the current list of functions bound to this event + var handlers = events[ type ], + special = jQuery.event.special[ type ] || {}; + + // Init the event handler queue + if ( !handlers ) { + handlers = events[ type ] = []; + + // Check for a special event handler + // Only use addEventListener/attachEvent if the special + // events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add the function to the element's handler list + handlers.push( handleObj ); + + // Keep track of which events have been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, pos ) { + // don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + if ( handler === false ) { + handler = returnFalse; + } + + var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ), + events = elemData && elemData.events; + + if ( !elemData || !events ) { + return; + } + + // types is actually an event object here + if ( types && types.type ) { + handler = types.handler; + types = types.type; + } + + // Unbind all events for the element + if ( !types || typeof types === "string" && types.charAt(0) === "." ) { + types = types || ""; + + for ( type in events ) { + jQuery.event.remove( elem, type + types ); + } + + return; + } + + // Handle multiple events separated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + types = types.split(" "); + + while ( (type = types[ i++ ]) ) { + origType = type; + handleObj = null; + all = type.indexOf(".") < 0; + namespaces = []; + + if ( !all ) { + // Namespaced event handlers + namespaces = type.split("."); + type = namespaces.shift(); + + namespace = new RegExp("(^|\\.)" + + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + eventType = events[ type ]; + + if ( !eventType ) { + continue; + } + + if ( !handler ) { + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( all || namespace.test( handleObj.namespace ) ) { + jQuery.event.remove( elem, origType, handleObj.handler, j ); + eventType.splice( j--, 1 ); + } + } + + continue; + } + + special = jQuery.event.special[ type ] || {}; + + for ( j = pos || 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( handler.guid === handleObj.guid ) { + // remove the given handler for the given type + if ( all || namespace.test( handleObj.namespace ) ) { + if ( pos == null ) { + eventType.splice( j--, 1 ); + } + + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + + if ( pos != null ) { + break; + } + } + } + + // remove generic event handler if no more handlers exist + if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + ret = null; + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + var handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + delete elemData.events; + delete elemData.handle; + + if ( jQuery.isEmptyObject( elemData ) ) { + jQuery.removeData( elem, undefined, true ); + } + } + }, + + // Events that are safe to short-circuit if no handlers are attached. + // Native DOM events should not be added, they may have inline handlers. + customEvent: { + "getData": true, + "setData": true, + "changeData": true + }, + + trigger: function( event, data, elem, onlyHandlers ) { + // Event object or event type + var type = event.type || event, + namespaces = [], + exclusive; + + if ( type.indexOf("!") >= 0 ) { + // Exclusive events trigger only for the exact event (no namespaces) + type = type.slice(0, -1); + exclusive = true; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + + if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { + // No jQuery handlers for this event type, and it can't have inline handlers + return; + } + + // Caller can pass in an Event, Object, or just an event type string + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + new jQuery.Event( type, event ) : + // Just the event type (string) + new jQuery.Event( type ); + + event.type = type; + event.exclusive = exclusive; + event.namespace = namespaces.join("."); + event.namespace_re = new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)"); + + // triggerHandler() and global events don't bubble or run the default action + if ( onlyHandlers || !elem ) { + event.preventDefault(); + event.stopPropagation(); + } + + // Handle a global trigger + if ( !elem ) { + // TODO: Stop taunting the data cache; remove global events and always attach to document + jQuery.each( jQuery.cache, function() { + // internalKey variable is just used to make it easier to find + // and potentially change this stuff later; currently it just + // points to jQuery.expando + var internalKey = jQuery.expando, + internalCache = this[ internalKey ]; + if ( internalCache && internalCache.events && internalCache.events[ type ] ) { + jQuery.event.trigger( event, data, internalCache.handle.elem ); + } + }); + return; + } + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // Clean up the event in case it is being reused + event.result = undefined; + event.target = elem; + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data ? jQuery.makeArray( data ) : []; + data.unshift( event ); + + var cur = elem, + // IE doesn't like method names with a colon (#3533, #8272) + ontype = type.indexOf(":") < 0 ? "on" + type : ""; + + // Fire event on the current element, then bubble up the DOM tree + do { + var handle = jQuery._data( cur, "handle" ); + + event.currentTarget = cur; + if ( handle ) { + handle.apply( cur, data ); + } + + // Trigger an inline bound script + if ( ontype && jQuery.acceptData( cur ) && cur[ ontype ] && cur[ ontype ].apply( cur, data ) === false ) { + event.result = false; + event.preventDefault(); + } + + // Bubble up to document, then to window + cur = cur.parentNode || cur.ownerDocument || cur === event.target.ownerDocument && window; + } while ( cur && !event.isPropagationStopped() ); + + // If nobody prevented the default action, do it now + if ( !event.isDefaultPrevented() ) { + var old, + special = jQuery.event.special[ type ] || {}; + + if ( (!special._default || special._default.call( elem.ownerDocument, event ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction)() check here because IE6/7 fails that test. + // IE<9 dies on focus to hidden element (#1486), may want to revisit a try/catch. + try { + if ( ontype && elem[ type ] ) { + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; + + if ( old ) { + elem[ ontype ] = null; + } + + jQuery.event.triggered = type; + elem[ type ](); + } + } catch ( ieError ) {} + + if ( old ) { + elem[ ontype ] = old; + } + + jQuery.event.triggered = undefined; + } + } + + return event.result; + }, + + handle: function( event ) { + event = jQuery.event.fix( event || window.event ); + // Snapshot the handlers list since a called handler may add/remove events. + var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []).slice(0), + run_all = !event.exclusive && !event.namespace, + args = Array.prototype.slice.call( arguments, 0 ); + + // Use the fix-ed Event rather than the (read-only) native event + args[0] = event; + event.currentTarget = this; + + for ( var j = 0, l = handlers.length; j < l; j++ ) { + var handleObj = handlers[ j ]; + + // Triggered event must 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event. + if ( run_all || event.namespace_re.test( handleObj.namespace ) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; + + var ret = handleObj.handler.apply( this, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + return event.result; + }, + + props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // store a copy of the original event object + // and "clone" to set read-only properties + var originalEvent = event; + event = jQuery.Event( originalEvent ); + + for ( var i = this.props.length, prop; i; ) { + prop = this.props[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary + if ( !event.target ) { + // Fixes #1925 where srcElement might not be defined either + event.target = event.srcElement || document; + } + + // check if target is a textnode (safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && event.fromElement ) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + var eventDocument = event.target.ownerDocument || document, + doc = eventDocument.documentElement, + body = eventDocument.body; + + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add which for key events + if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { + event.which = event.charCode != null ? event.charCode : event.keyCode; + } + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) { + event.metaKey = event.ctrlKey; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button !== undefined ) { + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + } + + return event; + }, + + // Deprecated, use jQuery.guid instead + guid: 1E8, + + // Deprecated, use jQuery.proxy instead + proxy: jQuery.proxy, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady, + teardown: jQuery.noop + }, + + live: { + add: function( handleObj ) { + jQuery.event.add( this, + liveConvert( handleObj.origType, handleObj.selector ), + jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); + }, + + remove: function( handleObj ) { + jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); + } + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !this.preventDefault ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers +var withinElement = function( event ) { + // Check if mouse(over|out) are still within the same parent element + var parent = event.relatedTarget; + + // set the correct event type + event.type = event.data; + + // Firefox sometimes assigns relatedTarget a XUL element + // which we cannot access the parentNode property of + try { + + // Chrome does something similar, the parentNode property + // can be accessed but is null. + if ( parent && parent !== document && !parent.parentNode ) { + return; + } + + // Traverse up the tree + while ( parent && parent !== this ) { + parent = parent.parentNode; + } + + if ( parent !== this ) { + // handle event if we actually just moused on to a non sub-element + jQuery.event.handle.apply( this, arguments ); + } + + // assuming we've left the element since we most likely mousedover a xul element + } catch(e) { } +}, + +// In case of event delegation, we only need to rename the event.type, +// liveHandler will take care of the rest. +delegate = function( event ) { + event.type = event.data; + jQuery.event.handle.apply( this, arguments ); +}; + +// Create mouseenter and mouseleave events +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + setup: function( data ) { + jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); + }, + teardown: function( data ) { + jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); + } + }; +}); + +// submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function( data, namespaces ) { + if ( !jQuery.nodeName( this, "form" ) ) { + jQuery.event.add(this, "click.specialSubmit", function( e ) { + var elem = e.target, + type = elem.type; + + if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { + trigger( "submit", this, arguments ); + } + }); + + jQuery.event.add(this, "keypress.specialSubmit", function( e ) { + var elem = e.target, + type = elem.type; + + if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { + trigger( "submit", this, arguments ); + } + }); + + } else { + return false; + } + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialSubmit" ); + } + }; + +} + +// change delegation, happens here so we have bind. +if ( !jQuery.support.changeBubbles ) { + + var changeFilters, + + getVal = function( elem ) { + var type = elem.type, val = elem.value; + + if ( type === "radio" || type === "checkbox" ) { + val = elem.checked; + + } else if ( type === "select-multiple" ) { + val = elem.selectedIndex > -1 ? + jQuery.map( elem.options, function( elem ) { + return elem.selected; + }).join("-") : + ""; + + } else if ( jQuery.nodeName( elem, "select" ) ) { + val = elem.selectedIndex; + } + + return val; + }, + + testChange = function testChange( e ) { + var elem = e.target, data, val; + + if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) { + return; + } + + data = jQuery._data( elem, "_change_data" ); + val = getVal(elem); + + // the current data will be also retrieved by beforeactivate + if ( e.type !== "focusout" || elem.type !== "radio" ) { + jQuery._data( elem, "_change_data", val ); + } + + if ( data === undefined || val === data ) { + return; + } + + if ( data != null || val ) { + e.type = "change"; + e.liveFired = undefined; + jQuery.event.trigger( e, arguments[1], elem ); + } + }; + + jQuery.event.special.change = { + filters: { + focusout: testChange, + + beforedeactivate: testChange, + + click: function( e ) { + var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : ""; + + if ( type === "radio" || type === "checkbox" || jQuery.nodeName( elem, "select" ) ) { + testChange.call( this, e ); + } + }, + + // Change has to be called before submit + // Keydown will be called before keypress, which is used in submit-event delegation + keydown: function( e ) { + var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : ""; + + if ( (e.keyCode === 13 && !jQuery.nodeName( elem, "textarea" ) ) || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" ) { + testChange.call( this, e ); + } + }, + + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information + beforeactivate: function( e ) { + var elem = e.target; + jQuery._data( elem, "_change_data", getVal(elem) ); + } + }, + + setup: function( data, namespaces ) { + if ( this.type === "file" ) { + return false; + } + + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); + } + + return rformElems.test( this.nodeName ); + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialChange" ); + + return rformElems.test( this.nodeName ); + } + }; + + changeFilters = jQuery.event.special.change.filters; + + // Handle when the input is .focus()'d + changeFilters.focus = changeFilters.beforeactivate; +} + +function trigger( type, elem, args ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + // Don't pass args or remember liveFired; they apply to the donor event. + var event = jQuery.extend( {}, args[ 0 ] ); + event.type = type; + event.originalEvent = {}; + event.liveFired = undefined; + jQuery.event.handle.call( elem, event ); + if ( event.isDefaultPrevented() ) { + args[ 0 ].preventDefault(); + } +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + + function handler( donor ) { + // Donor event is always a native one; fix it and switch its type. + // Let focusin/out handler cancel the donor focus/blur event. + var e = jQuery.event.fix( donor ); + e.type = fix; + e.originalEvent = {}; + jQuery.event.trigger( e, null, e.target ); + if ( e.isDefaultPrevented() ) { + donor.preventDefault(); + } + } + }); +} + +jQuery.each(["bind", "one"], function( i, name ) { + jQuery.fn[ name ] = function( type, data, fn ) { + var handler; + + // Handle object literals + if ( typeof type === "object" ) { + for ( var key in type ) { + this[ name ](key, data, type[key], fn); + } + return this; + } + + if ( arguments.length === 2 || data === false ) { + fn = data; + data = undefined; + } + + if ( name === "one" ) { + handler = function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }; + handler.guid = fn.guid || jQuery.guid++; + } else { + handler = fn; + } + + if ( type === "unload" && name !== "one" ) { + this.one( type, data, fn ); + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.add( this[i], type, handler, data ); + } + } + + return this; + }; +}); + +jQuery.fn.extend({ + unbind: function( type, fn ) { + // Handle object literals + if ( typeof type === "object" && !type.preventDefault ) { + for ( var key in type ) { + this.unbind(key, type[key]); + } + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.remove( this[i], type, fn ); + } + } + + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.live( types, data, fn, selector ); + }, + + undelegate: function( selector, types, fn ) { + if ( arguments.length === 0 ) { + return this.unbind( "live" ); + + } else { + return this.die( types, null, fn, selector ); + } + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + + triggerHandler: function( type, data ) { + if ( this[0] ) { + return jQuery.event.trigger( type, data, this[0], true ); + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + }; + + // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; + while ( i < args.length ) { + args[ i++ ].guid = guid; + } + + return this.click( toggler ); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +var liveMap = { + focus: "focusin", + blur: "focusout", + mouseenter: "mouseover", + mouseleave: "mouseout" +}; + +jQuery.each(["live", "die"], function( i, name ) { + jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { + var type, i = 0, match, namespaces, preType, + selector = origSelector || this.selector, + context = origSelector ? this : jQuery( this.context ); + + if ( typeof types === "object" && !types.preventDefault ) { + for ( var key in types ) { + context[ name ]( key, data, types[key], selector ); + } + + return this; + } + + if ( name === "die" && !types && + origSelector && origSelector.charAt(0) === "." ) { + + context.unbind( origSelector ); + + return this; + } + + if ( data === false || jQuery.isFunction( data ) ) { + fn = data || returnFalse; + data = undefined; + } + + types = (types || "").split(" "); + + while ( (type = types[ i++ ]) != null ) { + match = rnamespaces.exec( type ); + namespaces = ""; + + if ( match ) { + namespaces = match[0]; + type = type.replace( rnamespaces, "" ); + } + + if ( type === "hover" ) { + types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); + continue; + } + + preType = type; + + if ( liveMap[ type ] ) { + types.push( liveMap[ type ] + namespaces ); + type = type + namespaces; + + } else { + type = (liveMap[ type ] || type) + namespaces; + } + + if ( name === "live" ) { + // bind live handler + for ( var j = 0, l = context.length; j < l; j++ ) { + jQuery.event.add( context[j], "live." + liveConvert( type, selector ), + { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); + } + + } else { + // unbind live handler + context.unbind( "live." + liveConvert( type, selector ), fn ); + } + } + + return this; + }; +}); + +function liveHandler( event ) { + var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, + elems = [], + selectors = [], + events = jQuery._data( this, "events" ); + + // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911) + if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) { + return; + } + + if ( event.namespace ) { + namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + event.liveFired = this; + + var live = events.live.slice(0); + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { + selectors.push( handleObj.selector ); + + } else { + live.splice( j--, 1 ); + } + } + + match = jQuery( event.target ).closest( selectors, event.currentTarget ); + + for ( i = 0, l = match.length; i < l; i++ ) { + close = match[i]; + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) { + elem = close.elem; + related = null; + + // Those two events require additional checking + if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { + event.type = handleObj.preType; + related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; + + // Make sure not to accidentally match a child element with the same selector + if ( related && jQuery.contains( elem, related ) ) { + related = elem; + } + } + + if ( !related || related !== elem ) { + elems.push({ elem: elem, handleObj: handleObj, level: close.level }); + } + } + } + } + + for ( i = 0, l = elems.length; i < l; i++ ) { + match = elems[i]; + + if ( maxLevel && match.level > maxLevel ) { + break; + } + + event.currentTarget = match.elem; + event.data = match.handleObj.data; + event.handleObj = match.handleObj; + + ret = match.handleObj.origHandler.apply( match.elem, arguments ); + + if ( ret === false || event.isPropagationStopped() ) { + maxLevel = match.level; + + if ( ret === false ) { + stop = false; + } + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + + return stop; +} + +function liveConvert( type, selector ) { + return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspaces, "&"); +} + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.bind( name, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } +}); + + + +/*! + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true, + rBackslash = /\\/g, + rNonWord = /\W/; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function() { + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function( selector, context, results, seed ) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec( "" ); + m = chunker.exec( soFar ); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set ); + } + } + + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray( set ); + + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function( results ) { + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); + } + } + } + } + + return results; +}; + +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); +}; + +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; +}; + +Sizzle.find = function( expr, context, isXML ) { + var set; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var match, + type = Expr.order[i]; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice( 1, 1 ); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace( rBackslash, "" ); + set = Expr.find[ type ]( match, context, isXML ); + + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName( "*" ) : + []; + } + + return { set: set, expr: expr }; +}; + +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var found, item, + filter = Expr.filter[ type ], + left = match[1]; + + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + + } else { + curLoop[i] = false; + } + + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + + leftMatch: {}, + + attrMap: { + "class": "className", + "for": "htmlFor" + }, + + attrHandle: { + href: function( elem ) { + return elem.getAttribute( "href" ); + }, + type: function( elem ) { + return elem.getAttribute( "type" ); + } + }, + + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !rNonWord.test( part ), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + + "": function(checkSet, part, isXML){ + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); + }, + + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); + } + }, + + find: { + ID: function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }, + + NAME: function( match, context ) { + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], + results = context.getElementsByName( match[1] ); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + + TAG: function( match, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( match[1] ); + } + } + }, + preFilter: { + CLASS: function( match, curLoop, inplace, result, not, isXML ) { + match = " " + match[1].replace( rBackslash, "" ) + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID: function( match ) { + return match[1].replace( rBackslash, "" ); + }, + + TAG: function( match, curLoop ) { + return match[1].replace( rBackslash, "" ).toLowerCase(); + }, + + CHILD: function( match ) { + if ( match[1] === "nth" ) { + if ( !match[2] ) { + Sizzle.error( match[0] ); + } + + match[2] = match[2].replace(/^\+|\s*/g, ''); + + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + else if ( match[2] ) { + Sizzle.error( match[0] ); + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { + var name = match[1] = match[1].replace( rBackslash, "" ); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + // Handle if an un-quoted value was used + match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + + PSEUDO: function( match, curLoop, inplace, result, not ) { + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + + if ( !inplace ) { + result.push.apply( result, ret ); + } + + return false; + } + + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + + POS: function( match ) { + match.unshift( true ); + + return match; + } + }, + + filters: { + enabled: function( elem ) { + return elem.disabled === false && elem.type !== "hidden"; + }, + + disabled: function( elem ) { + return elem.disabled === true; + }, + + checked: function( elem ) { + return elem.checked === true; + }, + + selected: function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + parent: function( elem ) { + return !!elem.firstChild; + }, + + empty: function( elem ) { + return !elem.firstChild; + }, + + has: function( elem, i, match ) { + return !!Sizzle( match[3], elem ).length; + }, + + header: function( elem ) { + return (/h\d/i).test( elem.nodeName ); + }, + + text: function( elem ) { + var attr = elem.getAttribute( "type" ), type = elem.type; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); + }, + + radio: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; + }, + + checkbox: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; + }, + + file: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; + }, + + password: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; + }, + + submit: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "submit" === elem.type; + }, + + image: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; + }, + + reset: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "reset" === elem.type; + }, + + button: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && "button" === elem.type || name === "button"; + }, + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); + }, + + focus: function( elem ) { + return elem === elem.ownerDocument.activeElement; + } + }, + setFilters: { + first: function( elem, i ) { + return i === 0; + }, + + last: function( elem, i, match, array ) { + return i === array.length - 1; + }, + + even: function( elem, i ) { + return i % 2 === 0; + }, + + odd: function( elem, i ) { + return i % 2 === 1; + }, + + lt: function( elem, i, match ) { + return i < match[3] - 0; + }, + + gt: function( elem, i, match ) { + return i > match[3] - 0; + }, + + nth: function( elem, i, match ) { + return match[3] - 0 === i; + }, + + eq: function( elem, i, match ) { + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; + + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + + } else { + Sizzle.error( name ); + } + }, + + CHILD: function( elem, match ) { + var type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + if ( type === "first" ) { + return true; + } + + node = elem; + + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + return true; + + case "nth": + var first = match[2], + last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + + if ( first === 0 ) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + + ID: function( elem, match ) { + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + + TAG: function( elem, match ) { + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + + CLASS: function( elem, match ) { + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + + ATTR: function( elem, match ) { + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} + +var makeArray = function( array, results ) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder, siblingCheck; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + return a.compareDocumentPosition ? -1 : 1; + } + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; + }; + +} else { + sortOrder = function( a, b ) { + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Fallback to using sourceIndex (in IE) if it's available on both nodes + } else if ( a.sourceIndex && b.sourceIndex ) { + return a.sourceIndex - b.sourceIndex; + } + + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // If the nodes are siblings (or identical) we can do a quick check + if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + + siblingCheck = function( a, b, ret ) { + if ( a === b ) { + return ret; + } + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; +} + +// Utility function for retreiving the text value of an array of DOM nodes +Sizzle.getText = function( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += Sizzle.getText( elem.childNodes ); + } + } + + return ret; +}; + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(), + root = document.documentElement; + + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; + } + }; + + Expr.filter.ID = function( elem, match ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + + // release memory in IE + root = form = null; +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); + }; + } + + // release memory in IE + div = null; +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + + div.innerHTML = "

"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function( query, context, extra, seed ) { + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && !Sizzle.isXML(context) ) { + // See if we find a selector to speed up + var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); + + if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { + // Speed-up: Sizzle("TAG") + if ( match[1] ) { + return makeArray( context.getElementsByTagName( query ), extra ); + + // Speed-up: Sizzle(".CLASS") + } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { + return makeArray( context.getElementsByClassName( match[2] ), extra ); + } + } + + if ( context.nodeType === 9 ) { + // Speed-up: Sizzle("body") + // The body element only exists once, optimize finding it + if ( query === "body" && context.body ) { + return makeArray( [ context.body ], extra ); + + // Speed-up: Sizzle("#ID") + } else if ( match && match[3] ) { + var elem = context.getElementById( match[3] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id === match[3] ) { + return makeArray( [ elem ], extra ); + } + + } else { + return makeArray( [], extra ); + } + } + + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(qsaError) {} + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + var oldContext = context, + old = context.getAttribute( "id" ), + nid = old || id, + hasParent = context.parentNode, + relativeHierarchySelector = /^\s*[+~]/.test( query ); + + if ( !old ) { + context.setAttribute( "id", nid ); + } else { + nid = nid.replace( /'/g, "\\$&" ); + } + if ( relativeHierarchySelector && hasParent ) { + context = context.parentNode; + } + + try { + if ( !relativeHierarchySelector || hasParent ) { + return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); + } + + } catch(pseudoError) { + } finally { + if ( !old ) { + oldContext.removeAttribute( "id" ); + } + } + } + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + // release memory in IE + div = null; + })(); +} + +(function(){ + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; + + if ( matches ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9 fails this) + var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( document.documentElement, "[test!='']:sizzle" ); + + } catch( pseudoError ) { + pseudoWorks = true; + } + + Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { + try { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { + var ret = matches.call( node, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || !disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9, so check for that + node.document && node.document.nodeType !== 11 ) { + return ret; + } + } + } catch(e) {} + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } +})(); + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
"; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function( match, context, isXML ) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + // release memory in IE + div = null; +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function( selector, context ) { + var match, + tmpSet = [], + later = "", + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})(); + + +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice, + POS = jQuery.expr.match.POS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var self = this, + i, l; + + if ( typeof selector !== "string" ) { + return jQuery( selector ).filter(function() { + for ( i = 0, l = self.length; i < l; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }); + } + + var ret = this.pushStack( "", "find", selector ), + length, n, r; + + for ( i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( n = length; n < ret.length; n++ ) { + for ( r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && ( typeof selector === "string" ? + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var ret = [], i, l, cur = this[0]; + + // Array + if ( jQuery.isArray( selectors ) ) { + var match, selector, + matches = {}, + level = 1; + + if ( cur && selectors.length ) { + for ( i = 0, l = selectors.length; i < l; i++ ) { + selector = selectors[i]; + + if ( !matches[ selector ] ) { + matches[ selector ] = POS.test( selector ) ? + jQuery( selector, context || this.context ) : + selector; + } + } + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( selector in matches ) { + match = matches[ selector ]; + + if ( match.jquery ? match.index( cur ) > -1 : jQuery( cur ).is( match ) ) { + ret.push({ selector: selector, elem: cur, level: level }); + } + } + + cur = cur.parentNode; + level++; + } + } + + return ret; + } + + // String + var pos = POS.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( i = 0, l = this.length; i < l; i++ ) { + cur = this[i]; + + while ( cur ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + + } else { + cur = cur.parentNode; + if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { + break; + } + } + } + } + + ret = ret.length > 1 ? jQuery.unique( ret ) : ret; + + return this.pushStack( ret, "closest", selectors ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + if ( !elem || typeof elem === "string" ) { + return jQuery.inArray( this[0], + // If it receives a string, the selector is used + // If it receives nothing, the siblings are used + elem ? jQuery( elem ) : this.parent().children() ); + } + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( elem.parentNode.firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ), + // The variable 'args' was introduced in + // https://github.com/jquery/jquery/commit/52a0238 + // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed. + // http://code.google.com/p/v8/issues/detail?id=1050 + args = slice.call(arguments); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, args.join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return (elem === qualifier) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return (jQuery.inArray( elem, qualifier ) >= 0) === keep; + }); +} + + + + +var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /", "" ], + legend: [ 1, "
", "
" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + col: [ 2, "", "
" ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize and " + + "" + + "" + + WYMeditor.DIALOG_BODY + + "", + + dialogLinkHtml: "" + + "
" + + "
" + + "" + + "{Link}" + + "
" + + "" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "
" + + "
" + + "", + + dialogImageHtml: "" + + "
" + + "
" + + "" + + "{Image}" + + "
" + + "" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "
" + + "
" + + "", + + dialogTableHtml: "" + + "
" + + "
" + + "" + + "{Table}" + + "
" + + "" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "
" + + "
" + + "", + + dialogPasteHtml: "" + + "
" + + "" + + "
" + + "{Paste_From_Word}" + + "
" + + "" + + "
" + + "
" + + "" + + "" + + "
" + + "
" + + "
" + + "", + + dialogPreviewHtml: "", + + dialogStyles: [], + + stringDelimiterLeft: "{", + stringDelimiterRight:"}", + + preInit: null, + preBind: null, + postInit: null, + + preInitDialog: null, + postInitDialog: null + + }, options); + + return this.each(function() { + + new WYMeditor.editor(jQuery(this),options); + }); +}; + +/* @name extend + * @description Returns the WYMeditor instance based on its index + */ +jQuery.extend({ + wymeditors: function(i) { + return (WYMeditor.INSTANCES[i]); + } +}); + + +/********** WYMeditor **********/ + +/* @name Wymeditor + * @description WYMeditor class + */ + +/* @name init + * @description Initializes a WYMeditor instance + */ +WYMeditor.editor.prototype.init = function() { + + //load subclass - browser specific + //unsupported browsers: do nothing + if (jQuery.browser.msie) { + var WymClass = new WYMeditor.WymClassExplorer(this); + } + else if (jQuery.browser.mozilla) { + var WymClass = new WYMeditor.WymClassMozilla(this); + } + else if (jQuery.browser.opera) { + var WymClass = new WYMeditor.WymClassOpera(this); + } + else if (jQuery.browser.safari) { + var WymClass = new WYMeditor.WymClassSafari(this); + } + + if(WymClass) { + + if(jQuery.isFunction(this._options.preInit)) this._options.preInit(this); + + var SaxListener = new WYMeditor.XhtmlSaxListener(); + jQuery.extend(SaxListener, WymClass); + this.parser = new WYMeditor.XhtmlParser(SaxListener); + + if(this._options.styles || this._options.stylesheet){ + this.configureEditorUsingRawCss(); + } + + this.helper = new WYMeditor.XmlHelper(); + + //extend the Wymeditor object + //don't use jQuery.extend since 1.1.4 + //jQuery.extend(this, WymClass); + for (var prop in WymClass) { this[prop] = WymClass[prop]; } + + //load wymbox + this._box = jQuery(this._element).hide().after(this._options.boxHtml).next().addClass('wym_box_' + this._index); + + //store the instance index in wymbox and element replaced by editor instance + //but keep it compatible with jQuery < 1.2.3, see #122 + if( jQuery.isFunction( jQuery.fn.data ) ) { + jQuery.data(this._box.get(0), WYMeditor.WYM_INDEX, this._index); + jQuery.data(this._element.get(0), WYMeditor.WYM_INDEX, this._index); + } + + var h = WYMeditor.Helper; + + //construct the iframe + var iframeHtml = this._options.iframeHtml; + iframeHtml = h.replaceAll(iframeHtml, WYMeditor.INDEX, this._index); + iframeHtml = h.replaceAll(iframeHtml, WYMeditor.IFRAME_BASE_PATH, this._options.iframeBasePath); + + //construct wymbox + var boxHtml = jQuery(this._box).html(); + + boxHtml = h.replaceAll(boxHtml, WYMeditor.LOGO, this._options.logoHtml); + boxHtml = h.replaceAll(boxHtml, WYMeditor.TOOLS, this._options.toolsHtml); + boxHtml = h.replaceAll(boxHtml, WYMeditor.CONTAINERS,this._options.containersHtml); + boxHtml = h.replaceAll(boxHtml, WYMeditor.CLASSES, this._options.classesHtml); + boxHtml = h.replaceAll(boxHtml, WYMeditor.HTML, this._options.htmlHtml); + boxHtml = h.replaceAll(boxHtml, WYMeditor.IFRAME, iframeHtml); + boxHtml = h.replaceAll(boxHtml, WYMeditor.STATUS, this._options.statusHtml); + + //construct tools list + var aTools = eval(this._options.toolsItems); + var sTools = ""; + + for(var i = 0; i < aTools.length; i++) { + var oTool = aTools[i]; + if(oTool.name && oTool.title) + var sTool = this._options.toolsItemHtml; + var sTool = h.replaceAll(sTool, WYMeditor.TOOL_NAME, oTool.name); + sTool = h.replaceAll(sTool, WYMeditor.TOOL_TITLE, this._options.stringDelimiterLeft + + oTool.title + + this._options.stringDelimiterRight); + sTool = h.replaceAll(sTool, WYMeditor.TOOL_CLASS, oTool.css); + sTools += sTool; + } + + boxHtml = h.replaceAll(boxHtml, WYMeditor.TOOLS_ITEMS, sTools); + + //construct classes list + var aClasses = eval(this._options.classesItems); + var sClasses = ""; + + for(var i = 0; i < aClasses.length; i++) { + var oClass = aClasses[i]; + if(oClass.name && oClass.title) + var sClass = this._options.classesItemHtml; + sClass = h.replaceAll(sClass, WYMeditor.CLASS_NAME, oClass.name); + sClass = h.replaceAll(sClass, WYMeditor.CLASS_TITLE, oClass.title); + sClasses += sClass; + } + + boxHtml = h.replaceAll(boxHtml, WYMeditor.CLASSES_ITEMS, sClasses); + + //construct containers list + var aContainers = eval(this._options.containersItems); + var sContainers = ""; + + for(var i = 0; i < aContainers.length; i++) { + var oContainer = aContainers[i]; + if(oContainer.name && oContainer.title) + var sContainer = this._options.containersItemHtml; + sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_NAME, oContainer.name); + sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_TITLE, + this._options.stringDelimiterLeft + + oContainer.title + + this._options.stringDelimiterRight); + sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_CLASS, oContainer.css); + sContainers += sContainer; + } + + boxHtml = h.replaceAll(boxHtml, WYMeditor.CONTAINERS_ITEMS, sContainers); + + //l10n + boxHtml = this.replaceStrings(boxHtml); + + //load html in wymbox + jQuery(this._box).html(boxHtml); + + //hide the html value + jQuery(this._box).find(this._options.htmlSelector).hide(); + + //enable the skin + this.loadSkin(); + + } +}; + +WYMeditor.editor.prototype.bindEvents = function() { + + //copy the instance + var wym = this; + + //handle click event on tools buttons + jQuery(this._box).find(this._options.toolSelector).click(function() { + wym._iframe.contentWindow.focus(); //See #154 + wym.exec(jQuery(this).attr(WYMeditor.NAME)); + return(false); + }); + + //handle click event on containers buttons + jQuery(this._box).find(this._options.containerSelector).click(function() { + wym.container(jQuery(this).attr(WYMeditor.NAME)); + return(false); + }); + + //handle keyup event on html value: set the editor value + //handle focus/blur events to check if the element has focus, see #147 + jQuery(this._box).find(this._options.htmlValSelector) + .keyup(function() { jQuery(wym._doc.body).html(jQuery(this).val());}) + .focus(function() { jQuery(this).toggleClass('hasfocus'); }) + .blur(function() { jQuery(this).toggleClass('hasfocus'); }); + + //handle click event on classes buttons + jQuery(this._box).find(this._options.classSelector).click(function() { + + var aClasses = eval(wym._options.classesItems); + var sName = jQuery(this).attr(WYMeditor.NAME); + + var oClass = WYMeditor.Helper.findByName(aClasses, sName); + + if(oClass) { + var jqexpr = oClass.expr; + wym.toggleClass(sName, jqexpr); + } + wym._iframe.contentWindow.focus(); //See #154 + return(false); + }); + + //handle event on update element + jQuery(this._options.updateSelector) + .bind(this._options.updateEvent, function() { + wym.update(); + }); +}; + +WYMeditor.editor.prototype.ready = function() { + return(this._doc != null); +}; + + +/********** METHODS **********/ + +/* @name box + * @description Returns the WYMeditor container + */ +WYMeditor.editor.prototype.box = function() { + return(this._box); +}; + +/* @name html + * @description Get/Set the html value + */ +WYMeditor.editor.prototype.html = function(html) { + + if(typeof html === 'string') jQuery(this._doc.body).html(html); + else return(jQuery(this._doc.body).html()); +}; + +/* @name xhtml + * @description Cleans up the HTML + */ +WYMeditor.editor.prototype.xhtml = function() { + return this.parser.parse(this.html()); +}; + +/* @name exec + * @description Executes a button command + */ +WYMeditor.editor.prototype.exec = function(cmd) { + + //base function for execCommand + //open a dialog or exec + switch(cmd) { + case WYMeditor.CREATE_LINK: + var container = this.container(); + if(container || this._selected_image) this.dialog(WYMeditor.DIALOG_LINK); + break; + + case WYMeditor.INSERT_IMAGE: + this.dialog(WYMeditor.DIALOG_IMAGE); + break; + + case WYMeditor.INSERT_TABLE: + this.dialog(WYMeditor.DIALOG_TABLE); + break; + + case WYMeditor.PASTE: + this.dialog(WYMeditor.DIALOG_PASTE); + break; + + case WYMeditor.TOGGLE_HTML: + this.update(); + this.toggleHtml(); + break; + + case WYMeditor.PREVIEW: + this.dialog(WYMeditor.PREVIEW, this._options.dialogFeaturesPreview); + break; + + default: + this._exec(cmd); + break; + } +}; + +/* @name container + * @description Get/Set the selected container + */ +WYMeditor.editor.prototype.container = function(sType) { + + if(sType) { + + var container = null; + + if(sType.toLowerCase() == WYMeditor.TH) { + + container = this.container(); + + //find the TD or TH container + switch(container.tagName.toLowerCase()) { + + case WYMeditor.TD: case WYMeditor.TH: + break; + default: + var aTypes = new Array(WYMeditor.TD,WYMeditor.TH); + container = this.findUp(this.container(), aTypes); + break; + } + + //if it exists, switch + if(container!=null) { + + sType = (container.tagName.toLowerCase() == WYMeditor.TD)? WYMeditor.TH: WYMeditor.TD; + this.switchTo(container,sType); + this.update(); + } + } else { + + //set the container type + var aTypes=new Array(WYMeditor.P,WYMeditor.H1,WYMeditor.H2,WYMeditor.H3,WYMeditor.H4,WYMeditor.H5, + WYMeditor.H6,WYMeditor.PRE,WYMeditor.BLOCKQUOTE); + container = this.findUp(this.container(), aTypes); + + if(container) { + + var newNode = null; + + //blockquotes must contain a block level element + if(sType.toLowerCase() == WYMeditor.BLOCKQUOTE) { + + var blockquote = this.findUp(this.container(), WYMeditor.BLOCKQUOTE); + + if(blockquote == null) { + + newNode = this._doc.createElement(sType); + container.parentNode.insertBefore(newNode,container); + newNode.appendChild(container); + this.setFocusToNode(newNode.firstChild); + + } else { + + var nodes = blockquote.childNodes; + var lgt = nodes.length; + var firstNode = null; + + if(lgt > 0) firstNode = nodes.item(0); + for(var x=0; x') ) + + '

'; + } + + // Insert where appropriate + if (container && container.tagName.toLowerCase() != WYMeditor.BODY) { + // No .last() pre jQuery 1.4 + //focusNode = jQuery(html).insertAfter(container).last()[0]; + paragraphs = jQuery(html, this._doc).insertAfter(container); + focusNode = paragraphs[paragraphs.length - 1]; + } else { + paragraphs = jQuery(html, this._doc).appendTo(this._doc.body); + focusNode = paragraphs[paragraphs.length - 1]; + } + + // Do some minor cleanup (#131) + if (jQuery(container).text() == '') { + jQuery(container).remove(); + } + // And remove br (if editor was empty) + jQuery('body > br', this._doc).remove(); + + // Restore focus + this.setFocusToNode(focusNode); +}; + +WYMeditor.editor.prototype.insert = function(html) { + // Do we have a selection? + var selection = this._iframe.contentWindow.getSelection(), + range, + node; + if (selection.focusNode != null) { + // Overwrite selection with provided html + range = selection.getRangeAt(0); + node = range.createContextualFragment(html); + range.deleteContents(); + range.insertNode(node); + } else { + // Fall back to the internal paste function if there's no selection + this.paste(html) + } +}; + +WYMeditor.editor.prototype.wrap = function(left, right) { + this.insert(left + this._iframe.contentWindow.getSelection().toString() + right); +}; + +WYMeditor.editor.prototype.unwrap = function() { + this.insert(this._iframe.contentWindow.getSelection().toString()); +}; + +WYMeditor.editor.prototype.setFocusToNode = function(node, toStart) { + var range = this._doc.createRange(), + selection = this._iframe.contentWindow.getSelection(); + toStart = toStart ? 0 : 1; + + range.selectNodeContents(node); + selection.addRange(range); + selection.collapse(node, toStart); + this._iframe.contentWindow.focus(); +}; + +WYMeditor.editor.prototype.addCssRules = function(doc, aCss) { + var styles = doc.styleSheets[0]; + if(styles) { + for(var i = 0; i < aCss.length; i++) { + var oCss = aCss[i]; + if(oCss.name && oCss.css) this.addCssRule(styles, oCss); + } + } +}; + +/********** CONFIGURATION **********/ + +WYMeditor.editor.prototype.computeBasePath = function() { + return jQuery(jQuery.grep(jQuery('script'), function(s){ + return (s.src && s.src.match(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/ )) + })).attr('src').replace(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/, ''); +}; + +WYMeditor.editor.prototype.computeWymPath = function() { + return jQuery(jQuery.grep(jQuery('script'), function(s){ + return (s.src && s.src.match(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/ )) + })).attr('src'); +}; + +WYMeditor.editor.prototype.computeJqueryPath = function() { + return jQuery(jQuery.grep(jQuery('script'), function(s){ + return (s.src && s.src.match(/jquery(-(.*)){0,1}(\.pack|\.min|\.packed)?\.js(\?.*)?$/ )) + })).attr('src'); +}; + +WYMeditor.editor.prototype.computeCssPath = function() { + return jQuery(jQuery.grep(jQuery('link'), function(s){ + return (s.href && s.href.match(/wymeditor\/skins\/(.*)screen\.css(\?.*)?$/ )) + })).attr('href'); +}; + +WYMeditor.editor.prototype.configureEditorUsingRawCss = function() { + + var CssParser = new WYMeditor.WymCssParser(); + if(this._options.stylesheet){ + CssParser.parse(jQuery.ajax({url: this._options.stylesheet,async:false}).responseText); + }else{ + CssParser.parse(this._options.styles, false); + } + + if(this._options.classesItems.length == 0) { + this._options.classesItems = CssParser.css_settings.classesItems; + } + if(this._options.editorStyles.length == 0) { + this._options.editorStyles = CssParser.css_settings.editorStyles; + } + if(this._options.dialogStyles.length == 0) { + this._options.dialogStyles = CssParser.css_settings.dialogStyles; + } +}; + +/********** EVENTS **********/ + +WYMeditor.editor.prototype.listen = function() { + //don't use jQuery.find() on the iframe body + //because of MSIE + jQuery + expando issue (#JQ1143) + //jQuery(this._doc.body).find("*").bind("mouseup", this.mouseup); + + jQuery(this._doc.body).bind("mousedown", this.mousedown); +}; + +WYMeditor.editor.prototype.mousedown = function(evt) { + var wym = WYMeditor.INSTANCES[this.ownerDocument.title]; + wym._selected_image = (evt.target.tagName.toLowerCase() == WYMeditor.IMG) ? evt.target : null; +}; + +/********** SKINS **********/ + +/* + * Function: WYMeditor.loadCss + * Loads a stylesheet in the document. + * + * Parameters: + * href - The CSS path. + */ +WYMeditor.loadCss = function(href) { + + var link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = href; + + var head = jQuery('head').get(0); + head.appendChild(link); +}; + +/* + * Function: WYMeditor.editor.loadSkin + * Loads the skin CSS and initialization script (if needed). + */ +WYMeditor.editor.prototype.loadSkin = function() { + + //does the user want to automatically load the CSS (default: yes)? + //we also test if it hasn't been already loaded by another instance + //see below for a better (second) test + if(this._options.loadSkin && !WYMeditor.SKINS[this._options.skin]) { + + //check if it hasn't been already loaded + //so we don't load it more than once + //(we check the existing elements) + + var found = false; + var rExp = new RegExp(this._options.skin + + '\/' + WYMeditor.SKINS_DEFAULT_CSS + '$'); + + jQuery('link').each( function() { + if(this.href.match(rExp)) found = true; + }); + + //load it, using the skin path + if(!found) WYMeditor.loadCss( this._options.skinPath + + WYMeditor.SKINS_DEFAULT_CSS ); + } + + //put the classname (ex. wym_skin_default) on wym_box + jQuery(this._box).addClass( "wym_skin_" + this._options.skin ); + + //does the user want to use some JS to initialize the skin (default: yes)? + //also check if it hasn't already been loaded by another instance + if(this._options.initSkin && !WYMeditor.SKINS[this._options.skin]) { + + eval(jQuery.ajax({url:this._options.skinPath + + WYMeditor.SKINS_DEFAULT_JS, async:false}).responseText); + } + + //init the skin, if needed + if(WYMeditor.SKINS[this._options.skin] + && WYMeditor.SKINS[this._options.skin].init) + WYMeditor.SKINS[this._options.skin].init(this); + +}; + + +/********** DIALOGS **********/ + +WYMeditor.INIT_DIALOG = function(index) { + + var wym = window.opener.WYMeditor.INSTANCES[index]; + var doc = window.document; + var selected = wym.selected(); + var dialogType = jQuery(wym._options.dialogTypeSelector).val(); + var sStamp = wym.uniqueStamp(); + + switch(dialogType) { + + case WYMeditor.DIALOG_LINK: + //ensure that we select the link to populate the fields + if(selected && selected.tagName && selected.tagName.toLowerCase != WYMeditor.A) + selected = jQuery(selected).parentsOrSelf(WYMeditor.A); + + //fix MSIE selection if link image has been clicked + if(!selected && wym._selected_image) + selected = jQuery(wym._selected_image).parentsOrSelf(WYMeditor.A); + break; + + } + + //pre-init functions + if(jQuery.isFunction(wym._options.preInitDialog)) + wym._options.preInitDialog(wym,window); + + //add css rules from options + var styles = doc.styleSheets[0]; + var aCss = eval(wym._options.dialogStyles); + + wym.addCssRules(doc, aCss); + + //auto populate fields if selected container (e.g. A) + if(selected) { + jQuery(wym._options.hrefSelector).val(jQuery(selected).attr(WYMeditor.HREF)); + jQuery(wym._options.srcSelector).val(jQuery(selected).attr(WYMeditor.SRC)); + jQuery(wym._options.titleSelector).val(jQuery(selected).attr(WYMeditor.TITLE)); + jQuery(wym._options.relSelector).val(jQuery(selected).attr(WYMeditor.REL)); + jQuery(wym._options.altSelector).val(jQuery(selected).attr(WYMeditor.ALT)); + } + + //auto populate image fields if selected image + if(wym._selected_image) { + jQuery(wym._options.dialogImageSelector + " " + wym._options.srcSelector) + .val(jQuery(wym._selected_image).attr(WYMeditor.SRC)); + jQuery(wym._options.dialogImageSelector + " " + wym._options.titleSelector) + .val(jQuery(wym._selected_image).attr(WYMeditor.TITLE)); + jQuery(wym._options.dialogImageSelector + " " + wym._options.altSelector) + .val(jQuery(wym._selected_image).attr(WYMeditor.ALT)); + } + + jQuery(wym._options.dialogLinkSelector + " " + + wym._options.submitSelector).submit(function() { + + var sUrl = jQuery(wym._options.hrefSelector).val(); + if(sUrl.length > 0) { + var link; + + if (selected[0] && selected[0].tagName.toLowerCase() == WYMeditor.A) { + link = selected; + } else { + wym._exec(WYMeditor.CREATE_LINK, sStamp); + link = jQuery("a[href=" + sStamp + "]", wym._doc.body); + } + + link.attr(WYMeditor.HREF, sUrl) + .attr(WYMeditor.TITLE, jQuery(wym._options.titleSelector).val()) + .attr(WYMeditor.REL, jQuery(wym._options.relSelector).val()); + + } + window.close(); + }); + + jQuery(wym._options.dialogImageSelector + " " + + wym._options.submitSelector).submit(function() { + + var sUrl = jQuery(wym._options.srcSelector).val(); + if(sUrl.length > 0) { + + wym._exec(WYMeditor.INSERT_IMAGE, sStamp); + + jQuery("img[src$=" + sStamp + "]", wym._doc.body) + .attr(WYMeditor.SRC, sUrl) + .attr(WYMeditor.TITLE, jQuery(wym._options.titleSelector).val()) + .attr(WYMeditor.ALT, jQuery(wym._options.altSelector).val()); + } + window.close(); + }); + + jQuery(wym._options.dialogTableSelector + " " + + wym._options.submitSelector).submit(function() { + + var iRows = jQuery(wym._options.rowsSelector).val(); + var iCols = jQuery(wym._options.colsSelector).val(); + + if(iRows > 0 && iCols > 0) { + + var table = wym._doc.createElement(WYMeditor.TABLE); + var newRow = null; + var newCol = null; + + var sCaption = jQuery(wym._options.captionSelector).val(); + + //we create the caption + var newCaption = table.createCaption(); + newCaption.innerHTML = sCaption; + + //we create the rows and cells + for(x=0; x
+* this.tag ('br', false, true) +* # =>
+* this.tag ('input', jQuery({type:'text',disabled:true }) ) +* # => +*/ +WYMeditor.XmlHelper.prototype.tag = function(name, options, open) +{ + options = options || false; + open = open || false; + return '<'+name+(options ? this.tagOptions(options) : '')+(open ? '>' : ' />'); +}; + +/* +* @name contentTag +* @description +* Returns a XML block tag of type *name* surrounding the *content*. Add +* XML attributes by passing an attributes array to *options*. For attributes +* with no value like (disabled and readonly), give it a value of true in +* the *options* array. You can use symbols or strings for the attribute names. +* +* this.contentTag ('p', 'Hello world!' ) +* # =>

Hello world!

+* this.contentTag('div', this.contentTag('p', "Hello world!"), jQuery({class : "strong"})) +* # =>

Hello world!

+* this.contentTag("select", options, jQuery({multiple : true})) +* # => +*/ +WYMeditor.XmlHelper.prototype.contentTag = function(name, content, options) +{ + options = options || false; + return '<'+name+(options ? this.tagOptions(options) : '')+'>'+content+''; +}; + +/* +* @name cdataSection +* @description +* Returns a CDATA section for the given +content+. CDATA sections +* are used to escape blocks of text containing characters which would +* otherwise be recognized as markup. CDATA sections begin with the string +* <![CDATA[ and } with (and may not contain) the string +* ]]>. +*/ +WYMeditor.XmlHelper.prototype.cdataSection = function(content) +{ + return ''; +}; + + +/* +* @name escapeOnce +* @description +* Returns the escaped +xml+ without affecting existing escaped entities. +* +* this.escapeOnce( "1 > 2 & 3") +* # => "1 > 2 & 3" +*/ +WYMeditor.XmlHelper.prototype.escapeOnce = function(xml) +{ + return this._fixDoubleEscape(this.escapeEntities(xml)); +}; + +/* +* @name _fixDoubleEscape +* @description +* Fix double-escaped entities, such as &amp;, &#123;, etc. +*/ +WYMeditor.XmlHelper.prototype._fixDoubleEscape = function(escaped) +{ + return escaped.replace(/&([a-z]+|(#\d+));/ig, "&$1;"); +}; + +/* +* @name tagOptions +* @description +* Takes an array like the one generated by Tag.parseAttributes +* [["src", "http://www.editam.com/?a=b&c=d&f=g"], ["title", "Editam, CMS"]] +* or an object like {src:"http://www.editam.com/?a=b&c=d&f=g", title:"Editam, CMS"} +* and returns a string properly escaped like +* ' src = "http://www.editam.com/?a=b&c=d&f=g" title = "Editam, <Simplified> CMS"' +* which is valid for strict XHTML +*/ +WYMeditor.XmlHelper.prototype.tagOptions = function(options) +{ + var xml = this; + xml._formated_options = ''; + + for (var key in options) { + var formated_options = ''; + var value = options[key]; + if(typeof value != 'function' && value.length > 0) { + + if(parseInt(key) == key && typeof value == 'object'){ + key = value.shift(); + value = value.pop(); + } + if(key != '' && value != ''){ + xml._formated_options += ' '+key+'="'+xml.escapeOnce(value)+'"'; + } + } + } + return xml._formated_options; +}; + +/* +* @name escapeEntities +* @description +* Escapes XML/HTML entities <, >, & and ". If seccond parameter is set to false it +* will not escape ". If set to true it will also escape ' +*/ +WYMeditor.XmlHelper.prototype.escapeEntities = function(string, escape_quotes) +{ + this._entitiesDiv.innerHTML = string; + this._entitiesDiv.textContent = string; + var result = this._entitiesDiv.innerHTML; + if(typeof escape_quotes == 'undefined'){ + if(escape_quotes != false) result = result.replace('"', '"'); + if(escape_quotes == true) result = result.replace('"', '''); + } + return result; +}; + +/* +* Parses a string conatining tag attributes and values an returns an array formated like +* [["src", "http://www.editam.com"], ["title", "Editam, Simplified CMS"]] +*/ +WYMeditor.XmlHelper.prototype.parseAttributes = function(tag_attributes) +{ + // Use a compounded regex to match single quoted, double quoted and unquoted attribute pairs + var result = []; + var matches = tag_attributes.split(/((=\s*")(")("))|((=\s*\')(\')(\'))|((=\s*[^>\s]*))/g); + if(matches.toString() != tag_attributes){ + for (var k in matches) { + var v = matches[k]; + if(typeof v != 'function' && v.length != 0){ + var re = new RegExp('(\\w+)\\s*'+v); + if(match = tag_attributes.match(re) ){ + var value = v.replace(/^[\s=]+/, ""); + var delimiter = value.charAt(0); + delimiter = delimiter == '"' ? '"' : (delimiter=="'"?"'":''); + if(delimiter != ''){ + value = delimiter == '"' ? value.replace(/^"|"+$/g, '') : value.replace(/^'|'+$/g, ''); + } + tag_attributes = tag_attributes.replace(match[0],''); + result.push([match[1] , value]); + } + } + } + } + return result; +}; + +/** +* XhtmlValidator for validating tag attributes +* +* @author Bermi Ferrer - http://bermi.org +*/ +WYMeditor.XhtmlValidator = { + "_attributes": + { + "core": + { + "except":[ + "base", + "head", + "html", + "meta", + "param", + "script", + "style", + "title" + ], + "attributes":[ + "class", + "id", + "style", + "title", + "accesskey", + "tabindex" + ] + }, + "language": + { + "except":[ + "base", + "br", + "hr", + "iframe", + "param", + "script" + ], + "attributes": + { + "dir":[ + "ltr", + "rtl" + ], + "0":"lang", + "1":"xml:lang" + } + }, + "keyboard": + { + "attributes": + { + "accesskey":/^(\w){1}$/, + "tabindex":/^(\d)+$/ + } + } + }, + "_events": + { + "window": + { + "only":[ + "body" + ], + "attributes":[ + "onload", + "onunload" + ] + }, + "form": + { + "only":[ + "form", + "input", + "textarea", + "select", + "a", + "label", + "button" + ], + "attributes":[ + "onchange", + "onsubmit", + "onreset", + "onselect", + "onblur", + "onfocus" + ] + }, + "keyboard": + { + "except":[ + "base", + "bdo", + "br", + "frame", + "frameset", + "head", + "html", + "iframe", + "meta", + "param", + "script", + "style", + "title" + ], + "attributes":[ + "onkeydown", + "onkeypress", + "onkeyup" + ] + }, + "mouse": + { + "except":[ + "base", + "bdo", + "br", + "head", + "html", + "meta", + "param", + "script", + "style", + "title" + ], + "attributes":[ + "onclick", + "ondblclick", + "onmousedown", + "onmousemove", + "onmouseover", + "onmouseout", + "onmouseup" + ] + } + }, + "_tags": + { + "a": + { + "attributes": + { + "0":"charset", + "1":"coords", + "2":"href", + "3":"hreflang", + "4":"name", + "5":"rel", + "6":"rev", + "shape":/^(rect|rectangle|circ|circle|poly|polygon)$/, + "7":"type" + } + }, + "0":"abbr", + "1":"acronym", + "2":"address", + "area": + { + "attributes": + { + "0":"alt", + "1":"coords", + "2":"href", + "nohref":/^(true|false)$/, + "shape":/^(rect|rectangle|circ|circle|poly|polygon)$/ + }, + "required":[ + "alt" + ] + }, + "3":"b", + "base": + { + "attributes":[ + "href" + ], + "required":[ + "href" + ] + }, + "bdo": + { + "attributes": + { + "dir":/^(ltr|rtl)$/ + }, + "required":[ + "dir" + ] + }, + "4":"big", + "blockquote": + { + "attributes":[ + "cite" + ] + }, + "5":"body", + "6":"br", + "button": + { + "attributes": + { + "disabled":/^(disabled)$/, + "type":/^(button|reset|submit)$/, + "0":"value" + }, + "inside":"form" + }, + "7":"caption", + "8":"cite", + "9":"code", + "col": + { + "attributes": + { + "align":/^(right|left|center|justify)$/, + "0":"char", + "1":"charoff", + "span":/^(\d)+$/, + "valign":/^(top|middle|bottom|baseline)$/, + "2":"width" + }, + "inside":"colgroup" + }, + "colgroup": + { + "attributes": + { + "align":/^(right|left|center|justify)$/, + "0":"char", + "1":"charoff", + "span":/^(\d)+$/, + "valign":/^(top|middle|bottom|baseline)$/, + "2":"width" + } + }, + "10":"dd", + "del": + { + "attributes": + { + "0":"cite", + "datetime":/^([0-9]){8}/ + } + }, + "11":"div", + "12":"dfn", + "13":"dl", + "14":"dt", + "15":"em", + "fieldset": + { + "inside":"form" + }, + "form": + { + "attributes": + { + "0":"action", + "1":"accept", + "2":"accept-charset", + "3":"enctype", + "method":/^(get|post)$/ + }, + "required":[ + "action" + ] + }, + "head": + { + "attributes":[ + "profile" + ] + }, + "16":"h1", + "17":"h2", + "18":"h3", + "19":"h4", + "20":"h5", + "21":"h6", + "22":"hr", + "html": + { + "attributes":[ + "xmlns" + ] + }, + "23":"i", + "img": + { + "attributes":[ + "alt", + "src", + "height", + "ismap", + "longdesc", + "usemap", + "width" + ], + "required":[ + "alt", + "src" + ] + }, + "input": + { + "attributes": + { + "0":"accept", + "1":"alt", + "checked":/^(checked)$/, + "disabled":/^(disabled)$/, + "maxlength":/^(\d)+$/, + "2":"name", + "readonly":/^(readonly)$/, + "size":/^(\d)+$/, + "3":"src", + "type":/^(button|checkbox|file|hidden|image|password|radio|reset|submit|text)$/, + "4":"value" + }, + "inside":"form" + }, + "ins": + { + "attributes": + { + "0":"cite", + "datetime":/^([0-9]){8}/ + } + }, + "24":"kbd", + "label": + { + "attributes":[ + "for" + ], + "inside":"form" + }, + "25":"legend", + "26":"li", + "link": + { + "attributes": + { + "0":"charset", + "1":"href", + "2":"hreflang", + "media":/^(all|braille|print|projection|screen|speech|,|;| )+$/i, + //next comment line required by Opera! + /*"rel":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i,*/ + "rel":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i, + "rev":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i, + "3":"type" + }, + "inside":"head" + }, + "map": + { + "attributes":[ + "id", + "name" + ], + "required":[ + "id" + ] + }, + "meta": + { + "attributes": + { + "0":"content", + "http-equiv":/^(content\-type|expires|refresh|set\-cookie)$/i, + "1":"name", + "2":"scheme" + }, + "required":[ + "content" + ] + }, + "27":"noscript", + "object": + { + "attributes":[ + "archive", + "classid", + "codebase", + "codetype", + "data", + "declare", + "height", + "name", + "standby", + "type", + "usemap", + "width" + ] + }, + "28":"ol", + "optgroup": + { + "attributes": + { + "0":"label", + "disabled": /^(disabled)$/ + }, + "required":[ + "label" + ] + }, + "option": + { + "attributes": + { + "0":"label", + "disabled":/^(disabled)$/, + "selected":/^(selected)$/, + "1":"value" + }, + "inside":"select" + }, + "29":"p", + "param": + { + "attributes": + { + "0":"type", + "valuetype":/^(data|ref|object)$/, + "1":"valuetype", + "2":"value" + }, + "required":[ + "name" + ] + }, + "30":"pre", + "q": + { + "attributes":[ + "cite" + ] + }, + "31":"samp", + "script": + { + "attributes": + { + "type":/^(text\/ecmascript|text\/javascript|text\/jscript|text\/vbscript|text\/vbs|text\/xml)$/, + "0":"charset", + "defer":/^(defer)$/, + "1":"src" + }, + "required":[ + "type" + ] + }, + "select": + { + "attributes": + { + "disabled":/^(disabled)$/, + "multiple":/^(multiple)$/, + "0":"name", + "1":"size" + }, + "inside":"form" + }, + "32":"small", + "33":"span", + "34":"strong", + "style": + { + "attributes": + { + "0":"type", + "media":/^(screen|tty|tv|projection|handheld|print|braille|aural|all)$/ + }, + "required":[ + "type" + ] + }, + "35":"sub", + "36":"sup", + "table": + { + "attributes": + { + "0":"border", + "1":"cellpadding", + "2":"cellspacing", + "frame":/^(void|above|below|hsides|lhs|rhs|vsides|box|border)$/, + "rules":/^(none|groups|rows|cols|all)$/, + "3":"summary", + "4":"width" + } + }, + "tbody": + { + "attributes": + { + "align":/^(right|left|center|justify)$/, + "0":"char", + "1":"charoff", + "valign":/^(top|middle|bottom|baseline)$/ + } + }, + "td": + { + "attributes": + { + "0":"abbr", + "align":/^(left|right|center|justify|char)$/, + "1":"axis", + "2":"char", + "3":"charoff", + "colspan":/^(\d)+$/, + "4":"headers", + "rowspan":/^(\d)+$/, + "scope":/^(col|colgroup|row|rowgroup)$/, + "valign":/^(top|middle|bottom|baseline)$/ + } + }, + "textarea": + { + "attributes":[ + "cols", + "rows", + "disabled", + "name", + "readonly" + ], + "required":[ + "cols", + "rows" + ], + "inside":"form" + }, + "tfoot": + { + "attributes": + { + "align":/^(right|left|center|justify)$/, + "0":"char", + "1":"charoff", + "valign":/^(top|middle|bottom)$/, + "2":"baseline" + } + }, + "th": + { + "attributes": + { + "0":"abbr", + "align":/^(left|right|center|justify|char)$/, + "1":"axis", + "2":"char", + "3":"charoff", + "colspan":/^(\d)+$/, + "4":"headers", + "rowspan":/^(\d)+$/, + "scope":/^(col|colgroup|row|rowgroup)$/, + "valign":/^(top|middle|bottom|baseline)$/ + } + }, + "thead": + { + "attributes": + { + "align":/^(right|left|center|justify)$/, + "0":"char", + "1":"charoff", + "valign":/^(top|middle|bottom|baseline)$/ + } + }, + "37":"title", + "tr": + { + "attributes": + { + "align":/^(right|left|center|justify|char)$/, + "0":"char", + "1":"charoff", + "valign":/^(top|middle|bottom|baseline)$/ + } + }, + "38":"tt", + "39":"ul", + "40":"var" + }, + + // Temporary skiped attributes + skiped_attributes : [], + skiped_attribute_values : [], + + getValidTagAttributes: function(tag, attributes) + { + var valid_attributes = {}; + var possible_attributes = this.getPossibleTagAttributes(tag); + for(var attribute in attributes) { + var value = attributes[attribute]; + var h = WYMeditor.Helper; + if(!h.contains(this.skiped_attributes, attribute) && !h.contains(this.skiped_attribute_values, value)){ + if (typeof value != 'function' && h.contains(possible_attributes, attribute)) { + if (this.doesAttributeNeedsValidation(tag, attribute)) { + if(this.validateAttribute(tag, attribute, value)){ + valid_attributes[attribute] = value; + } + }else{ + valid_attributes[attribute] = value; + } + } + } + } + return valid_attributes; + }, + getUniqueAttributesAndEventsForTag : function(tag) + { + var result = []; + + if (this._tags[tag] && this._tags[tag]['attributes']) { + for (k in this._tags[tag]['attributes']) { + result.push(parseInt(k) == k ? this._tags[tag]['attributes'][k] : k); + } + } + return result; + }, + getDefaultAttributesAndEventsForTags : function() + { + var result = []; + for (var key in this._events){ + result.push(this._events[key]); + } + for (var key in this._attributes){ + result.push(this._attributes[key]); + } + return result; + }, + isValidTag : function(tag) + { + if(this._tags[tag]){ + return true; + } + for(var key in this._tags){ + if(this._tags[key] == tag){ + return true; + } + } + return false; + }, + getDefaultAttributesAndEventsForTag : function(tag) + { + var default_attributes = []; + if (this.isValidTag(tag)) { + var default_attributes_and_events = this.getDefaultAttributesAndEventsForTags(); + + for(var key in default_attributes_and_events) { + var defaults = default_attributes_and_events[key]; + if(typeof defaults == 'object'){ + var h = WYMeditor.Helper; + if ((defaults['except'] && h.contains(defaults['except'], tag)) || (defaults['only'] && !h.contains(defaults['only'], tag))) { + continue; + } + + var tag_defaults = defaults['attributes'] ? defaults['attributes'] : defaults['events']; + for(var k in tag_defaults) { + default_attributes.push(typeof tag_defaults[k] != 'string' ? k : tag_defaults[k]); + } + } + } + } + return default_attributes; + }, + doesAttributeNeedsValidation: function(tag, attribute) + { + return this._tags[tag] && ((this._tags[tag]['attributes'] && this._tags[tag]['attributes'][attribute]) || (this._tags[tag]['required'] && + WYMeditor.Helper.contains(this._tags[tag]['required'], attribute))); + }, + validateAttribute : function(tag, attribute, value) + { + if ( this._tags[tag] && + (this._tags[tag]['attributes'] && this._tags[tag]['attributes'][attribute] && value.length > 0 && !value.match(this._tags[tag]['attributes'][attribute])) || // invalid format + (this._tags[tag] && this._tags[tag]['required'] && WYMeditor.Helper.contains(this._tags[tag]['required'], attribute) && value.length == 0) // required attribute + ) { + return false; + } + return typeof this._tags[tag] != 'undefined'; + }, + getPossibleTagAttributes : function(tag) + { + if (!this._possible_tag_attributes) { + this._possible_tag_attributes = {}; + } + if (!this._possible_tag_attributes[tag]) { + this._possible_tag_attributes[tag] = this.getUniqueAttributesAndEventsForTag(tag).concat(this.getDefaultAttributesAndEventsForTag(tag)); + } + return this._possible_tag_attributes[tag]; + } +}; + + +/** +* Compounded regular expression. Any of +* the contained patterns could match and +* when one does, it's label is returned. +* +* Constructor. Starts with no patterns. +* @param boolean case True for case sensitive, false +* for insensitive. +* @access public +* @author Marcus Baker (http://lastcraft.com) +* @author Bermi Ferrer (http://bermi.org) +*/ +WYMeditor.ParallelRegex = function(case_sensitive) +{ + this._case = case_sensitive; + this._patterns = []; + this._labels = []; + this._regex = null; + return this; +}; + + +/** +* Adds a pattern with an optional label. +* @param string pattern Perl style regex, but ( and ) +* lose the usual meaning. +* @param string label Label of regex to be returned +* on a match. +* @access public +*/ +WYMeditor.ParallelRegex.prototype.addPattern = function(pattern, label) +{ + label = label || true; + var count = this._patterns.length; + this._patterns[count] = pattern; + this._labels[count] = label; + this._regex = null; +}; + +/** +* Attempts to match all patterns at once against +* a string. +* @param string subject String to match against. +* +* @return boolean True on success. +* @return string match First matched portion of +* subject. +* @access public +*/ +WYMeditor.ParallelRegex.prototype.match = function(subject) +{ + if (this._patterns.length == 0) { + return [false, '']; + } + var matches = subject.match(this._getCompoundedRegex()); + + if(!matches){ + return [false, '']; + } + var match = matches[0]; + for (var i = 1; i < matches.length; i++) { + if (matches[i]) { + return [this._labels[i-1], match]; + } + } + return [true, matches[0]]; +}; + +/** +* Compounds the patterns into a single +* regular expression separated with the +* "or" operator. Caches the regex. +* Will automatically escape (, ) and / tokens. +* @param array patterns List of patterns in order. +* @access private +*/ +WYMeditor.ParallelRegex.prototype._getCompoundedRegex = function() +{ + if (this._regex == null) { + for (var i = 0, count = this._patterns.length; i < count; i++) { + this._patterns[i] = '(' + this._untokenizeRegex(this._tokenizeRegex(this._patterns[i]).replace(/([\/\(\)])/g,'\\$1')) + ')'; + } + this._regex = new RegExp(this._patterns.join("|") ,this._getPerlMatchingFlags()); + } + return this._regex; +}; + +/** +* Escape lookahead/lookbehind blocks +*/ +WYMeditor.ParallelRegex.prototype._tokenizeRegex = function(regex) +{ + return regex. + replace(/\(\?(i|m|s|x|U)\)/, '~~~~~~Tk1\$1~~~~~~'). + replace(/\(\?(\-[i|m|s|x|U])\)/, '~~~~~~Tk2\$1~~~~~~'). + replace(/\(\?\=(.*)\)/, '~~~~~~Tk3\$1~~~~~~'). + replace(/\(\?\!(.*)\)/, '~~~~~~Tk4\$1~~~~~~'). + replace(/\(\?\<\=(.*)\)/, '~~~~~~Tk5\$1~~~~~~'). + replace(/\(\?\<\!(.*)\)/, '~~~~~~Tk6\$1~~~~~~'). + replace(/\(\?\:(.*)\)/, '~~~~~~Tk7\$1~~~~~~'); +}; + +/** +* Unscape lookahead/lookbehind blocks +*/ +WYMeditor.ParallelRegex.prototype._untokenizeRegex = function(regex) +{ + return regex. + replace(/~~~~~~Tk1(.{1})~~~~~~/, "(?\$1)"). + replace(/~~~~~~Tk2(.{2})~~~~~~/, "(?\$1)"). + replace(/~~~~~~Tk3(.*)~~~~~~/, "(?=\$1)"). + replace(/~~~~~~Tk4(.*)~~~~~~/, "(?!\$1)"). + replace(/~~~~~~Tk5(.*)~~~~~~/, "(?<=\$1)"). + replace(/~~~~~~Tk6(.*)~~~~~~/, "(?", 'Comment'); +}; + +WYMeditor.XhtmlLexer.prototype.addScriptTokens = function(scope) +{ + this.addEntryPattern("", 'Script'); +}; + +WYMeditor.XhtmlLexer.prototype.addCssTokens = function(scope) +{ + this.addEntryPattern("", 'Css'); +}; + +WYMeditor.XhtmlLexer.prototype.addTagTokens = function(scope) +{ + this.addSpecialPattern("<\\s*[a-z0-9:\-]+\\s*>", scope, 'OpeningTag'); + this.addEntryPattern("<[a-z0-9:\-]+"+'[\\\/ \\\>]+', scope, 'OpeningTag'); + this.addInTagDeclarationTokens('OpeningTag'); + + this.addSpecialPattern("", scope, 'ClosingTag'); + +}; + +WYMeditor.XhtmlLexer.prototype.addInTagDeclarationTokens = function(scope) +{ + this.addSpecialPattern('\\s+', scope, 'Ignore'); + + this.addAttributeTokens(scope); + + this.addExitPattern('/>', scope); + this.addExitPattern('>', scope); + +}; + +WYMeditor.XhtmlLexer.prototype.addAttributeTokens = function(scope) +{ + this.addSpecialPattern("\\s*[a-z-_0-9]*:?[a-z-_0-9]+\\s*(?=\=)\\s*", scope, 'TagAttributes'); + + this.addEntryPattern('=\\s*"', scope, 'DoubleQuotedAttribute'); + this.addPattern("\\\\\"", 'DoubleQuotedAttribute'); + this.addExitPattern('"', 'DoubleQuotedAttribute'); + + this.addEntryPattern("=\\s*'", scope, 'SingleQuotedAttribute'); + this.addPattern("\\\\'", 'SingleQuotedAttribute'); + this.addExitPattern("'", 'SingleQuotedAttribute'); + + this.addSpecialPattern('=\\s*[^>\\s]*', scope, 'UnquotedAttribute'); +}; + + + +/** +* XHTML Parser. +* +* This XHTML parser will trigger the events available on on +* current SaxListener +* +* @author Bermi Ferrer (http://bermi.org) +*/ +WYMeditor.XhtmlParser = function(Listener, mode) +{ + var mode = mode || 'Text'; + this._Lexer = new WYMeditor.XhtmlLexer(this); + this._Listener = Listener; + this._mode = mode; + this._matches = []; + this._last_match = ''; + this._current_match = ''; + + return this; +}; + +WYMeditor.XhtmlParser.prototype.parse = function(raw) +{ + this._Lexer.parse(this.beforeParsing(raw)); + return this.afterParsing(this._Listener.getResult()); +}; + +WYMeditor.XhtmlParser.prototype.beforeParsing = function(raw) +{ + if(raw.match(/class="MsoNormal"/) || raw.match(/ns = "urn:schemas-microsoft-com/)){ + // Usefull for cleaning up content pasted from other sources (MSWord) + this._Listener.avoidStylingTagsAndAttributes(); + } + return this._Listener.beforeParsing(raw); +}; + +WYMeditor.XhtmlParser.prototype.afterParsing = function(parsed) +{ + if(this._Listener._avoiding_tags_implicitly){ + this._Listener.allowStylingTagsAndAttributes(); + } + return this._Listener.afterParsing(parsed); +}; + + +WYMeditor.XhtmlParser.prototype.Ignore = function(match, state) +{ + return true; +}; + +WYMeditor.XhtmlParser.prototype.Text = function(text) +{ + this._Listener.addContent(text); + return true; +}; + +WYMeditor.XhtmlParser.prototype.Comment = function(match, status) +{ + return this._addNonTagBlock(match, status, 'addComment'); +}; + +WYMeditor.XhtmlParser.prototype.Script = function(match, status) +{ + return this._addNonTagBlock(match, status, 'addScript'); +}; + +WYMeditor.XhtmlParser.prototype.Css = function(match, status) +{ + return this._addNonTagBlock(match, status, 'addCss'); +}; + +WYMeditor.XhtmlParser.prototype._addNonTagBlock = function(match, state, type) +{ + switch (state){ + case WYMeditor.LEXER_ENTER: + this._non_tag = match; + break; + case WYMeditor.LEXER_UNMATCHED: + this._non_tag += match; + break; + case WYMeditor.LEXER_EXIT: + switch(type) { + case 'addComment': + this._Listener.addComment(this._non_tag+match); + break; + case 'addScript': + this._Listener.addScript(this._non_tag+match); + break; + case 'addCss': + this._Listener.addCss(this._non_tag+match); + break; + } + } + return true; +}; + +WYMeditor.XhtmlParser.prototype.OpeningTag = function(match, state) +{ + switch (state){ + case WYMeditor.LEXER_ENTER: + this._tag = this.normalizeTag(match); + this._tag_attributes = {}; + break; + case WYMeditor.LEXER_SPECIAL: + this._callOpenTagListener(this.normalizeTag(match)); + break; + case WYMeditor.LEXER_EXIT: + this._callOpenTagListener(this._tag, this._tag_attributes); + } + return true; +}; + +WYMeditor.XhtmlParser.prototype.ClosingTag = function(match, state) +{ + this._callCloseTagListener(this.normalizeTag(match)); + return true; +}; + +WYMeditor.XhtmlParser.prototype._callOpenTagListener = function(tag, attributes) +{ + var attributes = attributes || {}; + this.autoCloseUnclosedBeforeNewOpening(tag); + + if(this._Listener.isBlockTag(tag)){ + this._Listener._tag_stack.push(tag); + this._Listener.fixNestingBeforeOpeningBlockTag(tag, attributes); + this._Listener.openBlockTag(tag, attributes); + this._increaseOpenTagCounter(tag); + }else if(this._Listener.isInlineTag(tag)){ + this._Listener.inlineTag(tag, attributes); + }else{ + this._Listener.openUnknownTag(tag, attributes); + this._increaseOpenTagCounter(tag); + } + this._Listener.last_tag = tag; + this._Listener.last_tag_opened = true; + this._Listener.last_tag_attributes = attributes; +}; + +WYMeditor.XhtmlParser.prototype._callCloseTagListener = function(tag) +{ + if(this._decreaseOpenTagCounter(tag)){ + this.autoCloseUnclosedBeforeTagClosing(tag); + + if(this._Listener.isBlockTag(tag)){ + var expected_tag = this._Listener._tag_stack.pop(); + if(expected_tag == false){ + return; + }else if(expected_tag != tag){ + tag = expected_tag; + } + this._Listener.closeBlockTag(tag); + }else{ + this._Listener.closeUnknownTag(tag); + } + }else{ + this._Listener.closeUnopenedTag(tag); + } + this._Listener.last_tag = tag; + this._Listener.last_tag_opened = false; +}; + +WYMeditor.XhtmlParser.prototype._increaseOpenTagCounter = function(tag) +{ + this._Listener._open_tags[tag] = this._Listener._open_tags[tag] || 0; + this._Listener._open_tags[tag]++; +}; + +WYMeditor.XhtmlParser.prototype._decreaseOpenTagCounter = function(tag) +{ + if(this._Listener._open_tags[tag]){ + this._Listener._open_tags[tag]--; + if(this._Listener._open_tags[tag] == 0){ + this._Listener._open_tags[tag] = undefined; + } + return true; + } + return false; +}; + +WYMeditor.XhtmlParser.prototype.autoCloseUnclosedBeforeNewOpening = function(new_tag) +{ + this._autoCloseUnclosed(new_tag, false); +}; + +WYMeditor.XhtmlParser.prototype.autoCloseUnclosedBeforeTagClosing = function(tag) +{ + this._autoCloseUnclosed(tag, true); +}; + +WYMeditor.XhtmlParser.prototype._autoCloseUnclosed = function(new_tag, closing) +{ + var closing = closing || false; + if(this._Listener._open_tags){ + for (var tag in this._Listener._open_tags) { + var counter = this._Listener._open_tags[tag]; + if(counter > 0 && this._Listener.shouldCloseTagAutomatically(tag, new_tag, closing)){ + this._callCloseTagListener(tag, true); + } + } + } +}; + +WYMeditor.XhtmlParser.prototype.getTagReplacements = function() +{ + return this._Listener.getTagReplacements(); +}; + +WYMeditor.XhtmlParser.prototype.normalizeTag = function(tag) +{ + tag = tag.replace(/^([\s<\/>]*)|([\s<\/>]*)$/gm,'').toLowerCase(); + var tags = this._Listener.getTagReplacements(); + if(tags[tag]){ + return tags[tag]; + } + return tag; +}; + +WYMeditor.XhtmlParser.prototype.TagAttributes = function(match, state) +{ + if(WYMeditor.LEXER_SPECIAL == state){ + this._current_attribute = match; + } + return true; +}; + +WYMeditor.XhtmlParser.prototype.DoubleQuotedAttribute = function(match, state) +{ + if(WYMeditor.LEXER_UNMATCHED == state){ + this._tag_attributes[this._current_attribute] = match; + } + return true; +}; + +WYMeditor.XhtmlParser.prototype.SingleQuotedAttribute = function(match, state) +{ + if(WYMeditor.LEXER_UNMATCHED == state){ + this._tag_attributes[this._current_attribute] = match; + } + return true; +}; + +WYMeditor.XhtmlParser.prototype.UnquotedAttribute = function(match, state) +{ + this._tag_attributes[this._current_attribute] = match.replace(/^=/,''); + return true; +}; + + + +/** +* XHTML Sax parser. +* +* @author Bermi Ferrer (http://bermi.org) +*/ +WYMeditor.XhtmlSaxListener = function() +{ + this.output = ''; + this.helper = new WYMeditor.XmlHelper(); + this._open_tags = {}; + this.validator = WYMeditor.XhtmlValidator; + this._tag_stack = []; + this.avoided_tags = []; + + this.entities = { + ' ':' ','¡':'¡','¢':'¢', + '£':'£','¤':'¤','¥':'¥', + '¦':'¦','§':'§','¨':'¨', + '©':'©','ª':'ª','«':'«', + '¬':'¬','­':'­','®':'®', + '¯':'¯','°':'°','±':'±', + '²':'²','³':'³','´':'´', + 'µ':'µ','¶':'¶','·':'·', + '¸':'¸','¹':'¹','º':'º', + '»':'»','¼':'¼','½':'½', + '¾':'¾','¿':'¿','À':'À', + 'Á':'Á','Â':'Â','Ã':'Ã', + 'Ä':'Ä','Å':'Å','Æ':'Æ', + 'Ç':'Ç','È':'È','É':'É', + 'Ê':'Ê','Ë':'Ë','Ì':'Ì', + 'Í':'Í','Î':'Î','Ï':'Ï', + 'Ð':'Ð','Ñ':'Ñ','Ò':'Ò', + 'Ó':'Ó','Ô':'Ô','Õ':'Õ', + 'Ö':'Ö','×':'×','Ø':'Ø', + 'Ù':'Ù','Ú':'Ú','Û':'Û', + 'Ü':'Ü','Ý':'Ý','Þ':'Þ', + 'ß':'ß','à':'à','á':'á', + 'â':'â','ã':'ã','ä':'ä', + 'å':'å','æ':'æ','ç':'ç', + 'è':'è','é':'é','ê':'ê', + 'ë':'ë','ì':'ì','í':'í', + 'î':'î','ï':'ï','ð':'ð', + 'ñ':'ñ','ò':'ò','ó':'ó', + 'ô':'ô','õ':'õ','ö':'ö', + '÷':'÷','ø':'ø','ù':'ù', + 'ú':'ú','û':'û','ü':'ü', + 'ý':'ý','þ':'þ','ÿ':'ÿ', + 'Œ':'Œ','œ':'œ','Š':'Š', + 'š':'š','Ÿ':'Ÿ','ƒ':'ƒ', + 'ˆ':'ˆ','˜':'˜','Α':'Α', + 'Β':'Β','Γ':'Γ','Δ':'Δ', + 'Ε':'Ε','Ζ':'Ζ','Η':'Η', + 'Θ':'Θ','Ι':'Ι','Κ':'Κ', + 'Λ':'Λ','Μ':'Μ','Ν':'Ν', + 'Ξ':'Ξ','Ο':'Ο','Π':'Π', + 'Ρ':'Ρ','Σ':'Σ','Τ':'Τ', + 'Υ':'Υ','Φ':'Φ','Χ':'Χ', + 'Ψ':'Ψ','Ω':'Ω','α':'α', + 'β':'β','γ':'γ','δ':'δ', + 'ε':'ε','ζ':'ζ','η':'η', + 'θ':'θ','ι':'ι','κ':'κ', + 'λ':'λ','μ':'μ','ν':'ν', + 'ξ':'ξ','ο':'ο','π':'π', + 'ρ':'ρ','ς':'ς','σ':'σ', + 'τ':'τ','υ':'υ','φ':'φ', + 'χ':'χ','ψ':'ψ','ω':'ω', + 'ϑ':'ϑ','ϒ':'ϒ','ϖ':'ϖ', + ' ':' ',' ':' ',' ':' ', + '‌':'‌','‍':'‍','‎':'‎', + '‏':'‏','–':'–','—':'—', + '‘':'‘','’':'’','‚':'‚', + '“':'“','”':'”','„':'„', + '†':'†','‡':'‡','•':'•', + '…':'…','‰':'‰','′':'′', + '″':'″','‹':'‹','›':'›', + '‾':'‾','⁄':'⁄','€':'€', + 'ℑ':'ℑ','℘':'℘','ℜ':'ℜ', + '™':'™','ℵ':'ℵ','←':'←', + '↑':'↑','→':'→','↓':'↓', + '↔':'↔','↵':'↵','⇐':'⇐', + '⇑':'⇑','⇒':'⇒','⇓':'⇓', + '⇔':'⇔','∀':'∀','∂':'∂', + '∃':'∃','∅':'∅','∇':'∇', + '∈':'∈','∉':'∉','∋':'∋', + '∏':'∏','∑':'∑','−':'−', + '∗':'∗','√':'√','∝':'∝', + '∞':'∞','∠':'∠','∧':'∧', + '∨':'∨','∩':'∩','∪':'∪', + '∫':'∫','∴':'∴','∼':'∼', + '≅':'≅','≈':'≈','≠':'≠', + '≡':'≡','≤':'≤','≥':'≥', + '⊂':'⊂','⊃':'⊃','⊄':'⊄', + '⊆':'⊆','⊇':'⊇','⊕':'⊕', + '⊗':'⊗','⊥':'⊥','⋅':'⋅', + '⌈':'⌈','⌉':'⌉','⌊':'⌊', + '⌋':'⌋','⟨':'〈','⟩':'〉', + '◊':'◊','♠':'♠','♣':'♣', + '♥':'♥','♦':'♦'}; + + this.block_tags = ["a", "abbr", "acronym", "address", "area", "b", + "base", "bdo", "big", "blockquote", "body", "button", + "caption", "cite", "code", "col", "colgroup", "dd", "del", "div", + "dfn", "dl", "dt", "em", "fieldset", "form", "head", "h1", "h2", + "h3", "h4", "h5", "h6", "html", "i", "ins", + "kbd", "label", "legend", "li", "map", "noscript", + "object", "ol", "optgroup", "option", "p", "param", "pre", "q", + "samp", "script", "select", "small", "span", "strong", "style", + "sub", "sup", "table", "tbody", "td", "textarea", "tfoot", "th", + "thead", "title", "tr", "tt", "ul", "var", "extends"]; + + + this.inline_tags = ["br", "hr", "img", "input"]; + + return this; +}; + +WYMeditor.XhtmlSaxListener.prototype.shouldCloseTagAutomatically = function(tag, now_on_tag, closing) +{ + var closing = closing || false; + if(tag == 'td'){ + if((closing && now_on_tag == 'tr') || (!closing && now_on_tag == 'td')){ + return true; + } + } + if(tag == 'option'){ + if((closing && now_on_tag == 'select') || (!closing && now_on_tag == 'option')){ + return true; + } + } + return false; +}; + +WYMeditor.XhtmlSaxListener.prototype.beforeParsing = function(raw) +{ + this.output = ''; + return raw; +}; + +WYMeditor.XhtmlSaxListener.prototype.afterParsing = function(xhtml) +{ + xhtml = this.replaceNamedEntities(xhtml); + xhtml = this.joinRepeatedEntities(xhtml); + xhtml = this.removeEmptyTags(xhtml); + xhtml = this.removeBrInPre(xhtml); + return xhtml; +}; + +WYMeditor.XhtmlSaxListener.prototype.replaceNamedEntities = function(xhtml) +{ + for (var entity in this.entities) { + xhtml = xhtml.replace(new RegExp(entity, 'g'), this.entities[entity]); + } + return xhtml; +}; + +WYMeditor.XhtmlSaxListener.prototype.joinRepeatedEntities = function(xhtml) +{ + var tags = 'em|strong|sub|sup|acronym|pre|del|address'; + return xhtml.replace(new RegExp('<\/('+tags+')><\\1>' ,''),''). + replace(new RegExp('(\s*<('+tags+')>\s*){2}(.*)(\s*<\/\\2>\s*){2}' ,''),'<\$2>\$3<\$2>'); +}; + +WYMeditor.XhtmlSaxListener.prototype.removeEmptyTags = function(xhtml) +{ + return xhtml.replace(new RegExp('<('+this.block_tags.join("|").replace(/\|td/,'').replace(/\|th/, '')+')>(
| | |\\s)*<\/\\1>' ,'g'),''); +}; + +WYMeditor.XhtmlSaxListener.prototype.removeBrInPre = function(xhtml) +{ + var matches = xhtml.match(new RegExp(']*>(.*?)<\/pre>','gmi')); + if(matches) { + for(var i=0; i', 'g'), String.fromCharCode(13,10))); + } + } + return xhtml; +}; + +WYMeditor.XhtmlSaxListener.prototype.getResult = function() +{ + return this.output; +}; + +WYMeditor.XhtmlSaxListener.prototype.getTagReplacements = function() +{ + return {'b':'strong', 'i':'em'}; +}; + +WYMeditor.XhtmlSaxListener.prototype.addContent = function(text) +{ + this.output += text; +}; + +WYMeditor.XhtmlSaxListener.prototype.addComment = function(text) +{ + if(this.remove_comments){ + this.output += text; + } +}; + +WYMeditor.XhtmlSaxListener.prototype.addScript = function(text) +{ + if(!this.remove_scripts){ + this.output += text; + } +}; + +WYMeditor.XhtmlSaxListener.prototype.addCss = function(text) +{ + if(!this.remove_embeded_styles){ + this.output += text; + } +}; + +WYMeditor.XhtmlSaxListener.prototype.openBlockTag = function(tag, attributes) +{ + this.output += this.helper.tag(tag, this.validator.getValidTagAttributes(tag, attributes), true); +}; + +WYMeditor.XhtmlSaxListener.prototype.inlineTag = function(tag, attributes) +{ + this.output += this.helper.tag(tag, this.validator.getValidTagAttributes(tag, attributes)); +}; + +WYMeditor.XhtmlSaxListener.prototype.openUnknownTag = function(tag, attributes) +{ + //this.output += this.helper.tag(tag, attributes, true); +}; + +WYMeditor.XhtmlSaxListener.prototype.closeBlockTag = function(tag) +{ + this.output = this.output.replace(/
$/, '')+this._getClosingTagContent('before', tag)+""+this._getClosingTagContent('after', tag); +}; + +WYMeditor.XhtmlSaxListener.prototype.closeUnknownTag = function(tag) +{ + //this.output += ""; +}; + +WYMeditor.XhtmlSaxListener.prototype.closeUnopenedTag = function(tag) +{ + this.output += ""; +}; + +WYMeditor.XhtmlSaxListener.prototype.avoidStylingTagsAndAttributes = function() +{ + this.avoided_tags = ['div','span']; + this.validator.skiped_attributes = ['style']; + this.validator.skiped_attribute_values = ['MsoNormal','main1']; // MS Word attributes for class + this._avoiding_tags_implicitly = true; +}; + +WYMeditor.XhtmlSaxListener.prototype.allowStylingTagsAndAttributes = function() +{ + this.avoided_tags = []; + this.validator.skiped_attributes = []; + this.validator.skiped_attribute_values = []; + this._avoiding_tags_implicitly = false; +}; + +WYMeditor.XhtmlSaxListener.prototype.isBlockTag = function(tag) +{ + return !WYMeditor.Helper.contains(this.avoided_tags, tag) && WYMeditor.Helper.contains(this.block_tags, tag); +}; + +WYMeditor.XhtmlSaxListener.prototype.isInlineTag = function(tag) +{ + return !WYMeditor.Helper.contains(this.avoided_tags, tag) && WYMeditor.Helper.contains(this.inline_tags, tag); +}; + +WYMeditor.XhtmlSaxListener.prototype.insertContentAfterClosingTag = function(tag, content) +{ + this._insertContentWhenClosingTag('after', tag, content); +}; + +WYMeditor.XhtmlSaxListener.prototype.insertContentBeforeClosingTag = function(tag, content) +{ + this._insertContentWhenClosingTag('before', tag, content); +}; + +WYMeditor.XhtmlSaxListener.prototype.fixNestingBeforeOpeningBlockTag = function(tag, attributes) +{ + if(tag != 'li' && (tag == 'ul' || tag == 'ol') && this.last_tag && !this.last_tag_opened && this.last_tag == 'li'){ + this.output = this.output.replace(/<\/li>$/, ''); + this.insertContentAfterClosingTag(tag, ''); + } +}; + +WYMeditor.XhtmlSaxListener.prototype._insertContentWhenClosingTag = function(position, tag, content) +{ + if(!this['_insert_'+position+'_closing']){ + this['_insert_'+position+'_closing'] = []; + } + if(!this['_insert_'+position+'_closing'][tag]){ + this['_insert_'+position+'_closing'][tag] = []; + } + this['_insert_'+position+'_closing'][tag].push(content); +}; + +WYMeditor.XhtmlSaxListener.prototype._getClosingTagContent = function(position, tag) +{ + if( this['_insert_'+position+'_closing'] && + this['_insert_'+position+'_closing'][tag] && + this['_insert_'+position+'_closing'][tag].length > 0){ + return this['_insert_'+position+'_closing'][tag].pop(); + } + return ''; +}; + + +/********** CSS PARSER **********/ + + +WYMeditor.WymCssLexer = function(parser, only_wym_blocks) +{ + var only_wym_blocks = (typeof only_wym_blocks == 'undefined' ? true : only_wym_blocks); + + jQuery.extend(this, new WYMeditor.Lexer(parser, (only_wym_blocks?'Ignore':'WymCss'))); + + this.mapHandler('WymCss', 'Ignore'); + + if(only_wym_blocks == true){ + this.addEntryPattern("/\\\x2a[<\\s]*WYMeditor[>\\s]*\\\x2a/", 'Ignore', 'WymCss'); + this.addExitPattern("/\\\x2a[<\/\\s]*WYMeditor[>\\s]*\\\x2a/", 'WymCss'); + } + + this.addSpecialPattern("[\\sa-z1-6]*\\\x2e[a-z-_0-9]+", 'WymCss', 'WymCssStyleDeclaration'); + + this.addEntryPattern("/\\\x2a", 'WymCss', 'WymCssComment'); + this.addExitPattern("\\\x2a/", 'WymCssComment'); + + this.addEntryPattern("\x7b", 'WymCss', 'WymCssStyle'); + this.addExitPattern("\x7d", 'WymCssStyle'); + + this.addEntryPattern("/\\\x2a", 'WymCssStyle', 'WymCssFeedbackStyle'); + this.addExitPattern("\\\x2a/", 'WymCssFeedbackStyle'); + + return this; +}; + +WYMeditor.WymCssParser = function() +{ + this._in_style = false; + this._has_title = false; + this.only_wym_blocks = true; + this.css_settings = {'classesItems':[], 'editorStyles':[], 'dialogStyles':[]}; + return this; +}; + +WYMeditor.WymCssParser.prototype.parse = function(raw, only_wym_blocks) +{ + var only_wym_blocks = (typeof only_wym_blocks == 'undefined' ? this.only_wym_blocks : only_wym_blocks); + this._Lexer = new WYMeditor.WymCssLexer(this, only_wym_blocks); + this._Lexer.parse(raw); +}; + +WYMeditor.WymCssParser.prototype.Ignore = function(match, state) +{ + return true; +}; + +WYMeditor.WymCssParser.prototype.WymCssComment = function(text, status) +{ + if(text.match(/end[a-z0-9\s]*wym[a-z0-9\s]*/mi)){ + return false; + } + if(status == WYMeditor.LEXER_UNMATCHED){ + if(!this._in_style){ + this._has_title = true; + this._current_item = {'title':WYMeditor.Helper.trim(text)}; + }else{ + if(this._current_item[this._current_element]){ + if(!this._current_item[this._current_element].expressions){ + this._current_item[this._current_element].expressions = [text]; + }else{ + this._current_item[this._current_element].expressions.push(text); + } + } + } + this._in_style = true; + } + return true; +}; + +WYMeditor.WymCssParser.prototype.WymCssStyle = function(match, status) +{ + if(status == WYMeditor.LEXER_UNMATCHED){ + match = WYMeditor.Helper.trim(match); + if(match != ''){ + this._current_item[this._current_element].style = match; + } + }else if (status == WYMeditor.LEXER_EXIT){ + this._in_style = false; + this._has_title = false; + this.addStyleSetting(this._current_item); + } + return true; +}; + +WYMeditor.WymCssParser.prototype.WymCssFeedbackStyle = function(match, status) +{ + if(status == WYMeditor.LEXER_UNMATCHED){ + this._current_item[this._current_element].feedback_style = match.replace(/^([\s\/\*]*)|([\s\/\*]*)$/gm,''); + } + return true; +}; + +WYMeditor.WymCssParser.prototype.WymCssStyleDeclaration = function(match) +{ + match = match.replace(/^([\s\.]*)|([\s\.*]*)$/gm, ''); + + var tag = ''; + if(match.indexOf('.') > 0){ + var parts = match.split('.'); + this._current_element = parts[1]; + var tag = parts[0]; + }else{ + this._current_element = match; + } + + if(!this._has_title){ + this._current_item = {'title':(!tag?'':tag.toUpperCase()+': ')+this._current_element}; + this._has_title = true; + } + + if(!this._current_item[this._current_element]){ + this._current_item[this._current_element] = {'name':this._current_element}; + } + if(tag){ + if(!this._current_item[this._current_element].tags){ + this._current_item[this._current_element].tags = [tag]; + }else{ + this._current_item[this._current_element].tags.push(tag); + } + } + return true; +}; + +WYMeditor.WymCssParser.prototype.addStyleSetting = function(style_details) +{ + for (var name in style_details){ + var details = style_details[name]; + if(typeof details == 'object' && name != 'title'){ + + this.css_settings.classesItems.push({ + 'name': WYMeditor.Helper.trim(details.name), + 'title': style_details.title, + 'expr' : WYMeditor.Helper.trim((details.expressions||details.tags).join(', ')) + }); + if(details.feedback_style){ + this.css_settings.editorStyles.push({ + 'name': '.'+ WYMeditor.Helper.trim(details.name), + 'css': details.feedback_style + }); + } + if(details.style){ + this.css_settings.dialogStyles.push({ + 'name': '.'+ WYMeditor.Helper.trim(details.name), + 'css': details.style + }); + } + } + } +}; + +/********** HELPERS **********/ + +// Returns true if it is a text node with whitespaces only +jQuery.fn.isPhantomNode = function() { + if (this[0].nodeType == 3) + return !(/[^\t\n\r ]/.test(this[0].data)); + + return false; +}; + +WYMeditor.isPhantomNode = function(n) { + if (n.nodeType == 3) + return !(/[^\t\n\r ]/.test(n.data)); + + return false; +}; + +WYMeditor.isPhantomString = function(str) { + return !(/[^\t\n\r ]/.test(str)); +}; + +// Returns the Parents or the node itself +// jqexpr = a jQuery expression +jQuery.fn.parentsOrSelf = function(jqexpr) { + var n = this; + + if (n[0].nodeType == 3) + n = n.parents().slice(0,1); + +// if (n.is(jqexpr)) // XXX should work, but doesn't (probably a jQuery bug) + if (n.filter(jqexpr).size() == 1) + return n; + else + return n.parents(jqexpr).slice(0,1); +}; + +// String & array helpers + +WYMeditor.Helper = { + + //replace all instances of 'old' by 'rep' in 'str' string + replaceAll: function(str, old, rep) { + var rExp = new RegExp(old, "g"); + return(str.replace(rExp, rep)); + }, + + //insert 'inserted' at position 'pos' in 'str' string + insertAt: function(str, inserted, pos) { + return(str.substr(0,pos) + inserted + str.substring(pos)); + }, + + //trim 'str' string + trim: function(str) { + return str.replace(/^(\s*)|(\s*)$/gm,''); + }, + + //return true if 'arr' array contains 'elem', or false + contains: function(arr, elem) { + for (var i = 0; i < arr.length; i++) { + if (arr[i] === elem) return true; + } + return false; + }, + + //return 'item' position in 'arr' array, or -1 + indexOf: function(arr, item) { + var ret=-1; + for(var i = 0; i < arr.length; i++) { + if (arr[i] == item) { + ret = i; + break; + } + } + return(ret); + }, + + //return 'item' object in 'arr' array, checking its 'name' property, or null + findByName: function(arr, name) { + for(var i = 0; i < arr.length; i++) { + var item = arr[i]; + if(item.name == name) return(item); + } + return(null); + } +}; + + +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * jquery.wymeditor.explorer.js + * MSIE specific class and functions. + * See the documentation for more info. + * + * File Authors: + * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg) + * Bermi Ferrer (wymeditor a-t bermi dotorg) + * Frédéric Palluel-Lafleur (fpalluel a-t gmail dotcom) + * Jonatan Lundin (jonatan.lundin a-t gmail dotcom) + */ + +WYMeditor.WymClassExplorer = function(wym) { + + this._wym = wym; + this._class = "className"; + this._newLine = "\r\n"; + +}; + +WYMeditor.WymClassExplorer.prototype.initIframe = function(iframe) { + + //This function is executed twice, though it is called once! + //But MSIE needs that, otherwise designMode won't work. + //Weird. + + this._iframe = iframe; + this._doc = iframe.contentWindow.document; + + //add css rules from options + var styles = this._doc.styleSheets[0]; + var aCss = eval(this._options.editorStyles); + + this.addCssRules(this._doc, aCss); + + this._doc.title = this._wym._index; + + //set the text direction + jQuery('html', this._doc).attr('dir', this._options.direction); + + //init html value + jQuery(this._doc.body).html(this._wym._html); + + //handle events + var wym = this; + + this._doc.body.onfocus = function() + {wym._doc.designMode = "on"; wym._doc = iframe.contentWindow.document;}; + this._doc.onbeforedeactivate = function() {wym.saveCaret();}; + this._doc.onkeyup = function() { + wym.saveCaret(); + wym.keyup(); + }; + this._doc.onclick = function() {wym.saveCaret();}; + + this._doc.body.onbeforepaste = function() { + wym._iframe.contentWindow.event.returnValue = false; + }; + + this._doc.body.onpaste = function() { + wym._iframe.contentWindow.event.returnValue = false; + wym.paste(window.clipboardData.getData("Text")); + }; + + //callback can't be executed twice, so we check + if(this._initialized) { + + //pre-bind functions + if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this); + + //bind external events + this._wym.bindEvents(); + + //post-init functions + if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this); + + //add event listeners to doc elements, e.g. images + this.listen(); + } + + this._initialized = true; + + //init designMode + this._doc.designMode="on"; + try{ + // (bermi's note) noticed when running unit tests on IE6 + // Is this really needed, it trigger an unexisting property on IE6 + this._doc = iframe.contentWindow.document; + }catch(e){} +}; + +(function(editorLoadSkin) { + WYMeditor.WymClassExplorer.prototype.loadSkin = function() { + // Mark container items as unselectable (#203) + // Fix for issue explained: http://stackoverflow.com/questions/1470932/ie8-iframe-designmode-loses-selection + jQuery(this._box).find(this._options.containerSelector) + .attr('unselectable', 'on'); + + editorLoadSkin.call(this); + }; +})(WYMeditor.editor.prototype.loadSkin); + +WYMeditor.WymClassExplorer.prototype._exec = function(cmd,param) { + + switch(cmd) { + + case WYMeditor.INDENT: case WYMeditor.OUTDENT: + + var container = this.findUp(this.container(), WYMeditor.LI); + if(container) { + var ancestor = container.parentNode.parentNode; + if(container.parentNode.childNodes.length>1 + || ancestor.tagName.toLowerCase() == WYMeditor.OL + || ancestor.tagName.toLowerCase() == WYMeditor.UL) + this._doc.execCommand(cmd); + } + break; + default: + if(param) this._doc.execCommand(cmd,false,param); + else this._doc.execCommand(cmd); + break; + } + +}; + +WYMeditor.WymClassExplorer.prototype.selected = function() { + + var caretPos = this._iframe.contentWindow.document.caretPos; + if(caretPos!=null) { + if(caretPos.parentElement!=undefined) + return(caretPos.parentElement()); + } +}; + +WYMeditor.WymClassExplorer.prototype.saveCaret = function() { + + this._doc.caretPos = this._doc.selection.createRange(); +}; + +WYMeditor.WymClassExplorer.prototype.addCssRule = function(styles, oCss) { + // IE doesn't handle combined selectors (#196) + var selectors = oCss.name.split(','); + for (var i in selectors) { + styles.addRule(selectors[i], oCss.css); + } +}; + +WYMeditor.WymClassExplorer.prototype.insert = function(html) { + + // Get the current selection + var range = this._doc.selection.createRange(); + + // Check if the current selection is inside the editor + if ( jQuery(range.parentElement()).parents( this._options.iframeBodySelector ).is('*') ) { + try { + // Overwrite selection with provided html + range.pasteHTML(html); + } catch (e) { } + } else { + // Fall back to the internal paste function if there's no selection + this.paste(html); + } +}; + +WYMeditor.WymClassExplorer.prototype.wrap = function(left, right) { + + // Get the current selection + var range = this._doc.selection.createRange(); + + // Check if the current selection is inside the editor + if ( jQuery(range.parentElement()).parents( this._options.iframeBodySelector ).is('*') ) { + try { + // Overwrite selection with provided html + range.pasteHTML(left + range.text + right); + } catch (e) { } + } +}; + +WYMeditor.WymClassExplorer.prototype.unwrap = function() { + + // Get the current selection + var range = this._doc.selection.createRange(); + + // Check if the current selection is inside the editor + if ( jQuery(range.parentElement()).parents( this._options.iframeBodySelector ).is('*') ) { + try { + // Unwrap selection + var text = range.text; + this._exec( 'Cut' ); + range.pasteHTML( text ); + } catch (e) { } + } +}; + +//keyup handler +WYMeditor.WymClassExplorer.prototype.keyup = function() { + this._selected_image = null; +}; + +WYMeditor.WymClassExplorer.prototype.setFocusToNode = function(node, toStart) { + var range = this._doc.selection.createRange(); + toStart = toStart ? true : false; + + range.moveToElementText(node); + range.collapse(toStart); + range.select(); + node.focus(); +}; + +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * jquery.wymeditor.mozilla.js + * Gecko specific class and functions. + * See the documentation for more info. + * + * File Authors: + * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg) + * Volker Mische (vmx a-t gmx dotde) + * Bermi Ferrer (wymeditor a-t bermi dotorg) + * Frédéric Palluel-Lafleur (fpalluel a-t gmail dotcom) + * Jonatan Lundin (jonatan.lundin a-t gmail dotcom) + */ + +WYMeditor.WymClassMozilla = function(wym) { + + this._wym = wym; + this._class = "class"; + this._newLine = "\n"; +}; + +WYMeditor.WymClassMozilla.prototype.initIframe = function(iframe) { + var wym = this; + + this._iframe = iframe; + this._doc = iframe.contentDocument; + + //add css rules from options + + var styles = this._doc.styleSheets[0]; + var aCss = eval(this._options.editorStyles); + + this.addCssRules(this._doc, aCss); + + this._doc.title = this._wym._index; + + //set the text direction + jQuery('html', this._doc).attr('dir', this._options.direction); + + //init html value + this.html(this._wym._html); + + //init designMode + this.enableDesignMode(); + + //pre-bind functions + if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this); + + //bind external events + this._wym.bindEvents(); + + //bind editor keydown events + jQuery(this._doc).bind("keydown", this.keydown); + + //bind editor keyup events + jQuery(this._doc).bind("keyup", this.keyup); + + //bind editor focus events (used to reset designmode - Gecko bug) + jQuery(this._doc).bind("focus", function () { + // Fix scope + wym.enableDesignMode.call(wym); + }); + + //post-init functions + if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this); + + //add event listeners to doc elements, e.g. images + this.listen(); +}; + +/* @name html + * @description Get/Set the html value + */ +WYMeditor.WymClassMozilla.prototype.html = function(html) { + + if(typeof html === 'string') { + + //disable designMode + try { this._doc.designMode = "off"; } catch(e) { }; + + //replace em by i and strong by bold + //(designMode issue) + html = html.replace(/]*)>/gi, "") + .replace(/<\/em>/gi, "") + .replace(/]*)>/gi, "") + .replace(/<\/strong>/gi, ""); + + //update the html body + jQuery(this._doc.body).html(html); + + //re-init designMode + this.enableDesignMode(); + } + else return(jQuery(this._doc.body).html()); +}; + +WYMeditor.WymClassMozilla.prototype._exec = function(cmd,param) { + + if(!this.selected()) return(false); + + switch(cmd) { + + case WYMeditor.INDENT: case WYMeditor.OUTDENT: + + var focusNode = this.selected(); + var sel = this._iframe.contentWindow.getSelection(); + var anchorNode = sel.anchorNode; + if(anchorNode.nodeName == "#text") anchorNode = anchorNode.parentNode; + + focusNode = this.findUp(focusNode, WYMeditor.BLOCKS); + anchorNode = this.findUp(anchorNode, WYMeditor.BLOCKS); + + if(focusNode && focusNode == anchorNode + && focusNode.tagName.toLowerCase() == WYMeditor.LI) { + + var ancestor = focusNode.parentNode.parentNode; + + if(focusNode.parentNode.childNodes.length>1 + || ancestor.tagName.toLowerCase() == WYMeditor.OL + || ancestor.tagName.toLowerCase() == WYMeditor.UL) + this._doc.execCommand(cmd,'',null); + } + + break; + + default: + + if(param) this._doc.execCommand(cmd,'',param); + else this._doc.execCommand(cmd,'',null); + } + + //set to P if parent = BODY + var container = this.selected(); + if(container.tagName.toLowerCase() == WYMeditor.BODY) + this._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); +}; + +/* @name selected + * @description Returns the selected container + */ +WYMeditor.WymClassMozilla.prototype.selected = function() { + + var sel = this._iframe.contentWindow.getSelection(); + var node = sel.focusNode; + if(node) { + if(node.nodeName == "#text") return(node.parentNode); + else return(node); + } else return(null); +}; + +WYMeditor.WymClassMozilla.prototype.addCssRule = function(styles, oCss) { + + styles.insertRule(oCss.name + " {" + oCss.css + "}", + styles.cssRules.length); +}; + + +//keydown handler, mainly used for keyboard shortcuts +WYMeditor.WymClassMozilla.prototype.keydown = function(evt) { + + //'this' is the doc + var wym = WYMeditor.INSTANCES[this.title]; + var container = null; + + if(evt.ctrlKey){ + if(evt.keyCode == 66){ + //CTRL+b => STRONG + wym._exec(WYMeditor.BOLD); + return false; + } + if(evt.keyCode == 73){ + //CTRL+i => EMPHASIS + wym._exec(WYMeditor.ITALIC); + return false; + } + } + + else if(evt.keyCode == 13) { + if(!evt.shiftKey){ + //fix PRE bug #73 + container = wym.selected(); + if(container && container.tagName.toLowerCase() == WYMeditor.PRE) { + evt.preventDefault(); + wym.insert('

'); + } + } + } +}; + +//keyup handler, mainly used for cleanups +WYMeditor.WymClassMozilla.prototype.keyup = function(evt) { + + //'this' is the doc + var wym = WYMeditor.INSTANCES[this.title]; + + wym._selected_image = null; + var container = null; + + if(evt.keyCode == 13 && !evt.shiftKey) { + + //RETURN key + //cleanup

between paragraphs + jQuery(wym._doc.body).children(WYMeditor.BR).remove(); + } + + if(evt.keyCode != 8 + && evt.keyCode != 17 + && evt.keyCode != 46 + && evt.keyCode != 224 + && !evt.metaKey + && !evt.ctrlKey) { + + //NOT BACKSPACE, NOT DELETE, NOT CTRL, NOT COMMAND + //text nodes replaced by P + + container = wym.selected(); + var name = container.tagName.toLowerCase(); + + //fix forbidden main containers + if( + name == "strong" || + name == "b" || + name == "em" || + name == "i" || + name == "sub" || + name == "sup" || + name == "a" + + ) name = container.parentNode.tagName.toLowerCase(); + + if(name == WYMeditor.BODY) wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); + } +}; + +WYMeditor.WymClassMozilla.prototype.enableDesignMode = function() { + if(this._doc.designMode == "off") { + try { + this._doc.designMode = "on"; + this._doc.execCommand("styleWithCSS", '', false); + } catch(e) { } + } +}; + +WYMeditor.WymClassMozilla.prototype.openBlockTag = function(tag, attributes) +{ + var attributes = this.validator.getValidTagAttributes(tag, attributes); + + // Handle Mozilla styled spans + if (tag == 'span' && attributes.style) { + var new_tag = this.getTagForStyle(attributes.style); + if (new_tag) { + tag = new_tag; + this._tag_stack.pop(); + this._tag_stack.push(tag); + attributes.style = ''; + } + } + + this.output += this.helper.tag(tag, attributes, true); +}; + +WYMeditor.WymClassMozilla.prototype.getTagForStyle = function(style) { + + if(/bold/.test(style)) return 'strong'; + if(/italic/.test(style)) return 'em'; + if(/sub/.test(style)) return 'sub'; + if(/super/.test(style)) return 'sup'; + return false; +}; + +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * jquery.wymeditor.opera.js + * Opera specific class and functions. + * See the documentation for more info. + * + * File Authors: + * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg) + */ + +WYMeditor.WymClassOpera = function(wym) { + + this._wym = wym; + this._class = "class"; + this._newLine = "\r\n"; +}; + +WYMeditor.WymClassOpera.prototype.initIframe = function(iframe) { + + this._iframe = iframe; + this._doc = iframe.contentWindow.document; + + //add css rules from options + var styles = this._doc.styleSheets[0]; + var aCss = eval(this._options.editorStyles); + + this.addCssRules(this._doc, aCss); + + this._doc.title = this._wym._index; + + //set the text direction + jQuery('html', this._doc).attr('dir', this._options.direction); + + //init designMode + this._doc.designMode = "on"; + + //init html value + this.html(this._wym._html); + + //pre-bind functions + if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this); + + //bind external events + this._wym.bindEvents(); + + //bind editor keydown events + jQuery(this._doc).bind("keydown", this.keydown); + + //bind editor events + jQuery(this._doc).bind("keyup", this.keyup); + + //post-init functions + if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this); + + //add event listeners to doc elements, e.g. images + this.listen(); +}; + +WYMeditor.WymClassOpera.prototype._exec = function(cmd,param) { + + if(param) this._doc.execCommand(cmd,false,param); + else this._doc.execCommand(cmd); + +}; + +WYMeditor.WymClassOpera.prototype.selected = function() { + + var sel=this._iframe.contentWindow.getSelection(); + var node=sel.focusNode; + if(node) { + if(node.nodeName=="#text")return(node.parentNode); + else return(node); + } else return(null); +}; + +WYMeditor.WymClassOpera.prototype.addCssRule = function(styles, oCss) { + + styles.insertRule(oCss.name + " {" + oCss.css + "}", + styles.cssRules.length); +}; + +//keydown handler +WYMeditor.WymClassOpera.prototype.keydown = function(evt) { + + //'this' is the doc + var wym = WYMeditor.INSTANCES[this.title]; + var sel = wym._iframe.contentWindow.getSelection(); + startNode = sel.getRangeAt(0).startContainer; + + //Get a P instead of no container + if(!jQuery(startNode).parentsOrSelf( + WYMeditor.MAIN_CONTAINERS.join(","))[0] + && !jQuery(startNode).parentsOrSelf('li') + && evt.keyCode != WYMeditor.KEY.ENTER + && evt.keyCode != WYMeditor.KEY.LEFT + && evt.keyCode != WYMeditor.KEY.UP + && evt.keyCode != WYMeditor.KEY.RIGHT + && evt.keyCode != WYMeditor.KEY.DOWN + && evt.keyCode != WYMeditor.KEY.BACKSPACE + && evt.keyCode != WYMeditor.KEY.DELETE) + wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); + +}; + +//keyup handler +WYMeditor.WymClassOpera.prototype.keyup = function(evt) { + + //'this' is the doc + var wym = WYMeditor.INSTANCES[this.title]; + wym._selected_image = null; +}; +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * jquery.wymeditor.safari.js + * Safari specific class and functions. + * See the documentation for more info. + * + * File Authors: + * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg) + * Scott Lewis (lewiscot a-t gmail dotcom) + */ + +WYMeditor.WymClassSafari = function(wym) { + + this._wym = wym; + this._class = "class"; + this._newLine = "\n"; +}; + +WYMeditor.WymClassSafari.prototype.initIframe = function(iframe) { + + this._iframe = iframe; + this._doc = iframe.contentDocument; + + //add css rules from options + + var styles = this._doc.styleSheets[0]; + var aCss = eval(this._options.editorStyles); + + this.addCssRules(this._doc, aCss); + + this._doc.title = this._wym._index; + + //set the text direction + jQuery('html', this._doc).attr('dir', this._options.direction); + + //init designMode + this._doc.designMode = "on"; + + //init html value + this.html(this._wym._html); + + //pre-bind functions + if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this); + + //bind external events + this._wym.bindEvents(); + + //bind editor keydown events + jQuery(this._doc).bind("keydown", this.keydown); + + //bind editor keyup events + jQuery(this._doc).bind("keyup", this.keyup); + + //post-init functions + if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this); + + //add event listeners to doc elements, e.g. images + this.listen(); +}; + +WYMeditor.WymClassSafari.prototype._exec = function(cmd,param) { + + if(!this.selected()) return(false); + + switch(cmd) { + + case WYMeditor.INDENT: case WYMeditor.OUTDENT: + + var focusNode = this.selected(); + var sel = this._iframe.contentWindow.getSelection(); + var anchorNode = sel.anchorNode; + if(anchorNode.nodeName == "#text") anchorNode = anchorNode.parentNode; + + focusNode = this.findUp(focusNode, WYMeditor.BLOCKS); + anchorNode = this.findUp(anchorNode, WYMeditor.BLOCKS); + + if(focusNode && focusNode == anchorNode + && focusNode.tagName.toLowerCase() == WYMeditor.LI) { + + var ancestor = focusNode.parentNode.parentNode; + + if(focusNode.parentNode.childNodes.length>1 + || ancestor.tagName.toLowerCase() == WYMeditor.OL + || ancestor.tagName.toLowerCase() == WYMeditor.UL) + this._doc.execCommand(cmd,'',null); + } + + break; + + case WYMeditor.INSERT_ORDEREDLIST: case WYMeditor.INSERT_UNORDEREDLIST: + + this._doc.execCommand(cmd,'',null); + + //Safari creates lists in e.g. paragraphs. + //Find the container, and remove it. + var focusNode = this.selected(); + var container = this.findUp(focusNode, WYMeditor.MAIN_CONTAINERS); + if(container) jQuery(container).replaceWith(jQuery(container).html()); + + break; + + default: + + if(param) this._doc.execCommand(cmd,'',param); + else this._doc.execCommand(cmd,'',null); + } + + //set to P if parent = BODY + var container = this.selected(); + if(container && container.tagName.toLowerCase() == WYMeditor.BODY) + this._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); + +}; + +/* @name selected + * @description Returns the selected container + */ +WYMeditor.WymClassSafari.prototype.selected = function() { + + var sel = this._iframe.contentWindow.getSelection(); + var node = sel.focusNode; + if(node) { + if(node.nodeName == "#text") return(node.parentNode); + else return(node); + } else return(null); +}; + +WYMeditor.WymClassSafari.prototype.addCssRule = function(styles, oCss) { + + styles.insertRule(oCss.name + " {" + oCss.css + "}", + styles.cssRules.length); +}; + + +//keydown handler, mainly used for keyboard shortcuts +WYMeditor.WymClassSafari.prototype.keydown = function(e) { + + //'this' is the doc + var wym = WYMeditor.INSTANCES[this.title]; + + if(e.ctrlKey){ + if(e.keyCode == 66){ + //CTRL+b => STRONG + wym._exec(WYMeditor.BOLD); + e.preventDefault(); + } + if(e.keyCode == 73){ + //CTRL+i => EMPHASIS + wym._exec(WYMeditor.ITALIC); + e.preventDefault(); + } + } else if(e.shiftKey && e.keyCode == 13) { + wym._exec('InsertLineBreak'); + e.preventDefault(); + } +}; + +//keyup handler, mainly used for cleanups +WYMeditor.WymClassSafari.prototype.keyup = function(evt) { + + //'this' is the doc + var wym = WYMeditor.INSTANCES[this.title]; + + wym._selected_image = null; + var container = null; + + if(evt.keyCode == 13 && !evt.shiftKey) { + + //RETURN key + //cleanup

between paragraphs + jQuery(wym._doc.body).children(WYMeditor.BR).remove(); + + //fix PRE bug #73 + container = wym.selected(); + if(container && container.tagName.toLowerCase() == WYMeditor.PRE) + wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); //create P after PRE + } + + if(evt.keyCode != 8 + && evt.keyCode != 17 + && evt.keyCode != 46 + && evt.keyCode != 224 + && !evt.metaKey + && !evt.ctrlKey) { + + //NOT BACKSPACE, NOT DELETE, NOT CTRL, NOT COMMAND + //text nodes replaced by P + + container = wym.selected(); + var name = container.tagName.toLowerCase(); + + //fix forbidden main containers + if( + name == "strong" || + name == "b" || + name == "em" || + name == "i" || + name == "sub" || + name == "sup" || + name == "a" || + name == "span" //fix #110 + + ) name = container.parentNode.tagName.toLowerCase(); + + if(name == WYMeditor.BODY || name == WYMeditor.DIV) wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); //fix #110 for DIV + } +}; + +WYMeditor.WymClassSafari.prototype.openBlockTag = function(tag, attributes) +{ + var attributes = this.validator.getValidTagAttributes(tag, attributes); + + // Handle Safari styled spans + if (tag == 'span' && attributes.style) { + var new_tag = this.getTagForStyle(attributes.style); + if (new_tag) { + tag = new_tag; + this._tag_stack.pop(); + this._tag_stack.push(tag); + attributes.style = ''; + + // Should fix #125 - also removed the xhtml() override + if(typeof attributes['class'] == 'string') { + attributes['class'] = attributes['class'].replace(/apple-style-span/gi, ''); + } + } + } + + this.output += this.helper.tag(tag, attributes, true); +}; + +WYMeditor.WymClassSafari.prototype.getTagForStyle = function(style) { + + if(/bold/.test(style)) return 'strong'; + if(/italic/.test(style)) return 'em'; + if(/sub/.test(style)) return 'sub'; + if(/super/.test(style)) return 'sup'; + return false; +}; diff --git a/app/javascripts/jquery/json2.js b/app/javascripts/jquery/json2.js new file mode 100644 index 000000000..b4c02d3f0 --- /dev/null +++ b/app/javascripts/jquery/json2.js @@ -0,0 +1,480 @@ +/* + http://www.JSON.org/json2.js + 2011-02-23 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. + + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. +*/ + +/*jslint evil: true, strict: false, regexp: false */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +var JSON; +if (!JSON) { + JSON = {}; +} + +(function () { + "use strict"; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function (key) { + + return isFinite(this.valueOf()) ? + this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 ? '[]' : gap ? + '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === 'string') { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 ? '{}' : gap ? + '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : + '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/ + .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' ? + walk({'': j}, '') : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } +}()); diff --git a/app/javascripts/jquery/lang/bg.js b/app/javascripts/jquery/lang/bg.js new file mode 100644 index 000000000..576bca574 --- /dev/null +++ b/app/javascripts/jquery/lang/bg.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['bg'] = { + Strong: 'Получер', + Emphasis: 'Курсив', + Superscript: 'Горен индекс', + Subscript: 'Долен индекс', + Ordered_List: 'Подреден списък', + Unordered_List: 'Неподреден списък', + Indent: 'Блок навътре', + Outdent: 'Блок навън', + Undo: 'Стъпка назад', + Redo: 'Стъпка напред', + Link: 'Създай хипервръзка', + Unlink: 'Премахни хипервръзката', + Image: 'Изображение', + Table: 'Таблица', + HTML: 'HTML', + Paragraph: 'Абзац', + Heading_1: 'Заглавие 1', + Heading_2: 'Заглавие 2', + Heading_3: 'Заглавие 3', + Heading_4: 'Заглавие 4', + Heading_5: 'Заглавие 5', + Heading_6: 'Заглавие 6', + Preformatted: 'Преформатиран', + Blockquote: 'Цитат', + Table_Header: 'Заглавие на таблицата', + URL: 'URL', + Title: 'Заглавие', + Alternative_Text: 'Алтернативен текст', + Caption: 'Етикет', + Summary: 'Общо', + Number_Of_Rows: 'Брой редове', + Number_Of_Cols: 'Брой колони', + Submit: 'Изпрати', + Cancel: 'Отмени', + Choose: 'Затвори', + Preview: 'Предварителен преглед', + Paste_From_Word: 'Вмъкни от MS WORD', + Tools: 'Инструменти', + Containers: 'Контейнери', + Classes: 'Класове', + Status: 'Статус', + Source_Code: 'Източник, код' +}; + diff --git a/app/javascripts/jquery/lang/ca.js b/app/javascripts/jquery/lang/ca.js new file mode 100644 index 000000000..c342406f2 --- /dev/null +++ b/app/javascripts/jquery/lang/ca.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['ca'] = { + Strong: 'Ressaltar', + Emphasis: 'Emfatitzar', + Superscript: 'Superindex', + Subscript: 'Subindex', + Ordered_List: 'Llistat ordenat', + Unordered_List: 'Llistat sense ordenar', + Indent: 'Indentat', + Outdent: 'Sense indentar', + Undo: 'Desfer', + Redo: 'Refer', + Link: 'Enllaçar', + Unlink: 'Eliminar enllaç', + Image: 'Imatge', + Table: 'Taula', + HTML: 'HTML', + Paragraph: 'Paràgraf', + Heading_1: 'Capçalera 1', + Heading_2: 'Capçalera 2', + Heading_3: 'Capçalera 3', + Heading_4: 'Capçalera 4', + Heading_5: 'Capçalera 5', + Heading_6: 'Capçalera 6', + Preformatted: 'Pre-formatejat', + Blockquote: 'Cita', + Table_Header: 'Capçalera de la taula', + URL: 'URL', + Title: 'Títol', + Alternative_Text: 'Text alternatiu', + Caption: 'Llegenda', + Summary: 'Summary', + Number_Of_Rows: 'Nombre de files', + Number_Of_Cols: 'Nombre de columnes', + Submit: 'Enviar', + Cancel: 'Cancel·lar', + Choose: 'Triar', + Preview: 'Vista prèvia', + Paste_From_Word: 'Pegar des de Word', + Tools: 'Eines', + Containers: 'Contenidors', + Classes: 'Classes', + Status: 'Estat', + Source_Code: 'Codi font' +}; + diff --git a/app/javascripts/jquery/lang/cs.js b/app/javascripts/jquery/lang/cs.js new file mode 100644 index 000000000..3939d71a4 --- /dev/null +++ b/app/javascripts/jquery/lang/cs.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['cs'] = { + Strong: 'Tučné', + Emphasis: 'Kurzíva', + Superscript: 'Horní index', + Subscript: 'Dolní index', + Ordered_List: 'Číslovaný seznam', + Unordered_List: 'Nečíslovaný seznam', + Indent: 'Zvětšit odsazení', + Outdent: 'Zmenšit odsazení', + Undo: 'Zpět', + Redo: 'Znovu', + Link: 'Vytvořit odkaz', + Unlink: 'Zrušit odkaz', + Image: 'Obrázek', + Table: 'Tabulka', + HTML: 'HTML', + Paragraph: 'Odstavec', + Heading_1: 'Nadpis 1. úrovně', + Heading_2: 'Nadpis 2. úrovně', + Heading_3: 'Nadpis 3. úrovně', + Heading_4: 'Nadpis 4. úrovně', + Heading_5: 'Nadpis 5. úrovně', + Heading_6: 'Nadpis 6. úrovně', + Preformatted: 'Předformátovaný text', + Blockquote: 'Citace', + Table_Header: 'Hlavičková buňka tabulky', + URL: 'Adresa', + Title: 'Text po najetí myší', + Alternative_Text: 'Text pro případ nezobrazení obrázku', + Caption: 'Titulek tabulky', + Summary: 'Shrnutí obsahu', + Number_Of_Rows: 'Počet řádek', + Number_Of_Cols: 'Počet sloupců', + Submit: 'Vytvořit', + Cancel: 'Zrušit', + Choose: 'Vybrat', + Preview: 'Náhled', + Paste_From_Word: 'Vložit z Wordu', + Tools: 'Nástroje', + Containers: 'Typy obsahu', + Classes: 'Třídy', + Status: 'Stav', + Source_Code: 'Zdrojový kód' +}; + diff --git a/app/javascripts/jquery/lang/cy.js b/app/javascripts/jquery/lang/cy.js new file mode 100644 index 000000000..7d15b7917 --- /dev/null +++ b/app/javascripts/jquery/lang/cy.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['cy'] = { + Strong: 'Bras', + Emphasis: 'Italig', + Superscript: 'Uwchsgript', + Subscript: 'Is-sgript', + Ordered_List: 'Rhestr mewn Trefn', + Unordered_List: 'Pwyntiau Bwled', + Indent: 'Mewnoli', + Outdent: 'Alloli', + Undo: 'Dadwneud', + Redo: 'Ailwneud', + Link: 'Cysylltu', + Unlink: 'Datgysylltu', + Image: 'Delwedd', + Table: 'Tabl', + HTML: 'HTML', + Paragraph: 'Paragraff', + Heading_1: 'Pennawd 1', + Heading_2: 'Pennawd 2', + Heading_3: 'Pennawd 3', + Heading_4: 'Pennawd 4', + Heading_5: 'Pennawd 5', + Heading_6: 'Pennawd 6', + Preformatted: 'Rhagfformat', + Blockquote: 'Bloc Dyfyniad', + Table_Header: 'Pennyn Tabl', + URL: 'URL', + Title: 'Teitl', + Alternative_Text: 'Testun Amgen', + Caption: 'Pennawd', + Summary: 'Crynodeb', + Number_Of_Rows: 'Nifer y rhesi', + Number_Of_Cols: 'Nifer y colofnau', + Submit: 'Anfon', + Cancel: 'Diddymu', + Choose: 'Dewis', + Preview: 'Rhagolwg', + Paste_From_Word: 'Gludo o Word', + Tools: 'Offer', + Containers: 'Cynhwysyddion', + Classes: 'Dosbarthiadau', + Status: 'Statws', + Source_Code: 'Cod ffynhonnell' +}; + diff --git a/app/javascripts/jquery/lang/de.js b/app/javascripts/jquery/lang/de.js new file mode 100644 index 000000000..a1e01e110 --- /dev/null +++ b/app/javascripts/jquery/lang/de.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['de'] = { + Strong: 'Fett', + Emphasis: 'Kursiv', + Superscript: 'Text hochstellen', + Subscript: 'Text tiefstellen', + Ordered_List: 'Geordnete Liste einfügen', + Unordered_List: 'Ungeordnete Liste einfügen', + Indent: 'Einzug erhöhen', + Outdent: 'Einzug vermindern', + Undo: 'Befehle rückgängig machen', + Redo: 'Befehle wiederherstellen', + Link: 'Hyperlink einfügen', + Unlink: 'Hyperlink entfernen', + Image: 'Bild einfügen', + Table: 'Tabelle einfügen', + HTML: 'HTML anzeigen/verstecken', + Paragraph: 'Absatz', + Heading_1: 'Überschrift 1', + Heading_2: 'Überschrift 2', + Heading_3: 'Überschrift 3', + Heading_4: 'Überschrift 4', + Heading_5: 'Überschrift 5', + Heading_6: 'Überschrift 6', + Preformatted: 'Vorformatiert', + Blockquote: 'Zitat', + Table_Header: 'Tabellenüberschrift', + URL: 'URL', + Title: 'Titel', + Alternative_Text: 'Alternativer Text', + Caption: 'Tabellenüberschrift', + Summary: 'Summary', + Number_Of_Rows: 'Anzahl Zeilen', + Number_Of_Cols: 'Anzahl Spalten', + Submit: 'Absenden', + Cancel: 'Abbrechen', + Choose: 'Auswählen', + Preview: 'Vorschau', + Paste_From_Word: 'Aus Word einfügen', + Tools: 'Werkzeuge', + Containers: 'Inhaltstyp', + Classes: 'Klassen', + Status: 'Status', + Source_Code: 'Quellcode' +}; + diff --git a/app/javascripts/jquery/lang/en.js b/app/javascripts/jquery/lang/en.js new file mode 100644 index 000000000..dc7eb21b3 --- /dev/null +++ b/app/javascripts/jquery/lang/en.js @@ -0,0 +1,46 @@ +WYMeditor.STRINGS['en'] = { + Strong: 'Strong', + Emphasis: 'Emphasis', + Superscript: 'Superscript', + Subscript: 'Subscript', + Ordered_List: 'Ordered List', + Unordered_List: 'Unordered List', + Indent: 'Indent', + Outdent: 'Outdent', + Undo: 'Undo', + Redo: 'Redo', + Link: 'Link', + Unlink: 'Unlink', + Image: 'Image', + Table: 'Table', + HTML: 'HTML', + Paragraph: 'Paragraph', + Heading_1: 'Heading 1', + Heading_2: 'Heading 2', + Heading_3: 'Heading 3', + Heading_4: 'Heading 4', + Heading_5: 'Heading 5', + Heading_6: 'Heading 6', + Preformatted: 'Preformatted', + Blockquote: 'Blockquote', + Table_Header: 'Table Header', + URL: 'URL', + Title: 'Title', + Relationship: 'Relationship', + Alternative_Text: 'Alternative text', + Caption: 'Caption', + Summary: 'Summary', + Number_Of_Rows: 'Number of rows', + Number_Of_Cols: 'Number of cols', + Submit: 'Submit', + Cancel: 'Cancel', + Choose: 'Choose', + Preview: 'Preview', + Paste_From_Word: 'Paste from Word', + Tools: 'Tools', + Containers: 'Containers', + Classes: 'Classes', + Status: 'Status', + Source_Code: 'Source code' +}; + diff --git a/app/javascripts/jquery/lang/es.js b/app/javascripts/jquery/lang/es.js new file mode 100644 index 000000000..cdb03c10e --- /dev/null +++ b/app/javascripts/jquery/lang/es.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['es'] = { + Strong: 'Resaltar', + Emphasis: 'Enfatizar', + Superscript: 'Superindice', + Subscript: 'Subindice', + Ordered_List: 'Lista ordenada', + Unordered_List: 'Lista sin ordenar', + Indent: 'Indentado', + Outdent: 'Sin indentar', + Undo: 'Deshacer', + Redo: 'Rehacer', + Link: 'Enlazar', + Unlink: 'Eliminar enlace', + Image: 'Imagen', + Table: 'Tabla', + HTML: 'HTML', + Paragraph: 'Párrafo', + Heading_1: 'Cabecera 1', + Heading_2: 'Cabecera 2', + Heading_3: 'Cabecera 3', + Heading_4: 'Cabecera 4', + Heading_5: 'Cabecera 5', + Heading_6: 'Cabecera 6', + Preformatted: 'Preformateado', + Blockquote: 'Cita', + Table_Header: 'Cabecera de la tabla', + URL: 'URL', + Title: 'Título', + Alternative_Text: 'Texto alternativo', + Caption: 'Leyenda', + Summary: 'Summary', + Number_Of_Rows: 'Número de filas', + Number_Of_Cols: 'Número de columnas', + Submit: 'Enviar', + Cancel: 'Cancelar', + Choose: 'Seleccionar', + Preview: 'Vista previa', + Paste_From_Word: 'Pegar desde Word', + Tools: 'Herramientas', + Containers: 'Contenedores', + Classes: 'Clases', + Status: 'Estado', + Source_Code: 'Código fuente' +}; + diff --git a/app/javascripts/jquery/lang/fa.js b/app/javascripts/jquery/lang/fa.js new file mode 100644 index 000000000..9d70fcb16 --- /dev/null +++ b/app/javascripts/jquery/lang/fa.js @@ -0,0 +1,46 @@ +//Translation To Persian: Ghassem Tofighi (http://ght.ir) +WYMeditor.STRINGS['fa'] = { + Strong: 'پررنگ',//Strong + Emphasis: 'ایتالیک',//Emphasis + Superscript: 'بالانويس‌ ',//Superscript + Subscript: 'زيرنويس‌',//Subscript + Ordered_List: 'لیست مرتب',//Ordered List + Unordered_List: 'لیست نامرتب',//Unordered List + Indent: 'افزودن دندانه',//Indent + Outdent: 'کاهش دندانه',//Outdent + Undo: 'واگردانی',//Undo + Redo: 'تکرار',//Redo + Link: 'ساختن پیوند',//Link + Unlink: 'برداشتن پیوند',//Unlink + Image: 'تصویر',//Image + Table: 'جدول',//Table + HTML: 'HTML',//HTML + Paragraph: 'پاراگراف',//Paragraph + Heading_1: 'سرتیتر ۱',//Heading 1 + Heading_2: 'سرتیتر ۲',//Heading 2 + Heading_3: 'سرتیتر ۳',//Heading 3 + Heading_4: 'سرتیتر ۴',//Heading 4 + Heading_5: 'سرتیتر ۵',//Heading 5 + Heading_6: 'سرتیتر ۶',//Heading 6 + Preformatted: 'قالب آماده',//Preformatted + Blockquote: 'نقل قول',//Blockquote + Table_Header: 'سرجدول',//Table Header + URL: 'آدرس اینترنتی',//URL + Title: 'عنوان',//Title + Alternative_Text: 'متن جایگزین',//Alternative text + Caption: 'عنوان',//Caption + Summary: 'Summary', + Number_Of_Rows: 'تعداد سطرها',//Number of rows + Number_Of_Cols: 'تعداد ستون‌ها',//Number of cols + Submit: 'فرستادن',//Submit + Cancel: 'لغو',//Cancel + Choose: 'انتخاب',//Choose + Preview: 'پیش‌نمایش',//Preview + Paste_From_Word: 'انتقال از ورد',//Paste from Word + Tools: 'ابزار',//Tools + Containers: '‌قالب‌ها',//Containers + Classes: 'کلاس‌ها',//Classes + Status: 'وضعیت',//Status + Source_Code: 'کد مبدأ'//Source code +}; + diff --git a/app/javascripts/jquery/lang/fi.js b/app/javascripts/jquery/lang/fi.js new file mode 100644 index 000000000..fe1eab4e7 --- /dev/null +++ b/app/javascripts/jquery/lang/fi.js @@ -0,0 +1,44 @@ +WYMeditor.STRINGS['fi'] = { + Strong: 'Lihavoitu', + Emphasis: 'Korostus', + Superscript: 'Yläindeksi', + Subscript: 'Alaindeksi', + Ordered_List: 'Numeroitu lista', + Unordered_List: 'Luettelomerkit', + Indent: 'Suurenna sisennystä', + Outdent: 'Pienennä sisennystä', + Undo: 'Kumoa', + Redo: 'Toista', + Link: 'Linkitä', + Unlink: 'Poista linkitys', + Image: 'Kuva', + Table: 'Taulukko', + HTML: 'HTML', + Paragraph: 'Kappale', + Heading_1: 'Otsikko 1', + Heading_2: 'Otsikko 2', + Heading_3: 'Otsikko 3', + Heading_4: 'Otsikko 4', + Heading_5: 'Otsikko 5', + Heading_6: 'Otsikko 6', + Preformatted: 'Esimuotoilu', + Blockquote: 'Sitaatti', + Table_Header: 'Taulukon otsikko', + URL: 'URL', + Title: 'Otsikko', + Alternative_Text: 'Vaihtoehtoinen teksti', + Caption: 'Kuvateksti', + Summary: 'Yhteenveto', + Number_Of_Rows: 'Rivien määrä', + Number_Of_Cols: 'Palstojen määrä', + Submit: 'Lähetä', + Cancel: 'Peruuta', + Choose: 'Valitse', + Preview: 'Esikatsele', + Paste_From_Word: 'Tuo Wordista', + Tools: 'Työkalut', + Containers: 'Muotoilut', + Classes: 'Luokat', + Status: 'Tila', + Source_Code: 'Lähdekoodi' +}; diff --git a/app/javascripts/jquery/lang/fr.js b/app/javascripts/jquery/lang/fr.js new file mode 100644 index 000000000..9b6deb956 --- /dev/null +++ b/app/javascripts/jquery/lang/fr.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['fr'] = { + Strong: 'Mise en évidence', + Emphasis: 'Emphase', + Superscript: 'Exposant', + Subscript: 'Indice', + Ordered_List: 'Liste Ordonnée', + Unordered_List: 'Liste Non-Ordonnée', + Indent: 'Imbriqué', + Outdent: 'Non-imbriqué', + Undo: 'Annuler', + Redo: 'Rétablir', + Link: 'Lien', + Unlink: 'Supprimer le Lien', + Image: 'Image', + Table: 'Tableau', + HTML: 'HTML', + Paragraph: 'Paragraphe', + Heading_1: 'Titre 1', + Heading_2: 'Titre 2', + Heading_3: 'Titre 3', + Heading_4: 'Titre 4', + Heading_5: 'Titre 5', + Heading_6: 'Titre 6', + Preformatted: 'Pré-formatté', + Blockquote: 'Citation', + Table_Header: 'Cellule de titre', + URL: 'URL', + Title: 'Titre', + Alternative_Text: 'Texte alternatif', + Caption: 'Légende', + Summary: 'Résumé', + Number_Of_Rows: 'Nombre de lignes', + Number_Of_Cols: 'Nombre de colonnes', + Submit: 'Envoyer', + Cancel: 'Annuler', + Choose: 'Choisir', + Preview: 'Prévisualisation', + Paste_From_Word: 'Copier depuis Word', + Tools: 'Outils', + Containers: 'Type de texte', + Classes: 'Type de contenu', + Status: 'Infos', + Source_Code: 'Code source' +}; + diff --git a/app/javascripts/jquery/lang/gl.js b/app/javascripts/jquery/lang/gl.js new file mode 100644 index 000000000..d4786b8b4 --- /dev/null +++ b/app/javascripts/jquery/lang/gl.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['gl'] = { + Strong: 'Moita énfase', + Emphasis: 'Énfase', + Superscript: 'Superíndice', + Subscript: 'Subíndice', + Ordered_List: 'Lista ordenada', + Unordered_List: 'Lista sen ordenar', + Indent: 'Aniñar', + Outdent: 'Desaniñar', + Undo: 'Desfacer', + Redo: 'Refacer', + Link: 'Ligazón', + Unlink: 'Desligar', + Image: 'Imaxe', + Table: 'Táboa', + HTML: 'HTML', + Paragraph: 'Parágrafo', + Heading_1: 'Título 1', + Heading_2: 'Título 2', + Heading_3: 'Título 3', + Heading_4: 'Título 4', + Heading_5: 'Título 5', + Heading_6: 'Título 6', + Preformatted: 'Preformatado', + Blockquote: 'Cita en parágrafo', + Table_Header: 'Cabeceira da táboa', + URL: 'URL', + Title: 'Título', + Alternative_Text: 'Texto alternativo', + Caption: 'Título', + Summary: 'Resumo', + Number_Of_Rows: 'Número de filas', + Number_Of_Cols: 'Número de columnas', + Submit: 'Enviar', + Cancel: 'Cancelar', + Choose: 'Escoller', + Preview: 'Previsualizar', + Paste_From_Word: 'Colar dende Word', + Tools: 'Ferramentas', + Containers: 'Contenedores', + Classes: 'Clases', + Status: 'Estado', + Source_Code: 'Código fonte' +}; + diff --git a/app/javascripts/jquery/lang/he.js b/app/javascripts/jquery/lang/he.js new file mode 100644 index 000000000..97c9675f4 --- /dev/null +++ b/app/javascripts/jquery/lang/he.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['he'] = { + Strong: 'חזק', + Emphasis: 'מובלט', + Superscript: 'כתב עילי', + Subscript: 'כתב תחתי', + Ordered_List: 'רשימה ממוספרת', + Unordered_List: 'רשימה לא ממוספרת', + Indent: 'הזחה פנימה', + Outdent: 'הזחה החוצה', + Undo: 'בטל פעולה', + Redo: 'בצע מחדש פעולה', + Link: 'קישור', + Unlink: 'בטל קישור', + Image: 'תמונה', + Table: 'טבלה', + HTML: 'קוד HTML', + Paragraph: 'פסקה', + Heading_1: 'כותרת 1 ; תג <h1>', + Heading_2: 'כותרת 2 ; תג <h2>', + Heading_3: 'כותרת 3 ; תג <h3>', + Heading_4: 'כותרת 4 ; תג <h4>', + Heading_5: 'כותרת 5 ; תג <h5>', + Heading_6: 'כותרת 6 ; תג <h6>', + Preformatted: 'משמר רווחים', + Blockquote: 'ציטוט', + Table_Header: 'כותרת טבלה', + URL: 'קישור (URL)', + Title: 'כותרת', + Alternative_Text: 'טקסט חלופי', + Caption: 'כותרת', + Summary: 'סיכום', + Number_Of_Rows: 'מספר שורות', + Number_Of_Cols: 'מספר טורים', + Submit: 'שלח', + Cancel: 'בטל', + Choose: 'בחר', + Preview: 'תצוגה מקדימה', + Paste_From_Word: 'העתק מ-Word', + Tools: 'כלים', + Containers: 'מיכלים', + Classes: 'מחלקות', + Status: 'מצב', + Source_Code: 'קוד מקור' +}; + diff --git a/app/javascripts/jquery/lang/hr.js b/app/javascripts/jquery/lang/hr.js new file mode 100644 index 000000000..193e31a93 --- /dev/null +++ b/app/javascripts/jquery/lang/hr.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['hr'] = { + Strong: 'Podebljano', + Emphasis: 'Naglašeno', + Superscript: 'Iznad', + Subscript: 'Ispod', + Ordered_List: 'Pobrojana lista', + Unordered_List: 'Nepobrojana lista', + Indent: 'Uvuci', + Outdent: 'Izvuci', + Undo: 'Poništi promjenu', + Redo: 'Ponovno promjeni', + Link: 'Hiperveza', + Unlink: 'Ukloni hipervezu', + Image: 'Slika', + Table: 'Tablica', + HTML: 'HTML', + Paragraph: 'Paragraf', + Heading_1: 'Naslov 1', + Heading_2: 'Naslov 2', + Heading_3: 'Naslov 3', + Heading_4: 'Naslov 4', + Heading_5: 'Naslov 5', + Heading_6: 'Naslov 6', + Preformatted: 'Unaprijed formatirano', + Blockquote: 'Citat', + Table_Header: 'Zaglavlje tablice', + URL: 'URL', + Title: 'Naslov', + Alternative_Text: 'Alternativni tekst', + Caption: 'Zaglavlje', + Summary: 'Sažetak', + Number_Of_Rows: 'Broj redova', + Number_Of_Cols: 'Broj kolona', + Submit: 'Snimi', + Cancel: 'Odustani', + Choose: 'Izaberi', + Preview: 'Pregled', + Paste_From_Word: 'Zalijepi iz Word-a', + Tools: 'Alati', + Containers: 'Kontejneri', + Classes: 'Klase', + Status: 'Status', + Source_Code: 'Izvorni kod' +}; + diff --git a/app/javascripts/jquery/lang/hu.js b/app/javascripts/jquery/lang/hu.js new file mode 100644 index 000000000..a8cdbc610 --- /dev/null +++ b/app/javascripts/jquery/lang/hu.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['hu'] = { + Strong: 'Félkövér', + Emphasis: 'Kiemelt', + Superscript: 'Felső index', + Subscript: 'Alsó index', + Ordered_List: 'Rendezett lista', + Unordered_List: 'Rendezetlen lista', + Indent: 'Bekezdés', + Outdent: 'Bekezdés törlése', + Undo: 'Visszavon', + Redo: 'Visszaállít', + Link: 'Link', + Unlink: 'Link törlése', + Image: 'Kép', + Table: 'Tábla', + HTML: 'HTML', + Paragraph: 'Bekezdés', + Heading_1: 'Címsor 1', + Heading_2: 'Címsor 2', + Heading_3: 'Címsor 3', + Heading_4: 'Címsor 4', + Heading_5: 'Címsor 5', + Heading_6: 'Címsor 6', + Preformatted: 'Előformázott', + Blockquote: 'Idézet', + Table_Header: 'Tábla Fejléc', + URL: 'Webcím', + Title: 'Megnevezés', + Alternative_Text: 'Alternatív szöveg', + Caption: 'Fejléc', + Summary: 'Summary', + Number_Of_Rows: 'Sorok száma', + Number_Of_Cols: 'Oszlopok száma', + Submit: 'Elküld', + Cancel: 'Mégsem', + Choose: 'Választ', + Preview: 'Előnézet', + Paste_From_Word: 'Másolás Word-ból', + Tools: 'Eszközök', + Containers: 'Tartalmak', + Classes: 'Osztályok', + Status: 'Állapot', + Source_Code: 'Forráskód' +}; + diff --git a/app/javascripts/jquery/lang/it.js b/app/javascripts/jquery/lang/it.js new file mode 100644 index 000000000..ca632a909 --- /dev/null +++ b/app/javascripts/jquery/lang/it.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['it'] = { + Strong: 'Grassetto', + Emphasis: 'Corsetto', + Superscript: 'Apice', + Subscript: 'Pedice', + Ordered_List: 'Lista Ordinata', + Unordered_List: 'Lista Puntata', + Indent: 'Indenta', + Outdent: 'Caccia', + Undo: 'Indietro', + Redo: 'Avanti', + Link: 'Inserisci Link', + Unlink: 'Togli Link', + Image: 'Inserisci Immagine', + Table: 'Inserisci Tabella', + HTML: 'HTML', + Paragraph: 'Paragrafo', + Heading_1: 'Heading 1', + Heading_2: 'Heading 2', + Heading_3: 'Heading 3', + Heading_4: 'Heading 4', + Heading_5: 'Heading 5', + Heading_6: 'Heading 6', + Preformatted: 'Preformattato', + Blockquote: 'Blockquote', + Table_Header: 'Header Tabella', + URL: 'Indirizzo', + Title: 'Titolo', + Alternative_Text: 'Testo Alternativo', + Caption: 'Caption', + Summary: 'Summary', + Number_Of_Rows: 'Numero di Righe', + Number_Of_Cols: 'Numero di Colonne', + Submit: 'Invia', + Cancel: 'Cancella', + Choose: 'Scegli', + Preview: 'Anteprima', + Paste_From_Word: 'Incolla', + Tools: 'Tools', + Containers: 'Contenitori', + Classes: 'Classi', + Status: 'Stato', + Source_Code: 'Codice Sorgente' +}; + diff --git a/app/javascripts/jquery/lang/ja.js b/app/javascripts/jquery/lang/ja.js new file mode 100644 index 000000000..c9b641027 --- /dev/null +++ b/app/javascripts/jquery/lang/ja.js @@ -0,0 +1,44 @@ +WYMeditor.STRINGS['ja'] = { + Strong: '強調', + Emphasis: '強調', + Superscript: '上付き', + Subscript: '下付き', + Ordered_List: '番号付きリスト', + Unordered_List: '番号無リスト', + Indent: 'インデントを増やす', + Outdent: 'インデントを減らす', + Undo: '元に戻す', + Redo: 'やり直す', + Link: 'リンク', + Unlink: 'リンク取消', + Image: '画像', + Table: 'テーブル', + HTML: 'HTML', + Paragraph: '段落', + Heading_1: '見出し 1', + Heading_2: '見出し 2', + Heading_3: '見出し 3', + Heading_4: '見出し 4', + Heading_5: '見出し 5', + Heading_6: '見出し 6', + Preformatted: '整形済みテキスト', + Blockquote: '引用文', + Table_Header: '表見出し', + URL: 'URL', + Title: 'タイトル', + Alternative_Text: '代替テキスト', + Caption: 'キャプション', + Summary: 'サマリー', + Number_Of_Rows: '行数', + Number_Of_Cols: '列数', + Submit: '送信', + Cancel: 'キャンセル', + Choose: '選択', + Preview: 'プレビュー', + Paste_From_Word: '貼り付け', + Tools: 'ツール', + Containers: 'コンテナ', + Classes: 'クラス', + Status: 'ステータス', + Source_Code: 'ソースコード' +}; diff --git a/app/javascripts/jquery/lang/nb.js b/app/javascripts/jquery/lang/nb.js new file mode 100644 index 000000000..7573b78a5 --- /dev/null +++ b/app/javascripts/jquery/lang/nb.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['nb'] = { + Strong: 'Fet', + Emphasis: 'Uthevet', + Superscript: 'Opphøyet', + Subscript: 'Nedsenket', + Ordered_List: 'Nummerert liste', + Unordered_List: 'Punktliste', + Indent: 'Rykk inn', + Outdent: 'Rykk ut', + Undo: 'Angre', + Redo: 'Gjenta', + Link: 'Lenke', + Unlink: 'Ta bort lenken', + Image: 'Bilde', + Table: 'Tabell', + HTML: 'HTML', + Paragraph: 'Avsnitt', + Heading_1: 'Overskrift 1', + Heading_2: 'Overskrift 2', + Heading_3: 'Overskrift 3', + Heading_4: 'Overskrift 4', + Heading_5: 'Overskrift 5', + Heading_6: 'Overskrift 6', + Preformatted: 'Preformatert', + Blockquote: 'Sitat', + Table_Header: 'Tabelloverskrift', + URL: 'URL', + Title: 'Tittel', + Alternative_Text: 'Alternativ tekst', + Caption: 'Overskrift', + Summary: 'Sammendrag', + Number_Of_Rows: 'Antall rader', + Number_Of_Cols: 'Antall kolonner', + Submit: 'Ok', + Cancel: 'Avbryt', + Choose: 'Velg', + Preview: 'Forhåndsvis', + Paste_From_Word: 'Lim inn fra Word', + Tools: 'Verktøy', + Containers: 'Formatering', + Classes: 'Klasser', + Status: 'Status', + Source_Code: 'Kildekode' +}; + diff --git a/app/javascripts/jquery/lang/nl.js b/app/javascripts/jquery/lang/nl.js new file mode 100644 index 000000000..cdfa21cc5 --- /dev/null +++ b/app/javascripts/jquery/lang/nl.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['nl'] = { + Strong: 'Sterk benadrukken', + Emphasis: 'Benadrukken', + Superscript: 'Bovenschrift', + Subscript: 'Onderschrift', + Ordered_List: 'Geordende lijst', + Unordered_List: 'Ongeordende lijst', + Indent: 'Inspringen', + Outdent: 'Terugspringen', + Undo: 'Ongedaan maken', + Redo: 'Opnieuw uitvoeren', + Link: 'Linken', + Unlink: 'Ontlinken', + Image: 'Afbeelding', + Table: 'Tabel', + HTML: 'HTML', + Paragraph: 'Paragraaf', + Heading_1: 'Kop 1', + Heading_2: 'Kop 2', + Heading_3: 'Kop 3', + Heading_4: 'Kop 4', + Heading_5: 'Kop 5', + Heading_6: 'Kop 6', + Preformatted: 'Voorgeformatteerd', + Blockquote: 'Citaat', + Table_Header: 'Tabel-kop', + URL: 'URL', + Title: 'Titel', + Alternative_Text: 'Alternatieve tekst', + Caption: 'Bijschrift', + Summary: 'Summary', + Number_Of_Rows: 'Aantal rijen', + Number_Of_Cols: 'Aantal kolommen', + Submit: 'Versturen', + Cancel: 'Annuleren', + Choose: 'Kiezen', + Preview: 'Voorbeeld bekijken', + Paste_From_Word: 'Plakken uit Word', + Tools: 'Hulpmiddelen', + Containers: 'Teksttypes', + Classes: 'Klassen', + Status: 'Status', + Source_Code: 'Broncode' +}; + diff --git a/app/javascripts/jquery/lang/nn.js b/app/javascripts/jquery/lang/nn.js new file mode 100644 index 000000000..51cec2bd0 --- /dev/null +++ b/app/javascripts/jquery/lang/nn.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['nn'] = { + Strong: 'Feit', + Emphasis: 'Utheva', + Superscript: 'Opphøgd', + Subscript: 'Nedsenka', + Ordered_List: 'Nummerert liste', + Unordered_List: 'Punktliste', + Indent: 'Rykk inn', + Outdent: 'Rykk ut', + Undo: 'Angre', + Redo: 'Gjentaka', + Link: 'Lenkje', + Unlink: 'Ta bort lenkja', + Image: 'Bilete', + Table: 'Tabell', + HTML: 'HTML', + Paragraph: 'Avsnitt', + Heading_1: 'Overskrift 1', + Heading_2: 'Overskrift 2', + Heading_3: 'Overskrift 3', + Heading_4: 'Overskrift 4', + Heading_5: 'Overskrift 5', + Heading_6: 'Overskrift 6', + Preformatted: 'Preformatert', + Blockquote: 'Sitat', + Table_Header: 'Tabelloverskrift', + URL: 'URL', + Title: 'Tittel', + Alternative_Text: 'Alternativ tekst', + Caption: 'Overskrift', + Summary: 'Samandrag', + Number_Of_Rows: 'Tal på rader', + Number_Of_Cols: 'Tal på kolonnar', + Submit: 'Ok', + Cancel: 'Avbryt', + Choose: 'Vel', + Preview: 'Førehandsvis', + Paste_From_Word: 'Lim inn frå Word', + Tools: 'Verkty', + Containers: 'Formatering', + Classes: 'Klassar', + Status: 'Status', + Source_Code: 'Kjeldekode' +}; + diff --git a/app/javascripts/jquery/lang/pl.js b/app/javascripts/jquery/lang/pl.js new file mode 100644 index 000000000..d6c0471a9 --- /dev/null +++ b/app/javascripts/jquery/lang/pl.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['pl'] = { + Strong: 'Nacisk', + Emphasis: 'Emfaza', + Superscript: 'Indeks górny', + Subscript: 'Indeks dolny', + Ordered_List: 'Lista numerowana', + Unordered_List: 'Lista wypunktowana', + Indent: 'Zwiększ wcięcie', + Outdent: 'Zmniejsz wcięcie', + Undo: 'Cofnij', + Redo: 'Ponów', + Link: 'Wstaw link', + Unlink: 'Usuń link', + Image: 'Obraz', + Table: 'Tabela', + HTML: 'Źródło HTML', + Paragraph: 'Akapit', + Heading_1: 'Nagłówek 1', + Heading_2: 'Nagłówek 2', + Heading_3: 'Nagłówek 3', + Heading_4: 'Nagłówek 4', + Heading_5: 'Nagłówek 5', + Heading_6: 'Nagłówek 6', + Preformatted: 'Preformatowany', + Blockquote: 'Cytat blokowy', + Table_Header: 'Nagłówek tabeli', + URL: 'URL', + Title: 'Tytuł', + Alternative_Text: 'Tekst alternatywny', + Caption: 'Tytuł tabeli', + Summary: 'Summary', + Number_Of_Rows: 'Liczba wierszy', + Number_Of_Cols: 'Liczba kolumn', + Submit: 'Wyślij', + Cancel: 'Anuluj', + Choose: 'Wybierz', + Preview: 'Podgląd', + Paste_From_Word: 'Wklej z Worda', + Tools: 'Narzędzia', + Containers: 'Format', + Classes: 'Styl', + Status: 'Status', + Source_Code: 'Kod źródłowy' +}; + diff --git a/app/javascripts/jquery/lang/pt-br.js b/app/javascripts/jquery/lang/pt-br.js new file mode 100644 index 000000000..2ec18feec --- /dev/null +++ b/app/javascripts/jquery/lang/pt-br.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['pt-br'] = { + Strong: 'Resaltar', + Emphasis: 'Enfatizar', + Superscript: 'Sobre escrito', + Subscript: 'Sub escrito ', + Ordered_List: 'Lista ordenada', + Unordered_List: 'Lista desordenada', + Indent: 'Indentado', + Outdent: 'Desidentar', + Undo: 'Desfazer', + Redo: 'Refazer', + Link: 'Link', + Unlink: 'Remover Link', + Image: 'Imagem', + Table: 'Tabela', + HTML: 'HTML', + Paragraph: 'Parágrafo', + Heading_1: 'Título 1', + Heading_2: 'Título 2', + Heading_3: 'Título 3', + Heading_4: 'Título 4', + Heading_5: 'Título 5', + Heading_6: 'Título 6', + Preformatted: 'Preformatado', + Blockquote: 'Citação', + Table_Header: 'Título de tabela', + URL: 'URL', + Title: 'Título', + Alternative_Text: 'Texto alternativo', + Caption: 'Legenda', + Summary: 'Summary', + Number_Of_Rows: 'Número de linhas', + Number_Of_Cols: 'Número de colunas', + Submit: 'Enviar', + Cancel: 'Cancelar', + Choose: 'Selecionar', + Preview: 'Previsualizar', + Paste_From_Word: 'Copiar do Word', + Tools: 'Ferramentas', + Containers: 'Conteneiners', + Classes: 'Classes', + Status: 'Estado', + Source_Code: 'Código fonte' +}; + diff --git a/app/javascripts/jquery/lang/pt.js b/app/javascripts/jquery/lang/pt.js new file mode 100644 index 000000000..a3d1a1762 --- /dev/null +++ b/app/javascripts/jquery/lang/pt.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['pt'] = { + Strong: 'Negrito', + Emphasis: 'Itálico', + Superscript: 'Sobrescrito', + Subscript: 'Subsescrito', + Ordered_List: 'Lista Numerada', + Unordered_List: 'Lista Marcada', + Indent: 'Aumentar Indentaçã', + Outdent: 'Diminuir Indentaçã', + Undo: 'Desfazer', + Redo: 'Restaurar', + Link: 'Link', + Unlink: 'Tirar link', + Image: 'Imagem', + Table: 'Tabela', + HTML: 'HTML', + Paragraph: 'Parágrafo', + Heading_1: 'Título 1', + Heading_2: 'Título 2', + Heading_3: 'Título 3', + Heading_4: 'Título 4', + Heading_5: 'Título 5', + Heading_6: 'Título 6', + Preformatted: 'Pré-formatado', + Blockquote: 'Citação', + Table_Header: 'Cabeçalho Tabela', + URL: 'URL', + Title: 'Título', + Alternative_Text: 'Texto Alterativo', + Caption: 'Título Tabela', + Summary: 'Summary', + Number_Of_Rows: 'Número de Linhas', + Number_Of_Cols: 'Número de Colunas', + Submit: 'Enviar', + Cancel: 'Cancelar', + Choose: 'Escolha', + Preview: 'Prever', + Paste_From_Word: 'Colar do Word', + Tools: 'Ferramentas', + Containers: 'Containers', + Classes: 'Classes', + Status: 'Status', + Source_Code: 'Código Fonte' +}; + diff --git a/app/javascripts/jquery/lang/ru.js b/app/javascripts/jquery/lang/ru.js new file mode 100644 index 000000000..7895f8d9f --- /dev/null +++ b/app/javascripts/jquery/lang/ru.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['ru'] = { + Strong: 'Жирный', + Emphasis: 'Наклонный', + Superscript: 'Надстрочный', + Subscript: 'Подстрочный', + Ordered_List: 'Нумерованый список', + Unordered_List: 'Ненумерованый список', + Indent: 'Увеличить отступ', + Outdent: 'Уменьшить отступ', + Undo: 'Отменить', + Redo: 'Повторить', + Link: 'Ссылка', + Unlink: 'Удалить ссылку', + Image: 'Изображение', + Table: 'Таблица', + HTML: 'Править HTML', + Paragraph: 'Параграф', + Heading_1: 'Заголовок 1', + Heading_2: 'Заголовок 2', + Heading_3: 'Заголовок 3', + Heading_4: 'Заголовок 4', + Heading_5: 'Заголовок 5', + Heading_6: 'Заголовок 6', + Preformatted: 'Preformatted', + Blockquote: 'Цитата', + Table_Header: 'Заголовок таблицы', + URL: 'URL', + Title: 'Заголовок', + Alternative_Text: 'Альтернативный текст', + Caption: 'Надпись', + Summary: 'Summary', + Number_Of_Rows: 'Кол-во строк', + Number_Of_Cols: 'Кол-во столбцов', + Submit: 'Отправить', + Cancel: 'Отмена', + Choose: 'Выбор', + Preview: 'Просмотр', + Paste_From_Word: 'Вставить из Word', + Tools: 'Инструменты', + Containers: 'Контейнеры', + Classes: 'Классы', + Status: 'Статус', + Source_Code: 'Исходный код' +}; + diff --git a/app/javascripts/jquery/lang/sv.js b/app/javascripts/jquery/lang/sv.js new file mode 100644 index 000000000..634795625 --- /dev/null +++ b/app/javascripts/jquery/lang/sv.js @@ -0,0 +1,46 @@ +WYMeditor.STRINGS['sv'] = { + Strong: 'Viktigt', + Emphasis: 'Betoning', + Superscript: 'Upphöjt', + Subscript: 'Nedsänkt', + Ordered_List: 'Nummerlista', + Unordered_List: 'Punktlista', + Indent: 'Indrag', + Outdent: 'Utdrag', + Undo: 'Ångra', + Redo: 'Gör om', + Link: 'Länk', + Unlink: 'Ta bort länk', + Image: 'Bild', + Table: 'Tabell', + HTML: 'HTML', + Paragraph: 'Paragraf', + Heading_1: 'Rubrik 1', + Heading_2: 'Rubrik 2', + Heading_3: 'Rubrik 3', + Heading_4: 'Rubrik 4', + Heading_5: 'Rubrik 5', + Heading_6: 'Rubrik 6', + Preformatted: 'Förformaterad', + Blockquote: 'Blockcitat', + Table_Header: 'Tabellrubrik', + URL: 'URL', + Title: 'Titel', + Relationship: 'Relation', + Alternative_Text: 'Alternativ text', + Caption: 'Överskrift', + Summary: 'Summary', + Number_Of_Rows: 'Antal rader', + Number_Of_Cols: 'Antal kolumner', + Submit: 'Skicka', + Cancel: 'Avbryt', + Choose: 'Välj', + Preview: 'Förhandsgranska', + Paste_From_Word: 'Klistra in från Word', + Tools: 'Verktyg', + Containers: 'Formatering', + Classes: 'Klasser', + Status: 'Status', + Source_Code: 'Källkod' +}; + diff --git a/app/javascripts/jquery/lang/tr.js b/app/javascripts/jquery/lang/tr.js new file mode 100644 index 000000000..d26f0ff67 --- /dev/null +++ b/app/javascripts/jquery/lang/tr.js @@ -0,0 +1,45 @@ +WYMeditor.STRINGS['tr'] = { + Strong: 'Kalın', + Emphasis: 'Vurgu', + Superscript: 'Superscript', + Subscript: 'Subscript', + Ordered_List: 'Sıralı List', + Unordered_List: 'Sırasız List', + Indent: 'İçerlek', + Outdent: 'Çıkıntılı', + Undo: 'Geri Al', + Redo: 'Yinele', + Link: 'Bağlantı', + Unlink: 'Bağlantıyı Kaldır', + Image: 'İmaj', + Table: 'Tablo', + HTML: 'HTML', + Paragraph: 'Parağraf', + Heading_1: 'Başlık 1', + Heading_2: 'Başlık 2', + Heading_3: 'Başlık 3', + Heading_4: 'Başlık 4', + Heading_5: 'Başlık 5', + Heading_6: 'Başlık 6', + Preformatted: 'Önceden Formatlı', + Blockquote: 'Alıntı', + Table_Header: 'Tablo Başlığı', + URL: 'URL', + Title: 'Başlık', + Alternative_Text: 'Alternatif Metin', + Caption: 'Etiket', + Summary: 'Summary', + Number_Of_Rows: 'Satır sayısı', + Number_Of_Cols: 'Sütun sayısı', + Submit: 'Gönder', + Cancel: 'İptal', + Choose: 'Seç', + Preview: 'Önizleme', + Paste_From_Word: 'Wordden yapıştır', + Tools: 'Araçlar', + Containers: 'Kapsayıcılar', + Classes: 'Sınıflar', + Status: 'Durum', + Source_Code: 'Kaynak Kodu' +}; + diff --git a/app/javascripts/jquery/lang/zh_cn.js b/app/javascripts/jquery/lang/zh_cn.js new file mode 100644 index 000000000..72f5aaf50 --- /dev/null +++ b/app/javascripts/jquery/lang/zh_cn.js @@ -0,0 +1,47 @@ +WYMeditor.STRINGS['zh_cn'] = { + Strong: '加粗', + Emphasis: '斜体', + Superscript: '上标', + Subscript: '下标', + Ordered_List: '有序列表', + Unordered_List: '无序列表', + Indent: '增加缩进', + Outdent: '减少缩进', + Undo: '撤消', + Redo: '重做', + Link: '链接', + Unlink: '取消链接', + Image: '图片', + Table: '表格', + HTML: 'HTML源代码', + Paragraph: '段落', + Heading_1: '标题 1', + Heading_2: '标题 2', + Heading_3: '标题 3', + Heading_4: '标题 4', + Heading_5: '标题 5', + Heading_6: '标题 6', + Preformatted: '原始文本', + Blockquote: '引语', + Table_Header: '表头', + URL: '地址', + Title: '提示文字', + Alternative_Text: '失效文字', + Caption: '标题', + Summary: 'Summary', + Number_Of_Rows: '行数', + Number_Of_Cols: '列数', + Submit: '提交', + Cancel: '放弃', + Choose: '选择', + Preview: '预览', + Paste_From_Word: '从Word粘贴纯文本', + Tools: '工具', + Containers: '容器', + Classes: '预定义样式', + Status: '状态', + Source_Code: '源代码', + Attachment: '附件', + NewParagraph: '新段落' +}; + diff --git a/app/javascripts/jquery/mustache.js b/app/javascripts/jquery/mustache.js new file mode 100644 index 000000000..35a8976a1 --- /dev/null +++ b/app/javascripts/jquery/mustache.js @@ -0,0 +1,325 @@ +/* + mustache.js — Logic-less templates in JavaScript + + See http://mustache.github.com/ for more info. +*/ + +var Mustache = function() { + var Renderer = function() {}; + + Renderer.prototype = { + otag: "{{", + ctag: "}}", + pragmas: {}, + buffer: [], + pragmas_implemented: { + "IMPLICIT-ITERATOR": true + }, + context: {}, + + render: function(template, context, partials, in_recursion) { + // reset buffer & set context + if(!in_recursion) { + this.context = context; + this.buffer = []; // TODO: make this non-lazy + } + + // fail fast + if(!this.includes("", template)) { + if(in_recursion) { + return template; + } else { + this.send(template); + return; + } + } + + template = this.render_pragmas(template); + var html = this.render_section(template, context, partials); + if(in_recursion) { + return this.render_tags(html, context, partials, in_recursion); + } + + this.render_tags(html, context, partials, in_recursion); + }, + + /* + Sends parsed lines + */ + send: function(line) { + if(line != "") { + this.buffer.push(line); + } + }, + + /* + Looks for %PRAGMAS + */ + render_pragmas: function(template) { + // no pragmas + if(!this.includes("%", template)) { + return template; + } + + var that = this; + var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + + this.ctag); + return template.replace(regex, function(match, pragma, options) { + if(!that.pragmas_implemented[pragma]) { + throw({message: + "This implementation of mustache doesn't understand the '" + + pragma + "' pragma"}); + } + that.pragmas[pragma] = {}; + if(options) { + var opts = options.split("="); + that.pragmas[pragma][opts[0]] = opts[1]; + } + return ""; + // ignore unknown pragmas silently + }); + }, + + /* + Tries to find a partial in the curent scope and render it + */ + render_partial: function(name, context, partials) { + name = this.trim(name); + if(!partials || partials[name] === undefined) { + throw({message: "unknown_partial '" + name + "'"}); + } + if(typeof(context[name]) != "object") { + return this.render(partials[name], context, partials, true); + } + return this.render(partials[name], context[name], partials, true); + }, + + /* + Renders inverted (^) and normal (#) sections + */ + render_section: function(template, context, partials) { + if(!this.includes("#", template) && !this.includes("^", template)) { + return template; + } + + var that = this; + // CSW - Added "+?" so it finds the tighest bound, not the widest + var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag + + "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag + + "\\s*", "mg"); + + // for each {{#foo}}{{/foo}} section do... + return template.replace(regex, function(match, type, name, content) { + var value = that.find(name, context); + if(type == "^") { // inverted section + if(!value || that.is_array(value) && value.length === 0) { + // false or empty list, render it + return that.render(content, context, partials, true); + } else { + return ""; + } + } else if(type == "#") { // normal section + if(that.is_array(value)) { // Enumerable, Let's loop! + return that.map(value, function(row) { + return that.render(content, that.create_context(row), + partials, true); + }).join(""); + } else if(that.is_object(value)) { // Object, Use it as subcontext! + return that.render(content, that.create_context(value), + partials, true); + } else if(typeof value === "function") { + // higher order section + return value.call(context, content, function(text) { + return that.render(text, context, partials, true); + }); + } else if(value) { // boolean section + return that.render(content, context, partials, true); + } else { + return ""; + } + } + }); + }, + + /* + Replace {{foo}} and friends with values from our view + */ + render_tags: function(template, context, partials, in_recursion) { + // tit for tat + var that = this; + + var new_regex = function() { + return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + + that.ctag + "+", "g"); + }; + + var regex = new_regex(); + var tag_replace_callback = function(match, operator, name) { + switch(operator) { + case "!": // ignore comments + return ""; + case "=": // set new delimiters, rebuild the replace regexp + that.set_delimiters(name); + regex = new_regex(); + return ""; + case ">": // render partial + return that.render_partial(name, context, partials); + case "{": // the triple mustache is unescaped + return that.find(name, context); + default: // escape the value + return that.escape(that.find(name, context)); + } + }; + var lines = template.split("\n"); + for(var i = 0; i < lines.length; i++) { + lines[i] = lines[i].replace(regex, tag_replace_callback, this); + if(!in_recursion) { + this.send(lines[i]); + } + } + + if(in_recursion) { + return lines.join("\n"); + } + }, + + set_delimiters: function(delimiters) { + var dels = delimiters.split(" "); + this.otag = this.escape_regex(dels[0]); + this.ctag = this.escape_regex(dels[1]); + }, + + escape_regex: function(text) { + // thank you Simon Willison + if(!arguments.callee.sRE) { + var specials = [ + '/', '.', '*', '+', '?', '|', + '(', ')', '[', ']', '{', '}', '\\' + ]; + arguments.callee.sRE = new RegExp( + '(\\' + specials.join('|\\') + ')', 'g' + ); + } + return text.replace(arguments.callee.sRE, '\\$1'); + }, + + /* + find `name` in current `context`. That is find me a value + from the view object + */ + find: function(name, context) { + name = this.trim(name); + + // Checks whether a value is thruthy or false or 0 + function is_kinda_truthy(bool) { + return bool === false || bool === 0 || bool; + } + + var value; + if(is_kinda_truthy(context[name])) { + value = context[name]; + } else if(is_kinda_truthy(this.context[name])) { + value = this.context[name]; + } + + if(typeof value === "function") { + return value.apply(context); + } + if(value !== undefined) { + return value; + } + // silently ignore unkown variables + return ""; + }, + + // Utility methods + + /* includes tag */ + includes: function(needle, haystack) { + return haystack.indexOf(this.otag + needle) != -1; + }, + + /* + Does away with nasty characters + */ + escape: function(s) { + s = String(s === null ? "" : s); + return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) { + switch(s) { + case "&": return "&"; + case "\\": return "\\\\"; + case '"': return '"'; + case "'": return '''; + case "<": return "<"; + case ">": return ">"; + default: return s; + } + }); + }, + + // by @langalex, support for arrays of strings + create_context: function(_context) { + if(this.is_object(_context)) { + return _context; + } else { + var iterator = "."; + if(this.pragmas["IMPLICIT-ITERATOR"]) { + iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator; + } + var ctx = {}; + ctx[iterator] = _context; + return ctx; + } + }, + + is_object: function(a) { + return a && typeof a == "object"; + }, + + is_array: function(a) { + return Object.prototype.toString.call(a) === '[object Array]'; + }, + + /* + Gets rid of leading and trailing whitespace + */ + trim: function(s) { + return s.replace(/^\s*|\s*$/g, ""); + }, + + /* + Why, why, why? Because IE. Cry, cry cry. + */ + map: function(array, fn) { + if (typeof array.map == "function") { + return array.map(fn); + } else { + var r = []; + var l = array.length; + for(var i = 0; i < l; i++) { + r.push(fn(array[i])); + } + return r; + } + } + }; + + return({ + name: "mustache.js", + version: "0.3.1-dev", + + /* + Turns a template and view into HTML + */ + to_html: function(template, view, partials, send_fun) { + var renderer = new Renderer(); + if(send_fun) { + renderer.send = send_fun; + } + renderer.render(template, view, partials); + if(!send_fun) { + return renderer.buffer.join("\n"); + } + } + }); +}(); diff --git a/app/javascripts/jquery/skins/compact/icons.png b/app/javascripts/jquery/skins/compact/icons.png new file mode 100644 index 000000000..c6eb463f1 Binary files /dev/null and b/app/javascripts/jquery/skins/compact/icons.png differ diff --git a/app/javascripts/jquery/skins/compact/skin.css b/app/javascripts/jquery/skins/compact/skin.css new file mode 100644 index 000000000..4a6a0c6cc --- /dev/null +++ b/app/javascripts/jquery/skins/compact/skin.css @@ -0,0 +1,134 @@ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * screen.css + * main stylesheet for the WYMeditor skin + * See the documentation for more info. + * + * File Authors: + * Daniel Reszka (d.reszka a-t wymeditor dotorg) + * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg) +*/ + +/*TRYING TO RESET STYLES THAT MAY INTERFERE WITH WYMEDITOR*/ + .wym_skin_compact p, .wym_skin_compact h2, .wym_skin_compact h3, + .wym_skin_compact ul, .wym_skin_compact li { background: transparent url(); margin: 0; padding: 0; border-width:0; list-style: none; } + + +/*HIDDEN BY DEFAULT*/ + .wym_skin_compact .wym_area_left { display: none; } + .wym_skin_compact .wym_area_right { display: none; } + + +/*TYPO*/ + .wym_skin_compact { font-size: 10px; font-family: Verdana, Arial, sans-serif; } + .wym_skin_compact h2 { font-size: 110%; /* = 11px */} + .wym_skin_compact h3 { font-size: 100%; /* = 10px */} + .wym_skin_compact li { font-size: 100%; /* = 10px */} + + +/*WYM_BOX*/ + .wym_skin_compact { border: 1px solid gray; padding: 5px} + + /*auto-clear the wym_box*/ + .wym_skin_compact:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + * html .wym_skin_compact { height: 1%;} + + +/*WYM_HTML*/ + .wym_skin_compact .wym_html { width: 98%;} + .wym_skin_compact .wym_html textarea { font-size: 120%; width: 100%; height: 200px; border: 1px solid gray; background: white; } + + +/*WYM_IFRAME*/ + .wym_skin_compact .wym_iframe { width: 98%;} + .wym_skin_compact .wym_iframe iframe { width: 100%; height: 200px; border: 1px solid gray; background: white } + + +/*AREAS*/ + .wym_skin_compact .wym_area_left { width: 100px; float: left;} + .wym_skin_compact .wym_area_right { width: 150px; float: right;} + .wym_skin_compact .wym_area_bottom { height: 1%; clear: both;} + * html .wym_skin_compact .wym_area_main { height: 1%;} + * html .wym_skin_compact .wym_area_top { height: 1%;} + *+html .wym_skin_compact .wym_area_top { height: 1%;} + +/*SECTIONS SYSTEM*/ + + /*common defaults for all sections*/ + .wym_skin_compact .wym_section { margin-bottom: 5px; } + .wym_skin_compact .wym_section h2, + .wym_skin_compact .wym_section h3 { padding: 1px 3px; margin: 0; } + .wym_skin_compact .wym_section a { padding: 0 3px; display: block; text-decoration: none; color: black; } + .wym_skin_compact .wym_section a:hover { background-color: yellow; } + /*hide section titles by default*/ + .wym_skin_compact .wym_section h2 { display: none; } + /*disable any margin-collapse*/ + .wym_skin_compact .wym_section { padding-top: 1px; padding-bottom: 1px; } + /*auto-clear sections*/ + .wym_skin_compact .wym_section ul:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + * html .wym_skin_compact .wym_section ul { height: 1%;} + + /*option: add this class to a section to make it render as a panel*/ + .wym_skin_compact .wym_panel { } + .wym_skin_compact .wym_panel h2 { display: block; } + + /*option: add this class to a section to make it render as a dropdown menu*/ + .wym_skin_compact .wym_dropdown h2 { display: block; } + .wym_skin_compact .wym_dropdown ul { display: none; position: absolute; background: white; } + .wym_skin_compact .wym_dropdown:hover ul, + .wym_skin_compact .wym_dropdown.hover ul { display: block; } + + /*option: add this class to a section to make its elements render buttons (icons are only available for the wym_tools section for now)*/ + .wym_skin_compact .wym_buttons li { float:left;} + .wym_skin_compact .wym_buttons a { width: 20px; height: 20px; overflow: hidden; padding: 2px } + /*image replacements*/ + .wym_skin_compact .wym_buttons li a { background: url(icons.png) no-repeat; text-indent: -9999px;} + .wym_skin_compact .wym_buttons li.wym_tools_strong a { background-position: 0 -382px;} + .wym_skin_compact .wym_buttons li.wym_tools_emphasis a { background-position: 0 -22px;} + .wym_skin_compact .wym_buttons li.wym_tools_superscript a { background-position: 0 -430px;} + .wym_skin_compact .wym_buttons li.wym_tools_subscript a { background-position: 0 -454px;} + .wym_skin_compact .wym_buttons li.wym_tools_ordered_list a { background-position: 0 -48px;} + .wym_skin_compact .wym_buttons li.wym_tools_unordered_list a{ background-position: 0 -72px;} + .wym_skin_compact .wym_buttons li.wym_tools_indent a { background-position: 0 -574px;} + .wym_skin_compact .wym_buttons li.wym_tools_outdent a { background-position: 0 -598px;} + .wym_skin_compact .wym_buttons li.wym_tools_undo a { background-position: 0 -502px;} + .wym_skin_compact .wym_buttons li.wym_tools_redo a { background-position: 0 -526px;} + .wym_skin_compact .wym_buttons li.wym_tools_link a { background-position: 0 -96px;} + .wym_skin_compact .wym_buttons li.wym_tools_unlink a { background-position: 0 -168px;} + .wym_skin_compact .wym_buttons li.wym_tools_image a { background-position: 0 -121px;} + .wym_skin_compact .wym_buttons li.wym_tools_table a { background-position: 0 -144px;} + .wym_skin_compact .wym_buttons li.wym_tools_paste a { background-position: 0 -552px;} + .wym_skin_compact .wym_buttons li.wym_tools_html a { background-position: 0 -193px;} + .wym_skin_compact .wym_buttons li.wym_tools_preview a { background-position: 0 -408px;} + +/*DECORATION*/ + .wym_skin_compact .wym_section h2 { background: #f0f0f0; border: solid gray; border-width: 0 0 1px;} + .wym_skin_compact .wym_section h2 span { color: gray;} + .wym_skin_compact .wym_panel { padding: 0; border: solid gray; border-width: 1px; background: white;} + .wym_skin_compact .wym_panel ul { margin: 2px 0 5px; } + .wym_skin_compact .wym_dropdown { padding: 0; border: solid gray; border-width: 1px 1px 0 1px; } + .wym_skin_compact .wym_dropdown ul { border: solid gray; border-width: 0 1px 1px 1px; margin-left: -1px; padding: 5px 10px 5px 3px;} + +/*DIALOGS*/ + .wym_dialog div.row { margin-bottom: 5px;} + .wym_dialog div.row input { margin-right: 5px;} + .wym_dialog div.row label { float: left; width: 150px; display: block; text-align: right; margin-right: 10px; } + .wym_dialog div.row-indent { padding-left: 160px; } + /*autoclearing*/ + .wym_dialog div.row:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + .wym_dialog div.row { display: inline-block; } + /* Hides from IE-mac \*/ + * html .wym_dialog div.row { height: 1%; } + .wym_dialog div.row { display: block; } + /* End hide from IE-mac */ + +/*WYMEDITOR_LINK*/ + a.wym_wymeditor_link { text-indent: -9999px; float: right; display: block; width: 50px; height: 15px; background: url(../wymeditor_icon.png); overflow: hidden; text-decoration: none; } diff --git a/app/javascripts/jquery/skins/compact/skin.js b/app/javascripts/jquery/skins/compact/skin.js new file mode 100644 index 000000000..cfb7cc15b --- /dev/null +++ b/app/javascripts/jquery/skins/compact/skin.js @@ -0,0 +1,35 @@ +WYMeditor.SKINS['compact'] = { + + init: function(wym) { + + //move the containers panel to the top area + jQuery(wym._options.containersSelector + ', ' + + wym._options.classesSelector, wym._box) + .appendTo( jQuery("div.wym_area_top", wym._box) ) + .addClass("wym_dropdown") + .css({"margin-right": "10px", "width": "120px", "float": "left"}); + + //render following sections as buttons + jQuery(wym._options.toolsSelector, wym._box) + .addClass("wym_buttons") + .css({"margin-right": "10px", "float": "left"}); + + //make hover work under IE < 7 + jQuery(".wym_section", wym._box).hover(function(){ + jQuery(this).addClass("hover"); + },function(){ + jQuery(this).removeClass("hover"); + }); + + var postInit = wym._options.postInit; + wym._options.postInit = function(wym) { + + if(postInit) postInit.call(wym, wym); + var rule = { + name: 'body', + css: 'background-color: #f0f0f0;' + }; + wym.addCssRule( wym._doc.styleSheets[0], rule); + }; + } +}; diff --git a/app/javascripts/jquery/skins/default/icons.png b/app/javascripts/jquery/skins/default/icons.png new file mode 100644 index 000000000..c6eb463f1 Binary files /dev/null and b/app/javascripts/jquery/skins/default/icons.png differ diff --git a/app/javascripts/jquery/skins/default/skin.css b/app/javascripts/jquery/skins/default/skin.css new file mode 100644 index 000000000..01624c700 --- /dev/null +++ b/app/javascripts/jquery/skins/default/skin.css @@ -0,0 +1,133 @@ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * skin.css + * main stylesheet for the default WYMeditor skin + * See the documentation for more info. + * + * File Authors: + * Daniel Reszka (d.reszka a-t wymeditor dotorg) +*/ + +/*TRYING TO RESET STYLES THAT MAY INTERFERE WITH WYMEDITOR*/ + .wym_skin_default p, .wym_skin_default h2, .wym_skin_default h3, + .wym_skin_default ul, .wym_skin_default li { background: transparent url(); margin: 0; padding: 0; border-width:0; list-style: none; } + + +/*HIDDEN BY DEFAULT*/ + .wym_skin_default .wym_area_left { display: none; } + .wym_skin_default .wym_area_right { display: block; } + + +/*TYPO*/ + .wym_skin_default { font-size: 62.5%; font-family: Verdana, Arial, sans-serif; } + .wym_skin_default h2 { font-size: 110%; /* = 11px */} + .wym_skin_default h3 { font-size: 100%; /* = 10px */} + .wym_skin_default li { font-size: 100%; /* = 10px */} + + +/*WYM_BOX*/ + .wym_skin_default { border: 1px solid gray; background: #f2f2f2; padding: 5px; border-radius: 5px; } + + /*auto-clear the wym_box*/ + .wym_skin_default:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + * html .wym_skin_default { height: 1%;} + + +/*WYM_HTML*/ + .wym_skin_default .wym_html { width: 98%;} + .wym_skin_default .wym_html textarea { width: 100%; height: 250px; border: 1px solid gray; background: white; } + + +/*WYM_IFRAME*/ + .wym_skin_default .wym_iframe { width: 98%;} + .wym_skin_default .wym_iframe iframe { width: 100%; height: 250px; border: 1px solid gray; background: #eaeaea; margin-top: -13px; } + + +/*AREAS*/ + .wym_skin_default .wym_area_left { width: 150px; float: left;} + .wym_skin_default .wym_area_right { width: 150px; float: right;} + .wym_skin_default .wym_area_bottom { height: 1%; clear: both;} + * html .wym_skin_default .wym_area_main { height: 1%;} + * html .wym_skin_default .wym_area_top { height: 1%;} + *+html .wym_skin_default .wym_area_top { height: 1%;} + +/*SECTIONS SYSTEM*/ + + /*common defaults for all sections*/ + .wym_skin_default .wym_section { margin-bottom: 5px; } + .wym_skin_default .wym_section h2, + .wym_skin_default .wym_section h3 { padding: 1px 3px; margin: 0; } + .wym_skin_default .wym_section a { padding: 0 3px; display: block; text-decoration: none; color: black; } + .wym_skin_default .wym_section a:hover { background-color: yellow; } + /*hide section titles by default*/ + .wym_skin_default .wym_section h2 { display: none; } + /*disable any margin-collapse*/ + .wym_skin_default .wym_section { padding-top: 1px; padding-bottom: 1px; } + /*auto-clear sections*/ + .wym_skin_default .wym_section ul:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + * html .wym_skin_default .wym_section ul { height: 1%;} + + /*option: add this class to a section to make it render as a panel*/ + .wym_skin_default .wym_panel { } + .wym_skin_default .wym_panel h2 { display: block; } + + /*option: add this class to a section to make it render as a dropdown menu*/ + .wym_skin_default .wym_dropdown h2 { display: block; } + .wym_skin_default .wym_dropdown ul { display: none; position: absolute; background: white; } + .wym_skin_default .wym_dropdown:hover ul, + .wym_skin_default .wym_dropdown.hover ul { display: block; } + + /*option: add this class to a section to make its elements render buttons (icons are only available for the wym_tools section for now)*/ + .wym_skin_default .wym_buttons li { float:left;} + .wym_skin_default .wym_buttons a { width: 20px; height: 20px; overflow: hidden; padding: 2px } + /*image replacements*/ + .wym_skin_default .wym_buttons li a { background: url(icons.png) no-repeat; text-indent: -9999px;} + .wym_skin_default .wym_buttons li.wym_tools_strong a { background-position: 0 -382px;} + .wym_skin_default .wym_buttons li.wym_tools_emphasis a { background-position: 0 -22px;} + .wym_skin_default .wym_buttons li.wym_tools_superscript a { background-position: 0 -430px;} + .wym_skin_default .wym_buttons li.wym_tools_subscript a { background-position: 0 -454px;} + .wym_skin_default .wym_buttons li.wym_tools_ordered_list a { background-position: 0 -48px;} + .wym_skin_default .wym_buttons li.wym_tools_unordered_list a{ background-position: 0 -72px;} + .wym_skin_default .wym_buttons li.wym_tools_indent a { background-position: 0 -574px;} + .wym_skin_default .wym_buttons li.wym_tools_outdent a { background-position: 0 -598px;} + .wym_skin_default .wym_buttons li.wym_tools_undo a { background-position: 0 -502px;} + .wym_skin_default .wym_buttons li.wym_tools_redo a { background-position: 0 -526px;} + .wym_skin_default .wym_buttons li.wym_tools_link a { background-position: 0 -96px;} + .wym_skin_default .wym_buttons li.wym_tools_unlink a { background-position: 0 -168px;} + .wym_skin_default .wym_buttons li.wym_tools_image a { background-position: 0 -121px;} + .wym_skin_default .wym_buttons li.wym_tools_table a { background-position: 0 -144px;} + .wym_skin_default .wym_buttons li.wym_tools_paste a { background-position: 0 -552px;} + .wym_skin_default .wym_buttons li.wym_tools_html a { background-position: 0 -193px;} + .wym_skin_default .wym_buttons li.wym_tools_preview a { background-position: 0 -408px;} + +/*DECORATION*/ + .wym_skin_default .wym_section h2 { background: #ddd; border: solid gray; border-width: 0 0 1px;} + .wym_skin_default .wym_section h2 span { color: gray;} + .wym_skin_default .wym_panel { padding: 0; border: solid gray; border-width: 1px; background: white;} + .wym_skin_default .wym_panel ul { margin: 2px 0 5px; } + .wym_skin_default .wym_dropdown { padding: 0; border: solid gray; border-width: 1px 1px 0 1px; } + .wym_skin_default .wym_dropdown ul { border: solid gray; border-width: 0 1px 1px 1px; margin-left: -1px; padding: 5px 10px 5px 3px;} + +/*DIALOGS*/ + .wym_dialog div.row { margin-bottom: 5px;} + .wym_dialog div.row input { margin-right: 5px;} + .wym_dialog div.row label { float: left; width: 150px; display: block; text-align: right; margin-right: 10px; } + .wym_dialog div.row-indent { padding-left: 160px; } + /*autoclearing*/ + .wym_dialog div.row:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + .wym_dialog div.row { display: inline-block; } + /* Hides from IE-mac \*/ + * html .wym_dialog div.row { height: 1%; } + .wym_dialog div.row { display: block; } + /* End hide from IE-mac */ + +/*WYMEDITOR_LINK*/ + a.wym_wymeditor_link { text-indent: -9999px; float: right; display: block; width: 50px; height: 15px; background: url(../wymeditor_icon.png); overflow: hidden; text-decoration: none; } diff --git a/app/javascripts/jquery/skins/default/skin.js b/app/javascripts/jquery/skins/default/skin.js new file mode 100644 index 000000000..5f6d97e00 --- /dev/null +++ b/app/javascripts/jquery/skins/default/skin.js @@ -0,0 +1,40 @@ +WYMeditor.SKINS['default'] = { + + init: function(wym) { + + //render following sections as panels + jQuery(wym._box).find(wym._options.classesSelector) + .addClass("wym_panel"); + + //render following sections as buttons + jQuery(wym._box).find(wym._options.toolsSelector) + .addClass("wym_buttons"); + + //render following sections as dropdown menus + jQuery(wym._box).find(wym._options.containersSelector) + .addClass("wym_dropdown") + .find(WYMeditor.H2) + .append(" >"); + + // auto add some margin to the main area sides if left area + // or right area are not empty (if they contain sections) + jQuery(wym._box).find("div.wym_area_right ul") + .parents("div.wym_area_right").show() + .parents(wym._options.boxSelector) + .find("div.wym_area_main") + .css({"margin-right": "155px"}); + + jQuery(wym._box).find("div.wym_area_left ul") + .parents("div.wym_area_left").show() + .parents(wym._options.boxSelector) + .find("div.wym_area_main") + .css({"margin-left": "155px"}); + + //make hover work under IE < 7 + jQuery(wym._box).find(".wym_section").hover(function(){ + jQuery(this).addClass("hover"); + },function(){ + jQuery(this).removeClass("hover"); + }); + } +}; diff --git a/app/javascripts/jquery/skins/minimal/images/bg.header.gif b/app/javascripts/jquery/skins/minimal/images/bg.header.gif new file mode 100644 index 000000000..b2d2907bd Binary files /dev/null and b/app/javascripts/jquery/skins/minimal/images/bg.header.gif differ diff --git a/app/javascripts/jquery/skins/minimal/images/bg.selector.silver.gif b/app/javascripts/jquery/skins/minimal/images/bg.selector.silver.gif new file mode 100644 index 000000000..e65976bdd Binary files /dev/null and b/app/javascripts/jquery/skins/minimal/images/bg.selector.silver.gif differ diff --git a/app/javascripts/jquery/skins/minimal/images/bg.wymeditor.png b/app/javascripts/jquery/skins/minimal/images/bg.wymeditor.png new file mode 100644 index 000000000..1e84813f7 Binary files /dev/null and b/app/javascripts/jquery/skins/minimal/images/bg.wymeditor.png differ diff --git a/app/javascripts/jquery/skins/minimal/images/icons.silver.gif b/app/javascripts/jquery/skins/minimal/images/icons.silver.gif new file mode 100644 index 000000000..8c6a4fbfb Binary files /dev/null and b/app/javascripts/jquery/skins/minimal/images/icons.silver.gif differ diff --git a/app/javascripts/jquery/skins/minimal/skin.css b/app/javascripts/jquery/skins/minimal/skin.css new file mode 100644 index 000000000..cea8d842c --- /dev/null +++ b/app/javascripts/jquery/skins/minimal/skin.css @@ -0,0 +1,131 @@ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * skin.css + * main stylesheet for the minimal WYMeditor skin + * See the documentation for more info. + * + * File Authors: + * Jean-Francois Hovinne + * Scott Lewis (see Silver skin) +*/ + +/* Set iframe */ +.wym_skin_minimal div.wym_iframe iframe { + width: 90%; + height: 200px; +} + +/* Hide h2 by default */ +.wym_skin_minimal h2 { + display: none; +} + +/* Show specific h2 */ +.wym_skin_minimal div.wym_tools h2, +.wym_skin_minimal div.wym_containers h2, +.wym_skin_minimal div.wym_classes h2 { + display: block; +} + +.wym_skin_minimal div.wym_section ul { + margin: 0; +} + +.wym_skin_minimal div.wym_section ul li { + float: left; + list-style-type: none; + margin-right: 5px; +} + +.wym_skin_minimal div.wym_area_top, +.wym_skin_minimal div.wym_area_right, +.wym_skin_minimal div.wym_containers, +.wym_skin_minimal div.wym_classes { + float: left; +} + +.wym_skin_minimal div.wym_area_main { + clear: both; +} + +.wym_skin_minimal div.wym_html { + width: 90%; +} + +.wym_skin_minimal textarea.wym_html_val { + width: 100%; + height: 100px; +} + +/* DROPDOWNS (see Silver skin) */ +.wym_skin_minimal div.wym_dropdown { + cursor: pointer; + margin: 0px 4px 10px 0px; + padding: 0px; + z-index: 1001; + display: block; +} + +.wym_skin_minimal div.wym_dropdown ul { + display: none; + width: 124px; + padding: 0px; + margin: 0px; + list-style-type: none; + list-style-image: none; + z-index: 1002; + position: absolute; + border-top: 1px solid #AAA; +} + +.wym_skin_minimal div.wym_dropdown ul li { + width: 146px; + height: 20px; + padding: 0px; + margin: 0px; + border: 1px solid #777; + border-top: none; + background: #EEE; + list-style-image: none; +} + +.wym_skin_minimal div.wym_dropdown h2 { + width: 138px; + height: 16px; + color: #000; + background-image: url(images/bg.selector.silver.gif); + background-position: 0px -18px; + background-repeat: no-repeat; + border: none; + font-family: "Trebuchet MS", Verdana, Arial, Helvetica, Sanserif; + font-size: 12px; + font-weight: bold; + padding: 2px 0px 0px 10px; + margin: 0px; +} + +.wym_skin_minimal div.wym_dropdown a { + text-decoration: none; + font-family: "Trebuchet MS", Verdana, Arial, Helvetica, Sanserif; + font-size: 12px; + padding: 5px 0px 0px 10px; + display: block; + width: 136px; + height: 15px; + color: #000; + text-align: left; + margin-left: 0px; +} + +.wym_skin_minimal div.wym_dropdown a:hover { + background: #BBB; + border-bottom: none; +} diff --git a/app/javascripts/jquery/skins/minimal/skin.js b/app/javascripts/jquery/skins/minimal/skin.js new file mode 100644 index 000000000..af29ed425 --- /dev/null +++ b/app/javascripts/jquery/skins/minimal/skin.js @@ -0,0 +1,30 @@ +jQuery.fn.selectify = function() { + return this.each(function() { + jQuery(this).hover( + function() { + jQuery("h2", this).css("background-position", "0px -18px"); + jQuery("ul", this).fadeIn("fast"); + }, + function() { + jQuery("h2", this).css("background-position", ""); + jQuery("ul", this).fadeOut("fast"); + } + ); + }); +}; + +WYMeditor.SKINS['minimal'] = { + //placeholder for the skin JS, if needed + + //init the skin + //wym is the WYMeditor.editor instance + init: function(wym) { + + //render following sections as dropdown menus + jQuery(wym._box).find(wym._options.toolsSelector + ', ' + wym._options.containersSelector + ', ' + wym._options.classesSelector) + .addClass("wym_dropdown") + .selectify(); + + + } +}; diff --git a/app/javascripts/jquery/skins/silver/COPYING b/app/javascripts/jquery/skins/silver/COPYING new file mode 100755 index 000000000..94a9ed024 --- /dev/null +++ b/app/javascripts/jquery/skins/silver/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/app/javascripts/jquery/skins/silver/README b/app/javascripts/jquery/skins/silver/README new file mode 100755 index 000000000..130dc46e0 --- /dev/null +++ b/app/javascripts/jquery/skins/silver/README @@ -0,0 +1,27 @@ +/** +* @version Alpha 0.1 2008-05-10 23:28:43 $ +* @package Silver skin for WYMeditor +* @copyright Copyright (C) 2008 Scott Edwin Lewis. All rights reserved. +* @license GNU/GPL, see COPYING +* Silver skin for WYMeditor is free software and is licensed under the +* GNU General Public License. See COPYING for copyright notices and details. +*/ + +Adds custom buttons and color palette to the WYMeditor XHTML Editor. + +INSTALLATION: + +1. Copy the entire /silver/ directory to /wymeditor/skins/ +2. Initialize the WYMeditor 'skin' option as below: + + + +That's it. You're done. diff --git a/app/javascripts/jquery/skins/silver/images/bg.header.gif b/app/javascripts/jquery/skins/silver/images/bg.header.gif new file mode 100644 index 000000000..b2d2907bd Binary files /dev/null and b/app/javascripts/jquery/skins/silver/images/bg.header.gif differ diff --git a/app/javascripts/jquery/skins/silver/images/bg.selector.silver.gif b/app/javascripts/jquery/skins/silver/images/bg.selector.silver.gif new file mode 100644 index 000000000..e65976bdd Binary files /dev/null and b/app/javascripts/jquery/skins/silver/images/bg.selector.silver.gif differ diff --git a/app/javascripts/jquery/skins/silver/images/bg.wymeditor.png b/app/javascripts/jquery/skins/silver/images/bg.wymeditor.png new file mode 100644 index 000000000..1e84813f7 Binary files /dev/null and b/app/javascripts/jquery/skins/silver/images/bg.wymeditor.png differ diff --git a/app/javascripts/jquery/skins/silver/images/icons.silver.gif b/app/javascripts/jquery/skins/silver/images/icons.silver.gif new file mode 100644 index 000000000..8c6a4fbfb Binary files /dev/null and b/app/javascripts/jquery/skins/silver/images/icons.silver.gif differ diff --git a/app/javascripts/jquery/skins/silver/skin.css b/app/javascripts/jquery/skins/silver/skin.css new file mode 100644 index 000000000..8284d81ba --- /dev/null +++ b/app/javascripts/jquery/skins/silver/skin.css @@ -0,0 +1,297 @@ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * screen.css + * main stylesheet for the default WYMeditor skin + * See the documentation for more info. + * + * File Authors: + * Daniel Reszka (d.reszka a-t wymeditor dotorg) + * Scott Edwin Lewis +*/ + +/*TRYING TO RESET STYLES THAT MAY INTERFERE WITH WYMEDITOR*/ + .wym_skin_silver p, .wym_skin_silver h2, .wym_skin_silver h3, + .wym_skin_silver ul, .wym_skin_silver li { background: transparent url(); margin: 0; padding: 0; border-width:0; list-style: none; } + + +/*HIDDEN BY DEFAULT*/ + .wym_skin_silver .wym_area_left { display: none; } + .wym_skin_silver .wym_area_right { display: block; } + + +/*TYPO*/ + .wym_skin_silver { font-size: 62.5%; font-family: Verdana, Arial, sans-serif; } + .wym_skin_silver h2 { font-size: 110%; /* = 11px */} + .wym_skin_silver h3 { font-size: 100%; /* = 10px */} + .wym_skin_silver li { font-size: 100%; /* = 10px */} + + +/*WYM_BOX*/ + .wym_skin_silver { border: 1px solid gray; background: #f2f2f2; padding: 0px; margin: 0px;} + + /*auto-clear the wym_box*/ + .wym_skin_silver:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + * html .wym_skin_silver { height: 1%;} + + +/*WYM_HTML*/ + .wym_skin_silver .wym_html { width: 98%;} + .wym_skin_silver .wym_html textarea { width: 100%; height: 200px; border: 1px solid gray; background: white; } + + +/*WYM_IFRAME*/ + .wym_skin_silver .wym_iframe { width: 98%;} + .wym_skin_silver .wym_iframe iframe { width: 100%; height: 200px; border: 1px solid gray; background: white } + + +/*AREAS*/ + .wym_skin_silver .wym_area_left { width: 150px; float: left;} + .wym_skin_silver .wym_area_right { width: 150px; float: right;} + .wym_skin_silver .wym_area_bottom { height: 1%; clear: both;} + * html .wym_skin_silver .wym_area_main { height: 1%;} + * html .wym_skin_silver .wym_area_top { height: 1%;} + *+html .wym_skin_silver .wym_area_top { height: 1%;} + +/*SECTIONS SYSTEM*/ + + /*common defaults for all sections*/ + .wym_skin_silver .wym_section { margin-bottom: 5px; } + .wym_skin_silver .wym_section h2, + .wym_skin_silver .wym_section h3 { padding: 1px 3px; margin: 0; cursor: pointer; } + .wym_skin_silver .wym_section a { padding: 5px 0px 0px 10px; display: block; text-decoration: none; color: black; } + .wym_skin_silver .wym_section a:hover { /*background-color: #DDD;*/} + /*hide section titles by default*/ + .wym_skin_silver .wym_section h2 { display: none; } + /*disable any margin-collapse*/ + .wym_skin_silver .wym_section { padding-top: 1px; padding-bottom: 1px; } + /*auto-clear sections*/ + .wym_skin_silver .wym_section ul:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; padding: 0px; } + * html .wym_skin_silver .wym_section ul { height: 1%;} + .wym_skin_silver .wym_section li {} + + /*option: add this class to a section to make it render as a panel*/ + .wym_skin_silver .wym_panel { } + .wym_skin_silver .wym_panel h2 { display: block; font-size: 11px; } + + /*option: add this class to a section to make it render as a dropdown menu*/ + .wym_skin_silver .wym_dropdown h2 { display: block; font-size: 11px;} + .wym_skin_silver .wym_dropdown ul { position: absolute; background: white; padding: 0px;} + .wym_skin_silver .wym_dropdown:hover ul, + .wym_skin_silver .wym_dropdown.hover ul { cursor: pointer;} + .wym_skin_silver .wym_dropdown ul li a {/*border-bottom: 1px solid #AAA;*/} + + /*option: add this class to a section to make its elements render buttons (icons are only available for the wym_tools section for now)*/ + .wym_skin_silver .wym_buttons li { float:left;} + .wym_skin_silver .wym_buttons a { width: 20px; height: 20px; overflow: hidden; padding: 2px; text-decoration: none !important; border: 1px solid #666; } + .wym_skin_silver .wym_buttons a:hover { text-decoration: none !important; border: 1px solid #000;} + /*image replacements*/ + .wym_skin_silver .wym_buttons li a { background: url(images/icons.silver.gif) no-repeat; text-indent: -9999px;} + .wym_skin_silver .wym_buttons li.wym_tools_strong a { background-position: 0 -384px;} + .wym_skin_silver .wym_buttons li.wym_tools_emphasis a { background-position: 0 -24px;} + .wym_skin_silver .wym_buttons li.wym_tools_superscript a { background-position: 0 -432px;} + .wym_skin_silver .wym_buttons li.wym_tools_subscript a { background-position: 0 -456px;} + .wym_skin_silver .wym_buttons li.wym_tools_ordered_list a { background-position: 0 -48px;} + .wym_skin_silver .wym_buttons li.wym_tools_unordered_list a{ background-position: 0 -72px;} + .wym_skin_silver .wym_buttons li.wym_tools_indent a { background-position: 0 -600px;} + .wym_skin_silver .wym_buttons li.wym_tools_outdent a { background-position: 0 -624px;} + .wym_skin_silver .wym_buttons li.wym_tools_undo a { background-position: 0 -504px;} + .wym_skin_silver .wym_buttons li.wym_tools_redo a { background-position: 0 -528px;} + .wym_skin_silver .wym_buttons li.wym_tools_link a { background-position: 0 -96px;} + .wym_skin_silver .wym_buttons li.wym_tools_unlink a { background-position: 0 -168px;} + .wym_skin_silver .wym_buttons li.wym_tools_image a { background-position: 0 -120px;} + .wym_skin_silver .wym_buttons li.wym_tools_table a { background-position: 0 -144px;} + .wym_skin_silver .wym_buttons li.wym_tools_paste a { background-position: 0 -552px;} + .wym_skin_silver .wym_buttons li.wym_tools_html a { background-position: 0 -192px;} + .wym_skin_silver .wym_buttons li.wym_tools_preview a { background-position: 0 -408px;} + .wym_skin_silver .wym_buttons li.wym_tools_gadget a { background-position: 0 -576px;} + + .wym_skin_silver .wym_buttons li.wym_tools_strong a:hover { background-position: -24px -384px;} + .wym_skin_silver .wym_buttons li.wym_tools_emphasis a:hover { background-position: -24px -24px;} + .wym_skin_silver .wym_buttons li.wym_tools_superscript a:hover { background-position: -24px -432px;} + .wym_skin_silver .wym_buttons li.wym_tools_subscript a:hover { background-position: -24px -456px;} + .wym_skin_silver .wym_buttons li.wym_tools_ordered_list a:hover { background-position: -24px -48px;} + .wym_skin_silver .wym_buttons li.wym_tools_unordered_list a:hover{ background-position: -24px -72px;} + .wym_skin_silver .wym_buttons li.wym_tools_indent a:hover { background-position: -24px -600px;} + .wym_skin_silver .wym_buttons li.wym_tools_outdent a:hover { background-position: -24px -624px;} + .wym_skin_silver .wym_buttons li.wym_tools_undo a:hover { background-position: -24px -504px;} + .wym_skin_silver .wym_buttons li.wym_tools_redo a:hover { background-position: -24px -528px;} + .wym_skin_silver .wym_buttons li.wym_tools_link a:hover { background-position: -24px -96px;} + .wym_skin_silver .wym_buttons li.wym_tools_unlink a:hover { background-position: -24px -168px;} + .wym_skin_silver .wym_buttons li.wym_tools_image a:hover { background-position: -24px -120px;} + .wym_skin_silver .wym_buttons li.wym_tools_table a:hover { background-position: -24px -144px;} + .wym_skin_silver .wym_buttons li.wym_tools_paste a:hover { background-position: -24px -552px;} + .wym_skin_silver .wym_buttons li.wym_tools_html a:hover { background-position: -24px -192px;} + .wym_skin_silver .wym_buttons li.wym_tools_preview a:hover { background-position: -24px -408px;} + .wym_skin_silver .wym_buttons li.wym_tools_gadget a:hover { background-position: -24px -576px;} + +/*DECORATION*/ + .wym_skin_silver .wym_section h2 { background: #ddd; border: none;} + .wym_skin_silver .wym_section h2 span { color: gray;} + .wym_skin_silver .wym_panel { padding: 0; border: solid gray; border-width: 0px;} + .wym_skin_silver .wym_panel ul { margin: 2px 0 5px; } + .wym_skin_silver .wym_dropdown { padding: 0; border: none; } + .wym_skin_silver .wym_dropdown ul { border: none; margin-left: -1px; padding: 0px;} + +/*DIALOGS*/ + .wym_dialog div.row { margin-bottom: 5px;} + .wym_dialog div.row input { margin-right: 5px;} + .wym_dialog div.row label { float: left; width: 150px; display: block; text-align: right; margin-right: 10px; } + .wym_dialog div.row-indent { padding-left: 160px; } + /*autoclearing*/ + .wym_dialog div.row:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + .wym_dialog div.row { display: inline-block; } + /* Hides from IE-mac \*/ + * html .wym_dialog div.row { height: 1%; } + .wym_dialog div.row { display: block; } + /* End hide from IE-mac */ + +/*WYMEDITOR_LINK*/ + a.wym_wymeditor_link + { + text-indent: -9999px; + float: right; + display: block; + width: 50px; + height: 15px; + background: url(../wymeditor_icon.png); + background-position: 1px 1px; + background-repeat: no-repeat; + overflow: hidden; + text-decoration: none; + padding: 1px !important; + border: 1px solid #333 !important; + background-color: #FFF !important; + } + +.wym_box +{ + padding: 0px !important; + margin: 0px; +} +.wym_inner +{ + border-left: 1px solid #FFF; + border-top: 1px solid #FFF; + border-right: 1px solid #FFF; + border-bottom: 1px solid #FFF; + padding: 5px; + background-color: #B8C1C4; + height: auto; +} + +.clear {clear: both;} + +div.wym_dropdown +{ + cursor: pointer; + width: 138px !important; + margin: 0px 4px 10px 0px !important; + padding: 0px; + z-index: 1001; + display: block; + border: 1px solid red; +} + +div.wym_dropdown ul +{ + display: none; + width: 124px; + padding: 0px !important; + margin: 0px !important; + list-style-type: none; + list-style-image: none; + z-index: 1002; + position: absolute; + border-top: 1px solid #AAA; +} + +div.wym_dropdown ul li +{ + width: 146px; + height: 20px; + padding: 0px !important; + margin: 0px; + border: 1px solid #777; + border-top: none; + background: #DDD; + list-style-image: none; +} + +div.wym_dropdown h2 +{ + width: 138px; + height: 16px; + color: #000 !important; + background-image: url(images/bg.selector.silver.gif) !important; + background-position: 0px -18px; + background-repeat: no-repeat; + border: none; + font-family: "Trebuchet MS", Verdana, Arial, Helvetica, Sanserif; + font-size: 12px !important; + font-weight: bold !important; + padding: 2px 0px 0px 10px !important; + margin: 0px !important; +} + +.wym_skin_silver .wym_panel h2 +{ + width: 138px; + height: 16px; + color: #000 !important; + background-image: url(images/bg.header.gif) !important; + background-position: 0px 0px; + background-repeat: no-repeat; + border: none; + font-family: "Trebuchet MS", Verdana, Arial, Helvetica, Sanserif; + font-size: 12px !important; + font-weight: bold !important; + padding: 2px 0px 0px 10px !important; + margin: 0px !important; +} + +.wym_skin_silver .wym_panel ul +{ + margin-top: 0px !important; +} + +.wym_skin_silver .wym_panel ul li +{ + width: 146px; + height: 20px; + padding: 0px !important; + margin: 0px; + border: 1px solid #777; + border-top: none; + background: #DDD; + list-style-image: none; +} + +.wym_skin_silver .wym_panel a, +div.wym_dropdown a +{ + text-decoration: none; + font-family: "Trebuchet MS", Verdana, Arial, Helvetica, Sanserif; + font-size: 12px; + padding: 5px 0px 0px 10px !important; + display: block; + width: 136px; + height: 15px; + color: #000; + text-align: left !important; + margin-left: 0px !important; +} + +div.wym_dropdown a:hover, +.wym_skin_silver .wym_panel a:hover +{ + background: #BBB; + border-bottom: none !important; +} diff --git a/app/javascripts/jquery/skins/silver/skin.js b/app/javascripts/jquery/skins/silver/skin.js new file mode 100644 index 000000000..948ed91c6 --- /dev/null +++ b/app/javascripts/jquery/skins/silver/skin.js @@ -0,0 +1,61 @@ +/* This file is part of the Silver skin for WYMeditor by Scott Edwin Lewis */ + +jQuery.fn.selectify = function() { + return this.each(function() { + jQuery(this).hover( + function() { + jQuery("h2", this).css("background-position", "0px -18px"); + jQuery("ul", this).fadeIn("fast"); + }, + function() { + jQuery("h2", this).css("background-position", ""); + jQuery("ul", this).fadeOut("fast"); + } + ); + }); +}; + +WYMeditor.SKINS['silver'] = { + + init: function(wym) { + + //add some elements to improve the rendering + jQuery(wym._box) + .append('
') + .wrapInner('
'); + + //render following sections as panels + jQuery(wym._box).find(wym._options.classesSelector) + .addClass("wym_panel"); + + //render following sections as buttons + jQuery(wym._box).find(wym._options.toolsSelector) + .addClass("wym_buttons"); + + //render following sections as dropdown menus + jQuery(wym._box).find(wym._options.containersSelector) + .addClass("wym_dropdown") + .selectify(); + + // auto add some margin to the main area sides if left area + // or right area are not empty (if they contain sections) + jQuery(wym._box).find("div.wym_area_right ul") + .parents("div.wym_area_right").show() + .parents(wym._options.boxSelector) + .find("div.wym_area_main") + .css({"margin-right": "155px"}); + + jQuery(wym._box).find("div.wym_area_left ul") + .parents("div.wym_area_left").show() + .parents(wym._options.boxSelector) + .find("div.wym_area_main") + .css({"margin-left": "155px"}); + + //make hover work under IE < 7 + jQuery(wym._box).find(".wym_section").hover(function(){ + jQuery(this).addClass("hover"); + },function(){ + jQuery(this).removeClass("hover"); + }); + } +}; diff --git a/app/javascripts/jquery/skins/twopanels/icons.png b/app/javascripts/jquery/skins/twopanels/icons.png new file mode 100644 index 000000000..c6eb463f1 Binary files /dev/null and b/app/javascripts/jquery/skins/twopanels/icons.png differ diff --git a/app/javascripts/jquery/skins/twopanels/skin.css b/app/javascripts/jquery/skins/twopanels/skin.css new file mode 100644 index 000000000..7e6b8fda2 --- /dev/null +++ b/app/javascripts/jquery/skins/twopanels/skin.css @@ -0,0 +1,134 @@ +/* + * WYMeditor : what you see is What You Mean web-based editor + * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ + * Dual licensed under the MIT (MIT-license.txt) + * and GPL (GPL-license.txt) licenses. + * + * For further information visit: + * http://www.wymeditor.org/ + * + * File Name: + * screen.css + * main stylesheet for the WYMeditor skin + * See the documentation for more info. + * + * File Authors: + * Daniel Reszka (d.reszka a-t wymeditor dotorg) + * Jean-Francois Hovinne +*/ + +/*TRYING TO RESET STYLES THAT MAY INTERFERE WITH WYMEDITOR*/ + .wym_skin_twopanels p, .wym_skin_twopanels h2, .wym_skin_twopanels h3, + .wym_skin_twopanels ul, .wym_skin_twopanels li { background: transparent url(); margin: 0; padding: 0; border-width:0; list-style: none; } + + +/*HIDDEN BY DEFAULT*/ + .wym_skin_twopanels .wym_area_left { display: block; } + .wym_skin_twopanels .wym_area_right { display: block; } + + +/*TYPO*/ + .wym_skin_twopanels { font-size: 62.5%; font-family: Verdana, Arial, sans-serif; } + .wym_skin_twopanels h2 { font-size: 110%; /* = 11px */} + .wym_skin_twopanels h3 { font-size: 100%; /* = 10px */} + .wym_skin_twopanels li { font-size: 100%; /* = 10px */} + + +/*WYM_BOX*/ + .wym_skin_twopanels { border: 1px solid gray; background: #f2f2f2; padding: 5px} + + /*auto-clear the wym_box*/ + .wym_skin_twopanels:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + * html .wym_skin_twopanels { height: 1%;} + + +/*WYM_HTML*/ + .wym_skin_twopanels .wym_html { width: 98%;} + .wym_skin_twopanels .wym_html textarea { width: 100%; height: 200px; border: 1px solid gray; background: white; } + + +/*WYM_IFRAME*/ + .wym_skin_twopanels .wym_iframe { width: 98%;} + .wym_skin_twopanels .wym_iframe iframe { width: 100%; height: 200px; border: 1px solid gray; background: white } + + +/*AREAS*/ + .wym_skin_twopanels .wym_area_left { width: 100px; float: left;} + .wym_skin_twopanels .wym_area_right { width: 150px; float: right;} + .wym_skin_twopanels .wym_area_bottom { height: 1%; clear: both;} + * html .wym_skin_twopanels .wym_area_main { height: 1%;} + * html .wym_skin_twopanels .wym_area_top { height: 1%;} + *+html .wym_skin_twopanels .wym_area_top { height: 1%;} + +/*SECTIONS SYSTEM*/ + + /*common defaults for all sections*/ + .wym_skin_twopanels .wym_section { margin-bottom: 5px; } + .wym_skin_twopanels .wym_section h2, + .wym_skin_twopanels .wym_section h3 { padding: 1px 3px; margin: 0; } + .wym_skin_twopanels .wym_section a { padding: 0 3px; display: block; text-decoration: none; color: black; } + .wym_skin_twopanels .wym_section a:hover { background-color: yellow; } + /*hide section titles by default*/ + .wym_skin_twopanels .wym_section h2 { display: none; } + /*disable any margin-collapse*/ + .wym_skin_twopanels .wym_section { padding-top: 1px; padding-bottom: 1px; } + /*auto-clear sections*/ + .wym_skin_twopanels .wym_section ul:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + * html .wym_skin_twopanels .wym_section ul { height: 1%;} + + /*option: add this class to a section to make it render as a panel*/ + .wym_skin_twopanels .wym_panel { } + .wym_skin_twopanels .wym_panel h2 { display: block; } + + /*option: add this class to a section to make it render as a dropdown menu*/ + .wym_skin_twopanels .wym_dropdown h2 { display: block; } + .wym_skin_twopanels .wym_dropdown ul { display: none; position: absolute; background: white; } + .wym_skin_twopanels .wym_dropdown:hover ul, + .wym_skin_twopanels .wym_dropdown.hover ul { display: block; } + + /*option: add this class to a section to make its elements render buttons (icons are only available for the wym_tools section for now)*/ + .wym_skin_twopanels .wym_buttons li { float:left;} + .wym_skin_twopanels .wym_buttons a { width: 20px; height: 20px; overflow: hidden; padding: 2px } + /*image replacements*/ + .wym_skin_twopanels .wym_buttons li a { background: url(icons.png) no-repeat; text-indent: -9999px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_strong a { background-position: 0 -382px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_emphasis a { background-position: 0 -22px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_superscript a { background-position: 0 -430px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_subscript a { background-position: 0 -454px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_ordered_list a { background-position: 0 -48px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_unordered_list a{ background-position: 0 -72px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_indent a { background-position: 0 -574px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_outdent a { background-position: 0 -598px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_undo a { background-position: 0 -502px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_redo a { background-position: 0 -526px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_link a { background-position: 0 -96px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_unlink a { background-position: 0 -168px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_image a { background-position: 0 -121px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_table a { background-position: 0 -144px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_paste a { background-position: 0 -552px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_html a { background-position: 0 -193px;} + .wym_skin_twopanels .wym_buttons li.wym_tools_preview a { background-position: 0 -408px;} + +/*DECORATION*/ + .wym_skin_twopanels .wym_section h2 { background: #ddd; border: solid gray; border-width: 0 0 1px;} + .wym_skin_twopanels .wym_section h2 span { color: gray;} + .wym_skin_twopanels .wym_panel { padding: 0; border: solid gray; border-width: 1px; background: white;} + .wym_skin_twopanels .wym_panel ul { margin: 2px 0 5px; } + .wym_skin_twopanels .wym_dropdown { padding: 0; border: solid gray; border-width: 1px 1px 0 1px; } + .wym_skin_twopanels .wym_dropdown ul { border: solid gray; border-width: 0 1px 1px 1px; margin-left: -1px; padding: 5px 10px 5px 3px;} + +/*DIALOGS*/ + .wym_dialog div.row { margin-bottom: 5px;} + .wym_dialog div.row input { margin-right: 5px;} + .wym_dialog div.row label { float: left; width: 150px; display: block; text-align: right; margin-right: 10px; } + .wym_dialog div.row-indent { padding-left: 160px; } + /*autoclearing*/ + .wym_dialog div.row:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } + .wym_dialog div.row { display: inline-block; } + /* Hides from IE-mac \*/ + * html .wym_dialog div.row { height: 1%; } + .wym_dialog div.row { display: block; } + /* End hide from IE-mac */ + +/*WYMEDITOR_LINK*/ + a.wym_wymeditor_link { text-indent: -9999px; float: right; display: block; width: 50px; height: 15px; background: url(../wymeditor_icon.png); overflow: hidden; text-decoration: none; } diff --git a/app/javascripts/jquery/skins/twopanels/skin.js b/app/javascripts/jquery/skins/twopanels/skin.js new file mode 100644 index 000000000..e82efc540 --- /dev/null +++ b/app/javascripts/jquery/skins/twopanels/skin.js @@ -0,0 +1,39 @@ +WYMeditor.SKINS['twopanels'] = { + + init: function(wym) { + + //move the containers panel to the left area + jQuery(wym._box).find(wym._options.containersSelector) + .appendTo("div.wym_area_left"); + + //render following sections as panels + jQuery(wym._box).find(wym._options.classesSelector + ', ' + + wym._options.containersSelector) + .addClass("wym_panel"); + + //render following sections as buttons + jQuery(wym._box).find(wym._options.toolsSelector) + .addClass("wym_buttons"); + + // auto add some margin to the main area sides if left area + // or right area are not empty (if they contain sections) + jQuery(wym._box).find("div.wym_area_right ul") + .parents("div.wym_area_right").show() + .parents(wym._options.boxSelector) + .find("div.wym_area_main") + .css({"margin-right": "155px"}); + + jQuery(wym._box).find("div.wym_area_left ul") + .parents("div.wym_area_left").show() + .parents(wym._options.boxSelector) + .find("div.wym_area_main") + .css({"margin-left": "115px"}); + + //make hover work under IE < 7 + jQuery(wym._box).find(".wym_section").hover(function(){ + jQuery(this).addClass("hover"); + },function(){ + jQuery(this).removeClass("hover"); + }); + } +}; diff --git a/app/javascripts/jquery/skins/wymeditor_icon.png b/app/javascripts/jquery/skins/wymeditor_icon.png new file mode 100644 index 000000000..d4fc155fd Binary files /dev/null and b/app/javascripts/jquery/skins/wymeditor_icon.png differ diff --git a/app/javascripts/jquery/underscore.js b/app/javascripts/jquery/underscore.js new file mode 100644 index 000000000..eaba008c4 --- /dev/null +++ b/app/javascripts/jquery/underscore.js @@ -0,0 +1,807 @@ +// Underscore.js 1.1.6 +// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore is freely distributable under the MIT license. +// Portions of Underscore are inspired or borrowed from Prototype, +// Oliver Steele's Functional, and John Resig's Micro-Templating. +// For all details and documentation: +// http://documentcloud.github.com/underscore + +(function() { + + // Baseline setup + // -------------- + + // Establish the root object, `window` in the browser, or `global` on the server. + var root = this; + + // Save the previous value of the `_` variable. + var previousUnderscore = root._; + + // Establish the object that gets returned to break out of a loop iteration. + var breaker = {}; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; + + // Create quick reference variables for speed access to core prototypes. + var slice = ArrayProto.slice, + unshift = ArrayProto.unshift, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // All **ECMAScript 5** native function implementations that we hope to use + // are declared here. + var + nativeForEach = ArrayProto.forEach, + nativeMap = ArrayProto.map, + nativeReduce = ArrayProto.reduce, + nativeReduceRight = ArrayProto.reduceRight, + nativeFilter = ArrayProto.filter, + nativeEvery = ArrayProto.every, + nativeSome = ArrayProto.some, + nativeIndexOf = ArrayProto.indexOf, + nativeLastIndexOf = ArrayProto.lastIndexOf, + nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeBind = FuncProto.bind; + + // Create a safe reference to the Underscore object for use below. + var _ = function(obj) { return new wrapper(obj); }; + + // Export the Underscore object for **CommonJS**, with backwards-compatibility + // for the old `require()` API. If we're not in CommonJS, add `_` to the + // global object. + if (typeof module !== 'undefined' && module.exports) { + module.exports = _; + _._ = _; + } else { + root._ = _; + } + + // Current version. + _.VERSION = '1.1.6'; + + // Collection Functions + // -------------------- + + // The cornerstone, an `each` implementation, aka `forEach`. + // Handles objects implementing `forEach`, arrays, and raw objects. + // Delegates to **ECMAScript 5**'s native `forEach` if available. + var each = _.each = _.forEach = function(obj, iterator, context) { + if (obj == null) return; + if (nativeForEach && obj.forEach === nativeForEach) { + obj.forEach(iterator, context); + } else if (_.isNumber(obj.length)) { + for (var i = 0, l = obj.length; i < l; i++) { + if (iterator.call(context, obj[i], i, obj) === breaker) return; + } + } else { + for (var key in obj) { + if (hasOwnProperty.call(obj, key)) { + if (iterator.call(context, obj[key], key, obj) === breaker) return; + } + } + } + }; + + // Return the results of applying the iterator to each element. + // Delegates to **ECMAScript 5**'s native `map` if available. + _.map = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); + each(obj, function(value, index, list) { + results[results.length] = iterator.call(context, value, index, list); + }); + return results; + }; + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. + _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { + var initial = memo !== void 0; + if (obj == null) obj = []; + if (nativeReduce && obj.reduce === nativeReduce) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); + } + each(obj, function(value, index, list) { + if (!initial && index === 0) { + memo = value; + initial = true; + } else { + memo = iterator.call(context, memo, value, index, list); + } + }); + if (!initial) throw new TypeError("Reduce of empty array with no initial value"); + return memo; + }; + + // The right-associative version of reduce, also known as `foldr`. + // Delegates to **ECMAScript 5**'s native `reduceRight` if available. + _.reduceRight = _.foldr = function(obj, iterator, memo, context) { + if (obj == null) obj = []; + if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { + if (context) iterator = _.bind(iterator, context); + return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); + } + var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse(); + return _.reduce(reversed, iterator, memo, context); + }; + + // Return the first value which passes a truth test. Aliased as `detect`. + _.find = _.detect = function(obj, iterator, context) { + var result; + any(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) { + result = value; + return true; + } + }); + return result; + }; + + // Return all the elements that pass a truth test. + // Delegates to **ECMAScript 5**'s native `filter` if available. + // Aliased as `select`. + _.filter = _.select = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); + each(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) results[results.length] = value; + }); + return results; + }; + + // Return all the elements for which a truth test fails. + _.reject = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + each(obj, function(value, index, list) { + if (!iterator.call(context, value, index, list)) results[results.length] = value; + }); + return results; + }; + + // Determine whether all of the elements match a truth test. + // Delegates to **ECMAScript 5**'s native `every` if available. + // Aliased as `all`. + _.every = _.all = function(obj, iterator, context) { + var result = true; + if (obj == null) return result; + if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); + each(obj, function(value, index, list) { + if (!(result = result && iterator.call(context, value, index, list))) return breaker; + }); + return result; + }; + + // Determine if at least one element in the object matches a truth test. + // Delegates to **ECMAScript 5**'s native `some` if available. + // Aliased as `any`. + var any = _.some = _.any = function(obj, iterator, context) { + iterator || (iterator = _.identity); + var result = false; + if (obj == null) return result; + if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); + each(obj, function(value, index, list) { + if (result = iterator.call(context, value, index, list)) return breaker; + }); + return result; + }; + + // Determine if a given value is included in the array or object using `===`. + // Aliased as `contains`. + _.include = _.contains = function(obj, target) { + var found = false; + if (obj == null) return found; + if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; + any(obj, function(value) { + if (found = value === target) return true; + }); + return found; + }; + + // Invoke a method (with arguments) on every item in a collection. + _.invoke = function(obj, method) { + var args = slice.call(arguments, 2); + return _.map(obj, function(value) { + return (method.call ? method || value : value[method]).apply(value, args); + }); + }; + + // Convenience version of a common use case of `map`: fetching a property. + _.pluck = function(obj, key) { + return _.map(obj, function(value){ return value[key]; }); + }; + + // Return the maximum element or (element-based computation). + _.max = function(obj, iterator, context) { + if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); + var result = {computed : -Infinity}; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed >= result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Return the minimum element (or element-based computation). + _.min = function(obj, iterator, context) { + if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); + var result = {computed : Infinity}; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed < result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Sort the object's values by a criterion produced by an iterator. + _.sortBy = function(obj, iterator, context) { + return _.pluck(_.map(obj, function(value, index, list) { + return { + value : value, + criteria : iterator.call(context, value, index, list) + }; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }), 'value'); + }; + + // Use a comparator function to figure out at what index an object should + // be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iterator) { + iterator || (iterator = _.identity); + var low = 0, high = array.length; + while (low < high) { + var mid = (low + high) >> 1; + iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; + } + return low; + }; + + // Safely convert anything iterable into a real, live array. + _.toArray = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) return iterable.toArray(); + if (_.isArray(iterable)) return iterable; + if (_.isArguments(iterable)) return slice.call(iterable); + return _.values(iterable); + }; + + // Return the number of elements in an object. + _.size = function(obj) { + return _.toArray(obj).length; + }; + + // Array Functions + // --------------- + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. Aliased as `head`. The **guard** check allows it to work + // with `_.map`. + _.first = _.head = function(array, n, guard) { + return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; + }; + + // Returns everything but the first entry of the array. Aliased as `tail`. + // Especially useful on the arguments object. Passing an **index** will return + // the rest of the values in the array from that index onward. The **guard** + // check allows it to work with `_.map`. + _.rest = _.tail = function(array, index, guard) { + return slice.call(array, (index == null) || guard ? 1 : index); + }; + + // Get the last element of an array. + _.last = function(array) { + return array[array.length - 1]; + }; + + // Trim out all falsy values from an array. + _.compact = function(array) { + return _.filter(array, function(value){ return !!value; }); + }; + + // Return a completely flattened version of an array. + _.flatten = function(array) { + return _.reduce(array, function(memo, value) { + if (_.isArray(value)) return memo.concat(_.flatten(value)); + memo[memo.length] = value; + return memo; + }, []); + }; + + // Return a version of the array that does not contain the specified value(s). + _.without = function(array) { + var values = slice.call(arguments, 1); + return _.filter(array, function(value){ return !_.include(values, value); }); + }; + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // Aliased as `unique`. + _.uniq = _.unique = function(array, isSorted) { + return _.reduce(array, function(memo, el, i) { + if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo[memo.length] = el; + return memo; + }, []); + }; + + // Produce an array that contains every item shared between all the + // passed-in arrays. + _.intersect = function(array) { + var rest = slice.call(arguments, 1); + return _.filter(_.uniq(array), function(item) { + return _.every(rest, function(other) { + return _.indexOf(other, item) >= 0; + }); + }); + }; + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + _.zip = function() { + var args = slice.call(arguments); + var length = _.max(_.pluck(args, 'length')); + var results = new Array(length); + for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); + return results; + }; + + // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), + // we need this function. Return the position of the first occurrence of an + // item in an array, or -1 if the item is not included in the array. + // Delegates to **ECMAScript 5**'s native `indexOf` if available. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + _.indexOf = function(array, item, isSorted) { + if (array == null) return -1; + var i, l; + if (isSorted) { + i = _.sortedIndex(array, item); + return array[i] === item ? i : -1; + } + if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); + for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i; + return -1; + }; + + + // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. + _.lastIndexOf = function(array, item) { + if (array == null) return -1; + if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); + var i = array.length; + while (i--) if (array[i] === item) return i; + return -1; + }; + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](http://docs.python.org/library/functions.html#range). + _.range = function(start, stop, step) { + if (arguments.length <= 1) { + stop = start || 0; + start = 0; + } + step = arguments[2] || 1; + + var len = Math.max(Math.ceil((stop - start) / step), 0); + var idx = 0; + var range = new Array(len); + + while(idx < len) { + range[idx++] = start; + start += step; + } + + return range; + }; + + // Function (ahem) Functions + // ------------------ + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). Binding with arguments is also known as `curry`. + // Delegates to **ECMAScript 5**'s native `Function.bind` if available. + // We check for `func.bind` first, to fail fast when `func` is undefined. + _.bind = function(func, obj) { + if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); + var args = slice.call(arguments, 2); + return function() { + return func.apply(obj, args.concat(slice.call(arguments))); + }; + }; + + // Bind all of an object's methods to that object. Useful for ensuring that + // all callbacks defined on an object belong to it. + _.bindAll = function(obj) { + var funcs = slice.call(arguments, 1); + if (funcs.length == 0) funcs = _.functions(obj); + each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); + return obj; + }; + + // Memoize an expensive function by storing its results. + _.memoize = function(func, hasher) { + var memo = {}; + hasher || (hasher = _.identity); + return function() { + var key = hasher.apply(this, arguments); + return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); + }; + }; + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + _.delay = function(func, wait) { + var args = slice.call(arguments, 2); + return setTimeout(function(){ return func.apply(func, args); }, wait); + }; + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + _.defer = function(func) { + return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); + }; + + // Internal function used to implement `_.throttle` and `_.debounce`. + var limit = function(func, wait, debounce) { + var timeout; + return function() { + var context = this, args = arguments; + var throttler = function() { + timeout = null; + func.apply(context, args); + }; + if (debounce) clearTimeout(timeout); + if (debounce || !timeout) timeout = setTimeout(throttler, wait); + }; + }; + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. + _.throttle = function(func, wait) { + return limit(func, wait, false); + }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. + _.debounce = function(func, wait) { + return limit(func, wait, true); + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = function(func) { + var ran = false, memo; + return function() { + if (ran) return memo; + ran = true; + return memo = func.apply(this, arguments); + }; + }; + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + _.wrap = function(func, wrapper) { + return function() { + var args = [func].concat(slice.call(arguments)); + return wrapper.apply(this, args); + }; + }; + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function() { + var funcs = slice.call(arguments); + return function() { + var args = slice.call(arguments); + for (var i=funcs.length-1; i >= 0; i--) { + args = [funcs[i].apply(this, args)]; + } + return args[0]; + }; + }; + + // Returns a function that will only be executed after being called N times. + _.after = function(times, func) { + return function() { + if (--times < 1) { return func.apply(this, arguments); } + }; + }; + + + // Object Functions + // ---------------- + + // Retrieve the names of an object's properties. + // Delegates to **ECMAScript 5**'s native `Object.keys` + _.keys = nativeKeys || function(obj) { + if (obj !== Object(obj)) throw new TypeError('Invalid object'); + var keys = []; + for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key; + return keys; + }; + + // Retrieve the values of an object's properties. + _.values = function(obj) { + return _.map(obj, _.identity); + }; + + // Return a sorted list of the function names available on the object. + // Aliased as `methods` + _.functions = _.methods = function(obj) { + return _.filter(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort(); + }; + + // Extend a given object with all the properties in passed-in object(s). + _.extend = function(obj) { + each(slice.call(arguments, 1), function(source) { + for (var prop in source) { + if (source[prop] !== void 0) obj[prop] = source[prop]; + } + }); + return obj; + }; + + // Fill in a given object with default properties. + _.defaults = function(obj) { + each(slice.call(arguments, 1), function(source) { + for (var prop in source) { + if (obj[prop] == null) obj[prop] = source[prop]; + } + }); + return obj; + }; + + // Create a (shallow-cloned) duplicate of an object. + _.clone = function(obj) { + return _.isArray(obj) ? obj.slice() : _.extend({}, obj); + }; + + // Invokes interceptor with the obj, and then returns obj. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + _.tap = function(obj, interceptor) { + interceptor(obj); + return obj; + }; + + // Perform a deep comparison to check if two objects are equal. + _.isEqual = function(a, b) { + // Check object identity. + if (a === b) return true; + // Different types? + var atype = typeof(a), btype = typeof(b); + if (atype != btype) return false; + // Basic equality test (watch out for coercions). + if (a == b) return true; + // One is falsy and the other truthy. + if ((!a && b) || (a && !b)) return false; + // Unwrap any wrapped objects. + if (a._chain) a = a._wrapped; + if (b._chain) b = b._wrapped; + // One of them implements an isEqual()? + if (a.isEqual) return a.isEqual(b); + // Check dates' integer values. + if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); + // Both are NaN? + if (_.isNaN(a) && _.isNaN(b)) return false; + // Compare regular expressions. + if (_.isRegExp(a) && _.isRegExp(b)) + return a.source === b.source && + a.global === b.global && + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline; + // If a is not an object by this point, we can't handle it. + if (atype !== 'object') return false; + // Check for different array lengths before comparing contents. + if (a.length && (a.length !== b.length)) return false; + // Nothing else worked, deep compare the contents. + var aKeys = _.keys(a), bKeys = _.keys(b); + // Different object sizes? + if (aKeys.length != bKeys.length) return false; + // Recursive comparison of contents. + for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false; + return true; + }; + + // Is a given array or object empty? + _.isEmpty = function(obj) { + if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; + for (var key in obj) if (hasOwnProperty.call(obj, key)) return false; + return true; + }; + + // Is a given value a DOM element? + _.isElement = function(obj) { + return !!(obj && obj.nodeType == 1); + }; + + // Is a given value an array? + // Delegates to ECMA5's native Array.isArray + _.isArray = nativeIsArray || function(obj) { + return toString.call(obj) === '[object Array]'; + }; + + // Is a given variable an arguments object? + _.isArguments = function(obj) { + return !!(obj && hasOwnProperty.call(obj, 'callee')); + }; + + // Is a given value a function? + _.isFunction = function(obj) { + return !!(obj && obj.constructor && obj.call && obj.apply); + }; + + // Is a given value a string? + _.isString = function(obj) { + return !!(obj === '' || (obj && obj.charCodeAt && obj.substr)); + }; + + // Is a given value a number? + _.isNumber = function(obj) { + return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed)); + }; + + // Is the given value `NaN`? `NaN` happens to be the only value in JavaScript + // that does not equal itself. + _.isNaN = function(obj) { + return obj !== obj; + }; + + // Is a given value a boolean? + _.isBoolean = function(obj) { + return obj === true || obj === false; + }; + + // Is a given value a date? + _.isDate = function(obj) { + return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear); + }; + + // Is the given value a regular expression? + _.isRegExp = function(obj) { + return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false)); + }; + + // Is a given value equal to null? + _.isNull = function(obj) { + return obj === null; + }; + + // Is a given variable undefined? + _.isUndefined = function(obj) { + return obj === void 0; + }; + + // Utility Functions + // ----------------- + + // Run Underscore.js in *noConflict* mode, returning the `_` variable to its + // previous owner. Returns a reference to the Underscore object. + _.noConflict = function() { + root._ = previousUnderscore; + return this; + }; + + // Keep the identity function around for default iterators. + _.identity = function(value) { + return value; + }; + + // Run a function **n** times. + _.times = function (n, iterator, context) { + for (var i = 0; i < n; i++) iterator.call(context, i); + }; + + // Add your own custom functions to the Underscore object, ensuring that + // they're correctly added to the OOP wrapper as well. + _.mixin = function(obj) { + each(_.functions(obj), function(name){ + addToWrapper(name, _[name] = obj[name]); + }); + }; + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + _.uniqueId = function(prefix) { + var id = idCounter++; + return prefix ? prefix + id : id; + }; + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate : /<%([\s\S]+?)%>/g, + interpolate : /<%=([\s\S]+?)%>/g + }; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + _.template = function(str, data) { + var c = _.templateSettings; + var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + + 'with(obj||{}){__p.push(\'' + + str.replace(/\\/g, '\\\\') + .replace(/'/g, "\\'") + .replace(c.interpolate, function(match, code) { + return "'," + code.replace(/\\'/g, "'") + ",'"; + }) + .replace(c.evaluate || null, function(match, code) { + return "');" + code.replace(/\\'/g, "'") + .replace(/[\r\n\t]/g, ' ') + "__p.push('"; + }) + .replace(/\r/g, '\\r') + .replace(/\n/g, '\\n') + .replace(/\t/g, '\\t') + + "');}return __p.join('');"; + var func = new Function('obj', tmpl); + return data ? func(data) : func; + }; + + // The OOP Wrapper + // --------------- + + // If Underscore is called as a function, it returns a wrapped object that + // can be used OO-style. This wrapper holds altered versions of all the + // underscore functions. Wrapped objects may be chained. + var wrapper = function(obj) { this._wrapped = obj; }; + + // Expose `wrapper.prototype` as `_.prototype` + _.prototype = wrapper.prototype; + + // Helper function to continue chaining intermediate results. + var result = function(obj, chain) { + return chain ? _(obj).chain() : obj; + }; + + // A method to easily add functions to the OOP wrapper. + var addToWrapper = function(name, func) { + wrapper.prototype[name] = function() { + var args = slice.call(arguments); + unshift.call(args, this._wrapped); + return result(func.apply(_, args), this._chain); + }; + }; + + // Add all of the Underscore functions to the wrapper object. + _.mixin(_); + + // Add all mutator Array functions to the wrapper. + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + wrapper.prototype[name] = function() { + method.apply(this._wrapped, arguments); + return result(this._wrapped, this._chain); + }; + }); + + // Add all accessor Array functions to the wrapper. + each(['concat', 'join', 'slice'], function(name) { + var method = ArrayProto[name]; + wrapper.prototype[name] = function() { + return result(method.apply(this._wrapped, arguments), this._chain); + }; + }); + + // Start chaining a wrapped Underscore object. + wrapper.prototype.chain = function() { + this._chain = true; + return this; + }; + + // Extracts the result from a wrapped and chained object. + wrapper.prototype.value = function() { + return this._wrapped; + }; + +})(); diff --git a/app/javascripts/lib.js b/app/javascripts/lib.js new file mode 100644 index 000000000..14cd128b1 --- /dev/null +++ b/app/javascripts/lib.js @@ -0,0 +1,349 @@ +var CommonPlace = CommonPlace || {}; +function _ajax_request(url, data, callback, type, method) { + if (jQuery.isFunction(data)) { + callback = data; + data = {}; + } + return jQuery.ajax({ + type: method, + url: url, + data: data, + success: callback, + dataType: type + }); +} + +jQuery.extend({ + put: function(url, data, callback, type) { + return _ajax_request(url, data, callback, type, 'PUT'); + }, + del: function(url, data, callback, type) { + return _ajax_request(url, data, callback, type, 'DELETE'); + } +}); + +CommonPlace.render = function(name, params) { + return Mustache.to_html( + Templates[name], + _.extend({ auth_token: CommonPlace.auth_token, + account_avatar_url: CommonPlace.account.get('avatar_url'), + t: function() { + return function(key,render) { + var text = I18N["main_page/" + CommonPlace.community.get('locale')][name][key]; + return text ? render(text) : key ; + }; + } + }, params), + Templates); +}; + +Mustache.template = function(templateString) { + return templateString; +}; + + +$.preLoadImages("/assets/loading.gif"); + +CommonPlace.linkify = function(text) { + var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/i; + return text.replace(exp,"$1"); +}; + +CommonPlace.timeAgoInWords = function(date_str) { + var time = CommonPlace.parseDate(date_str); + var diff_in_seconds = (time - (new Date)) / 1000; + var diff_in_minutes = Math.abs(Math.floor((diff_in_seconds / 60))); + var add_token = function (in_words) { return diff_in_seconds > 0 ? "in " + in_words : in_words + " ago"; }; + if (diff_in_minutes === 0) { return add_token('less than a minute'); } + if (diff_in_minutes == 1) { return add_token('a minute'); } + if (diff_in_minutes < 45) { return add_token(diff_in_minutes + ' minutes'); } + if (diff_in_minutes < 90) { return add_token('about 1 hour'); } + if (diff_in_minutes < 1440) { return add_token('about ' + Math.floor(diff_in_minutes / 60) + ' hours'); } + if (diff_in_minutes < 2880) { return add_token('1 day'); } + if (diff_in_minutes < 43200) { return add_token(Math.floor(diff_in_minutes / 1440) + ' days'); } + if (diff_in_minutes < 86400) { return add_token('about 1 month'); } + if (diff_in_minutes < 525960) { return add_token(Math.floor(diff_in_minutes / 43200) + ' months'); } + if (diff_in_minutes < 1051199) { return add_token('about 1 year'); } + + return add_token('over ' + Math.floor(diff_in_minutes / 525960) + ' years'); +}; + +CommonPlace.parseDate = function(date_str) { + var m = date_str.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z/); + return Date.UTC(m[1],m[2] - 1,m[3],m[4],m[5],m[6]); +}; + + +$(function() { + + + //todo: this code block is duplicated in app.js + $('form.formtastic.feed input:text, form.formtastic.feed textarea').keydown(function(e) { + var $input = $(e.currentTarget); + setTimeout(function() { + $("#preview").find("[data-track='" + $input.attr('name') + "']").html($input.val()); + }, 10); + }); + + $('#sign_in_button').click(function() { + if ( $(this).hasClass("open") ) { + $(this).removeClass("open"); + $("form.user_session").slideUp(); + } else { + $(this).addClass("open"); + //$("form.user_session").slideDown(300); + $("form.user_session").slideDown(); + } + }); + + + $(window).bind('resize.modal', function () { + var $m = $("#modal-content"); + if ($m.get(0)) { + var w = $m.width(), + h = $m.height(), + $b = $(window), + bw = $b.width(), + bh = $b.height(); + + $m.css({top: (bh - h) / 2, left: (bw - w) / 2 - 20}); + } + }); + + $("#modal-close").live('click', function(e) { + $("#modal").html(""); + e.preventDefault(); + }); + + $("body").bind("#modal", function(e, content) { + if (content) { + $("#modal").replaceWith(window.innerShiv(content, false)); + } + $(window).trigger('resize.modal'); + setTimeout(function(){$(window).trigger("resize.modal");}, 500); + }); + + + // Feed Profile + $('#post-to-feed h2 nav li:last-child').hide(); + $('#post-to-feed h2 nav ul').hover(function(){ + $('#post-to-feed h2 nav li:last-child').show(); + }, function(){ + $('#post-to-feed h2 nav li:last-child').hide(); + }); + +}); +/*! + * JavaScript Linkify - v0.3 - 6/27/2009 + * http://benalman.com/projects/javascript-linkify/ + * + * Copyright (c) 2009 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + * + * Some regexps adapted from http://userscripts.org/scripts/review/7122 + */ + +// Script: JavaScript Linkify: Process links in text! +// +// *Version: 0.3, Last updated: 6/27/2009* +// +// Project Home - http://benalman.com/projects/javascript-linkify/ +// GitHub - http://github.com/cowboy/javascript-linkify/ +// Source - http://github.com/cowboy/javascript-linkify/raw/master/ba-linkify.js +// (Minified) - http://github.com/cowboy/javascript-linkify/raw/master/ba-linkify.min.js (2.8kb) +// +// About: License +// +// Copyright (c) 2009 "Cowboy" Ben Alman, +// Dual licensed under the MIT and GPL licenses. +// http://benalman.com/about/license/ +// +// About: Examples +// +// This working example, complete with fully commented code, illustrates one way +// in which this code can be used. +// +// Linkify - http://benalman.com/code/projects/javascript-linkify/examples/linkify/ +// +// About: Support and Testing +// +// Information about what browsers this code has been tested in. +// +// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4, Chrome, Opera 9.6-10. +// +// About: Release History +// +// 0.3 - (6/27/2009) Initial release + +// Function: linkify +// +// Turn text into linkified html. +// +// Usage: +// +// > var html = linkify( text [, options ] ); +// +// Arguments: +// +// text - (String) Non-HTML text containing links to be parsed. +// options - (Object) An optional object containing linkify parse options. +// +// Options: +// +// callback (Function) - If specified, this will be called once for each link- +// or non-link-chunk with two arguments, text and href. If the chunk is +// non-link, href will be omitted. If unspecified, the default linkification +// callback is used. +// punct_regexp (RegExp) - A RegExp that will be used to trim trailing +// punctuation from links, instead of the default. If set to null, trailing +// punctuation will not be trimmed. +// +// Returns: +// +// (String) An HTML string containing links. + +window.linkify = (function(){ + var + SCHEME = "[a-z\\d.-]+://", + IPV4 = "(?:(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])", + HOSTNAME = "(?:(?:[^\\s!@#$%^&*()_=+[\\]{}\\\\|;:'\",.<>/?]+)\\.)+", + TLD = "(?:ac|ad|aero|ae|af|ag|ai|al|am|an|ao|aq|arpa|ar|asia|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|biz|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|cat|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|coop|com|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|info|int|in|io|iq|ir|is|it|je|jm|jobs|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mobi|mo|mp|mq|mr|ms|mt|museum|mu|mv|mw|mx|my|mz|name|na|nc|net|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pro|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|tl|tm|tn|to|tp|travel|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g|xn--80akhbyknj4f|xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp|xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw)", + HOST_OR_IP = "(?:" + HOSTNAME + TLD + "|" + IPV4 + ")", + PATH = "(?:[;/][^#?<>\\s]*)?", + QUERY_FRAG = "(?:\\?[^#<>\\s]*)?(?:#[^<>\\s]*)?", + URI1 = "\\b" + SCHEME + "[^<>\\s]+", + URI2 = "\\b" + HOST_OR_IP + PATH + QUERY_FRAG + "(?!\\w)", + + MAILTO = "mailto:", + EMAIL = "(?:" + MAILTO + ")?[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@" + HOST_OR_IP + QUERY_FRAG + "(?!\\w)", + + URI_RE = new RegExp( "(?:" + URI1 + "|" + URI2 + "|" + EMAIL + ")", "ig" ), + SCHEME_RE = new RegExp( "^" + SCHEME, "i" ), + + quotes = { + "'": "`", + '>': '<', + ')': '(', + ']': '[', + '}': '{', + '»': '«', + '›': '‹' + }, + + default_options = { + callback: function( text, href ) { + return href ? '' + text + '' : text; + }, + punct_regexp: /(?:[!?.,:;'"]|(?:&|&)(?:lt|gt|quot|apos|raquo|laquo|rsaquo|lsaquo);)$/ + }; + + return function( txt, options ) { + options = options || {}; + + // Temp variables. + var arr, + i, + link, + href, + + // Output HTML. + html = '', + + // Store text / link parts, in order, for re-combination. + parts = [], + + // Used for keeping track of indices in the text. + idx_prev, + idx_last, + idx, + link_last, + + // Used for trimming trailing punctuation and quotes from links. + matches_begin, + matches_end, + quote_begin, + quote_end; + + // Initialize options. + for ( i in default_options ) { + if ( options[ i ] === undefined ) { + options[ i ] = default_options[ i ]; + } + } + + // Find links. + while ( arr = URI_RE.exec( txt ) ) { + + link = arr[0]; + idx_last = URI_RE.lastIndex; + idx = idx_last - link.length; + + // Not a link if preceded by certain characters. + if ( /[\/:]/.test( txt.charAt( idx - 1 ) ) ) { + continue; + } + + // Trim trailing punctuation. + do { + // If no changes are made, we don't want to loop forever! + link_last = link; + + quote_end = link.substr( -1 ); + quote_begin = quotes[ quote_end ]; + + // Ending quote character? + if ( quote_begin ) { + matches_begin = link.match( new RegExp( '\\' + quote_begin + '(?!$)', 'g' ) ); + matches_end = link.match( new RegExp( '\\' + quote_end, 'g' ) ); + + // If quotes are unbalanced, remove trailing quote character. + if ( ( matches_begin ? matches_begin.length : 0 ) < ( matches_end ? matches_end.length : 0 ) ) { + link = link.substr( 0, link.length - 1 ); + idx_last--; + } + } + + // Ending non-quote punctuation character? + if ( options.punct_regexp ) { + link = link.replace( options.punct_regexp, function(a){ + idx_last -= a.length; + return ''; + }); + } + } while ( link.length && link !== link_last ); + + href = link; + + // Add appropriate protocol to naked links. + if ( !SCHEME_RE.test( href ) ) { + href = ( href.indexOf( '@' ) !== -1 ? ( !href.indexOf( MAILTO ) ? '' : MAILTO ) + : !href.indexOf( 'irc.' ) ? 'irc://' + : !href.indexOf( 'ftp.' ) ? 'ftp://' + : 'http://' ) + + href; + } + + // Push preceding non-link text onto the array. + if ( idx_prev != idx ) { + parts.push([ txt.slice( idx_prev, idx ) ]); + idx_prev = idx_last; + } + + // Push massaged link onto the array + parts.push([ link, href ]); + }; + + // Push remaining non-link text onto the array. + parts.push([ txt.substr( idx_prev ) ]); + + // Process the array items. + for ( i = 0; i < parts.length; i++ ) { + html += options.callback.apply( window, parts[i] ); + } + + // In case of catastrophic failure, return the original text; + return html || txt; + }; + +})(); diff --git a/app/javascripts/main_page.js b/app/javascripts/main_page.js new file mode 100644 index 000000000..3099acd83 --- /dev/null +++ b/app/javascripts/main_page.js @@ -0,0 +1,101 @@ +//= require json2 +//= require showdown +//= require jquery +//= require jquery-ui +//= require actual +//= require underscore +//= require config +//= require feature_switches +//= require placeholder +//= require time_ago_in_words +//= require scrollTo +//= require mustache +//= require backbone +//= require autoresize +//= require dropkick +//= require truncator +//= require views +//= require_tree ../templates/shared +//= require_tree ../templates/main_page +//= require info_boxes +//= require models +//= require en +//= require college +//= require main_page/app +//= require info_boxes +//= require wires +//= require wire_items +//= require_tree ./main_page + + +function setPostBoxTop() { + if ($(window).scrollTop() < 60) { + $("#post-box").css({top: 85}); + } else { + $("#post-box").css({top: 85}); + } +} + +function setProfileBoxBottom() { + if ($(document).height() - $(window).scrollTop() - $(window).height() < 65) { + $("#info-box").css({bottom: 15}); + } else { + $("#info-box").css({bottom: 15}); + } +} + +function setProfileBoxTop() { + var $postBox = $("#post-box"); + $("#info-box").css({ + top: $postBox.outerHeight() + parseInt($postBox.css("top"), 10) + 4 + }); +} + +function setProfileBoxInfoUpperHeight() { + $("#info-upper").css({ + height: $("#info-box").height() - + $("#info-box h2").outerHeight() - + $("#info-box form").outerHeight() - + $("#info-box ul.filter").outerHeight() - 40 + }); +} + + + +$(function() { + + if (Features.isActive("fixedLayout")) { + var $navs = $('.z2, .z3, .z4').data({fixed:false}); + + window.adjustView = function() { + var scrollTop = $(window).scrollTop(); + + setPostBoxTop(); + setProfileBoxBottom(); + setProfileBoxTop(); + setProfileBoxInfoUpperHeight(); + + $navs.each(function() { + // sticky wire headers + var $nav = $(this), + position = $nav.position().top, + fixed = $nav.data('fixed'); + if ((scrollTop > position) && !fixed) { + $('.sub-navigation', $nav).addClass('fixed') + $nav.data({fixed: true}); + } else if ((scrollTop < position) && fixed) { + $('.sub-navigation', $nav).removeClass('fixed'); + $nav.data({fixed: false}); + } + }); + + }; + + + $("body").addClass("fixedLayout"); + + adjustView(); + $(window).scroll(adjustView).resize(adjustView); + } + +}); diff --git a/app/javascripts/main_page/app.js b/app/javascripts/main_page/app.js new file mode 100644 index 000000000..5f1edee60 --- /dev/null +++ b/app/javascripts/main_page/app.js @@ -0,0 +1,149 @@ + +var MainPageView = CommonPlace.View.extend({ + template: "main_page/main-page", + id: "main", + + initialize: function(options) { + this.account = this.options.account; + this.community = this.options.community; + + this.postBox = new PostBox({ + account: this.account, + community: this.community + }); + + this.lists = new CommunityResources({ + account: this.account, + community: this.community + }); + + this.infoBox = new InfoBox({ + account: this.account, + community: this.community + }); + + window.infoBox = this.infoBox; + + this.views = [this.postBox, this.lists, this.infoBox]; + }, + + afterRender: function() { + var self = this; + _(this.views).each(function(view) { + view.render(); + self.$("#" + view.id).replaceWith(view.el); + }); + } + +}); + +var MainPageRouter = Backbone.Router.extend({ + + initialize: function(options) { + this.account = options.account; + this.community = options.community; + + this.view = new MainPageView({ + account: this.account, + community: this.community + }); + + this.view.render(); + + this.lists = this.view.lists; + this.postBox = this.view.postBox; + this.infoBox = this.view.infoBox; + + $("#main").replaceWith(this.view.el); + }, + + routes: { + "/": "landing", + "": "landing", + "/posts": "posts", + "/announcements": "announcements", + "/events": "events", + "/group_posts": "groupPosts", + "/users": "users", + "/feeds": "feeds", + "/groups": "groups", + "/new-neighborhood-post": "newPost", + "/new-announcement": "newAnnouncement", + "/new-event": "newEvent", + "/new-group-post": "newGroupPost", + + "/posts/:id": "post", + "/events/:id": "event", + "/group_posts/:id": "groupPost", + "/announcements/:id": "announcement", + + "/users/:id/messages/new": "messageUser", + "/feeds/:id/messages/new": "messageFeed", + + "/tour": "tour" + }, + + posts: function() { this.lists.switchTab("posts"); }, + events: function() { this.lists.switchTab("events"); }, + announcements: function() { this.lists.switchTab("announcements"); }, + groupPosts: function() { this.lists.switchTab("groupPosts"); }, + users: function() { this.lists.switchTab("users"); }, + feeds: function() { this.lists.switchTab("feeds"); }, + groups: function() { this.lists.switchTab("groups"); }, + landing: function() { this.lists.switchTab("landing"); }, + + newPost: function() { this.postBox.switchTab("create-neighborhood-post"); }, + newAnnouncement: function() { this.postBox.switchTab("create-announcement"); }, + newEvent: function() { this.postBox.switchTab("create-event"); }, + newGroupPost: function() { this.postBox.switchTab("create-group-post"); }, + + post: function(id) { + this.lists.showPost(new Post({links: {self: "/posts/" + id}})); + }, + + event: function(id) { + this.lists.showEvent(new Event({links: {self: "/events/" + id }})); + }, + + announcement: function(id) { + this.lists.showAnnouncement(new Event({links: {self: "/announcements/" + id}})); + }, + + groupPost: function(id) { + this.lists.showGroupPost(new GroupPost({links: {self: "/group_posts/" + id}})); + }, + + messageUser: function(id) { + var user = new User({ links: { self: "/users/" + id } }); + user.fetch({ + success: function() { + var form = new MessageFormView({ + model: new Message({ messagable: user }) + }); + form.render(); + } + }); + }, + + messageFeed: function(id) { + var feed = new Feed({ links: { self: "/feeds/" + id } }); + feed.fetch({ + success: function() { + var form = new MessageFormView({ + model: new Message({ messagable: feed }) + }); + form.render(); + } + }); + }, + + tour: function() { + var tour = new Tour({ + el: $("#main"), + account: this.account, + community: this.community + }); + tour.render(); + } +}); + diff --git a/app/javascripts/main_page/community_resources.js b/app/javascripts/main_page/community_resources.js new file mode 100644 index 000000000..e35efe243 --- /dev/null +++ b/app/javascripts/main_page/community_resources.js @@ -0,0 +1,205 @@ + +var CommunityResources = CommonPlace.View.extend({ + template: "main_page/community-resources", + id: "community-resources", + + initialize: function(options) { + var self = this; + var community = this.options.community; + + community.posts.bind("add", function() { self.switchTab("posts"); }); + community.announcements.bind("add", function() { self.switchTab("announcements"); }); + community.events.bind("add", function() { self.switchTab("events"); }); + community.groupPosts.bind("add", function() { self.switchTab("groupPosts"); }); + }, + + switchTab: function(tab) { + var self = this, + $tabContent = self.$(".resources"), + views = this.tabs[tab](); // todo simplify + + this.$(".tab-button").removeClass("current"); + this.$("." + tab).addClass("current"); + + $tabContent.html(''); + _.each(views, function(view){ + view.render(); + $tabContent.append(view.el); + }); + + if (tab == 'landing'){ + $('form.search').detach().appendTo($('.landing-resources')) + } + + + if (window['mpq'] !== undefined){ // todo: move this in to helper method under CommonPlace + mpq.track('Wire:' + tab, {'community': CommonPlace.community.get('slug') }); + } + }, + + tabs: { + landing: function() { + return [( new LandingResources() )]; + }, + + posts: function() { + var collection; //TODO: DRY this (against landing_resources) + if (CommonPlace.community.get('locale') == "college") { + collection = CommonPlace.account.neighborhoodsPosts(); + } else { + collection = CommonPlace.community.posts; + } + + return [ + (new WireHeader({ + template: 'main_page/post-resources', //fixme: DRY this and just past in an initialization arg + search: true + })), + (new PaginatingResourceWire({ + collection: collection, + className: "posts wire", + modelToView: function(model) { //fixme: passage of model not dry + return new PostWireItem({ model: model }); + } + })) + ]; + + }, + + events: function() { + return [ + (new WireHeader({ + template: 'main_page/event-resources', + search: true + })), + (new PaginatingResourceWire({ + emptyMessage: "No events here yet", //todo: dry this crap.. + collection: CommonPlace.community.events, + className: "events wire", + modelToView: function(model) { + return new EventWireItem({ model: model }); + } + })) + ]; + }, + + announcements: function() { + return [ + (new WireHeader({ + template: 'main_page/announcement-resources', + search: true + })), + (new PaginatingResourceWire({ + emptyMessage: "No announcements here yet", //todo: dry this crap.. + collection: CommonPlace.community.announcements, + className: "announcements wire", + modelToView: function(model) { + return new AnnouncementWireItem({ model: model }); + } + })) + ]; + }, + + groupPosts: function() { + return [ + (new WireHeader({ + template: "main_page/group-post-resources", + search: true + })), + (new PaginatingResourceWire({ + emptyMessage: "No posts here yet", + collection: CommonPlace.community.groupPosts, + className: "groupPosts wire", + modelToView: function(model) { + return new GroupPostWireItem({ model: model }); + } + })) + ]; + }, + + users: function() { + return [ + (new WireHeader({ + template: "main_page/directory-resources" + })), + (new ResourceWire({ + emptyMessage: "No posts here yet", + collection: CommonPlace.community.users, + className: "users wire", + active: 'users', + modelToView: function(model) { + return new UserWireItem({ model: model }); + } + })) + ]; + }, + + groups: function() { + return [ + (new WireHeader({ + template: "main_page/directory-resources" + })), + (new ResourceWire({ + emptyMessage: "No posts here yet", + collection: CommonPlace.community.groups, + className: "users wire", + active: 'groups', + modelToView: function(model) { + return new GroupWireItem({ model: model }); + } + })) + ]; + }, + + feeds: function() { + return [ + (new WireHeader({ + template: "main_page/directory-resources" + })), + (new ResourceWire({ + emptyMessage: "No posts here yet", + collection: CommonPlace.community.feeds, + className: "users wire", + active: 'feeds', + modelToView: function(model) { + return new FeedWireItem({ model: model }); + } + })) + ]; + } + }, + + showPost: function(post) { + this.showSingleItem(post, GroupPostWireItem); + }, + showAnnouncement: function(announcement) { + this.showSingleItem(announcement, AnnouncementWireItem); + }, + showEvent: function(event) { + this.showSingleItem(event, EventWireItem); + }, + showGroupPost: function(groupPost) { + this.showSingleItem(groupPost, GroupPostWireItem); + }, + + showSingleItem: function(model, ItemView) { + var self = this; + model.fetch({ + success: function(model) { + var item = new ItemView({model: model}); + + self.$(".tab-button").removeClass("current"); + + item.render(); + + self.$(".resources").html($("
", { + "class": "wire", + html: $("
    ", { + "class": "wire-list", + html: item.el + }) + })); + } + }); + } +}); diff --git a/app/javascripts/main_page/event_form.js b/app/javascripts/main_page/event_form.js new file mode 100644 index 000000000..0be99b33e --- /dev/null +++ b/app/javascripts/main_page/event_form.js @@ -0,0 +1,77 @@ + +var EventForm = CommonPlace.View.extend({ + template: "main_page/event-form", + tagName: "form", + className: "create-event", + + events: { + "click button": "createEvent", + "change .post-label-selector input": "toggleCheckboxLIClass" + }, + + afterRender: function() { + this.$("input.date", this.el).datepicker({dateFormat: 'yy-mm-dd'}); + this.$('input[placeholder], textarea[placeholder]').placeholder(); + this.$("select.time").dropkick(); + }, + + createEvent: function(e) { + e.preventDefault(); + this.$("button").hide(); + this.$(".spinner").show(); + var self = this; + this.cleanUpPlaceholders(); + this.collection.create({ // use $.fn.serialize here + title: this.$("[name=title]").val(), + about: this.$("[name=about]").val(), + date: this.$("[name=date]").val(), + start: this.$("[name=start]").val(), + end: this.$("[name=end]").val(), + venue: this.$("[name=venue]").val(), + address: this.$("[name=address]").val(), + tags: this.$("[name=tags]").val(), + groups: this.$("[name=groups]:checked").map(function() { + return $(this).val(); + }).toArray() + }, { + success: function() { self.render(); }, + error: function(attribs, response) { + self.$("button").show(); + self.$(".spinner").hide(); + self.showError(response); + } + }); + }, + + showError: function(response) { + this.$(".error").text(response.responseText); + this.$(".error").show(); + }, + + groups: function() { + return this.options.community.get('groups'); + }, + + toggleCheckboxLIClass: function(e) { + $(e.target).closest("li").toggleClass("checked"); + }, + + time_values: _.flatten( + _.map( + ["AM", "PM"], + function(half) { + return _.map( + [12,1,2,3,4,5,6,7,8,9,10,11], + function(hour) { + return _.map( + ["00", "30"], + function(minute) { + return String(hour) + ":" + minute + " " + half; + } + ); + } + ); + } + ) + ) +}); diff --git a/app/javascripts/main_page/group_post_form.js b/app/javascripts/main_page/group_post_form.js new file mode 100644 index 000000000..292f553f6 --- /dev/null +++ b/app/javascripts/main_page/group_post_form.js @@ -0,0 +1,13 @@ +var GroupPostForm = CommonPlace.View.extend({ + template: "main_page/group-post-form", + tagName: "form", + className: "create-group-post", + + groups: function() { + return _.map(this.options.community.get('groups'), function(g, i) { + g['class'] = ((i % 2) === 0) ? "even" : "odd"; + return g; + }); + } +}); + diff --git a/app/javascripts/main_page/landing_resources.js b/app/javascripts/main_page/landing_resources.js new file mode 100644 index 000000000..bbd7f212c --- /dev/null +++ b/app/javascripts/main_page/landing_resources.js @@ -0,0 +1,87 @@ +var LandingResources = CommonPlace.View.extend({ + template: "main_page/landing-resources", + className: "landing-resources", + + afterRender: function() { + //fixme: invoke should be a function on the enumerable object + _(this.wires()).invoke("render"); + }, + + wires: function() { + var collection; + if (CommonPlace.community.get('locale') == "college") { + collection = CommonPlace.account.neighborhoodsPosts(); + } else { + collection = CommonPlace.community.posts; + } + if (!this._wires) { + this._wires = [ + (new WireHeader({ + link: '/#posts', + text: this.t('posts'), + search: true, + el: this.$(".posts.wireHeader") + })), + (new PreviewWire({ + collection: collection, + el: this.$(".posts.wire"), + emptyMessage: "No posts here yet.", + isRecent: true, + modelToView: function(model) { + return new PostWireItem({ model: model }); + } + })), + + (new WireHeader({ + link: '/#events', + text: this.t('events'), + el: this.$(".events.wireHeader") + })), + (new PreviewWire({ + collection: CommonPlace.community.events, + el: this.$(".events.wire"), + emptyMessage: "There are no upcoming events yet. Add some.", + isRecent: true, + modelToView: function(model) { + return new EventWireItem({ model: model }); + } + })), + + (new WireHeader({ + link: '/#announcements', + text: this.t('announcements'), + el: this.$(".announcements.wireHeader") + })), + (new PreviewWire({ + collection: CommonPlace.community.announcements, + el: this.$(".announcements.wire"), + emptyMessage: "No announcements here yet.", + isRecent: true, + modelToView: function(model) { + return new AnnouncementWireItem({ model: model }); + } + })), + + (new WireHeader({ + link: '/#groupPosts', + text: this.t('group-posts'), + el: this.$(".groupPosts.wireHeader") + })), + (new PreviewWire({ + collection: CommonPlace.community.groupPosts, + el: this.$(".groupPosts.wire"), + emptyMessage: "No posts here yet.", + isRecent: true, + modelToView: function(model) { + return new GroupPostWireItem({ model: model }); + } + })) + ]; + } + return this._wires; + } + + + + +}); diff --git a/app/javascripts/main_page/post_box.js b/app/javascripts/main_page/post_box.js new file mode 100644 index 000000000..3ed3b4973 --- /dev/null +++ b/app/javascripts/main_page/post_box.js @@ -0,0 +1,70 @@ + +var PostBox = CommonPlace.View.extend({ + template: "main_page/post-box", + id: "post-box", + + initialize: function(options) { + this.community = options.community; + this.account = options.account; + }, + + afterRender: function() { + var self = this; + _(this.forms()).each(function(view) { + self.$(".form-container").append(view.el); + }); + this.showTab("create-neighborhood-post"); + }, + + forms: function() { + this._forms || (this._forms = [ + (new PostForm({ collection: this.community.posts, + community: this.community })), + + (new EventForm({ collection: this.community.events, + community: this.community + })), + (new GroupPostForm({ collection: this.community.groupPosts, + community: this.community + })) + + ]); + return this._forms; + }, + + switchTab: function(tab) { + this.$(".on-focus").hide(); + this.$tabForms().removeClass("current"); + this.$tabButtons().removeClass("current"); + this.showTab(tab); + }, + + showTab: function(tab) { + this.$("." + tab).addClass("current"); + _(this.forms()).invoke("render"); + window.adjustView(); + }, + + $tabForms: function() { return this.$("form"); }, + + $tabButtons: function() { return this.$("a.tab-button"); }, + + accountHasFeeds: function() { return this.account.get('feeds').length > 0; }, + + firstFeedUrl: function() { + if (this.account.get('feeds')[0]) { + return "/pages/" + this.account.get('feeds')[0].id; + } else { + return "/feed-registrations/new"; + } + } +}); + + + + + + + + + diff --git a/app/javascripts/main_page/post_form.js b/app/javascripts/main_page/post_form.js new file mode 100644 index 000000000..147d0596f --- /dev/null +++ b/app/javascripts/main_page/post_form.js @@ -0,0 +1,80 @@ +var PostForm = CommonPlace.View.extend({ + template: "main_page/post-form", + tagName: "form", + className: "create-neighborhood-post", + + events: { + "click button": "createPost", + "click [name=commercial][value=yes]": "showPublicityWarning", + "click [name=commercial][value=no]": "hidePublicityWarning", + "focusin input, textarea": "onFormFocus" + }, + + afterRender: function() { + this.$('input[placeholder], textarea[placeholder]').placeholder(); + }, + + createPost: function(e) { + e.preventDefault(); + + this.cleanUpPlaceholders(); + + this.$(".spinner").show(); + this.$("button").hide(); + + var collection = this.collection; + if ($("[name=commercial]:checked").val() == "yes") { + collection = this.options.community.announcements; + } + + var self = this; + collection.create({ + title: this.$("[name=title]").val(), + body: this.$("[name=body]").val() + }, { + success: function() { self.render(); }, + error: function(attribs, response) { + self.$(".spinner").hide(); + self.$("button").show(); + self.showError(response); + } + }); + }, + + showError: function(response) { + this.$(".error").text(response.responseText); + this.$(".error").show(); + }, + + showPublicityWarning: function() { + this.$("p.warning").show(); + window.adjustView(); + }, + + hidePublicityWarning: function() { + this.$("p.warning").hide(); + window.adjustView(); + }, + + onFormFocus: function() { + $moreInputs = this.$(".on-focus"); + if (!$moreInputs.is(":visible")) { + + // animate to it's natural height (set explicitly to + // avoid choppiness) + var height = $moreInputs.actual('height') + 'px'; + $moreInputs.css({height:'0px'}).show().animate( + { height: height }, + { + step: window.adjustView, + complete: function() { + // set height back to auto so the element can + // naturally expand/contract + $moreInputs.css({height: "auto"}); + console.log('done'); + } + }); + } + + } +}); diff --git a/app/javascripts/main_page/tour.js b/app/javascripts/main_page/tour.js new file mode 100644 index 000000000..929377d18 --- /dev/null +++ b/app/javascripts/main_page/tour.js @@ -0,0 +1,103 @@ + +var Tour = CommonPlace.View.extend({ + id: "main", + tagName: "div", + + overlayLevel: 1000, + + changedElements: [], + + events: { + "click a.wire-tour" : "wire", + "click a.end-tour" : "end", + "click a.profile-tour" : "profile", + "click a.feed-tour" : "feed", + "click a.post-tour" : "post", + "click #tour-shadow" : "end", + "click #info-box, #community-resources, #post-box" : "end" + }, + + initialize: function(options) { + this.account = options.account; + this.community = options.community; + }, + + render: function() { + $(this.el).append("
    ").append("
    "); + this.welcome(); + return this; + }, + + community_name: function() { return this.community.get('name'); }, + + first_name: function() { return this.account.get('short_name'); }, + + welcome: function() { + this.template = "main_page/tour/welcome"; + this.$("#tour").html(this.renderTemplate("main_page/tour/welcome", this)) + .attr('class','welcome'); + }, + + wire: function() { + this.cleanUp(); + this.template = "main_page/tour/wire"; + this.$("#tour").html(this.renderTemplate("main_page/tour/wire", this)) + .attr('class','wire'); + this.removeShadows("#community-resources"); + this.raise("#community-resources"); + $.scrollTo(20, 700); + }, + + profile: function() { + this.cleanUp(); + this.template = "main_page/tour/profile"; + this.$("#tour").html(this.renderTemplate("main_page/tour/profile", this)) + .attr('class','profile'); + this.raise("#info-box"); + $.scrollTo(250, 700); + }, + + feed: function() { + this.cleanUp(); + this.template = "main_page/tour/feed"; + this.$("#tour").html(this.renderTemplate("main_page/tour/feed", this)) + .attr('class', 'feed'); + this.raise("#header"); + $.scrollTo(0, 0); + }, + + post: function() { + this.cleanUp(); + this.template = "main_page/tour/post"; + this.$("#tour").html(this.renderTemplate("main_page/tour/post", this)) + .attr('class','post'); + this.removeShadows("#post-box"); + this.raise("#post-box"); + $.scrollTo(0, 700); + }, + + end: function() { + this.cleanUp(); + $("#tour-shadow").remove(); + $("#tour").remove(); + }, + + raise: function(el) { + $(el).css({zIndex: this.overlayLevel + 1, position: "relative"}); + this.changedElements.push(el); + }, + + removeShadows: function(el) { + var shadowVal = "0 0 0 transparent"; + $(el).css({"-moz-box-shadow": shadowVal, "-webkit-box-shadow": shadowVal, + "-o-box-shadow": shadowVal, "box-shadow": shadowVal}); + this.changedElements.push(el); + }, + + cleanUp: function() { + _(this.changedElements).each(function(e) { $(e).attr('style', ""); }); + this.changedElements = []; + } + +}); + diff --git a/public/javascripts/maps.js b/app/javascripts/maps.js similarity index 100% rename from public/javascripts/maps.js rename to app/javascripts/maps.js diff --git a/app/javascripts/mobile.js b/app/javascripts/mobile.js new file mode 100644 index 000000000..5c7c84de9 --- /dev/null +++ b/app/javascripts/mobile.js @@ -0,0 +1,8 @@ +//= require jquery +//= require placeholder +//= require mobile/functions.js + + +$(function() { + $("input[placeholder]").placeholder(); +}); diff --git a/app/javascripts/mobile/functions.js b/app/javascripts/mobile/functions.js new file mode 100644 index 000000000..c9cbea868 --- /dev/null +++ b/app/javascripts/mobile/functions.js @@ -0,0 +1 @@ +var iWebkit;if(!iWebkit){iWebkit=window.onload=function(){function fullscreen(){var a=document.getElementsByTagName("a");for(var i=0;i 0) { + var responseText = "Please fill in the " + missing.shift(); + _.each(missing, function(field) { + responseText = responseText + " and " + field; + }); + var response = { + status: 400, + responseText: responseText + "." + }; + return response; + } + } +}); + +var Announcements = Collection.extend({ model: Announcement }); diff --git a/app/javascripts/models/base.js b/app/javascripts/models/base.js new file mode 100644 index 000000000..4f15af208 --- /dev/null +++ b/app/javascripts/models/base.js @@ -0,0 +1,30 @@ +Model = Backbone.Model.extend({ + url: function() { + if (this.get('links') && this.get('links').self) { + return "/api" + this.get('links').self; + } else { + return Backbone.Model.prototype.url.call(this); // super + } + }, + + link: function(name) { + return this.get("links")[name]; + } +}); + +Collection = Backbone.Collection.extend({ + initialize: function(models,options) { this.uri = options.uri; }, + url: function() { return "/api" + this.uri; } +}); + +Repliable = Model.extend({ + replies: function() { + this._replies = this._replies || + new Replies(_.map(this.get('replies'), + function (reply) { + return new Reply(reply); + }), + { uri: this.link("replies") }); + return this._replies; + } +}); diff --git a/app/javascripts/models/community.js b/app/javascripts/models/community.js new file mode 100644 index 000000000..b0fb679c0 --- /dev/null +++ b/app/javascripts/models/community.js @@ -0,0 +1,21 @@ + +var Community = Model.extend({ + initialize: function() { + this.posts = new Posts([], { uri: this.link("posts") }); + this.events = new Events([], { uri: this.link("events") }); + this.announcements = new Announcements([], { uri: this.link("announcements") }); + this.groupPosts = new GroupPosts([], { uri: this.link("group_posts") }); + this.users = new Users([], { uri: this.link("users") }); + this.feeds = new Feeds([], { uri: this.link("feeds") }); + this.groups = new Groups([], { uri: this.link("groups") }); + this.search = { + users: new Users([], { uri: this.link("users_search") }), + feeds: new Feeds([], { uri: this.link("feeds_search") }), + groups: new Groups([], { uri: this.link("groups_search") }), + groupPosts: new GroupPosts([], {uri: this.link("groupPosts_search")}), + posts: new Posts([], {uri: this.link("posts_search")}), + announcements: new Announcements([], {uri: this.link("announcements_search")}), + events: new Events([], {uri: this.link("events_search")}) + } + } +}); diff --git a/app/javascripts/models/event.js b/app/javascripts/models/event.js new file mode 100644 index 000000000..9c776386d --- /dev/null +++ b/app/javascripts/models/event.js @@ -0,0 +1,35 @@ + + +var Event = Repliable.extend({ + author: function() { + if (this.get('owner_type') == "Feed") { + return new Feed({ + links: { self: this.link('author') } + }); + } else { + return new User({ + links: { self: this.link('author') } + }); + } + }, + + validate: function(attribs) { + var missing = []; + if (!attribs.title) { missing.push("title"); } + if (!attribs.about && !attribs.body) { missing.push("body"); } + if (!attribs.date && !attribs.occurs_at) { missing.push("date"); } + if (missing.length > 0) { + var responseText = "Please fill in the " + missing.shift(); + _.each(missing, function(field) { + responseText = responseText + " and " + field; + }); + var response = { + status: 400, + responseText: responseText + "." + }; + return response; + } + } +}); + +var Events = Collection.extend({ model: Event }); diff --git a/app/javascripts/models/feed.js b/app/javascripts/models/feed.js new file mode 100644 index 000000000..240ef0ed5 --- /dev/null +++ b/app/javascripts/models/feed.js @@ -0,0 +1,29 @@ +var Feed = Model.extend({ + initialize: function() { + this.announcements = new Announcements([], { uri: this.link("announcements") }); + this.events = new Events([], { uri: this.link("events") }); + this.subscribers = new Users([], { uri: this.link("subscribers") }); + this.owners = new FeedOwners([], { uri: this.link("owners") }); + }, + + validate: function(attribs) { + var missing = []; + if (!attribs.name) { missing.push("feed name"); } + if (!attribs.slug) { missing.push("nickname"); } + if (missing.length > 0) { + var responseText = "Please fill in the " + missing.shift(); + _.each(missing, function(field) { + responseText = responseText + " and " + field; + }); + var response = { + status: 400, + responseText: responseText + "." + }; + return response; + } + } +}); + +var Feeds = Collection.extend({ + model: Feed +}); diff --git a/app/javascripts/models/feed_owner.js b/app/javascripts/models/feed_owner.js new file mode 100644 index 000000000..88c9dc90b --- /dev/null +++ b/app/javascripts/models/feed_owner.js @@ -0,0 +1,5 @@ +var FeedOwner = Model.extend({ }); + +var FeedOwners = Collection.extend({ + model: FeedOwner +}); diff --git a/app/javascripts/models/group.js b/app/javascripts/models/group.js new file mode 100644 index 000000000..b11ec9d46 --- /dev/null +++ b/app/javascripts/models/group.js @@ -0,0 +1,13 @@ + +var Group = Model.extend({ + initialize: function() { + this.posts = new GroupPosts([], { uri: this.link("posts") }); + this.members = new Users([], { uri: this.link("members") }); + this.announcements = new Announcements([], { uri: this.link("announcements") }); + this.events = new Events([], { uri: this.link("events") }); + } +}); + +var Groups = Collection.extend({ + model: Group +}); diff --git a/app/javascripts/models/group_post.js b/app/javascripts/models/group_post.js new file mode 100644 index 000000000..2f6db1c59 --- /dev/null +++ b/app/javascripts/models/group_post.js @@ -0,0 +1,31 @@ + + +var GroupPost = Repliable.extend({ + group: function(callback) { + if (!this._group) { + this._group = new Group({ + links: { self: this.get("links").group } + }); + } + this._group.fetch({ success: callback }); + }, + + validate: function(attribs) { + var missing = []; + if (!attribs.title) { missing.push("title"); } + if (!attribs.body) { missing.push("body"); } + if (missing.length > 0) { + var responseText = "Please fill in the " + missing.shift(); + _.each(missing, function(field) { + responseText = responseText + " and " + field; + }); + var response = { + status: 400, + responseText: responseText + "." + }; + return response; + } + } +}); + +var GroupPosts = Collection.extend({ model: GroupPost }); diff --git a/app/javascripts/models/message.js b/app/javascripts/models/message.js new file mode 100644 index 000000000..2109e93ab --- /dev/null +++ b/app/javascripts/models/message.js @@ -0,0 +1,32 @@ +var Message = Repliable.extend({ + initialize: function(options) { + this.messagable = options.messagable; + }, + + url: function() { + return "/api" + this.messagable.get("links").messages; + }, + + name: function() { + return this.messagable.get("name"); + }, + + validate: function(attribs) { + var missing = []; + if (!attribs.subject && !attribs.title) { missing.push("title"); } + if (!attribs.body) { missing.push("body"); } + if (missing.length > 0) { + var responseText = "Please fill in the " + missing.shift(); + _.each(missing, function(field) { + responseText = responseText + " and " + field; + }); + var response = { + status: 400, + responseText: responseText + "." + }; + return response; + } + } +}); + +var Messages = Collection.extend({ model: Message }); diff --git a/app/javascripts/models/post.js b/app/javascripts/models/post.js new file mode 100644 index 000000000..7b2ef5524 --- /dev/null +++ b/app/javascripts/models/post.js @@ -0,0 +1,30 @@ + +var Post = Repliable.extend({ + user: function(callback) { + if (!this._user) { + this._user = new User({ + links: { self: this.get("links").author } + }); + } + this._user.fetch({ success: callback }); + }, + + validate: function(attribs) { + var missing = []; + if (!attribs.title) { missing.push("title"); } + if (!attribs.body) { missing.push("body"); } + if (missing.length > 0) { + var responseText = "Please fill in the " + missing.shift(); + _.each(missing, function(field) { + responseText = responseText + " and " + field; + }); + var response = { + status: 400, + responseText: responseText + "." + }; + return response; + } + } +}); + +var Posts = Collection.extend({ model: Post }); diff --git a/app/javascripts/models/reply.js b/app/javascripts/models/reply.js new file mode 100644 index 000000000..2c4c03d87 --- /dev/null +++ b/app/javascripts/models/reply.js @@ -0,0 +1,15 @@ + + +var Reply = Model.extend({ + user: function(callback) { + if (!this._user) { + this._user = new User({ + links: { self: this.get("links").author } + }); + } + this._user.fetch({ success: callback }); + } +}); + + +var Replies = Collection.extend({ model: Reply }); diff --git a/app/javascripts/models/user.js b/app/javascripts/models/user.js new file mode 100644 index 000000000..e09343efc --- /dev/null +++ b/app/javascripts/models/user.js @@ -0,0 +1,6 @@ + +var User = Model.extend({}); + +var Users = Collection.extend({ + model: User +}); diff --git a/app/javascripts/registration_page.js b/app/javascripts/registration_page.js new file mode 100644 index 000000000..0ec1916e7 --- /dev/null +++ b/app/javascripts/registration_page.js @@ -0,0 +1,74 @@ +//= require jquery +//= require placeholder +//= require jcrop +//= require chosen + +$(function() { + + // all pages + $('input[placeholder], textarea[placeholder]').placeholder(); + + // Register + $('#sign_in_button').click(function() { + $(this).toggleClass("open"); + $(this).siblings("#sign_in_form").children("form").slideToggle(); + }); + + // Add more info + $("#user_interest_list, #user_good_list, #user_skill_list").chosen(); + + $("
    ", { id: "file_input_fix" }). + append($("", { type: "text", name: "file_fix", id: "file_style_fix" })). + append($("
    ", { id: "browse_button", text: "Browse..." })). + appendTo("#user_avatar_input"); + + + $('#user_avatar').change(function() { + $("#file_input_fix input").val($(this).val().replace(/^.*\\/,"")); + }); + + $('#user_referral_source').change(function() { + var selection = $("#user_referral_source option:selected").text(); + + var new_label = { + "At a table or booth at an event": "What was the event?", + "In an email": "Who was the email from?", + "On Facebook or Twitter": "From what person or organization?", + "On another website": "What website?", + "In the news": "From which news source?", + "Word of mouth": "From what person or organization?", + "Other": "Where?" }[selection]; + + if (new_label) { + $('#user_referral_metadata_input label').text(new_label); + $('#user_referral_metadata_input').show('slow'); + } + }); + + // Avatar crop + var updateCrop = function(coords) { + $("#crop_x").val(coords.x); + $("#crop_y").val(coords.y); + $("#crop_w").val(coords.w); + $("#crop_h").val(coords.h); + }; + + $("form.crop img").load(function() { + $("form.crop").css({ width: Math.max($("#cropbox").width(), 420) }); + }); + + $("#cropbox").Jcrop({ + onChange: updateCrop, + onSelect: updateCrop, + aspectRatio: 1.0 + }); + + // add feeds and add groups + $('.add_groups .group, .add_feeds .feed').click(function(){ + $('div', this).toggleClass('checked'); + var $checkbox = $("input:checkbox", this); + $checkbox.attr("checked", + $checkbox.is(":checked") ? false : "checked"); + }); + +}); diff --git a/app/javascripts/sharing.js b/app/javascripts/sharing.js new file mode 100644 index 000000000..c4fd7e132 --- /dev/null +++ b/app/javascripts/sharing.js @@ -0,0 +1,18 @@ +function share(url, name1, description, name, community) { + var header = name + " Posted an Event to " + community + "'s CommonPlace"; + FB.login( + function(response) { + if (response.session) { + FB.ui({ + method: 'feed', + name: header, + link: url, + picture: '', + caption: community + ' CommonPlace', + description: name + ": " + description, + message: 'Message' + }, + function(response) {}); + } + }); +} \ No newline at end of file diff --git a/app/javascripts/sign_in.js b/app/javascripts/sign_in.js new file mode 100644 index 000000000..4a04269e8 --- /dev/null +++ b/app/javascripts/sign_in.js @@ -0,0 +1,8 @@ +//= require jquery + +$(function() { + $('#sign_in_button').click(function() { + $(this).toggleClass("open"); + $(this).siblings("#sign_in_form").children("form").slideToggle(); + }); +}); \ No newline at end of file diff --git a/app/javascripts/starter_site.js b/app/javascripts/starter_site.js new file mode 100644 index 000000000..5f30cbb9b --- /dev/null +++ b/app/javascripts/starter_site.js @@ -0,0 +1,6 @@ +//= require starter_site/application +//= require starter_site/easing +//= require starter_site/bubbles +//= require starter_site/tabs +//= require starter_site/slideshow +//= require starter_site/scroll \ No newline at end of file diff --git a/app/javascripts/starter_site/application.js b/app/javascripts/starter_site/application.js new file mode 100644 index 000000000..fc262c4f3 --- /dev/null +++ b/app/javascripts/starter_site/application.js @@ -0,0 +1,27 @@ +function toggleEditor(id) { + if (!tinyMCE.get(id)) + tinyMCE.execCommand('mceAddControl', false, id); + else + tinyMCE.execCommand('mceRemoveControl', false, id); +} + +jQuery.ajaxSetup({ + 'beforeSend': function(xhr) { + $('form.ajax_submit').html("Please wait..."); + xhr.setRequestHeader("Accept", "text/javascript"); + } +}); + +jQuery.fn.submitWithAjax = function() { + this.submit(function() { + $.post(this.action, $(this).serialize(), null, "script"); + return false; + }); + return this; +}; + + + +$(document).ready(function() { + $('form.ajax_submit').submitWithAjax(); +}); \ No newline at end of file diff --git a/app/javascripts/starter_site/bubbles.js b/app/javascripts/starter_site/bubbles.js new file mode 100644 index 000000000..6bc055f4d --- /dev/null +++ b/app/javascripts/starter_site/bubbles.js @@ -0,0 +1,54 @@ +var pause; +$(document).ready(function () { + $('#banner_menu').mouseenter(function(){ + pause = true; + }).mouseleave(function(){ + pause = false; + }) + $('.bubbles li').each(function(index) { + $(this).css({position: "absolute", left: 260*index+"px", bottom: "-200px"}).delay( index * 500 ).slideDown(500); + }); + + setTimeout("slideIt()", 4000); + //if mouseover the menu item + $('#banner_menu li').hover(function () { + if(!$(this).hasClass('selected')){ + //select current item + $('#banner_menu li').removeClass('selected'); + $(this).addClass('selected'); + + //grabs li from hidden ul, puts text in bubble. + var list = $(this).find("ul").html(); + $('.bubbles').html(list); + + $('.bubbles li').each(function(index) { + $(this).css({position: "absolute", left: 260*index+"px", bottom: "-200px"}).delay( index * 500 ).slideDown(500); + }); + } + else{ + //Do nothing. You are already hovered on the selected tab + } + + }).click(function () { + return false; + }); +}); + +function slideIt(){ + if(pause != true){ + if($('.selected').next().length == 0){ + $('.selected').removeClass('selected'); + $('#banner_menu li:first-child').addClass('selected'); + } + else{ + $('.selected').removeClass('selected').next().addClass('selected'); + } + var list = $('.selected').find("ul").html(); + $('.bubbles').html(list); + $('.bubbles li').each(function(index) { + $(this).css({position: "absolute", left: 260*index+"px", bottom: "-200px"}).delay( index * 500 ).slideDown(500); + }); + } + setTimeout('slideIt()', 5000); + +} \ No newline at end of file diff --git a/app/javascripts/starter_site/easing.js b/app/javascripts/starter_site/easing.js new file mode 100644 index 000000000..ef7432107 --- /dev/null +++ b/app/javascripts/starter_site/easing.js @@ -0,0 +1,205 @@ +/* + * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/ + * + * Uses the built in easing capabilities added In jQuery 1.1 + * to offer multiple easing options + * + * TERMS OF USE - jQuery Easing + * + * Open source under the BSD License. + * + * Copyright © 2008 George McGinley Smith + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the author nor the names of contributors may be used to endorse + * or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * +*/ + +// t: current time, b: begInnIng value, c: change In value, d: duration +jQuery.easing['jswing'] = jQuery.easing['swing']; + +jQuery.extend( jQuery.easing, +{ + def: 'easeOutQuad', + swing: function (x, t, b, c, d) { + //alert(jQuery.easing.default); + return jQuery.easing[jQuery.easing.def](x, t, b, c, d); + }, + easeInQuad: function (x, t, b, c, d) { + return c*(t/=d)*t + b; + }, + easeOutQuad: function (x, t, b, c, d) { + return -c *(t/=d)*(t-2) + b; + }, + easeInOutQuad: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t + b; + return -c/2 * ((--t)*(t-2) - 1) + b; + }, + easeInCubic: function (x, t, b, c, d) { + return c*(t/=d)*t*t + b; + }, + easeOutCubic: function (x, t, b, c, d) { + return c*((t=t/d-1)*t*t + 1) + b; + }, + easeInOutCubic: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t*t + b; + return c/2*((t-=2)*t*t + 2) + b; + }, + easeInQuart: function (x, t, b, c, d) { + return c*(t/=d)*t*t*t + b; + }, + easeOutQuart: function (x, t, b, c, d) { + return -c * ((t=t/d-1)*t*t*t - 1) + b; + }, + easeInOutQuart: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t*t*t + b; + return -c/2 * ((t-=2)*t*t*t - 2) + b; + }, + easeInQuint: function (x, t, b, c, d) { + return c*(t/=d)*t*t*t*t + b; + }, + easeOutQuint: function (x, t, b, c, d) { + return c*((t=t/d-1)*t*t*t*t + 1) + b; + }, + easeInOutQuint: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b; + return c/2*((t-=2)*t*t*t*t + 2) + b; + }, + easeInSine: function (x, t, b, c, d) { + return -c * Math.cos(t/d * (Math.PI/2)) + c + b; + }, + easeOutSine: function (x, t, b, c, d) { + return c * Math.sin(t/d * (Math.PI/2)) + b; + }, + easeInOutSine: function (x, t, b, c, d) { + return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b; + }, + easeInExpo: function (x, t, b, c, d) { + return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b; + }, + easeOutExpo: function (x, t, b, c, d) { + return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b; + }, + easeInOutExpo: function (x, t, b, c, d) { + if (t==0) return b; + if (t==d) return b+c; + if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b; + return c/2 * (-Math.pow(2, -10 * --t) + 2) + b; + }, + easeInCirc: function (x, t, b, c, d) { + return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b; + }, + easeOutCirc: function (x, t, b, c, d) { + return c * Math.sqrt(1 - (t=t/d-1)*t) + b; + }, + easeInOutCirc: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b; + return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b; + }, + easeInElastic: function (x, t, b, c, d) { + var s=1.70158;var p=0;var a=c; + if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; + if (a < Math.abs(c)) { a=c; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (c/a); + return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; + }, + easeOutElastic: function (x, t, b, c, d) { + var s=1.70158;var p=0;var a=c; + if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; + if (a < Math.abs(c)) { a=c; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (c/a); + return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b; + }, + easeInOutElastic: function (x, t, b, c, d) { + var s=1.70158;var p=0;var a=c; + if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5); + if (a < Math.abs(c)) { a=c; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (c/a); + if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; + return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b; + }, + easeInBack: function (x, t, b, c, d, s) { + if (s == undefined) s = 1.70158; + return c*(t/=d)*t*((s+1)*t - s) + b; + }, + easeOutBack: function (x, t, b, c, d, s) { + if (s == undefined) s = 1.70158; + return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b; + }, + easeInOutBack: function (x, t, b, c, d, s) { + if (s == undefined) s = 1.70158; + if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b; + return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b; + }, + easeInBounce: function (x, t, b, c, d) { + return c - jQuery.easing.easeOutBounce (x, d-t, 0, c, d) + b; + }, + easeOutBounce: function (x, t, b, c, d) { + if ((t/=d) < (1/2.75)) { + return c*(7.5625*t*t) + b; + } else if (t < (2/2.75)) { + return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b; + } else if (t < (2.5/2.75)) { + return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b; + } else { + return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b; + } + }, + easeInOutBounce: function (x, t, b, c, d) { + if (t < d/2) return jQuery.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b; + return jQuery.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b; + } +}); + +/* + * + * TERMS OF USE - EASING EQUATIONS + * + * Open source under the BSD License. + * + * Copyright © 2001 Robert Penner + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the author nor the names of contributors may be used to endorse + * or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ \ No newline at end of file diff --git a/app/javascripts/starter_site/scroll.js b/app/javascripts/starter_site/scroll.js new file mode 100644 index 000000000..e2ed6d4fc --- /dev/null +++ b/app/javascripts/starter_site/scroll.js @@ -0,0 +1,17 @@ +$(document).ready(function(){ + $('.items').css({position: "relative", height: "280px", overflow: "hidden"}); + $('.rotate').css({position: "absolute"}); + $('.rotate').each(function(index){ + $(this).css({left: (index*324)+"px"}); + }); + setTimeout('startRotate()', 5000); +}); + +function startRotate(){ + $('.rotate').each(function(index){ + $(this).css({left: (index*324)+"px"}); + }).delay(600) + $('.rotate').animate({left: "-=324px"}, 500); + $('.rotate:first-child').appendTo('.items'); + setTimeout('startRotate()', 5000); +} \ No newline at end of file diff --git a/app/javascripts/starter_site/slideshow.js b/app/javascripts/starter_site/slideshow.js new file mode 100644 index 000000000..50f0991bd --- /dev/null +++ b/app/javascripts/starter_site/slideshow.js @@ -0,0 +1,34 @@ +var play=false; +$(document).ready(function () { + $('#slides img').css({left: "420px"}); + $('#slides img:first-child').css({left: "0px"}); + + $('#startSlides').toggle(function(){ + $(this).html('Stop Slideshow'); + play=true; + }, function(){ + $(this).html('Start Slideshow'); + play =false; + }); + startSlideshow(); +}); + +function startSlideshow(){ + if(play){ + if($('.active').next('img').length != 0){ + $('.active').removeClass('active').animate({left: "-420px"}, 500).next('img').animate({left: "0px"}, 500).addClass('active'); + $('.slide_info').fadeOut(300, function(){ + $('.slide_info').delay(400).html($('.active').attr('alt')).delay(200).fadeIn(); + }); + } + else{ + $('#slides img:first-child').addClass('back').appendTo('#slides'); + $('.back').css({left: "420px"}).removeClass('back'); + $('.active').removeClass('active').animate({left: "-420px"}, 500).next('img').animate({left: "0px"}, 500).addClass('active'); + $('.slide_info').fadeOut(300, function(){ + $('.slide_info').delay(400).html($('.active').attr('alt')).delay(200).fadeIn(); + }); + } + } + setTimeout("startSlideshow()", 5000); +} \ No newline at end of file diff --git a/app/javascripts/starter_site/tabs.js b/app/javascripts/starter_site/tabs.js new file mode 100644 index 000000000..a89e7f2c8 --- /dev/null +++ b/app/javascripts/starter_site/tabs.js @@ -0,0 +1,21 @@ +$(document).ready(function(){ + $('.tabs li a').click(function(){ + + $('.tabs li a').removeClass('current'); + + $(this).addClass('current'); + + $('.panes div').hide(); + + if($(this).parent().attr('id')=="ini"){ + $('#init').show(); + } + else if($(this).parent().attr('id')=="tec"){ + $('#tech').show(); + } + else if($(this).parent().attr('id')=="org"){ + $('#organ').show(); + } + return false; + }); +}); \ No newline at end of file diff --git a/app/javascripts/sticky_footer.js b/app/javascripts/sticky_footer.js new file mode 100644 index 000000000..80a7fbfbe --- /dev/null +++ b/app/javascripts/sticky_footer.js @@ -0,0 +1,15 @@ +$(function () { + // position the footer when window resizes + if ($('#sticky-wrapper').get(0)) { + function positionFooter() { + if ($(window).height() >= $(document).height()) { + $('html, body').css('height', '100%'); + } else { + $('html, body').css('height', ''); + } + } + + positionFooter(); + $(window).resize(positionFooter); + } +}); \ No newline at end of file diff --git a/app/javascripts/themes/dark-blue.js b/app/javascripts/themes/dark-blue.js new file mode 100755 index 000000000..a39d6caf2 --- /dev/null +++ b/app/javascripts/themes/dark-blue.js @@ -0,0 +1,170 @@ +/** + * Dark blue theme for Highcharts JS + * @author Torstein Hønsi + */ + +Highcharts.theme = { + colors: ["#DDDF0D", "#55BF3B", "#DF5353", "#7798BF", "#aaeeee", "#ff0066", "#eeaaee", + "#55BF3B", "#DF5353", "#7798BF", "#aaeeee"], + chart: { + backgroundColor: { + linearGradient: [0, 0, 250, 500], + stops: [ + [0, 'rgb(48, 48, 96)'], + [1, 'rgb(0, 0, 0)'] + ] + }, + borderColor: '#000000', + borderWidth: 2, + className: 'dark-container', + plotBackgroundColor: 'rgba(255, 255, 255, .1)', + plotBorderColor: '#CCCCCC', + plotBorderWidth: 1 + }, + title: { + style: { + color: '#C0C0C0', + font: 'bold 16px "Trebuchet MS", Verdana, sans-serif' + } + }, + subtitle: { + style: { + color: '#666666', + font: 'bold 12px "Trebuchet MS", Verdana, sans-serif' + } + }, + xAxis: { + gridLineColor: '#333333', + gridLineWidth: 1, + labels: { + style: { + color: '#A0A0A0' + } + }, + lineColor: '#A0A0A0', + tickColor: '#A0A0A0', + title: { + style: { + color: '#CCC', + fontWeight: 'bold', + fontSize: '12px', + fontFamily: 'Trebuchet MS, Verdana, sans-serif' + + } + } + }, + yAxis: { + gridLineColor: '#333333', + labels: { + style: { + color: '#A0A0A0' + } + }, + lineColor: '#A0A0A0', + minorTickInterval: null, + tickColor: '#A0A0A0', + tickWidth: 1, + title: { + style: { + color: '#CCC', + fontWeight: 'bold', + fontSize: '12px', + fontFamily: 'Trebuchet MS, Verdana, sans-serif' + } + } + }, + legend: { + itemStyle: { + font: '9pt Trebuchet MS, Verdana, sans-serif', + color: '#A0A0A0' + } + }, + tooltip: { + backgroundColor: 'rgba(0, 0, 0, 0.75)', + style: { + color: '#F0F0F0' + } + }, + toolbar: { + itemStyle: { + color: 'silver' + } + }, + plotOptions: { + line: { + dataLabels: { + color: '#CCC' + }, + marker: { + lineColor: '#333' + } + }, + spline: { + marker: { + lineColor: '#333' + } + }, + scatter: { + marker: { + lineColor: '#333' + } + } + }, + legend: { + itemStyle: { + color: '#CCC' + }, + itemHoverStyle: { + color: '#FFF' + }, + itemHiddenStyle: { + color: '#444' + } + }, + credits: { + style: { + color: '#666' + } + }, + labels: { + style: { + color: '#CCC' + } + }, + + navigation: { + buttonOptions: { + backgroundColor: { + linearGradient: [0, 0, 0, 20], + stops: [ + [0.4, '#606060'], + [0.6, '#333333'] + ] + }, + borderColor: '#000000', + symbolStroke: '#C0C0C0', + hoverSymbolStroke: '#FFFFFF' + } + }, + + exporting: { + buttons: { + exportButton: { + symbolFill: '#55BE3B' + }, + printButton: { + symbolFill: '#7797BE' + } + } + }, + + // special colors for some of the + legendBackgroundColor: 'rgba(0, 0, 0, 0.5)', + legendBackgroundColorSolid: 'rgb(35, 35, 70)', + dataLabelsColor: '#444', + textColor: '#C0C0C0', + maskColor: 'rgba(255,255,255,0.3)' +}; + +// Apply the theme +var highchartsOptions = Highcharts.setOptions(Highcharts.theme); \ No newline at end of file diff --git a/app/javascripts/themes/dark-green.js b/app/javascripts/themes/dark-green.js new file mode 100755 index 000000000..cec50e7b3 --- /dev/null +++ b/app/javascripts/themes/dark-green.js @@ -0,0 +1,170 @@ +/** + * Dark blue theme for Highcharts JS + * @author Torstein Hønsi + */ + +Highcharts.theme = { + colors: ["#DDDF0D", "#55BF3B", "#DF5353", "#7798BF", "#aaeeee", "#ff0066", "#eeaaee", + "#55BF3B", "#DF5353", "#7798BF", "#aaeeee"], + chart: { + backgroundColor: { + linearGradient: [0, 0, 250, 500], + stops: [ + [0, 'rgb(48, 96, 48)'], + [1, 'rgb(0, 0, 0)'] + ] + }, + borderColor: '#000000', + borderWidth: 2, + className: 'dark-container', + plotBackgroundColor: 'rgba(255, 255, 255, .1)', + plotBorderColor: '#CCCCCC', + plotBorderWidth: 1 + }, + title: { + style: { + color: '#C0C0C0', + font: 'bold 16px "Trebuchet MS", Verdana, sans-serif' + } + }, + subtitle: { + style: { + color: '#666666', + font: 'bold 12px "Trebuchet MS", Verdana, sans-serif' + } + }, + xAxis: { + gridLineColor: '#333333', + gridLineWidth: 1, + labels: { + style: { + color: '#A0A0A0' + } + }, + lineColor: '#A0A0A0', + tickColor: '#A0A0A0', + title: { + style: { + color: '#CCC', + fontWeight: 'bold', + fontSize: '12px', + fontFamily: 'Trebuchet MS, Verdana, sans-serif' + + } + } + }, + yAxis: { + gridLineColor: '#333333', + labels: { + style: { + color: '#A0A0A0' + } + }, + lineColor: '#A0A0A0', + minorTickInterval: null, + tickColor: '#A0A0A0', + tickWidth: 1, + title: { + style: { + color: '#CCC', + fontWeight: 'bold', + fontSize: '12px', + fontFamily: 'Trebuchet MS, Verdana, sans-serif' + } + } + }, + legend: { + itemStyle: { + font: '9pt Trebuchet MS, Verdana, sans-serif', + color: '#A0A0A0' + } + }, + tooltip: { + backgroundColor: 'rgba(0, 0, 0, 0.75)', + style: { + color: '#F0F0F0' + } + }, + toolbar: { + itemStyle: { + color: 'silver' + } + }, + plotOptions: { + line: { + dataLabels: { + color: '#CCC' + }, + marker: { + lineColor: '#333' + } + }, + spline: { + marker: { + lineColor: '#333' + } + }, + scatter: { + marker: { + lineColor: '#333' + } + } + }, + legend: { + itemStyle: { + color: '#CCC' + }, + itemHoverStyle: { + color: '#FFF' + }, + itemHiddenStyle: { + color: '#444' + } + }, + credits: { + style: { + color: '#666' + } + }, + labels: { + style: { + color: '#CCC' + } + }, + + navigation: { + buttonOptions: { + backgroundColor: { + linearGradient: [0, 0, 0, 20], + stops: [ + [0.4, '#606060'], + [0.6, '#333333'] + ] + }, + borderColor: '#000000', + symbolStroke: '#C0C0C0', + hoverSymbolStroke: '#FFFFFF' + } + }, + + exporting: { + buttons: { + exportButton: { + symbolFill: '#55BE3B' + }, + printButton: { + symbolFill: '#7797BE' + } + } + }, + + // special colors for some of the + legendBackgroundColor: 'rgba(0, 0, 0, 0.5)', + legendBackgroundColorSolid: 'rgb(35, 35, 70)', + dataLabelsColor: '#444', + textColor: '#C0C0C0', + maskColor: 'rgba(255,255,255,0.3)' +}; + +// Apply the theme +var highchartsOptions = Highcharts.setOptions(Highcharts.theme); \ No newline at end of file diff --git a/app/javascripts/themes/gray.js b/app/javascripts/themes/gray.js new file mode 100755 index 000000000..a292a7629 --- /dev/null +++ b/app/javascripts/themes/gray.js @@ -0,0 +1,164 @@ +/** + * Gray theme for Highcharts JS + * @author Torstein Hønsi + */ + +Highcharts.theme = { + colors: ["#DDDF0D", "#7798BF", "#55BF3B", "#DF5353", "#aaeeee", "#ff0066", "#eeaaee", + "#55BF3B", "#DF5353", "#7798BF", "#aaeeee"], + chart: { + backgroundColor: { + linearGradient: [0, 0, 0, 400], + stops: [ + [0, 'rgb(96, 96, 96)'], + [1, 'rgb(16, 16, 16)'] + ] + }, + borderWidth: 0, + borderRadius: 15, + plotBackgroundColor: null, + plotShadow: false, + plotBorderWidth: 0 + }, + title: { + style: { + color: '#FFF', + font: '16px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif' + } + }, + subtitle: { + style: { + color: '#DDD', + font: '12px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif' + } + }, + xAxis: { + gridLineWidth: 0, + lineColor: '#999', + tickColor: '#999', + labels: { + style: { + color: '#999', + fontWeight: 'bold' + } + }, + title: { + style: { + color: '#AAA', + font: 'bold 12px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif' + } + } + }, + yAxis: { + alternateGridColor: null, + minorTickInterval: null, + gridLineColor: 'rgba(255, 255, 255, .1)', + lineWidth: 0, + tickWidth: 0, + labels: { + style: { + color: '#999', + fontWeight: 'bold' + } + }, + title: { + style: { + color: '#AAA', + font: 'bold 12px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif' + } + } + }, + legend: { + itemStyle: { + color: '#CCC' + }, + itemHoverStyle: { + color: '#FFF' + }, + itemHiddenStyle: { + color: '#333' + } + }, + labels: { + style: { + color: '#CCC' + } + }, + tooltip: { + backgroundColor: { + linearGradient: [0, 0, 0, 50], + stops: [ + [0, 'rgba(96, 96, 96, .8)'], + [1, 'rgba(16, 16, 16, .8)'] + ] + }, + borderWidth: 0, + style: { + color: '#FFF' + } + }, + + + plotOptions: { + line: { + dataLabels: { + color: '#CCC' + }, + marker: { + lineColor: '#333' + } + }, + spline: { + marker: { + lineColor: '#333' + } + }, + scatter: { + marker: { + lineColor: '#333' + } + } + }, + + toolbar: { + itemStyle: { + color: '#CCC' + } + }, + + navigation: { + buttonOptions: { + backgroundColor: { + linearGradient: [0, 0, 0, 20], + stops: [ + [0.4, '#606060'], + [0.6, '#333333'] + ] + }, + borderColor: '#000000', + symbolStroke: '#C0C0C0', + hoverSymbolStroke: '#FFFFFF' + } + }, + + exporting: { + buttons: { + exportButton: { + symbolFill: '#55BE3B' + }, + printButton: { + symbolFill: '#7797BE' + } + } + }, + + // special colors for some of the demo examples + legendBackgroundColor: 'rgba(48, 48, 48, 0.8)', + legendBackgroundColorSolid: 'rgb(70, 70, 70)', + dataLabelsColor: '#444', + textColor: '#E0E0E0', + maskColor: 'rgba(255,255,255,0.3)' +}; + +// Apply the theme +var highchartsOptions = Highcharts.setOptions(Highcharts.theme); diff --git a/app/javascripts/themes/grid.js b/app/javascripts/themes/grid.js new file mode 100755 index 000000000..ab2011b11 --- /dev/null +++ b/app/javascripts/themes/grid.js @@ -0,0 +1,97 @@ +/** + * Grid theme for Highcharts JS + * @author Torstein Hønsi + */ + +Highcharts.theme = { + colors: ['#058DC7', '#50B432', '#ED561B', '#DDDF00', '#24CBE5', '#64E572', '#FF9655', '#FFF263', '#6AF9C4'], + chart: { + backgroundColor: { + linearGradient: [0, 0, 500, 500], + stops: [ + [0, 'rgb(255, 255, 255)'], + [1, 'rgb(240, 240, 255)'] + ] + } +, + borderWidth: 2, + plotBackgroundColor: 'rgba(255, 255, 255, .9)', + plotShadow: true, + plotBorderWidth: 1 + }, + title: { + style: { + color: '#000', + font: 'bold 16px "Trebuchet MS", Verdana, sans-serif' + } + }, + subtitle: { + style: { + color: '#666666', + font: 'bold 12px "Trebuchet MS", Verdana, sans-serif' + } + }, + xAxis: { + gridLineWidth: 1, + lineColor: '#000', + tickColor: '#000', + labels: { + style: { + color: '#000', + font: '11px Trebuchet MS, Verdana, sans-serif' + } + }, + title: { + style: { + color: '#333', + fontWeight: 'bold', + fontSize: '12px', + fontFamily: 'Trebuchet MS, Verdana, sans-serif' + + } + } + }, + yAxis: { + minorTickInterval: 'auto', + lineColor: '#000', + lineWidth: 1, + tickWidth: 1, + tickColor: '#000', + labels: { + style: { + color: '#000', + font: '11px Trebuchet MS, Verdana, sans-serif' + } + }, + title: { + style: { + color: '#333', + fontWeight: 'bold', + fontSize: '12px', + fontFamily: 'Trebuchet MS, Verdana, sans-serif' + } + } + }, + legend: { + itemStyle: { + font: '9pt Trebuchet MS, Verdana, sans-serif', + color: 'black' + + }, + itemHoverStyle: { + color: '#039' + }, + itemHiddenStyle: { + color: 'gray' + } + }, + labels: { + style: { + color: '#99b' + } + } +}; + +// Apply the theme +var highchartsOptions = Highcharts.setOptions(Highcharts.theme); + diff --git a/app/javascripts/views.js b/app/javascripts/views.js new file mode 100644 index 000000000..a4048e37f --- /dev/null +++ b/app/javascripts/views.js @@ -0,0 +1,2 @@ +//= require views/base +//= require_tree ./views diff --git a/app/javascripts/views/base.js b/app/javascripts/views/base.js new file mode 100644 index 000000000..8484e7598 --- /dev/null +++ b/app/javascripts/views/base.js @@ -0,0 +1,131 @@ +var CommonPlace = CommonPlace || {}; + +CommonPlace.View = Backbone.View.extend({ + + render: function() { + var self = this; + // trigger around, before, and after hooks + self.aroundRender(function() { + self.beforeRender(); + $(self.el).html(self.renderTemplate(self.getTemplate(), self)); + self.afterRender(); + }); + return this; + }, + + beforeRender: function() {}, + afterRender: function() {}, + aroundRender: function(render) { + render(); + }, + + renderTemplate: function(templateName, params) { + if (templateName.indexOf('/') !== -1){ + // uncomment this line when we're putting in the effort to deprecate: + // console.warn('slashes are deprecated in favor of dots in template names:', templateName) + templateName = templateName.replace(/\//g,'.'); + } + if (!Templates[templateName]) { + throw new Error("template '" + templateName + "' does not exist"); + } + + return Mustache.to_html(Templates[templateName], params, Templates); + }, + + attr_accessible: function(attributes) { + var self = this; + _.each(attributes, function(attribute) { + + self[attribute] = function() { + return self.model.get(attribute); + }; + + }); + }, + + // these functions work both directly and in templates + + assets: function(path) { + var makeUrl = function(path) { return "/assets/" + path; }; + return path ? makeUrl(path) : makeUrl; + }, + + getTemplate: function() { + return this.options.template || this.template; + }, + + t: function(key) { + var locale = I18N[CommonPlace.community.get('locale')]; + if (!locale) { throw new Error("Unknown locale"); } + var templateTexts = locale[this.getTemplate()] || {}; + console.log(this, this.getTemplate()); + var translate = function(key, render) { + render || (render = function(t) { return t; }); + var text = templateTexts[key]; + return text ? render(text) : key; + }; + return key ? translate(key) : translate; + }, + + markdown: function(text) { + var markdownify = function(text,render) { + render || (render = function(t) { return t; }); + text = render(text).replace(/!\[/g, "["); + return '
    ' + + (new Showdown.converter()).makeHtml(text) + + '
    '; + }; + return text ? markdownify(text) : markdownify; + }, + + //TODO: make this part of the placeholder plugin + cleanUpPlaceholders: function() { + this.$("[placeholder]").each(function() { + var input = $(this); + if (input.val() == input.attr('placeholder')) { + input.val(''); + } + }); + }, + + isActive: function(feature) { + return window.Features.isActive(feature); + } + +}); + + + +var FormView = CommonPlace.View.extend({ + initialize: function(options) { + this.template = (this.options.template || this.template); // todo: use getTemplate + this.modal = new ModalView({form: this.el}); + }, + + afterRender: function() { + this.modal.render(); + }, + + events: { + "click form a.cancel": "exit", + "click form a.delete": "deletePost", + "submit form": "send" + }, + + send: function(e) { + e.preventDefault(); + var self = this; + this.save(function() { self.modal.exit(); }); + }, + + exit: function(e) { + e && e.preventDefault(); + this.modal.exit(); + }, + + deletePost: function(e) { + e && e.preventDefault(); + var self = this; + this.remove(function() { self.modal.exit(); }); + } +}); diff --git a/app/javascripts/views/event_form.js b/app/javascripts/views/event_form.js new file mode 100644 index 000000000..623886693 --- /dev/null +++ b/app/javascripts/views/event_form.js @@ -0,0 +1,80 @@ +var EventFormView = FormView.extend({ + afterRender: function() { + this.modal.render(); + this.$("input.date").datepicker({dateFormat: 'yy-mm-dd'}); + this.$("select.time").dropkick(); + }, + + save: function(callback) { + var self = this; + this.model.save({ + title: this.$("[name=title]").val(), + body: this.$("[name=body]").val(), + occurs_at: this.$("[name=date]").val(), + starts_at: this.$("[name=start]").val(), + ends_at: this.$("[name=end]").val(), + venue: this.$("[name=venue]").val(), + address: this.$("[name=address]").val() + }, { + success: callback, + error: function(attribs, response) { self.showError(response); } + }); + }, + + showError: function(response) { + this.$(".error").text(response.responseText); + this.$(".error").show(); + }, + + remove: function(callback) { + this.model.destroy({ success: callback }); + }, + + title: function() { + return this.model.get("title"); + }, + + body: function() { + return this.model.get("body"); + }, + + date: function() { + return this.model.get("occurs_at").split("T")[0]; + }, + + venue: function() { + return this.model.get("venue"); + }, + + address: function() { + return this.model.get("address"); + }, + + time_values: function() { + var start_value = (this.model.get("starts_at") || "").replace(" ", ""); + var end_value = (this.model.get("ends_at") || "").replace(" ", ""); + var list = _.flatten(_.map(["AM", "PM"], + function(half) { + return _.map(_.range(1,13), + function(hour) { + return _.map(["00", "30"], + function(minute) { + return String(hour) + ":" + minute + " " + half; + }); + }); + }) + ); + var result = []; + _.each(list, function(time) { + var obj = { + ".": time, + "is_start": (time.replace(" ","").toLowerCase() == start_value), + "is_end": (time.replace(" ","").toLowerCase() == end_value) + }; + result.push(obj); + }); + return result; + } +}); + + diff --git a/app/javascripts/views/feature_switching.js b/app/javascripts/views/feature_switching.js new file mode 100644 index 000000000..3061a59f3 --- /dev/null +++ b/app/javascripts/views/feature_switching.js @@ -0,0 +1,38 @@ +FeatureSwitching = CommonPlace.View.extend({ + template: "shared.feature-switching", + + events: { + "change input:checkbox": "toggleFeature", + "click a.feature-panel-toggle": "toggleFeaturePanel", + "click button.refresh-page": "refreshPage" + }, + + features: function() { + return _(window.Features.features()).map(function(feature) { + return { + name: feature, + isEnabled: window.Features.isActive(feature) + }; + }); + }, + + toggleFeature: function(e) { + var $checkbox = $(e.target); + window.Features.toggle($checkbox.val()); + }, + + canTryFeatures: function() { + return CommonPlace.account.canTryFeatures(); + }, + + toggleFeaturePanel: function(e) { + e.preventDefault(); + this.$("div.feature-panel").slideToggle(); + this.$("a.feature-panel-toggle").toggleClass("shown"); + }, + + refreshPage: function() { + window.location.reload(); + } + +}); diff --git a/app/javascripts/views/feed_edit_form.js b/app/javascripts/views/feed_edit_form.js new file mode 100644 index 000000000..63f4ff95e --- /dev/null +++ b/app/javascripts/views/feed_edit_form.js @@ -0,0 +1,77 @@ +var FeedEditFormView = FormView.extend({ + template: "shared/feed-edit-form", + + afterRender: function() { + this.modal.render(); + var kind = this.model.get("kind"); + this.$("[name=kind] [value="+kind+"]").attr("selected", "selected"); + this.$("select.kind_list").dropkick(); + }, + + save: function(callback) { + var self = this; + this.model.save({ + name: this.$("[name=name]").val(), + about: this.$("[name=about]").val(), + kind: this.$("[name=kind]").val(), + slug: this.$("[name=slug]").val(), + rss: this.$("[name=rss]").val(), + website: this.$("[name=website]").val(), + phone: this.$("[name=phone]").val(), + address: this.$("[name=address]").val() + }, { + success: function() { + callback(); + window.location.pathname = self.model.get("url"); + }, + error: function(attribs, response) { self.showError(response); } + }); + }, + + showError: function(response) { + this.$(".error").text(response.responseText); + this.$(".error").show(); + }, + + feedName: function() { + return this.model.get("name"); + }, + + about: function() { + return this.model.get("about"); + }, + + address: function() { + return this.model.get("address"); + }, + + rss: function() { + return this.model.get("rss_url"); + }, + + website: function() { + return this.model.get("website"); + }, + + phone: function() { + return this.model.get("phone"); + }, + + slug: function() { + return this.model.get("slug"); + }, + + deleteUrl: function() { + return this.model.get("delete_url"); + }, + + avatarUrl: function() { + return this.model.link("avatar").thumb; + }, + + editUrl: function() { + return this.model.link("edit"); + } +}); + + diff --git a/app/javascripts/views/feed_owners_form.js b/app/javascripts/views/feed_owners_form.js new file mode 100644 index 000000000..def0367f8 --- /dev/null +++ b/app/javascripts/views/feed_owners_form.js @@ -0,0 +1,119 @@ +var FeedOwnersFormView = FormView.extend({ + template: "shared/feed-owners-form", + + events: { + "click form a.cancel": "exit", + "submit form": "addOwners" + }, + + afterRender: function() { + this.modal.render(); + this.fillList(); + }, + + fillList: function(callback) { + var list = this.$(".existing-owners"); + list.empty(); + var self = this; + var owners = this.model.owners; + owners.fetch({ + success: function() { + owners.each(function(owner) { + var item = new FeedOwnersItemView({ model: owner, form: self }); + item.render(); + list.append(item.el); + }); + if (callback) { callback(owners); } + } + }); + }, + + addOwners: function(e) { + e && e.preventDefault(); + + // hiding the error notifications + this.$(".incomplete-form").hide(); + this.$(".bad-emails").hide(); + + var self = this; + var emailform = this.$("[name=emails]"); + + // unlike message/event modals, we're not validating a model + // and $.post() doesn't have an error callback + // so we check to see if the form is empty here + if (emailform.val() == "") { return this.$(".incomplete-form").show(); } + + $.post( + "/api" + this.model.link("owners"), + { emails: emailform.val() }, + function(response) { + self.fillList(function(owners) { + var bad_emails = _.select(emailform.val().split(","), function(email) { + return !owners.any(function(obj) { + return obj.get("user_email") == email.replace(/ /g, ""); + }); + }); + if (bad_emails.length) { + self.$(".bad-emails").show(); + emailform.val(bad_emails); + } else { + emailform.val(""); + } + }); + }, + "JSON" + ); + }, + + name: function() { return this.model.get("name"); } +}); + +var FeedOwnersItemView = CommonPlace.View.extend({ + template: "shared/feed-owners-item", + tagName: "tr", + + initialize: function(options) { + this.model = options.model; + this.form = options.form; + }, + + events: { + "click .message": "sendMessage", + "click .remove-owner": "removeOwner" + }, + + name: function() { + return this.model.get("user_name"); + }, + + sendMessage: function(e) { + e.preventDefault(); + this.form.exit(); + var user = new User({ + links: { + self: this.model.link("user") + } + }); + user.fetch({ + success: function() { + var formview = new MessageFormView({ + model: new Message({messagable: user}) + }); + formview.render(); + } + }); + }, + + removeOwner: function(e) { + e.preventDefault(); + var form = this.form; + this.model.destroy({ + success: function() { + form.fillList(); + } + }); + } +}); + + + diff --git a/app/javascripts/views/message_form.js b/app/javascripts/views/message_form.js new file mode 100644 index 000000000..bd2801254 --- /dev/null +++ b/app/javascripts/views/message_form.js @@ -0,0 +1,25 @@ +var MessageFormView = FormView.extend({ + template: "shared/message-form", + + save: function(callback) { + var self = this; + this.model.save({ + subject: this.$("[name=subject]").val(), + body: this.$("[name=body]").val() + }, { + success: function() { callback(); }, + error: function(attribs, response) { self.showError(response); } + }); + }, + + showError: function(response) { + this.$(".error").text(response.responseText); + this.$(".error").show(); + }, + + name: function() { + return this.model.name(); + } +}); + + diff --git a/app/javascripts/views/modal.js b/app/javascripts/views/modal.js new file mode 100644 index 000000000..dd66f0184 --- /dev/null +++ b/app/javascripts/views/modal.js @@ -0,0 +1,37 @@ +var ModalView = CommonPlace.View.extend({ + template: "shared/modal", + className: "modal", + + initialize: function(options) { + var self = this; + this.form = this.options.form; + }, + + afterRender: function() { + $("body").append(this.el); + $(".modal-container").append(this.form); + this.$("textarea").autoResize(); + this.centerEl(); + }, + + centerEl: function() { + var $el = $(".modal-container"); + var $window = $(window); + var scrolled = $window.scrollTop(); + var top = (($window.height() - $el.height()) /2) + scrolled; + top = top < 1 ? 10 : top; + top = top < scrolled + 10 ? scrolled + 10 : top; + var left = ($window.width() - $el.width()) /2; + $el.css({ top: top, left: left }); + }, + + events: { + "click #modal-shadow": "exit" + }, + + exit: function() { + $(this.el).remove(); + } +}); + + diff --git a/app/javascripts/views/post_form.js b/app/javascripts/views/post_form.js new file mode 100644 index 000000000..df33b2439 --- /dev/null +++ b/app/javascripts/views/post_form.js @@ -0,0 +1,31 @@ +var PostFormView = FormView.extend({ + save: function(callback) { + var self = this; + this.model.save({ + title: this.$("[name=title]").val(), + body: this.$("[name=body]").val() + }, { + success: callback, + error: function(attribs, response) { self.showError(response); } + }); + }, + + showError: function(response) { + this.$(".error").text(response.responseText); + this.$(".error").show(); + }, + + remove: function(callback) { + this.model.destroy({ success: callback }); + }, + + title: function() { + return this.model.get("title"); + }, + + body: function() { + return this.model.get("body"); + } +}); + + diff --git a/app/javascripts/views/replies.js b/app/javascripts/views/replies.js new file mode 100644 index 000000000..f31f7ef8f --- /dev/null +++ b/app/javascripts/views/replies.js @@ -0,0 +1,55 @@ +var RepliesView = CommonPlace.View.extend({ + className: "replies", + template: "shared/replies", + initialize: function(options) { + var self = this; + this.collection.bind("add", function() { self.render(); }); + this.collection.bind("remove", function() { self.render(); }); + }, + + afterRender: function() { + this.$("textarea").placeholder(); + this.$("textarea").autoResize(); + this.appendReplies(); + }, + + events: { + "keydown form textarea": "sendReply", + "focus form textarea": "showHint", + "blur form textarea": "hideHint" + }, + + appendReplies: function() { + var self = this; + var elements = this.collection.map(function(reply) { + var view = new ReplyWireItem({ model: reply }); + view.render(); + return view.el; + }); + this.$("ul.reply-list").append(elements); + }, + + pluralizedReplies: function(){ + return (this.hiddenReplyCount() > 1) ? 'replies' : 'reply'; + }, + + sendReply: function(e) { + this.showHint(); + if (e.which == 13) { + e.preventDefault(); + if (e.shiftKey) { + var form = this.$("[name=body]"); + form.val(form.val() + "\n"); + } else { + this.cleanUpPlaceholders(); + this.collection.create({ body: this.$("[name=body]").val()}); + } + } + }, + + showHint: function(e) { this.$(".enter-hint").show(); }, + + hideHint: function(e) { this.$(".enter-hint").hide(); }, + + accountAvatarUrl: function() { return CommonPlace.account.get('avatar_url'); } +}); diff --git a/app/javascripts/wire_items.js b/app/javascripts/wire_items.js new file mode 100644 index 000000000..181ecb732 --- /dev/null +++ b/app/javascripts/wire_items.js @@ -0,0 +1,3 @@ +//= require ./wires/items/base +//= require_tree ./wires/items +//= require_tree ../templates/wires/items \ No newline at end of file diff --git a/app/javascripts/wires.js b/app/javascripts/wires.js new file mode 100644 index 000000000..f4ed11051 --- /dev/null +++ b/app/javascripts/wires.js @@ -0,0 +1,4 @@ +//= require wires/base +//= require_tree ./wires +//= require_tree ../templates/wires +//= require jquery.highlight-3 \ No newline at end of file diff --git a/app/javascripts/wires/base.js.coffee b/app/javascripts/wires/base.js.coffee new file mode 100644 index 000000000..3515e5323 --- /dev/null +++ b/app/javascripts/wires/base.js.coffee @@ -0,0 +1,78 @@ +# how do classes and extension work? take a look at: +# http://stackoverflow.com/questions/7735133/backbone-inheratance +# http://jashkenas.github.com/coffee-script/ -- section on classes + +# todo on this class +# dry emptymessage (use only noun & share) +# make modelToView more intuitive, dry object creation +# hold all data upon pagination +# go over options, make them dryer, create a Coffee View class that can be extendable anywhere +# make events dom-centric +# look for ways to remove rendering logic, or at least separate from template population methods +# upgrade to handlebars.js -- should be faster and allow better code. + +class window.Wire extends CommonPlace.View + constructor: (options) -> + @options = options # todo: can we upgrade this syntax? + # DRY + # todo: move account to a global CommonPlace.account variable. + # then: remove from wire options, including modelToView + # then: make modelToView simply a class that gets passed in as an option + + @scope['limit'] = @perPage() + @scope['page'] = 0 + + + Backbone.View.apply(this, [options]) + # this is the backbone initialization. created cid, default option magic, the element, and the events. + # todo- can we use proto chain to call this, instead? + + # todo: see if we can remove these: + @emptyMessage = options.emptyMessage + @modelToView = options.modelToView + + $(this.el).bind 'search', (eventType, eventData) => + @search(eventData['query']) + + + template: "wires/wire" + + aroundRender: (render) -> + @collection.fetch({ + data: @scope + success: render + }) + + afterRender: () -> + $ul = this.$("ul.wire-list") + this.collection.each (model) => + $ul.append(@modelToView(model).render().el) + $(this.el).highlight(@query()) + + scope: {} + + query: () -> + (@scope['query'] || '') + + + perPage: () -> + (@options.perPage || @_defaultPerPage) + + allFetched: () -> + # this is a hack so that we can easily share a template with paginating wire + true + + isEmpty: () -> + @collection.isEmpty() + + emptyMessage: () -> + @options.emptyMessage; + + search: (query) -> + @scope['query'] = query + @render() + + isSearchEnabled: () -> + # todo: move this in to a global helper w/ help of handlerbars + # http://yehudakatz.com/2010/09/09/announcing-handlebars-js/ + @isActive('wireSearch') diff --git a/app/javascripts/wires/items/base.js b/app/javascripts/wires/items/base.js new file mode 100644 index 000000000..a56cf9871 --- /dev/null +++ b/app/javascripts/wires/items/base.js @@ -0,0 +1,2 @@ +window.WireItem = CommonPlace.View.extend({ +}); diff --git a/app/javascripts/wires/items/cannouncement.js b/app/javascripts/wires/items/cannouncement.js new file mode 100644 index 000000000..d272dab16 --- /dev/null +++ b/app/javascripts/wires/items/cannouncement.js @@ -0,0 +1,69 @@ +var AnnouncementWireItem = WireItem.extend({ + template: "wires/items/announcement-tpl", + tagName: "li", + className: "wire-item", + + initialize: function(options) { + var self = this; + this.model.bind("destroy", function() { self.remove(); }); + }, + + afterRender: function() { + var repliesView = new RepliesView({ collection: this.model.replies(), + el: this.$(".replies") + }); + repliesView.render(); + this.model.bind("change", this.render, this); + this.$(".announcement-body").truncate({max_length: 450}); + }, + + publishedAt: function() { + return timeAgoInWords(this.model.get('published_at')); + }, + + avatarUrl: function() { return this.model.get('avatar_url'); }, + + url: function() { return this.model.get('url'); }, + + title: function() { return this.model.get('title'); }, + + author: function() { return this.model.get('author'); }, + + body: function() { + return this.model.get("body"); + }, + + events: { + "click .editlink": "editAnnouncement", + "mouseenter": "showProfile" + }, + + editAnnouncement: function(e) { + e && e.preventDefault(); + var formview = new PostFormView({ + model: this.model, + template: "shared/announcement-edit-form" + }); + formview.render(); + }, + + // todo: DRY this CRAP + canEdit: function() { + return CommonPlace.account.canEditAnnouncement(this.model); + }, + + isMore: function() { + return !this.allwords; + }, + + loadMore: function(e) { + e.preventDefault(); + this.allwords = true; + this.render(); + }, + + showProfile: function(e) { + window.infoBox.showProfile(this.model.author()); + } + +}); diff --git a/app/javascripts/wires/items/event.js b/app/javascripts/wires/items/event.js new file mode 100644 index 000000000..72eb57fdb --- /dev/null +++ b/app/javascripts/wires/items/event.js @@ -0,0 +1,80 @@ +var EventWireItem = WireItem.extend({ + template: "wires/items/event-tpl", + tagName: "li", + className: "wire-item", + + initialize: function(options) { + var self = this; + this.model.bind("destroy", function() { self.remove(); }); + }, + + afterRender: function() { + var repliesView = new RepliesView({ collection: this.model.replies(), + el: this.$(".replies") + }); + repliesView.render(); + this.model.bind("change", this.render, this); + this.$(".event-body").truncate({max_length: 450}); + }, + + short_month_name: function() { + var m = this.model.get("occurs_on").match(/(\d{4})-(\d{2})-(\d{2})/); + return this.monthAbbrevs[m[2] - 1]; + }, + + day_of_month: function() { + var m = this.model.get("occurs_on").match(/(\d{4})-(\d{2})-(\d{2})/); + return m[3]; + }, + + publishedAt: function() { return timeAgoInWords(this.model.get('published_at')); }, + + title: function() { return this.model.get('title'); }, + + author: function() { return this.model.get('author'); }, + + venue: function() { return this.model.get('venue'); }, + + address: function() { return this.model.get('address'); }, + + time: function() { return this.model.get('starts_at'); }, + + body: function() { + return this.model.get("body"); + }, + + monthAbbrevs: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"], + + events: { + "click .editlink": "editEvent", + "mouseenter": "showProfile" + }, + + editEvent: function(e) { + e && e.preventDefault(); + var formview = new EventFormView({ + model: this.model, + template: "shared/event-edit-form" + }); + formview.render(); + }, + + // todo: dry this crap against other items + canEdit: function() { return CommonPlace.account.canEditEvent(this.model); }, + + isMore: function() { + return !this.allwords; + }, + + loadMore: function(e) { + e.preventDefault(); + this.allwords = true; + this.render(); + }, + + showProfile: function(e) { + window.infoBox.showProfile(this.model.author()); + } + +}); diff --git a/app/javascripts/wires/items/feed.js b/app/javascripts/wires/items/feed.js new file mode 100644 index 000000000..38f1cc910 --- /dev/null +++ b/app/javascripts/wires/items/feed.js @@ -0,0 +1,30 @@ +// this is line-for-line the same as group_item. todo: DRY +var FeedWireItem = WireItem.extend({ + template: "wires/items/feed-tpl", + tagName: "li", + className: "wire-item feed", + + initialize: function() { + CommonPlace.account.bind("change", this.render, this); + this.attr_accessible(['name', 'url', 'avatar_url']); + }, + + events: { + "mouseenter": "showProfile", + "click button.subscribe": "subscribe", + "click button.unsubscribe": "unsubscribe" + }, + + showProfile: function(callback) { + window.infoBox.showProfile(this.model); + }, + + subscribe: function() { CommonPlace.account.subscribeToFeed(this.model); }, + + unsubscribe: function() { CommonPlace.account.unsubscribeFromFeed(this.model); }, + + isSubscribed: function() { return CommonPlace.account.isSubscribedToFeed(this.model); } + +}); + + diff --git a/app/javascripts/wires/items/group.js b/app/javascripts/wires/items/group.js new file mode 100644 index 000000000..4599483dc --- /dev/null +++ b/app/javascripts/wires/items/group.js @@ -0,0 +1,29 @@ +var GroupWireItem = WireItem.extend({ + // this is line-for-line the same as feed_item. todo: DRY + + template: "wires/items/feed-tpl", + tagName: "li", + className: "wire-item feed", + + initialize: function() { + CommonPlace.account.bind("change", this.render, this); + this.attr_accessible(['name', 'url', 'avatar_url']); + }, + + events: { + "mouseenter": "showProfile", + "click button.subscribe": "subscribe", + "click button.unsubscribe": "unsubscribe" + }, + + showProfile: function(e) { + window.infoBox.showProfile(this.model); + }, + + subscribe: function() { CommonPlace.account.subscribeToGroup(this.model); }, + + unsubscribe: function() { CommonPlace.account.unsubscribeFromGroup(this.model); }, + + isSubscribed: function() { return CommonPlace.account.isSubscribedToGroup(this.model); } + +}); diff --git a/app/javascripts/wires/items/group_post.js b/app/javascripts/wires/items/group_post.js new file mode 100644 index 000000000..a87349c4c --- /dev/null +++ b/app/javascripts/wires/items/group_post.js @@ -0,0 +1,94 @@ +var GroupPostWireItem = WireItem.extend({ + template: "wires/items/post-tpl", + tagName: "li", + className: "wire-item", + + initialize: function(options) { + var self = this; + this.model.bind("destroy", function() { self.remove(); }); + }, + + afterRender: function() { + var repliesView = new RepliesView({ collection: this.model.replies(), + el: this.$(".replies") + }); + repliesView.render(); + this.model.bind("change", this.render, this); + var self = this; + repliesView.collection.bind("add", function() { self.render(); }); + this.$(".post-body").truncate({max_length: 450}); + }, + + publishedAt: function() { + return timeAgoInWords(this.model.get("published_at")); + }, + + avatarUrl: function() { + return this.model.get("avatar_url"); + }, + + title: function() { + return this.model.get("title"); + }, + + author: function() { + return this.model.get("author"); + }, + + body: function() { + return this.model.get("body"); + }, + + events: { + "click div.group-post > .author": "messageUser", + "click .moreBody": "loadMore", + "mouseenter": "showProfile", + "click .editlink": "editGroupPost" + }, + + messageUser: function(e) { + e && e.preventDefault(); + var user = new User({ + links: { + self: this.model.get("links").author + } + }); + user.fetch({ + success: function() { + var formview = new MessageFormView({ + model: new Message({messagable: user}) + }); + formview.render(); + } + }); + }, + + isMore: function() { + return !this.allwords; + }, + + loadMore: function(e) { + e.preventDefault(); + this.allwords = true; + this.render(); + }, + + showProfile: function(e) { + var group = new Group({ + links: { self: this.model.link("group") } + }); + window.infoBox.showProfile(group); + }, + + canEdit: function() { return CommonPlace.account.canEditGroupPost(this.model); }, + + editGroupPost: function(e) { + e && e.preventDefault(); + var formview = new PostFormView({ + model: this.model, + template: "shared/group-post-edit-form" + }); + formview.render(); + } + +}); diff --git a/app/javascripts/wires/items/post.js b/app/javascripts/wires/items/post.js new file mode 100644 index 000000000..faf2aaa49 --- /dev/null +++ b/app/javascripts/wires/items/post.js @@ -0,0 +1,94 @@ + +var PostWireItem = WireItem.extend({ + template: "wires/items/post-tpl", + tagName: "li", + className: "wire-item", + + initialize: function(options) { + var self = this; + this.model.bind("destroy", function() { self.remove(); }); + }, + + afterRender: function() { + var repliesView = new RepliesView({ collection: this.model.replies(), + el: this.$(".replies") + }); + repliesView.render(); + this.model.bind("change", this.render, this); + var self = this; + repliesView.collection.bind("add", function() { self.render(); }); + this.$(".post-body").truncate({max_length: 450}); + }, + + publishedAt: function() { + return timeAgoInWords(this.model.get("published_at")); + }, + + avatarUrl: function() { + return this.model.get("avatar_url"); + }, + + title: function() { + return this.model.get("title"); + }, + + author: function() { + return this.model.get("author"); + }, + + body: function() { + return this.model.get("body"); + }, + + events: { + "click div.group-post > .author": "messageUser", + "click .editlink": "editPost", + "mouseenter": "showProfile" + }, + + messageUser: function(e) { + e && e.preventDefault(); + var user = new User({ + links: { + self: this.model.get("links").author + } + }); + user.fetch({ + success: function() { + var formview = new MessageFormView({ + model: new Message({messagable: user}) + }); + formview.render(); + } + }); + }, + + isMore: function() { + return !this.allwords; + }, + + loadMore: function(e) { + e.preventDefault(); + this.allwords = true; + this.render(); + }, + + showProfile: function(e) { + var user = new User({ + links: { self: this.model.link("author") } + }); + window.infoBox.showProfile(user); + }, + + canEdit: function() { return CommonPlace.account.canEditPost(this.model); }, + + editPost: function(e) { + e.preventDefault(); + var formview = new PostFormView({ + model: this.model, + template: "shared/post-edit-form" + }); + formview.render(); + } + +}); diff --git a/app/javascripts/wires/items/reply.js b/app/javascripts/wires/items/reply.js new file mode 100644 index 000000000..c73d8b895 --- /dev/null +++ b/app/javascripts/wires/items/reply.js @@ -0,0 +1,67 @@ + +var ReplyWireItem = WireItem.extend({ + template: "wires/items/reply-tpl", + tagName: 'li', + className: 'reply-item', + + initialize: function(options) { + this.model = options.model; + }, + + afterRender: function() { + this.$(".reply-body").truncate({max_length: 450}); + }, + + events: { + "click .reply-text > .author": "messageUser", + "mouseenter": "showProfile", + "click .delete-reply": "deleteReply" + }, + + time: function() { + return timeAgoInWords(this.model.get("published_at")); + }, + + author: function() { + return this.model.get("author"); + }, + + authorAvatarUrl: function() { + return this.model.get("avatar_url"); + }, + + body: function() { + return this.model.get("body"); + }, + + messageUser: function(e) { + if (e) { e.preventDefault(); } + + if (this.model.get("author_id") != CommonPlace.account.id) { + this.model.user(function(user) { + var formview = new MessageFormView({ + model: new Message({messagable: user}) + }); + formview.render(); + }); + } + }, + + showProfile: function(e) { + var user = new User({ + links: { self: this.model.link("author") } + }); + window.infoBox.showProfile(user); + }, + + canEdit: function() { + return CommonPlace.account.canEditReply(this.model); + }, + + deleteReply: function(e) { + e.preventDefault(); + var self = this; + window.f = this.model; + this.model.destroy(); + } +}); diff --git a/app/javascripts/wires/items/user.js b/app/javascripts/wires/items/user.js new file mode 100644 index 000000000..d778e04af --- /dev/null +++ b/app/javascripts/wires/items/user.js @@ -0,0 +1,32 @@ +var UserWireItem = WireItem.extend({ + template: "wires/items/user-tpl", + tagName: "li", + className: "wire-item group-member", + + initialize: function(options) { + this.attr_accessible(['first_name', 'last_name', 'avatar_url']); + }, + + afterRender: function() { + this.model.bind("change", this.render, this); + }, + + + events: { + "click button": "messageUser", + "mouseenter": "showProfile" + }, + + messageUser: function(e) { + e && e.preventDefault(); + var formview = new MessageFormView({ + model: new Message({messagable: this.model}) + }); + formview.render(); + }, + + showProfile: function(e) { + window.infoBox.showProfile(this.model); + } + +}); diff --git a/app/javascripts/wires/paginating_wire.js.coffee b/app/javascripts/wires/paginating_wire.js.coffee new file mode 100644 index 000000000..9afbedf97 --- /dev/null +++ b/app/javascripts/wires/paginating_wire.js.coffee @@ -0,0 +1,26 @@ +class window.PaginatingWire extends window.Wire + constructor: (options) -> + super(options) + @options = options + + events: { + "click a.more": "showMore" + } + + _defaultPerPage: 10, + + allFetched: () -> + #currently, only the latest page is stored in the collection. this maybe should change. + (@collection.length < @perPage()) + + showMore: (event) -> + event.preventDefault() + @scope['page'] += 1 + + # this is a bit of a hack. It reuses code to add li elements to the ul, without re-rendering the wire. + @aroundRender () => + @afterRender() + if(@allFetched()) + this.$('.more').hide() + + diff --git a/app/javascripts/wires/zother_wires.js.coffee b/app/javascripts/wires/zother_wires.js.coffee new file mode 100644 index 000000000..8f1df0352 --- /dev/null +++ b/app/javascripts/wires/zother_wires.js.coffee @@ -0,0 +1,93 @@ +# currently files are included alphabetically. This could be changed. +# but perhaps its nice to ensure include order just the same as an IDE view. + +class window.WireHeader extends CommonPlace.View + constructor: (options) -> + super(options) + @options = options + + template: 'wires.header' + + events:{ + "click .sub-navigation": "loadCurrent" + } + + afterRender: () -> + # bind events so that form can be moved in the dom. + @options.$searchForm = @$("form.search") + @options.submiter = _.debounce(() => + @options.$searchForm.submit() + , CommonPlace.autoActionTimeout) + + @$('.cancelSearch').bind 'click', @cancelSearch + @options.$searchForm + .bind('keyup', @autoSearch) # in coffee, be sure to use prens when chaining + .bind('submit', @search) + + autoSearch: () => + if $('input', @options.$searchForm) == '' + $('.cancelSearch', @options.$searchForm).hide() + else + $('.cancelSearch', @options.$searchForm).show() + @options.submiter.apply(this) + + search: () => # don't require a passed event + $(".wire").trigger('search',{query: $('form.search input').val()}) + return false + + cancelSearch: (event) => + event.preventDefault() + $('form.search input').val('').focus() + $('.cancelSearch', @options.$searchForm).hide() + @search() + + isSearchEnabled: () -> + @isActive('wireSearch') && @options.search + + loadCurrent: (event) => + window.location = $('a.current', this.el).attr('href') + + stopPropagation: (event) -> + event.stopPropagation() + + text: () -> + @options.text + + link: () -> + @options.link + + + + + +class window.PreviewWire extends window.Wire + constructor: (options) -> + super(options) + @options = options + + _defaultPerPage: 3 + + fullWireLink: () -> + @options.fullWireLink + + +class window.PaginatingResourceWire extends window.PaginatingWire + # the usefulness of this class is questionable. + constructor: (options) -> + super(options) + _defaultPerPage: 15 + + +class window.ResourceWire extends window.Wire + constructor: (options) -> + super(options) + @options = options + + usersLinkClass: () -> + @options.active == 'users' ? 'current' : '' + + feedsLinkClass: () -> + @options.active == 'feeds' ? 'current' : '' + + groupsLinkClass: () -> + @options.active == 'groups' ? 'current' : '' \ No newline at end of file diff --git a/app/models/ability.rb b/app/models/ability.rb index d46d3a890..e0400bfcf 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -8,20 +8,24 @@ def initialize(user) alias_action(:neighborhood, :subscribed, :your, :suggested, :neighbors, :business, :municipal, :to => :read) - if user.new_record? - can :create, User - can :create, UserSession - else + if user.present? && !user.new_record? + can :read, Message do |m| + m.user == user || m.messagable == user + end can :create, Announcement can :create, Event can :update, User can :read, User can :create, Post can :destroy, Post, :user_id => user.id - can :destroy, UserSession + can :update, Post, :user_id => user.id + can :create, GroupPost + can :destroy, GroupPost, :user_id => user.id + can :update, GroupPost, :user_id => user.id can :read, Reply can :create, Reply can :read, Post + can :read, GroupPost can :read, User can :read, Announcement can :read, Event @@ -29,7 +33,24 @@ def initialize(user) can :profile, Feed can :create, Feed can :manage, Feed, :user_id => user.id - can :read, ActsAsTaggableOn::Tag + + can :manage, Event do |e| + e.user == user + end + + can :manage, Announcement do |a| + case a.owner + when Feed then a.owner.user_id == user.id + when User then a.owner.id == user.id + else false + end + end + + if user.admin? + can :notify_all, Post + can :destroy, Post + end + end can :read, Community end diff --git a/app/models/account.rb b/app/models/account.rb new file mode 100644 index 000000000..9fb6e9ece --- /dev/null +++ b/app/models/account.rb @@ -0,0 +1,76 @@ +class Account + def initialize(user) + @user = user + end + + attr_accessor :user + + def feed_subscriptions + @user.subscriptions.map &:feed_id + end + + def mets + @user.mets.map &:wanted_id + end + + def group_subscriptions + @user.memberships.map &:group_id + end + + def avatar_url(style=nil) + @user.avatar_url(style) + end + + def id + @user.id + end + + def is_admin + @user.admin? + end + + def accounts + [@user] + @user.managable_feeds + end + + def short_name + @user.first_name + end + + def email + @user.email + end + + def posts + @user.posts.map &:id + end + + def events + @user.direct_event_ids + @user.managable_feeds.flat_map {|f| f.event_ids } + end + + def announcements + @user.announcement_ids + @user.managable_feeds.flat_map {|f| f.announcement_ids } + end + + def group_posts + GroupPost.find_all_by_user_id(@user.id).map &:id + end + + def neighborhood + @user.neighborhood_id + end + + def feeds + @user.managable_feeds.map do |feed| + {"name" => feed.name, + "id" => feed.id, + "slug" => feed.slug.blank? ? feed.id : feed.slug + } + end + end + + def method_missing(method, *args) + @user.send(method, *args) + end +end diff --git a/app/models/address.rb b/app/models/address.rb deleted file mode 100644 index cc9640cb4..000000000 --- a/app/models/address.rb +++ /dev/null @@ -1,4 +0,0 @@ -class Address < ActiveRecord::Base - validates_uniqueness_of :name - -end diff --git a/app/models/admin_user.rb b/app/models/admin_user.rb new file mode 100644 index 000000000..039464fad --- /dev/null +++ b/app/models/admin_user.rb @@ -0,0 +1,9 @@ +class AdminUser < ActiveRecord::Base + # Include default devise modules. Others available are: + # :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable + devise :database_authenticatable, + :recoverable, :rememberable, :trackable, :validatable + + # Setup accessible (or protected) attributes for your model + attr_accessible :email, :password, :password_confirmation, :remember_me +end diff --git a/app/models/announcement.rb b/app/models/announcement.rb index bee209a2c..4b3057590 100644 --- a/app/models/announcement.rb +++ b/app/models/announcement.rb @@ -1,18 +1,52 @@ class Announcement < ActiveRecord::Base - - require "lib/helper" + #track_on_creation - has_many :replies, :as => :repliable + + has_many :replies, :as => :repliable, :order => :created_at has_many :repliers, :through => :replies, :uniq => true, :source => :user - belongs_to :feed - validates_presence_of :subject, :body, :feed - - def time - help.post_date(self.created_at) - end - - def owner - self.feed + belongs_to :owner, :polymorphic => true + belongs_to :community + + has_many :announcement_cross_postings + has_many :groups, :through => :announcement_cross_postings + + validates_presence_of :subject, :body + + scope :between, lambda { |start_date, end_date| + { :conditions => + ["? <= announcements.created_at AND announcements.created_at < ?", start_date, end_date] } + } + scope :up_to, lambda { |end_date| { :conditions => ["announcements.created_at <= ?", end_date.utc] } } + + scope :created_on, lambda { |date| { :conditions => ["announcements.created_at between ? and ?", date.utc.beginning_of_day, date.utc.end_of_day] } } + + scope :today, :conditions => ["announcements.created_at between ? and ?", DateTime.now.at_beginning_of_day, DateTime.now] + + default_scope where(:deleted_at => nil) + + def feed + self.owner + end + + def user + if owner.is_a? Feed + owner.user + else + owner + end + end + + def user_id + user.id + end + + searchable do + text :subject, :body + text :replies do + replies.map &:body + end + integer :community_id + time :created_at end end diff --git a/app/models/announcement_cross_posting.rb b/app/models/announcement_cross_posting.rb new file mode 100644 index 000000000..de4627157 --- /dev/null +++ b/app/models/announcement_cross_posting.rb @@ -0,0 +1,6 @@ +class AnnouncementCrossPosting < ActiveRecord::Base + + belongs_to :announcement + belongs_to :group + +end diff --git a/app/models/avatar.rb b/app/models/avatar.rb deleted file mode 100644 index c79e3f602..000000000 --- a/app/models/avatar.rb +++ /dev/null @@ -1,56 +0,0 @@ -class Avatar < ActiveRecord::Base - - require 'open-uri' - - has_attached_file(:image, - :styles => { - :thumb => "100x100^", - :normal => "120x120^", - :large => "200x200^" - }, - :default_url => "/avatars/missing.png", - :processors => [:cropper], - :url => "/system/avatar/:id/:style.:extension", - :path => ":rails_root/public/system/avatar/:id/:style.:extension" ) - belongs_to :owner, :polymorphic => true - - after_update :reprocess_image, :if => :cropping? - - attr_accessor :image_url - - after_create :download_remote_image, :if => :image_url_provided? - - validates_presence_of :avatar_remote_url, :if => :image_url_provided?, :message => 'is invalid or inaccessible' - - attr_accessor :x, :y, :w, :h - - def cropping? - [x,y,w,h].all? {|b| !b.blank?} - end - - def image_geometry(style = :original) - @geometry ||= {} - @geometry[style] ||= Paperclip::Geometry.from_file(image.path(style)) - end - - def image_url_provided? - !self.avatar_remote_url.blank? - end - - def download_remote_image - self.image = do_download_remote_image - self.avatar_remote_url = image_url - end - - def do_download_remote_image - io = open(URI.parse(self.avatar_remote_url)) - def io.original_filename; base_uri.path.split('/').last; end - io.original_filename.blank? ? nil : io - end - - private - def reprocess_image - image.reprocess! - end - -end diff --git a/app/models/community.rb b/app/models/community.rb index 7ddb0961c..ae00133d1 100644 --- a/app/models/community.rb +++ b/app/models/community.rb @@ -1,8 +1,27 @@ class Community < ActiveRecord::Base has_many :feeds has_many :neighborhoods, :order => :created_at - has_many :announcements, :through => :feeds - validates_presence_of :name, :slug, :zip_code + has_many(:announcements, + :order => "announcements.created_at DESC", + :include => [:replies]) + has_many(:events, + :order => "events.date ASC", + :include => [:replies]) + + has_many :users, :order => "last_name, first_name" + def organizers + self.users.select { |u| u.admin } + end + + has_many :groups + + has_many(:posts, + :order => "posts.updated_at DESC", + :include => [:user, {:replies => :user}]) + + before_destroy :ensure_marked_for_deletion + + validates_presence_of :name, :slug accepts_nested_attributes_for :neighborhoods @@ -13,25 +32,177 @@ class Community < ActiveRecord::Base has_attached_file(:email_header, :url => "/system/community/:id/email_header.:extension", - :path => ":rails_root/public/system/community/:id/email_headero.:extension") + :path => ":rails_root/public/system/community/:id/email_header.:extension") - def posts - neighborhoods.map(&:posts).flatten + has_attached_file(:organizer_avatar, + :url => "/system/community/:id/organizer_avatar.:extension", + :path => ":rails_root/public/system/community/:id/organizer_avatar.:extension", + :default_url => "/avatars/missing.png") + + acts_as_api + + api_accessible :default do |t| + t.add :id + t.add :name + t.add :locale + t.add :groups + t.add :organizer_name, :as => :admin_name + t.add :organizer_email, :as => :admin_email + t.add :links + end + + def links + community_asset_url = "https://s3.amazonaws.com/commonplace-community-assets/#{slug}/" + { + "launch_letter" => community_asset_url + "launchletter.pdf", + "information_sheet" => community_asset_url + "infosheet.pdf", + "neighborhood_flyer" => community_asset_url + "neighborflyer.pdf", + "all_flyers" => community_asset_url + "archives.zip", + "groups" => "/communities/#{id}/groups", + "feeds" => "/communities/#{id}/feeds", + "posts" => "/communities/#{id}/posts", + "events" => "/communities/#{id}/events", + "announcements" => "/communities/#{id}/announcements", + "group_posts" => "/communities/#{id}/group_posts", + "users" => "/communities/#{id}/users", + "self" => "/communities/#{id}", + "feeds_search" => "/search/community/#{id}/feeds?query=", + "users_search" => "/search/community/#{id}/users?query=", + "groups_search" => "/search/community/#{id}/groups?query=", + "posts_search" => "/search/community/#{id}/posts?query=" + } end def self.find_by_name(name) - find(:first, :conditions => ["LOWER(name) = ?", name.downcase]) + where("LOWER(name) = ?", name.downcase).first end def self.find_by_slug(slug) - find(:first, :conditions => ["LOWER(slug) = ?", slug.downcase]) + where("LOWER(slug) = ?", slug.downcase).first + end + + def ensure_marked_for_deletion + raise "Can not destroy community" unless self.should_delete + end + + def neighborhood_for(address) + if self.is_college + self.neighborhoods.select { |n| n.name == address } + else + default = self.neighborhoods.first + if position = LatLng.from_address(address, self.zip_code) + self.neighborhoods.to_a.find(lambda { default }) do |n| + n.contains?(position) + end + else + default + end + end + end + + def add_default_groups + I18n.t("default_groups").each do |group| + Group.create(:community => self, + :name => group['name'], + :about => group['about'].gsub("%{community_name}", self.name), + :avatar_url => group['avatar'], + :slug => group['slug']) + end + nil + end + + # Convenience accessors for some mapped values + def group_posts + self.groups.map(&:group_posts).flatten + end + + def group_posts_today + group_posts.select { |post| post.created_at > DateTime.now.at_beginning_of_day and post.created_at < DateTime.now } + end + + def private_messages + self.users.map(&:messages).flatten + end + + def private_messages_today + private_messages.select { |message| message.created_at > DateTime.now.at_beginning_of_day and message.created_at < DateTime.now } end - def users - neighborhoods.map(&:users).flatten + def completed_registrations + # A registration is complete if the user has updated their data after the initial creation (incl. setting a password) + User.where("created_at < updated_at AND community_id = ?", self.id) end - def events - (users.map{|u|u.direct_events.upcoming} + feeds.map{|f|f.events.upcoming}).flatten.sort_by(&:start_datetime) + def incomplete_registrations + User.where("created_at >= updated_at AND community_id = ?", self.id) end + + def c(model) + #count = 0 + #model.all.select { |o| o.owner.community_id == self.id }.each { |o| count += 1 } + #count + model.find(:all, :conditions => {:community_id => self.id }).count + end + + def c_today(model) + model.find(:all, :conditions => ["created_at between ? and ? AND community_id = ?", Date.today, DateTime.now, self.id]).count + end + + def registrations_since_n_days_ago(days) + registrations = [] + for i in (1..days) + registrations.push(self.users.up_to(i.days.ago).count) + end + registrations.reverse + end + + def since_n_days_ago(days,set,polymorphic=false) + items = [] + for i in (1..days) + if polymorphic + items.push(set.created_on(i.days.ago).to_a.count) + else + items.push(set.created_on(i.days.ago).count) + end + end + items.reverse + end + + def private_messages_since_n_days_ago(day) + items = [] + for i in (1..day) + items.push(self.private_messages.select { |m| m.created_at >= day.days.ago.beginning_of_day and m.created_at <= day.days.ago.end_of_day } ) + end + items.reverse + end + + def to_param + slug + end + + def locale + (self.is_college) ? :college : :en + end + + def posts_for_user(user) + if user.community.is_college + Post.includes(:user).where(:users => { :neighborhood_id => user.neighborhood_id}) + user.neighborhood.posts + else + user.community.posts + end + end + + def launch_date + self.read_attribute(:launch_date) || self.created_at + end + + def has_launched? + self.launch_date < DateTime.now + end + + def user_count + self.users.count + end + end diff --git a/app/models/event.rb b/app/models/event.rb index 038522405..4c112ed03 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -1,25 +1,44 @@ class Event < ActiveRecord::Base + #track_on_creation - require 'lib/helper' attr_accessor :pledge - - acts_as_taggable_on :tags validates_presence_of :name, :description, :date validates_uniqueness_of :source_feed_id, :if => Proc.new { |event| event.owner_type == "Feed" && event.source_feed_id } has_many :referrals - has_many :replies, :as => :repliable + has_many :replies, :as => :repliable, :order => :created_at has_many :repliers, :through => :replies, :uniq => true, :source => :user has_many :attendances has_many :attendees, :through => :attendances, :source => :user belongs_to :owner, :polymorphic => true + belongs_to :community has_many :invites, :as => :inviter - named_scope :upcoming, :conditions => ["? <= date", Time.now.beginning_of_day] - named_scope :past, :conditions => ["date < ?", Time.now] + has_many :event_cross_postings + has_many :groups, :through => :event_cross_postings + + scope :upcoming, lambda { { :conditions => ["? <= events.date", Time.now.beginning_of_day.utc] } } + scope :between, lambda { |start_date, end_date| + { :conditions => ["? <= events.date AND events.date < ?", start_date, end_date] } + } + scope :past, :conditions => ["events.date < ?", Time.now.utc] + scope :today, :conditions => ["events.created_at between ? and ?", DateTime.now.at_beginning_of_day, DateTime.now] + scope :up_to, lambda { |end_date| { :conditions => ["events.created_at <= ?", end_date.utc] } } + + scope :created_on, lambda { |date| { :conditions => ["events.created_at between ? and ?", date.utc.beginning_of_day, date.utc.end_of_day] } } + + default_scope where(:deleted_at => nil) + + def tag_list + self.cached_tag_list + end + + def tag_list=(string) + self.cached_tag_list= string + end def search(term) Event.all @@ -28,6 +47,10 @@ def search(term) def time date.strftime("%b %d") end + + def occurs_at + DateTime.strptime("#{self.date.strftime("%Y-%m-%d")}T00:00:00#{Time.zone.formatted_offset}") + end def start_datetime date.to_time + start_time.hour.hours + start_time.min.minutes @@ -40,5 +63,34 @@ def subject def body self.description end + + def user + case owner + when User then owner + when Feed then owner.user + end + end + + def user_id + user.id + end + + def long_id + IDEncoder.to_long_id(self.id) + end + + def self.find_by_long_id(long_id) + Event.find(IDEncoder.from_long_id(long_id)) + end + + searchable do + text :name, :description, :venue, :address + text :replies do + replies.map &:body + end + time :date + integer :community_id + time :created_at + end end diff --git a/app/models/event_cross_posting.rb b/app/models/event_cross_posting.rb new file mode 100644 index 000000000..0ce0ec896 --- /dev/null +++ b/app/models/event_cross_posting.rb @@ -0,0 +1,6 @@ +class EventCrossPosting < ActiveRecord::Base + + belongs_to :event + belongs_to :group + +end diff --git a/app/models/feed.rb b/app/models/feed.rb index 5065813de..598630a4b 100644 --- a/app/models/feed.rb +++ b/app/models/feed.rb @@ -1,62 +1,203 @@ class Feed < ActiveRecord::Base - - acts_as_taggable_on :tags + #track_on_creation - validates_presence_of :name, :community, :about + validates_presence_of :name, :community + + validates_attachment_presence :avatar validates_uniqueness_of :slug, :scope => :community_id, :allow_nil => true - before_create :generate_slug + before_validation(:on => :create) do + if self.slug? + sanitize_slug + else + generate_slug + end + true + end belongs_to :community + has_many :feed_owners + has_many :owners, :through => :feed_owners, :class_name => "User", :source => :user belongs_to :user - has_many :events, :dependent => :destroy, :as => :owner + has_many :events, :dependent => :destroy, :as => :owner, :include => :replies - has_many :announcements, :dependent => :destroy - - has_many :profile_fields, :order => "position" + has_many :announcements, :dependent => :destroy, :as => :owner, :include => :replies has_many :subscriptions, :dependent => :destroy - has_many :subscribers, :through => :subscriptions, :source => :user + has_many :subscribers, :through => :subscriptions, :source => :user, :uniq => true + + acts_as_api + + api_accessible :default do |t| + t.add :id + t.add lambda {|f| "feeds"}, :as => :schema + t.add :slug + t.add :user_id + t.add lambda {|f| "/pages/#{f.slug}"}, :as => :url + t.add :name + t.add :about + t.add lambda {|f| f.avatar_url(:normal)}, :as => :avatar_url + t.add lambda {|f| "/feeds/#{f.id}/profile"}, :as => :profile_url + t.add :rss_url, :as => :feed_url + t.add lambda {|f| "/feeds/#{f.id}/delete"}, :as => :delete_url + t.add :tag_list, :as => :tags + t.add :website + t.add :phone + t.add :address + t.add :kind + t.add lambda {|f| "/feeds/#{f.id}/#{f.user_id}"}, :as => :messagable_author_url + t.add lambda {|f| f.name}, :as => :messagable_author_name + t.add :links + end + + def links + { + "avatar" => { + "large" => self.avatar_url(:large), + "normal" => self.avatar_url(:normal), + "thumb" => self.avatar_url(:thumb) + }, + "announcements" => "/feeds/#{id}/announcements", + "events" => "/feeds/#{id}/events", + "invites" => "/feeds/#{id}/invites", + "messages" => "/feeds/#{id}/messages", + "edit" => "/feeds/#{id}/edit", + "subscribers" => "/feeds/#{id}/subscribers", + "self" => "/feeds/#{id}", + "owners" => "/feeds/#{id}/owners" + } + end + + def live_subscribers + self.subscriptions.all(:conditions => "receive_method = 'Live'").map &:user + end + include CroppableAvatar has_attached_file(:avatar, - :styles => { - :thumb => "100x100^", - :normal => "120x120^", - :large => "200x200^" - }, - :default_url => "/avatars/missing.png", - :url => "/system/feeds/:id/avatar/:style.:extension", - :path => ":rails_root/public/system/feeds/:id/avatar/:style.:extension") + { :styles => { + :thumb => {:geometry => "100x100", :processors => [:cropper]}, + :normal => {:geometry => "120x120", :processors => [:cropper]}, + :large => {:geometry => "200x200", :processors => [:cropper]}, + :original => "1000x1000>" + }, + :default_url => "https://s3.amazonaws.com/commonplace-avatars-production/missing.png" + }.merge(Rails.env.development? || Rails.env.test? ? + { :path => ":rails_root/public/system/feeds/:id/avatar/:style.:extension", + :storage => :filesystem, + :url => "/system/feeds/:id/avatar/:style.:extension" + } : { + :storage => Rails.env.development? ? :filesystem : :s3, + :s3_protocol => "https", + :bucket => "commonplace-avatars-#{Rails.env}", + :path => "/feeds/:id/avatar/:style.:extension", + :s3_credentials => { + :access_key_id => ENV['S3_KEY_ID'], + :secret_access_key => ENV['S3_KEY_SECRET'] + } + })) + + + def tag_list + self.cached_tag_list + end + + def tag_list=(string) + self.cached_tag_list = string + end + + def website=(string) + if string.present? && !(string =~ /^https?:\/\//) + super("http://" + string) + else + super(string) + end + end + + def avatar_url(style_name = nil) + self.avatar.url(style_name || self.avatar.default_style) + end - accepts_nested_attributes_for :profile_fields def wire (self.announcements + self.events).sort_by do |item| time = case item - when Event then item.start_datetime + when Event then item.created_at when Announcement then item.created_at end (time - Time.now).abs end end + def is_news + self.kind == 4 + end + + def self.kinds + feed_kinds = ActiveSupport::OrderedHash.new + feed_kinds["A non-profit"] = 0 + feed_kinds["A community group"] = 1 + feed_kinds["A business"] = 2 + feed_kinds["A municipal entity"] = 3 + feed_kinds["A newspaper, news service, or news blog"] = 4 + feed_kinds["Other"] = 5 + feed_kinds + end + + def slug + read_attribute(:slug).blank? ? id : read_attribute(:slug) + end + + def messages + Message.where("messagable_type = 'Feed' AND messagable_id = ?", self.id) + end + + searchable do + text :name + text :about + integer :community_id + end + + def get_feed_owner(user) + owner = self.feed_owners.select { |o| o.user == user } + if (owner.empty?) + false + else + owner + end + end + + def rss_feed + RSSFeed.new(self) + end + + private + def sanitize_slug + string = self.slug.split("@").first + string.gsub!(/[']+/, '') + string.gsub!(/\W+/, ' ') + string.gsub!(/\W+/, ' ') + string.strip! + string.downcase! + string.gsub!(' ', '-') + self.slug = string + end + def generate_slug string = self.name.mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/n, '').to_s string.gsub!(/[']+/, '') string.gsub!(/\W+/, ' ') + string.gsub!(/\W+/, ' ') string.strip! string.downcase! string.gsub!(' ', '-') - test_string = string - i = 1 - while Feed.exists?(:slug => test_string, :community_id => self.community.id) - test_string = "#{string}-#{i}" - i += 1 + if Feed.exists?(:slug => string, :community_id => self.community_id) + self.slug = nil + else + self.slug = string end - self.slug = test_string end end diff --git a/app/models/feed_owner.rb b/app/models/feed_owner.rb new file mode 100644 index 000000000..3a3171c1c --- /dev/null +++ b/app/models/feed_owner.rb @@ -0,0 +1,7 @@ +class FeedOwner < ActiveRecord::Base + + belongs_to :feed + + belongs_to :user + +end diff --git a/app/models/feed_registration.rb b/app/models/feed_registration.rb new file mode 100644 index 000000000..a9a40d4b9 --- /dev/null +++ b/app/models/feed_registration.rb @@ -0,0 +1,53 @@ +class FeedRegistration + + def initialize(feed) + @feed = feed + end + + attr_reader :feed + + def self.find_or_create(options) + feed = Feed.find_by_id(options[:id]) || + Feed.new(:community => options[:community], :user => options[:user]) + + new(feed) + end + + def attributes=(params) + feed.attributes=(params) + end + + def update_attributes(params) + feed.update_attributes(params) + end + + def save + if feed.save + feed.owners << feed.user + kickoff.deliver_feed_owner_welcome(feed) + true + else + false + end + end + + def invite_subscribers(emails) + kickoff.deliver_feed_invite(emails, feed) + end + + def to_param + feed.id.to_s + end + + def avatar_url(style = nil) + feed.avatar_url(style) + end + + def has_avatar? + feed.avatar? + end + + def kickoff + @kickoff || KickOff.new + end +end diff --git a/app/models/group.rb b/app/models/group.rb new file mode 100644 index 000000000..7787fade8 --- /dev/null +++ b/app/models/group.rb @@ -0,0 +1,91 @@ +class Group < ActiveRecord::Base + #track_on_creation + + validates_presence_of :name, :slug, :about, :community + + before_validation(:on => :create) do + generate_slug unless self.slug? + true + end + + + belongs_to :community + + has_many :group_posts + + has_many :memberships + has_many :subscribers, :through => :memberships, :source => :user, :uniq => true + + has_many :event_cross_postings + has_many :events, :through => :event_cross_postings + + has_many :announcement_cross_postings + has_many :announcements, :through => :announcement_cross_postings + + acts_as_api + + api_accessible :default do |t| + t.add :id + t.add lambda {|g| "groups"}, :as => :schema + t.add lambda {|g| "/groups/#{g.id}"}, :as => :url + t.add :name + t.add :about + t.add :avatar_url + t.add :slug + t.add :links + end + + def links + { + "posts" => "/groups/#{id}/posts", + "members" => "/groups/#{id}/members", + "announcements" => "/groups/#{id}/announcements", + "events" => "/groups/#{id}/events", + "self" => "/groups/#{id}" + } + end + + def avatar_url=(url) + self.avatar_file_name = url + end + + def avatar_url(style_name = nil) + if Rails.env.development? + self.avatar_file_name || "/avatars/missing.png" + else + "https://s3.amazonaws.com/commonplace-avatars-#{Rails.env}/groups/#{self.slug}.png" + end + end + + def live_subscribers + self.memberships.all(:conditions => "memberships.receive_method = 'Live'").map &:user + end + + def self.find_by_slug(slug) + Group.where(slug: slug).first || Group.find_by_id(slug) + end + + private + + def generate_slug + string = self.name.mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/n, '').to_s + string.gsub!(/[']+/, '') + string.gsub!(/\W+/, ' ') + string.strip! + string.downcase! + string.gsub!(' ', '-') + + if Feed.exists?(:slug => string, :community_id => self.community_id) + self.slug = nil + else + self.slug = string + end + end + + searchable do + text :name + text :about + integer :community_id + end + +end diff --git a/app/models/group_post.rb b/app/models/group_post.rb new file mode 100644 index 000000000..f634f9915 --- /dev/null +++ b/app/models/group_post.rb @@ -0,0 +1,44 @@ +class GroupPost < ActiveRecord::Base + #track_on_creation + + belongs_to :user + belongs_to :group + + has_many :replies, :as => :repliable, :order => :created_at + has_many :repliers, :through => :replies, :uniq => true, :source => :user + + validates_presence_of :subject, :message => "Please enter a subject for your post" + validates_presence_of :body, :message => "Please enter some text for your post" + + scope :today, where("group_posts.created_at between ? and ?", Date.today, Time.now) + + scope :between, lambda { |start_date, end_date| { :conditions => ["? <= created_at AND created_at < ?", start_date.utc, end_date.utc] } } + + default_scope where(:deleted_at => nil) + + def owner + self.user + end + + def community_id + self.user.community_id + end + + def community + self.user.community + end + + def between?(start_date, end_date) + start_date <= self.created_at and self.created_at <= end_date + end + + searchable do + text :subject, :body + text :replies do + replies.map &:body + end + integer :community_id + time :created_at + end + +end diff --git a/app/models/half_user.rb b/app/models/half_user.rb new file mode 100644 index 000000000..65244b0cf --- /dev/null +++ b/app/models/half_user.rb @@ -0,0 +1,18 @@ +class HalfUser < ActiveRecord::Base + #track_on_creation + + belongs_to :community + validates_presence_of :email + + def full_name + [first_name, middle_name, last_name].select(&:present?).join(" ") + end + + def full_name=(string) + split_name = string.to_s.split(" ") + self.first_name = split_name.shift.to_s.capitalize + self.last_name = split_name.pop.to_s.capitalize + self.middle_name = split_name.map(&:capitalize).join(" ") + self.full_name + end +end diff --git a/app/models/invite.rb b/app/models/invite.rb index 8d5479ae6..066358863 100644 --- a/app/models/invite.rb +++ b/app/models/invite.rb @@ -1,8 +1,7 @@ class Invite < ActiveRecord::Base + #track_on_creation belongs_to :invitee, :class_name => "User" belongs_to :inviter, :polymorphic => true - - validates_presence_of :body end diff --git a/app/models/invite_mailer.rb b/app/models/invite_mailer.rb deleted file mode 100644 index b7a35f11b..000000000 --- a/app/models/invite_mailer.rb +++ /dev/null @@ -1,20 +0,0 @@ -class InviteMailer < ActionMailer::Base - include Resque::Mailer - - def user_invite(user_id, email, message) - @user = User.find(user_id) - @community = @user.community - recipients email - from "invites@commonplaceusa.com" - subject "#{@user.name} invited you to join #{@community.name} CommonPlace" - end - - def feed_invite(feed_id, email) - @feed = Feed.find(feed_id) - @community = @feed.community - recipients email - from "invites@commonplaceusa.com" - subject "#{@feed.name} invited you to join #{@community.name} CommonPlace" - end - -end diff --git a/app/models/item.rb b/app/models/item.rb deleted file mode 100644 index 47bb58a5d..000000000 --- a/app/models/item.rb +++ /dev/null @@ -1,8 +0,0 @@ -class Item < ActiveRecord::Base - - def self.find(type,params) - type.capitalize.constantize.find(params) - end - - -end diff --git a/app/models/location.rb b/app/models/location.rb deleted file mode 100644 index 8791bd343..000000000 --- a/app/models/location.rb +++ /dev/null @@ -1,49 +0,0 @@ -class Location < ActiveRecord::Base - - belongs_to :locatable, :polymorphic => true - - before_save :update_lat_and_lng - - validates_presence_of :street_address, :zip_code - - def update_lat_and_lng - if street_address_changed? - location = Geokit::Geocoders::GoogleGeocoder.geocode("#{street_address}, #{zip_code}") - if location && location.success? - write_attribute(:lat,location.lat) - write_attribute(:lng, location.lng) - end - end - end - - def within?(bounds) - return false if outside_bounding_box?(*[bounds.map(&:last).minmax, - bounds.map(&:first).minmax].flatten) - bounds.each_with_index.count { |point,index| - previous_point = bounds[index - 1] - between_ys_of_line_segment?(point, previous_point) && - ray_crosses_line_segment?(point, previous_point) - - }.odd? - end - - private - - def between_ys_of_line_segment?(a,b) - low_y, high_y = [a.last,b.last].sort - (low_y...high_y).include?(lng) - end - - def ray_crosses_line_segment?(a,b) - lat < ((b.first - a.first) * (lng - a.last) / - (b.last - a.last ) + a.first) - end - - def outside_bounding_box?(s,n,e,w) - lat < e || w < lat || - lng < s || n < lng - end - - - -end diff --git a/app/models/management_ability.rb b/app/models/management_ability.rb deleted file mode 100644 index e68ae8a6d..000000000 --- a/app/models/management_ability.rb +++ /dev/null @@ -1,18 +0,0 @@ -class ManagementAbility - include CanCan::Ability - - def initialize(user) - if user.new_record? - else - can :manage, Feed do |action, feed| - feed.user == user - end - - can :manage, Event do |action, event| - event.owner.is_a?(User) ? event.owner == user : event.owner.user == user - end - - can :read, :management - end - end -end diff --git a/app/models/membership.rb b/app/models/membership.rb new file mode 100644 index 000000000..8cfe67130 --- /dev/null +++ b/app/models/membership.rb @@ -0,0 +1,12 @@ +class Membership < ActiveRecord::Base + + def self.receive_methods + ["Live", "Daily", "Never"] + end + + validates_presence_of :user, :group + attr_accessible :receive_method, :group_id, :user_id + belongs_to :user + belongs_to :group + +end diff --git a/app/models/message.rb b/app/models/message.rb index ce27e1f0f..cf07685de 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -1,6 +1,40 @@ class Message < ActiveRecord::Base + #track_on_creation + + belongs_to :user - belongs_to :recipient, :class_name => "User" - validates_presence_of :subject, :body + belongs_to :messagable, :polymorphic => true + validates_presence_of :subject, :body, :user, :messagable + + has_many :replies, :as => :repliable, :order => :created_at + + scope :today, :conditions => ["created_at between ? and ?", DateTime.now.at_beginning_of_day, DateTime.now] + + scope :between, lambda { |start_date, end_date| { :conditions => ["? <= created_at AND created_at < ?", start_date.utc, end_date.utc] } } + + def long_id + IDEncoder.to_long_id(self.id) + end + + def self.find_by_long_id(long_id) + Message.find(IDEncoder.from_long_id(long_id)) + end + + + def most_recent_body + if replies.empty? + self.body + else + replies.last.body + end + end + + def between?(start_date, end_date) + start_date <= self.created_at and self.created_at <= end_date + end + + def community + user.community + end end diff --git a/app/models/met.rb b/app/models/met.rb index 99087dcad..9c3186c6b 100644 --- a/app/models/met.rb +++ b/app/models/met.rb @@ -2,4 +2,8 @@ class Met < ActiveRecord::Base belongs_to :requestee, :class_name => "User" belongs_to :requester, :class_name => "User" + def wanted_id + requestee_id + end + end diff --git a/app/models/neighborhood.rb b/app/models/neighborhood.rb index 00c743175..7c80628a1 100644 --- a/app/models/neighborhood.rb +++ b/app/models/neighborhood.rb @@ -1,12 +1,32 @@ class Neighborhood < ActiveRecord::Base + #track_on_creation + has_many :users - has_many :posts - belongs_to :community - has_many :notifications, :as => :notified + reverse_geocoded_by :latitude, :longitude + + belongs_to :community serialize :bounds, Array + validates_presence_of :name + + def self.closest_to(location) + geocoded.near(location, 15).limit(1).first + end + + def coordinates + [latitude,longitude] + end + + def coordinates=(cs) + self.latitude, self.longitude = *cs + end - validates_presence_of :name, :bounds + def contains?(position) + position.within? self.bounds + end + def posts + Post.where("user_id in (?)", self.user_ids) + end end diff --git a/app/models/notification.rb b/app/models/notification.rb deleted file mode 100644 index a70ba2135..000000000 --- a/app/models/notification.rb +++ /dev/null @@ -1,23 +0,0 @@ -class Notification < ActiveRecord::Base - belongs_to :notified, :polymorphic => true - belongs_to :notifiable, :polymorphic => true - - validates_presence_of :notified, :notifiable - - after_create :enqueue - - protected - - def enqueue - - if RAILS_ENV == "production" - Resque.enqueue(NotificationsMailer, - notified.class.to_s, notifiable.class.to_s, - notified.id, notifiable.id) - else - NotificationsMailer.perform(notified.class.to_s, notifiable.class.to_s, - notified.id, notifiable.id) - end - end - -end diff --git a/app/models/notifications_mailer.rb b/app/models/notifications_mailer.rb deleted file mode 100644 index c5eef4cf8..000000000 --- a/app/models/notifications_mailer.rb +++ /dev/null @@ -1,84 +0,0 @@ -require 'smtp_api_header.rb' - -class NotificationsMailer < ActionMailer::Base - helper :text - helper_method :url - include TextHelper - @queue = :notifications_mailer - - def url(path = "") - "http://" + @community.slug + ".ourcommonplace.com" + path - end - - RECIPIENT = "sengrid@example.com" - def self.perform(notified_type, notifiable_type, - notified_id, notifiable_id) - - notified = notified_type.constantize.find(notified_id.to_i) - notifiable = notifiable_type.constantize.find(notifiable_id.to_i) - method = [notified_type, notifiable_type].join("_").downcase - self.send("deliver_#{method}", notified, notifiable) - end - - - def neighborhood_post(neighborhood, post) - recipients RECIPIENT - @community = neighborhood.community - users = neighborhood.users.reject{|u| u == post.user}.select(&:receive_posts) - header = SmtpApiHeader.new - header.addTo(users.map(&:email)) - header.addSubVal('', users.map(&:name)) - @headers['X-SMTPAPI'] = header.asJSON - subject "#{post.user.full_name} posted to your neighborhood" - from "CommonPlace <#{post.long_id}@replies.commonplaceusa.com>" - body :post => post - end - - def user_message(user, message) - @community = user.community - recipients user.email - from "CommonPlace " - subject "#{message.user.name} just sent you a message on CommonPlace" - body :message => message - end - - def post_reply(post, reply) - @community = post.user.community - users = (post.replies.map(&:user) + [post.user]).uniq.reject {|u| u == reply.user} - header = SmtpApiHeader.new - header.addTo(users.map(&:email)) - header.addSubVal('', users.map(&:name)) - @headers['X-SMTPAPI'] = header.asJSON - recipients post.user.email - from "CommonPlace <#{post.long_id}@replies.commonplaceusa.com>" - subject "#{reply.user.name} just replied to a post on CommonPlace" - body :reply => reply, :post => post - end - - def feed_event(feed, event) - @community = feed.community - recipients RECIPIENT - users = feed.subscribers - header = SmtpApiHeader.new - header.addTo(users.map(&:email)) - header.addSubVal('', users.map(&:name)) - @headers['X-SMTPAPI'] = header.asJSON - users feed.subscribers.map(&:email) - subject "#{feed.name} posted a new event" - from "events@commonplaceusa.com" - body :feed => feed, :event => event - end - - def feed_announcement(feed, announcement) - @community = feed.community - recipients RECIPIENT - users = feed.subscribers - header = SmtpApiHeader.new - header.addTo(users.map(&:email)) - header.addSubVal('', users.map(&:name)) - @headers['X-SMTPAPI'] = header.asJSON - subject "#{feed.name} posted a new announcement" - from "announcements@commonplaceusa.com" - body :feed => feed, :announcement => announcement - end -end diff --git a/app/models/notifier.rb b/app/models/notifier.rb deleted file mode 100644 index a8e3f353a..000000000 --- a/app/models/notifier.rb +++ /dev/null @@ -1,4 +0,0 @@ -class Notifier - def self.reply_notify(reply) - end -end diff --git a/app/models/organizer_data_point.rb b/app/models/organizer_data_point.rb new file mode 100644 index 000000000..22f620f3a --- /dev/null +++ b/app/models/organizer_data_point.rb @@ -0,0 +1,22 @@ +class OrganizerDataPoint < ActiveRecord::Base + include Geokit::Geocoders + + def community + User.find(self.organizer_id).community + end + + def generate_point + if self.attempted_geolocating or (self.lat.present? and self.lng.present?) + if self.attempted_geolocating and not self.lat.present? + return + end + else + self.attempted_geolocating = true + loc = MultiGeocoder.geocode(self.address) + self.lat = loc.lat + self.lng = loc.lng + self.save + end + end + +end diff --git a/app/models/outside_announcement.rb b/app/models/outside_announcement.rb deleted file mode 100644 index fd08a1401..000000000 --- a/app/models/outside_announcement.rb +++ /dev/null @@ -1,3 +0,0 @@ -class OutsideAnnouncement < Announcement - validates_uniqueness_of :url -end diff --git a/app/models/passwords_mailer.rb b/app/models/passwords_mailer.rb deleted file mode 100644 index 5da703fd3..000000000 --- a/app/models/passwords_mailer.rb +++ /dev/null @@ -1,15 +0,0 @@ -class PasswordsMailer < ActionMailer::Base - - - def reset(user) - recipients user.email - from "passwords@commonplaceusa.com" - subject "CommonPlace password reset" - body <Follow this link to reset your password: #{edit_password_reset_url(user.perishable_token, :host => "#{user.community.slug}.ourcommonplace.com") }

    -
    --- -#{ user.community.name }'s CommonPlace: #{user.community.slug + ".ourcommonplace.com"} -END - end -end diff --git a/app/models/platform_update.rb b/app/models/platform_update.rb deleted file mode 100644 index 440aaa71c..000000000 --- a/app/models/platform_update.rb +++ /dev/null @@ -1,2 +0,0 @@ -class PlatformUpdate < ActiveRecord::Base -end diff --git a/app/models/post.rb b/app/models/post.rb index 2f8bd520e..50dde024c 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -1,71 +1,61 @@ class Post < ActiveRecord::Base + #acts_as_trackable + CATEGORIES = %w{Request Offer Invitation Announcement Question} - require "lib/helper" + + delegate :neighborhood, :to => :user belongs_to :user + belongs_to :community - has_many :replies, :as => :repliable + has_many :replies, :as => :repliable, :order => :created_at has_many :repliers, :through => :replies, :uniq => true, :source => :user - validates_presence_of :user + validates_presence_of :user, :community validates_presence_of :subject, :message => "Please enter a subject for your post" validates_presence_of :body, :message => "Please enter some text for your post" + default_scope where(:deleted_at => nil) + + attr_accessor :post_to_facebook - has_many :notifications, :as => :notified + scope :between, lambda { |start_date, end_date| + { :conditions => + ["posts.created_at between ? and ?", start_date.utc, end_date.utc] } + } + scope :up_to, lambda { |end_date| { :conditions => ["posts.created_at <= ?", end_date.utc] } } + + scope :created_on, lambda { |date| { :conditions => ["posts.created_at between ? and ?", date.utc.beginning_of_day, date.utc.end_of_day] } } + + scope :today, :conditions => ["posts.created_at between ? and ?", DateTime.now.at_beginning_of_day, Time.now] + + def self.deleted + self.unscoped.where('deleted_at IS NOT NULL') + end def self.human_name "Neighborhood Post" end - def long_id - # Return the base-64 encoded post ID, replacing any tailing = characters with their quantity - require 'base64' - long_id = Base64.b64encode(self.id.to_s) - m = long_id.match(/[A-Za-z0-9]*(=*)/) - - if m[1] - long_id = long_id.gsub(m[1],m[1].length.to_s) - end - long_id.gsub("\n","") - end - - def self.find_by_long_id(long_id) - # Decode the base-64 encoding done in Post.long_id, and get the post - require 'base64' - # Reconstruct the equal signs at the end - num = long_id[long_id.length-1,long_id.length-1] - long_id = long_id[0,long_id.length-1] - num.to_i.times do |i| - long_id += "=" - end - # Find the post - post_id = Base64.decode64(long_id) - Post.find(post_id) - end - def category super || "Announcement" end - def time - help.post_date(self.created_at) - end - def owner self.user end - - def user_may_delete(current_user) - return current_user.is_same_as(self.user) + + def last_activity + ([self.created_at] + self.replies.map(&:created_at)).max end - - def deleteLink - if self.user_may_delete(UserSession.find.user) - "" - else - "" + + searchable do + text :subject + text :body + text :replies do + replies.map &:body end + integer :community_id + time :created_at end - - + end diff --git a/app/models/profile_field.rb b/app/models/profile_field.rb deleted file mode 100644 index c3e9e4ef0..000000000 --- a/app/models/profile_field.rb +++ /dev/null @@ -1,15 +0,0 @@ -class ProfileField < ActiveRecord::Base - - belongs_to :feed - - validates_presence_of :subject, :body - - before_create :append_field - - protected - - def append_field - self.position = self.feed.profile_fields.length - end - -end diff --git a/app/models/registration.rb b/app/models/registration.rb new file mode 100644 index 000000000..5c302e56b --- /dev/null +++ b/app/models/registration.rb @@ -0,0 +1,63 @@ +class Registration + + def initialize(user) + @user = user + end + + attr_reader :user + + def has_avatar? + user.avatar? + end + + def save + user.save + end + + def attributes=(params) + user.attributes = params + end + + def update_attributes(params) + user.update_attributes(params) + end + + def first_name + user.first_name + end + + def from_facebook? + user.is_facebook_user + end + + def avatar_url(type = nil) + user.avatar_url(type) + end + + def add_feeds(feed_ids) + user.feed_ids = feed_ids + end + + def add_groups(group_ids) + user.group_ids = group_ids + end + + def community + user.community + end + + def referral_sources + [ + "Flyer at my door", + "Someone knocked on my door", + "In a meeting with #{community.organizer_name}", + "At a table or booth at an event", + "In an email", + "On Facebook or Twitter", + "On another website", + "In the news", + "Word of mouth", + "Other" + ] + end +end diff --git a/app/models/repliable.rb b/app/models/repliable.rb new file mode 100644 index 000000000..b7ff2a8bc --- /dev/null +++ b/app/models/repliable.rb @@ -0,0 +1,11 @@ +class Repliable + + # takes an id of the form "#{repliable.class.name.downcase}_#{repliable.id}" + # and returns the relevant record + def self.find(repliable_id) + if match_data = repliable_id.match(/^(.+)_(\d+)$/) + match_data[1].camelize.constantize.find(match_data[2]) + end + end + +end diff --git a/app/models/reply.rb b/app/models/reply.rb index 6f0f28c53..06aa8fed6 100644 --- a/app/models/reply.rb +++ b/app/models/reply.rb @@ -1,11 +1,14 @@ class Reply < ActiveRecord::Base + #track_on_creation - belongs_to :repliable, :polymorphic => true + belongs_to :repliable, :polymorphic => true, :touch => true belongs_to :user validates_presence_of :repliable validates_presence_of :user validates_presence_of :body + scope :between, lambda { |start_date, end_date| { :conditions => ["? <= created_at AND created_at < ?", start_date.utc, end_date.utc] } } + end diff --git a/app/models/request.rb b/app/models/request.rb new file mode 100644 index 000000000..afb2a81ca --- /dev/null +++ b/app/models/request.rb @@ -0,0 +1,3 @@ +class Request < ActiveRecord::Base + +end diff --git a/app/models/rss_announcement.rb b/app/models/rss_announcement.rb index 065146aeb..61c823efa 100644 --- a/app/models/rss_announcement.rb +++ b/app/models/rss_announcement.rb @@ -1,3 +1,35 @@ +require 'rss' +require 'htmlentities' + class RSSAnnouncement < Announcement + validates_uniqueness_of :url + + def self.from_rss_item(item, params = {}) + self.record_timestamps = false + + timestamp = item.date.try(:to_datetime) || DateTime.now + + self.new(params) do |a| + a.subject = item.title + a.url = item.link + a.body = self.extract_rss_body(item.description) + a.created_at = timestamp + a.updated_at = timestamp + end + ensure + self.record_timestamps = true + end + + def self.extract_rss_body(body) + body = self.strip_feedflare(body) + body = HTMLEntities.new.decode(body) + body = ReverseMarkdown.new.parse_string(body) + body + end + + def self.strip_feedflare(html) + html.gsub(/
    (.*)<\/div>/m, "") + end + end diff --git a/app/models/rss_feed.rb b/app/models/rss_feed.rb index 876901c94..2b139dd16 100644 --- a/app/models/rss_feed.rb +++ b/app/models/rss_feed.rb @@ -1,39 +1,25 @@ -class RSSFeed < Feed +class RSSFeed - validates_uniqueness_of :feed_url - - after_create :find_avatar - - def find_avatar - # Pull the favicon - require 'open-uri' - begin - f = open(self.website) - rescue - err = $!.to_s + def initialize(feed) + @feed = feed + end + + def items + if @feed.rss_feed.present? + RSS::Parser.parse(open(@feed.feed_url).read, false).items + else + [] end - if !err - a = f.read - s = a.slice(/link.*rel\=.*shortcut.*\"/) - if s != nil - exp = s.split("href=") - exp1 = exp[1].split('"') - exp2 = exp1[1].split("http:") - if exp2[0] != "" - self.avatar_file_name = main_url+exp1[1] - else - self.avatar_file_name = exp1[1] - end - else - self.avatar_file_name = self.website + "/favicon.ico" + end + + def update! + self.items.each do |item| + Exceptional.rescue do + RSSAnnouncement.from_rss_item(item, + :owner => @feed, + :community_id => @feed.community_id).save end - self.save end - end - - def profile_link - return "/feeds/" + self.owner_id - end - + end diff --git a/app/models/subscription.rb b/app/models/subscription.rb index c5305bf43..633053b6a 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -1,6 +1,10 @@ class Subscription < ActiveRecord::Base - + attr_accessible :receive_method, :feed_id, :user_id belongs_to :user belongs_to :feed + def self.receive_methods + ["Live", "Daily", "Never"] + end + end diff --git a/app/models/twitter_announcement.rb b/app/models/twitter_announcement.rb deleted file mode 100644 index 1d6604aee..000000000 --- a/app/models/twitter_announcement.rb +++ /dev/null @@ -1,2 +0,0 @@ -class TwitterAnnouncement < Announcement -end diff --git a/app/models/user.rb b/app/models/user.rb index 9249e19eb..9503d12b0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,175 +1,225 @@ +class NamedPoint + attr_accessor :lat, :lng, :name, :address +end + class User < ActiveRecord::Base - - # State machine - # This is messy, but it's the way I was able to get the system working. - - # A cleaner implementation would have a single function that checks and returns the status - - def check_state - if self.first_name && self.last_name && self.email - if self.crypted_password - if self.about || self.cached_skill_list - if (self.state != :oneA) - update_state_to_oneA - end - else - if (self.state != :oneB) - update_state_to_oneB - end - end - else - if (self.state != :twoA) - update_state_to_twoA - end - end - else - if user.email - if (self.state != :three) - update_state_to_three - end - else - if (self.state != :four) - update_state_to_four - end - end - end + + before_save :ensure_authentication_token + + #track_on_creation + include Geokit::Geocoders + + def self.post_receive_options + ["Live", "Three", "Daily", "Never"] end - - include AASM - - aasm_column :state - - aasm_state :nullstate - - aasm_initial_state Proc.new { |user| - if user.first_name && user.last_name && user.email - if user.crypted_password - if user.about || user.cached_skill_list - :oneA - else - :oneB - end - else - :twoA - end - else - if user.email - :three - else - :four - end + + + validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i + + devise :database_authenticatable, :encryptable, :token_authenticatable, :recoverable, :omniauthable, :omniauth_providers => [:facebook] + + def self.find_for_facebook_oauth(access_token) + User.find_by_facebook_uid(access_token["uid"]) + end + + def self.new_from_facebook(params, facebook_data) + User.new(params).tap do |user| + user.email = facebook_data["user_info"]["email"] + user.full_name = facebook_data["user_info"]["name"] + user.facebook_uid = facebook_data["uid"] end - } - aasm_state :oneA - aasm_state :oneB - aasm_state :twoA - aasm_state :three - aasm_state :four - - - - aasm_event :update_state_to_oneA do - transitions :to => :oneA, :from => [:oneA, :oneB, :twoA, :three, :four] end - - aasm_event :update_state_to_oneB do - transitions :to => :oneB, :from => [:oneA, :oneB, :twoA, :three, :four] + + + geocoded_by :normalized_address + + belongs_to :community + belongs_to :neighborhood + + def organizer_data_points + OrganizerDataPoint.find_all_by_organizer_id(self.id) end + + before_validation :geocode, :if => :address_changed? + before_validation :place_in_neighborhood, :if => :address_changed? + + validates_presence_of :community + validates_presence_of :address, :on => :create, :unless => :is_transitional_user, :message => "Please provide a street address so CommonPlace can verify that you live in our community." + validates_presence_of :address, :on => :update - aasm_event :update_state_to_twoA do - transitions :to => :twoA, :from => [:oneA, :oneB, :twoA, :three, :four] + + validates_presence_of :first_name, :unless => :is_transitional_user + validate :validate_first_and_last_names, :unless => :is_transitional_user + + validates_presence_of :neighborhood, :unless => :is_transitional_user + validates_uniqueness_of :facebook_uid, :allow_blank => true + + scope :between, lambda { |start_date, end_date| + { :conditions => + ["? <= created_at AND created_at < ?", start_date.utc, end_date.utc] } + } + + scope :today, { :conditions => ["created_at between ? and ?", DateTime.now.utc.beginning_of_day, DateTime.now.utc.end_of_day] } + + scope :up_to, lambda { |end_date| { :conditions => ["created_at <= ?", end_date.utc] } } + + scope :logged_in_since, lambda { |date| { :conditions => ["last_login_at >= ?", date.utc] } } + + def facebook_user? + facebook_uid end - aasm_event :update_state_to_three do - transitions :to => :three, :from => [:oneA, :oneB, :twoA, :three, :four] + def validate_password? + if facebook_user? + return false + end + return true end - - aasm_event :update_state_to_four do - transitions :to => :four, :from => [:oneA, :oneB, :twoA, :three, :four] + + validates_presence_of :email + validates_uniqueness_of :email + + # HACK HACK HACK TODO: This should be in the database schema, or slugs for college towns should ALWAYS be the domain suffix + validates_format_of :email, :with => /^([^\s]+)umw\.edu/, :if => :college? + + def college? + self.community and self.community.is_college end - # after_save :check_state - + validates_presence_of :first_name, :last_name - acts_as_authentic do |c| - c.login_field :email - c.require_password_confirmation = false - c.validate_email_field=false - c.validate_password_field=false + def self.find_by_email(email) + where("LOWER(users.email) = ?", email.downcase).first end - - belongs_to :neighborhood - + has_many :attendances, :dependent => :destroy + has_many :events, :through => :attendances has_many :posts, :dependent => :destroy + has_many :group_posts, :dependent => :destroy + has_many :announcements, :dependent => :destroy, :as => :owner, :include => :replies + has_many :replies, :dependent => :destroy + + has_many :subscriptions, :dependent => :destroy - has_many :feeds, :through => :subscriptions - has_many :managable_feeds, :class_name => "Feed" - has_many :direct_events, :class_name => "Event", :as => :owner + accepts_nested_attributes_for :subscriptions + has_many :feeds, :through => :subscriptions, :uniq => true + + has_many :memberships, :dependent => :destroy + accepts_nested_attributes_for :memberships + has_many :groups, :through => :memberships, :uniq => true + + has_many :feed_owners + has_many :managable_feeds, :through => :feed_owners, :class_name => "Feed", :source => :feed + has_many :direct_events, :class_name => "Event", :as => :owner, :include => :replies, :dependent => :destroy has_many :referrals, :foreign_key => "referee_id" - - has_many :messages, :foreign_key => "recipient_id" + has_many :sent_messages, :dependent => :destroy, :class_name => "Message" + + has_many :received_messages, :as => :messagable, :class_name => "Message", :dependent => :destroy + has_many :mets, :foreign_key => "requester_id" has_many :people, :through => :mets, :source => "requestee" + + include CroppableAvatar + has_attached_file(:avatar, + {:styles => { + :thumb => {:geometry => "100x100", :processors => [:cropper]}, + :normal => {:geometry => "120x120", :processors => [:cropper]}, + :large => {:geometry => "200x200", :processors => [:cropper]}, + :original => "1000x1000>" + }, + :default_url => "https://s3.amazonaws.com/commonplace-avatars-production/missing.png" + }.merge(Rails.env.development? || Rails.env.test? ? + { :path => ":rails_root/public/system/users/:id/avatar/:style.:extension", + :storage => :filesystem, + :url => "/system/users/:id/avatar/:style.:extension" + } : { + :storage => :s3, + :s3_protocol => "https", + :bucket => "commonplace-avatars-#{Rails.env}", + :path => "/users/:id/avatar/:style.:extension", + :s3_credentials => { + :access_key_id => ENV['S3_KEY_ID'], + :secret_access_key => ENV['S3_KEY_SECRET'] + } + })) - has_many :notifications, :as => :notified + acts_as_api - validates_uniqueness_of :email - validates_presence_of :first_name, :last_name, :address, :neighborhood - acts_as_taggable_on :skills - acts_as_taggable_on :interests - acts_as_taggable_on :goods + api_accessible :default do |t| + t.add :id + t.add lambda {|u| "users"}, :as => :schema + t.add lambda {|u| u.avatar_url(:normal)}, :as => :avatar_url + t.add lambda {|u| "/users/#{u.id}"}, :as => :url + t.add :name + t.add :first_name + t.add :last_name + t.add :about + t.add :interest_list, :as => :interests + t.add :good_list, :as => :goods + t.add :skill_list, :as => :skills + t.add :links + end + def links + { + "messages" => "/users/#{id}/messages", + "self" => "/users/#{id}" + } + end - has_attached_file(:avatar, - :styles => { - :thumb => "100x100^", - :normal => "120x120^", - :large => "200x200^" - }, - :default_url => "/avatars/missing.png", - :url => "/system/users/:id/avatar/:style.:extension", - :path => ":rails_root/public/system/users/:id/avatar/:style.:extension") - - - def validate - errors.add(:full_name, "can't be blank") if first_name.blank? || last_name.blank? + def avatar_geometry(style = :original) + @geometry ||= {} + path = (avatar.options[:storage]==:s3) ? avatar.url(style) : avatar.path(style) + @geometry[style] ||= Paperclip::Geometry.from_file(path) + end + + scope :receives_weekly_bulletin, :conditions => {:receive_weekly_digest => true} + + scope :receives_daily_digest, :conditions => {:post_receive_method => "Daily"} + + scope :receives_posts_live, :conditions => {:post_receive_method => ["Live", "Three"]} + + scope :receives_posts_live_unlimited, :conditions => {:post_receive_method => "Live"} + + scope :receives_posts_live_limited, :conditions => {:post_receive_method => "Three"} + + def messages + self.sent_messages.select {|m| m.replies.count > 0 } + end + + def validate_first_and_last_names + errors.add(:full_name, "CommonPlace requires people to register with their first \& last names.") if first_name.blank? || last_name.blank? end - - def subscribed_announcements - feeds.map(&:announcements).flatten + def daily_subscribed_announcements + self.subscriptions.all(:conditions => "receive_method = 'Daily'"). + map(&:feed).map(&:announcements).flatten end def suggested_events [] end - def search(term) - User.all - end - def full_name - first_name && last_name ? first_name.to_s + " " + last_name.to_s : nil + [first_name,middle_name,last_name].select(&:present?).join(" ") end def full_name=(string) - @full_name = string - self.first_name, self.last_name = string.split(" ", 2) + split_name = string.to_s.split(" ") + self.first_name = split_name.shift.to_s.capitalize + self.last_name = split_name.pop.to_s.capitalize + self.middle_name = split_name.map(&:capitalize).join(" ") + self.full_name end def name full_name end - - def community - neighborhood && neighborhood.community - end - + def wire if new_record? community.announcements + community.events @@ -192,18 +242,184 @@ def feed_list feeds.map(&:name).join(", ") end + def group_list + groups.map(&:name).join(", ") + end - alias_method :real_neighborhood, :neighborhood + def feed_messages + Message.where("messagable_type = 'Feed' AND messagable_id IN (?)", self.managable_feed_ids) + end - def neighborhood - real_neighborhood || Neighborhood.new + def place_in_neighborhood + if self.community.is_college + self.neighborhood = self.community.neighborhoods.select { |n| n.name == self.address }.first + else + self.neighborhood = self.community.neighborhoods.near(self.to_coordinates, 15).first || self.community.neighborhoods.first + end + unless self.neighborhood + errors.add :address, I18n.t('activerecord.errors.models.user.address', + :community => self.community.name) + end end - def is_same_as(other_user) - puts (other_user.email == self.email && other_user.crypted_password == self.crypted_password) - (other_user.email == self.email && other_user.crypted_password == self.crypted_password) + def is_facebook_user + facebook_uid.present? end + def facebook_avatar_url + "https://graph.facebook.com/" + self.facebook_uid.to_s + "/picture/?type=large" + end - + def avatar_url(style_name = nil) + if is_facebook_user && !self.avatar.file? + facebook_avatar_url + else + self.avatar.url(style_name || self.avatar.default_style) + end + end + + def value_adding? + (self.posts.size >= 1 || self.announcements.size >= 1 || self.events.size >= 1) + end + + def normalized_address + if address.match(/#{self.community.name}/i) + address + else + address + ", #{self.community.name}" + end + end + + def generate_point + if self.attempted_geolocating or (self.generated_lat.present? and self.generated_lng.present?) + if self.attempted_geolocating and not self.generated_lat.present? + return + end + else + self.attempted_geolocating = true + loc = MultiGeocoder.geocode("#{self.address}, #{self.community.zip_code}") + self.generated_lat = loc.lat + self.generated_lng = loc.lng + self.save + end + point = NamedPoint.new + point.lat = self.generated_lat + point.lng = self.generated_lng + point.name = self.full_name + point.address = self.address + point + end + + def self.received_reply_to_object_in_last(repliable_type, days_ago) + # We expect repliable_type to be Post + if repliable_type == 'Post' + item = Post + elsif repliable_type == 'Event' + item = Event + elsif repliable_type == 'Announcement' + item = Announcement + end + user_ids = [] + Reply.between(days_ago.days.ago, Time.now).select {|r| r.repliable_type == repliable_type}.map(&:repliable_id).uniq.each do |i| user_ids << item.find(i).owner end + user_ids + end + + def emails_are_limited? + self.post_receive_method == "Three" + end + + def inbox + Message.where(< 0) OR + ("messages"."messagable_type" = 'User' AND + "messages"."messagable_id" = ?) +WHERE + end + + searchable do + text :first_name + text :last_name + text :about + text :skills + text :goods + text :interests + integer :community_id + end + + def skill_list + (self.skills || "").split(", ") + end + + def interest_list + (self.interests || "").split(", ") + end + + def good_list + (self.goods || "").split(", ") + end + + def skill_list=(skill_list) + case skill_list + when Array + self.skills = skill_list.join(", ") + else + self.skills = skill_list + end + end + + def good_list=(good_list) + case good_list + when Array + self.goods = good_list.join(", ") + else + self.goods = good_list + end + end + + def interest_list=(interest_list) + case interest_list + when Array + self.interests = interest_list.join(", ") + else + self.interests = interest_list + end + end + + # Devise calls this on POST /users/password + def send_reset_password_instructions + generate_reset_password_token! if should_generate_token? + kickoff.deliver_password_reset(self) + end + + def kickoff=(kickoff) + @kickoff = kickoff + end + + def kickoff + @kickoff ||= KickOff.new + end + + def last_checked_inbox + read_attribute(:last_checked_inbox) || Time.at(0).to_datetime + end + + def unread + (self.inbox + self.feed_messages).select { |m| + m.updated_at > self.last_checked_inbox + }.length + end + + def checked_inbox! + self.last_checked_inbox = DateTime.now + self.save :validate => false + end + + private + + def is_transitional_user + transitional_user + end + end diff --git a/app/models/user_session.rb b/app/models/user_session.rb deleted file mode 100644 index 8c19d1955..000000000 --- a/app/models/user_session.rb +++ /dev/null @@ -1,2 +0,0 @@ -class UserSession < Authlogic::Session::Base -end \ No newline at end of file diff --git a/app/stylesheets/_accounts.css.sass b/app/stylesheets/_accounts.css.sass new file mode 100644 index 000000000..5e6a152bc --- /dev/null +++ b/app/stylesheets/_accounts.css.sass @@ -0,0 +1,15 @@ +@import accounts/learn_more, accounts/edit, accounts/delete, accounts/facebook_invite + +body.application.accounts + .new + background-image: image-url('clouds.png') + h1 + width: 960px + color: #3b6085 + line-height: 1.2 + font-size: 24px + padding: 0 20px + margin: 5px auto 20px + text-align: center + +text-shadow(#bfc8cf 1px 1px 1px) + diff --git a/app/stylesheets/_accounts.sass b/app/stylesheets/_accounts.sass deleted file mode 100644 index f5342896e..000000000 --- a/app/stylesheets/_accounts.sass +++ /dev/null @@ -1,29 +0,0 @@ -@import accounts/edit_new, accounts/learn_more, accounts/edit_interests, accounts/new, accounts/edit - -body.application.accounts - - h1 - width: 960px - color: #3b6085 - line-height: 1.2 - font-size: 25px - padding: 0 20px - margin: 5px auto 20px - text-align: center - +text-shadow(#bfc8cf, 1px, 1px, 1px) - a - text-decoration: none - +text-shadow(#d4dee7, 1px, 1px, 1px) - font-size: 16px - color: #fff - +inline-block - background-color: #b53f3c - padding: - left: 10px - right: 10px - top: 3px - bottom: 3px - border: 1px solid #8F2424 - +border-radius(5px) - +box-shadow(#E88080, 0, 1px, 0px, 0px,inset) - +text-shadow(#444, 1px, 1px, 1px) \ No newline at end of file diff --git a/app/stylesheets/_admin.css.sass b/app/stylesheets/_admin.css.sass new file mode 100644 index 000000000..0cf2f200d --- /dev/null +++ b/app/stylesheets/_admin.css.sass @@ -0,0 +1 @@ +@import admin/show_referrers diff --git a/app/stylesheets/_admin_bar.css.sass b/app/stylesheets/_admin_bar.css.sass new file mode 100644 index 000000000..6c93bb363 --- /dev/null +++ b/app/stylesheets/_admin_bar.css.sass @@ -0,0 +1,31 @@ +@import _admin +#admin_bar + padding: 5px 20px + border-bottom: 4px solid #B53F3C + form.button-to + display: inline + div + display: inline + .current + background-color: #4A7EB0 + color: white + + #deliveries + float: right + position: relative + ul + right: 0px + margin-top: 5px + position: absolute + display: none + background-color: #fff + z-index: 10 + width: 700px + overflow: auto + max-height: 500px + li + padding: 5px + p + margin: 10px + + diff --git a/app/stylesheets/_admin_bar.sass b/app/stylesheets/_admin_bar.sass deleted file mode 100644 index 00cad9781..000000000 --- a/app/stylesheets/_admin_bar.sass +++ /dev/null @@ -1,31 +0,0 @@ - -#admin_bar - padding: 5px 20px - border-bottom: 4px solid #B53F3C - form.button-to - display: inline - div - display: inline - .current - background-color: #4A7EB0 - color: white - - #deliveries - float: right - position: relative - ul - right: 0px - margin-top: 5px - position: absolute - display: none - background-color: #fff - z-index: 10 - width: 700px - overflow: auto - max-height: 500px - li - padding: 5px - p - margin: 10px - - diff --git a/app/stylesheets/_administration.css.sass b/app/stylesheets/_administration.css.sass new file mode 100644 index 000000000..98a135f78 --- /dev/null +++ b/app/stylesheets/_administration.css.sass @@ -0,0 +1,28 @@ +.administration + div.polygon-canvas + width: 500px + height: 350px + padding: 0 + + textarea.polygon + width: 500px + + .formtastic + fieldset + legend + padding: 10px 0px + font-size: 14px + font-weight: bold + + fieldset ol li + padding: 10px + label + +inline-block + width: 100px + + + header, #header + border-bottom: 5px solid grey + margin-bottom: 20px + #logout + float: right \ No newline at end of file diff --git a/app/stylesheets/_administration.sass b/app/stylesheets/_administration.sass deleted file mode 100644 index ef3689276..000000000 --- a/app/stylesheets/_administration.sass +++ /dev/null @@ -1,28 +0,0 @@ -.administration - div.polygon-canvas - width: 500px - height: 350px - padding: 0 - - textarea.polygon - width: 500px - - .formtastic - fieldset - legend - padding: 10px 0px - font-size: 14px - font-weight: bold - - fieldset ol li - padding: 10px - label - +inline-block - width: 100px - - - header - border-bottom: 5px solid grey - margin-bottom: 20px - #logout - float: right \ No newline at end of file diff --git a/app/stylesheets/_colors.sass b/app/stylesheets/_colors.css.sass similarity index 100% rename from app/stylesheets/_colors.sass rename to app/stylesheets/_colors.css.sass diff --git a/app/stylesheets/_communities.css.sass b/app/stylesheets/_communities.css.sass new file mode 100644 index 000000000..726c60700 --- /dev/null +++ b/app/stylesheets/_communities.css.sass @@ -0,0 +1 @@ +@import communities/good_neighbor_discount diff --git a/app/stylesheets/_core.css.sass b/app/stylesheets/_core.css.sass new file mode 100644 index 000000000..132592e08 --- /dev/null +++ b/app/stylesheets/_core.css.sass @@ -0,0 +1,45 @@ + +@import compass/utilities/general/clearfix +@import compass/typography/lists/inline-list +@import compass/css3/border-radius +@import compass/css3/gradient +@import compass/css3/inline-block +@import compass/css3/box-shadow +@import compass/css3/text-shadow +@import compass/css3/opacity +@import compass/reset/utilities +@import compass/utilities/general/min +@import mixins +@import colors +@import measurements +@import compass/layout/sticky-footer + ++global-reset +body + text-rendering: optimizeLegibility + +#win, #fail + padding: 7px 10px + +sans + font-size: 12px + font-weight: bold + margin-bottom: 10px + +border-radius($defaultRadius) + +#win + background-color: #249C3A + color: white + +nav, header, footer, aside + display: block + +hr.staple + width: 40px + margin: 20px auto + +strong + font-weight: bold + +em + font-style: italic + diff --git a/app/stylesheets/_core.sass b/app/stylesheets/_core.sass deleted file mode 100644 index 38c9faab5..000000000 --- a/app/stylesheets/_core.sass +++ /dev/null @@ -1,69 +0,0 @@ - -@import compass/utilities/general/clearfix -@import compass/utilities/lists/inline-list -@import compass/css3/border-radius -@import compass/css3/gradient -@import compass/css3/inline-block -@import compass/css3/box-shadow -@import compass/css3/text-shadow -@import compass/reset/utilities -@import compass/utilities/general/min -@import mixins -@import colors -@import measurements - -@import tipsy - -+global-reset -body - text-rendering: optimizeLegibility - -#win, #fail - padding: 7px 10px - +sans - font-size: 12px - font-weight: bold - margin-bottom: 10px - +border-radius($defaultRadius) - -#win - background-color: #249C3A - color: white - -nav, header, footer, aside - display: block - -hr.staple - width: 40px - margin: 20px auto - -strong - font-weight: bold - -em - font-style: italic - -.hide - display: none - -#footer_grad - height: 10px - width: 100% - +linear-gradient(color-stops(#ddd, #efefef)) -footer - +clearfix - +sans - font-size: 0.8em - width: 972px - margin: 1em auto - color: #333 - .left_text - float: left - .right_text - float: right - a - font-weight: bold - color: #333 - text-decoration: none - &:hover - text-decoration: underline \ No newline at end of file diff --git a/app/stylesheets/_date_picker.css.sass b/app/stylesheets/_date_picker.css.sass new file mode 100644 index 000000000..b16891cc5 --- /dev/null +++ b/app/stylesheets/_date_picker.css.sass @@ -0,0 +1,65 @@ +.ui-datepicker + border: 1px solid $lightGray + display: none + background-color: white + width: 240px + +border-radius($defaultRadius) + + .ui-datepicker-header + background-color: $calendarTopColor + +border-radius($defaultRadius) + padding: 7px + font-size: 14px + margin: 5px + color: white + .ui-datepicker-prev + float: left + .ui-datepicker-next + float: right + .ui-datepicker-next, .ui-datepicker-prev + font-size: 12px + color: white + cursor: pointer + padding: 0 4px + +border-radius(4px) + &:hover + text-decoration: none + background-color: $calendarTopColor + #222 + .ui-datepicker-title + text-align: center + .ui-datepicker-month + .ui-datepicker-year + .ui-datepicker-calendar + border-collapse: collapse + margin: 5px + +sans + font-size: 12px + td, th + width: 28.5px + padding: 4px + text-align: center + td + border: 2px solid white + th + font-weight: bold + background-color: $lightGray + td + &:hover + background-color: #fafaaa + .ui-datepicker-week-end + .ui-datepicker-today + background-color: $lightGray + .ui-datepicker-current-day + background-color: #fafaaa + .ui-state-default + +inline-block + a.ui-state-default + text-decoration: underline + .ui-datepicker-other-month + color: $mediumGray + &:hover + background-color: white + &.ui-datepicker-today + &:hover + background-color: $lightGray + diff --git a/app/stylesheets/_date_picker.sass b/app/stylesheets/_date_picker.sass deleted file mode 100644 index 315168dc8..000000000 --- a/app/stylesheets/_date_picker.sass +++ /dev/null @@ -1,64 +0,0 @@ - -.ui-datepicker - border: 1px solid $lightGray - display: none - background-color: white - width: 200px - +border-radius($defaultRadius) - .ui-datepicker-header - background-color: $calendarTopColor - +border-radius($defaultRadius) - padding: 7px - font-size: 14px - margin: 5px - color: white - .ui-datepicker-prev - float: left - .ui-datepicker-next - float: right - .ui-datepicker-next, .ui-datepicker-prev - color: white - cursor: pointer - padding: 0 4px - +border-radius(4px) - &:hover - text-decoration: none - background-color: $calendarTopColor + #222 - .ui-datepicker-title - text-align: center - .ui-datepicker-month - .ui-datepicker-year - .ui-datepicker-calendar - border-collapse: collapse - margin: 5px - +sans - font-size: 12px - td, th - width: 28.5px - padding: 4px - text-align: center - td - border: 2px solid white - th - font-weight: bold - background-color: $lightGray - td - &:hover - background-color: #fafaaa - .ui-datepicker-week-end - .ui-datepicker-today - background-color: $lightGray - .ui-datepicker-current-day - background-color: #fafaaa - .ui-state-default - +inline-block - a.ui-state-default - text-decoration: underline - .ui-datepicker-other-month - color: $mediumGray - &:hover - background-color: white - &.ui-datepicker-today - &:hover - background-color: $lightGray - \ No newline at end of file diff --git a/app/stylesheets/_edit_new.sass b/app/stylesheets/_edit_new.sass deleted file mode 100644 index 62f8332cb..000000000 --- a/app/stylesheets/_edit_new.sass +++ /dev/null @@ -1,33 +0,0 @@ -#modal - .edit_new#modal-content - background-color: #f6f6f6 - padding: 20px - .edit_account, .info_box - float: left - .edit_account - margin-right: 15px - h1 - font-size: 30px - margin-bottom: 0px - p.subhead - font-size: 15px - margin-top: 0px - form.new_entity - width: 392px - fieldset ol li - label - color: $cpBlue - font-size: 11px - text-align: left - padding-right: 5px - width: 75px - margin-top: 5px - line-height: 130% - input[type="text"], textarea, input[type="password"] - width: 300px - p.inline-hints - margin-left: 80px - #information.info_box - width: 435px - #info_box_main .body - width: 260px \ No newline at end of file diff --git a/app/stylesheets/_faq.css.sass b/app/stylesheets/_faq.css.sass new file mode 100644 index 000000000..e79fee3b1 --- /dev/null +++ b/app/stylesheets/_faq.css.sass @@ -0,0 +1,126 @@ +body.application.faq + background-image: image-url('clouds.png') + background-repeat: repeat-x + background-position: top + header, #header + text-align: left + + text-align: center + h1 + width: 960px + color: #3b6085 + line-height: 1.2 + font-size: 25px + padding: 0 20px + margin: 5px auto 20px + text-align: center + +text-shadow(#bfc8cf 1px 1px 1px) + #main + width: 755px + margin: 0 auto + position: relative + text-align: left + h2 + color: #525252 + font-size: 20px + text-align: center + p + margin: 10px 0 + #sections_picker + position: absolute + right: -135px + top: 30px + width: 170px + background-color: #b53f3c + color: white + padding: 10px 5px 10px 30px + z-index: 1 + +border-radius(15px) + border: 10px solid #922f2c + +box-shadow($default-box-shadow-color 0 0 $default-box-shadow-blur 0 inset) + h2 + color: white + margin: 0 0 10px + li + margin: 5px 0 + a + color: white + text-decoration: none + + + #content + position: relative + margin: 50px auto 70px + +lightbox + width: 560px + z-index: 2 + color: #3A3A3A + h2 + font-size: 26px + margin-bottom: 20px + ul + margin-bottom: 40px + li + margin: 10px 0 + a + color: #b53f3c + font-size: 18px + text-decoration: none + ul + margin: 5px 20px 5px + li + a + font-size: 16px + color: #525252 + text-decoration: underline + + + .section_head + margin: 50px 0 + font-size: 22px + border-bottom: 1px solid #ddd + border-top: 1px solid #ddd + padding: 10px 0 + .item_head + color: #b53f3c + border-bottom: 1px solid #ddd + padding-bottom: 2px + margin: 0 + + .answer + font-family: Georgia, times, serif + font-size: 14px + p + margin: 5px 0 20px + a + color: #3b6085 + text-decoration: none + form + label + margin: 5px 0 + input + margin: 10px 0 + input[type="text"] + border: 1px solid #999 + +border-radius(5px) + +box-shadow(#ccc 1px 1px 2px 0 inset) + padding: 0px 5px + font-size: 22px + width: 465px + +serif + textarea + margin: 10px 0 0 + border: 1px solid #999 + +border-radius(5px) + +box-shadow(#ccc 1px 1px 2px 0 inset) + padding: 0px 5px + font-size: 22px + width: 465px + +serif + input[type="submit"] + float: right + .cleared + clear: both + height: 1px + + \ No newline at end of file diff --git a/app/stylesheets/_feeds.css.sass b/app/stylesheets/_feeds.css.sass new file mode 100644 index 000000000..a245e7b3f --- /dev/null +++ b/app/stylesheets/_feeds.css.sass @@ -0,0 +1 @@ +@import feeds/new diff --git a/app/stylesheets/_feeds.sass b/app/stylesheets/_feeds.sass deleted file mode 100644 index 4391c58c9..000000000 --- a/app/stylesheets/_feeds.sass +++ /dev/null @@ -1,3 +0,0 @@ -@import feeds/profile -@import feeds/new -@import feeds/invites diff --git a/app/stylesheets/_footer.css.sass b/app/stylesheets/_footer.css.sass new file mode 100644 index 000000000..722100da9 --- /dev/null +++ b/app/stylesheets/_footer.css.sass @@ -0,0 +1,31 @@ +div#footer, footer + background: #1b334a + height: 30px + border-top: 4px solid #fff + padding: 20px 10px 0px + margin: 20px 0 0 0 + width: auto + clear: both + color: #fff + #cont + +serif + width: 980px + margin: 0 auto + font-size: 12px + color: #3b6085 + a + color: #3b6085+#222 + font-weight: normal + text-decoration: none + .left_text + float: left + .right_text + float: right + font-size: 30px + line-height: 12px + a + display: inline-block + vertical-align: middle + margin-top: -8px + margin-left: 3px + font-size: 12px diff --git a/app/stylesheets/_form.css.sass b/app/stylesheets/_form.css.sass new file mode 100644 index 000000000..bd056055d --- /dev/null +++ b/app/stylesheets/_form.css.sass @@ -0,0 +1,158 @@ +form.new_account + +border-radius(5px) + + padding: + top: 10px + bottom: 10px + border: 1px solid #ddd + background: #f7f7f7 + + label + text-align: right + + input[type="text|password"] + +div#submit_post + +border-radius(5px) + margin-right: 10px + margin-bottom: 10px + padding: 10px + border: 1px solid #bbb + border-bottom-color: #888 + background-color: $lightestGray + + + +form.user + .inline-errors + font-size: 0.7em + display: block + line-height: 120% + width: 430px + font-weight: normal + font-family: Rockwell, Georgia, Helvetica, Sans-serif + color: #cc0000 + label + color: #3a3a3a + + +form.standard_form + + input[type=text], textarea, input[type=password] + font-size: 1em + width: 250px + +serif + + + textarea + height: 4em + + input[type=submit] + +styled-button + margin-left: 100px + 16px + position: relative + margin-top: 10px + font-size: 1em + +serif + + label + width: 100px + padding: 8px + font-size: 0.8em + font-weight: normal + font-family: Arial, Helvetica, sans-serif + color: #444 + text-align: right + position: relative + top: 0px + float: left + + p.inline-errors + font-size: 0.7em + display: inline + line-height: 120% + margin-left: 10px + font-weight: normal + +sans + color: #cc0000 + + p.hint + +sans + font-size: 0.8em + margin-left: 100px + 16px + + li.boolean + left: $labelWidth + 10px + label + text-align: left + + ul.inline + li + +inline-block + label + float: none + text-align: left + padding-left: 5px + width: 50px + + fieldset ol + li + display: block + position: relative + margin-bottom: 1em + +form.formtastic fieldset ol li.date + label + display: none + fieldset + ol + li + display: inline-block + label + text-align: right + display: inline-block + width: auto + margin-left: 15px + &:first-child label + width: $labelWidth + margin-left: 0px + +#say-something + form.formtastic + color: white + .inputs + input, textarea + border: 1px solid $inputBorders + + + p.inline-hints + text-align: right + padding: 3px + margin-right: 25px + font-size: 11px + color: $mediumGray + + .buttons + .skip + float: right + margin: 10px + +form.formtastic + color: $cpGrey + .inputs + input, textarea + border: 1px solid $inputBorders + + + p.inline-hints + text-align: right + padding: 3px + margin-right: 25px + font-size: 11px + color: $mediumGray + + .buttons + .skip + float: right + margin: 10px + diff --git a/app/stylesheets/_form.sass b/app/stylesheets/_form.sass deleted file mode 100644 index 429dfbd35..000000000 --- a/app/stylesheets/_form.sass +++ /dev/null @@ -1,161 +0,0 @@ -form.new_account - +border-radius(5px) - - padding: - top: 10px - bottom: 10px - border: 1px solid #ddd - background: #f7f7f7 - - label - text-align: right - - input[type="text|password"] - -div#submit_post - +border-radius(5px) - margin-right: 10px - margin-bottom: 10px - padding: 10px - border: 1px solid #bbb - border-bottom-color: #888 - background-color: $lightestGray - - -form#new_user - li - height: 65px - -form.user - .inline-errors - font-size: 0.7em - display: block - line-height: 120% - width: 430px - font-weight: normal - font-family: Rockwell, Georgia, Helvetica, Sans-serif - color: #cc0000 - label - color: #3a3a3a - - -form.standard_form - - input[type=text], textarea, input[type=password] - font-size: 1em - width: 250px - +serif - - - textarea - height: 4em - - input[type=submit] - +styled-button - margin-left: 100px + 16px - position: relative - margin-top: 10px - font-size: 1em - +serif - - label - width: 100px - padding: 8px - font-size: 0.8em - font-weight: normal - font-family: Arial, Helvetica, sans-serif - color: #444 - text-align: right - position: relative - top: 0px - float: left - - p.inline-errors - font-size: 0.7em - display: inline - line-height: 120% - margin-left: 10px - font-weight: normal - +sans - color: #cc0000 - - p.hint - +sans - font-size: 0.8em - margin-left: 100px + 16px - - li.boolean - left: $labelWidth + 10px - label - text-align: left - - ul.inline - li - +inline-block - label - float: none - text-align: left - padding-left: 5px - width: 50px - - fieldset ol - li - display: block - position: relative - margin-bottom: 1em - -form.formtastic fieldset ol li.date - label - display: none - fieldset - ol - li - display: inline-block - label - text-align: right - display: inline-block - width: auto - margin-left: 15px - &:first-child label - width: $labelWidth - margin-left: 0px - -#say-something - form.formtastic - color: white - .inputs - input, textarea - border: 1px solid $inputBorders - - - p.inline-hints - text-align: right - padding: 3px - margin-right: 25px - font-size: 11px - color: $mediumGray - - .buttons - .skip - float: right - margin: 10px - -form.formtastic - color: $cpGrey - .inputs - input, textarea - border: 1px solid $inputBorders - - - p.inline-hints - text-align: right - padding: 3px - margin-right: 25px - font-size: 11px - color: $mediumGray - - .buttons - .skip - float: right - margin: 10px - diff --git a/app/stylesheets/_header.css.sass b/app/stylesheets/_header.css.sass new file mode 100644 index 000000000..780ffa2d2 --- /dev/null +++ b/app/stylesheets/_header.css.sass @@ -0,0 +1,322 @@ +div#header, header + background: #1b334a + height: 31px + padding: 15px 0px + border-bottom: 4px solid #fff + margin: 0 + #nav_wrapper + width: 1004px + margin: 0 auto + position: relative + left: 4px + #logo + margin-top: 10px + text-decoration: none + text-align: left + color: #fff + font-size: 30px + line-height: 30px + +text-shadow(#000 -1px 1px 0) + span + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + color: #b7430c + font-size: 25px + position: relative + bottom: 1px + img + vertical-align: bottom + nav, .nav + padding: 14px 0 12px 0 + float: right + display: inline + a, .disabled_link + vertical-align: top + font-size: 14px + letter-spacing: .5px + padding: 3px 10px + color: #cee7fe + font-weight: normal + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + text-decoration: none + +text-shadow(#000 1px 1px 0px) + .notif + color: #fff + background: #ac322f + +border-radius(5px) + margin-left: 2px + padding: 0 5px + span + vertical-align: top + a.selected_nav + color: #fff + background: #2e4b67 + +border-radius(5px) + cursor: default + a:hover + color: #fff + background: #2e4b67 + +border-radius(5px) + div.user-feeds + display: inline + position: relative + padding-top: 13px + height: 22px + vertical-align: top + h4 + font-size: 14px + color: #cee7fe + font-weight: normal + height: 20px + width: 66px + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + display: inline + vertical-align: top + padding: 3px 10px + text-decoration: none + +text-shadow(#000 1px 1px 0px) + ul + display: none + top: 30px + right: 4px + position: absolute + z-index: 1000 + width: 148px + background-color: white + color: #3b6085 !important + border: 1px solid #3b6085 + +border-bottom-radius(5px) + +border-top-left-radius(5px) + li + &:first-child + +border-top-left-radius(5px) + &:last-child + +border-bottom-radius(5px) + border-bottom: 0px + &:hover + background-color: #eee + a + color: #3B6085 + +text-shadow() + background: #eee + padding: 5px 5px 3px + border-bottom: 1px solid #3b6085 + a + display: block + color: #3B6085 + +text-shadow() + &:hover + h4 + color: #fff + position: relative + z-index: 4 + background: #2e4b67 + +border-radius(5px) + cursor: default + ul + display: block + .remove_border + border: + top: 1px solid #ccc + bottom: none !important + left: 1px solid #ccc + right: 1px solid #ccc + +border-radius(5px 5px 0px 0px) + .border + border: 1px solid #ccc + +border-radius(5px) + +body.application.logged_out + div#header, header + margin: 20px auto 30px + width: 970px + background: none + border: none + + #logo + margin-top: 10px + text-decoration: none + text-align: left + color: #444545 + font-size: 30px + line-height: 30px + span + color: #b7430c + font-size: 25px + position: relative + bottom: 1px + img + vertical-align: bottom + + + nav, .nav + padding: 8px 0 12px 0 + float: right + display: inline + a, .disabled_link + vertical-align: top + font-size: 11px + padding: 5px 5px + color: $baseText + #222 + font-weight: bold + +sans + text-decoration: none + a.selected_nav + color: #3b6085 + a:hover + color: #3b6085 + div.user-feeds + display: inline + position: relative + padding-top: 5px + height: 22px + vertical-align: top + h4 + font-size: 11px + color: $baseText + #222 + font-weight: bold + height: 14px + width: 66px + +sans + display: inline + vertical-align: top + padding: 5px + right: 20px + background-image: image-url('nav-down-arrow.png') + background-repeat: no-repeat + background-position: 48px + border: 1px solid transparent + ul + display: none + top: 25px + left: 0px + position: absolute + z-index: 1 + width: 150px + background-color: white + border: 1px solid #3b6085 + +border-bottom-radius(5px) + +border-top-right-radius(5px) + li + &:first-child + +border-top-right-radius(5px) + &:last-child + +border-bottom-radius(5px) + border-bottom: 0px + &:hover + background-color: #eee + padding: 5px 5px 3px + border-bottom: 1px solid #3b6085 + a + display: block + + &:hover + h4 + +border-top-radius(5px) + border: 1px solid #3b6085 + bottom-color: #fff + color: #3b6085 + position: relative + z-index: 4 + background-color: white + ul + display: block + #user_sign_in + float: right + margin-top: 10px + position: relative + + li + margin: 0 5px + +inline-block + color: #545454 + #sign_in_button + background: url(/images/nav-down-arrow.png) no-repeat 95% 40% #fff + padding: 0px 20px 3px 5px + line-height: 18px + position: relative + z-index: 100 + cursor: pointer + border: 1px solid #ccc + +border-radius(5px) + &.open + border-bottom: 0px + +border-radius(5px 5px 0px 0px) + #sign_in_form + position: absolute + right: -1px + top: 3px + + form.user + position: absolute + right: 1px + top: 18px + z-index: 90 + width: 200px + background: #fff + border: 1px solid #ccc + +border-radius(5px 0px 5px 5px) + display: none + .inputs, .buttons + +inline-block + li + margin: 0 + display: block + width: 180px + padding: 2px 10px + text-align: right + + label + display: block + padding: 4px 2px + color: #b3b3b3 + +sans + text-align: left + font-weight: bold + font-size: 11px + + input[type=text], input[type=password] + +border-radius(5px) + width: 156px + outline: none + padding: 5px 7px + font-size: 0.9em + +box-shadow(#ccc 1px 1px 2px 0 inset) + margin: 0 + border: 1px solid #ddd + + .buttons + width: 180px + margin-top: 3px + .commit:first-child + float: right + width: 60px + li:last-child + padding-top: 5px + .commit + submit + vertical-align: middle + padding-bottom: 2px + .forgot-password + text-align: right + display: block + padding: 0 10px 10px + font-size: 12px + color: #3b6085 + ul.errors li + margin: 0px + text-align: right + padding: 0 10px 10px + color: #b7403c + font-size: 13px + #fb-login + position: relative + top: -4px + border: 1px solid #314A63 + +border-radius(5px) + background: #3B6085 + color: white + padding: 5px 5px + +box-shadow(inset 0px -1px 2px #335170, inset 0px 1px 0px #5883AD, 1px 1px 2px #999) + font-family: Rockwell, Georgia, serif + font-size: 11px + +text-shadow(#454545 1px 1px 0px) diff --git a/app/stylesheets/_info_box.sass b/app/stylesheets/_info_box.sass deleted file mode 100644 index 818828cd3..000000000 --- a/app/stylesheets/_info_box.sass +++ /dev/null @@ -1,146 +0,0 @@ -@import "compass/css3/opacity" - -.info_box - background-color: #fff - border: 1px solid #ccc - +border-radius(5px) - overflow: hidden - padding-bottom: 15px - min-height: 250px - h2 - color: #3b6085 - background-color: #eaeaea - border-bottom: 1px solid #ccc - text-align: center - font-size: 14px - padding: 5px - +border-top-radius(4px) - margin-bottom: 20px - - .avatar, .message_me - clear: both - float: left - margin-left: 15px - width: 120px - - .avatar - margin-bottom: 10px - #hidden_input - display: block - width: 140px - height: 40px - overflow: hidden - position: relative - left: -5px - #file_uploader - font-size: 50px - width: 120px - +opacity(0) - position: relative - top: -40px - left: -20px - #upload_file_button - width: 130px - height: 40px - background: transparent - border: none - position: relative - left: -5px - a - display: block - background: $cpRed - border: 1px solid #9E3533 - +border-radius(3px) - -webkit-box-shadow: inset 0px -3px 2px #9e3533, inset 0px 1px 0px #d14745, 1px 1px 1px #545454 - -moz-box-shadow: inset 0px -3px 2px #9e3533, inset 0px 1px 0px #d14745, 1px 1px 1px #545454 - box-shadow: inset 0px -3px 2px #9e3533, inset 0px 1px 0px #d14745, 1px 1px 1px #545454 - +text-shadow(#545454, 1px, 1px, 1px) - text-decoration: none - font-family: Rockwell, Helvetica, sans-sarif - font-size: 15px - color: white - height: 20px - padding: 5px - width: 110px - - a.message_me - display: block - text-align: center - +border-radius(4px) - text-decoration: none - color: #fff - border: 1px solid #355371 - span - +border-radius(3px) - padding: 6px 0 - display: block - background-color: #3b6085 - border: 1px solid #6c88a4 - bottom-color: #425d7d - - h3, .info, table - margin-left: 150px - margin-right: 15px - - h3 - color: #b53f3c - font-size: 24px - margin-bottom: 10px - - .info - line-height: 1.3 - font-size: 16px - margin-bottom: 20px - color: #454545 - font-family: Georgia - font-size: 0.9em - padding-bottom: 10px - - table - width: 300px - border-spacing: 0px 20px - - th - font-family: Rockwell, Helvetica, Arial, Verdana, sans-serif - color: #999 - font-size: 11px - line-height: 18px - font-weight: normal - padding-right: 10px - vertical-align: top - width: 100px - td - font-size: 14px - font-family: Georgia - line-height: 16px - color: #545454 - width: 168px - - - div[data-map] - border: 1px solid $lightGray - margin: 15px - bottom: 0px - clear: left - height: 200px - - - #big_calendar - margin-bottom: 10px - width: 120px - background-color: white - border: 1px solid $lightGray - text-align: center - +border-radius($defaultRadius) - .month - +sans - background-color: $calendarTopColor - +border-top-radius($defaultRadius) - color: white - padding: 7px 10px - font-size: 16px - line-height: 16px - .date - +oldStandardBold - font-size: 40px - padding: 25px diff --git a/app/stylesheets/_inline_form.sass b/app/stylesheets/_inline_form.sass deleted file mode 100644 index aedb1b2dc..000000000 --- a/app/stylesheets/_inline_form.sass +++ /dev/null @@ -1,43 +0,0 @@ - -.inline-form.editable - - [data-field][contenteditable] - border: 1px solid #749dc2 - padding: 3px - +border-radius(5px) - - .edit - display: none - - .show - display: block - - -.inline-form - position: relative - .edit, .show - position: absolute - width: 480px - margin-top: 3px - color: $cpBlue - text-align: right - span - &:hover - text-decoration: underline - .edit - .show - display: none - position: absolute - - form#avatar-form - width: 120px - text-align: center - clear: both - float: left - padding-left: 15px - input.file - display: none - - - - diff --git a/app/stylesheets/_jcrop.sass b/app/stylesheets/_jcrop.sass deleted file mode 100644 index 1e01d1077..000000000 --- a/app/stylesheets/_jcrop.sass +++ /dev/null @@ -1,53 +0,0 @@ -/* Fixes issue here http://code.google.com/p/jcrop/issues/detail?id=1 */ -.jcrop-holder - text-align: left - -.jcrop-vline, .jcrop-hline - font-size: 0 - position: absolute - background: white url('/images/Jcrop.gif') top left repeat - -.jcrop-vline - height: 100% - width: 1px !important - -.jcrop-hline - width: 100% - height: 1px !important - -.jcrop-handle - font-size: 1px - width: 7px !important - height: 7px !important - border: 1px #eee solid - background-color: #333 - *width: 9px - *height: 9px - -.jcrop-tracker - width: 100% - height: 100% - -.custom .jcrop-vline -.custom .jcrop-hline - background: yellow - -.custom .jcrop-handle - border-color: black - background-color: #C7BB00 - -moz-border-radius: 3px - -webkit-border-radius: 3px - -.hidden_field - display: none - -form.edit_avatar - +clearfix - -img#avatar_to_crop - display: block - margin-bottom: 1em - -input#avatar_submit - margin-top: 1em - float: right diff --git a/app/stylesheets/_logged_out_header.css.sass b/app/stylesheets/_logged_out_header.css.sass new file mode 100644 index 000000000..e85a53c9e --- /dev/null +++ b/app/stylesheets/_logged_out_header.css.sass @@ -0,0 +1,120 @@ +#header + background: #1b334a + height: 31px + padding: 15px 0px + border-bottom: 4px solid #fff + margin: 0 + + #nav_wrapper + width: 976px + margin: 0 auto + + #logo + margin-top: 10px + text-decoration: none + text-align: left + color: #444545 + font-size: 30px + line-height: 30px + +text-shadow(#000 -1px 1px 0) + span + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + color: #b7430c + font-size: 25px + position: relative + bottom: 1px + img + vertical-align: bottom + + #user_sign_in + float: right + margin-top: 2px + position: relative + + li + margin: 0 5px + +inline-block + color: #BCD5EC + font-size: 13px + font-weight: 300 + + #sign_in_button + + background: image-url("nav-down-arrow.png") no-repeat 95% 40% #e0e0e0 + padding: 2px 27px 3px 10px + line-height: 22px + position: relative + color: #1B334A + font-size: 13px + z-index: 100 + cursor: pointer + border: 1px solid #ccc + +border-radius(5px) + &.open + border-bottom: 0px + +border-radius(5px 5px 0px 0px) + #sign_in_form + z-index: 10000 + position: absolute + right: -1px + top: 15px + + form.user + position: absolute + right: 1px + top: 13px + z-index: 90 + width: 200px + background: #fff + border: 1px solid #ccc + +border-radius(5px 0px 5px 5px) + display: none + .inputs + +inline-block + li + margin: 0 + display: block + width: 180px + padding: 2px 5px + text-align: right + + label + display: block + padding: 4px 2px + color: #b3b3b3 + +sans + text-align: left + font-weight: bold + font-size: 11px + + input[type=text], input[type=password] + +border-radius(5px) + width: 165px + outline: none + padding: 5px 7px + font-size: 0.9em + +box-shadow(#ccc 1px 1px 2px 0 inset) + margin: 0 + border: 1px solid #ddd + + .buttons + margin-top: 3px + padding: 5px + .commit + float: right + + .forgot-password + text-align: right + display: block + padding: 0 10px 10px + font-size: 12px + color: #3b6085 + ul.errors li + margin: 0px + text-align: right + padding: 0 10px 10px + color: #b7403c + font-size: 13px + .facebook + padding-top: 3px + float: right diff --git a/app/stylesheets/_management_form.sass b/app/stylesheets/_management_form.sass deleted file mode 100644 index f03e0e5b8..000000000 --- a/app/stylesheets/_management_form.sass +++ /dev/null @@ -1,54 +0,0 @@ - -form.management_form - margin-bottom: 2em - +clearfix - label - +sans - font-size: 0.8em - color: #555 - text-align: right - width: $labelWidth - display: block - float: left - margin: 3px 10px 0 0 - line-height: 13px - input[type=text], input[type=password], textarea - width: 200px - +border-radius(5px) - background-color: white - border: 1px solid $mediumGray - padding: 3px 6px - margin: 0 - - input[type=submit] - +styled-button - margin-left: $labelWidth + 10px - - - ol - li - margin-bottom: 7px - +clearfix - p.inline-hints - text-align: right - padding: 3px - margin-right: 45px - font-size: 10px - color: $mediumGray - &.boolean - label - padding-left: $labelWidth - text-align: left - width: 200px - padding-bottom: 7px - input - float: left - margin-left: -15px - margin-right: 12px -p.form-hint - text-align: left - font-size: 11px - color: $mediumGray - margin: 15px 20px - line-height: 1.2 - \ No newline at end of file diff --git a/app/stylesheets/_measurements.css.sass b/app/stylesheets/_measurements.css.sass new file mode 100644 index 000000000..e0882f399 --- /dev/null +++ b/app/stylesheets/_measurements.css.sass @@ -0,0 +1,3 @@ +$defaultRadius: 5px +$inputBoxWidth: 300px +$labelWidth: 75px \ No newline at end of file diff --git a/app/stylesheets/_measurements.sass b/app/stylesheets/_measurements.sass deleted file mode 100644 index 063c0e4f2..000000000 --- a/app/stylesheets/_measurements.sass +++ /dev/null @@ -1,3 +0,0 @@ -$defaultRadius = 5px -$inputBoxWidth = 300px -$labelWidth = 75px \ No newline at end of file diff --git a/app/stylesheets/_mixins.css.sass b/app/stylesheets/_mixins.css.sass new file mode 100644 index 000000000..d7f2cc198 --- /dev/null +++ b/app/stylesheets/_mixins.css.sass @@ -0,0 +1,61 @@ +@mixin lightbox + background-color: #ebebeb + +border-radius(15px) + border: 10px solid #c5c5c5 + +box-shadow($default-box-shadow-color 0 0 $default-box-shadow-blur 0 inset) + padding: 25px + +@mixin redlightbox + background-color: #ac322f + +border-radius(4px) + padding: 24px + box-shadow: 0 0 18px 4px #f6fbeb + -webkit-box-shadow: 0 0 18px 4px #f6fbeb + -moz-box-shadow: 0 0 18px 4px #f6fbeb + +@mixin styled-item($color) + .header .header_text span.author + color: $color + a .reply_count + color: $color + +@mixin styledBox + +box-shadow($lightestGray 1px 1px 3px) + +border-radius($defaultRadius) + border: 1px solid $lightGray + background: $itemBackground + #020202 + +background-image(linear-gradient($itemBackground + #060606, $itemBackground)) + +@mixin starBullet + margin: 0 3px 15px 0px + background: image-url('bullet-star.png') + background-position: 0px 0px + background-repeat: no-repeat + line-height: 1.5 + font-weight: normal + +@mixin sans + font-family: Helvetica, Arial, Verdana, sans-serif + +@mixin serif + font-family: Georgia, Times, serif + font-family: Rockwell, Georgia, Times, "Times New Roman", serif + +@mixin styledInput + height: 14px + padding: 4px + border: 1px solid $mediumGray + @include sans + +border-radius(5px) + +@mixin styled-button + font-size: 1em + cursor: pointer + +border-radius(7px) + +serif + padding: 4px 7px + background-color: $offWhite + border: 1px solid $lightGray + &:hover + border-color: #888 + diff --git a/app/stylesheets/_mixins.sass b/app/stylesheets/_mixins.sass deleted file mode 100644 index a833dec0f..000000000 --- a/app/stylesheets/_mixins.sass +++ /dev/null @@ -1,97 +0,0 @@ -@mixin lightbox - background-color: #f5f5f5 - +border-radius(15px) - border: 10px solid #c5c5c5 - +box-shadow($default-box-shadow-color, 0, 0, $default-box-shadow-blur, 0, inset) - padding: 25px - - -@mixin styled-item($color) - .header .header_text span.author - color: $color - a .reply_count - color: $color - -@mixin styledBox - +box-shadow($lightestGray, 1px, 1px, 3px) - +border-radius($defaultRadius) - border: 1px solid $lightGray - background: $itemBackground + #020202 - +linear-gradient(color-stops($itemBackground + #060606, $itemBackground )) - -@mixin sans - font-family: Helvetica, Arial, Verdana, sans-serif - -@mixin serif - font-family: Georgia, Times, serif - font-family: Rockwell, Georgia, Times, "Times New Roman", serif - -@mixin styledInput - height: 14px - padding: 4px - border: 1px solid $mediumGray - @include sans - +border-radius(5px) - -@mixin styled-button - font-size: 1em - cursor: pointer - +border-radius(7px) - +serif - padding: 4px 7px - background-color: $offWhite - border: 1px solid $lightGray - &:hover - border-color: #888 - -@mixin red-button - float: right - +box-shadow(#aaa, 0, 2px, 2px, 0, false) - text-decoration: none - font-weight: bold - font-size: 10px - +sans - display: block - background-color: $cpRed - margin-top: 4px - border: 1px solid $cpDarkRed - #111 - +border-radius(5px) - input[type=submit] - background-color: transparent - margin: 0 - color: white - +text-shadow($cpDarkRed, 2px, 1px, 1px) - cursor: pointer - border: 1px solid $cpRed + #222 - bottom: 1px solid $cpDarkRed - display: block - padding: 3px 8px 4px - +border-radius(5px) - -@mixin oldStandardBold - font-family: 'OldStandardTTBold', Times, serif - font-weight: normal - -@mixin oldStandard - font-family: 'OldStandardTTRegular', Times, serif - -@font-face - font-family: 'OldStandardTTRegular' - src: url('/fonts/old-standard/OldStandard-Regular-webfont.eot') - src: local('OldStandardTTRegular'), url('/fonts/old-standard/OldStandard-Regular-webfont.woff') format('woff'), url('/fonts/old-standard/OldStandard-Regular-webfont.ttf') format('truetype'), url('/fonts/old-standard/OldStandard-Regular-webfont.svg') format('svg') - font-weight: normal - font-style: normal - -@font-face - font-family: 'OldStandardTTItalic' - src: url('/fonts/old-standard/OldStandard-Italic-webfont.eot') - src: local('OldStandardTTItalic'), url('/fonts/old-standard/OldStandard-Italic-webfont.woff') format('woff'), url('/fonts/old-standard/OldStandard-Italic-webfont.ttf') format('truetype'), url('/fonts/old-standard/OldStandard-Italic-webfont.svg') format('svg') - font-weight: normal - font-style: normal - -@font-face - font-family: 'OldStandardTTBold' - src: url('/fonts/old-standard/OldStandard-Bold-webfont.eot') - src: local('OldStandardTTBold'), url('/fonts/old-standard/OldStandard-Bold-webfont.woff') format('woff'), url('/fonts/old-standard/OldStandard-Bold-webfont.ttf') format('truetype'), url('/fonts/old-standard/OldStandard-Bold-webfont.svg') format('svg') - font-weight: normal - font-style: normal diff --git a/app/stylesheets/_modal.sass b/app/stylesheets/_modal.sass deleted file mode 100644 index 90d5b2df6..000000000 --- a/app/stylesheets/_modal.sass +++ /dev/null @@ -1,51 +0,0 @@ -body - z-index: 0 - -#modal.not_empty - - #modal-content - border: 1px solid black - z-index: 2000 - background-color: white - position: fixed - margin: auto - padding: 20px 40px - - h1 - font-size: 20px - color: #b7403c - margin: 10px 0px 20px - +serif - line-height: 120% - - p.subhead - font-size: 12px - line-height: 1.4 - color: #3b6085 - margin: 10px 0px 15px - +sans - - #modal-close - padding: 0 - background: transparent - float: right - position: absolute - top: -15px - left: -15px - - #modal-overlay - content: " " - position: fixed - bottom: 0 - top: 0 - left: 0 - right: 0 - background-color: black - - // opacity: order is important! - -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=52)" // ie8 - filter: alpha(opacity=52) // ie5-7 - opacity: 0.52 // everyone else - -#welcome - margin-bottom: 20px \ No newline at end of file diff --git a/app/stylesheets/_modal_form.sass b/app/stylesheets/_modal_form.sass deleted file mode 100644 index 1e16faa22..000000000 --- a/app/stylesheets/_modal_form.sass +++ /dev/null @@ -1,112 +0,0 @@ - -form.new_entity - width: 425px - display: block - a.skip - float: right - color: #888 - text-decoration: none - +sans - font-size: 0.8em - &:hover - text-decoration: underline - fieldset - ol - #submit_wrapper li - float: right - +box-shadow(#aaa, 0, 2px, 2px, 0, false) - text-decoration: none - font-weight: bold - font-size: 10px - +sans - display: block - background-color: $cpRed - margin-top: 4px - border: 1px solid $cpDarkRed - #111 - +border-radius(5px) - input[type=submit] - background-color: transparent - margin: 0 - color: white - font-weight: bold - font-size: 13px - +text-shadow($cpDarkRed, 2px, 1px, 1px) - cursor: pointer - border: 1px solid $cpRed + #222 - bottom: 1px solid $cpDarkRed - display: block - padding: 5px 10px - +border-radius(5px) - - li - margin: 15px 0 - label - +sans - font-size: 12px - color: #555 - margin: 6px 0 0 0 - width: 95px - font-weight: bold - float: left - input[type=text], textarea, input[type=password] - width: 320px - max-width: 320px - outline: none - margin: 0 - border: 1px solid #888 - padding: 4px - +serif - font-size: 1em - p.inline-hints - color: #555 - +sans - margin-right: 0 - &.check_boxes - margin-left: 95px - label - float: right - width: 300px - margin-top: 3px - &.radio - legend - +inline-block - width: 70px - float: left - font-weight: bold - fieldset ol - float: right - +inline-block - width: 330px - li - +inline-block - width: 150px - margin: 0 0 1px 0 - label - font-weight: normal - width: auto - &.after_registration - fieldset ol li - p.inline-hints - text-align: left - padding: 0 - margin: 2px 0 0 95px - font-size: 0.7em - line-height: 130% - - // fieldset ol li - // input[type=text], textarea, input[type=password] - // width: 410px - // max-width: 410px - // &#post_category_input fieldset legend - // label - // font-weight: bold - // label - // line-height: 130% - // margin-bottom: 6px - // display: block - // width: 100% - // font-weight: normal - // .strong - // font-weight: bold - // margin-bottom: 6px - // display: block \ No newline at end of file diff --git a/app/stylesheets/_neutral_notification.css.sass b/app/stylesheets/_neutral_notification.css.sass new file mode 100644 index 000000000..0e225da08 --- /dev/null +++ b/app/stylesheets/_neutral_notification.css.sass @@ -0,0 +1,12 @@ + +@mixin neutral-notification + background-color: #fffceb + text-align: center + padding: 10px 0 15px + font-family: Helvetica, sans-serif + color: #626059 + font-size: 12px + font-weight: normal + + a + color: #ac322f diff --git a/app/stylesheets/_notification.css.sass b/app/stylesheets/_notification.css.sass new file mode 100644 index 000000000..b44290853 --- /dev/null +++ b/app/stylesheets/_notification.css.sass @@ -0,0 +1,11 @@ +.notification + position: fixed + top: 200px + margin: 0 220px + width: 500px + background-color: white + padding: 5px + border: 5px solid #1B334A + z-index: 3000 + +border-radius(5px) + text-align: center \ No newline at end of file diff --git a/app/stylesheets/_password_resets.css.sass b/app/stylesheets/_password_resets.css.sass new file mode 100644 index 000000000..d416a2692 --- /dev/null +++ b/app/stylesheets/_password_resets.css.sass @@ -0,0 +1,61 @@ + +body.password_resets + h1 + font-size: 50px + color: #1b334a + padding: 18px 0px 12px + font-weight: bold + text-align: center + .inline-errors + width: 360px + margin: 0px 0px 0px 0px + padding: 0px + text-align: right + font-size: 11px + color: #FFFFFF + #email + +border-radius(5px) + +box-shadow(#ccc 1px 1px 2px 0 inset) + border: 1px solid #ccc + padding: 0px 5px + font-size: 22px + width: 400px + display: block + +serif + margin-top: 20px + #new_password_reset, .user.formtastic + +redlightbox + padding: 30px 40px + width: 430px + margin: 20px auto 100px + #submitimage + margin-top: 15px + margin-bottom: 10px + margin-left: 0px + width: 416px + p + text-align: left + float:left + margin-top: 7px + color: #FFFFFF + font-weight: bold + font-size: 16px + text-shadow: #000000 1px 1px 1px + padding-bottom: 8px + p.fail + text-align: left + float:left + color: #FFFFFF + font-weight: normal + font-size: 14px + text-shadow: #000000 0px -1px 1px + padding-bottom: 2px + + .lightbox + +redlightbox + padding: 30px 40px + width: 350px + margin: 80px auto 80px + .buttons li + padding: 10px 0 + diff --git a/app/stylesheets/_password_resets.sass b/app/stylesheets/_password_resets.sass deleted file mode 100644 index 542e869f7..000000000 --- a/app/stylesheets/_password_resets.sass +++ /dev/null @@ -1,28 +0,0 @@ - -body.password_resets - - #new_password_reset, .user.formtastic - +lightbox - width: 430px - margin: 40px auto - - h2 - color: #3b6085 - font-size: 24px - margin: 20px 0 - text-align: center - - - - p - margin: 10px 0px - - - .buttons li - padding: 10px 0 - - .lightbox - +lightbox - padding: 50px 10px - width: 440px - margin: 40px auto 100px \ No newline at end of file diff --git a/app/stylesheets/_profile_fields.sass b/app/stylesheets/_profile_fields.sass deleted file mode 100644 index 228272c91..000000000 --- a/app/stylesheets/_profile_fields.sass +++ /dev/null @@ -1,35 +0,0 @@ -ul#modules - +clearfix - li - width: 300px - li.profile_field - +clearfix - cursor: move - margin-bottom: 0.5em - background: $lightestGray - padding: 10px - +border-radius(7px) - h3 - +sans - font-size: 0.9em - font-weight: bold - color: #444 - padding-bottom: 3px - border-bottom: 1px solid #ccc - margin-bottom: 4px - span.admin - float: right - a, input[type=submit] - font-size: 0.8em - color: #666 - font-weight: normal - - p - color: #444 - line-height: 140% - font-size: 0.8em - -.new_profile_field, .save_profile_fields - float: left -.continue - float: right \ No newline at end of file diff --git a/app/stylesheets/_replies.sass b/app/stylesheets/_replies.sass deleted file mode 100644 index 861182dfa..000000000 --- a/app/stylesheets/_replies.sass +++ /dev/null @@ -1,84 +0,0 @@ - -.item - .replies - background-color: #efefef - margin: 10px - padding: 10px - +pie-clearfix - position: relative - - img.avatar - float: left - width: 30px - height: 30px - margin-right: 10px - img.reply-arrow - position: absolute - top: -10px - - - form - margin-top: 5px - textarea - padding: 5px - max-width: 383px - width: 383px - border: 1px solid $mediumGray - outline: none - margin: 0 - +serif - font-size: 12px - #submit_wrapper - float: right - +box-shadow( rgba(0, 0, 0, 0.2), 0, 2px, 2px, 0, false) - text-decoration: none - font-weight: bold - font-size: 10px - +sans - display: block - background-color: $cpRed - margin-top: 4px - border: 1px solid $cpDarkRed - #111 - +border-radius(5px) - input[type=submit] - background-color: transparent - margin: 0 - color: white - +text-shadow($cpDarkRed, 2px, 1px, 1px) - cursor: pointer - border: 1px solid $cpRed + #222 - bottom: 1px solid $cpDarkRed - display: block - padding: 3px 8px 4px - +border-radius(5px) - - - .reply - +clearfix - margin-bottom: 10px - &:hover - background-color: #fafafa - - a - display: block - text-decoration: none - .body - float: right - width: 394px - font-size: 12px - +serif - line-height: 1.2 - color: black - time - color: #555 - font-size: 10px - +sans - white-space: nowrap - .author - font-size: 11px - color: $baseRed - font-weight: bold - text-decoration: none - &:hover - text-decoration: underline - +sans diff --git a/app/stylesheets/_say_something.sass b/app/stylesheets/_say_something.sass deleted file mode 100644 index b80468995..000000000 --- a/app/stylesheets/_say_something.sass +++ /dev/null @@ -1,96 +0,0 @@ -#say-something - background-color: #b53f3c - +border-radius(5px) - border: 1px solid #8c3431 - +clearfix - h2 - background-color: #792e2e - +border-top-radius(3px) - border: 1px solid #c95855 - bottom: 0px - text-align: center - color: #fff - font-size: 14px - padding: 5px - - nav - float: left - margin: 20px -130px 20px 15px - a - margin-bottom: 8px - display: block - padding: 5px 20px 5px 5px - height: 40px - font-size: 11px - line-height: 13px - width: 85px - background-color: #a63b38 - color: #fff - +border-radius(4px) - text-decoration: none - background-image: url('/images/say-something-unselected.png') - background-position: 95px - background-repeat: no-repeat - &:hover, &.current - background-image: url('/images/say-something-selected.png') - background-color: #792e2e - .information - p - line-height: 1.3 - a - color: #eee - a.create-a-feed - margin: 20px 0px - text-align: center - padding-right: 50px - display: block - - form, .information - padding: 15px 12px 15px 135px - +clearfix - display: block - border: 1px solid #c95855 - top: 0px - +border-bottom-radius(3px) - - legend - width: 175px - font-size: 14px - color: white - - fieldset.inputs li - margin-bottom: 15px - - label - display: block - color: #fff - padding-bottom: 5px - font-size: 14px - - input[type=text], textarea - width: 330px - font-size: 18px - padding: 3px - +border-radius(5px) - +box-shadow(#eee, 1px, 1px, 3px, 0, inset) - - fieldset.buttons - float: right - li - display: inline - li.plaxo - a - color: #3b6085 - background-color: #fff - +border-radius(4px) - font-size: 10px - padding: 3px - text-decoration: none - font-weight: bold - li.commit - padding-left: 10px - - - &#new_post - textarea - height: 130px \ No newline at end of file diff --git a/app/stylesheets/_site_info.css.sass b/app/stylesheets/_site_info.css.sass new file mode 100644 index 000000000..ebfc15fca --- /dev/null +++ b/app/stylesheets/_site_info.css.sass @@ -0,0 +1,33 @@ +body.site.terms, .site.privacy, .site.dmca + + .info + margin: auto + width: 800px + background-color: #fff + +border-radius(5px) + border: 7px $lightGray solid + padding: 1em + line-height: 100% + +sans + p, li + font-size: 0.7em + margin-bottom: 0.5em + ol + list-style-type: decimal + ul + list-style-type: disc + li + margin-left: 2em + + h1 + font-size: 1em + h2 + font-size: 0.9em + h3 + font-size: 0.8em + h4 + margin-top: 1em + h1, h2, h3, h4 + font-weight: bold + margin-bottom: 0.5em + width: auto \ No newline at end of file diff --git a/app/stylesheets/_site_info.sass b/app/stylesheets/_site_info.sass deleted file mode 100644 index 7af2f1e42..000000000 --- a/app/stylesheets/_site_info.sass +++ /dev/null @@ -1,36 +0,0 @@ -#modal.info.not_empty - #modal-content - padding: 0 - border: none - +border-radius(5px) - .info - width: 800px - height: 400px - overflow-y: scroll - +border-radius(5px) - border: 7px $lightGray solid - padding: 1em - line-height: 100% - +sans - p, li - font-size: 0.7em - margin-bottom: 0.5em - ol - list-style-type: decimal - ul - list-style-type: disc - li - margin-left: 2em - - h1 - font-size: 1em - h2 - font-size: 0.9em - h3 - font-size: 0.8em - h4 - margin-top: 1em - h1, h2, h3, h4 - font-weight: bold - margin-bottom: 0.5em - width: auto \ No newline at end of file diff --git a/app/stylesheets/_small_calendar.sass b/app/stylesheets/_small_calendar.sass deleted file mode 100644 index c49da17bc..000000000 --- a/app/stylesheets/_small_calendar.sass +++ /dev/null @@ -1,28 +0,0 @@ - -.small_calendar - +inline-block - width: 50px - height: 50px - margin-right: 7px - margin-bottom: 5px - vertical-align: top - text-align: center - background-color: white - - .month - +sans - background-color: #b43e3b - +border-top-radius($defaultRadius) - color: white - padding: 1px 7px - font-size: 11px - line-height: 16px - - .date - +serif - font-size: 22px - padding: 4px 0 - background-color: $lightestGray - height: 23px - border-top: 1px solid white - +border-bottom-radius(7px) diff --git a/app/stylesheets/_syndicate.sass b/app/stylesheets/_syndicate.sass deleted file mode 100644 index ed6f56f8b..000000000 --- a/app/stylesheets/_syndicate.sass +++ /dev/null @@ -1,109 +0,0 @@ -@import replies - -.application - - #syndicate - +min-height(800px) - h3 - background-color: #EFEFEF - padding: 7px 12px - color: $postColorDark - border: 1px solid #ccc - top: 1px solid #DDD - bottom: 1px solid #DDD - left: 1px solid #CCC - right: 1px solid #CCC - font: - size: 13px - a - text-decoration: none - color: $postColorDark - .items - background-color: white - padding: 0px - border: none - +border-bottom-radius(5px) - - .item - background-color: white - border: 1px solid - bottom-color: #ddd - left-color: #ccc - right-color: #ccc - top-width: 0px - - padding: 0px - overflow: hidden - &:last-child - border-bottom: none - > .user, > .post, > .event, > .announcement, > .feed - &:hover - background-color: $listingHover - - > .user, > .post, > .event, > .announcement, > .feed - text-decoration: none - +border-radius(5px) - padding: 12px 10px - +clearfix - display: block - color: $baseText - .title, .author, .body - margin-left: 62px - .body - font-family: Georgia, Times, 'Times New Roman', serif - font-size: 0.8em - line-height: 1.4 - color: $textColor - p - margin-top: 0.8em - .title - color: $titleColor - font-size: 18px - margin-bottom: 3px - display: block - text-decoration: none - font-family: Georgia, Times, 'Times New Roman', serif - .author - font-size: 13px - color: $subtitle - line-height: 1.2 - margin-bottom: 5px - font-family: Rockwell, Helvetica, Arial, Verdana, sans-serif - font-weight: normal - time - float: right - color: #888 - font-family: Rockwell, Helvetica, Arial, Verdana, sans-serif - font-size: 11px - font-weight: normal - .avatar - float: left - width: 50px - height: 50px - display: block - border: 1px solid #aaa - +border-radius(5px) - - .reply-count - float: left - width: 50px - margin-top: 5px - text-align: center - font-size: 10px - clear: left - font-family: Rockwell, Helvetica, Arial, Verdana, sans-serif - - &.user, &.feed - a - .avatar - height: 30px - width: 30px - .title - margin-left: 40px - margin-top: 4px - &.odd - background-color: white - &.even - background-color: #efefef - &:hover - background-color: $listingHover diff --git a/app/stylesheets/_tipsy.sass b/app/stylesheets/_tipsy.sass deleted file mode 100644 index 0cf0eaf4d..000000000 --- a/app/stylesheets/_tipsy.sass +++ /dev/null @@ -1,23 +0,0 @@ -.tipsy - padding: 5px - font-size: 11px - @include sans - opacity: 0.8 - filter: alpha(opacity=80) - background-repeat: no-repeat - background-image: url(../images/tipsy.gif) -.tipsy-inner - padding: 5px 8px 5px 8px - background-color: black - color: white - max-width: 200px - text-align: center - +border-radius(3px) -.tipsy-north - background-position: top center -.tipsy-south - background-position: bottom center -.tipsy-east - background-position: right center -.tipsy-west - background-position: left center \ No newline at end of file diff --git a/app/stylesheets/_whats_happening.sass b/app/stylesheets/_whats_happening.sass deleted file mode 100644 index c8d0f7bbe..000000000 --- a/app/stylesheets/_whats_happening.sass +++ /dev/null @@ -1,55 +0,0 @@ -#whats-happening - float: right - - h2 - background-color: #EFEFEF - border: 1px solid #ccc - +border-top-radius(5px) - padding: 1px - - #tooltip - +serif - color: $cpDarkBlue - font-size: 14px - text-align: center - background-color: #EFEFEF - +border-top-radius(5px) - padding: 5px - - #zones - border: - top: 0px - left: 1px solid #ccc - right: 1px solid #ccc - padding: 8px 4px - background-color: white - - a - width: 95px - +inline-block - text-align: center - vertical-align: top - font-size: 12px - text-decoration: none - div - margin: 5px auto - width: 65px - height: 65px - &:hover div, &.selected_nav div - background-position: 0px 66px - &.posts div - background-image: url("/images/posts.png") - &.announcements div - background-image: url("/images/announcements.png") - &.events div - background-image: url("/images/events.png") - &.people div - background-image: url("/images/people.png") - &.feeds div - background-image: url("/images/feeds.png") - &.goods-and-skills div - background-image: url("/images/goods-and-skills.png") - - span - color: $cpGrey - cursor: pointer \ No newline at end of file diff --git a/app/stylesheets/accounts/_delete.css.sass b/app/stylesheets/accounts/_delete.css.sass new file mode 100644 index 000000000..f60069cff --- /dev/null +++ b/app/stylesheets/accounts/_delete.css.sass @@ -0,0 +1,17 @@ +body.application.accounts.delete + form.formtastic.user + margin: auto + +lightbox + width: 600px + margin-top: 80px + margin-bottom: 70px + + p + font-size: 18px + line-height: 24px + + .buttons li + display: block + margin: 50px 0 + text-align: center + \ No newline at end of file diff --git a/app/stylesheets/accounts/_edit.css.sass b/app/stylesheets/accounts/_edit.css.sass new file mode 100644 index 000000000..1a9d52083 --- /dev/null +++ b/app/stylesheets/accounts/_edit.css.sass @@ -0,0 +1,101 @@ +body.application.accounts.edit, body.application.accounts.profile + + h1 + font-size: 20px + padding: 10px + width: auto + text-shadow: auto + + form.formtastic.user + margin: auto + +lightbox + width: 480px + margin-top: 50px + margin-bottom: 70px + + fieldset.inputs + width: 450px + > ol > li + margin: 20px 0px + label, input, textarea + color: #444545 + display: block + padding: 0px 3px + float: none + font-size: 18px + +serif + margin: 10px + width: 100% + > input[type=file] + border: none + > input, > textarea + height: 38px + +border-radius(5px) + +box-shadow(#eee 1px 1px 3px 0 inset) + p.inline-errors + margin-left: 10px + color: #b53f3c + p.inline-hints + float: left + margin: 30px 0px 0px -90px + font-size: 12px + font-weight: bold + +sans + color: #3a3a3a + li.boolean + width: auto + input + width: auto + display: inline + height: auto + + fieldset.inputs + > li + padding: 0px 20px + width: 400px + > input, > textarea + width: 100% + > textarea + height: 70px + > label + margin: 10px + + li.radio + width: 570px + fieldset + + ol + display: inline + li + display: inline + label + display: inline + width: auto + input + display: inline + height: auto + width: auto + padding: 0 5px + li.radio#user_post_receive_method_input + width: 570px + fieldset + ol + display: inline + li + display: block + label + display: inline + width: auto + input + display: inline + height: auto + width: auto + padding: 0 5px + + fieldset.buttons + text-align: right + + input + padding: 10px + li.delete + float: left diff --git a/app/stylesheets/accounts/_edit.sass b/app/stylesheets/accounts/_edit.sass deleted file mode 100644 index ef4fd8dae..000000000 --- a/app/stylesheets/accounts/_edit.sass +++ /dev/null @@ -1,63 +0,0 @@ -body.application.accounts.edit - - h1 - font-size: 20px - padding: 10px - width: auto - text-shadow: auto - - form.formtastic.user - margin: auto - +lightbox - width: 600px - margin-top: 50px - margin-bottom: 70 - - fieldset.inputs - li - margin: 20px 0px - label, input, textarea - color: #444545 - display: block - float: none - font-size: 18px - +serif - margin: 10px - width: 100% - - input, textarea - height: 38px - +border-radius(5px) - +box-shadow(#eee, 1px, 1px, 3px, 0, inset) - p.inline-errors - margin-left: 10px - color: #b53f3c - p.inline-hints - float: left - margin: 30px 0px 0px -90px - font-size: 12px - font-weight: bold - +sans - color: #3a3a3a - li.boolean - width: auto - input - width: auto - display: inline - height: auto - label - - fieldset.inputs - li - padding: 0px 20px - width: 400px - input, textarea - width: 100% - textarea - height: 70px - label - margin: 10px - fieldset.buttons - text-align: right - input - padding: 10px diff --git a/app/stylesheets/accounts/_edit_interests.sass b/app/stylesheets/accounts/_edit_interests.sass deleted file mode 100644 index 9c5cc4e1c..000000000 --- a/app/stylesheets/accounts/_edit_interests.sass +++ /dev/null @@ -1,63 +0,0 @@ -body.application.accounts.edit_interests - #register - width: 1000px - form - background-image: url('/images/step-5-icon.png') - background-repeat: no-repeat - background-position: 20px 30px - padding: 25px - +clearfix - h2 - margin: 15px 55px 50px - font-size: 16px - font-weight: bold - +serif - fieldset.inputs - float: left - height: 260px - legend - label - display: block - color: #3b6085 - margin: 0px - padding-bottom: 10px - input - display: none - fieldset li - +inline-block - margin-bottom: 15px - label - +border-radius(5px) - background-color: #b53f3c - margin: 3px - padding: 5px - display: none - +serif - font-size: 10px - color: white - font-weight: normal - - fieldset.inputs, #interest_list_toggles - background-color: white - width: 240px - padding: 15px - +border-radius(7px) - - #interest_list_toggles - float: right - width: 660px - padding: 5px - td - color: #b53f3c - font-weight: bold - background-color: #f2f2f2 - height: 60px - border: 5px white solid - text-align: center - vertical-align: middle - &:hover, &.checked - color: white - background-color: #3b6085 - fieldset.buttons - margin-top: 30px - float: right diff --git a/app/stylesheets/accounts/_edit_new.sass b/app/stylesheets/accounts/_edit_new.sass deleted file mode 100644 index e8489661a..000000000 --- a/app/stylesheets/accounts/_edit_new.sass +++ /dev/null @@ -1,48 +0,0 @@ - -body.application.accounts.edit_new, body.application.accounts.update_new - h1 - margin: 20px auto - font-size: 28px - #register - +lightbox - margin: 20px auto - width: 960px - form - background-image: url('/images/step-4-icon.png') - background-repeat: no-repeat - background-position: 00px 30px - padding-left: 50px - +clearfix - .info_box - float: right - fieldset.inputs - li - margin: 20px 0 - padding: 0px 20px - width: 390px - input[type=text], input[type=password], textarea - +border-radius(5px) - +box-shadow(#ccc, 1px, 1px, 2px, 0, inset) - font-size: 22px - +serif - padding: 0 5px - width: 400px - margin: 10px 0 - input[type=file] - font-size: 20px - width: 400px - margin: 10px 0 - textarea - height: 70px - label - display: block - - fieldset.buttons - margin-top: 20px - text-align: right - width: 435px - .info_box - width: 450px - #info_box_top - background-color: #b53f3c - color: white diff --git a/app/stylesheets/accounts/_facebook_invite.css.sass b/app/stylesheets/accounts/_facebook_invite.css.sass new file mode 100644 index 000000000..b555d368f --- /dev/null +++ b/app/stylesheets/accounts/_facebook_invite.css.sass @@ -0,0 +1,130 @@ +body.application.accounts.facebook_invite + #planewrapper + margin: 0px auto 10px + text-align: center + a:link, a:visited,a:hover, a:active + text-decoration: none + #plane + width: 960px + background-image: image-url('invite/plane.png') + background-repeat: no-repeat + background-position: 114px 10px + padding-top: 25px + padding-bottom: 25px + margin: 0px auto + text-align: center + h1 + color: #172b3f + line-height: 25px + font-weight: bold + font-size: 40px + text-align: center + margin-left: -5px + h2 + color: #71879a + line-height: 1.2 + font-weight: bold + font-size: 22px + text-align: center + h3 + color: #FFFFFF + line-height: 1em + font-weight: bold + font-size: 23px + text-shadow: #631e1c 2px 2px 0px + text-align: left + margin-bottom: 5px + + h4 + color: #000000 + font-size: 17px + text-align: left + text-shadow: #a94946 1px 1px 0px + font-weight: normal + line-height: 1.25em + padding: 5px 0 17px 0 + h5 + color: #FFFFFF + font-size: 17px + text-align: left + text-shadow: none + font-weight: normal + line-height: .8em + padding: 0px 0 0px 0 + margin-top: 15px + h6 + @extend h4 + font-size: 14px + font-weight: bold + #groups_box + +redlightbox + width: 380px + margin: 20px auto + margin-bottom: 40px + position: relative + font-family: Rockwell, Georgia, Times, serif + + #groups_wrapper + width: 100% + text-align: center + margin: 0 auto + li + margin-left: 2px + display: block + padding: 0px 0 15px 0 + #custom-tweet-button + height: 41px + width: 356px + margin-top: 10px + #step + padding: 0px 0px 0px 0px + text-align: left + width: 378px + #step_one + @extend #step + height: 133px + #step_two + @extend #step + height: 185px + #step_three + @extend #step + height: 542px + #step_four + @extend #step + height: 200px + #step_five + @extend #step + height: 150px + label + display: none + #invite_email + text-align: left + vertical-align: top + height: 95px + padding: 6px 10px 6px + font-size: 16px + width: 356px + border: 1px solid #982c28 + +border-radius(5px) + +box-shadow(#1B0A1C 1px 1px 6px 0px inset) + +serif + #invite_body + vertical-align: top + text-align: left + height: 110px + width: 356px + padding: 8px 10px 8px + font-size: 14px + border: 1px solid #982c28 + +border-radius(5px) + +box-shadow(#1B0A1C 1px 1px 6px 0px inset) + +serif + margin-top: 14px + margin-bottom: 0px + input[type="submit"] + width: 356px + height: 41px + margin: 5px auto 15px auto + font-family: Rockwell, Georgia, Times, serif + #doctable + vertical-align: middle diff --git a/app/stylesheets/accounts/_learn_more.css.sass b/app/stylesheets/accounts/_learn_more.css.sass new file mode 100644 index 000000000..d956487cb --- /dev/null +++ b/app/stylesheets/accounts/_learn_more.css.sass @@ -0,0 +1,52 @@ + +body.application.accounts.learn_more + + h1 a + text-decoration: none + color: #b53f3c + + #learn-more + text-align: center + width: 960px + padding: 10px + margin: auto + img + margin: 0px auto + ul + list-style-image: image-url('bullet-pin.png') + +serif + margin-top: 20px + margin-left: 10px + min-height: 200px + li + font: + size: 20px + color: #444 + margin: 25px 0px 25px 40px + padding-left: 10px + .p_wrapper + text-align: center + width: 860px + padding: 0 40px + p + line-height: 1.4 + text-align: left + margin: 40px 0px + font-size: 18px + color: #444 + + #small_arrows + font: + size: 16px + #content + width: 800px + padding: + left: 70px + right: 70px + text-align: left + .img_wrapper + text-align: center + margin: 0 auto + a + color: #b53f3c + text-decoration: none \ No newline at end of file diff --git a/app/stylesheets/accounts/_learn_more.sass b/app/stylesheets/accounts/_learn_more.sass deleted file mode 100644 index 49a16093b..000000000 --- a/app/stylesheets/accounts/_learn_more.sass +++ /dev/null @@ -1,45 +0,0 @@ - -body.application.accounts.learn_more - - #learn-more - text-align: center - width: 960px - padding: 10px - margin: auto - img - margin: 0px auto - ul - list-style-image: url('/images/bullet-pin.png') - +serif - margin-top: 20px - margin-left: 10px - min-height: 200px - li - font: - size: 20px - color: #444 - margin: 25px 0px 25px 40px - padding-left: 10px - - p - line-height: 1.4 - text-align: left - margin: 40px 0px 40px 20px - font-size: 18px - color: #444 - - #small_arrows - font: - size: 16px - #content - width: 800px - padding: - left: 70px - right: 70px - text-align: left - .img_wrapper - text-align: center - margin: 0 auto - a - color: #b53f3c - text-decoration: none \ No newline at end of file diff --git a/app/stylesheets/accounts/_new.sass b/app/stylesheets/accounts/_new.sass deleted file mode 100644 index 614197f83..000000000 --- a/app/stylesheets/accounts/_new.sass +++ /dev/null @@ -1,116 +0,0 @@ - -body.application.accounts.new, body.application.accounts.create - - position: relative - h1 - header - position: relative - - html - position: relative - - #main - width: 610px - position: relative - #create-a-feed - position: absolute - right: -225px - top: 30px - width: 175px - background-color: #b53f3c - color: white - padding: 20px 0px 20px 30px - z-index: 1 - +border-radius(15px) - border: 10px solid #922f2c - +box-shadow($default-box-shadow-color, 0, 0, $default-box-shadow-blur, 0, inset) - - h3 - font-weight: bold - margin-bottom: 15px - line-height: 1.2 - p - font-size: 14px - line-height: 1.4 - margin-bottom: 15px - - form - position: relative - margin: 50px auto 70px - +lightbox - width: 560px - z-index: 2 - h3 - text-align: center - font-size: 20px - color: #525252 - .fb_button - position: relative - top: -5px - fieldset.inputs - li - padding-left: 100px - list-style-position: outside - background-repeat: no-repeat - background-position: 20px 0px - margin: 20px 0px - width: 485px - - label - display: block - margin: 5px 0 - input - +border-radius(5px) - +box-shadow(#ccc, 1px, 1px, 2px, 0, inset) - padding: 0px 7px - font-size: 22px - width: 430px - +serif - - li#user_address_input - p.inline-hints - float: left - margin: 30px 0px -50px -90px - font-size: 12px - font-weight: bold - +sans - color: #3a3a3a - - fieldset.buttons - margin-right: 12px - li - float: right - #bubble-1, #bubble-2 - display: none - - background-image: url('/images/speech-bubble.png') - width: 160px - height: 50px - text-align: center - padding: 42px 13px 60px - bottom: -140px - position: absolute - +serif - color: #3b6085 - +text-shadow(#bfc8cf, 1px, 1px, 1px) - #bubble-1 - left: -17px - #bubble-2 - right: 0 - - li#user_full_name_input - background-image: url('/images/step-1-icon.png') - li#user_email_input - background-image: url('/images/step-2-icon.png') - li#user_address_input - background-image: url('/images/step-3-icon.png') - - - &.short - - h1 - - li#user_password_input - background-image: url('/images/step-3-icon.png') - li#user_address_input - background-image: url('/images/step-4-icon-large.png') \ No newline at end of file diff --git a/app/stylesheets/admin/show_referrers.css.sass b/app/stylesheets/admin/show_referrers.css.sass new file mode 100644 index 000000000..fed274aea --- /dev/null +++ b/app/stylesheets/admin/show_referrers.css.sass @@ -0,0 +1,25 @@ +body.application.admin.show_referrers + #referral_sources + text-align: center + + table + border-width: 1px + border-style: solid + border-color: #000000 + border-collapse: collapse + margin-left: auto + margin-right: auto + + th, td + border-width: 1px + border-style: solid + border-color: #000000 + text-align: center + + th + font-weight: bold + + h1 + margin: 20px auto + font-size: 28px + color: blue diff --git a/app/stylesheets/administration.css.sass b/app/stylesheets/administration.css.sass new file mode 100644 index 000000000..3af483e93 --- /dev/null +++ b/app/stylesheets/administration.css.sass @@ -0,0 +1,78 @@ +@import core + +#wrap + width: 1000px + margin: auto + +header, #header + margin-top: 30px + background-color: #D74F4C + +border-top-radius(7px) + overflow: hidden + h1 + +inline-block + color: white + background-color: #B53F3C + width: 186px + padding: 10px 7px + h2 + +inline-block + color: white + +#admin_global_nav + overflow: hidden + width: 200px + display: block + +border-bottom-left-radius(7px) + float: left + + a + display: block + background-color: #E9C5C3 + padding: 8px 10px 8px 15px + &.selected_nav + background-color: #D4706E + +#main + width: 798px + border: + left: 1px solid black + right: 1px solid black + +table + margin: 30px auto + width: 650px + tr.even + background-color: $offWhite + tr.odd + background-color: white + th + font-weight: bold + th,td + padding: 5px 10px + +div[data-map] + height: 600px + width: 850px + +#addresses + float: right + height: 600px + width: 400px + overflow-y: auto + + +.formtastic + fieldset + background-color: tan + width: 300px + margin: 10px + padding: 10px + +#profile_fields + float: right +.formtastic.feed + float: left + width: 300px + + \ No newline at end of file diff --git a/app/stylesheets/administration.sass b/app/stylesheets/administration.sass deleted file mode 100644 index aa0681078..000000000 --- a/app/stylesheets/administration.sass +++ /dev/null @@ -1,78 +0,0 @@ -@import core - -#wrap - width: 1000px - margin: auto - -header - margin-top: 30px - background-color: #D74F4C - +border-top-radius(7px) - overflow: hidden - h1 - +inline-block - color: white - background-color: #B53F3C - width: 186px - padding: 10px 7px - h2 - +inline-block - color: white - -#admin_global_nav - overflow: hidden - width: 200px - display: block - +border-bottom-left-radius(7px) - float: left - - a - display: block - background-color: #E9C5C3 - padding: 8px 10px 8px 15px - &.selected_nav - background-color: #D4706E - -#main - width: 798px - border: - left: 1px solid black - right: 1px solid black - -table - margin: 30px auto - width: 650px - tr.even - background-color: $offWhite - tr.odd - background-color: white - th - font-weight: bold - th,td - padding: 5px 10px - -div[data-map] - height: 600px - width: 850px - -#addresses - float: right - height: 600px - width: 400px - overflow-y: auto - - -.formtastic - fieldset - background-color: tan - width: 300px - margin: 10px - padding: 10px - -#profile_fields - float: right -.formtastic.feed - float: left - width: 300px - - \ No newline at end of file diff --git a/app/stylesheets/application.css.sass b/app/stylesheets/application.css.sass new file mode 100644 index 000000000..8fe8b38c0 --- /dev/null +++ b/app/stylesheets/application.css.sass @@ -0,0 +1,473 @@ +//= require chosen +@import core +@import form +@import accounts +@import site_info +@import feeds +@import password_resets +@import faq + +#footer_grad + height: 10px + width: 100% + +background-image(linear-gradient(#ddd, #efefef)) +footer, #footer + +clearfix + +sans + font-size: 0.8em + width: 972px + margin: 1em auto + color: #333 + .left_text + float: left + .right_text + float: right + a + font-weight: bold + color: #333 + text-decoration: none + &:hover + text-decoration: underline + +.clear + clear: both + +body.application + background: #cee7fe image-url('neighborhood-bg.png') repeat-x center bottom + +serif + + #sticky-wrapper + +pie-clearfix + position: relative + min-height: 100% // real browsers + height: auto !important // real browsers + height: 100% // IE6: treaded as min-height + + footer, #footer + width: 100% + position: absolute + bottom: 0 + + header, #header + background: #1b334a + height: 31px + padding: 15px 0 + border-bottom: 4px solid #fff + margin: 0 0 20px 0 + #nav_wrapper + width: 976px + margin: 0 auto + //white-space: nowrap + #logo + //white-space: nowrap + margin-top: 10px + text-decoration: none + text-align: left + color: #fff + font-size: 30px + line-height: 30px + +text-shadow(#000 -1px 1px 0) + span + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + color: #b7430c + font-size: 25px + position: relative + bottom: 1px + img + vertical-align: bottom + nav, .nav + padding: 14px 0 12px 0 + float: right + display: inline + a, .disabled_link + vertical-align: top + font-size: 14px + letter-spacing: .5px + padding: 3px 10px + color: #cee7fe + font-weight: normal + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + text-decoration: none + +text-shadow(#000 1px 1px 0px) + a.selected_nav + color: #fff + background: #2e4b67 + +border-radius(5px) + cursor: default + a:hover + color: #fff + background: #2e4b67 + +border-radius(5px) + div.user-feeds + display: inline + position: relative + padding-top: 13px + height: 22px + vertical-align: top + h4 + font-size: 14px + color: #cee7fe + font-weight: normal + height: 20px + width: 66px + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + display: inline + vertical-align: top + padding: 3px 10px + text-decoration: none + +text-shadow(#000 1px 1px 0px) + ul + display: none + top: 30px + right: 4px + position: absolute + z-index: 1000 + width: 148px + background-color: white + color: #3b6085 !important + border: 1px solid #3b6085 + +border-bottom-radius(5px) + +border-top-left-radius(5px) + li + &:first-child + +border-top-left-radius(5px) + &:last-child + +border-bottom-radius(5px) + border-bottom: 0px + &:hover + background-color: #eee + a + color: #3B6085 + +text-shadow() + background: #eee + padding: 5px 5px 3px + border-bottom: 1px solid #3b6085 + a + display: block + color: #3B6085 + +text-shadow() + &:hover + h4 + color: #fff + position: relative + z-index: 4 + background: #2e4b67 + +border-radius(5px) + cursor: default + ul + display: block + .remove_border + border: + top: 1px solid #ccc + bottom: none !important + left: 1px solid #ccc + right: 1px solid #ccc + +border-radius(5px 5px 0px 0px) + .border + border: 1px solid #ccc + +border-radius(5px) + + #main + +pie-clearfix + width: 980px + padding-bottom: 75px + margin: auto + + footer, #footer + width: auto + background: #1b334a + height: 30px + border-top: 4px solid #fff + padding: 20px 0 0 + margin: 0 + color: #fff + #cont + +serif + width: 980px + margin: 0 auto + font-size: 12px + color: #3b6085 + a + color: #3b6085+#222 + font-weight: normal + .right_text + font-size: 30px + line-height: 12px + a + display: inline-block + vertical-align: middle + margin-top: -8px + margin-left: 3px + font-size: 12px + + #wrap + +clearfix + width: 972px + margin: auto + color: $baseText + + #say-something + width: 485px + #community-profiles + width: 487px + margin-top: 10px + +border-radius(5px) + #whats-happening + width: 485px + +border-bottom-radius(5px) + + #information + +single-box-shadow(#666, 0px, 2px, 7px, -2px) + +body.application.logged_out + background-image: none + background-color: #cee7fe + background-attachment: fixed + +serif + + header, #header + text-align: center + background: #1b334a + border-bottom: 4px solid #fff + height: 31px + width: 100% + #logo + margin-top: 10px + text-decoration: none + text-align: left + color: #444545 + font-size: 30px + line-height: 30px + span + color: #b7430c + font-size: 25px + position: relative + bottom: 1px + img + vertical-align: bottom + + #nav_wrapper + margin: 0px auto 20px + width: 976px + text-align: left + nav, .nav + padding: 8px 0 12px 0 + float: right + display: inline + a, .disabled_link + vertical-align: top + font-size: 11px + padding: 5px 5px + color: $baseText + #222 + font-weight: bold + +sans + text-decoration: none + a.selected_nav + color: #3b6085 + a:hover + color: #3b6085 + div.user-feeds + display: inline + position: relative + padding-top: 5px + height: 22px + vertical-align: top + h4 + font-size: 11px + color: $baseText + #222 + font-weight: bold + height: 14px + width: 66px + +sans + display: inline + vertical-align: top + padding: 5px + right: 20px + background-image: image-url('nav-down-arrow.png') + background-repeat: no-repeat + background-position: 48px + border: 1px solid transparent + ul + display: none + top: 25px + left: 0px + position: absolute + z-index: 1 + width: 150px + background-color: white + border: 1px solid #3b6085 + +border-bottom-radius(5px) + +border-top-right-radius(5px) + li + &:first-child + +border-top-right-radius(5px) + &:last-child + +border-bottom-radius(5px) + border-bottom: 0px + &:hover + background-color: #eee + padding: 5px 5px 3px + border-bottom: 1px solid #3b6085 + a + display: block + + &:hover + h4 + +border-top-radius(5px) + border: 1px solid #3b6085 + bottom-color: #fff + color: #3b6085 + position: relative + z-index: 4 + background-color: white + ul + display: block + #user_sign_in + float: right + position: relative + margin-top: 2px + li + margin: 0 5px + +inline-block + color: #BCD5EC + font-size: 13px + font-weight: 300 + + #sign_in_button + background: image-url("nav-down-arrow.png") no-repeat 95% 40% #e0e0e0 + padding: 2px 27px 3px 10px + line-height: 22px + position: relative + color: #1B334A + font-size: 13px + z-index: 100 + cursor: pointer + border: 1px solid #ccc + +border-radius(5px) + &.open + border-bottom: 0px + +border-radius(5px 5px 0px 0px) + #sign_in_form + position: absolute + right: -1px + top: 15px + + form.user + position: absolute + right: 1px + top: 18px + z-index: 90 + width: 200px + background: #fff + border: 1px solid #ccc + +border-radius(5px 0px 5px 5px) + padding: 10px 5px 10px 15px + display: none + .inputs, .buttons + +inline-block + li + margin: 0px 0px 0px 10px + display: block + width: 180px + padding: 2px 0px 5px 0px + text-align: center + + label + display: block + padding: 4px 2px + color: #1b334a + +sans + text-align: left + font-weight: bold + font-size: 11px + + input[type=text], input[type=password] + +border-radius(5px) + width: 165px + outline: none + padding: 5px 7px + font-size: 0.9em + +box-shadow(#ccc 1px 1px 2px 0 inset) + margin: 0 + border: 1px solid #ddd + #facebook + padding-top: 5px + +inline-block + float: left + margin-left: 58px + margin-top: 2px + fieldset.buttons + margin-top: 3px + .commit + float: right + width: 60px + vertical-align: middle + padding-bottom: 2px + +inline-block + + .forgot-password + text-align: right + display: block + padding: 3px 15px 10px + font-size: 12px + color: #3b6085 + ul.errors li + margin: 0px + text-align: right + padding: 0 10px 10px + color: #b7403c + font-size: 13px + #fb-login + position: relative + top: -4px + border: 1px solid #314A63 + +border-radius(5px) + background: #3B6085 + color: white + padding: 5px 5px + +box-shadow(inset 0px -1px 2px #335170, inset 0px 1px 0px #5883AD, 1px 1px 2px #999) + font-family: Rockwell, Georgia, serif + font-size: 11px + +text-shadow(#454545 1px 1px 0px) + + footer, #footer + clear: both + background: transparent image-url('houses.png') repeat-x center bottom + border: none + padding: 200px 0 10px + height: 20px + margin: 0 + color: #ADD794 !important + .left_text + color: #ADD794 !important + .right_text + color: #ADD794 !important + a + color: #BFF4A0 !important + + +input.placeholder, textarea.placeholder + color: #aaa + +.notification + position: fixed + top: 200px + margin: 0 220px + width: 500px + background-color: white + padding: 5px + border: 5px solid #1B334A + z-index: 3000 + +border-radius(5px) + text-align: center + +.chzn-container + margin-top: 5px + +select[multiple] + width: 365px + +form.formtastic.user + label + margin-top: 8px + width: 150px diff --git a/app/stylesheets/application.sass b/app/stylesheets/application.sass deleted file mode 100644 index e8c67f963..000000000 --- a/app/stylesheets/application.sass +++ /dev/null @@ -1,199 +0,0 @@ -@import core -@import syndicate -@import small_calendar -@import info_box -@import modal -@import form -@import modal_form -@import date_picker -@import profile_fields -@import accounts -@import site_info -@import edit_new -@import administration -@import admin_bar -@import inline_form -@import say_something -@import whats_happening -@import feeds -@import password_resets - -.clear - clear: both - -body.application - background-color: #cee7fe - background-attachment: fixed - +serif - - header - margin: 20px auto 30px - width: 970px - - #logo - margin-top: 10px - text-decoration: none - text-align: left - color: #444545 - font-size: 30px - line-height: 30px - span - color: #b7430c - font-size: 25px - position: relative - bottom: 3px - img - vertical-align: bottom - - - nav - padding: 8px 0 12px 0 - float: right - display: inline - a, .disabled_link - vertical-align: top - font-size: 11px - padding: 5px 5px - color: $baseText + #222 - font-weight: bold - +sans - text-decoration: none - a.selected_nav - color: #3b6085 - a:hover - color: #3b6085 - div.user-feeds - display: inline - position: relative - padding-top: 5px - height: 22px - vertical-align: top - h4 - font-size: 11px - color: $baseText + #222 - font-weight: bold - height: 14px - +sans - display: inline - vertical-align: top - padding: 5px - right: 20px - background-image: url('/images/nav-down-arrow.png') - background-repeat: no-repeat - background-position: 42px - border: 1px solid transparent - ul - display: none - top: 25px - left: 0px - position: absolute - z-index: 1 - width: 150px - background-color: white - border: 1px solid #3b6085 - +border-bottom-radius(5px) - +border-top-right-radius(5px) - li - &:first-child - +border-top-right-radius(5px) - &:last-child - +border-bottom-radius(5px) - border-bottom: 0px - &:hover - background-color: #eee - padding: 5px 5px 3px - border-bottom: 1px solid #3b6085 - a - display: block - - &:hover - h4 - +border-top-radius(5px) - border: 1px solid #3b6085 - bottom-color: #fff - color: #3b6085 - position: relative - z-index: 4 - background-color: white - ul - display: block - - #new_user_session - float: right - width: 500px - .inputs, .buttons - +inline-block - li - margin: 0 2px - +inline-block - - label - display: block - padding: 4px 2px - color: #b3b3b3 - +sans - font-weight: bold - font-size: 11px - - input[type=text], input[type=password] - +border-radius(5px) - width: 155px - outline: none - padding: 5px 7px - font-size: 0.9em - +box-shadow(#ccc, 1px, 1px, 2px, 0, inset) - margin: 0 - border: 1px solid #ddd - #facebook_login - position: relative - top: 10px - color: $cpBlue - - - .buttons - padding-top: 25px - .forgot-password - float: right - margin-right: 15px - padding: 4px - font-size: 12px - color: #3b6085 - p.errors - margin-left: 2px - color: #b7403c - font-size: 13px - #main - width: 980px - margin: auto - - footer - clear: both - background-image: url('/images/houses.png') - background-repeat: repeat-x - background-position: center bottom - padding: 220px 10px 10px - height: 20px - width: auto - margin: 0px - color: #ADD794 - a - color: #BFF4A0 - - #wrap - +clearfix - width: 972px - margin: auto - color: $baseText - - #say-something - width: 485px - #community-profiles - width: 487px - margin-top: 10px - - #whats-happening - width: 485px - - -input.placeholder, textarea.placeholder - color: #aaa diff --git a/app/stylesheets/communities/_good_neighbor_discount.css.sass b/app/stylesheets/communities/_good_neighbor_discount.css.sass new file mode 100644 index 000000000..c4bb619cf --- /dev/null +++ b/app/stylesheets/communities/_good_neighbor_discount.css.sass @@ -0,0 +1,81 @@ +body.application.communities.good_neighbor_discount + background-color: #cee7fe + background-image: none + #planewrapper + margin: 0px auto 10px + text-align: center + a:link, a:visited,a:hover, a:active + text-decoration: none + #plane + width: 960px + background-image: image-url("invite/plane.png") + background-repeat: no-repeat + background-position: 114px 10px + padding-top: 32px + padding-bottom: 15px + margin: 0px auto + text-align: center + h1 + color: #ac322f + font-weight: bold + font-size: 20px + text-align: center + h2 + color: #172b3f + font-weight: bold + font-size: 40px + text-align: center + margin-top: 6px + h3 + color: #748da4 + line-height: 1em + font-size: 12px + margin-bottom: 5px + +sans + + #gbox + width: 430px + margin: 28px auto 14px auto + position: relative + font-family: Rockwell, Georgia, Times, serif + + #card_box + width: 490px + margin: 20px auto + position: relative + + #businessinformation + color: #1b334a + text-align: center + margin: 2px auto 20px + padding: 6px 10px 6px + font-size: 16px + width: 440px + border: none + +serif + li + background: image-url('bullet-star2.png') + background-position: 1px 3px + background-repeat: no-repeat + line-height: 1.6 + font-weight:normal + margin: 0 5px 5px 0px + font-size: 14px + font-weight: bold + padding-left: 18px + label + display: none + #tablerow1 + text-align: center + #tablecol1, #tablecol2 + vertical-align: top + #infolist1 + padding-left: 35px + #infolist2 + padding-left: 8px + #footerinfo + margin: 30px auto 15px + text-align: center + + #doctable + vertical-align:middle diff --git a/app/stylesheets/feed_page.css.sass b/app/stylesheets/feed_page.css.sass new file mode 100644 index 000000000..6a1935a48 --- /dev/null +++ b/app/stylesheets/feed_page.css.sass @@ -0,0 +1,432 @@ +@import core +@import shared/buttons +@import header +@import header +@import footer +@import date_picker +@import shared/replies +@import shared/wire +@import shared/form +@import shared/modal +@import shared/markdown +@import shared/dropkick_selects + +body + background-color: #eee + margin: 0 + +#main + width: 980px + margin: auto + position: relative + +#feed-admin-bar + margin: auto + text-align: right + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + li + display: inline + float: left + a + background-color: #fff + +border-bottom-radius(5px) + text-decoration: none + padding: 4px 8px 5px 8px + margin: 0 0 0 5px + display: block + color: #555 + &:hover + color: #AC322F + text-decoration: underline + &.current + display: none + +#feed-header + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + font-weight: bold + margin-top: 21px + padding-bottom: 1px + + h1 + color: #262626 + font-size: 44px + display: inline + padding-right: 10px + + a.subscribe, + a.unsubscribe, + a.edit + color: #ac322f + font-size: 18px + text-decoration: none + font-weight: bold + + +#left-column + width: 235px + padding-right: 10px + float: left + +#center-column + width: 485px + padding-right: 10px + float: left + +#right-column + width: 240px + float: left + +#feed-profile + background-color: #fff + border: 2px solid #bebebe + +border-radius(3px) + +box-shadow(#888 2px 2px 10px) + margin-top: 15px + + h1 + +border-top-radius(1px) + margin: 0px + background-color: #e0e0e0 + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + font-size: 15px + padding: 5px + border-bottom: 1px solid #bebebe + text-align: center + color: #656565 + +text-shadow(#fafafa 0 1px 0) + + img.avatar + display: block + margin: 20px auto 0 + padding: 0 + border: 0 + + dl.contact-info + padding: 20px 20px 0 + dt + color: #1b334a + font-family: Helvetica, sans-serif + font-size: 12px + font-weight: bold + padding-bottom: 5px + dd + color: #252525 + font-family: Helvetica, sans-serif + font-size: 12px + padding-bottom: 10px + line-height: 15px + + a + text-decoration: none + color: #ac322f + &:hover + text-decoration: underline + + button, .button + +blue-button + display: block + text-decoration: none + font-size: 12px + width: auto + text-align: center + margin-bottom: 20px + margin-left: 15px + margin-right: 15px + a + text-decoration: none + font-size: 12px + text-align: center + color: white + +#feed-nav + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + font-size: 18px + margin-top: 20px + + a + +border-radius(3px) + display: block + padding: 5px 20px + color: #1b334a + text-decoration: none + + a.current + background-color: #ac322f + color: white + + +#feeds-list + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + margin-top: 15px + + h1 + border-bottom: 2px solid #e0e0e0 + font-size: 16px + font-weight: bold + margin-bottom: 14px + margin-top: 0 + color: #1d334c + + ul + overflow-y: auto + height: 416px + + a + text-decoration: none + color: #333 + padding: 6px 6px 6px 37px + display: block + + li + +border-radius(3px) + margin-bottom: 5px + background-color: #e0e0e0 + line-height: 25px + display: block + + li.current, li:hover + background-color: #fff + + img.avatar + vertical-align: top + border: 0 + float: left + margin-left: -31px + display: block + + +#feed-actions + margin-top: 15px + #feed-action-nav + a + display: block + float: left + width: 100px + background-color: #1b334a + height: 25px + padding: 20px 10px 20px 50px + color: #fff + text-decoration: none + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + font-size: 15px + background-repeat: no-repeat + background-position: 15px 22px + +text-shadow(#000 0 1px 0) + + a.post-announcement + +border-top-left-radius(3px) + border-left: 1px solid #182e42 + border-right: 1px solid #192837 + background-image: image-url("feed_page/announcement-tab-icon.png") + a.post-event + border-right: 1px solid #192837 + border-left: 1px solid #3a4d5e + background-image: image-url("feed_page/event-tab-icon.png") + a.invite-subscribers + +border-top-right-radius(3px) + border-left: 1px solid #3a4d5e + background-image: image-url("feed_page/invite-tab-icon.png") + + a.current + background-color: #2e4b67 + + + .tab + clear: both + +border-bottom-radius(3px) + background-color: #ac322f + padding-top: 10px + border: 2px solid #9a2c2a + top: 0px + + display: none + + &.current + display: block + + + + + img.avatar + float: left + margin-left: 10px + margin-top: 16px + + .error + background-color: #f4e15f + border: 2px solid #ffe534 + +border-radius(3px) + color: #363636 + display: none + margin-right: 15px + margin-left: 73px + margin-top: 5px + padding: 5px + font-family: Helvetica, Arial, sans-serif + font-size: 14px + + form + +form + display: block + margin: 16px 15px 20px 73px + + input.date + z-index: 10000 + position: relative + + input, + textarea + display: block + width: 375px + + + button + +blue-button + float: right + margin: 20px 0 + + label + color: #fff + margin-top: 15px + font-size: 16px + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + display: block + + label.time + float: left + margin-right: 30px + padding-bottom: 15px + margin-top: 5px + + .post-label-selector + margin-top: 5px + height: 125px + overflow-y: scroll + + li + &:first-child + margin-top: 0px + + margin-top: 10px + margin-right: 10px + padding: 7px 5px 3px + background-color: #fff + +border-radius(5px) + background-image: image-url("feed_page/check-off.png") + background-repeat: no-repeat + background-position: 340px 9px + + &.checked + background-image: image-url("feed_page/check-on.png") + + label + display: block + font-size: 18px + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + color: #333 + margin: 0 + padding: 0 + + img + position: relative + top: -3px + vertical-align: middle + margin-right: 5px + border: 0 + + input + display: none + + + + + +.invite-subscribers h1 + padding-left: 73px + color: #fff + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + height: 16px + padding-top: 10px + +.invite-subscribers div.invite-by-facebook + margin: 20px 0 20px 73px + + button + +blue-button + +.invite-subscribers div.invite-by-hand + margin: 16px 15px 20px 73px + + button + margin-top: 20px + +blue-button + +.invite-subscribers .invite-by-hand a.download + background-image: image-url("feed_page/doc-icon.png") + background-position: 0 0 + background-repeat: no-repeat + padding-top: 135px + display: block + float: left + width: 100px + text-decoration: none + + padding-right: 40px + + &.last + padding-right: 0 + + .description-text + color: #fff + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + font-size: 12px + + .download-pdf + margin-top: 5px + color: #5a1a19 + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + font-size: 12px + + + + +#feed-subresources, #feed-about + border: 2px solid #bebebe + +border-radius(4px) + + margin-top: 15px + + .feed-sub-header + background-color: #e0e0e0 + color: #656565 + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + font-size: 15px + +border-top-radius(2px) + padding: 5px + text-align: center + +#feed-about + .about + border-top: 1px solid #bebebe + padding: 15px + background-color: #fff + p + margin-top: 5px + font-family: Helvetica, sans-serif + font-size: 0.8em + line-height: 1.4 + color: $textColor + + +#feed-subresources + .tab + display: none + + .tab.current + display: block + + + .replies textarea + width: 300px +#main + margin-bottom: 75px diff --git a/app/stylesheets/feed_registration.css.sass b/app/stylesheets/feed_registration.css.sass new file mode 100644 index 000000000..717c89a40 --- /dev/null +++ b/app/stylesheets/feed_registration.css.sass @@ -0,0 +1,195 @@ +@import core +@import header +@import footer + +body + font-family: Rockwell,Georgia,Times,"Times New Roman",serif + background-color: #cee7fe + +#main + width: 980px + margin: auto + +h1 + font-size: 49px + color: #1b334a + padding: 18px 0px + font-weight: bold + text-align: center + margin: 5px auto 20px + width: 960px + line-height: 1.2 + +text-shadow(#bfc8cf, 1px, 1px, 1px) + +form + margin-right: 75px + position: relative + float: right + display: block + position: relative + +redlightbox + z-index: 2 + + li + list-style: none + + p.inline-hints + width: 340px + + input[type=text], textarea + +border-radius(5px) + +box-shadow(#ccc, 1px, 1px, 2px, 0, inset) + border: 1px solid #ccc + padding: 3px 5px + font-size: 18px + font-weight: 300 + width: 340px + display: block + +serif + margin: 5px 0 20px + + label + display: block + text-align: left + color: #FFFFFF + font-weight: 600 + font-size: 14px + text-shadow: #000000 1px 1px 1px + line-height: 1.2 + width: 350px + + fieldset + margin-bottom: 20px + legend + color: #FFFFFF + font-weight: 600 + font-size: 14px + text-shadow: #000000 1px 1px 1px + + input[type=radio] + margin-top: 10px + + #feed_avatar_input + position: relative + margin-bottom: 60px + + #feed_avatar + cursor: pointer + position: relative + left: 145px + +opacity(0) + z-index: 2 + position: absolute + top: 25px + height: 30px + + #file_input_fix + position: absolute + width: 360px + margin: 0px 0px 15px + + #file_style_fix + height: 27px + + #browse_button + position: absolute + top: 5px + right: 8px + padding: 7px + height: 20px + width: 80px + color: white + background: #808080 + border: 1px solid #666666 + +border-radius(0 5px 5px 0) + +box-shadow(#a0a0a0 0px 0px 0px 1px inset) + cursor: pointer + + #feed_slug_input + position: relative + p.inline-hints + left: -370px + line-height: 1.2 + font-size: 12 + font-family: Helvetica, sans-serif + top: 20px + padding: 10px + position: absolute + display: none + background-color: white + border: 1px solid #888 + +form.crop + margin: auto + float: none + padding: 25px + width: 1025px + + .jcrop-holder + margin: auto + + input[type=image] + margin: 20px auto 0 + display: block + +form.edit_feed + float: none + margin: auto + width: 350px + + + li.other-actions + text-align: right + color: #fff + a + color: #fff + +form.new_subscribers + margin: 30px auto + float: none + width: 450px + + label + display: block + margin-bottom: 20px + + .scrolling-table-container + height: 400px + overflow-y: auto + overflow-x: hidden + padding-right: 1px + + table + font-family: Helvetica, sans-serif + width: 100% + background-color: #fff + font-size: 14px + + th + font-weight: bold + padding: 2px 5px 8px + td, th + height: 15px + border: 1px solid #333 + padding: 8px 5px 2px + + input[type=image] + margin: 20px auto 0 + display: block + + + +#registration_information + float: left + margin-left: 58px + margin-right: 40px + width: 370px + position: relative + + li + +starBullet + font-size: 16px + padding-left: 35px + +@include sticky-footer(74px, "#wrapper", "#wrapper-footer", "#footer") + diff --git a/app/stylesheets/feeds/_invites.sass b/app/stylesheets/feeds/_invites.sass deleted file mode 100644 index 739575920..000000000 --- a/app/stylesheets/feeds/_invites.sass +++ /dev/null @@ -1,53 +0,0 @@ - -body.application.feeds_invites.new - - h1 - width: 660px - color: #3b6085 - line-height: 1.2 - font-size: 30px - padding: 0 20px - margin: 5px auto 20px - text-align: center - +text-shadow(#bfc8cf, 1px, 1px, 1px) - - h2 - color: #444545 - line-height: 1.4 - font-size: 18px - - #new_feed_invites - padding: 50px 80px - +lightbox - width: 650px - margin: auto - - - fieldset.inputs - padding: 15px 0px - - li.emails - background-color: #3b6085 - padding: 10px 25px 30px - +border-radius(5px) - - label - display: block - padding: 10px 0 - color: #fff - font-size: 16px - textarea - width: 100% - +border-radius(4px) - margin-bottom: 15px - a - color: #b53f3c - background-color: #fff - +border-radius(4px) - font-size: 10px - padding: 3px - text-decoration: none - font-weight: bold - - fieldset.buttons - text-align: right \ No newline at end of file diff --git a/app/stylesheets/feeds/_new.css.sass b/app/stylesheets/feeds/_new.css.sass new file mode 100644 index 000000000..5a6deca49 --- /dev/null +++ b/app/stylesheets/feeds/_new.css.sass @@ -0,0 +1,81 @@ + +body.application.feeds.new, body.application.feeds.edit, body.application.feeds.create, body.application.feeds.update + + h1 + width: 800px + color: #3b6085 + line-height: 1.2 + font-size: 25px + padding: 0 20px + margin: 5px auto 20px + text-align: center + +text-shadow(#bfc8cf 1px 1px 1px) + + + form.formtastic.feed + +lightbox + width: 900px + margin: 0 auto 75px + + fieldset.inputs + width: 400px + margin: 0px 20px 0px 15px + + li + margin-bottom: 10px + label + display: block + padding-bottom: 10px + input[type=text],input[type=url], input[type=tel], textarea + +border-radius(5px) + +box-shadow(#ccc 1px 1px 2px 0 inset) + padding: 0px 7px + font-size: 18px + width: 100% + +serif + + textarea + height: 80px + + p.inline-errors + color: #b7403c + + p.inline-hints + font-size: 12px + text-align: left + input#feed_is_news + float: right + + fieldset#contact-info + #feed_website_input, #feed_phone_input + margin: 10px 0px + width: 180px + +inline-block + vertical-align: bottom + input + width: 180px + font-size: 18px + #feed_phone_input + margin-left: 35px + + + fieldset.buttons + width: 435px + li.delete a, li.owner a + vertical-align: middle + color: #3B6085 + li + margin-left: 20px + float: right + + #preview + float: right + margin-left: 550px + margin-bottom: 15px + position: fixed + width: 447px + zoom: 90% + h2 + background-color: #b53f3c + color: #fff + border: 1px solid #fff diff --git a/app/stylesheets/feeds/_new.sass b/app/stylesheets/feeds/_new.sass deleted file mode 100644 index 5952b3577..000000000 --- a/app/stylesheets/feeds/_new.sass +++ /dev/null @@ -1,67 +0,0 @@ - -body.application.feeds.new, body.application.feeds.edit, body.application.feeds.create, body.application.feeds.update - - h1 - width: 800px - color: #3b6085 - line-height: 1.2 - font-size: 25px - padding: 0 20px - margin: 5px auto 20px - text-align: center - +text-shadow(#bfc8cf, 1px, 1px, 1px) - - - form.formtastic.feed - +lightbox - width: 900px - margin: auto - - fieldset.inputs - width: 400px - margin: 0px 20px 0px 15px - - li - margin-bottom: 10px - label - display: block - padding-bottom: 10px - input[type=text], textarea - +border-radius(5px) - +box-shadow(#ccc, 1px, 1px, 2px, 0, inset) - padding: 0px 7px - font-size: 18px - width: 100% - +serif - - textarea - height: 80px - - p.inline-errors - color: #b7403c - - fieldset#contact-info - #feed_website_input, #feed_phone_input - margin: 10px 0px - width: 180px - +inline-block - vertical-align: bottom - input - width: 180px - font-size: 18px - #feed_phone_input - margin-left: 35px - - - fieldset.buttons - li - text-align: right - - #preview - float: right - width: 447px - zoom: 90% - h2 - background-color: #b53f3c - color: #fff - border: 1px solid #fff \ No newline at end of file diff --git a/app/stylesheets/feeds/_profile.sass b/app/stylesheets/feeds/_profile.sass deleted file mode 100644 index c5e947e30..000000000 --- a/app/stylesheets/feeds/_profile.sass +++ /dev/null @@ -1,234 +0,0 @@ - -body.application.feeds.profile, body.feeds_announcements.new, body.feeds_events.new, body.feeds.import - +serif - #main - width: 900px - min-height: 600px - color: #454545 - background-color: #f5f5f5 - +border-radius(15px) - +box-shadow($default-box-shadow-color, 0, 0, $default-box-shadow-blur, 0, inset) - border: 10px solid #c5c5c5 - padding: 25px - width: 900px - .inactive - z-index: 0 !important - h1 - font-size: 40px - margin-bottom: 25px - a.edit - text-align: left - display: inline - vertical-align: top - - h2 - color: #3b6085 - h3 - font-size: 14px - background-color: #385f84 - color: white - +border-top-radius(7px) - padding: 5px - #column-1, #column-3 - width: 225px - - #column-2 - width: 400px - margin: 0px 25px - - #column-1,#column-2,#column-3 - +inline-block - vertical-align: top - - img#feed-avatar - border: 7.5px #c4c4c4 solid - +border-radius(7px) - margin-bottom: 25px - - #contact-info - - dl - border: 1px solid #d0d0d0 - bottom-width: 0px - dt, dd - border: 1px solid white - bottom-color: #d0d0d0 - +inline-block - font-size: 12px - padding: 5px 0 - dt - +sans - color: #3b6085 - width: 58px - padding-left: 4px - border-right-width: 0px - dd - border-left-width: 0px - width: 159px - dd:last-child - +border-radius(0px, 7px) - - a.edit - border: 1px solid #d0d0d0 - top-width: 0px - +border-bottom-radius(4px) - padding: 5px 5px - - #post-to-feed - background-color: #dadada - +border-radius(5px) - padding: 10px - margin-bottom: 25px - a.import - display: block - float: right - width: 100px - margin-top: 6px - font-size: 12px - line-height: 1.2 - text-align: right - text-decoration: none - color: #385f84 - h2 - font-size: 18px - padding-top: 10px - - nav - width: 157px - +inline-block - vertical-align: top - margin-top: -4px - ul - margin: 0 - padding: 0 - border: 3px solid #c5c5c5 - +border-radius(3px) - width: auto - li - display: block - background-color: white - &:hover - background-color: #eee - a - display: block - width: 143px - li:first-child - a - background-image: url('/images/feed-post-dropdown-arrow.png') - background-repeat: no-repeat - background-position: 130px - li:last-child - display: none - &:hover - li:last-child - display: inline - position: absolute - border: - top: 2px solid #c5c5c5 - bottom: 3px solid #c5c5c5 - left: 3px solid #c5c5c5 - right: 3px solid #c5c5c5 - +border-radius(3px) - margin-left: -3px - a - font-size: 14px - padding: 4px - text-decoration: none - color: #525252 - display: block - - - #event_start_time_input - float: left - margin-right: 40px - - form.formtastic - label - display: block - margin: 10px 0px - textarea - border: 1px solid #9f9f9f - +border-radius(5px) - +box-shadow(#ddd, 2px, 2px, 1px, 0px, inset) - padding: 5px - margin-bottom: 10px - height: 60px - width: 367px - input[type=text] - border: 1px solid #9f9f9f - +border-radius(5px) - +box-shadow(#ddd, 2px, 2px, 1px, 0px, inset) - padding: 5px - height: 30px - width: 367px - fieldset.buttons - text-align: right - margin: 10px - p.inline-errors - color: #cc0000 - - #recent-posts - h2 - margin-bottom: 10px - .no-recent-posts - text-align: center - border: 1px solid #d0d0d0 - padding: 40px - +border-radius(3px) - background-color: #f7f7f7 - ul.items - border: 0px - background-color: transparent - li - border: 1px solid #d0d0d0 - margin-bottom: 5px - background-color: #f7f7f7 - +border-radius(3px) - time - color: #3b6085 - - #about - padding: 15px - bottom: 10px - line-height: 1.6 - border: 1px solid #d0d0d0 - +border-radius(4px) - background-color: #f8f8f8 - margin-bottom: 25px - p - font-size: 12px - a.edit - margin-top: 10px - - #members - border: 1px solid #d0d0d0 - - ul - text-align: left - li - padding: 7px - border-bottom: 1px solid #d0d0d0 - img - vertical-align: top - text-align: center - a.subscribe - color: #fff - background-color: #b43e3b - padding: 8px - +border-radius(5px) - text-align: center - +inline-block - margin: 5px - text-decoration: none - a.edit - display: block - color: #b53f3c - text-align: right - text-decoration: none - font-size: 12px - &:hover - text-decoration: underline - #reply_body - width: 345px - margin-top: 5px - \ No newline at end of file diff --git a/app/stylesheets/feeds/delete.css.scss b/app/stylesheets/feeds/delete.css.scss new file mode 100644 index 000000000..c0b169de3 --- /dev/null +++ b/app/stylesheets/feeds/delete.css.scss @@ -0,0 +1,15 @@ +@import "core" ; +body.application.feeds.delete { + form.formtastic.feed { + margin: auto ; + @include lightbox ; + width: 600px; + margin-top: 80px; + margin-bottom: 70px; + + p { font-size: 18px ; line-height: 24px } + + .buttons li { display: block ; margin: 50px 0 ; text-align: center } + } + +} \ No newline at end of file diff --git a/app/stylesheets/feeds/edit_owner.css.scss b/app/stylesheets/feeds/edit_owner.css.scss new file mode 100644 index 000000000..d8056c2b1 --- /dev/null +++ b/app/stylesheets/feeds/edit_owner.css.scss @@ -0,0 +1,16 @@ +@import "core" ; +body.application.feeds.edit_owner, body.application.feeds.update_owner { + form.feed { + margin: auto ; + @include lightbox ; + width: 600px; + margin-top: 80px; + margin-bottom: 70px; + + p { font-size: 18px ; line-height: 24px ; color: #3B6085 } + p.error { color: #B53F3C; } + + .buttons { display: block ; margin: 50px 0 ; text-align: center } + } + +} \ No newline at end of file diff --git a/app/stylesheets/group_page.css.sass b/app/stylesheets/group_page.css.sass new file mode 100644 index 000000000..1e444327b --- /dev/null +++ b/app/stylesheets/group_page.css.sass @@ -0,0 +1,211 @@ +@import core +@import shared/buttons +@import header +@import date_picker +@import footer +@import shared/replies +@import shared/wire +@import shared/form +@import shared/modal +@import shared/markdown +@import shared/dropkick_selects + +body + background-color: #eee + margin: 0 + +#main + width: 980px + margin: auto + +#group-header + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + font-weight: bold + margin-top: 21px + padding-bottom: 1px + + h1 + color: #262626 + font-size: 44px + display: inline + padding-right: 10px + + a.subscribe, + a.unsubscribe, + a.edit + color: #ac322f + font-size: 14px + text-decoration: none + font-weight: bold + + +#left-column + width: 235px + padding-right: 10px + float: left + +#center-column + width: 485px + padding-right: 10px + float: left + +#right-column + width: 240px + float: left + +#group-profile + background-color: #fff + border: 2px solid #bebebe + +border-radius(3px) + +box-shadow(#888 2px 2px 10px) + margin-top: 15px + + h1 + +border-top-radius(1px) + margin: 0px + background-color: #e0e0e0 + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + font-size: 15px + padding: 5px + border-bottom: 1px solid #bebebe + text-align: center + color: #656565 + +text-shadow(#fafafa 0 1px 0) + + img.avatar + display: block + margin: 20px auto 0 + padding: 0 + border: 0 + + .about + width: 200px + margin: 20px auto 0 + padding: 0 0 20px + font-family: Helvetica, sans-serif + font-size: 13px + line-height: 1.4 + +#group-nav + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + font-size: 18px + margin-top: 20px + + a + +border-radius(3px) + display: block + padding: 5px 20px + color: #1b334a + text-decoration: none + + a.current + background-color: #ac322f + color: white + + +#groups-list + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + margin-top: 15px + + h1 + border-bottom: 2px solid #e0e0e0 + font-size: 16px + font-weight: bold + margin-bottom: 14px + margin-top: 0 + color: #1d334c + + ul + overflow-y: auto + + a + text-decoration: none + color: #333 + padding: 6px 6px 6px 37px + display: block + font-weight: bold + + li + +border-radius(3px) + margin-bottom: 5px + background-color: #e0e0e0 + line-height: 25px + display: block + + li.current, li:hover + background-color: #fff + + img.avatar + vertical-align: top + border: 0 + float: left + margin-left: -31px + display: block + + +#new-post + +border-top-radius(3px) + background-color: #ac322f + border: 2px solid #9a2c2a + margin-top: 15px + + h1 + +border-top-radius(3px) + background-color: #9a2c2a + padding: 5px + text-align: center + font-size: 15px + margin-bottom: 5px + color: #fff + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + font-weight: bold + + img.avatar + float: left + margin-left: 10px + margin-top: 16px + + .error + background-color: #f4e15f + border: 2px solid #ffe534 + +border-radius(3px) + color: #363636 + display: none + margin-right: 15px + margin-left: 73px + margin-top: 20px + padding: 5px + font-family: Helvetica, Arial, sans-serif + font-size: 14px + + form + +form + display: block + margin: 16px 15px 20px 73px + + input, + textarea + width: 375px + + button + +blue-button + float: right + margin-bottom: 20px + margin-top: 12px + +#post-list, #group-subresources + +border-bottom-radius(3px) + background-color: #fff + border: 2px solid #bebebe + border-top: 0px + + .tab + display: none + + .tab.current + display: block + +#group-subresources + .replies textarea + width: 300px + diff --git a/app/stylesheets/inbox.css.sass b/app/stylesheets/inbox.css.sass new file mode 100644 index 000000000..3eb6b51b5 --- /dev/null +++ b/app/stylesheets/inbox.css.sass @@ -0,0 +1,132 @@ +@import core +@import shared/buttons +@import header +@import header +@import footer +@import date_picker +@import shared/replies +@import shared/wire +@import shared/form +@import shared/modal +@import shared/markdown + +body + background-color: #eee + margin: 0 + +#main + width: 800px + margin: auto + position: relative + +#inbox-nav + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + font-size: 18px + width: 100px + padding-left: 30px + padding-right: 30px + margin-top: 20px + float: left + + a + +border-radius(3px) + display: block + padding: 5px 20px + color: #1b334a + text-decoration: none + + .current + background-color: #ac322f + color: white + +#inbox + border: #bbb + width: 598px + +border-radius(5px) + border: 1px solid #bbb + background-color: white + margin-top: 20px + float: left + + .more + margin-top: 10px + + h2 + background-color: #f2f2f2 + color: #67769e + text-align: center + padding: 6px + border-bottom: 1px solid #d5d6d6 + +border-top-radius(5px) + font-family: Helvetica, Arial, sans-serif + font-size: 14px + + ul.list + +border-bottom-radius(4px) + +min-height(500px) + + .message-item + border-bottom: 1px solid #bbb + padding: 15px + + .clear + clear: both + + .replies + .reply-info + margin-left: 60px + img.avatar + width: 50px + height: 50px + textarea + width: 390px + height: 43px + margin-bottom: 10px + .author + display: block + font-size: 14px + float: none + margin-bottom: 5px + .body + display: block + font-size: 14px + margin-bottom: 5px + .time + font-size: 14px + .reply-arrow + left: 25px + + .main-message + img.avatar + float: left + margin-right: 10px + + .corner + float: right + color: #bab9bf + font-size: 18px + + h3 + color: #af473e + font-size: 24px + margin-bottom: 10px + font-weight: bold + font-family: Rokkitt, Rockwell, Helvetica, Arial, Verdana, sans-serif + + .messagable + color: #b65951 + text-decoration: none + + .date + color: #bab9bf + font-size: 16px + margin-bottom: 5px + + .body + margin-left: 85px + color: #3b3b3b + line-height: 22px + li + margin-left: 10px + p, ul, ol, h1, h2, h3, h4, h5, h6 + margin-bottom: 10px diff --git a/app/stylesheets/learn_more.sass b/app/stylesheets/learn_more.sass new file mode 100644 index 000000000..a9186a875 --- /dev/null +++ b/app/stylesheets/learn_more.sass @@ -0,0 +1,136 @@ +@import core +@import logged_out_header +@import footer + +body + font-family: Rockwell,Georgia,Times,"Times New Roman",serif + background-image: url('/images/neighborhood-bg.png') + background-repeat: repeat-x + background-position: 0 100% + background-color: #cee7fe + +#wrapper + #main + margin: auto auto 150px + position: relative + z-index: 1000 + width: 1000px + + h1 + font-size: 50px + color: #1b334a + padding: 18px 0px + font-weight: bold + text-align: center + margin: 30px auto auto + line-height: 1.2 + +text-shadow(#bfc8cf, 1px, 1px, 1px) + background: url('/images/learn_more/plane.png') no-repeat 138px + + #smallbanner + font-size: 22px + color: #ac322f + +text-shadow(none) + margin-bottom: -12px + + h2 + font-size: 28px + text-align: center + clear: both + color: #1b334a + font-weight: bold + margin: 40px auto 40px + +text-shadow(#fff, 1px, 1px, 1px) + + li + display: block + margin: 40px auto 0 + width: 700px + font-size: 15px + + h3 + color: #ac322f + font-size: 22px + padding-bottom: 15px + font-weight: bold + +text-shadow(#fff, 1px, 1px, 1px) + + p + width: 340px + line-height: 1.2 + color: #1b334a + padding-bottom: 20px + + a + color: #ac322f + text-decoration: none + + li + width: 320px + +starBullet + padding-left: 30px + color: #1b334a + margin-bottom: 5px + + img + float: right + position: relative + border: 12px solid #fff + +border-radius(5px) + +single-box-shadow(#c3dbf3, -5px, 5px, 0, 0, false) + + #testimonial + background-color: #fff + font-family: Georgia,Times,"Times New Roman",serif + font-weight: light + margin: 30px auto + width: 700px + display: block + border: 12px solid #fff + +border-radius(5px) + +single-box-shadow(#c3dbf3, -5px, 5px, 0, 0, false) + color: #242424 + position: relative + + iframe + float: left + position: relative + width: 330px + height: 215px + margin: 0 15px 0 0 + + h3 + font-size: 33px + color: #1b334a + margin: 0 auto 10px + + blockquote + font-size: 25px + color: #242424 + margin: 0 auto 20px + line-height: 1.2 + + p + font-family: Helvetica, sans-serif + margin: 10px auto 15px + line-height: 1.3 + font-size: 15px + + + #more + font-size: 12px + font-family: Helvetica, sans-serif + color: #ac322f + position: absolute + bottom: 0 + right: 0 + align: right + + #author + font-size: 12px + font-family: Helvetica, sans-serif + color: #1b334a + position: absolute + bottom: 0 + right: 0 + align: right diff --git a/app/stylesheets/login_page.css.sass b/app/stylesheets/login_page.css.sass new file mode 100644 index 000000000..06d8eeced --- /dev/null +++ b/app/stylesheets/login_page.css.sass @@ -0,0 +1,92 @@ +@import core +@import logged_out_header +@import footer + +body + background-color: #cee7fe + font-family: Rockwell,Georgia,Times,"Times New Roman",serif + ++sticky_footer(74px, "#wrapper", "#wrapper-footer", "#footer") + +h1 + font-size: 44px + color: #1b334a + padding: 20px 0px 10px + font-weight: bold + text-align: center + +#main + width: 800px + margin: auto + form + position: relative + margin: 50px auto 40px + +redlightbox + padding: 14px 24px 26px 24px + width: 360px + z-index: 2 + +clearfix + a + .flash + text-align: center + color: white + .inline-errors + width: 360px + margin: 0px 0px 0px 0px + padding: 0px + text-align: left + font-size: 13px + color: #FFFFFF + .errors + display: block + width: 360px + margin: 0px 0px 0px 0px + padding: 0px + text-align: left + font-size: 13px + color: #FFFFFF + h4 + text-align: center + margin-top: 10px + fieldset.inputs + li.hidden + display: none + li + min-height: 60px + margin: 10px auto 20px auto + label + display: block + text-align: left + color: #FFFFFF + font-weight: normal + font-size: 17px + text-shadow: #000000 1px 1px 1px + word-spacing: 1px + margin-bottom: 8px + input + +border-radius(5px) + +box-shadow(#ccc 1px 1px 2px 0 inset) + border: 1px solid #ccc + padding: 0px 5px + font-size: 22px + width: 340px + display: block + +serif + fieldset.buttons + #user_session_submit + width: 377px + margin-top: 10px + margin-bottom: 10px + .forgot-password + float: left + color: #fff + margin-top: 15px + .facebook + margin-top: 15px + float: right + margin-right: 6px + + &.forgot-password + .buttons + text-align: center + diff --git a/app/stylesheets/main_page.css.sass b/app/stylesheets/main_page.css.sass new file mode 100644 index 000000000..45efd87c2 --- /dev/null +++ b/app/stylesheets/main_page.css.sass @@ -0,0 +1,66 @@ +@import core +@import header +@import shared/buttons +@import footer +@import date_picker +@import neutral_notification +@import tour +@import admin_bar +@import shared/wire +@import shared/replies +@import shared/form +@import shared/modal +@import shared/markdown +@import shared/dropkick_selects +@import main_page/post_box +@import main_page/community_resources +@import main_page/info_box +@import shared/feature_switching + +body + background-color: #cee7fe + font-family: Rockwell,Georgia,Times,"Times New Roman",serif + +.prelaunch-notification + +neutral-notification + +#main + width: 990px + margin: 10px auto 0 + +#left-column, #right-column + width: 490px + float: left + +#right-column + margin-left: 10px + +body.fixedLayout + #header + position: fixed + top: 0 + left: 0 + right: 0 + z-index: 1000 + #footer + display: none + #left-column, #right-column + float: none + + #right-column + margin-left: 0 + float: right + + #left-column + position: absolute + top: 15px + bottom: 15px + + #post-box + position: fixed + width: 485px + + #info-box + position: fixed + bottom: 0 + width: 455px diff --git a/app/stylesheets/main_page/community_resources.css.sass b/app/stylesheets/main_page/community_resources.css.sass new file mode 100644 index 000000000..a3d8c888a --- /dev/null +++ b/app/stylesheets/main_page/community_resources.css.sass @@ -0,0 +1,110 @@ +@import community_resources_navigation + +#community-resources + +box-shadow(#bcd4eb 2px 2px 0) + .resources + border: 1px solid #bebebe + top: 0 + + .sub-navigation + background-color: #F8F8F8 + border: 1px solid #e8e8e8 + left: 0 + right: 0 + + cursor: pointer + padding: 5px 10px + + h2 + font-size: 20px + padding: 4px + a + color: #949494 + text-decoration: none + a.current + color: #062A48 + a:hover + text-decoration: underline + + + + form.search + float: right + width: auto + position: relative + cursor: default + + input[type=text] + width: 144px + +border-radius(4px) + border: 0 + display: block + font-size: 12px + color: #666 + line-height: 13px + padding: 6px 25px 4px 25px + +box-shadow(0px 0px 2px rgba(0,0,0,0.25) inset,0 0 0 0 rgba(0,0,0,0.24)) + background: white image-url("shared/icons/search.png") 8px 7px no-repeat + &:focus + color: #333 + outline: 0 + + .cancelSearch + position: absolute + display: none + font-family: "Helvetica" + top: 6px + right: 6px + background-color: #bbb + color: white + width: 16px + height: 16px + +border-radius(16px) + span + float: left + padding-left: 4px + cursor: default + &:hover + background-color: #ccc + + +body.fixedLayout + #community-resources > .navigation + position: fixed + top: 65px + background-color: #cee7fe + padding-top: 20px + z-index: 9 + #community-resources + .resources + margin-top: 114px + + .landing-resources + position: relative + + .wire + padding-top: 38px + + .sub-navigation + width: 468px + position: absolute + &.fixed + position: fixed + top: 125px // this should not be necessary. otherwise div flies away :/ + + .z1 .sub-navigation + z-index: 1 + position: fixed + + .z2 .sub-navigation + z-index: 2 + .z3 .sub-navigation + z-index: 3 + .z4 .sub-navigation + z-index: 4 + + form.search + position: fixed + z-index: 9 + top: 130px + margin-left: 288px diff --git a/app/stylesheets/main_page/community_resources_navigation.css.sass b/app/stylesheets/main_page/community_resources_navigation.css.sass new file mode 100644 index 000000000..c34765b7b --- /dev/null +++ b/app/stylesheets/main_page/community_resources_navigation.css.sass @@ -0,0 +1,60 @@ + +#community-resources + .navigation + height: auto + width: 490px + + a.tab-button + position: relative + text-decoration: none + display: block + float: left + color: #fff + margin-left: 4px + text-align: center + padding: 5px 0 7px + line-height: 1.2em + padding: 6px 2px 6px 3px + font-weight: bold + font-family: Helvetica, Arial, sans-serif + +text-shadow(black 1px 1px 0) + font-size: 12px + background: #ac322f + +border-radius(4px 4px 0 0) + +box-shadow(0 0 0 1px rgba(0, 0, 0, 0.1) inset, 0 1px 0 #ac322f inset, 0 2px 1px rgba(255, 255, 255, 0.2) inset) + + &:first-child + margin-left: 0 + + &.posts + width: 108px + + &.events + width: 86px + + &.announcements + width: 110px + + &.groupPosts + width: 80px + + &.users + width: 70px + height: 15px + padding: 10px 0 15px + + &.current, &:hover + color: #1B334A + background-color: #e0e0e0 + +text-shadow(#fff 1px 1px 0) + +box-shadow(0 0 0 1px rgba(0, 0, 0, 0.1) inset, 0 2px 1px rgba(255, 255, 255, 0.2) inset) + z-index: 20 + + .nav-under + clear: both + height: 1px + top: -1px + position: relative + background-color: #bebebe + z-index: 10 + margin-bottom: -1px \ No newline at end of file diff --git a/app/stylesheets/main_page/info_box.css.sass b/app/stylesheets/main_page/info_box.css.sass new file mode 100644 index 000000000..fe1679398 --- /dev/null +++ b/app/stylesheets/main_page/info_box.css.sass @@ -0,0 +1,251 @@ + +#info-box + margin-top: 12px + background: #2e4b67 + color: white + font-family: Helvetica, Arial, sans-serif + font-size: 12px + padding: 15px + padding-bottom: 5px + +border-radius(4px) + +box-shadow(4px 4px 2px rgba(46, 75, 103, 0.1), 0 0 0 1px #1b334a, 0 1px 0 rgba(70, 117, 161, 0.7) inset) + h1, h2, h3, h4, h5, h6, p + margin: 0 + + ul, li + margin: 0 + padding: 0 + list-style: none + + #info-upper + +border-radius(4px) + +box-shadow(2px 2px 6px rgba(0, 0, 0, 0.25) inset, 0 0 0 1px rgba(0, 0, 0, 0.24)) + overflow: hidden + margin: 3px 0 15px + height: 400px + + .search + height: 20px + padding: 5px + right: 20px + color: white + background-color: #1c2e40 + background-image: image-url("search-blue.png") + background-position: 95% center + background-repeat: no-repeat + border: 0 + border-top: 1px solid #28394a + width: 128px + &:focus + outline: 0 + + #info-list-area + width: 153px + float: left + + &.searching + ul + height: 350px + ul + height: 370px + overflow-x: hidden + overflow-y: auto + border-bottom: 1px solid #101f30 + background-color: #1c2e40 + border-right: 5px solid #1c2e40 + + .remove-search + text-align: center + font-weight: bold + text-transform: none + color: #cee7fe + text-decoration: none + padding: 4px 8px + +text-shadow(black 1px 1px 0) + +border-radius(4px) + display: none + background-image: image-url("delete_grey.png") + background-position: center right + background-repeat: no-repeat + + .empty + background-color: #888 + +box-shadow(0 0 0 1px rgba(0, 0, 0, 0.1) inset, 0 1px 0 #888 inset, 0 2px 1px rgba(255, 255, 255, 0.2) inset) + + .not-empty + background-color: #ac322f + +box-shadow(0 0 0 1px rgba(0, 0, 0, 0.1) inset, 0 1px 0 #ac322f inset, 0 2px 1px rgba(255, 255, 255, 0.2) inset) + + .none + width: 133px + background-color: #1c2e40 + position: absolute + display: none + padding: 10px + text-align: center + + li + padding: 8px + overflow: hidden + + + &:hover, &.current + background-color: rgba(#385a7e,.3) + cursor:pointer + + a + color: #738392 + text-decoration: none + img + float: left + margin-right: 5px + p + float: right + width: 80px + strong + color: white + display: block + + #profile-area + width: 307px + float: right + background: white + overflow: auto + color: #444 + height: 100% + + .none + display: none + a + text-decoration: none + h3 + &:hover + text-decoration: underline + img + float: left + margin: 0 15px 15px 0 + .profile-main + overflow: hidden + .profile-top + width: 47% + padding-right: 1px + margin-bottom: 25px + margin-left: 138px + .profile-data > li + margin-bottom: 10px + h3 + color: #333 + font-family: Rokkitt, Rockwell + font-size: 24px + font-weight: bold + //line-height: 1em + margin-bottom: 10px + h4 + color: #2e4b67 + text-transform: uppercase + font-size: 11px + font-weight: bold + float: left + margin-right: 5px + position: relative + top: 1px + line-height: 1.4 + clear: left + .about + font-size: 12px + margin-bottom: 10px + p + line-height: 1.4 + li.interest + display: inline + font-weight: bold + font-size: 12px + color: #888 + margin-left: 4px + line-height: 1.4 + ul.filter + margin: 10px 0 + &:after + content: "." + display: block + height: 0 + clear: both + visibility: hidden + li + text-transform: uppercase + font-weight: bold + float: left + margin-right: 5px + &:first-child + margin-right: 8px + a + text-transform: none + color: #cee7fe + text-decoration: none + padding: 4px 8px + +text-shadow(black 1px 1px 0) + &.current, &:hover + background: #ac322f + +border-radius(4px) + +box-shadow(0 0 0 1px rgba(0, 0, 0, 0.1) inset, 0 1px 0 #ac322f inset, 0 2px 1px rgba(255, 255, 255, 0.2) inset) + + span + padding: 0 10px + a.gray-button + display: block + line-height: 20px + text-align: center + margin-top: 10px + +border-radius(4px) + background: #cfcfcf + color: #1b334a + text-transform: uppercase + font-weight: bold + text-decoration: none + font-size: 10px + +text-shadow(1px 1px 0 rgba(255, 255, 255, 0.9)) + +box-shadow(0 0 0 1px rgba(0, 0, 0, 0.1) inset, 0 1px 0 #cfcfcf inset, 0 2px 0 rgba(255, 255, 255, 0.7) inset) + .spacing + padding: 15px + +body.fixedLayout + + .contentBlinder + position: absolute + top: 15px + bottom: 15px + overflow: hidden + left: 15px + right: 15px + + #info-upper + position: relative + + #info-list-area + height: auto + position: absolute + top: 0 + bottom: 0 + + #profile-area + overflow-y: scroll + overflow-x: hidden + position: absolute + top: 0 + bottom: 0 + right: 0 + + .profile-top + width: 120px + +#info-list::-webkit-scrollbar + width: 6px + +#info-list::-webkit-scrollbar-button + height: 5px + +#info-list::-webkit-scrollbar-thumb:vertical + width: 6px + background-color: #738392 + -webkit-border-radius: 3px + diff --git a/app/stylesheets/main_page/post_box.css.sass b/app/stylesheets/main_page/post_box.css.sass new file mode 100644 index 000000000..6aad83dfb --- /dev/null +++ b/app/stylesheets/main_page/post_box.css.sass @@ -0,0 +1,220 @@ +@mixin input + font-family: Helvetica, Arial, sans-serif + font-size: 14px + color: #888 + background: white + border: none + width: 94% + @include border-radius(4px) + padding: 10px + margin: 0 0 15px 0 + resize: none + @include box-shadow(0 0 0 1px #8b2324, 2px 2px 5px #ccc inset) + + &:focus + outline: none + color: #333 + +#post-box + +border-radius(5px) + +box-shadow(#bcd4eb 3px 3px 0) + + background-color: #AC322F + + border: 1px solid #8F2927 + + .navigation + float: left + margin-left: 15px + width: 140px + padding-bottom: 10px + + a.tab-button + display: block + cursor: pointer + color: white + font-family: Rokkitt, Rockwell + text-decoration: none + padding: 10px 0 10px 6px + font-size: 15px + margin-top: 15px + line-height: 1em + border: none + font-weight: bold + +border-radius(5px) + + background: #8B2324 + +background-image(linear-gradient(#b42d2f 0%, #8b2324 50%)) + +text-shadow(#4e1414 0 -1px 0) + +box-shadow(#a82a2b 0 0 0 1px inset, #6e1c1d 0 0 0 1px, #4e1414 0 5px 6px -4px) + + &.current + background: #1B334A + +background-image(linear-gradient(#294d6f 0%, #1b334a 50%)) + + +text-shadow(#070C12 0 -1px 0) + +box-shadow(#254564 0 0 0 1px inset, #112130 0 0 0 1px, #070c12 0 5px 6px -4px) + &:hover + +background-image(linear-gradient(#2d547b 0%, #1f3b55 50%)) + + &:active + +background-image(linear-gradient(#22405d 0%, #182e43 50%)) + +box-shadow(#1e3851 0 0 0 1px inset, #112130 0 0 0 1px) + + img + float: left + margin-right: 6px + + &:hover + +background-image(linear-gradient(#c03032 0%, #972627 50%)) + + &:active + position: relative + top: 2px + +background-image(linear-gradient(#9f2829 0%, #832122 50%)) + +box-shadow(#932526 0 0 0 1px inset, #6e1c1d 0 0 0 1px) + + .form-container + float: left + margin-left: 15px + margin-top: 15px + width: 305px + + form + +form + display: none + + .error + background-color: #f4e15f + border: 2px solid #ffe534 + +border-radius(3px) + color: #363636 + display: none + padding: 5px + font-family: Helvetica, Arial, sans-serif + font-size: 14px + margin-bottom: 10px + + &.current + display: block + + input[type=text] + width: 285px + +input() + + textarea + +input() + width: 285px + height: 110px + + label.radio + display: inline + + p.warning + display: none + color: #fff + font-size: 13px + + .post-label-selector + margin-top: 15px + height: 125px + overflow-y: scroll + + li + &:first-child + margin-top: 0px + + margin-top: 10px + margin-right: 10px + padding: 7px 5px 3px + background-color: #fff + +border-radius(5px) + background-image: image-url("feed_page/check-off.png") + background-repeat: no-repeat + background-position: 252px 7px + + &.checked + background-image: image-url("feed_page/check-on.png") + + label + display: block + font-size: 18px + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + color: #333 + margin: 0 + padding: 0 + + img + position: relative + top: -3px + vertical-align: middle + margin-right: 5px + border: 0 + + input + display: none + + button + margin-top: 15px + font-size: 18px + padding: 15px 15px + margin-bottom: 15px + + .on-focus + display: none + margin-top: 15px + + input.date + position: relative + z-index: 10000 + + .spinner + display: none + background-image: image-url("loading.gif") + background-repeat: no-repeat + width: 32px + height: 32px + + form.create-group-post + + .group-select + background-color: #fff + height: 210px + overflow-y: scroll + +border-radius(5px) + li a + display: block + padding: 10px 0 10px 10px + height: 50px + font-size: 16px + font-family: Rokkitt, Rockwell + font-weight: bold + color: #1B334A + text-decoration: none + background-position: 10px center + background-repeat: no-repeat + + &.odd + background-color: #EEE + + &.even + + + &:hover + background-color: #DEDEDE + + img + +border-radius(5px) + margin-right: 10px + float: left + span + display: block + width: 230px + margin-top: 10px + + .group-post-inputs + display: none + + + + diff --git a/app/stylesheets/mobile.css b/app/stylesheets/mobile.css new file mode 100644 index 000000000..d972a8831 --- /dev/null +++ b/app/stylesheets/mobile.css @@ -0,0 +1,17 @@ +/*iWebKit css 5.04 by Christopher Plieger*/body{position:relative;margin:0;-webkit-text-size-adjust:none;min-height:416px;font-family:helvetica,sans-serif;-webkit-background-size:0.438em 100%;background:-webkit-gradient(linear,left top,right top,from(#004890),color-stop(71%, #004890),color-stop(72%, #004890),to(#004890));-webkit-touch-callout:none}.center{margin:auto;display:block;text-align:center!important}img{border:0}a:hover .arrow{background-position:0 -13px!important}@media screen and (max-width:320px){#topbar{height:44px}#title{line-height:44px;height:44px;font-size:16pt}#tributton a:first-child,#duobutton a:first-child{width:101px}#tributton a:last-child,#duobutton a:last-child{width:101px}#tributton a{width:106px}#duobutton .links{width:195px}#tributton .links{width:302px}#doublead{width:300px!important}#duoselectionbuttons{width:191px;height:30px;top:7px}#triselectionbuttons{width:290px;height:30px;top:7px}#triselectionbuttons a:first-child,#duoselectionbuttons a:first-child{width:99px;height:28px;line-height:28px}#triselectionbuttons a{width:98px;height:28px;line-height:28px}#triselectionbuttons a:last-child,#duoselectionbuttons a:last-child{width:99px;height:28px;line-height:28px}.searchbox form{width:272px}.searchbox input[type="text"]{width:275px}.menu .name{max-width:77%}.checkbox .name{max-width:190px}.radiobutton .name{max-width:190px}#leftnav a,#rightnav a,#leftbutton a,#rightbutton a,#blueleftbutton a,#bluerightbutton a{line-height:30px;height:30px}#leftnav img,#rightnav img{margin-top:4px}#leftnav,#leftbutton,#blueleftbutton{top:7px}#rightnav,#rightbutton,#bluerightbutton{top:7px}.musiclist .name{max-width:55%}.textbox textarea{width:280px}.bigfield input{width:295px}}@media screen and (min-width:321px){#topbar{height:32px}#title{line-height:32px;height:32px;font-size:13pt}.menu .name{max-width:85%}.checkbox .name{max-width:75%}.radiobutton .name{max-width:75%}#leftnav a,#rightnav a,#leftbutton a,#rightbutton a,#blueleftbutton a,#bluerightbutton a{line-height:24px;height:24px}#leftnav img,#rightnav img{margin-top:4px;height:70%}#leftnav,#leftbutton,#blueleftbutton{top:4px}#rightnav,#rightbutton,#bluerightbutton{top:4px}.musiclist .name{max-width:70%}.textbox textarea{width:440px}#tributton a:first-child,#duobutton a:first-child{width:152px}#tributton a:last-child,#duobutton a:last-child{width:152px}#tributton a{width:154px}#tributton .links{width:452px}#duobutton .links{width:298px}#doublead{width:350px!important}#duoselectionbuttons{width:293px;height:24px;top:4px}#triselectionbuttons{width:450px;height:24px;top:4px}#triselectionbuttons a:first-child,#duoselectionbuttons a:first-child{width:150px;height:22px;line-height:22px}#triselectionbuttons a{width:156px;height:22px;line-height:22px}#triselectionbuttons a:last-child,#duoselectionbuttons a:last-child{width:150px;height:22px;line-height:22px}.searchbox form{width:432px}.searchbox input[type="text"]{width:435px}.bigfield input{width:455px}}#topbar.black{background:-webkit-gradient(linear,0% 0%,0% 100%,from(#858585),color-stop(3%,#636363),color-stop(50%,#202020),color-stop(51%,black),color-stop(97%,black),to(#262626))}#topbar.transparent{background:-webkit-gradient(linear,0% 0%,0% 100%,from(rgba(133,133,133,0.7)),color-stop(3%,rgba(99,99,99,0.7)),color-stop(50%,rgba(32,32,32,0.7)),color-stop(51%,rgba(0,0,0,0.7)),color-stop(97%,rgba(0,0,0,0.7)),to(rgba(38,38,38,0.7)))}#topbar{position:relative;left:0;top:0;width:auto;background:-webkit-gradient(linear,0% 0%,0% 100%,from(#cdd5df),color-stop(3%,#b0bccd),color-stop(50%,#889bb3),color-stop(51%,#8195af),color-stop(97%,#6d84a2),to(#2d3642));margin-bottom:13px}#title{position:absolute;font-weight:bold;top:0;left:0;right:0;padding:0 10px;text-align:center;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;color:#FFF;text-shadow:rgba(0,0,0,0.6) 0 -1px 0}#content{width:100%;position:relative;min-height:250px;margin-top:10px;height:auto;z-index:0;overflow:hidden}#footer{text-align:center;position:relative;margin:20px 10px 0;height:auto;width:auto;bottom:10px}.ipodlist #footer,.ipodlist #footer a{text-shadow:#FFF 0 -1px 0}#footer a,#footer{text-decoration:none;font-size:9pt;color:#4C4C4C;text-shadow:#FFF 0 1px 0}.pageitem{-webkit-border-radius:8px;background-color:#fff;border:#878787 solid 1px;font-size:12pt;overflow:hidden;padding:0;position:relative;display:block;height:auto;width:auto;margin:3px 9px 17px;list-style:none}.textbox{padding:5px 9px;position:relative;overflow:hidden;border-top:1px solid #878787}#tributton,#duobutton{height:44px;background:-webkit-gradient(linear,0% 0%,0% 100%,from(#cdd4d9),color-stop(3%,#c0c9cf),color-stop(97%,#abb7bf),to(#81929f));margin:-13px 0 13px 0;text-align:center}#tributton .links,#duobutton .links{height:30px;-webkit-border-image:url("../images/tributton.png") 0 4 0 4;border-width:0 4px 0 4px;margin:0 auto 0px auto;position:relative;top:7px}#tributton a:first-child,#duobutton a:first-child{border-right:1px solid #6d7e91;-webkit-border-top-left-radius:5px;-webkit-border-bottom-left-radius:5px;margin-left:-4px}#tributton a,#duobutton a{text-overflow:ellipsis;overflow:hidden;white-space:nowrap;height:27px;display:inline-block;line-height:27px;margin-top:1px;font:bold 13px;text-decoration:none;color:#3f5c84;text-shadow:#FFF 0 1px 0}#duobutton a:last-child{border:0}#tributton a:last-child{border-left:1px solid #6d7e91}#tributton a:last-child,#duobutton a:last-child{-webkit-border-top-right-radius:5px;-webkit-border-bottom-right-radius:5px;margin-right:-4px}#tributton a:hover,#tributton a#pressed,#duobutton a:hover,#duobutton a#pressed{background:-webkit-gradient(linear,0% 0%,0% 100%,from(#7b8b9f),color-stop(3%,#8c9baf),to(#647792));color:white;text-shadow:black 0 -1px 0}#triselectionbuttons,#duoselectionbuttons{-webkit-border-image:url('../images/navbutton.png') 0 5 0 5;border-width:0 5px 0 5px;position:relative;margin:auto}#duoselectionbuttons a:first-child{border:0}#triselectionbuttons a:first-child{border-right:solid 1px #556984}#triselectionbuttons a:first-child,#duoselectionbuttons a:first-child{margin-left:-4px;-webkit-border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px}#triselectionbuttons a,#duoselectionbuttons a{display:inline-block;text-align:center;color:white;text-decoration:none;margin-top:1px;text-shadow:black 0 -1px 0;background:-webkit-gradient(linear,0% 0%,0% 100%,from(#909baa),color-stop(3%,#a5b4c6),color-stop(50%,#798eaa),color-stop(51%,#6b83a1),color-stop(97%,#6e85a3),to(#526379))}#triselectionbuttons a:last-child,#duoselectionbuttons a:last-child{border-left:solid 1px #556984;margin-right:-4px;-webkit-border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px}#triselectionbuttons a:hover,#triselectionbuttons a#pressed,#duoselectionbuttons a:hover,#duoselectionbuttons a#pressed{background:none}#doublead{height:83px!important;position:relative;margin:0 auto 13px auto}#doublead a:first-child{left:0!important}#doublead a:last-child{right:0!important}#doublead a{width:147px!important;height:83px!important;position:absolute;-webkit-border-radius:8px;display:block;background:-webkit-gradient(linear,0% 0%,0% 100%,from(#7c7c7c),color-stop(3%,#858585),color-stop(97%,#a4a4a4),to(#c2c2c2))}li#doublead{margin-top:25px;margin-bottom:10px!important;background:none}li#doublead:hover{background:none}.searchbox{height:44px;background:-webkit-gradient(linear,0% 0%,0% 100%,from(#f1f3f4),color-stop(3%,#e0e4e7),color-stop(50%,#c7cfd4),color-stop(51%,#bec7cd),color-stop(97%,#b4bec6),to(#8999a5));margin:-13px 0 13px 0;width:100%}.searchbox form{height:24px;-webkit-border-image:url('../images/searchfield.png') 4 14 1 24;border-width:4px 14px 1px 24px;display:block;position:relative;top:8px;margin:auto}fieldset{border:0;margin:0;padding:0}.searchbox input[type="text"]{border:0;-webkit-appearance:none;height:18px;float:left;font-size:13px;padding:0;position:relative;top:2px;left:2px}.textbox img{max-width:100%}.textbox p{margin-top:2px}.textbox p{margin-top:2px;color:#000;margin-bottom:2px;text-align:left}.textbox img{max-width:100%}.textbox ul{margin:3px 0 3px 0;list-style:circle!important}.textbox li{margin:0!important}.pageitem li:first-child,.pageitem li.form:first-child{border-top:0}.menu,.checkbox,.radiobutton,.select,li.button,li.bigfield,li.smallfield{position:relative;list-style-type:none;display:block;height:43px;overflow:hidden;border-top:1px solid #878787;width:auto}.pageitem li:first-child:hover,.pageitem li:first-child a,.radiobutton:first-child input,.select:first-child select,li.button:first-child input,.bigfield:first-child input{-webkit-border-top-left-radius:8px;-webkit-border-top-right-radius:8px}.pageitem li:last-child:hover,.pageitem li:last-child a,.radiobutton:last-child input,.select:last-child select,li.button:last-child input,.bigfield:last-child input{-webkit-border-bottom-left-radius:8px;-webkit-border-bottom-right-radius:8px}.menu:hover,.store:hover,.list #content li a:hover,.list .withimage:hover,.applist li:hover:nth-child(n),.ipodlist li:hover:nth-child(n){background:-webkit-gradient(linear,0% 0%,0% 100%,from(#058cf5),to(#015fe6))}.ipodlist li:hover:nth-child(n) .name,.ipodlist li:hover:nth-child(n) .time{border:0}.menu a:hover .name,.store:hover .starcomment,.store:hover .name,.store:hover .comment,.list .withimage a:hover .comment{color:#fff}.menu a:hover .comment{color:#CCF}.menu a{display:block;height:43px;width:auto;text-decoration:none}.menu a img{width:auto;height:32px;margin:5px 0 0 5px;float:left}.menu .name,.checkbox .name,.radiobutton .name{margin:11px 0 0 7px;width:auto;color:#000;font-weight:bold;font-size:17px;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;float:left}.menu .comment{margin:11px 30px 0 0;width:auto;font-size:17px;text-overflow:ellipsis;overflow:hidden;max-width:75%;white-space:nowrap;float:right;color:#324f85}.menu .arrow,.store .arrow,.musiclist .arrow,.list .arrow{position:absolute;width:8px!important;height:13px!important;right:10px;top:15px;margin:0!important;background:url("../images/arrow.png") 0 0 no-repeat}.applist .arrow{position:absolute;width:8px!important;height:13px!important;right:10px;top:29px;margin:0!important;background:url("../images/arrow.png") 0 0 no-repeat}.store{height:90px;border-top:#878787 solid 1px;overflow:hidden;position:relative}.store a{width:100%;height:90px;display:block;text-decoration:none;position:absolute}.store .image{position:absolute;left:0;top:0;height:90px;width:90px;display:block;background:-webkit-gradient(linear,0% 0%,0% 100%,from(#eff1f5),to(#d6dce6));-webkit-background-size:90px}.applist .image{width:57px;height:57px;display:block;position:absolute;top:7px;left:11px;-webkit-border-radius:8px;-webkit-box-shadow:0 2px 3px rgb(0,0,0);background:-webkit-gradient(linear,0% 0%,0% 100%,from(#7c7c7c),color-stop(3%,#858585),color-stop(97%,#a4a4a4),to(#c2c2c2));-webkit-background-size:57px}li:first-child.store .image,.store:first-child a{-webkit-border-top-left-radius:8px 8px}li:last-child.store .image,.store:last-child a{-webkit-border-bottom-left-radius:8px 8px}.store .name,.applist .name{font-size:15px;white-space:nowrap;display:block;overflow:hidden;color:#000;max-width:60%;text-overflow:ellipsis;font-weight:bold}.store .name{position:absolute;left:95px;top:35px}.applist .name{position:absolute;top:27px;left:80px;text-shadow:#eee 0 1px 0}.store .comment,.list .withimage .comment,.applist .comment,.applist .price{font-size:12px;color:#7f7f7f;display:block;width:60%;font-weight:bold;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.store .comment,.list .withimage .comment{margin:16px 0 0 95px}.applist .comment{position:absolute;top:9px;left:80px;text-shadow:#eee 0 1px 0;color:#3b3b3b}.applist .price{position:absolute;top:29px;right:26px;text-shadow:#eee 0 1px 0;text-align:right;color:#3b3b3b}.store .arrow,.list .withimage .arrow{top:39px!important}.store .stars0,.store .stars1,.store .stars2,.store .stars3,.store .stars4,.store .stars5{position:absolute;top:56px;left:95px;width:65px;height:18px;display:block!important}.store .stars0{background:url('../images/0starsborder.png')}.store .stars1{background:url('../images/1starsborder.png')}.store .stars2{background:url('../images/2starsborder.png')}.store .stars3{background:url('../images/3starsborder.png')}.store .stars4{background:url('../images/4starsborder.png')}.store .stars5,.applist .stars5{background:url('../images/5stars.png')}.applist .stars0,.applist .stars1,.applist .stars2,.applist .stars3,.applist .stars4,.applist .stars5{position:absolute;top:46px;left:79px;width:65px;height:18px;display:block!important}.applist .stars0{background:url('../images/0stars.png')}.applist .stars1{background:url('../images/1stars.png')}.applist .stars2{background:url('../images/2stars.png')}.applist .stars3{background:url('../images/3stars.png')}.applist .stars4{background:url('../images/4stars.png')}.applist .starcomment{left:147px;top:46px;color:#3b3b3b}.starcomment{position:absolute;left:165px;top:56px;font-size:12px;color:#7f7f7f;font-weight:lighter}.applist a:hover .name,.applist a:hover .starcomment,.applist a:hover .comment,.applist a:hover .price{color:white;text-shadow:none}.graytitle{position:relative;font-weight:bold;font-size:17px;right:20px;left:9px;color:#4C4C4C;text-shadow:#FFF 0 1px 0;padding:1px 0 3px 8px}.header{display:block;font-weight:bold;color:rgb(73,102,145);font-size:12pt;margin-bottom:6px;line-height:14pt}.musiclist ul,.ipodlist ul,.applist ul{padding:0}.ipodlist ul{margin:0}.musiclist li:nth-child(odd){background:#dddee0}.applist li:nth-child(even){background:-webkit-gradient(linear,0% 0%,0% 100%,from(#adadb0),color-stop(98%,#adadb0),to(#898a8d))}.applist li:nth-child(odd){background:-webkit-gradient(linear,0% 0%,0% 100%,from(#98989c),color-stop(98%,#98989c),to(#898a8d))}.ipodlist li:nth-child(even){background:-webkit-gradient(linear,0% 0%,0% 100%,from(#414041),color-stop(3%,rgba(45,45,45,0.2)),to(rgba(45,45,45,0.2)))}.ipodlist li:nth-child(odd){background:-webkit-gradient(linear,0% 0%,0% 100%,from(#414041),color-stop(3%,rgba(50,50,50,0.4)),to(rgba(50,50,50,0.4)))}.musiclist #content li,.ipodlist #content li,.applist #content li{list-style:none;width:auto;position:relative}.musiclist #content li{height:44px;border-bottom:1px solid #e6e6e6}.applist #content li{height:70px;margin-bottom:1px}.ipodlist #content li{height:42px}.ipodlist #content{background:-webkit-gradient(radial,50% -70,0,50% 0,200,from(#444),to(rgb(13,13,13)));top:16px}.musiclist #content li a,.ipodlist #content li a{text-decoration:none;color:#000;width:100%!important;height:100%;display:block}.applist #content li a{text-decoration:none;color:#000;width:100%;height:100%;display:block}.musiclist .number,.musiclist .name,.musiclist .time{display:inline-block;height:44px;font-weight:bold;font-size:large;width:44px;text-align:center;line-height:46px}.musiclist .name{margin-left:0;width:auto!important;font-size:medium;padding-left:5px;border-left:solid 1px #e6e6e6}.musiclist .time{color:#848484;font-size:medium;margin-left:4px;width:auto!important;font-weight:normal}.musiclist{background-image:none!important;background-color:#cbcccf}.ipodlist{background-image:none!important;background-color:black}.applist{background-image:none!important;background-color:#98989c}.ipodlist span{color:white;font-weight:bold;font-size:14px}.musiclist .name{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.musiclist a:hover .name{color:#0380f2}.ipodlist .number{width:23px;display:block;float:left;height:42px;margin-right:3px;text-align:right;line-height:43px}.ipodlist .stop,.ipodlist .auto,.ipodlist .play{width:18px;display:block;float:left;height:10px;text-align:right;line-height:43px;margin-top:16px}.ipodlist .play{background:url('../images/play.gif') no-repeat}.ipodlist a:hover .auto,.ipodlist a:hover .play{background:url('../images/play.gif') no-repeat;background-position:0 -10px}.ipodlist .time{width:48px;float:right;border-left:solid #414041 1px;display:block;height:42px;text-align:center;line-height:43px}.ipodlist .name{display:block;float:left;width:inherit;height:42px;text-overflow:ellipsis;line-height:42px;padding-left:5px;overflow:hidden;white-space:nowrap;max-width:62%;border-left:solid #414041 1px}.list .title{background:-webkit-gradient(linear,0% 0%,0% 100%,from(#a5b1ba),color-stop(3%,#909faa),color-stop(97%,#b5bfc6),to(#989ea4));height:22px!important;width:100%;color:#fff;font-weight:bold;font-size:16px;text-shadow:gray 0 1px 0;line-height:22px;padding-left:20px;border-bottom:none!important}.list ul{background-color:#fff;width:100%;overflow:hidden;padding:0;margin:0}.list #content li{height:40px;border-bottom:1px solid #e1e1e1;list-style:none}.list{background-color:#fff;background-image:none!important}.list #footer{margin-top:24px!important}.ipodlist #footer{margin-top:48px!important}.list #content li a{padding:9px 0 0 20px;font-size:large;font-weight:bold;position:relative;display:block;color:#000;text-decoration:none;height:32px}.list #content li a .name{text-overflow:ellipsis;overflow:hidden;max-width:93%;white-space:nowrap;display:block}.list #content li a:hover{color:#fff}.list #content{margin-top:-13px!important}.ipodlist #content,.musiclist #content,.applist #content{margin-top:-29px!important}.list ul img{width:90px;height:90px;position:absolute;left:0;top:0}.list .withimage{height:90px!important}.list .withimage .name{margin:13px 0 0 90px;text-overflow:ellipsis;overflow:hidden;max-width:63%!important;white-space:nowrap}.list .withimage .comment{margin:10px auto auto 90px !important;max-width:63%!important}.list .withimage a,.list .withimage:hover a{height:81px!important}#leftnav,#leftbutton,#blueleftbutton{position:absolute;font-size:12px;left:9px;font-weight:bold}#leftnav,#leftbutton,#rightnav,#rightbutton,#blueleftbutton,#bluerightbutton{z-index:5000}#leftnav a,#rightnav a,#leftbutton a,#rightbutton a,#blueleftbutton a,#bluerightbutton a{display:block;color:#fff;text-shadow:rgba(0,0,0,0.6) 0 -1px 0;text-decoration:none}.black #leftnav a:first-child,.transparent #leftnav a:first-child{-webkit-border-image:url("../images/navleftblack.png") 0 5 0 13}.black #leftnav a,.transparent #leftnav a{-webkit-border-image:url("../images/navlinkleftblack.png") 0 5 0 13}.black #rightnav a:first-child,.transparent #rightnav a:first-child{-webkit-border-image:url("../images/navrightblack.png") 0 13 0 5}.black #rightnav a,.transparent #rightnav a{-webkit-border-image:url("../images/navlinkrightblack.png") 0 13 0 5}.black #leftbutton a,.black #rightbutton a,.transparent #leftbutton a,.transparent #rightbutton a{-webkit-border-image:url("../images/navbuttonblack.png") 0 5 0 5}#leftnav a:first-child{z-index:2;-webkit-border-image:url("../images/navleft.png") 0 5 0 13;border-width:0 5px 0 13px;-webkit-border-top-left-radius:16px;-webkit-border-bottom-left-radius:16px;-webkit-border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;width:auto}#leftnav a{-webkit-border-image:url("../images/navlinkleft.png") 0 5 0 13;z-index:3;margin-left:-4px;border-width:0 5px 0 13px;padding-right:4px;-webkit-border-top-left-radius:16px;-webkit-border-bottom-left-radius:16px;-webkit-border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;float:left}#rightnav,#rightbutton,#bluerightbutton{position:absolute;font-size:12px;right:9px;font-weight:bold}#rightnav a{-webkit-border-image:url("../images/navlinkright.png") 0 13 0 5;z-index:3;margin-right:-4px;border-width:0 13px 0 5px;padding-left:4px;-webkit-border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;float:right;-webkit-border-top-right-radius:16px;-webkit-border-bottom-right-radius:16px}#rightnav a:first-child{z-index:2;-webkit-border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-webkit-border-image:url("../images/navright.png") 0 13 0 5;border-width:0 13px 0 5px;-webkit-border-top-right-radius:16px;-webkit-border-bottom-right-radius:16px}#leftbutton a,#rightbutton a{-webkit-border-image:url("../images/navbutton.png") 0 5 0 5;border-width:0 5px;-webkit-border-radius:6px}#blueleftbutton a,#bluerightbutton a{-webkit-border-image:url("../images/navbuttonblue.png") 0 5 0 5;border-width:0 5px;-webkit-border-radius:6px}input[type="checkbox"]{width:94px;height:27px;background:url('../images/checkbox.png');-webkit-appearance:none;border:0;float:right;margin:8px 4px 0 0}input[type="checkbox"]:checked{background-position:0 27px}input[type="radio"]{-webkit-appearance:none;border:0;width:100%;height:100%;z-index:2;position:absolute;left:0;margin:0;-webkit-border-radius:0}input[type="radio"]:checked{background:url('../images/radiobutton.png') no-repeat;background-position:right center}.radiobutton .name{z-index:1}select{-webkit-appearance:none;height:100%;width:100%;border:0}.select select{-webkit-border-radius:0;color:#000;font-weight:bold;font-size:17px}.select option{max-width:90%}.select .arrow{background:url('../images/arrow.png');width:8px;height:13px;display:block;-webkit-transform:rotate(90deg);position:absolute;right:10px;top:18px}.button input{width:100%;height:100%;-webkit-appearance:none;border:0;-webkit-border-radius:0;font-weight:bold;font-size:17px;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;background:none}.textbox textarea{padding:0;margin-top:5px;font-size:medium}.bigfield input{-webkit-appearance:none;border:0;height:100%;padding:0;-webkit-border-radius:0;background:transparent;font-weight:bold;font-size:17px;padding-left:5px}.smallfield .name{width:48%;position:absolute;left:0;font-size:17px;text-overflow:ellipsis;white-space:nowrap;font-weight:bold;line-height:44px;font-size:17px;padding-left:5px;overflow:hidden}.smallfield input{width:50%;position:absolute;right:0;height:44px;-webkit-appearance:none;border:none;padding:0;background:transparent;-webkit-border-radius:0;font-weight:bold;font-size:17px}.smallfield:first-child input{-webkit-border-top-right-radius:8px}.smallfield:last-child input{-webkit-border-bottom-right-radius:8px} + +.logo-link { + display: block; + padding-top: 20px; + color: #983B2A; + font-size: 20px; + vertical-align: top; + -webkit-text-shadow: #000 1px 1px 1px; + text-shadow: #000 1px 1px 1px; + line-height: 35px; + text-decoration: none; +} + +.logo-link img { + vertical-align: top; +} diff --git a/app/stylesheets/organizer/_map.sass b/app/stylesheets/organizer/_map.sass new file mode 100644 index 000000000..972b42f4d --- /dev/null +++ b/app/stylesheets/organizer/_map.sass @@ -0,0 +1,10 @@ +#community_referrers + h1 + font-size: 20px + padding: 10px + width: auto + color: #3B6085 + line-height: 1.2 + margin: 5px auto 20px; + text-align: center; + font-family: Rockwell,Georgia,Times,"Times New Roman",serif diff --git a/app/stylesheets/registration_page.css.sass b/app/stylesheets/registration_page.css.sass new file mode 100644 index 000000000..5ee2f8897 --- /dev/null +++ b/app/stylesheets/registration_page.css.sass @@ -0,0 +1,312 @@ +//= require chosen +@import core +@import logged_out_header +@import neutral_notification +body + font-family: Rockwell,Georgia,Times,"Times New Roman",serif + background-color: #cee7fe + +.prelaunch-notification + +neutral-notification + +h1 + font-size: 49px + color: #1b334a + padding: 18px 0px + font-weight: bold + text-align: center + margin: 5px auto 20px + width: 960px + line-height: 1.2 + +text-shadow(#bfc8cf 1px 1px 1px) + +#registration_information + float: left + margin-left: 58px + margin-right: 40px + width: 370px + position: relative + + li + +inline-block + +starBullet + font-size: 16px + padding-left: 35px + + #learnmore + position: relative + left: 210px + +#main + form + margin-right: 75px + position: relative + float: left + margin-bottom: 250px + display: block + position: relative + margin: 0px auto 10px + +redlightbox + padding-left: 35px + width: 380px + z-index: 2 + + .facebook + float: right + margin-right: 16px + + h4 + text-align: left + margin-top: 7px + color: #FFFFFF + font-weight: bold + font-size: 20px + text-shadow: #000000 2px 2px 2px + padding-bottom: 4px + letter-spacing: 1px + word-spacing: 2px + select#user_address + width: 366px + height: 23px + margin-top: 20px + font-size: 18px + +serif + font-weight: 300 + display: block + fieldset.inputs + margin-bottom: 10px + ol > li + margin: 5px 0px + width: 360px + label + display: none + color: #898989 + ol > li > input[type=text], input[type=password] + +border-radius(5px) + +box-shadow(#ccc 1px 1px 2px 0 inset) + border: 1px solid #ccc + padding: 3px 5px + font-size: 18px + font-weight: 300 + width: 350px + display: block + +serif + margin-top: 20px + + + fieldset.buttons + margin-left: auto + margin-right: auto + + #user_submit + margin-top: 5px + width: 366px + + .inline-errors + width: 360px + margin: 0px 0px 0px 0px + padding: 0px + text-align: right + font-size: 11px + color: #FFFFFF + + form.add_profile + + .chzn-container + margin-top: 5px + + #user_avatar_input + position: relative + min-height: 54px + + #user_avatar + cursor: pointer + position: relative + left: 145px + +opacity(0) + z-index: 2 + position: absolute + top: 25px + height: 30px + + #file_input_fix + position: absolute + width: 360px + margin: 0px 0px 15px + + #file_style_fix + height: 27px + + #browse_button + position: absolute + top: 5px + right: -12px + padding: 5px + height: 17px + width: 80px + color: white + background: #808080 + border: 1px solid #666666 + +border-radius(0 5px 5px 0) + +box-shadow(#a0a0a0 0px 0px 0px 1px inset) + cursor: pointer + + .facebook + width: 265px + #file_input_fix + width: 240px + #file_style_fix + width: 200px + label + width: 220px + #user_avatar + left: 0 + + ol > li + margin: 10px 0 + padding: 0px 0px + width: 355px + + > select[multiple] + width: 365px + + > input[type=text], > input[type=password], + > textarea, #file_style_fix + border: 1px solid #ccc + +border-radius(5px) + +box-shadow(#ccc 1px 1px 2px 0 inset) + font-size: 22px + +serif + padding: 0 5px + width: 350px + margin: 5px 0 + + + + textarea + height: 70px + + label + display: block + text-align: left + color: #FFFFFF + font-weight: 600 + font-size: 14px + text-shadow: #000000 1px 1px 1px + .inline-hints + width: 360px + margin: 0px 0px 3px 0px + padding: 0px + text-align: right + font-size: 12px + color: #FFFFFF + + #user_referral_metadata_input + display: none + + + form.crop + margin: auto + float: none + padding: 25px + + .jcrop-holder + margin: auto + + input[type=image] + margin: 20px auto 0 + display: block + + form.add_feeds, form.add_groups + h2 + display: block + text-align: left + color: #fff + font-weight: bold + font-size: 18px + text-shadow: #000000 0px -2px 2px + margin-bottom: 3px + + input[type=image] + margin: auto + display: block + input[type=checkbox] + display: none + + #feeds_container, #groups_container + width: 390px + height: 240px + margin: 10px 0 + overflow-y: scroll + overflow-x: hidden + .feed + height: 30px + h3 + margin-top: 6px + .feed, .group + width: 350px + padding: 10px + margin: 0px 0px 4px + +border-radius(5px) + background: white + + &:hover + background-color: #fffdf0 + cursor: pointer + h3 + color: $cpBlue + font-size: 16px + margin-left: 40px + img + float: left + margin-right: 5px + height: 30px + width: 30px + p + font-size: 13px + margin-right: 30px + margin-left: 40px + line-height: 1.3 + + .unchecked, .checked + float: right + height: 20px + width: 20px + margin-left: 5px + margin-top: 5px + .unchecked + background: image-url('check-off.png') + .checked + background: image-url('check-on.png') + + +#wrapper + #main + width: 960px + margin: auto + position: relative + z-index: 1000 + +@include sticky-footer(230px, "#wrapper", "#wrapper-footer", "#footer") + +#footer + background: transparent image-url('houses.png') repeat-x center bottom + height: 20px + padding: 200px 50px 10px + color: #ADD794 + +sans + font-size: 0.8em + .left_text + color: #ADD794 + float: left + .right_text + color: #ADD794 + float: right + a + color: #BFF4A0 + font-weight: bold + text-decoration: none + &:hover + text-decoration: underline + + diff --git a/app/stylesheets/shared/_modal.css.sass b/app/stylesheets/shared/_modal.css.sass new file mode 100644 index 000000000..8817f8a13 --- /dev/null +++ b/app/stylesheets/shared/_modal.css.sass @@ -0,0 +1,124 @@ +.message-modal, .modal-container + position: absolute + background-color: #AC322F + border: 10px solid #fff + +border-radius(15px) + z-index: 2000000 + + .error + background-color: #f4e15f + border: 2px solid #ffe534 + +border-radius(3px) + color: #363636 + display: none + margin-top: 5px + margin-bottom: 15px + padding: 5px + font-family: Helvetica, Arial, sans-serif + font-size: 14px + text-align: center + + table + width: 390px + margin-top: 2px + border: none + +border-radius(5px) + tr:nth-child(even) + background-color: #ddd + tr:nth-child(odd) + background-color: white + tr:first-child + td:first-child + +border-top-left-radius(5px) + td:last-child + +border-top-right-radius(5px) + tr:last-child + td:first-child + +border-bottom-left-radius(5px) + td:last-child + +border-bottom-right-radius(5px) + td + padding: 10px 9px + font-size: 13px + font-family: Helvetica, sans-serif + a + color: #ac322f + text-decoration: none + &:hover + text-decoration: underline + .right + text-align: right + + .unimportant + color: #aaa + + .avatar-link + text-align: center + margin-top: 15px + a + color: #fff + text-decoration: none + &:hover + text-decoration: underline + img + vertical-align: middle + margin-right: 5px + + form + +form + width: 392px + padding: 15px + display: block + + h1 + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + font-size: 16px + color: #fff + text-align: center + + label + display: block + margin-top: 20px + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + font-size: 16px + color: #fff + + input[type=text] + margin-top: 2px + width: 372px + + textarea + margin-top: 2px + width: 372px + + .controls + margin-top: 20px + text-align: right + + button + +blue-button + margin-left: 15px + float: none + + a.cancel, a.delete + color: #fff + font-size: 15px + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + text-decoration: none + &:hover + text-decoration: underline + + a.delete + right: 15px + position: absolute + margin-top: 2px + +#modal-shadow + +opacity(0.8) + background-color: #000 + position: fixed + top: 0 + bottom: 0 + right: 0 + left: 0 + z-index: 1000000 diff --git a/app/stylesheets/shared/_replies.css.sass b/app/stylesheets/shared/_replies.css.sass new file mode 100644 index 000000000..db00e2503 --- /dev/null +++ b/app/stylesheets/shared/_replies.css.sass @@ -0,0 +1,126 @@ +.replies + padding: 1px 10px 10px 22px + margin-top: 5px + position: relative + font-family: Helvetica, sans-serif + + .replies-more + display: block + font-size: 12px + color: #B7403C + font-weight: bold + padding: 4px + text-align: right + + margin-top: 10px + text-decoration: none + &:hover + text-decoration: underline + +.replies .reply-item + margin-top: 12px + padding: 0 + + &:hover + + a.delete-reply + display: inline + .avatar + float: left + width: 30px + height: 30px + +border-radius(0) + border: 0 + + .reply-info + padding-top: 5px + margin-left: 40px + border-top: 1px solid #efefef + + .author, .body + margin-left: 0 + line-height: 18px + font-size: 12px + + .author + margin-bottom: 0 + margin-right: 5px + position: relative + font-weight: bold + color: #1b334a + float: left + + .body, h1, h2, h3, h4, h5, h6 + color: #242424 + margin-top: 0px + margin-bottom: 0px + p + margin-top: 0px + + h1, h2, h3, h4, h5, h6 + font-weight: bold + + .time + color: #909090 + font-size: 11px + margin-left: 5px + + a.delete-reply + display: none + font-size: 11px + margin-left: 5px + padding-right: 10px + text-decoration: none + color: #b7403c + &:hover + text-decoration: underline + +.replies form.new-reply + margin-top: 10px + + .avatar + float: left + width: 30px + height: 30px + +border-radius(0) + border: 0 + + textarea + font-family: Helvetica, Arial, sans-serif + font-size: 12px + color: #888 + background: white + border: 1px solid #c3c3c3 + padding: 8px 10px + height: 12px + width: 373px + float: right + line-height: 14px + + &:focus + outline: none + color: #333 + + .reply-text + float: right + + textarea + font-family: Helvetica, Arial, sans-serif + font-size: 12px + color: #888 + background: white + border: 1px solid #c3c3c3 + padding: 8px 10px + height: 12px + width: 373px + line-height: 14px + + &:focus + outline: none + color: #333 + + .enter-hint + display: none + font-size: 12px + color: #909090 + diff --git a/app/stylesheets/shared/_wire.css.sass b/app/stylesheets/shared/_wire.css.sass new file mode 100644 index 000000000..204663f03 --- /dev/null +++ b/app/stylesheets/shared/_wire.css.sass @@ -0,0 +1,138 @@ +.wire + background-color: white + padding-bottom: 10px + + a.more + +blue-button + display: block + margin: 0px 10px 0px + + p.wire-empty-message + text-align: center + padding: 20px 0px 10px + font-family: Helvetica, sans-serif + color: #888 + +.wire-item + position: relative + border-top: 1px solid #bebebe + padding: 12px 10px 15px 10px + .title, .author, .body, .message-user + margin-left: 62px + .body + font-family: Helvetica, sans-serif + font-size: 0.8em + line-height: 1.4 + color: $textColor + word-wrap: break-word + a + color: #ac322f + text-decoration: none + p + margin-top: 0.8em + .title, h1, h2, h3, h4, h5, h6 + color: #333 + font-size: 18px + margin-bottom: 3px + display: block + text-decoration: none + font-family: Helvetica, sans-serif + + a + color: #ac322f + text-decoration: underline + + .highlight + background-color: #efefaa + + .author + font-size: 13px + color: #1b334a + line-height: 1.2 + margin-bottom: 5px + font-weight: normal + font-family: Helvetica, sans-serif + font-weight: bold + + .corner + float: right + color: #888 + font-family: Helvetica, sans-serif + font-size: 11px + font-weight: normal + a + color: #ac322f + text-decoration: none + .avatar + float: left + width: 50px + height: 50px + display: block + border: 1px solid #aaa + +border-radius(3px) + + .reply-count + float: left + width: 50px + margin-top: 5px + text-align: center + font-size: 10px + clear: left + font-family: Helvetica, sans-serif + + .avatar.calendar + font-family: Rokkitt, Rockwell, Helvetica, Arial, Verdana, sans-serif + border: 0 + + .month + background-color: #ac322f + text-align: center + color: #fff + padding: 3px 0 + +border-top-radius(3px) + + .day-of-month + background-color: #e0e0e0 + text-align: center + padding: 3px 0 + font-size: 26px + color: #081428 + +border-bottom-radius(3px) + +box-shadow(0 0 0 1px #ccc inset) + +text-shadow(1px 1px 0 white) + + .message-user + +red-button + display: block + margin-top: 10px + padding: 5px + + + button.subscribe, button.unsubscribe + +blue-button + display: block + margin-top: 10px + padding: 5px + float: right + +.wire-item .event + .event-time + margin-top: 10px + font-size: 13px + float: left + width: 50px + color: #1B334A + clear: left + text-align: center + + .event-location + margin-top: 10px + margin-left: 60px + font-size: 13px + font-family: Helvetica, sans-serif + th + color: #B7403C + padding-right: 10px + padding-bottom: 5px + td + color: $textColor diff --git a/app/stylesheets/shared/buttons.css.sass b/app/stylesheets/shared/buttons.css.sass new file mode 100644 index 000000000..9db034698 --- /dev/null +++ b/app/stylesheets/shared/buttons.css.sass @@ -0,0 +1,84 @@ +@mixin shiny-button + position: relative + padding: 15px 10px + + &:hover + cursor: pointer + &::-moz-focus-inner + border: 0 + &[disabled] + background: #999 + border: 0 + box-shadow: none + -moz-box-shadow: none + -webkit-box-shadow: none + color: #aaa + cursor: not-allowed + text-shadow: none + &:hover + background: #999 + border: 0 + box-shadow: none + -moz-box-shadow: none + -webkit-box-shadow: none + color: #aaa + cursor: not-allowed + text-shadow: none +@mixin red-button + +shiny-button + background: #ac322f + background: -moz-linear-gradient(top, #ee432e 0%, #ac322f 50%, #ac322f 50%, #ac322f 100%) + background: -webkit-gradient(linear, 0 0, 0 100%, color-stop(0, #d1433f), color-stop(0.5, #ac322f), color-stop(0.5, #ac322f), color-stop(1, #ac322f)) + border: 1px solid #8f2927 + -moz-border-radius: 5px + -webkit-border-radius: 5px + border-radius: 5px + -moz-box-shadow: inset 0px 0px 0px 1px rgba(255, 115, 100, 0.4), 0 1px 3px #555555 + -webkit-box-shadow: inset 0px 0px 0px 1px rgba(255, 115, 100, 0.4), 0 1px 3px #555555 + box-shadow: inset 0px 0px 0px 1px rgba(255, 115, 100, 0.4), 0 1px 3px #555555 + color: #fff + font-family: Rockwell, Georgia, sans-serif, "helvetica neue", helvetica, arial, sans-serif + font-weight: bold + line-height: 1 + text-align: center + text-shadow: 0px -1px 1px rgba(0, 0, 0, 0.8) + &:hover + background: #c23c39 + background: -moz-linear-gradient(top, #e15855 0%, #c23c39 50%, #c23c39 50%, #c23c39 100%) + background: -webkit-gradient(linear, 0 0, 0 100%, color-stop(0, #e15855), color-stop(0.5, #c23c39), color-stop(0.5, #c23c39), color-stop(1, #c23c39)) + cursor: pointer + +@mixin blue-button + +shiny-button + background: #1b334a + background: -moz-linear-gradient(top, #2e4b67 0%, #1b334a 50%, #1b334a 50%, #1b334a 100%) + background: -webkit-gradient(linear, 0 0, 0 100%, color-stop(0, #2e4b67), color-stop(0.5, #1b334a), color-stop(0.5, #1b334a), color-stop(1, #1b334a)) + border: 1px solid #112639 + -moz-border-radius: 5px + -webkit-border-radius: 5px + border-radius: 5px + -moz-box-shadow: inset 0px 0px 0px 1px rgba(46, 75, 103, 0.8), 0 1px 3px #2e4b67 + -webkit-box-shadow: inset 0px 0px 0px 1px rgba(46, 75, 103, 0.8), 0 1px 3px #2e4b67 + box-shadow: inset 0px 0px 0px 1px rgba(46, 75, 103, 0.8), 0 1px 3px #2e4b67 + color: #fff + font-family: Rockwell, Georgia, sans-serif, "helvetica neue", helvetica, arial, sans-serif + font-weight: bold + line-height: 1 + text-align: center + text-shadow: 0px -1px 1px rgba(0, 0, 0, 1) + + &:hover + background: #2e4b67 + background: -moz-linear-gradient(top, #436383 0%, #2e4b67 50%, #2e4b67 50%, #2e4b67 100%) + background: -webkit-gradient(linear, 0 0, 0 100%, color-stop(0, #436383), color-stop(0.5, #2e4b67), color-stop(0.5, #2e4b67), color-stop(1, #2e4b67)) + cursor: pointer +a + &:focus + outline: none + + +button::-moz-focus-inner + border: 0 + + + diff --git a/app/stylesheets/shared/dropkick_selects.scss b/app/stylesheets/shared/dropkick_selects.scss new file mode 100755 index 000000000..49d44ad9c --- /dev/null +++ b/app/stylesheets/shared/dropkick_selects.scss @@ -0,0 +1,206 @@ +/** + * Default DropKick theme + * + * Feel free to edit the default theme + * or even add your own. + * + * See the readme for themeing help + * + */ + +/***** Begin Theme, feel free to edit in here! ******/ + +@import "compass/css3"; + +/* One container to bind them... */ +.dk_container { + width: 100%; +} +.dk_container:focus { + outline: 0; +} +.dk_container a { + cursor: pointer; + text-decoration: none; +} + +/* Opens the dropdown and holds the menu label */ +.dk_toggle { + /** + * Help: Arrow image not appearing + * Try updating this property to your correct dk_arrows.png path + */ + padding: 10px 3% 9px; + width: 94%; + display: block; +} + +a.dk_toggle { + font-size: 14px; + color: #888; + display: block; + background: white; + border: none; + width: 100%; + @include border-radius(4px); + margin: 0 0 3% 0; + resize: none; + position: relative; + z-index: 9999; + font-size: 16px; + font-family: Rokkitt, Rockwell; + font-weight: bold; + color: #1B334A; + text-indent: 5px; + @include box-shadow(0 0 0 1px darken(#ac322f,10%), + 2px 2px 5px #ccc inset); + + +} + +.dk_toggle:hover { + +} +/* Applied when the dropdown is focused */ +.dk_focus .dk_toggle { + +} +.dk_focus .dk_toggle { + +} + +/* Applied whenever the dropdown is open */ +.dk_open { + /** + * Help: Dropdown menu is covered by something + * Try setting this value higher + */ + z-index: 10; +} +.dk_open .dk_toggle { + +} + +/* The outer container of the options */ +.dk_options { + position: relative; + z-index: 1; + @include border-radius(0 0 4px 4px); + background: white; + width: 99.9%; + display: block; + @include box-shadow(0 1px 2px #666); + padding-top: 4px; + font-size: 16px; + font-family: Rokkitt, Rockwell; + font-weight: bold; + color: #1B334A; +} +.dk_options a { + padding: 10px 13px; +} +.dk_options li:last-child a { + @include border-radius(0 0 4px 4px); +} +.dk_options a:hover, +.dk_option_current a { + background: #eee; +} + +/* Inner container for options, this is what makes the scrollbar possible. */ +.dk_options_inner { + max-height: 250px; +} + +/* Set a max-height on the options inner */ +.dk_options_inner, +.dk_touch .dk_options { + max-height: 250px; +} + +/****** End Theme ******/ + +/***** Critical to the continued enjoyment of working dropdowns ******/ + +.dk_container { + float: left; + position: relative; +} +.dk_container a { + outline: 0; +} + +.dk_toggle { + display: block; + position: relative; + zoom: 1; + width: 94%; +} + +.dk_open { + position: relative; +} +.dk_open .dk_options { + display: block; +} +.dk_open .dk_label { + color: inherit; +} + +.dk_options { + display: none; + margin-top: -1px; + position: absolute; + left: 0; +} +.dk_options a, +.dk_options a:link, +.dk_options a:visited { + display: block; +} +.dk_options_inner { + overflow: auto; + position: relative; +} + +.dk_touch .dk_options { + overflow: hidden; +} + +.dk_touch .dk_options_inner { + max-height: none; + overflow: visible; +} + +.dk_fouc select { + position: relative; + top: -99999em; + visibility: hidden; +} + +a.dk_toggle { + span.dk_arrow { + float: right; + height: 13px; + line-height: 27px; + font-size: 13px; + display: block; + cursor: pointer; + background: #1B334A; + text-align: center; + color: white; + font-family: Rokkitt, Rockwell; + line-height: 1em; + border: none; + text-indent: 5px; + font-weight: bold; + padding: 10px 13px 11px 9px; + margin: -10px -3.5% 0 0; + @include background-image(linear-gradient(#294d6f 0%, #1b334a 50%)); + @include border-radius(4px); + text-decoration: none; + @include box-shadow(0 0 0 1px #254564 inset, 0 0 0 1px #112130, 0 5px 6px -4px #070c12); + } +} + +/** Critical to the continued enjoyment of working dropdowns ******/ diff --git a/app/stylesheets/shared/feature_switching.css.sass b/app/stylesheets/shared/feature_switching.css.sass new file mode 100644 index 000000000..2d30e4baf --- /dev/null +++ b/app/stylesheets/shared/feature_switching.css.sass @@ -0,0 +1,36 @@ +#feature-switching + position: absolute + z-index: 10 + right: 0 + font-family: Helvetica + a.feature-panel-toggle + cursor: pointer + background-color: #fff + padding: 5px 5px 5px 20px + display: block + background-image: image-url("blue-arrow-up.png") + background-position: 5px center + background-repeat: no-repeat + + &.shown + background-image: image-url("blue-arrow-down.png") + + div.feature-panel + border-top: 1px solid #fafafa + display: none + background-color: #fff + padding-bottom: 5px + + li + padding: 10px 0 + + button + display: block + width: 90% + margin: auto + + +body.fixedLayout + #feature-switching + position: fixed + top: 65px diff --git a/app/stylesheets/shared/form.css.sass b/app/stylesheets/shared/form.css.sass new file mode 100644 index 000000000..8bd49e298 --- /dev/null +++ b/app/stylesheets/shared/form.css.sass @@ -0,0 +1,38 @@ +@mixin form + display: block + + input[type=text], input[type=password], textarea + margin: 12px 0 + display: block + border: none + padding: 10px 9px + font-size: 13px + font-family: Helvetica, sans-serif + +border-radius(5px) + +box-shadow(#333 1px 1px 3px 0 inset) + + &:first-child + margin-top: 0 + button + +blue-button + float: right + + label + color: #fff + margin-top: 15px + font-size: 16px + font-family: Rokkitt, Rockwell, Helvetica, sans-serif + display: block + + &:first-child + margin-top: 0 + + fieldset.event-times + label + margin-top: 0 + float: left + display: block + width: 145px + &:first-child + margin-right: 14px + margin-bottom: 5px diff --git a/app/stylesheets/shared/markdown.css.sass b/app/stylesheets/shared/markdown.css.sass new file mode 100644 index 000000000..476fb5ae9 --- /dev/null +++ b/app/stylesheets/shared/markdown.css.sass @@ -0,0 +1,30 @@ + +.markdown + + h1, h2, h3, h4, h5, h6, a, p, ul, ol, hr, strong, em, blockquote, code, pre + background-color: none + border: none + word-wrap: break-word + + a + color: #b7403c + + ul + margin-top: 0 + margin-left: 10px + clear: left + li + list-style-type: disc + + ol + margin-top: 0 + margin-left: 10px + clear: left + li + list-style-type: upper-roman + + hr + display: none + + h1, h2, h3, h4, h5, h6 + font-weight: bold diff --git a/app/stylesheets/starter_site.css.sass b/app/stylesheets/starter_site.css.sass new file mode 100644 index 000000000..d8421644c --- /dev/null +++ b/app/stylesheets/starter_site.css.sass @@ -0,0 +1,20 @@ +@import compass/reset/utilities +@import compass/css3/inline-block +@import compass/utilities/general/clearfix +@import compass/css3/border-radius +@import compass/css3/box-shadow +@import compass/css3/text-shadow +@import compass/css3/gradient + +@import starter_site/reset +@import starter_site/mixins +@import starter_site/colors +@import starter_site/measurements +@import starter_site/tabs +@import starter_site/960 +@import starter_site/requestform + + +@import starter_site/core +@import starter_site/formtastic +@import starter_site/test diff --git a/app/stylesheets/starter_site/_960.css.sass b/app/stylesheets/starter_site/_960.css.sass new file mode 100644 index 000000000..fa2dc8e27 --- /dev/null +++ b/app/stylesheets/starter_site/_960.css.sass @@ -0,0 +1,503 @@ +.grid-1, .grid-2, .grid-3, .grid-4, .grid-5, .grid-6, .grid-7, .grid-8, .grid-9, .grid-10, .grid-11, .grid-12, .grid-13, .grid-14, .grid-15, .grid-16 + display: inline + float: left + position: relative + margin-left: 10px + margin-right: 10px + background: none +//// +// Grid >> Children (Alpha ~ First, Omega ~ Last) + +.alpha + margin-left: 0 + +.omega + margin-right: 0 + +//// +// Container >> 16 Columns + +.container-16 + margin-left: auto + margin-right: auto + width: 960px + + // Grid >> 16 Columns + + .grid-1 + width: 40px + background: none + + .grid-2 + width: 100px + background: none + + .grid-3 + width: 160px + background: none + + .grid-4 + width: 220px + background: none + + .grid-5 + width: 280px + background: none + + .grid-6 + width: 340px + background: none + + .grid-7 + width: 400px + background: none + + .grid-8 + width: 460px + background: none + + .grid-9 + background: none + width: 520px + + .grid-10 + width: 580px + background: none + + .grid-11 + width: 640px + background: none + + .grid-12 + width: 700px + background: none + + .grid-13 + width: 760px + + .grid-14 + width: 820px + background: none + + .grid-15 + width: 880px + background: none + + .grid-16 + width: 940px + background: none + + // Prefix Extra Space >> 16 Columns + + .prefix-1 + padding-left: 60px + + .prefix-2 + padding-left: 120px + + .prefix-3 + padding-left: 180px + + .prefix-4 + padding-left: 240px + + .prefix-5 + padding-left: 300px + + .prefix-6 + padding-left: 360px + + .prefix-7 + padding-left: 420px + + .prefix-8 + padding-left: 480px + + .prefix-9 + padding-left: 540px + + .prefix-10 + padding-left: 600px + + .prefix-11 + padding-left: 660px + + .prefix-12 + padding-left: 720px + + .prefix-13 + padding-left: 780px + + .prefix-14 + padding-left: 840px + + .prefix-15 + padding-left: 900px + + // Suffix Extra Space >> 16 Columns + + .suffix-1 + padding-right: 60px + + .suffix-2 + padding-right: 120px + + .suffix-3 + padding-right: 180px + + .suffix-4 + padding-right: 240px + + .suffix-5 + padding-right: 300px + + .suffix-6 + padding-right: 360px + + .suffix-7 + padding-right: 420px + + .suffix-8 + padding-right: 480px + + .suffix-9 + padding-right: 540px + + .suffix-10 + padding-right: 600px + + .suffix-11 + padding-right: 660px + + .suffix-12 + padding-right: 720px + + .suffix-13 + padding-right: 780px + + .suffix-14 + padding-right: 840px + + .suffix-15 + padding-right: 900px + + // Push Extra Space >> 16 Columns + + .push-1 + left: 60px + + .push-2 + left: 120px + + .push-3 + left: 180px + + .push-4 + left: 240px + + .push-5 + left: 300px + + .push-6 + left: 360px + + .push-7 + left: 420px + + .push-8 + left: 480px + + .push-9 + left: 540px + + .push-10 + left: 600px + + .push-11 + left: 660px + + .push-12 + left: 720px + + .push-13 + left: 780px + + .push-14 + left: 840px + + .push-15 + left: 900px + + // Pull Extra Space >> 16 Columns + + .pull-1 + left: -60px + + .pull-2 + left: -120px + + .pull-3 + left: -180px + + .pull-4 + left: -240px + + .pull-5 + left: -300px + + .pull-6 + left: -360px + + .pull-7 + left: -420px + + .pull-8 + left: -480px + + .pull-9 + left: -540px + + .pull-10 + left: -600px + + .pull-11 + left: -660px + + .pull-12 + left: -720px + + .pull-13 + left: -780px + + .pull-14 + left: -840px + + .pull-15 + left: -900px + +//// +// Container >> 12 Columns + +.container-12 + margin-left: auto + margin-right: auto + width: 960px + background: none + // Grid >> 12 Columns + + .grid-1 + width: 60px + background: none + + .grid-2 + width: 140px + background: none + + .grid-3 + width: 220px + background: none + + .grid-4 + width: 300px + background: none + + .grid-5 + width: 380px + background: none + + .grid-6 + width: 460px + background: none + + .grid-7 + width: 540px + background: none + + .grid-8 + width: 620px + background: none + + .grid-9 + width: 700px + background: none + + .grid-10 + width: 780px + background: none + + .grid-11 + width: 860px + background: none + + .grid-12 + width: 940px + background: none + + // Prefix Extra Space >> 12 Columns + + .prefix-1 + padding-left: 80px + + .prefix-2 + padding-left: 160px + + .prefix-3 + padding-left: 240px + + .prefix-4 + padding-left: 320px + + .prefix-5 + padding-left: 400px + + .prefix-6 + padding-left: 480px + + .prefix-7 + padding-left: 560px + + .prefix-8 + padding-left: 640px + + .prefix-9 + padding-left: 720px + + .prefix-10 + padding-left: 800px + + .prefix-11 + padding-left: 880px + + // Suffix Extra Space >> 12 Columns + + .suffix-1 + padding-right: 80px + + .suffix-2 + padding-right: 160px + + .suffix-3 + padding-right: 240px + + .suffix-4 + padding-right: 320px + + .suffix-5 + padding-right: 400px + + .suffix-6 + padding-right: 480px + + .suffix-7 + padding-right: 560px + + .suffix-8 + padding-right: 640px + + .suffix-9 + padding-right: 720px + + .suffix-10 + padding-right: 800px + + .suffix-11 + padding-right: 880px + + // Push Extra Space >> 12 Columns + + .push-1 + left: 80px + + .push-2 + left: 160px + + .push-3 + left: 240px + + .push-4 + left: 320px + + .push-5 + left: 400px + + .push-6 + left: 480px + + .push-7 + left: 560px + + .push-8 + left: 640px + + .push-9 + left: 720px + + .push-10 + left: 800px + + .push-11 + left: 880px + + // Pull Extra Space >> 12 Columns + + .pull-1 + left: -80px + + .pull-2 + left: -160px + + .pull-3 + left: -240px + + .pull-4 + left: -320px + + .pull-5 + left: -400px + + .pull-6 + left: -480px + + .pull-7 + left: -560px + + .pull-8 + left: -640px + + .pull-9 + left: -720px + + .pull-10 + left: -800px + + .pull-11 + left: -880px + +//// +// Clear Floating Elements + +// http://sonspring.com/journal/clearing-floats + +.clear + clear: both + display: block + overflow: hidden + visibility: hidden + width: 0 + height: 0 + +// http://perishablepress.com/press/2008/02/05/lessons-learned-concerning-the-clearfix-css-hack + +.clearfix:after + clear: both + content: ' ' + display: block + font-size: 0 + line-height: 0 + visibility: hidden + width: 0 + height: 0 + +* html .clearfix + height: 1% + diff --git a/app/stylesheets/starter_site/_colors.css.sass b/app/stylesheets/starter_site/_colors.css.sass new file mode 100644 index 000000000..28087ffe0 --- /dev/null +++ b/app/stylesheets/starter_site/_colors.css.sass @@ -0,0 +1,13 @@ +$cpBlue: #4880b1 +$cpDarkBlue: #3b6085 +$cpRed: #b7403c +$cpDarkRed: $cpRed - #222 + +$frontPageDarkRed: #9a3633 +$frontPageRed: #b53f3c +$frontPageBlue: #3b6085 + +$lightGray: #ddd +$mediumGray: #888 +$darkGray: #444545 +$textColor: #222 \ No newline at end of file diff --git a/app/stylesheets/starter_site/_core.css.sass b/app/stylesheets/starter_site/_core.css.sass new file mode 100644 index 000000000..eb979fbbf --- /dev/null +++ b/app/stylesheets/starter_site/_core.css.sass @@ -0,0 +1,74 @@ +a + text-decoration: none + color: $cpDarkBlue + &:hover + text-decoration: underline + +strong, .strong + font-weight: bold +em + font-style: oblique + +#notice + padding: 10px + +sans + background-color: $lightGray + margin-bottom: 1em + +border-radius(7px) + +#main + width: 970px + margin: 10px auto + +museo + +nav.page-navigation, .nav.page-navigation + .links + text-align: right + padding-top: 8px + + +header, #header + display: block + text-align: right + +clearfix + +header, #header + img + float: left + +header, #header + nav, .nav + float: right + margin-top: 1em + a + font-size: 18px + margin-left: 20px + +headline_font + &:hover + text-decoration: none + color: $cpRed + padding-bottom: 2px + border-bottom: 2px solid $cpRed + &.selected_nav + color: $cpRed + padding-bottom: 2px + border-bottom: 2px solid $cpRed + + +footer, #footer + +sans + display: block + font-weight: bold + margin-top: 2em + font-size: 13px + color: #aaa + img + width: 25px + height: 25px + a + color: #777 + p + +inline-block + vertical-align: top + padding: 7px + diff --git a/app/stylesheets/starter_site/_formtastic.css.sass b/app/stylesheets/starter_site/_formtastic.css.sass new file mode 100644 index 000000000..fde7ba9f3 --- /dev/null +++ b/app/stylesheets/starter_site/_formtastic.css.sass @@ -0,0 +1,66 @@ +#form_container + margin: auto + padding: 2em + background-color: $lightGray + border: 1px solid silver + +border-radius(7px) + width: 700px + h2 + margin-bottom: 1em + font-size: 1.5em + +form.formtastic + + input[type=text], textarea, input[type=password] + width: $inputBoxWidth + font-size: 1em + +serif + + input#post_title, textarea#post_body + width: 500px + + textarea + height: 12em + + input[type=submit] + left: $labelWidth + 10px + position: relative + margin-top: 10px + + #toggle + left: $labelWidth + 45px + position: relative + + label + width: $labelWidth + padding: 4px 10px 0 0 + display: inline-block + font-size: 0.9em + font-weight: normal + font-family: Arial, Helvetica, sans-serif + color: #555 + text-align: right + position: relative + top: 0px + float: left + + p.inline-errors + font-size: 0.7em + display: inline + line-height: 120% + margin-left: 10px + font-weight: normal + +sans + color: #cc0000 + + li.boolean + left: $labelWidth + 10px + label + text-align: left + + fieldset ol + li + display: block + position: relative + margin-bottom: 7px + diff --git a/app/stylesheets/starter_site/_measurements.css.sass b/app/stylesheets/starter_site/_measurements.css.sass new file mode 100644 index 000000000..5a885472e --- /dev/null +++ b/app/stylesheets/starter_site/_measurements.css.sass @@ -0,0 +1,2 @@ +$inputBoxWidth: 350px +$labelWidth: 80px \ No newline at end of file diff --git a/app/stylesheets/starter_site/_mixins.css.sass b/app/stylesheets/starter_site/_mixins.css.sass new file mode 100644 index 000000000..b0ed571d5 --- /dev/null +++ b/app/stylesheets/starter_site/_mixins.css.sass @@ -0,0 +1,12 @@ +@mixin sans + font-family: Helvetica, Arial, Verdana, sans-serif + +@mixin serif + font-family: Georgia, Times, serif + +@mixin museo + font-family: 'MuseoSlab500', Georgia, Times, serif + +@mixin headline_font + font-family: 'Rockwell', 'AdelleBasicBold', Georgia, Times, serif + word-spacing: 2px \ No newline at end of file diff --git a/app/stylesheets/starter_site/_requestform.css.sass b/app/stylesheets/starter_site/_requestform.css.sass new file mode 100644 index 000000000..e0af3ba07 --- /dev/null +++ b/app/stylesheets/starter_site/_requestform.css.sass @@ -0,0 +1,37 @@ +#new_request + color: #fff + font-family: "Rockwell",Georgia,Times,serif + label + color: #fff + font-family: "Rockwell",Georgia,Times,serif + width: 200px + input[type="text"] + height: 20px + padding: 5px + border: 1px solid #902c28 + +border-radius(5px) + +box-shadow(#545454 1px 1px 8px 0px inset) + input[type="email"] + height: 20px + padding: 5px + border: 1px solid #982c28 + +border-radius(5px) + +box-shadow(#545454 1px 1px 8px 0px inset) + input[type="submit"] + width: 90px !important + height: 30px + border: 1px solid #ccc + +border-radius(5px) + +background-image(linear-gradient(#e6e6e6, #ebebeb)) + padding-bottom: 1px + box-shadow: inset 0px 1px 0px #fff, inset 0px -1px 0px #c7c7c7, 1px 1px 4px 0px #222 + -moz-box-shadow: inset 0px 1px 0px #fff, inset 0px -1px 0px #c7c7c7, 1px 1px 4px 0px #222 + -webkit-box-shadow: inset 0px 1px 0px #fff, inset 0px -1px 0px #c7c7c7, 1px 1px 4px 0px #222 + font-family: "Rockwell",Georgia,Times,serif + +text-shadow(#ffffff 1px 1px 0px) + .buttons + position: relative + ol + position: absolute + left: 230px + diff --git a/app/stylesheets/starter_site/_reset.css.sass b/app/stylesheets/starter_site/_reset.css.sass new file mode 100644 index 000000000..81dc5a19e --- /dev/null +++ b/app/stylesheets/starter_site/_reset.css.sass @@ -0,0 +1,44 @@ +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, font, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td + :background transparent + :border 0 + :font-size 100% + :margin 0 + :outline 0 + :padding 0 + :vertical-align baseline + +body + :line-height 1 + +ol, ul + :list-style none + +blockquote, q + :quotes none + +// remember to define focus styles! + +\:focus + :outline 0 + +// remember to highlight inserts somehow! + +ins + :text-decoration none + +del + :text-decoration line-through + +// tables still need 'cellspacing="0"' in the markup + +table + :border-collapse collapse + :border-spacing 0 \ No newline at end of file diff --git a/app/stylesheets/starter_site/_tabs.css.sass b/app/stylesheets/starter_site/_tabs.css.sass new file mode 100644 index 000000000..63fdd5f93 --- /dev/null +++ b/app/stylesheets/starter_site/_tabs.css.sass @@ -0,0 +1,119 @@ +@import "compass/css3/opacity" +.tabs + list-style: none + margin: 0 !important + padding: 0 + height: 30px + li + float: left + text-indent: 0 + padding: 0 + margin: 0 !important + list-style-image: none !important + .current + a + cursor: default + color: #ac322f + background: #fff + a + background: #ac322f + font-size: 15px + display: block + height: 30px + line-height: 30px + padding: 0 10px + text-align: center + color: #fff !important + margin-right: 3px + position: relative + +border-radius(5px 5px 0px 0px) + &:active + outline: none + a:hover + color: #ac322f !important + background: #fff !important + .current, .current:hover + cursor: default !important + color: #ac322f !important + background: #fff !important +.panes + #tech, #organ, #init + display: none + padding: 15px 20px + min-height: 340px + background: #fff + color: #0d1a26 + +border-radius(0px 5px 5px 5px) + p:first-child + margin: 0px 0px 10px 0px + #init + display: block + #slides_wrapper + position: relative + display: block !important + .slide_info + background: #000 + width: 420px + height: 20px + padding: 5px 0px + position: absolute + left: 0px + bottom: 0px + z-index: 4 + color: white + text-align: center + +opacity(.8) + #slides + display: block !important + height: 248px + width: 420px + overflow: hidden + position: relative + margin-top: 10px + img + position: absolute + top: 0 + .active + z-index: 3 + #startSlides + width: 110px !important + height: 30px + border: 1px solid #ccc + text-align: center + +border-radius(5px) + +background-image(linear-gradient(#e6e6e6, #ebebeb)) + padding-bottom: 1px + cursor: pointer + box-shadow: inset 0px 1px 0px #fff, inset 0px -1px 0px #c7c7c7, 1px 1px 4px 0px #222 + -moz-box-shadow: inset 0px 1px 0px #fff, inset 0px -1px 0px #c7c7c7, 1px 1px 4px 0px #222 + -webkit-box-shadow: inset 0px 1px 0px #fff, inset 0px -1px 0px #c7c7c7, 1px 1px 4px 0px #222 + font-family: "Rockwell",Georgia,Times,serif + +text-shadow(#ffffff 1px 1px 0px) + font-size: 12px + line-height: 30px + &:active + box-shadow: inset 0px -1px 0px #fff, inset 0px 1px 0px #c7c7c7 + -moz-box-shadow: inset 0px -1px 0px #fff, inset 0px 1px 0px #c7c7c7 + -webkit-box-shadow: inset 0px -1px 0px #fff, inset 0px 1px 0px #c7c7c7 + h3 + font-size: 18px + line-height: 23px + color: #1b334a + a + color: #3B6085 !important + a:hover + color: #1B334A !important + p + margin: 10px 0 + text-align: left + font-size: 14px + line-height: 24px + + #organ + ul + margin: 20px 0px 0px 36px + list-style-type: disc + font-size: 14px + li + margin: 15px 0px + \ No newline at end of file diff --git a/app/stylesheets/starter_site/_test.css.sass b/app/stylesheets/starter_site/_test.css.sass new file mode 100644 index 000000000..9e87aa3dc --- /dev/null +++ b/app/stylesheets/starter_site/_test.css.sass @@ -0,0 +1,199 @@ +@import "compass/css3/text-shadow" + +/* Fix IE. Hide from IE Mac \*/ +* html ul li + float: left +* html ul li a + height: 1% +/* End */ + +body + font-family: "Rockwell",Georgia,Times,serif + background: #1b334a + color: #fff + #alert + color: #000 + position: relative + top: -12px + height: 70px + font-size: 17px + text-align: center + width: 500px + float: right + padding-top: 5px + background: image-url('topbubble.png') no-repeat 100% 0 + a:visited, a:active, a:link + color: #8EA0B6 + text-decoration: none !important + cursor: pointer + a:hover + color: #fff + text-decoration: none !important + cursor: pointer + nav, .nav + border-bottom: 5px solid #D0EBFF + height: 50px + margin-top: 20px + display: block + background: none + #logo + background: none + #menu + float: right + position: relative + margin-top: 20px + background: none + ul + position: absolute + right: 0 + li + list-style: none + display: inline + float: left + margin-top: -10px + a + padding: 20px 5px + color: #fff + &:hover + color: #bbb + h1 + text-indent: -9999px + h2 + margin-left: 20px + font-size: 35px + font-weight: bold + line-height: 36px + #header + position: relative + padding-top: 30px + height: 282px + border-bottom: 5px solid #2c4b68 + background: #fff image-url('starter_site/skyline-bg.png') repeat-x + h2 + color: #000 + #banner_menu + background: #fff + opacity: .9 + color: #000 + position: relative + height: 220px + text-align: left + +border-radius(6px) + li.selected + a + color: #fff !important + background: #ac322f + +text-shadow(#545454 1px 1px 1px) + li + list-style-type: none + a + color: #000 + +text-shadow(#fff 1px 1px 1px) + ul + display: none + &:hover ul, .hover ul + display: none + + .rounded + display: block + margin: 10px + padding: 5px 10px + +border-radius(5px) + + + .bubbles + list-style: none + li + display: none + float: left + height: 112px + width: 130px + background: image-url('starter_site/bubble.png') no-repeat + padding: 15px + margin: 30px + color: #800 + text-align: center + .vert + position: relative + p + position: absolute + + + #main + font-family: "Rockwell",Georgia,Times,serif + padding: 20px 0 + #nominate + background: #ac322f + height: 400px + +border-radius(5px) + h3 + font-size: 28px + line-height: 30px + color: #062834 + input, textarea + width: 400px + margin: 0px auto + label + float: left + text-align: left + font-size: 13px + padding: 10px 0px 3px 0px + #nominate-padding + margin: 20px + #leading + color: #fff + font-size: 22px + line-height: 29px + #heart + margin: 50px 0px 30px 0px + font-size: 2.5em + h3 + +text-shadow(#000 2px 2px 0px) + .border + clear: both + background: image-url('starter_site/border.jpg') repeat-x + height: 30px + .items + padding: 30px 0px 20px + .bottom + border-top: 1px solid #304962 + border-bottom: none !important + .rotate + background: image-url('starter_site/bubble2.png') no-repeat + p + display: block + height: 145px + width: 263px + color: #555 + padding: 10px + margin: 0 10px 40px + font-style: italic + line-height: 26px + font-size: 16px + letter-spacing: .5px + img + float: left + padding: 5px 20px + h4 + font-size: 18px + line-height: 16px + +text-shadow(#122334 2px 2p, 0px) + font-weight: normal + h5 + color: #83a1be + font-size: 13px + padding-top: 5px + font-weight: normal + line-height: 18px + +text-shadow(#122334 2px 2px 0px) + + +#footer + background: #0c1c2c + color: #899baf + border-top: 5px solid #304a63 + height: 60px + p + padding: 20px 10px + font-size: 0.8em + \ No newline at end of file diff --git a/app/stylesheets/tour.css.scss b/app/stylesheets/tour.css.scss new file mode 100644 index 000000000..99dc3ae52 --- /dev/null +++ b/app/stylesheets/tour.css.scss @@ -0,0 +1,119 @@ +@import "core" ; + +$darkRed: #ad312f ; +$darkBlue: #1b334a ; + +#main { position: relative ; } +#tour-shadow { position: fixed ; + top: 0 ; left: 0 ; bottom: 0 ; right: 0 ; + background-color: black ; @include opacity(0.5) ; + z-index: 1000; } + +#tour { background-color: white ; position: absolute; + z-index: 1001; left: 2; top: 0; } + +#tour.welcome { + background-image: image-url("tour/welcome-bg.png"); + background-color: transparent; + width: 530px; height: 400px; + padding: 65px 75px 100px ; + left: 150px; top: 20px; + + .welcome-to { + background: image-url("tour/welcome-to.png") no-repeat center ; + height: 36px; + } + + .community-name { + margin-top: 10px ; color: $darkBlue ; font-size: 50px ; + font-family: Rockwell, Georgia, Times, serif ; + text-shadow: 1px 1px 1px #fff , 3px 3px 0 $darkRed ; + font-weight: bold ; text-align: center ; + } + + .thanks { + margin-top: 20px ; background-color: #eee ; color: $darkRed ; + text-align: center ; padding: 15px ; font-weight: bold ; + } + + h3 { + color: $darkBlue ; text-align: center ; + font-weight: bold ; margin-top: 16px ; + } + + ul { + margin-top: 10px ; padding: 0px 50px ; + + li { + list-style: none ; + background: image-url('tour/star-icon.png') no-repeat left center ; + color: #474747 ; padding: 10px 0px 10px 30px; font-size: 14px ; + } + + } + + a.wire-tour { + display: block ; margin: 30px auto 0px ; height: 56px ; + width: 243px ; background-image: image-url('tour/tour-button.png') ; + cursor: pointer; + } + + a.end-tour { display: block ; color: #a5a5a5 ; text-align: center ; text-decoration: underline ; cursor: pointer; } +} + +#tour.wire { + top: 0px; left: 0px; width: 390px; + padding: 50px 140px 50px 50px; + background: transparent image-url('tour/small-tour-bg.png') no-repeat; + height: 236px; font-size: 15px; line-height: 1.2; + + a.profile-tour { display: block ; margin: 20px auto ; + background: image-url('tour/next-button.png') no-repeat ; + height: 36px ; width: 123px ; cursor: pointer ; + } +} + +#tour.profile { + top: 270px ; left: 490px ; + width: 390px ; height: 236px ; padding: 50px 50px 50px 60px ; + background: transparent image-url('tour/reverse-small-tour-bg.png') no-repeat; + p { font-size: 15px; line-height: 1.2; } + + a.feed-tour { display: block ; margin: 20px auto ; + background: image-url('tour/next-button.png') no-repeat ; + height: 36px ; width: 123px ; cursor: pointer ; + } +} + +#tour.feed { + top: -30px; left: 486px; + width: 390px ; height: 236px ; padding: 60px 50px 50px 60px ; + background: transparent image-url('tour/up-tour-bg.png') no-repeat; + p { font-size: 15px; line-height: 1.2; } + + a.post-tour { display: block ; margin: 20px auto ; + background: image-url('tour/next-button.png') no-repeat ; + height: 36px ; width: 123px ; cursor: pointer ; + } +} + +#tour.post { + top: 0px ; left: 490px ; + background: image-url('tour/large-tour-bg.png') no-repeat ; + width: 370px; height: 415px ; + padding: 25px 50px 70px 65px ; + + p { font-size: 15px; line-height: 1.2; margin-top: 15px ;} + ul { padding: 0 40px ; margin-top: 20px ; } + ul li { + font-size: 13px ; color: #666 ; + margin-top: 20px ; + padding-left: 40px ; + background: no-repeat left top ; + &.post { background-image: image-url('tour/neighborhood-post-icon.png') ; } + &.event{ background-image: image-url('tour/events-icon.png') ; } + &.announcement { background-image: image-url('tour/announcement-icon.png') ; } + &.group { background-image: image-url('tour/discussion-icon.png') ; } + } + a.end-tour { display: block; margin-top: 5px ; color: $darkRed ; font-weight: bold ; font-size: 16px ; text-decoration: underline ; cursor: pointer ; } +} diff --git a/app/templates/feed_page/feed-about.js.mustache b/app/templates/feed_page/feed-about.js.mustache new file mode 100644 index 000000000..ea74bcbdb --- /dev/null +++ b/app/templates/feed_page/feed-about.js.mustache @@ -0,0 +1,2 @@ +

    Feed Description

    +
    {{#markdown}}{{about}}{{/markdown}}
    diff --git a/app/templates/feed_page/feed-actions.js.mustache b/app/templates/feed_page/feed-actions.js.mustache new file mode 100644 index 000000000..2139ad43e --- /dev/null +++ b/app/templates/feed_page/feed-actions.js.mustache @@ -0,0 +1,129 @@ + + +
    + + +
    + +
    + + + + + + + +
    +
    +
    + +
    + + +
    + +
    + + + + + + + +
    + + + +
    +
    + + + + + + + + + + +
    + +
    +
    + +
    + +

    Invite by email:

    +
    + + + + + +
    +
    + +

    Invite by hand:

    + +
    +
    diff --git a/app/templates/feed_page/feed-admin-bar.js.mustache b/app/templates/feed_page/feed-admin-bar.js.mustache new file mode 100644 index 000000000..443372a95 --- /dev/null +++ b/app/templates/feed_page/feed-admin-bar.js.mustache @@ -0,0 +1,10 @@ + +
      + {{#feeds}} +
    • + {{name}} +
    • + {{/feeds}} +
    + +
    diff --git a/app/templates/feed_page/feed-all-posts.js.mustache b/app/templates/feed_page/feed-all-posts.js.mustache new file mode 100644 index 000000000..447421de4 --- /dev/null +++ b/app/templates/feed_page/feed-all-posts.js.mustache @@ -0,0 +1,5 @@ +
    +

    All Posts from {{name}}

    +
      + Show More +
      diff --git a/app/templates/feed_page/feed-header.js.mustache b/app/templates/feed_page/feed-header.js.mustache new file mode 100644 index 000000000..f0d1f1b59 --- /dev/null +++ b/app/templates/feed_page/feed-header.js.mustache @@ -0,0 +1,14 @@ +

      {{feedName}}

      + +{{#isOwner}} + EDIT FEED PROFILE +{{/isOwner}} + +{{^isOwner}} + {{#isSubscribed}} + UNSUBSCRIBE + {{/isSubscribed}} + {{^isSubscribed}} + + {{/isSubscribed}} +{{/isOwner}} diff --git a/app/templates/feed_page/feed-modal.js.mustache b/app/templates/feed_page/feed-modal.js.mustache new file mode 100644 index 000000000..cf3773a2f --- /dev/null +++ b/app/templates/feed_page/feed-modal.js.mustache @@ -0,0 +1,3 @@ + + + diff --git a/app/templates/feed_page/feed-nav.js.mustache b/app/templates/feed_page/feed-nav.js.mustache new file mode 100644 index 000000000..345a5bed7 --- /dev/null +++ b/app/templates/feed_page/feed-nav.js.mustache @@ -0,0 +1,14 @@ +Announcements + + +Events + + +Subscribers + diff --git a/app/templates/feed_page/feed-profile.js.mustache b/app/templates/feed_page/feed-profile.js.mustache new file mode 100644 index 000000000..288479a55 --- /dev/null +++ b/app/templates/feed_page/feed-profile.js.mustache @@ -0,0 +1,23 @@ +

      Organization Information

      + + + +
      + {{#address}} +
      ADDRESS
      +
      {{address}}
      + {{/address}} + {{#phone}} +
      PHONE
      +
      {{phone}}
      + {{/phone}} + {{#website}} +
      WEBSITE
      +
      {{website}}
      + {{/website}} +
      + +Send Us a Message +{{#isOwner}}{{/isOwner}} + +
      diff --git a/app/templates/feed_page/feed-subresources.js.mustache b/app/templates/feed_page/feed-subresources.js.mustache new file mode 100644 index 000000000..53769bd43 --- /dev/null +++ b/app/templates/feed_page/feed-subresources.js.mustache @@ -0,0 +1,21 @@ + +
      +

      Announcements from {{feedName}}

      + +
      +
      +
      + +
      +

      Events from {{feedName}}

      + +
      +
      +
      + +
      +

      Subscribers to {{feedName}}

      + +
      +
      +
      diff --git a/app/templates/feed_page/feed.js.mustache b/app/templates/feed_page/feed.js.mustache new file mode 100644 index 000000000..377192d0f --- /dev/null +++ b/app/templates/feed_page/feed.js.mustache @@ -0,0 +1,14 @@ +
      +
      +
      +
      +
      +
      + +
      + {{#isOwner}} +
      + {{/isOwner}} +
      +
      +
      diff --git a/app/templates/feed_page/feeds-list.js.mustache b/app/templates/feed_page/feeds-list.js.mustache new file mode 100644 index 000000000..d88da86e2 --- /dev/null +++ b/app/templates/feed_page/feeds-list.js.mustache @@ -0,0 +1,12 @@ + +

      Recommended Feeds

      + diff --git a/app/templates/group_page/group.js.mustache b/app/templates/group_page/group.js.mustache new file mode 100644 index 000000000..3eada41ed --- /dev/null +++ b/app/templates/group_page/group.js.mustache @@ -0,0 +1,10 @@ +
      +
      +
      +
      +
      + +
      +
      +
      +
      diff --git a/app/templates/group_page/groups-list.js.mustache b/app/templates/group_page/groups-list.js.mustache new file mode 100644 index 000000000..560f67ff3 --- /dev/null +++ b/app/templates/group_page/groups-list.js.mustache @@ -0,0 +1,12 @@ + +

      All {{community_name}} Groups

      + diff --git a/app/templates/group_page/header.js.mustache b/app/templates/group_page/header.js.mustache new file mode 100644 index 000000000..02e8bb17d --- /dev/null +++ b/app/templates/group_page/header.js.mustache @@ -0,0 +1,8 @@ +

      {{name}}

      + +{{#isSubscribed}} + UNSUBSCRIBE +{{/isSubscribed}} +{{^isSubscribed}} + +{{/isSubscribed}} diff --git a/app/templates/group_page/nav.js.mustache b/app/templates/group_page/nav.js.mustache new file mode 100644 index 000000000..92bfa6471 --- /dev/null +++ b/app/templates/group_page/nav.js.mustache @@ -0,0 +1,20 @@ + +All Posts + + +Members + + +Announcements + + +Events + diff --git a/app/templates/group_page/new-post.js.mustache b/app/templates/group_page/new-post.js.mustache new file mode 100644 index 000000000..fedcf5a5a --- /dev/null +++ b/app/templates/group_page/new-post.js.mustache @@ -0,0 +1,13 @@ +

      Post to this group:

      + + + +
      + +
      + + + +
      + +
      diff --git a/app/templates/group_page/post-list.js.mustache b/app/templates/group_page/post-list.js.mustache new file mode 100644 index 000000000..0c86c1e35 --- /dev/null +++ b/app/templates/group_page/post-list.js.mustache @@ -0,0 +1,15 @@ +
        +
      • +
        september 2
        +
        + +

        this is a reply

        +

        + +

        Post title

        + +
        aristotle
        + +

        saying stuff

        +
      • +
      diff --git a/app/templates/group_page/profile.js.mustache b/app/templates/group_page/profile.js.mustache new file mode 100644 index 000000000..aa4955e8f --- /dev/null +++ b/app/templates/group_page/profile.js.mustache @@ -0,0 +1,5 @@ +

      Group Profile

      + + + +

      {{about}}

      \ No newline at end of file diff --git a/app/templates/group_page/subresources.js.mustache b/app/templates/group_page/subresources.js.mustache new file mode 100644 index 000000000..68cd980b7 --- /dev/null +++ b/app/templates/group_page/subresources.js.mustache @@ -0,0 +1,20 @@ + +
      +
      +
      +
      + +
      +
      +
      +
      + +
      +
      +
      +
      + +
      +
      +
      +
      diff --git a/app/templates/inbox/inbox.js.mustache b/app/templates/inbox/inbox.js.mustache new file mode 100644 index 000000000..87bf1b735 --- /dev/null +++ b/app/templates/inbox/inbox.js.mustache @@ -0,0 +1,4 @@ + +

      {{title}}

      + +
      diff --git a/app/templates/inbox/message-list.js.mustache b/app/templates/inbox/message-list.js.mustache new file mode 100644 index 000000000..b10b4f422 --- /dev/null +++ b/app/templates/inbox/message-list.js.mustache @@ -0,0 +1,3 @@ +
        + +{{#areMore}}More{{/areMore}} diff --git a/app/templates/inbox/message.js.mustache b/app/templates/inbox/message.js.mustache new file mode 100644 index 000000000..003f6f971 --- /dev/null +++ b/app/templates/inbox/message.js.mustache @@ -0,0 +1,17 @@ +
        + + {{^isFeed}} + {{^isSent}}
        {{author}} sent {{date}}:
        {{/isSent}} + {{#isSent}}
        You sent to {{recipient}} {{date}}:
        {{/isSent}} + {{/isFeed}} + {{#isFeed}} + {{^isSent}}
        {{author}} sent to {{recipient}} {{date}}:
        {{/isSent}} + {{#isSent}}
        You sent to {{recipient}} {{date}}:
        {{/isSent}} + {{/isFeed}} +

        {{title}}

        +
        {{#markdown}}{{body}}{{/markdown}}
        +
        +
        + +
        + diff --git a/app/templates/inbox/nav.js.mustache b/app/templates/inbox/nav.js.mustache new file mode 100644 index 000000000..27276d776 --- /dev/null +++ b/app/templates/inbox/nav.js.mustache @@ -0,0 +1,3 @@ +Inbox +Sent +{{#hasFeeds}}Feeds{{/hasFeeds}} diff --git a/app/templates/main_page/announcement-resources.js.mustache b/app/templates/main_page/announcement-resources.js.mustache new file mode 100644 index 000000000..6d3bbd301 --- /dev/null +++ b/app/templates/main_page/announcement-resources.js.mustache @@ -0,0 +1,17 @@ + + + \ No newline at end of file diff --git a/app/templates/main_page/chrono-resources.js.mustache b/app/templates/main_page/chrono-resources.js.mustache new file mode 100644 index 000000000..77e7d3a34 --- /dev/null +++ b/app/templates/main_page/chrono-resources.js.mustache @@ -0,0 +1,7 @@ + diff --git a/app/templates/main_page/community-resources.js.mustache b/app/templates/main_page/community-resources.js.mustache new file mode 100644 index 000000000..93d33214a --- /dev/null +++ b/app/templates/main_page/community-resources.js.mustache @@ -0,0 +1,27 @@ + + +
        +
        \ No newline at end of file diff --git a/app/templates/main_page/directory-resources.js.mustache b/app/templates/main_page/directory-resources.js.mustache new file mode 100644 index 000000000..cce750446 --- /dev/null +++ b/app/templates/main_page/directory-resources.js.mustache @@ -0,0 +1,9 @@ + diff --git a/app/templates/main_page/event-form.js.mustache b/app/templates/main_page/event-form.js.mustache new file mode 100644 index 000000000..8618a1626 --- /dev/null +++ b/app/templates/main_page/event-form.js.mustache @@ -0,0 +1,49 @@ +
        + + + + + + + +
        + + + +
        +
        + + + + + + + + + + +
        diff --git a/app/templates/main_page/event-resources.js.mustache b/app/templates/main_page/event-resources.js.mustache new file mode 100644 index 000000000..dc1b39e50 --- /dev/null +++ b/app/templates/main_page/event-resources.js.mustache @@ -0,0 +1,13 @@ + diff --git a/app/templates/main_page/group-post-form.js.mustache b/app/templates/main_page/group-post-form.js.mustache new file mode 100644 index 000000000..8aff10478 --- /dev/null +++ b/app/templates/main_page/group-post-form.js.mustache @@ -0,0 +1,14 @@ + + + diff --git a/app/templates/main_page/group-post-resources.js.mustache b/app/templates/main_page/group-post-resources.js.mustache new file mode 100644 index 000000000..2592bb8df --- /dev/null +++ b/app/templates/main_page/group-post-resources.js.mustache @@ -0,0 +1,15 @@ + diff --git a/app/templates/main_page/info-box.js.mustache b/app/templates/main_page/info-box.js.mustache new file mode 100644 index 000000000..9ecc7dd7c --- /dev/null +++ b/app/templates/main_page/info-box.js.mustache @@ -0,0 +1,22 @@ +
        +
        +
        + +
        Sorry, we couldn't find anything.
        +
          +
        + +
        +
        +
        +
        No search results.
        +
        +
        + +
        \ No newline at end of file diff --git a/app/templates/main_page/info-list.js.mustache b/app/templates/main_page/info-list.js.mustache new file mode 100644 index 000000000..1696bf0d3 --- /dev/null +++ b/app/templates/main_page/info-list.js.mustache @@ -0,0 +1,8 @@ + + +

        + {{title}} + {{about}} +

        +
        + diff --git a/app/templates/main_page/landing-resources.js.mustache b/app/templates/main_page/landing-resources.js.mustache new file mode 100644 index 000000000..82dc5d830 --- /dev/null +++ b/app/templates/main_page/landing-resources.js.mustache @@ -0,0 +1,12 @@ +
        +
        + +
        +
        + +
        +
        + +
        +
        + diff --git a/app/templates/main_page/main-page.js.mustache b/app/templates/main_page/main-page.js.mustache new file mode 100644 index 000000000..22853eb46 --- /dev/null +++ b/app/templates/main_page/main-page.js.mustache @@ -0,0 +1,11 @@ +
        +
        + +
        +
        + +
        +
        +
        + +
        diff --git a/app/templates/main_page/post-box.js.mustache.erb b/app/templates/main_page/post-box.js.mustache.erb new file mode 100644 index 000000000..fd629d5e1 --- /dev/null +++ b/app/templates/main_page/post-box.js.mustache.erb @@ -0,0 +1,34 @@ + diff --git a/app/templates/main_page/post-form.js.mustache b/app/templates/main_page/post-form.js.mustache new file mode 100644 index 000000000..81bb3e97e --- /dev/null +++ b/app/templates/main_page/post-form.js.mustache @@ -0,0 +1,26 @@ +
        + + + + + +
        + + + + + + +

        {{#t}}publicity.warning{{/t}}

        + + +
        + +
        +
        diff --git a/app/templates/main_page/post-resources.js.mustache b/app/templates/main_page/post-resources.js.mustache new file mode 100644 index 000000000..7e1742de8 --- /dev/null +++ b/app/templates/main_page/post-resources.js.mustache @@ -0,0 +1,13 @@ + diff --git a/app/templates/main_page/profiles/account-profile.js.mustache b/app/templates/main_page/profiles/account-profile.js.mustache new file mode 100644 index 000000000..d62f99a54 --- /dev/null +++ b/app/templates/main_page/profiles/account-profile.js.mustache @@ -0,0 +1,28 @@ +
        +
        + +
        +

        {{fullName}}

        + Edit Profile + Invite +
        +
        +
          + {{#hasAbout}}
        • +

          About

          +
          {{#markdown}}{{about}}{{/markdown}}
          +
        • {{/hasAbout}} + {{#hasInterests}}
        • +

          Interests

          +
            {{#interests}}
          • {{.}}
          • {{/interests}}
          +
        • {{/hasInterests}} + {{#hasSkills}}
        • +

          Skills

          +
            {{#skills}}
          • {{.}}
          • {{/skills}}
          +
        • {{/hasSkills}} + {{#hasGoods}}
        • +

          Goods

          +
            {{#goods}}
          • {{.}}
          • {{/goods}}
          +
        • {{/hasGoods}} +
        +
        diff --git a/app/templates/main_page/profiles/feed-none.js.mustache b/app/templates/main_page/profiles/feed-none.js.mustache new file mode 100644 index 000000000..d22a57d5c --- /dev/null +++ b/app/templates/main_page/profiles/feed-none.js.mustache @@ -0,0 +1,7 @@ +
        +
        +

        No Search Results

        +
        +
        No feeds were found matching {{query}}.
        + Register a New Feed +
        diff --git a/app/templates/main_page/profiles/feed-profile.js.mustache b/app/templates/main_page/profiles/feed-profile.js.mustache new file mode 100644 index 000000000..fde8560bc --- /dev/null +++ b/app/templates/main_page/profiles/feed-profile.js.mustache @@ -0,0 +1,17 @@ +
        +
        + +
        +

        {{fullName}}

        + View Feed Page + {{^isOwner}}Send a Message{{/isOwner}} + {{#isOwner}}Edit Feed Profile{{/isOwner}} + {{^isSubscribed}}{{/isSubscribed}} + {{#isSubscribed}}Unsubscribe{{/isSubscribed}} +
        +
        + {{#about}} +

        About

        +
        {{#markdown}}{{about}}{{/markdown}}
        + {{/about}} +
        diff --git a/app/templates/main_page/profiles/group-none.js.mustache b/app/templates/main_page/profiles/group-none.js.mustache new file mode 100644 index 000000000..6d795219d --- /dev/null +++ b/app/templates/main_page/profiles/group-none.js.mustache @@ -0,0 +1,7 @@ +
        +
        +

        No Search Results

        +
        +
        No groups were found matching {{query}}.
        + Message your Community Organizers +
        diff --git a/app/templates/main_page/profiles/group-profile.js.mustache b/app/templates/main_page/profiles/group-profile.js.mustache new file mode 100644 index 000000000..01073138d --- /dev/null +++ b/app/templates/main_page/profiles/group-profile.js.mustache @@ -0,0 +1,15 @@ +
        +
        + +
        +

        {{fullName}}

        + View Group Page + {{^isSubscribed}}{{/isSubscribed}} + {{#isSubscribed}}Unsubscribe{{/isSubscribed}} +
        +
        + {{#about}} +

        About

        +
        {{#markdown}}{{about}}{{/markdown}}
        + {{/about}} +
        diff --git a/app/templates/main_page/profiles/user-none.js.mustache b/app/templates/main_page/profiles/user-none.js.mustache new file mode 100644 index 000000000..f3ea46055 --- /dev/null +++ b/app/templates/main_page/profiles/user-none.js.mustache @@ -0,0 +1,7 @@ +
        +
        +

        No Search Results

        +
        +
        No users were found matching {{query}}.
        + Invite New Neighbors +
        diff --git a/app/templates/main_page/profiles/user-profile.js.mustache b/app/templates/main_page/profiles/user-profile.js.mustache new file mode 100644 index 000000000..adde189b7 --- /dev/null +++ b/app/templates/main_page/profiles/user-profile.js.mustache @@ -0,0 +1,29 @@ +
        +
        + +
        +

        {{fullName}}

        + Send a Message + {{^hasMet}}Have You Met?{{/hasMet}} + {{#hasMet}}Don't Know Them?{{/hasMet}} +
        +
        +
          + {{#hasAbout}}
        • +

          About

          +
          {{#markdown}}{{about}}{{/markdown}}
          +
        • {{/hasAbout}} + {{#hasInterests}}
        • +

          Interests

          +
            {{#interests}}
          • {{.}}
          • {{/interests}}
          +
        • {{/hasInterests}} + {{#hasSkills}}
        • +

          Skills

          +
            {{#skills}}
          • {{.}}
          • {{/skills}}
          +
        • {{/hasSkills}} + {{#hasGoods}}
        • +

          Goods

          +
            {{#goods}}
          • {{.}}
          • {{/goods}}
          +
        • {{/hasGoods}} +
        +
        diff --git a/app/templates/main_page/tour/feed.js.mustache b/app/templates/main_page/tour/feed.js.mustache new file mode 100644 index 000000000..3646b21b4 --- /dev/null +++ b/app/templates/main_page/tour/feed.js.mustache @@ -0,0 +1,4 @@ + +

        {{#t}}p1{{/t}}

        + + diff --git a/app/templates/main_page/tour/post.js.mustache b/app/templates/main_page/tour/post.js.mustache new file mode 100644 index 000000000..47d86410d --- /dev/null +++ b/app/templates/main_page/tour/post.js.mustache @@ -0,0 +1,14 @@ + + +

        {{#t}}p1{{/t}}

        + +
          +
        • {{#t}}ex1{{/t}}
        • +
        • {{#t}}ex2{{/t}}
        • +
        • {{#t}}ex3{{/t}}
        • +
        • {{#t}}ex4{{/t}}
        • +
        + +

        {{#t}}p2{{/t}}

        + +Get started! diff --git a/app/templates/main_page/tour/profile.js.mustache b/app/templates/main_page/tour/profile.js.mustache new file mode 100644 index 000000000..f2b2c891f --- /dev/null +++ b/app/templates/main_page/tour/profile.js.mustache @@ -0,0 +1,4 @@ + +

        {{#t}}p1{{/t}}

        + + diff --git a/app/templates/main_page/tour/welcome.js.mustache b/app/templates/main_page/tour/welcome.js.mustache new file mode 100644 index 000000000..01ba57e4d --- /dev/null +++ b/app/templates/main_page/tour/welcome.js.mustache @@ -0,0 +1,18 @@ + +
        + +
        {{community_name}}
        + +
        Thanks for signing up, {{first_name}}!
        + +

        You can use CommonPlace to:

        + +
          +
        • {{#t}}use1{{/t}}
        • +
        • {{#t}}use2{{/t}}
        • +
        • {{#t}}use3{{/t}}
        • +
        + + +
        +No thanks, skip this step. diff --git a/app/templates/main_page/tour/wire.js.mustache b/app/templates/main_page/tour/wire.js.mustache new file mode 100644 index 000000000..2f78b35f0 --- /dev/null +++ b/app/templates/main_page/tour/wire.js.mustache @@ -0,0 +1,5 @@ + +

        {{#t}}p1{{/t}}

        + + + diff --git a/app/templates/shared/announcement-edit-form.js.mustache b/app/templates/shared/announcement-edit-form.js.mustache new file mode 100644 index 000000000..84c854996 --- /dev/null +++ b/app/templates/shared/announcement-edit-form.js.mustache @@ -0,0 +1,15 @@ +
        +
        + Delete +

        Edit your announcement

        + + + + + + +
        + Cancel + +
        +
        diff --git a/app/templates/shared/event-edit-form.js.mustache b/app/templates/shared/event-edit-form.js.mustache new file mode 100644 index 000000000..10e4336e0 --- /dev/null +++ b/app/templates/shared/event-edit-form.js.mustache @@ -0,0 +1,48 @@ + +
        +
        + Delete +

        Edit your event

        + + + + + + + + + +
        + + + +
        + +
        + + + + + + +
        + Cancel + +
        +
        diff --git a/app/templates/shared/feature-switching.js.mustache b/app/templates/shared/feature-switching.js.mustache new file mode 100644 index 000000000..5653adcbe --- /dev/null +++ b/app/templates/shared/feature-switching.js.mustache @@ -0,0 +1,19 @@ +{{#canTryFeatures}} + Feature Switches +
        +
          + {{#features}} +
        • + +
        • + {{/features}} +
        + +
        +{{/canTryFeatures}} diff --git a/app/templates/shared/feed-edit-form.js.mustache b/app/templates/shared/feed-edit-form.js.mustache new file mode 100644 index 000000000..4b2ccac40 --- /dev/null +++ b/app/templates/shared/feed-edit-form.js.mustache @@ -0,0 +1,52 @@ + +
        +
        + Delete +

        Edit Your Feed

        + + + + + + + + + + + + +
        + + + + + + + + + + + + + + + +
        + Cancel + +
        +
        diff --git a/app/templates/shared/feed-owners-form.js.mustache b/app/templates/shared/feed-owners-form.js.mustache new file mode 100644 index 000000000..9684fb4cc --- /dev/null +++ b/app/templates/shared/feed-owners-form.js.mustache @@ -0,0 +1,14 @@ + +
        +
        Please fill in some emails.
        +
        Some email addresses not found in CommonPlace.
        +

        Feed Permissions for {{name}}

        + +
        + + +
        + Cancel + +
        +
        diff --git a/app/templates/shared/feed-owners-item.js.mustache b/app/templates/shared/feed-owners-item.js.mustache new file mode 100644 index 000000000..8b6ffb320 --- /dev/null +++ b/app/templates/shared/feed-owners-item.js.mustache @@ -0,0 +1,3 @@ + + {{name}} + Send Message or Remove diff --git a/app/templates/shared/group-post-edit-form.js.mustache b/app/templates/shared/group-post-edit-form.js.mustache new file mode 100644 index 000000000..686e25751 --- /dev/null +++ b/app/templates/shared/group-post-edit-form.js.mustache @@ -0,0 +1,15 @@ +
        +
        + Delete +

        Edit your post

        + + + + + + +
        + Cancel + +
        +
        diff --git a/app/templates/shared/message-form.js.mustache b/app/templates/shared/message-form.js.mustache new file mode 100644 index 000000000..c068cb006 --- /dev/null +++ b/app/templates/shared/message-form.js.mustache @@ -0,0 +1,14 @@ +
        +
        +

        Write a private message to {{name}}

        + + + + + + +
        + Cancel + +
        +
        diff --git a/app/templates/shared/modal.js.mustache b/app/templates/shared/modal.js.mustache new file mode 100644 index 000000000..cf3773a2f --- /dev/null +++ b/app/templates/shared/modal.js.mustache @@ -0,0 +1,3 @@ + + + diff --git a/app/templates/shared/post-edit-form.js.mustache b/app/templates/shared/post-edit-form.js.mustache new file mode 100644 index 000000000..686e25751 --- /dev/null +++ b/app/templates/shared/post-edit-form.js.mustache @@ -0,0 +1,15 @@ +
        +
        + Delete +

        Edit your post

        + + + + + + +
        + Cancel + +
        +
        diff --git a/app/templates/shared/replies.js.mustache b/app/templates/shared/replies.js.mustache new file mode 100644 index 000000000..e51970158 --- /dev/null +++ b/app/templates/shared/replies.js.mustache @@ -0,0 +1,10 @@ +
          + +
          + +
          + +
          Press Enter to submit, and press Shift-Enter to start a new line
          +
          +
          +
          diff --git a/app/templates/wires/header.js.mustache b/app/templates/wires/header.js.mustache new file mode 100644 index 000000000..b05073d2a --- /dev/null +++ b/app/templates/wires/header.js.mustache @@ -0,0 +1,12 @@ + diff --git a/app/templates/wires/items/announcement-tpl.js.mustache b/app/templates/wires/items/announcement-tpl.js.mustache new file mode 100644 index 000000000..90644965b --- /dev/null +++ b/app/templates/wires/items/announcement-tpl.js.mustache @@ -0,0 +1,16 @@ +
          + +
          {{publishedAt}}{{#canEdit}} - EDIT{{/canEdit}}
          + + + + {{title}} + +
          {{author}}
          + +
          {{#markdown}}{{body}}{{/markdown}}
          + +
          +
          + +
          diff --git a/app/templates/wires/items/event-tpl.js.mustache b/app/templates/wires/items/event-tpl.js.mustache new file mode 100644 index 000000000..206ed163a --- /dev/null +++ b/app/templates/wires/items/event-tpl.js.mustache @@ -0,0 +1,31 @@ +
          + +
          {{publishedAt}}{{#canEdit}} - EDIT{{/canEdit}}
          + +
          +
          {{short_month_name}}
          +
          {{day_of_month}}
          +
          + + {{#time}} +
          + {{time}} +
          + {{/time}} + +
          {{title}}
          + +
          {{author}}
          + +
          {{#markdown}}{{body}}{{/markdown}}
          + + + {{#venue}}{{/venue}} + {{#address}}{{/address}} +
          Venue:{{venue}}
          Address:{{address}}
          + +
          + +
          + +
          diff --git a/app/templates/wires/items/feed-tpl.js.mustache b/app/templates/wires/items/feed-tpl.js.mustache new file mode 100644 index 000000000..a9a8a375e --- /dev/null +++ b/app/templates/wires/items/feed-tpl.js.mustache @@ -0,0 +1,12 @@ + + + + +{{#isSubscribed}} + +{{/isSubscribed}} +{{^isSubscribed}} + +{{/isSubscribed}} + +
          diff --git a/app/templates/wires/items/post-tpl.js.mustache b/app/templates/wires/items/post-tpl.js.mustache new file mode 100644 index 000000000..468d5475b --- /dev/null +++ b/app/templates/wires/items/post-tpl.js.mustache @@ -0,0 +1,16 @@ +
          + +
          {{publishedAt}}{{#canEdit}} - EDIT{{/canEdit}}
          + + + + {{title}} + +
          {{author}}
          + +
          {{#markdown}}{{body}}{{/markdown}}
          + +
          +
          + +
          diff --git a/app/templates/wires/items/reply-tpl.js.mustache b/app/templates/wires/items/reply-tpl.js.mustache new file mode 100644 index 000000000..2c3747c34 --- /dev/null +++ b/app/templates/wires/items/reply-tpl.js.mustache @@ -0,0 +1,18 @@ + + +
          +
          + {{author}} +
          + {{#markdown}} + {{body}} + {{/markdown}} + {{time}} + {{#canEdit}}DELETE{{/canEdit}} +
          + +
          + +
          + +
          diff --git a/app/templates/wires/items/user-tpl.js.mustache b/app/templates/wires/items/user-tpl.js.mustache new file mode 100644 index 000000000..b44662621 --- /dev/null +++ b/app/templates/wires/items/user-tpl.js.mustache @@ -0,0 +1,7 @@ + + +
          {{first_name}} {{last_name}}
          + + + +
          diff --git a/app/templates/wires/wire.js.mustache b/app/templates/wires/wire.js.mustache new file mode 100644 index 000000000..74a21dc0e --- /dev/null +++ b/app/templates/wires/wire.js.mustache @@ -0,0 +1,10 @@ +{{#isEmpty}} +

          {{emptyMessage}}

          +{{/isEmpty}} +{{^isEmpty}} +
            +
          +{{/isEmpty}} +{{^allFetched}} + More +{{/allFetched}} \ No newline at end of file diff --git a/app/text/college.js.i18n b/app/text/college.js.i18n new file mode 100644 index 000000000..8e7a7b75a --- /dev/null +++ b/app/text/college.js.i18n @@ -0,0 +1,97 @@ +//# todo: can we have this fallback on english translations? + +{ + "main_page/post-box": { + "create-neighborhood-post.h1": "Say something to your fellow students", + "create-event.h1": "Post an event to your campus", + "create-group-post.h1": "Post to a Discussion Group", + "create-neighborhood-post.a": "Post to Your
          Hall", + "create-announcement.a": "Post to Your
          Feed", + "create-event.a": "Post an
          Event", + "create-group-post.a": "Discussion
          Groups" + }, + + "main_page/post-form": { + "publicity.label": "Is this publicity relating to a organization or event?", + "publicity.warning": "Your post will be sorted into the 'campus announcements' board." + }, + + "main_page/community-resources": { + "posts.a": "Hall
          Posts", + "events.a": "Campus
          Events", + "announcements.a": "Campus
          Announcements", + "groupPosts.a": "Discussion
          Posts", + "users.a": "Directory" + }, + + + "wire/header":{ + "posts": "Neighborhood Posts", + "events": "Community Events", + "announcements": "Announcements", + "groupPosts": "Group Posts" + }, + + "main_page/feed-resources": { + "users.a": "Your Fellow Students", + "feeds.a": "Campus Feeds", + "groups.a": "Discussion Groups" + }, + + "main_page/user-resources": { + "users.a": "Your Fellow Students", + "feeds.a": "Campus Feeds", + "groups.a": "Discussion Groups" + }, + + "main_page/group-resources": { + "users.a": "Your Fellow Students", + "feeds.a": "Campus Feeds", + "groups.a": "Discussion Groups" + }, + + "main_page/landing-resources": { + "posts.a": "Recent Hall Posts", + "events.a": "Upcoming Events", + "announcements.a": "Recent Campus Announcements", + "groupPosts.a": "Recent Discussion Group Posts" + }, + + "main_page/info-box": { + "users.h2": "Learn about your fellow students", + "account.h2": "Learn about your fellow students", + "groups.h2": "Learn about your campus", + "feeds.h2": "Learn about your campus" + }, + + "main_page/tour/post": { + "p1": "Here's where you go whenever you want to share with the campus community.", + + "ex1": "\"Post to your hall\" to share requests, goods, recommendations, questions, updates, etc. with your fellow students, like \"I need to borrow a stapler,\" \"I lost phone, has anyone seen it?\" or \"I made too many brownies and there's extra's free for the taking in the common room.\"", + + "ex2": "\"Publicize an announcement\" to advertise things to the whole campus, like \"Upcoming volunteer drive\" or \"Free t-shirts being given out by my club this week.\"", + + "ex3": "\"Post an Event\" to announce an upcoming event to the campus", + + "ex4": "\"Post to a Discussion Group\" to share specific information with those interested on campus -- sports, environment, dorm life, academic, and more", + + "p2": "Get a feel for how it works by saying \"Hi!\" to your fellow dormmates." + }, + "main_page/tour/wire": { + "p1": "Here's the CommonPlace \"Campus Wire.\" Here you'll find needs, events and announcements written by your fellow students and local organizations on campus." + }, + "main_page/tour/profile": { + "p1": "This is your \"campus profile.\" Your fellow students will see this profile when you post to the site. You can always add to it by clicking \"edit\" in the top right-hand corner." + }, + + + "main_page/tour/welcome": { + "use1": "Share needs, announcements and events with campus", + "use2": "Meet your fellow students and find great campus organizations", + "use3": "Discover ways to get involved with the campus community" + }, + + "main_page/tour/feed": { + "p1": "Are you the leader of a community organization, business or municipal entity? Register a community feed so that you send out announcements and events as your organization. It is a great way for neighbors to stay in touch with your organization: those who subscribe to your community feed get emailed every time you send out an update!" + } +} diff --git a/app/text/college/accounts.yml b/app/text/college/accounts.yml new file mode 100644 index 000000000..00a01fe44 --- /dev/null +++ b/app/text/college/accounts.yml @@ -0,0 +1,68 @@ +college: + accounts: + + facebook_wall_post: + wall_post_content: I just joined CommonPlace so I can connect with my neighbors. You should sign up too! + + facebook_invite: + heading: Make CommonPlace Better! + heading2: Improve CommonPlace by inviting fellow students you know. + + emails_label: "Invite fellow students to join CommonPlace!" + neighbors_name_label: "Neighbor's Name" + email_label: "Email address" + tool_tip: "Find your fellow students on Facebook and invite them to join you on CommonPlace!" + + step_one: "1. Invite neighbors by Facebook" + step_one_label: "Find your neighbors on Facebook and invite them to join you on CommonPlace!" + step_two: "2. Share on Facebook and Twitter" + step_two_label: "Let your friends and followers know you are on CommonPlace." + step_three: "3. Invite fellow students by email" + step_three_label: "Invite your fellow students by entering their email addresses and a short message below." + step_four: "4. Invite neighbors face-to-face" + step_four_label: "Invite neighbors the old fashioned way by handing out flyers and reading materials." + step_five: "5. Join the Friends of The %{community} CommonPlace Network" + step_five_label: "Click below to join a group of %{community} neighbors committed to improving our new community-building utility." + download_tag: "DOWNLOAD PDF" + file_descriptions: + - "CommonPlace Launch Letter" + - "Information Sheet" + - "Neighborhood Flyer" + file_names: + - "launchletter" + - "infosheet" + - "neighborflyer" + file_zip: "archives" + twitter_message: "Hey neighbors! If you live in %{community}, check out %{community}\'s CommonPlace, a new campus bulletin board:" + + facebook: + name: "Register for The %{community} CommonPlace" + caption: "The %{community} CommonPlace is a new online community bulletin board for students at %{community}." + description: "Join your neighbors at: www.%{community}.OurCommonPlace.com." + message: "I joined The %{community} CommonPlace, a new online community bulletin board for students at %{community}. You should join too at: www.%{slug}.OurCommonPlace.com." + + invitation: "I joined The %{community} CommonPlace, a new online community bulletin board for students at %{community}. You should join too at: www.%{slug}.OurCommonPlace.com." + edit: + title: Change Settings + receive_options: + live: "Receive all neighborhood posts by email" + three: "Receive three neighborhood posts by email per day" + daily: "Only receive neigborhood posts in the daily bulletin" + never: "Receive no neighborhood posts" + + learn_more: + title_html: Join CommonPlace to receive important campus announcements
          from your fellow students and local organizations at %{community}. %{sign_up} + sign_up: Sign up. + p1: CommonPlace brings your fellow students and campus leaders at %{community} together into one "community network" online. + p2: Sign up once and you'll start receiving short email messages from your fellow students and campus organization leaders. + p3: Whenever something's interesting to you, you can hit "reply" and start a community conversation. + p4: All these community conversations, events and announcements can always be viewed at %{link}. + p5: And of course, whenever YOU have a need or announcement, CommonPlace makes it really easy to share it with the people around you. + p6: To post, simply go to: %{community_link} + p7: Or email it in at: %{community_email} + list: + - Ask to borrow a textbook or power drill + - Publicize a club event or campus-wide party + - Find people and organizations with shared interests or hobbies around you + - Ask about how to fix something + - Organize a service project for %{community} diff --git a/app/text/college/announcements.yml b/app/text/college/announcements.yml new file mode 100644 index 000000000..09f16ccdc --- /dev/null +++ b/app/text/college/announcements.yml @@ -0,0 +1,11 @@ +college: + announcements: + new: + title: Send an announcement to the whole campus + link_html: Publicize an Announcement + + edit: + title: Edit your announcement + + index: + title: Send an announcement to your whole campus diff --git a/app/text/college/communities.yml b/app/text/college/communities.yml new file mode 100644 index 000000000..cb14ae2e5 --- /dev/null +++ b/app/text/college/communities.yml @@ -0,0 +1,80 @@ +college: + communities: + say_something: + title: "Say something to your hall:" + community_profiles: + title: "View campus profiles:" + whats_happening: + title: "Find out what's happening in town:" + good_neighbor_discount: + htitle: "The CommonPlace" + title: "Good Neighbor Discount:" + card_msg: "Write this on your card:" + business_msg: "Use at these participating businesses:" + 1li1: "Nielsen's Frozen Custard" + 1li2: "The Pure Pasty Co." + 1li3: "Caffe Amouri" + 1li4: "Plush" + 2li1: "Culinaria Cooking School" + 2li2: "Baskets-n-Bags" + 2li3: "Purple Onion Catering" + 2li4: "Norm's Beer & Wine" + foot_msg: "%{community} businesses are offering members of CommonPlace a \"Good Neighbor Discount.\" Ask at the register for details." + + faq: + h1: Got questions? We've got answers. + h2section: Frequently Asked Questions + p1: Click on the questions below to see the answer, if you don't see your question submit it in the form below + q1_1_html: What is CommonPlace? + a1_1_html: CommonPlace is a web platform for local community engagement. It is designed to make it easier for you to connect and share information with your neighbors and local leaders. + q1_2_html: What can I use CommonPlace for? + a1_2_html: CommonPlace is like an online bulletin board for %{community} that allows you to share events, needs, offers, questions and announcements with the people who live right around you. You can use %{community}'s CommonPlace to: notify your neighbors about a lost cat, or to ask to borrow a ladder; use it to keep up with local events happening in town, or to publicize your own; use it to get announcements from the city government and local organizations, or to start up you own group in the community. + q1_3_html: What happens if I sign up for %{community}'s CommonPlace? + a1_3_html: If you sign up, you can start receiving important community announcements from your neighbors and local leaders here in %{community}. You will easily stay in touch with what's happening in %{community}, as well be able to get the word out to everyone in town about any event, need, offer, question or announcement that you want to share with your neighbors. + q1_4_html: Who is behind CommonPlace? + a1_4_1_html: The lead CommonPlace organizer for %{community} is %{organizer}, who is helping to make the %{community} community more civic and more connected by organizing neighbors onto the %{community} CommonPlace platform. If you have any questions or concerns about the platform, contact %{organizer_firstname} at %{organizer_email} + a1_4_2_html: CommonPlace America - the umbrella organization for CommonPlaces in towns and cities across the country - was founded in 2009 by Harvard students Peter Davis and Max Novendstern. They began the CommonPlace project with the question: how can the internet be utilized to build up civic life in America? For more information on the national CommonPlace movement, check out %{starter_site}. + q1_5_html: Why was CommonPlace created? + a1_5_html: CommonPlace was created with the mission of using the internet to help revitalize local community in America. We believe that web tools need not only connect us to 'friends' around the world - rather they can be used to connect us to the people who live right around us. We aim to create a new standard for community information-sharing infrastructure that make neighborhoods better places to live in: more connected, more civic and more empowered. + q2_1_html: What's the difference between a neighborhood post and an announcement? + a2_1_1_html: A neighborhood post is meant to be a neighborly post from an individual to their neighbors, like requests ("Can we borrow a ladder?"), offers ("I'm giving away maternity clothes"), questions ("Anyone know a good electrician?"), and personal, non-commercial updates ("We just had a baby!"). + a2_1_2_html: A community announcement is the place for publicity to the whole town and commercial announcements. If you want to announce that you do tax preparation or are an SAT tutor, post it as a community announcement. If you are seeking donations for your cause or volunteers for your fair, post it as a community announcement. If you are an organization, city service or business that will posting many community announcements, consider starting a "Community Feed" so that you can post as the organization or business, and so that people can subscribe to your announcements. + a2_1_3_html: In short, neighborhood posts are for neighbor-to-neighbor requests, offers, questions and updates while community announcements are for town-wide publicity and commercial use. + q2_2_html: What's a Community Feed? + a2_2_html: Any organization, business, city service or cause can create a "Community Feed" on CommonPlace. This allows you to send out announcements and events to the community from your organization. For example, if you are the President of the PTA, you could create a PTA "Community Feed" and then send out announcements and events which would show up as coming from the PTA, as opposed to from an individual. In addition, people can subscribe to your community feed and thus receive your announcements and events by email. + q2_3_html: What is my 'neighborhood' on CommonPlace? + a2_3_html: Each town on CommonPlace is split into neighborhoods of roughly 700 residences each. When you write a neighborhood post, it is emailed immediately to those who are in your neighborhood. Everyone else in town will receive that post at the end of the day in a daily digest, under "Posts Around Town." For specific information about your neighborhood's borders, email your community CommonPlace organizer at %{organizer_email}. + q2_4_html: How do email notifications work on CommonPlace? + a2_4_1_html: Everything on CommonPlace - events, announcements and neighborhood posts - can be sent out in live emails (as they are posted), compiled into daily digest and/or compiled into a weekly bulletin. + a2_4_2_html: The community weekly bulletin is a compilation of all announcements posted to CommonPlace that week as well as the next two weeks of upcoming events. + a2_4_3_html: The daily digest is an email tailored directly to you, that includes posts from around town, as well as announcements and events from the community feeds to which you have subscribed. + a2_4_4_html: All posts from your neighborhood are, by default, emailed to you live. You can also select to receive announcements and events from certain community feeds immediately as they are posted. + a2_4_5_html: If you ever want to change your email notification settings, feel free to go to the %{email_notification_settings_page}. + q2_5_html: How can CommonPlace help me publicize things around town? + a2_5_html: If you post announcements and events to CommonPlace, they not only are immediately posted to CommonPlace for everyone in town to see - they are also emailed to everyone every week in the community weekly bulletin. If you have an organization, city service, business or cause in %{community}, consider starting a Community Feed, which will allow you to send out announcements and events from your feed name. + q2_6_html: What about privacy? + a2_6_1_html: CommonPlace is a closed network, meaning nothing you ever post or share on CommonPlace can be Googled-- one has to register and log in to see the information. + a2_6_2_html: CommonPlace is all about sharing public, community information -- only share on CommonPlace what you would want your neighbors to see. + q2_7_html: Why do I have to input my full name when I register? + a2_7_html: When researching internet platforms while building CommonPlace, we noted that internet anonymity quickly leads to vandalism and unproductive web communication. By associating posts with names, it ensures healthy, productive and neighborly interaction on CommonPlace. Plus, we have already had cases of people meeting in person and saying, "Oh, wow, you must be Janet - I saw your post on CommonPlace! Great to meet you!" + q2_8_html: Why do I have to input my street address when I register? + a2_8_html: This allows us to place you into the right neighborhood and to ensure you live in the correct CommonPlace town. + q2_9_html: My friend in another town wants to use CommonPlace - can she join ours? + a2_9_1_html: Anyone can sign up to see the public events and community announcements posted to %{community}'s CommonPlace. However, you have to have a street address inside the borders of the town to see neighborhood posts. + a2_9_2_html: If your friend wants to bring CommonPlace to her town, tell her to fill out our "Nominate Your Town to have a CommonPlace" form at %{starter_site}. + q3_1_html: How do I invite one of my neighbors to %{community}'s CommonPlace? + a3_1_html: Fill out our invite form %{invite_link} - thanks for being a good neighbor! + q3_2_html: How do I post to CommonPlace? + a3_2_html: Everything involving posting to CommonPlace is in the red box in the upper left hand corner of the mainpage, after you log in. + q3_3_html: How do I create a community feed? + a3_3_html: Create a community feed %{create_feed}. + q3_4_html: How do I retrieve my lost password? + a3_4_html: Click %{lost_password} to receive your lost password. + q3_5_html: How do I report a bug? + a3_5_html: Fill out the form below to report a bug. Thanks for being on the look out! + q3_6_html: How do I help out with the CommonPlace movement in %{community}? + a3_6_html: We are always looking for volunteers who believe in our mission of bringing local communities closer together. If you're interested, email %{community}'s lead CommonPlace organizer, %{organizer_name}, at %{organizer_email}. + q3_7_html: How do I bring CommonPlace to a new town? + a3_7_html: Fill out our "Nominate Your Town to have a CommonPlace" %{new_community} at %{commonplace_usa}. + + diff --git a/app/text/college/default_groups.yml b/app/text/college/default_groups.yml new file mode 100644 index 000000000..b1d7fd3db --- /dev/null +++ b/app/text/college/default_groups.yml @@ -0,0 +1,32 @@ +college: + default_groups: + - name: Volunteer Opportunities + about: Sign up if you are interested in hearing about how you can help out around %{community_name}. + avatar: "/images/group-icons/volunteer-opportunities.png" + slug: Volunteering + + - name: Music & The Arts + about: Share and receive announcements and events about upcoming concerts, art shows and creative get-togethers and arts-related happenings around %{community_name}. + avatar: "/images/group-icons/music-arts.png" + slug: MusicAndArts + + - name: Campus Problem Solving and Ideas + about: "Have a big idea about how to help make %{community_name} better? Join up and get the discussion going. " + avatar: "/images/group-icons/city-problem-solving.png" + slug: CampusProblemSolving + + - name: Social Opportunities + about: Subscribe to stay in the loop about get-togethers with others at %{community_name}. + avatar: "/images/group-icons/social-opportunities.png" + slug: SocialOpportunities + + - name: Academic Life + about: Talk about courses and requirements. Form study groups and share textbooks. + avatar: "/images/group-icons/academic-life.png" + slug: AcademicLife + + - name: Sports & Recreation + about: Stay in the loop about sports, IMs, health, wellness and recreational activities at %{community_name}. + avatar: "/images/group-icons/sports-recreation.png" + slug: SportsAndRec + diff --git a/app/text/college/events.yml b/app/text/college/events.yml new file mode 100644 index 000000000..c54e03b67 --- /dev/null +++ b/app/text/college/events.yml @@ -0,0 +1,10 @@ +college: + events: + new: + link_html: Post an
          Event + title: Post an event to campus + edit: + title: Edit your Event + + index: + title: Post an event to campus diff --git a/app/text/college/feeds.yml b/app/text/college/feeds.yml new file mode 100644 index 000000000..f68bd9a7e --- /dev/null +++ b/app/text/college/feeds.yml @@ -0,0 +1,42 @@ +college: + feeds: + register: "Register a Campus Feed" + account: + h1: Welcome! Feeds make it easy for you to send events and announcement to the people who live at Mary Washington. But before you can create a feed, you need to register a personal profile on CommonPlace. + + invites: + new: + h1: Finally, invite some people to receive your announcements and events. + h2: Use CommonPlace to send your announcements and events to everyone on the %{community} CommonPlace network. The more email addresses you add, the more widely your announcements and events (and everyone else's!) will be sent. Invite everyone who your organization cares about. + emails_label: "To invite people to join CommonPlace and subscribe to your Feed, type in email addresses, separated by commas:" + new: + link: Create a Campus Feed + + h1: Create a campus feed so that your organization can post events and announcements to everyone on CommonPlace. + edit: + h1: Edit Your Feed + + profile: + contact_info: + title: Contact Information + recent_posts: + title: Recent Posts + about: + title: About Us + members: + title: Subscribers (%{count}) + post: + title: Post an: + post_tabs: + - Post Announcement + - Post Event + - Import Feed + preview: + name: "Enter your feed's name" + about: "Go ahead and describe your feed. Don't be afraid to use some detail. There's space." + tag_list: "music, politics, food" + website: "http://www.YourWebsite.com" + hours: "9:00AM - 1:00PM" + phone: "867-5309" + address: "0 Bleecker Street" + diff --git a/app/text/college/formtastic.yml b/app/text/college/formtastic.yml new file mode 100644 index 000000000..3324e0730 --- /dev/null +++ b/app/text/college/formtastic.yml @@ -0,0 +1,63 @@ +college: + formtastic: + titles: + contact_information: "Contact Information:" + + labels: + user: + full_name: "Full Name:" + email: "Email Address:" + password: "Create a Password*:" + about: "Tell your fellow students a bit about yourself:" + interest_list: "Add some interests:" + offer_list: "Do you have any goods and skills you can share with your hallmates?" + referral_source: "How did you find out about CommonPlace?" + address: "Street Address*:" + avatar: "Add a photo:" + edit: + password: "Change your password:" + receive_posts: Get new hall posts in emails + receive_events_and_announcements: Get new announcements and events in emails + receive_weekly_digest: Get a daily bulletin + post_receive_method: "Hall Posts:" + invites: + emails: Add comma-separated emails to invite other students at %{community} to CommonPlace + message: Optional message + + user_session: + email: "Enter your email:" + password: "Enter your password:" + + feed: + name: "Feed Name:" + slug: "CommonPlace Address:" + avatar: "Add a photo of your organization:" + is_news: "This is a Newspaper or News Service" + about: "Describe your feed:" + phone: "Phone Number:" + address: "Street Address:" + kind: "Is your community feed for:" + website: "Website:" + feed_url: "Enter an RSS feed and we'll send out announcements when you update it:" + twitter_name: "Enter your Twitter username and we'll syndicate your tweets on CommonPlace" + show: + phone: "Phone:" + website: "Website:" + address: "Address:" + hours: "Hours:" + announcement: + feed: "Send announcement from:" + + post: + subject: Title + body: Body + post_to_facebook: "Post to your facebook wall as well?" + event: + name: "Event name:" + description: "Event description:" + + hints: + user: + address: "*This verifies that you live in %{community}. We will not share this information with anyone." + feed: + slug: "You will be able post announcements to your feeds by emailing {CommonPlace Address}@ourcommonplace.com" diff --git a/app/text/college/group_posts.yml b/app/text/college/group_posts.yml new file mode 100644 index 000000000..afdb10f2c --- /dev/null +++ b/app/text/college/group_posts.yml @@ -0,0 +1,6 @@ +college: + + group_posts: + new: + link_html: Post to a Discussion Group + title: Post to a Discussion Group diff --git a/app/text/college/invites.yml b/app/text/college/invites.yml new file mode 100644 index 000000000..a0d671861 --- /dev/null +++ b/app/text/college/invites.yml @@ -0,0 +1,7 @@ +college: + invites: + new: + h1_html: Invite People on campus
          to %{community}'s CommonPlace + h2: The more people on campus that are on %{community}'s CommonPlace, the better a network it is for everyone! + message_label: Feel free to attach a personal message below to encourage people to sign up. You can share stories about how CommonPlace has helped you out. + emails_label: Type in email addresses, separated by commas. diff --git a/app/text/college/messages.yml b/app/text/college/messages.yml new file mode 100644 index 000000000..faa2b4536 --- /dev/null +++ b/app/text/college/messages.yml @@ -0,0 +1,7 @@ +en: + messages: + index: + h2: Your Inbox + show: + to_index: "Back to inbox" + archive: "Archive" diff --git a/app/text/college/posts.yml b/app/text/college/posts.yml new file mode 100644 index 000000000..34e9c3c88 --- /dev/null +++ b/app/text/college/posts.yml @@ -0,0 +1,6 @@ +college: + + posts: + new: + title: Say something to your hallmates + link_html: Post to Your Hall diff --git a/app/text/college/registrations.yml b/app/text/college/registrations.yml new file mode 100644 index 000000000..c7389d735 --- /dev/null +++ b/app/text/college/registrations.yml @@ -0,0 +1,34 @@ +college: + registrations: + new: + h1: Plug Into the %{community} Community + li1: CommonPlace is a web platform built for %{community} students. It's like an online bulletin board for the %{community} community. + li2: Sign up to send and receive campus announcements written by your fellow students and organization leaders here at %{community}. + li3: CommonPlace is a community-building project. Join and help make the %{community} community even better. + + li1fb: You are now connected to CommonPlace through Facebook. Please verify that you live in %{community} by providing your residence hall. + + li2fb: Your information will not be shared with anyone. It is only used to verify that you live in %{community}. + + profile: + h1: Fill out your campus profile + p2: CommonPlace makes it easy for you to share needs, announcements and events with your fellow students at %{community}. + p3: Use CommonPlace whenever you want to get local recommendations, borrow goods from your neighbors, or plan the next big campus initiative. + p4: Before you start, take a moment to fill out your campus profile. + password_hint: "*Required field." + + avatar: + h1: Please crop your avatar + + feeds: + h1: Stay in the Loop! + h2: "Subscribe to some campus feeds:" + p1: Organization leaders use CommonPlace to share events and announcements with campus. + p2: Subscribe to some campus feeds in order to get notified whenever they send out updates. + + groups: + h1: Get Connected! + p1: CommonPlace Groups help you connect with people on campus who share similar interests. + p2: Subscribe to groups to send and receive information about specific community topics that you care about. + p3: Feel free to join as many as you want! + diff --git a/app/text/college/subscription.yml b/app/text/college/subscription.yml new file mode 100644 index 000000000..b81d14642 --- /dev/null +++ b/app/text/college/subscription.yml @@ -0,0 +1,3 @@ +college: + subscription: + "Neighborhood_Posts": "N" diff --git a/app/text/college/syndicate.yml b/app/text/college/syndicate.yml new file mode 100644 index 000000000..155f3c31f --- /dev/null +++ b/app/text/college/syndicate.yml @@ -0,0 +1,27 @@ +college: + syndicate: + events: + title: Upcoming Events + link_html: Campus
          Events + posts: + title: Recent Hall Posts + link_html: Hall
          Posts + announcements: + title: Recent Campus Announcements + link_html: Campus
          Announcements + people: + title: Your Fellow Students + users: + link_html: Directory + groups: + title: Discussion Groups + feeds: + title: Campus Feeds + link_html: Campus
          Feeds + group_posts: + link_html: Group
          Posts + title: Discussion Group Posts + messages: + title: Your Inbox + + diff --git a/app/text/college/tooltips.yml b/app/text/college/tooltips.yml new file mode 100644 index 000000000..622fa5316 --- /dev/null +++ b/app/text/college/tooltips.yml @@ -0,0 +1,8 @@ +college: + tooltips: + posts: View announcements, offers, and needs from your fellow students + events: View upcoming events on campus + announcements: View community announcements from organizations in your area + people: Find fellow swtudents and group leaders in your community + feeds: View Campus Announcement Feeds + goods_and_skills: View Campus Directory diff --git a/app/text/college/user.yml b/app/text/college/user.yml new file mode 100644 index 000000000..461a794bf --- /dev/null +++ b/app/text/college/user.yml @@ -0,0 +1,9 @@ +college: + activerecord: + errors: + models: + user: + attributes: + email: + invalid: "Please enter your college e-mail address" + address: "The address you entered is outside of %{community}. If this is not the case, please email Pete@CommonPlaceUSA.com." diff --git a/app/text/college/users.yml b/app/text/college/users.yml new file mode 100644 index 000000000..f06ab516f --- /dev/null +++ b/app/text/college/users.yml @@ -0,0 +1,16 @@ +college: + users: + preview: + about: "Go ahead and describe yourself to your fellow students" + interest_list: "Reading, Community Engagement, Bicycling" + offer_list: "Lawnmower, Bikes, Word Processing" + + + activerecord: + errors: + models: + user: + attributes: + password: + blank: A password is required + diff --git a/app/text/en.js.i18n b/app/text/en.js.i18n new file mode 100644 index 000000000..afc7ed14e --- /dev/null +++ b/app/text/en.js.i18n @@ -0,0 +1,76 @@ +{ + "main_page/post-box": { + "create-neighborhood-post.h1": "Say something to your neighbors", + "create-event.h1": "Post an event to your community", + "create-group-post.h1": "Post to a Discussion Group", + "create-neighborhood-post.a": "Post to Your
          Neighborhood", + "create-announcement.a": "Post to Your
          Feed", + "create-event.a": "Post an
          Event", + "create-group-post.a": "Discussion
          Groups" + }, + + "main_page/post-form": { + "publicity.label": "Is this publicity relating to a business, organization, or event?", + "publicity.warning": "Your post will be sorted into the 'community announcements' board." + }, + + "main_page/community-resources": { + "posts.a": "Neighborhood
          Posts", + "events.a": "Community
          Events", + "announcements.a": "Community
          Announcements", + "groupPosts.a": "Group
          Posts", + "users.a": "Directory" + }, + + "wire.header":{ + "posts": "Neighborhood Posts", + "events": "Community Events", + "announcements": "Announcements", + "groupPosts": "Group Posts" + }, + + "main_page/directory-resources": { + "users.a": "Neighbors", + "feeds.a": "Feeds", + "groups.a": "Groups" + }, + + "main_page/info-box": { + "users.h2": "Learn about your neighbors", + "account.h2": "Learn about your neighbors", + "groups.h2": "Learn about your community", + "feeds.h2": "Learn about your community" + }, + + "main_page/tour/post": { + "p1": "Here's where you go whenever you want to share with the community.", + + "ex1": "\"Post to your neighborhood\" to share requests, goods, recommendations, questions, updates, etc. with your neighbors, like \"I need to borrow a ladder,\" \"I lost my cat,\" or \"Anybody know a good babysitter?\"", + + "ex2": "\"Publicize an announcement\" to advertise things to the whole community, like \"upcoming volunteer drive\" or \"Free tacos this Thursday at our store.\"", + + "ex3": "\"Post an Event\" to announce an upcoming event to the community", + + "ex4": "\"Post to a Discussion Group\" to share specific information with interest groups in your town -- dog lovers, moms, sports, and more", + + "p2": "Get a feel for how it works by saying \"Hi!\" to your neighbors." + }, + + "main_page/tour/wire": { + "p1": "Here's the CommonPlace \"Community Wire.\" Here you'll find needs, events and announcements written by your neighbors and local organizations in town." + }, + + "main_page/tour/profile": { + "p1": "This is your \"civic profile.\" Your neighbors will see this profile when you post to the site. You can always add to it by clicking \"edit\" in the top right-hand corner." + }, + + "main_page/tour/welcome": { + "use1": "Share needs, announcements and events with the town", + "use2": "Meet your neighbors and find great local organizations", + "use3": "Discover ways to get involved in the community" + }, + + "main_page/tour/feed": { + "p1": "Are you the leader of a community organization, business or municipal entity? Register a community feed so that you send out announcements and events as your organization. It is a great way for neighbors to stay in touch with your organization: those who subscribe to your community feed get emailed every time you send out an update!" + } +} diff --git a/app/text/en/accounts.yml b/app/text/en/accounts.yml new file mode 100644 index 000000000..b4404bfca --- /dev/null +++ b/app/text/en/accounts.yml @@ -0,0 +1,65 @@ +en: + accounts: + + edit: + title: Change Settings + receive_options: + live: "Receive all neighborhood posts by email" + three: "Receive three neighborhood posts by email per day" + daily: "Only receive neigborhood posts in the daily bulletin" + never: "Receive no neighborhood posts" + + learn_more: + signup1: "CommonPlace brings your neighbors and civic leaders in %{community} together onto one 'community network' online." + signup2: "Sign up once and you'll start receiving short email messages from your neighbors and community leaders like the police chief and the mayor." + bulletins1: "Each day you'll receive a 'community bulletin' featuring events and announcements written by your neighbors and local leaders. Learn about local events. Find a good mechanic. Join a new biking group." + bulletins2: "Whenever something's interesting to you, you can hit 'reply' and start a community conversation." + search1_html: "All these community conversations, events, and announcements can always be views at %{community}.OurCommonPlace.com." + search2: "CommonPlace centralizes community information. Use it to search for people in your area, find cool local organizations, or locate goods and skills in your neighborhood." + post1: "Of course, whenever YOU have a need or announcement, CommonPlace makes it really easy to share it with the people around you." + post2: "Ask to borrow a ladder or a power drill" + post3: "Publicize a tag sale or block party" + post4: "Ask about how to fix a pot hole" + post5: "Organize service projects for %{community}" + + facebook_wall_post: + wall_post_content: I just joined CommonPlace so I can connect with my neighbors. You should sign up too! + + facebook_invite: + heading: Make CommonPlace Better! + heading2: Improve CommonPlace by inviting people you know. + + emails_label: "Invite people to join CommonPlace!" + neighbors_name_label: "Neighbor's Name" + email_label: "Email address" + tool_tip: "Find your neighbors on Facebook and invite them to join you on CommonPlace!" + + step_one: "1. Invite neighbors by Facebook" + step_one_label: "Find your neighbors on Facebook and invite them to join you on CommonPlace!" + step_two: "2. Share on Facebook and Twitter" + step_two_label: "Let your friends and followers know you are on CommonPlace." + step_three: "3. Invite neighbors by email" + step_three_label: "Invite your neighbors by adding their email addresses and writing a short message." + step_four: "4. Invite neighbors face-to-face" + step_four_label: "Invite neighbors the old fashioned way by handing out flyers and reading materials." + step_five: "5. Join the Friends of The %{community} CommonPlace Network" + step_five_label: "Click below to join a group of %{community} neighbors committed to improving our new community-building utility." + download_tag: "DOWNLOAD PDF" + file_descriptions: + - "CommonPlace Launch Letter" + - "Information Sheet" + - "Neighborhood Flyer" + file_names: + - "launchletter" + - "infosheet" + - "neighborflyer" + file_zip: "archives" + twitter_message: "Hey neighbors! If you live in %{community}, check out %{community}\'s CommonPlace, a new local community bulletin board:" + + facebook: + name: "Register for The %{community} CommonPlace" + caption: "The %{community} CommonPlace is a new online community bulletin board for neighbors in %{community}." + description: "Join your neighbors at: www.%{community}.OurCommonPlace.com." + message: "I joined The %{community} CommonPlace, a new online community bulletin board for neighbors in %{community}. You should join too at: www.%{slug}.OurCommonPlace.com." + + invitation: "I joined The %{community} CommonPlace, a new online community bulletin board for neighbors in %{community}. You should join too at: www.%{slug}.OurCommonPlace.com." diff --git a/app/text/en/announcements.yml b/app/text/en/announcements.yml new file mode 100644 index 000000000..9b46d3125 --- /dev/null +++ b/app/text/en/announcements.yml @@ -0,0 +1,11 @@ +en: + announcements: + new: + title: Send an announcement to your whole community + link_html: Publicize an Announcement + + edit: + title: Edit your announcement + + index: + title: Send an announcement to your whole community \ No newline at end of file diff --git a/app/text/en/communities.yml b/app/text/en/communities.yml new file mode 100644 index 000000000..42a7cf644 --- /dev/null +++ b/app/text/en/communities.yml @@ -0,0 +1,80 @@ +en: + communities: + say_something: + title: "Say something to your neighbors:" + community_profiles: + title: "View community profiles:" + whats_happening: + title: "Find out what's happening in town:" + good_neighbor_discount: + htitle: "The CommonPlace" + title: "Good Neighbor Discount:" + card_msg: "Write this on your card:" + business_msg: "Use at these participating businesses:" + 1li1: "Nielsen's Frozen Custard" + 1li2: "The Pure Pasty Co." + 1li3: "Caffe Amouri" + 1li4: "Plush" + 2li1: "Culinaria Cooking School" + 2li2: "Baskets-n-Bags" + 2li3: "Purple Onion Catering" + 2li4: "Norm's Beer & Wine" + foot_msg: "%{community} businesses are offering members of CommonPlace a \"Good Neighbor Discount.\" Ask at the register for details." + + faq: + h1: Got questions? We've got answers. + h2section: Frequently Asked Questions + p1: Click on the questions below to see the answer, if you don't see your question submit it in the form below + q1_1_html: What is CommonPlace? + a1_1_html: CommonPlace is a web platform for local community engagement. It is designed to make it easier for you to connect and share information with your neighbors and local leaders. + q1_2_html: What can I use CommonPlace for? + a1_2_html: CommonPlace is like an online bulletin board for %{community} that allows you to share events, needs, offers, questions and announcements with the people who live right around you. You can use %{community}'s CommonPlace to: notify your neighbors about a lost cat, or to ask to borrow a ladder; use it to keep up with local events happening in town, or to publicize your own; use it to get announcements from the city government and local organizations, or to start up you own group in the community. + q1_3_html: What happens if I sign up for %{community}'s CommonPlace? + a1_3_html: If you sign up, you can start receiving important community announcements from your neighbors and local leaders here in %{community}. You will easily stay in touch with what's happening in %{community}, as well be able to get the word out to everyone in town about any event, need, offer, question or announcement that you want to share with your neighbors. + q1_4_html: Who is behind CommonPlace? + a1_4_1_html: The lead CommonPlace organizer for %{community} is %{organizer}, who is helping to make the %{community} community more civic and more connected by organizing neighbors onto the %{community} CommonPlace platform. If you have any questions or concerns about the platform, contact %{organizer_firstname} at %{organizer_email} + a1_4_2_html: CommonPlace America - the umbrella organization for CommonPlaces in towns and cities across the country - was founded in 2009 by Harvard students Peter Davis and Max Novendstern. They began the CommonPlace project with the question: how can the internet be utilized to build up civic life in America? For more information on the national CommonPlace movement, check out %{starter_site}. + q1_5_html: Why was CommonPlace created? + a1_5_html: CommonPlace was created with the mission of using the internet to help revitalize local community in America. We believe that web tools need not only connect us to 'friends' around the world - rather they can be used to connect us to the people who live right around us. We aim to create a new standard for community information-sharing infrastructure that make neighborhoods better places to live in: more connected, more civic and more empowered. + q2_1_html: What's the difference between a neighborhood post and an announcement? + a2_1_1_html: A neighborhood post is meant to be a neighborly post from an individual to their neighbors, like requests ("Can we borrow a ladder?"), offers ("I'm giving away maternity clothes"), questions ("Anyone know a good electrician?"), and personal, non-commercial updates ("We just had a baby!"). + a2_1_2_html: A community announcement is the place for publicity to the whole town and commercial announcements. If you want to announce that you do tax preparation or are an SAT tutor, post it as a community announcement. If you are seeking donations for your cause or volunteers for your fair, post it as a community announcement. If you are an organization, city service or business that will posting many community announcements, consider starting a "Community Feed" so that you can post as the organization or business, and so that people can subscribe to your announcements. + a2_1_3_html: In short, neighborhood posts are for neighbor-to-neighbor requests, offers, questions and updates while community announcements are for town-wide publicity and commercial use. + q2_2_html: What's a Community Feed? + a2_2_html: Any organization, business, city service or cause can create a "Community Feed" on CommonPlace. This allows you to send out announcements and events to the community from your organization. For example, if you are the President of the PTA, you could create a PTA "Community Feed" and then send out announcements and events which would show up as coming from the PTA, as opposed to from an individual. In addition, people can subscribe to your community feed and thus receive your announcements and events by email. + q2_3_html: What is my 'neighborhood' on CommonPlace? + a2_3_html: Each town on CommonPlace is split into neighborhoods of roughly 700 residences each. When you write a neighborhood post, it is emailed immediately to those who are in your neighborhood. Everyone else in town will receive that post at the end of the day in a daily digest, under "Posts Around Town." For specific information about your neighborhood's borders, email your community CommonPlace organizer at %{organizer_email}. + q2_4_html: How do email notifications work on CommonPlace? + a2_4_1_html: Everything on CommonPlace - events, announcements and neighborhood posts - can be sent out in live emails (as they are posted), compiled into daily digest and/or compiled into a weekly bulletin. + a2_4_2_html: The community weekly bulletin is a compilation of all announcements posted to CommonPlace that week as well as the next two weeks of upcoming events. + a2_4_3_html: The daily digest is an email tailored directly to you, that includes posts from around town, as well as announcements and events from the community feeds to which you have subscribed. + a2_4_4_html: All posts from your neighborhood are, by default, emailed to you live. You can also select to receive announcements and events from certain community feeds immediately as they are posted. + a2_4_5_html: If you ever want to change your email notification settings, feel free to go to the %{email_notification_settings_page}. + q2_5_html: How can CommonPlace help me publicize things around town? + a2_5_html: If you post announcements and events to CommonPlace, they not only are immediately posted to CommonPlace for everyone in town to see - they are also emailed to everyone every week in the community weekly bulletin. If you have an organization, city service, business or cause in %{community}, consider starting a Community Feed, which will allow you to send out announcements and events from your feed name. + q2_6_html: What about privacy? + a2_6_1_html: CommonPlace is a closed network, meaning nothing you ever post or share on CommonPlace can be Googled-- one has to register and log in to see the information. + a2_6_2_html: CommonPlace is all about sharing public, community information -- only share on CommonPlace what you would want your neighbors to see. + q2_7_html: Why do I have to input my full name when I register? + a2_7_html: When researching internet platforms while building CommonPlace, we noted that internet anonymity quickly leads to vandalism and unproductive web communication. By associating posts with names, it ensures healthy, productive and neighborly interaction on CommonPlace. Plus, we have already had cases of people meeting in person and saying, "Oh, wow, you must be Janet - I saw your post on CommonPlace! Great to meet you!" + q2_8_html: Why do I have to input my street address when I register? + a2_8_html: This allows us to place you into the right neighborhood and to ensure you live in the correct CommonPlace town. + q2_9_html: My friend in another town wants to use CommonPlace - can she join ours? + a2_9_1_html: Anyone can sign up to see the public events and community announcements posted to %{community}'s CommonPlace. However, you have to have a street address inside the borders of the town to see neighborhood posts. + a2_9_2_html: If your friend wants to bring CommonPlace to her town, tell her to fill out our "Nominate Your Town to have a CommonPlace" form at %{starter_site}. + q3_1_html: How do I invite one of my neighbors to %{community}'s CommonPlace? + a3_1_html: Fill out our invite form %{invite_link} - thanks for being a good neighbor! + q3_2_html: How do I post to CommonPlace? + a3_2_html: Everything involving posting to CommonPlace is in the red box in the upper left hand corner of the mainpage, after you log in. + q3_3_html: How do I create a community feed? + a3_3_html: Create a community feed %{create_feed}. + q3_4_html: How do I retrieve my lost password? + a3_4_html: Click %{lost_password} to receive your lost password. + q3_5_html: How do I report a bug? + a3_5_html: Fill out the form below to report a bug. Thanks for being on the look out! + q3_6_html: How do I help out with the CommonPlace movement in %{community}? + a3_6_html: We are always looking for volunteers who believe in our mission of bringing local communities closer together. If you're interested, email %{community}'s lead CommonPlace organizer, %{organizer_name}, at %{organizer_email}. + q3_7_html: How do I bring CommonPlace to a new town? + a3_7_html: Fill out our "Nominate Your Town to have a CommonPlace" %{new_community} at %{commonplace_usa}. + + diff --git a/app/text/en/default_groups.yml b/app/text/en/default_groups.yml new file mode 100644 index 000000000..efcf3120c --- /dev/null +++ b/app/text/en/default_groups.yml @@ -0,0 +1,56 @@ +en: + default_groups: + - name: Volunteer Opportunities + about: Sign up if you are interested in hearing about how you can help out around %{community_name}. + avatar: "/assets/group-icons/volunteer-opportunities.png" + slug: Volunteering + + - name: Social Opportunities + about: Subscribe to stay in the loop about get-togethers with others in %{community_name}. + avatar: "/assets/group-icons/social-opportunities.png" + slug: Social + + - name: Music & The Arts + about: Share and receive announcements and events about upcoming concerts, art shows and creative get-togethers and arts-related happenings around %{community_name}. + avatar: "/assets/group-icons/music-arts.png" + slug: MusicAndArts + + - name: Gardening and Home Improvement + about: "Have a question about tools or a tip about planting bulbs? Fancy yourself a %{community_name} Martha Stewart? " + avatar: "/assets/group-icons/gardening-home-improvement.png" + slug: HomeAndGarden + + - name: Food & Dining + about: Share recommendations and reviews about %{community_name} restaurants! + avatar: "/assets/group-icons/local-dining.png" + slug: Food + + - name: Dogs + about: Organize a pack walk, ask for vet recommendations, find a good dog sitter and much more. + avatar: "/assets/group-icons/dogs.png" + slug: Dogs + + - name: Parents & Families + about: Receive family-friendly event notifications, suggest babysitter recommendations, and discuss family life with other parents in %{community_name}. + avatar: "/assets/group-icons/parents-families.png" + slug: Parents + + - name: New Mothers + about: Find babysitters, organize nanny co-ops, plan playdates and share tips with other new moms in %{community_name}. + avatar: "/assets/group-icons/new-mothers.png" + slug: Moms + + - name: Sports & Recreation + about: Stay in the loop about sports, health, wellness, and recreational activities in %{community_name}. + avatar: "/assets/group-icons/sports-recreation.png" + slug: SportsAndRec + + - name: City Problem Solving and Ideas + about: Have a big idea about how to help make %{community_name} better. Join up and get the discussion going! + avatar: "/assets/group-icons/city-problem-solving.png" + slug: City + + - name: Environment + about: Get connected with other %{community_name} environmentalists to discuss environmental issues in %{community_name}. + avatar: "/assets/group-icons/environment.png" + slug: Environment diff --git a/app/text/en/events.yml b/app/text/en/events.yml new file mode 100644 index 000000000..6f120cecc --- /dev/null +++ b/app/text/en/events.yml @@ -0,0 +1,10 @@ +en: + events: + new: + link_html: Post an
          Event + title: Post an event to your community + edit: + title: Edit your Event + + index: + title: Post an event to your community \ No newline at end of file diff --git a/app/text/en/feeds.yml b/app/text/en/feeds.yml new file mode 100644 index 000000000..3c339de49 --- /dev/null +++ b/app/text/en/feeds.yml @@ -0,0 +1,42 @@ +en: + feeds: + register: "Register a Community Feed" + account: + h1: Welcome! Feeds make it easy for you to send events and announcement to the people who live in Falls Church. But before you can create a feed, you need to register a personal profile on CommonPlace. + + invites: + new: + h1: Finally, invite some people to receive your announcements and events. + h2: Use CommonPlace to send your announcements and events to everyone on the %{community} CommonPlace network. The more email addresses you add, the more widely your announcements and events (and everyone else's!) will be sent. Invite everyone who your organization cares about. + emails_label: "To invite people to join CommonPlace and subscribe to your Feed, type in email addresses, separated by commas:" + new: + link: Create a Community Feed + + h1: Create a community feed so that your organization can post events and announcements to everyone on CommonPlace. + edit: + h1: Edit Your Feed Avatar + + profile: + contact_info: + title: Contact Information + recent_posts: + title: Recent Posts + about: + title: About Us + members: + title: Subscribers (%{count}) + post: + title: Post an: + post_tabs: + - Post Announcement + - Post Event + - Import Feed + preview: + name: "Enter your feed's name" + about: "Go ahead and describe your feed. Don't be afraid to use some detail. There's space." + tag_list: "music, politics, food" + website: "http://www.YourWebsite.com" + hours: "9:00AM - 1:00PM" + phone: "867-5309" + address: "0 Bleecker Street" + diff --git a/app/text/en/formtastic.yml b/app/text/en/formtastic.yml new file mode 100644 index 000000000..9ce85f1fc --- /dev/null +++ b/app/text/en/formtastic.yml @@ -0,0 +1,63 @@ +en: + formtastic: + titles: + contact_information: "Contact Information:" + + labels: + user: + full_name: "Full Name:" + email: "Email Address:" + password: "Create a Password*:" + about: "Tell your neighbors a bit about yourself:" + interest_list: "Add some interests:" + offer_list: "Do you have any goods and skills you can share with your neighbors?" + referral_source: "How did you find out about CommonPlace?" + address: "Street Address*:" + avatar: "Add a photo:" + edit: + password: "Change your password:" + receive_posts: Get new neighborhood posts in emails + receive_events_and_announcements: Get new announcements and events in emails + receive_weekly_digest: Get a daily bulletin + post_receive_method: "Neighborhood Posts:" + invites: + emails: Add comma-separated emails to invite other residents of %{community} to CommonPlace + message: Optional message + + user_session: + email: "Enter your email:" + password: "Enter your password:" + + feed: + name: "Organization Name:" + slug: "CommonPlace Address:" + avatar: "Add a photo of your organization:" + is_news: "This is a Newspaper or News Service" + about: "Describe your organization:" + phone: "Phone Number:" + address: "Street Address:" + kind: "Is your community feed for:" + website: "Website:" + feed_url: "Enter an RSS feed and we'll send out announcements when you update it:" + twitter_name: "Enter your Twitter username and we'll syndicate your tweets on CommonPlace" + show: + phone: "Phone:" + website: "Website:" + address: "Address:" + hours: "Hours:" + announcement: + feed: "Send announcement from:" + + post: + subject: Title + body: Body + post_to_facebook: "Post to your facebook wall as well?" + event: + name: "Event name:" + description: "Event description:" + + hints: + user: + address: "*This verifies that you live in %{community}. We will not share this information with anyone." + feed: + slug: "You will be able post announcements to your feeds by emailing {CommonPlace Address}@ourcommonplace.com" diff --git a/app/text/en/group_posts.yml b/app/text/en/group_posts.yml new file mode 100644 index 000000000..317d30720 --- /dev/null +++ b/app/text/en/group_posts.yml @@ -0,0 +1,6 @@ +en: + + group_posts: + new: + link_html: Post to a Discussion Group + title: Post to a Discussion Group \ No newline at end of file diff --git a/app/text/en/invites.yml b/app/text/en/invites.yml new file mode 100644 index 000000000..f636f647c --- /dev/null +++ b/app/text/en/invites.yml @@ -0,0 +1,7 @@ +en: + invites: + new: + h1_html: Invite People in Town
          to %{community}'s CommonPlace + h2: The more people in town that are on %{community}'s CommonPlace, the better a network it is for everyone! + message_label: Feel free to attach a personal message below to encourage people to sign up. You can share stories about how CommonPlace has helped you out. + emails_label: Type in email addresses, separated by commas. diff --git a/app/text/en/messages.yml b/app/text/en/messages.yml new file mode 100644 index 000000000..faa2b4536 --- /dev/null +++ b/app/text/en/messages.yml @@ -0,0 +1,7 @@ +en: + messages: + index: + h2: Your Inbox + show: + to_index: "Back to inbox" + archive: "Archive" diff --git a/app/text/en/posts.yml b/app/text/en/posts.yml new file mode 100644 index 000000000..50538d357 --- /dev/null +++ b/app/text/en/posts.yml @@ -0,0 +1,6 @@ +en: + + posts: + new: + title: Say something to your neighbors + link_html: Post to Your Neighborhood diff --git a/app/text/en/registrations.yml b/app/text/en/registrations.yml new file mode 100644 index 000000000..73551f5a1 --- /dev/null +++ b/app/text/en/registrations.yml @@ -0,0 +1,34 @@ +en: + registrations: + new: + h1: Plug Into the %{community} Community + li1: CommonPlace is a web platform built for %{community} residents. It's like an online bulletin board for the %{community} community. + li2: Sign up to send and receive community announcements written by your neighbors and local leaders here in %{community}. + li3: CommonPlace is a community service project. Join and help make %{community} an even better place to live. + + li1fb: You are now connected to CommonPlace through Facebook. Please verify that you live in %{community} by providing your street address. + + li2fb: Your street address will not be shared with anyone. It is only used to verify that you live in %{community}. + + profile: + h1: Fill out your community profile + p2: CommonPlace makes it easy for you to share needs, announcements and events with folks who live in %{community}. + p3: Use CommonPlace whenever you want to get local recommendations, borrow goods from your neighbors, or plan the next big town initiative. + p4: Before you start, take a moment to fill out your community profile. + password_hint: "*Required field." + + avatar: + h1: Please crop your avatar + + feeds: + h1: Stay in the Loop! + h2: "Subscribe to some community feeds:" + p1: Community leaders use CommonPlace to share events and announcements with people who live in %{community}. + p2: Subscribe to some local community feeds in order to get notified whenever they send out updates. + + groups: + h1: Get Connected! + p1: CommonPlace Groups help you connect with people in your community who share similar interests. + p2: Subscribe to groups to send and receive information about specific community topics that you care about. + p3: Feel free to join as many as you want! + diff --git a/app/text/en/syndicate.yml b/app/text/en/syndicate.yml new file mode 100644 index 000000000..1b4b9c8c2 --- /dev/null +++ b/app/text/en/syndicate.yml @@ -0,0 +1,27 @@ +en: + syndicate: + events: + title: Upcoming Events + link_html: Community
          Events + posts: + title: Recent Neighborhood Posts + link_html: Neighborhood
          Posts + announcements: + title: Recent Community Announcements + link_html: Community
          Announcements + people: + title: Your Neighbors + users: + link_html: Directory + groups: + title: Discussion Groups + feeds: + title: Community Feeds + link_html: Community
          Feeds + group_posts: + link_html: Group
          Posts + title: Discussion Group Posts + messages: + title: Your Inbox + + diff --git a/app/text/en/tooltips.yml b/app/text/en/tooltips.yml new file mode 100644 index 000000000..c30525d69 --- /dev/null +++ b/app/text/en/tooltips.yml @@ -0,0 +1,8 @@ +en: + tooltips: + posts: View announcements, offers, and needs from your neighbors + events: View upcoming events in your community + announcements: View community announcements from organizations in your area + people: Find neighbors and civic leaders in your community + feeds: View Community Announcement Feeds + goods_and_skills: View Community Directory \ No newline at end of file diff --git a/app/text/en/user.yml b/app/text/en/user.yml new file mode 100644 index 000000000..461a794bf --- /dev/null +++ b/app/text/en/user.yml @@ -0,0 +1,9 @@ +college: + activerecord: + errors: + models: + user: + attributes: + email: + invalid: "Please enter your college e-mail address" + address: "The address you entered is outside of %{community}. If this is not the case, please email Pete@CommonPlaceUSA.com." diff --git a/app/text/en/users.yml b/app/text/en/users.yml new file mode 100644 index 000000000..365efbd4b --- /dev/null +++ b/app/text/en/users.yml @@ -0,0 +1,16 @@ +en: + users: + preview: + about: "Go ahead and describe yourself to your neighbors" + interest_list: "Reading, Community Engagement, Bicycling" + offer_list: "Lawnmower, Bikes, Word Processing" + + + activerecord: + errors: + models: + user: + attributes: + password: + blank: A password is required + diff --git a/app/text/goods.txt b/app/text/goods.txt new file mode 100644 index 000000000..1bac61a8b --- /dev/null +++ b/app/text/goods.txt @@ -0,0 +1,48 @@ +A/V Equipment +Aerator +Balloons +Barbeque +Barn +Basketball +Bicycles +Blow Torch +Boat +Bull Horn +Car (willing to loan) +Carpentry Tools +Carpet Cleaner +Cat Materials +Chain Saw +Childcare Materials +Corck Pot +Cots +Crutches +Dog Materials +Gardening Tools +Generator +Hay Bailer +Jack Hammer +Kids Books +Ladder +Lawn mower +Leaf Blower +Masonry Tools +Mechanics Tools +Medical Tools +Nail Gun +Outdoor Equipment +Paint Sprayer +Pick-up Truck +Plumbing Tools +Power Sander +Scanner +Sewing Machine +Shovels +Shredder +Sleeping Bag +Snow Blower +Sporting Equipment +Video Camera +Video Editing Equipment +Wet Vac +Wheelchair diff --git a/app/text/interests.txt b/app/text/interests.txt new file mode 100644 index 000000000..251c1e25e --- /dev/null +++ b/app/text/interests.txt @@ -0,0 +1,127 @@ +Acting +Activism +Animals +Antiques +Arts +Athletics +Baking +Baseball +Basketball +Beach +Biking +Birds +Boating +Books +Bowling +Bridge +Business +Camping +Cards +Chess +Church +Community Engagement +Community Service +Computer Games +Computers +Cooking +Cooking +Craft Making +Crafts +Creative Writing +Creativity +Dance +Design +Drama +Drawing +Economics +Environmental Work +Extreme Sports +Faith +Family +Fencing +Fishing +Fishing +Fitness +Food +Football +Foreign Languages +Gardening +Golf +Green Energy +Guitar +Gym +Health and Wellness +Hiking +History +Home Improvement +Horseback Riding +Hunting +Interior Design +Jewelry +Kids +Knitting +Lacrosse +LGBT +Local +Local Politics +Marketing +Martial Arts +Military +MMA +Modeling +Motorcycles +Movies/Film +Music +Nutrition +Organic +Outdoors +Painting +Parenting +Pets +Photography +Piano +Pilates +Poetry +Politics +Politics +Pottery +Psychology +Quilting +Radio +Reading +Religion +Retired +Running +Schools +Science +Scuba Diving +Senior Citizen +Sewing +Shopping +Singing +Single Parent +Skiing +Soccer +Social Justice +Social Media +Sports +Sustainability +Swimming +Teaching +Technology +Tennis +Travel +Video Games +Violin +Volunteering +Walking +Water Sports +Wine +Winter Sports +Woodworking +Working Out +Worship +Writing +Yoga +Youth Work +Zumba diff --git a/app/text/skills.txt b/app/text/skills.txt new file mode 100644 index 000000000..4acd5bff2 --- /dev/null +++ b/app/text/skills.txt @@ -0,0 +1,134 @@ +Accountant +Animals +Art +Attorney/Lawyer +Audio +Babysitting +Baking +Baking +Basketball +Bass +Bible Study +Bicycle Repair +Business Services +Cakemaking +Cameras +Cardmaking +Career Counseling +Carpentry +Cat Care +Chess +Childcare +Chinese +City Government Knowledge +Clarinet Lessons +Cleaning +College Admissions Process Help +Communications +Community Organizing +Competitive Analysis +Computer Help +Conservation +Construction +Cooking +Counseling +CPA Accountant +CPR/First Aid Help +Design +Doctor +Dog Knowledge +Dog Walker +Drums +Drywall construction +Early Childhood education +Editor +Education +Electrician +Emergency Preparedness +Excel +Family Counseling +Film/Video +Financial Literacy +Fitness +Flute Lessons +French +Gardening +German +Grant Writing +Graphic Design +Green living +Guitar Lessons +Hair Stylist / Barber +Handmade Jewelry +Handyman +Health and Wellness +Health Care +Home Nursing / Hospice +Home Recording +House Cleaning +Interior Decorating +Internet Savvy +IT +Italian +Knitting +Knitting/Sewing +Korean +Lawn maintenance +Local History +Manual Labor +Marketing +Mentoring +Microsoft Office +Military +Ministry +Music +Music +Nurse +Organic living +Organizing +Outdoor skills +Painting +Party Planning +Personal Trainer +Pet Sitting +Photography +Piano +Piano Lessons +Pilates +Plumber +Portugese +Proof Reading +Public Speaking +Quilting +Reading Specialist +Real Estate +Recipes +Repair work +Resume Writing +Retirement +SAT Tutoring +Scrapbooking +Sewing +Singing +Social Media +Spanish +Sports +Sports Coach +Substance Abuse Counseling +Survival skills +Swimming Instructor +Taxes +Technology +Tree Removal +Tutoring +Utility Work +Vietnamese +Violin +Violin Lessons +Volunteer +Web Design +Wine +Woodworking +Working Out +Yardwork +Yoga diff --git a/app/views/accounts/_facebook_wall_post.haml b/app/views/accounts/_facebook_wall_post.haml new file mode 100644 index 000000000..6d4e084a9 --- /dev/null +++ b/app/views/accounts/_facebook_wall_post.haml @@ -0,0 +1,11 @@ +#fb-root +%script(type="text/javascript" src="http://connect.facebook.net/en_US/all.js") +:javascript + FB.init({ + appId:'#{CONFIG['facebook_api_key']}', cookie: true, + status: true, xfbml: true + }); + FB.ui({ + method: 'feed', + message: '#{t '.wall_post_content'}' + }); \ No newline at end of file diff --git a/app/views/accounts/_form.haml b/app/views/accounts/_form.haml deleted file mode 100644 index 63e7a6a38..000000000 --- a/app/views/accounts/_form.haml +++ /dev/null @@ -1,30 +0,0 @@ -- form_for user, :url => account_path, :html => {:class => "new_entity inline-labels"} do |f| - .field.full_name.long - = f.label :full_name, "Your Full Name" - = f.text_field :full_name - = f.error_message_on :full_name - .field.email.long - = f.label :email, "Your Email" - = f.text_field :email - = f.error_message_on :email - .field.password.long - = f.label :password, "Create a Password" - = f.password_field :password, :autocomplete => "off" - = f.error_message_on :password - - - f.fields_for user.location do |l| - .field.street_address.long - = l.label :street_address, "Your Street Address" - = l.text_field :street_address - = l.error_message_on :street_address - - .field.short.check_box - %label - = check_box_tag "user[terms]" - I agree to the #{ link_to 'Terms of Service', terms_path, 'data-remote' => true }. - = f.error_message_on :terms - - - .buttons.submit - #submit_wrapper - = f.submit "Sign up!".upcase \ No newline at end of file diff --git a/app/views/accounts/_info_box.haml b/app/views/accounts/_info_box.haml deleted file mode 100644 index 6cda54eaa..000000000 --- a/app/views/accounts/_info_box.haml +++ /dev/null @@ -1,31 +0,0 @@ - -#information.info_box.user.inline-form{'data-form-url'=>"/account", 'data-form-method'=>"put"} - .edit - %span.inline-edit edit - .show - %span.inline-save save - ⋅ - %span.inline-cancel cancel - %h2 Your Civic Profile - - = image_tag current_user.avatar.url(:normal), :width => 120, :class => "avatar" - - form_tag "/account/avatar", :multipart => true, :method => "post", :id => "avatar-form" do - #hidden_input - %button{:id=>"upload_file_button"} - %a{:href=>"javascript: void(0)"} - Upload Image - %input{:type => 'file', :name => "user[avatar]", :id=>"file_uploader"} - %h3= current_user.full_name - .info{'data-field' => "user[about]"}= markdown current_user.about - %table - %tr - %th INTERESTS - %td{'data-field' => "user[interest_list]"}= current_user.interest_list - %tr - %th GOODS & SKILLS - %td{'data-field' => "user[good_list]"}= current_user.good_list - %tr - %th SUBSCRIPTIONS - %td= current_user.feed_list - - .clear \ No newline at end of file diff --git a/app/views/accounts/_preview.haml b/app/views/accounts/_preview.haml deleted file mode 100644 index 95c80cf13..000000000 --- a/app/views/accounts/_preview.haml +++ /dev/null @@ -1,19 +0,0 @@ - -#preview.info_box - - %h2 Your Civic Profile - - = image_tag current_user.avatar.url(:normal), :width => 120, :class => "avatar" - - %h3= current_user.full_name - - .info{'data-track' => "user[about]"}= current_user.about.present? ? current_user.about : t("users.preview.about") - %table - %tr - %th INTERESTS - %td{'data-track' => "user[interest_list]"}= current_user.interest_list.present? ? current_user.interest_list : t("users.preview.interest_list") - %tr - %th GOODS & SKILLS - %td{'data-track' => "user[good_list]"}= current_user.good_list.present? ? current_user.good_list : t("users.preview.good_list") - - .clear \ No newline at end of file diff --git a/app/views/accounts/delete.haml b/app/views/accounts/delete.haml new file mode 100644 index 000000000..99226630d --- /dev/null +++ b/app/views/accounts/delete.haml @@ -0,0 +1,7 @@ + += semantic_form_for current_user, :url => account_path, :html => {:method => :delete} do |f| + %p If you delete your account everything will be removed, including your posts and replies. If you're absolutely sure you want to remove your account, press the following button. + + = f.buttons do + = f.commit_button "Fully remove my account from CommonPlace" + diff --git a/app/views/accounts/edit.haml b/app/views/accounts/edit.haml index e456b8ccd..af3be506e 100644 --- a/app/views/accounts/edit.haml +++ b/app/views/accounts/edit.haml @@ -1,11 +1,22 @@ -- semantic_form_for current_user, :url => settings_account_path do |f| += semantic_form_for current_user, :url => settings_account_path do |f| %h1= t ".title" - - f.inputs do + = f.inputs do = f.input :email = f.input :password - = f.input :receive_posts, :as => :boolean - = f.input :receive_events_and_announcements, :as => :boolean - - f.buttons do + = f.input :receive_weekly_digest, :as => :boolean + = f.input :post_receive_method, :as => :radio, :collection => translate_collection("accounts.edit.receive_options", User.post_receive_options, kv_store=true) + + = f.semantic_fields_for :subscriptions do |sf| + = sf.inputs do + = sf.input :receive_method, :as => :radio, :collection => Subscription.receive_methods, :label => "#{sf.object.feed.name}:" + + + = f.semantic_fields_for :memberships do |mf| + = mf.inputs do + = mf.input :receive_method, :as => :radio, :collection => Membership.receive_methods, :label => "#{mf.object.group.name}:" + + = f.buttons do + %li.delete + = link_to "Delete my CommonPlace account", delete_account_path = f.commit_button "Update Settings" - \ No newline at end of file diff --git a/app/views/accounts/edit_avatar.haml b/app/views/accounts/edit_avatar.haml deleted file mode 100644 index 69a10f9fe..000000000 --- a/app/views/accounts/edit_avatar.haml +++ /dev/null @@ -1,30 +0,0 @@ -- render :layout => 'shared/modal' do - - - form_for @avatar, :html => { :id => "crop_avatar_form", :class => "new_entity" }, :url => update_avatar_account_path do |f| - %hr.staple - - %h1 Add your profile picture - - %p.subhead Upload and crop a profile picture. - - = image_tag @avatar.image.url(:original), :id => 'avatar_to_crop' - - = f.hidden_field :x - = f.hidden_field :y - = f.hidden_field :w - = f.hidden_field :h - - %fieldset - %ol - #submit_wrapper - %li= f.submit "Crop" - -- update_content "#syndicate" do - #syndicate - %nav - - link_to new_post_path, 'data-remote' => true, :id => "create-entity", :class => 'tooltip', 'data-title' => 'Write a new post to your neighbors.' do - %span - = image_tag 'pin-icon.png' - %span POST TO YOUR NEIGHBORS - = render 'shared/items', :items => @items - diff --git a/app/views/accounts/edit_interests.haml b/app/views/accounts/edit_interests.haml deleted file mode 100644 index c79a182b4..000000000 --- a/app/views/accounts/edit_interests.haml +++ /dev/null @@ -1,15 +0,0 @@ - -%h1= t ".title" - -#register - - semantic_form_for current_user, :url => update_interests_account_url do |f| - %h2= t ".subtitle" - - f.inputs do - = f.input :interest_list, :collection => t(".starter_interests"), :as => :check_boxes - %table#interest_list_toggles - - t(".starter_interests").each_slice(4) do |row| - %tr - - row.each do |interest| - %td{'data-selected' => 'false', 'data-value' => interest}<>= interest - - f.buttons do - = f.commit_button :button_html => {:type => "image", :src => "/images/buttons/continue.png"} \ No newline at end of file diff --git a/app/views/accounts/edit_new.haml b/app/views/accounts/edit_new.haml deleted file mode 100644 index 1f467dc4d..000000000 --- a/app/views/accounts/edit_new.haml +++ /dev/null @@ -1,18 +0,0 @@ - -%h1= t '.title' - -#register - - semantic_form_for current_user, :url => update_new_account_path, :html => {:multipart => true}, :method => :put do |f| - - = render 'preview' - - - f.inputs do - - if !current_user.facebook_uid - = f.input :password - = f.input :avatar, :input_html => {:size => 20} - = f.input :about - = f.input :good_list - = f.input :interest_list, :label => "What are your interests:" - - - f.buttons do - = f.commit_button :button_html => {:type => "image", :src => "/images/buttons/continue.png"} diff --git a/app/views/accounts/facebook_invite.haml b/app/views/accounts/facebook_invite.haml new file mode 100644 index 000000000..63d534aee --- /dev/null +++ b/app/views/accounts/facebook_invite.haml @@ -0,0 +1,187 @@ += javascript_include_tag "https://www.plaxo.com/css/m/js/util.js" += javascript_include_tag "https://www.plaxo.com/css/m/js/basic.js" += javascript_include_tag "https://www.plaxo.com/css/m/js/abc_launcher.js" += javascript_include_tag "invite_page" + +%script + = "var slug = '#{current_community.slug}';" + = "var message = '#{t(".facebook.invitation", :community => current_community.name, :slug => current_community.slug)}';" + + +- if Rails.env.production? + :javascript + mpq.track('Visited invitation page', {'community': '#{current_community.slug}'}); + +:javascript + + function fb_share() { + FB.ui( + { + method: 'feed', + name: '#{t(".facebook.name", :community => current_community.name)}', + link: "https://www.OurCommonPlace.com/#{current_community.slug}", + picture: 'http://www.ourcommonplace.com/assets/logo-pin.png', + caption: '#{t(".facebook.caption", :community => current_community.name)}', + description: '#{t(".facebook.description", :community => current_community.name)}', + message: '#{t(".facebook.message", :community => current_community.name, :slug => current_community.slug)}' + }, + function(response) { + } + ); + + } + fbEnsureInit(function() { + $("#step_one").append( + ''); + + $("#step_two").append(''); + }); + + + +:css + .fb_share_button { + display: -moz-inline-block; + display:inline-block; + padding:1px 20px 0 5px; + height:15px; + border:1px solid #d8dfea; + background: url("assets/findneighbors.png") no-repeat top right; + } + .fb_share_button:hover { + color:#fff; + border-color:#295582; + background:#3b5998 url("/assets/invite/findneighbors.png") no-repeat top right; + text-decoration:none; + } + + +#planewrapper + #plane + %h1 + = t(".heading") + %br + %h2 + = t(".heading2") + +#groups_box + #groups_wrapper + %ul + %li + #step_one + %h3 + =t(".step_one") + %h4 + =t(".step_one_label") + +#groups_box + #groups_wrapper + %ul + %li + #step_two + %h3 + =t(".step_two") + %h4 + =t(".step_two_label") + -#The javascript will add the buttons in here. + +#groups_box + #groups_wrapper + %ul{style: "height: auto;"} + %li + #step_three{style: "height:auto;"} + %h3 + =t(".step_three") + %h4 + %span{ :class => "vertical-align: middle" } + =t(".step_three_label") + %br + %br + %center + %a{ :href => "#", :onclick => "showPlaxoABChooser('invite_email', '/plaxo-importer.html'); return false;" } + %img{ :src => asset_path("contacts-from-email-btn.jpg"), :alt => "Add from my address book", :width => 370} + %br + %center + OR + #invite_errors + = semantic_form_for @invitation, :url => "/api/communities/#{current_community.id}/invites" do |f| + = f.inputs do + = f.input :email,:as => 'text', :input_html => { :placeholder => "Enter email addresses here (separated by commas)", :class => "required email", :width => 370, :style => "height: 66px;" } + = f.input :body, :input_html => { :placeholder => "Include a message about why you love CommonPlace", :rows =>"4"}, :html => {:class => "required"} + = f.buttons do + = f.submit :type => "image", :src => asset_path("invite/neighborinvite.png"), :width => "370px", :style => "margin-left: 3px; margin-top: 15px;" + #forms + +- unless current_community.is_college + #groups_box + #groups_wrapper + %ul + %li + #step_four + %h3 + =t(".step_four") + %h4 + =t(".step_four_label") + %table + %tr{:style =>"width:370px"} + %td{:style =>"width:60px"} + %a{:href => "https://s3.amazonaws.com/commonplace-community-assets/#{current_community.slug}/#{t('.file_names')[0]}.pdf"} + = image_tag "invite/PDFLogo.png" + %td{:style => "width:310px; text-align: center;vertical-align: middle;"} + %h5 + =t(".file_descriptions")[0] + %a{:href => "https://s3.amazonaws.com/commonplace-community-assets/#{current_community.slug}/#{t('.file_names')[0]}.pdf"} + %h6 + =t(".download_tag") + %tr{:style =>"width:370px"} + %td{:style =>"width:60px"} + %a{:href => "https://s3.amazonaws.com/commonplace-community-assets/#{current_community.slug}/#{t('.file_names')[1]}.pdf"} + = image_tag "invite/PDFLogo.png" + %td{:style => "width:310px; text-align: center;vertical-align: middle;"} + %h5 + =t(".file_descriptions")[1] + %a{:href => "https://s3.amazonaws.com/commonplace-community-assets/#{current_community.slug}/#{t('.file_names')[1]}.pdf"} + %h6 + =t(".download_tag") + + +/ + #groups_box + #groups_wrapper + %ul + %li + #step_five + %h3 + =t(".step_five", :community => current_community.name) + %h4 + =t(".step_five_label", :community => current_community.name) + + = image_tag "invite/joinfriends.png", :width =>"370px",:style =>"margin-left:3px; margin-top:10px;", :onclick => "add_to_friends_of_commonplace('#{current_user.email}',slug);", :onmouseout => "document.body.style.cursor = \'auto\';", :onmouseover => "document.body.style.cursor = \'pointer\';" + %br + +-# Facebook + +%script + window.fbAsyncInit = function() { + FB.init({ + appId : "#{($FacebookConfig || {})['app_id']}", + status : true, + cookie : true, + xfbml : true + }); + fbApiInit = true; + }; + $(document).ready(function(){ + $('a[href^="http://"]').attr({ + target: "_blank", + title: "Opens in a new window" + }); + }); + (function() { + var e = document.createElement('script'); + e.src = document.location.protocol + '//connect.facebook.net/en_US/all.js'; + e.async = true; + document.getElementById('fb-root').appendChild(e); + }()); + + diff --git a/app/views/accounts/learn_more.haml b/app/views/accounts/learn_more.haml index 5fd36a731..8f630f27d 100644 --- a/app/views/accounts/learn_more.haml +++ b/app/views/accounts/learn_more.haml @@ -1,23 +1,117 @@ +!!! 5 +%html{:xmlns => "https://www.w3.org/1999/xhtml", 'xmlns:fb' => "https://www.facebook.com/2008/fbml"} + %head + %link{ :href=> 'https://webfonts.fontslive.com/css/0a25c9dc-bb5a-4ea9-88b2-1c7a8802e172.css', :rel =>"stylesheet", :type =>"text/css"} + %link{ :href=> 'https://fonts.googleapis.com/css?family=Rokkitt:400,700', :rel =>"stylesheet", :type =>"text/css"} -#learn-more - %h1= t ".title", :sign_up => link_to(t(".sign_up"), new_account_path) - #content - %p= t ".p1", :community=>current_community.name - %p= t ".p2" - .img_wrapper - =image_tag "example-email.png", :width => 603, :height => 357 - %p= t ".p3" - %p= t ".p4", :community=>current_community.name - .img_wrapper - =image_tag "example-platform.png", :width => 611, :height => 551 - %p= t ".p5" - %ul - - t(".list").each do |text| - %li= text.match("COMMUNITY") ? text.sub!(/COMMUNITY/, current_community.name) : text - %p= t ".p6", :community_link => link_to(root_url, root_url) - - //%p= t ".p7", :community_email => mail_to("announcement-"+current_community.slug+"@ourcommonplace.com", "announcement-"+current_community.slug+"@ourcommonplace.com") - .img_wrapper - -link_to new_account_path do - =image_tag "sign-up-now-btn.png", :width => 193, :height => 49 + %meta{ :content => "text/html;charset=utf-8", "http-equiv" => "Content-Type" } + %meta{ :name => "google-site-verification", :content => "HntCS6GTLIAye5nM6FCNHgtcSUlb0-aF2jVbGtd8oyI" } + %meta{ :name => "description", :content => "The #{params[:community] && current_community.name} CommonPlace is an online community bulletin board for neighbors in #{params[:community] && current_community.name}, designed to make it easy for neighbors and community groups to share and connect." } + + %title= "CommonPlace #{ params[:community] && current_community.name}" + + %link{ :href => image_path('favicon.png'), :rel => "icon", :type => "image/png" } + + = javascript_include_tag "https://exceptional-js.heroku.com/exceptional.js" + + = stylesheet_link_tag 'learn_more' + + :javascript + CommonPlace = {}; + %body + + #wrapper + = render 'layouts/registration_header' + + #main + %h1 + #smallbanner Learn more about + CommonPlace + + %h2 How CommonPlace Works + + %ul + %li + = image_tag "learn_more/sign_up_screen_shot.png", :size => "300x210" + + %h3 1. Sign Up + + %p= t ".signup1" + + %p= t ".signup2" + + %div{:style => "clear: both;"} + + %li + = image_tag "learn_more/bulletin_screen_shot.png", :size => "300x210" + + %h3 2. Receive Community Bulletins + + %p= t ".bulletins1" + + %p= t ".bulletins2" + + %div{:style => "clear: both;"} + + %li + = image_tag "learn_more/community_screen_shot.png", :size => "300x210" + + %h3 3. Search the Community + + %p= t ".search1_html", :slug => current_community.slug + + %p= t ".search2" + + %div{:style => "clear: both;"} + + %li + = image_tag "learn_more/post_screen_shot.png", :size => "300x210" + + %h3 4. Post to the Neighborhood + + %p= t ".post1" + + %ul + %li= t ".post2" + %li= t ".post3" + %li= t ".post4" + %li= t ".post5" + + %div{:style => "clear: both;"} + + + %h2 Press and Local Testimonials + + #testimonial + %h3 Neighbors now have a common online community bulletin board + %p Warwick - Neighbors in Warwick now have a new way to connect with their neighbors and local leaders, thanks to two college students at Harvard University. CommonPlace is an online bulletin board that allows members to post questions, event information, requests and announcements for their neighbors. + #more + %a{:href => "http://strausnews.com/articles/2011/06/12/warwick_advertiser/news/1.txt"} READ MORE... + %div{:style => "clear: both;"} + + #testimonial + + %h3 Local news helps to describe CommonPlace + %p Watch this video from Marquette, Michigan. Description using community organizer's name and name of program/TV show. + #more + %a{:href => "http://www.youtube.com/watch?v=6yD_MCgyFBI"} VIEW MORE... + %div{:style => "clear: both;"} + + #testimonial + %blockquote "I've posted on and responded to Falls Church CommonPlace posts. People attended a community event I promoted. I found work through it. And I scored a free perfect-condition TV from it." + #author - Debra Roth, a Falls Church neighbor + %div{:style => "clear: both;"} + + #testimonial + %h3 In Order to Form a More Perfect Community + %p Because community is the heart of Raleigh, and because our neighborhoods are made up of amazing and inspiring neighbors who love their families and want to help each other, it is time we hammer the Internet into a tool that unites rather than divides our communities. + %p (A beautiful article about CommonPlace published in The Raleigh Creative District) + #more + %a{:href => "http://southwestraleigh.com/2011/06/in-order-to-form-a-more-perfect-community/"} READ MORE... + %div{:style => "clear: both;"} + + #wrapper-footer + + + = render "layouts/footer" diff --git a/app/views/accounts/new.haml b/app/views/accounts/new.haml deleted file mode 100644 index e076b671f..000000000 --- a/app/views/accounts/new.haml +++ /dev/null @@ -1,29 +0,0 @@ - -%h1= t ".h1", :community => current_community.name, :learn_more => link_to(t(".learn_more"), learn_more_account_path) - -= update_content "#main" do - #main - #create-a-feed - %h3 Are you a community leader or shop owner? - %p Create a feed to send events and announce-
          ments to the whole community. - - link_to new_account_path(:short => true) do - = image_tag 'create-a-feed.png' - - semantic_form_for @user, :url => account_path, :html => {'data-remote' => true}, :id => 'registration_form' do |f| - %h3 - Fill out the form to get started or - %fb:login-button{:size => 'medium', :length => 'long', :onlogin => 'facebookUserLoggedIn();', :perms => 'email,user_hometown,user_address'} - - f.inputs do - = f.input :full_name - = f.input :email - = f.input :address, :hint => t("formtastic.hints.user.address", :community => current_community.name) - = f.input :facebook_uid, :as => :hidden - - f.buttons do - = f.commit_button :button_html => {:type => "image", :src => "/images/buttons/sign-up.png"} - -#fb-root -:javascript - // Attempt a query; maybe the user has already authenticated? - FB.api('/me', function(user) { facebookUserLoggedIn(user); }); - -%p#bubble-1= t ".bubble_1" -%p#bubble-2= t ".bubble_2" diff --git a/app/views/accounts/profile.haml b/app/views/accounts/profile.haml new file mode 100644 index 000000000..7db98653c --- /dev/null +++ b/app/views/accounts/profile.haml @@ -0,0 +1,16 @@ + += semantic_form_for current_user, :url => account_path, :html => {:multipart => true } do |f| + + %h1 Update your profile + + = f.inputs do + = f.input :full_name + = f.input :avatar + = f.input :about + + = f.input :interest_list, :as => :select, :collection => $interests, :multiple => true, :input_html => {"data-placeholder" => "Select some interests" }, :label => "Select some interests to share with your neighbors:" + = f.input :skill_list, :as => :select, :collection => $skills, :multiple => true, :input_html => {"data-placeholder" => "Select some skills" }, :label => "Select some skills you can share with your neighbors:" + = f.input :good_list, :as => :select, :collection => $goods, :multiple => true, :input_html => {"data-placeholder" => "Select some goods" }, :label => "Select some goods you can share with your neighbors:" + + = f.buttons do + = f.commit_button "Update Profile" diff --git a/app/views/accounts/short.haml b/app/views/accounts/short.haml deleted file mode 100644 index 876d05c80..000000000 --- a/app/views/accounts/short.haml +++ /dev/null @@ -1,13 +0,0 @@ - -%h1= t "feeds.account.h1" - -= update_content "#main" do - #main - - semantic_form_for @user, :url => account_path(:short => true), :html => {'data-remote' => true} do |f| - - f.inputs do - = f.input :full_name - = f.input :email - = f.input :password - = f.input :address, :hint => t("formtastic.hints.user.address", :community => current_community.name) - - f.buttons do - = f.commit_button :button_html => {:type => "image", :src => "/images/buttons/sign-up.png"} diff --git a/app/views/accounts/update.json.erb b/app/views/accounts/update.json.erb deleted file mode 100644 index ee290f0b4..000000000 --- a/app/views/accounts/update.json.erb +++ /dev/null @@ -1,3 +0,0 @@ -{ - "content": <%= h2j { render "info_box" } %> -} \ No newline at end of file diff --git a/app/views/admin/_community_overview.haml b/app/views/admin/_community_overview.haml new file mode 100644 index 000000000..56b6f3775 --- /dev/null +++ b/app/views/admin/_community_overview.haml @@ -0,0 +1,71 @@ += javascript_include_tag "highcharts", "date" +.community{:width => "100%", :style => "border:1px solid black;"} + - community = community_overview + - community_user_count = community.users.size + .community{:width => "100%", :style => "border: 1px solid black;"} + %h2{:style => "text-align: center;"} + = community.name + = link_to "(csv)", "/admin/#{community.slug}/export_csv" + %table + %tr + %th Users + %th User Data + %tr + %td{:valign => "top"} + Total Users: + = community.users.count + %br + Percentage of Field: + = "#{(100*community.users.count) / community.households.to_i}%" + %br + User Gains Today: + = community.users.today.count + %div[community, :user_growth]{:style => "width: 250px;"} + %td{:valign => "top"} + %table{:border => "1"} + %tr + %td + Total Neighborhood Posts: + = community.posts.count + %br + Neighborhood Posts This Week: + = community.posts.between(7.days.ago, 0.days.ago).count + %br + Average Per Day: + = community.posts.between(7.days.ago, 0.days.ago).count / 7.0 + %div[community, :neighborhood_post_growth]{:style => "width: 250px;"} + %td + Total Events: + = community.events.size + %br + Events This Week: + = community.events.between(7.days.ago, 0.days.ago).size + %br + Average Per Day: + = community.events.between(7.days.ago, 0.days.ago).size / 7.0 + %span[community, :event_growth]{:style => "width: 250px;"} + %tr + %td + Total Announcements: + = community.announcements.size + %br + Announcements This Week: + = community.announcements.between(7.days.ago,0.days.ago).size + %br + Average Per Day: + = community.announcements.between(7.days.ago,0.days.ago).size / 7.0 + %span[community, :announcement_growth]{:style => "width: 250px;"} + -#%td + Total Private Messages: + = community.private_messages.size + %br + Private Messages This Week: + = community.private_messages.select { |m| m.created_at < 0.days.ago and m.created_at > 7.days.ago }.size + %br + Average Per Day: + = community.private_messages.select { |m| m.created_at < 0.days.ago and m.created_at > 7.days.ago }.size / 7.0 + %span[community, :private_message_growth]{:style => "width: 250px;"} + -#%br + -#Value-Adding User Percentage: + -#= "#{((100*community.users.select { |u| u.value_adding? }.size) / community_user_count)}%" + diff --git a/app/views/admin/_community_referrer.haml b/app/views/admin/_community_referrer.haml new file mode 100644 index 000000000..2fc311d23 --- /dev/null +++ b/app/views/admin/_community_referrer.haml @@ -0,0 +1,18 @@ +#referral_sources +%table + %tr + %th Name + %th Community + %th Timestamp + %th Referral Source + %th Metadata + - community_referrer.users.select{|u| u.referral_source.present?}.sort{|a,b| a.created_at <=> b.created_at}.reverse.each do |user| + %tr + %td{:width => "20%"}= user.full_name + + %td{:width => "10%"}= user.community.name + %td{:width => "20%"} + = user.created_at.getlocal.to_date + = user.created_at.getlocal + %td{:width => "25%"}= user.referral_source + %td{:width => "25%"}= user.referral_metadata diff --git a/app/views/admin/clipboard.haml b/app/views/admin/clipboard.haml new file mode 100644 index 000000000..348188182 --- /dev/null +++ b/app/views/admin/clipboard.haml @@ -0,0 +1,26 @@ +#bulk-register + - if flash[:notice].present? + #notice= flash[:notice] + #instructions + %p + Please enter the details for your new users, one per line, in the following format: + %br + %br + FIRST_NAME LAST_NAME;EMAIL_ADDRESS;STREET_ADDRESS + %br + %br + So, if you have two users, your entry would look something like: + %br + %br + John Doe;johndoe@aol.com;123 Anywhere Street + %br + Jane Public;janegirl93@yahoo.com;1955 Broadway + %br + = form_tag '/admin/clipboard' do + = label_tag "clipboard_community", "Community:" + = select_tag "clipboard_community", options_from_collection_for_select(Community.all, "id", "name") + %br + = text_area_tag 'registrants', nil, :size => "120x20" + + %br + = submit_tag diff --git a/app/views/admin/map.haml b/app/views/admin/map.haml new file mode 100644 index 000000000..d38e82113 --- /dev/null +++ b/app/views/admin/map.haml @@ -0,0 +1,14 @@ +%script{:src => "http://maps.google.com/maps?file=api&v=2&key=ABQIAAAAzr2EBOXUKnm_jVnk0OJI7xSosDVG8KKPE1-m51RBrvYughuyMxQ-i1QfUnH94QxWIa6N4U6MouMmBA", :type => "text/javascript"} +%script{:type => "text/javascript"} + function initialize() { + if (GBrowserIsCompatible()) { + var map = new GMap2(document.getElementById("map_canvas")); + map.setCenter(new GLatLng(37.4419, -122.1419), 1); + + - User.all.select{ |u| u.latitude.present? and u.longitude.present? }.each do |user| + = "map.addOverlay(new GMarker(new GLatLng(#{user.latitude}, #{user.longitude})));" + } + } + $(document).ready(initialize); +#map_canvas{:style => "width: 500px; height: 300px"} + diff --git a/app/views/admin/overview.haml b/app/views/admin/overview.haml new file mode 100644 index 000000000..59a711194 --- /dev/null +++ b/app/views/admin/overview.haml @@ -0,0 +1,77 @@ += javascript_include_tag "jquery-1.6.1" + +:css + td { + border: 3px solid #c3c3c3; + } + th { + border: 3px solid #c3c3c3; + } + table { + border-collapse: collapse; + } +%h2 Community Statistics +%table + %thead + %tr + %th Statistic + - @communities.each do |c| + %th= c[0] + %th Total + %tbody + - @communities.first[1].each do |statistic_name, value| + %tr + %th= statistic_name.titleize + - @communities.each do |c| + %td= c[1][statistic_name] + - if statistic_name.include? "average" + %td.average + - else + %td.total +%hr +%h2 Platform Statistics +%table + %tr + %th Statistic + %th Value + - @overall_statistics.each do |name,value| + %tr + %td= name.titleize + %td= value +%hr +%h1 Historical Statistics +- @historical_statistics.each do |community, stats| + %h2 + = "#{community} -" + = link_to "Export CSV", "/admin/export_csv?community=#{Community.find_by_name(community).slug}" + %table + %tr + %th Statistic + - stats.each_with_index do |data, days_ago| + %th= days_ago.to_i.days.ago.to_date.to_s + - stats.first.each do |name, val| + %tr + %th= name.titleize + - stats.each_with_index do |statistics, d| + %td= statistics[name].to_s +:javascript + $(document).ready(function(){ + findTotals(); + }); + + function findTotals() { + $("tbody tr").each(function() { + row_total = 0; + row_count = 0; + $("td:not(.total)", this).each(function() { + row_total += Number($(this).html()); + row_count += 1; + }); + $(".total", this).html(roundNumber(row_total, 2)); + $(".average", this).html(roundNumber(row_total / row_count, 2)); + }); + } + + function roundNumber(num, dec) { + return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec); + } diff --git a/app/views/admin/show_referrers.haml b/app/views/admin/show_referrers.haml new file mode 100644 index 000000000..01b6ae174 --- /dev/null +++ b/app/views/admin/show_referrers.haml @@ -0,0 +1,35 @@ +:css + #referral_sources { + text-align: center; + } + table { + border-width: 1px; + border-style: solid; + border-color: #000000; + border-collapse: collapse; + width: 100%; + } + + th { + border-width: 1px; + border-style: solid; + border-color: #000000; + text-align: center; + font-weight: bold; + } + + td { + border-width: 1px; + border-style: solid; + border-color: #000000; + text-align: center; + } + + h1 { + margin: 20px auto; + font-size: 28px; + color: blue; + } +.referral_sources + %h1 Referral Sources + = render :partial => "community_referrer", :collection => Community.all diff --git a/app/views/admin/view_messages.haml b/app/views/admin/view_messages.haml new file mode 100644 index 000000000..f85812a8d --- /dev/null +++ b/app/views/admin/view_messages.haml @@ -0,0 +1,13 @@ +- @messages.each do |message| + .message + %h3.subject= message.subject + %p.community= message.user.community.name + %p.to to #{message.messagable.name} + %p.from from #{message.user.name} + %p.date= message.created_at + %p.body= message.body + .replies + - message.replies.each do |reply| + .reply + %p.date= reply.created_at + %p.body= reply.body diff --git a/app/views/administration/_navigation.haml b/app/views/administration/_navigation.haml deleted file mode 100644 index a1ff58798..000000000 --- a/app/views/administration/_navigation.haml +++ /dev/null @@ -1,6 +0,0 @@ - -%nav#admin_global_nav - = tab_to 'New Community', new_community_path - = tab_to 'List Communities', communities_path - - diff --git a/app/views/administration/addresses/index.haml b/app/views/administration/addresses/index.haml deleted file mode 100644 index 659bdcd47..000000000 --- a/app/views/administration/addresses/index.haml +++ /dev/null @@ -1,11 +0,0 @@ - - -%ul#addresses - - @addresses.each do |address| - %li= address.name - - -= render_map do |map| - - map.center :lat => 42.283285, :lng => -71.157144 - - @addresses.each do |address| - - map.marker :lat => address.lat, :lng => address.lng diff --git a/app/views/administration/communities/edit.haml b/app/views/administration/communities/edit.haml deleted file mode 100644 index 9043c89fe..000000000 --- a/app/views/administration/communities/edit.haml +++ /dev/null @@ -1,6 +0,0 @@ - -- semantic_form_for @community, :html => {:multipart => true} do |f| - %h2 Edit Community - = f.inputs :name, :slug, :zip_code, :logo, :email_header, :signup_message - - = f.buttons \ No newline at end of file diff --git a/app/views/administration/communities/index.haml b/app/views/administration/communities/index.haml deleted file mode 100644 index 0f0baa751..000000000 --- a/app/views/administration/communities/index.haml +++ /dev/null @@ -1,4 +0,0 @@ - -%ul - - @communities.each do |community| - %li= community.name \ No newline at end of file diff --git a/app/views/administration/communities/new.haml b/app/views/administration/communities/new.haml deleted file mode 100644 index 06dffc59c..000000000 --- a/app/views/administration/communities/new.haml +++ /dev/null @@ -1,12 +0,0 @@ - -- semantic_form_for @community, :html => {:multipart => true} do |f| - %h2 New Community - = f.inputs :name, :slug, :zip_code, :logo, :email_header, :signup_message - - - f.inputs :for => :neighborhoods, :name => "First Neighborhood" do |n| - = n.input :name - = n.input :bounds, :input_html => { :class => "polygon" } - - = f.buttons - - \ No newline at end of file diff --git a/app/views/administration/feeds/edit.haml b/app/views/administration/feeds/edit.haml deleted file mode 100644 index 061f886f6..000000000 --- a/app/views/administration/feeds/edit.haml +++ /dev/null @@ -1,21 +0,0 @@ - -- content_for(:current_page, "Editing #{@feed.name}") - -- semantic_form_for @feed do |f| - - f.inputs do - = f.input :community - = f.input :name - - f.inputs do - = f.input :about - = f.input :phone - = f.input :website - = f.input :category - = f.input :tag_list - = f.input :claimed - = f.input :code - - - f.inputs :for => :avatar do |a| - = a.input :image, :as => :file - - = f.buttons - diff --git a/app/views/administration/feeds/index.haml b/app/views/administration/feeds/index.haml deleted file mode 100644 index c9498c04a..000000000 --- a/app/views/administration/feeds/index.haml +++ /dev/null @@ -1,18 +0,0 @@ - -- content_for(:current_page, 'All Feeds') - -%table - %tr.even - %th Name - %th Created - %th Claimed - %th Edit - %th Delete - - - @feeds.each do |feed| - %tr{:class => cycle('odd','even')} - %td= link_to feed.name, feed - %td= feed.created_at.to_s(:date_time12) - %td= feed.claimed ? "claimed" : "unclaimed" - %td= link_to 'edit', edit_feed_path(feed) - %td= button_to 'delete', feed_path(feed), :method => :delete diff --git a/app/views/administration/feeds/new.haml b/app/views/administration/feeds/new.haml deleted file mode 100644 index 8f91f5936..000000000 --- a/app/views/administration/feeds/new.haml +++ /dev/null @@ -1,21 +0,0 @@ - -- content_for(:current_page, "New Feed") - -- semantic_form_for @feed do |f| - - f.inputs do - = f.input :community - = f.input :name - = f.input :about - = f.input :phone - = f.input :website - = f.input :category - = f.input :tag_list - = f.input :claimed, :checked => false - = f.input :code - - f.inputs :for => :avatar do |a| - = a.input :image, :as => :file - - f.inputs :for => :profile_fields do |pf| - = pf.input :subject - = pf.input :body - - = f.buttons diff --git a/app/views/administration/feeds/show.haml b/app/views/administration/feeds/show.haml deleted file mode 100644 index 23276149c..000000000 --- a/app/views/administration/feeds/show.haml +++ /dev/null @@ -1,12 +0,0 @@ - -- content_for(:current_page, "Viewing #{@feed.name}") - -#statistics - %dl - %dt - Events: - %dd= @feed.events.count - %dt - Subscribers: - %dd= @feed.subscribers.count - diff --git a/app/views/administration/show.haml b/app/views/administration/show.haml deleted file mode 100644 index 458b3ffe8..000000000 --- a/app/views/administration/show.haml +++ /dev/null @@ -1,2 +0,0 @@ -%p This is the administration index - diff --git a/app/views/announcements/_announcement.haml b/app/views/announcements/_announcement.haml deleted file mode 100644 index 49c2b4266..000000000 --- a/app/views/announcements/_announcement.haml +++ /dev/null @@ -1,7 +0,0 @@ -%div{'data-href' => announcement_path(announcement), :class => "announcement tooltip", 'data-title' => "Click to see replies and this community feed's profile"} - = image_tag announcement.owner.avatar.url(:thumb), :class => 'avatar' - %time= announcement.time - .reply-count #{ announcement.replies.size } replies - .title= announcement.subject - .author= announcement.owner.name - .body= markdown auto_link(announcement.body, :all, :target => "_blank") \ No newline at end of file diff --git a/app/views/announcements/_list.haml b/app/views/announcements/_list.haml deleted file mode 100644 index a6e8f2a01..000000000 --- a/app/views/announcements/_list.haml +++ /dev/null @@ -1,3 +0,0 @@ -%ul#wire - - @announcements.each do |announcement| - = render announcement diff --git a/app/views/announcements/index.haml b/app/views/announcements/index.haml deleted file mode 100644 index f1f088dae..000000000 --- a/app/views/announcements/index.haml +++ /dev/null @@ -1,14 +0,0 @@ -- update_content "#syndicate" do - #syndicate - %h3= t "syndicate.announcements.title" - = render 'shared/items', :items => @items - -- update_content "#say-something" do - #say-something - %h2= t "posts.new.title" - %nav - = link_to t("posts.new.link"), new_post_path, :class => "current", 'data-remote' => true - = link_to t("events.new.link"), new_event_path, 'data-remote' => true - = link_to t("feeds.new.link"), new_feed_url - = link_to t("invites.new.link"), new_invites_path, 'data-remote' => true - = render 'posts/form', :post => Post.new \ No newline at end of file diff --git a/app/views/announcements/new.haml b/app/views/announcements/new.haml deleted file mode 100644 index 712ed09e3..000000000 --- a/app/views/announcements/new.haml +++ /dev/null @@ -1,21 +0,0 @@ -= update_content "#say-something" do - #say-something - %h2= t ".title" - = render 'shared/say_something_nav', :current => :announcement - - if current_user.managable_feeds.empty? - .information - %p Only community feeds can send out announcements to the entire community. If you are a community organization or plan to send out many announcements and events to the community, consider creating a Community Feed: - - = link_to image_tag('create-a-feed.png'), new_feed_path, :class => 'create-a-feed' - - %p To post only to your neighborhood, #{ link_to 'click here to post to your neighborhood board right now.', new_post_path, 'data-remote' => true } - - - else - - - semantic_form_for @announcement, :html => {'data-remote' => true} do |f| - - f.inputs do - = f.input :feed, :collection => current_user.managable_feeds - = f.input :subject - = f.input :body, :input_html => { :rows => 5, :cols => 21 } - - f.buttons do - = f.commit_button :button_html => {:type => "image", :src => "/images/buttons/post-now.png"} diff --git a/app/views/announcements/show.haml b/app/views/announcements/show.haml deleted file mode 100644 index 611c0fff1..000000000 --- a/app/views/announcements/show.haml +++ /dev/null @@ -1,10 +0,0 @@ - -- update_content "#information" do - = render 'feeds/info_box', :feed => @announcement.feed - -= render 'shared/tooltip' - -- unless xhr? - - update_content "#syndicate" do - #syndicate - = render 'shared/items', :items => [@announcement] \ No newline at end of file diff --git a/app/views/bootstraps/_exceptional.haml b/app/views/bootstraps/_exceptional.haml new file mode 100644 index 000000000..8f9d3327b --- /dev/null +++ b/app/views/bootstraps/_exceptional.haml @@ -0,0 +1,5 @@ +- if Rails.env.production? + = javascript_include_tag "https://exceptional-js.heroku.com/exceptional.js" + :javascript + Exceptional.setHost('exceptional-api.heroku.com'); + Exceptional.setKey('0556a141945715c3deb50a0288ec3bea5417f6bf'); diff --git a/app/views/bootstraps/community.haml b/app/views/bootstraps/community.haml new file mode 100644 index 000000000..e2d24b7a6 --- /dev/null +++ b/app/views/bootstraps/community.haml @@ -0,0 +1,48 @@ +!!! 5 +%html{:xmlns => "https://www.w3.org/1999/xhtml", 'xmlns:fb' => "https://www.facebook.com/2008/fbml"} + %head + = render :partial => 'layouts/common_head', :locals => { :javascripts => ["main_page"], :stylesheets => ["main_page"], :title => 'Community', :track => 'Community Home Page' } + + %body{ :class => "application #{params[:controller].gsub(/\//,"_")} #{params[:action]} #{ logged_in? ? "logged_in" : "logged_out" } #{ current_community.is_college ? "is_college" : ""}"} + + #wrapper + + = render 'shared/admin_bar' + + = render 'layouts/header' + + - if !current_community.has_launched? + .prelaunch-notification + Hey #{current_user.first_name}, welcome to #{current_community.name} CommonPlace! We're officially + launching on #{current_community.launch_date.strftime("%B")} #{current_community.launch_date.day.ordinalize}. + In the meantime, help us improve by inviting some more neighbors. + + #feature-switching + + #main + + #wrapper-footer + + = render "layouts/footer" + + = render "layouts/bottom_scripts" + + :javascript + //todo: move to rails helper or erb to allow indentation. + $(function() { + window.router = new MainPageRouter({ + account: CommonPlace.account, //todo: remove + community: CommonPlace.community //todo: remove + }); + + (new FeatureSwitching({ + el: $("#feature-switching") + })).render(); + + Backbone.history.start(); + + window.router.navigate(window.location.pathname, true); + }); + + + diff --git a/app/views/bootstraps/feed.haml b/app/views/bootstraps/feed.haml new file mode 100644 index 000000000..92411ceb5 --- /dev/null +++ b/app/views/bootstraps/feed.haml @@ -0,0 +1,30 @@ +!!! 5 +%html{:xmlns => "https://www.w3.org/1999/xhtml", 'xmlns:fb' => "https://www.facebook.com/2008/fbml"} + %head + = render :partial => 'layouts/common_head', :locals => { :javascripts => ["feed_page"], :stylesheets => ["feed_page"], :title => 'Feeds', :track => 'Feed Page' } + + %body + #sticky-wrapper + + = render 'layouts/header' + + #main + #feed + + #right-column + #feeds-list + + %div{:style => "clear: both"} + + = render 'layouts/footer' + + + :javascript + $(function() { + $.getJSON("/api/account", function(account_response) { + $.getJSON("/api/communities/#{current_community.id}/feeds", function(feeds) { + new FeedPageRouter({ account: new Account(account_response), community: #{serialize(current_community)}, feed: "#{params[:id]}", feeds: feeds }); + Backbone.history.start(); + }); + }); + }); diff --git a/app/views/bootstraps/group.html.erb b/app/views/bootstraps/group.html.erb new file mode 100644 index 000000000..489221b34 --- /dev/null +++ b/app/views/bootstraps/group.html.erb @@ -0,0 +1,45 @@ + + + + + <%= render :partial => 'layouts/common_head', + :locals => { :javascripts => ["group_page"], + :stylesheets => ["group_page"], + :title => 'Groups', + :track => 'Groups Page' } %> + + + + + <%= render "layouts/header" %> + +
          + +
          + +
          +
          +
          +
          +
          +
          + + <%= render "layouts/footer" %> + + + + + + diff --git a/app/views/bootstraps/inbox.haml b/app/views/bootstraps/inbox.haml new file mode 100644 index 000000000..e8b9f26a8 --- /dev/null +++ b/app/views/bootstraps/inbox.haml @@ -0,0 +1,28 @@ +!!! 5 +%html{:xmlns => "https://www.w3.org/1999/xhtml", 'xmlns:fb' => "https://www.facebook.com/2008/fbml"} + %head + = render :partial => 'layouts/common_head', :locals => { :javascripts => ["inbox"], :stylesheets => ["inbox"], :title => 'Inbox', :track => 'Inbox' } + + %body + #sticky-wrapper + + = render 'layouts/header' + + #main + #inbox-nav + #inbox + + %div{:style => "clear: both"} + + = render 'layouts/footer' + + + :javascript + $(function() { + $.getJSON("/api/account", function(account_response) { + new InboxRouter({ account: new Account(account_response), community: #{serialize(current_community)} }); + Backbone.history.start(); + }); + }); + + diff --git a/app/views/claims/_profile_form.haml b/app/views/claims/_profile_form.haml deleted file mode 100644 index 414bd1305..000000000 --- a/app/views/claims/_profile_form.haml +++ /dev/null @@ -1,12 +0,0 @@ - -%div.claim - - semantic_form_for @feed, :url => feed_claim_path(@feed), :html => { :class => "management_form", :method => :put } do |f| - %h3.management_field Edit #{ @feed.name }'s profile - - f.inputs do - = f.input :name - = f.input :about, :input_html => {:rows => 4, :cols => 40} - = f.input :phone - = f.input :website - = f.input :tag_list, :as => :string, :label => "Tag list", :hint => "For example: outdoors, skiing" - - f.buttons do - = f.commit_button "Continue" diff --git a/app/views/claims/new.haml b/app/views/claims/new.haml deleted file mode 100644 index 78450bbf7..000000000 --- a/app/views/claims/new.haml +++ /dev/null @@ -1,8 +0,0 @@ -- render :layout => 'shared/modal' do - %hr.staple - - - form_tag feed_claim_path(@feed) do - = label_tag "Enter your claim code:" - = text_field_tag :code - = submit_tag "Submit" - %p If you do not have a code, please email Max Novendstern at Max@CommonPlaceUSA.com diff --git a/app/views/communities/_list.haml b/app/views/communities/_list.haml deleted file mode 100644 index df1f52979..000000000 --- a/app/views/communities/_list.haml +++ /dev/null @@ -1 +0,0 @@ -= render 'shared/list', :items => current_user.wire \ No newline at end of file diff --git a/app/views/communities/_right.haml b/app/views/communities/_right.haml deleted file mode 100644 index c0a61a875..000000000 --- a/app/views/communities/_right.haml +++ /dev/null @@ -1,47 +0,0 @@ -#welcome_top - #welcome_neighbor.intro_post> - %hr - %h2 Welcome, Neighbor! - %p CommonPlace is a place for neighbors. Take a look at what your neighbors have to say and reply or post your own messages. - - #recommended_posts.intro_post - %hr - %h2.right_side= "Recommended Posts".upcase - %ul#recommended - %li - %h3= "Free Lassi".upcase - %p Hey everyone, come by the... - %li - %h3= "Lunch Meeting".upcase - %p Yo guys, we're throwing a... - %li - %h3= "Yard Sale".upcase - %p Wow! We're having an... - -#welcome_bottom - #featured_posts - %h2.right_side= "Featured Posts".upcase - %ul - %li - %h3 Featured Post - .author Your Neighbor - %p Hey everyone, we're throwing a potluck lunch event next week for everyone to get a chance to meet each other. It would be great if everyone could reply and say what they're bringing! - - %li - %h3 Featured Post Two Lines - .author Ashley Costelloe - %p Hey guys, I'm just trying to put this new sattelite dish up on my roof, but I don't have a ladder! Anyone have one I can borrow for the afternoon? (You might end up with some cookies later) - - %li - %h3 Free Lassi - .author Punjabi Dhaba - %p Hey everyone, come by the restaurant today between 4PM and 6PM for a free lassi with any dinner special! Your choice of plain, mango, or pineapple. - - - - #neighborhood_activity - %h2.right_side= "Your Neighborhood Activity".upcase - #map_container - = render_map do |map| - - map.center current_user - - map.marker current_user \ No newline at end of file diff --git a/app/views/communities/faq.haml b/app/views/communities/faq.haml new file mode 100644 index 000000000..922816a25 --- /dev/null +++ b/app/views/communities/faq.haml @@ -0,0 +1,177 @@ += javascript_include_tag "faq" + +%h1= t ".h1" +#notice + = flash[:warning] || flash[:notice] +#main + #sections_picker + %h2 Quick links + %ul + %li= link_to "Top of the page", "#content" + %li= link_to "General","#1" + %li= link_to "Platform Functionality","#2" + %li= link_to "How do I... ?", "#3" + %li= link_to "Ask your Question", "#4" + #content + %h2= t".h2section" + %p= t".p1" + %ul + %li + = link_to "General", "#1" + %ul + %li= link_to(t(".q1_1_html", :community => current_community.name), "#1_1") + %li= link_to(t(".q1_2_html", :community => current_community.name), "#1_2") + %li= link_to(t(".q1_3_html", :community => current_community.name), "#1_3") + %li= link_to(t(".q1_4_html", :community => current_community.name), "#1_4") + %li= link_to(t(".q1_5_html", :community => current_community.name), "#1_5") + %li + = link_to "Platform Functionality", "#2" + %ul + %li= link_to(t(".q2_1_html", :community => current_community.name), "#2_1") + %li= link_to(t(".q2_2_html", :community => current_community.name), "#2_2") + %li= link_to(t(".q2_3_html", :community => current_community.name), "#2_3") + %li= link_to(t(".q2_4_html", :community => current_community.name), "#2_4") + %li= link_to(t(".q2_5_html", :community => current_community.name), "#2_5") + %li= link_to(t(".q2_6_html", :community => current_community.name), "#2_6") + %li= link_to(t(".q2_7_html", :community => current_community.name), "#2_7") + %li= link_to(t(".q2_8_html", :community => current_community.name), "#2_8") + %li= link_to(t(".q2_9_html", :community => current_community.name), "#2_9") + %li + = link_to "How do I... ?", "#3" + %ul + %li= link_to(t(".q3_1_html", :community => current_community.name), "#3_1") + %li= link_to(t(".q3_2_html", :community => current_community.name), "#3_2") + %li= link_to(t(".q3_3_html", :community => current_community.name), "#3_3") + %li= link_to(t(".q3_4_html", :community => current_community.name), "#3_4") + %li= link_to(t(".q3_5_html", :community => current_community.name), "#3_5") + %li= link_to(t(".q3_6_html", :community => current_community.name), "#3_6") + %li= link_to(t(".q3_7_html", :community => current_community.name), "#3_7") + %li + = link_to "Ask your question", "#4" + %ul + %li= link_to "Question form", "#4" + + + #1 + %h2{ :class => "section_head"} General + #1_1 + %h3{ :class => "item_head"}= t ".q1_1_html", :community => current_community.name + .answer + %p= t ".a1_1_html", :community => current_community.name + #1_2 + %h3{ :class => "item_head"}= t ".q1_2_html", :community => current_community.name + .answer + %p= t ".a1_2_html", :community => current_community.name + #1_3 + %h3{:class => "item_head"}= t ".q1_3_html", :community => current_community.name + .answer + %p= t ".a1_3_html", :community => current_community.name + #1_4 + %h3{:class => "item_head"}= t ".q1_4_html", :community => current_community.name + .answer + %p= t ".a1_4_1_html", :community => current_community.name, :organizer => current_community.organizer_name, :organizer_firstname => current_community.organizer_name.split(' ').first, :organizer_email => current_community.organizer_email + %p= t ".a1_4_2_html", :community => current_community.name, :starter_site => link_to("CommonPlaceUSA.com" , "http://CommonPlaceUSA.com/") + #1_5 + %h3{:class=>"item_head"}=t ".q1_5_html", :community => current_community.name + .answer + %p= t ".a1_5_html", :community => current_community.name + + + #2 + %h2{ :class => "section_head"} Platform Functionality + #2_1 + %h3{ :class => "item_head"}= t ".q2_1_html", :community => current_community.name + .answer + %p= t ".a2_1_1_html", :community => current_community.name + %p= t ".a2_1_2_html", :community => current_community.name + %p= t ".a2_1_3_html", :community => current_community.name + #2_2 + %h3{ :class => "item_head"}= t ".q2_2_html", :community => current_community.name + .answer + %p= t ".a2_2_html", :community => current_community.name + #2_3 + %h3{ :class => "item_head"}= t ".q2_3_html", :community => current_community.name + .answer + %p= t ".a2_3_html", :community => current_community.name, :organizer_email => current_community.name + #2_4 + %h3{:class => "item_head"}= t ".q2_4_html", :community => current_community.name + .answer + %p= t ".a2_4_1_html", :community => current_community.name + %p= t ".a2_4_2_html", :community => current_community.name + %p= t ".a2_4_3_html", :community => current_community.name + %p= t ".a2_4_4_html", :community => current_community.name + %p= t ".a2_4_5_html", :community => current_community.name, :email_notification_settings_page => link_to("email notification settings page", "/account/edit") + #2_5 + %h3{ :class => "item_head"}= t ".q2_5_html", :community => current_community.name + .answer + %p= t ".a2_5_html", :community => current_community.name + #2_6 + %h3{ :class => "item_head"}= t ".q2_6_html", :community => current_community.name + .answer + %p= t ".a2_6_1_html", :community => current_community.name + %p= t ".a2_6_2_html", :community => current_community.name + #2_7 + %h3{ :class => "item_head"}= t ".q2_7_html", :community => current_community.name + .answer + %p= t ".a2_7_html", :community => current_community.name + #2_8 + %h3{ :class => "item_head"}= t ".q2_8_html", :community => current_community.name + .answer + %p= t ".a2_8_html", :community => current_community.name + #2_9 + %h3{ :class => "item_head"}= t ".q2_9_html", :community => current_community.name + .answer + %p= t ".a2_9_1_html", :community => current_community.name + %p= t ".a2_9_2_html", :community => current_community.name, :starter_site => link_to("CommonPlaceUSA.com", "http://CommonPlaceUSA.com/") + + + #3 + %h2{ :class => "section_head"} How do I... ? + #3_1 + %h3{ :class => "item_head"}= t ".q3_1_html", :community => current_community.name + .answer + %p= t ".a3_1_html", :community => current_community.name, :invite_link => link_to("here (you must be logged in)", "/invite") + #3_2 + %h3{ :class => "item_head"}= t ".q3_2_html", :community => current_community.name + .answer + %p= t ".a3_2_html", :community => current_community.name + #3_3 + %h3{:class => "item_head"}= t ".q3_3_html", :community => current_community.name + .answer + %p= t ".a3_3_html", :community => current_community.name, :create_feed=> link_to("here", "/feeds_registrations/new") + #3_4 + %h3{:class => "item_head"}= t ".q3_4_html", :community => current_community.name + .answer + %p= t ".a3_4_html", :community => current_community.name, :lost_password => link_to("here", "/users/password/new") + #3_5 + %h3{:class => "item_head"}= t ".q3_5_html", :community => current_community.name + .answer + %p= t ".a3_5_html", :community => current_community.name + #3_6 + %h3{:class => "item_head"}= t ".q3_6_html", :community => current_community.name + .answer + %p= t ".a3_6_html", :community => current_community.name, :organizer_name => current_community.organizer_name, :organizer_email => current_community.organizer_email + #3_7 + %h3{:class => "item_head"}= t ".q3_7_html", :community => current_community.name + .answer + %p= t ".a3_7_html", :community => current_community.name, :new_community => link_to("form", "#"), :commonplace_usa => link_to("CommonPlaceUSA", "http://CommonPlaceUSA.com/") + + + + #4 + %h2{ :class => "section_head"} Question form + = form_tag faq_url do + %label{ :for => "email" } Your Email Address: + %br + =text_field_tag :email_address + %br + %label{ :for => "name" } Your Name: + %br + =text_field_tag :name + %br + %label{ :for => "message" } Your Message: + %br + =text_area_tag :message + %br + =submit_tag "Submit", :class => "submit_button" + .cleared diff --git a/app/views/communities/good_neighbor_discount.haml b/app/views/communities/good_neighbor_discount.haml new file mode 100644 index 000000000..488665d5f --- /dev/null +++ b/app/views/communities/good_neighbor_discount.haml @@ -0,0 +1,38 @@ +#planewrapper + #plane + %h1 + = t(".htitle") + %br + %h2 + = t(".title") + +#gbox + %h1 + = t(".card_msg") + +#card_box + = image_tag "cards/#{current_community.slug.downcase}.png", :alt => "Community Card" + +#gbox + %h1 + = t(".business_msg") + +#businessinformation + %table + %tr{:id=>"tablerow1"} + %td{:id=>"tablecol1"} + %ul{:id=>"infolist1"} + %li= t(".1li1") + %li= t(".1li2") + %li= t(".1li3") + %li= t(".1li4") + %td{:id=>"tablecol2"} + %ul{:id=>"infolist2"} + %li= t(".2li1") + %li= t(".2li2") + %li= t(".2li3") + %li= t(".2li4") + +#footerinfo + %h3 + = t(".foot_msg") diff --git a/app/views/communities/new.haml b/app/views/communities/new.haml deleted file mode 100644 index bc9ef1948..000000000 --- a/app/views/communities/new.haml +++ /dev/null @@ -1,7 +0,0 @@ - - -- semantic_form_for @community do |f| - = f.inputs - = f.buttons - - \ No newline at end of file diff --git a/app/views/communities/search.haml b/app/views/communities/search.haml deleted file mode 100644 index 7e863823d..000000000 --- a/app/views/communities/search.haml +++ /dev/null @@ -1,6 +0,0 @@ - -#list - = render 'shared/list', :items => @results - -#info - \ No newline at end of file diff --git a/app/views/communities/show.haml b/app/views/communities/show.haml deleted file mode 100644 index 1a044f922..000000000 --- a/app/views/communities/show.haml +++ /dev/null @@ -1,11 +0,0 @@ - -- update_content "#syndicate" do - #syndicate - %h3= link_to t("syndicate.posts.title"), :posts - = render 'shared/items', :items => @posts - - %h3= link_to t("syndicate.events.title"), :events - = render 'shared/items', :items => @events - - %h3= link_to t("syndicate.announcements.title"), :events - = render 'shared/items', :items => @announcements diff --git a/app/views/deliveries/index.haml b/app/views/deliveries/index.haml deleted file mode 100644 index a926a623c..000000000 --- a/app/views/deliveries/index.haml +++ /dev/null @@ -1,12 +0,0 @@ - -- @deliveries.each do |delivery| - %div - %dl - %dt To: - %dd= delivery.to - %dt From: - %dd= delivery.from - %dt Subject: - %dd= delivery.subject - %dt Body: - %dd= delivery.body diff --git a/app/views/events/_calendar.haml b/app/views/events/_calendar.haml deleted file mode 100644 index 17fc11b22..000000000 --- a/app/views/events/_calendar.haml +++ /dev/null @@ -1,2 +0,0 @@ -.month= Date::ABBR_MONTHNAMES[event.date.mon] -.date= event.date.day \ No newline at end of file diff --git a/app/views/events/_event.haml b/app/views/events/_event.haml deleted file mode 100644 index 1ebb5f029..000000000 --- a/app/views/events/_event.haml +++ /dev/null @@ -1,7 +0,0 @@ -%div{'data-href' => event_path(event), :class => "event tooltip", 'data-title' => 'Click to see replies and learn more.'} - .small_calendar.avatar= render 'events/calendar', :event => event - %time= event_date event.start_datetime - .reply-count #{event.replies.size} replies - .title= event.subject - .author= event.owner.name - .body= markdown event.body \ No newline at end of file diff --git a/app/views/events/_info_box.haml b/app/views/events/_info_box.haml deleted file mode 100644 index 2fb9b1360..000000000 --- a/app/views/events/_info_box.haml +++ /dev/null @@ -1,36 +0,0 @@ - -#information.info_box.event - %h2 Community Event - - #big_calendar.avatar= render 'events/calendar', :event => event - - - link_to new_event_message_path(event), :class => "message_me", 'data-remote' => true, 'data-title' => "Click to send a private message to #{ event.name }" do - %span Message #{ event.name } - - %h3= event.name - .info= markdown event.description - %table - %tr - %th TAGS - %td event.tag_list - %tr - %th STARTS - %td= event.start_time - %tr - %th ENDS - %td= event.end_time - %tr - %th ADDRESS - %td= event.address - - .clear - - - if event.address.present? - = render_map do |map| - - map.center event - - map.marker current_user - - map.marker event - - map.directions do |dir| - - dir.origin current_user - - dir.destination event - \ No newline at end of file diff --git a/app/views/events/_list.haml b/app/views/events/_list.haml deleted file mode 100644 index b7cd92014..000000000 --- a/app/views/events/_list.haml +++ /dev/null @@ -1,5 +0,0 @@ -%ul#wire - - @events.each do |event| - = render event - - diff --git a/app/views/events/index.haml b/app/views/events/index.haml deleted file mode 100644 index 5bcf9e036..000000000 --- a/app/views/events/index.haml +++ /dev/null @@ -1,14 +0,0 @@ -- update_content "#syndicate" do - #syndicate - %h3= t "syndicate.events.title" - = render 'shared/items', :items => @items - -- update_content "#say-something" do - #say-something - %h2= t "posts.new.title" - %nav - = link_to t("posts.new.link"), new_post_path, :class => "current", 'data-remote' => true - = link_to t("events.new.link"), new_event_path, 'data-remote' => true - = link_to t("feeds.new.link"), new_feed_url - = link_to t("invites.new.link"), new_invites_path, 'data-remote' => true - = render 'posts/form', :post => Post.new diff --git a/app/views/events/new.haml b/app/views/events/new.haml deleted file mode 100644 index a7851a36b..000000000 --- a/app/views/events/new.haml +++ /dev/null @@ -1,24 +0,0 @@ -= update_content "#say-something" do - #say-something - %h2= t ".title" - = render 'shared/say_something_nav', :current => :event - - semantic_form_for @event, :html => {'data-remote' => true} do |f| - - f.inputs do - %li - %label{:for => "event_feed", :class => "select required"} - Post as: - = f.select :owner, options_for_select(creation_feeds_for(current_user), dom_id(current_user)) - = f.input :name - = f.input :description, :input_html => { :rows => 5, :cols => 21} - = f.input :date, :as => :string, :input_html => {:class => 'date'} - = event_select_time('start_time') - = f.input :address - %fieldset - %ol - %li.check_boxes - = f.label :pledge, "I pledge that this is a public, face-to-face event." - = f.check_box :pledge, :label => "I pledge..." - - - - f.buttons do - = f.commit_button :button_html => {:type => "image", :src => "/images/buttons/post-now.png"} \ No newline at end of file diff --git a/app/views/events/show.haml b/app/views/events/show.haml deleted file mode 100644 index 36de3ebc8..000000000 --- a/app/views/events/show.haml +++ /dev/null @@ -1,10 +0,0 @@ - -- update_content "#information" do - = render "info_box", :event => @event - -= render 'shared/tooltip' - -- unless xhr? - - update_content "#syndicate" do - #syndicate - = render 'shared/items', :items => [@event] diff --git a/app/views/facebook_canvas/index.haml b/app/views/facebook_canvas/index.haml new file mode 100644 index 000000000..3840e4ac4 --- /dev/null +++ b/app/views/facebook_canvas/index.haml @@ -0,0 +1,15 @@ += javascript_include_tag "application" += javascript_include_tag "https://connect.facebook.net/en_US/all.js#xfbml=1" +%script + $(document).ready(function() { + FB.init({ + appId : "#{($FacebookConfig || {})['app_id']}", + status : true, + cookie : true, + xfbml : true + }); + s = FB.getSession(); + = "FB.api('/#{@request_id}?access_token=#{@application_token}', 'GET', {access_token: '#{@application_token}'}, function(r) { $('#join').attr('href','http://www.ourcommonplace.com/' + r.data); });" + }); += link_to(image_tag("http://www.ourcommonplace.com/images/join-commonplace.png"),"https://www.ourcommonplace.com/", :target => "_top", :id => "join") +#fb-root diff --git a/app/views/feed_registrations/avatar.haml b/app/views/feed_registrations/avatar.haml new file mode 100644 index 000000000..e1361f1b3 --- /dev/null +++ b/app/views/feed_registrations/avatar.haml @@ -0,0 +1,14 @@ + +#main + %h1 Crop your Organization Photo + + = form_for registration.feed, :url => crop_avatar_feed_registration_url(registration), :method => :put, :html => { :class => "crop" } do |f| + = image_tag registration.avatar_url(:original), :id => "cropbox" + + + - [:crop_x, :crop_y, :crop_w, :crop_h].each do |attribute| + = f.hidden_field attribute, :id => attribute + + = f.submit :type => "image", :src => asset_path("buttons/continue2.png") + + diff --git a/app/views/feed_registrations/new.haml b/app/views/feed_registrations/new.haml new file mode 100644 index 000000000..e91843d3e --- /dev/null +++ b/app/views/feed_registrations/new.haml @@ -0,0 +1,33 @@ + +%h1 Create a Community Feed + +%ul#registration_information + + %li + Any organization, business, city service or cause can create a + "Community Feed" on CommonPlace. + + %li + This allows you to send out announcements and events from your + organization to everyone on CommonPlace. + + %li + People can subscribe to your community feed and receive your + announcements and events by email. + += semantic_form_for registration.feed, :url => feed_registrations_url, :html => {:multipart => true} do |f| + + = f.inputs do + = f.input :name, :input_html => {'autocomplete' => "off"} + + = f.input(:slug, :label => "Feed address:", :hint => "Your feed address serves as your CommonPlace URL: www.OurCommonPlace.com/pages/____

          Also, you can send announcements by email using this email address: ____@OurCommonPlace.com") + + - unless current_community.is_college + = f.input :kind, :as => :radio, :collection => Feed.kinds.to_hash + + = f.input :avatar, :input_html => {:size => 20} + + = f.input :feed_url, :label => "To pull Announcements from an RSS feed, type in your RSS URL here:", :as => :string + + = f.buttons do + = f.commit_button :button_html => {:type => "image", :src => asset_path("buttons/continue2.png")} diff --git a/app/views/feed_registrations/profile.haml b/app/views/feed_registrations/profile.haml new file mode 100644 index 000000000..1bf85f5e3 --- /dev/null +++ b/app/views/feed_registrations/profile.haml @@ -0,0 +1,19 @@ +%h1 Build your Feed Profile + +%ul#registration_information + + %li + Fill out a community feed description so that neighbors can know a bit about your organization and what types of announcements you will be sending out. + += semantic_form_for registration.feed, :url => add_profile_feed_registration_url(registration), :method => :put, :html => {:multipart => true} do |f| + + = f.inputs do + = f.input :about + = f.input :website, :as => :string + = f.input :address + = f.input :phone, :as => :string + + + = f.buttons do + = f.commit_button :button_html => {:type => "image", :src => asset_path("buttons/continue2.png")} + diff --git a/app/views/feed_registrations/subscribers.haml b/app/views/feed_registrations/subscribers.haml new file mode 100644 index 000000000..2bbf459de --- /dev/null +++ b/app/views/feed_registrations/subscribers.haml @@ -0,0 +1,18 @@ + += form_for registration.feed, :url => invite_subscribers_feed_registration_url(registration), :html => {:class => "new_subscribers"} do |f| + + %label + Add subscribers directly to your feed + + .scrolling-table-container + %table + %tr + %th Subscriber Name + %th Email Address + + - 20.times do + %tr.input-row + %td.name{contenteditable: true}... + %td.email{contenteditable: true}... + + = f.submit :type => "image", :src => asset_path("buttons/continue2.png") \ No newline at end of file diff --git a/app/views/feeds/_feed.haml b/app/views/feeds/_feed.haml deleted file mode 100644 index ee5ceda81..000000000 --- a/app/views/feeds/_feed.haml +++ /dev/null @@ -1,3 +0,0 @@ -%div{'data-href' => feed_path(feed), :class => "feed tooltip", 'data-title' => 'Click to see more information about this feed'} - = image_tag feed.avatar.url(:thumb), :class => "avatar" - .title= feed.name \ No newline at end of file diff --git a/app/views/feeds/_form.haml b/app/views/feeds/_form.haml deleted file mode 100644 index 451320580..000000000 --- a/app/views/feeds/_form.haml +++ /dev/null @@ -1,17 +0,0 @@ - -- semantic_form_for feed do |f| - - = render 'preview', :feed => feed - - - f.inputs do - = f.input :name - = f.input :about - = f.input :avatar, :input_html => {:size => 20} - = f.input :tag_list - - f.inputs :contact_information, :id => 'contact-info' do - = f.input :website - = f.input :phone - = f.input :address - - f.buttons do - = f.commit_button :button_html => {:type => "image", :src => "/images/buttons/continue.png"} - diff --git a/app/views/feeds/_info_box.haml b/app/views/feeds/_info_box.haml deleted file mode 100644 index 94a6479b5..000000000 --- a/app/views/feeds/_info_box.haml +++ /dev/null @@ -1,34 +0,0 @@ - -#information.info_box.feed - %h2 Community Feed - - = image_tag feed.avatar.url(:normal), :width => 120, :class => "avatar" - - - link_to new_feed_message_path(feed), :class => "message_me", 'data-remote' => true, 'data-title' => "Click to send a private message to #{ feed.name }" do - %span Message #{ feed.name } - - %h3= feed.name - .info= markdown feed.about - - %table - %tr - %th TAGS - %td= feed.tag_list - %tr - %th WEBSITE - %td= link_to feed.website, feed.website, :target => '_blank' - %tr - %th PHONE - %td= feed.phone - %tr - %th ADDRESS - %td= feed.address - - .clear - - - if feed.address.present? - = render_map do |map| - - map.center feed - - map.marker feed - - diff --git a/app/views/feeds/_list.haml b/app/views/feeds/_list.haml deleted file mode 100644 index 5d58e9b87..000000000 --- a/app/views/feeds/_list.haml +++ /dev/null @@ -1,4 +0,0 @@ -%ul#wire - - @feeds.each do |feed| - = render feed - \ No newline at end of file diff --git a/app/views/feeds/_preview.haml b/app/views/feeds/_preview.haml deleted file mode 100644 index 169d69881..000000000 --- a/app/views/feeds/_preview.haml +++ /dev/null @@ -1,23 +0,0 @@ -#preview.info_box - %h2 Community Feed - - = image_tag feed.avatar.url(:normal), :width => 120, :class => "avatar" - - %h3{'data-track' => 'feed[name]'}= feed.name.present? ? feed.name : t("feeds.preview.name") - .info{'data-track' => 'feed[about]'}= feed.about.present? ? feed.about : t("feeds.preview.about") - - %table - %tr - %th TAGS - %td{'data-track' => 'feed[tag_list]'}= feed.tag_list.present? ? feed.tag_list : t("feeds.preview.tag_list") - %tr - %th WEBSITE - %td{'data-track' => 'feed[website]'}= feed.website.present? ? feed.website : t("feeds.preview.website") - %tr - %th PHONE - %td{'data-track' => 'feed[phone]'}= feed.phone.present? ? feed.phone : t("feeds.preview.phone") - %tr - %th ADDRESS - %td{'data-track' => 'feed[address]'}= feed.address.present? ? feed.address : t("feeds.preview.address") - - .clear \ No newline at end of file diff --git a/app/views/feeds/announcements/_announcement.haml b/app/views/feeds/announcements/_announcement.haml deleted file mode 100644 index 9b3f9e84b..000000000 --- a/app/views/feeds/announcements/_announcement.haml +++ /dev/null @@ -1,2 +0,0 @@ - -= render 'announcements/announcement', :announcement => announcement diff --git a/app/views/feeds/announcements/_form.haml b/app/views/feeds/announcements/_form.haml deleted file mode 100644 index 6f2ed96e1..000000000 --- a/app/views/feeds/announcements/_form.haml +++ /dev/null @@ -1,12 +0,0 @@ -- semantic_form_for [feed, announcement], :html => {:id => "post-to-feed", 'data-remote' => true} do |f| - = link_to "Import from rss or Twitter", import_feed_path(feed), 'data-remote' => true, :class => 'import' - %h2 - = t("feeds.profile.post.title") - %nav - %ul - %li= link_to "Announcement", new_feed_announcement_path(feed), 'data-remote' => true - %li= link_to "Event", new_feed_event_path(feed), 'data-remote' => true - - = f.inputs :subject, :body - - f.buttons do - = f.commit_button :button_html => {:type => "image", :src => "/images/buttons/red-post-now-button.png"} \ No newline at end of file diff --git a/app/views/feeds/announcements/create.haml b/app/views/feeds/announcements/create.haml deleted file mode 100644 index 3f81cbe4f..000000000 --- a/app/views/feeds/announcements/create.haml +++ /dev/null @@ -1,12 +0,0 @@ - -= update_content "#post-to-feed" do - = render 'form', :feed => @feed, :announcement => Announcement.new - - -= update_content "#recent-posts" do - #recent-posts - %h2= t 'feeds.profile.recent_posts.title' - - if @feed.announcements.empty? && @feed.events.empty? - .no-recent-posts No Recent Posts - - else - = render 'shared/items', :items => @feed.wire diff --git a/app/views/feeds/announcements/new.haml b/app/views/feeds/announcements/new.haml deleted file mode 100644 index 7a9ddabdd..000000000 --- a/app/views/feeds/announcements/new.haml +++ /dev/null @@ -1,2 +0,0 @@ -= update_content "#post-to-feed" do - = render 'form', :feed => @feed, :announcement => @announcement diff --git a/app/views/feeds/avatar.haml b/app/views/feeds/avatar.haml new file mode 100644 index 000000000..3015a1bcf --- /dev/null +++ b/app/views/feeds/avatar.haml @@ -0,0 +1,14 @@ + +#main + %h1 Crop your Organization Photo + + = form_for @feed, :url => crop_avatar_feed_url(@feed), :method => :put, :html => { :class => "crop" } do |f| + = image_tag @feed.avatar_url(:original), :id => "cropbox" + + + - [:crop_x, :crop_y, :crop_w, :crop_h].each do |attribute| + = f.hidden_field attribute, :id => attribute + + = f.submit :type => "image", :src => asset_path("buttons/continue2.png") + + diff --git a/app/views/feeds/create.haml b/app/views/feeds/create.haml deleted file mode 100644 index 9524f3afd..000000000 --- a/app/views/feeds/create.haml +++ /dev/null @@ -1,7 +0,0 @@ - -= update_content "#post-to-feed" do - = render 'form', :feed => @feed, :announcement => Announcement.new - - -= update_content "#recent-posts .items" do - = render 'shared/items', :items => @feed.wire \ No newline at end of file diff --git a/app/views/feeds/delete.haml b/app/views/feeds/delete.haml new file mode 100644 index 000000000..a3ff2d3a4 --- /dev/null +++ b/app/views/feeds/delete.haml @@ -0,0 +1,8 @@ + + += semantic_form_for @feed, :url => feed_path(@feed), :html => {:method => :delete} do |f| + %p If you delete your feed everything will be removed, including your feed's announcements and events. If you're absolutely sure you want to remove your feed, press the following button. + + = f.buttons do + = f.commit_button "Fully remove my feed from CommonPlace" + diff --git a/app/views/feeds/edit.haml b/app/views/feeds/edit.haml index 1c4e0a99f..49e92fdc8 100644 --- a/app/views/feeds/edit.haml +++ b/app/views/feeds/edit.haml @@ -1,5 +1,9 @@ - %h1= t ".h1" -= update_content "form.feed" do - = render 'form', :feed => @feed \ No newline at end of file += semantic_form_for @feed, :html => {:multipart => true, :class => "edit_feed"} do |f| + + = f.inputs do + = f.input :avatar, :input_html => {:size => 20} + + = f.buttons do + = f.commit_button :button_html => {:type => "image", :src => asset_path("buttons/continue2.png")} diff --git a/app/views/feeds/edit_owner.haml b/app/views/feeds/edit_owner.haml new file mode 100644 index 000000000..a461f8f44 --- /dev/null +++ b/app/views/feeds/edit_owner.haml @@ -0,0 +1,11 @@ + + += form_tag update_owner_feed_url(@feed), :method => :put, :class => "feed" do + %p To change the owner of this feed, enter the email address associated with the account that will be the new owner. + - if @error + %p.error We could not find an account with that email address. + = label_tag "email", "Email:" + = text_field_tag "email" + .buttons + = submit_tag "Change Owner" + diff --git a/app/views/feeds/events/_event.haml b/app/views/feeds/events/_event.haml deleted file mode 100644 index 9363fe7a2..000000000 --- a/app/views/feeds/events/_event.haml +++ /dev/null @@ -1,2 +0,0 @@ - -= render 'events/event', :event => event \ No newline at end of file diff --git a/app/views/feeds/events/_form.haml b/app/views/feeds/events/_form.haml deleted file mode 100644 index 1959abd6c..000000000 --- a/app/views/feeds/events/_form.haml +++ /dev/null @@ -1,18 +0,0 @@ -- semantic_form_for [feed, event], :html => {:id => "post-to-feed", 'data-remote' => true} do |f| - %h2 - = t("feeds.profile.post.title") - %nav - %ul - %li= link_to "Event", new_feed_event_path(feed), 'data-remote' => true - %li= link_to "Announcement", new_feed_announcement_path(feed), 'data-remote' => true - - - f.inputs do - = f.input :name - = f.input :description, :input => {:rows => 3} - = f.input :date, :as => :string, :input_html => {:class => 'date'} - = event_select_time('start_time') - = event_select_time('end_time') - = f.input :address - = f.input :tag_list - - f.buttons do - = f.commit_button :button_html => {:type => "image", :src => "/images/buttons/red-post-now-button.png"} diff --git a/app/views/feeds/events/create.haml b/app/views/feeds/events/create.haml deleted file mode 100644 index 381e8998b..000000000 --- a/app/views/feeds/events/create.haml +++ /dev/null @@ -1,12 +0,0 @@ - -= update_content "#post-to-feed" do - = render 'form', :feed => @feed, :event => Event.new - - -= update_content "#recent-posts" do - #recent-posts - %h2= t 'feeds.profile.recent_posts.title' - - if @feed.announcements.empty? && @feed.events.empty? - .no-recent-posts No Recent Posts - - else - = render 'shared/items', :items => @feed.wire diff --git a/app/views/feeds/events/new.haml b/app/views/feeds/events/new.haml deleted file mode 100644 index 963ebda5a..000000000 --- a/app/views/feeds/events/new.haml +++ /dev/null @@ -1,2 +0,0 @@ -= update_content "#post-to-feed" do - = render 'form', :feed => @feed, :event => @event \ No newline at end of file diff --git a/app/views/feeds/import.haml b/app/views/feeds/import.haml deleted file mode 100644 index fc2f528f4..000000000 --- a/app/views/feeds/import.haml +++ /dev/null @@ -1,13 +0,0 @@ -= update_content "#post-to-feed" do - - semantic_form_for @feed, :html => {:id => "post-to-feed"} do |f| - %h2 - = t("feeds.profile.post.title") - %nav - %ul - %li= link_to "Announcement", new_feed_announcement_path(@feed), 'data-remote' => true - %li= link_to "Event", new_feed_event_path(@feed), 'data-remote' => true - - f.inputs do - = f.input :feed_url - = f.input :twitter_name - = f.buttons :commit => "Save" - \ No newline at end of file diff --git a/app/views/feeds/index.haml b/app/views/feeds/index.haml deleted file mode 100644 index e5a21aa69..000000000 --- a/app/views/feeds/index.haml +++ /dev/null @@ -1,4 +0,0 @@ -- update_content "#syndicate" do - #syndicate - %h3= t "syndicate.feeds.title" - = render 'shared/items', :items => @items \ No newline at end of file diff --git a/app/views/feeds/invites/new.haml b/app/views/feeds/invites/new.haml deleted file mode 100644 index def290ca8..000000000 --- a/app/views/feeds/invites/new.haml +++ /dev/null @@ -1,17 +0,0 @@ - -%h1= t ".h1" - -- form_tag feed_invites_path(@feed), :id => "new_feed_invites", :class => "invites formtastic" do - - %h2= t ".h2", :community => current_community.name - %fieldset.inputs - %ol - %li.text.emails - = label_tag :emails, t(".emails_label") - = text_area_tag :emails, "", :rows => 5 - %a{ :href => "#", :onclick => "showPlaxoABChooser('emails', '/plaxo-importer.html'); return false"} - ✚ Import emails from my address book - %fieldset.buttons - %ol - %li.commit - = submit_tag "Invite" \ No newline at end of file diff --git a/app/views/feeds/new.haml b/app/views/feeds/new.haml deleted file mode 100644 index 412868eb6..000000000 --- a/app/views/feeds/new.haml +++ /dev/null @@ -1,5 +0,0 @@ - -%h1= t ".h1" - -= update_content "#new_feed" do - = render 'form', :feed => @feed diff --git a/app/views/feeds/profile.haml b/app/views/feeds/profile.haml deleted file mode 100644 index 7e5bea7fa..000000000 --- a/app/views/feeds/profile.haml +++ /dev/null @@ -1,48 +0,0 @@ - -- render :layout => "layouts/application" do - #main - %h1 - = @feed.name - - if can?(:edit, @feed) - = link_to 'Edit', edit_feed_path(@feed), :class => 'edit' - - - #column-1<> - = image_tag @feed.avatar.url, :id => 'feed-avatar', :size => "210x210" - - if [:phone, :website, :address].any? {|method| @feed.send(method).present?} - %h3= t 'feeds.profile.contact_info.title' - #contact-info - %dl - - [:phone, :website, :address].each do |method| - - if @feed.send(method).present? - %dt<>= t("formtastic.labels.feed.show.#{method}") - %dd<>= @feed.send(method) - - #column-2<> - - if can?(:edit, @feed) - = updated_content_or("#post-to-feed") do - = render 'feeds/announcements/form', :feed => @feed, :announcement => Announcement.new - - - #recent-posts - %h2= t 'feeds.profile.recent_posts.title' - - if @feed.announcements.empty? && @feed.events.empty? - .no-recent-posts No Recent Posts - - else - = render 'shared/items', :items => @feed.wire - - #column-3<> - #about - %h2= t 'feeds.profile.about.title' - %p= @feed.about - - %h3= t 'feeds.profile.members.title', :count => @feed.subscribers.count - #members - %ul - - @feed.subscribers.each do |user| - %li - = image_tag user.avatar.url, :size => "32x32" - = user.name - = link_to "Subscribe!", "#", :class => "subscribe" - - diff --git a/app/views/feeds/show.haml b/app/views/feeds/show.haml deleted file mode 100644 index 79719edd1..000000000 --- a/app/views/feeds/show.haml +++ /dev/null @@ -1,10 +0,0 @@ - -- update_content "#information" do - = render 'info_box', :feed => @feed - -= render 'shared/tooltip' - -- unless xhr? - - update_content "#syndicate" do - #syndicate - = render 'shared/items', :items => [@feed] \ No newline at end of file diff --git a/app/views/feeds/twitter.haml b/app/views/feeds/twitter.haml deleted file mode 100644 index 34126cf04..000000000 --- a/app/views/feeds/twitter.haml +++ /dev/null @@ -1,10 +0,0 @@ -= update_content "#post-to-feed" do - - semantic_form_for @feed, :html => {:id => "post-to-feed"} do |f| - %h2 - = t("feeds.profile.post.title") - %nav - %ul - %li= link_to "Announcement", new_feed_announcement_path(@feed), 'data-remote' => true - %li= link_to "Event", new_feed_event_path(@feed), 'data-remote' => true - = f.inputs :name, :about, :feed_url - = f.buttons :commit => "Save" \ No newline at end of file diff --git a/app/views/invite_mailer/feed_invite.haml b/app/views/invite_mailer/feed_invite.haml deleted file mode 100644 index 022aef34e..000000000 --- a/app/views/invite_mailer/feed_invite.haml +++ /dev/null @@ -1 +0,0 @@ -%p #{@feed.name} invited you to #{@community.name} CommonPlace \ No newline at end of file diff --git a/app/views/invite_mailer/user_invite.haml b/app/views/invite_mailer/user_invite.haml deleted file mode 100644 index 7ed03ee01..000000000 --- a/app/views/invite_mailer/user_invite.haml +++ /dev/null @@ -1,31 +0,0 @@ -%html - %head - %body - %p Dear Neighbor, - - %p #{@user.name} has invited you to join #{@community.name} CommonPlace! - - %p CommonPlace is a great way to keep up with what's happening in #{@community.name}. Use it to share information with your neighborhood, find local events and stay in the loop with organizations and businesses in town. - - %p - %em Need to borrow a ladder? Find a lost cat? Celebrate a new baby? - - %p Use CommonPlace to easily post needs, offers, events and announcements to all of your neighbors. - - %p - %em Want to go out on a Friday night? Having a tag sale? - - %p Use CommonPlace to find local events in #{@community.name} Or use CommonPlace to publicize your own. - - %p - %em Want to stay informed about local organizations and businesses? - - %p Subscribe to community announcement feeds on CommonPlace to stay up to receive announcements from civic organizations, important information from city officials, and deals from local businesses. Or start your own community feed to get your message out to the whole community. - - %p CommonPlace is simply the best place to go for anything in #{@community.name}. The site gets better the more neighbors join, so please register #{ link_to "Click to Join #{@community.name} CommonPlace", "#{@community.slug}.ourcommonplace.com" } - - %p If you have any questions, contact #{@community.name}'s lead organizer, Pete, at Pete@CommonPlaceUSA.com. - - %p Hope you can be a great neighbor and join #{@community.name} CommonPlace! - - %p -#{@community.name} CommonPlace \ No newline at end of file diff --git a/app/views/invites/_form.haml b/app/views/invites/_form.haml deleted file mode 100644 index 0429d5c64..000000000 --- a/app/views/invites/_form.haml +++ /dev/null @@ -1,11 +0,0 @@ - -- form_tag "/invites" do |f| - %fieldset.inputs - %ol - %li.text.required - = f.label_tag :emails - = f.text_area :emails - %fieldset.buttons - %ol - %li.commit - = f.submit "Invite" diff --git a/app/views/invites/new.haml b/app/views/invites/new.haml deleted file mode 100644 index 01145beb9..000000000 --- a/app/views/invites/new.haml +++ /dev/null @@ -1,21 +0,0 @@ - -= update_content "#say-something" do - #say-something - %h2= t ".title" - = render 'shared/say_something_nav', :current => :invites - - form_tag "/invites", 'data-remote' => true, :class => "formtastic" do - %fieldset.inputs - %ol - %li.text - = label_tag :message - = text_area_tag :message - %li.text - = label_tag :emails - = text_area_tag :emails, "", :rows => 6 - %fieldset.buttons - %ol - %li.plaxo - %a{ :href => "#", :onclick => "showPlaxoABChooser('emails', '/plaxo-importer.html'); return false"} - ✚ Import emails from my address book - %li.commit - = submit_tag "Invite" diff --git a/app/views/items/replies/_form.haml b/app/views/items/replies/_form.haml deleted file mode 100644 index 8a403623e..000000000 --- a/app/views/items/replies/_form.haml +++ /dev/null @@ -1,7 +0,0 @@ - -- form_for reply do |f| - = image_tag reply.user.avatar.url(:thumb), :class => "avatar" - = f.hidden_field :repliable_type => item.class.to_s - = f.hidden_field :repliable_id => item.id - = f.text_area :body, "data-label" => "Reply here!", :rows => 1 - = f.submit "Reply" \ No newline at end of file diff --git a/app/views/layouts/_application.haml b/app/views/layouts/_application.haml deleted file mode 100644 index 7fc78c954..000000000 --- a/app/views/layouts/_application.haml +++ /dev/null @@ -1,90 +0,0 @@ -- unless updated_content["#tooltip"] - - render 'shared/tooltip' - -!!! 5 -%html{ "xmlns:fb" => "http://www.facebook.com/2008/fbml", "xmlns"=>"http://www.w3.org/1999/xhtml" } - %head - - %meta{ :content => "text/html;charset=utf-8", "http-equiv" => "Content-Type" } - %meta{ :name => "google-site-verification", :content => "HntCS6GTLIAye5nM6FCNHgtcSUlb0-aF2jVbGtd8oyI" } - %link{ :href => image_path('favicon.png'), :rel => "icon", :type => "image/png" } - %title= "CommonPlace - #{current_community.name}" - - / [if IE] - = javascript_include_tag "http://html5shiv.googlecode.com/svn/trunk/html5.js" - %script{ :type => 'text/javascript', :src => 'http://maps.google.com/maps/api/js?sensor=false' } - - = javascript_include_tag 'jquery-1.4.3', 'jquery-ui' - = include_javascript_folder 'jquery' - - = javascript_include_tag 'innershiv', 'app', 'inlineform', 'maps', 'tooltips', 'merge', 'syndicate', 'modal', 'infobox', 'merge' - - %script{:type => "text/javascript", :src => "http://connect.facebook.net/en_US/all.js"} - = javascript_include_tag 'feed_profile', 'facebook_connect' - - - - = stylesheet_link_tag 'application' - - %body{:class => "application #{params[:controller].gsub(/\//,"_")} #{params[:action]}"} - - if current_user_session - = render 'shared/admin_bar' - %header - - if current_user_session - %nav - = tab_to "Home", "/" - .user-feeds - %h4 Feeds - %ul - - current_user.managable_feeds.each do |feed| - %li= link_to feed.name, feed_profile_path(feed) - %li= link_to 'Create a feed', new_feed_path - = tab_to "Settings", edit_account_path - = tab_to "Log Out", "/user_session", :method => :delete - - else - - semantic_form_for @user_sesson || UserSession.new, :url => user_session_path do |f| - = f.inputs :email, :password - - f.buttons do - = f.commit_button :button_html => {:type => "image", :src => "/images/buttons/login.png"} - %span{:id=>"facebook_login"} - or - %fb:login-button{:size => 'small', :length => 'small', :onlogin => 'facebookUserWantsToLogIn();', :perms => 'email,user_hometown,user_address'} - = link_to "Forgot your password?", new_password_reset_path, :class => 'forgot-password' - - if @user_session_errors - %p.errors= t('authlogic.error_messages.no_authentication_details') - - - link_to root_url, :id => "logo" do - = image_tag "logo.png" - %span.community= current_community.name - %div{:style => "clear: both;"} - = yield - - %footer - .left_text - Find out more at #{ link_to "CommonPlaceUSA.com", "http://commonplaceusa.com", :target => :blank } - .right_text - = mail_to "pete@commonplaceusa.com", "Questions? Email Pete@CommonPlaceUSA.com" - ⋅ - = link_to "About", "http://commonplaceusa.com", :target => :blank - ⋅ - = link_to "Terms", terms_path, 'data-remote' => true - ⋅ - = link_to "Privacy Policy", privacy_path, 'data-remote' => true - - - if updated_content["#modal"] - = updated_content["#modal"] - - else - #modal - - - - if RAILS_ENV == "production" - %script - var _gaq = _gaq || []; - _gaq.push(['_setAccount', 'UA-12888551-3']); - _gaq.push(['_trackPageview', location.pathname + location.hash]); - - (function() { - var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; - ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; - var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); - })(); diff --git a/app/views/layouts/_bottom_scripts.haml b/app/views/layouts/_bottom_scripts.haml new file mode 100644 index 000000000..7b66d299c --- /dev/null +++ b/app/views/layouts/_bottom_scripts.haml @@ -0,0 +1,35 @@ +- if Rails.env.production? + -# Mixpanel metrics + - if current_user.try(:email).present? + :javascript + mpq.identify("#{current_user.email}"); + mpq.register({'email': '#{current_user.email}', 'community': '#{current_community.slug}', 'referral_source': '#{current_user.referral_source ? current_user.referral_source.gsub("'","") : ''}'}); + mpq.name_tag("#{current_user.full_name} - #{current_user.email}"); + + -# Chartbeat + :javascript + var _sf_async_config={uid:25400,domain:"ourcommonplace.com"}; + (function(){ + function loadChartbeat() { + window._sf_endpt=(new Date()).getTime(); + var e = document.createElement('script'); + e.setAttribute('language', 'javascript'); + e.setAttribute('type', 'text/javascript'); + e.setAttribute('src', + (("https:" == document.location.protocol) ? "https://a248.e.akamai.net/chartbeat.download.akamai.com/102508/" : "http://static.chartbeat.com/") + + "js/chartbeat.js"); + document.body.appendChild(e); + } + var oldonload = window.onload; + window.onload = (typeof window.onload != 'function') ? + loadChartbeat : function() { oldonload(); loadChartbeat(); }; + })(); + +-# JQuery Magic +-# fixme: this should go somewhere with context and no magic +:javascript + $(document).ready(function() { + $('textarea').autoResize(); + // FIXME: this code is non-DRY confusing crap: Appears many times w/o apparent reason. + $('input[placeholder], textarea[placeholder]').placeholder(); + }); diff --git a/app/views/layouts/_common_head.haml b/app/views/layouts/_common_head.haml new file mode 100644 index 000000000..bd2977fc2 --- /dev/null +++ b/app/views/layouts/_common_head.haml @@ -0,0 +1,43 @@ +- title ||= nil; track ||= nil; stylesheets ||= nil; javascripts ||= nil +-# NewRelic: +:javascript + var _sf_startpt=(new Date()).getTime(); + +%meta{ :content => "text/html;charset=utf-8", "http-equiv" => "Content-Type" } +%meta{ :name => "keywords", :content => "CommonPlace, CommonPlace USA" } +%meta{ :name => "google-site-verification", :content => "HntCS6GTLIAye5nM6FCNHgtcSUlb0-aF2jVbGtd8oyI" } + +- if community_name = current_community.try(:name) || params[:community] + %meta{ :name => "description", :content => "The #{community_name} CommonPlace is an online community bulletin board for neighbors in #{community_name}, designed to make it easy for neighbors and community groups to share and connect." } +- else + %meta{ :name => "description", :content => "CommonPlace helps you share and connect with your neighbors." } + +%link{ :href=> 'https://webfonts.fontslive.com/css/0a25c9dc-bb5a-4ea9-88b2-1c7a8802e172.css', :rel =>"stylesheet", :type =>"text/css"} +%link{ :href=> 'https://fonts.googleapis.com/css?family=Rokkitt:400,700', :rel =>"stylesheet", :type =>"text/css"} +%link{ :href => image_path('favicon.png'), :rel => "icon", :type => "image/png" } + +%title= "CommonPlace #{community_name} #{title}" + += stylesheet_link_tag(*stylesheets) if stylesheets + += include_dummy_console += include_commonplace(title) += javascript_include_tag('commonplace') += javascript_include_tag(*javascripts) if javascripts += populate_commonplace # this could be moved to the footer. + += include_mixpanel +- if track.present? + :javascript + mpq.track('#{track}', {'community': '#{current_community.try(:slug)}'}); + += include_ga += include_exceptional + +- if params[:first].present? and current_user.is_facebook_user + -# fixme: what does this do? # invite page only! + :javascript + $(document).ready(function() { facebook_invite(); }); + +/ [if IE] + = javascript_include_tag "https://html5shiv.googlecode.com/svn/trunk/html5.js" diff --git a/app/views/layouts/_footer.haml b/app/views/layouts/_footer.haml new file mode 100644 index 000000000..4d5be7e04 --- /dev/null +++ b/app/views/layouts/_footer.haml @@ -0,0 +1,26 @@ +#footer + #cont + .left_text + Find out more at #{link_to "CommonPlaceUSA.com", about_url, :target => :blank } + .right_text + - if params[:community] + = mail_to current_community.organizer_email, "Questions? Email #{current_community.organizer_email}" + - else + = mail_to "FAQ@CommonPlaceUSA.com", "Questions? Email FAQ@CommonPlaceUsa.com" + - if params[:community] + ⋅ + = link_to "FAQ", faq_path(current_community) + ⋅ + = link_to "About", about_url, :target => :blank + -# TODO Uncomment this if it *isn't* causing timeouts + -# - if params[:community] + -# - if logged_in? + -# - if $rollout.active?(:good_neighbor_discount, current_user) + -# ⋅ + -# = link_to "Good Neighbor Discount", "/#{current_community.slug}/good_neighbor_discount/" + - if params[:community] + ⋅ + = link_to "Terms", terms_path(current_community) + - if params[:community] + ⋅ + = link_to "Privacy Policy", privacy_path(current_community) diff --git a/app/views/layouts/_header.haml b/app/views/layouts/_header.haml new file mode 100644 index 000000000..691d31cdf --- /dev/null +++ b/app/views/layouts/_header.haml @@ -0,0 +1,53 @@ +#header + - if logged_in? + #nav_wrapper + .nav + = tab_to "Home", "/#{current_community.slug}" + - if current_user.admin? + = tab_to "Stats", "#", {:onclick => "$('#admin_bar').toggle('slow');"} + - if current_user.unread > 0 + %a{:href => "/inbox"} + %span Inbox + %span.notif= current_user.unread + - else + = tab_to "Inbox", "/inbox" + = tab_to "Invite", "/account/facebook_invite" + .user-feeds + - if current_user.managable_feeds.count > 0 + %h4 Your Feeds + %ul + - current_user.managable_feeds.each do |feed| + %li= link_to feed.name, "/pages/#{feed.slug.blank? ? feed.id : feed.slug}" + %li= link_to 'Create a feed', new_feed_registration_url + - else + %h4 + = link_to((t "feeds.register"), new_feed_registration_url) + = tab_to "Settings", "/account/edit" + = tab_to "Logout", "/users/sign_out" + = link_to "/", :id => "logo" do + = image_tag "logo2.png", :alt => 'CommonPlace' + %span.community= current_community.name + - else + #nav_wrapper + :javascript + $(function() { + $('#sign_in_button').click(function() { + $("#header form.user").slideToggle(); + }); + }); + + %ul{:id=>"user_sign_in"} + %li Have an Account? + %li{:id=>"sign_in_button", :class=>"remove_border"} Sign in + %li{:id=>"sign_in_form"} + = render 'user_sessions/form' + + - if params[:community] + = link_to "/#{params[:community]}", :id => "logo" do + = image_tag "logo2.png", :alt => 'CommonPlace' + %span.community= current_community.name + - else + = link_to "/", :id => "logo" do + = image_tag "logo2.png", :alt => 'CommonPlace' + + .clear diff --git a/app/views/layouts/_registration_header.haml b/app/views/layouts/_registration_header.haml new file mode 100644 index 000000000..4f694c373 --- /dev/null +++ b/app/views/layouts/_registration_header.haml @@ -0,0 +1,18 @@ +#header + + #nav_wrapper + %ul{:id=>"user_sign_in"} + %li Have an Account? + %li{:id=>"sign_in_button", :class=>"remove_border"} Sign in + %li{:id=>"sign_in_form"} + = render "user_sessions/form" + + - if params[:community] + = link_to "/#{params[:community]}", :id => "logo" do + = image_tag "logo2.png", :alt => 'CommonPlace' + %span.community= current_community.name + - else + = link_to "/", :id => "logo" do + = image_tag "logo2.png", :alt => 'CommonPlace' + + .clear diff --git a/app/views/layouts/administration.haml b/app/views/layouts/administration.haml index 26e0266f5..2f9e3de20 100644 --- a/app/views/layouts/administration.haml +++ b/app/views/layouts/administration.haml @@ -11,12 +11,12 @@ %script{ :type => 'text/javascript', :src => 'http://maps.google.com/maps/api/js?sensor=false' } - = javascript_include_tag 'jquery-1.4.3', 'jquery-ui' - = include_javascript_folder 'jquery' + = javascript_include_tag "administration" - = javascript_include_tag 'sammy', 'innershiv', 'core', 'application', 'routes' - - = stylesheet_link_tag 'application' + = stylesheet_link_tag "application" + %style{:type => "text/css"} + = 'div.polygon-canvas { width: 600px; height: 300px; }' + %body.application.administration #body_color @@ -31,7 +31,7 @@ #main = yield - else - - form_for UserSession.new, :url => user_session_path do |f| + = form_for UserSession.new, :url => user_session_path do |f| .field.email %label= 'Email'.upcase = f.text_field :email, :size => 20 diff --git a/app/views/layouts/application.haml b/app/views/layouts/application.haml index 6151326dc..f0222a92e 100644 --- a/app/views/layouts/application.haml +++ b/app/views/layouts/application.haml @@ -1,6 +1,18 @@ +!!! 5 +%html{:xmlns => "https://www.w3.org/1999/xhtml", 'xmlns:fb' => "https://www.facebook.com/2008/fbml"} + %head + = render :partial => 'layouts/common_head', :locals => { :javascripts => ["application"], :stylesheets => ["application"] } -- content_for :content do - = yield + %body{ :class => "application #{params[:controller].gsub(/\//,"_")} #{params[:action]} #{ logged_in? ? "logged_in" : "logged_out" }"} -- render :layout => 'layouts/application' do - = yield :content + #sticky-wrapper + + #fb-root + + = render 'layouts/header' + + = yield + + = render "layouts/footer" + + = render "layouts/bottom_scripts" \ No newline at end of file diff --git a/app/views/layouts/application.json.erb b/app/views/layouts/application.json.erb deleted file mode 100644 index 729623d66..000000000 --- a/app/views/layouts/application.json.erb +++ /dev/null @@ -1,3 +0,0 @@ -<%- updated_content["#modal"] ||= '' %> -<%- updated_content["#deliveries"] = render 'shared/deliveries' %> -<%= updated_content.to_json %> \ No newline at end of file diff --git a/app/views/layouts/communities.haml b/app/views/layouts/communities.haml deleted file mode 100644 index 893a50e82..000000000 --- a/app/views/layouts/communities.haml +++ /dev/null @@ -1,15 +0,0 @@ -- render :layout => 'layouts/application' do - #main - #whats-happening - %h2 - = updated_content["#tooltip"] - = render 'shared/zones' - = updated_content["#syndicate"] - = updated_content_or("#say-something") do - #say-something - %h2= t "posts.new.title" - = render 'shared/say_something_nav', :current => :post - = render 'posts/form', :post => Post.new - #community-profiles - = updated_content_or "#information" do - = render 'accounts/info_box' diff --git a/app/views/layouts/communities.json.erb b/app/views/layouts/communities.json.erb deleted file mode 100644 index a68920900..000000000 --- a/app/views/layouts/communities.json.erb +++ /dev/null @@ -1,6 +0,0 @@ - -<%- updated_content["#modal"] ||= '' %> -<%- updated_content["#tooltip"] or render 'shared/tooltip' %> -<%- updated_content["#zones"] ||= render 'shared/zones' %> -<%- updated_content["#deliveries"] = render 'shared/deliveries' %> -<%= updated_content.to_json %> diff --git a/app/views/layouts/feed_registration.haml b/app/views/layouts/feed_registration.haml new file mode 100644 index 000000000..ea7b5d9dd --- /dev/null +++ b/app/views/layouts/feed_registration.haml @@ -0,0 +1,24 @@ +!!! 5 +%html{:xmlns => "https://www.w3.org/1999/xhtml", 'xmlns:fb' => "https://www.facebook.com/2008/fbml"} + %head + %link{ :href=> 'https://webfonts.fontslive.com/css/0a25c9dc-bb5a-4ea9-88b2-1c7a8802e172.css', :rel =>"stylesheet", :type =>"text/css"} + %link{ :href=> 'https://fonts.googleapis.com/css?family=Rokkitt:400,700', :rel =>"stylesheet", :type =>"text/css"} + %title CommonPlace #{current_community.name} Feeds + + %link{ :href => image_path('favicon.png'), :rel => "icon", :type => "image/png" } + = stylesheet_link_tag "feed_registration" + + %body + + #wrapper + = render "layouts/header" + + #main + = yield + %div{ :style => "clear: both;" } + + #wrapper-footer + + = render "layouts/footer" + + = javascript_include_tag "feed_registration" diff --git a/app/views/layouts/management.haml b/app/views/layouts/management.haml deleted file mode 100644 index 65267bd26..000000000 --- a/app/views/layouts/management.haml +++ /dev/null @@ -1,53 +0,0 @@ -!!! 5 -%html - %head - %meta{ :content => "text/html;charset=utf-8", "http-equiv" => "Content-Type" } - %meta{ :name => "google-site-verification", :content => "HntCS6GTLIAye5nM6FCNHgtcSUlb0-aF2jVbGtd8oyI" } - %link{ :href => image_path('favicon.png'), :rel => "icon", :type => "image/png" } - %title CommonPlace - Edit your accounts - - / [if IE] - = javascript_include_tag "http://html5shiv.googlecode.com/svn/trunk/html5.js" - - %script{ :type => 'text/javascript', :src => 'http://maps.google.com/maps/api/js?sensor=false' } - - = javascript_include_tag 'jquery-1.4.3', 'jquery-ui' - = include_javascript_folder 'jquery' - = javascript_include_tag 'sammy', 'innershiv', 'core', 'application', 'routes' - - = stylesheet_link_tag 'management' - - %body - #wrap - %header.application - %h1 - - link_to root_url do - = image_tag current_community.logo.url, :alt => "Commonplace #{current_community.name}" - - - if current_user_session - %nav - = tab_to "Home", "/" - = tab_to "Manage Profile", management_url - %span{ :class => "disabled_link" } Create a Feed - -# = tab_to "Create a Feed", new_feed_path, 'data-remote' => true - = tab_to "Log Out", "/user_session", :method => :delete - - = yield :title - = render 'management/side_nav' - #feed_main> - = yield :navigation - #main - = yield - - if params["welcome"] - - render :layout => 'shared/modal' do - #welcome - Dear #{current_user.first_name} - %p.subhead Congratulations! Your #{@feed.category.downcase} is now registered with CommonPlace. Here is your management zone, where you can edit #{@feed.name}'s profile, as well as post announcements and events. Plus, you can click your name on the left to edit your personal CommonPlace profile. You can click "Home" to get back to the main page and click "Manage Profiles" if you ever want to get back to this management zone. - %p.subhead In a few weeks, the whole town will be on the platform. Feel free to email Max Novendstern (Max@CommonPlaceUSA.com) anytime with any questions or concerns. - Thanks, - %br - The #{current_community.name} CommonPlace Team - - = button_to "Continue to Management Zone", management_feed_path(@feed), :method => :get, 'data-remote' => false - - else - #modal diff --git a/app/views/layouts/mobile_registration.html.erb b/app/views/layouts/mobile_registration.html.erb new file mode 100644 index 000000000..35fd6d452 --- /dev/null +++ b/app/views/layouts/mobile_registration.html.erb @@ -0,0 +1,45 @@ + + + + + + + + + + + + <%= stylesheet_link_tag 'mobile' %> + <%= javascript_include_tag 'mobile' %> + + CommonPlace <%= current_community.name %> + + + +
          + <%= image_tag "logo2.png" %> + <%= current_community.name %> +
          +
          + <%= yield %> +
          + + + + + diff --git a/app/views/layouts/notifications_mailer.html.erb b/app/views/layouts/notifications_mailer.html.erb deleted file mode 100644 index 9cc62c8ab..000000000 --- a/app/views/layouts/notifications_mailer.html.erb +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          - <% link_to url, :target => "_blank" do %> - <%= image_tag url("/images/#{@community.slug}_notification_header.png"), :width => 600, :height => 56, :border => 0, :align => "top" %> - <% end %> -
             
          - - <%= yield :greeting %> - -
          - <%= image_tag url('/images/notification_border.png'), :width => 568, :height => 12 %> -
             - - - <%= yield :subject %> - - -
          - - <%= yield :body %> - -
          -
           
          - - <%= yield :avatar %> - -
            - - - <%= yield :reply_with %> - - -
          - <%= image_tag url("/images/notification_border.png"), :width => 586, :height => 12 %> -
           
          -
          - Want to disable this and/or other e-mails? Click - <% link_to url("/account/edit") do %> - here - <% end %> - to manage your subscriptions. -
           
          - - - www.OurCommonPlace.com - - -
          - © 2010 CommonPlace. -
           
          - - diff --git a/app/views/layouts/registration.haml b/app/views/layouts/registration.haml new file mode 100644 index 000000000..2972e692c --- /dev/null +++ b/app/views/layouts/registration.haml @@ -0,0 +1,16 @@ +!!! 5 +%html{:xmlns => "https://www.w3.org/1999/xhtml", 'xmlns:fb' => "https://www.facebook.com/2008/fbml"} + %head + = render :partial => 'layouts/common_head', :locals => { :javascripts => ["registration_page"], :stylesheets => ["registration_page"] } + + %body + + #wrapper + = render 'layouts/registration_header' + + = yield + + #wrapper-footer + + + = render "layouts/footer" diff --git a/app/views/layouts/sign_in.haml b/app/views/layouts/sign_in.haml new file mode 100644 index 000000000..1b55b8308 --- /dev/null +++ b/app/views/layouts/sign_in.haml @@ -0,0 +1,19 @@ +!!! 5 +%html{:xmlns => "https://www.w3.org/1999/xhtml", 'xmlns:fb' => "https://www.facebook.com/2008/fbml"} + %head + = render :partial => 'layouts/common_head', :locals => { :javascripts => ["sign_in"], :stylesheets => ["login_page"] } + + %body + + #wrapper + = render 'layouts/registration_header' + + = yield + + #wrapper-footer + + = render "layouts/footer" + + + + diff --git a/app/views/layouts/signup.json.erb b/app/views/layouts/signup.json.erb deleted file mode 100644 index 3dda19fe7..000000000 --- a/app/views/layouts/signup.json.erb +++ /dev/null @@ -1 +0,0 @@ -<%= updated_content.to_json %> \ No newline at end of file diff --git a/app/views/layouts/starter_site.haml b/app/views/layouts/starter_site.haml new file mode 100644 index 000000000..9bf6f622f --- /dev/null +++ b/app/views/layouts/starter_site.haml @@ -0,0 +1,103 @@ +!!! 5 +%html + %head + = render :partial => 'layouts/common_head', :locals => { :javascripts => ["jquery-1.6.1", "starter_site"], :stylesheets => ["starter_site"] } + + + %body + + .nav.page-navigation + .container-12 + #logo.grid-4 + %a{:href=>"/"} + = image_tag "ourcommonplace-logo.png" + .links + %a.grid-8{ :href => "/users/sign_in"} Login + .clear + + #header + .container-12 + #banner_menu.grid-3 + %ol + %li.selected + %a.rounded{:href=>"#"} + 1. Share + %br + Neighborhood Needs + %ul + %li + .vert + %p{:style=>"top: 10px;"}="Can I borrow a ladder?" + %li + .vert + %p{:style=>"top: 15px;"}="My cat ran away!" + %li + .vert + %p{:style=>"top: 10px;"}="Anyone know a great babysitter?" + %li + %a.rounded{:href=>"#"} + 2. Organize local + %br + Community Events + %ul + %li + .vert + %p{:style=>"top: 10px;"}="Music festival this Saturday" + %li + .vert + %p{:style=>"top: 10px;"}="Neighborhood block party!" + %li + .vert + %p{:style=>"top: 2px;"}="School Board meeting Wednesday night" + %li + %a.rounded{:href=>"#"} + 3. Find new people + %br + in your area + %ul + %li + .vert + %p{:style=>"top: 10px;"}="Parenting tips from local moms" + %li + .vert + %p{:style=>"top: 2px;"}="Discuss local community issues" + %li + .vert + %p{:style=>"top: 2px;"}="Welcome new neighbors to the block" + %li + %a.rounded{:href=>"#"} + 4. Build a better + %br + Community + %ul + %li + .vert + %p{:style=>"top: 2px;"}="Mobilize resources in your town" + %li + .vert + %p{:style=>"top: 2px;"}="Build exciting community projects" + %li + .vert + %p{:style=>"top: 10px;"}="Found new clubs and organizations" + #picture.grid-9 + %h2 + CommonPlace helps you + %br + share and connect with your neighbors. + #box + %ul.bubbles + %li + .vert + %p{:style=>"top: 10px;"}="Can I borrow a ladder?" + %li + .vert + %p{:style=>"top: 15px;"}="My cat ran away!" + %li + .vert + %p{:style=>"top: 10px;"}="Anyone know a great babysitter?" + .clear + + #main= yield + #footer + .container-12 + %p="Copyright 2011 CommonPlace." diff --git a/app/views/messages/create.haml b/app/views/messages/create.haml deleted file mode 100644 index 6ef2ede30..000000000 --- a/app/views/messages/create.haml +++ /dev/null @@ -1,3 +0,0 @@ -= render 'shared/tooltip' - -#modal \ No newline at end of file diff --git a/app/views/messages/new.haml b/app/views/messages/new.haml deleted file mode 100644 index c8e31ac75..000000000 --- a/app/views/messages/new.haml +++ /dev/null @@ -1,8 +0,0 @@ -- render :layout => 'shared/modal' do - - - semantic_form_for [parent,@message], :html => { :class => "new_entity", 'data-remote' => true } do |f| - %hr.staple - %h1 Send a message to #{parent.name} - = f.inputs :subject, :body - - f.buttons do - #submit_wrapper= f.commit_button "Send" \ No newline at end of file diff --git a/app/views/mets/_met.haml b/app/views/mets/_met.haml deleted file mode 100644 index ff39e26e3..000000000 --- a/app/views/mets/_met.haml +++ /dev/null @@ -1,2 +0,0 @@ -%p This person thinks they know you -%p= met.requester.full_name \ No newline at end of file diff --git a/app/views/neighborhood/people/index.haml b/app/views/neighborhood/people/index.haml deleted file mode 100644 index 9c61c1f78..000000000 --- a/app/views/neighborhood/people/index.haml +++ /dev/null @@ -1,4 +0,0 @@ -- update_content "#syndicate" do - #syndicate - = render 'shared/neighborhood_syndicate_nav' - = render 'shared/items', :items => @items \ No newline at end of file diff --git a/app/views/notifications_mailer/feed_announcement.html.erb b/app/views/notifications_mailer/feed_announcement.html.erb deleted file mode 100644 index f05a073fa..000000000 --- a/app/views/notifications_mailer/feed_announcement.html.erb +++ /dev/null @@ -1,70 +0,0 @@ - - - Notifications - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          CommonPlace <%= @feed.community.name %>
             
          Hey , <%= @feed.name %> posted a new announcement to your community on CommonPlace.
            <%= @announcement.subject %>
          - posted by <%= @feed.name %> on <%= @announcement.created_at %> -
          <%= markdown @announcement.body %>
          -
           
          - <%= image_tag @feed.avatar.url(:thumb), :width => 49, :height => 48, :vspace => 0, :border => 0, :align => "top" %> -
            - -
           
          -
          Want to disable this and/or other e-mails? Click unsubscribe. Click here to manage your subscriptions.
           
          www.OurCommonPlace.com
          - © 2010 CommonPlace.
           
          - - - diff --git a/app/views/notifications_mailer/feed_event.html.erb b/app/views/notifications_mailer/feed_event.html.erb deleted file mode 100644 index 4fddf9751..000000000 --- a/app/views/notifications_mailer/feed_event.html.erb +++ /dev/null @@ -1,70 +0,0 @@ - - - Notifications - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          CommonPlace <%= @feed.community.name %>
             
          Hey , <%= @feed.name %> posted a new event to your community on CommonPlace.
            <%= @event.name %>
          - posted by <%= @feed.name %> on <%= @event.created_at %> -
          <%= @event.description %>
          -
           
          - <%= image_tag @feed.avatar.url(:thumb), :width => 49, :height => 48, :vspace => 0, :border => 0, :align => "top" %> -
            - -
           
          -
          Want to disable this and/or other e-mails? Click unsubscribe. Click here to manage your subscriptions.
           
          www.OurCommonPlace.com
          - © 2010 CommonPlace.
           
          - - - diff --git a/app/views/notifications_mailer/neighborhood_post.html.erb b/app/views/notifications_mailer/neighborhood_post.html.erb deleted file mode 100644 index f4e6d6ba4..000000000 --- a/app/views/notifications_mailer/neighborhood_post.html.erb +++ /dev/null @@ -1,30 +0,0 @@ - -<% content_for :greeting do %> -
          Hi ,
          - <%= @post.user.full_name %> just posted to your neighborhood board on CommonPlace. -<% end %> - -<% content_for :subject, @post.subject %> - -<% content_for :body, markdown(@post.body) %> - -<% content_for :avatar, - image_tag(url(@post.user.avatar.url(:thumb)), :width => 49, :height => 48, :vspace => 0, :border => 0, :align => "top") -%> - -<% content_for :reply_with do %> -
          - <% link_to url(post_path(@post)), :style => "text-decoration:none;" do %> - Reply to <%= @post.user.first_name %> - <% end %> -
          - -
          - - <% link_to url(new_user_message_path(@post.user)) do %> - Send <%= @post.user.first_name %> a private message - <% end %> - -
          -<% end %> - diff --git a/app/views/notifications_mailer/post_reply.html.erb b/app/views/notifications_mailer/post_reply.html.erb deleted file mode 100644 index 65bed03af..000000000 --- a/app/views/notifications_mailer/post_reply.html.erb +++ /dev/null @@ -1,43 +0,0 @@ - -<% content_for :greeting do %> -
          Hi ,
          - - - <%= @reply.user.full_name %> - - - just posted a new reply to a post: - <% link_to url(post_path(@post)) do %> - - <%= @post.subject %> - - <% end %> - -<% end %> - -<% content_for :subject, @post.subject %> - -<% content_for :body, markdown(@reply.body) %> - -<% content_for :avatar, - image_tag(url(@reply.user.avatar.url(:thumb)), :width => 49, :height => 48, :vspace => 0, :border => 0, :align => "top") %> - -<% content_for :reply_with do %> -
          - <% link_to url(post_path(@post)), :style => "text-decoration:none;" do %> - - Reply to <%= @post.user.first_name %> - - <% end %> -
          -
          - - <% link_to url(new_user_message_path(@reply.user)) do %> - - Send <%= @reply.user.first_name %> a private message - - <% end %> - -
          -<% end %> - diff --git a/app/views/notifications_mailer/user_message.html.erb b/app/views/notifications_mailer/user_message.html.erb deleted file mode 100644 index 2a16da140..000000000 --- a/app/views/notifications_mailer/user_message.html.erb +++ /dev/null @@ -1,27 +0,0 @@ - -<% content_for :greeting do %> -
          Hi <%= @message.recipient.first_name %>,
          - Your neighbor, - <% link_to url(user_path(@message.user)) do %> - - <%= @message.user.name %> - - <% end %> - just sent you a message through CommonPlace. -<% end %> - -<% content_for :subject, @message.subject %> - -<% content_for :body, markdown(@message.body) %> - -<% content_for :avatar, - image_tag(url(@message.user.avatar.url(:thumb)), :width => 49, :height => 48, :vspace => 0, :border => 0, :align => "top") %> - - -<% content_for :reply_with do %> -
          - <% link_to url(user_path(@message.user)), :style => "text-decoration:none;" do %> - Reply to <%= @message.user.first_name %> - <% end %> -
          -<% end %> diff --git a/app/views/organizer/app.haml b/app/views/organizer/app.haml new file mode 100644 index 000000000..013bd5a56 --- /dev/null +++ b/app/views/organizer/app.haml @@ -0,0 +1,413 @@ +-#= javascript_include_tag 'chosen' +:css + #referral_sources { + text-align: center; + } + table { + border-width: 1px; + border-style: solid; + border-color: #000000; + border-collapse: collapse; + width: 100%; + } + + th { + border-width: 1px; + border-style: solid; + border-color: #000000; + text-align: center; + font-weight: bold; + } + + td { + border-width: 1px; + border-style: solid; + border-color: #000000; + text-align: center; + } + + h1 { + margin: 20px auto; + font-size: 28px; + color: blue; + } +%script{:src => "http://maps.googleapis.com/maps/api/js?sensor=false", :type => "text/javascript"} +:javascript + var map; + var clusterer; + var infowindow; + var greenMarkerOptions, redMarkerOptions, orangeMarkerOptions, blueMarkerOptions; + var allHouseMarkers = Array(); + var droppedMarkers = Array(); + var knockedMarkers = Array(); + var postKnockedMarkers = Array(); + var noMarkers = Array(); + var onMarkers = Array(); + var streetAddresses = Array(); + function createMarker(point, html, image) { + var marker = new google.maps.Marker({ + position: point, + title: html, + icon: image + }); + google.maps.event.addListener(marker, 'click', function(marker){ + infowindow = new google.maps.InfoWindow(); + infowindow.open(map, marker, marker.title); + }); + + return marker; + } + function initialize() { + geocoder = new google.maps.Geocoder(); + var myOptions = { + zoom: 16, + mapTypeId: google.maps.MapTypeId.ROADMAP + }; + map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); + + if (navigator.geolocation) + { + navigator.geolocation.getCurrentPosition(function(position) { + var pos = new google.maps.LatLng(position.coords.latitude, position.coords.longitude); + map.setCenter(pos); + }, function(){ + handleNoGeolocation(true); + }); + } + else + handleNoGeolocation(false); + + // Set up markers + greenMarker = "http://www.google.com/intl/en_us/mapfiles/ms/micons/green-dot.png"; + greyMarker = "/images/grey-dot.png"; + redMarker = "http://www.google.com/intl/en_us/mapfiles/ms/micons/red-dot.png"; + yellowMarker = "http://www.google.com/intl/en_us/mapfiles/ms/micons/yellow-dot.png"; + orangeMarker = "http://www.google.com/intl/en_us/mapfiles/ms/micons/orange-dot.png"; + blueMarker = "http://www.google.com/intl/en_us/mapfiles/ms/micons/blue-dot.png"; + blackMarker = "http://www.google.com/intl/en_us/mapfiles/ms/micons/black-dot.png"; + drawPoints(); + } + + function handleNoGeolocation(errorFlag) { + if (errorFlag) + { + var content = 'Error: The Geolocation service failed.'; + } + else + { + var content = 'Error: Your browser doesn\'t support geolocation.'; + } + var options = { + map: map, + position: new google.maps.LatLng(60, 105), + content: content + }; + var infowindow = new google.maps.InfoWindow(options); + map.setCenter(options.position); + } + + function showMarkerSet(markers, cluster) + { + cluster = typeof(cluster) != 'undefined' ? cluster : false; + $.each(markers, function(key, marker){ + if (cluster) + clusterer.AddMarker(marker); + else + marker.setMap(map); + }); + } + + function hideMarkerSet(markers, cluster) + { + cluster = typeof(cluster) != 'undefined' ? cluster : false; + $.each(markers, function(key, marker){ + if (cluster) + clusterer.RemoveMarker(marker); + else + marker.setMap(null); + }); + } + + function drawPoints() + { + $.ajax({ + url: '/api/communities/#{@community_id}/registration_points', + dataType: 'json', + success: function(data) { + $.each(data, function(key, val) { + var point = new google.maps.LatLng(val['lat'], val['lng']); + var marker = createMarker(point, val['name'] + '\n' + val['address'], greenMarker); + onMarkers.push(marker); + }); + showMarkerSet(onMarkers); + } + }); + $.ajax({ + url: '/api/communities/#{@community_id}/data_points?top=true', + dataType: 'json', + success: function(data) { + $.each(data, function(key, val) { + var colorOption; + streetAddresses.push(val['address']); + if (val['status'] == 'house') { + var point = new google.maps.LatLng(val['lat'], val['lng']); + allHouseMarkers.push(createMarker(point, val['address'], greyMarker)); + } + if (val['status'] == 'dropped') { + if (val['lat'] && val['lng']) + { + colorOption = yellowMarker; + var point = new google.maps.LatLng(val['lat'], val['lng']); + var marker = createMarker(point, val['address'], colorOption); + droppedMarkers.push(marker); + } + else + { + geocoder.geocode({ 'address' : val['address'] }, function(result, status){ + if (status == google.maps.GeocoderStatus.OK) + droppedMarkers.push(createMarker(result[0].geometry.location, val['address'], yellowMarker)); + }); + } + } + if (val['status'] == 'knocked') { + if (val['lat'] && val['lng']) + { + var point = new google.maps.LatLng(val['lat'], val['lng']); + var marker = createMarker(point, val['address'], orangeMarker); + knockedMarkers.push(marker); + } + else + { + geocoder.geocode({ 'address' : val['address'] }, function(result, status){ + if (status == google.maps.GeocoderStatus.OK) + knockedMarkers.push(createMarker(result[0].geometry.location, val['address'], orangeMarker)); + }); + } + } + if (val['status'] == 'post_knocked_1') { + if (val['lat'] && val['lng']) + { + var point = new google.maps.LatLng(val['lat'], val['lng']); + var marker = createMarker(point, val['address'], blueMarker); + postKnockedMarkers.push(marker); + } + else + { + geocoder.geocode({ 'address' : val['address'] }, function(result, status){ + if (status == google.maps.GeocoderStatus.OK) + postKnockedMarkers.push(createMarker(result[0].geometry.location, val['address'], blueMarker)); + }); + } + } + if (val['status'] == 'no') { + if (val['lat'] && val['lng']) + { + var point = new google.maps.LatLng(val['lat'], val['lng']); + var marker = createMarker(point, val['address'], redMarker); + noMarkers.push(marker); + } + else + { + geocoder.geocode({ 'address' : val['address'] }, function(result, status){ + if (status == google.maps.GeocoderStatus.OK) + noMarkers.push(createMarker(result[0].geometry.location, val['address'], redMarker)); + }); + } + } + }); + // Show default markers + // Load street addresses + if (streetAddresses[0].substr(0,7) != "= 5) + streetAddresses[key] = tmp[1] + " " + tmp[2] + " " + tmp[3]; + else + { + for (i = 1; i < tmp.length - 1; i++) + streetAddresses[key] += tmp[i] + " "; + } + }); + streetAddresses = (_.uniq(streetAddresses)).sort(); + + $.each(streetAddresses, function(k, v) { + streetAddresses[k] = ''; + }); + $('.address_selector').html(streetAddresses.join('')); + $('.address_selector').width('250px'); + $('.address_selector').chosen(); + + showMarkerSet(droppedMarkers); + showMarkerSet(knockedMarkers); + showMarkerSet(postKnockedMarkers); + showMarkerSet(onMarkers); + showMarkerSet(noMarkers); + hiddenInput = $('', {type: 'hidden', id: 'entry_count', value: '1', name: 'entry_count'}); + hiddenInput.appendTo("#addresses"); + } + } + }); + } + $(document).ready(initialize); + var TOGGLE_OPTS = 'slow'; + function toggle_map() + { + hide_all(); + $("#map").toggle(TOGGLE_OPTS); + initialize(); + } + function toggle_overview() + { + hide_all(); + $("#community_overview").toggle(TOGGLE_OPTS); + } + function toggle_doc() + { + hide_all(); + $("#google_doc").toggle(TOGGLE_OPTS); + } + function toggle_referrers() + { + hide_all(); + $("#community_referrers").toggle(TOGGLE_OPTS); + } + function toggle_journal() + { + hide_all(); + $("#daily_journal").toggle(TOGGLE_OPTS); + } + function toggle_goals() + { + hide_all(); + $("#goals").toggle(TOGGLE_OPTS); + } + function hide_all() + { + $("#map").hide(); + $("#community_overview").hide(); + $("#google_doc").hide(); + $("#community_referrers").hide(); + $("#daily_journal").hide(); + $("#goals").hide(); + } + + var entry_count = 1; + function add_entry() + { + entry_count += 1; + $("#addresses").append('
          '); + $('.address_selector').html(streetAddresses.join('')); + $('.address_selector').width('250px'); + $('.address_selector').chosen(); + $("#entry_count").val(entry_count); + } + + function query(run_immediately, e) + { + if (!run_immediately) + { + var key_code; + if (window.event) + { + key_code = e.keyCode; + } + else if (e.which) + { + key_code = e.which; + } + if (key_code == 13) + run_immediately = true; + } + if (run_immediately) + { + var resultSet = Array(); + q = $("#search_query").val(); + geocoder.geocode({'address': q}, function(results, status) { + if (status == google.maps.GeocoderStatus.OK) { + var marker = createMarker(results[0].geometry.location, q, blueMarker); + marker.setMap(map); + } + }); + } + } + +#organizer_app + #links + %a{:onclick => "toggle_goals();", :style => "text-decoration: underline; cursor: pointer;"} Goals + %br + %a{:onclick => "toggle_map();", :style=>"text-decoration: underline; cursor: pointer;"} Neighborhood Canvassing Map + %br + %a{:onclick => "toggle_overview();", :style=>"text-decoration: underline; cursor: pointer;"} Overview + %br + %a{:onclick => "toggle_doc();", :style=>"text-decoration: underline; cursor: pointer;"} Community Leaders + %br + %a{:onclick => "toggle_referrers();", :style=>"text-decoration: underline; cursor: pointer;"} Referrers + %br + %a{:onclick => "toggle_journal();", :style => "text-decoration: underline; cursor: pointer;"} Daily Journal + %br + #goals{:style => "display: none;"} + %h1= "Your team needs to get #{(1000-current_community.users.count)/(DateTime.new(2011, 12, 15) - DateTime.now).to_i} neighbors on board per day to fulfill your mission of 1000 neighbors before you leave." + #map{:style => "display: none;"} + #map_canvas{:style => "width: 1024px; height: 350px;"} + #map_form + = text_field_tag :search_query, "", :placeholder => "Search", :onkeypress => "query(false, event)" + = submit_tag "Search", :id => "search_query_commit", :onclick => "query(true, event);" + %br + = form_tag "/organizer/add", :method => :post do + #addresses + = text_field_tag :number_1, "", :size => 10, :placeholder => "1-10O" + = select_tag :address_1, "", :class => "address_selector", "data-placeholder" => "Street" + = @center_zip_code + = select_tag :status, options_for_select([["Door-Dropped", 'dropped'], ['Door-Knocked', 'knocked'], ['Post-Knocked Once', 'post_knocked_1'], ['Impossibility', 'no']]) + %a{:onclick => "add_entry();"} Add More + %br + = submit_tag "Enter" + %img{:src => "/images/grey-dot.png"} + %a{:onclick => "showMarkerSet(allHouseMarkers);", :class => "markers show", :style => "cursor: pointer"} Show All Houses + | + %a{:onclick => "hideMarkerSet(allHouseMarkers);", :class => "markers hide", :style => "cursor: pointer"} Hide All Houses + %br + %img{:src => "http://www.google.com/intl/en_us/mapfiles/ms/micons/green-dot.png"} + %a{:onclick => "showMarkerSet(onMarkers);", :class => "markers show", :style => "cursor: pointer"} Show People on CommonPlace + | + %a{:onclick => "hideMarkerSet(onMarkers);", :class => "markers hide", :style => "cursor: pointer"} Hide People on CommonPlace + %br + %img{:src => "http://www.google.com/intl/en_us/mapfiles/ms/micons/yellow-dot.png"} + %a{:onclick => "showMarkerSet(droppedMarkers);", :class => "markers show", :style => "cursor: pointer"} Show Doordrops + | + %a{:onclick => "hideMarkerSet(droppedMarkers);", :class => "markers hide", :style => "cursor: pointer"} Hide Doordrops + %br + %img{:src => "http://www.google.com/intl/en_us/mapfiles/ms/micons/orange-dot.png"} + %a{:onclick => "showMarkerSet(knockedMarkers);", :class => "markers show", :style => "cursor: pointer"} Show Doorknocks + | + %a{:onclick => "hideMarkerSet(knockedMarkers);", :class => "markers hide", :style => "cursor: pointer"} Hide Doorknocks + %br + %img{:src => "http://www.google.com/intl/en_us/mapfiles/ms/micons/blue-dot.png"} + %a{:onclick => "showMarkerSet(postKnockedMarkers);", :class => "markers show", :style => "cursor: pointer"} Show Post-Knocks + | + %a{:onclick => "hideMarkerSet(postKnockedMarkers);", :class => "markers hide", :style => "cursor: pointer"} Hide Post-Knocks + %br + %img{:src => "http://www.google.com/intl/en_us/mapfiles/ms/micons/red-dot.png"} + %a{:onclick => "showMarkerSet(noMarkers);", :class => "markers show", :style => "cursor: pointer"} Show Negative Responses + | + %a{:onclick => "hideMarkerSet(noMarkers);", :class=> "markers hide", :style => "cursor: pointer"} Hide Negative Responses + + #community_overview{:style => "display: none;"} + = render :partial => "admin/community_overview", :collection => [current_community] + #google_doc{:style => "display: none;"} + - if current_community.google_docs_url.present? + %iframe{:src => current_community.google_docs_url, :width => "100%", :height => "800px"} + - else + No such document exists + #community_referrers{:style => "display: none;"} + %h1 Recent Referrals + = render :partial => "admin/community_referrer", :collection => [current_community] + #daily_journal{:style => "display: none;"} + %iframe{:src => "https://docs.google.com/spreadsheet/embeddedform?formkey=dEJIZHNyYjdLOUFMUEpEMkZ5dHFBa0E6MQ", :width => "760", :height => "3301", :frameborder => "0", :marginheight => "0", :marginwidth => "0"} diff --git a/app/views/organizer/map.haml b/app/views/organizer/map.haml new file mode 100644 index 000000000..bbbb2d882 --- /dev/null +++ b/app/views/organizer/map.haml @@ -0,0 +1,311 @@ +:css + #referral_sources { + text-align: center; + } + table { + border-width: 1px; + border-style: solid; + border-color: #000000; + border-collapse: collapse; + width: 100%; + } + + th { + border-width: 1px; + border-style: solid; + border-color: #000000; + text-align: center; + font-weight: bold; + } + + td { + border-width: 1px; + border-style: solid; + border-color: #000000; + text-align: center; + } + + h1 { + margin: 20px auto; + font-size: 28px; + color: blue; + } +%script{:src => "http://maps.google.com/maps?file=api&v=2&key=#{google_api_key()}", :type => "text/javascript"} +%script{:src => "http://acme.com/javascript/Clusterer2.jsm", :type => "text/javascript"} +:javascript + var map; + var clusterer; + var greenMarkerOptions, redMarkerOptions, orangeMarkerOptions, blueMarkerOptions; + var allHouseMarkers = Array(); + var droppedMarkers = Array(); + var knockedMarkers = Array(); + var postKnockedMarkers = Array(); + var noMarkers = Array(); + var onMarkers = Array(); + function createMarker(point, html, colorOptions) { + var marker = new GMarker(point, colorOptions); + GEvent.addListener(marker, "click", function() { + marker.openInfoWindowHtml(html); + }); + return marker; + } + function createMarkerLight(point, html, colorOptions) { + var marker = new MarkerLight(point, colorOptions); + return marker; + } + function initialize() { + console.log("Initializing"); + if (GBrowserIsCompatible()) { + map = new GMap2(document.getElementById("map_canvas")); + map.enableContinuousZoom(); + map.enableGoogleBar(); + map.enableScrollWheelZoom(); + map.enablePinchToZoom(); + control = new GLargeMapControl() + map.addControl(control, control.getDefaultPosition()) + clusterer = new Clusterer(map); + clusterer.SetMaxVisibleMarkers(1500); + geocoder = new GClientGeocoder(); + geocoder.getLatLng('#{@center_zip_code}', function(point){ map.setCenter(point, 16); }); + console.log("Centered"); + map.setCenter(new GLatLng(37.4419, -122.1419), 1); + + var greyIcon = new GIcon(G_DEFAULT_ICON); + greyIcon.image = "http://maps.google.com/mapfiles/kml/shapes/shaded_dot.png"; + greyMarkerOptions = { icon: greyIcon }; + + var greenIcon = new GIcon(G_DEFAULT_ICON) + greenIcon.image = "http://www.google.com/intl/en_us/mapfiles/ms/micons/green-dot.png" + greenMarkerOptions = { icon: greenIcon }; + + var redIcon = new GIcon(G_DEFAULT_ICON); + redIcon.image = "http://www.google.com/intl/en_us/mapfiles/ms/micons/red-dot.png" + redMarkerOptions = { icon: redIcon }; + + var yellowIcon = new GIcon(G_DEFAULT_ICON); + yellowIcon.image = "http://www.google.com/intl/en_us/mapfiles/ms/micons/yellow-dot.png"; + yellowMarkerOptions = { icon: yellowIcon }; + + var orangeIcon = new GIcon(G_DEFAULT_ICON); + orangeIcon.image = "http://www.google.com/intl/en_us/mapfiles/ms/micons/orange-dot.png"; + orangeMarkerOptions = { icon: orangeIcon }; + + var blueIcon = new GIcon(G_DEFAULT_ICON); + blueIcon.image = "http://www.google.com/intl/en_us/mapfiles/ms/micons/blue-dot.png"; + blueMarkerOptions = { icon: blueIcon }; + } + drawPoints(); + } + + function showMarkerSet(markers, cluster) + { + cluster = typeof(cluster) != 'undefined' ? cluster : false; + $.each(markers, function(key, marker){ + if (cluster) + clusterer.AddMarker(marker); + else + map.addOverlay(marker); + }); + } + + function hideMarkerSet(markers, cluster) + { + cluster = typeof(cluster) != 'undefined' ? cluster : false; + $.each(markers, function(key, marker){ + if (cluster) + clusterer.RemoveMarker(marker); + else + map.removeOverlay(marker); + }); + } + + function drawPoints() + { + console.log("Getting reg points"); + $.ajax({ + url: '/api/communities/#{@community_id}/registration_points', + dataType: 'jsonp', + success: function(data) { + console.log(data); + console.log("Got reg points"); + $.each(data, function(key, val) { + var point = new GLatLng(val['lat'], val['lng']); + var marker = createMarker(point, val['name'] + '
          ' + val['address'], greenMarkerOptions); + onMarkers.push(marker); + }); + showMarkerSet(onMarkers); + } + }); + console.log("Getting data points"); + $.ajax({ + url: '/api/communities/#{@community_id}/data_points?top=true', + dataType: 'jsonp', + success: function(data) { + console.log(data); + console.log("Got data points"); + $.each(data, function(key, val) { + var colorOption; + if (val['status'] == 'house') { + colorOption = greyMarkerOptions; + var point = new GLatLng(val['lat'], val['lng']); + var marker = createMarker(point, val['address'], greyMarkerOptions); + allHouseMarkers.push(marker); + } + if (val['status'] == 'dropped') { + colorOption = yellowMarkerOptions; + if (val['lat'] && val['lng']) + { + var point = new GLatLng(val['lat'], val['lng']); + var marker = createMarker(point, val['address'], colorOption); + droppedMarkers.push(marker); + } + else + geocoder.getLatLng(val['address'], function(p){ droppedMarkers.push(new GMarker(p, colorOption)); }); + } + if (val['status'] == 'knocked') { + colorOption = orangeMarkerOptions; + if (val['lat'] && val['lng']) + { + var point = new GLatLng(val['lat'], val['lng']); + var marker = createMarker(point, val['address'], colorOptions); + knockedMarkers.push(marker); + } + else + geocoder.getLatLng(val['address'], function(p){ knockedMarkers.push(new GMarker(p, colorOption)); }); + } + if (val['status'] == 'post_knocked_1') { + colorOption = blueMarkerOptions; + if (val['lat'] && val['lng']) + { + var point = new GLatLng(val['lat'], val['lng']); + var marker = createMarker(point, '', colorOptions); + postKnockedMarkers.push(marker); + } + else + geocoder.getLatLng(val['address'], function(p){ postKnockedMarkers.push(new GMarker(p, colorOption)); }); + } + if (val['status'] == 'no') { + colorOption = redMarkerOptions; + if (val['lat'] && val['lng']) + { + var point = new GLatLng(val['lat'], val['lng']); + var marker = createMarker(point, val['address'], colorOptions); + noMarkers.push(marker); + } + else + geocoder.getLatLng(val['address'], function(p){ noMarkers.push(new GMarker(p, colorOption)); }); + } + }); + console.log("Showing all markers"); + showMarkerSet(allHouseMarkers.concat(droppedMarkers).concat(knockedMarkers).concat(postKnockedMarkers).concat(noMarkers)); + } + }); + + } + $(document).ready(initialize); + $(document).ready(function(){ + $('.marker').onHover(function(){ + document.body.style.cursor = 'pointer'; + }); + }); + var TOGGLE_OPTS = 'slow'; + function toggle_map() + { + hide_all(); + $("#map").toggle(TOGGLE_OPTS); + } + function toggle_overview() + { + hide_all(); + $("#community_overview").toggle(TOGGLE_OPTS); + } + function toggle_doc() + { + hide_all(); + $("#google_doc").toggle(TOGGLE_OPTS); + } + function toggle_referrers() + { + hide_all(); + $("#community_referrers").toggle(TOGGLE_OPTS); + } + function toggle_journal() + { + hide_all(); + $("#daily_journal").toggle(TOGGLE_OPTS); + } + function hide_all() + { + $("#map").hide(); + $("#community_overview").hide(); + $("#google_doc").hide(); + $("#community_referrers").hide(); + $("#daily_journal").hide(); + } + + var entry_count = 0; + function add_entry() + { + entry_count += 1; + $("#addresses").append('
          '); + } +#organizer_app + #links + %a{:onclick => "toggle_map();", :style=>"text-decoration: underline;"} Toggle Map + %br + %a{:onclick => "toggle_overview();", :style=>"text-decoration: underline;"} Overview + %br + %a{:onclick => "toggle_doc();", :style=>"text-decoration: underline;"} Community Leaders + %br + %a{:onclick => "toggle_referrers();", :style=>"text-decoration: underline;"} Referrers + %br + %a{:onclick => "toggle_journal();", :style => "text-decoration: underline;"} Daily Journal + %br + #map{:style => "display: none;"} + #map_canvas{:style => "margin-left: 200px; width: 350px; height: 350px;"} + #map_form + = form_tag "/organizer/add", :method => :post do + #addresses + = text_field_tag :number, "", :size => 10, :placeholder => "1-10O" + = text_field_tag :address, "", :placeholder => "Maple Ave", :size => 50 + = @center_zip_code + = select_tag :status, options_for_select([["Door-Dropped", 'dropped'], ['Door-Knocked', 'knocked'], ['Post-Knocked Once', 'post_knocked_1'], ['Impossibility', 'no']]) + %a{:onclick => "add_entry();"} Add More + %br + = submit_tag "Enter" + %a{:onclick => "showMarkerSet(allHouseMarkers);", :class => "markers show"} Show All Houses + | + %a{:onclick => "hideMarkerSet(allHouseMarkers);", :class => "markers hide"} Hide All Houses + %br + %a{:onclick => "showMarkerSet(onMarkers);", :class => "markers show"} Show People on CommonPlace + | + %a{:onclick => "hideMarkerSet(onMarkers);", :class => "markers hide"} Hide People on CommonPlace + %br + %a{:onclick => "showMarkerSet(droppedMarkers);", :class => "markers show"} Show Doordrops + | + %a{:onclick => "hideMarkerSet(droppedMarkers);", :class => "markers hide"} Hide Doordrops + %br + %a{:onclick => "showMarkerSet(knockedMarkers);", :class => "markers show"} Show Doorknocks + | + %a{:onclick => "hideMarkerSet(knockedMarkers);", :class => "markers hide"} Hide Doorknocks + %br + %a{:onclick => "showMarkerSet(postKnockedMarkers);", :class => "markers show"} Show Post-Knocks + | + %a{:onclick => "hideMarkerSet(postKnockedMarkers);", :class => "markers hide"} Hide Post-Knocks + %br + %a{:onclick => "showMarkerSet(noMarkers);", :class => "markers show"} Show Negative Responses + | + %a{:onclick => "hideMarkerSet(noMarkers);", :class=> "markers hide"} Hide Negative Responses + + #community_overview{:style => "display: none;"} + = render :partial => "admin/community_overview", :collection => [current_community] + #google_doc{:style => "display: none;"} + - if current_community.google_docs_link.present? + %iframe{:src => current_community.google_docs_link, :width => "100%", :height => "300px"} + - else + No such document exists + #community_referrers{:style => "display: none;"} + %h1 Recent Referrals + = render :partial => "admin/community_referrer", :collection => [current_community] + #daily_journal{:style => "display: none;"} + Coming soon :-) diff --git a/app/views/password_resets/edit.haml b/app/views/password_resets/edit.haml index 0424dd382..c72962a23 100644 --- a/app/views/password_resets/edit.haml +++ b/app/views/password_resets/edit.haml @@ -1,10 +1,12 @@ +#main + = semantic_form_for(resource, :url => user_password_url, :html => { :method => "put"}) do |f| + = f.inputs do + = f.input :reset_password_token, :as => :hidden + = f.input :password, :label => "Your new password:" + = f.input :password_confirmation, :label => "Confirm your new password:" + = f.buttons do + = f.commit_button :button_html => {:type => "image", :src => asset_path("buttons/login2.png")} -- semantic_form_for @user, :url => password_reset_path, :method => :put do |f| - %h2 Enter a new password - - f.inputs do - = f.input :password - - f.buttons do - = f.commit_button 'Update' - + diff --git a/app/views/password_resets/new.haml b/app/views/password_resets/new.haml index 14b45f2e5..724e369c4 100644 --- a/app/views/password_resets/new.haml +++ b/app/views/password_resets/new.haml @@ -1,18 +1,7 @@ - -- form_tag password_resets_path, :method => :post, :id => "new_password_reset" do - - %h2 Reset Password - %p.subhead Please enter the email associated with your account. - - - if flash[:error] - %p.fail= flash[:error] - - %fieldset - %ol - %li - = label_tag :email, "Email" - = text_field_tag :email - %fieldset - %ol - #submit_wrapper - %li= submit_tag "Send" +#main + %h1 Enter the email you registered with to reset your password + = semantic_form_for(:user, :url => user_password_url, :html => {:class => "forgot-password"}) do |f| + = f.inputs do + = f.input :email + = f.buttons do + = f.submit "Send me password reset instructions" diff --git a/app/views/password_resets/show.haml b/app/views/password_resets/show.haml deleted file mode 100644 index dd49d65ab..000000000 --- a/app/views/password_resets/show.haml +++ /dev/null @@ -1,3 +0,0 @@ - -.lightbox - %p Instructions have been sent to #{@user.email} diff --git a/app/views/posts/_form.haml b/app/views/posts/_form.haml deleted file mode 100644 index e39d6d0e1..000000000 --- a/app/views/posts/_form.haml +++ /dev/null @@ -1,9 +0,0 @@ - -- semantic_form_for post, :html=>{'data-remote' => true} do |f| - - f.inputs do - = f.input :subject - = f.input :body - - f.buttons do - = f.commit_button :button_html => {:type => "image", :src => "/images/buttons/post-now.png"} - %legend - Have an announcement, need, offer, or question? diff --git a/app/views/posts/_list.haml b/app/views/posts/_list.haml deleted file mode 100644 index b6fc1b80e..000000000 --- a/app/views/posts/_list.haml +++ /dev/null @@ -1 +0,0 @@ -= render 'shared/list', :items => @posts \ No newline at end of file diff --git a/app/views/posts/_post.haml b/app/views/posts/_post.haml deleted file mode 100644 index d2cfb8678..000000000 --- a/app/views/posts/_post.haml +++ /dev/null @@ -1,11 +0,0 @@ -%div{'data-href' => post_path(post), :class => "post tooltip", 'data-title' => "Click to see replies and your neighbor's profile."} - = image_tag post.owner.avatar.url(:thumb), :class => "avatar" - %time - =post.time - - if can? :destroy, post - = post.deleteLink - - .reply-count #{ post.replies.size } replies - = link_to post.subject, post, 'data-remote' => true, :class => "title" - .author= post.owner.name - .body= markdown auto_link(post.body, :all, :target => "_blank") diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml deleted file mode 100644 index dbe3851d2..000000000 --- a/app/views/posts/index.haml +++ /dev/null @@ -1,7 +0,0 @@ -- update_content "#syndicate" do - #syndicate - %h3= t "syndicate.posts.title" - = render 'shared/items', :items => @items - -- update_content "#say-something form" do - = render 'form', :post => Post.new \ No newline at end of file diff --git a/app/views/posts/new.haml b/app/views/posts/new.haml deleted file mode 100644 index c7dbd008c..000000000 --- a/app/views/posts/new.haml +++ /dev/null @@ -1,5 +0,0 @@ -= update_content "#say-something" do - #say-something - %h2= t ".title" - = render 'shared/say_something_nav', :current => :post - = render 'form', :post => post diff --git a/app/views/posts/show.haml b/app/views/posts/show.haml deleted file mode 100644 index ae8d35124..000000000 --- a/app/views/posts/show.haml +++ /dev/null @@ -1,9 +0,0 @@ -= render 'shared/tooltip' - -- update_content "#information" do - = render 'users/info_box', :user => @post.user - -- unless xhr? - - update_content "#syndicate" do - #syndicate - = render 'shared/items', :items => [@post] \ No newline at end of file diff --git a/app/views/profile_fields/_form.haml b/app/views/profile_fields/_form.haml deleted file mode 100644 index 9100d2d42..000000000 --- a/app/views/profile_fields/_form.haml +++ /dev/null @@ -1,13 +0,0 @@ - -- content_tag_for :li, @profile_field do - - form_for [@feed, @profile_field] do |f| - = f.hidden_field :subject - = f.hidden_field :body - %h3 - %span.admin - = f.submit 'save' - ⋅ - = link_to 'cancel', "cancel", 'data-method' => 'delete', 'data-remote' => true - %span.subject{:contenteditable => true}= @profile_field.subject - %p.body{:contenteditable => true}= @profile_field.body - \ No newline at end of file diff --git a/app/views/profile_fields/_index_form.haml b/app/views/profile_fields/_index_form.haml deleted file mode 100644 index c91c53b76..000000000 --- a/app/views/profile_fields/_index_form.haml +++ /dev/null @@ -1,12 +0,0 @@ -%ul#modules - - @feed.profile_fields.sort{ |x,y| x.position <=> y.position }.each do |p| - - content_tag_for(:li, p) do - %h3 - %span.admin - = link_to 'edit', edit_profile_field_path(p), 'data-remote' => true - ⋅ - = link_to 'delete', profile_field_path(p), :method => "delete" - %span.subject= p.subject - - %p.body= p.body - %li#new_profile_field diff --git a/app/views/profile_fields/edit.haml b/app/views/profile_fields/edit.haml deleted file mode 100644 index f5b1ba7b8..000000000 --- a/app/views/profile_fields/edit.haml +++ /dev/null @@ -1,2 +0,0 @@ - -= render 'form' diff --git a/app/views/profile_fields/index.haml b/app/views/profile_fields/index.haml deleted file mode 100644 index 956f43b34..000000000 --- a/app/views/profile_fields/index.haml +++ /dev/null @@ -1,14 +0,0 @@ -- render :layout => 'shared/modal' do - - %hr.staple - - %h1 Step 2 of 3: Create Your Profile - %p.subhead You can add profile fields to your #{@feed.category}'s CommonPlace page. Click "New Profile Field, and write in the input boxes the Header and Body of your field. For example, you could write "Hours" in the header field and "Monday Through Friday, 9 AM to 5PM" in the body field. If you add more than one profile field, you can drag and drop the fields to change the order. Click "Continue" when you are done. - - = render 'index_form' - - = button_to "New Profile Field", new_feed_profile_field_path(@feed), :method => :get, :class => "new_profile_field" - - = button_to "Save order", order_feed_profile_fields_path(@feed), :method => :put, :class => "save_profile_fields" - - = button_to "Continue", new_feed_announcement_path(@feed), :method => :get, :class => "continue" \ No newline at end of file diff --git a/app/views/profile_fields/new.haml b/app/views/profile_fields/new.haml deleted file mode 100644 index d5b312861..000000000 --- a/app/views/profile_fields/new.haml +++ /dev/null @@ -1,2 +0,0 @@ - -= render 'form' \ No newline at end of file diff --git a/app/views/profile_fields/show.haml b/app/views/profile_fields/show.haml deleted file mode 100644 index 49a0ccbbb..000000000 --- a/app/views/profile_fields/show.haml +++ /dev/null @@ -1,2 +0,0 @@ - -= render 'index_form' \ No newline at end of file diff --git a/app/views/referrals/_referral.haml b/app/views/referrals/_referral.haml deleted file mode 100644 index 5bcd49dfc..000000000 --- a/app/views/referrals/_referral.haml +++ /dev/null @@ -1,14 +0,0 @@ - -.referral_clickable - .title_top - .icon - %imp{ :alt => "Referral" } - .top_text - %span.category REFERRAL - %span.from from - %span.author= referral.referrer.full_name - %span.time= post_date referral.created_at - .bottom - image - .body - someone has referred this thing to you \ No newline at end of file diff --git a/app/views/referrals/show.js.erb b/app/views/referrals/show.js.erb deleted file mode 100644 index d1dce86ad..000000000 --- a/app/views/referrals/show.js.erb +++ /dev/null @@ -1 +0,0 @@ -alert("the referral has been saved"); \ No newline at end of file diff --git a/app/views/registrations/avatar.haml b/app/views/registrations/avatar.haml new file mode 100644 index 000000000..229e20630 --- /dev/null +++ b/app/views/registrations/avatar.haml @@ -0,0 +1,16 @@ + +#main + %h1 + =t(".h1") + = form_for registration.user, :url => crop_avatar_registration_path, :method => :put, :html => { :class => "crop" } do |f| + = image_tag registration.avatar_url(:original), :id => "cropbox" + + + - [:crop_x, :crop_y, :crop_w, :crop_h].each do |attribute| + = f.hidden_field attribute, :id => attribute + + = f.submit :type => "image", :src => asset_path("buttons/continue2.png") + +- if Rails.env.production? + :javascript + mpq.track('Cropping Avatar'); diff --git a/app/views/registrations/facebook_new.haml b/app/views/registrations/facebook_new.haml new file mode 100644 index 000000000..70ca7c72b --- /dev/null +++ b/app/views/registrations/facebook_new.haml @@ -0,0 +1,26 @@ +#main + + %h1 You are now connected through Facebook + + #registration_information + %ul{:id=>"infolist"} + %li To complete your registration, fill out your street address so we can verify you live in town. + %li If you want, feel free to adjust your display name and contact email as well. + + = semantic_form_for registration.user, :url => create_registration_url(current_community) do |f| + %h4 + Add your street address + + = f.inputs do + = f.input :full_name, :input_html => { :placeholder => "Full name"} + = f.input :email, :input_html => { :placeholder => "Email address", :type => "text"} + - if current_community.is_college + = f.select :address, college_dorms_for_school(current_community) + - else + = f.input :address, :hint => "", :input_html => {:placeholder => "Street address"} + = f.input :facebook_uid, :as => :hidden + = f.input :community_id, :as => :hidden + = f.buttons do + = f.commit_button :button_html => {:type => "image", :src => asset_path("buttons/signup.png")} + + %div{:style=>"clear: both;"} diff --git a/app/views/registrations/feeds.haml b/app/views/registrations/feeds.haml new file mode 100644 index 000000000..a7e5e8e5a --- /dev/null +++ b/app/views/registrations/feeds.haml @@ -0,0 +1,24 @@ + +%h1= t(".h1").html_safe +#main + #registration_information + %ul + %li= t ".p1" + %li= t ".p2" + = form_tag add_feeds_registration_url, :class => "add_feeds", :method => "put" do + %h2= t ".h2" + #feeds_container + - current_community.feeds.each do |feed| + .feed + =image_tag(feed.avatar_url(:thumb)) + .unchecked + %h3= feed.name + %input{:class =>"feedId", :name => "feed_ids[]", :value => feed.id.to_s, :type =>"checkbox"} + %input{:type => "image", :src => asset_path("buttons/continue2.png")} + + +- if Rails.env.production? + %script + mpq.register({'referral_source': "#{current_user.referral_source ? current_user.referral_source.gsub('\'','') : ''}"}); + mpq.register({'referral_metadata': "#{current_user.referral_metadata ? current_user.referral_metadata.gsub('\'', '') : ''}"}); + = "mpq.track('Add feeds', {'community': '#{current_community.slug}'});" diff --git a/app/views/registrations/groups.haml b/app/views/registrations/groups.haml new file mode 100644 index 000000000..2f302e98e --- /dev/null +++ b/app/views/registrations/groups.haml @@ -0,0 +1,25 @@ + +%h1= t(".h1").html_safe +#main + #registration_information + %ul + %li= t ".p1" + %li= t ".p2" + %li= t ".p3" + + = form_tag add_groups_registration_url, :id => "add_groups", :class => "add_groups", :method => "put" do + %h2 Subscribe to some groups: + #groups_container + - current_community.groups.each do |group| + .group + = image_tag(group.avatar_url) + .unchecked + %h3= group.name + %p= group.about + %input{:class =>"groupId", :name => "group_ids[]", :value => group.id.to_s, :type =>"checkbox"} + %input{:type => "image", :src => asset_path("buttons/continue2.png")} + +- if Rails.env.production? + %script + = "mpq.track('Add groups', {'community': '#{current_community.slug}'});" + diff --git a/app/views/registrations/mobile_new.html.erb b/app/views/registrations/mobile_new.html.erb new file mode 100644 index 000000000..299f6de6c --- /dev/null +++ b/app/views/registrations/mobile_new.html.erb @@ -0,0 +1,21 @@ +
            +
          • Plug Into the
            <%= current_community.name %> Community

            +

            CommonPlace is a web platform built for <%= current_community.name %> residents. It's like an online bulletin board for the <%= current_community.name %> community.

             

            +

            CommonPlace is a community service project. Join and help make <%= current_community.name %> an even better place to live.

             

            +

            Sign up by completing the short form below to send and receive community announcements written by your neighbors and local leaders here in <%= current_community.name %>.

             

            + <%= form_tag create_registration_url(current_community) do %> + +
              +
            • +
            • +
            • +
            +
              +
            • +
            • +
            + <% end %> +

             

            +
          • + +
          diff --git a/app/views/registrations/mobile_profile.html.erb b/app/views/registrations/mobile_profile.html.erb new file mode 100644 index 000000000..f1f086d16 --- /dev/null +++ b/app/views/registrations/mobile_profile.html.erb @@ -0,0 +1,5 @@ +
            +
          • +

            Thanks for signing up!

            We'll send you an email with a link to finish your registration.

            +
          • +
          diff --git a/app/views/registrations/new.haml b/app/views/registrations/new.haml new file mode 100644 index 000000000..956e1ccc6 --- /dev/null +++ b/app/views/registrations/new.haml @@ -0,0 +1,44 @@ +- if !current_community.has_launched? + .prelaunch-notification + Welcome to #{current_community.name} CommonPlace! We're officially launching on #{current_community.launch_date.strftime("%B")} #{current_community.launch_date.day.ordinalize} but you can be first to get a sneak peek. Sign up below to test it out. + +#main + + %h1= current_community.has_launched? ? t(".h1", :community => current_community.name).html_safe : "Coming soon to #{current_community.name}" + + #registration_information + %ul{:id=>"infolist"} + %li#li1= t(".li1", :community => current_community.name) + %li#li2= t(".li2", :community => current_community.name) + %li#li3= t(".li3", :community => current_community.name) + %ul{:id=>"link_item"} + %a{:href => "/#{current_community.slug}/learn_more"} + %img{:src=> asset_path("buttons/learnmore.png"), :alt=>"Learn more", :id=>"learnmore"} + + + = semantic_form_for registration.user, :url => create_registration_url(current_community) do |f| + %h4 + = link_to user_omniauth_authorize_path(:facebook, :state => current_community.id), :class => "facebook" do + = image_tag "buttons/facebook-sign-up.png" + Register Here + = f.inputs do + = f.input :full_name, :input_html => { :placeholder => "Full name"} + = f.input :email, :input_html => { :placeholder => "Email address", :type => "text"} + - if current_community.is_college + = f.select :address, college_dorms_for_school(current_community) + - else + = f.input :address, :hint => "", :input_html => {:placeholder => "Street address"} + = f.input :facebook_uid, :as => :hidden + = f.input :community_id, :as => :hidden + = f.buttons do + + = f.commit_button :button_html => {:type => "image", :src => asset_path("buttons/signup.png")} + + + + %div{:style=>"clear: both;"} + +- if Rails.env.production? + %script + = "mpq.track('Community Registration Page', {'community': '#{current_community.slug}'});" + diff --git a/app/views/registrations/profile.haml b/app/views/registrations/profile.haml new file mode 100644 index 000000000..602e19d13 --- /dev/null +++ b/app/views/registrations/profile.haml @@ -0,0 +1,32 @@ +%h1= t(".h1", :community => current_community.name).html_safe +#main + #registration_information + %ul + %li= "Thanks for signing up, #{registration.first_name}!" + %li= t ".p3" + %li= t ".p4" + + = semantic_form_for registration.user, :url => add_profile_registration_url, :html => {:multipart => true, :class => "add_profile"}, :method => :put do |f| + = f.inputs do + - unless registration.from_facebook? + = f.input :password, :required => true, :hint => t(".password_hint") + + - if registration.from_facebook? + %img{:src => registration.avatar_url, :style => "vertical-align:middle;float:left;", :width => "100px"} + = f.input :avatar, :input_html => {:size => 25}, :wrapper_html => { :class => "facebook"} , :label => "Upload a different photo?:" + %div{style: "clear: both; height: 10px"} + - else + = f.input :avatar, :input_html => {:size => 25} + = f.input :about + = f.input :interest_list, :as => :select, :collection => $interests, :multiple => true, :input_html => {"data-placeholder" => "Select some interests" }, :label => "Select some interests to share with your neighbors:" + = f.input :skill_list, :as => :select, :collection => $skills, :multiple => true, :input_html => {"data-placeholder" => "Select some skills" }, :label => "Select some skills you can share with your neighbors:" + = f.input :good_list, :as => :select, :collection => $goods, :multiple => true, :input_html => {"data-placeholder" => "Select some goods" }, :label => "Select some goods you can share with your neighbors:" + = f.input :referral_source, :as => :select, :collection => registration.referral_sources + = f.input :referral_metadata + + = f.buttons do + = f.commit_button :button_html => {:type => "image", :src => asset_path("buttons/continue2.png")} + +- if Rails.env.production? + %script + = "mpq.track('Password entry', {'community': '#{current_community.slug}'});" diff --git a/app/views/replies/_form.haml b/app/views/replies/_form.haml deleted file mode 100644 index 5bf85ded3..000000000 --- a/app/views/replies/_form.haml +++ /dev/null @@ -1,8 +0,0 @@ - -- form_for reply, :html => {'data-remote' => true} do |f| - = image_tag reply.user.avatar.url(:thumb), :class => "avatar" - = f.hidden_field :repliable_id - = f.hidden_field :repliable_type - = f.text_area :body, "data-label" => "Reply here!", :rows => 1 - #submit_wrapper - = f.submit "Reply" \ No newline at end of file diff --git a/app/views/replies/_reply.haml b/app/views/replies/_reply.haml deleted file mode 100644 index 91f76e455..000000000 --- a/app/views/replies/_reply.haml +++ /dev/null @@ -1,8 +0,0 @@ -%li.reply.tooltip{'data-title' => "Click to view #{reply.user.name}'s profile."} - - link_to reply.user, 'data-remote' => true, :class => 'tooltip' do - = image_tag reply.user.avatar.url(:thumb), :class => "avatar" - .body - %p - %span.author= reply.user.name - = reply.body - %time #{time_ago_in_words(reply.created_at, include_seconds = true)} ago \ No newline at end of file diff --git a/app/views/replies/new.haml b/app/views/replies/new.haml deleted file mode 100644 index 37bc84928..000000000 --- a/app/views/replies/new.haml +++ /dev/null @@ -1 +0,0 @@ -= render 'shared/replies', :item => @reply.repliable, :hide => false \ No newline at end of file diff --git a/app/views/replies/show.haml b/app/views/replies/show.haml deleted file mode 100644 index ac79b6831..000000000 --- a/app/views/replies/show.haml +++ /dev/null @@ -1,3 +0,0 @@ - -- update_content "##{dom_id(@reply.repliable)}_replies" do - = render('shared/replies', :item => @reply.repliable, :hide => false) \ No newline at end of file diff --git a/app/views/rss_announcements/_rss_announcement.haml b/app/views/rss_announcements/_rss_announcement.haml deleted file mode 100644 index 532b62328..000000000 --- a/app/views/rss_announcements/_rss_announcement.haml +++ /dev/null @@ -1,7 +0,0 @@ -%div{'data-href' => announcement_path(rss_announcement), :class => "announcement tooltip", 'data-title' => "Click to see replies and this community feed's profile"} - = image_tag rss_announcement.owner.avatar_file_name, :class => 'avatar' - %time= rss_announcement.time - .reply-count #{ rss_announcement.replies.size } replies - .title= rss_announcement.subject - .author= link_to(rss_announcement.owner.name,rss_announcement.owner.profile_link) - .body= markdown auto_link(rss_announcement.body, :all, :target => "_blank") \ No newline at end of file diff --git a/app/views/rss_feeds/_rss_feed.haml b/app/views/rss_feeds/_rss_feed.haml deleted file mode 100644 index b744ea40b..000000000 --- a/app/views/rss_feeds/_rss_feed.haml +++ /dev/null @@ -1,3 +0,0 @@ -%div{'data-href' => feed_path(rss_feed), :class => "feed tooltip", 'data-title' => 'Click to see more information about this feed'} - = image_tag rss_feed.avatar_file_name, :class => "avatar" - .title= rss_feed.name \ No newline at end of file diff --git a/app/views/shared/_admin_bar.haml b/app/views/shared/_admin_bar.haml index 3d8d91c25..4e3275d47 100644 --- a/app/views/shared/_admin_bar.haml +++ b/app/views/shared/_admin_bar.haml @@ -1,7 +1,9 @@ - if current_user.admin? - #admin_bar - = render 'shared/deliveries' - Neighborhood: + #admin_bar{:style => "display: none"} - current_community.neighborhoods.each do |neighborhood| - = button_to "#{neighborhood.name}: #{neighborhood.users.count}u/#{neighborhood.posts.count}p", set_neighborhood_path(neighborhood), :title => "users/posts", :class => current_neighborhood == neighborhood ? "current" : "" + %a{:href => "/?neighborhood_id=#{neighborhood.id}"} + #{neighborhood.name} + \: #{neighborhood.users.count}u/#{Post.includes(:user).where(:users => {:neighborhood_id => neighborhood.id}).count}   + Total Members: #{current_community.users.count} + Total Posts: #{current_community.posts.count} diff --git a/app/views/shared/_deliveries.haml b/app/views/shared/_deliveries.haml deleted file mode 100644 index 1c62d6f8c..000000000 --- a/app/views/shared/_deliveries.haml +++ /dev/null @@ -1,12 +0,0 @@ -- if RAILS_ENV != "production" - #deliveries - Deliveries(#{ActionMailer::Base.deliveries.count}) - %ul - - ActionMailer::Base.deliveries.each do |delivery| - %li - %p To: #{ delivery.to } - %p From: #{ delivery.from } - %p Subject: #{ delivery.subject } - %div.body= delivery.body - %hr - - ActionMailer::Base.deliveries = [] \ No newline at end of file diff --git a/app/views/shared/_info_modal.haml b/app/views/shared/_info_modal.haml deleted file mode 100644 index 95dfdf834..000000000 --- a/app/views/shared/_info_modal.haml +++ /dev/null @@ -1,13 +0,0 @@ -- update_content "#modal" do - #modal.info.not_empty - #modal-overlay - #modal-content - - link_to "", :id => "modal-close", 'data-remote' => true do - = image_tag 'modal-close.png' - - = yield - - - - - \ No newline at end of file diff --git a/app/views/shared/_items.haml b/app/views/shared/_items.haml deleted file mode 100644 index afd2242e0..000000000 --- a/app/views/shared/_items.haml +++ /dev/null @@ -1,9 +0,0 @@ -%ul.items - - items.each do |item| - %li.item{ :class => "#{cycle('odd', 'even')} #{dom_class(item)} #{dom_id(item)}" } - - if @render_args - = render *@render_args.call(item) - - else - = render item - - if item.respond_to?(:replies) - = render 'shared/replies', :item => item, :hide => true \ No newline at end of file diff --git a/app/views/shared/_modal.haml b/app/views/shared/_modal.haml index 175419efd..9908993c3 100644 --- a/app/views/shared/_modal.haml +++ b/app/views/shared/_modal.haml @@ -1,11 +1,10 @@ -- update_content "#modal" do - #modal.not_empty - #modal-overlay - #modal-content - - link_to "", :id => "modal-close", 'data-remote' => true do - = image_tag 'modal-close.png' - - = yield + +#modal.not_empty + #modal-overlay + #modal-content + = image_tag 'modal-close.png', :id => "modal-close" + + = yield diff --git a/app/views/shared/_replies.haml b/app/views/shared/_replies.haml deleted file mode 100644 index 0108ddfa2..000000000 --- a/app/views/shared/_replies.haml +++ /dev/null @@ -1,11 +0,0 @@ -%div{:id => "#{dom_id(item)}_replies", :class => ["replies",hide ? "hide": nil].join(" ")} - = image_tag 'reply-arrow-top.png', :class => "reply-arrow" - - if can? :read, Reply - %ul - - item.replies.each do |reply| - = render 'replies/reply', :reply => reply - = render 'replies/form', :reply => item.replies.new(:user => current_user) - - else - %p Register for CommonPlace to be able to reply. - - diff --git a/app/views/shared/_say_something_nav.haml b/app/views/shared/_say_something_nav.haml deleted file mode 100644 index d43e31be5..000000000 --- a/app/views/shared/_say_something_nav.haml +++ /dev/null @@ -1,5 +0,0 @@ -%nav - = link_to t("posts.new.link"), new_post_path, 'data-remote' => true, :class => current == :post ? "current" : "" - = link_to t("events.new.link"), new_event_path, 'data-remote' => true, :class => current == :event ? "current" : "" - = link_to t("announcements.new.link"), new_announcement_path, 'data-remote' => true, :class => current == :announcement ? "current" : "" - = link_to t("invites.new.link"), new_invites_path, 'data-remote'=> true, :class => current == :invites ? "current" : "" diff --git a/app/views/shared/_tooltip.haml b/app/views/shared/_tooltip.haml deleted file mode 100644 index 47b2d70ff..000000000 --- a/app/views/shared/_tooltip.haml +++ /dev/null @@ -1,2 +0,0 @@ -- update_content "#tooltip" do - #tooltip{ :title => (flash[:message] || t("communities.whats_happening.title"))} \ No newline at end of file diff --git a/app/views/shared/_zones.haml b/app/views/shared/_zones.haml deleted file mode 100644 index 5001340fc..000000000 --- a/app/views/shared/_zones.haml +++ /dev/null @@ -1,23 +0,0 @@ -%nav#zones - - tab_to :posts, :class => 'posts zone tooltip', 'data-title' => t('tooltips.posts'), 'data-remote' => true do - %div - %span Neighborhood Posts - - - tab_to :events, :class => 'events zone tooltip', 'data-title' => t('tooltips.events'), 'data-remote' => true do - %div - %span Events - - - tab_to :announcements, :class => 'announcements zone tooltip', 'data-title' => t('tooltips.announcements'), 'data-remote' => true do - %div - %span Announcements - - - tab_to :feeds, :class => 'feeds zone tooltip', 'data-title' => t('tooltips.feeds'), 'data-remote' => true do - %div - %span Community Feeds - - - tab_to :users, :class => 'people zone tooltip', 'data-title' => t('tooltips.people'), 'data-remote' => true do - %div - %span Directory - - - diff --git a/app/views/site/dmca.haml b/app/views/site/dmca.haml index 3c6418619..a54fcefa4 100644 --- a/app/views/site/dmca.haml +++ b/app/views/site/dmca.haml @@ -1,51 +1,49 @@ -- render :layout => 'shared/info_modal' do - - .info.dmca - :textile - h1. Digital Millennium Copyright Act Notice and Counter Notice +.info.dmca + :textile + h1. Digital Millennium Copyright Act Notice and Counter Notice - h2. Copyright Infringement Notification + h2. Copyright Infringement Notification - If you believe that content available on or through this site (“Website”) infringes one or more of your copyrights, please send our Copyright Agent by mail, email, or fax a notification (“Notification”) providing the information described below. A copy of your Notification will be sent to the person who posted the material addressed in the Notification. + If you believe that content available on or through this site (“Website”) infringes one or more of your copyrights, please send our Copyright Agent by mail, email, or fax a notification (“Notification”) providing the information described below. A copy of your Notification will be sent to the person who posted the material addressed in the Notification. - Pursuant to federal law you may be held liable for damages and attorneys’ fees if you make any material misrepresentations in a Notification. Thus, if you are not sure whether content located on or accessible via a link from the Website infringes your copyright, you should contact an attorney. + Pursuant to federal law you may be held liable for damages and attorneys’ fees if you make any material misrepresentations in a Notification. Thus, if you are not sure whether content located on or accessible via a link from the Website infringes your copyright, you should contact an attorney. - All Notifications should include the following: + All Notifications should include the following: - # A physical or electronic signature of a person authorized to act on behalf of the owner of an exclusive right that is allegedly infringed. - # Identification of the copyrighted work claimed to have been infringed, or, if multiple copyrighted works at a single online site are covered by a single notification, a representative list of such works at that site. - # Identification of the material that is claimed to be infringing or to be the subject of infringing activity and that is to be removed or access to which is to be disabled, and information reasonably sufficient to permit the service provider to locate the material. - # Information reasonably sufficient to permit the service provider to contact the complaining party, such as an address, telephone number, and an email address at which the complaining party may be contacted. - # A statement that the complaining party has a good faith belief that use of the material in the manner complained of is not authorized by the copyright owner, its agent, or the law. - # A statement that the information in the Notification is accurate, and under penalty of perjury, that the complaining party is authorized to act on behalf of the owner of an exclusive right that is allegedly infringed. - Notifications should be sent to: + # A physical or electronic signature of a person authorized to act on behalf of the owner of an exclusive right that is allegedly infringed. + # Identification of the copyrighted work claimed to have been infringed, or, if multiple copyrighted works at a single online site are covered by a single notification, a representative list of such works at that site. + # Identification of the material that is claimed to be infringing or to be the subject of infringing activity and that is to be removed or access to which is to be disabled, and information reasonably sufficient to permit the service provider to locate the material. + # Information reasonably sufficient to permit the service provider to contact the complaining party, such as an address, telephone number, and an email address at which the complaining party may be contacted. + # A statement that the complaining party has a good faith belief that use of the material in the manner complained of is not authorized by the copyright owner, its agent, or the law. + # A statement that the information in the Notification is accurate, and under penalty of perjury, that the complaining party is authorized to act on behalf of the owner of an exclusive right that is allegedly infringed. + Notifications should be sent to: - Max Novendstern + Max Novendstern - CommonPlace Initiative + CommonPlace Initiative - 1033 Massachusetts Ave - Cambridge, MA 02138 + 1033 Massachusetts Ave + Cambridge, MA 02138 - Email: "dmca@commonplace.in":mailto:dmca@commonplace.in + Email: "dmca@commonplace.in":mailto:dmca@commonplace.in - h2. Counter Notification + h2. Counter Notification - If you believe that material that you posted to the site has been wrongfully removed in response to a Copyright Infringement Notification submitted by a copyright owner pursuant to the Digital Millennium Copyright Act, you may send us a counter notification (“Counter Notification”) asking that the material be restored (if you are eligible to have the material restored as outlined below). Pursuant to federal law you may be held liable for damages and attorneys' fees if you make any material misrepresentations in a Counter Notification. Please note that we are required to send a copy of your Counter Notification to the person who submitted the original Copyright Infringement Notification and that in response to a Counter Notification that person may file a lawsuit against you seeking a determination of its rights with respect to the material. + If you believe that material that you posted to the site has been wrongfully removed in response to a Copyright Infringement Notification submitted by a copyright owner pursuant to the Digital Millennium Copyright Act, you may send us a counter notification (“Counter Notification”) asking that the material be restored (if you are eligible to have the material restored as outlined below). Pursuant to federal law you may be held liable for damages and attorneys' fees if you make any material misrepresentations in a Counter Notification. Please note that we are required to send a copy of your Counter Notification to the person who submitted the original Copyright Infringement Notification and that in response to a Counter Notification that person may file a lawsuit against you seeking a determination of its rights with respect to the material. - To be effective your Counter Notification must be a written communication provided to CP’s designated agent that includes substantially the following information: + To be effective your Counter Notification must be a written communication provided to CP’s designated agent that includes substantially the following information: - # Your physical or electronic signature. - # Identification of the material that has been removed or to which access has been disabled and the location at which the material appeared before it was removed or access to it was disabled. - # A statement under penalty of perjury that you have a good faith belief that the material was removed or disabled as a result of mistake or misidentification or the material to be removed or disabled. - # Your name, address, telephone number, and a statement that you consent to the jurisdiction of the Federal District Court for the judicial district where you live and that you will accept service of process from the person who submitted the Notification (or an agent for that person). - Counter Notifications should be sent to: + # Your physical or electronic signature. + # Identification of the material that has been removed or to which access has been disabled and the location at which the material appeared before it was removed or access to it was disabled. + # A statement under penalty of perjury that you have a good faith belief that the material was removed or disabled as a result of mistake or misidentification or the material to be removed or disabled. + # Your name, address, telephone number, and a statement that you consent to the jurisdiction of the Federal District Court for the judicial district where you live and that you will accept service of process from the person who submitted the Notification (or an agent for that person). + Counter Notifications should be sent to: - Max Novendstern + Max Novendstern - CommonPlace Initiative + CommonPlace Initiative - 1033 Massachusetts Ave - Cambridge, MA 02138 + 1033 Massachusetts Ave + Cambridge, MA 02138 - Email: "dmca@commonplace.in":mailto:dmca@commonplace.in \ No newline at end of file + Email: "dmca@commonplace.in":mailto:dmca@commonplace.in diff --git a/app/views/site/index.haml b/app/views/site/index.haml new file mode 100644 index 000000000..fb6af0222 --- /dev/null +++ b/app/views/site/index.haml @@ -0,0 +1,133 @@ +#first + .container-12 + #tabsmenu.grid-6 + %ul.tabs + %li#ini + %a{:href=>"#", :class=>"current"}="The Initiative" + %li#tec + %a{:href=>"#"}="Our Technology" + %li#org + %a{:href=>"#"}="Organizing" + .panes + #init + %h3="The Initiative" + %p + CommonPlace is a web platform for local community engagement. + %p + Our technology is designed to make it really easy for neighbors and community leaders to share information about local events, neighborhood needs or community announcements with the folks who live in their town. + %p + CommonPlace works with communities across the country. We’d love to work with yours! If you’re interested in bringing CommonPlace to your town, contact + =mail_to("Organizing@CommonPlaceUSA.com", "Organizing@CommonPlaceUSA.com") + and fill out the form to the right. + #tech + %h3="Our Technology" + #slides_wrapper + .slide_info="Post Messages to Your Town" + #slides + = image_tag "starter_site/screenshot1.png", :alt=>"Registering is Easy", :class=>"active", :height=>"248", :width=>"420px" + = image_tag "starter_site/screenshot4.png", :alt=>"Post Messages to Your Town", :height=>"248", :width=>"420px" + = image_tag "starter_site/screenshot3.png", :alt=>"Start Receiving Emails from your Neighbors", :height=>"248", :width=>"420px" + %p#startSlides + Start Slideshow + #organ + %h3="Organizing" + %p + Interested in bringing CommonPlace to your town? + %p + For our community partners, we provide: + %ul + %li="A fully hosted CommonPlace platform" + %li="A full time staff person on-the-ground in your community, helping to organize around the CommonPlace platform" + %li="Customer service" + %li="Lifetime feature updates" + #nominate.grid-6 + #nominate-padding + %p#leading="Want CommonPlace?" + %h3="Nominate Your Town:" + = semantic_form_for @request do |f| + = f.inputs do + = f.input :community_name, :label=>"Name of Community:" + = f.input :name, :label => "Your name:" + = f.input :email, :label => "Your email:" + = f.input :sponsor_organization, :label => "Sponsoring organization:" + = f.buttons do + = f.commit_button :label => "Submit Now" + + .clear +#middle + .grid-12 + #heart + %h3 + Neighbors + = image_tag "starter_site/heart.png", :alt=>"Heart" + CommonPlace! + .clear +#end + #scroller + .items + .grid-4.rotate + %p + "I love getting to know my neighbors better, and it has led me to talk in person to neighbors I might not have ever crossed paths with by starting communications through a posting." + = image_tag "starter_site/user1.png", :alt=>"Julie Paradiso" + %h4="Julie Paradiso" + %h5 + Neighbor + %br + Falls Church, VA + + .grid-4.rotate + %p + "I've posted on and responded to Falls Church CommonPlace posts. People attended a community event I promoted. I found work through it. And I scored a free perfect-condition TV from it." + = image_tag "starter_site/person5.png", :alt=>"Debra Z. Roth" + %h4="Debra Z. Roth" + %h5 + Neighbor + %br + Falls Church, VA + .grid-4.rotate + %p + "It really makes living in Falls Church feel more neighborly because it increases interaction with people we sometimes have been living a long side of for a long time." + = image_tag "starter_site/user1.png", :alt=>"Julie Read" + %h4="Julie Read" + %h5 + Neighbor + %br + Falls Church, VA + .grid-4.rotate + %p + "CommonPlace has been a wonderful addition to the Falls Church Community. Don't know how we got along before without it." + = image_tag "starter_site/person6.png", :alt=>"Barry Buschow" + %h4="Barry Buschow" + %h5 + Civic Leader + %br + Falls Church, VA + .grid-4.rotate + %p + "I have used CommonPlace several times in the past 2 months. I love the site. [...] I feel part of a dynamic community by being able to use CommonPlace so easily." + = image_tag "starter_site/user1.png", :alt=>"James Kestell" + %h4="James Kestell" + %h5 + Neighbor + %br + Falls Church, VA + .grid-4.rotate + %p + "CommonPlace provides a great way to reconnect with neighbors, exchange ideas and resources" + = image_tag "starter_site/user1.png", :alt=>"Ariadne Autor" + %h4="Ariadne Autor" + %h5 + Neighbor + %br + Falls Church, VA + + .clear + :javascript + setTimeout(function(){var a=document.createElement("script"); + var b=document.getElementsByTagName('script')[0]; + a.src=document.location.protocol+"//dnn506yrbagrg.cloudfront.net/pages/scripts/0004/8420.js"; + a.async=true;a.type="text/javascript";b.parentNode.insertBefore(a,b)}, 1); + + - if Rails.env.production? + :javascript + mpq.track('Index Page', {'community': '#{current_community.slug}'}); diff --git a/app/views/site/privacy.haml b/app/views/site/privacy.haml index ceb68c5da..e8af1f982 100644 --- a/app/views/site/privacy.haml +++ b/app/views/site/privacy.haml @@ -1,85 +1,83 @@ -- render :layout => 'shared/info_modal' do +.info.privacy + :textile + _This policy refers to the current iteration and features of the CommonPlace platform as well as planned iterations and features to be implemented in the near future._ + + h1. Privacy Policy - .info.privacy - :textile - _This policy refers to the current iteration and features of the CommonPlace platform as well as planned iterations and features to be implemented in the near future._ - - h1. Privacy Policy + CommonPlace (“CP”) respects your privacy and values being transparent regarding how its website (“Website”) works. This Privacy Policy is intended to inform you, as a user of the Website, about the types of information that CP may gather about or collect from you in connection with your use of the Website. It is also intended to explain the conditions under which CP uses and discloses that information, and your rights in relation to that information. - CommonPlace (“CP”) respects your privacy and values being transparent regarding how its website (“Website”) works. This Privacy Policy is intended to inform you, as a user of the Website, about the types of information that CP may gather about or collect from you in connection with your use of the Website. It is also intended to explain the conditions under which CP uses and discloses that information, and your rights in relation to that information. + The Privacy Policy may change as described below. Each time you use the Website the current version of the Privacy Policy will apply. You should check the date of the current Privacy Policy regularly and inform yourself regarding any changes. The date of this Privacy Policy is displayed at the end of this document. - The Privacy Policy may change as described below. Each time you use the Website the current version of the Privacy Policy will apply. You should check the date of the current Privacy Policy regularly and inform yourself regarding any changes. The date of this Privacy Policy is displayed at the end of this document. + h2. Use of this Website is restricted to United States and United Kingdom Residents - h2. Use of this Website is restricted to United States Residents + The Website is hosted on servers located in the United States of America and is intended for use by United States and United Kingdom residents. You may not use this Website if you are not a United States or United Kingdom resident. - The Website is hosted on servers located in the United States of America and is intended for use by United States residents. You may not use this Website if you are not a United States resident. + h2. Children - h2. Children + The Website is not intended for children under 18 years of age. CP does not knowingly collect personal information from anyone under 18. - The Website is not intended for children under 18 years of age. CP does not knowingly collect personal information from anyone under 18. + h2. Definitions - h2. Definitions + *“Non-Personally-Identifying Information”* is information that, without the aid of additional information, cannot be directly associated with a specific person. + + *“Personally-Identifying Information”* is information such as a name or email address that, without more, can be directly associated with a specific person. + + *“Neighborhood”* is a set of users defined based on their residence within a fixed geographical area. + + *“Community”* is a set of users defined based on their residence within a small number of identified Neighborhoods. + + *“Have”* is an item that you possess and are willing to lend to those in your Neighborhood. - *“Non-Personally-Identifying Information”* is information that, without the aid of additional information, cannot be directly associated with a specific person. - - *“Personally-Identifying Information”* is information such as a name or email address that, without more, can be directly associated with a specific person. - - *“Neighborhood”* is a set of users defined based on their residence within a fixed geographical area. - - *“Community”* is a set of users defined based on their residence within a small number of identified Neighborhoods. - - *“Have”* is an item that you possess and are willing to lend to those in your Neighborhood. + *“Met”* is another user that has offered a Met request that you accepted or has accepted a Met request that you offered. A Met relationship is intended to represent interaction beyond mere shared residence within a Neighborhood or Community. - *“Met”* is another user that has offered a Met request that you accepted or has accepted a Met request that you offered. A Met relationship is intended to represent interaction beyond mere shared residence within a Neighborhood or Community. + *“Wire”* is a collection of needs, events, announcements, discussions, and activities within a given Neighborhood or Community. - *“Wire”* is a collection of needs, events, announcements, discussions, and activities within a given Neighborhood or Community. + h2. Information CP receives - h2. Information CP receives + h3. Information you provide to CP - h3. Information you provide to CP + Upon registering with CP you provide CP with your name, email address, home address, gender, and birthday. After registering with the site you may update your profile with other information or content at your discretion such as information about your contact information, family, interests, or employer. You have the option of supplementing your profile with your interests, skills, and Haves. - Upon registering with CP you provide CP with your name, email address, home address, gender, and birthday. After registering with the site you may update your profile with other information or content at your discretion such as information about your contact information, family, interests, or employer. You have the option of supplementing your profile with your interests, skills, and Haves. + As a CP user you will have opportunities to post content to the Website. This content may be posted to your profile, on another user’s profile, or to the Wire. - As a CP user you will have opportunities to post content to the Website. This content may be posted to your profile, on another user’s profile, or to the Wire. + h3. Information CP gathers about you - h3. Information CP gathers about you + Like most website operators, CP gathers, from users of the Website, Non-Personally-Identifying Information of the sort that web browsers, depending on their settings, may make available. That information includes the user’s internet protocol (“IP”) address, operating system, and browser type, and the location of web pages that the user views right before arriving at, while navigating, and immediately after leaving the Website. - Like most website operators, CP gathers, from users of the Website, Non-Personally-Identifying Information of the sort that web browsers, depending on their settings, may make available. That information includes the user’s internet protocol (“IP”) address, operating system, and browser type, and the location of web pages that the user views right before arriving at, while navigating, and immediately after leaving the Website. + h4. Web Cookies - h4. Web Cookies + A web cookie (“Cookie”) is a string of information that a website stores on a user’s computer, and that a user’s browser provides to the website each time the user submits a query to the site. CP, or a third party commissioned by CP, uses Cookies to track pages that users visit during each Website session, both to help the CP improve user’s experiences and to help CP understand how the Website is being used. - A web cookie (“Cookie”) is a string of information that a website stores on a user’s computer, and that a user’s browser provides to the website each time the user submits a query to the site. CP, or a third party commissioned by CP, uses Cookies to track pages that users visit during each Website session, both to help the CP improve user’s experiences and to help CP understand how the Website is being used. + Users who do not wish to have Cookies placed on their computers should set their browsers to refuse Cookies before accessing the Website. However, certain features of the Website may not function properly without the aid of Cookies. Website users who refuse Cookies assume all responsibility for any resulting loss of functionality. - Users who do not wish to have Cookies placed on their computers should set their browsers to refuse Cookies before accessing the Website. However, certain features of the Website may not function properly without the aid of Cookies. Website users who refuse Cookies assume all responsibility for any resulting loss of functionality. + h4. Web Beacons - h4. Web Beacons + A web beacon (“Beacon”) is an object that is embedded in a web page that is usually invisible to the user and allows website operators to check whether a user has viewed a particular web page. CP, or a third party commissioned by CP, may use Beacons on the Website to count users who have visited particular pages and to deliver other services. Beacons are not used to access users’ Personally-Identifying Information; they are a technique used to compile aggregate statistics about Website usage. Beacon’s are used to collect only a limited set of information including a Cookie number, time and date of a page view, and a description of the page on which the Beacon resides. - A web beacon (“Beacon”) is an object that is embedded in a web page that is usually invisible to the user and allows website operators to check whether a user has viewed a particular web page. CP, or a third party commissioned by CP, may use Beacons on the Website to count users who have visited particular pages and to deliver other services. Beacons are not used to access users’ Personally-Identifying Information; they are a technique used to compile aggregate statistics about Website usage. Beacon’s are used to collect only a limited set of information including a Cookie number, time and date of a page view, and a description of the page on which the Beacon resides. + You may not decline Beacons; however, they can be rendered ineffective by declining all Cookies. - You may not decline Beacons; however, they can be rendered ineffective by declining all Cookies. + h4. Other Information - h4. Other Information + CP keeps track of information related to your actions on the Website. This information includes, but is not limited to your interactions with other users of the site, content you post to the website, and your network of Mets. - CP keeps track of information related to your actions on the Website. This information includes, but is not limited to your interactions with other users of the site, content you post to the website, and your network of Mets. + h2. How CP Uses Your Information - h2. How CP Uses Your Information + CP uses information it collects about you in managing the Website. CP, or a third party commissioned by CP, may analyze Non-Personally-Identifying Information gathered from users of the Website (including information gathered from Cookies and Beacons as described above) to better understand how the Website is being used. By identifying patterns and trends in usage, CP is able to better design the Website to improve users’ experiences, both in terms of content and ease of use. From time to time, CP may also release the Non-Personally-Identifying Information gathered from Website users in the aggregate. - CP uses information it collects about you in managing the Website. CP, or a third party commissioned by CP, may analyze Non-Personally-Identifying Information gathered from users of the Website (including information gathered from Cookies and Beacons as described above) to better understand how the Website is being used. By identifying patterns and trends in usage, CP is able to better design the Website to improve users’ experiences, both in terms of content and ease of use. From time to time, CP may also release the Non-Personally-Identifying Information gathered from Website users in the aggregate. + CP may also use your information to facilitate interactions among users of the Website. For example, CP may suggest that you Met a Neighbor or that a Neighbor Met you when you possess interests, needs, skills, or Haves that are complementary. As another example, CP may suggest future events that are consistent with your stated interests or that are similar to events you attended in the future. CP may suggest to a member of your Neighborhood that he or she make an introduction between you and a third member of you Neighborhood when you have both Met the intermediary and share complementary interests, skills, Haves, or needs. - CP may also use your information to facilitate interactions among users of the Website. For example, CP may suggest that you Met a Neighbor or that a Neighbor Met you when you possess interests, needs, skills, or Haves that are complementary. As another example, CP may suggest future events that are consistent with your stated interests or that are similar to events you attended in the future. CP may suggest to a member of your Neighborhood that he or she make an introduction between you and a third member of you Neighborhood when you have both Met the intermediary and share complementary interests, skills, Haves, or needs. + CP may also use your contact information to contact you regarding service related announcements. This may include announcements related to changes in the Website or new events that may be of interest. - CP may also use your contact information to contact you regarding service related announcements. This may include announcements related to changes in the Website or new events that may be of interest. + h2. How You Share Your Information - h2. How You Share Your Information + The Website is intended to promote interactions among residents of its Neighborhoods and Communities and is specifically intended to promote sharing of information among residents of a given Neighborhood. Accordingly, you should use discretion in posting content to the site. Your name and address, as well as your listed interests, skills, haves and your directory of Mets, will be viewable members of your Neighborhood. In addition, content you post to the Wire will be available to members of your Neighborhood. - The Website is intended to promote interactions among residents of its Neighborhoods and Communities and is specifically intended to promote sharing of information among residents of a given Neighborhood. Accordingly, you should use discretion in posting content to the site. Your name and address, as well as your listed interests, skills, haves and your directory of Mets, will be viewable members of your Neighborhood. In addition, content you post to the Wire will be available to members of your Neighborhood. + You will have the option of making certain content available to the larger Community. Responses to content that is made available to the Community will also be available to the Community. - You will have the option of making certain content available to the larger Community. Responses to content that is made available to the Community will also be available to the Community. + Information regarding certain actions you take on the Website will be available through either your profile or the Wire. Such actions include registering for events or Metting other users. Information about searches you make or profiles you view is not made available to other users of the Website. - Information regarding certain actions you take on the Website will be available through either your profile or the Wire. Such actions include registering for events or Metting other users. Information about searches you make or profiles you view is not made available to other users of the Website. + h2. How CP Shares Your Information - h2. How CP Shares Your Information + CP does not sell, rent, or otherwise provide Personally-Identifying Information to third parties for marketing purposes. - CP does not sell, rent, or otherwise provide Personally-Identifying Information to third parties for marketing purposes. - - CP may partner with scholars or researchers who are interested in the sociology of neighborhoods and communities. Any such partners must comply with this Privacy Policy and are prohibited from publicly releasing Personally-Identifying Information about a user without that user’s express consent. \ No newline at end of file + CP may partner with scholars or researchers who are interested in the sociology of neighborhoods and communities. Any such partners must comply with this Privacy Policy and are prohibited from publicly releasing Personally-Identifying Information about a user without that user’s express consent. diff --git a/app/views/site/terms.haml b/app/views/site/terms.haml index bcb5df2d8..3c4ecef14 100644 --- a/app/views/site/terms.haml +++ b/app/views/site/terms.haml @@ -1,81 +1,79 @@ -- render :layout => 'shared/info_modal' do +.info.terms + :textile + h1. Terms of Use - .info.terms - :textile - h1. Terms of Use + CommonPlace (“CP”) is an internet based website (the “Website”) designed specifically to connect people to their local communities. Residents of neighborhoods with a CommonPlace will be able to use the Website to share information about events, needs, and announcements with their neighbors and connect to the people and institutions nearby. To provide this service responsibly, we have set out some ground rules for using the service in this document (the "Terms"). These Terms constitute a legally binding agreement between you, whether personally or on behalf of an entity, (“you”) and CP concerning your use and access to the Website. By accessing, using, or contributing to the Website you agree to abide by the Terms in full. If you disagree with any part of the Terms do not use the Website. - CommonPlace (“CP”) is an internet based website (the “Website”) designed specifically to connect people to their local communities. Residents of neighborhoods with a CommonPlace will be able to use the Website to share information about events, needs, and announcements with their neighbors and connect to the people and institutions nearby. To provide this service responsibly, we have set out some ground rules for using the service in this document (the "Terms"). These Terms constitute a legally binding agreement between you, whether personally or on behalf of an entity, (“you”) and CP concerning your use and access to the Website. By accessing, using, or contributing to the Website you agree to abide by the Terms in full. If you disagree with any part of the Terms do not use the Website. + CP may change the Terms from time to time, at the CP's sole discretion. Your continued use of the Website following the posting of such changes will constitute your assent to all such changes. Please periodically visit this section of the Website to review the current version of the Terms. - CP may change the Terms from time to time, at the CP's sole discretion. Your continued use of the Website following the posting of such changes will constitute your assent to all such changes. Please periodically visit this section of the Website to review the current version of the Terms. + h2. 1. Eligibility - h2. 1. Eligibility + You must be at least eighteen (18) years of age to register as a member of the Website or use the Website. By using the Website, you represent and warrant you are 18 years of age or older and that you have the right, authority and capacity to enter into this Agreement and to abide by all of the terms and conditions of this Agreement. - You must be at least eighteen (18) years of age to register as a member of the Website or use the Website. By using the Website, you represent and warrant you are 18 years of age or older and that you have the right, authority and capacity to enter into this Agreement and to abide by all of the terms and conditions of this Agreement. + h2. 2. Intellectual Property Rights - h2. 2. Intellectual Property Rights + Unless otherwise stated, we or our licensors own the intellectual property rights in the Website and material on the Website. You may view, download for caching purposes only, and print pages from the Website for your own personal use. Otherwise, all these intellectual property rights are reserved. - Unless otherwise stated, we or our licensors own the intellectual property rights in the Website and material on the Website. You may view, download for caching purposes only, and print pages from the Website for your own personal use. Otherwise, all these intellectual property rights are reserved. + h2. 3. Content - h2. 3. Content + You own all content that you post to the Website. By posting content to the Website you grant CP a license to display the content on the website or otherwise use the content in conjunction with the operation of the Website. - You own all content that you post to the Website. By posting content to the Website you grant CP a license to display the content on the website or otherwise use the content in conjunction with the operation of the Website. + You are responsible for all content that you post to the website. By posting content to the Website you represent and warrant that: - You are responsible for all content that you post to the website. By posting content to the Website you represent and warrant that: + # the creation, distribution, transmission, public display and performance, accessing, downloading and copying of your contribution does not and will not infringe any proprietary rights, including but not limited to the copyright, patent, trademark or trade secret rights of any third party; + # your contribution is not obscene, lewd, lascivious, filthy, excessively violent, harassing or otherwise objectionable, libelous or slanderous, does not incite, or threaten immediate physical harm against another, does not violate any applicable law, regulation, or rule, and does not violate the privacy or publicity rights of any third party; or + # any factual information that you post to your account is accurate to the best of your knowledge. + + CP does not screen content you post to the Website. CP does maintain the right (though not the obligation) to refuse, delete, or move content. - # the creation, distribution, transmission, public display and performance, accessing, downloading and copying of your contribution does not and will not infringe any proprietary rights, including but not limited to the copyright, patent, trademark or trade secret rights of any third party; - # your contribution is not obscene, lewd, lascivious, filthy, excessively violent, harassing or otherwise objectionable, libelous or slanderous, does not incite, or threaten immediate physical harm against another, does not violate any applicable law, regulation, or rule, and does not violate the privacy or publicity rights of any third party; or - # any factual information that you post to your account is accurate to the best of your knowledge. - - CP does not screen content you post to the Website. CP does maintain the right (though not the obligation) to refuse, delete, or move content. + h2. 4. Copyright Policy - h2. 4. Copyright Policy + Repeated posting of content that infringes another’s copyright will lead to termination of your account. If you are a copyright owner, or the legal agent of a copyright owner, and you believe that any user submission or content on the Website infringes upon your copyrights, you may submit a notice pursuant to the #{link_to "CP Digital Millennium Copyright Act (“DMCA”) Notice", dmca_url}. - Repeated posting of content that infringes another’s copyright will lead to termination of your account. If you are a copyright owner, or the legal agent of a copyright owner, and you believe that any user submission or content on the Website infringes upon your copyrights, you may submit a notice pursuant to the #{link_to "CP Digital Millennium Copyright Act (“DMCA”) Notice", dmca_url}. - - h2. 5. Third Party Interactions + h2. 5. Third Party Interactions - Your interactions with third parties found on or through the Website, including payment and delivery or exchange of goods or services, are solely between you and such third parties. You should exercise appropriate care before proceeding with interactions with third parties. + Your interactions with third parties found on or through the Website, including payment and delivery or exchange of goods or services, are solely between you and such third parties. You should exercise appropriate care before proceeding with interactions with third parties. - You agree that CP shall not be responsible or liable for any loss or damage of any sort that is incurred as the result of any such interactions. You understand and agree that CP is under no obligation to intervene in any dispute between participants on this site, or between users and any third party. In the event that you have a dispute with one or more other users, you hereby release CP, its officers, employees, and agents from any claims arising out of or in any way related to such disputes. + You agree that CP shall not be responsible or liable for any loss or damage of any sort that is incurred as the result of any such interactions. You understand and agree that CP is under no obligation to intervene in any dispute between participants on this site, or between users and any third party. In the event that you have a dispute with one or more other users, you hereby release CP, its officers, employees, and agents from any claims arising out of or in any way related to such disputes. - h2. 6. Privacy and Information disclosure + h2. 6. Privacy and Information disclosure - CP has established a Privacy Policy to explain to users how their information is collected and used, which is located at the following web address: + CP has established a Privacy Policy to explain to users how their information is collected and used, which is located at the following web address: - #{ link_to privacy_url, privacy_url } - - Please take the time to read and understand this policy. Your use of the Website signifies acknowledgement of, and agreement to, our Privacy Policy. + #{ link_to privacy_url, privacy_url } - h2. 7. Conduct + Please take the time to read and understand this policy. Your use of the Website signifies acknowledgement of, and agreement to, our Privacy Policy. - The Website is intended to foster positive interactions among residents of certain neighborhoods. In using the website please show the respect and courtesy that you would want to receive from your neighbors. CP may, in its sole discretion, terminate or otherwise restrict access to the Website for users who consistently violate common rules of etiquette or who engage in prohibited activities absent express prior agreement with CP. Prohibited activities include but are not limited to: + h2. 7. Conduct - # criminal or tortious activity, including child pornography, fraud, trafficking in obscene material, drug dealing, gambling, harassment, stalking, or spamming; - # transmitting chain letters or junk mail to other users; - # engaging in any automated use of the Website, such as using scripts to add friends or send comments or messages; - # interfering with, disrupting, or creating an undue burden on the Website or the networks or services connected to the Website; - # attempting to impersonate another person; - # using the username of another user; - # selling or otherwise transferring your profile; - # using any information obtained from the Website in order to harass, abuse, or harm another person; and - # using the website in a manner inconsistent with any and all applicable laws and regulations. - - h2. 8. Termination of Service + The Website is intended to foster positive interactions among residents of certain neighborhoods. In using the website please show the respect and courtesy that you would want to receive from your neighbors. CP may, in its sole discretion, terminate or otherwise restrict access to the Website for users who consistently violate common rules of etiquette or who engage in prohibited activities absent express prior agreement with CP. Prohibited activities include but are not limited to: + + # criminal or tortious activity, including child pornography, fraud, trafficking in obscene material, drug dealing, gambling, harassment, stalking, or spamming; + # transmitting chain letters or junk mail to other users; + # engaging in any automated use of the Website, such as using scripts to add friends or send comments or messages; + # interfering with, disrupting, or creating an undue burden on the Website or the networks or services connected to the Website; + # attempting to impersonate another person; + # using the username of another user; + # selling or otherwise transferring your profile; + # using any information obtained from the Website in order to harass, abuse, or harm another person; and + # using the website in a manner inconsistent with any and all applicable laws and regulations. + + h2. 8. Termination of Service - You agree that CP, in its sole discretion, has the right (but not the obligation) to delete or deactivate your account, block your email or IP address, or otherwise terminate your access to or use of the Website (or any part thereof), immediately and without notice, and remove and discard any content within the Website, for any reason, including, without limitation, if CP believes that you have acted inconsistently with the letter or spirit of the Terms. You agree that CP shall not be liable to you or any third-party for any termination of your access to the Service. + You agree that CP, in its sole discretion, has the right (but not the obligation) to delete or deactivate your account, block your email or IP address, or otherwise terminate your access to or use of the Website (or any part thereof), immediately and without notice, and remove and discard any content within the Website, for any reason, including, without limitation, if CP believes that you have acted inconsistently with the letter or spirit of the Terms. You agree that CP shall not be liable to you or any third-party for any termination of your access to the Service. - You may terminate your use or participation at any time by following the instructions on the My Account page of your profile. + You may terminate your use or participation at any time by following the instructions on the My Account page of your profile. - Sections 3, 4, 5, 6, 8, 9, 10, and 11 shall survive termination of service. + Sections 3, 4, 5, 6, 8, 9, 10, and 11 shall survive termination of service. - h2. 9. Disclaimer of Warranties + h2. 9. Disclaimer of Warranties - YOU AGREE THAT YOUR USE OF THE WEBSITE WILL BE AT YOUR SOLE RISK. THE WEBSITE IS PROVIDED ON AN "AS IS" OR "AS AVAILABLE" BASIS, WITHOUT ANY WARRANTIES OF ANY KIND. TO THE FULLEST EXTENT PERMITTED BY LAW, CP, ITS OFFICERS, DIRECTORS, EMPLOYEES, AND AGENTS DISCLAIM ALL WARRANTIES EXPRESS OR IMPLIED, IN CONNECTION WITH THE WEBSITE AND YOUR USE THEREOF. CP MAKES NO WARRANTIES OR REPRESENTATIONS ABOUT THE ACCURACY OR COMPLETENESS OF THE WEBSITE’S CONTENT OR THE CONTENT OF ANY WEBSITES LINKED TO THIS WEBSITE AND ASSUMES NO LIABILITY OR RESPONSIBILITY FOR ANY (A)ERRORS, MISTAKES, OR INACCURACIES OF CONTENT AND MATERIALS, (B) PERSONAL INJURY OR PROPERTY DAMAGE, OF ANY NATURE WHATSOEVER, RESULTING FROM YOUR ACCESS TO OR USE OF THE WEBSITE, ==(C)== ANY UNAUTHORIZED ACCESS TO OR USE OF OUR SECURE SERVERS AND/OR ANY AND ALL PERSONAL AND/OR FINANCIAL INFORMATION STORED THEREIN, (D) ANY INTERRUPRION OR CESSATION OF TRANSMISSIONS TO OR FROM THE WEBSITE, (E) ANY BUGS, VIRUSES, TOJAN HORSES, OR THE LIKE WHICH MAY BE TRANSMITTED TO OR THROUGH THE WEBSITE BY ANY THIRD PARTY, AND/OR (F) ANY ERRORS OR OMISSIONS IN ANY CONTENT AND MATERIALS OR FOR ANY LOSS OR DAMAGE OF ANY KIND INCURRED AS A RESULT OF ANY CONTENT POSTED, TRANSMITTED, OR OTHERWISE MADE AVAILABLE VIA THE WEBSITE. CP DOES NOT WARRANT, ENDORSE, GUARANTEE, OR ASSUME RESPONSIBILITY FOR ANY PRODUCT OR SERVICE ADVERTISED OR OFFERED BY A THIRD PARTY THROUGH THE WEBSITE OR ANY HYPERLINKED WEBSITE OR FEATURED IN ANY BANNER OR OTHER ADVERTISING, AND CP WILL NOT BE A PARTY TO OR IN ANY WAY BE RESPONSIBLE FOR MONITORING ANY TRANSACTION BETWEEN YOU AND THRID-PARTY PROVIDERS OF PRODUCTS OR SERVICES. AS WITH THE PURCHASE OF A PRODUCT OR SERVICE THROUGH ANY MEDIUM OR IN ANY ENVIRONMENT, YOU SHOULD USE YOUR BEST JUDGMENT AND EXERCISE CAUTION WHERE APPROPRIATE. + YOU AGREE THAT YOUR USE OF THE WEBSITE WILL BE AT YOUR SOLE RISK. THE WEBSITE IS PROVIDED ON AN "AS IS" OR "AS AVAILABLE" BASIS, WITHOUT ANY WARRANTIES OF ANY KIND. TO THE FULLEST EXTENT PERMITTED BY LAW, CP, ITS OFFICERS, DIRECTORS, EMPLOYEES, AND AGENTS DISCLAIM ALL WARRANTIES EXPRESS OR IMPLIED, IN CONNECTION WITH THE WEBSITE AND YOUR USE THEREOF. CP MAKES NO WARRANTIES OR REPRESENTATIONS ABOUT THE ACCURACY OR COMPLETENESS OF THE WEBSITE’S CONTENT OR THE CONTENT OF ANY WEBSITES LINKED TO THIS WEBSITE AND ASSUMES NO LIABILITY OR RESPONSIBILITY FOR ANY (A)ERRORS, MISTAKES, OR INACCURACIES OF CONTENT AND MATERIALS, (B) PERSONAL INJURY OR PROPERTY DAMAGE, OF ANY NATURE WHATSOEVER, RESULTING FROM YOUR ACCESS TO OR USE OF THE WEBSITE, ==(C)== ANY UNAUTHORIZED ACCESS TO OR USE OF OUR SECURE SERVERS AND/OR ANY AND ALL PERSONAL AND/OR FINANCIAL INFORMATION STORED THEREIN, (D) ANY INTERRUPRION OR CESSATION OF TRANSMISSIONS TO OR FROM THE WEBSITE, (E) ANY BUGS, VIRUSES, TOJAN HORSES, OR THE LIKE WHICH MAY BE TRANSMITTED TO OR THROUGH THE WEBSITE BY ANY THIRD PARTY, AND/OR (F) ANY ERRORS OR OMISSIONS IN ANY CONTENT AND MATERIALS OR FOR ANY LOSS OR DAMAGE OF ANY KIND INCURRED AS A RESULT OF ANY CONTENT POSTED, TRANSMITTED, OR OTHERWISE MADE AVAILABLE VIA THE WEBSITE. CP DOES NOT WARRANT, ENDORSE, GUARANTEE, OR ASSUME RESPONSIBILITY FOR ANY PRODUCT OR SERVICE ADVERTISED OR OFFERED BY A THIRD PARTY THROUGH THE WEBSITE OR ANY HYPERLINKED WEBSITE OR FEATURED IN ANY BANNER OR OTHER ADVERTISING, AND CP WILL NOT BE A PARTY TO OR IN ANY WAY BE RESPONSIBLE FOR MONITORING ANY TRANSACTION BETWEEN YOU AND THRID-PARTY PROVIDERS OF PRODUCTS OR SERVICES. AS WITH THE PURCHASE OF A PRODUCT OR SERVICE THROUGH ANY MEDIUM OR IN ANY ENVIRONMENT, YOU SHOULD USE YOUR BEST JUDGMENT AND EXERCISE CAUTION WHERE APPROPRIATE. - h2. 10. Limitations of Liability + h2. 10. Limitations of Liability - IN ORDER TO PROVIDE YOU WITH THIS FREE SERVICE, WE ARE UNABLE TO ACCEPT LIABILITY FOR ANY CONDUCT, ACTS, OR OMISSIONS OCCURRING AT THIS WEBSITE. IN NO EVENT SHALL CP BE LIABLE TO YOU OR ANY THIRD PARTY FOR DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, PUNITIVE, OR EXEMPLARY DAMAGES, INCLUDING LOST PROFIT DAMAGES, ARISING FROM YOUR USE OF THE WEBSITE, EVEN IF CP HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. NOTWITHSTANDING ANYTHING TO THE CONTRARY CONTAINED HEREIN, CP’S LIABILITY. + IN ORDER TO PROVIDE YOU WITH THIS FREE SERVICE, WE ARE UNABLE TO ACCEPT LIABILITY FOR ANY CONDUCT, ACTS, OR OMISSIONS OCCURRING AT THIS WEBSITE. IN NO EVENT SHALL CP BE LIABLE TO YOU OR ANY THIRD PARTY FOR DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, PUNITIVE, OR EXEMPLARY DAMAGES, INCLUDING LOST PROFIT DAMAGES, ARISING FROM YOUR USE OF THE WEBSITE, EVEN IF CP HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. NOTWITHSTANDING ANYTHING TO THE CONTRARY CONTAINED HEREIN, CP’S LIABILITY. - h2. 11. Choice of Law and Jurisdiction + h2. 11. Choice of Law and Jurisdiction - These Terms and any disputes arising between you and CP will be governed by and construed in accordance with Massachusetts law and shall be subject to the exclusive jurisdiction of Massachusetts state or federal courts. \ No newline at end of file + These Terms and any disputes arising between you and CP will be governed by and construed in accordance with Massachusetts law and shall be subject to the exclusive jurisdiction of Massachusetts state or federal courts. diff --git a/app/views/subscriptions/index.haml b/app/views/subscriptions/index.haml deleted file mode 100644 index d3cfa6304..000000000 --- a/app/views/subscriptions/index.haml +++ /dev/null @@ -1,4 +0,0 @@ -- update_content "#syndicate" do - #syndicate - = render 'shared/feeds_syndicate_nav' - = render 'shared/items', :items => @items \ No newline at end of file diff --git a/app/views/tags/_form.haml b/app/views/tags/_form.haml deleted file mode 100644 index 259bd452b..000000000 --- a/app/views/tags/_form.haml +++ /dev/null @@ -1,5 +0,0 @@ - -- semantic_form_for current_user do |f| - = f.inputs :goods, :skills - = f.buttons - \ No newline at end of file diff --git a/app/views/tags/_list.haml b/app/views/tags/_list.haml deleted file mode 100644 index caed571c3..000000000 --- a/app/views/tags/_list.haml +++ /dev/null @@ -1,9 +0,0 @@ - -%ul#wire - - @goods.each do |tag| - = render 'tags/tag', :tag => tag, :type => "goods" - - - @skills.each do |tag| - = render 'tags/tag', :tag => tag, :type => "skills" - - diff --git a/app/views/tags/_tag.haml b/app/views/tags/_tag.haml deleted file mode 100644 index 5f273fe3a..000000000 --- a/app/views/tags/_tag.haml +++ /dev/null @@ -1,14 +0,0 @@ -- item_tag(tag) do - %div.item_body - %h3= tag.name - = button_to "I have this #{type.singularize}" - - %p #{User.tagged_with(tag.name, :on => type).count} people have this #{type.singularize}: - %ul - - User.tagged_with(tag.name, :on => type).each do |user| - %li - = link_to user.name, user, 'data-remote' => true - - - - diff --git a/app/views/tags/index.haml b/app/views/tags/index.haml deleted file mode 100644 index 5f81f559c..000000000 --- a/app/views/tags/index.haml +++ /dev/null @@ -1,4 +0,0 @@ -link_to_add("✚ Add goods and skills to your profile".upcase, new_tag_path, 'data-remote' => true) -#list - = render '_list -#info \ No newline at end of file diff --git a/app/views/thread_memberships/_thread_membership.haml b/app/views/thread_memberships/_thread_membership.haml deleted file mode 100644 index 2fcd5288e..000000000 --- a/app/views/thread_memberships/_thread_membership.haml +++ /dev/null @@ -1 +0,0 @@ -= link_to 'updated post', thread_membership.thread \ No newline at end of file diff --git a/app/views/twitter_announcements/_twitter_announcement.haml b/app/views/twitter_announcements/_twitter_announcement.haml deleted file mode 100644 index 6d699bb9e..000000000 --- a/app/views/twitter_announcements/_twitter_announcement.haml +++ /dev/null @@ -1,7 +0,0 @@ -%div{'data-href' => announcement_path(twitter_announcement), :class => "announcement tooltip", 'data-title' => "Click to see replies and this community feed's profile"} - = image_tag Feed.find(twitter_announcement.feed_id).avatar.url, :class => 'avatar' - %time= twitter_announcement.time - .reply-count #{ twitter_announcement.replies.size } replies - .title= twitter_announcement.subject - .author= link_to(twitter_announcement.owner.name,twitter_announcement.url) - .body= markdown auto_link(twitter_announcement.body, :all, :target => "_blank") \ No newline at end of file diff --git a/app/views/user_sessions/_form.haml b/app/views/user_sessions/_form.haml index 95a110dca..c7b285700 100644 --- a/app/views/user_sessions/_form.haml +++ b/app/views/user_sessions/_form.haml @@ -1,7 +1,16 @@ -- semantic_form_for @user_session || UserSession.new, :url => user_session_path, :html => { :class => "standard_form", :id => "sign_in" } do |f| - %h2 Sign in - = f.semantic_errors - = f.inputs :email, :password - %p.hint= link_to("forgot password?", new_password_reset_path) - - f.buttons do - = f.commit_button "Log in" \ No newline at end of file += semantic_form_for(:user, :url => "/users/sign_in") do |f| + = f.inputs do + = f.input :email + = f.input :password, :label => "Enter your password:" + + = f.buttons do + + = f.commit_button :button_html => {:type => "image", :src => asset_path("buttons/login.png")} + + %li.facebook + = link_to user_omniauth_authorize_path(:facebook) do + = image_tag "buttons/facebook-login.png" + + = link_to "Forgot your password?", new_user_password_url, :class => 'forgot-password' + + diff --git a/app/views/user_sessions/new.haml b/app/views/user_sessions/new.haml index 353bf75ba..6adeb26b3 100644 --- a/app/views/user_sessions/new.haml +++ b/app/views/user_sessions/new.haml @@ -1,16 +1,15 @@ +#main + = semantic_form_for(:user, :url => "/users/sign_in") do |f| + .flash + = flash[:alert] + = f.inputs do + = f.input :email + = f.input :password, :label => "Enter your password:" + = f.buttons do + = f.commit_button :button_html => {:type => "image", :src => asset_path("buttons/login2.png")} + = link_to user_omniauth_authorize_path(:facebook), :class => "facebook" do + = image_tag "buttons/facebook-login.png" + = link_to "Forgot your password?", new_user_password_url, :class => 'forgot-password' -#get_started - = render 'form' - - semantic_form_for @user, :url => account_url, :html => { :class => "standard_form", :id => "sign_up"} do |f| - %h2 Sign up - - - f.inputs do - = f.input :first_name - = f.input :last_name - = f.input :email - = f.input :password - = f.input :password_confirmation - = f.inputs :street_address, :zip_code, :for => :location - - f.buttons do - = f.commit_button :label => "Create an account" \ No newline at end of file + diff --git a/app/views/users/_info_box.haml b/app/views/users/_info_box.haml deleted file mode 100644 index f0f4e66d0..000000000 --- a/app/views/users/_info_box.haml +++ /dev/null @@ -1,25 +0,0 @@ - -#information.info_box.user - %h2 Your Neighbor - - = image_tag user.avatar.url(:normal), :width => 120, :class => "avatar" - - - if user != current_user - - link_to new_user_message_path(user), :class => "message_me", 'data-remote' => true, 'data-title' => "Click to send a private message to #{ user.first_name }" do - %span Message #{ user.first_name } - - - %h3= user.full_name - .info= markdown user.about - %table - %tr - %th INTERESTS - %td= user.interest_list - %tr - %th GOODS & SKILLS - %td= user.good_list - %tr - %th SUBSCRIPTIONS - %td= user.feed_list - - .clear diff --git a/app/views/users/_list.haml b/app/views/users/_list.haml deleted file mode 100644 index 73e397e3e..000000000 --- a/app/views/users/_list.haml +++ /dev/null @@ -1,4 +0,0 @@ -%ul#wire - - @users.each do |user| - %li{ :class => cycle('odd','even') } - = render user diff --git a/app/views/users/_user.haml b/app/views/users/_user.haml deleted file mode 100644 index 9039a5960..000000000 --- a/app/views/users/_user.haml +++ /dev/null @@ -1,3 +0,0 @@ -%div{'data-href' => user_path(user), :class => "user tooltip", 'data-title' => 'Click to see more information about your neighbor'} - = image_tag user.avatar.url(:thumb), :class => "avatar" - .title= user.name diff --git a/app/views/users/autocomplete.erb b/app/views/users/autocomplete.erb deleted file mode 100644 index f42445a86..000000000 --- a/app/views/users/autocomplete.erb +++ /dev/null @@ -1 +0,0 @@ -<%= @users.map {|u| {:label => u.full_name, :value => u.id} }.to_json %> \ No newline at end of file diff --git a/app/views/users/index.haml b/app/views/users/index.haml deleted file mode 100644 index 0560ef665..000000000 --- a/app/views/users/index.haml +++ /dev/null @@ -1,7 +0,0 @@ -- update_content "#syndicate" do - #syndicate - %h3 Your Neighbors - = render 'shared/items', :items => @neighbors - %h3 Community Members - = render 'shared/items', :items => @users - \ No newline at end of file diff --git a/app/views/users/show.haml b/app/views/users/show.haml deleted file mode 100644 index 3df59799e..000000000 --- a/app/views/users/show.haml +++ /dev/null @@ -1,10 +0,0 @@ -- update_content "#information" do - = render 'info_box', :user => @user - -= render 'shared/tooltip' - -- unless xhr? - - update_content "#syndicate" do - #syndicate - = render 'shared/items', :items => [@user] - \ No newline at end of file diff --git a/autotest/discover.rb b/autotest/discover.rb index 6352649cc..35ce8e18e 100644 --- a/autotest/discover.rb +++ b/autotest/discover.rb @@ -3,4 +3,4 @@ at.add_exception 'app/stylesheets' at.add_exception 'test' end -Autotest.add_discovery { "rspec" } + diff --git a/config.ru b/config.ru new file mode 100644 index 000000000..3c34b3ec4 --- /dev/null +++ b/config.ru @@ -0,0 +1,32 @@ +# This file is used by Rack-based servers to start the application. + +require ::File.expand_path('../config/environment', __FILE__) +require 'resque/server' +require 'exceptional' + +app = Rack::Builder.new do + + + if Rails.env.staging? || Rails.env.production? + + use Rack::Exceptional, ENV['exceptional_key'] + + use Rack::Timeout + Rack::Timeout.timeout = 15 # seconds + + use(Rack::Cache, + :verbose => true, + :metastore => Dalli::Client.new, + :entitystore => Dalli::Client.new) + end + + map("/api") { + run API + } + + map("/resque") { run Resque::Server } + + map("/") { run Commonplace::Application } +end + +run app diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 000000000..6f7b69d40 --- /dev/null +++ b/config/application.rb @@ -0,0 +1,80 @@ +require File.expand_path('../boot', __FILE__) + +require 'rails/all' + +if defined?(Bundler) + # If you precompile assets before deploying to production, use this line + # Bundler.require *Rails.groups(:assets => %w(development test)) + # If you want your assets lazily compiled in production, use this line + Bundler.require(:default, :assets, Rails.env) +end + +module Commonplace + class Application < Rails::Application + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + + # Custom directories with classes and modules you want to be autoloadable. + # config.autoload_paths += %W(#{config.root}/extras) + + # Enable the asset pipeline + config.assets.enabled = true + + # Version of your assets, change this if you want to expire all your assets + config.assets.version = '1.0' + + config.assets.paths += [File.join(Rails.root, 'app', 'javascripts'), + File.join(Rails.root, 'app', 'stylesheets'), + File.join(Rails.root, 'app', 'templates'), + File.join(Rails.root, 'app', 'text'), + File.join(Rails.root, 'app', 'images'), + File.join(Rails.root, 'vendor', 'javascripts'), + File.join(Rails.root, 'vendor', 'images'), + File.join(Rails.root, 'vendor', 'stylesheets'), + File.join(Rails.root, 'lib', 'javascripts') + ] + + config.assets.precompile += ['main_page.js', 'group_page.js', 'inbox.js', + 'feed_page.js', 'invite_page.js', + 'registration_page.js', 'feed_registration.js', + 'sign_in.js', 'accounts.js'] + + config.assets.precompile += ['feed_registration.css.sass', 'main_page.css.sass', + 'group_page.css.sass', 'feed_page.css.sass', + 'registration_page.css.sass', 'login_page.css.sass', + 'inbox.css.sass'] + + config.generators do |g| + g.orm :active_record + end + + config.autoload_paths += %W( #{config.root}/mail #{config.root}/lib #{config.root}/**/ ) + + # Only load the plugins named here, in the order given (default is alphabetical). + # :all can be used as a placeholder for all plugins not explicitly named. + # config.plugins = [ :exception_notification, :ssl_requirement, :all ] + + # Activate observers that should always be running. + # config.active_record.observers = :cacher, :garbage_collector, :forum_observer + + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. + # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. + # config.time_zone = 'Central Time (US & Canada)' + + config.time_zone = 'UTC' + + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] + # config.i18n.default_locale = :de + + # JavaScript files you want as :defaults (application.js is always included). + # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) + + # Configure the default encoding used in templates for Ruby 1.9. + config.encoding = "utf-8" + + # Configure sensitive parameters which will be filtered from the log file. + config.filter_parameters += [:password] + end +end diff --git a/config/boot.rb b/config/boot.rb index c040e6833..d9556b05d 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,125 +1,7 @@ -# Don't change this file! -# Configure your app in config/environment.rb and config/environments/*.rb +require 'rubygems' +require 'yaml' +YAML::ENGINE.yamler = 'syck' +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) -RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) - -module Rails - class << self - def boot! - unless booted? - preinitialize - pick_boot.run - end - end - - def booted? - defined? Rails::Initializer - end - - def pick_boot - (vendor_rails? ? VendorBoot : GemBoot).new - end - - def vendor_rails? - File.exist?("#{RAILS_ROOT}/vendor/rails") - end - - def preinitialize - load(preinitializer_path) if File.exist?(preinitializer_path) - end - - def preinitializer_path - "#{RAILS_ROOT}/config/preinitializer.rb" - end - end - - class Boot - def run - load_initializer - Rails::Initializer.run(:set_load_path) - end - end - - class VendorBoot < Boot - def load_initializer - require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" - Rails::Initializer.run(:install_gem_spec_stubs) - Rails::GemDependency.add_frozen_gem_path - end - end - - class GemBoot < Boot - def load_initializer - self.class.load_rubygems - load_rails_gem - require 'initializer' - end - - def load_rails_gem - if version = self.class.gem_version - gem 'rails', version - else - gem 'rails' - end - rescue Gem::LoadError => load_error - $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.) - exit 1 - end - - class << self - def rubygems_version - Gem::RubyGemsVersion rescue nil - end - - def gem_version - if defined? RAILS_GEM_VERSION - RAILS_GEM_VERSION - elsif ENV.include?('RAILS_GEM_VERSION') - ENV['RAILS_GEM_VERSION'] - else - parse_gem_version(read_environment_rb) - end - end - - def load_rubygems - min_version = '1.3.2' - require 'rubygems' - unless rubygems_version >= min_version - $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) - exit 1 - end - - rescue LoadError - $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) - exit 1 - end - - def parse_gem_version(text) - $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/ - end - - private - def read_environment_rb - File.read("#{RAILS_ROOT}/config/environment.rb") - end - end - end -end - -# All that for this: - -class Rails::Boot - def run - load_initializer - - Rails::Initializer.class_eval do - def load_gems - @bundler_loaded ||= Bundler.require :default, Rails.env - end - end - - Rails::Initializer.run(:set_load_path) - end -end - -Rails.boot! +require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) diff --git a/config/config.yml.example b/config/config.yml.example deleted file mode 100644 index 40020effb..000000000 --- a/config/config.yml.example +++ /dev/null @@ -1,2 +0,0 @@ -sendgrid_user_name: user_name -sendgrid_password: password \ No newline at end of file diff --git a/config/database.yml.example b/config/database.yml.example index 1dde6fb1b..dcfe5df6d 100644 --- a/config/database.yml.example +++ b/config/database.yml.example @@ -3,6 +3,12 @@ development: database: commonplace_development pool: 5 timeout: 5000 + +frontend: + adapter: postgresql + database: commonplace_development + pool: 5 + timeout: 5000 # Warning: The database defined as "test" will be erased and diff --git a/config/deploy.rb b/config/deploy.rb deleted file mode 100644 index 5e665bd76..000000000 --- a/config/deploy.rb +++ /dev/null @@ -1,22 +0,0 @@ -lib_path = File.expand_path(File.join(File.dirname(__FILE__), "deploy")) -# basics -load "#{lib_path}/settings.rb" -load "#{lib_path}/helpers.rb" - -# deployment tasks -load "#{lib_path}/setup.rb" -load "#{lib_path}/gems.rb" -load "#{lib_path}/deploy.rb" -load "#{lib_path}/symlinks.rb" -load "#{lib_path}/process.rb" -load "#{lib_path}/nginx.rb" - -# load deployment targets -load "#{lib_path}/targets.rb" - - - - # Add RVM's lib directory to the load path. -$:.unshift(File.expand_path('./lib', ENV['rvm_path'])) -puts File.expand_path('./lib', ENV['rvm_path']) -require "rvm/capistrano" diff --git a/config/deploy/deploy.rb b/config/deploy/deploy.rb deleted file mode 100644 index 638f62d2c..000000000 --- a/config/deploy/deploy.rb +++ /dev/null @@ -1,30 +0,0 @@ -namespace :deploy do - desc "Deploy the application" - task :default do - update - restart - end - - task :symlink do - end - - task :update_code, :except => { :no_release => true } do - commands = ["cd #{current_path}", - "git fetch origin", - "git reset --hard #{branch}", - "git submodule update --init"] - run commands.join("; ") - end - - namespace :rollback do - desc "Rollback a single commit." - task :code, :except => { :no_release => true } do - set :branch, "HEAD^" - deploy.default - end - - task :default do - rollback.code - end - end -end \ No newline at end of file diff --git a/config/deploy/gems.rb b/config/deploy/gems.rb deleted file mode 100644 index db5c8e1e8..000000000 --- a/config/deploy/gems.rb +++ /dev/null @@ -1,7 +0,0 @@ -namespace :gems do - task :install, :roles => :app do - # For when we use bundler - run "cd #{current_path} && bundle install --path vendor --without test development" - end -end -after "deploy:update_code", "gems:install" diff --git a/config/deploy/helpers.rb b/config/deploy/helpers.rb deleted file mode 100644 index 55756f5e4..000000000 --- a/config/deploy/helpers.rb +++ /dev/null @@ -1,44 +0,0 @@ -def set(*args) - if args.first == :web_server - lib_path = File.dirname(__FILE__) - load "#{lib_path}/#{args.last.to_s}.rb" - end - super(*args) -end - -def value(setting, default = nil) - if respond_to?(setting) && !send(setting).nil? - send(setting) - else - default - end -end - -def run(cmd, options = {}, &block) - options.merge!(:env => { "PATH" => "#{ruby_path}:$PATH" }) if value(:ruby_path) - super(cmd, options, &block) -end - -def sudo_put(data, target) - tmp = "#{shared_path}/~tmp-#{rand(9999999)}" - put data, tmp - on_rollback { run "rm #{tmp}" } - sudo "cp -f #{tmp} #{target} && rm #{tmp}" -end - -namespace :sass do - desc 'Updates the stylesheets generated by Sass' - task :update, :roles => :app do - run("cd #{current_path} && #{bin_path}/rake sass:update RAILS_ENV=#{rails_env}") - end - - # Generate all the stylesheets manually (from their Sass templates) before each restart. - before 'deploy:restart', 'sass:update' -end - -namespace :resque do - desc "Restarts resque workers" - task :restart, :roles => :app do - sudo("#{bin_path}/god restart resque-workers") - end -end diff --git a/config/deploy/nginx.rb b/config/deploy/nginx.rb deleted file mode 100644 index 2de79f227..000000000 --- a/config/deploy/nginx.rb +++ /dev/null @@ -1,115 +0,0 @@ -namespace :deploy do - namespace :nginx do - task :start, :roles => :web do - sudo "/etc/rc.d/nginx start" - end - - task :stop, :roles => :web do - sudo "/etc/rc.d/nginx stop" - end - - task :restart, :roles => :web do - sudo "/etc/rc.d/nginx restart" - end - - task :setup, :roles => :web do - return unless web_server == :nginx - if uses_ssl - deploy.nginx.ssl.setup - else - if respond_to?(:rails_env) - nginx_rails_env = rails_env || "production" - else - nginx_rails_env = "production" - end - nginx_config_file = <<-EOF - upstream #{application}_server { - server unix:#{deploy_to}/current/tmp/sockets/unicorn.sock fail_timeout=0; - } - server { - listen #{web_port}; - - client_max_body_size 4G; - server_name #{domain}; - - keepalive_timeout 5; - - root #{deploy_to}/current/public; - - location / { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - - proxy_set_header Host $http_host; - - proxy_redirect off; - - if (!-f $request_filename) { - proxy_pass http://#{application}_server; - break; - } - } - - error_page 500 502 503 504 /500.html; - location = /500.html { - root #{deploy_to}/current/public; - } - - access_log /var/log/nginx/#{domain}.access.log; - error_log /var/log/nginx/#{domain}.error.log; - - } - EOF - sudo_put nginx_config_file, "/etc/nginx/conf/#{application}_#{nginx_rails_env}.conf" - end - end - namespace :ssl do - task :setup, :roles => :web do - if respond_to?(:rails_env) - nginx_rails_env = rails_env || "production" - else - nginx_rails_env = "production" - end - nginx_config_file = <<-EOF - server { - listen 443; - passenger_enabled on; - - ssl on; - ssl_certificate #{deploy_to}/shared/certificates/#{domain}.crt.merged; - ssl_certificate_key #{deploy_to}/shared/certificates/#{domain}.key; - ssl_session_timeout 5m; - ssl_protocols SSLv3 TLSv1; - ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+EXP; - ssl_prefer_server_ciphers on; - - keepalive_timeout 70; - client_max_body_size 50M; - server_name www.#{domain} #{domain}; - root #{deploy_to}/current/public; - access_log /var/log/nginx/#{domain}.access.log; - error_log /var/log/nginx/#{domain}.error.log; - - if (-f $document_root/system/maintenance.html) { - rewrite ^(.*)$ /system/maintenance.html last; - break; - } - - error_page 500 502 503 504 /500.html; - location = /500.html { - root #{deploy_to}/current/public; - } - } - - server { - listen 80; - passenger_enabled on; - client_max_body_size 50M; - server_name www.#{domain} #{domain}; - rewrite ^/(.*) https://#{domain}/$1 permanent; - } - EOF - sudo_put nginx_config_file, "#{nginx_conf_dir}/#{application}_#{nginx_rails_env}.conf" - end - end - end -end diff --git a/config/deploy/process.rb b/config/deploy/process.rb deleted file mode 100644 index c7fa49624..000000000 --- a/config/deploy/process.rb +++ /dev/null @@ -1,31 +0,0 @@ -namespace :deploy do - task :start do - end - task :stop do - end - - desc "Display staging logs" - task :tail_staging_logs, :roles => :app do - run "tail -f #{shared_path}/logs/staging.log" do |channel, stream, data| - puts # for an extra line break before the host name - puts "#{channel[:host]}: #{data}" - break if stream == :err - end - end - - desc "Restart the application" - task :restart, :roles => :app, :except => { :no_release => true } do - run "kill -USR2 `cat #{shared_path}/pids/unicorn.pid`" - end - - namespace :web do - task :setup, :roles => :app do - eval("deploy.#{web_server.to_s}.setup") - end - - task :restart, :roles => :app do - eval("deploy.#{web_server.to_s}.restart") - end - end -end -after "deploy:setup", "deploy:web:restart" diff --git a/config/deploy/settings.rb b/config/deploy/settings.rb deleted file mode 100644 index dc0837b39..000000000 --- a/config/deploy/settings.rb +++ /dev/null @@ -1,18 +0,0 @@ -set :application, "commonplace" -default_run_options[:pty] = true -set :scm, :git -set :repository, "git@github.com:maxtilford/#{application}.git" -set :uses_ssl, false -set :normal_symlinks, %w( - config/database.yml - config/config.yml - config/unicorn.rb -) -set :bin_path, "/usr/local/rvm/gems/ree-1.8.7-2010.02/bin" -set :weird_symlinks, { - "system" => "public/system", - "logs" => "log" -} -set :user, "deploy" - - diff --git a/config/deploy/setup.rb b/config/deploy/setup.rb deleted file mode 100644 index 1fe6701d5..000000000 --- a/config/deploy/setup.rb +++ /dev/null @@ -1,19 +0,0 @@ -namespace :deploy do - desc "Setup a GitHub-style deployment." - task :setup, :except => { :no_release => true } do - commands = ["mkdir -p #{deploy_to}", - "git clone #{repository} #{current_path}", - "mkdir -p #{current_path}/tmp", - "mkdir -p #{deploy_to}/shared/config", - "mkdir -p #{deploy_to}/shared/logs", - "mkdir -p #{deploy_to}/shared/system"] - run commands.join(" && ") - end - - - desc "Sets up and starts a new application." - task :cold do - deploy.setup - end -end - diff --git a/config/deploy/symlinks.rb b/config/deploy/symlinks.rb deleted file mode 100644 index 8f5dc1128..000000000 --- a/config/deploy/symlinks.rb +++ /dev/null @@ -1,20 +0,0 @@ -namespace :symlinks do - task :make, :roles => :app, :except => { :no_release => true } do - run "mkdir -p #{current_path}/tmp" - commands = normal_symlinks.map do |path| - "rm -rf #{current_path}/#{path} && \ - ln -s #{shared_path}/#{path} #{current_path}/#{path}" - end - - commands += weird_symlinks.map do |from, to| - "rm -rf #{current_path}/#{to} && \ - ln -s #{shared_path}/#{from} #{current_path}/#{to}" - end - - run <<-CMD - cd #{current_path} && - #{commands.join(" && ")} - CMD - end -end -after "deploy:update_code", "symlinks:make" \ No newline at end of file diff --git a/config/deploy/targets.rb b/config/deploy/targets.rb deleted file mode 100644 index b076136a5..000000000 --- a/config/deploy/targets.rb +++ /dev/null @@ -1,36 +0,0 @@ -task :production do - role :web, "69.164.212.22" - role :app, "69.164.212.22" - role :db, "69.164.212.22", :primary => true - set :web_server, :nginx - set :nginx_conf_dir, '/etc/nginx/conf' - set :domain, "westroxbury.commonplaceusa.com westroxbury.ourcommonplace.com" - set :web_port, "80" - set :rails_env, "production" - set :branch, "origin/master" - set :deploy_to, "/home/#{user}/#{application}" -end - -task :staging do - role :web, "69.164.215.169" - role :app, "69.164.215.169" - role :db, "69.164.215.169", :primary => true - set :web_server, :nginx - set :domain, "staging.commonplace.co" - set :web_port, "80" - set :rails_env, "staging" - set :branch, "origin/master" - set :deploy_to, "/home/#{user}/staging" -end - -task :demo do - role :web, "69.164.215.169" - role :app, "69.164.215.169" - role :db, "69.164.215.169", :primary => true - set :web_server, :nginx - set :domain, "yourtown.ourcommonplace.com yourtown.commonplaceusa.com" - set :web_port, "80" - set :rails_env, "production" - set :branch, "origin/demo" - set :deploy_to, "/home/#{user}/demo" -end diff --git a/config/environment.rb b/config/environment.rb index db5a12406..9325f3819 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,61 +1,5 @@ -# Be sure to restart your server when you modify this file - -# Specifies gem version of Rails to use when vendor/rails is not present -RAILS_GEM_VERSION = '2.3.9' unless defined? RAILS_GEM_VERSION - -# Bootstrap the Rails environment, frameworks, and default configuration -require File.join(File.dirname(__FILE__), 'boot') - -Rails::Initializer.run do |config| - # Settings in config/environments/* take precedence over those specified here. - # Application configuration should go into files in config/initializers - # -- all .rb files in that directory are automatically loaded. - - # Add additional load paths for your own custom dirs - # config.load_paths += %W( #{RAILS_ROOT}/extras ) - - # Specify gems that this application depends on and have them installed with rake gems:install - # config.gem "bj" - # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net" - # config.gem "sqlite3-ruby", :lib => "sqlite3" - # config.gem "aws-s3", :lib => "aws/s3" - - # config.gem "facebooker" - - config.gem "facebooker" - - config.action_mailer.default_content_type = "text/html" - # Only load the plugins named here, in the order given (default is alphabetical). - # :all can be used as a placeholder for all plugins not explicitly named - # config.plugins = [ :exception_notification, :ssl_requirement, :all ] - - # Skip frameworks you're not going to use. To use Rails without a database, - # you must remove the Active Record framework. - # config.frameworks -= [ :active_record, :active_resource, :action_mailer ] - - # Activate observers that should always be running - # config.active_record.observers = :cacher, :garbage_collector, :forum_observer - - # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. - # Run "rake -D time" for a list of tasks for finding time zone names. - config.time_zone = 'UTC' - - # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. - # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')] - # config.i18n.default_locale = :de -end - -ActionController::Base.exempt_from_layout 'js.erb', 'json.erb' - -require 'tlsmail' -Net::SMTP.enable_tls(OpenSSL::SSL::VERIFY_NONE) - -ActionMailer::Base.smtp_settings = { - :address => 'smtp.sendgrid.net', - :port => 587, - :domain => "commonplaceusa.com", - :authentication => :plain, - :user_name => 'maxtilford@gmail.com', - :password => 'tlax1copo' -} +# Load the rails application +require File.expand_path('../application', __FILE__) +# Initialize the rails application +Commonplace::Application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb index b3543b5ba..5dab8b386 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,18 +1,31 @@ -# Settings specified here will take precedence over those in config/environment.rb +Commonplace::Application.configure do + # Settings specified here will take precedence over those in config/application.rb -# In the development environment your application's code is reloaded on -# every request. This slows down response time but is perfect for development -# since you don't have to restart the webserver when you make code changes. -config.cache_classes = false + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the webserver when you make code changes. + config.cache_classes = false -# Log error messages when you accidentally call methods on nil. -config.whiny_nils = true + # Do not compress assets + config.assets.compress = false -# Show full error reports and disable caching -config.action_controller.consider_all_requests_local = true -config.action_view.debug_rjs = true -config.action_controller.perform_caching = false + # Expands the lines which load the assets + config.assets.debug = true -config.action_mailer.delivery_method = :test + # Log error messages when you accidentally call methods on nil. + config.whiny_nils = true + + # Show full error reports and disable caching + + config.action_controller.perform_caching = false + + # Don't care if the mailer can't send + config.action_mailer.raise_delivery_errors = false + + # Print deprecation notices to the Rails logger + config.active_support.deprecation = :log + + # Only use best-standards-support built into browsers + config.action_dispatch.best_standards_support = :builtin +end -config.action_mailer.default_url_options = { :host => "localhost:3000" } diff --git a/config/environments/frontend.rb b/config/environments/frontend.rb new file mode 100644 index 000000000..c69176bec --- /dev/null +++ b/config/environments/frontend.rb @@ -0,0 +1,32 @@ +Commonplace::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the webserver when you make code changes. + config.cache_classes = true + + # Do not compress assets + config.assets.compress = false + + # Expands the lines which load the assets + config.assets.debug = true + + # Log error messages when you accidentally call methods on nil. + config.whiny_nils = true + + # Show full error reports and disable caching + + config.action_controller.perform_caching = false + + # Don't care if the mailer can't send + config.action_mailer.raise_delivery_errors = false + + # Print deprecation notices to the Rails logger + config.active_support.deprecation = :log + + # Only use best-standards-support built into browsers + config.action_dispatch.best_standards_support = :builtin +end + +Rails.instance_variable_set("@_env", ActiveSupport::StringInquirer.new("development")) diff --git a/config/environments/production.rb b/config/environments/production.rb index 8beac4ea6..647c01637 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,30 +1,62 @@ -# Settings specified here will take precedence over those in config/environment.rb +Commonplace::Application.configure do + # Settings specified here will take precedence over those in config/application.rb -# The production environment is meant for finished, "live" apps. -# Code is not reloaded between requests -config.cache_classes = true + # Compress JavaScripts and CSS + config.assets.compress = true -# Full error reports are disabled and caching is turned on -config.action_controller.consider_all_requests_local = false -config.action_controller.perform_caching = true -config.action_view.cache_template_loading = true -ActionMailer::Base.delivery_method = :smtp -# See everything in the log (default is :info) -# config.log_level = :debug + # Generate digests for assets URLs + config.assets.digest = true -# Use a different logger for distributed setups -# config.logger = SyslogLogger.new + # Defaults to Rails.root.join("public/assets") + # config.assets.manifest = YOUR_PATH -# Use a different cache store in production -# config.cache_store = :mem_cache_store + config.assets.js_compressor = :uglifier + config.assets.css_compressor = :scss -# Enable serving of images, stylesheets, and javascripts from an asset server -# config.action_controller.asset_host = "http://assets.example.com" + # The production environment is meant for finished, "live" apps. + # Code is not reloaded between requests + config.cache_classes = true -# Disable delivery errors, bad email addresses will be ignored -# config.action_mailer.raise_delivery_errors = false + # Full error reports are disabled and caching is turned on + config.action_controller.perform_caching = true -# Enable threaded mode -# config.threadsafe! + #config.action_controller.asset_host = "http://www.commonplaceusa.com" -config.action_mailer.default_url_options = { :host => "ourcommonplace.com" } + # Specifies the header that your server uses for sending files + config.action_dispatch.x_sendfile_header = "X-Sendfile" + + # For nginx: + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' + + # If you have no front-end server that supports something like X-Sendfile, + # just comment this out and Rails will serve the files + + # See everything in the log (default is :info) + # config.log_level = :debug + + # Use a different logger for distributed setups + # config.logger = SyslogLogger.new + + # Use a different cache store in production + config.cache_store = :dalli_store + + # Disable Rails's static asset server + # In production, Apache or nginx will already do this + config.serve_static_assets = false + + # Enable serving of images, stylesheets, and javascripts from an asset server + # config.action_controller.asset_host = "http://assets.example.com" + + # Disable delivery errors, bad email addresses will be ignored + # config.action_mailer.raise_delivery_errors = false + + # Enable threaded mode + # config.threadsafe! + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation can not be found) + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners + config.active_support.deprecation = :notify +end diff --git a/config/environments/profile.rb b/config/environments/profile.rb new file mode 100644 index 000000000..f3468eb04 --- /dev/null +++ b/config/environments/profile.rb @@ -0,0 +1,8 @@ +require 'production_log/syslog_logger' +Commonplace::Application.configure do + config.cache_classes = true + + config.action_controller.perform_caching = false + config.log_level = :info + RAILS_DEFAULT_LOGGER = SyslogLogger.new +end diff --git a/config/environments/sendmail.rb b/config/environments/sendmail.rb new file mode 100644 index 000000000..0f41626ab --- /dev/null +++ b/config/environments/sendmail.rb @@ -0,0 +1,17 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# In the development environment your application's code is reloaded on +# every request. This slows down response time but is perfect for development +# since you don't have to restart the webserver when you make code changes. +config.cache_classes = false + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching + +config.action_controller.perform_caching = false + +config.action_mailer.delivery_method = :sendmail + +config.action_mailer.default_url_options = { :host => "localhost:3000" } diff --git a/config/environments/staging.rb b/config/environments/staging.rb index 4c713329f..492cbc67e 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -1,10 +1,26 @@ -# Don't care if the mailer can't send -config.action_mailer.delivery_method = :test +Commonplace::Application.configure do -config.cache_classes = true + # Don't Compress JavaScripts and CSS + config.assets.compress = true -# Full error reports are disabled and caching is turned on -config.action_controller.consider_all_requests_local = false -config.action_controller.perform_caching = true -config.action_view.cache_template_loading = true -config.log_level = :debug + # Fallback to assets pipeline if a precompiled asset is missed + config.assets.compile = true + + # Generate digests for assets URLs + config.assets.digest = true + + config.action_mailer.delivery_method = :smtp + + config.cache_classes = true + + config.cache_store = :dalli_store + + # Full error reports are disabled and caching is turned on + config.consider_all_requests_local = true + + config.action_controller.perform_caching = true + + config.log_level = :debug + + +end diff --git a/config/environments/test.rb b/config/environments/test.rb index 509ebeb26..78d376134 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,29 +1,38 @@ -# Settings specified here will take precedence over those in config/environment.rb +Commonplace::Application.configure do + # Settings specified here will take precedence over those in config/application.rb -# The test environment is used exclusively to run your application's -# test suite. You never need to work with it otherwise. Remember that -# your test database is "scratch space" for the test suite and is wiped -# and recreated between test runs. Don't rely on the data there! -config.cache_classes = true + # Configure static asset server for tests with Cache-Control for performance + config.serve_static_assets = true + config.static_cache_control = "public, max-age=3600" -# Log error messages when you accidentally call methods on nil. -config.whiny_nils = true + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = false -# Show full error reports and disable caching -config.action_controller.consider_all_requests_local = true -config.action_controller.perform_caching = false -config.action_view.cache_template_loading = true + # Log error messages when you accidentally call methods on nil. + config.whiny_nils = true -# Disable request forgery protection in test environment -config.action_controller.allow_forgery_protection = false + # Show full error reports and disable caching + config.action_controller.perform_caching = false -# Tell Action Mailer not to deliver emails to the real world. -# The :test delivery method accumulates sent emails in the -# ActionMailer::Base.deliveries array. -config.action_mailer.delivery_method = :test + # Raise exceptions instead of rendering exception templates + config.action_dispatch.show_exceptions = false -# Use SQL instead of Active Record's schema dumper when creating the test database. -# This is necessary if your schema can't be completely dumped by the schema dumper, -# like if you have constraints or database-specific column types -# config.active_record.schema_format = :sql + # Disable request forgery protection in test environment + config.action_controller.allow_forgery_protection = false + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Use SQL instead of Active Record's schema dumper when creating the test database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql + + # Print deprecation notices to the stderr + config.active_support.deprecation = :stderr +end diff --git a/config/facebooker.yml b/config/facebooker.yml deleted file mode 100644 index 8149793b3..000000000 --- a/config/facebooker.yml +++ /dev/null @@ -1,50 +0,0 @@ -# The api key, secret key, and canvas page name are required to get started -# Tunnel configuration is only needed if you are going to use the facebooker:tunnel Rake tasks -# Your callback url in Facebook should be set to http://public_host:public_port -# If you're building a Facebook connect site, -# change the value of set_asset_host_to_callback_url to false -# To develop for the new profile design, add the following key.. -# api: new -# remove the key or set it to anything else to use the old facebook design. -# This should only be necessary until the final version of the new profile is released. - -development: - api: new - api_key: "179741908724938" - secret_key: "4d1d96dc9b402ca6779f77bc9e88b89a" - canvas_page_name: - callback_url: http://test.commonplace.co:4020/account/new - pretty_errors: true - set_asset_host_to_callback_url: false - tunnel: - public_host_username: deploy - public_host: staging.commonplaceusa.com - public_port: 4007 - local_port: 3000 - server_alive_interval: 0 - -staging: - api_key: - secret_key: - canvas_page_name: - callback_url: - set_asset_host_to_callback_url: true - tunnel: - public_host_username: - public_host: - public_port: 4007 - local_port: 3000 - server_alive_interval: 0 - -production: - api_key: - secret_key: - canvas_page_name: - callback_url: - set_asset_host_to_callback_url: true - tunnel: - public_host_username: - public_host: - public_port: 4007 - local_port: 3000 - server_alive_interval: 0 diff --git a/config/initializers/active_admin.rb b/config/initializers/active_admin.rb new file mode 100644 index 000000000..81578da66 --- /dev/null +++ b/config/initializers/active_admin.rb @@ -0,0 +1,78 @@ +ActiveAdmin.setup do |config| + + # == Site Title + # + # Set the title that is displayed on the main layout + # for each of the active admin pages. + # + config.site_title = "Commonplace" + + + # == Default Namespace + # + # Set the default namespace each administration resource + # will be added to. + # + # eg: + # config.default_namespace = :hello_world + # + # This will create resources in the HelloWorld module and + # will namespace routes to /hello_world/* + # + # To set no namespace by default, use: + # config.default_namespace = false + config.default_namespace = :admin + + + # == User Authentication + # + # Active Admin will automatically call an authentication + # method in a before filter of all controller actions to + # ensure that there is a currently logged in admin user. + # + # This setting changes the method which Active Admin calls + # within the controller. + config.authentication_method = :authenticate_admin_user! + + + # == Current User + # + # Active Admin will associate actions with the current + # user performing them. + # + # This setting changes the method which Active Admin calls + # to return the currently logged in user. + config.current_user_method = :current_admin_user + + + # == Admin Comments + # + # Admin notes allow you to add notes to any model + # + # Admin notes are enabled by default in the default + # namespace only. You can turn them on in a namesapce + # by adding them to the comments array. + # + # config.allow_comments_in = [:admin] + + + # == Controller Filters + # + # You can add before, after and around filters to all of your + # Active Admin resources from here. + # + # config.before_filter :do_something_awesome + + + # == Register Stylesheets & Javascripts + # + # We recomend using the built in Active Admin layout and loading + # up your own stylesheets / javascripts to customize the look + # and feel. + # + # To load a stylesheet: + # config.register_stylesheet 'my_stylesheet.css' + # + # To load a javascript file: + # config.register_javascript 'my_javascript.js' +end diff --git a/config/initializers/active_record_json.rb b/config/initializers/active_record_json.rb new file mode 100644 index 000000000..e8d084b30 --- /dev/null +++ b/config/initializers/active_record_json.rb @@ -0,0 +1,3 @@ + +ActiveRecord::Base.include_root_in_json = false + diff --git a/config/initializers/acts_as_api.rb b/config/initializers/acts_as_api.rb new file mode 100644 index 000000000..26b4ce7c7 --- /dev/null +++ b/config/initializers/acts_as_api.rb @@ -0,0 +1,2 @@ +ActsAsApi::Config.include_root_in_json_collections = false +ActsAsApi::Config.add_root_node_for = [] diff --git a/config/initializers/acts_as_taggable_on.rb b/config/initializers/acts_as_taggable_on.rb deleted file mode 100644 index 1fec410fb..000000000 --- a/config/initializers/acts_as_taggable_on.rb +++ /dev/null @@ -1,113 +0,0 @@ -module ActsAsTaggableOn::Taggable - module Core - - module ClassMethods - def tagged_with_aliases(tags, options = {}) - tags = tags.map do |t| - tag = ActsAsTaggableOn::Tag.find_by_name(t) - tag ? tag.aliases.map(&:name) : nil - end.flatten - tag_list = ActsAsTaggableOn::TagList.from(tags) - - return [] if tag_list.empty? - - joins = [] - conditions = [] - - context = options.delete(:on) - - if options.delete(:exclude) - tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name LIKE ?", t]) }.join(" OR ") - conditions << "#{table_name}.#{primary_key} NOT IN (SELECT #{ActsAsTaggableOn::Tagging.table_name}.taggable_id FROM #{ActsAsTaggableOn::Tagging.table_name} JOIN #{ActsAsTaggableOn::Tag.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND (#{tags_conditions}) WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})" - - elsif options.delete(:any) - tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name LIKE ?", t]) }.join(" OR ") - conditions << "#{table_name}.#{primary_key} IN (SELECT #{ActsAsTaggableOn::Tagging.table_name}.taggable_id FROM #{ActsAsTaggableOn::Tagging.table_name} JOIN #{ActsAsTaggableOn::Tag.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND (#{tags_conditions}) WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})" - - else - tags = ActsAsTaggableOn::Tag.named_any(tag_list) - return scoped(:conditions => "1 = 0") unless tags.length == tag_list.length - - tags.each do |tag| - safe_tag = tag.name.gsub(/[^a-zA-Z0-9]/, '') - prefix = "#{safe_tag}_#{rand(1024)}" - - taggings_alias = "#{undecorated_table_name}_taggings_#{prefix}" - - tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" + - " ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" + - " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}" + - " AND #{taggings_alias}.tag_id = #{tag.id}" - tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context - - joins << tagging_join - end - end - - taggings_alias, tags_alias = "#{undecorated_table_name}_taggings_group", "#{undecorated_table_name}_tags_group" - - if options.delete(:match_all) - joins << "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" + - " ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" + - " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}" - - - group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}" - group = "#{group_columns} HAVING COUNT(#{taggings_alias}.taggable_id) = #{tags.size}" - end - - - scoped(:joins => joins.join(" "), - :group => group, - :conditions => conditions.join(" AND "), - :order => options[:order], - :readonly => false) - end - - end - end -end - -module ActsAsTaggableOn::Taggable - module Related - module InstanceMethods - - def related_of_class(klass, options = {}) - tags_to_find = base_tags.map { |t| t.aliases.map(&:name) }.flatten - - exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass - -group_columns = "#{klass.table_name}.#{klass.primary_key}" - - klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.id) AS count", - :from => "#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}", - :conditions => ["#{exclude_self} #{klass.table_name}.id = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?)", tags_to_find], - :group => group_columns, - :order => "count DESC" }.update(options)) - end - - end - end -end - - -class ActsAsTaggableOn::Tag < ActiveRecord::Base - belongs_to :canonical_tag, :class_name => 'ActsAsTaggableOn::Tag' - has_many :aliases, :foreign_key => 'canonical_tag_id', :class_name => 'ActsAsTaggableOn::Tag', :dependent => :nullify, :primary_key => :canonical_tag_id - - after_create :set_canonical_tag_id - - def canonical? - canonical_tag_id == id - end - - protected - - def set_canonical_tag_id - unless self.canonical_tag_id - self.canonical_tag_id = self.id - self.save - end - end - -end diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb index c2169ed01..59385cdf3 100644 --- a/config/initializers/backtrace_silencers.rb +++ b/config/initializers/backtrace_silencers.rb @@ -3,5 +3,5 @@ # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } -# You can also remove all the silencers if you're trying do debug a problem that might steem from framework code. -# Rails.backtrace_cleaner.remove_silencers! \ No newline at end of file +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/config/initializers/compass.rb b/config/initializers/compass.rb deleted file mode 100644 index 6811b3267..000000000 --- a/config/initializers/compass.rb +++ /dev/null @@ -1,3 +0,0 @@ -require 'compass' -require 'compass/app_integration/rails' -Compass::AppIntegration::Rails.initialize! diff --git a/config/initializers/cookie_verification_secret.rb b/config/initializers/cookie_verification_secret.rb deleted file mode 100644 index 34eeb6771..000000000 --- a/config/initializers/cookie_verification_secret.rb +++ /dev/null @@ -1,7 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Your secret key for verifying the integrity of signed cookies. -# If you change this key, all old signed cookies will become invalid! -# Make sure the secret is at least 30 characters and all random, -# no regular words or you'll be exposed to dictionary attacks. -ActionController::Base.cookie_verifier_secret = 'fe29a19e596d582ba94a9c2b35c5c6611347fdba26de0ccf1409b5535bd063cc26a9c867b522252cf3791520ea202be41107dc1a4187a06bfcc86ea4bc9ad20b'; diff --git a/config/initializers/cropper.rb b/config/initializers/cropper.rb new file mode 100644 index 000000000..589f70af9 --- /dev/null +++ b/config/initializers/cropper.rb @@ -0,0 +1,12 @@ +module Paperclip + class Cropper < Thumbnail + def transformation_command + target = @attachment.instance + if target.cropping? + ['-crop', "'#{target.crop_w}x#{target.crop_h}+#{target.crop_x}+#{target.crop_y}'"] + super + else + super + end + end + end +end diff --git a/config/initializers/date_time_formats.rb b/config/initializers/date_time_formats.rb index 04dec226b..e0f85cd28 100644 --- a/config/initializers/date_time_formats.rb +++ b/config/initializers/date_time_formats.rb @@ -1,6 +1,6 @@ -ActiveSupport::CoreExtensions::Date::Conversions::DATE_FORMATS.merge!(:default => '%m/%d/%Y') - -ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!( +Time::DATE_FORMATS.merge!( :default => "%I:%M %p", - :date_time12 => "%m/%d/%Y %I:%M%p" + :date_time12 => "%m/%d/%Y %I:%M%p", + :month_day_at_time => "%B %d at %I:%M%p", + :month_day_year => "%B %d, %Y" ) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb new file mode 100644 index 000000000..dccfcb3b7 --- /dev/null +++ b/config/initializers/devise.rb @@ -0,0 +1,196 @@ +# Use this hook to configure devise mailer, warden hooks and so forth. The first +# four configuration values can also be set straight in your models. +Devise.setup do |config| + # ==> Mailer Configuration + # Configure the e-mail address which will be shown in DeviseMailer. + config.mailer_sender = "please-change-me-at-config-initializers-devise@example.com" + + # Configure the class responsible to send e-mails. + # config.mailer = "Devise::Mailer" + + # ==> ORM configuration + # Load and configure the ORM. Supports :active_record (default) and + # :mongoid (bson_ext recommended) by default. Other ORMs may be + # available as additional gems. + require 'devise/orm/active_record' + + # ==> Configuration for any authentication mechanism + # Configure which keys are used when authenticating a user. The default is + # just :email. You can configure it to use [:username, :subdomain], so for + # authenticating a user, both parameters are required. Remember that those + # parameters are used only when authenticating and not when retrieving from + # session. If you need permissions, you should implement that in a before filter. + # You can also supply a hash where the value is a boolean determining whether + # or not authentication should be aborted when the value is not present. + # config.authentication_keys = [ :email ] + + # Configure parameters from the request object used for authentication. Each entry + # given should be a request method and it will automatically be passed to the + # find_for_authentication method and considered in your model lookup. For instance, + # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. + # The same considerations mentioned for authentication_keys also apply to request_keys. + # config.request_keys = [] + + # Configure which authentication keys should be case-insensitive. + # These keys will be downcased upon creating or modifying a user and when used + # to authenticate or find a user. Default is :email. + config.case_insensitive_keys = [ :email ] + + # Tell if authentication through request.params is enabled. True by default. + # config.params_authenticatable = true + + # Tell if authentication through HTTP Basic Auth is enabled. False by default. + # config.http_authenticatable = false + + # If http headers should be returned for AJAX requests. True by default. + # config.http_authenticatable_on_xhr = true + + # The realm used in Http Basic Authentication. "Application" by default. + # config.http_authentication_realm = "Application" + + # ==> Configuration for :database_authenticatable + # For bcrypt, this is the cost for hashing the password and defaults to 10. If + # using other encryptors, it sets how many times you want the password re-encrypted. + config.stretches = 20 + + # Setup a pepper to generate the encrypted password. + # config.pepper = "8c5b81bee2c1480efb85b794780ee664e4eea855b7c30b9e9fcea713377a09da8b45151443a2922a9a9205f90d24433d2a07cd0f19b034f9095c5df05c97c2a0" + + # ==> Configuration for :confirmable + # The time you want to give your user to confirm his account. During this time + # he will be able to access your application without confirming. Default is 0.days + # When confirm_within is zero, the user won't be able to sign in without confirming. + # You can use this to let your user access some features of your application + # without confirming the account, but blocking it after a certain period + # (ie 2 days). + # config.confirm_within = 2.days + + # Defines which key will be used when confirming an account + # config.confirmation_keys = [ :email ] + + # ==> Configuration for :rememberable + # The time the user will be remembered without asking for credentials again. + # config.remember_for = 2.weeks + + # If true, a valid remember token can be re-used between multiple browsers. + # config.remember_across_browsers = true + + # If true, extends the user's remember period when remembered via cookie. + # config.extend_remember_period = false + + # If true, uses the password salt as remember token. This should be turned + # to false if you are not using database authenticatable. + config.use_salt_as_remember_token = true + + # Options to be passed to the created cookie. For instance, you can set + # :secure => true in order to force SSL only cookies. + # config.cookie_options = {} + + # ==> Configuration for :validatable + # Range for password length. Default is 6..128. + # config.password_length = 6..128 + + # Regex to use to validate the email address + # config.email_regexp = /\A([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})\z/i + + # ==> Configuration for :timeoutable + # The time you want to timeout the user session without activity. After this + # time the user will be asked for credentials again. Default is 30 minutes. + # config.timeout_in = 30.minutes + + # ==> Configuration for :lockable + # Defines which strategy will be used to lock an account. + # :failed_attempts = Locks an account after a number of failed attempts to sign in. + # :none = No lock strategy. You should handle locking by yourself. + # config.lock_strategy = :failed_attempts + + # Defines which key will be used when locking and unlocking an account + # config.unlock_keys = [ :email ] + + # Defines which strategy will be used to unlock an account. + # :email = Sends an unlock link to the user email + # :time = Re-enables login after a certain amount of time (see :unlock_in below) + # :both = Enables both strategies + # :none = No unlock strategy. You should handle unlocking by yourself. + # config.unlock_strategy = :both + + # Number of authentication tries before locking an account if lock_strategy + # is failed attempts. + # config.maximum_attempts = 20 + + # Time interval to unlock the account if :time is enabled as unlock_strategy. + # config.unlock_in = 1.hour + + # ==> Configuration for :recoverable + # + # Defines which key will be used when recovering the password for an account + # config.reset_password_keys = [ :email ] + + # Time interval you can reset your password with a reset password key. + # Don't put a too small interval or your users won't have the time to + # change their passwords. + config.reset_password_within = 2.hours + + # ==> Configuration for :encryptable + # Allow you to use another encryption algorithm besides bcrypt (default). You can use + # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1, + # :authlogic_sha512 (then you should set stretches above to 20 for default behavior) + # and :restful_authentication_sha1 (then you should set stretches to 10, and copy + # REST_AUTH_SITE_KEY to pepper) + config.encryptor = :authlogic_sha512 + + # ==> Configuration for :token_authenticatable + # Defines name of the authentication token params key + # config.token_authentication_key = :auth_token + + # If true, authentication through token does not store user in session and needs + # to be supplied on each request. Useful if you are using the token as API token. + # config.stateless_token = false + + # ==> Scopes configuration + # Turn scoped views on. Before rendering "sessions/new", it will first check for + # "users/sessions/new". It's turned off by default because it's slower if you + # are using only default views. + # config.scoped_views = false + + # Configure the default scope given to Warden. By default it's the first + # devise role declared in your routes (usually :user). + config.default_scope = :user + + # Configure sign_out behavior. + # Sign_out action can be scoped (i.e. /users/sign_out affects only :user scope). + # The default is true, which means any logout action will sign out all active scopes. + # config.sign_out_all_scopes = true + + # ==> Navigation configuration + # Lists the formats that should be treated as navigational. Formats like + # :html, should redirect to the sign in page when the user does not have + # access, but formats like :xml or :json, should return 401. + # + # If you have any extra navigational formats, like :iphone or :mobile, you + # should add them to the navigational formats lists. + # + # The :"*/*" and "*/*" formats below is required to match Internet + # Explorer requests. + # config.navigational_formats = [:"*/*", "*/*", :html] + + # The default HTTP method used to sign out a resource. Default is :get. + # config.sign_out_via = :get + + # ==> OmniAuth + # Add a new OmniAuth provider. Check the wiki for more information on setting + # up on your models and hooks. + # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo' + + config.omniauth :facebook, ENV["facebook_app_id"], ENV["facebook_app_secret"] + + # ==> Warden configuration + # If you want to use other strategies, that are not supported by Devise, or + # change the failure app, you can configure them inside the config.warden block. + # + # config.warden do |manager| + # manager.failure_app = AnotherApp + # manager.intercept_401 = false + # manager.default_strategies(:scope => :user).unshift :some_external_strategy + # end +end diff --git a/config/initializers/environment.rb b/config/initializers/environment.rb new file mode 100644 index 000000000..70e798e90 --- /dev/null +++ b/config/initializers/environment.rb @@ -0,0 +1,5 @@ +# Refine our environment +CP_ENV = ( ENV['HEROKU_APP'] == 'commonplace' ) ? 'production' : 'staging' + +#p "Loading env: #{Rails.env}" +#require "sinatra/reloader" if development? # not quite sure where to properly put this diff --git a/config/initializers/facebook.rb b/config/initializers/facebook.rb new file mode 100644 index 000000000..4b13063b2 --- /dev/null +++ b/config/initializers/facebook.rb @@ -0,0 +1 @@ +$FacebookConfig = {"app_id" => ENV["facebook_app_id"] || "12345", "app_secret" => ENV["facebook_app_secret"]} diff --git a/config/initializers/formtastic.rb b/config/initializers/formtastic.rb index d43cdb0a1..82b796d25 100644 --- a/config/initializers/formtastic.rb +++ b/config/initializers/formtastic.rb @@ -4,6 +4,8 @@ # Set the default text area height when input is a text. Default is 20. Formtastic::SemanticFormBuilder.default_text_area_height = 5 +Formtastic::SemanticFormBuilder.escape_html_entities_in_hints_and_labels = false + # Should all fields be considered "required" by default? # Defaults to true, see ValidationReflection notes below. # Formtastic::SemanticFormBuilder.all_fields_required_by_default = true diff --git a/config/initializers/haml.rb b/config/initializers/haml.rb index e507a737f..88d555b44 100644 --- a/config/initializers/haml.rb +++ b/config/initializers/haml.rb @@ -1 +1,2 @@ -Haml::Template.options[:format] = :html5 + Haml::Template.options[:format] = :html5 + Haml::Template.options[:ugly] = true diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index d531b8bb8..9e8b0131f 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -1,6 +1,6 @@ # Be sure to restart your server when you modify this file. -# Add new inflection rules using the following format +# Add new inflection rules using the following format # (all these examples are active by default): # ActiveSupport::Inflector.inflections do |inflect| # inflect.plural /^(ox)$/i, '\1en' diff --git a/config/initializers/load_config.rb b/config/initializers/load_config.rb deleted file mode 100644 index 8c16c8169..000000000 --- a/config/initializers/load_config.rb +++ /dev/null @@ -1 +0,0 @@ -CONFIG = Rails.root.join("config", "config.yml").open{ |file| YAML::load(file) } diff --git a/config/initializers/load_resque.rb b/config/initializers/load_resque.rb index b989901ab..0ac25a13c 100644 --- a/config/initializers/load_resque.rb +++ b/config/initializers/load_resque.rb @@ -1,4 +1,19 @@ require 'resque' -Resque.redis = "localhost:6379" require 'resque_scheduler' -Resque.schedule = YAML.load_file(File.join(File.dirname(__FILE__), '../resque_schedule.yml')) + +uri = if Rails.env.development? || Rails.env.test? + URI.parse("localhost:6379") + elsif ENV["REDISTOGO_URL"].present? + URI.parse(ENV["REDISTOGO_URL"]) + end + +if uri + Resque.redis = Redis.new(:host => uri.host, + :port => uri.port, + :password => uri.password, + :thread_safe => true) + + Resque.schedule = + YAML.load_file(File.join(File.dirname(__FILE__), '../resque_schedule.yml')) + +end diff --git a/config/initializers/load_skills_interests_goods.rb b/config/initializers/load_skills_interests_goods.rb new file mode 100644 index 000000000..ac8fa980f --- /dev/null +++ b/config/initializers/load_skills_interests_goods.rb @@ -0,0 +1,4 @@ + +$goods = File.read(Rails.root.join("app", "text", "goods.txt")).split("\n") +$skills = File.read(Rails.root.join("app", "text", "skills.txt")).split("\n") +$interests = File.read(Rails.root.join("app", "text", "interests.txt")).split("\n") diff --git a/config/initializers/locales.rb b/config/initializers/locales.rb new file mode 100644 index 000000000..ba19db625 --- /dev/null +++ b/config/initializers/locales.rb @@ -0,0 +1,2 @@ + +I18n.load_path += Dir[Rails.root.join('app', 'text', '*', '*.{rb,yml}')] diff --git a/config/initializers/mail.rb b/config/initializers/mail.rb new file mode 100644 index 000000000..c32807629 --- /dev/null +++ b/config/initializers/mail.rb @@ -0,0 +1,15 @@ +$MailDeliveryMethod = ENV['mail_delivery_method'].try(:intern) || :file + +$MailDeliveryOptions = + if $MailDeliveryMethod == :file + {:location => Rails.root.join('tmp','inboxes')} + else + { + :address => ENV['mail_address'], + :port => ENV['mail_port'], + :domain => ENV['domain'], + :authentication => ENV['mail_authentication'], + :user_name => ENV['mail_username'], + :password => ENV['mail_password'] + } + end diff --git a/config/initializers/new_rails_defaults.rb b/config/initializers/new_rails_defaults.rb deleted file mode 100644 index c94db0a66..000000000 --- a/config/initializers/new_rails_defaults.rb +++ /dev/null @@ -1,21 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# These settings change the behavior of Rails 2 apps and will be defaults -# for Rails 3. You can remove this initializer when Rails 3 is released. - -if defined?(ActiveRecord) - # Include Active Record class name as root for JSON serialized output. - ActiveRecord::Base.include_root_in_json = true - - # Store the full class name (including module namespace) in STI type column. - ActiveRecord::Base.store_full_sti_class = true -end - -ActionController::Routing.generate_best_match = false - -# Use ISO 8601 format for JSON serialized times and dates. -ActiveSupport.use_standard_json_time_format = true - -# Don't escape HTML entities in JSON, leave that for the #json_escape helper. -# if you're including raw json in an HTML page. -ActiveSupport.escape_html_entities_in_json = false \ No newline at end of file diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb new file mode 100644 index 000000000..5b4b5fba4 --- /dev/null +++ b/config/initializers/omniauth.rb @@ -0,0 +1,3 @@ +#Rails.application.config.middleware.use OmniAuth::Builder do +# provider :facebook, '77442856c681c12c270c8863ec113375', '09ba7cc287744ba99dd508d640eb57b7', {:scope => 'offline_access'} +#end diff --git a/config/initializers/pry.rb b/config/initializers/pry.rb new file mode 100644 index 000000000..20b8218df --- /dev/null +++ b/config/initializers/pry.rb @@ -0,0 +1,11 @@ +Commonplace::Application.configure do + silence_warnings do + # This will gracefully fall back to IRB in production (pry gem not loaded) + # But it whines. Let's shut it up. + begin + require 'pry' + IRB = Pry + rescue LoadError + end + end +end diff --git a/config/initializers/register_lib_tilt_templates.rb b/config/initializers/register_lib_tilt_templates.rb new file mode 100644 index 000000000..465aa9ba1 --- /dev/null +++ b/config/initializers/register_lib_tilt_templates.rb @@ -0,0 +1,2 @@ +Commonplace::Application.assets.register_engine ".mustache", Tilt::MustacheTemplate +Commonplace::Application.assets.register_engine ".i18n", Tilt::I18nTemplate diff --git a/config/initializers/remove_rack_timeout_middleware.rb b/config/initializers/remove_rack_timeout_middleware.rb new file mode 100644 index 000000000..b3c6995b7 --- /dev/null +++ b/config/initializers/remove_rack_timeout_middleware.rb @@ -0,0 +1 @@ +Rails.application.config.middleware.delete Rack::Timeout diff --git a/config/initializers/require_mapifier.rb b/config/initializers/require_mapifier.rb index de6a77278..cedca8277 100644 --- a/config/initializers/require_mapifier.rb +++ b/config/initializers/require_mapifier.rb @@ -1 +1 @@ -require "#{RAILS_ROOT}/lib/mapifier.rb" +require "#{Rails.root}/lib/mapifier.rb" diff --git a/config/initializers/resque.rb b/config/initializers/resque.rb new file mode 100644 index 000000000..83b0dace4 --- /dev/null +++ b/config/initializers/resque.rb @@ -0,0 +1,15 @@ +require 'resque' +require 'resque-exceptional' + +require 'resque/failure/multiple' +require 'resque/failure/redis' + +if Rails.env.production? + Resque::Failure::Exceptional.configure do |config| + config.api_key = '0556a141945715c3deb50a0288ec3bea5417f6bf' + end + + Resque::Failure::Multiple.classes = [Resque::Failure::Redis, Resque::Failure::Exceptional] + Resque::Failure.backend = Resque::Failure::Multiple +end + diff --git a/config/initializers/resque_mailer.rb b/config/initializers/resque_mailer.rb deleted file mode 100644 index 5225f8045..000000000 --- a/config/initializers/resque_mailer.rb +++ /dev/null @@ -1 +0,0 @@ - Resque::Mailer.excluded_environments = [:development, :test, :cucumber] diff --git a/config/initializers/rollout.rb b/config/initializers/rollout.rb new file mode 100644 index 000000000..46584c49f --- /dev/null +++ b/config/initializers/rollout.rb @@ -0,0 +1,35 @@ +uri = if Rails.env.development? || Rails.env.test? + URI.parse("localhost:6379") + elsif ENV["REDISTOGO_URL"].present? + URI.parse(ENV["REDISTOGO_URL"]) + end + +if uri + $rollout = Rollout.new(Redis.new(:host => uri.host, + :port => uri.port, + :password => uri.password, + :thread_safe => true)) + + + $rollout.define_group(:alpha) do |user| + user.community.slug == "CommonPlace" or user.community.slug == "test" + end + + $rollout.define_group(:harrisonburg) do |user| + user.community.slug == "harrisonburg" + end + + $rollout.define_group(:fallschurch) do |user| + user.community.slug == "fallschurch" + end + + $rollout.define_group(:discount) do |user| + if user == nil or user.community == nil + return false + end + user.present? and user.community.present? and user.community.slug == "Vienna" or user.community.slug == "test" + end + + $rollout.activate_group(:facebook_invite, :alpha) + $rollout.activate_group(:good_neighbor_discount, :discount) +end diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb new file mode 100644 index 000000000..c07886e6a --- /dev/null +++ b/config/initializers/secret_token.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +Commonplace::Application.config.secret_token = 'ee09454727e29988fcfc649a77979f0805edff2a7deba07330576e9a313e81ac2d8a11dcc57d05aae830635d18f773fe09d8ccd309824df454881940ef21a6de' diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index 9dede2b9c..325b2a4db 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -1,15 +1,8 @@ # Be sure to restart your server when you modify this file. -# Your secret key for verifying cookie session data integrity. -# If you change this key, all old sessions will become invalid! -# Make sure the secret is at least 30 characters and all random, -# no regular words or you'll be exposed to dictionary attacks. -ActionController::Base.session = { - :key => '_commonplace_session', - :secret => '02b8a3c04f5b1b0f661c653a1c1543b5041f4a77f4998c02048228ef90d213e9cbf4c4f0ffb85d09c3c30f23c5de7557f31b999f83db6a85de612fbfe7a737c7' -} +Commonplace::Application.config.session_store :cookie_store, :key => '_commonplace_session' # Use the database for sessions instead of the cookie-based default, # which shouldn't be used to store highly confidential information -# (create the session table with "rake db:sessions:create") -# ActionController::Base.session_store = :active_record_store +# (create the session table with "rails generate session_migration") +# Commonplace::Application.config.session_store :active_record_store diff --git a/config/initializers/sham_rack_api.rb b/config/initializers/sham_rack_api.rb new file mode 100644 index 000000000..d4b5dfaa9 --- /dev/null +++ b/config/initializers/sham_rack_api.rb @@ -0,0 +1,9 @@ + +ShamRack.at("commonplace.api").rackup do + use(Rack::Cache, + :verbose => true, + :metastore => Dalli::Client.new, + :entitystore => Dalli::Client.new) + run API +end + diff --git a/config/initializers/smtp.rb b/config/initializers/smtp.rb deleted file mode 100644 index 56954ca78..000000000 --- a/config/initializers/smtp.rb +++ /dev/null @@ -1,9 +0,0 @@ -CONFIG ||= Rails.root.join("config", "config.yml").open{ |file| YAML::load(file) } -ActionMailer::Base.smtp_settings = { - :address => "smtp.sendgrid.net", - :port => '25', - :domain => "commmonplace.co", - :authentication => :plain, - :user_name => CONFIG["sendgrid_user_name"], - :password => CONFIG["sendgrid_password"] -} diff --git a/config/initializers/statistics.rb b/config/initializers/statistics.rb new file mode 100644 index 000000000..5711c8024 --- /dev/null +++ b/config/initializers/statistics.rb @@ -0,0 +1,11 @@ +$MixpanelAPIToken = ENV['mixpanel_token'] +$MailgunAPIToken = ENV['mail_api_key'] +$MailgunAPIDomain = ENV['domain'] + +$GoogleAnalyticsAPILogin = ENV['google_analytics_login'] +$GoogleAnalyticsAPIPassword = ENV['google_analytics_password'] +$GoogleAnalyticsPropertyID = ENV['google_analytics_property_id'] || 'UA-12888551-3' + +COMMUNITY_STATISTICS_BUCKET = "statistics:community" +OVERALL_STATISTICS_BUCKET = "statistics:overall" +HISTORICAL_STATISTICS_BUCKET = "statistics:historical" diff --git a/config/initializers/subdomain_fu.rb b/config/initializers/subdomain_fu.rb deleted file mode 100644 index 745738da4..000000000 --- a/config/initializers/subdomain_fu.rb +++ /dev/null @@ -1,6 +0,0 @@ -SubdomainFu.tld_sizes = { - :development => 1, - :test => 0, - :production => 1, - :staging => 1 -} diff --git a/config/initializers/sunspot.rb b/config/initializers/sunspot.rb new file mode 100644 index 000000000..f7103c885 --- /dev/null +++ b/config/initializers/sunspot.rb @@ -0,0 +1,7 @@ + +if Rails.env.development? || Rails.env.test? + Sunspot.session = Sunspot::SessionProxy::SilentFailSessionProxy.new(Sunspot.session) +end + +Sunspot.config.pagination.default_per_page = 25 + diff --git a/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb new file mode 100644 index 000000000..999df2018 --- /dev/null +++ b/config/initializers/wrap_parameters.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. +# +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] +end + +# Disable root element in JSON by default. +ActiveSupport.on_load(:active_record) do + self.include_root_in_json = false +end diff --git a/config/jslint.yml b/config/jslint.yml new file mode 100644 index 000000000..3c051d17d --- /dev/null +++ b/config/jslint.yml @@ -0,0 +1,80 @@ +# ------------ rake task options ------------ + +# JS files to check by default, if no parameters are passed to rake jslint +# (you may want to limit this only to your own scripts and exclude any external scripts and frameworks) + +# this can be overridden by adding 'paths' and 'exclude_paths' parameter to rake command: +# rake jslint paths=path1,path2,... exclude_paths=library1,library2,... + +paths: + - public/javascripts/**/*.js + +exclude_paths: + + +# ------------ jslint options ------------ +# see http://www.jslint.com/lint.html#options for more detailed explanations + +# "enforce" type options (true means potentially more warnings) + +adsafe: false # true if ADsafe rules should be enforced. See http://www.ADsafe.org/ +bitwise: true # true if bitwise operators should not be allowed +newcap: true # true if Initial Caps must be used with constructor functions +eqeqeq: false # true if === should be required (for ALL equality comparisons) +immed: false # true if immediate function invocations must be wrapped in parens +nomen: false # true if initial or trailing underscore in identifiers should be forbidden +onevar: false # true if only one var statement per function should be allowed +plusplus: false # true if ++ and -- should not be allowed +regexp: false # true if . and [^...] should not be allowed in RegExp literals +safe: false # true if the safe subset rules are enforced (used by ADsafe) +strict: false # true if the ES5 "use strict"; pragma is required +undef: false # true if variables must be declared before used +white: false # true if strict whitespace rules apply (see also 'indent' option) + +# "allow" type options (false means potentially more warnings) + +cap: false # true if upper case HTML should be allowed +css: true # true if CSS workarounds should be tolerated +debug: false # true if debugger statements should be allowed (set to false before going into production) +es5: false # true if ECMAScript 5 syntax should be allowed +evil: false # true if eval should be allowed +forin: true # true if unfiltered 'for in' statements should be allowed +fragment: true # true if HTML fragments should be allowed +laxbreak: false # true if statement breaks should not be checked +on: false # true if HTML event handlers (e.g. onclick="...") should be allowed +sub: false # true if subscript notation may be used for expressions better expressed in dot notation + +# other options + +maxlen: 120 # Maximum line length +indent: 2 # Number of spaces that should be used for indentation - used only if 'white' option is set +maxerr: 50 # The maximum number of warnings reported (per file) +passfail: false # true if the scan should stop on first error (per file) +# following are relevant only if undef = true +predef: '' # Names of predefined global variables - comma-separated string or a YAML array +browser: true # true if the standard browser globals should be predefined +rhino: false # true if the Rhino environment globals should be predefined +windows: false # true if Windows-specific globals should be predefined +widget: false # true if the Yahoo Widgets globals should be predefined +devel: true # true if functions like alert, confirm, console, prompt etc. are predefined + + +# ------------ jslint_on_rails custom lint options (switch to true to disable some annoying warnings) ------------ + +# ignores "missing semicolon" warning at the end of a function; this lets you write one-liners +# like: x.map(function(i) { return i + 1 }); without having to put a second semicolon inside the function +lastsemic: false + +# allows you to use the 'new' expression as a statement (without assignment) +# so you can call e.g. new Ajax.Request(...), new Effect.Highlight(...) without assigning to a dummy variable +newstat: false + +# ignores the "Expected an assignment or function call and instead saw an expression" warning, +# if the expression contains a proper statement and makes sense; this lets you write things like: +# element && element.show(); +# valid || other || lastChance || alert('OMG!'); +# selected ? show() : hide(); +# although these will still cause a warning: +# element && link; +# selected ? 5 : 10; +statinexp: true diff --git a/config/locales/college.yml b/config/locales/college.yml new file mode 100644 index 000000000..fdbaf7860 --- /dev/null +++ b/config/locales/college.yml @@ -0,0 +1,2 @@ +college: + hello: "Hello world" diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml new file mode 100644 index 000000000..25022e1c5 --- /dev/null +++ b/config/locales/devise.en.yml @@ -0,0 +1,50 @@ +# Additional translations at http://github.com/plataformatec/devise/wiki/I18n + +en: + errors: + messages: + expired: "has expired, please request a new one" + not_found: "not found" + already_confirmed: "was already confirmed, please try signing in" + not_locked: "was not locked" + not_saved: + one: "1 error prohibited this %{resource} from being saved:" + other: "%{count} errors prohibited this %{resource} from being saved:" + + devise: + failure: + already_authenticated: 'You are already signed in.' + unauthenticated: 'You need to sign in or sign up before continuing.' + unconfirmed: 'You have to confirm your account before continuing.' + locked: 'Your account is locked.' + invalid: 'Invalid email or password.' + invalid_token: 'Invalid authentication token.' + timeout: 'Your session expired, please sign in again to continue.' + inactive: 'Your account was not activated yet.' + sessions: + signed_in: 'Signed in successfully.' + signed_out: 'Signed out successfully.' + passwords: + send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.' + updated: 'Your password was changed successfully. You are now signed in.' + confirmations: + send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.' + confirmed: 'Your account was successfully confirmed. You are now signed in.' + registrations: + signed_up: 'Welcome! You have signed up successfully.' + inactive_signed_up: 'You have signed up successfully. However, we could not sign you in because your account is %{reason}.' + updated: 'You updated your account successfully.' + destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.' + unlocks: + send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.' + unlocked: 'Your account was successfully unlocked. You are now signed in.' + omniauth_callbacks: + success: 'Successfully authorized from %{kind} account.' + failure: 'Could not authorize you from %{kind} because "%{reason}".' + mailer: + confirmation_instructions: + subject: 'Confirmation instructions' + reset_password_instructions: + subject: 'Reset password instructions' + unlock_instructions: + subject: 'Unlock Instructions' diff --git a/config/locales/en.yml b/config/locales/en.yml index 9a4a8f193..a747bfa69 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3,208 +3,3 @@ en: hello: "Hello world" - - authlogic: - error_messages: - no_authentication_details: "Invalid email or password" - - lorem_ipsum: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.... - - feeds: - account: - h1: Welcome! Feeds make it easy for you to send events and announcement to the people who live in Falls Church. But before you can create a feed, you need to register a personal profile on CommonPlace. - - invites: - new: - h1: Finally, invite some people to receive your announcements and events. - h2: Use CommonPlace to send your announcements and events to everyone on the %{community} CommonPlace network. The more email addresses you add, the more widely your announcements and events (and everyone else's!) will be sent. - emails_label: "Invite people to join CommonPlace and subscribe to your Feed:" - new: - link: Create a Community Feed - - h1: Fill out your feed profile so that people on CommonPlace can get in touch with you. - edit: - h1: Edit Your Feed - - profile: - contact_info: - title: Contact Information - recent_posts: - title: Recent Posts - about: - title: About Us - members: - title: Members (%{count}) - post: - title: Post an: - post_tabs: - - Post Announcement - - Post Event - - Import Feed - preview: - name: "Enter your feed's name" - about: "Go ahead and describe your feed. Don't be afraid to use some detail. There's space." - tag_list: "music, politics, food" - website: "http://www.yourfeed.com" - hours: "9:00AM - 1:00PM" - phone: "867-5309" - address: "0 Bleecker Street" - - users: - preview: - about: "Go ahead and describe yourself to your neighbors" - interest_list: "Reading, Community Engagement, Bicycling" - good_list: "Lawnmower, Bikes, Word Processing" - - - posts: - new: - title: Say something to your Neighbors - link: Create a Neighborhood Post - - announcements: - new: - title: Send an announcement to your whole community - link: Send an announcement from your feed - - - events: - new: - link: Create a Community Event - title: Post an event to your community - - invites: - new: - title: Invite Your Neighbors to CommonPlace - link: Invite Your Neighbors to CommonPlace - - communities: - say_something: - title: "Say something to your neighbors:" - community_profiles: - title: "View community profiles:" - whats_happening: - title: "Find out what's happening in town:" - - accounts: - new: - h1: Sign up to receive community announcements from your neighbors and local organizations in %{community}. %{learn_more} - learn_more: Learn More. - bubble_1: Publicize your events to the community - bubble_2: Get crime reports from the police - edit_new: - title: Register a community profile so you
          can send information to your neighbors - edit: - title: Change Settings - learn_more: - title: Join CommonPlace to receive important community announcements
          from your neighbors and local organizations in %{community}. %{sign_up} - sign_up: SIGN ME UP >> - p1: CommonPlace brings your neighborhoods and civic leaders in %{community} together into one "community network" online. - p2: Sign up once and you'll start receiving short email messages from your neighbors and community leaders like the police chief and mayor. - p3: Whenever something's interesting to you, you can hit "reply" and start a community conversation. - p4: All these community conversations, events and announcements can always be viewed at www.%{community}.OurCommonPlace.com - p5: And of course, whenever YOU have a need or announcement, CommonPlace makes it really easy to share it with the people around you. - p6: To post, simply go to: %{community_link} - p7: Or email it in at: %{community_email} - list: - - Ask to borrow a ladder or power drill - - Publicize a tag sale or block party - - Find people and organizations with shared interests or hobbies around you - - Ask about how to fix a pot hole - - Organize a service project for COMMUNITY - - edit_interests: - title: Community leaders use West Roxbury CommonPlace
          to send out information about what's happening in town. - subtitle: "Tell us what interests you in the community:" - starter_interests: - - Patch Local News - - Library - - Police - - Gardening - - Parks - - Indoor Sports - - Local Politics - - School - - Pets - - Arts & Crafts - - Biking - - Running - - Music - - Video Games - - Games - - Outdoor Sports - - - formtastic: - titles: - contact_information: "Contact Information:" - - labels: - user: - full_name: "Full Name:" - email: "Email Address:" - password: "Create a Password:" - about: "Tell your neighbors who you are:" - good_list: "List three goods and/or skills to share:" - interests: "My Interests:" - address: "Street Address*:" - edit: - password: "Change your password:" - receive_posts: Get new neighborhood posts in emails - receive_events_and_announcements: Get new announcements and events in emails - - user_session: - email: "EMAIL" - password: "PASSWORD" - - post: - subject: "Subject" - body: "Post" - - feed: - name: "Feed Name:" - avatar: "Add a photo of your organization:" - about: "Describe your feed:" - phone: "Phone Number:" - address: "Street Address:" - website: "Website:" - feed_url: "Enter an RSS feed and we'll send out announcements when you update it:" - twitter_name: "Enter your Twitter username and we'll syndicate your tweets on CommonPlace" - show: - phone: "Phone:" - website: "Website:" - address: "Address:" - hours: "Hours:" - announcement: - feed: "Send announcement from:" - - post: - subject: Have a need, announcement, offer or event? - body: Share it with your neighbors! - - event: - name: "Event name:" - description: "Event description:" - hints: - user: - address: "*This helps us place you in the correct %{community} neighborhood." - - tooltips: - posts: View announcements, offers, needs, and requests from your neighbors - events: View upcoming events in your community - announcements: View community announcements from organizations in your area - people: Find neighbors and civic leaders in your community - feeds: View Community Announcement Feeds - goods_and_skills: View Community Directory - - syndicate: - events: - title: Upcoming Events - posts: - title: Recent Neighborhood Posts - announcements: - title: Recent Feed Announcements - people: - title: Neighbors - feeds: - title: Community Feeds diff --git a/config/newrelic.yml b/config/newrelic.yml new file mode 100644 index 000000000..2edb100a1 --- /dev/null +++ b/config/newrelic.yml @@ -0,0 +1,24 @@ +production: + error_collector: + capture_source: true + enabled: true + apdex_t: 0.5 + ssl: false + monitor_mode: true + capture_params: true + license_key: <%= ENV["NEW_RELIC_LICENSE_KEY"] %> + developer_mode: false + app_name: <%= ENV["NEW_RELIC_APPNAME"] %> + transaction_tracer: + record_sql: raw + enabled: true + stack_trace_threshold: 0.5 + transaction_threshold: apdex_f + explain_enabled: true + capture_params: false + log_level: info + background: + monitor_mode: true + app_name: 'Background Jobs' + transaction_tracer: + enabled: false diff --git a/config/resque b/config/resque new file mode 100644 index 000000000..952bec887 --- /dev/null +++ b/config/resque @@ -0,0 +1,13 @@ +require 'resque' +require 'resque-exceptional' + +require 'resque/failure/multiple' +require 'resque/failure/redis' + +Resque::Failure::Exceptional.configure do |config| + config.api_key = '0556a141945715c3deb50a0288ec3bea5417f6bf' +end + +Resque::Failure::Multiple.classes = [Resque::Failure::Redis, Resque::Failure::Exceptional] +Resque::Failure.backend = Resque::Failure::Multiple + diff --git a/config/resque.god b/config/resque.god deleted file mode 100644 index 1b639707a..000000000 --- a/config/resque.god +++ /dev/null @@ -1,53 +0,0 @@ -rails_env = ENV['RAILS_ENV'] || "production" -rails_root = ENV['RAILS_ROOT'] || "/home/deploy/commonplace/current" -num_workers = 1 - -num_workers.times do |num| - God.watch do |w| - w.name = "resque-#{num}" - w.group = 'resque-workers' - w.interval = 30.seconds - w.env = {"QUEUE"=>"*", "RAILS_ENV"=>rails_env} - w.start = "cd #{rails_root} && #{BIN_PATH}/rake -f #{rails_root}/Rakefile environment resque:work" - - w.uid = 'deploy' - w.gid = 'deploy' - - # retart if memory gets too high - w.transition(:up, :restart) do |on| - on.condition(:memory_usage) do |c| - c.above = 350.megabytes - c.times = 2 - end - end - - # determine the state on startup - w.transition(:init, { true => :up, false => :start }) do |on| - on.condition(:process_running) do |c| - c.running = true - end - end - - # determine when process has finished starting - w.transition([:start, :restart], :up) do |on| - on.condition(:process_running) do |c| - c.running = true - c.interval = 5.seconds - end - - # failsafe - on.condition(:tries) do |c| - c.times = 5 - c.transition = :start - c.interval = 5.seconds - end - end - - # start if process is not running - w.transition(:up, :start) do |on| - on.condition(:process_running) do |c| - c.running = false - end - end - end -end diff --git a/config/resque_schedule.yml b/config/resque_schedule.yml index 23e9e3662..043fbce60 100644 --- a/config/resque_schedule.yml +++ b/config/resque_schedule.yml @@ -1,19 +1,28 @@ -import_announcements_from_outside_dot_in: - cron: "0 * * * *" - class: AnnouncementImporter - args: - queue: outside-data - description: "This job imports relevant posts from outside.in as Announcements" -import_events_from_eventful: - cron: "0 * * * *" - class: EventfulImporter - args: - queue: outside-data - description: "This job imports events from Eventful into each community as Events" -import_twitter_posts: - cron: "0 * * * *" - class: TwitterImporter - args: - queue: outside-data - description: "This job imports posts from Twitter as TwitterAnnouncements" - \ No newline at end of file +send_daily_digest: + cron: "00 21 * * *" + class: DailyDigestJob + args: + queue: daily_digest + description: "Sends daily digest" + +import_feeds_rss: + cron: "00 * * * *" + class: RSSImporter + args: + queue: rss_importer + description: "Imports feed announcements from rss" + +geolocate_data_points: + cron: "0 0 * * *" + class: AddressGeolocator + args: + queue: geolocation + description: "Geolocates bulk entered data points from organizers" + +generate_statistics: + cron: "00 23 * * *" + class: StatisticsAggregator + args: + queue: statistics + description: "Aggregates community statistics" + diff --git a/config/routes.rb b/config/routes.rb index b3ecccb38..ab3ccfca3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,130 +1,183 @@ -ActionController::Routing::Routes.draw do |map| - map.with_options :conditions => { :subdomain => "admin" } do |admin| - admin.root :controller => "administration", :action => "show" - - admin.resources :addresses, :controller => "administration/addresses" - admin.resources :feeds, :controller => "administration/feeds" - admin.resources :communities, :controller => "administration/communities" +Commonplace::Application.routes.draw do + + # Community specific redirects + match "/corunna" => redirect { "/owossocorunna" } + match "/owosso" => redirect { "/owossocorunna" } + constraints :subdomain => "m" do + match "/:community" => "registrations#mobile_new" + match "/registration/profile" => "registrations#mobile_profile" end - if RAILS_ENV != 'production' - map.resources :deliveries + resource :registration, :only => [:new, :create] do + member do + get :profile, :avatar, :feeds, :groups + put :add_profile, :crop_avatar, :add_feeds, :add_groups + end end - - map.set_neighborhood("set_neighborhood/:neighborhood_id", - :controller => "application", :action => "set_neighborhood", :method => :post) - - map.with_options :conditions => { :subdomain => /[A-Za-z]+/ }, :shallow => true do |community| - community.resources :avatars, :only => [:edit, :update] - - community.root :controller => "communities", :action => "show" - community.resources :posts - community.resources :first_posts, :only => [:create,:new] - - community.resources :announcements, :collection => {"subscribed" => :get} - community.resources :subscriptions, :collection => {"recommended" => :get} - community.resources :replies - - community.resources :tags - - community.resources :events, :collection => {"your" => :get, "suggested" => :get} do |event| - event.resource :attendance - event.resources :referrals - event.resources :messages, :only => [:create, :new], :requirements => {:messagable => "Event"} + resources :feed_registrations, :only => [:new, :create] do + member do + get :profile, :avatar, :subscribers + put :add_profile, :crop_avatar, :invite_subscribers end - - community.resources :users do |user| - user.resource :met, :only => [:create] - user.resources :messages, :only => [:create, :new], :requirements => {:messagable => "User"} + end + + get "facebook_canvas/index" + match "/facebook_canvas/" => "facebook_canvas#index" + + # Global routes + + match "/about" => "site#index", :as => :about + match "/gatekeeper" => "accounts#gatekeeper" + + match 'email_parse/parse' => 'email_parse#parse', :via => :post + match "/admin/overview" => "admin#overview" + match "/admin/export_csv" => "admin#export_csv" + match "/admin/view_messages" => "admin#view_messages" + match "/admin/:community/export_csv" => "admin#export_csv" + match "/admin/overview_no_render" => "admin#overview_no_render" + match "/admin/clipboard" => "admin#clipboard" + match "/admin/show_referrers" => "admin#show_referrers" + match "/admin/map" => "admin#map" + + # Blog and Starter Site routes + resources :requests, :only => [:new, :create] + + match "/facebook_canvas/" => "facebook_canvas#index" + + resource :user_session + + resources :feeds, :only => [:edit, :update, :destroy] do + member do + get :import + get :profile + get :delete + get :edit_owner + put :update_owner + get :avatar + put :crop_avatar end + end + + match "/pages/:id" => "bootstraps#feed" + + match "/messages/:id", :to => "bootstraps#inbox" + match "/inbox", :to => "bootstraps#inbox" - community.resource :invites + resource :account do + member do + get :edit_avatar, :edit_interests, :delete, :profile + put :update_avatar, :update_interests, :settings + post :avatar + end + end + + # jasmine test routes + mount TestTrack::Engine => "test" unless Rails.env.production? || Rails.env.staging? + + begin + ActiveAdmin.routes(self) + devise_for :admin_users, ActiveAdmin::Devise.config - community.resources :feeds, :member => [:profile, :import] do |feed| - feed.resource :subscription, :only => [:index, :show, :create, :destroy] - feed.resource :claim, :member => [:edit_fields] - feed.resources :announcements, :controller => "feeds/announcements" - feed.resources :events, :controller => "feeds/events" - feed.resource :invites, :controller => "feeds/invites" - feed.resources :profile_fields, :collection => {"order" => :put} - feed.resources :messages, :only => [:create, :new], :requirements => {:messagable => "Feed"} + devise_for :users, :controllers => { + :sessions => "user_sessions", + :passwords => "password_resets", + :omniauth_callbacks => "users_omniauth_callbacks" + } do + get '/users/auth/:provider' => 'users_omniauth_callbacks#passthru' end + rescue + Rails.logger.warn "ActiveAdmin routes not initialized" + Rails.logger.warn "Devise routes not initialized" + # ActiveAdmin and Devise try to hit the database on initialization. + # That fails when Heroku is compiling assets, so we catch the error here. + end + + - community.namespace :neighborhood do |neighborhood| - neighborhood.resources :people, :only => :index + authenticated do + + match 'logout' => 'user_sessions#destroy' + + # Invitations + resource :account do + member do + get :facebook_invite + end end + match "/send_invite", :to => "accounts#send_invite" + match "/invite", :to => "accounts#facebook_invite", :as => :invites - map.about 'about', :controller => 'site', :action => 'about' - map.privacy 'privacy', :controller => 'site', :action => 'privacy' - map.terms 'terms', :controller => 'site', :action => 'terms' - map.dmca 'dmca', :controller => 'site', :action => 'dmca' - map.logout 'logout', :controller => 'user_sessions', :action => 'destroy' - map.login 'get-started', :controller => 'user_sessions', :action => 'new' + # Community routes - map.resource :inbox - map.resources :platform_updates - map.resource :user_session - map.resources :password_resets + match "/:community/good_neighbor_discount", :to => "communities#good_neighbor_discount" - map.resource :account, :member => { - :edit_new => :get, - :update_new => :put, - :edit_avatar => :get, - :update_avatar => :put, - :learn_more => :get, - :edit_interests => :get, - :update_interests => :put, - :settings => :put, - :avatar => :post - } - map.resources :mets + + resources :organizer do + collection do + get :map, :app + post :add + end + end + + match '/?community=:community', :to => "bootstraps#community" + + + resources :password_resets + + root :to => "bootstraps#community" + + match "/groups/:slug", :to => "bootstraps#group" end + + scope "/:community" do + match 'about' => 'site#about' + match 'privacy' => 'site#privacy', :as => :privacy + match 'terms' => 'site#terms', :as => :terms + match 'dmca' => 'site#dmca', :as => :dmca + match "faq", :to => "communities#faq", :as => :faq, :via => :get + match "faq", :to => "communities#send_faq", :via => :post + end - - # The priority is based upon order of creation: first created -> highest priority. - # Sample of regular route: - # map.connect 'products/:id', :controller => 'catalog', :action => 'view' - # Keep in mind you can assign values other than :controller and :action - # Sample of named route: - # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase' - # This route can be invoked with purchase_url(:id => product.id) + match "/account/make_focp", :to => "accounts#make_focp" - # Sample resource route (maps HTTP verbs to controller actions automatically): - # map.resources :products - # Sample resource route with options: - # map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get } + # explicitly list paths that we want the main_page js app to handle + ["/posts(/:id)", "/users(/:id)", "/events(/:id)", "/feeds", + "/announcements(/:id)", "/group_posts(/:/id)", "/groups(/:id)", + "/users/:id/messages/new", "/feeds/:id/messages/new", "/new-event", + "/new-group-post", "/new-announcement", "/new-neighborhood-post"].each do |s| + match s, :to => "bootstraps#community", :via => :get, :as => :community + end - # Sample resource route with sub-resources: - # map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller - - # Sample resource route with more complex sub-resources - # map.resources :products do |products| - # products.resources :comments - # products.resources :sales, :collection => { :recent => :get } - # end - - # Sample resource route within a namespace: - # map.namespace :admin do |admin| - # # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb) - # admin.resources :products - # end - - # You can have the root of your site routed with map.root -- just remember to delete public/index.html. - # map.root :controller => "welcome" - - # See how all your routes lay out with "rake routes" - - # Install the default routes as the lowest priority. - # Note: These default routes make all actions in every controller accessible via GET requests. You should - # consider removing or commenting them out if you're using named routes and resources. - map.connect ':controller/:action/:id' - map.connect ':controller/:action/:id.:format' - + + unauthenticated do + + root :to => "site#index" + match "/invite", :to => "accounts#facebook_invite" + match "/:community", :to => "registrations#new", :via => :get + + match "/:community/learn_more", :to => "accounts#learn_more", :via => :get + + + resources :password_resets + + match "/:community/registrations", :via => :post, :to => "registrations#create", :as => "create_registration" + + # Invitations + resource :account do + member do + get :facebook_invite + end + end + + + end + + match '/:nil_community', :to => "bootstraps#community" end diff --git a/config/stale.god b/config/stale.god deleted file mode 100644 index 5f1c22b89..000000000 --- a/config/stale.god +++ /dev/null @@ -1,26 +0,0 @@ -# This will ride alongside god and kill any rogue stale worker -# processes. Their sacrifice is for the greater good. - -WORKER_TIMEOUT = 60 * 10 # 10 minutes - -Thread.new do - loop do - begin - `ps -e -o pid,command | grep [r]esque`.split("\n").each do |line| - parts = line.split(' ') - next if parts[-2] != "at" - started = parts[-1].to_i - elapsed = Time.now - Time.at(started) - - if elapsed >= WORKER_TIMEOUT - ::Process.kill('USR1', parts[0].to_i) - end - end - rescue - # don't die because of stupid exceptions - nil - end - - sleep 30 - end -end diff --git a/config/sunspot.yml b/config/sunspot.yml new file mode 100644 index 000000000..5d7cbf579 --- /dev/null +++ b/config/sunspot.yml @@ -0,0 +1,17 @@ +production: + solr: + hostname: localhost + port: 8983 + log_level: WARNING + +development: + solr: + hostname: localhost + port: 8983 + log_level: INFO + +test: + solr: + hostname: localhost + port: 8981 + log_level: WARNING diff --git a/config/unicorn.god b/config/unicorn.god deleted file mode 100644 index 9338ef803..000000000 --- a/config/unicorn.god +++ /dev/null @@ -1,84 +0,0 @@ -rails_root = '/home/deploy/commonplace/current' -pid_dir = '/home/deploy/commonplace/shared/pids' -God.watch do |w| - w.name = "commonplace_unicorn" - w.interval = 30.seconds # default - - # unicorn needs to be run from the rails root - w.start = "cd #{rails_root} && #{BIN_PATH}/unicorn_rails -c #{rails_root}/config/unicorn.rb -E #{RAILS_ENV} -D" - - # QUIT gracefully shuts down workers - w.stop = "kill -QUIT `cat #{pid_dir}/unicorn.pid`" - - # USR2 causes the master to re-create itself and spawn a new worker pool - w.restart = "kill -USR2 `cat #{pid_dir}/unicorn.pid`" - - w.start_grace = 10.seconds - w.restart_grace = 10.seconds - w.pid_file = "#{pid_dir}/unicorn.pid" - - w.uid = 'deploy' - w.gid = 'deploy' - - w.behavior(:clean_pid_file) - - w.start_if do |start| - start.condition(:process_running) do |c| - c.interval = 5.seconds - c.running = false - end - end - - w.restart_if do |restart| - restart.condition(:memory_usage) do |c| - c.above = 300.megabytes - c.times = [3, 5] # 3 out of 5 intervals - end - - restart.condition(:cpu_usage) do |c| - c.above = 50.percent - c.times = 5 - end - end - - # lifecycle - w.lifecycle do |on| - on.condition(:flapping) do |c| - c.to_state = [:start, :restart] - c.times = 5 - c.within = 5.minute - c.transition = :unmonitored - c.retry_in = 10.minutes - c.retry_times = 5 - c.retry_within = 2.hours - end - end -end - -unicorn_worker_memory_limit = 300_000 - -Thread.new do - loop do - begin - # unicorn workers - # - # ps output line format: - # 31580 275444 unicorn_rails worker[15] -c /data/github/current/config/unicorn.rb -E production -D - # pid ram command - - lines = `ps -e -www -o pid,rss,command | grep '[u]nicorn_rails worker'`.split("\n") - lines.each do |line| - parts = line.split(' ') - if parts[1].to_i > unicorn_worker_memory_limit - # tell the worker to die after it finishes serving its request - ::Process.kill('QUIT', parts[0].to_i) - end - end - rescue Object - # don't die ever once we've tested this - nil - end - - sleep 30 - end -end diff --git a/db/messages.yml b/db/messages.yml new file mode 100644 index 000000000..8c46f218b --- /dev/null +++ b/db/messages.yml @@ -0,0 +1,79 @@ +--- +- !ruby/object:Message + attributes: + created_at: 2011-02-02 03:34:17.133337 + body: You should end up in your inbox if you click reply. + messagable_type: User + updated_at: 2011-02-02 03:34:17.133337 + archived: + messagable_id: "457" + subject: Inboxes? + id: "73" + user_id: "462" + attributes_cache: {} + +- !ruby/object:Message + attributes: + created_at: 2011-02-02 03:36:19.565916 + body: It didn't -- normal message back thing + messagable_type: User + updated_at: 2011-02-02 03:36:19.565916 + archived: + messagable_id: "462" + subject: Ack + id: "74" + user_id: "439" + attributes_cache: {} + +- !ruby/object:Message + attributes: + created_at: 2011-02-17 17:36:51.066038 + body: Hi + messagable_type: User + updated_at: 2011-02-17 17:36:51.066038 + archived: + messagable_id: "467" + subject: Test Message + id: "75" + user_id: "470" + attributes_cache: {} + +- !ruby/object:Message + attributes: + created_at: 2011-02-17 17:40:39.072419 + body: Should be dancin + messagable_type: User + updated_at: 2011-02-17 17:40:39.072419 + archived: + messagable_id: "467" + subject: You + id: "76" + user_id: "470" + attributes_cache: {} + +- !ruby/object:Message + attributes: + created_at: 2011-02-17 17:40:49.61594 + body: Should be dancin + messagable_type: User + updated_at: 2011-02-17 17:40:49.61594 + archived: + messagable_id: "467" + subject: You + id: "77" + user_id: "470" + attributes_cache: {} + +- !ruby/object:Message + attributes: + created_at: 2011-02-17 17:43:56.433009 + body: Aas + messagable_type: User + updated_at: 2011-02-17 17:43:56.433009 + archived: + messagable_id: "467" + subject: Test + id: "78" + user_id: "470" + attributes_cache: {} + diff --git a/db/migrate/20110118071451_add_oauth2_fields_to_users.rb b/db/migrate/20110118071451_add_oauth2_fields_to_users.rb new file mode 100644 index 000000000..0c2aed79c --- /dev/null +++ b/db/migrate/20110118071451_add_oauth2_fields_to_users.rb @@ -0,0 +1,11 @@ +class AddOauth2FieldsToUsers < ActiveRecord::Migration + def self.up + add_column :users, :oauth2_token, :string + add_index :users, :oauth2_token + change_column :users, :persistence_token, :string, :null => true + end + + def self.down + remove_column :users, :oauth2_token + end +end diff --git a/db/migrate/20110118080708_add_community_to_users.rb b/db/migrate/20110118080708_add_community_to_users.rb new file mode 100644 index 000000000..27f4b89b3 --- /dev/null +++ b/db/migrate/20110118080708_add_community_to_users.rb @@ -0,0 +1,14 @@ +class AddCommunityToUsers < ActiveRecord::Migration + def self.up + add_column :users, :community_id, :integer + User.reset_column_information + User.find_each do |user| + user.community_id = Neighborhood.find(user.neighborhood_id).community_id + user.save! + end + end + + def self.down + remove_column :users, :community_id + end +end diff --git a/db/migrate/20110118164820_add_venue_to_events.rb b/db/migrate/20110118164820_add_venue_to_events.rb new file mode 100644 index 000000000..d65595658 --- /dev/null +++ b/db/migrate/20110118164820_add_venue_to_events.rb @@ -0,0 +1,9 @@ +class AddVenueToEvents < ActiveRecord::Migration + def self.up + add_column :events, :venue, :string + end + + def self.down + remove_column :events, :venue + end +end diff --git a/db/migrate/20110121041244_add_meetup_events.rb b/db/migrate/20110121041244_add_meetup_events.rb new file mode 100644 index 000000000..1f5908959 --- /dev/null +++ b/db/migrate/20110121041244_add_meetup_events.rb @@ -0,0 +1,11 @@ +class AddMeetupEvents < ActiveRecord::Migration + def self.up + add_column :events, :type, :string + add_column :events, :host_group_name, :string + end + + def self.down + remove_column :events, :type + remove_column :events, :host_group_name + end +end diff --git a/db/migrate/20110123230935_make_post_area_polymorphic.rb b/db/migrate/20110123230935_make_post_area_polymorphic.rb new file mode 100644 index 000000000..0df806b15 --- /dev/null +++ b/db/migrate/20110123230935_make_post_area_polymorphic.rb @@ -0,0 +1,21 @@ +class MakePostAreaPolymorphic < ActiveRecord::Migration + def self.up + rename_column :posts, :neighborhood_id, :area_id + add_column :posts, :area_type, :string + + Post.reset_column_information + + Post.find_each do |post| + post.area_type = "Neighborhood" + post.save! + end + end + + def self.down + Post.find_each do |post| + if !post.area.is_a? Neigbhorhood + raise "Post has an area that is not a Neigbhorhood" + end + end + end +end diff --git a/db/migrate/20110125193859_change_twitter_announcement_to_digest.rb b/db/migrate/20110125193859_change_twitter_announcement_to_digest.rb new file mode 100644 index 000000000..2cb49b0d5 --- /dev/null +++ b/db/migrate/20110125193859_change_twitter_announcement_to_digest.rb @@ -0,0 +1,16 @@ +class ChangeTwitterAnnouncementToDigest < ActiveRecord::Migration + def self.up + create_table "tweets", :force => true do |t| + t.string "screen_name", :null => false + t.text "body", :null => false + t.string "url", :null => false + t.integer "twitter_announcement_id", :null => false + t.datetime "created_at" + t.datetime "updated_at" + end + end + + def self.down + drop_table :tweets + end +end diff --git a/db/migrate/20110131072028_remove_avatars.rb b/db/migrate/20110131072028_remove_avatars.rb new file mode 100644 index 000000000..00707e417 --- /dev/null +++ b/db/migrate/20110131072028_remove_avatars.rb @@ -0,0 +1,9 @@ +class RemoveAvatars < ActiveRecord::Migration + def self.up + drop_table :avatars + end + + def self.down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/migrate/20110131072200_remove_locations.rb b/db/migrate/20110131072200_remove_locations.rb new file mode 100644 index 000000000..de6c8c9f8 --- /dev/null +++ b/db/migrate/20110131072200_remove_locations.rb @@ -0,0 +1,9 @@ +class RemoveLocations < ActiveRecord::Migration + def self.up + drop_table :locations + end + + def self.down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/migrate/20110201222144_messages_belong_to_messagable.rb b/db/migrate/20110201222144_messages_belong_to_messagable.rb new file mode 100644 index 000000000..98869c27f --- /dev/null +++ b/db/migrate/20110201222144_messages_belong_to_messagable.rb @@ -0,0 +1,22 @@ +class MessagesBelongToMessagable < ActiveRecord::Migration + def self.up + rename_column :messages, :recipient_id, :messagable_id + add_column :messages, :messagable_type, :string + + Message.reset_column_information + + Message.find_each do |message| + message.messagable_type = "User" + message.save! + end + end + + def self.down + if Message.exists?(:conditions => ["messagable_type != 'User'"]) + raise "Message exists with non-User messagable_type" + end + + rename_column :messages, :messagable_id, :recipient_id + remove_column :messages, :messagable_type + end +end diff --git a/db/migrate/20110208185254_remove_unused_tables.rb b/db/migrate/20110208185254_remove_unused_tables.rb new file mode 100644 index 000000000..5cf0939be --- /dev/null +++ b/db/migrate/20110208185254_remove_unused_tables.rb @@ -0,0 +1,12 @@ +class RemoveUnusedTables < ActiveRecord::Migration + def self.up + drop_table :addresses + drop_table :notifications + drop_table :profile_fields + drop_table :slugs + end + + def self.down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/migrate/20110218063923_add_news_to_feed.rb b/db/migrate/20110218063923_add_news_to_feed.rb new file mode 100644 index 000000000..d4d1b53da --- /dev/null +++ b/db/migrate/20110218063923_add_news_to_feed.rb @@ -0,0 +1,9 @@ +class AddNewsToFeed < ActiveRecord::Migration + def self.up + add_column :feeds, :is_news, :boolean + end + + def self.down + remove_column :feeds, :is_news + end +end diff --git a/db/migrate/20110218231034_add_archivable_to_messages.rb b/db/migrate/20110218231034_add_archivable_to_messages.rb new file mode 100644 index 000000000..8ed37f1c1 --- /dev/null +++ b/db/migrate/20110218231034_add_archivable_to_messages.rb @@ -0,0 +1,9 @@ +class AddArchivableToMessages < ActiveRecord::Migration + def self.up + add_column :messages, :archived, :boolean + end + + def self.down + remove_column :messages, :archived + end +end diff --git a/db/migrate/20110222054253_add_published_status_to_posts.rb b/db/migrate/20110222054253_add_published_status_to_posts.rb new file mode 100644 index 000000000..263b12ca5 --- /dev/null +++ b/db/migrate/20110222054253_add_published_status_to_posts.rb @@ -0,0 +1,9 @@ +class AddPublishedStatusToPosts < ActiveRecord::Migration + def self.up + add_column :posts, :published, :boolean, :default => true + end + + def self.down + remove_column :posts, :published + end +end diff --git a/db/migrate/20110301155223_add_organizer_fields_to_communities.rb b/db/migrate/20110301155223_add_organizer_fields_to_communities.rb new file mode 100644 index 000000000..3e2903a85 --- /dev/null +++ b/db/migrate/20110301155223_add_organizer_fields_to_communities.rb @@ -0,0 +1,16 @@ +class AddOrganizerFieldsToCommunities < ActiveRecord::Migration + def self.up + add_column :communities, :organizer_email, :string + add_column :communities, :organizer_name, :string + add_column :communities, :organizer_avatar_file_name, :string + add_column :communities, :organizer_about, :text + end + + def self.down + remove_column :communities, :organizer_email + remove_column :communities, :organizer_name + remove_column :communities, :organizer_avatar_file_name + remove_column :communities, :organizer_about + end + +end diff --git a/db/migrate/20110301222336_create_groups.rb b/db/migrate/20110301222336_create_groups.rb new file mode 100644 index 000000000..a8505e561 --- /dev/null +++ b/db/migrate/20110301222336_create_groups.rb @@ -0,0 +1,16 @@ +class CreateGroups < ActiveRecord::Migration + def self.up + create_table :groups do |t| + t.string :name + t.string :slug + t.string :about + t.integer :community_id + t.string :avatar_file_name + t.timestamps + end + end + + def self.down + drop_table :groups + end +end diff --git a/db/migrate/20110302001343_create_memberships.rb b/db/migrate/20110302001343_create_memberships.rb new file mode 100644 index 000000000..5c0958664 --- /dev/null +++ b/db/migrate/20110302001343_create_memberships.rb @@ -0,0 +1,14 @@ +class CreateMemberships < ActiveRecord::Migration + def self.up + create_table :memberships do |t| + t.integer :user_id + t.integer :group_id + + t.timestamps + end + end + + def self.down + drop_table :memberships + end +end diff --git a/db/migrate/20110303021537_posts_belong_to_communities_not_neighborhoods.rb b/db/migrate/20110303021537_posts_belong_to_communities_not_neighborhoods.rb new file mode 100644 index 000000000..c187b5743 --- /dev/null +++ b/db/migrate/20110303021537_posts_belong_to_communities_not_neighborhoods.rb @@ -0,0 +1,38 @@ +class PostsBelongToCommunitiesNotNeighborhoods < ActiveRecord::Migration + def self.up + add_column :posts, :community_id, :integer + + Post.reset_column_information + + Post.find_each do |post| + case post.area_type + when "Neighborhood" + post.community_id = Neighborhood.find(post.area_id).community_id + when "Community" + post.community_id = post.area_id + else + raise "Unknown area_type" + end + + post.save! + end + + remove_column :posts, :area_id + remove_column :posts, :area_type + end + + def self.down + add_column :posts, :area_id, :integer + add_column :posts, :area_type, :string + + Post.reset_column_information + + Post.find_each do |post| + post.area_type = "Neighborhood" + post.area_id = post.user.neighborhood.id + post.save! + end + + remove_column :posts, :community_id + end +end diff --git a/db/migrate/20110303072220_dont_use_acts_as_taggable_on_for_user_tag_fields.rb b/db/migrate/20110303072220_dont_use_acts_as_taggable_on_for_user_tag_fields.rb new file mode 100644 index 000000000..260b5462e --- /dev/null +++ b/db/migrate/20110303072220_dont_use_acts_as_taggable_on_for_user_tag_fields.rb @@ -0,0 +1,15 @@ +class DontUseActsAsTaggableOnForUserTagFields < ActiveRecord::Migration + def self.up + rename_column :users, :cached_interest_list, :interest_list + + rename_column :users, :cached_good_list, :offer_list + remove_column :users, :cached_skill_list + end + + def self.down + rename_column :users, :interest_list, :cached_interest_list + + rename_column :users, :offer_list, :cached_good_list + add_column :users, :cached_skill_list, :string + end +end diff --git a/db/migrate/20110303232552_add_send_to_community_flag_to_posts.rb b/db/migrate/20110303232552_add_send_to_community_flag_to_posts.rb new file mode 100644 index 000000000..8d39b2db0 --- /dev/null +++ b/db/migrate/20110303232552_add_send_to_community_flag_to_posts.rb @@ -0,0 +1,9 @@ +class AddSendToCommunityFlagToPosts < ActiveRecord::Migration + def self.up + add_column :posts, :sent_to_community, :boolean + end + + def self.down + remove_column :posts, :sent_to_community + end +end diff --git a/db/migrate/20110309192552_add_community_id_to_events_and_announcements.rb b/db/migrate/20110309192552_add_community_id_to_events_and_announcements.rb new file mode 100644 index 000000000..dd43da694 --- /dev/null +++ b/db/migrate/20110309192552_add_community_id_to_events_and_announcements.rb @@ -0,0 +1,25 @@ +class AddCommunityIdToEventsAndAnnouncements < ActiveRecord::Migration + def self.up + add_column :events, :community_id, :integer + add_column :announcements, :community_id, :integer + + Event.reset_column_information + Announcement.reset_column_information + + Event.find_each do |event| + event.community_id = event.owner.community_id + event.save! + end + + Announcement.find_each do |announcement| + announcement.community_id = announcement.owner.community_id + announcement.save! + end + + end + + def self.down + remove_column :events, :community_id + remove_column :announcements, :community_id + end +end diff --git a/db/migrate/20110324011132_add_receive_weekly_bulletin_flag_to_users.rb b/db/migrate/20110324011132_add_receive_weekly_bulletin_flag_to_users.rb new file mode 100644 index 000000000..8d910c80a --- /dev/null +++ b/db/migrate/20110324011132_add_receive_weekly_bulletin_flag_to_users.rb @@ -0,0 +1,16 @@ +class AddReceiveWeeklyBulletinFlagToUsers < ActiveRecord::Migration + def self.up + add_column :users, :receive_weekly_digest, :boolean, :default => true + User.reset_column_information + User.find_each do |user| + if !user.receive_posts && !user.receive_events_and_announcements + user.receive_weekly_digest = false + user.save! + end + end + end + + def self.down + remove_column :users, :receive_weekly_digest + end +end diff --git a/db/migrate/20110324140903_create_requests.rb b/db/migrate/20110324140903_create_requests.rb new file mode 100644 index 000000000..0c22c4055 --- /dev/null +++ b/db/migrate/20110324140903_create_requests.rb @@ -0,0 +1,15 @@ +class CreateRequests < ActiveRecord::Migration + def self.up + create_table :requests do |t| + t.string :community_name + t.string :name + t.string :email + t.string :sponsor_organization + t.timestamps + end + end + + def self.down + drop_table :requests + end +end diff --git a/db/migrate/20110331174735_user_announcements.rb b/db/migrate/20110331174735_user_announcements.rb new file mode 100644 index 000000000..d60e0011e --- /dev/null +++ b/db/migrate/20110331174735_user_announcements.rb @@ -0,0 +1,33 @@ +class UserAnnouncements < ActiveRecord::Migration + def self.up + add_column :announcements, :owner_type, :string + add_column :announcements, :owner_id, :integer + + Announcement.reset_column_information + + Announcement.find_each do |announcement| + announcement.owner_type = "Feed" + announcement.owner_id = announcement.feed_id + announcement.save! + end + + remove_column :announcements, :feed_id + end + + def self.down + add_column :announcements, :feed_id + + Announcement.reset_column_information + + Announcement.find_each do |announcement| + if announcement.feed_type != "Feed" + raise "Announcement not belonging to a Feed not allowed" + end + announcement.feed_id = announcement.owner_id + announcement.save! + end + + remove_column :announcements, :owner_type + remove_column :announcements, :owner_id + end +end diff --git a/db/migrate/20110331213831_rework_email_options.rb b/db/migrate/20110331213831_rework_email_options.rb new file mode 100644 index 000000000..be6681da3 --- /dev/null +++ b/db/migrate/20110331213831_rework_email_options.rb @@ -0,0 +1,38 @@ +class ReworkEmailOptions < ActiveRecord::Migration + def self.up + add_column :users, :post_receive_method, :string + + User.reset_column_information + + User.find_each do |user| + user.post_receive_method = + if user.receive_digests + user.post_receive_method = "Daily" + elsif user.receive_posts + user.post_receive_method = "Live" + else + "Never" + end + user.save! + end + + remove_column :users, :receive_digests + remove_column :users, :receive_posts + end + + def self.down + add_column :users, :receive_digest, :boolean + add_column :users, :receive_posts, :boolean + + User.reset_column_information + + User.find_each do |user| + user.receive_digests = + user.post_receive_method == "Daily" + user.receive_posts = user.post_receive_method == "Live" + user.save! + end + + remove_column :users, :post_receive_method + end +end diff --git a/db/migrate/20110402033009_create_group_posts.rb b/db/migrate/20110402033009_create_group_posts.rb new file mode 100644 index 000000000..658fb3d89 --- /dev/null +++ b/db/migrate/20110402033009_create_group_posts.rb @@ -0,0 +1,16 @@ +class CreateGroupPosts < ActiveRecord::Migration + def self.up + create_table :group_posts do |t| + t.string :subject + t.string :body + t.integer :user_id + t.integer :group_id + + t.timestamps + end + end + + def self.down + drop_table :group_posts + end +end diff --git a/db/migrate/20110402060302_create_internships.rb b/db/migrate/20110402060302_create_internships.rb new file mode 100644 index 000000000..64cc0d8b0 --- /dev/null +++ b/db/migrate/20110402060302_create_internships.rb @@ -0,0 +1,18 @@ +class CreateInternships < ActiveRecord::Migration + def self.up + create_table :internships do |t| + t.string :name + t.string :email + t.string :phone_number + t.string :college + t.integer :graduation_year + t.text :essay + + t.timestamps + end + end + + def self.down + drop_table :internships + end +end diff --git a/db/migrate/20110405002246_add_type_to_feed.rb b/db/migrate/20110405002246_add_type_to_feed.rb new file mode 100644 index 000000000..1a3fbcac9 --- /dev/null +++ b/db/migrate/20110405002246_add_type_to_feed.rb @@ -0,0 +1,9 @@ +class AddTypeToFeed < ActiveRecord::Migration + def self.up + add_column :feeds, :kind, :integer + end + + def self.down + remove_column :feeds, :kind + end +end diff --git a/db/migrate/20110405005451_remove_news_flag_from_feed.rb b/db/migrate/20110405005451_remove_news_flag_from_feed.rb new file mode 100644 index 000000000..d32913371 --- /dev/null +++ b/db/migrate/20110405005451_remove_news_flag_from_feed.rb @@ -0,0 +1,9 @@ +class RemoveNewsFlagFromFeed < ActiveRecord::Migration + def self.up + remove_column :feeds, :is_news + end + + def self.down + add_column :feeds, :is_news, :boolean + end +end diff --git a/db/migrate/20110405051232_group_post_body_is_text.rb b/db/migrate/20110405051232_group_post_body_is_text.rb new file mode 100644 index 000000000..d332228e7 --- /dev/null +++ b/db/migrate/20110405051232_group_post_body_is_text.rb @@ -0,0 +1,9 @@ +class GroupPostBodyIsText < ActiveRecord::Migration + def self.up + change_column :group_posts, :body, :text + end + + def self.down + change_column :group_posts, :body, :string + end +end diff --git a/db/migrate/20110406024735_add_tweet_id_to_announcements.rb b/db/migrate/20110406024735_add_tweet_id_to_announcements.rb new file mode 100644 index 000000000..8696491e3 --- /dev/null +++ b/db/migrate/20110406024735_add_tweet_id_to_announcements.rb @@ -0,0 +1,9 @@ +class AddTweetIdToAnnouncements < ActiveRecord::Migration + def self.up + add_column :announcements, :tweet_id, :string + end + + def self.down + remove_column :announcements, :tweet_id + end +end diff --git a/db/migrate/20110406204407_set_default_post_receive_method.rb b/db/migrate/20110406204407_set_default_post_receive_method.rb new file mode 100644 index 000000000..3618cc6bb --- /dev/null +++ b/db/migrate/20110406204407_set_default_post_receive_method.rb @@ -0,0 +1,9 @@ +class SetDefaultPostReceiveMethod < ActiveRecord::Migration + def self.up + change_column :users, :post_receive_method, :string, :default => "Live" + end + + def self.down + change_column :users, :post_receive_method, :string, :default => "Daily" + end +end diff --git a/db/migrate/20110409180957_add_middle_name_to_user.rb b/db/migrate/20110409180957_add_middle_name_to_user.rb new file mode 100644 index 000000000..3c209310f --- /dev/null +++ b/db/migrate/20110409180957_add_middle_name_to_user.rb @@ -0,0 +1,18 @@ +class AddMiddleNameToUser < ActiveRecord::Migration + def self.up + add_column :users, :middle_name, :string + + User.reset_column_information + + User.find(:all).each do |user| + if user.last_name.include? " " + user.full_name = user.first_name.to_s + " " + user.last_name.to_s + user.save + end + end + end + + def self.down + remove_column :users, :middle_name + end +end diff --git a/db/migrate/20110419000032_add_receive_method_to_subscriptions.rb b/db/migrate/20110419000032_add_receive_method_to_subscriptions.rb new file mode 100644 index 000000000..ebf45aaf0 --- /dev/null +++ b/db/migrate/20110419000032_add_receive_method_to_subscriptions.rb @@ -0,0 +1,17 @@ +class AddReceiveMethodToSubscriptions < ActiveRecord::Migration + def self.up + add_column :subscriptions, :receive_method, :string, :default => "Daily" + + Subscription.reset_column_information + + Subscription.find_each do |s| + s.receive_method = "Daily" + s.save! + end + + end + + def self.down + remove_column :subscriptions, :receive_method + end +end diff --git a/db/migrate/20110426205322_add_receive_method_to_memberships.rb b/db/migrate/20110426205322_add_receive_method_to_memberships.rb new file mode 100644 index 000000000..59d628412 --- /dev/null +++ b/db/migrate/20110426205322_add_receive_method_to_memberships.rb @@ -0,0 +1,17 @@ +class AddReceiveMethodToMemberships < ActiveRecord::Migration + def self.up + add_column :memberships, :receive_method, :string, :default => "Live" + + Membership.reset_column_information + + Membership.find_each do |membership| + membership.receive_method = "Live" + membership.save! + end + + end + + def self.down + remove_column :memberships, :receive_method + end +end diff --git a/db/migrate/20110510034428_add_timezone_to_community.rb b/db/migrate/20110510034428_add_timezone_to_community.rb new file mode 100644 index 000000000..6bff037c8 --- /dev/null +++ b/db/migrate/20110510034428_add_timezone_to_community.rb @@ -0,0 +1,16 @@ +class AddTimezoneToCommunity < ActiveRecord::Migration + def self.up + add_column :communities, :time_zone, :string, :default => "Eastern Time (US & Canada)" + + Community.reset_column_information + + Community.find_each do |community| + community.time_zone = "Eastern Time (US & Canada)" + community.save! + end + end + + def self.down + remove_column :communities, :time_zone + end +end diff --git a/db/migrate/20110510194302_add_latitude_and_longitude_to_neighborhoods.rb b/db/migrate/20110510194302_add_latitude_and_longitude_to_neighborhoods.rb new file mode 100644 index 000000000..de9c5c869 --- /dev/null +++ b/db/migrate/20110510194302_add_latitude_and_longitude_to_neighborhoods.rb @@ -0,0 +1,11 @@ +class AddLatitudeAndLongitudeToNeighborhoods < ActiveRecord::Migration + def self.up + add_column :neighborhoods, :latitude, :decimal + add_column :neighborhoods, :longitude, :decimal + end + + def self.down + remove_column :neighborhoods, :latitude + remove_column :neighborhoods, :longitude + end +end diff --git a/db/migrate/20110510204754_add_latitude_and_longitude_to_users.rb b/db/migrate/20110510204754_add_latitude_and_longitude_to_users.rb new file mode 100644 index 000000000..2afd2ee72 --- /dev/null +++ b/db/migrate/20110510204754_add_latitude_and_longitude_to_users.rb @@ -0,0 +1,11 @@ +class AddLatitudeAndLongitudeToUsers < ActiveRecord::Migration + def self.up + add_column :users, :latitude, :decimal + add_column :users, :longitude, :decimal + end + + def self.down + remove_column :users, :latitude + remove_column :users, :longitude + end +end diff --git a/db/migrate/20110513171458_set_neighborhood_lat_and_lng_from_bounds.rb b/db/migrate/20110513171458_set_neighborhood_lat_and_lng_from_bounds.rb new file mode 100644 index 000000000..a76a783fe --- /dev/null +++ b/db/migrate/20110513171458_set_neighborhood_lat_and_lng_from_bounds.rb @@ -0,0 +1,19 @@ +class SetNeighborhoodLatAndLngFromBounds < ActiveRecord::Migration + def self.up + Neighborhood.find_each do |neighborhood| + unless neighborhood.latitude + lat_lng_sum = neighborhood.bounds.inject do |lat_lng_sum, lat_lng| + [lat_lng_sum.first + lat_lng.first, lat_lng_sum.last + lat_lng.last] + end + + neighborhood.latitude = lat_lng_sum.first / neighborhood.bounds.count + neighborhood.longitude = lat_lng_sum.last / neighborhood.bounds.count + + neighborhood.save! + end + end + end + + def self.down + end +end diff --git a/db/migrate/20110515163926_add_referral_source_to_users.rb b/db/migrate/20110515163926_add_referral_source_to_users.rb new file mode 100644 index 000000000..22088aeaf --- /dev/null +++ b/db/migrate/20110515163926_add_referral_source_to_users.rb @@ -0,0 +1,9 @@ +class AddReferralSourceToUsers < ActiveRecord::Migration + def self.up + add_column :users, :referral_source, :string + end + + def self.down + remove_column :users, :referral_source + end +end diff --git a/db/migrate/20110603202149_add_last_login_tracker.rb b/db/migrate/20110603202149_add_last_login_tracker.rb new file mode 100644 index 000000000..f59de34bd --- /dev/null +++ b/db/migrate/20110603202149_add_last_login_tracker.rb @@ -0,0 +1,9 @@ +class AddLastLoginTracker < ActiveRecord::Migration + def self.up + add_column :users, :last_login_at, :datetime + end + + def self.down + remove_column :users, :last_login_at + end +end diff --git a/db/migrate/20110603203135_add_household_count_to_communities.rb b/db/migrate/20110603203135_add_household_count_to_communities.rb new file mode 100644 index 000000000..1cb02a9e4 --- /dev/null +++ b/db/migrate/20110603203135_add_household_count_to_communities.rb @@ -0,0 +1,9 @@ +class AddHouseholdCountToCommunities < ActiveRecord::Migration + def self.up + add_column :communities, :households, :integer, :default => 0 + end + + def self.down + remove_column :communities, :households + end +end diff --git a/db/migrate/20110605115152_add_seen_tour_flag_to_users.rb b/db/migrate/20110605115152_add_seen_tour_flag_to_users.rb new file mode 100644 index 000000000..70acea319 --- /dev/null +++ b/db/migrate/20110605115152_add_seen_tour_flag_to_users.rb @@ -0,0 +1,20 @@ +class AddSeenTourFlagToUsers < ActiveRecord::Migration + def self.up + add_column :users, :seen_tour, :boolean + + User.reset_column_information + + User.find_each do |user| + # if the user has posted, seen_tour is true + + user.toggle!(:seen_tour) if [user.posts, user.events, + user.announcements, user.group_posts, + user.replies].any?(&:any?) + end + + end + + def self.down + remove_column :users, :seen_tour + end +end diff --git a/db/migrate/20110605145804_add_core_community_flag_to_communities.rb b/db/migrate/20110605145804_add_core_community_flag_to_communities.rb new file mode 100644 index 000000000..4bab626e9 --- /dev/null +++ b/db/migrate/20110605145804_add_core_community_flag_to_communities.rb @@ -0,0 +1,9 @@ +class AddCoreCommunityFlagToCommunities < ActiveRecord::Migration + def self.up + add_column :communities, :core, :boolean + end + + def self.down + remove_column :communities, :core + end +end diff --git a/db/migrate/20110619194212_create_admin_notes.rb b/db/migrate/20110619194212_create_admin_notes.rb new file mode 100644 index 000000000..a2d3247ee --- /dev/null +++ b/db/migrate/20110619194212_create_admin_notes.rb @@ -0,0 +1,16 @@ +class CreateAdminNotes < ActiveRecord::Migration + def self.up + create_table :admin_notes do |t| + t.references :resource, :polymorphic => true, :null => false + t.references :admin_user, :polymorphic => true + t.text :body + t.timestamps + end + add_index :admin_notes, [:resource_type, :resource_id] + add_index :admin_notes, [:admin_user_type, :admin_user_id] + end + + def self.down + drop_table :admin_notes + end +end diff --git a/db/migrate/20110619194213_move_admin_notes_to_comments.rb b/db/migrate/20110619194213_move_admin_notes_to_comments.rb new file mode 100644 index 000000000..8e37ec46e --- /dev/null +++ b/db/migrate/20110619194213_move_admin_notes_to_comments.rb @@ -0,0 +1,25 @@ +class MoveAdminNotesToComments < ActiveRecord::Migration + def self.up + remove_index :admin_notes, [:admin_user_type, :admin_user_id] + rename_table :admin_notes, :active_admin_comments + rename_column :active_admin_comments, :admin_user_type, :author_type + rename_column :active_admin_comments, :admin_user_id, :author_id + add_column :active_admin_comments, :namespace, :string + add_index :active_admin_comments, [:namespace] + add_index :active_admin_comments, [:author_type, :author_id] + + # Update all the existing comments to the default namespace + say "Updating any existing comments to the #{ActiveAdmin.default_namespace} namespace." + execute "UPDATE active_admin_comments SET namespace='#{ActiveAdmin.default_namespace}'" + end + + def self.down + remove_index :active_admin_comments, :column => [:author_type, :author_id] + remove_index :active_admin_comments, :column => [:namespace] + remove_column :active_admin_comments, :namespace + rename_column :active_admin_comments, :author_id, :admin_user_id + rename_column :active_admin_comments, :author_type, :admin_user_type + rename_table :active_admin_comments, :admin_notes + add_index :admin_notes, [:admin_user_type, :admin_user_id] + end +end diff --git a/db/migrate/20110619234212_devise_create_admin_users.rb b/db/migrate/20110619234212_devise_create_admin_users.rb new file mode 100644 index 000000000..9074d3c7c --- /dev/null +++ b/db/migrate/20110619234212_devise_create_admin_users.rb @@ -0,0 +1,31 @@ +class DeviseCreateAdminUsers < ActiveRecord::Migration + def self.up + create_table(:admin_users) do |t| + t.database_authenticatable :null => false + t.recoverable + t.rememberable + t.trackable + + # t.encryptable + # t.confirmable + # t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both + # t.token_authenticatable + + + t.timestamps + end + + # Create a default user + AdminUser.create!(:email => 'admin@example.com', :password => 'password', :password_confirmation => 'password') + + add_index :admin_users, :email, :unique => true + add_index :admin_users, :reset_password_token, :unique => true + # add_index :admin_users, :confirmation_token, :unique => true + # add_index :admin_users, :unlock_token, :unique => true + # add_index :admin_users, :authentication_token, :unique => true + end + + def self.down + drop_table :admin_users + end +end diff --git a/db/migrate/20110620055609_add_deleted_at_to_posts.rb b/db/migrate/20110620055609_add_deleted_at_to_posts.rb new file mode 100644 index 000000000..6f106a1ff --- /dev/null +++ b/db/migrate/20110620055609_add_deleted_at_to_posts.rb @@ -0,0 +1,9 @@ +class AddDeletedAtToPosts < ActiveRecord::Migration + def self.up + add_column :posts, :deleted_at, :datetime + end + + def self.down + remove_column :posts, :deleted_at, :datetime + end +end diff --git a/db/migrate/20110630212436_create_half_users.rb b/db/migrate/20110630212436_create_half_users.rb new file mode 100644 index 000000000..0ea043090 --- /dev/null +++ b/db/migrate/20110630212436_create_half_users.rb @@ -0,0 +1,17 @@ +class CreateHalfUsers < ActiveRecord::Migration + def self.up + create_table :half_users do |t| + t.string "first_name" + t.string "last_name" + t.string "password" + t.string "street_address" + t.string "email" + t.string "single_access_token" + t.timestamps + end + end + + def self.down + drop_table :half_users + end +end diff --git a/db/migrate/20110630213721_add_middle_name_to_half_users.rb b/db/migrate/20110630213721_add_middle_name_to_half_users.rb new file mode 100644 index 000000000..14005b2db --- /dev/null +++ b/db/migrate/20110630213721_add_middle_name_to_half_users.rb @@ -0,0 +1,9 @@ +class AddMiddleNameToHalfUsers < ActiveRecord::Migration + def self.up + add_column :half_users, :middle_name, :string + end + + def self.down + remove_column :half_users, :middle_name + end +end diff --git a/db/migrate/20110630213838_add_community_id_to_half_users.rb b/db/migrate/20110630213838_add_community_id_to_half_users.rb new file mode 100644 index 000000000..06c96163d --- /dev/null +++ b/db/migrate/20110630213838_add_community_id_to_half_users.rb @@ -0,0 +1,9 @@ +class AddCommunityIdToHalfUsers < ActiveRecord::Migration + def self.up + add_column :half_users, :community_id, :integer + end + + def self.down + remove_column :half_users, :community_id + end +end diff --git a/db/migrate/20110701040703_add_transitional_state_to_users.rb b/db/migrate/20110701040703_add_transitional_state_to_users.rb new file mode 100644 index 000000000..f7f304699 --- /dev/null +++ b/db/migrate/20110701040703_add_transitional_state_to_users.rb @@ -0,0 +1,9 @@ +class AddTransitionalStateToUsers < ActiveRecord::Migration + def self.up + add_column :users, :transitional_user, :boolean + end + + def self.down + remove_column :users, :transitional_user + end +end diff --git a/db/migrate/20110713042546_add_should_delete_to_community.rb b/db/migrate/20110713042546_add_should_delete_to_community.rb new file mode 100644 index 000000000..87e364c0c --- /dev/null +++ b/db/migrate/20110713042546_add_should_delete_to_community.rb @@ -0,0 +1,9 @@ +class AddShouldDeleteToCommunity < ActiveRecord::Migration + def self.up + add_column :communities, :should_delete, :boolean, :default => false + end + + def self.down + remove_column :communities, :should_delete + end +end diff --git a/db/migrate/20110804175412_add_college_flag_to_community.rb b/db/migrate/20110804175412_add_college_flag_to_community.rb new file mode 100644 index 000000000..f78c6f986 --- /dev/null +++ b/db/migrate/20110804175412_add_college_flag_to_community.rb @@ -0,0 +1,9 @@ +class AddCollegeFlagToCommunity < ActiveRecord::Migration + def self.up + add_column :communities, :is_college, :boolean, :default => false + end + + def self.down + remove_column :communities, :is_college + end +end diff --git a/db/migrate/20110808165533_create_organizer_data_points.rb b/db/migrate/20110808165533_create_organizer_data_points.rb new file mode 100644 index 000000000..1536269f2 --- /dev/null +++ b/db/migrate/20110808165533_create_organizer_data_points.rb @@ -0,0 +1,15 @@ +class CreateOrganizerDataPoints < ActiveRecord::Migration + def self.up + create_table :organizer_data_points do |t| + t.integer :organizer_id + t.string :address + t.string :status + + t.timestamps + end + end + + def self.down + drop_table :organizer_data_points + end +end diff --git a/db/migrate/20110810150906_add_referral_metadata_to_user.rb b/db/migrate/20110810150906_add_referral_metadata_to_user.rb new file mode 100644 index 000000000..f0ef913c3 --- /dev/null +++ b/db/migrate/20110810150906_add_referral_metadata_to_user.rb @@ -0,0 +1,9 @@ +class AddReferralMetadataToUser < ActiveRecord::Migration + def self.up + add_column :users, :referral_metadata, :string + end + + def self.down + remove_column :users, :referral_metadata + end +end diff --git a/db/migrate/20110811150030_add_deleted_at_to_events_and_announcements.rb b/db/migrate/20110811150030_add_deleted_at_to_events_and_announcements.rb new file mode 100644 index 000000000..0d680f9e7 --- /dev/null +++ b/db/migrate/20110811150030_add_deleted_at_to_events_and_announcements.rb @@ -0,0 +1,11 @@ +class AddDeletedAtToEventsAndAnnouncements < ActiveRecord::Migration + def self.up + add_column :events, :deleted_at, :datetime + add_column :announcements, :deleted_at, :datetime + end + + def self.down + remove_column :events, :deleted_at + remove_column :announcements, :deleted_at + end +end diff --git a/db/migrate/20110811202725_create_delayed_jobs.rb b/db/migrate/20110811202725_create_delayed_jobs.rb new file mode 100644 index 000000000..871cb2d65 --- /dev/null +++ b/db/migrate/20110811202725_create_delayed_jobs.rb @@ -0,0 +1,19 @@ +class CreateDelayedJobs < ActiveRecord::Migration + def self.up + create_table :delayed_jobs, :force => true do |table| + table.integer :priority, :default => 0 # jobs can jump to the front of + table.integer :attempts, :default => 0 # retries, but still fail eventually + table.text :handler # YAML object dump + table.text :last_error # last failure + table.datetime :run_at # schedule for later + table.datetime :locked_at # set when client working this job + table.datetime :failed_at # set when all retries have failed + table.text :locked_by # who is working on this object + table.timestamps + end + end + + def self.down + drop_table :delayed_jobs + end +end diff --git a/db/migrate/20110816171445_add_generated_lat_and_generated_lng_to_user.rb b/db/migrate/20110816171445_add_generated_lat_and_generated_lng_to_user.rb new file mode 100644 index 000000000..6dfff2140 --- /dev/null +++ b/db/migrate/20110816171445_add_generated_lat_and_generated_lng_to_user.rb @@ -0,0 +1,11 @@ +class AddGeneratedLatAndGeneratedLngToUser < ActiveRecord::Migration + def self.up + add_column :users, :generated_lat, :float + add_column :users, :generated_lng, :float + end + + def self.down + remove_column :users, :generated_lng + remove_column :users, :generated_lat + end +end diff --git a/db/migrate/20110829201145_add_launch_date_to_communities.rb b/db/migrate/20110829201145_add_launch_date_to_communities.rb new file mode 100644 index 000000000..61434f4de --- /dev/null +++ b/db/migrate/20110829201145_add_launch_date_to_communities.rb @@ -0,0 +1,10 @@ +class AddLaunchDateToCommunities < ActiveRecord::Migration + def self.up + # January 1st 2010 was the beginning of time + add_column :communities, :launch_date, :date, :default => Date.parse("2010-01-01") + end + + def self.down + remove_column :communities, :launch_date + end +end diff --git a/db/migrate/20110830143431_add_email_counter_to_user.rb b/db/migrate/20110830143431_add_email_counter_to_user.rb new file mode 100644 index 000000000..f4332784e --- /dev/null +++ b/db/migrate/20110830143431_add_email_counter_to_user.rb @@ -0,0 +1,9 @@ +class AddEmailCounterToUser < ActiveRecord::Migration + def self.up + add_column :users, :emails_sent, :integer, :default => 0 + end + + def self.down + remove_column :users, :emails_sent + end +end diff --git a/db/migrate/20110908201405_change_email_post_receive_default_to_limited_live.rb b/db/migrate/20110908201405_change_email_post_receive_default_to_limited_live.rb new file mode 100644 index 000000000..bb113089e --- /dev/null +++ b/db/migrate/20110908201405_change_email_post_receive_default_to_limited_live.rb @@ -0,0 +1,9 @@ +class ChangeEmailPostReceiveDefaultToLimitedLive < ActiveRecord::Migration + def self.up + change_column_default(:users, :post_receive_method, "Three") + end + + def self.down + change_column_default(:users, :post_receive_method, "Live") + end +end diff --git a/db/migrate/20110909212621_authlogic_to_devise.rb b/db/migrate/20110909212621_authlogic_to_devise.rb new file mode 100644 index 000000000..a23f6bf56 --- /dev/null +++ b/db/migrate/20110909212621_authlogic_to_devise.rb @@ -0,0 +1,32 @@ +class AuthlogicToDevise < ActiveRecord::Migration + def self.up + add_column :users, :reset_password_token, :string + add_column :users, :remember_token, :string + add_column :users, :remember_created_at, :datetime + add_column :users, :authentication_token, :string + + rename_column :users, :crypted_password, :encrypted_password + + remove_column :users, :persistence_token + remove_column :users, :single_access_token + remove_column :users, :perishable_token + + User.find_each do |u| + u.reset_authentication_token + u.save(:validate => false) + end + end + + def self.down + add_column :users, :perishable_token, :string + add_column :users, :single_access_token, :string + add_column :users, :persistence_token, :string + + rename_column :users, :encrypted_password, :crypted_password + + remove_column :users, :authentication_token + remove_column :users, :remember_created_at + remove_column :users, :remember_token + remove_column :users, :reset_password_token + end +end diff --git a/db/migrate/20110913044436_use_acts_as_taggable_on_for_user_tag_fields.rb b/db/migrate/20110913044436_use_acts_as_taggable_on_for_user_tag_fields.rb new file mode 100644 index 000000000..76853b807 --- /dev/null +++ b/db/migrate/20110913044436_use_acts_as_taggable_on_for_user_tag_fields.rb @@ -0,0 +1,11 @@ +require "#{Rails.root}/db/migrate/20110303072220_dont_use_acts_as_taggable_on_for_user_tag_fields.rb" + +class UseActsAsTaggableOnForUserTagFields < ActiveRecord::Migration + def self.up + DontUseActsAsTaggableOnForUserTagFields.down + end + + def self.down + DontUseActsAsTaggableOnForUserTagFields.up + end +end diff --git a/db/migrate/20110914144318_downcase_emails.rb b/db/migrate/20110914144318_downcase_emails.rb new file mode 100644 index 000000000..83e86f7c1 --- /dev/null +++ b/db/migrate/20110914144318_downcase_emails.rb @@ -0,0 +1,11 @@ +class DowncaseEmails < ActiveRecord::Migration + def self.up + User.find_each do |user| + user.email = user.email.downcase + user.save :validate => false + end + end + + def self.down + end +end diff --git a/db/migrate/20110916135546_move_group_avatars_to_assets.rb b/db/migrate/20110916135546_move_group_avatars_to_assets.rb new file mode 100644 index 000000000..29aa87abe --- /dev/null +++ b/db/migrate/20110916135546_move_group_avatars_to_assets.rb @@ -0,0 +1,22 @@ +class MoveGroupAvatarsToAssets < ActiveRecord::Migration + def up + Group.find_each do |group| + old_file_name = group.avatar_file_name + if old_file_name.match(%r{^/images/}) + group.avatar_file_name = old_file_name.gsub(%r{^/images/}, "/assets/") + group.save(:validate => false) + end + end + end + + def down + Group.find_each do |group| + old_file_name = group.avatar_file_name + if old_file_name.match(%r{^/assets/}) + group.avatar_file_name = old_file_name.gsub(%r{^/assets/}, "/images/") + group.save(:validate => false) + end + end + + end +end diff --git a/db/migrate/20110916202652_create_event_cross_postings.rb b/db/migrate/20110916202652_create_event_cross_postings.rb new file mode 100644 index 000000000..8cff8d779 --- /dev/null +++ b/db/migrate/20110916202652_create_event_cross_postings.rb @@ -0,0 +1,13 @@ +class CreateEventCrossPostings < ActiveRecord::Migration + def self.up + create_table :event_cross_postings do |t| + t.integer :event_id + t.integer :group_id + t.timestamps + end + end + + def self.down + drop_table :event_cross_postings + end +end diff --git a/db/migrate/20110916205352_create_announcement_cross_postings.rb b/db/migrate/20110916205352_create_announcement_cross_postings.rb new file mode 100644 index 000000000..0dbbe4ba3 --- /dev/null +++ b/db/migrate/20110916205352_create_announcement_cross_postings.rb @@ -0,0 +1,13 @@ +class CreateAnnouncementCrossPostings < ActiveRecord::Migration + def self.up + create_table :announcement_cross_postings do |t| + t.integer :announcement_id + t.integer :group_id + t.timestamps + end + end + + def self.down + drop_table :announcement_cross_postings + end +end diff --git a/db/migrate/20110920185338_facebook_uid_should_be_a_string.rb b/db/migrate/20110920185338_facebook_uid_should_be_a_string.rb new file mode 100644 index 000000000..e479e8fe0 --- /dev/null +++ b/db/migrate/20110920185338_facebook_uid_should_be_a_string.rb @@ -0,0 +1,9 @@ +class FacebookUidShouldBeAString < ActiveRecord::Migration + def up + change_column :users, :facebook_uid, :string + end + + def down + change_column :users, :facebook_uid, :integer + end +end diff --git a/db/migrate/20110921174419_removes_unused_column_users_seen_tour.rb b/db/migrate/20110921174419_removes_unused_column_users_seen_tour.rb new file mode 100644 index 000000000..e0f543f26 --- /dev/null +++ b/db/migrate/20110921174419_removes_unused_column_users_seen_tour.rb @@ -0,0 +1,9 @@ +class RemovesUnusedColumnUsersSeenTour < ActiveRecord::Migration + def up + remove_column :users, :seen_tour + end + + def down + add_column :users, :seen_tour, :boolean + end +end diff --git a/db/migrate/20110925215853_add_attempted_geolocating_to_user.rb b/db/migrate/20110925215853_add_attempted_geolocating_to_user.rb new file mode 100644 index 000000000..f135be1ea --- /dev/null +++ b/db/migrate/20110925215853_add_attempted_geolocating_to_user.rb @@ -0,0 +1,5 @@ +class AddAttemptedGeolocatingToUser < ActiveRecord::Migration + def change + add_column :users, :attempted_geolocating, :boolean + end +end diff --git a/db/migrate/20110928235927_add_lat_and_lng_to_organizer_data_points.rb b/db/migrate/20110928235927_add_lat_and_lng_to_organizer_data_points.rb new file mode 100644 index 000000000..e5990bd58 --- /dev/null +++ b/db/migrate/20110928235927_add_lat_and_lng_to_organizer_data_points.rb @@ -0,0 +1,7 @@ +class AddLatAndLngToOrganizerDataPoints < ActiveRecord::Migration + def change + add_column :organizer_data_points, :lat, :float + add_column :organizer_data_points, :lng, :float + add_column :organizer_data_points, :attempted_geolocating, :boolean + end +end diff --git a/db/migrate/20110930232557_users_dont_use_acts_as_taggable_on.rb b/db/migrate/20110930232557_users_dont_use_acts_as_taggable_on.rb new file mode 100644 index 000000000..65d2d3e22 --- /dev/null +++ b/db/migrate/20110930232557_users_dont_use_acts_as_taggable_on.rb @@ -0,0 +1,35 @@ +class UsersDontUseActsAsTaggableOn < ActiveRecord::Migration + def up + say_with_time("Changing skill_list to text and renaming to skills") do + change_column :users, :cached_skill_list, :text + rename_column :users, :cached_skill_list, :skills + end + + say_with_time("Changing good_list to text and renaming to goods") do + change_column :users, :cached_good_list, :text + rename_column :users, :cached_good_list, :goods + end + + say_with_time("Changing interest_list to text and renaming to interests") do + change_column :users, :cached_interest_list, :text + rename_column :users, :cached_interest_list, :interests + end + end + + def down + say_with_time("Changing skills to string and renaming to skill_list") do + change_column :users, :skills, :text + rename_column :users, :skills, :cached_skill_list + end + + say_with_time("Changing goods to string and renaming to good_list") do + change_column :users, :goods, :sting + rename_column :users, :goods, :cached_good_list + end + + say_with_time("Changing interests to text and renaming to interest_list") do + change_column :users, :interests, :text + rename_column :users, :interests, :cached_interest_list + end + end +end diff --git a/db/migrate/20111009154752_add_google_docs_link_to_communities.rb b/db/migrate/20111009154752_add_google_docs_link_to_communities.rb new file mode 100644 index 000000000..4c1af3dd4 --- /dev/null +++ b/db/migrate/20111009154752_add_google_docs_link_to_communities.rb @@ -0,0 +1,5 @@ +class AddGoogleDocsLinkToCommunities < ActiveRecord::Migration + def change + add_column :communities, :google_docs_url, :string + end +end diff --git a/db/migrate/20111010234525_add_last_checked_inbox_to_users.rb b/db/migrate/20111010234525_add_last_checked_inbox_to_users.rb new file mode 100644 index 000000000..8b0525afa --- /dev/null +++ b/db/migrate/20111010234525_add_last_checked_inbox_to_users.rb @@ -0,0 +1,7 @@ +class AddLastCheckedInboxToUsers < ActiveRecord::Migration + def change + + add_column :users, :last_checked_inbox, :datetime + + end +end diff --git a/db/migrate/20111011154345_add_deleted_at_column_to_group_posts.rb b/db/migrate/20111011154345_add_deleted_at_column_to_group_posts.rb new file mode 100644 index 000000000..fcac86415 --- /dev/null +++ b/db/migrate/20111011154345_add_deleted_at_column_to_group_posts.rb @@ -0,0 +1,6 @@ +class AddDeletedAtColumnToGroupPosts < ActiveRecord::Migration + def change + + add_column :group_posts, :deleted_at, :datetime + end +end diff --git a/db/migrate/20111011221234_create_feed_owners.rb b/db/migrate/20111011221234_create_feed_owners.rb new file mode 100644 index 000000000..01249ce50 --- /dev/null +++ b/db/migrate/20111011221234_create_feed_owners.rb @@ -0,0 +1,18 @@ +class CreateFeedOwners < ActiveRecord::Migration + def change + create_table :feed_owners do |t| + + t.integer :feed_id + t.integer :user_id + + t.timestamps + end + + FeedOwner.reset_column_information + + Feed.find_each do |feed| + FeedOwner.create!(:user_id => feed.user_id, :feed_id => feed.id) + end + + end +end diff --git a/db/schema.rb b/db/schema.rb index 963a793ca..c6604b9df 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1,56 +1,96 @@ -# This file is auto-generated from the current state of the database. Instead of editing this file, -# please use the migrations feature of Active Record to incrementally modify your database, and -# then regenerate this schema definition. +# encoding: UTF-8 +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. # -# Note that this schema.rb definition is the authoritative source for your database schema. If you need -# to create the application database on another system, you should be using db:schema:load, not running -# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations # you'll amass, the slower it'll run and the greater likelihood for issues). # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110117073853) do +ActiveRecord::Schema.define(:version => 20111011221234) do - create_table "addresses", :force => true do |t| - t.string "name" - t.string "primary" - t.decimal "lat" - t.decimal "lng" + create_table "active_admin_comments", :force => true do |t| + t.integer "resource_id", :null => false + t.string "resource_type", :null => false + t.integer "author_id" + t.string "author_type" + t.text "body" + t.datetime "created_at" + t.datetime "updated_at" + t.string "namespace" + end + + add_index "active_admin_comments", ["author_type", "author_id"], :name => "index_active_admin_comments_on_author_type_and_author_id" + add_index "active_admin_comments", ["namespace"], :name => "index_active_admin_comments_on_namespace" + add_index "active_admin_comments", ["resource_type", "resource_id"], :name => "index_admin_notes_on_resource_type_and_resource_id" + + create_table "admin_users", :force => true do |t| + t.string "email", :default => "", :null => false + t.string "encrypted_password", :limit => 128, :default => "", :null => false + t.string "reset_password_token" + t.datetime "reset_password_sent_at" + t.datetime "remember_created_at" + t.integer "sign_in_count", :default => 0 + t.datetime "current_sign_in_at" + t.datetime "last_sign_in_at" + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "admin_users", ["email"], :name => "index_admin_users_on_email", :unique => true + add_index "admin_users", ["reset_password_token"], :name => "index_admin_users_on_reset_password_token", :unique => true + + create_table "announcement_cross_postings", :force => true do |t| + t.integer "announcement_id" + t.integer "group_id" t.datetime "created_at" t.datetime "updated_at" end create_table "announcements", :force => true do |t| - t.string "subject", :null => false - t.text "body", :null => false - t.integer "feed_id", :null => false + t.string "subject", :null => false + t.text "body", :null => false t.datetime "created_at" t.datetime "updated_at" - t.boolean "private", :default => false, :null => false - t.string "type" + t.boolean "private", :default => false, :null => false + t.string "type", :default => "Announcement" t.string "url" + t.integer "community_id" + t.string "owner_type" + t.integer "owner_id" + t.string "tweet_id" + t.datetime "deleted_at" end - create_table "attendances", :force => true do |t| - t.integer "event_id", :null => false - t.integer "user_id", :null => false + create_table "archived_posts", :id => false, :force => true do |t| + t.integer "id", :null => false + t.text "body", :null => false + t.integer "user_id", :null => false t.datetime "created_at" t.datetime "updated_at" + t.string "subject" + t.string "category" + t.integer "community_id" + t.boolean "sent_to_community" + t.boolean "published" + t.datetime "deleted_at" end - create_table "avatars", :force => true do |t| - t.integer "owner_id" - t.string "owner_type" - t.string "image_file_name" - t.string "image_content_type" - t.string "image_file_size" + create_table "attendances", :force => true do |t| + t.integer "event_id", :null => false + t.integer "user_id", :null => false t.datetime "created_at" t.datetime "updated_at" - t.string "avatar_remote_url" end create_table "communities", :force => true do |t| - t.string "name", :null => false + t.string "name", :null => false t.datetime "created_at" t.datetime "updated_at" t.string "slug" @@ -58,6 +98,17 @@ t.string "logo_file_name" t.string "email_header_file_name" t.text "signup_message" + t.string "organizer_email" + t.string "organizer_name" + t.string "organizer_avatar_file_name" + t.text "organizer_about" + t.string "time_zone", :default => "Eastern Time (US & Canada)" + t.integer "households", :default => 0 + t.boolean "core" + t.boolean "should_delete", :default => false + t.boolean "is_college", :default => false + t.date "launch_date", :default => '2010-01-01' + t.string "google_docs_url" end create_table "conversation_memberships", :force => true do |t| @@ -73,6 +124,26 @@ t.datetime "updated_at" end + create_table "delayed_jobs", :force => true do |t| + t.integer "priority", :default => 0 + t.integer "attempts", :default => 0 + t.text "handler" + t.text "last_error" + t.datetime "run_at" + t.datetime "locked_at" + t.datetime "failed_at" + t.text "locked_by" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "event_cross_postings", :force => true do |t| + t.integer "event_id" + t.integer "group_id" + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "events", :force => true do |t| t.string "name", :null => false t.text "description", :null => false @@ -86,11 +157,16 @@ t.string "owner_type" t.string "source_feed_id" t.string "address" + t.string "venue" + t.string "type" + t.string "host_group_name" + t.integer "community_id" + t.datetime "deleted_at" end - create_table "feedbacks", :force => true do |t| - t.integer "user_id", :null => false - t.string "contents", :null => false + create_table "feed_owners", :force => true do |t| + t.integer "feed_id" + t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" end @@ -113,18 +189,53 @@ t.string "avatar_file_name" t.string "address" t.string "hours" -<<<<<<< HEAD -<<<<<<< HEAD t.string "slug" - t.integer "owner_id" t.string "twitter_name" -======= ->>>>>>> d209f88... Loaded initial data from Facebook to landing page -======= + t.integer "kind" + end + + create_table "group_posts", :force => true do |t| + t.string "subject" + t.text "body" + t.integer "user_id" + t.integer "group_id" + t.datetime "created_at" + t.datetime "updated_at" + t.datetime "deleted_at" + end + + create_table "groups", :force => true do |t| + t.string "name" t.string "slug" - t.integer "owner_id" - t.string "twitter_name" ->>>>>>> 4aeb53b... Laid the foundation for Facebook Connect integration. Pulling data and Facebook UID is now functional. + t.string "about" + t.integer "community_id" + t.string "avatar_file_name" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "half_users", :force => true do |t| + t.string "first_name" + t.string "last_name" + t.string "password" + t.string "street_address" + t.string "email" + t.string "single_access_token" + t.datetime "created_at" + t.datetime "updated_at" + t.string "middle_name" + t.integer "community_id" + end + + create_table "internships", :force => true do |t| + t.string "name" + t.string "email" + t.string "phone_number" + t.string "college" + t.integer "graduation_year" + t.text "essay" + t.datetime "created_at" + t.datetime "updated_at" end create_table "invites", :force => true do |t| @@ -146,24 +257,23 @@ t.datetime "updated_at" end - create_table "locations", :force => true do |t| - t.string "street_address" - t.string "zip_code" - t.decimal "lat" - t.decimal "lng" + create_table "memberships", :force => true do |t| + t.integer "user_id" + t.integer "group_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "locatable_id" - t.string "locatable_type" + t.string "receive_method", :default => "Live" end create_table "messages", :force => true do |t| - t.integer "user_id", :null => false - t.text "body", :null => false + t.integer "user_id", :null => false + t.text "body", :null => false t.datetime "created_at" t.datetime "updated_at" t.string "subject" - t.integer "recipient_id" + t.integer "messagable_id" + t.string "messagable_type" + t.boolean "archived" end create_table "mets", :force => true do |t| @@ -179,15 +289,16 @@ t.datetime "created_at" t.datetime "updated_at" t.text "bounds" + t.decimal "latitude" + t.decimal "longitude" end create_table "notifications", :force => true do |t| + t.integer "user_id", :null => false t.integer "notifiable_id", :null => false t.string "notifiable_type", :null => false t.datetime "created_at" t.datetime "updated_at" - t.integer "notified_id" - t.string "notified_type" end create_table "organizations", :force => true do |t| @@ -206,6 +317,17 @@ t.string "category" end + create_table "organizer_data_points", :force => true do |t| + t.integer "organizer_id" + t.string "address" + t.string "status" + t.datetime "created_at" + t.datetime "updated_at" + t.float "lat" + t.float "lng" + t.boolean "attempted_geolocating" + end + create_table "platform_updates", :force => true do |t| t.string "subject", :null => false t.text "body", :null => false @@ -214,20 +336,23 @@ end create_table "posts", :force => true do |t| - t.text "body", :null => false - t.integer "user_id", :null => false + t.text "body", :null => false + t.integer "user_id", :null => false t.datetime "created_at" t.datetime "updated_at" t.string "subject" t.string "category" - t.integer "neighborhood_id" + t.integer "community_id" + t.boolean "sent_to_community" + t.boolean "published", :default => true + t.datetime "deleted_at" end create_table "profile_fields", :force => true do |t| - t.string "subject", :null => false - t.text "body", :null => false - t.integer "feed_id", :null => false - t.integer "position", :null => false + t.string "subject", :null => false + t.text "body", :null => false + t.integer "organization_id", :null => false + t.integer "position", :null => false t.datetime "created_at" t.datetime "updated_at" end @@ -250,30 +375,28 @@ t.boolean "official", :default => false, :null => false end - create_table "roles", :force => true do |t| - t.integer "user_id", :null => false - t.integer "organization_id", :null => false + create_table "requests", :force => true do |t| + t.string "community_name" + t.string "name" + t.string "email" + t.string "sponsor_organization" t.datetime "created_at" t.datetime "updated_at" end - create_table "slugs", :force => true do |t| - t.string "name" - t.integer "sluggable_id" - t.integer "sequence", :default => 1, :null => false - t.string "sluggable_type", :limit => 40 - t.string "scope" + create_table "roles", :force => true do |t| + t.integer "user_id", :null => false + t.integer "organization_id", :null => false t.datetime "created_at" + t.datetime "updated_at" end - add_index "slugs", ["name", "sluggable_type", "sequence", "scope"], :name => "index_slugs_on_n_s_s_and_s", :unique => true - add_index "slugs", ["sluggable_id"], :name => "index_slugs_on_sluggable_id" - create_table "subscriptions", :force => true do |t| - t.integer "user_id", :null => false - t.integer "feed_id", :null => false + t.integer "user_id", :null => false + t.integer "feed_id", :null => false t.datetime "created_at" t.datetime "updated_at" + t.string "receive_method", :default => "Daily" end create_table "taggings", :force => true do |t| @@ -294,43 +417,56 @@ t.integer "canonical_tag_id" end + create_table "tweets", :force => true do |t| + t.string "screen_name", :null => false + t.text "body", :null => false + t.string "url", :null => false + t.integer "twitter_announcement_id", :null => false + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "users", :force => true do |t| - t.string "email", :null => false - t.string "crypted_password" + t.string "email", :null => false + t.string "encrypted_password" t.string "password_salt" - t.string "persistence_token", :null => false - t.string "single_access_token", :null => false - t.string "perishable_token", :null => false t.datetime "created_at" t.datetime "updated_at" - t.string "first_name", :null => false - t.string "last_name", :null => false + t.string "first_name", :null => false + t.string "last_name", :null => false t.text "about" - t.integer "neighborhood_id", :null => false - t.string "cached_skill_list" - t.string "cached_interest_list" - t.string "cached_good_list" - t.boolean "receive_digests", :default => false, :null => false - t.boolean "receive_posts", :default => true - t.boolean "receive_events_and_announcements", :default => true - t.boolean "admin", :default => false + t.integer "neighborhood_id", :null => false + t.text "interests" + t.text "goods" + t.boolean "receive_events_and_announcements", :default => true + t.boolean "admin", :default => false t.string "state" t.string "avatar_file_name" -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - t.integer "facebook_uid", :limit => 8 - t.string "address" -======= ->>>>>>> Loaded initial data from Facebook to landing page - t.integer "facebook_uid", :limit => 8 - t.string "address" -======= ->>>>>>> d209f88... Loaded initial data from Facebook to landing page -======= ->>>>>>> 4aeb53b... Laid the foundation for Facebook Connect integration. Pulling data and Facebook UID is now functional. - t.integer "facebook_uid", :limit => 8 t.string "address" + t.string "facebook_uid" + t.string "oauth2_token" + t.integer "community_id" + t.boolean "receive_weekly_digest", :default => true + t.string "post_receive_method", :default => "Three" + t.string "middle_name" + t.decimal "latitude" + t.decimal "longitude" + t.string "referral_source" + t.datetime "last_login_at" + t.boolean "transitional_user" + t.string "referral_metadata" + t.float "generated_lat" + t.float "generated_lng" + t.integer "emails_sent", :default => 0 + t.string "reset_password_token" + t.string "remember_token" + t.datetime "remember_created_at" + t.string "authentication_token" + t.text "skills" + t.boolean "attempted_geolocating" + t.datetime "last_checked_inbox" end + add_index "users", ["oauth2_token"], :name => "index_users_on_oauth2_token" + end diff --git a/db/seeds.rb b/db/seeds.rb index 6c9555921..e0cdde7b4 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -12,14 +12,16 @@ :community => community, :bounds => [[42.29337, -71.16252], [42.29061, -71.16827], [42.28391, -71.16162], [42.28163, -71.16004], [42.28077, -71.15819], [42.2848, -71.15669], [42.28614, -71.1548], [42.29061, -71.15999]]) user = User.create!(:first_name => "test", :last_name => "dev", - :email => "test@example.com", :location => Location.new(:zip_code => "02132", :street_address => "420 Baker St."), + :email => "test@example.com", :address => "221B Baker St.", :password => "password", :neighborhood => neighborhood, - :avatar => Avatar.new) + :community => community) user.admin = true user.save! post = Post.create(:body => "This is a test post", :user => user, :subject => "Subject", - :neighborhood_id => neighborhood.id) + :community => community) post.save +event = Event.create(:name => "Test Event", :description => "Event for testing", :owner => User.find(:first), :date => Time.now, :start_time => Time.now, :end_time => (Time.now + 60*60*24*3)) +event.save diff --git a/deploy b/deploy new file mode 100755 index 000000000..ef07ac850 --- /dev/null +++ b/deploy @@ -0,0 +1,2 @@ +git pull origin master +git push origin master && git push production master --force diff --git a/doc/README_FOR_APP b/doc/README_FOR_APP deleted file mode 100644 index fe41f5cc2..000000000 --- a/doc/README_FOR_APP +++ /dev/null @@ -1,2 +0,0 @@ -Use this README file to introduce your application and point to useful places in the API for learning more. -Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. diff --git a/lib/address_geolocator.rb b/lib/address_geolocator.rb new file mode 100644 index 000000000..a70940cd5 --- /dev/null +++ b/lib/address_geolocator.rb @@ -0,0 +1,6 @@ +class AddressGeolocator + + def self.perform + OrganizerDataPoint.all.map &:generate_point + end +end diff --git a/lib/announcement_importer.rb b/lib/announcement_importer.rb deleted file mode 100644 index 7ba05f371..000000000 --- a/lib/announcement_importer.rb +++ /dev/null @@ -1,21 +0,0 @@ -class AnnouncementImporter - require 'outside_in' - - def self.perform - OutsideIn.key = CONFIG['outside_in_key'] - OutsideIn.secret = CONFIG['outside_in_secret'] - - Community.find(:all).each do |community| - OutsideIn::Story.for_zip_code(community.zip_code)[:stories].each do |story| - o = OutsideAnnouncement.new() - o.subject = story.title - o.body = story.summary - o.feed_id = Feed.find_or_create_by_name_and_website_and_community_id(story.feed_title, story.feed_url, community).id - o.url = story.story_url - o.save() - end - end - - end - -end diff --git a/lib/api.rb b/lib/api.rb new file mode 100644 index 000000000..305bb43d9 --- /dev/null +++ b/lib/api.rb @@ -0,0 +1,42 @@ +require 'rack/contrib/jsonp' +%w{ base accounts announcements communities events + feeds users group_posts groups messages + neighborhoods posts }.each do |path| + require Rails.root.join("lib", "api", path) +end + +class API + + def initialize + @app = Rack::Builder.new do + + use Rack::JSONP + + map("/account") { run Accounts } + map("/announcements") { run Announcements } + map("/communities") { run Communities } + map("/events") { run Events } + map("/feeds") { run Feeds } + map("/users") { run Users } + map("/group_posts") { run GroupPosts } + map("/groups") { run Groups } + map("/messages") { run Messages } + map("/neighborhoods") { run Neighborhoods } + map("/posts") { run Posts } + map("/search/community") { run Search } + map("/replies") { run Replies } + + map("/") { run lambda {|env| [200, {}, ["Invalid Request"]] } } + end + end + + def call(env) + @app.call(env) + end + + def self.call(env) + new.call(env) + end + +end + diff --git a/lib/api/accounts.rb b/lib/api/accounts.rb new file mode 100644 index 000000000..fdcc0573a --- /dev/null +++ b/lib/api/accounts.rb @@ -0,0 +1,68 @@ +class API + class Accounts < Base + + helpers do + + def checked_inbox + current_account.checked_inbox! + serialize(Account.new(current_account)) + end + + end + + get "/" do + serialize Account.new(current_account) + end + + post "/subscriptions/feeds" do + feed = Feed.find(params[:id] || request_body['id']) + halt [401, "wrong community"] unless in_comm(feed.community.id) + current_account.feeds << feed + serialize(Account.new(current_account)) + end + + delete "/subscriptions/feeds/:id" do |id| + current_account.feeds.delete(Feed.find(id)) + serialize(Account.new(current_account)) + end + + post "/subscriptions/groups" do + group = Group.find(params[:id] || request_body['id']) + halt [401, "wrong community"] unless in_comm(group.community.id) + current_account.groups << group + serialize(Account.new(current_account)) + end + + delete "/subscriptions/groups/:id" do |id| + current_account.groups.delete(Group.find(id)) + serialize(Account.new(current_account)) + end + + post "/mets" do + user = User.find(params[:id] || request_body["id"]) + halt [401, "wrong community"] unless in_comm(user.community.id) + current_account.people << user + serialize(Account.new(current_account)) + end + + delete "/mets/:id" do |id| + current_account.people.delete(User.find(id)) + serialize(Account.new(current_account)) + end + + get "/inbox" do + checked_inbox() + serialize(paginate(current_account.inbox.reorder("updated_at DESC"))) + end + + get "/inbox/sent" do + serialize(paginate(current_account.sent_messages.reorder("updated_at DESC"))) + end + + get "/inbox/feeds" do + checked_inbox() + serialize(paginate(current_account.feed_messages.reorder("updated_at DESC"))) + end + + end +end diff --git a/lib/api/announcements.rb b/lib/api/announcements.rb new file mode 100644 index 000000000..ee046d607 --- /dev/null +++ b/lib/api/announcements.rb @@ -0,0 +1,72 @@ +class API + class Announcements < Base + + helpers do + + def auth(announcement) + halt [401, "wrong community"] unless in_comm(announcement.community.id) + if (announcement.owner_type == "Feed") + announcement.owner.get_feed_owner(current_account) or current_account.admin + else + announcement.owner == current_account or announcement.user == current_account or current_account.admin + end + end + + end + + put "/:id" do |id| + announcement = Announcement.find(id) + unless announcement.present? + [404, "errors"] + end + + announcement.subject = request_body['title'] + announcement.body = request_body['body'] + + if auth(announcement) and announcement.save + serialize(announcement) + else + unless auth(announcement) + [401, 'unauthorized'] + else + [500, 'could not save'] + end + end + end + + delete "/:id" do |id| + announcement = Announcement.find(id) + unless announcement.present? + [404, "errors"] + end + + if (auth(announcement)) + announcement.destroy + else + [404, "errors"] + end + end + + get "/:id" do |id| + announcement = Announcement.find(id) + halt [401, "wrong community"] unless in_comm(announcement.community.id) + serialize announcement + end + + post "/:id/replies" do |id| + announcement = Announcement.find(id) + halt [401, "wrong community"] unless in_comm(announcement.community.id) + reply = Reply.new(:repliable => announcement, + :user => current_account, + :body => request_body['body']) + + if reply.save + kickoff.deliver_reply(reply) + Serializer::serialize(reply).to_json + else + [400, "errors"] + end + end + + end +end diff --git a/lib/api/base.rb b/lib/api/base.rb new file mode 100644 index 000000000..023a135b0 --- /dev/null +++ b/lib/api/base.rb @@ -0,0 +1,91 @@ +class API + + class Base < Sinatra::Base + set :raise_errors, true + + helpers do + + def current_user + @_user ||= if request.env["HTTP_AUTHORIZATION"].present? + User.find_by_authentication_token(request.env["HTTP_AUTHORIZATION"]) + elsif params['authentication_token'].present? + User.find_by_authentication_token(params[:authentication_token]) + else + User.find_by_authentication_token(request.cookies['authentication_token']) + end + end + + def current_account + # TODO: THIS IS NOT AN ACCOUNT. REMOVE. + current_user + end + + def request_body + @_request_body ||= JSON.parse(request.body.read.to_s) + end + + def authorize! + halt [401, "not logged in"] unless current_user + end + + def serialize(thing) + Serializer::serialize(thing).to_json + end + + def logger + @logger ||= Logger.new(Rails.root.join("log", "api.log")) + end + + def page + (params[:page] || 0).to_i + end + + def limit + (params[:limit] || 25).to_i + end + + def paginate(scope) + limit.to_i == 0 ? scope : scope.limit(limit).offset(limit * page) + end + + def kickoff + request.env["kickoff"] ||= KickOff.new + end + + def last_modified_by_updated_at(scope) + # sets last modified header for this request to that of the newest record + last_modified(scope.unscoped. + reorder("updated_at DESC"). + select('updated_at'). + first.try(&:updated_at)) + end + + def jsonp(callback, data) + "#{callback}(#{data})" + end + + def in_comm(community_id) + current_user.community.id == community_id.to_i || current_user.admin + end + + NO_CALLBACK = ["no_callback"].to_json + + end + + before do + cache_control :public, :must_revalidate, :max_age => 0 + content_type :json + authorize! + end + + #configure(:development) do + # # don't require server restart between changes: + # # https://github.com/rkh/sinatra-reloader/blob/master/README.md + # register Sinatra::Reloader + # #also_reload "app/models/*.rb" + # #dont_reload "lib/**/*.rb" + #end + + end + +end diff --git a/lib/api/communities.rb b/lib/api/communities.rb new file mode 100644 index 000000000..947ce208b --- /dev/null +++ b/lib/api/communities.rb @@ -0,0 +1,263 @@ +class API + class Communities < Base + + before "/:community_id/*" do |community_id, stuff| + unless current_account.community.id == community_id || current_account.admin + [401, "wrong community"] + end + end + + helpers do + + def search(klass, params, community_id, options = nil) + keywords = phrase(params["query"]) + search = Sunspot.search(klass) do + keywords keywords + paginate(:page => params["page"].to_i + 1, :per_page => params["limit"]) + with(:community_id, community_id) + yield(self) if block_given? + end + + serialize search + end + + def phrase(string) + # group quoted words + string.split('"').each_with_index.map { |object, i| + i.odd? ? object : object.split(" ") + }.flatten + end + + end + + post "/:community_id/posts" do |community_id| + post = Post.new(:user => current_account, + :community_id => community_id, + :subject => request_body['title'], + :body => request_body['body']) + if post.save + kickoff.deliver_post(post) + serialize(post) + else + [400, "errors"] + end + end + + post "/:community_id/announcements" do |community_id| + announcement = Announcement.new(:owner => request_body['feed'].present? ? Feed.find(request_body['feed']) : current_account, + :subject => request_body['title'], + :body => request_body['body'], + :community_id => community_id, + :group_ids => request_body["groups"]) + + if announcement.save + kickoff.deliver_announcement(announcement) + serialize(announcement) + else + [400, "errors"] + end + end + + post "/:community_id/events" do |community_id| + event = Event.new(:owner => current_account, + :name => request_body['title'], + :description => request_body['about'], + :date => request_body['date'], + :start_time => request_body['start'], + :end_time => request_body['end'], + :venue => request_body['venue'], + :address => request_body['address'], + :tag_list => request_body['tags'], + :community_id => community_id, + :group_ids => request_body['groups'] + ) + if event.save + serialize(event) + else + [400, "errors"] + end + end + + # todo: all these GET methods are not so DRY. could make a common search or not function that takes a class name + # would require a lookup to see how to paginate various classes + + get "/:community_id/posts" do |community_id| + last_modified_by_updated_at(Post) + + if params["query"].present? + search(Post, params, community_id) + else + serialize paginate Community.find(community_id).posts.includes(:user, :replies) + end + end + + get "/:community_id/events" do |community_id| + last_modified([Event.unscoped.reorder("updated_at DESC"). + select('updated_at'). + first.try(&:updated_at), + Date.today.beginning_of_day].compact.max) + + if params["query"].present? + search(Event, params, community_id) do |search| + search.with(:date).greater_than(Time.now.beginning_of_day) + end + else + serialize(paginate(Community.find(community_id).events.upcoming. + includes(:replies).reorder("date ASC"))) + end + end + + get "/:community_id/announcements" do |community_id| + last_modified_by_updated_at(Announcement) + + if params["query"].present? + search(Announcement, params, community_id) + else + serialize(paginate(Community.find(community_id).announcements. + includes(:replies, :owner). + reorder("updated_at DESC"))) + end + end + + get "/:community_id/group_posts" do |community_id| + last_modified_by_updated_at(GroupPost) + + if params["query"].present? + search(GroupPost, params, community_id) + else + serialize(paginate(GroupPost.order("group_posts.updated_at DESC"). + includes(:group, :user, :replies => :user). + where(:groups => {:community_id => community_id}))) + end + end + + get "/:community_id/feeds" do |community_id| + last_modified_by_updated_at(Feed) + + if params["query"].present? + search(Feed, params, community_id) + else + scope = Community.find(community_id).feeds.reorder("avatar_file_name DESC nulls last, about DESC nulls last") + serialize(paginate(scope)) + end + end + + get "/:community_id/groups" do |community_id| + last_modified_by_updated_at(Group) + + if params["query"].present? + search(Group, params, community_id) + else + serialize(paginate(Community.find(community_id).groups)) + end + end + + get "/:community_id/users" do |community_id| + last_modified_by_updated_at(User) + + if params["query"].present? + search(User, params, community_id) + else + scope = Community.find(community_id).users.reorder("avatar_file_name DESC nulls last, about DESC nulls last") + serialize(paginate(scope)) + end + end + + get "/:community_slug/user_count" do |community_slug| + serialize(Community.find_by_slug(community_slug).users.count) + end + + + get "/:community_id/group-like" do |community_id| + # only search + halt [200, {}, "[]"] if params["query"].blank? + + search([Feed, Group, User], params, community_id) + end + + get "/:community_id/post-like" do |community_id| + if params["query"].present? + search([Announcement, Event, Post, GroupPost], params, community_id) + else + search([Announcement, Event, Post, GroupPost], params, community_id) do + order_by(:created_at, :desc) + end + end + end + + + post "/:community_id/add_data_point" do |community_id| + num = params[:number] + zip_code = User.find(current_account.id).community.zip_code + if num.include? "-" + odds = false + evens = false + all = true + if num.include? "O" + odds = true + all = false + num = num.gsub("O", "") + elsif num.include? "E" + evens = true + all = false + num = num.gsub("E", "") + end + + range = num.split("-") + (range[0].to_i..range[1].to_i).each do |n| + if (odds and (n % 2 == 1)) or (evens and (n % 2 == 0)) or all + data_point = OrganizerDataPoint.new + data_point.organizer_id = current_account.id + data_point.address = "#{n} #{params[:address]} #{zip_code}" + data_point.status = params[:status] + data_point.save + end + end + else + data_point = OrganizerDataPoint.new + data_point.organizer_id = current_account.id + data_point.address = "#{num} #{params[:address]} #{zip_code}" + data_point.status = params[:status] + data_point.save + data_point.generate_point + end + [200, "OK"] + end + + + get "/:community_id/registration_points" do |community_id| + headers 'Access-Control-Allow-Origin' => '*' + community = Community.find(community_id) + callback = params.delete("callback") + unless callback.present? + serialize(community.users.map &:generate_point) + else + jsonp(callback, serialize(community.users.map &:generate_point)) + end + end + + get "/:community_id/data_points" do |community_id| + headers 'Access-Control-Allow-Origin' => '*' + community = Community.find(community_id) + callback = params.delete("callback") + unless callback.present? + if params[:top] + serialize(community.organizers.map(&:organizer_data_points).flatten.uniq { |p| p.address }.select { |p| p.present? }) + else + serialize(community.organizers.map(&:organizer_data_points).flatten.select { |p| p.present? }) + end + else + if params[:top] + jsonp(callback, serialize(community.organizers.map(&:organizer_data_points).flatten.uniq { |p| p.address }.select { |p| p.present? })) + end + jsonp(callback, serialize(community.organizers.map(&:organizer_data_points).flatten.select { |p| p.present? })) + end + end + + post "/:community_id/invites" do |community_id| + kickoff.deliver_user_invite(request_body['emails'], current_account, request_body['message']) + [200, {}, ""] + end + + end +end diff --git a/lib/api/events.rb b/lib/api/events.rb new file mode 100644 index 000000000..313dbfd32 --- /dev/null +++ b/lib/api/events.rb @@ -0,0 +1,78 @@ +class API + class Events < Base + + helpers do + + def auth(event) + halt [401, "wrong community"] unless in_comm(event.community.id) + if (event.owner_type == "Feed") + event.owner.get_feed_owner(current_account) or current_account.admin + else + event.owner == current_account or event.user == current_account or current_account.admin + end + end + + end + + put "/:id" do |id| + event = Event.find(id) + unless event.present? + [404, "errors"] + end + + event.name = request_body['title'] + event.description = request_body['body'] + event.date = request_body['occurs_on'] + event.start_time = request_body['starts_at'] + event.end_time = request_body['ends_at'] + event.venue = request_body['venue'] + event.address = request_body['address'] + event.tag_list = request_body['tags'] + + if auth(event) and event.save + serialize(event) + else + unless auth(event) + [401, "unauthorized"] + else + [500, "could not save"] + end + end + end + + delete "/:id" do |id| + event = Event.find(id) + unless event.present? + [404, "errors"] + end + + if auth(event) + event.destroy + else + [404, "errors"] + end + end + + get "/:id" do |id| + event = Event.find(id) + halt [401, "wrong community"] unless in_comm(event.community.id) + serialize event + end + + post "/:id/replies" do |id| + event = Event.find(id) + halt [401, "wrong community"] unless in_comm(event.community.id) + reply = Reply.new(:repliable => event, + :user => current_account, + :body => request_body['body']) + + if reply.save + kickoff.deliver_reply(reply) + Serializer::serialize(reply).to_json + else + [400, "errors"] + end + end + + end +end diff --git a/lib/api/feeds.rb b/lib/api/feeds.rb new file mode 100644 index 000000000..f8e852df7 --- /dev/null +++ b/lib/api/feeds.rb @@ -0,0 +1,142 @@ +class API + class Feeds < Base + + before "/:feed_id/*" do |feed_id, stuff| + feed = feed_id =~ /[^\d]/ ? Feed.find_by_slug(feed_id) : Feed.find(feed_id) + halt [401, "wrong community"] unless in_comm(feed.community.id) + end + + helpers do + + def auth(feed) + feed.get_feed_owner(current_account) or current_account.admin + end + + end + + put "/:feed_id" do |feed_id| + feed = Feed.find(feed_id) + halt [404, "errors"] unless feed.present? + halt [401, "unauthorized"] unless auth(feed) + + feed.name = request_body["name"] + feed.about = request_body["about"] + feed.kind = request_body["kind"] + feed.phone = request_body["phone"] + feed.website = request_body["website"] + feed.address = request_body["address"] + feed.slug = request_body["slug"] + feed.feed_url = request_body["rss"] + + if feed.save + serialize(feed) + else + [500, "could not save"] + end + end + + post "/:feed_id/announcements" do |feed_id| + halt [401, "unauthorized"] unless auth(Feed.find(feed_id)) + announcement = Announcement.new(:owner_type => "Feed", + :owner_id => feed_id, + :subject => request_body['title'], + :body => request_body['body'], + :community => current_account.community, + :group_ids => request_body["groups"]) + if announcement.save + kickoff.deliver_announcement(announcement) + serialize(announcement) + else + [400, "errors"] + end + end + + get "/:feed_id/announcements" do |feed_id| + scope = Announcement.where("owner_id = ? AND owner_type = ?", feed_id, "Feed") + serialize(paginate(scope.includes(:replies, :owner).reorder("updated_at DESC"))) + end + + post "/:feed_id/events" do |feed_id| + halt [401, "unauthorized"] unless auth(Feed.find(feed_id)) + event = Event.new(:owner_type => "Feed", + :owner_id => feed_id, + :name => request_body['title'], + :description => request_body['about'], + :date => request_body['date'], + :start_time => request_body['start'], + :end_time => request_body['end'], + :venue => request_body['venue'], + :address => request_body['address'], + :tag_list => request_body['tags'], + :community => current_account.community, + :group_ids => request_body["groups"]) + if event.save + serialize(event) + else + [400, "errors"] + end + end + + get "/:feed_id/events" do |feed_id| + scope = Event.where("owner_id = ? AND owner_type = ?",feed_id, "Feed") + serialize(paginate(scope.upcoming.includes(:replies).reorder("date ASC"))) + end + + get "/:feed_id/subscribers" do |feed_id| + serialize(paginate(Feed.find(feed_id).subscribers)) + end + + post "/:feed_id/invites" do |feed_id| + halt [401, "unauthorized"] unless auth(Feed.find(feed_id)) + kickoff.deliver_feed_invite(request_body['emails'], Feed.find(feed_id)) + [200, ""] + end + + post "/:feed_id/messages" do |feed_id| + message = Message.new(:subject => request_body['subject'], + :body => request_body['body'], + :messagable_type => "Feed", + :messagable_id => feed_id, + :user => current_account) + if message.save + [200, ""] + else + [400, "errors"] + end + end + + get "/:feed_id" do |feed_id| + serialize(feed_id =~ /[^\d]/ ? Feed.find_by_slug(feed_id) : Feed.find(feed_id)) + end + + get "/:feed_id/owners" do |feed_id| + if Feed.find(feed_id).get_feed_owner(current_user) + serialize(Feed.find(feed_id).feed_owners) + else + [401, "unauthorized"] + end + end + + post "/:feed_id/owners" do |feed_id| + feed = Feed.find(feed_id) + halt [401, "unauthorized"] unless auth(feed) + params["emails"].split(",").each do |email| + user = User.find_by_email(email.gsub(" ","")) + existing_owner = feed.get_feed_owner(user) + if user and !existing_owner + owner = FeedOwner.new(:feed => feed, + :user => user) + owner.save + end + end + [200, ""] + end + + delete "/:feed_id/owners/:id" do |feed_id, id| + halt [401, "unauthorized"] unless auth(Feed.find(feed_id)) + owner = FeedOwner.find(id) + owner.destroy + end + + end +end diff --git a/lib/api/group_posts.rb b/lib/api/group_posts.rb new file mode 100644 index 000000000..46f723996 --- /dev/null +++ b/lib/api/group_posts.rb @@ -0,0 +1,68 @@ +class API + class GroupPosts < Base + + helpers do + + def auth(post) + halt [401, "wrong community"] unless in_comm(post.group.community.id) + post.user == current_account or current_account.admin + end + + end + + put "/:id" do |id| + post = GroupPost.find(id) + unless post.present? + [404, 'errors'] + end + + post.subject = request_body['title'] + post.body = request_body['body'] + + if auth(post) and post.save + serialize(post) + else + unless auth(post) + [401, 'unauthorized'] + else + [500, 'could not save'] + end + end + end + + delete "/:id" do |id| + post = GroupPost.find(id) + unless post.present? + [404, "errors"] + end + + if auth(post) + post.destroy + else + [404, "errors"] + end + end + + get "/:id" do |id| + post = GroupPost.find(id) + halt [401, "wrong community"] unless in_comm(post.group.community.id) + serialize post + end + + post "/:id/replies" do |id| + post = GroupPost.find(id) + halt [401, "wrong community"] unless in_comm(post.group.community.id) + reply = Reply.new(:repliable => post, + :user => current_account, + :body => request_body['body']) + + if reply.save + kickoff.deliver_reply(reply) + Serializer::serialize(reply).to_json + else + [400, "errors"] + end + end + + end +end diff --git a/lib/api/groups.rb b/lib/api/groups.rb new file mode 100644 index 000000000..79f1b554e --- /dev/null +++ b/lib/api/groups.rb @@ -0,0 +1,49 @@ +class API + class Groups < Base + + before "/:group_id/*" do |group_id, stuff| + group = Group.find(group_id) + halt [401, "wrong community"] unless in_comm(group.community.id) + end + + get "/:group_id" do |group_id| + serialize(Group.find(group_id)) + end + + post "/:group_id/posts" do |group_id| + group_post = GroupPost.new(:group => Group.find(group_id), + :subject => request_body['title'], + :body => request_body['body'], + :user => current_account) + if group_post.save + group_post.group.live_subscribers.each do |user| + Resque.enqueue(GroupPostNotification, group_post.id, user.id) + end + serialize(group_post) + else + [400, "errors"] + end + end + + get "/:group_id/posts" do |group_id| + scope = Group.find(group_id).group_posts.reorder("updated_at DESC") + serialize( paginate(scope) ) + end + + get "/:group_id/members" do |group_id| + scope = Group.find(group_id).subscribers + serialize( paginate(scope) ) + end + + get "/:group_id/events" do |group_id| + scope = Group.find(group_id).events.upcoming.reorder("date ASC") + serialize( paginate(scope) ) + end + + get "/:group_id/announcements" do |group_id| + scope = Group.find(group_id).announcements.reorder("updated_at DESC") + serialize( paginate(scope) ) + end + + end +end diff --git a/lib/api/messages.rb b/lib/api/messages.rb new file mode 100644 index 000000000..5282bc9c7 --- /dev/null +++ b/lib/api/messages.rb @@ -0,0 +1,20 @@ +class API + class Messages < Base + + post "/:id/replies" do |id| + message = Message.find(id) + halt [401, "wrong community"] unless [message.user,message.messagable].include? current_user + reply = Reply.new(:repliable => message, + :user => current_account, + :body => request_body['body']) + + if reply.save + kickoff.deliver_reply(reply) + Serializer::serialize(reply).to_json + else + [400, "errors"] + end + end + + end +end diff --git a/lib/api/neighborhoods.rb b/lib/api/neighborhoods.rb new file mode 100644 index 000000000..c5e58dd71 --- /dev/null +++ b/lib/api/neighborhoods.rb @@ -0,0 +1,13 @@ +class API + class Neighborhoods < Base + + get "/:id/posts" do |id| + halt [401, "wrong neighborhood"] unless current_user.neighborhood.id == id + posts = Post.includes(:user).where(:users => {:neighborhood_id => id}). + reorder("posts.updated_at") + + serialize(paginate(posts.includes(:user, :replies))) + end + + end +end diff --git a/lib/api/posts.rb b/lib/api/posts.rb new file mode 100644 index 000000000..48fcfc0ed --- /dev/null +++ b/lib/api/posts.rb @@ -0,0 +1,65 @@ +class API + class Posts < Base + + helpers do + + def auth(post) + halt [401, "wrong community"] unless in_comm(post.community.id) + post.user == current_account or current_account.admin + end + + end + + put "/:id" do |id| + post = Post.find(id) + unless post.present? + [404, "errors"] + end + post.subject = request_body['title'] + post.body = request_body['body'] + + if auth(post) and post.save + serialize(post) + elsif !auth(post) + [401, "errors: #{current_account} does not have access."] + else + [400, "errors: #{post.errors.full_messages.to_s}"] + end + end + + delete "/:id" do |id| + post = Post.find(id) + unless post.present? + [404, "errors"] + end + + if auth(post) + post.destroy + else + [404, "errors"] + end + end + + get "/:id" do |id| + post = Post.find(id) + halt [401, "wrong community"] unless in_comm(post.community.id) + serialize post + end + + post "/:id/replies" do |id| + post = Post.find(id) + halt [401, "wrong community"] unless in_comm(post.community.id) + reply = Reply.new(:repliable => post, + :user => current_account, + :body => request_body['body']) + + if reply.save + kickoff.deliver_reply(reply) + Serializer::serialize(reply).to_json + else + [400, "errors"] + end + end + + end +end diff --git a/lib/api/replies.rb b/lib/api/replies.rb new file mode 100644 index 000000000..3650d0abb --- /dev/null +++ b/lib/api/replies.rb @@ -0,0 +1,27 @@ +class API + class Replies < Base + + helpers do + + def auth(reply) + halt [401, "wrong community"] unless in_comm(reply.repliable.community.id) + reply.user == current_account or current_account.admin + end + + end + + delete "/:id" do |reply_id| + reply = Reply.find(reply_id) + unless reply.present? + [404, "errors"] + end + + if auth(reply) + reply.destroy + else + [401, "unauthorized"] + end + end + + end +end diff --git a/lib/api/search.rb b/lib/api/search.rb new file mode 100644 index 000000000..8d97b1357 --- /dev/null +++ b/lib/api/search.rb @@ -0,0 +1,58 @@ +class API + class Search < Base + + before "/:community_id/*" do |community_id, stuff| + halt [401, "wrong community"] unless in_comm(params[:community_id]) + end + + helpers do + + def search(klass, params, community_id) + search = Sunspot.search(klass) do + keywords phrase(params["query"]) + paginate(:page => params["page"].to_i + 1) + with(:community_id, community_id) + end + serialize(search) + end + + def phrase(string) + string.split('"').each_with_index.map { |object, i| + i.odd? ? object : object.split(" ") + }.flatten + end + + end + + get "/:community_id/feeds" do |community_id| + halt [200, {}, "[]"] if params["query"].blank? + + search(Feed, params, community_id) + end + + get "/:community_id/groups" do |community_id| + halt [200, {}, "[]"] if params["query"].blank? + + search(Group, params, community_id) + end + + get "/:community_id/users" do |community_id| + halt [200, {}, "[]"] if params["query"].blank? + + search(User, params, community_id) + end + + get "/:community_id/group-like" do |community_id| + halt [200, {}, "[]"] if params["query"].blank? + + search([Feed, Group, User], params, community_id) + end + + get "/:community_id/post-like" do |community_id| + halt [200, {}, "[]"] if params["query"].blank? + + search([Announcement, Event, Post, GroupPost], params, community_id) + end + + end +end diff --git a/lib/api/users.rb b/lib/api/users.rb new file mode 100644 index 000000000..6e3fac629 --- /dev/null +++ b/lib/api/users.rb @@ -0,0 +1,32 @@ +class API + class Users < Base + + post "/:id/messages" do |id| + user = User.find(id) + halt [401, "wrong community"] unless in_comm(user.community.id) + message = Message.new(:subject => request_body['subject'], + :body => request_body['body'], + :messagable => user, + :user => current_account) + if message.save + kickoff.deliver_user_message(message) + [200, ""] + else + [400, "errors"] + end + end + + get "/user_by_email/:email" do |email| + user = User.find_by_email(email) + halt [401, "wrong community"] unless current_user.admin + user.to_json + end + + get "/:id" do |id| + user = User.find(id) + halt [401, "wrong community"] unless in_comm(user.community.id) + serialize user + end + + end +end diff --git a/lib/api_failure_app.rb b/lib/api_failure_app.rb new file mode 100644 index 000000000..5794ab31d --- /dev/null +++ b/lib/api_failure_app.rb @@ -0,0 +1,5 @@ +class ApiFailureApp + def self.call(env) + [400, {"Content-Type" => "application/json"}, "{ status: 400 }"] + end +end diff --git a/lib/community_daily_bulletin_job.rb b/lib/community_daily_bulletin_job.rb new file mode 100644 index 000000000..1aceb0a68 --- /dev/null +++ b/lib/community_daily_bulletin_job.rb @@ -0,0 +1,18 @@ +class CommunityDailyBulletinJob + @queue = :community_daily_bulletin + + def self.perform(community_id, date) + kickoff = KickOff.new + community = Community.find(community_id) + + community.users.where("post_receive_method != 'Never'").find_each do |user| + Exceptional.rescue do + kickoff.deliver_daily_bulletin(user, date) + end + end + end + +end + + + diff --git a/lib/cp_client.rb b/lib/cp_client.rb new file mode 100644 index 000000000..34813ab3a --- /dev/null +++ b/lib/cp_client.rb @@ -0,0 +1,97 @@ + +class CPClient + + def initialize(options = {}) + @host = options[:host] + @authentication = options[:api_key] + @logger = options[:logger] || Rails.logger + end + + def community_posts(community, options = {}) + get("/communities/#{community}/posts", options) { [] } + end + + def neighborhood_posts(neighborhood, options = {}) + get("/neighborhoods/#{neighborhood}/posts", options) { [] } + end + + def community_events(community, options = {}) + get("/communities/#{community}/events", options) { [] } + end + + def community_publicity(community, options = {}) + get("/communities/#{community}/announcements", options) { [] } + end + + def community_group_posts(community, options = {}) + get("/communities/#{community}/group_posts", options) { [] } + end + + def community_neighbors(community, options = {}) + get("/communities/#{community}/users", options) { [] } + end + + def community_feeds(community, options = {}) + get("/communities/#{community}/feeds", options) { [] } + end + + def community_groups(community, options = {}) + get("/communities/#{community}/groups", options) { [] } + end + + def post_info(id) + get("/posts/#{id}") + end + + def user_info(id) + get("/users/#{id}") + end + + def addresses_for_community(community, options = {}) + get("/communities/#{community.id}/addresses", options) + end + + def add_data_point(community,options) + post("/communities/#{community.id}/add_data_point", options) + end + private + + def logger + @logger + end + + def connection + @_connection ||= Faraday.new(:url => @host, :headers => {"AUTHORIZATION" => @authentication}) + end + + def get(uri, params = {}, &on_fail) + request = connection.get {|req| req.url uri, params} + if request.success? + JSON.parse request.body + else + if on_fail + result = on_fail.call + logger.warn "GET #{uri} #{params.inspect} failed; falling back to #{result.inspect}" + result + else + raise "GET #{uri} #{params.inspect} failed with no fallback" + end + end + end + + def post(uri, params = {}, &on_fail) + request = connection.post { |req| req.url uri, params } + if request.success? + true + else + if on_fail + result = on_fail.call + logger.warn "POST #{uri} #{params.inspect} failed; falling back to #{result.inspect}" + result + else + raise "POST #{uri} #{params.inspect} failed with no fallback" + end + end + end + +end diff --git a/lib/croppable_avatar.rb b/lib/croppable_avatar.rb new file mode 100644 index 000000000..70df29313 --- /dev/null +++ b/lib/croppable_avatar.rb @@ -0,0 +1,17 @@ +module CroppableAvatar + attr_accessor :crop_x, :crop_y, :crop_w, :crop_h + + def self.included(klass) + klass.after_update :reprocess_avatar, :if => :cropping? + end + + def cropping? + !crop_x.blank? && !crop_y.blank? && !crop_w.blank? && !crop_h.blank? + end + + def reprocess_avatar + avatar.reprocess! + end + + +end diff --git a/lib/daily_digest_job.rb b/lib/daily_digest_job.rb new file mode 100644 index 000000000..57b0a601f --- /dev/null +++ b/lib/daily_digest_job.rb @@ -0,0 +1,14 @@ +class DailyDigestJob + @queue = :daily_digest + + def self.perform + date = DateTime.now.utc.to_s(:db) + + Community.all.each do |community| + Exceptional.rescue do + Resque.enqueue(CommunityDailyBulletinJob, community.id, date) + end + end + end + +end diff --git a/lib/daily_digest_test_job.rb b/lib/daily_digest_test_job.rb new file mode 100644 index 000000000..29dea539d --- /dev/null +++ b/lib/daily_digest_test_job.rb @@ -0,0 +1,15 @@ +class DailyDigestTestJob + @queue = :daily_digest + + def self.perform + kickoff = KickOff.new + date = DateTime.now.utc + sent = [] + User.where("post_receive_method != 'Never'").find_each do |user| + Exceptional.rescue do + sent << user + end + end + Resque.redis.set("daily_digest_test_result", "#{sent.count}") + end +end diff --git a/lib/eventful_importer.rb b/lib/eventful_importer.rb deleted file mode 100644 index f5714f178..000000000 --- a/lib/eventful_importer.rb +++ /dev/null @@ -1,73 +0,0 @@ -class EventfulImporter - require 'rubygems' - require 'eventful/api' - - def self.import_event(event,community) - # The only properly formed input is a Hash - if event.is_a?(Array) - return - end - - begin - e = Event.new() - if !event || event.nil? - return - end - - # Ensure that all necessary fields are present - required_keys = ['title', 'description', 'start_time', 'postal_code', 'venue_address', 'id'] - required_keys.each do |key| - if !event[key].present? - return - end - end - - # If this event has already been imported, skip it - if Event.find_by_source_feed_id(event['id']) - return - end - - e.name = event['title'] - - # Convert from HTML to Markdown - mccbean = McBean.fragment event['description'] - e.description = mccbean.to_markdown - - e.date = event['start_time'].split(" ")[0] - e.start_time = event['start_time'] - - if event['stop_time'].present? - e.end_time = event['stop_time'] - end - - e.location = Location.new(:zip_code => event['postal_code'], :street_address => event['venue_address']) - - e.owner_id = Feed.find_or_create_by_name_and_website_and_community_id("Eventful", "http://www.eventful.com/", community).id - e.owner_type = "Feed" - e.source_feed_id = event['id'] - e.save - rescue TypeError => e - puts "Failed with TypeError on Community with zipcode #{community.zip_code}!" - end - end - - def self.perform - eventful = Eventful::API.new CONFIG['eventful_application_key'], - :user => CONFIG['eventful_username'], - :password => CONFIG['eventful_password'] - - Community.find(:all).each do |community| - results = eventful.call 'events/search', - :location => community.zip_code, - :page_size => 10, - :mature => 'safe' - - if results['events'].nil? then - next - end - results['events']['event'].each do |event| - self.import_event(event,community) - end - end - end -end \ No newline at end of file diff --git a/lib/forgery/forgeries/latlng.rb b/lib/forgery/forgeries/latlng.rb new file mode 100644 index 000000000..aafbf2954 --- /dev/null +++ b/lib/forgery/forgeries/latlng.rb @@ -0,0 +1,36 @@ +class Forgery::Latlng < Forgery + + + def self.random(options = {}) + if options[:within] && options[:miles_of] + point = options[:miles_of] + distance = options[:within] + + location = Geocoder::Calculations.to_radians(point) + + radius = Geocoder::Calculations.distance_to_radians(distance) + + a = 2 * Math::PI * rand(0) + + distance_coefficient = Math.sqrt(rand(0)) + + Geocoder::Calculations. + to_degrees([radius * distance_coefficient * Math.cos(a) + location.first, + radius * distance_coefficient * Math.sin(a) + location.last]) + + else + [random_latitude, random_longitude] + end + end + + private + + def self.random_latitude + (rand(180) - 90) + rand(0) + end + + def self.random_longitude + (rand(360) - 180) + rand(0) + end + +end diff --git a/lib/google_analytics.rb b/lib/google_analytics.rb new file mode 100644 index 000000000..ff7a28bec --- /dev/null +++ b/lib/google_analytics.rb @@ -0,0 +1,6 @@ +class GoogleAnalytics + class Pageviews + extend Garb::Model + metrics :pageviews + end +end diff --git a/lib/helper.rb b/lib/helper.rb deleted file mode 100644 index 56e27a3a1..000000000 --- a/lib/helper.rb +++ /dev/null @@ -1,9 +0,0 @@ -class Helper - include Singleton - include ActionView::Helpers::DateHelper - include TimeHelper -end - -def help - Helper.instance -end \ No newline at end of file diff --git a/lib/javascripts/autoresize.js b/lib/javascripts/autoresize.js new file mode 100644 index 000000000..ce532cdf2 --- /dev/null +++ b/lib/javascripts/autoresize.js @@ -0,0 +1,101 @@ +/* + * jQuery autoResize (textarea auto-resizer) + * @copyright James Padolsey http://james.padolsey.com + * Further edited by Max Tilford. + * @version 1.04.Max + */ + +(function($){ + var makeClone = function(textarea) { + + // Properties which may effect space taken up by chracters: + var props = ['height','width','lineHeight','textDecoration','letterSpacing'], + propOb = {}; + + // Create object of styles to apply: + $.each(props, function(i, prop){ + propOb[prop] = textarea.css(prop); + }); + + // Clone the actual textarea removing unique properties + // and insert before original textarea: + return textarea.clone().removeAttr('id').removeAttr('name').css({ + position: 'absolute', + top: 0, + left: -9999 + }).css(propOb).attr('tabIndex','-1').insertBefore(textarea); + }; + + $.fn.autoResize = function(options) { + + // Just some abstracted details, + // to make plugin users happy: + var settings = $.extend({ + onResize : function(){}, + animate : true, + animateDuration : 150, + animateCallback : function(){}, + extraSpace : 0, + limit: 1000 + }, options); + + // Only textarea's auto-resize: + this.filter('textarea').each(function(){ + + // Get rid of scrollbars and disable WebKit resizing: + var textarea = $(this).css({resize:'none','overflow-y':'hidden'}); + + // Cache original height, for use later: + var origHeight; + + // Need clone of textarea, hidden off screen: + var clone; + var lastScrollTop = null; + var updateSize = function() { + + // Initialize origHeight + origHeight = origHeight || $(textarea).height(); + + // Prepare the clone: + clone = clone || makeClone(textarea); + clone.height(0).val($(this).val()).scrollTop(10000); + + // Find the height of text: + var scrollTop = Math.max(clone.scrollTop(), origHeight) + settings.extraSpace, + toChange = $(this).add(clone); + + // Don't do anything if scrollTip hasen't changed: + if (lastScrollTop === scrollTop) { return; } + lastScrollTop = scrollTop; + + // Check for limit: + if ( scrollTop >= settings.limit ) { + $(this).css('overflow-y',''); + return; + } + // Fire off callback: + settings.onResize.call(this); + + // Either animate or directly apply height: + settings.animate && textarea.css('display') === 'block' ? + toChange.stop().animate({height:scrollTop}, settings.animateDuration, settings.animateCallback) + : toChange.height(scrollTop); + }; + + // Bind namespaced handlers to appropriate events: + textarea + .unbind('.dynSiz') + .bind('keyup.dynSiz', updateSize) + .bind('keydown.dynSiz', updateSize) + .bind('change.dynSiz', updateSize); + if ($.contains(document.body, textarea.get(0))) { textarea.trigger("change.dynSiz") } + }); + + // Chain: + return this; + + }; + + + +})(jQuery); diff --git a/lib/javascripts/jquery.highlight-3.js b/lib/javascripts/jquery.highlight-3.js new file mode 100644 index 000000000..f4e9d63ac --- /dev/null +++ b/lib/javascripts/jquery.highlight-3.js @@ -0,0 +1,56 @@ +/* + + highlight v3 + + Highlights arbitrary terms. + + + + MIT license. + + Johann Burkard + + + + */ + +jQuery.fn.highlight = function(pat) { + if (pat == '') return; //prevent browser crash --peter + function innerHighlight(node, pat) { + var skip = 0; + if (node.nodeType == 3) { + var pos = node.data.toUpperCase().indexOf(pat); + if (pos >= 0) { + var spannode = document.createElement('span'); + spannode.className = 'highlight'; + var middlebit = node.splitText(pos); + var endbit = middlebit.splitText(pat.length); + var middleclone = middlebit.cloneNode(true); + spannode.appendChild(middleclone); + middlebit.parentNode.replaceChild(spannode, middlebit); + skip = 1; + } + } + else if (node.nodeType == 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) { + for (var i = 0; i < node.childNodes.length; ++i) { + i += innerHighlight(node.childNodes[i], pat); + } + } + return skip; + } + + return this.each(function() { + innerHighlight(this, pat.toUpperCase()); + }); +}; + +jQuery.fn.removeHighlight = function() { + return this.find("span.highlight").each( + function() { + this.parentNode.firstChild.nodeName; + with (this.parentNode) { + replaceChild(this.firstChild, this); + normalize(); + } + }).end(); +}; diff --git a/lib/javascripts/showdown.js b/lib/javascripts/showdown.js new file mode 100644 index 000000000..e30265b84 --- /dev/null +++ b/lib/javascripts/showdown.js @@ -0,0 +1,1316 @@ +// +// showdown.js -- A javascript port of Markdown. +// +// Copyright (c) 2007 John Fraser. +// +// Original Markdown Copyright (c) 2004-2005 John Gruber +// +// +// +// Redistributable under a BSD-style open source license. +// See license.txt for more information. +// +// The full source distribution is at: +// +// A A L +// T C A +// T K B +// +// +// + +// +// Wherever possible, Showdown is a straight, line-by-line port +// of the Perl version of Markdown. +// +// This is not a normal parser design; it's basically just a +// series of string substitutions. It's hard to read and +// maintain this way, but keeping Showdown close to the original +// design makes it easier to port new features. +// +// More importantly, Showdown behaves like markdown.pl in most +// edge cases. So web applications can do client-side preview +// in Javascript, and then build identical HTML on the server. +// +// This port needs the new RegExp functionality of ECMA 262, +// 3rd Edition (i.e. Javascript 1.5). Most modern web browsers +// should do fine. Even with the new regular expression features, +// We do a lot of work to emulate Perl's regex functionality. +// The tricky changes in this file mostly have the "attacklab:" +// label. Major or self-explanatory changes don't. +// +// Smart diff tools like Araxis Merge will be able to match up +// this file with markdown.pl in a useful way. A little tweaking +// helps: in a copy of markdown.pl, replace "#" with "//" and +// replace "$text" with "text". Be sure to ignore whitespace +// and line endings. +// + +// Modified for CommonPlace. Modifications marked by ** CFM ** or ** GFM ** + +// +// Showdown usage: +// +// var text = "Markdown *rocks*."; +// +// var converter = new Showdown.converter(); +// var html = converter.makeHtml(text); +// +// alert(html); +// +// Note: move the sample code to the bottom of this +// file before uncommenting it. +// + + +// +// Showdown namespace +// +var Showdown = {}; + +// +// converter +// +// Wraps all "globals" so that the only thing +// exposed is makeHtml(). +// +Showdown.converter = function() { + +// +// Globals: +// + +// Global hashes, used by various utility routines +var g_urls; +var g_titles; +var g_html_blocks; + +// Used to track when we're inside an ordered or unordered list +// (see _ProcessListItems() for details): +var g_list_level = 0; + + +this.makeHtml = function(text) { +// +// Main function. The order in which other subs are called here is +// essential. Link and image substitutions need to happen before +// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the +// and tags get encoded. +// + + // Clear the global hashes. If we don't clear these, you get conflicts + // from other articles when generating a page which contains more than + // one article (e.g. an index page that shows the N most recent + // articles): + g_urls = new Array(); + g_titles = new Array(); + g_html_blocks = new Array(); + + // attacklab: Replace ~ with ~T + // This lets us use tilde as an escape char to avoid md5 hashes + // The choice of character is arbitray; anything that isn't + // magic in Markdown will work. + text = text.replace(/~/g,"~T"); + + // attacklab: Replace $ with ~D + // RegExp interprets $ as a special character + // when it's in a replacement string + text = text.replace(/\$/g,"~D"); + + // Standardize line endings + text = text.replace(/\r\n/g,"\n"); // DOS to Unix + text = text.replace(/\r/g,"\n"); // Mac to Unix + + // Make sure text begins and ends with a couple of newlines: + text = "\n\n" + text + "\n\n"; + + // Convert all tabs to spaces. + text = _Detab(text); + + // Strip any lines consisting only of spaces and tabs. + // This makes subsequent regexen easier to write, because we can + // match consecutive blank lines with /\n+/ instead of something + // contorted like /[ \t]*\n+/ . + text = text.replace(/^[ \t]+$/mg,""); + + // Turn block-level HTML blocks into hash entries + text = _HashHTMLBlocks(text); + + // Strip link definitions, store in hashes. + text = _StripLinkDefinitions(text); + + text = _RunBlockGamut(text); + + text = _UnescapeSpecialChars(text); + + // attacklab: Restore dollar signs + text = text.replace(/~D/g,"$$"); + + // attacklab: Restore tildes + text = text.replace(/~T/g,"~"); + + // ** GFM ** Auto-link URLs and emails ** CFM ** + text = text.replace(/https?\:\/\/[^"\s\<\>]*[^.,;'">\:\s\<\>\)\]\!]/g, function(wholeMatch,matchIndex){ + var left = text.slice(0, matchIndex), right = text.slice(matchIndex) + if (left.match(/<[^>]+$/) && right.match(/^[^>]*>/)) {return wholeMatch} + href = wholeMatch.replace(/^http:\/\/github.com\//, "https://github.com/") + return "" + wholeMatch + ""; + }); + text = text.replace(/[a-z0-9_\-+=.]+@[a-z0-9\-]+(\.[a-z0-9-]+)+/ig, function(wholeMatch){return "" + wholeMatch + "";}); + + return text; +} + + +var _StripLinkDefinitions = function(text) { +// +// Strips link definitions from text, stores the URLs and titles in +// hash references. +// + + // Link defs are in the form: ^[id]: url "optional title" + + /* + var text = text.replace(/ + ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 + [ \t]* + \n? // maybe *one* newline + [ \t]* + ? // url = $2 + [ \t]* + \n? // maybe one newline + [ \t]* + (?: + (\n*) // any lines skipped = $3 attacklab: lookbehind removed + ["(] + (.+?) // title = $4 + [")] + [ \t]* + )? // title is optional + (?:\n+|$) + /gm, + function(){...}); + */ + var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm, + function (wholeMatch,m1,m2,m3,m4) { + m1 = m1.toLowerCase(); + g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive + if (m3) { + // Oops, found blank lines, so it's not a title. + // Put back the parenthetical statement we stole. + return m3+m4; + } else if (m4) { + g_titles[m1] = m4.replace(/"/g,"""); + } + + // Completely remove the definition from the text + return ""; + } + ); + + return text; +} + + +var _HashHTMLBlocks = function(text) { + // attacklab: Double up blank lines to reduce lookaround + text = text.replace(/\n/g,"\n\n"); + + // Hashify HTML blocks: + // We only want to do this for block-level HTML tags, such as headers, + // lists, and tables. That's because we still want to wrap

          s around + // "paragraphs" that are wrapped in non-block-level tags, such as anchors, + // phrase emphasis, and spans. The list of tags we're looking for is + // hard-coded: + var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del" + var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math" + + // First, look for nested blocks, e.g.: + //

          + //
          + // tags for inner block must be indented. + //
          + //
          + // + // The outermost tags must start at the left margin for this to match, and + // the inner nested divs must be indented. + // We need to do this before the next, more liberal match, because the next + // match will start at the first `
          ` and stop at the first `
          `. + + // attacklab: This regex can be expensive when it fails. + /* + var text = text.replace(/ + ( // save in $1 + ^ // start of line (with /m) + <($block_tags_a) // start tag = $2 + \b // word break + // attacklab: hack around khtml/pcre bug... + [^\r]*?\n // any number of lines, minimally matching + // the matching end tag + [ \t]* // trailing spaces/tabs + (?=\n+) // followed by a newline + ) // attacklab: there are sentinel newlines at end of document + /gm,function(){...}}; + */ + text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement); + + // + // Now match more liberally, simply from `\n` to `\n` + // + + /* + var text = text.replace(/ + ( // save in $1 + ^ // start of line (with /m) + <($block_tags_b) // start tag = $2 + \b // word break + // attacklab: hack around khtml/pcre bug... + [^\r]*? // any number of lines, minimally matching + .* // the matching end tag + [ \t]* // trailing spaces/tabs + (?=\n+) // followed by a newline + ) // attacklab: there are sentinel newlines at end of document + /gm,function(){...}}; + */ + text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement); + + // Special case just for
          . It was easier to make a special case than + // to make the other regex more complicated. + + /* + text = text.replace(/ + ( // save in $1 + \n\n // Starting after a blank line + [ ]{0,3} + (<(hr) // start tag = $2 + \b // word break + ([^<>])*? // + \/?>) // the matching end tag + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,hashElement); + */ + text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement); + + // Special case for standalone HTML comments: + + /* + text = text.replace(/ + ( // save in $1 + \n\n // Starting after a blank line + [ ]{0,3} // attacklab: g_tab_width - 1 + + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,hashElement); + */ + text = text.replace(/(\n\n[ ]{0,3}[ \t]*(?=\n{2,}))/g,hashElement); + + // PHP and ASP-style processor instructions ( and <%...%>) + + /* + text = text.replace(/ + (?: + \n\n // Starting after a blank line + ) + ( // save in $1 + [ ]{0,3} // attacklab: g_tab_width - 1 + (?: + <([?%]) // $2 + [^\r]*? + \2> + ) + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,hashElement); + */ + text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement); + + // attacklab: Undo double lines (see comment at top of this function) + text = text.replace(/\n\n/g,"\n"); + return text; +} + +var hashElement = function(wholeMatch,m1) { + var blockText = m1; + + // Undo double lines + blockText = blockText.replace(/\n\n/g,"\n"); + blockText = blockText.replace(/^\n/,""); + + // strip trailing blank lines + blockText = blockText.replace(/\n+$/g,""); + + // Replace the element text with a marker ("~KxK" where x is its key) + blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n"; + + return blockText; +}; + +var _RunBlockGamut = function(text) { +// +// These are all the transformations that form block-level +// tags like paragraphs, headers, and list items. +// + text = _DoHeaders(text); + + // Do Horizontal Rules: + var key = hashBlock("
          "); + text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key); + text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key); + text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key); + + text = _DoLists(text); + text = _DoCodeBlocks(text); + text = _DoBlockQuotes(text); + + // We already ran _HashHTMLBlocks() before, in Markdown(), but that + // was to escape raw HTML in the original Markdown source. This time, + // we're escaping the markup we've just created, so that we don't wrap + //

          tags around block-level tags. + text = _HashHTMLBlocks(text); + text = _FormParagraphs(text); + + return text; +} + + +var _RunSpanGamut = function(text) { +// +// These are all the transformations that occur *within* block-level +// tags like paragraphs, headers, and list items. +// + + text = _DoCodeSpans(text); + text = _EscapeSpecialCharsWithinTagAttributes(text); + text = _EncodeBackslashEscapes(text); + + // Process anchor and image tags. Images must come first, + // because ![foo][f] looks like an anchor. + text = _DoImages(text); + text = _DoAnchors(text); + + // Make links out of things like `` + // Must come after _DoAnchors(), because you can use < and > + // delimiters in inline links like [this](). + text = _DoAutoLinks(text); + text = _EncodeAmpsAndAngles(text); + text = _DoItalicsAndBold(text); + + // Do hard breaks: + text = text.replace(/ +\n/g,"
          \n"); + + return text; +} + +var _EscapeSpecialCharsWithinTagAttributes = function(text) { +// +// Within tags -- meaning between < and > -- encode [\ ` * _] so they +// don't conflict with their use in Markdown for code, italics and strong. +// + + // Build a regex to find HTML tags and comments. See Friedl's + // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. + var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi; + + text = text.replace(regex, function(wholeMatch) { + var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`"); + tag = escapeCharacters(tag,"\\`*_"); + return tag; + }); + + return text; +} + +var _DoAnchors = function(text) { +// +// Turn Markdown link shortcuts into XHTML tags. +// + // + // First, handle reference-style links: [link text] [id] + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + \[ + ( + (?: + \[[^\]]*\] // allow brackets nested one level + | + [^\[] // or anything else + )* + ) + \] + + [ ]? // one optional space + (?:\n[ ]*)? // one optional newline followed by spaces + + \[ + (.*?) // id = $3 + \] + )()()()() // pad remaining backreferences + /g,_DoAnchors_callback); + */ + text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag); + + // + // Next, inline-style links: [link text](url "optional title") + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + \[ + ( + (?: + \[[^\]]*\] // allow brackets nested one level + | + [^\[\]] // or anything else + ) + ) + \] + \( // literal paren + [ \t]* + () // no id, so leave $3 empty + ? // href = $4 + [ \t]* + ( // $5 + (['"]) // quote char = $6 + (.*?) // Title = $7 + \6 // matching quote + [ \t]* // ignore any spaces/tabs between closing quote and ) + )? // title is optional + \) + ) + /g,writeAnchorTag); + */ + text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag); + + // + // Last, handle reference-style shortcuts: [link text] + // These must come last in case you've also got [link test][1] + // or [link test](/foo) + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + \[ + ([^\[\]]+) // link text = $2; can't contain '[' or ']' + \] + )()()()()() // pad rest of backreferences + /g, writeAnchorTag); + */ + text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag); + + return text; +} + +var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { + if (m7 == undefined) m7 = ""; + var whole_match = m1; + var link_text = m2; + var link_id = m3.toLowerCase(); + var url = m4; + var title = m7; + + if (url == "") { + if (link_id == "") { + // lower-case and turn embedded newlines into spaces + link_id = link_text.toLowerCase().replace(/ ?\n/g," "); + } + url = "#"+link_id; + + if (g_urls[link_id] != undefined) { + url = g_urls[link_id]; + if (g_titles[link_id] != undefined) { + title = g_titles[link_id]; + } + } + else { + if (whole_match.search(/\(\s*\)$/m)>-1) { + // Special case for explicit empty url + url = ""; + } else { + return whole_match; + } + } + } + + url = escapeCharacters(url,"*_"); + var result = ""; + + return result; +} + + +var _DoImages = function(text) { +// +// Turn Markdown image shortcuts into tags. +// + + // + // First, handle reference-style labeled images: ![alt text][id] + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + !\[ + (.*?) // alt text = $2 + \] + + [ ]? // one optional space + (?:\n[ ]*)? // one optional newline followed by spaces + + \[ + (.*?) // id = $3 + \] + )()()()() // pad rest of backreferences + /g,writeImageTag); + */ + text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag); + + // + // Next, handle inline images: ![alt text](url "optional title") + // Don't forget: encode * and _ + + /* + text = text.replace(/ + ( // wrap whole match in $1 + !\[ + (.*?) // alt text = $2 + \] + \s? // One optional whitespace character + \( // literal paren + [ \t]* + () // no id, so leave $3 empty + ? // src url = $4 + [ \t]* + ( // $5 + (['"]) // quote char = $6 + (.*?) // title = $7 + \6 // matching quote + [ \t]* + )? // title is optional + \) + ) + /g,writeImageTag); + */ + text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag); + + return text; +} + +var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { + var whole_match = m1; + var alt_text = m2; + var link_id = m3.toLowerCase(); + var url = m4; + var title = m7; + + if (!title) title = ""; + + if (url == "") { + if (link_id == "") { + // lower-case and turn embedded newlines into spaces + link_id = alt_text.toLowerCase().replace(/ ?\n/g," "); + } + url = "#"+link_id; + + if (g_urls[link_id] != undefined) { + url = g_urls[link_id]; + if (g_titles[link_id] != undefined) { + title = g_titles[link_id]; + } + } + else { + return whole_match; + } + } + + alt_text = alt_text.replace(/"/g,"""); + url = escapeCharacters(url,"*_"); + var result = "\""' + _RunSpanGamut(m1) + "");}); + + text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, + function(matchFound,m1){return hashBlock('

          ' + _RunSpanGamut(m1) + "

          ");}); + + // atx-style headers: + // # Header 1 + // ## Header 2 + // ## Header 2 with closing hashes ## + // ... + // ###### Header 6 + // + + /* + text = text.replace(/ + ^(\#{1,6}) // $1 = string of #'s + [ \t]* + (.+?) // $2 = Header text + [ \t]* + \#* // optional closing #'s (not counted) + \n+ + /gm, function() {...}); + */ + + text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, + function(wholeMatch,m1,m2) { + var h_level = m1.length; + return hashBlock("' + _RunSpanGamut(m2) + ""); + }); + + function headerId(m) { + return m.replace(/[^\w]/g, '').toLowerCase(); + } + return text; +} + +// This declaration keeps Dojo compressor from outputting garbage: +var _ProcessListItems; + +var _DoLists = function(text) { +// +// Form HTML ordered (numbered) and unordered (bulleted) lists. +// + + // attacklab: add sentinel to hack around khtml/safari bug: + // http://bugs.webkit.org/show_bug.cgi?id=11231 + text += "~0"; + + // Re-usable pattern to match any entirel ul or ol list: + + /* + var whole_list = / + ( // $1 = whole list + ( // $2 + [ ]{0,3} // attacklab: g_tab_width - 1 + ([*+-]|\d+[.]) // $3 = first list item marker + [ \t]+ + ) + [^\r]+? + ( // $4 + ~0 // sentinel for workaround; should be $ + | + \n{2,} + (?=\S) + (?! // Negative lookahead for another list item marker + [ \t]* + (?:[*+-]|\d+[.])[ \t]+ + ) + ) + )/g + */ + var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; + + if (g_list_level) { + text = text.replace(whole_list,function(wholeMatch,m1,m2) { + var list = m1; + var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol"; + + // Turn double returns into triple returns, so that we can make a + // paragraph for the last item in a list, if necessary: + list = list.replace(/\n{2,}/g,"\n\n\n");; + var result = _ProcessListItems(list); + + // Trim any trailing whitespace, to put the closing `` + // up on the preceding line, to get it past the current stupid + // HTML block parser. This is a hack to work around the terrible + // hack that is the HTML block parser. + result = result.replace(/\s+$/,""); + result = "<"+list_type+">" + result + "\n"; + return result; + }); + } else { + whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; + text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) { + var runup = m1; + var list = m2; + + var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol"; + // Turn double returns into triple returns, so that we can make a + // paragraph for the last item in a list, if necessary: + var list = list.replace(/\n{2,}/g,"\n\n\n");; + var result = _ProcessListItems(list); + result = runup + "<"+list_type+">\n" + result + "\n"; + return result; + }); + } + + // attacklab: strip sentinel + text = text.replace(/~0/,""); + + return text; +} + +_ProcessListItems = function(list_str) { +// +// Process the contents of a single ordered or unordered list, splitting it +// into individual list items. +// + // The $g_list_level global keeps track of when we're inside a list. + // Each time we enter a list, we increment it; when we leave a list, + // we decrement. If it's zero, we're not in a list anymore. + // + // We do this because when we're not inside a list, we want to treat + // something like this: + // + // I recommend upgrading to version + // 8. Oops, now this line is treated + // as a sub-list. + // + // As a single paragraph, despite the fact that the second line starts + // with a digit-period-space sequence. + // + // Whereas when we're inside a list (or sub-list), that line will be + // treated as the start of a sub-list. What a kludge, huh? This is + // an aspect of Markdown's syntax that's hard to parse perfectly + // without resorting to mind-reading. Perhaps the solution is to + // change the syntax rules such that sub-lists must start with a + // starting cardinal number; e.g. "1." or "a.". + + g_list_level++; + + // trim trailing blank lines: + list_str = list_str.replace(/\n{2,}$/,"\n"); + + // attacklab: add sentinel to emulate \z + list_str += "~0"; + + /* + list_str = list_str.replace(/ + (\n)? // leading line = $1 + (^[ \t]*) // leading whitespace = $2 + ([*+-]|\d+[.]) [ \t]+ // list marker = $3 + ([^\r]+? // list item text = $4 + (\n{1,2})) + (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+)) + /gm, function(){...}); + */ + list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm, + function(wholeMatch,m1,m2,m3,m4){ + var item = m4; + var leading_line = m1; + var leading_space = m2; + + if (leading_line || (item.search(/\n{2,}/)>-1)) { + item = _RunBlockGamut(_Outdent(item)); + } + else { + // Recursion for sub-lists: + item = _DoLists(_Outdent(item)); + item = item.replace(/\n$/,""); // chomp(item) + item = _RunSpanGamut(item); + } + + return "
        • " + item + "
        • \n"; + } + ); + + // attacklab: strip sentinel + list_str = list_str.replace(/~0/g,""); + + g_list_level--; + return list_str; +} + + +var _DoCodeBlocks = function(text) { +// +// Process Markdown `
          ` blocks.
          +//  
          +
          +	/*
          +		text = text.replace(text,
          +			/(?:\n\n|^)
          +			(								// $1 = the code block -- one or more lines, starting with a space/tab
          +				(?:
          +					(?:[ ]{4}|\t)			// Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
          +					.*\n+
          +				)+
          +			)
          +			(\n*[ ]{0,3}[^ \t\n]|(?=~0))	// attacklab: g_tab_width
          +		/g,function(){...});
          +	*/
          +
          +	// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
          +	text += "~0";
          +	
          +	text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
          +		function(wholeMatch,m1,m2) {
          +			var codeblock = m1;
          +			var nextChar = m2;
          +		
          +			codeblock = _EncodeCode( _Outdent(codeblock));
          +			codeblock = _Detab(codeblock);
          +			codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
          +			codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
          +
          +			codeblock = "
          " + codeblock + "\n
          "; + + return hashBlock(codeblock) + nextChar; + } + ); + + // attacklab: strip sentinel + text = text.replace(/~0/,""); + + return text; +} + +var hashBlock = function(text) { + text = text.replace(/(^\n+|\n+$)/g,""); + return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n"; +} + + +var _DoCodeSpans = function(text) { +// +// * Backtick quotes are used for spans. +// +// * You can use multiple backticks as the delimiters if you want to +// include literal backticks in the code span. So, this input: +// +// Just type ``foo `bar` baz`` at the prompt. +// +// Will translate to: +// +//

          Just type foo `bar` baz at the prompt.

          +// +// There's no arbitrary limit to the number of backticks you +// can use as delimters. If you need three consecutive backticks +// in your code, use four for delimiters, etc. +// +// * You can use spaces to get literal backticks at the edges: +// +// ... type `` `bar` `` ... +// +// Turns to: +// +// ... type `bar` ... +// + + /* + text = text.replace(/ + (^|[^\\]) // Character before opening ` can't be a backslash + (`+) // $2 = Opening run of ` + ( // $3 = The code block + [^\r]*? + [^`] // attacklab: work around lack of lookbehind + ) + \2 // Matching closer + (?!`) + /gm, function(){...}); + */ + + text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, + function(wholeMatch,m1,m2,m3,m4) { + var c = m3; + c = c.replace(/^([ \t]*)/g,""); // leading whitespace + c = c.replace(/[ \t]*$/g,""); // trailing whitespace + c = _EncodeCode(c); + return m1+""+c+""; + }); + + return text; +} + + +var _EncodeCode = function(text) { +// +// Encode/escape certain characters inside Markdown code runs. +// The point is that in code, these characters are literals, +// and lose their special Markdown meanings. +// + // Encode all ampersands; HTML entities are not + // entities within a Markdown code span. + text = text.replace(/&/g,"&"); + + // Do the angle bracket song and dance: + text = text.replace(//g,">"); + + // Now, escape characters that are magic in Markdown: + text = escapeCharacters(text,"\*_{}[]\\",false); + +// jj the line above breaks this: +//--- + +//* Item + +// 1. Subitem + +// special char: * +//--- + + return text; +} + + +var _DoItalicsAndBold = function(text) { + + // must go first: + text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, + "$2"); + + // steal from GFM, don't trigger styling on underscores in middle of words + text = text.replace(/(\w)_(\w)/g, "$1~E95E$2") // ** GFM ** "~E95E" == escaped "_" + text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, + "$2"); + + return text; +} + + +var _DoBlockQuotes = function(text) { + + /* + text = text.replace(/ + ( // Wrap whole match in $1 + ( + ^[ \t]*>[ \t]? // '>' at the start of a line + .+\n // rest of the first line + (.+\n)* // subsequent consecutive lines + \n* // blanks + )+ + ) + /gm, function(){...}); + */ + + text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, + function(wholeMatch,m1) { + var bq = m1; + + // attacklab: hack around Konqueror 3.5.4 bug: + // "----------bug".replace(/^-/g,"") == "bug" + + bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); // trim one level of quoting + + // attacklab: clean up hack + bq = bq.replace(/~0/g,""); + + bq = bq.replace(/^[ \t]+$/gm,""); // trim whitespace-only lines + bq = _RunBlockGamut(bq); // recurse + + bq = bq.replace(/(^|\n)/g,"$1 "); + // These leading spaces screw with
           content, so we need to fix that:
          +			bq = bq.replace(
          +					/(\s*
          [^\r]+?<\/pre>)/gm,
          +				function(wholeMatch,m1) {
          +					var pre = m1;
          +					// attacklab: hack around Konqueror 3.5.4 bug:
          +					pre = pre.replace(/^  /mg,"~0");
          +					pre = pre.replace(/~0/g,"");
          +					return pre;
          +				});
          +			
          +			return hashBlock("
          \n" + bq + "\n
          "); + }); + return text; +} + + +var _FormParagraphs = function(text) { +// +// Params: +// $text - string to process with html

          tags +// + + // Strip leading and trailing lines: + text = text.replace(/^\n+/g,""); + text = text.replace(/\n+$/g,""); + + var grafs = text.split(/\n{2,}/g); + var grafsOut = new Array(); + + // + // Wrap

          tags. + // + var end = grafs.length; + for (var i=0; i= 0) { + grafsOut.push(str); + } + else if (str.search(/\S/) >= 0) { + str = _RunSpanGamut(str); + str = str.replace(/\n/g,"
          "); // ** CFM ** stolen from Github Flavored Markdown + str = str.replace(/^([ \t]*)/g,"

          "); + str += "

          " + grafsOut.push(str); + } + + } + + // + // Unhashify HTML blocks + // + end = grafsOut.length; + for (var i=0; i= 0) { + var blockText = g_html_blocks[RegExp.$1]; + blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs + grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText); + } + } + + return grafsOut.join("\n\n"); +} + + +var _EncodeAmpsAndAngles = function(text) { +// Smart processing for ampersands and angle brackets that need to be encoded. + + // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: + // http://bumppo.net/projects/amputator/ + text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"); + + // Encode naked <'s + text = text.replace(/<(?![a-z\/?\$!])/gi,"<"); + + return text; +} + + +var _EncodeBackslashEscapes = function(text) { +// +// Parameter: String. +// Returns: The string, with after processing the following backslash +// escape sequences. +// + + // attacklab: The polite way to do this is with the new + // escapeCharacters() function: + // + // text = escapeCharacters(text,"\\",true); + // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); + // + // ...but we're sidestepping its use of the (slow) RegExp constructor + // as an optimization for Firefox. This function gets called a LOT. + + text = text.replace(/\\(\\)/g,escapeCharacters_callback); + text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback); + return text; +} + + +var _DoAutoLinks = function(text) { + + text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"
          $1"); + + // Email addresses: + + /* + text = text.replace(/ + < + (?:mailto:)? + ( + [-.\w]+ + \@ + [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ + ) + > + /gi, _DoAutoLinks_callback()); + */ + text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, + function(wholeMatch,m1) { + return _EncodeEmailAddress( _UnescapeSpecialChars(m1) ); + } + ); + + return text; +} + + +var _EncodeEmailAddress = function(addr) { +// +// Input: an email address, e.g. "foo@example.com" +// +// Output: the email address as a mailto link, with each character +// of the address encoded as either a decimal or hex entity, in +// the hopes of foiling most address harvesting spam bots. E.g.: +// +// foo +// @example.com +// +// Based on a filter by Matthew Wickline, posted to the BBEdit-Talk +// mailing list: +// + + // attacklab: why can't javascript speak hex? + function char2hex(ch) { + var hexDigits = '0123456789ABCDEF'; + var dec = ch.charCodeAt(0); + return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15)); + } + + var encode = [ + function(ch){return "&#"+ch.charCodeAt(0)+";";}, + function(ch){return "&#x"+char2hex(ch)+";";}, + function(ch){return ch;} + ]; + + addr = "mailto:" + addr; + + addr = addr.replace(/./g, function(ch) { + if (ch == "@") { + // this *must* be encoded. I insist. + ch = encode[Math.floor(Math.random()*2)](ch); + } else if (ch !=":") { + // leave ':' alone (to spot mailto: later) + var r = Math.random(); + // roughly 10% raw, 45% hex, 45% dec + ch = ( + r > .9 ? encode[2](ch) : + r > .45 ? encode[1](ch) : + encode[0](ch) + ); + } + return ch; + }); + + addr = "" + addr + ""; + addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part + + return addr; +} + + +var _UnescapeSpecialChars = function(text) { +// +// Swap back in all the special characters we've hidden. +// + text = text.replace(/~E(\d+)E/g, + function(wholeMatch,m1) { + var charCodeToReplace = parseInt(m1); + return String.fromCharCode(charCodeToReplace); + } + ); + return text; +} + + +var _Outdent = function(text) { +// +// Remove one level of line-leading tabs or spaces +// + + // attacklab: hack around Konqueror 3.5.4 bug: + // "----------bug".replace(/^-/g,"") == "bug" + + text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width + + // attacklab: clean up hack + text = text.replace(/~0/g,"") + + return text; +} + +var _Detab = function(text) { +// attacklab: Detab's completely rewritten for speed. +// In perl we could fix it by anchoring the regexp with \G. +// In javascript we're less fortunate. + + // expand first n-1 tabs + text = text.replace(/\t(?=\t)/g," "); // attacklab: g_tab_width + + // replace the nth with two sentinels + text = text.replace(/\t/g,"~A~B"); + + // use the sentinel to anchor our regex so it doesn't explode + text = text.replace(/~B(.+?)~A/g, + function(wholeMatch,m1,m2) { + var leadingText = m1; + var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width + + // there *must* be a better way to do this: + for (var i=0; i 0 ? "in " + in_words : in_words + " ago"; }; + if (diff_in_minutes === 0) { return add_token('less than a minute'); } + if (diff_in_minutes == 1) { return add_token('a minute'); } + if (diff_in_minutes < 45) { return add_token(diff_in_minutes + ' minutes'); } + if (diff_in_minutes < 90) { return add_token('about 1 hour'); } + if (diff_in_minutes < 1440) { return add_token('about ' + Math.floor(diff_in_minutes / 60) + ' hours'); } + if (diff_in_minutes < 2880) { return add_token('1 day'); } + if (diff_in_minutes < 43200) { return add_token(Math.floor(diff_in_minutes / 1440) + ' days'); } + if (diff_in_minutes < 86400) { return add_token('about 1 month'); } + if (diff_in_minutes < 525960) { return add_token(Math.floor(diff_in_minutes / 43200) + ' months'); } + if (diff_in_minutes < 1051199) { return add_token('about 1 year'); } + + return add_token('over ' + Math.floor(diff_in_minutes / 525960) + ' years'); +}; + + +window.parseDate = function(date_str) { + var m = date_str.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z/); + return Date.UTC(m[1],m[2] - 1,m[3],m[4],m[5],m[6]); +}; \ No newline at end of file diff --git a/lib/kick_off.rb b/lib/kick_off.rb new file mode 100644 index 000000000..60553dd3c --- /dev/null +++ b/lib/kick_off.rb @@ -0,0 +1,184 @@ +class KickOff + + def initialize(queuer = Resque) + @queuer = queuer + end + + def deliver_post(post) + send_post_to_neighborhood(post, post.neighborhood) + end + + + def deliver_announcement(post) + # We're only actually delivering anything if the poster is a feed + return if !post.owner.is_a? Feed + + # We're delivering to live subscribers of the feed + recipient_ids = post.owner.live_subscribers.map &:id + + # Send it + recipient_ids.each do |user_id| + enqueue(AnnouncementNotification, post.id, user_id) + end + end + + + def deliver_reply(reply) + # We're delivering a reply to the author of the repliable + repliable_ids = [reply.repliable.user_id] + + # As well as anyone else who replied + repliable_ids += reply.repliable.replies.map &:user_id + + # But not the author of the current reply + repliable_ids.delete(reply.user_id) + + # Only send once to each person + repliable_ids.uniq! + + # Send it + repliable_ids.each do |user_id| + enqueue(ReplyNotification, reply.id, user_id) + end + end + + + def deliver_feed_invite(emails, feed) + # Given some emails + recipients = Array(emails) # it's definitely an array now + + # That don't already exist in the system + + recipients.reject! do |email| + User.exists?(:email => Mail::Address.new(email).address) + end + + # Send invites + recipients.each do |email| + enqueue(FeedInvitation, email, feed.id) + end + end + + + def deliver_group_post(post) + # We're delivering a post to subscribers of the group + recipients = post.group.live_subscribers.map(&:id) + + # Who aren't the poster + recipients.delete(post.user_id) + + # Send it + recipients.each do |user_id| + enqueue(GroupPostNotification, post.id, user_id) + end + end + + + def deliver_user_message(message) + enqueue(MessageNotification, message.id, message.messagable_id) + end + + + def deliver_post_to_community(post) + # Uplifting a post (sending it to whole community) + community = post.community + + neighborhoods = community.neighborhoods + + # Its already been sent to it's neighborhood, don't do it again + neighborhoods.delete(post.neighborhood) + + neighborhoods.each do |neighborhood| + send_post_to_neighborhood(post, neighborhood) + end + end + + + def deliver_clipboard_welcome(half_user) + enqueue(ClipboardWelcome, half_user.id) + end + + + def deliver_user_invite(emails, from_user, message = nil) + # emails is an array + emails = Array(emails) + + emails.reject! do |email| + User.exists?(:email => Mail::Address.new(email).address) + end + + emails.each do |email| + enqueue(Invitation, email, from_user.id, message) + end + end + + + def deliver_post_confirmation(post) + enqueue(PostConfirmation, post.id) + end + + + def deliver_announcement_confirmation(post) + enqueue(AnnouncementConfirmation, post.id) + end + + + def deliver_feed_permission_warning(user, feed) + enqueue(NoFeedPermission, user.id, feed.id) + end + + + def deliver_unknown_address_warning(user) + enqueue(UnknownAddress, user.id) + end + + + def deliver_unknown_user_warning(email) + enqueue(UnknownUser, email) + end + + + def deliver_welcome_email(user) + enqueue(Welcome, user.id) + end + + + def deliver_password_reset(user) + enqueue(PasswordReset, user.id) + end + + + def deliver_admin_question(from_email, message, name) + enqueue(AdminQuestion, from_email, message, name) + end + + + def deliver_daily_bulletin(user, date_string) + enqueue(DailyBulletin, user.id, date_string) + end + + def deliver_feed_owner_welcome(feed) + enqueue(FeedWelcome, feed.id) + end + + private + + def enqueue(*args) + @queuer.enqueue(*args) + end + + def send_post_to_neighborhood(post, neighborhood) + # Send to the people in the neighborhood + # Who receive posts live + recipient_ids = neighborhood.users.receives_posts_live.map(&:id) + + # Who are not the poster + recipient_ids.delete(post.user_id) + + # Send it + recipient_ids.each do |user_id| + enqueue(PostNotification, post.id, user_id) + end + end + +end diff --git a/lib/organizer_data_import.rb b/lib/organizer_data_import.rb new file mode 100644 index 000000000..dd2da4c60 --- /dev/null +++ b/lib/organizer_data_import.rb @@ -0,0 +1,16 @@ +require 'net/https' + +http = Net::HTTP.new('www.google.com', 443) +http.use_ssl = true +path = '/accounts/ClientLogin' + +data = 'accountType=HOSTED_OR_GOOGLE&Email=jason@commonplaceusa.com&Passwd=601845jrB&service=wise' + +headers = { + 'Content-Type' => 'application/x-www-form-urlencoded' +} + +resp, data = http.post(path, data, headers) + +auth_string = data[/Auth=(.*)/, 1] +headers['Authorization'] = "GoogleLogin auth=#{auth_string}" diff --git a/lib/paperclip_processors/cropper.rb b/lib/paperclip_processors/cropper.rb deleted file mode 100644 index 5d87ee55c..000000000 --- a/lib/paperclip_processors/cropper.rb +++ /dev/null @@ -1,19 +0,0 @@ -module Paperclip - class Cropper < Thumbnail - def transformation_command - if crop_command - - crop_command + super - else - super - end - end - - def crop_command - target = @attachment.instance - if target.cropping? - ["-crop","#{target.w.to_i}x#{target.h.to_i}+#{target.x.to_i}+#{target.y.to_i}"] - end - end - end -end diff --git a/app/controllers/feeds/import_controller.rb b/lib/profilers/admin/overview.rb similarity index 100% rename from app/controllers/feeds/import_controller.rb rename to lib/profilers/admin/overview.rb diff --git a/lib/reverse_markdown.rb b/lib/reverse_markdown.rb new file mode 100644 index 000000000..b761d4239 --- /dev/null +++ b/lib/reverse_markdown.rb @@ -0,0 +1,207 @@ +require 'rexml/document' +require 'benchmark' +include REXML +include Benchmark + +# reverse markdown for ruby +# author: JO +# e-mail: xijo@gmx.de +# date: 14.7.2009 +# version: 0.1 +# license: GPL + +# TODO +# - ol numbering is buggy, in fact doesn't matter for markdown code +# - + +class ReverseMarkdown + + # set basic variables: + # - @li_counter: numbering list item (li) tags in an ordered list (ol) + # - @links: hold the links for adding them to the bottom of the @output + # this means 'reference style', please take a look at http://daringfireball.net/projects/markdown/syntax#link + # - @outout: fancy markdown code in here! + # - @indent: control indention level for nested lists + # - @errors: appearing errors, like unknown tags, go into this array + def initialize() + @li_counter = 0 + @links = [] + @output = "" + @indent = 0 + @errors = [] + end + + # Invokes the HTML parsing by using a string. Returns the markdown code in @output. + # To garantuee well-formed xml for REXML a element will be added, but has no effect. + # After parsing all elements, the 'reference style'-links will be inserted. + def parse_string(string) + doc = Document.new("\n"+string+"\n") + root = doc.root + root.elements.each do |element| + parse_element(element, :root) + end + insert_links() + @output + end + + # Parsing an element and its children (recursive) and writing its markdown code to @output + # 1. do indent for nested list items + # 2. add the markdown opening tag for this element + # 3a. if element only contains text, handle it like a text node + # 3b. if element is a container handle its children, which may be text- or element nodes + # 4. finally add the markdown ending tag for this element + def parse_element(element, parent) + name = element.name.to_sym + # 1. + @output << indent() if name.eql?(:li) + # 2. + @output << opening(element, parent) + + # 3a. + if (element.has_text? and element.children.size < 2) + @output << text_node(element, parent) + end + + # 3b. + if element.has_elements? + element.children.each do |child| + # increase indent if nested list + @indent += 1 if element.name=~/(ul|ol)/ and parent.eql?(:li) + + if child.node_type.eql?(:element) + parse_element(child, element.name.to_sym) + else + if parent.eql?(:blockquote) + @output << child.to_s.gsub("\n ", "\n>") + else + @output << child.to_s + end + end + + # decrease indent if end of nested list + @indent -= 1 if element.name=~/(ul|ol)/ and parent.eql?(:li) + end + end + + # 4. + @output << ending(element, parent) + end + + # Returns opening markdown tag for the element. Its parent matters sometimes! + def opening(type, parent) + case type.name.to_sym + when :h1 + "# " + when :li + parent.eql?(:ul) ? " - " : " "+(@li_counter+=1).to_s+". " + when :ol + @li_counter = 0 + "" + when :ul + "" + when :h2 + "## " + when :em + "*" + when :strong + "**" + when :blockquote + # remove leading newline + type.children.first.value = "" + "> " + when :code + parent.eql?(:pre) ? " " : "`" + when :a + "[" + when :img + "![" + when :hr + "----------\n\n" + else + @errors << "unknown start tag: "+type.name.to_s + "" + end + end + + # Returns the closing markdown tag, like opening() + def ending(type, parent) + case type.name.to_sym + when :h1 + " #\n\n" + when :h2 + " ##\n\n" + when :p + parent.eql?(:root) ? "\n\n" : "\n" + when :ol + parent.eql?(:li) ? "" : "\n" + when :ul + parent.eql?(:li) ? "" : "\n" + when :em + "*" + when :strong + "**" + when :li + "" + when :blockquote + "" + when :code + parent.eql?(:pre) ? "" : "`" + when :a + @links << type.attribute('href').to_s + "][" + @links.size.to_s + "] " + when :img + @links << type.attribute('src').to_s + "" + type.attribute('alt').to_s + "][" + @links.size.to_s + "] " + "#{type.attribute('alt')}][#{@links.size}] " + else + @errors << " unknown end tag: "+type.name.to_s + "" + end + end + + # Perform indent: two space, @indent times - quite simple! :) + def indent + str = "" + @indent.times do + str << " " + end + str + end + + # Return the content of element, which should be just text. + # If its a code block to indent of 4 spaces. + # For block quotation add a leading '>' + def text_node(element, parent) + if element.name.to_sym.eql?(:code) and parent.eql?(:pre) + element.text.gsub("\n","\n ") << "\n" + elsif parent.eql?(:blockquote) + element.text.gsub!("\n ","\n>") + else + element.text + end + end + + # Insert the mentioned reference style links. + def insert_links + @output << "\n" + @links.each_index do |index| + @output << " [#{index+1}]: #{@links[index]}\n" + end + end + + # Print out all errors, that occured and have been written to @errors. + def print_errors + @errors.each do |error| + puts error + end + end + + # Perform a benchmark on a given string n-times. + def speed_benchmark(string, n) + initialize() + bm(15) do |test| + test.report("reverse markdown:") { n.times do; parse_string(string); initialize(); end; } + end + end + +end diff --git a/lib/rss_importer.rb b/lib/rss_importer.rb index 2eb4d2815..d47d133fd 100644 --- a/lib/rss_importer.rb +++ b/lib/rss_importer.rb @@ -1,41 +1,19 @@ + class RSSImporter - require 'rss' - def self.perform - - subject_tag = CONFIG['rss_subject_tag'] || 'title' - body_tag = CONFIG['rss_body_tag'] || 'description' - story_url_tag = CONFIG['rss_story_url_tag'] || 'link' - - - Community.find(:all).each do |community| - community.feeds.find(:all, :conditions => ["feed_url != ?", "" ]).each do |feed| - rss = RSS::Parser.parse(open(feed.feed_url).read, false) - rss.items.each { |i| - # Determine if the item has been added yet - - # Pull the values from the Item object, using arbitrary tag names (accessors) - subject = i.send(subject_tag) - # Convert the body to Markdown - mccbean = McBean.fragment i.send(body_tag) - body = mccbean.to_markdown - - feed_id = feed.id - url = i.send(story_url_tag) - - if !RSSAnnouncement.find_by_subject_and_body_and_feed_id(subject,body,feed_id) - o = RSSAnnouncement.new() - o.subject = subject - o.body = body - o.feed_id = feed_id - o.url = url - o.save() + Feed.find(:all, :conditions => ["feed_url != ?", "" ]).each do |feed| + unless (feed.feed_url =~ URI::regexp).nil? + if feed.feed_url.match /^http/ + Exceptional.rescue do + feed.rss_feed.update! end - - } + end end end - end - +end + +# Hack. +class RssImporter < RSSImporter + end diff --git a/lib/serializer.rb b/lib/serializer.rb new file mode 100644 index 000000000..5b957ffcf --- /dev/null +++ b/lib/serializer.rb @@ -0,0 +1,238 @@ +module Serializer + def self.serialize(o, full_dump = false) + o = o.results if o.respond_to?(:results) + o = o.to_a if o.respond_to?(:to_a) + as_json = + case o + + when String + o + + when Fixnum + o.to_s + + when Float + o.to_s + + when Array + o.map {|t| serialize t } + + when NamedPoint + { + "lat" => o.lat, + "lng" => o.lng, + "name" => o.name, + "address" => o.address + } + + when OrganizerDataPoint + { + "address" => o.address, + "status" => o.status, + "lat" => o.lat, + "lng" => o.lng + } + + when User + o.as_api_response(:default) + + when Post + { + "id" => o.id, + "schema" => "posts", + "avatar_url" => o.user.avatar_url(:thumb), + "published_at" => o.created_at.utc, + "url" => "/posts/#{o.id}", + "title" => o.subject, + "author" => o.user.name, + "body" => o.body, + "author_url" => "/users/#{o.user_id}", + "user_id" => o.user_id, + "replies" => serialize(o.replies.to_a), + "last_activity" => o.last_activity.utc, + "links" => { + "author" => "/users/#{o.user_id}", + "replies" => "/posts/#{o.id}/replies", + "self" => "/posts/#{o.id}" + } + } + + when Event + { + "id" => o.id, + "schema" => "events", + "published_at" => o.created_at.utc, + "url" => "/events/#{o.id}", + "occurs_on" => o.date.to_time.utc, + "occurs_at" => o.occurs_at.utc.strftime.gsub("+00:00","Z"), + "date" => o.date.to_time.utc, + "title" => o.name, + "author" => o.owner.name, + "body" => o.description, + "author_url" => "/#{o.owner_type.downcase.pluralize}/#{o.owner_id}", + "messagable_author_url" => (o.owner_type.downcase == "feed") ? "/feeds/#{o.owner_id}/#{o.owner.user_id}" : "/users/#{o.owner_id}", + "messagable_author_name" => (o.owner_type.downcase == "feed") ? o.owner.name : o.owner.first_name, + "tags" => o.tag_list, + "starts_at" => o.start_time.try(:strftime, "%l:%M%P"), + "ends_at" => o.end_time.try(:strftime, "%l:%M%P"), + "venue" => o.venue, + "address" => o.address, + "user_id" => o.user_id, + "feed_id" => o.owner_type == "Feed" ? o.owner_id : nil, + "owner_type" => o.owner_type, + "replies" => serialize(o.replies.to_a), + "links" => { + "replies" => "/events/#{o.id}/replies", + "self" => "/events/#{o.id}", + "author" => "/#{o.owner_type.downcase.pluralize}/#{o.owner_id}" + } + } + + when Announcement + { + "id" => o.id, + "schema" => "announcements", + "url" => "/announcements/#{o.id}", + "published_at" => o.created_at.utc, + "avatar_url" => o.owner.avatar_url(:thumb), + "author_url" => "/#{o.owner_type.downcase.pluralize}/#{o.owner_id}", + "author" => o.owner.name, + "user_id" => o.user_id, + "feed_id" => o.owner_type == "Feed" ? o.owner_id : nil, + "title" => o.subject, + "body" => o.body, + "owner_type" => o.owner_type, + "replies" => serialize(o.replies.to_a), + "links" => { + "replies" => "/announcements/#{o.id}/replies", + "self" => "/announcements/#{o.id}", + "author" => "/#{o.owner_type.downcase.pluralize}/#{o.owner_id}" + } + + } + + when GroupPost + { + "id" => o.id, + "schema" => "group_posts", + "url" => "/group_posts/#{o.id}", + "published_at" => o.created_at.utc, + "author" => o.user.name, + "avatar_url" => o.group.avatar_url(:thumb), + "author_url" => "/users/#{o.user_id}", + "group" => o.group.name, + "group_url" => "/groups/#{o.group_id}", + "user_id" => o.user_id, + "group_id" => o.group_id, + "title" => o.subject, + "body" => o.body, + "replies" => serialize(o.replies.to_a), + "links" => { + "replies" => "/group_posts/#{o.id}/replies", + "author" => "/users/#{o.user_id}", + "group" => "/groups/#{o.group_id}", + "self" => "/group_posts/#{o.id}" + } + } + + when Message + { + "id" => o.id, + "type" => o.messagable_type, + "url" => "/users/#{o.messagable_id}/messages/#{o.id}", + "published_at" => o.created_at.utc, + "user_id" => o.messagable_id, + "user" => o.messagable.name, + "author_id" => o.user_id, + "avatar_url" => o.user.avatar_url(:thumb), + "author" => o.user.name, + "title" => o.subject, + "body" => o.body, + "replies" => serialize(o.replies.to_a), + "links" => { + "replies" => "/messages/#{o.id}/replies", + "author" => "/users/#{o.user_id}", + "self" => "/messages/#{o.id}", + "user" => (o.messagable_type == "User" ? "/users" : "/feeds") + "/#{o.messagable_id}" + } + } + + when Reply + { + "id" => o.id, + "schema" => "replies", + "author" => o.user.name, + "avatar_url" => o.user.avatar_url(:thumb), + "author_url" => "/users/#{o.user_id}", + "author_id" => o.user.id, + "body" => o.body, + "published_at" => o.created_at.utc, + "links" => { + "author" => "/users/#{o.user_id}", + "self" => "/replies/#{o.id}" + } + } + + when Feed + o.as_api_response(:default) + when FeedOwner + { + "id" => o.id, + "user_id" => o.user_id, + "feed_id" => o.feed_id, + "user_name" => o.user.name, + "user_email" => o.user.email, + "links" => { + "self" => "/feeds/#{o.feed_id}/owners/#{o.id}", + "user" => "/users/#{o.user_id}", + "feed" => "/feeds/#{o.feed_id}" + } + } + + when Group + o.as_api_response(:default) + when Account + { + "id" => o.id, + "schema" => "account", + "avatar_url" => o.avatar_url(:normal), + "feed_subscriptions" => o.feed_subscriptions, + "group_subscriptions" => o.group_subscriptions, + "is_admin" => o.is_admin, + "accounts" => o.accounts.map {|a| {:name => a.name, :uid => "#{a.class.name.underscore}_#{a.id}"} }, + "short_name" => o.short_name, + "name" => o.full_name, + "email" => o.email, + "posts" => o.posts, + "events" => o.events, + "feeds" => o.feeds, + "mets" => o.mets, + "announcements" => o.announcements, + "group_posts" => o.group_posts, + "neighborhood" => o.neighborhood, + "interests" => o.interest_list, + "goods" => o.good_list, + "skills" => o.skill_list, + "subscriptions" => o.feeds, + "about" => o.about, + "links" => { + "feed_subscriptions" => "/account/subscriptions/feeds", + "group_subscriptions" => "/account/subscriptions/groups", + "mets" => "/account/mets", + "self" => "/account", + "edit" => "/account/profile", + "inbox" => "/account/inbox", + "sent" => "/account/inbox/sent", + "feed_messages" => "/account/inbox/feeds", + "neighborhoods_posts" => "/neighborhoods/#{o.neighborhood_id}/posts" + } + } + + when Community + o.as_api_response(:default) + end + + as_json + end +end + diff --git a/lib/smtp_api_header.rb b/lib/smtp_api_header.rb deleted file mode 100644 index 7fa637d9d..000000000 --- a/lib/smtp_api_header.rb +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/ruby -# Version 1.0 -# Last Updated 6/22/2009 -require 'json' - -class SmtpApiHeader - - def initialize() - @data = {} - end - - def addTo(to) - @data['to'] ||= [] - @data['to'] += to.kind_of?(Array) ? to : [to] - end - - def addSubVal(var, val) - if not @data['sub'] - @data['sub'] = {} - end - if val.instance_of?(Array) - @data['sub'][var] = val - else - @data['sub'][var] = [val] - end - end - - def setUniqueArgs(val) - if val.instance_of?(Hash) - @data['unique_args'] = val - end - end - - def setCategory(cat) - - @data['category'] = cat - end - - def addFilterSetting(fltr, setting, val) - if not @data['filters'] - @data['filters'] = {} - end - if not @data['filters'][fltr] - @data['filters'][fltr] = {} - end - if not @data['filters'][fltr]['settings'] - @data['filters'][fltr]['settings'] = {} - end - @data['filters'][fltr]['settings'][setting] = val - end - - def asJSON() - json = JSON.generate @data - return json.gsub(/(["\]}])([,:])(["\[{])/, '\\1\\2 \\3') - end - - def as_string() - json = asJSON() - str = 'X-SMTPAPI: %s' % json.gsub(/(.{1,72})( +|$\n?)|(.{1,72})/,"\\1\\3\n") - return str - end - -end diff --git a/lib/statistics_aggregator.rb b/lib/statistics_aggregator.rb new file mode 100644 index 000000000..7bd5387c6 --- /dev/null +++ b/lib/statistics_aggregator.rb @@ -0,0 +1,144 @@ +class StatisticsAggregator + + AVERAGE_DAYS = 7.0 + HISTORICAL_DAYS = 13 + DATA_UNAVAILABLE_MESSAGE = "N/A" + + MailGunStatistics = RestClient::Resource.new "https://api:#{$MailgunAPIToken}@api.mailgun.net/v2/#{$MailgunAPIDomain}/stats" + + NETWORK_SIZE = User.count + + def self.historical_statistics + ActiveSupport::JSON.decode(Resque.redis.get "statistics:historical") + end + + def self.statistics_for_community_between_days_ago(c, yday, tday) + community_average_days = StatisticsAggregator.average_days(c) || AVERAGE_DAYS + result = {} + result[:total_users] = User.between(StatisticsAggregator.first_day(c).to_datetime,tday.days.ago).select { |u| u.community == c}.count + result[:user_gains_today] = User.between(yday.days.ago.utc, tday.days.ago).select {|u| u.community == c}.count + + result[:percentage_of_field] = (result[:total_users] / c.households.to_f).round(4) + + result[:neighborhood_posts_today] = c.posts.between(yday.days.ago, tday.days.ago).count + result[:average_neighborhood_posts_daily] = (c.posts.between(community_average_days.days.ago, tday.days.ago).count / community_average_days).round(6) + + result[:announcements_today] = c.announcements.between(yday.days.ago, tday.days.ago).count + result[:average_announcements_daily] = (c.announcements.between(community_average_days.days.ago, tday.days.ago).count / community_average_days).round(6) + + result[:events_today] = c.events.between(yday.days.ago, tday.days.ago).count + result[:average_events_daily] = (c.events.between(community_average_days.days.ago, tday.days.ago).count / community_average_days).round(6) + + result[:private_messages_today] = Message.between(yday.days.ago, tday.days.ago).select { |m| m.user.community == c}.count + result[:average_private_messages_daily] = (Message.between(community_average_days.days.ago, tday.days.ago).select { |m| m.user.community == c}.count / community_average_days).round(6) + + result[:replies_today] = Reply.between(yday.day.ago, tday.days.ago).select { |r| r.user.community == c }.count + result[:average_replies_daily] = (Reply.between(community_average_days.days.ago, tday.days.ago).select { |r| r.user.community == c }.count / community_average_days).round(6) + + result[:group_posts_today] = c.group_posts.select { |p| p.between?(yday.days.ago, tday.days.ago) }.count + result[:average_group_posts_daily] = (c.group_posts.select { |p| p.between?(community_average_days.days.ago, tday.days.ago) }.count / community_average_days).round(6) + + result + end + + def self.historical_statistics_for_community(community, days) + result = [] + day_counter = 0 + # We only need the first day's results as long as a result set exists + if StatisticsAggregator.historical_statistics.count >= days + result = StatisticsAggregator.historical_statistics[community.name] + result.delete(result.last) + result.insert(0, StatisticsAggregator.statistics_for_community_between_days_ago(community, 1, 0)) + else + while day_counter <= days do + # Generate statistics between day_counter+1.days.ago and day_counter.days.ago + result[day_counter] = StatisticsAggregator.statistics_for_community_between_days_ago(community, day_counter + 1, day_counter) + day_counter += 1 + end + end + result + end + + def self.perform + # Prepare to access Google Analytics + Garb::Session.login($GoogleAnalyticsAPILogin, $GoogleAnalyticsAPIPassword) + profile = Garb::Management::Profile.all.detect {|p| p.web_property_id == $GoogleAnalyticsPropertyID} + # Aggregate platform-wide statistics (for every community) + communities = {} + historical = {} + overall = {} + Community.all.sort.select{|c| c.core and c.posts.present?}.each do |c| + historical[c.name] = StatisticsAggregator.historical_statistics_for_community(c, HISTORICAL_DAYS) + communities[c.name] = historical[c.name][0] + end + + overall[:page_views_today] = GoogleAnalytics::Pageviews.results(profile, :start_date => 1.day.ago, :end_date => 0.days.ago).first.pageviews.to_i + overall[:page_views_this_week] = GoogleAnalytics::Pageviews.results(profile, :start_date => 7.days.ago, :end_date => 0.days.ago).first.pageviews.to_i + overall[:average_page_views_per_day] = (GoogleAnalytics::Pageviews.results(profile, :start_date => AVERAGE_DAYS.day.ago, :end_date => 0.days.ago).first.pageviews.to_i / AVERAGE_DAYS).round(2) + + overall[:unique_platform_visits_today] = DATA_UNAVAILABLE_MESSAGE + overall[:average_number_of_visitors_daily] = DATA_UNAVAILABLE_MESSAGE + + overall[:network_engagement_daily] = DATA_UNAVAILABLE_MESSAGE + overall[:network_engagement_weekly] = DATA_UNAVAILABLE_MESSAGE + overall[:average_time_spent_on_platform] = DATA_UNAVAILABLE_MESSAGE + + overall[:average_percent_of_emails_read] = (StatisticsAggregator.email_open_pctg()) + + overall[:percent_of_network_adding_data] = DATA_UNAVAILABLE_MESSAGE + + overall[:average_number_of_profile_adds_daily] = DATA_UNAVAILABLE_MESSAGE + overall[:percentage_with_profile_photos] = DATA_UNAVAILABLE_MESSAGE + overall[:percentage_with_tags_in_profile] = DATA_UNAVAILABLE_MESSAGE + + overall[:number_of_profile_clicks] = DATA_UNAVAILABLE_MESSAGE + overall[:average_profile_clicks_per_visitor] = DATA_UNAVAILABLE_MESSAGE + overall[:number_of_searches] = DATA_UNAVAILABLE_MESSAGE + overall[:average_number_of_searches_per_visitor] = DATA_UNAVAILABLE_MESSAGE + + overall[:average_load_time] = DATA_UNAVAILABLE_MESSAGE + + Resque.redis.set COMMUNITY_STATISTICS_BUCKET, ActiveSupport::JSON.encode(communities) + Resque.redis.set OVERALL_STATISTICS_BUCKET, ActiveSupport::JSON.encode(overall) + Resque.redis.set HISTORICAL_STATISTICS_BUCKET, ActiveSupport::JSON.encode(historical) + end + + def self.emails_opened + emails_opened = 0 + params = {} + params[:event] = 'opened' + result = ActiveSupport::JSON.decode(MailGunStatistics.get({:params => params})) + result['items'].each do |obj| + emails_opened += obj['total_count'].to_i + end + return emails_opened + end + + def self.emails_sent + emails_sent = 0 + params = {} + params[:event] = 'sent' + result = ActiveSupport::JSON.decode(MailGunStatistics.get({:params => params})) + result['items'].each do |obj| + emails_sent += obj['total_count'].to_i + end + return emails_sent + end + + def self.email_open_pctg + return "#{(100 * StatisticsAggregator.emails_opened.to_f / StatisticsAggregator.emails_sent.to_f).round(2)}%" + end + + def self.first_day(community) + community.posts.last.created_at.to_date + end + + def self.average_days(community) + r = 0 + StatisticsAggregator.first_day(community).upto(DateTime.now) do |day| + r += 1 + end + r.to_f + end + +end diff --git a/app/views/email_parse/parse.erb b/lib/tasks/.gitkeep similarity index 100% rename from app/views/email_parse/parse.erb rename to lib/tasks/.gitkeep diff --git a/lib/tasks/_heroku/deploy.rake b/lib/tasks/_heroku/deploy.rake new file mode 100644 index 000000000..d191d1b69 --- /dev/null +++ b/lib/tasks/_heroku/deploy.rake @@ -0,0 +1,50 @@ +ENVIRONMENTS = { + :production => "commonplace", + :staging => "commonplace-staging" +} + +namespace :deploy do + ENVIRONMENTS.keys.each do |env| + desc "Deploy to #{env}" + task env do + current_branch = `git branch | grep ^* | awk '{ print $2 }'`.strip + + Rake::Task['deploy:before_deploy'].invoke(env, current_branch) + Rake::Task['deploy:update_code'].invoke(env, current_branch) + Rake::Task['deploy:after_deploy'].invoke(env, current_branch) + end + end + + task :before_deploy, :env, :branch do |t, args| + puts "Making sure we are in sync with Github" + Rake::Task['git:push'].invoke(args[:branch]) + puts "Deploying #{args[:branch]} to #{args[:env]}" + end + + task :after_deploy, :env, :branch do |t, args| + puts "Deployment Complete" + end + + task :update_code, :env, :branch do |t, args| + FileUtils.cd Rails.root do + puts "Updating #{ENVIRONMENTS[args[:env]]} with branch #{args[:branch]}" + `git push +github:#{ENVIRONMENTS[args[:env]]} +#{args[:remote]}:#{args[:branch]}` + end + end +end + +namespace :git do + desc "Push to Github" + task :push do + current_branch = `git branch | grep ^* | awk '{ print $2 }'`.strip + `git push github #{current_branch}` + end + + desc "Add Github and Heroku remotes" + task :setup do + `git remote add github git@github.com:commonplaceusa/commonplace.git` + ENVIRONMENTS.each do |env,remote| + `git remote add #{env} git@heroku.com:#{remote}.git` + end + end +end diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake new file mode 100644 index 000000000..8d13a522a --- /dev/null +++ b/lib/tasks/cache.rake @@ -0,0 +1,7 @@ + +namespace :cache do + desc 'Clear cache' + task :clear => :environment do + Rails.cache.clear + end +end diff --git a/lib/tasks/cron.rake b/lib/tasks/cron.rake new file mode 100644 index 000000000..b458f012f --- /dev/null +++ b/lib/tasks/cron.rake @@ -0,0 +1,9 @@ +desc "This task is called by the Heroku cron add-on" +task :cron => :environment do + if Time.now.hour == 0 # run at midnight + + # Reset the number of e-mails that have been sent to a user + ActiveRecord::Base.establish_connection + ActiveRecord::Base.connection.execute("UPDATE users SET emails_sent = 0 WHERE 1=1") + end +end diff --git a/lib/tasks/git/git.rake b/lib/tasks/git/git.rake new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/lib/tasks/git/git.rake @@ -0,0 +1 @@ + diff --git a/lib/tasks/resque.rake b/lib/tasks/resque.rake index c1349a868..97b52269c 100644 --- a/lib/tasks/resque.rake +++ b/lib/tasks/resque.rake @@ -3,10 +3,20 @@ require 'resque_scheduler/tasks' task "resque:scheduler_setup" => :environment # load the env so we know about the job classes -task "resque:setup" => :environment +task "resque:setup" => :environment do + Resque.before_fork do |job| + ActiveRecord::Base.establish_connection + end +end namespace :resque do task :restart_workers => :environment do system("sudo god restart resque-workers") end -end \ No newline at end of file + + task :free_workers => :environment do + Resque.workers.each do |w| + w.unregister_worker + end + end +end diff --git a/lib/tasks/sanitize_production_data.rake b/lib/tasks/sanitize_production_data.rake new file mode 100644 index 000000000..bc46a01b6 --- /dev/null +++ b/lib/tasks/sanitize_production_data.rake @@ -0,0 +1,4 @@ +desc "Will sanitize the current database (removes avatar_file_name, ..)" +task "db:sanitize" => :environment do + User.update_all("avatar_file_name = null") +end diff --git a/lib/tilt/i18n_template.rb b/lib/tilt/i18n_template.rb new file mode 100644 index 000000000..7af2a4ebf --- /dev/null +++ b/lib/tilt/i18n_template.rb @@ -0,0 +1,20 @@ +module Tilt + + class I18nTemplate < Template + def self.default_mime_type + 'application/javascript' + end + + def prepare + end + + def evaluate(scope, locals, &block) + <<-END + (function() { + this.I18N || (this.I18N = {}); + this.I18N[#{scope.logical_path.inspect}] = #{data}; + })(this); + END + end + end +end diff --git a/lib/tilt/mustache_template.rb b/lib/tilt/mustache_template.rb new file mode 100644 index 000000000..a97db1d6a --- /dev/null +++ b/lib/tilt/mustache_template.rb @@ -0,0 +1,31 @@ +module Tilt + # todo: automatically add -tpl suffix as extension is removed, so as to differentiate files. + class MustacheTemplate < Template + def self.default_mime_type + 'application/javascript' + end + + # Do whatever preparation is necessary to setup the underlying template + # engine. Called immediately after template data is loaded. Instance + # variables set in this method are available when #evaluate is called. + # + # Subclasses must provide an implementation of this method. + def prepare + end + + # Execute the compiled template and return the result string. Template + # evaluation is guaranteed to be performed in the scope object with the + # locals specified and with support for yielding to the block. + # + # This method is only used by source generating templates. Subclasses that + # override render() may not support all features. + def evaluate(scope, locals, &block) + <<-END + (function() { + this.Templates || (this.Templates = {}); + this.Templates[#{scope.logical_path.inspect.gsub("/",".")}] = #{data.inspect}; + })(); + END + end + end +end diff --git a/lib/twitter_importer.rb b/lib/twitter_importer.rb deleted file mode 100644 index 1fc5c258a..000000000 --- a/lib/twitter_importer.rb +++ /dev/null @@ -1,25 +0,0 @@ -class TwitterImporter - require 'twitter' - - def self.perform - Community.find(:all).each do |community| - community.feeds.find(:all, :conditions => ["twitter_name != ?", "" ]).each do |feed| - Twitter.user_timeline(feed.twitter_name).each do |tweet| - # Convert the body to Markdown - mccbean = McBean.fragment tweet.text - body = mccbean.to_markdown - if !TwitterAnnouncement.find_by_subject_and_body_and_feed_id(tweet.user.screen_name,body,feed.id) - o = TwitterAnnouncement.new() - o.subject = tweet.user.screen_name - o.body = body - o.feed_id = feed.id - o.url = "http://twitter.com/" + tweet.user.screen_name + "/statuses/" + tweet.id_str - o.save() - end - end - end - end - - end - -end \ No newline at end of file diff --git a/log/server.log b/log/server.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/mail/.gitignore b/mail/.gitignore new file mode 100644 index 000000000..54b726158 --- /dev/null +++ b/mail/.gitignore @@ -0,0 +1 @@ +config.rb diff --git a/mail/admin_question.rb b/mail/admin_question.rb new file mode 100644 index 000000000..60133bd93 --- /dev/null +++ b/mail/admin_question.rb @@ -0,0 +1,36 @@ +class AdminQuestion < MailBase + + def initialize(sender, question, name) + @sender, @question, @name = sender, question, name + end + + def to + "faq@commonplace.zendesk.com" + #"jason@commonplaceusa.com" + end + + def tag + "question" + end + + def sender + @sender + end + + def name + @name + end + + def question + @question + end + + def subject + "New CommonPlace Question" + end + + def from + "#{name} <#{sender}>" + end + +end diff --git a/mail/announcement_confirmation.rb b/mail/announcement_confirmation.rb new file mode 100644 index 000000000..5bc4b1cb9 --- /dev/null +++ b/mail/announcement_confirmation.rb @@ -0,0 +1,39 @@ +class AnnouncementConfirmation < MailBase + + def initialize(announcement_id) + @announcement = Announcement.find(announcement_id) + end + + def announcement + @announcement + end + + def owner + @announcement.owner + end + + def user + owner.user + end + + def short_poster_name + @announcement.owner.user.first_name + end + + def announcement_url + url("/announcements/#{announcement.id}") + end + + def community + announcement.community + end + + def community_name + community.name + end + + def tag + 'announcement_confirmation' + end + +end diff --git a/mail/announcement_notification.rb b/mail/announcement_notification.rb new file mode 100644 index 000000000..ba76debe3 --- /dev/null +++ b/mail/announcement_notification.rb @@ -0,0 +1,86 @@ +class AnnouncementNotification < PostNotification + + def initialize(announcement_id, user_id) + @announcement, @user = Announcement.find(announcement_id), User.find(user_id) + end + + def subject + "#{poster_name} just posted an announcement to CommonPlace" + end + + def announcement + @announcement + end + + def reply_to + "reply+announcement_#{announcement.id}@ourcommonplace.com" + end + + def user + @user + end + + def poster + @announcement.owner + end + + def community + @announcement.community + end + + def community_name + community.name + end + + def poster_name + poster.name + end + + def short_poster_name + case poster + when User then poster.first_name + when Feed then poster.name + end + end + + def announcement_url + url("/announcements/#{announcement.id}") + end + + def new_message_url + case poster + when User then url("/users/#{poster.id}/messages/new") + when Feed then url("/feeds/#{poster.id}/messages/new") + else root_url + end + end + + def announcement_subject + announcement.subject + end + + def announcement_body + markdown(announcement.body) + end + + def poster_avatar_url + poster.avatar_url + end + + def poster_url + case poster + when User then url("/users/#{poster.id}") + when Feed then url("/feeds/#{poster.id}") + else root_url + end + end + + def user_name + user.name + end + + def tag + 'announcement' + end + +end diff --git a/mail/clipboard_welcome.rb b/mail/clipboard_welcome.rb new file mode 100644 index 000000000..f57a31fc9 --- /dev/null +++ b/mail/clipboard_welcome.rb @@ -0,0 +1,48 @@ +class ClipboardWelcome < MailBase + + def initialize(user_id) + @user = HalfUser.find(user_id) + @community = user.community + end + + def user + @user + end + + def community + @community + end + + def community_name + community.name + end + + def community_slug + community.slug + end + + def short_user_name + user.first_name + end + + def organizer_email + community.organizer_email + end + + def organizer_name + community.organizer_name + end + + def subject + "Welcome to #{community_name}'s CommonPlace!" + end + + def single_access_login + url("/gatekeeper?husat=#{user.single_access_token}") + end + + def tag + 'clipboard_welcome' + end + +end diff --git a/mail/daily_bulletin.rb b/mail/daily_bulletin.rb new file mode 100644 index 000000000..eaf7f1cc1 --- /dev/null +++ b/mail/daily_bulletin.rb @@ -0,0 +1,109 @@ +class DailyBulletin < MailBase + + def initialize(user_id, date) + @user, @date = User.find(user_id), DateTime.parse(date) + end + + def user + @user + end + + def logo_url + asset_url("logo2.png") + end + + def reply_button_url + asset_url("reply-button.png") + end + + def invite_them_now_button_url + asset_url("invite-them-now-button.png") + end + + def short_user_name + @user.first_name + end + + def subject + "The #{community_name} CommonPlace Daily Bulletin" + end + + def header_text + @date.strftime("%A, %B %d, %Y") + end + + def community + @user.community + end + + def community_name + community.name + end + + def deliver? + posts_present || announcements_present || events_present + end + + def posts_present + posts.present? + end + + def yesterday + @date.advance(:days => -1) + end + + def posts + @posts ||= community.posts_for_user(@user).between(yesterday,@date).map do |post| + Serializer::serialize(post).tap do |post| + post['replies'].each {|reply| + reply['published_at'] = reply['published_at'].strftime("%l:%M%P") + reply['avatar_url'] = asset_url(reply['avatar_url']) + } + post['avatar_url'] = asset_url(post['avatar_url']) + post['url'] = url(post['url']) + post['new_message_url'] = url(post['author_url'] + "/messages/new") + end + end + end + + def announcements_present + announcements.present? + end + + def announcements + @announcements ||= community.announcements.between(yesterday, @date).map do |announcement| + Serializer::serialize(announcement).tap do |announcement| + announcement['replies'].each {|reply| + reply['published_at'] = reply['published_at'].strftime("%l:%M%P") + reply['avatar_url'] = asset_url(reply['avatar_url']) + } + announcement['avatar_url'] = asset_url(announcement['avatar_url']) + announcement['url'] = url(announcement['url']) + end + end + end + + def events_present + events.present? + end + + def events + @events ||= community.events.between(@date, @date.advance(:weeks => 1)).map do |event| + + Serializer::serialize(event).tap do |event| + event['replies'].each {|reply| + reply['published_at'] = reply['published_at'].strftime("%l:%M%P") + reply['avatar_url'] = asset_url(reply['avatar_url']) + } + event["short_month"] = event['occurs_on'].strftime("%b") + event["day"] = event['occurs_on'].strftime("%d") + event['url'] = url(event['url']) + end + end + end + + def tag + 'daily_bulletin' + end + +end diff --git a/mail/feed_invitation.rb b/mail/feed_invitation.rb new file mode 100644 index 000000000..6d7cd7d6c --- /dev/null +++ b/mail/feed_invitation.rb @@ -0,0 +1,22 @@ +class FeedInvitation < Invitation + + def initialize(email, feed_id, message = nil) + @to = email + @feed = Feed.find(feed_id) + @inviter = @feed.user + @message = message.present? ? message : nil + end + + def feed + @feed + end + + def feed_name + @feed.name + end + + def tag + 'feed_invitation' + end + +end diff --git a/mail/feed_welcome.rb b/mail/feed_welcome.rb new file mode 100644 index 000000000..2df020cc3 --- /dev/null +++ b/mail/feed_welcome.rb @@ -0,0 +1,57 @@ +class FeedWelcome < MailBase + + def initialize(feed_id) + @feed = Feed.find(feed_id) + @user = User.find(feed.user_id) + @community = user.community + end + + def user + @user + end + + def community + @community + end + + def feed + @feed + end + + def town_name + community.name + end + + def short_user_name + user.first_name + end + + def feed_name + feed.name.titlecase + end + + def feed_url + feed_profile(feed) + end + + def feed_email_address + "#{feed.slug}@OurCommonPlace.com" + end + + def organizer_email + community.organizer_email + end + + def organizer_name + community.organizer_name + end + + def subject + "Your #{feed_name} feed on The #{town_name} CommonPlace" + end + + def tag + "feed_welcome" + end + +end diff --git a/mail/group_post_notification.rb b/mail/group_post_notification.rb new file mode 100644 index 000000000..0850b3323 --- /dev/null +++ b/mail/group_post_notification.rb @@ -0,0 +1,75 @@ +class GroupPostNotification < PostNotification + + def initialize(post_id, user_id) + @post, @user = GroupPost.find(post_id), User.find(user_id) + end + + def group_name + group.name + end + + def group + post.group + end + + def post + @post + end + + def subject + "#{poster_name} just posted to #{group_name}" + end + + def poster + @post.user + end + + def user + @user + end + + def poster_name + poster.name + end + + def community_name + community.name + end + + def community + user.community + end + + def short_poster_name + poster.first_name + end + + def post_url + url("/group_posts/#{post.id}") + end + + def post_subject + post.subject + end + + def post_body + markdown(post.body) + end + + def poster_avatar_url + asset_url(poster.avatar_url(:thumb)) + end + + def group_avatar_url + asset_url(group.avatar_url(:thumb)) + end + + def user_name + user.name + end + + def tag + 'group_post' + end + +end diff --git a/mail/help_a_neighbor_out.rb b/mail/help_a_neighbor_out.rb new file mode 100644 index 000000000..f6324075c --- /dev/null +++ b/mail/help_a_neighbor_out.rb @@ -0,0 +1,109 @@ +class HelpANeighborOut < MailBase + + def initialize(user_id, date) + @user, @date = User.find(user_id), DateTime.parse(date) + end + + def user + @user + end + + def logo_url + asset_url("logo2.png") + end + + def reply_button_url + asset_url("reply-button.png") + end + + def invite_them_now_button_url + asset_url("invite-them-now-button.png") + end + + def short_user_name + @user.first_name + end + + def subject + "The #{community_name} CommonPlace Daily Bulletin" + end + + def header_text + @date.strftime("%A, %B %d, %Y") + end + + def community + @user.community + end + + def community_name + community.name + end + + def deliver? + posts_present || announcements_present || events_present + end + + def posts_present + posts.present? + end + + def yesterday + @date.advance(:days => -1) + end + + def posts + @posts ||= community.posts_for_user(@user).between(yesterday,@date).map do |post| + Serializer::serialize(post).tap do |post| + post['replies'].each {|reply| + reply['published_at'] = reply['published_at'].strftime("%l:%M%P") + reply['avatar_url'] = asset_url(reply['avatar_url']) + } + post['avatar_url'] = asset_url(post['avatar_url']) + post['url'] = url(post['url']) + post['new_message_url'] = url(post['author_url'] + "/messages/new") + end + end + end + + def announcements_present + announcements.present? + end + + def announcements + @announcements ||= community.announcements.between(yesterday, @date).map do |announcement| + Serializer::serialize(announcement).tap do |announcement| + announcement['replies'].each {|reply| + reply['published_at'] = reply['published_at'].strftime("%l:%M%P") + reply['avatar_url'] = asset_url(reply['avatar_url']) + } + announcement['avatar_url'] = asset_url(announcement['avatar_url']) + announcement['url'] = url(announcement['url']) + end + end + end + + def events_present + events.present? + end + + def events + @events ||= community.events.between(@date, @date.advance(:weeks => 1)).map do |event| + + Serializer::serialize(event).tap do |event| + event['replies'].each {|reply| + reply['published_at'] = reply['published_at'].strftime("%l:%M%P") + reply['avatar_url'] = asset_url(reply['avatar_url']) + } + event["short_month"] = event['occurs_on'].strftime("%b") + event["day"] = event['occurs_on'].strftime("%d") + event['url'] = url(event['url']) + end + end + end + + def tag + 'daily_bulletin' + end +end + diff --git a/mail/invitation.rb b/mail/invitation.rb new file mode 100644 index 000000000..fc526c657 --- /dev/null +++ b/mail/invitation.rb @@ -0,0 +1,53 @@ +class Invitation < MailBase + + def initialize(email, inviter_id, message = nil) + @to = email + @inviter = User.find(inviter_id) + @message = message.present? ? message : nil + end + + def subject + "#{inviter_name} invites you to #{community_name}'s CommonPlace" + end + + def to + @to + end + + def inviter + @inviter + end + + def message + @message + end + + def community + inviter.community + end + + def community_name + community.name + end + + def inviter_name + inviter.name + end + + def short_inviter_name + inviter.first_name + end + + def organizer_name + community.organizer_name + end + + def organizer_email + community.organizer_email + end + + def tag + 'invitation' + end + +end diff --git a/mail/mail_base.rb b/mail/mail_base.rb new file mode 100644 index 000000000..40decbcea --- /dev/null +++ b/mail/mail_base.rb @@ -0,0 +1,144 @@ +require 'mustache' +require 'premailer' +require 'sass' + +Mail.defaults do + delivery_method($MailDeliveryMethod, + $MailDeliveryOptions) +end + +class MailBase < Mustache + include MailUrls + + def self.underscore(classified = name) + classified = name if classified.to_s.empty? + classified = superclass.name if classified.to_s.empty? + + string = classified.dup.split("#{view_namespace}::").last + + string.split('::').map do |part| + part[0] = part[0].chr.downcase + part.gsub(/[A-Z]/) { |s| "_#{s.downcase}"} + end.join('/') + end + + self.template_path = File.dirname(__FILE__) + '/templates' + + def underscored_name + MailBase.underscore(self.class.name) + end + + def text + @text ||= YAML.load_file(File.join(File.dirname(__FILE__), "text", "#{community.locale}.yml"))[self.underscored_name] + end + + def self.render_html(*args, &block) + if block_given? + new(*args, &block).render + else + self.render(*args) + end + end + + def community + end + + def t + lambda do |key| + text[key] ? render(text[key]) : key + end + end + + def render_html(*args) + Premailer.new(render(*args), :with_html_string => true, :inputencoding => 'UTF-8', :replace_html_entities => true).to_inline_css + end + + def styles + style_file = self.class.ancestors.map { |klass| + File.join(File.dirname(__FILE__), "stylesheets/#{MailBase.underscore(klass.name)}.scss") + }.find {|filename| File.exist?(filename) } + + Sass::Engine.for_file(style_file, :syntax => :scss).render if style_file + end + + def markdown(text = "") + Redcarpet.new(text).to_html + end + + def from + "#{community.name} CommonPlace " + end + + def reply_to + nil + end + + def to + user.email + end + + def subject + "Notification from CommonPlace" + end + + def tag + "administrative" + end + + def deliver? + unless limited? + true + else + increase_email_count + meets_limitation_requirement + end + end + + def limited? + false + #user.emails_are_limited? + end + + def increase_email_count + user.emails_sent += 1 + user.save + end + + def meets_limitation_requirement + user.emails_sent <= 3 + end + + def deliver + if deliver? + mail = Mail.deliver(:to => self.to, + :from => self.from, + :reply_to => self.reply_to, + :subject => self.subject, + :content_type => "text/html", + :body => self.render_html, + :charset => 'UTF-8', + :headers => { + "Precedence" => "list", + "Auto-Submitted" => "auto-generated", + "X-Campaign-Id" => community ? community.slug : "administrative", + "X-Mailgun-Tag" => self.tag + }) + end + end + + def self.queue + :notifications + end + + def self.perform(*args) + new(*args).deliver + end + + def self.after_perform_heroku(*args) + ActiveRecord::Base.connection.disconnect! + end + + def self.on_failure_heroku(e, *args) + ActiveRecord::Base.connection.disconnect! + end +end diff --git a/mail/mail_urls.rb b/mail/mail_urls.rb new file mode 100644 index 000000000..4f2ed0861 --- /dev/null +++ b/mail/mail_urls.rb @@ -0,0 +1,79 @@ +module MailUrls + + def url(path) + if path.start_with?("http") + path + else + if Rails.env.development? + "http://localhost:5000" + path + else + "https://www.ourcommonplace.com" + path + end + end + end + + def asset_url(path) + if path.start_with?("http") + path + else + "https://s3.amazonaws.com/commonplace-mail-assets-production/#{path}" + end + end + + def subscribe_url + url("/feeds") + end + + def new_event_url + url("/new-event") + end + + def new_announcement_url + url("/new-post") + end + + def new_post_url + url("/new-post") + end + + def new_invites_url + url("/invites/new") + end + + def new_feed_url + url("/feed_registrations/new") + end + + def new_group_post_url + url("/new-group-post") + end + + def starter_site_url + "http://commonplaceusa.com" + end + + def root_url + url("/" + community.slug) + end + + def logo_url + asset_url("logo.png") + end + + def settings_url + url("/account/edit") + end + + def faq_url + url("/faq") + end + + def feed_profile(feed) + url("/pages/#{feed.slug}") + end + + def inbox_url + url("/inbox") + end + +end diff --git a/mail/message_notification.rb b/mail/message_notification.rb new file mode 100644 index 000000000..8fb56f99f --- /dev/null +++ b/mail/message_notification.rb @@ -0,0 +1,75 @@ +class MessageNotification < PostNotification + + def initialize(message_id, user_id) + @message, @user = Message.find(message_id), User.find(user_id) + end + + def limited? + false + end + + def subject + "#{sender_name} just sent you a private message through CommonPlace" + end + + def message + @message + end + + def sender + message.user + end + + def reply_to + "reply+message_#{message.id}@ourcommonplace.com" + end + + def community + user.community + end + + def community_name + community.name + end + + def sender_name + sender.name + end + + def short_sender_name + sender.first_name + end + + def message_url + inbox_url + end + + def new_message_url + url("/users/#{sender.id}/messages/new") + end + + def message_subject + message.subject + end + + def message_body + markdown(message.body) + end + + def sender_avatar_url + sender.avatar_url + end + + def sender_url + url("/users/#{sender.id}") + end + + def user_name + user.name + end + + def tag + 'message' + end + +end diff --git a/mail/no_feed_permission.rb b/mail/no_feed_permission.rb new file mode 100644 index 000000000..8987e2824 --- /dev/null +++ b/mail/no_feed_permission.rb @@ -0,0 +1,39 @@ +class NoFeedPermission < MailBase + + def initialize(user_id, feed_id) + @user, @feed = User.find(user_id), Feed.find(feed_id) + end + + def subject + "CommonPlace announcement did not go through" + end + + def user + @user + end + + def feed + @feed + end + + def feed_name + @feed.name + end + + def community + @user.community + end + + def community_name + community.name + end + + def organizer_email + community.organizer_email + end + + def tag + 'no_permissions' + end + +end diff --git a/mail/password_reset.rb b/mail/password_reset.rb new file mode 100644 index 000000000..811c69fc6 --- /dev/null +++ b/mail/password_reset.rb @@ -0,0 +1,39 @@ +class PasswordReset < MailBase + + def initialize(user_id) + @user = User.find(user_id) + end + + def subject + "CommonPlace password reset" + end + + def from + "passwords@commonplaceusa.com" + end + + def user + @user + end + + def user_name + @user.full_name + end + + def reset_url + url("/users/password/edit?reset_password_token=#{@user.reset_password_token}") + end + + def community_name + community.name + end + + def community + user.community + end + + def tag + 'password_reset' + end + +end diff --git a/mail/post_confirmation.rb b/mail/post_confirmation.rb new file mode 100644 index 000000000..528ab0993 --- /dev/null +++ b/mail/post_confirmation.rb @@ -0,0 +1,39 @@ +class PostConfirmation < MailBase + + def initialize(post_id) + @post = Post.find(post_id) + end + + def subject + "Your post to CommonPlace" + end + + def post + @post + end + + def user + @user ||= post.user + end + + def short_poster_name + user.first_name + end + + def post_url + url("/posts/#{post.id}") + end + + def community + post.community + end + + def community_name + community.name + end + + def tag + 'post_confirmation' + end + +end diff --git a/mail/post_notification.rb b/mail/post_notification.rb new file mode 100644 index 000000000..9640b9757 --- /dev/null +++ b/mail/post_notification.rb @@ -0,0 +1,83 @@ +class PostNotification < MailBase + + def initialize(post_id, user_id) + @post, @user = Post.find(post_id), User.find(user_id) + end + + def subject + if @post.community.is_college + "#{poster_name} just posted to your hall board on CommonPlace" + else + "#{poster_name} just posted to our neighborhood on CommonPlace" + end + end + + def reply_to + "reply+post_#{post.id}@ourcommonplace.com" + end + + def post + @post + end + + def user + @user + end + + def poster + @post.user + end + + def community + @post.community + end + + def community_name + community.name + end + + def poster_name + poster.name + end + + def short_poster_name + poster.first_name + end + + def post_url + url("/posts/#{post.id}") + end + + def new_message_url + url("/users/#{poster.id}/messages/new") + end + + def post_subject + post.subject + end + + def post_body + markdown(post.body) rescue ("

          " + post.body + "

          ") + end + + def poster_avatar_url + asset_url(poster.avatar_url(:thumb)) + end + + def poster_url + url("/users/#{poster.id}") + end + + def user_name + user.name + end + + def tag + 'post' + end + + def limited? + user.emails_are_limited? + end + +end diff --git a/mail/reply_notification.rb b/mail/reply_notification.rb new file mode 100644 index 000000000..7efd592fb --- /dev/null +++ b/mail/reply_notification.rb @@ -0,0 +1,93 @@ +class ReplyNotification < MailBase + + def initialize(reply_id, user_id) + @reply, @user = Reply.find(reply_id), User.find(user_id) + end + + def subject + if @user.community.is_college + "#{replier_name} just replied to a hall board post on CommonPlace" + else + "#{replier_name} just replied to message on CommonPlace" + end + end + + def community + replier.community + end + + def community_name + community.name + end + + def reply + @reply + end + + def repliable + reply.repliable + end + + def user + @user + end + + def reply_to + "reply+#{repliable.class.name.downcase}_#{repliable.id}@ourcommonplace.com" + end + + def user_name + user.name + end + + def replier + reply.user + end + + def reply_body + markdown reply.body + end + + def replier_avatar_url + replier.avatar_url + end + + def repliable_subject + repliable.subject + end + + def new_message_url + url("/users/#{replier.id}/messages/new") + end + + def repliable_url + url("/#{repliable.class.name.downcase.pluralize}/#{repliable.id}") + end + + def replier_name + replier.name + end + + def short_replier_name + replier.first_name + end + + def repliable_is_a_message? + repliable.is_a? Message + end + + def subject_line + post_type = case repliable + when Message then "a private message" + when Post then "a post" + when Event then "an event" + when Announcement then "an announcement" + end + "#{replier_name} just replied to #{post_type} on CommonPlace." + end + + def tag + 'reply' + end + +end diff --git a/mail/stylesheets/clipboard_welcome.scss b/mail/stylesheets/clipboard_welcome.scss new file mode 100644 index 000000000..dc23741a2 --- /dev/null +++ b/mail/stylesheets/clipboard_welcome.scss @@ -0,0 +1,9 @@ +@import "mail_base" ; + +ul li { list-style: none ; color: #3b6085 ; } + +ul li h3 { color: #3b6085 ; font-family: $serifs ; margin: 5px 0px ; } + +p { margin-top: 0px ; line-height: 1.6 ; } + + diff --git a/mail/stylesheets/daily_bulletin.scss b/mail/stylesheets/daily_bulletin.scss new file mode 100644 index 000000000..9c5364eb2 --- /dev/null +++ b/mail/stylesheets/daily_bulletin.scss @@ -0,0 +1,183 @@ + +body { margin: 20px 0px; background-color: #cee7fe ; } + +p { + padding: 0; + margin: 0; +} + + +h1, h2, h3, h4, h5 { + font-family: Rockwell, Helvetica, Arial, sans-serif; +} +p { + font-family: Georgia, Times New Roman, Times, serif; + line-height: 1.4 ; +} +td { + vertical-align:top; +} +ul, ol { + margin: 0; + padding: 0 +} +a { + color:#ac322f; + text-decoration:none; +} + +img { border: 0 } + +#top { + tr { background-color: #1b334a; } + td { padding-top: 10px; padding-left: 10px; padding-bottom: 10px; } + #logo { margin: 0px; } + #community-name { } +} + +#header { + h2 { border-bottom: 10px solid #fafafa ; font-weight: bold ;} + .date { + margin:0; color:#ffffff; font-size: 12px; font-weight: bold; text-transform: uppercase; background-color: #2e4b67; border: 5px #2e4b67 solid; + } +} + +.section-header { + h2 { margin: 10px 0 0 0; + font-size: 22px; font-family: Rockwell, Helvetica, Arial;font-weight: weight; color: #1b334a; padding-bottom: 0px ; + } +} + +.post { + border-top: 15px solid #fafafa ; + padding:0px; font-size: 12px; font-family: Helvetica, Arial, sans-serif; color: #524E47;background-color:#fafafa; + .avatar { + padding:0px 10px 0px 0px; width: 50px ; height: 50px ; + img { border: 1px solid #cfcfcf ; } + } + .author { font-family: Rockwell, Helvetica, Arial; font-size: 16px; color: #a6352c; padding-bottom: 8px ; line-height: 15px ; } + + .title { span { font-family: Georgia, Times New Roman, Times, serif; font-size: 18px; color: #444; padding-bottom: 5px ; } + padding-bottom: 3px ; } + + .body { + p { font-size: 15px ; line-height: 18px ; margin-bottom: 5px ;} + } + + .subscribe { + div { margin-top: 5px; padding-bottom: 5px ; } + span { color: #304a6a ; font-size: 16px ; text-decoration: none ; + font-family: Georgia, Times New Roman, Times, serif; + font-weight: bold ; } + } + + .info { color: #a6352c ; + font-family: Rockwell, Georgia, Times New Roman, serif ; + } + + &.event { + .avatar { + + .calendar { + width: 50px ; text-align: center ; + .short-month { + height: 20px; + border: 1px solid #b55f58 ; border-bottom-width: 0px ; + background-color: #a6352c ; color: #ecd5d4 ; + } + .day { border: 1px solid #cfcfcf ; border-top-width: 0px ; + background-color: #f2f2f2 ; + font-size: 18px; + line-height: 24px ; color: #1d443c ; + font-family: Rockwell, Georgia, Times New Roman, serif ; + } + } + + + .time { font-size: 12px ; + font-family: Rockwell, Georgia, Times New Roman, serif ; + color: #1b334a ; padding-top: 5px ; + text-align: center ; } + + } + } + + .respond { img { vertical-align: middle } + a.message-link, a.message-link { font-family: Rockwell, Georgia, Times New Roman, serif ; } + } + +} + +.post-separator { + margin-top: 10px ; border-top: 1px solid #cfcfcf ; padding-bottom: 10px ; +} + +.reply-separator { + margin-top: 10px ; border-top: 1px solid #cfcfcf ; +} + +.reply { + width: 100%; + margin: 15px 0 0px; + .avatar { width: 30px ; padding: 0 ;} + .author { font-family: Rockwell, Helvetica, Arial; font-size: 14px; color: #a6352c; line-height: 15px ; } +} + +#sidebar { + h4 { + a { font-size: 13px; font-family:Rockwell, Georgia, Helvetica; color: #1b334a; } + margin: 0; text-align:left; font-size: 13px; font-family:Rockwell, Georgia, Helvetica; color: #1b334a; + border-bottom: 1px solid #ddd ; + padding-bottom: 5px ; + } + + p { + margin-top: 10px ; + padding-bottom: 5px ; + color: #666 ; + font-size: 14px ; + text-align: left ; + } + + a.post-now, a.post-now { font-size: 12px; font-family:Rockwell, Georgia, Helvetica; color: #1b334a; font-weight: bold ; } +} + + +#footer { + border: 0px ; + background-color: #f2f2f2 ; + .posting-links { + border-top: 10px solid #f2f2f2 ; + border-bottom: 10px solid #f2f2f2 ; + text-align: center ; + a { color: #a24841; font-size: 14px ; font-family: Rockwell, Georgia, Helvetica ; font-weight: bold ; } + .separator { color: #888 ; font-size: 18px ; } + } + + .settings { + border-top: 10px solid #f2f2f2 ; + border-bottom: 10px solid #f2f2f2 ; + text-align: center ; + p { + color: #666 ; + font-size: 12px ; + } + } + + .legal { + border-top: #304a6a 10px solid ; + border-bottom: #304a6a 10px solid ; + color: #fafafa; + font-family: Rockwell, Georgia, Helvetica ; + font-weight: bold; + font-size: 12px ; + background-color: #304a6a ; + text-align: center ; + a { + color: #fafafa; + font-family: Rockwell, Georgia, Helvetica ; + font-weight: bold; + font-size: 12px ; + } + } +} \ No newline at end of file diff --git a/mail/stylesheets/invitation.scss b/mail/stylesheets/invitation.scss new file mode 100644 index 000000000..bc0831c67 --- /dev/null +++ b/mail/stylesheets/invitation.scss @@ -0,0 +1,9 @@ +@import "mail_base" ; + +#footer { text-align: center ; } + +p.quote { margin-left: 15px ; font-style: italic ; } + +p em { color: #3b6085 ; } + +p.link-to-commonplace { text-align: center ; } diff --git a/mail/stylesheets/mail_base.scss b/mail/stylesheets/mail_base.scss new file mode 100644 index 000000000..51994e5e7 --- /dev/null +++ b/mail/stylesheets/mail_base.scss @@ -0,0 +1,52 @@ + +@mixin colored-a($color) { + color: $color ; + span { color: $color ; } +} + +@mixin button($border-color, $bg-color, $fg-color) { + @include colored-a($fg-color) ; + text-decoration: none ; + display: block ; + border: 1px solid $border-color ; + background-color: $bg-color ; + text-align: center ; + span { + text-decoration: none ; + display: block ; + padding: 3px 5px ; + text-align: center ; + } +} + +@mixin red-button { @include button(#82312f, #b53f3c, #ffffff) ; } +@mixin blue-button { @include button(#2e4359, #3b6085, #ffffff) ; } + +$main-width: 610px ; +$serifs: Rockwell, Georgia, Times, serif ; +$sans-serifs: Helvetica, sans ; +$body-serifs: Georgia, Times, serif ; + + +body { text-align: center ; background-color: #ffffff ; } + +p { color: #656565 ; font-size: 14px ; line-height: 1.8 ; font-family: $body-serifs ; line-height: 1.8 ; } + +#body { width: 650px ; text-align: left ; border-spacing: 20px ; background-color: #ffffff ; } + +#header { + table { width: $main-width ; border-spacing: 0px ; } + #logo { width: 225px ; img { border-width: 0px ; } } + #community_name { + color: #b53f3c; font-family: $serifs ; font-size: 22px ; font-weight: bold ; text-align: left ; vertical-align: bottom ; line-height: 32px ; + } + #header-text { text-align: right ; color: #3b6085 ; font-family: $serifs ; font-size: 14px ; font-weight: bold ; vertical-align: bottom ; line-height: 32px ; } +} +a { @include colored-a(#b53f3c) ; } + +#intro h1 { font-size: 20px ; color: #464646 ; font-family: $serifs ; } +#intro p { font-family: $sans-serifs; font-size: 13px ; } + +#settings { font-family: $sans-serifs; font-size: 12px ; } + +#footer { text-align: center ; } diff --git a/mail/stylesheets/post_notification.scss b/mail/stylesheets/post_notification.scss new file mode 100644 index 000000000..8bbc8865a --- /dev/null +++ b/mail/stylesheets/post_notification.scss @@ -0,0 +1,27 @@ +@import "mail_base" ; + +#post { + background-color: #f5f5f5 ; + + table { + border-spacing: 10px ; + td { vertical-align: top ; } + } ; + + .subject { color: #3b608e ; font-size: 22px ; } + + .body { + p { line-height: 1.2 ; font-size: 16px ; margin: 0 0 5px ; } + } + + .reply { + vertical-align: top ; + a { @include red-button ; } + } + + .reply-by-email { vertical-align: top ; p { font-size: 14px ; margin: 0 ; } } + + .message { + + } +} diff --git a/mail/stylesheets/reply_notification.scss b/mail/stylesheets/reply_notification.scss new file mode 100644 index 000000000..7e2b8a594 --- /dev/null +++ b/mail/stylesheets/reply_notification.scss @@ -0,0 +1 @@ +@import "post_notification" ; \ No newline at end of file diff --git a/mail/stylesheets/welcome.scss b/mail/stylesheets/welcome.scss new file mode 100644 index 000000000..681ffdef3 --- /dev/null +++ b/mail/stylesheets/welcome.scss @@ -0,0 +1,8 @@ +@import "mail_base" ; + +ul li { list-style: none ; color: #3b6085 ; } + +ul li h3 { color: #3b6085 ; font-family: $serifs ; margin: 5px 0px ; } + +p { margin-top: 0px ; line-height: 1.6 ; } + diff --git a/mail/templates/admin_question.mustache b/mail/templates/admin_question.mustache new file mode 100644 index 000000000..0e12313e2 --- /dev/null +++ b/mail/templates/admin_question.mustache @@ -0,0 +1 @@ +{{question}} diff --git a/mail/templates/announcement_confirmation.mustache b/mail/templates/announcement_confirmation.mustache new file mode 100644 index 000000000..de5e13eec --- /dev/null +++ b/mail/templates/announcement_confirmation.mustache @@ -0,0 +1,32 @@ + + + + + + + + + {{< header}} + + + + + + + + +
          +

          Hi {{short_poster_name}},
          + Thanks for posting to {{community_name}}'s CommonPlace! + To view and edit your announcement on CommonPlace, + go to {{announcement_url}}.

          + +

          Thanks for being a great neighbor,
          + {{community_name}}'s CommonPlace Team

          +
          + + \ No newline at end of file diff --git a/mail/templates/announcement_notification.mustache b/mail/templates/announcement_notification.mustache new file mode 100644 index 000000000..240276897 --- /dev/null +++ b/mail/templates/announcement_notification.mustache @@ -0,0 +1,63 @@ + + + + + + + + + + {{< header}} + + + + + + + + + + + + +
          +

          Hi {{user_name}},
          + {{poster_name}} just posted an announcement on CommonPlace. +

          +
          + + + + + + + + + + + + + + + + +
          + + + {{announcement_subject}} +
          + {{{announcement_body}}} +
          + Reply to {{short_poster_name}} + +

          Or just reply to this email.

          +
          +
          + + diff --git a/mail/templates/clipboard_welcome.mustache b/mail/templates/clipboard_welcome.mustache new file mode 100644 index 000000000..9e75cda8c --- /dev/null +++ b/mail/templates/clipboard_welcome.mustache @@ -0,0 +1,70 @@ + + + + + + + + {{< header }} + + + + + + + + + +
          +

          Hi {{short_user_name}},

          + + +

          We are happy to hear you have expressed interest in joining your neighbors on {{community_name}}'s CommonPlace, a new web platform for local community engagement here in {{community_name}}. {{community_name}}'s CommonPlace is like an online bulletin for people who live in {{community_name}}, designed to make it really easy for you to share requests, offers and other information with your neighbors and to stay up-to-date with what's happening in {{community_name}}.

          + +

          Since you gave your name and e-mail address to {{organizer_name}}, we have eliminated a step to the registration process for you: we have gone ahead and created an account for you! You can access your account to set up your password and get posting right away by clicking here: {{single_access_login}}

          + +

          Here are some platform features that you might be interested in using to get connected with your community:

          + +
            +
          • +

            Neighborhood Posts:

            +

            Need to borrow a ladder? Lost a cat? Looking for a babysitter or an electrician recommendation? See a downed wire in the neighborhood? Click "Post to Your Neighborhood" in the red box on the top left of the mainpage to post a message to all your neighbors. Have a ladder? Found the cat? Up for babysitting? Click on any neighborhood post and a reply box will appear for you to reply directly to your neighbor.

            +
          • + +
          • +

            Community Announcements:

            +

            Want to get the word out to everyone in the community about your upcoming canned food drive? Want to let every parent in town know that you do SAT tutoring or piano lessons? Click "Publicize a Community Announcement" to get the word out to the community. Interested in staying in the loop with what's going on in {{community_name}}? Click on "Community Announcements" to see all the updates that people, businesses, and organizations are sending out to the community. Like with neighborhood posts, click on any announcement and a reply box will appear for you to reply directly to the person or organization that posted the announcement.

            +
          • + +
          • +

            Community Events:

            +

            Click on "Community Events" to see everything going on in {{community_name}} in the coming weeks. Add your own event to the CommonPlace events board anytime by clicking "Post an Event." Like announcements and neighborhood posts, you can click on any event and reply to it to get in touch with the event coordinator.

            +
          • + +
          • +

            Community Feeds:

            +

            Any organization, business, city service or cause in {{community_name}} can create a "Community Feed" on CommonPlace. This allows them to send out announcements and events to the community from their organization. Subscribe to community feeds to be emailed directly when that organization posts an announcement or an event. If you are the leader of an organization or business in town and want to stream your announcements and events through CommonPlace, consider creating a community feed.

            +
          • + +
          • +

            Directory:

            +

            On {{community_name}}'s CommonPlace, you can see a directory of everyone in town that has signed up for CommonPlace. Click on any name to see their civic profile and click "Message" in their profile to send a neighbor a private message.

            +
          • + +
          + +

          If you have any questions about CommonPlace, consider checking out the Frequently Asked Questions page, and feel free to email {{organizer_email}} anytime. Also, please email us if you are interested in helping out with signing your neighbors up with CommonPlace. The more people in {{community_name}} that are on CommonPlace, the better a tool it is for neighbors to in {{community_name}} to connect, so make sure to tell your neighbors to sign up at {{root_url}}.

          + +

          Thanks again for joining {{community_name}}'s CommonPlace and thanks for being a great neighbor!

          + +

          Cheers,
          The {{community_name}} CommonPlace

          + +
          + + + + diff --git a/mail/templates/daily_bulletin.mustache b/mail/templates/daily_bulletin.mustache new file mode 100644 index 000000000..20e35e8e5 --- /dev/null +++ b/mail/templates/daily_bulletin.mustache @@ -0,0 +1,562 @@ + + + + {{community_name}} CommonPlace Daily Bulletin + + + + + + + + + + + + + + + + + + +
          + + + + + + + + + + + +
          +
          + {{community_name}} +
          +
          + + + + + + + + + + + + + +
          + + + diff --git a/mail/templates/feed_invitation.mustache b/mail/templates/feed_invitation.mustache new file mode 100644 index 000000000..08037ccea --- /dev/null +++ b/mail/templates/feed_invitation.mustache @@ -0,0 +1,22 @@ + + + + + + + + {{< header}} + + + + + + + + \ No newline at end of file diff --git a/mail/templates/feed_welcome.mustache b/mail/templates/feed_welcome.mustache new file mode 100644 index 000000000..839da18ba --- /dev/null +++ b/mail/templates/feed_welcome.mustache @@ -0,0 +1,49 @@ + + + + + +
          + {{#t}}body{{/t}} +
          + + {{< header }} + + + + + + + + + +
          +

          Dear {{short_user_name}},

          + +

          You have successfully created a feed for {{feed_name}} on CommonPlace. You can now send announcements and events to everyone on CommonPlace through your organization feed! Here are three ways to get the word out:

          + +
            +
          • +

            Visit your feed page at {{feed_url}} to post an announcement, post an event or edit your feed profile.

            +
          • + +
          • +

            Email in announcements to your feed by emailing {{feed_email_address}} with the exact announcement you want to post to CommonPlace.

            +
          • + +
          • +

            Use the red box in the upper left of the main page to quickly post announcements or events.

            +
          • + +
          + +

          We hope The {{town_name}} CommonPlace can help you get the word out to the community about your organization's updates and events. Feel free to email {{organizer_name}} at ({{organizer_email}}) with any questions about how to use your feed.

          + +

          Cheers,
          The {{town_name}} CommonPlace Team

          + +
          + + + diff --git a/mail/templates/group_post_notification.mustache b/mail/templates/group_post_notification.mustache new file mode 100644 index 000000000..624bb13d3 --- /dev/null +++ b/mail/templates/group_post_notification.mustache @@ -0,0 +1,63 @@ + + + + + + + + + + {{< header}} + + + + + + + + + + + + +
          +

          Hi {{user_name}},
          + {{subject}}. +

          +
          + + + + + + + + + + + + + + + + +
          + + + {{post_subject}} +
          + {{{post_body}}} +
          + Reply to {{short_poster_name}} + +

          Or just reply to this email.

          +
          +
          + + \ No newline at end of file diff --git a/mail/templates/header.mustache b/mail/templates/header.mustache new file mode 100644 index 000000000..fbfb11c01 --- /dev/null +++ b/mail/templates/header.mustache @@ -0,0 +1,17 @@ + + + + + + + + +
          + {{community_name}} + + {{header_text}} +
          + + \ No newline at end of file diff --git a/mail/templates/help_a_neighbor_out.mustache b/mail/templates/help_a_neighbor_out.mustache new file mode 100644 index 000000000..b75df67dc --- /dev/null +++ b/mail/templates/help_a_neighbor_out.mustache @@ -0,0 +1,563 @@ + + + + Help a Neighbor Out! + + + + + + + + + + + + + + + + + + +
          + + + + + + + + + + + +
          +
          + {{community_name}} +
          +
          + + + + + + + + + + + + + +
          + + + + diff --git a/mail/templates/invitation.mustache b/mail/templates/invitation.mustache new file mode 100644 index 000000000..5d880fb28 --- /dev/null +++ b/mail/templates/invitation.mustache @@ -0,0 +1,23 @@ + + + + + + + + {{< header}} + + + + + + +
          + {{#t}}body{{/t}} +
          + + diff --git a/mail/templates/legal.mustache b/mail/templates/legal.mustache new file mode 100644 index 000000000..95badcf4b --- /dev/null +++ b/mail/templates/legal.mustache @@ -0,0 +1,2 @@ +OurCommonPlace.com +

          © 2010-2011 CommonPlace.

          \ No newline at end of file diff --git a/mail/templates/message_notification.mustache b/mail/templates/message_notification.mustache new file mode 100644 index 000000000..620b80092 --- /dev/null +++ b/mail/templates/message_notification.mustache @@ -0,0 +1,63 @@ + + + + + + + + + + {{< header}} + + + + + + + + + + + + +
          +

          Hi {{user_name}},
          + {{sender_name}} just sent you a private message. +

          +
          + + + + + + + + + + + + + + + + +
          + + + {{message_subject}} +
          + {{{message_body}}} +
          + Reply to {{short_sender_name}} + +

          Or just reply to this email.

          +
          +
          + + \ No newline at end of file diff --git a/mail/templates/no_feed_permission.mustache b/mail/templates/no_feed_permission.mustache new file mode 100644 index 000000000..819038cee --- /dev/null +++ b/mail/templates/no_feed_permission.mustache @@ -0,0 +1,35 @@ + + + + + + + + {{< header}} + + + + + + + + +
          + +

          Hi,
          + You recently emailed an announcement into {{community_name}}'s CommonPlace to be posted through the community feed: {{feed_name}}. We were not able to post it because the email address from which you emailed is not the registered administrative email address for the community feed: {{feed_name}}.

          + +

          Only the user who created that feed can email in announcements to be posted to CommonPlace through that feed. If you are the user who created {{feed_name}}, it is possible that your announcement did not go through because you originally registered for CommonPlace with a different email address.

          + +

          If you are the creator of {{feed_name}} and are still having trouble emailing into CommonPlace, email us at {{organizer_email}} and we will quickly work out the problem.

          + +

          Thanks for being a great neighbor,
          + {{community_name}} CommonPlace

          + +
          + + \ No newline at end of file diff --git a/mail/templates/password_reset.mustache b/mail/templates/password_reset.mustache new file mode 100644 index 000000000..3dbffaa49 --- /dev/null +++ b/mail/templates/password_reset.mustache @@ -0,0 +1,28 @@ + + + + + + + + {{< header }} + + + + + + + + +
          +

          Hello {{user_name}}

          + +

          Follow this link to reset your password: {{reset_url}}

          +
          + + + diff --git a/mail/templates/post_confirmation.mustache b/mail/templates/post_confirmation.mustache new file mode 100644 index 000000000..bf342c147 --- /dev/null +++ b/mail/templates/post_confirmation.mustache @@ -0,0 +1,31 @@ + + + + + + + + + {{< header}} + + + + + + + + +
          +

          Hi {{short_poster_name}},
          + Thanks for posting to {{community_name}}'s CommonPlace! + To view and edit your post on CommonPlace, + go to {{post_url}}.

          + +

          {{#t}}thanks{{/t}}

          +
          + + \ No newline at end of file diff --git a/mail/templates/post_notification.mustache b/mail/templates/post_notification.mustache new file mode 100644 index 000000000..c5516d81e --- /dev/null +++ b/mail/templates/post_notification.mustache @@ -0,0 +1,66 @@ + + + + + + + + + + {{< header}} + + + + + + + + + + + + +
          +

          {{#t}}intro{{/t}}

          +
          + + + + + + + + + + + + + + + + + + + +
          + + + {{post_subject}} +
          + {{{post_body}}} +
          + Reply to {{short_poster_name}} + +

          Or just reply to this email.

          +
          + Send {{short_poster_name}} a private message +
          +
          + + \ No newline at end of file diff --git a/mail/templates/reply_notification.mustache b/mail/templates/reply_notification.mustache new file mode 100644 index 000000000..4d55e0ea3 --- /dev/null +++ b/mail/templates/reply_notification.mustache @@ -0,0 +1,70 @@ + + + + + + + + + + {{< header}} + + + + + + + + + + + + +
          +

          Hi {{user_name}},
          + {{subject_line}} + {{post_subject}} +

          +
          + + + + + + + + + + + + + + + + {{^repliable_is_a_message?}} + + + + {{/repliable_is_a_message?}} +
          + + + {{repliable_subject}} +
          + {{{reply_body}}} +
          + Reply to {{short_replier_name}} + +

          Or just reply to this email.

          +
          + Send {{short_replier_name}} a private message +
          +
          + + \ No newline at end of file diff --git a/mail/templates/settings.mustache b/mail/templates/settings.mustache new file mode 100644 index 000000000..a287f3661 --- /dev/null +++ b/mail/templates/settings.mustache @@ -0,0 +1,5 @@ +

          + Want to change your email settings? Click + here + to manage your subscriptions. +

          diff --git a/mail/templates/unknown_address.mustache b/mail/templates/unknown_address.mustache new file mode 100644 index 000000000..9ec3cfb51 --- /dev/null +++ b/mail/templates/unknown_address.mustache @@ -0,0 +1,38 @@ + + + + + + + + + {{< header}} + + + + + + + + +
          +

          Hi,
          + You recently emailed into {{community_name}}'s CommonPlace. We were not able to post it because the email address you emailed in to is not a recognized @OurCommonPlace.com email address.

          + +

          If you want to email a neighborhood post into CommonPlace, email: {{community_slug}}@OurCommonPlace.com

          + +

          If you want to email an announcement into CommonPlace through your Community Feed, email [FeedName]@OurCommonPlace.com

          + +

          If you want to email a group post into CommonPlace, email: [GroupName]@OurCommonPlace.com

          + +

          If you are still having trouble emailing into {{community_name}}'s CommonPlace or have any questions, email {{organizer_email}} and we will quickly work out the problem.

          + +

          Thanks for being a great neighbor,
          + {{community_name}} CommonPlace

          +
          + + \ No newline at end of file diff --git a/mail/templates/unknown_user.mustache b/mail/templates/unknown_user.mustache new file mode 100644 index 000000000..fafa63487 --- /dev/null +++ b/mail/templates/unknown_user.mustache @@ -0,0 +1,35 @@ + + + + + + + + {{< header}} + + + + + + + + +
          + +

          Hi,
          + You recently emailed a post in to CommonPlace. We were not able to post it because the email address from which you emailed is not registered with CommonPlace.

          + +

          If you have not signed up for CommonPlace yet, sign up at [YourTown'sName].OurCommonPlace.com (for example, FallsChurch.OurCommonPlace.com or Williamsburg.OurCommonPlace.com) to begin sharing with your neighborhood.

          + +

          If you have already signed up with CommonPlace, it is possible that your post did not go through because you originally registered with a different email address. If that is not the case, email us at Pete@CommonPlaceUSA.com and we will quickly work out the problem.

          + +

          Thanks for being a great neighbor, + + CommonPlace

          +
          + + \ No newline at end of file diff --git a/mail/templates/welcome.mustache b/mail/templates/welcome.mustache new file mode 100644 index 000000000..951332d7d --- /dev/null +++ b/mail/templates/welcome.mustache @@ -0,0 +1,65 @@ + + + + + + + + {{< header }} + + + + + + + + + +
          +

          Hi {{short_user_name}},

          + +

          {{#t}}intro{{/t}}

          + +

          {{#t}}list-intro{{/t}}

          + +
            +
          • +

            {{#t}}post-feature-title{{/t}}:

            +

            {{#t}}post-feature-desc{{/t}}

            +
          • + +
          • +

            {{#t}}announcement-feature-title{{/t}}:

            +

            {{#t}}announcement-feature-desc{{/t}}

            +
          • + +
          • +

            {{#t}}event-feature-title{{/t}}:

            +

            {{#t}}event-feature-desc{{/t}}

            +
          • + +
          • +

            {{#t}}feed-feature-title{{/t}}:

            +

            {{#t}}feed-feature-desc{{/t}}

            +
          • + +
          • +

            {{#t}}directory-feature-title{{/t}}:

            +

            {{#t}}directory-feature-desc{{/t}}

            +
          • + +
          + +

          {{#t}}outro{{/t}}

          +

          {{#t}}thanks{{/t}}

          + +

          Cheers,
          The {{community_name}} CommonPlace

          + +
          + + + diff --git a/mail/text/college.yml b/mail/text/college.yml new file mode 100644 index 000000000..3a6bcb253 --- /dev/null +++ b/mail/text/college.yml @@ -0,0 +1,110 @@ + +post_notification: + intro: Hi {{user_name}},
          {{poster_name}} just posted to your hall board on CommonPlace. + +welcome: + intro: We are happy to hear you have joined your fellow students on {{community_name}}'s CommonPlace, a new web platform for campus engagement here in {{community_name}}. {{community_name}}'s CommonPlace is like an online bulletin for students who attend {{community_name}}, designed to make it really easy for you to share requests, offers and other information with your fellow students and to stay up-to-date with what's happening at {{community_name}}. + + list-intro: | + Here are some platform features that you might be interested in using to get connected with your community: + + post-feature-title: Hall Posts + post-feature-desc: | + Need to borrow a stapler? Lost your textbook? Looking for a restaurant recommendation? See something wrong in your hall? Click "Post to Your Hall" in the red box on the top left of the mainpage to post a message to your hallmates. Have a stapler? Willing to share a textbook? Have an idea about how to improve a posted problem? Click on any hall post and a reply box will appear for you to reply directly to your fellow student. + + announcement-feature-title: Campus Announcements + announcement-feature-desc: | + Want to get the word out to everyone on campus about your upcoming event? Want to let every student know that you are looking for people to act in your play? Or that you need extra people for intermurals? Click "Publicize a Campus Announcement" to get the word out to campus. Interested in staying in the loop with what's going on in {{community_name}}? Click on "Campus Announcements" to see all the updates that people, businesses, and organizations are sending out to the community. Like with hall posts, click on any announcement and a reply box will appear for you to reply directly to the person or organization that posted the announcement. + event-feature-title: Campus Events + event-feature-desc: | + Click on "Campus Events" to see everything going on in {{community_name}} in the coming weeks. Add your own event to the CommonPlace events board anytime by clicking "Post an Event." Like announcements and hall posts, you can click on any event and reply to it to get in touch with the event coordinator. + + feed-feature-title: Campus Feeds + feed-feature-desc: | + Any organization, business, city service or cause in {{community_name}} can create a "Campus Feed" on CommonPlace. This allows them to send out announcements and events to the community from their organization. Subscribe to campus feeds to be emailed directly when that organization posts an announcement or an event. If you are the leader of an organization on campus and want to stream your announcements and events through CommonPlace, consider creating a campus feed. + + directory-feature-title: Directory + directory-feature-desc: | + On {{community_name}}'s CommonPlace, you can see a directory of everyone on campus that has signed up for CommonPlace. Click on any name to see their campus profile and click "Message" in their profile to send a neighbor a private message. + + outro: | + If you have any questions about CommonPlace, consider checking out the Frequently Asked Questions page, and feel free to email {{organizer_email}} anytime. Also, please email us if you are interested in helping out with signing your fellow students up with CommonPlace. The more people in {{community_name}} that are on CommonPlace, the better a tool it is for students at {{community_name}} to connect, so make sure to tell your fellow students to sign up at {{root_url}}. + + thanks: | + Thanks again for joining {{community_name}}'s CommonPlace! + + +post_confirmation: + thanks: | + Thanks,
          {{community_name}}'s CommonPlace Team + +invitation: + body: | +

          Hi Neighbor,

          +

          {{inviter_name}} has invited you to join {{community_name}}'s CommonPlace, a web platform that makes it easy for students and organization leaders at {{community_name}} to connect and share information with each other. {{#message}}{{short_inviter_name}} added the following message: {{/message}} +

          + + {{#message}} +

          "{{message}}"

          + {{/message}} + +

          {{community_name}}'s CommonPlace is like an online bulletin board for campus -- {{short_inviter_name}} and other neighbors in {{community_name}} have been using {{community_name}}'s CommonPlace to share events, needs, questions and announcements with the people who live right around them. Sign up once and start receiving important campus announcements from your fellow students and organization leaders here in {{community_name}}. To sign up, simply click the link below:

          + + + +

          You can use {{community_name}}'s CommonPlace to: notify your fellow students about a lost phone, or to ask to borrow a stapler; use it to keep up with campus events, or to publicize your own; use it to get announcements from campus organizations, or to start up you own group on campus.

          + +

          If you have any questions, feel free to contact {{organizer_name}}, the lead CommonPlace organizer for {{community_name}}, at {{organizer_email}} with any questions, concerns, or ideas about {{community_name}}'s Commonplace.

          + +

          Thanks,
          + {{community_name}}'s CommonPlace +

          + +feed_invitation: + body: | +

          Hi Neighbor,

          +

          {{inviter_name}} has invited you to subscribe to the community feed of {{feed_name}} on {{community_name}}'s CommonPlace, a web platform that makes it easy for neighbors and local leaders in {{community_name}} to connect and share information with each other. {{#message}}{{short_inviter_name}} added the following message: {{/message}} +

          + + {{#message}} +

          "{{message}}"

          + {{/message}} + +

          {{community_name}}'s CommonPlace is like an online bulletin board for your campus -- {{short_inviter_name}} and other students at {{community_name}} have been using {{community_name}}'s CommonPlace to share events, needs, questions and announcements with the people who live right around them. Sign up once and start receiving important campus announcements from your fellow students and organization leaders here in {{community_name}}. To sign up, simply click the link below:

          + + + +

          You can use {{community_name}}'s CommonPlace to: notify your fellow students about a lost phone, or to ask to borrow a stapler; use it to keep up with campus events, or to publicize your own; use it to get announcements from campus organizations, or to start up you own group on campus.

          + +

          If you have any questions, feel free to contact {{organizer_name}}, the lead CommonPlace organizer for {{community_name}}, at {{organizer_email}} with any questions, concerns, or ideas about {{communit_name}}'s Commonplace.

          + +

          Thanks,
          + {{community_name}}'s CommonPlace +

          + +daily_bulletin: + posts-section-title: Recent Hall Posts + events-section-title: Upcoming Campus Events + announcements-section-title: Recent Campus Announcements + + invite-action-title: Invite your fellow students! + invite-action-body: | + Know anyone in {{community_name}} not on CommonPlace? + post-action-title: Post to Your Hall + post-action-body: | + Need to borrow a stapler? Lost your phone? Offering up extra food you have? Share it with your hallmates! + + group-post-action-title: Post to a Discussion Group + group-post-action-body: | + Need to get in touch with other {{community_name}} sports players? Have an opinion on a campus life issue? Get the campus discussion group conversations going! + event-action-title: Post an Event + event-action-body: | + Does your organization have an event coming up? Publicize it on {{community_name}}'s CommonPlace! + + announce-action-title: Publicize an Announcement + announce-action-body: | + Is your organization recruiting new members? Are you running a volunteer drive? Get the word out in tomorrow's Daily Bulletin!

          + + new-feed-action-title: Register a Community Feed + new-feed-action-body: | + Are you part of a campus organization or cause that wants to send announcements and events through {{community_name}}'s CommonPlace? diff --git a/mail/text/en.yml b/mail/text/en.yml new file mode 100644 index 000000000..ec4fe915e --- /dev/null +++ b/mail/text/en.yml @@ -0,0 +1,124 @@ + +post_notification: + intro: Hi {{user_name}},
          {{poster_name}} just posted to your neighborhood board on CommonPlace. + +welcome: + intro: We are happy to hear you have joined your neighbors on {{community_name}}'s CommonPlace, a new web platform for local community engagement here in {{community_name}}. {{community_name}}'s CommonPlace is like an online bulletin for people who live in {{community_name}}, designed to make it really easy for you to share requests, offers and other information with your neighbors and to stay up-to-date with what's happening in {{community_name}}. + + list-intro: | + Here are some platform features that you might be interested in using to get connected with your community: + + post-feature-title: Neighborhood Posts + post-feature-desc: | + Need to borrow a ladder? Lost a cat? Looking for a babysitter or an electrician recommendation? See a downed wire in the neighborhood? Click "Post to Your Neighborhood" in the red box on the top left of the mainpage to post a message to all your neighbors. Have a ladder? Found the cat? Up for babysitting? Click on any neighborhood post and a reply box will appear for you to reply directly to your neighbor. + + announcement-feature-title: Community Announcements + announcement-feature-desc: | + Want to get the word out to everyone in the community about your upcoming canned food drive? Want to let every parent in town know that you do SAT tutoring or piano lessons? Click "Publicize a Community Announcement" to get the word out to the community. Interested in staying in the loop with what's going on in {{community_name}}? Click on "Community Announcements" to see all the updates that people, businesses, and organizations are sending out to the community. Like with neighborhood posts, click on any announcement and a reply box will appear for you to reply directly to the person or organization that posted the announcement. + + event-feature-title: Community Events + event-feature-desc: | + Click on "Community Events" to see everything going on in {{community_name}} in the coming weeks. Add your own event to the CommonPlace events board anytime by clicking "Post an Event." Like announcements and neighborhood posts, you can click on any event and reply to it to get in touch with the event coordinator. + + feed-feature-title: Community Feeds + feed-feature-desc: | + Any organization, business, city service or cause in {{community_name}} can create a "Community Feed" on CommonPlace. This allows them to send out announcements and events to the community from their organization. Subscribe to community feeds to be emailed directly when that organization posts an announcement or an event. If you are the leader of an organization or business in town and want to stream your announcements and events through CommonPlace, consider creating a community feed. + + directory-feature-title: Directory + directory-feature-desc: | + On {{community_name}}'s CommonPlace, you can see a directory of everyone in town that has signed up for CommonPlace. Click on any name to see their civic profile and click "Message" in their profile to send a neighbor a private message. + +

          If you have any questions about CommonPlace, consider checking out the Frequently Asked Questions page, and feel free to email {{organizer_email}} anytime. Also, please email us if you are interested in helping out with signing your neighbors up with CommonPlace. The more people in {{community_name}} that are on CommonPlace, the better a tool it is for neighbors in {{community_name}} to connect, so make sure to tell your neighbors to sign up at {{root_url}}.

          + +

          Thanks again for joining {{community_name}}'s CommonPlace and thanks for being a great neighbor!

          + +

          Cheers,
          The {{community_name}} CommonPlace

          + + outro: | + If you have any questions about CommonPlace, consider checking out the Frequently Asked Questions page, and feel free to email {{organizer_email}} anytime. Also, please email us if you are interested in helping out with signing your neighbors up with CommonPlace. The more people in {{community_name}} that are on CommonPlace, the better a tool it is for neighbors in {{community_name}} to connect, so make sure to tell your neighbors to sign up at {{root_url}}. + + thanks: | + Thanks again for joining {{community_name}}'s CommonPlace and thanks for being a great neighbor! + + +post_confirmation: + thanks: | + Thanks for being a great neighbor,
          {{community_name}}'s CommonPlace Team + +invitation: + body: | +

          Hi Neighbor,

          +

          {{inviter_name}} has invited you to join {{community_name}}'s CommonPlace, a web platform that makes it easy for neighbors and local leaders in {{community_name}} to connect and share information with each other. {{#message}}{{short_inviter_name}} added the following message: {{/message}} +

          + + {{#message}} +

          "{{message}}"

          + {{/message}} + +

          {{community_name}}'s CommonPlace is like an online bulletin board for your town -- {{short_inviter_name}} and other neighbors in {{community_name}} have been using {{community_name}}'s CommonPlace to share events, needs, questions and announcements with the people who live right around them. Sign up once and start receiving important community announcements from your neighbors and local leaders here in {{community_name}}. To sign up, simply click the link below:

          + + + +

          You can use {{community_name}}'s CommonPlace to: notify your neighbors about a lost cat, or to ask to borrow a ladder; use it to keep up with local events happening in town, or to publicize your own; use it to get announcements from the city government and local organizations, or to start up you own group in the community.

          + +

          If you have any questions, feel free to contact {{organizer_name}}, the lead CommonPlace organizer for {{community_name}}, at {{organizer_email}} with any questions, concerns, or ideas about {{community_name}}'s Commonplace.

          + +

          Thanks,
          + {{community_name}}'s CommonPlace +

          + +feed_invitation: + body: | +

          Hi Neighbor,

          +

          {{inviter_name}} has invited you to subscribe to the community feed of {{feed_name}} on {{community_name}}'s CommonPlace, a web platform that makes it easy for neighbors and local leaders in {{community_name}} to connect and share information with each other. {{#message}}{{short_inviter_name}} added the following message: {{/message}} +

          + + {{#message}} +

          "{{message}}"

          + {{/message}} + +

          {{community_name}}'s CommonPlace is like an online bulletin board for your town -- {{short_inviter_name}} and other neighbors in {{community_name}} have been using {{community_name}}'s CommonPlace to share events, needs, questions and announcements with the people who live right around them. Sign up once and start receiving important community announcements from your neighbors and local leaders here in {{community_name}}. To sign up, simply click the link below:

          + + + +

          In addition to receiving information about {{feed_name}}, you can also use {{community_name}}'s CommonPlace to: notify your neighbors about a lost cat, or to ask to borrow a ladder; use it to keep up with local events happening in town, or to publicize your own; use it to get announcements from the city government and local organizations, or to start up you own group in the community.

          + +

          If you have any questions, feel free to contact {{organizer_name}}, the lead CommonPlace organizer for {{community_name}}, at {{organizer_email}} with any questions, concerns, or ideas about {{communit_name}}'s Commonplace.

          + +

          Thanks,
          + {{community_name}}'s CommonPlace +

          + +daily_bulletin: + posts-section-title: Recent Neighborhood Posts + events-section-title: Upcoming Community Events + announcements-section-title: Recent Community Announcements + + invite-action-title: Invite your Neighbors! + invite-action-body: | + Know anyone in {{community_name}} not on CommonPlace? + + post-action-title: Post to Your Neighborhood + post-action-body: | + Need to borrow a ladder? Lost a cat? Offering up vegetables from your garden? Share it with your neighbors! + + group-post-action-title: Post to a Discussion Group + group-post-action-body: | + Need to get in touch with other {{community_name}} dog owners? Have an opinion on a local environmental issue? Get the town discussion group conversations going! + event-action-title: Post an Event + event-action-body: | + Does your community organization have an event coming up? Publicize it on {{community_name}}'s CommonPlace! + + announce-action-title: Publicize an Announcement + announce-action-body: | + Does your business have a new promotion? Is your civic organization running a volunteer drive? Get the word out in tomorrow's Daily Bulletin!

          + + new-feed-action-title: Register a Community Feed + new-feed-action-body: | + Are you part of a community organization, business, municipal agency or cause that wants to send announcements and events through {{community_name}}'s CommonPlace? + +help_a_neighbor_out: + posts-section-title: Unresolved Neighborhood Posts + post-action-title: Post to Your Neighborhood + post-action-body: | + Need to borrow a ladder? Lost a cat? Offering up vegetables from your garden? Share it with your neighbors! diff --git a/mail/unknown_address.rb b/mail/unknown_address.rb new file mode 100644 index 000000000..807d6fd0b --- /dev/null +++ b/mail/unknown_address.rb @@ -0,0 +1,35 @@ +class UnknownAddress < MailBase + + def initialize(user_id) + @user = User.find(user_id) + end + + def subject + "CommonPlace post did not go through" + end + + def user + @user + end + + def community + user.community + end + + def community_name + community.name + end + + def community_slug + community.slug + end + + def organizer_email + community.organizer_email + end + + def tag + 'unknown_address' + end + +end diff --git a/mail/unknown_user.rb b/mail/unknown_user.rb new file mode 100644 index 000000000..8cb4549b7 --- /dev/null +++ b/mail/unknown_user.rb @@ -0,0 +1,15 @@ +class UnknownUser < MailBase + + def initialize(sender) + @to = sender + end + + def to + @to + end + + def tag + 'unknown_user' + end + +end diff --git a/mail/welcome.rb b/mail/welcome.rb new file mode 100644 index 000000000..6babf43e1 --- /dev/null +++ b/mail/welcome.rb @@ -0,0 +1,36 @@ +class Welcome < MailBase + + def initialize(user_id) + @user = User.find(user_id) + @community = user.community + end + + def user + @user + end + + def community + user.community + end + + def community_name + community.name + end + + def short_user_name + user.first_name + end + + def organizer_email + community.organizer_email + end + + def subject + "Welcome to #{community_name}'s CommonPlace!" + end + + def tag + 'welcome' + end + +end diff --git a/public/404.html b/public/404.html index eff660b90..9a48320a5 100644 --- a/public/404.html +++ b/public/404.html @@ -1,23 +1,19 @@ - - - - + + - The page you were looking for doesn't exist (404) - + @@ -27,4 +23,4 @@

          The page you were looking for doesn't exist.

          You may have mistyped the address or the page may have moved.

          - \ No newline at end of file + diff --git a/public/422.html b/public/422.html index b54e4a3ca..83660ab18 100644 --- a/public/422.html +++ b/public/422.html @@ -1,23 +1,19 @@ - - - - + + - The change you wanted was rejected (422) - + @@ -27,4 +23,4 @@

          The change you wanted was rejected.

          Maybe you tried to change something you didn't have access to.

          - \ No newline at end of file + diff --git a/public/500.html b/public/500.html index ec3bbf02c..b80307fc1 100644 --- a/public/500.html +++ b/public/500.html @@ -1,23 +1,19 @@ - - - - + + - We're sorry, but something went wrong (500) - + diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 000000000..f44ea94f1 Binary files /dev/null and b/public/favicon.png differ diff --git a/public/fonts/old-standard/OldStandard-Bold-webfont.eot b/public/fonts/old-standard/OldStandard-Bold-webfont.eot deleted file mode 100755 index 3d9fafb06..000000000 Binary files a/public/fonts/old-standard/OldStandard-Bold-webfont.eot and /dev/null differ diff --git a/public/fonts/old-standard/OldStandard-Bold-webfont.svg b/public/fonts/old-standard/OldStandard-Bold-webfont.svg deleted file mode 100755 index b6169391a..000000000 --- a/public/fonts/old-standard/OldStandard-Bold-webfont.svg +++ /dev/null @@ -1,149 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Designer : Alexey Kryukov _alexios@thessalonica.org.ru_ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/fonts/old-standard/OldStandard-Bold-webfont.ttf b/public/fonts/old-standard/OldStandard-Bold-webfont.ttf deleted file mode 100755 index 86813a392..000000000 Binary files a/public/fonts/old-standard/OldStandard-Bold-webfont.ttf and /dev/null differ diff --git a/public/fonts/old-standard/OldStandard-Bold-webfont.woff b/public/fonts/old-standard/OldStandard-Bold-webfont.woff deleted file mode 100755 index a98421c26..000000000 Binary files a/public/fonts/old-standard/OldStandard-Bold-webfont.woff and /dev/null differ diff --git a/public/fonts/old-standard/OldStandard-Italic-webfont.eot b/public/fonts/old-standard/OldStandard-Italic-webfont.eot deleted file mode 100755 index c9508aff1..000000000 Binary files a/public/fonts/old-standard/OldStandard-Italic-webfont.eot and /dev/null differ diff --git a/public/fonts/old-standard/OldStandard-Italic-webfont.svg b/public/fonts/old-standard/OldStandard-Italic-webfont.svg deleted file mode 100755 index b7005157b..000000000 --- a/public/fonts/old-standard/OldStandard-Italic-webfont.svg +++ /dev/null @@ -1,149 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Designer : Alexey Kryukov _alexios@thessalonica.org.ru_ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/fonts/old-standard/OldStandard-Italic-webfont.ttf b/public/fonts/old-standard/OldStandard-Italic-webfont.ttf deleted file mode 100755 index 63711bd08..000000000 Binary files a/public/fonts/old-standard/OldStandard-Italic-webfont.ttf and /dev/null differ diff --git a/public/fonts/old-standard/OldStandard-Italic-webfont.woff b/public/fonts/old-standard/OldStandard-Italic-webfont.woff deleted file mode 100755 index af76d8028..000000000 Binary files a/public/fonts/old-standard/OldStandard-Italic-webfont.woff and /dev/null differ diff --git a/public/fonts/old-standard/OldStandard-Regular-webfont.eot b/public/fonts/old-standard/OldStandard-Regular-webfont.eot deleted file mode 100755 index 76a84b376..000000000 Binary files a/public/fonts/old-standard/OldStandard-Regular-webfont.eot and /dev/null differ diff --git a/public/fonts/old-standard/OldStandard-Regular-webfont.svg b/public/fonts/old-standard/OldStandard-Regular-webfont.svg deleted file mode 100755 index e37ae476f..000000000 --- a/public/fonts/old-standard/OldStandard-Regular-webfont.svg +++ /dev/null @@ -1,151 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Designer : Alexey Kryukov _alexios@thessalonica.org.ru_ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/fonts/old-standard/OldStandard-Regular-webfont.ttf b/public/fonts/old-standard/OldStandard-Regular-webfont.ttf deleted file mode 100755 index 882ec9720..000000000 Binary files a/public/fonts/old-standard/OldStandard-Regular-webfont.ttf and /dev/null differ diff --git a/public/fonts/old-standard/OldStandard-Regular-webfont.woff b/public/fonts/old-standard/OldStandard-Regular-webfont.woff deleted file mode 100755 index 8d71e0ae7..000000000 Binary files a/public/fonts/old-standard/OldStandard-Regular-webfont.woff and /dev/null differ diff --git a/public/images/example-email.png b/public/images/example-email.png deleted file mode 100644 index 3ea40fe0a..000000000 Binary files a/public/images/example-email.png and /dev/null differ diff --git a/public/images/example-platform.png b/public/images/example-platform.png deleted file mode 100644 index 4f02951be..000000000 Binary files a/public/images/example-platform.png and /dev/null differ diff --git a/public/images/grey-dot.png b/public/images/grey-dot.png new file mode 100644 index 000000000..048a52e49 Binary files /dev/null and b/public/images/grey-dot.png differ diff --git a/public/images/join-commonplace.png b/public/images/join-commonplace.png new file mode 100644 index 000000000..353c8c0b2 Binary files /dev/null and b/public/images/join-commonplace.png differ diff --git a/public/images/learn_more/plane.png b/public/images/learn_more/plane.png new file mode 100644 index 000000000..356227e2e Binary files /dev/null and b/public/images/learn_more/plane.png differ diff --git a/public/images/test b/public/images/test deleted file mode 120000 index 09e775fbb..000000000 --- a/public/images/test +++ /dev/null @@ -1 +0,0 @@ -westroxbury \ No newline at end of file diff --git a/public/javascripts/administration.js b/public/javascripts/administration.js deleted file mode 100644 index 076f9eaf3..000000000 --- a/public/javascripts/administration.js +++ /dev/null @@ -1,36 +0,0 @@ - - -$(function () { - - renderMaps(); - var map = $('[data-map]').first().data('map') - - geocoder = new google.maps.Geocoder(); - - google.maps.event.addListener(map,'click', function(event) { - - geocoder.geocode({'latLng': event.latLng}, function(results, status) { - if (status == google.maps.GeocoderStatus.OK) { - if (results[0]) { - $.post("/addresses", - {"address[name]": results[0].formatted_address, - "address[lat]": results[0].geometry.location.lat(), - "address[lng]": results[0].geometry.location.lng() - }, function (response) { - var address = response.address; - if (address.created_at) { - renderMarker({position: {lat: address.lat, lng: address.lng}}, map); - $("#addresses").prepend("
        • " + address.name + "
        • "); - } - - - }, "json"); - } - } else { - alert("Geocoder failed due to: " + status); - } - - }); - }); - -}); \ No newline at end of file diff --git a/public/javascripts/app.js b/public/javascripts/app.js deleted file mode 100644 index 8966635e5..000000000 --- a/public/javascripts/app.js +++ /dev/null @@ -1,65 +0,0 @@ - -if (window.location.hash.slice(1) != "") { - window.location.pathname = window.location.hash.slice(1); - window.location.hash = ""; -} - -$(function() { - - HOST_HREF_REGEX = new RegExp("^" + window.location.protocol + "//" + window.location.host); - - - $.preLoadImages("/images/loading.gif"); - - $('a[data-remote]').live('click', function(e) { - e.preventDefault(); - var path = $(this).attr('href').replace(HOST_HREF_REGEX, ""); - ajaj("get", path, null); - window.location.hash = path; - }); - - $('div[data-href]').live('click', function(e) { - e.preventDefault(); - ajaj("get", $(this).attr('data-href'), null); - window.location.hash = $(this).attr('data-href'); - }); - - $('div[data-href] a').live('click', function(e) { - e.stopPropagation(); - if ($(this).attr('data-remote')) { - e.preventDefault(); - var path = $(this).attr('href').replace(HOST_HREF_REGEX, ""); - ajaj("get", path, null); - window.location.hash = path; - } - }); - - $('form[data-remote]').live('submit', function(e) { - $('input[type=image]',$(this)) - .replaceWith(''); - e.preventDefault(); - ajaj("post", $(this).attr('action'), $(this).serialize()); - }); - -}); - -function ajaj(method, path, data) { - $.ajax({ - type: method, - url: path, - data: data, - dataType: "json", - success: function(response) { - if (response.redirect_to) { - if (response.redirect_to.match(/^https?:/)) { - window.location = response.redirect_to; - } else { - window.location.hash = response.redirect_to; - ajaj("get", response.redirect_to, null); - } - } else { - merge(response); - } - } - }); -} diff --git a/public/javascripts/facebook_connect.js b/public/javascripts/facebook_connect.js deleted file mode 100644 index d20cafb27..000000000 --- a/public/javascripts/facebook_connect.js +++ /dev/null @@ -1,26 +0,0 @@ -$(document).ready(function() { - FB.init({ - appId:179741908724938, cookie:true, - status:true, xfbml:true - }); -}); -function userExistsWithFacebookUID(uid) -{ - $.post() -} -function facebookUserWantsToLogIn(user) -{ - FB.api('/me', function(user) { - // Send the user to the User Sessions controller to handle the login - window.location = '/user_sessions/create_from_facebook'; - }); -} -function facebookUserLoggedIn(user) -{ - FB.api('/me', function(user) { - $('#user_full_name').val(user.name); - $('#user_email').val(user.email); - $('#user_facebook_uid').val(user.id); - $('#user_address').val(user.address.street); - }); -} \ No newline at end of file diff --git a/public/javascripts/facebooker.js b/public/javascripts/facebooker.js deleted file mode 100644 index 9890ecb89..000000000 --- a/public/javascripts/facebooker.js +++ /dev/null @@ -1,93 +0,0 @@ - -function $(element) { - if (typeof element == "string") { - element=document.getElementById(element); - } - if (element) - extend_instance(element,Element); - return element; -} - -function extend_instance(instance,hash) { - for (var name in hash) { - instance[name] = hash[name]; - } -} - -var Element = { - "hide": function () { - this.setStyle("display","none") - }, - "show": function () { - this.setStyle("display","block") - }, - "visible": function () { - return (this.getStyle("display") != "none"); - }, - "toggle": function () { - if (this.visible) { - this.hide(); - } else { - this.show(); - } - } -}; - -function encodeURIComponent(str) { - if (typeof(str) == "string") { - return str.replace(/=/g,'%3D').replace(/&/g,'%26'); - } - //checkboxes and radio buttons return objects instead of a string - else if(typeof(str) == "object"){ - for (prop in str) - { - return str[prop].replace(/=/g,'%3D').replace(/&/g,'%26'); - } - } -}; - -var Form = {}; -Form.serialize = function(form_element) { - return $(form_element).serialize(); -}; - -Ajax.Updater = function (container,url,options) { - this.container = container; - this.url=url; - this.ajax = new Ajax(); - this.ajax.requireLogin = 1; - if (options["onSuccess"]) { - this.ajax.responseType = Ajax.JSON; - this.ajax.ondone = options["onSuccess"]; - } else { - this.ajax.responseType = Ajax.FBML; - this.ajax.ondone = function(data) { - $(container).setInnerFBML(data); - } - } - if (options["onFailure"]) { - this.ajax.onerror = options["onFailure"]; - } - - if (!options['parameters']) { - options['parameters'] = {} - } - - // simulate other verbs over post - if (options['method']) { - options['parameters']['_method'] = options['method']; - } - - this.ajax.post(url,options['parameters']); - if (options["onLoading"]) { - options["onLoading"].call() - } -}; -Ajax.Request = function(url,options) { - Ajax.Updater('unused',url,options); -}; - -PeriodicalExecuter = function (callback, frequency) { - setTimeout(callback, frequency *1000); - setTimeout(function() { new PeriodicalExecuter(callback,frequency); }, frequency*1000); -}; diff --git a/public/javascripts/feed_profile.js b/public/javascripts/feed_profile.js deleted file mode 100644 index 484ce3f23..000000000 --- a/public/javascripts/feed_profile.js +++ /dev/null @@ -1,9 +0,0 @@ -$(document).ready(function(){ - $('#post-to-feed h2 nav li:last-child').hide(); - $('#post-to-feed h2 nav ul').hover(function(){ - $('#post-to-feed h2 nav li:last-child').show(); - }, function(){ - $('#post-to-feed h2 nav li:last-child').hide(); - }) -}); - diff --git a/public/javascripts/infobox.js b/public/javascripts/infobox.js deleted file mode 100644 index 574c12c78..000000000 --- a/public/javascripts/infobox.js +++ /dev/null @@ -1,14 +0,0 @@ - -function setInfoBoxPosition() { - if ($('#information').get(0) && $('#syndicate').get(0)) { - if ($(window).scrollTop() - 10 > $('#information').parent().offset().top){ - $('#information').css({'position':'fixed','top': 10, 'width':485}); - } else { - $('#information').css({'position': 'static'}); - } - } -} - -$(function(){ - window.onscroll = setInfoBoxPosition; -}); diff --git a/public/javascripts/jquery-1.4.3.js b/public/javascripts/jquery-1.4.3.js deleted file mode 100644 index ad9a79c43..000000000 --- a/public/javascripts/jquery-1.4.3.js +++ /dev/null @@ -1,6883 +0,0 @@ -/*! - * jQuery JavaScript Library v1.4.3 - * http://jquery.com/ - * - * Copyright 2010, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2010, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Thu Oct 14 23:10:06 2010 -0400 - */ -(function( window, undefined ) { - -// Use the correct document accordingly with window argument (sandbox) -var document = window.document; -var jQuery = (function() { - -// Define a local copy of jQuery -var jQuery = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context ); - }, - - // Map over jQuery in case of overwrite - _jQuery = window.jQuery, - - // Map over the $ in case of overwrite - _$ = window.$, - - // A central reference to the root jQuery(document) - rootjQuery, - - // A simple way to check for HTML strings or ID strings - // (both of which we optimize for) - quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/, - - // Is it a simple selector - isSimple = /^.[^:#\[\.,]*$/, - - // Check if a string has a non-whitespace character in it - rnotwhite = /\S/, - rwhite = /\s/, - - // Used for trimming whitespace - trimLeft = /^\s+/, - trimRight = /\s+$/, - - // Check for non-word characters - rnonword = /\W/, - - // Check for digits - rdigit = /\d/, - - // Match a standalone tag - rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, - - // JSON RegExp - rvalidchars = /^[\],:{}\s]*$/, - rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, - rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, - rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, - - // Useragent RegExp - rwebkit = /(webkit)[ \/]([\w.]+)/, - ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, - rmsie = /(msie) ([\w.]+)/, - rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, - - // Keep a UserAgent string for use with jQuery.browser - userAgent = navigator.userAgent, - - // For matching the engine and version of the browser - browserMatch, - - // Has the ready events already been bound? - readyBound = false, - - // The functions to execute on DOM ready - readyList = [], - - // The ready event handler - DOMContentLoaded, - - // Save a reference to some core methods - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty, - push = Array.prototype.push, - slice = Array.prototype.slice, - trim = String.prototype.trim, - indexOf = Array.prototype.indexOf, - - // [[Class]] -> type pairs - class2type = {}; - -jQuery.fn = jQuery.prototype = { - init: function( selector, context ) { - var match, elem, ret, doc; - - // Handle $(""), $(null), or $(undefined) - if ( !selector ) { - return this; - } - - // Handle $(DOMElement) - if ( selector.nodeType ) { - this.context = this[0] = selector; - this.length = 1; - return this; - } - - // The body element only exists once, optimize finding it - if ( selector === "body" && !context && document.body ) { - this.context = document; - this[0] = document.body; - this.selector = "body"; - this.length = 1; - return this; - } - - // Handle HTML strings - if ( typeof selector === "string" ) { - // Are we dealing with HTML string or an ID? - match = quickExpr.exec( selector ); - - // Verify a match, and that no context was specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) { - doc = (context ? context.ownerDocument || context : document); - - // If a single string is passed in and it's a single tag - // just do a createElement and skip the rest - ret = rsingleTag.exec( selector ); - - if ( ret ) { - if ( jQuery.isPlainObject( context ) ) { - selector = [ document.createElement( ret[1] ) ]; - jQuery.fn.attr.call( selector, context, true ); - - } else { - selector = [ doc.createElement( ret[1] ) ]; - } - - } else { - ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); - selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes; - } - - return jQuery.merge( this, selector ); - - // HANDLE: $("#id") - } else { - elem = document.getElementById( match[2] ); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id !== match[2] ) { - return rootjQuery.find( selector ); - } - - // Otherwise, we inject the element directly into the jQuery object - this.length = 1; - this[0] = elem; - } - - this.context = document; - this.selector = selector; - return this; - } - - // HANDLE: $("TAG") - } else if ( !context && !rnonword.test( selector ) ) { - this.selector = selector; - this.context = document; - selector = document.getElementsByTagName( selector ); - return jQuery.merge( this, selector ); - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return (context || rootjQuery).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return jQuery( context ).find( selector ); - } - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { - return rootjQuery.ready( selector ); - } - - if (selector.selector !== undefined) { - this.selector = selector.selector; - this.context = selector.context; - } - - return jQuery.makeArray( selector, this ); - }, - - // Start with an empty selector - selector: "", - - // The current version of jQuery being used - jquery: "1.4.3", - - // The default length of a jQuery object is 0 - length: 0, - - // The number of elements contained in the matched element set - size: function() { - return this.length; - }, - - toArray: function() { - return slice.call( this, 0 ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num == null ? - - // Return a 'clean' array - this.toArray() : - - // Return just the object - ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] ); - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems, name, selector ) { - // Build a new jQuery matched element set - var ret = jQuery(); - - if ( jQuery.isArray( elems ) ) { - push.apply( ret, elems ); - - } else { - jQuery.merge( ret, elems ); - } - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - ret.context = this.context; - - if ( name === "find" ) { - ret.selector = this.selector + (this.selector ? " " : "") + selector; - } else if ( name ) { - ret.selector = this.selector + "." + name + "(" + selector + ")"; - } - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - return jQuery.each( this, callback, args ); - }, - - ready: function( fn ) { - // Attach the listeners - jQuery.bindReady(); - - // If the DOM is already ready - if ( jQuery.isReady ) { - // Execute the function immediately - fn.call( document, jQuery ); - - // Otherwise, remember the function for later - } else if ( readyList ) { - // Add the function to the wait list - readyList.push( fn ); - } - - return this; - }, - - eq: function( i ) { - return i === -1 ? - this.slice( i ) : - this.slice( i, +i + 1 ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ), - "slice", slice.call(arguments).join(",") ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map(this, function( elem, i ) { - return callback.call( elem, i, elem ); - })); - }, - - end: function() { - return this.prevObject || jQuery(null); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: [].sort, - splice: [].splice -}; - -// Give the init function the jQuery prototype for later instantiation -jQuery.fn.init.prototype = jQuery.fn; - -jQuery.extend = jQuery.fn.extend = function() { - // copy reference to target object - var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy, copyIsArray; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction(target) ) { - target = {}; - } - - // extend jQuery itself if only one argument is passed - if ( length === i ) { - target = this; - --i; - } - - for ( ; i < length; i++ ) { - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) { - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { - if ( copyIsArray ) { - copyIsArray = false; - clone = src && jQuery.isArray(src) ? src : []; - - } else { - clone = src && jQuery.isPlainObject(src) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend({ - noConflict: function( deep ) { - window.$ = _$; - - if ( deep ) { - window.jQuery = _jQuery; - } - - return jQuery; - }, - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Handle when the DOM is ready - ready: function( wait ) { - // A third-party is pushing the ready event forwards - if ( wait === true ) { - jQuery.readyWait--; - } - - // Make sure that the DOM is not already loaded - if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) { - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( !document.body ) { - return setTimeout( jQuery.ready, 1 ); - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - if ( readyList ) { - // Execute all of them - var fn, i = 0; - while ( (fn = readyList[ i++ ]) ) { - fn.call( document, jQuery ); - } - - // Reset the list of functions - readyList = null; - } - - // Trigger any bound ready events - if ( jQuery.fn.triggerHandler ) { - jQuery( document ).triggerHandler( "ready" ); - } - } - }, - - bindReady: function() { - if ( readyBound ) { - return; - } - - readyBound = true; - - // Catch cases where $(document).ready() is called after the - // browser event has already occurred. - if ( document.readyState === "complete" ) { - // Handle it asynchronously to allow scripts the opportunity to delay ready - return setTimeout( jQuery.ready, 1 ); - } - - // Mozilla, Opera and webkit nightlies currently support this event - if ( document.addEventListener ) { - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", jQuery.ready, false ); - - // If IE event model is used - } else if ( document.attachEvent ) { - // ensure firing before onload, - // maybe late but safe also for iframes - document.attachEvent("onreadystatechange", DOMContentLoaded); - - // A fallback to window.onload, that will always work - window.attachEvent( "onload", jQuery.ready ); - - // If IE and not a frame - // continually check to see if the document is ready - var toplevel = false; - - try { - toplevel = window.frameElement == null; - } catch(e) {} - - if ( document.documentElement.doScroll && toplevel ) { - doScrollCheck(); - } - } - }, - - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). - isFunction: function( obj ) { - return jQuery.type(obj) === "function"; - }, - - isArray: Array.isArray || function( obj ) { - return jQuery.type(obj) === "array"; - }, - - // A crude way of determining if an object is a window - isWindow: function( obj ) { - return obj && typeof obj === "object" && "setInterval" in obj; - }, - - isNaN: function( obj ) { - return obj == null || !rdigit.test( obj ) || isNaN( obj ); - }, - - type: function( obj ) { - return obj == null ? - String( obj ) : - class2type[ toString.call(obj) ] || "object"; - }, - - isPlainObject: function( obj ) { - // Must be an Object. - // Because of IE, we also have to check the presence of the constructor property. - // Make sure that DOM nodes and window objects don't pass through, as well - if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { - return false; - } - - // Not own constructor property must be Object - if ( obj.constructor && - !hasOwn.call(obj, "constructor") && - !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { - return false; - } - - // Own properties are enumerated firstly, so to speed up, - // if last one is own, then all properties are own. - - var key; - for ( key in obj ) {} - - return key === undefined || hasOwn.call( obj, key ); - }, - - isEmptyObject: function( obj ) { - for ( var name in obj ) { - return false; - } - return true; - }, - - error: function( msg ) { - throw msg; - }, - - parseJSON: function( data ) { - if ( typeof data !== "string" || !data ) { - return null; - } - - // Make sure leading/trailing whitespace is removed (IE can't handle it) - data = jQuery.trim( data ); - - // Make sure the incoming data is actual JSON - // Logic borrowed from http://json.org/json2.js - if ( rvalidchars.test(data.replace(rvalidescape, "@") - .replace(rvalidtokens, "]") - .replace(rvalidbraces, "")) ) { - - // Try to use the native JSON parser first - return window.JSON && window.JSON.parse ? - window.JSON.parse( data ) : - (new Function("return " + data))(); - - } else { - jQuery.error( "Invalid JSON: " + data ); - } - }, - - noop: function() {}, - - // Evalulates a script in a global context - globalEval: function( data ) { - if ( data && rnotwhite.test(data) ) { - // Inspired by code by Andrea Giammarchi - // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html - var head = document.getElementsByTagName("head")[0] || document.documentElement, - script = document.createElement("script"); - - script.type = "text/javascript"; - - if ( jQuery.support.scriptEval ) { - script.appendChild( document.createTextNode( data ) ); - } else { - script.text = data; - } - - // Use insertBefore instead of appendChild to circumvent an IE6 bug. - // This arises when a base node is used (#2709). - head.insertBefore( script, head.firstChild ); - head.removeChild( script ); - } - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); - }, - - // args is for internal usage only - each: function( object, callback, args ) { - var name, i = 0, - length = object.length, - isObj = length === undefined || jQuery.isFunction(object); - - if ( args ) { - if ( isObj ) { - for ( name in object ) { - if ( callback.apply( object[ name ], args ) === false ) { - break; - } - } - } else { - for ( ; i < length; ) { - if ( callback.apply( object[ i++ ], args ) === false ) { - break; - } - } - } - - // A special, fast, case for the most common use of each - } else { - if ( isObj ) { - for ( name in object ) { - if ( callback.call( object[ name ], name, object[ name ] ) === false ) { - break; - } - } - } else { - for ( var value = object[0]; - i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} - } - } - - return object; - }, - - // Use native String.trim function wherever possible - trim: trim ? - function( text ) { - return text == null ? - "" : - trim.call( text ); - } : - - // Otherwise use our own trimming functionality - function( text ) { - return text == null ? - "" : - text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); - }, - - // results is for internal usage only - makeArray: function( array, results ) { - var ret = results || []; - - if ( array != null ) { - // The window, strings (and functions) also have 'length' - // The extra typeof function check is to prevent crashes - // in Safari 2 (See: #3039) - // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 - var type = jQuery.type(array); - - if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { - push.call( ret, array ); - } else { - jQuery.merge( ret, array ); - } - } - - return ret; - }, - - inArray: function( elem, array ) { - if ( array.indexOf ) { - return array.indexOf( elem ); - } - - for ( var i = 0, length = array.length; i < length; i++ ) { - if ( array[ i ] === elem ) { - return i; - } - } - - return -1; - }, - - merge: function( first, second ) { - var i = first.length, j = 0; - - if ( typeof second.length === "number" ) { - for ( var l = second.length; j < l; j++ ) { - first[ i++ ] = second[ j ]; - } - - } else { - while ( second[j] !== undefined ) { - first[ i++ ] = second[ j++ ]; - } - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, inv ) { - var ret = [], retVal; - inv = !!inv; - - // Go through the array, only saving the items - // that pass the validator function - for ( var i = 0, length = elems.length; i < length; i++ ) { - retVal = !!callback( elems[ i ], i ); - if ( inv !== retVal ) { - ret.push( elems[ i ] ); - } - } - - return ret; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var ret = [], value; - - // Go through the array, translating each of the items to their - // new value (or values). - for ( var i = 0, length = elems.length; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret[ ret.length ] = value; - } - } - - return ret.concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - proxy: function( fn, proxy, thisObject ) { - if ( arguments.length === 2 ) { - if ( typeof proxy === "string" ) { - thisObject = fn; - fn = thisObject[ proxy ]; - proxy = undefined; - - } else if ( proxy && !jQuery.isFunction( proxy ) ) { - thisObject = proxy; - proxy = undefined; - } - } - - if ( !proxy && fn ) { - proxy = function() { - return fn.apply( thisObject || this, arguments ); - }; - } - - // Set the guid of unique handler to the same of original handler, so it can be removed - if ( fn ) { - proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; - } - - // So proxy can be declared as an argument - return proxy; - }, - - // Mutifunctional method to get and set values to a collection - // The value/s can be optionally by executed if its a function - access: function( elems, key, value, exec, fn, pass ) { - var length = elems.length; - - // Setting many attributes - if ( typeof key === "object" ) { - for ( var k in key ) { - jQuery.access( elems, k, key[k], exec, fn, value ); - } - return elems; - } - - // Setting one attribute - if ( value !== undefined ) { - // Optionally, function values get executed if exec is true - exec = !pass && exec && jQuery.isFunction(value); - - for ( var i = 0; i < length; i++ ) { - fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); - } - - return elems; - } - - // Getting an attribute - return length ? fn( elems[0], key ) : undefined; - }, - - now: function() { - return (new Date()).getTime(); - }, - - // Use of jQuery.browser is frowned upon. - // More details: http://docs.jquery.com/Utilities/jQuery.browser - uaMatch: function( ua ) { - ua = ua.toLowerCase(); - - var match = rwebkit.exec( ua ) || - ropera.exec( ua ) || - rmsie.exec( ua ) || - ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || - []; - - return { browser: match[1] || "", version: match[2] || "0" }; - }, - - browser: {} -}); - -// Populate the class2type map -jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -}); - -browserMatch = jQuery.uaMatch( userAgent ); -if ( browserMatch.browser ) { - jQuery.browser[ browserMatch.browser ] = true; - jQuery.browser.version = browserMatch.version; -} - -// Deprecated, use jQuery.browser.webkit instead -if ( jQuery.browser.webkit ) { - jQuery.browser.safari = true; -} - -if ( indexOf ) { - jQuery.inArray = function( elem, array ) { - return indexOf.call( array, elem ); - }; -} - -// Verify that \s matches non-breaking spaces -// (IE fails on this test) -if ( !rwhite.test( "\xA0" ) ) { - trimLeft = /^[\s\xA0]+/; - trimRight = /[\s\xA0]+$/; -} - -// All jQuery objects should point back to these -rootjQuery = jQuery(document); - -// Cleanup functions for the document ready method -if ( document.addEventListener ) { - DOMContentLoaded = function() { - document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); - jQuery.ready(); - }; - -} else if ( document.attachEvent ) { - DOMContentLoaded = function() { - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( document.readyState === "complete" ) { - document.detachEvent( "onreadystatechange", DOMContentLoaded ); - jQuery.ready(); - } - }; -} - -// The DOM ready check for Internet Explorer -function doScrollCheck() { - if ( jQuery.isReady ) { - return; - } - - try { - // If IE is used, use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - document.documentElement.doScroll("left"); - } catch(e) { - setTimeout( doScrollCheck, 1 ); - return; - } - - // and execute any waiting functions - jQuery.ready(); -} - -// Expose jQuery to the global object -return (window.jQuery = window.$ = jQuery); - -})(); - - -(function() { - - jQuery.support = {}; - - var root = document.documentElement, - script = document.createElement("script"), - div = document.createElement("div"), - id = "script" + jQuery.now(); - - div.style.display = "none"; - div.innerHTML = "
          a"; - - var all = div.getElementsByTagName("*"), - a = div.getElementsByTagName("a")[0], - select = document.createElement("select"), - opt = select.appendChild( document.createElement("option") ); - - // Can't get basic test support - if ( !all || !all.length || !a ) { - return; - } - - jQuery.support = { - // IE strips leading whitespace when .innerHTML is used - leadingWhitespace: div.firstChild.nodeType === 3, - - // Make sure that tbody elements aren't automatically inserted - // IE will insert them into empty tables - tbody: !div.getElementsByTagName("tbody").length, - - // Make sure that link elements get serialized correctly by innerHTML - // This requires a wrapper element in IE - htmlSerialize: !!div.getElementsByTagName("link").length, - - // Get the style information from getAttribute - // (IE uses .cssText insted) - style: /red/.test( a.getAttribute("style") ), - - // Make sure that URLs aren't manipulated - // (IE normalizes it by default) - hrefNormalized: a.getAttribute("href") === "/a", - - // Make sure that element opacity exists - // (IE uses filter instead) - // Use a regex to work around a WebKit issue. See #5145 - opacity: /^0.55$/.test( a.style.opacity ), - - // Verify style float existence - // (IE uses styleFloat instead of cssFloat) - cssFloat: !!a.style.cssFloat, - - // Make sure that if no value is specified for a checkbox - // that it defaults to "on". - // (WebKit defaults to "" instead) - checkOn: div.getElementsByTagName("input")[0].value === "on", - - // Make sure that a selected-by-default option has a working selected property. - // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) - optSelected: opt.selected, - - // Will be defined later - optDisabled: false, - checkClone: false, - scriptEval: false, - noCloneEvent: true, - boxModel: null, - inlineBlockNeedsLayout: false, - shrinkWrapBlocks: false, - reliableHiddenOffsets: true - }; - - // Make sure that the options inside disabled selects aren't marked as disabled - // (WebKit marks them as diabled) - select.disabled = true; - jQuery.support.optDisabled = !opt.disabled; - - script.type = "text/javascript"; - try { - script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); - } catch(e) {} - - root.insertBefore( script, root.firstChild ); - - // Make sure that the execution of code works by injecting a script - // tag with appendChild/createTextNode - // (IE doesn't support this, fails, and uses .text instead) - if ( window[ id ] ) { - jQuery.support.scriptEval = true; - delete window[ id ]; - } - - root.removeChild( script ); - - if ( div.attachEvent && div.fireEvent ) { - div.attachEvent("onclick", function click() { - // Cloning a node shouldn't copy over any - // bound event handlers (IE does this) - jQuery.support.noCloneEvent = false; - div.detachEvent("onclick", click); - }); - div.cloneNode(true).fireEvent("onclick"); - } - - div = document.createElement("div"); - div.innerHTML = ""; - - var fragment = document.createDocumentFragment(); - fragment.appendChild( div.firstChild ); - - // WebKit doesn't clone checked state correctly in fragments - jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; - - // Figure out if the W3C box model works as expected - // document.body must exist before we can do this - jQuery(function() { - var div = document.createElement("div"); - div.style.width = div.style.paddingLeft = "1px"; - - document.body.appendChild( div ); - jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; - - if ( "zoom" in div.style ) { - // Check if natively block-level elements act like inline-block - // elements when setting their display to 'inline' and giving - // them layout - // (IE < 8 does this) - div.style.display = "inline"; - div.style.zoom = 1; - jQuery.support.inlineBlockNeedsLayout = div.offsetWidth === 2; - - // Check if elements with layout shrink-wrap their children - // (IE 6 does this) - div.style.display = ""; - div.innerHTML = "
          "; - jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2; - } - - div.innerHTML = "
          t
          "; - var tds = div.getElementsByTagName("td"); - - // Check if table cells still have offsetWidth/Height when they are set - // to display:none and there are still other visible table cells in a - // table row; if so, offsetWidth/Height are not reliable for use when - // determining if an element has been hidden directly using - // display:none (it is still safe to use offsets if a parent element is - // hidden; don safety goggles and see bug #4512 for more information). - // (only IE 8 fails this test) - jQuery.support.reliableHiddenOffsets = tds[0].offsetHeight === 0; - - tds[0].style.display = ""; - tds[1].style.display = "none"; - - // Check if empty table cells still have offsetWidth/Height - // (IE < 8 fail this test) - jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0; - div.innerHTML = ""; - - document.body.removeChild( div ).style.display = "none"; - div = tds = null; - }); - - // Technique from Juriy Zaytsev - // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ - var eventSupported = function( eventName ) { - var el = document.createElement("div"); - eventName = "on" + eventName; - - var isSupported = (eventName in el); - if ( !isSupported ) { - el.setAttribute(eventName, "return;"); - isSupported = typeof el[eventName] === "function"; - } - el = null; - - return isSupported; - }; - - jQuery.support.submitBubbles = eventSupported("submit"); - jQuery.support.changeBubbles = eventSupported("change"); - - // release memory in IE - root = script = div = all = a = null; -})(); - -jQuery.props = { - "for": "htmlFor", - "class": "className", - readonly: "readOnly", - maxlength: "maxLength", - cellspacing: "cellSpacing", - rowspan: "rowSpan", - colspan: "colSpan", - tabindex: "tabIndex", - usemap: "useMap", - frameborder: "frameBorder" -}; - - - - -var windowData = {}, - rbrace = /^(?:\{.*\}|\[.*\])$/; - -jQuery.extend({ - cache: {}, - - // Please use with caution - uuid: 0, - - // Unique for each copy of jQuery on the page - expando: "jQuery" + jQuery.now(), - - // The following elements throw uncatchable exceptions if you - // attempt to add expando properties to them. - noData: { - "embed": true, - // Ban all objects except for Flash (which handle expandos) - "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", - "applet": true - }, - - data: function( elem, name, data ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - elem = elem == window ? - windowData : - elem; - - var isNode = elem.nodeType, - id = isNode ? elem[ jQuery.expando ] : null, - cache = jQuery.cache, thisCache; - - if ( isNode && !id && typeof name === "string" && data === undefined ) { - return; - } - - // Get the data from the object directly - if ( !isNode ) { - cache = elem; - - // Compute a unique ID for the element - } else if ( !id ) { - elem[ jQuery.expando ] = id = ++jQuery.uuid; - } - - // Avoid generating a new cache unless none exists and we - // want to manipulate it. - if ( typeof name === "object" ) { - if ( isNode ) { - cache[ id ] = jQuery.extend(cache[ id ], name); - - } else { - jQuery.extend( cache, name ); - } - - } else if ( isNode && !cache[ id ] ) { - cache[ id ] = {}; - } - - thisCache = isNode ? cache[ id ] : cache; - - // Prevent overriding the named cache with undefined values - if ( data !== undefined ) { - thisCache[ name ] = data; - } - - return typeof name === "string" ? thisCache[ name ] : thisCache; - }, - - removeData: function( elem, name ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - elem = elem == window ? - windowData : - elem; - - var isNode = elem.nodeType, - id = isNode ? elem[ jQuery.expando ] : elem, - cache = jQuery.cache, - thisCache = isNode ? cache[ id ] : id; - - // If we want to remove a specific section of the element's data - if ( name ) { - if ( thisCache ) { - // Remove the section of cache data - delete thisCache[ name ]; - - // If we've removed all the data, remove the element's cache - if ( isNode && jQuery.isEmptyObject(thisCache) ) { - jQuery.removeData( elem ); - } - } - - // Otherwise, we want to remove all of the element's data - } else { - if ( isNode && jQuery.support.deleteExpando ) { - delete elem[ jQuery.expando ]; - - } else if ( elem.removeAttribute ) { - elem.removeAttribute( jQuery.expando ); - - // Completely remove the data cache - } else if ( isNode ) { - delete cache[ id ]; - - // Remove all fields from the object - } else { - for ( var n in elem ) { - delete elem[ n ]; - } - } - } - }, - - // A method for determining if a DOM node can handle the data expando - acceptData: function( elem ) { - if ( elem.nodeName ) { - var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; - - if ( match ) { - return !(match === true || elem.getAttribute("classid") !== match); - } - } - - return true; - } -}); - -jQuery.fn.extend({ - data: function( key, value ) { - if ( typeof key === "undefined" ) { - return this.length ? jQuery.data( this[0] ) : null; - - } else if ( typeof key === "object" ) { - return this.each(function() { - jQuery.data( this, key ); - }); - } - - var parts = key.split("."); - parts[1] = parts[1] ? "." + parts[1] : ""; - - if ( value === undefined ) { - var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); - - // Try to fetch any internally stored data first - if ( data === undefined && this.length ) { - data = jQuery.data( this[0], key ); - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && this[0].nodeType === 1 ) { - data = this[0].getAttribute( "data-" + key ); - - if ( typeof data === "string" ) { - try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - !jQuery.isNaN( data ) ? parseFloat( data ) : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; - } catch( e ) {} - - } else { - data = undefined; - } - } - } - - return data === undefined && parts[1] ? - this.data( parts[0] ) : - data; - - } else { - return this.each(function() { - var $this = jQuery( this ), args = [ parts[0], value ]; - - $this.triggerHandler( "setData" + parts[1] + "!", args ); - jQuery.data( this, key, value ); - $this.triggerHandler( "changeData" + parts[1] + "!", args ); - }); - } - }, - - removeData: function( key ) { - return this.each(function() { - jQuery.removeData( this, key ); - }); - } -}); - - - - -jQuery.extend({ - queue: function( elem, type, data ) { - if ( !elem ) { - return; - } - - type = (type || "fx") + "queue"; - var q = jQuery.data( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( !data ) { - return q || []; - } - - if ( !q || jQuery.isArray(data) ) { - q = jQuery.data( elem, type, jQuery.makeArray(data) ); - - } else { - q.push( data ); - } - - return q; - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), fn = queue.shift(); - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - } - - if ( fn ) { - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift("inprogress"); - } - - fn.call(elem, function() { - jQuery.dequeue(elem, type); - }); - } - } -}); - -jQuery.fn.extend({ - queue: function( type, data ) { - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - } - - if ( data === undefined ) { - return jQuery.queue( this[0], type ); - } - return this.each(function( i ) { - var queue = jQuery.queue( this, type, data ); - - if ( type === "fx" && queue[0] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - }); - }, - dequeue: function( type ) { - return this.each(function() { - jQuery.dequeue( this, type ); - }); - }, - - // Based off of the plugin by Clint Helfers, with permission. - // http://blindsignals.com/index.php/2009/07/jquery-delay/ - delay: function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; - type = type || "fx"; - - return this.queue( type, function() { - var elem = this; - setTimeout(function() { - jQuery.dequeue( elem, type ); - }, time ); - }); - }, - - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - } -}); - - - - -var rclass = /[\n\t]/g, - rspaces = /\s+/, - rreturn = /\r/g, - rspecialurl = /^(?:href|src|style)$/, - rtype = /^(?:button|input)$/i, - rfocusable = /^(?:button|input|object|select|textarea)$/i, - rclickable = /^a(?:rea)?$/i, - rradiocheck = /^(?:radio|checkbox)$/i; - -jQuery.fn.extend({ - attr: function( name, value ) { - return jQuery.access( this, name, value, true, jQuery.attr ); - }, - - removeAttr: function( name, fn ) { - return this.each(function(){ - jQuery.attr( this, name, "" ); - if ( this.nodeType === 1 ) { - this.removeAttribute( name ); - } - }); - }, - - addClass: function( value ) { - if ( jQuery.isFunction(value) ) { - return this.each(function(i) { - var self = jQuery(this); - self.addClass( value.call(this, i, self.attr("class")) ); - }); - } - - if ( value && typeof value === "string" ) { - var classNames = (value || "").split( rspaces ); - - for ( var i = 0, l = this.length; i < l; i++ ) { - var elem = this[i]; - - if ( elem.nodeType === 1 ) { - if ( !elem.className ) { - elem.className = value; - - } else { - var className = " " + elem.className + " ", setClass = elem.className; - for ( var c = 0, cl = classNames.length; c < cl; c++ ) { - if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { - setClass += " " + classNames[c]; - } - } - elem.className = jQuery.trim( setClass ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - if ( jQuery.isFunction(value) ) { - return this.each(function(i) { - var self = jQuery(this); - self.removeClass( value.call(this, i, self.attr("class")) ); - }); - } - - if ( (value && typeof value === "string") || value === undefined ) { - var classNames = (value || "").split( rspaces ); - - for ( var i = 0, l = this.length; i < l; i++ ) { - var elem = this[i]; - - if ( elem.nodeType === 1 && elem.className ) { - if ( value ) { - var className = (" " + elem.className + " ").replace(rclass, " "); - for ( var c = 0, cl = classNames.length; c < cl; c++ ) { - className = className.replace(" " + classNames[c] + " ", " "); - } - elem.className = jQuery.trim( className ); - - } else { - elem.className = ""; - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value, isBool = typeof stateVal === "boolean"; - - if ( jQuery.isFunction( value ) ) { - return this.each(function(i) { - var self = jQuery(this); - self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); - }); - } - - return this.each(function() { - if ( type === "string" ) { - // toggle individual class names - var className, i = 0, self = jQuery(this), - state = stateVal, - classNames = value.split( rspaces ); - - while ( (className = classNames[ i++ ]) ) { - // check each className given, space seperated list - state = isBool ? state : !self.hasClass( className ); - self[ state ? "addClass" : "removeClass" ]( className ); - } - - } else if ( type === "undefined" || type === "boolean" ) { - if ( this.className ) { - // store className if set - jQuery.data( this, "__className__", this.className ); - } - - // toggle whole className - this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || ""; - } - }); - }, - - hasClass: function( selector ) { - var className = " " + selector + " "; - for ( var i = 0, l = this.length; i < l; i++ ) { - if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { - return true; - } - } - - return false; - }, - - val: function( value ) { - if ( !arguments.length ) { - var elem = this[0]; - - if ( elem ) { - if ( jQuery.nodeName( elem, "option" ) ) { - // attributes.value is undefined in Blackberry 4.7 but - // uses .value. See #6932 - var val = elem.attributes.value; - return !val || val.specified ? elem.value : elem.text; - } - - // We need to handle select boxes special - if ( jQuery.nodeName( elem, "select" ) ) { - var index = elem.selectedIndex, - values = [], - options = elem.options, - one = elem.type === "select-one"; - - // Nothing was selected - if ( index < 0 ) { - return null; - } - - // Loop through all the selected options - for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { - var option = options[ i ]; - - // Don't return options that are disabled or in a disabled optgroup - if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && - (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { - - // Get the specific value for the option - value = jQuery(option).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - } - - // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified - if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { - return elem.getAttribute("value") === null ? "on" : elem.value; - } - - - // Everything else, we just grab the value - return (elem.value || "").replace(rreturn, ""); - - } - - return undefined; - } - - var isFunction = jQuery.isFunction(value); - - return this.each(function(i) { - var self = jQuery(this), val = value; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( isFunction ) { - val = value.call(this, i, self.val()); - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - } else if ( typeof val === "number" ) { - val += ""; - } else if ( jQuery.isArray(val) ) { - val = jQuery.map(val, function (value) { - return value == null ? "" : value + ""; - }); - } - - if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { - this.checked = jQuery.inArray( self.val(), val ) >= 0; - - } else if ( jQuery.nodeName( this, "select" ) ) { - var values = jQuery.makeArray(val); - - jQuery( "option", this ).each(function() { - this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; - }); - - if ( !values.length ) { - this.selectedIndex = -1; - } - - } else { - this.value = val; - } - }); - } -}); - -jQuery.extend({ - attrFn: { - val: true, - css: true, - html: true, - text: true, - data: true, - width: true, - height: true, - offset: true - }, - - attr: function( elem, name, value, pass ) { - // don't set attributes on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { - return undefined; - } - - if ( pass && name in jQuery.attrFn ) { - return jQuery(elem)[name](value); - } - - var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), - // Whether we are setting (or getting) - set = value !== undefined; - - // Try to normalize/fix the name - name = notxml && jQuery.props[ name ] || name; - - // Only do all the following if this is a node (faster for style) - if ( elem.nodeType === 1 ) { - // These attributes require special treatment - var special = rspecialurl.test( name ); - - // Safari mis-reports the default selected property of an option - // Accessing the parent's selectedIndex property fixes it - if ( name === "selected" && !jQuery.support.optSelected ) { - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - // Make sure that it also works with optgroups, see #5701 - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - - // If applicable, access the attribute via the DOM 0 way - // 'in' checks fail in Blackberry 4.7 #6931 - if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { - if ( set ) { - // We can't allow the type property to be changed (since it causes problems in IE) - if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { - jQuery.error( "type property can't be changed" ); - } - - if ( value === null ) { - if ( elem.nodeType === 1 ) { - elem.removeAttribute( name ); - } - - } else { - elem[ name ] = value; - } - } - - // browsers index elements by id/name on forms, give priority to attributes. - if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { - return elem.getAttributeNode( name ).nodeValue; - } - - // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set - // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - if ( name === "tabIndex" ) { - var attributeNode = elem.getAttributeNode( "tabIndex" ); - - return attributeNode && attributeNode.specified ? - attributeNode.value : - rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? - 0 : - undefined; - } - - return elem[ name ]; - } - - if ( !jQuery.support.style && notxml && name === "style" ) { - if ( set ) { - elem.style.cssText = "" + value; - } - - return elem.style.cssText; - } - - if ( set ) { - // convert the value to a string (all browsers do this but IE) see #1070 - elem.setAttribute( name, "" + value ); - } - - // Ensure that missing attributes return undefined - // Blackberry 4.7 returns "" from getAttribute #6938 - if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { - return undefined; - } - - var attr = !jQuery.support.hrefNormalized && notxml && special ? - // Some attributes require a special call on IE - elem.getAttribute( name, 2 ) : - elem.getAttribute( name ); - - // Non-existent attributes return null, we normalize to undefined - return attr === null ? undefined : attr; - } - } -}); - - - - -var rnamespaces = /\.(.*)$/, - rformElems = /^(?:textarea|input|select)$/i, - rperiod = /\./g, - rspace = / /g, - rescape = /[^\w\s.|`]/g, - fcleanup = function( nm ) { - return nm.replace(rescape, "\\$&"); - }, - focusCounts = { focusin: 0, focusout: 0 }; - -/* - * A number of helper functions used for managing events. - * Many of the ideas behind this code originated from - * Dean Edwards' addEvent library. - */ -jQuery.event = { - - // Bind an event to an element - // Original by Dean Edwards - add: function( elem, types, handler, data ) { - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // For whatever reason, IE has trouble passing the window object - // around, causing it to be cloned in the process - if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) { - elem = window; - } - - if ( handler === false ) { - handler = returnFalse; - } - - var handleObjIn, handleObj; - - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - } - - // Make sure that the function being executed has a unique ID - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure - var elemData = jQuery.data( elem ); - - // If no elemData is found then we must be trying to bind to one of the - // banned noData elements - if ( !elemData ) { - return; - } - - // Use a key less likely to result in collisions for plain JS objects. - // Fixes bug #7150. - var eventKey = elem.nodeType ? "events" : "__events__", - events = elemData[ eventKey ], - eventHandle = elemData.handle; - - if ( typeof events === "function" ) { - // On plain objects events is a fn that holds the the data - // which prevents this data from being JSON serialized - // the function does not need to be called, it just contains the data - eventHandle = events.handle; - events = events.events; - - } else if ( !events ) { - if ( !elem.nodeType ) { - // On plain objects, create a fn that acts as the holder - // of the values to avoid JSON serialization of event data - elemData[ eventKey ] = elemData = function(){}; - } - - elemData.events = events = {}; - } - - if ( !eventHandle ) { - elemData.handle = eventHandle = function() { - // Handle the second event of a trigger and when - // an event is called after a page has unloaded - return typeof jQuery !== "undefined" && !jQuery.event.triggered ? - jQuery.event.handle.apply( eventHandle.elem, arguments ) : - undefined; - }; - } - - // Add elem as a property of the handle function - // This is to prevent a memory leak with non-native events in IE. - eventHandle.elem = elem; - - // Handle multiple events separated by a space - // jQuery(...).bind("mouseover mouseout", fn); - types = types.split(" "); - - var type, i = 0, namespaces; - - while ( (type = types[ i++ ]) ) { - handleObj = handleObjIn ? - jQuery.extend({}, handleObjIn) : - { handler: handler, data: data }; - - // Namespaced event handlers - if ( type.indexOf(".") > -1 ) { - namespaces = type.split("."); - type = namespaces.shift(); - handleObj.namespace = namespaces.slice(0).sort().join("."); - - } else { - namespaces = []; - handleObj.namespace = ""; - } - - handleObj.type = type; - if ( !handleObj.guid ) { - handleObj.guid = handler.guid; - } - - // Get the current list of functions bound to this event - var handlers = events[ type ], - special = jQuery.event.special[ type ] || {}; - - // Init the event handler queue - if ( !handlers ) { - handlers = events[ type ] = []; - - // Check for a special event handler - // Only use addEventListener/attachEvent if the special - // events handler returns false - if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - // Bind the global event handler to the element - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle, false ); - - } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add the function to the element's handler list - handlers.push( handleObj ); - - // Keep track of which events have been used, for global triggering - jQuery.event.global[ type ] = true; - } - - // Nullify elem to prevent memory leaks in IE - elem = null; - }, - - global: {}, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, pos ) { - // don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - if ( handler === false ) { - handler = returnFalse; - } - - var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, - eventKey = elem.nodeType ? "events" : "__events__", - elemData = jQuery.data( elem ), - events = elemData && elemData[ eventKey ]; - - if ( !elemData || !events ) { - return; - } - - if ( typeof events === "function" ) { - elemData = events; - events = events.events; - } - - // types is actually an event object here - if ( types && types.type ) { - handler = types.handler; - types = types.type; - } - - // Unbind all events for the element - if ( !types || typeof types === "string" && types.charAt(0) === "." ) { - types = types || ""; - - for ( type in events ) { - jQuery.event.remove( elem, type + types ); - } - - return; - } - - // Handle multiple events separated by a space - // jQuery(...).unbind("mouseover mouseout", fn); - types = types.split(" "); - - while ( (type = types[ i++ ]) ) { - origType = type; - handleObj = null; - all = type.indexOf(".") < 0; - namespaces = []; - - if ( !all ) { - // Namespaced event handlers - namespaces = type.split("."); - type = namespaces.shift(); - - namespace = new RegExp("(^|\\.)" + - jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); - } - - eventType = events[ type ]; - - if ( !eventType ) { - continue; - } - - if ( !handler ) { - for ( j = 0; j < eventType.length; j++ ) { - handleObj = eventType[ j ]; - - if ( all || namespace.test( handleObj.namespace ) ) { - jQuery.event.remove( elem, origType, handleObj.handler, j ); - eventType.splice( j--, 1 ); - } - } - - continue; - } - - special = jQuery.event.special[ type ] || {}; - - for ( j = pos || 0; j < eventType.length; j++ ) { - handleObj = eventType[ j ]; - - if ( handler.guid === handleObj.guid ) { - // remove the given handler for the given type - if ( all || namespace.test( handleObj.namespace ) ) { - if ( pos == null ) { - eventType.splice( j--, 1 ); - } - - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - - if ( pos != null ) { - break; - } - } - } - - // remove generic event handler if no more handlers exist - if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { - if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { - jQuery.removeEvent( elem, type, elemData.handle ); - } - - ret = null; - delete events[ type ]; - } - } - - // Remove the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - var handle = elemData.handle; - if ( handle ) { - handle.elem = null; - } - - delete elemData.events; - delete elemData.handle; - - if ( typeof elemData === "function" ) { - jQuery.removeData( elem, eventKey ); - - } else if ( jQuery.isEmptyObject( elemData ) ) { - jQuery.removeData( elem ); - } - } - }, - - // bubbling is internal - trigger: function( event, data, elem /*, bubbling */ ) { - // Event object or event type - var type = event.type || event, - bubbling = arguments[3]; - - if ( !bubbling ) { - event = typeof event === "object" ? - // jQuery.Event object - event[ jQuery.expando ] ? event : - // Object literal - jQuery.extend( jQuery.Event(type), event ) : - // Just the event type (string) - jQuery.Event(type); - - if ( type.indexOf("!") >= 0 ) { - event.type = type = type.slice(0, -1); - event.exclusive = true; - } - - // Handle a global trigger - if ( !elem ) { - // Don't bubble custom events when global (to avoid too much overhead) - event.stopPropagation(); - - // Only trigger if we've ever bound an event for it - if ( jQuery.event.global[ type ] ) { - jQuery.each( jQuery.cache, function() { - if ( this.events && this.events[type] ) { - jQuery.event.trigger( event, data, this.handle.elem ); - } - }); - } - } - - // Handle triggering a single element - - // don't do events on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { - return undefined; - } - - // Clean up in case it is reused - event.result = undefined; - event.target = elem; - - // Clone the incoming data, if any - data = jQuery.makeArray( data ); - data.unshift( event ); - } - - event.currentTarget = elem; - - // Trigger the event, it is assumed that "handle" is a function - var handle = elem.nodeType ? - jQuery.data( elem, "handle" ) : - (jQuery.data( elem, "__events__" ) || {}).handle; - - if ( handle ) { - handle.apply( elem, data ); - } - - var parent = elem.parentNode || elem.ownerDocument; - - // Trigger an inline bound script - try { - if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { - if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { - event.result = false; - event.preventDefault(); - } - } - - // prevent IE from throwing an error for some elements with some event types, see #3533 - } catch (inlineError) {} - - if ( !event.isPropagationStopped() && parent ) { - jQuery.event.trigger( event, data, parent, true ); - - } else if ( !event.isDefaultPrevented() ) { - var target = event.target, old, targetType = type.replace(rnamespaces, ""), - isClick = jQuery.nodeName(target, "a") && targetType === "click", - special = jQuery.event.special[ targetType ] || {}; - - if ( (!special._default || special._default.call( elem, event ) === false) && - !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { - - try { - if ( target[ targetType ] ) { - // Make sure that we don't accidentally re-trigger the onFOO events - old = target[ "on" + targetType ]; - - if ( old ) { - target[ "on" + targetType ] = null; - } - - jQuery.event.triggered = true; - target[ targetType ](); - } - - // prevent IE from throwing an error for some elements with some event types, see #3533 - } catch (triggerError) {} - - if ( old ) { - target[ "on" + targetType ] = old; - } - - jQuery.event.triggered = false; - } - } - }, - - handle: function( event ) { - var all, handlers, namespaces, namespace_sort = [], namespace_re, events, args = jQuery.makeArray( arguments ); - - event = args[0] = jQuery.event.fix( event || window.event ); - event.currentTarget = this; - - // Namespaced event handlers - all = event.type.indexOf(".") < 0 && !event.exclusive; - - if ( !all ) { - namespaces = event.type.split("."); - event.type = namespaces.shift(); - namespace_sort = namespaces.slice(0).sort(); - namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)"); - } - - event.namespace = event.namespace || namespace_sort.join("."); - - events = jQuery.data(this, this.nodeType ? "events" : "__events__"); - - if ( typeof events === "function" ) { - events = events.events; - } - - handlers = (events || {})[ event.type ]; - - if ( events && handlers ) { - // Clone the handlers to prevent manipulation - handlers = handlers.slice(0); - - for ( var j = 0, l = handlers.length; j < l; j++ ) { - var handleObj = handlers[ j ]; - - // Filter the functions by class - if ( all || namespace_re.test( handleObj.namespace ) ) { - // Pass in a reference to the handler function itself - // So that we can later remove it - event.handler = handleObj.handler; - event.data = handleObj.data; - event.handleObj = handleObj; - - var ret = handleObj.handler.apply( this, args ); - - if ( ret !== undefined ) { - event.result = ret; - if ( ret === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - - if ( event.isImmediatePropagationStopped() ) { - break; - } - } - } - } - - return event.result; - }, - - props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), - - fix: function( event ) { - if ( event[ jQuery.expando ] ) { - return event; - } - - // store a copy of the original event object - // and "clone" to set read-only properties - var originalEvent = event; - event = jQuery.Event( originalEvent ); - - for ( var i = this.props.length, prop; i; ) { - prop = this.props[ --i ]; - event[ prop ] = originalEvent[ prop ]; - } - - // Fix target property, if necessary - if ( !event.target ) { - event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either - } - - // check if target is a textnode (safari) - if ( event.target.nodeType === 3 ) { - event.target = event.target.parentNode; - } - - // Add relatedTarget, if necessary - if ( !event.relatedTarget && event.fromElement ) { - event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; - } - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && event.clientX != null ) { - var doc = document.documentElement, body = document.body; - event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); - event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); - } - - // Add which for key events - if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { - event.which = event.charCode != null ? event.charCode : event.keyCode; - } - - // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) - if ( !event.metaKey && event.ctrlKey ) { - event.metaKey = event.ctrlKey; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && event.button !== undefined ) { - event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); - } - - return event; - }, - - // Deprecated, use jQuery.guid instead - guid: 1E8, - - // Deprecated, use jQuery.proxy instead - proxy: jQuery.proxy, - - special: { - ready: { - // Make sure the ready event is setup - setup: jQuery.bindReady, - teardown: jQuery.noop - }, - - live: { - add: function( handleObj ) { - jQuery.event.add( this, - liveConvert( handleObj.origType, handleObj.selector ), - jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); - }, - - remove: function( handleObj ) { - jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); - } - }, - - beforeunload: { - setup: function( data, namespaces, eventHandle ) { - // We only want to do this special case on windows - if ( jQuery.isWindow( this ) ) { - this.onbeforeunload = eventHandle; - } - }, - - teardown: function( namespaces, eventHandle ) { - if ( this.onbeforeunload === eventHandle ) { - this.onbeforeunload = null; - } - } - } - } -}; - -jQuery.removeEvent = document.removeEventListener ? - function( elem, type, handle ) { - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle, false ); - } - } : - function( elem, type, handle ) { - if ( elem.detachEvent ) { - elem.detachEvent( "on" + type, handle ); - } - }; - -jQuery.Event = function( src ) { - // Allow instantiation without the 'new' keyword - if ( !this.preventDefault ) { - return new jQuery.Event( src ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - // Event type - } else { - this.type = src; - } - - // timeStamp is buggy for some events on Firefox(#3843) - // So we won't rely on the native value - this.timeStamp = jQuery.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -function returnFalse() { - return false; -} -function returnTrue() { - return true; -} - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - preventDefault: function() { - this.isDefaultPrevented = returnTrue; - - var e = this.originalEvent; - if ( !e ) { - return; - } - - // if preventDefault exists run it on the original event - if ( e.preventDefault ) { - e.preventDefault(); - - // otherwise set the returnValue property of the original event to false (IE) - } else { - e.returnValue = false; - } - }, - stopPropagation: function() { - this.isPropagationStopped = returnTrue; - - var e = this.originalEvent; - if ( !e ) { - return; - } - // if stopPropagation exists run it on the original event - if ( e.stopPropagation ) { - e.stopPropagation(); - } - // otherwise set the cancelBubble property of the original event to true (IE) - e.cancelBubble = true; - }, - stopImmediatePropagation: function() { - this.isImmediatePropagationStopped = returnTrue; - this.stopPropagation(); - }, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse -}; - -// Checks if an event happened on an element within another element -// Used in jQuery.event.special.mouseenter and mouseleave handlers -var withinElement = function( event ) { - // Check if mouse(over|out) are still within the same parent element - var parent = event.relatedTarget; - - // Firefox sometimes assigns relatedTarget a XUL element - // which we cannot access the parentNode property of - try { - // Traverse up the tree - while ( parent && parent !== this ) { - parent = parent.parentNode; - } - - if ( parent !== this ) { - // set the correct event type - event.type = event.data; - - // handle event if we actually just moused on to a non sub-element - jQuery.event.handle.apply( this, arguments ); - } - - // assuming we've left the element since we most likely mousedover a xul element - } catch(e) { } -}, - -// In case of event delegation, we only need to rename the event.type, -// liveHandler will take care of the rest. -delegate = function( event ) { - event.type = event.data; - jQuery.event.handle.apply( this, arguments ); -}; - -// Create mouseenter and mouseleave events -jQuery.each({ - mouseenter: "mouseover", - mouseleave: "mouseout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - setup: function( data ) { - jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); - }, - teardown: function( data ) { - jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); - } - }; -}); - -// submit delegation -if ( !jQuery.support.submitBubbles ) { - - jQuery.event.special.submit = { - setup: function( data, namespaces ) { - if ( this.nodeName.toLowerCase() !== "form" ) { - jQuery.event.add(this, "click.specialSubmit", function( e ) { - var elem = e.target, type = elem.type; - - if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { - e.liveFired = undefined; - return trigger( "submit", this, arguments ); - } - }); - - jQuery.event.add(this, "keypress.specialSubmit", function( e ) { - var elem = e.target, type = elem.type; - - if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { - e.liveFired = undefined; - return trigger( "submit", this, arguments ); - } - }); - - } else { - return false; - } - }, - - teardown: function( namespaces ) { - jQuery.event.remove( this, ".specialSubmit" ); - } - }; - -} - -// change delegation, happens here so we have bind. -if ( !jQuery.support.changeBubbles ) { - - var changeFilters, - - getVal = function( elem ) { - var type = elem.type, val = elem.value; - - if ( type === "radio" || type === "checkbox" ) { - val = elem.checked; - - } else if ( type === "select-multiple" ) { - val = elem.selectedIndex > -1 ? - jQuery.map( elem.options, function( elem ) { - return elem.selected; - }).join("-") : - ""; - - } else if ( elem.nodeName.toLowerCase() === "select" ) { - val = elem.selectedIndex; - } - - return val; - }, - - testChange = function testChange( e ) { - var elem = e.target, data, val; - - if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) { - return; - } - - data = jQuery.data( elem, "_change_data" ); - val = getVal(elem); - - // the current data will be also retrieved by beforeactivate - if ( e.type !== "focusout" || elem.type !== "radio" ) { - jQuery.data( elem, "_change_data", val ); - } - - if ( data === undefined || val === data ) { - return; - } - - if ( data != null || val ) { - e.type = "change"; - e.liveFired = undefined; - return jQuery.event.trigger( e, arguments[1], elem ); - } - }; - - jQuery.event.special.change = { - filters: { - focusout: testChange, - - beforedeactivate: testChange, - - click: function( e ) { - var elem = e.target, type = elem.type; - - if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { - return testChange.call( this, e ); - } - }, - - // Change has to be called before submit - // Keydown will be called before keypress, which is used in submit-event delegation - keydown: function( e ) { - var elem = e.target, type = elem.type; - - if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || - (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || - type === "select-multiple" ) { - return testChange.call( this, e ); - } - }, - - // Beforeactivate happens also before the previous element is blurred - // with this event you can't trigger a change event, but you can store - // information - beforeactivate: function( e ) { - var elem = e.target; - jQuery.data( elem, "_change_data", getVal(elem) ); - } - }, - - setup: function( data, namespaces ) { - if ( this.type === "file" ) { - return false; - } - - for ( var type in changeFilters ) { - jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); - } - - return rformElems.test( this.nodeName ); - }, - - teardown: function( namespaces ) { - jQuery.event.remove( this, ".specialChange" ); - - return rformElems.test( this.nodeName ); - } - }; - - changeFilters = jQuery.event.special.change.filters; - - // Handle when the input is .focus()'d - changeFilters.focus = changeFilters.beforeactivate; -} - -function trigger( type, elem, args ) { - args[0].type = type; - return jQuery.event.handle.apply( elem, args ); -} - -// Create "bubbling" focus and blur events -if ( document.addEventListener ) { - jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { - jQuery.event.special[ fix ] = { - setup: function() { - if ( focusCounts[fix]++ === 0 ) { - document.addEventListener( orig, handler, true ); - } - }, - teardown: function() { - if ( --focusCounts[fix] === 0 ) { - document.removeEventListener( orig, handler, true ); - } - } - }; - - function handler( e ) { - e = jQuery.event.fix( e ); - e.type = fix; - return jQuery.event.trigger( e, null, e.target ); - } - }); -} - -jQuery.each(["bind", "one"], function( i, name ) { - jQuery.fn[ name ] = function( type, data, fn ) { - // Handle object literals - if ( typeof type === "object" ) { - for ( var key in type ) { - this[ name ](key, data, type[key], fn); - } - return this; - } - - if ( jQuery.isFunction( data ) || data === false ) { - fn = data; - data = undefined; - } - - var handler = name === "one" ? jQuery.proxy( fn, function( event ) { - jQuery( this ).unbind( event, handler ); - return fn.apply( this, arguments ); - }) : fn; - - if ( type === "unload" && name !== "one" ) { - this.one( type, data, fn ); - - } else { - for ( var i = 0, l = this.length; i < l; i++ ) { - jQuery.event.add( this[i], type, handler, data ); - } - } - - return this; - }; -}); - -jQuery.fn.extend({ - unbind: function( type, fn ) { - // Handle object literals - if ( typeof type === "object" && !type.preventDefault ) { - for ( var key in type ) { - this.unbind(key, type[key]); - } - - } else { - for ( var i = 0, l = this.length; i < l; i++ ) { - jQuery.event.remove( this[i], type, fn ); - } - } - - return this; - }, - - delegate: function( selector, types, data, fn ) { - return this.live( types, data, fn, selector ); - }, - - undelegate: function( selector, types, fn ) { - if ( arguments.length === 0 ) { - return this.unbind( "live" ); - - } else { - return this.die( types, null, fn, selector ); - } - }, - - trigger: function( type, data ) { - return this.each(function() { - jQuery.event.trigger( type, data, this ); - }); - }, - - triggerHandler: function( type, data ) { - if ( this[0] ) { - var event = jQuery.Event( type ); - event.preventDefault(); - event.stopPropagation(); - jQuery.event.trigger( event, data, this[0] ); - return event.result; - } - }, - - toggle: function( fn ) { - // Save reference to arguments for access in closure - var args = arguments, i = 1; - - // link all the functions, so any of them can unbind this click handler - while ( i < args.length ) { - jQuery.proxy( fn, args[ i++ ] ); - } - - return this.click( jQuery.proxy( fn, function( event ) { - // Figure out which function to execute - var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; - jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); - - // Make sure that clicks stop - event.preventDefault(); - - // and execute the function - return args[ lastToggle ].apply( this, arguments ) || false; - })); - }, - - hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); - } -}); - -var liveMap = { - focus: "focusin", - blur: "focusout", - mouseenter: "mouseover", - mouseleave: "mouseout" -}; - -jQuery.each(["live", "die"], function( i, name ) { - jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { - var type, i = 0, match, namespaces, preType, - selector = origSelector || this.selector, - context = origSelector ? this : jQuery( this.context ); - - if ( typeof types === "object" && !types.preventDefault ) { - for ( var key in types ) { - context[ name ]( key, data, types[key], selector ); - } - - return this; - } - - if ( jQuery.isFunction( data ) ) { - fn = data; - data = undefined; - } - - types = (types || "").split(" "); - - while ( (type = types[ i++ ]) != null ) { - match = rnamespaces.exec( type ); - namespaces = ""; - - if ( match ) { - namespaces = match[0]; - type = type.replace( rnamespaces, "" ); - } - - if ( type === "hover" ) { - types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); - continue; - } - - preType = type; - - if ( type === "focus" || type === "blur" ) { - types.push( liveMap[ type ] + namespaces ); - type = type + namespaces; - - } else { - type = (liveMap[ type ] || type) + namespaces; - } - - if ( name === "live" ) { - // bind live handler - for ( var j = 0, l = context.length; j < l; j++ ) { - jQuery.event.add( context[j], "live." + liveConvert( type, selector ), - { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); - } - - } else { - // unbind live handler - context.unbind( "live." + liveConvert( type, selector ), fn ); - } - } - - return this; - }; -}); - -function liveHandler( event ) { - var stop, maxLevel, elems = [], selectors = [], - related, match, handleObj, elem, j, i, l, data, close, namespace, ret, - events = jQuery.data( this, this.nodeType ? "events" : "__events__" ); - - if ( typeof events === "function" ) { - events = events.events; - } - - // Make sure we avoid non-left-click bubbling in Firefox (#3861) - if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { - return; - } - - if ( event.namespace ) { - namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); - } - - event.liveFired = this; - - var live = events.live.slice(0); - - for ( j = 0; j < live.length; j++ ) { - handleObj = live[j]; - - if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { - selectors.push( handleObj.selector ); - - } else { - live.splice( j--, 1 ); - } - } - - match = jQuery( event.target ).closest( selectors, event.currentTarget ); - - for ( i = 0, l = match.length; i < l; i++ ) { - close = match[i]; - - for ( j = 0; j < live.length; j++ ) { - handleObj = live[j]; - - if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) ) { - elem = close.elem; - related = null; - - // Those two events require additional checking - if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { - event.type = handleObj.preType; - related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; - } - - if ( !related || related !== elem ) { - elems.push({ elem: elem, handleObj: handleObj, level: close.level }); - } - } - } - } - - for ( i = 0, l = elems.length; i < l; i++ ) { - match = elems[i]; - - if ( maxLevel && match.level > maxLevel ) { - break; - } - - event.currentTarget = match.elem; - event.data = match.handleObj.data; - event.handleObj = match.handleObj; - - ret = match.handleObj.origHandler.apply( match.elem, arguments ); - - if ( ret === false || event.isPropagationStopped() ) { - maxLevel = match.level; - - if ( ret === false ) { - stop = false; - } - } - } - - return stop; -} - -function liveConvert( type, selector ) { - return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspace, "&"); -} - -jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup error").split(" "), function( i, name ) { - - // Handle event binding - jQuery.fn[ name ] = function( data, fn ) { - if ( fn == null ) { - fn = data; - data = null; - } - - return arguments.length > 0 ? - this.bind( name, data, fn ) : - this.trigger( name ); - }; - - if ( jQuery.attrFn ) { - jQuery.attrFn[ name ] = true; - } -}); - -// Prevent memory leaks in IE -// Window isn't included so as not to unbind existing unload events -// More info: -// - http://isaacschlueter.com/2006/10/msie-memory-leaks/ -if ( window.attachEvent && !window.addEventListener ) { - jQuery(window).bind("unload", function() { - for ( var id in jQuery.cache ) { - if ( jQuery.cache[ id ].handle ) { - // Try/Catch is to handle iframes being unloaded, see #4280 - try { - jQuery.event.remove( jQuery.cache[ id ].handle.elem ); - } catch(e) {} - } - } - }); -} - - -/*! - * Sizzle CSS Selector Engine - v1.0 - * Copyright 2009, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * More information: http://sizzlejs.com/ - */ -(function(){ - -var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, - done = 0, - toString = Object.prototype.toString, - hasDuplicate = false, - baseHasDuplicate = true; - -// Here we check if the JavaScript engine is using some sort of -// optimization where it does not always call our comparision -// function. If that is the case, discard the hasDuplicate value. -// Thus far that includes Google Chrome. -[0, 0].sort(function(){ - baseHasDuplicate = false; - return 0; -}); - -var Sizzle = function(selector, context, results, seed) { - results = results || []; - context = context || document; - - var origContext = context; - - if ( context.nodeType !== 1 && context.nodeType !== 9 ) { - return []; - } - - if ( !selector || typeof selector !== "string" ) { - return results; - } - - var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context), - soFar = selector, ret, cur, pop, i; - - // Reset the position of the chunker regexp (start from head) - do { - chunker.exec(""); - m = chunker.exec(soFar); - - if ( m ) { - soFar = m[3]; - - parts.push( m[1] ); - - if ( m[2] ) { - extra = m[3]; - break; - } - } - } while ( m ); - - if ( parts.length > 1 && origPOS.exec( selector ) ) { - if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { - set = posProcess( parts[0] + parts[1], context ); - } else { - set = Expr.relative[ parts[0] ] ? - [ context ] : - Sizzle( parts.shift(), context ); - - while ( parts.length ) { - selector = parts.shift(); - - if ( Expr.relative[ selector ] ) { - selector += parts.shift(); - } - - set = posProcess( selector, set ); - } - } - } else { - // Take a shortcut and set the context if the root selector is an ID - // (but not if it'll be faster if the inner selector is an ID) - if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && - Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { - ret = Sizzle.find( parts.shift(), context, contextXML ); - context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; - } - - if ( context ) { - ret = seed ? - { expr: parts.pop(), set: makeArray(seed) } : - Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); - set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; - - if ( parts.length > 0 ) { - checkSet = makeArray(set); - } else { - prune = false; - } - - while ( parts.length ) { - cur = parts.pop(); - pop = cur; - - if ( !Expr.relative[ cur ] ) { - cur = ""; - } else { - pop = parts.pop(); - } - - if ( pop == null ) { - pop = context; - } - - Expr.relative[ cur ]( checkSet, pop, contextXML ); - } - } else { - checkSet = parts = []; - } - } - - if ( !checkSet ) { - checkSet = set; - } - - if ( !checkSet ) { - Sizzle.error( cur || selector ); - } - - if ( toString.call(checkSet) === "[object Array]" ) { - if ( !prune ) { - results.push.apply( results, checkSet ); - } else if ( context && context.nodeType === 1 ) { - for ( i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { - results.push( set[i] ); - } - } - } else { - for ( i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && checkSet[i].nodeType === 1 ) { - results.push( set[i] ); - } - } - } - } else { - makeArray( checkSet, results ); - } - - if ( extra ) { - Sizzle( extra, origContext, results, seed ); - Sizzle.uniqueSort( results ); - } - - return results; -}; - -Sizzle.uniqueSort = function(results){ - if ( sortOrder ) { - hasDuplicate = baseHasDuplicate; - results.sort(sortOrder); - - if ( hasDuplicate ) { - for ( var i = 1; i < results.length; i++ ) { - if ( results[i] === results[i-1] ) { - results.splice(i--, 1); - } - } - } - } - - return results; -}; - -Sizzle.matches = function(expr, set){ - return Sizzle(expr, null, null, set); -}; - -Sizzle.matchesSelector = function(node, expr){ - return Sizzle(expr, null, null, [node]).length > 0; -}; - -Sizzle.find = function(expr, context, isXML){ - var set; - - if ( !expr ) { - return []; - } - - for ( var i = 0, l = Expr.order.length; i < l; i++ ) { - var type = Expr.order[i], match; - - if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { - var left = match[1]; - match.splice(1,1); - - if ( left.substr( left.length - 1 ) !== "\\" ) { - match[1] = (match[1] || "").replace(/\\/g, ""); - set = Expr.find[ type ]( match, context, isXML ); - if ( set != null ) { - expr = expr.replace( Expr.match[ type ], "" ); - break; - } - } - } - } - - if ( !set ) { - set = context.getElementsByTagName("*"); - } - - return {set: set, expr: expr}; -}; - -Sizzle.filter = function(expr, set, inplace, not){ - var old = expr, result = [], curLoop = set, match, anyFound, - isXMLFilter = set && set[0] && Sizzle.isXML(set[0]); - - while ( expr && set.length ) { - for ( var type in Expr.filter ) { - if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { - var filter = Expr.filter[ type ], found, item, left = match[1]; - anyFound = false; - - match.splice(1,1); - - if ( left.substr( left.length - 1 ) === "\\" ) { - continue; - } - - if ( curLoop === result ) { - result = []; - } - - if ( Expr.preFilter[ type ] ) { - match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); - - if ( !match ) { - anyFound = found = true; - } else if ( match === true ) { - continue; - } - } - - if ( match ) { - for ( var i = 0; (item = curLoop[i]) != null; i++ ) { - if ( item ) { - found = filter( item, match, i, curLoop ); - var pass = not ^ !!found; - - if ( inplace && found != null ) { - if ( pass ) { - anyFound = true; - } else { - curLoop[i] = false; - } - } else if ( pass ) { - result.push( item ); - anyFound = true; - } - } - } - } - - if ( found !== undefined ) { - if ( !inplace ) { - curLoop = result; - } - - expr = expr.replace( Expr.match[ type ], "" ); - - if ( !anyFound ) { - return []; - } - - break; - } - } - } - - // Improper expression - if ( expr === old ) { - if ( anyFound == null ) { - Sizzle.error( expr ); - } else { - break; - } - } - - old = expr; - } - - return curLoop; -}; - -Sizzle.error = function( msg ) { - throw "Syntax error, unrecognized expression: " + msg; -}; - -var Expr = Sizzle.selectors = { - order: [ "ID", "NAME", "TAG" ], - match: { - ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, - CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, - NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, - ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, - TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, - CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/, - POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, - PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ - }, - leftMatch: {}, - attrMap: { - "class": "className", - "for": "htmlFor" - }, - attrHandle: { - href: function(elem){ - return elem.getAttribute("href"); - } - }, - relative: { - "+": function(checkSet, part){ - var isPartStr = typeof part === "string", - isTag = isPartStr && !/\W/.test(part), - isPartStrNotTag = isPartStr && !isTag; - - if ( isTag ) { - part = part.toLowerCase(); - } - - for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { - if ( (elem = checkSet[i]) ) { - while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} - - checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? - elem || false : - elem === part; - } - } - - if ( isPartStrNotTag ) { - Sizzle.filter( part, checkSet, true ); - } - }, - ">": function(checkSet, part){ - var isPartStr = typeof part === "string", - elem, i = 0, l = checkSet.length; - - if ( isPartStr && !/\W/.test(part) ) { - part = part.toLowerCase(); - - for ( ; i < l; i++ ) { - elem = checkSet[i]; - if ( elem ) { - var parent = elem.parentNode; - checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; - } - } - } else { - for ( ; i < l; i++ ) { - elem = checkSet[i]; - if ( elem ) { - checkSet[i] = isPartStr ? - elem.parentNode : - elem.parentNode === part; - } - } - - if ( isPartStr ) { - Sizzle.filter( part, checkSet, true ); - } - } - }, - "": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck, nodeCheck; - - if ( typeof part === "string" && !/\W/.test(part) ) { - part = part.toLowerCase(); - nodeCheck = part; - checkFn = dirNodeCheck; - } - - checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); - }, - "~": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck, nodeCheck; - - if ( typeof part === "string" && !/\W/.test(part) ) { - part = part.toLowerCase(); - nodeCheck = part; - checkFn = dirNodeCheck; - } - - checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); - } - }, - find: { - ID: function(match, context, isXML){ - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - return m && m.parentNode ? [m] : []; - } - }, - NAME: function(match, context){ - if ( typeof context.getElementsByName !== "undefined" ) { - var ret = [], results = context.getElementsByName(match[1]); - - for ( var i = 0, l = results.length; i < l; i++ ) { - if ( results[i].getAttribute("name") === match[1] ) { - ret.push( results[i] ); - } - } - - return ret.length === 0 ? null : ret; - } - }, - TAG: function(match, context){ - return context.getElementsByTagName(match[1]); - } - }, - preFilter: { - CLASS: function(match, curLoop, inplace, result, not, isXML){ - match = " " + match[1].replace(/\\/g, "") + " "; - - if ( isXML ) { - return match; - } - - for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { - if ( elem ) { - if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { - if ( !inplace ) { - result.push( elem ); - } - } else if ( inplace ) { - curLoop[i] = false; - } - } - } - - return false; - }, - ID: function(match){ - return match[1].replace(/\\/g, ""); - }, - TAG: function(match, curLoop){ - return match[1].toLowerCase(); - }, - CHILD: function(match){ - if ( match[1] === "nth" ) { - // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' - var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( - match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || - !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); - - // calculate the numbers (first)n+(last) including if they are negative - match[2] = (test[1] + (test[2] || 1)) - 0; - match[3] = test[3] - 0; - } - - // TODO: Move to normal caching system - match[0] = done++; - - return match; - }, - ATTR: function(match, curLoop, inplace, result, not, isXML){ - var name = match[1].replace(/\\/g, ""); - - if ( !isXML && Expr.attrMap[name] ) { - match[1] = Expr.attrMap[name]; - } - - if ( match[2] === "~=" ) { - match[4] = " " + match[4] + " "; - } - - return match; - }, - PSEUDO: function(match, curLoop, inplace, result, not){ - if ( match[1] === "not" ) { - // If we're dealing with a complex expression, or a simple one - if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { - match[3] = Sizzle(match[3], null, null, curLoop); - } else { - var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); - if ( !inplace ) { - result.push.apply( result, ret ); - } - return false; - } - } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { - return true; - } - - return match; - }, - POS: function(match){ - match.unshift( true ); - return match; - } - }, - filters: { - enabled: function(elem){ - return elem.disabled === false && elem.type !== "hidden"; - }, - disabled: function(elem){ - return elem.disabled === true; - }, - checked: function(elem){ - return elem.checked === true; - }, - selected: function(elem){ - // Accessing this property makes selected-by-default - // options in Safari work properly - elem.parentNode.selectedIndex; - return elem.selected === true; - }, - parent: function(elem){ - return !!elem.firstChild; - }, - empty: function(elem){ - return !elem.firstChild; - }, - has: function(elem, i, match){ - return !!Sizzle( match[3], elem ).length; - }, - header: function(elem){ - return (/h\d/i).test( elem.nodeName ); - }, - text: function(elem){ - return "text" === elem.type; - }, - radio: function(elem){ - return "radio" === elem.type; - }, - checkbox: function(elem){ - return "checkbox" === elem.type; - }, - file: function(elem){ - return "file" === elem.type; - }, - password: function(elem){ - return "password" === elem.type; - }, - submit: function(elem){ - return "submit" === elem.type; - }, - image: function(elem){ - return "image" === elem.type; - }, - reset: function(elem){ - return "reset" === elem.type; - }, - button: function(elem){ - return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; - }, - input: function(elem){ - return (/input|select|textarea|button/i).test(elem.nodeName); - } - }, - setFilters: { - first: function(elem, i){ - return i === 0; - }, - last: function(elem, i, match, array){ - return i === array.length - 1; - }, - even: function(elem, i){ - return i % 2 === 0; - }, - odd: function(elem, i){ - return i % 2 === 1; - }, - lt: function(elem, i, match){ - return i < match[3] - 0; - }, - gt: function(elem, i, match){ - return i > match[3] - 0; - }, - nth: function(elem, i, match){ - return match[3] - 0 === i; - }, - eq: function(elem, i, match){ - return match[3] - 0 === i; - } - }, - filter: { - PSEUDO: function(elem, match, i, array){ - var name = match[1], filter = Expr.filters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - } else if ( name === "contains" ) { - return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; - } else if ( name === "not" ) { - var not = match[3]; - - for ( var j = 0, l = not.length; j < l; j++ ) { - if ( not[j] === elem ) { - return false; - } - } - - return true; - } else { - Sizzle.error( "Syntax error, unrecognized expression: " + name ); - } - }, - CHILD: function(elem, match){ - var type = match[1], node = elem; - switch (type) { - case 'only': - case 'first': - while ( (node = node.previousSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - if ( type === "first" ) { - return true; - } - node = elem; - case 'last': - while ( (node = node.nextSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - return true; - case 'nth': - var first = match[2], last = match[3]; - - if ( first === 1 && last === 0 ) { - return true; - } - - var doneName = match[0], - parent = elem.parentNode; - - if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { - var count = 0; - for ( node = parent.firstChild; node; node = node.nextSibling ) { - if ( node.nodeType === 1 ) { - node.nodeIndex = ++count; - } - } - parent.sizcache = doneName; - } - - var diff = elem.nodeIndex - last; - if ( first === 0 ) { - return diff === 0; - } else { - return ( diff % first === 0 && diff / first >= 0 ); - } - } - }, - ID: function(elem, match){ - return elem.nodeType === 1 && elem.getAttribute("id") === match; - }, - TAG: function(elem, match){ - return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; - }, - CLASS: function(elem, match){ - return (" " + (elem.className || elem.getAttribute("class")) + " ") - .indexOf( match ) > -1; - }, - ATTR: function(elem, match){ - var name = match[1], - result = Expr.attrHandle[ name ] ? - Expr.attrHandle[ name ]( elem ) : - elem[ name ] != null ? - elem[ name ] : - elem.getAttribute( name ), - value = result + "", - type = match[2], - check = match[4]; - - return result == null ? - type === "!=" : - type === "=" ? - value === check : - type === "*=" ? - value.indexOf(check) >= 0 : - type === "~=" ? - (" " + value + " ").indexOf(check) >= 0 : - !check ? - value && result !== false : - type === "!=" ? - value !== check : - type === "^=" ? - value.indexOf(check) === 0 : - type === "$=" ? - value.substr(value.length - check.length) === check : - type === "|=" ? - value === check || value.substr(0, check.length + 1) === check + "-" : - false; - }, - POS: function(elem, match, i, array){ - var name = match[2], filter = Expr.setFilters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - } - } - } -}; - -var origPOS = Expr.match.POS, - fescape = function(all, num){ - return "\\" + (num - 0 + 1); - }; - -for ( var type in Expr.match ) { - Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); - Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); -} - -var makeArray = function(array, results) { - array = Array.prototype.slice.call( array, 0 ); - - if ( results ) { - results.push.apply( results, array ); - return results; - } - - return array; -}; - -// Perform a simple check to determine if the browser is capable of -// converting a NodeList to an array using builtin methods. -// Also verifies that the returned array holds DOM nodes -// (which is not the case in the Blackberry browser) -try { - Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; - -// Provide a fallback method if it does not work -} catch(e){ - makeArray = function(array, results) { - var ret = results || [], i = 0; - - if ( toString.call(array) === "[object Array]" ) { - Array.prototype.push.apply( ret, array ); - } else { - if ( typeof array.length === "number" ) { - for ( var l = array.length; i < l; i++ ) { - ret.push( array[i] ); - } - } else { - for ( ; array[i]; i++ ) { - ret.push( array[i] ); - } - } - } - - return ret; - }; -} - -var sortOrder, siblingCheck; - -if ( document.documentElement.compareDocumentPosition ) { - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { - return a.compareDocumentPosition ? -1 : 1; - } - - return a.compareDocumentPosition(b) & 4 ? -1 : 1; - }; -} else { - sortOrder = function( a, b ) { - var ap = [], bp = [], aup = a.parentNode, bup = b.parentNode, - cur = aup, al, bl; - - // The nodes are identical, we can exit early - if ( a === b ) { - hasDuplicate = true; - return 0; - - // If the nodes are siblings (or identical) we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - - // If no parents were found then the nodes are disconnected - } else if ( !aup ) { - return -1; - - } else if ( !bup ) { - return 1; - } - - // Otherwise they're somewhere else in the tree so we need - // to build up a full list of the parentNodes for comparison - while ( cur ) { - ap.unshift( cur ); - cur = cur.parentNode; - } - - cur = bup; - - while ( cur ) { - bp.unshift( cur ); - cur = cur.parentNode; - } - - al = ap.length; - bl = bp.length; - - // Start walking down the tree looking for a discrepancy - for ( var i = 0; i < al && i < bl; i++ ) { - if ( ap[i] !== bp[i] ) { - return siblingCheck( ap[i], bp[i] ); - } - } - - // We ended someplace up the tree so do a sibling check - return i === al ? - siblingCheck( a, bp[i], -1 ) : - siblingCheck( ap[i], b, 1 ); - }; - - siblingCheck = function( a, b, ret ) { - if ( a === b ) { - return ret; - } - - var cur = a.nextSibling; - - while ( cur ) { - if ( cur === b ) { - return -1; - } - - cur = cur.nextSibling; - } - - return 1; - }; -} - -// Utility function for retreiving the text value of an array of DOM nodes -Sizzle.getText = function( elems ) { - var ret = "", elem; - - for ( var i = 0; elems[i]; i++ ) { - elem = elems[i]; - - // Get the text from text nodes and CDATA nodes - if ( elem.nodeType === 3 || elem.nodeType === 4 ) { - ret += elem.nodeValue; - - // Traverse everything else, except comment nodes - } else if ( elem.nodeType !== 8 ) { - ret += Sizzle.getText( elem.childNodes ); - } - } - - return ret; -}; - -// Check to see if the browser returns elements by name when -// querying by getElementById (and provide a workaround) -(function(){ - // We're going to inject a fake input element with a specified name - var form = document.createElement("div"), - id = "script" + (new Date()).getTime(); - form.innerHTML = ""; - - // Inject it into the root element, check its status, and remove it quickly - var root = document.documentElement; - root.insertBefore( form, root.firstChild ); - - // The workaround has to do additional checks after a getElementById - // Which slows things down for other browsers (hence the branching) - if ( document.getElementById( id ) ) { - Expr.find.ID = function(match, context, isXML){ - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; - } - }; - - Expr.filter.ID = function(elem, match){ - var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); - return elem.nodeType === 1 && node && node.nodeValue === match; - }; - } - - root.removeChild( form ); - root = form = null; // release memory in IE -})(); - -(function(){ - // Check to see if the browser returns only elements - // when doing getElementsByTagName("*") - - // Create a fake element - var div = document.createElement("div"); - div.appendChild( document.createComment("") ); - - // Make sure no comments are found - if ( div.getElementsByTagName("*").length > 0 ) { - Expr.find.TAG = function(match, context){ - var results = context.getElementsByTagName(match[1]); - - // Filter out possible comments - if ( match[1] === "*" ) { - var tmp = []; - - for ( var i = 0; results[i]; i++ ) { - if ( results[i].nodeType === 1 ) { - tmp.push( results[i] ); - } - } - - results = tmp; - } - - return results; - }; - } - - // Check to see if an attribute returns normalized href attributes - div.innerHTML = ""; - if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && - div.firstChild.getAttribute("href") !== "#" ) { - Expr.attrHandle.href = function(elem){ - return elem.getAttribute("href", 2); - }; - } - - div = null; // release memory in IE -})(); - -if ( document.querySelectorAll ) { - (function(){ - var oldSizzle = Sizzle, div = document.createElement("div"); - div.innerHTML = "

          "; - - // Safari can't handle uppercase or unicode characters when - // in quirks mode. - if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { - return; - } - - Sizzle = function(query, context, extra, seed){ - context = context || document; - - // Only use querySelectorAll on non-XML documents - // (ID selectors don't work in non-HTML documents) - if ( !seed && !Sizzle.isXML(context) ) { - if ( context.nodeType === 9 ) { - try { - return makeArray( context.querySelectorAll(query), extra ); - } catch(qsaError) {} - - // qSA works strangely on Element-rooted queries - // We can work around this by specifying an extra ID on the root - // and working up from there (Thanks to Andrew Dupont for the technique) - // IE 8 doesn't work on object elements - } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { - var old = context.id, id = context.id = "__sizzle__"; - - try { - return makeArray( context.querySelectorAll( "#" + id + " " + query ), extra ); - - } catch(pseudoError) { - } finally { - if ( old ) { - context.id = old; - - } else { - context.removeAttribute( "id" ); - } - } - } - } - - return oldSizzle(query, context, extra, seed); - }; - - for ( var prop in oldSizzle ) { - Sizzle[ prop ] = oldSizzle[ prop ]; - } - - div = null; // release memory in IE - })(); -} - -(function(){ - var html = document.documentElement, - matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector, - pseudoWorks = false; - - try { - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( document.documentElement, ":sizzle" ); - - } catch( pseudoError ) { - pseudoWorks = true; - } - - if ( matches ) { - Sizzle.matchesSelector = function( node, expr ) { - try { - if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) ) { - return matches.call( node, expr ); - } - } catch(e) {} - - return Sizzle(expr, null, null, [node]).length > 0; - }; - } -})(); - -(function(){ - var div = document.createElement("div"); - - div.innerHTML = "
          "; - - // Opera can't find a second classname (in 9.6) - // Also, make sure that getElementsByClassName actually exists - if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { - return; - } - - // Safari caches class attributes, doesn't catch changes (in 3.2) - div.lastChild.className = "e"; - - if ( div.getElementsByClassName("e").length === 1 ) { - return; - } - - Expr.order.splice(1, 0, "CLASS"); - Expr.find.CLASS = function(match, context, isXML) { - if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { - return context.getElementsByClassName(match[1]); - } - }; - - div = null; // release memory in IE -})(); - -function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - elem = elem[dir]; - var match = false; - - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 && !isXML ){ - elem.sizcache = doneName; - elem.sizset = i; - } - - if ( elem.nodeName.toLowerCase() === cur ) { - match = elem; - break; - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - elem = elem[dir]; - var match = false; - - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 ) { - if ( !isXML ) { - elem.sizcache = doneName; - elem.sizset = i; - } - if ( typeof cur !== "string" ) { - if ( elem === cur ) { - match = true; - break; - } - - } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { - match = elem; - break; - } - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -Sizzle.contains = document.documentElement.contains ? function(a, b){ - return a !== b && (a.contains ? a.contains(b) : true); -} : function(a, b){ - return !!(a.compareDocumentPosition(b) & 16); -}; - -Sizzle.isXML = function(elem){ - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -var posProcess = function(selector, context){ - var tmpSet = [], later = "", match, - root = context.nodeType ? [context] : context; - - // Position selectors must be done after the filter - // And so must :not(positional) so we move all PSEUDOs to the end - while ( (match = Expr.match.PSEUDO.exec( selector )) ) { - later += match[0]; - selector = selector.replace( Expr.match.PSEUDO, "" ); - } - - selector = Expr.relative[selector] ? selector + "*" : selector; - - for ( var i = 0, l = root.length; i < l; i++ ) { - Sizzle( selector, root[i], tmpSet ); - } - - return Sizzle.filter( later, tmpSet ); -}; - -// EXPOSE -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; -jQuery.expr[":"] = jQuery.expr.filters; -jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; - - -})(); - - -var runtil = /Until$/, - rparentsprev = /^(?:parents|prevUntil|prevAll)/, - // Note: This RegExp should be improved, or likely pulled from Sizzle - rmultiselector = /,/, - isSimple = /^.[^:#\[\.,]*$/, - slice = Array.prototype.slice, - POS = jQuery.expr.match.POS; - -jQuery.fn.extend({ - find: function( selector ) { - var ret = this.pushStack( "", "find", selector ), length = 0; - - for ( var i = 0, l = this.length; i < l; i++ ) { - length = ret.length; - jQuery.find( selector, this[i], ret ); - - if ( i > 0 ) { - // Make sure that the results are unique - for ( var n = length; n < ret.length; n++ ) { - for ( var r = 0; r < length; r++ ) { - if ( ret[r] === ret[n] ) { - ret.splice(n--, 1); - break; - } - } - } - } - } - - return ret; - }, - - has: function( target ) { - var targets = jQuery( target ); - return this.filter(function() { - for ( var i = 0, l = targets.length; i < l; i++ ) { - if ( jQuery.contains( this, targets[i] ) ) { - return true; - } - } - }); - }, - - not: function( selector ) { - return this.pushStack( winnow(this, selector, false), "not", selector); - }, - - filter: function( selector ) { - return this.pushStack( winnow(this, selector, true), "filter", selector ); - }, - - is: function( selector ) { - return !!selector && jQuery.filter( selector, this ).length > 0; - }, - - closest: function( selectors, context ) { - var ret = [], i, l, cur = this[0]; - - if ( jQuery.isArray( selectors ) ) { - var match, matches = {}, selector, level = 1; - - if ( cur && selectors.length ) { - for ( i = 0, l = selectors.length; i < l; i++ ) { - selector = selectors[i]; - - if ( !matches[selector] ) { - matches[selector] = jQuery.expr.match.POS.test( selector ) ? - jQuery( selector, context || this.context ) : - selector; - } - } - - while ( cur && cur.ownerDocument && cur !== context ) { - for ( selector in matches ) { - match = matches[selector]; - - if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { - ret.push({ selector: selector, elem: cur, level: level }); - } - } - - cur = cur.parentNode; - level++; - } - } - - return ret; - } - - var pos = POS.test( selectors ) ? - jQuery( selectors, context || this.context ) : null; - - for ( i = 0, l = this.length; i < l; i++ ) { - cur = this[i]; - - while ( cur ) { - if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { - ret.push( cur ); - break; - - } else { - cur = cur.parentNode; - if ( !cur || !cur.ownerDocument || cur === context ) { - break; - } - } - } - } - - ret = ret.length > 1 ? jQuery.unique(ret) : ret; - - return this.pushStack( ret, "closest", selectors ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - if ( !elem || typeof elem === "string" ) { - return jQuery.inArray( this[0], - // If it receives a string, the selector is used - // If it receives nothing, the siblings are used - elem ? jQuery( elem ) : this.parent().children() ); - } - // Locate the position of the desired element - return jQuery.inArray( - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[0] : elem, this ); - }, - - add: function( selector, context ) { - var set = typeof selector === "string" ? - jQuery( selector, context || this.context ) : - jQuery.makeArray( selector ), - all = jQuery.merge( this.get(), set ); - - return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? - all : - jQuery.unique( all ) ); - }, - - andSelf: function() { - return this.add( this.prevObject ); - } -}); - -// A painfully simple check to see if an element is disconnected -// from a document (should be improved, where feasible). -function isDisconnected( node ) { - return !node || !node.parentNode || node.parentNode.nodeType === 11; -} - -jQuery.each({ - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return jQuery.dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return jQuery.dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return jQuery.nth( elem, 2, "nextSibling" ); - }, - prev: function( elem ) { - return jQuery.nth( elem, 2, "previousSibling" ); - }, - nextAll: function( elem ) { - return jQuery.dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return jQuery.dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return jQuery.dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return jQuery.dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return jQuery.sibling( elem.parentNode.firstChild, elem ); - }, - children: function( elem ) { - return jQuery.sibling( elem.firstChild ); - }, - contents: function( elem ) { - return jQuery.nodeName( elem, "iframe" ) ? - elem.contentDocument || elem.contentWindow.document : - jQuery.makeArray( elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var ret = jQuery.map( this, fn, until ); - - if ( !runtil.test( name ) ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - ret = jQuery.filter( selector, ret ); - } - - ret = this.length > 1 ? jQuery.unique( ret ) : ret; - - if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { - ret = ret.reverse(); - } - - return this.pushStack( ret, name, slice.call(arguments).join(",") ); - }; -}); - -jQuery.extend({ - filter: function( expr, elems, not ) { - if ( not ) { - expr = ":not(" + expr + ")"; - } - - return elems.length === 1 ? - jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : - jQuery.find.matches(expr, elems); - }, - - dir: function( elem, dir, until ) { - var matched = [], cur = elem[dir]; - while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { - if ( cur.nodeType === 1 ) { - matched.push( cur ); - } - cur = cur[dir]; - } - return matched; - }, - - nth: function( cur, result, dir, elem ) { - result = result || 1; - var num = 0; - - for ( ; cur; cur = cur[dir] ) { - if ( cur.nodeType === 1 && ++num === result ) { - break; - } - } - - return cur; - }, - - sibling: function( n, elem ) { - var r = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - r.push( n ); - } - } - - return r; - } -}); - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, keep ) { - if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep(elements, function( elem, i ) { - var retVal = !!qualifier.call( elem, i, elem ); - return retVal === keep; - }); - - } else if ( qualifier.nodeType ) { - return jQuery.grep(elements, function( elem, i ) { - return (elem === qualifier) === keep; - }); - - } else if ( typeof qualifier === "string" ) { - var filtered = jQuery.grep(elements, function( elem ) { - return elem.nodeType === 1; - }); - - if ( isSimple.test( qualifier ) ) { - return jQuery.filter(qualifier, filtered, !keep); - } else { - qualifier = jQuery.filter( qualifier, filtered ); - } - } - - return jQuery.grep(elements, function( elem, i ) { - return (jQuery.inArray( elem, qualifier ) >= 0) === keep; - }); -} - - - - -var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, - rleadingWhitespace = /^\s+/, - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, - rtagName = /<([\w:]+)/, - rtbody = /\s]+\/)>/g, - wrapMap = { - option: [ 1, "" ], - legend: [ 1, "
          ", "
          " ], - thead: [ 1, "", "
          " ], - tr: [ 2, "", "
          " ], - td: [ 3, "", "
          " ], - col: [ 2, "", "
          " ], - area: [ 1, "", "" ], - _default: [ 0, "", "" ] - }; - -wrapMap.optgroup = wrapMap.option; -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -// IE can't serialize and - - - -

          <%= flash[:notice] %>

          - - <%= fb_connect_javascript_tag %> - <%= init_fb_connect "XFBML" %> - - <%= yield %> - - - - -=== 5. Add the Facebook Connect button to your login form - - <%= authlogic_facebook_login_button %> - -=== Notes - -If you want to save some user data when connecting to facebook you can use the before_connect hook in your user model. - - def before_connect(facebook_session) - self.name = facebook_session.user.name - end - -For more information about what you can get form the facebook_session checkout the Facebooker gem rdoc. - \ No newline at end of file diff --git a/vendor/plugins/authlogic_facebook_connect/init.rb b/vendor/plugins/authlogic_facebook_connect/init.rb deleted file mode 100644 index fc590d35b..000000000 --- a/vendor/plugins/authlogic_facebook_connect/init.rb +++ /dev/null @@ -1 +0,0 @@ -require 'authlogic_facebook_connect' \ No newline at end of file diff --git a/vendor/plugins/authlogic_facebook_connect/lib/authlogic_facebook_connect.rb b/vendor/plugins/authlogic_facebook_connect/lib/authlogic_facebook_connect.rb deleted file mode 100644 index c05de1401..000000000 --- a/vendor/plugins/authlogic_facebook_connect/lib/authlogic_facebook_connect.rb +++ /dev/null @@ -1,10 +0,0 @@ -# require "authlogic_facebook_connect/version" -require "authlogic_facebook_connect/acts_as_authentic" -require "authlogic_facebook_connect/session" -require "authlogic_facebook_connect/helper" - -if ActiveRecord::Base.respond_to?(:add_acts_as_authentic_module) - ActiveRecord::Base.send(:include, AuthlogicFacebookConnect::ActsAsAuthentic) - Authlogic::Session::Base.send(:include, AuthlogicFacebookConnect::Session) - ActionController::Base.helper AuthlogicFacebookConnect::Helper -end diff --git a/vendor/plugins/authlogic_facebook_connect/lib/authlogic_facebook_connect/acts_as_authentic.rb b/vendor/plugins/authlogic_facebook_connect/lib/authlogic_facebook_connect/acts_as_authentic.rb deleted file mode 100644 index fb9fa1b86..000000000 --- a/vendor/plugins/authlogic_facebook_connect/lib/authlogic_facebook_connect/acts_as_authentic.rb +++ /dev/null @@ -1,20 +0,0 @@ -module AuthlogicFacebookConnect - module ActsAsAuthentic - def self.included(klass) - klass.class_eval do - extend Config - add_acts_as_authentic_module(Methods, :prepend) - end - end - - module Config - end - - module Methods - def self.included(klass) - klass.class_eval do - end - end - end - end -end \ No newline at end of file diff --git a/vendor/plugins/authlogic_facebook_connect/lib/authlogic_facebook_connect/helper.rb b/vendor/plugins/authlogic_facebook_connect/lib/authlogic_facebook_connect/helper.rb deleted file mode 100644 index 4c1c7bdd7..000000000 --- a/vendor/plugins/authlogic_facebook_connect/lib/authlogic_facebook_connect/helper.rb +++ /dev/null @@ -1,29 +0,0 @@ -module AuthlogicFacebookConnect - module Helper - def authlogic_facebook_login_button(options = {}) - # TODO: Make this with correct helpers istead of this uggly hack. - - options[:controller] ||= "user_session" - options[:js] ||= :prototype - - case options[:js] - when :prototype - js_selector = "$('connect_to_facebook_form')" - when :jquery - js_selector = "jQuery('#connect_to_facebook_form')" - end - - output = "
          \n" - output << "\n" - output << "
          \n" - output << "\n" - options.delete(:controller) - output << fb_login_button("connect_to_facebook()", options) - output - end - end -end diff --git a/vendor/plugins/authlogic_facebook_connect/lib/authlogic_facebook_connect/session.rb b/vendor/plugins/authlogic_facebook_connect/lib/authlogic_facebook_connect/session.rb deleted file mode 100644 index a9aeb7688..000000000 --- a/vendor/plugins/authlogic_facebook_connect/lib/authlogic_facebook_connect/session.rb +++ /dev/null @@ -1,158 +0,0 @@ -module AuthlogicFacebookConnect - module Session - def self.included(klass) - klass.class_eval do - extend Config - include Methods - end - end - - module Config - # Should the user be saved with our without validations? - # - # The default behavior is to save the user without validations and then - # in an application specific interface ask for the additional user - # details to make the user valid as facebook just provides a facebook id. - # - # This is useful if you do want to turn on user validations, maybe if you - # just have facebook connect as an additional authentication solution and - # you already have valid users. - # - # * Default: true - # * Accepts: Boolean - def facebook_valid_user(value = nil) - rw_config(:facebook_valid_user, value, false) - end - alias_method :facebook_valid_user=, :facebook_valid_user - - # What user field should be used for the facebook UID? - # - # This is useful if you want to use a single field for multiple types of - # alternate user IDs, e.g. one that handles both OpenID identifiers and - # facebook ids. - # - # * Default: :facebook_uid - # * Accepts: Symbol - def facebook_uid_field(value = nil) - rw_config(:facebook_uid_field, value, :facebook_uid) - end - alias_method :facebook_uid_field=, :facebook_uid_field - - # What session key field should be used for the facebook session key - # - # - # * Default: :facebook_session_key - # * Accepts: Symbol - def facebook_session_key_field(value = nil) - rw_config(:facebook_session_key_field, value, :facebook_session_key) - end - alias_method :facebook_session_key_field=, :facebook_session_key_field - - # Class representing facebook users we want to authenticate against - # - # * Default: klass - # * Accepts: Class - def facebook_user_class(value = nil) - rw_config(:facebook_user_class, value, klass) - end - alias_method :facebook_user_class=, :facebook_user_class - - # Should a new user creation be skipped if there is no user with given facebook uid? - # - # The default behavior is not to skip (hence create new user). You may want to turn it on - # if you want to try with different model. - # - # * Default: false - # * Accepts: Boolean - def facebook_skip_new_user_creation(value = nil) - rw_config(:facebook_skip_new_user_creation, value, false) - end - alias_method :facebook_skip_new_user_creation=, :facebook_skip_new_user_creation - end - - module Methods - def self.included(klass) - klass.class_eval do - validate :validate_by_facebook_connect, :if => :authenticating_with_facebook_connect? - end - - def credentials=(value) - # TODO: Is there a nicer way to tell Authlogic that we don't have any credentials than this? - values = [:facebook_connect] - super - end - end - - def validate_by_facebook_connect - facebook_session = controller.facebook_session - self.attempted_record = facebook_user_class.find(:first, :conditions => { facebook_uid_field => facebook_session.user.uid }) - - if self.attempted_record - self.attempted_record.send(:"#{facebook_session_key_field}=", facebook_session.session_key) - self.attempted_record.save - end - - unless self.attempted_record || facebook_skip_new_user_creation - begin - # Get the user from facebook and create a local user. - # - # We assign it after the call to new in case the attribute is protected. - - new_user = klass.new - - if klass == facebook_user_class - new_user.send(:"#{facebook_uid_field}=", facebook_session.user.uid) - new_user.send(:"#{facebook_session_key_field}=", facebook_session.session_key) - else - new_user.send(:"build_#{facebook_user_class.to_s.underscore}", :"#{facebook_uid_field}" => facebook_session.user.uid, :"#{facebook_session_key_field}" => facebook_session.session_key) - end - - new_user.before_connect(facebook_session) if new_user.respond_to?(:before_connect) - - self.attempted_record = new_user - - if facebook_valid_user - errors.add_to_base( - I18n.t('error_messages.facebook_user_creation_failed', - :default => 'There was a problem creating a new user ' + - 'for your Facebook account')) unless self.attempted_record.valid? - - self.attempted_record = nil - else - self.attempted_record.save_with_validation(false) - end - rescue Facebooker::Session::SessionExpired - errors.add_to_base(I18n.t('error_messages.facebooker_session_expired', - :default => "Your Facebook Connect session has expired, please reconnect.")) - end - end - end - - def authenticating_with_facebook_connect? - controller.set_facebook_session - attempted_record.nil? && errors.empty? && controller.facebook_session - end - - private - def facebook_valid_user - self.class.facebook_valid_user - end - - def facebook_uid_field - self.class.facebook_uid_field - end - - def facebook_session_key_field - self.class.facebook_session_key_field - end - - def facebook_user_class - self.class.facebook_user_class - end - - def facebook_skip_new_user_creation - self.class.facebook_skip_new_user_creation - end - end - end -end diff --git a/vendor/plugins/authlogic_facebook_connect/lib/authlogic_facebook_connect/version.rb b/vendor/plugins/authlogic_facebook_connect/lib/authlogic_facebook_connect/version.rb deleted file mode 100644 index e69de29bb..000000000 diff --git a/vendor/plugins/facebooker/.autotest b/vendor/plugins/facebooker/.autotest deleted file mode 100644 index f8dae1986..000000000 --- a/vendor/plugins/facebooker/.autotest +++ /dev/null @@ -1,15 +0,0 @@ -Autotest.add_hook(:initialize) do |at| - at.clear_mappings - at.find_directories = %w(lib test) - - at.add_exception("test/test_helper.rb") - at.add_exception("test/rails_test_helper.rb") - - at.add_mapping(/^lib\/.*\.rb$/) do |file, _| - at.files_matching(/^test\/.*_test\.rb$/) - end - - at.add_mapping(/^test\/.*_test\.rb$/) do |file, _| - file - end -end diff --git a/vendor/plugins/facebooker/CHANGELOG.rdoc b/vendor/plugins/facebooker/CHANGELOG.rdoc deleted file mode 100644 index 40c653866..000000000 --- a/vendor/plugins/facebooker/CHANGELOG.rdoc +++ /dev/null @@ -1,24 +0,0 @@ -=== HEAD - -* New Features - - * Facebooker::User#publish_to for publishing to a Wall or News Stream - * Optionally parses with Nokogiri. Just require 'nokogiri' in your app. - -=== 1.0.13 / 2009-02-26 - -* Modified fql_query to return results as Hashes as a last resort [Alan Larkin] -* fixed typo in set app properties parser [Andrew Grim, shane] -* Removed actor_id param from templatized feed [shane] -* Added admin.get_allocation [shane] -* Added data.get_cookies and data.set_cookie [shane] -* Added admin.get_app_properties and admin.set_app_properties [shane] - -=== 0.9.9 / 2008-09-08 - -* Re-package as gem after reworking for new API - - -=== 0.9.5 / 2008-02-13 - -* Next release of documentation diff --git a/vendor/plugins/facebooker/COPYING.rdoc b/vendor/plugins/facebooker/COPYING.rdoc deleted file mode 100644 index c52d9e604..000000000 --- a/vendor/plugins/facebooker/COPYING.rdoc +++ /dev/null @@ -1,19 +0,0 @@ -# -# Copyright (c) 2007 Chad Fowler -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of -# this software and associated documentation files (the "Software"), to deal in the -# Software without restriction, including without limitation the rights to use, -# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the -# Software, and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN -# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/plugins/facebooker/Manifest.txt b/vendor/plugins/facebooker/Manifest.txt deleted file mode 100644 index c9c87fc1f..000000000 --- a/vendor/plugins/facebooker/Manifest.txt +++ /dev/null @@ -1,152 +0,0 @@ -.autotest -CHANGELOG.rdoc -COPYING.rdoc -Manifest.txt -README.rdoc -Rakefile -TODO.rdoc -examples/desktop_login.rb -facebooker.gemspec -generators/facebook/facebook_generator.rb -generators/facebook/templates/config/facebooker.yml -generators/facebook/templates/public/javascripts/facebooker.js -generators/facebook_controller/USAGE -generators/facebook_controller/facebook_controller_generator.rb -generators/facebook_controller/templates/controller.rb -generators/facebook_controller/templates/functional_test.rb -generators/facebook_controller/templates/helper.rb -generators/facebook_controller/templates/view.fbml.erb -generators/facebook_controller/templates/view.html.erb -generators/facebook_publisher/facebook_publisher_generator.rb -generators/facebook_publisher/templates/create_facebook_templates.rb -generators/facebook_publisher/templates/publisher.rb -generators/facebook_scaffold/USAGE -generators/facebook_scaffold/facebook_scaffold_generator.rb -generators/facebook_scaffold/templates/controller.rb -generators/facebook_scaffold/templates/facebook_style.css -generators/facebook_scaffold/templates/functional_test.rb -generators/facebook_scaffold/templates/helper.rb -generators/facebook_scaffold/templates/layout.fbml.erb -generators/facebook_scaffold/templates/layout.html.erb -generators/facebook_scaffold/templates/style.css -generators/facebook_scaffold/templates/view_edit.fbml.erb -generators/facebook_scaffold/templates/view_edit.html.erb -generators/facebook_scaffold/templates/view_index.fbml.erb -generators/facebook_scaffold/templates/view_index.html.erb -generators/facebook_scaffold/templates/view_new.fbml.erb -generators/facebook_scaffold/templates/view_new.html.erb -generators/facebook_scaffold/templates/view_show.fbml.erb -generators/facebook_scaffold/templates/view_show.html.erb -generators/publisher/publisher_generator.rb -generators/xd_receiver/templates/xd_receiver_ssl.html -generators/xd_receiver/templates/xd_receiver.html -generators/xd_receiver/xd_receiver_generator.rb -init.rb -install.rb -lib/facebooker.rb -lib/facebooker/adapters/adapter_base.rb -lib/facebooker/adapters/bebo_adapter.rb -lib/facebooker/adapters/facebook_adapter.rb -lib/facebooker/attachment.rb -lib/facebooker/admin.rb -lib/facebooker/application.rb -lib/facebooker/batch_request.rb -lib/facebooker/data.rb -lib/facebooker/feed.rb -lib/facebooker/logging.rb -lib/facebooker/mobile.rb -lib/facebooker/mock/service.rb -lib/facebooker/mock/session.rb -lib/facebooker/model.rb -lib/facebooker/models/affiliation.rb -lib/facebooker/models/album.rb -lib/facebooker/models/applicationproperties.rb -lib/facebooker/models/applicationrestrictions.rb -lib/facebooker/models/cookie.rb -lib/facebooker/models/comment.rb -lib/facebooker/models/education_info.rb -lib/facebooker/models/event.rb -lib/facebooker/models/family_relative_info.rb -lib/facebooker/models/friend_list.rb -lib/facebooker/models/group.rb -lib/facebooker/models/info_item.rb -lib/facebooker/models/info_section.rb -lib/facebooker/models/location.rb -lib/facebooker/models/notifications.rb -lib/facebooker/models/page.rb -lib/facebooker/models/photo.rb -lib/facebooker/models/tag.rb -lib/facebooker/models/user.rb -lib/facebooker/models/video.rb -lib/facebooker/models/work_info.rb -lib/facebooker/models/message_thread.rb -lib/facebooker/parser.rb -lib/facebooker/rails/backwards_compatible_param_checks.rb -lib/facebooker/rails/controller.rb -lib/facebooker/rails/cucumber.rb -lib/facebooker/rails/cucumber/world.rb -lib/facebooker/rails/extensions/action_controller.rb -lib/facebooker/rails/extensions/rack_setup.rb -lib/facebooker/rails/extensions/routing.rb -lib/facebooker/rails/facebook_form_builder.rb -lib/facebooker/rails/facebook_pretty_errors.rb -lib/facebooker/rails/facebook_request_fix.rb -lib/facebooker/rails/facebook_request_fix_2-3.rb -lib/facebooker/rails/facebook_session_handling.rb -lib/facebooker/rails/facebook_url_helper.rb -lib/facebooker/rails/facebook_url_rewriting.rb -lib/facebooker/rails/helpers.rb -lib/facebooker/rails/helpers/fb_connect.rb -lib/facebooker/rails/helpers/stream_publish.rb -lib/facebooker/rails/integration_session.rb -lib/facebooker/rails/profile_publisher_extensions.rb -lib/facebooker/rails/publisher.rb -lib/facebooker/rails/routing.rb -lib/facebooker/rails/test_helpers.rb -lib/facebooker/rails/utilities.rb -lib/facebooker/server_cache.rb -lib/facebooker/service.rb -lib/facebooker/service/base_service.rb -lib/facebooker/service/curl_service.rb -lib/facebooker/service/net_http_service.rb -lib/facebooker/service/typhoeus_service.rb -lib/facebooker/service/typhoeus_multi_service.rb -lib/facebooker/session.rb -lib/facebooker/stream_post.rb -lib/facebooker/version.rb -lib/net/http_multipart_post.rb -lib/rack/facebook.rb -lib/rack/facebook_session.rb -lib/tasks/facebooker.rake -lib/tasks/facebooker.rb -lib/tasks/tunnel.rake -rails/init.rb -setup.rb -templates/layout.erb -test/facebooker/adapters_test.rb -test/facebooker/admin_test.rb -test/facebooker/application_test.rb -test/facebooker/attachment_test.rb -test/facebooker/batch_request_test.rb -test/facebooker/data_test.rb -test/facebooker/logging_test.rb -test/facebooker/mobile_test.rb -test/facebooker/model_test.rb -test/facebooker/models/event_test.rb -test/facebooker/models/page_test.rb -test/facebooker/models/photo_test.rb -test/facebooker/models/user_test.rb -test/facebooker/rails/publisher_test.rb -test/facebooker/rails_integration_test.rb -test/facebooker/rails/facebook_request_fix_2-3_test.rb -test/facebooker/server_cache_test.rb -test/facebooker/session_test.rb -test/facebooker_test.rb -test/fixtures/multipart_post_body_with_only_parameters.txt -test/fixtures/multipart_post_body_with_single_file.txt -test/fixtures/multipart_post_body_with_single_file_that_has_nil_key.txt -test/net/http_multipart_post_test.rb -test/rack/facebook_test.rb -test/rack/facebook_session_test.rb -test/rails_test_helper.rb -test/test_helper.rb diff --git a/vendor/plugins/facebooker/README.rdoc b/vendor/plugins/facebooker/README.rdoc deleted file mode 100644 index b72faf409..000000000 --- a/vendor/plugins/facebooker/README.rdoc +++ /dev/null @@ -1,119 +0,0 @@ -= Facebooker - -* http://facebooker.rubyforge.org - -== DESCRIPTION: - -Facebooker is a Ruby wrapper over the Facebook[http://facebook.com] {REST API}[http://wiki.developers.facebook.com/index.php/API]. Its goals are: - -* Idiomatic Ruby -* No dependencies outside of the Ruby standard library (This is true with Rails 2.1. Previous Rails versions require the JSON gem) -* Concrete classes and methods modeling the Facebook data, so it's easy for a Rubyist to understand what's available -* Well tested - - -== FEATURES/PROBLEMS: - -* Idiomatic Ruby -* No dependencies outside of the Ruby standard library -* Concrete classes and methods modeling the Facebook data, so it's easy for a Rubyist to understand what's available -* Well tested - -== SYNOPSIS: - -View David Clements' {excellent tutorial}[http://apps.facebook.com/facebooker_tutorial] at {http://apps.facebook.com/facebooker_tutorial/}[http://apps.facebook.com/facebooker_tutorial] or check out {Developing Facebook Platform Applications with Rails}[http://www.pragprog.com/titles/mmfacer]. -{Join the Mailing List}:[groups.google.com/group/facebooker] - -== REQUIREMENTS: - -None - -== INSTALL: - -=== Non Rails - -The best way is: - - gem install facebooker - -If, for some reason, you can't/won't use RubyGems, you can do: - - (sudo) ruby setup.rb - -=== Rails - -Facebooker can be installed as a Rails plugin by: - - script/plugin install git://github.com/mmangino/facebooker.git - -If you don't have git, the plugin can be downloaded from http://github.com/mmangino/facebooker/tarball/master - -=== Using Gem in Rails - -The rake task would not be added automatically, so to use it in rails you would have to add the following towards the end of your Rakefile: - - require 'tasks/facebooker' - -Once the plugin is installed, you will need to configure your Facebook app in config/facebooker.yml. - -Your application users will need to have added the application in facebook to access all of facebooker's features. You enforce this by adding - - ensure_application_is_installed_by_facebook_user - -to your application controller. - -To prevent a violation of Facebook Terms of Service while reducing log bloat, you should also add - - filter_parameter_logging :fb_sig_friends - -to your application controller. - -== using MemCache session - -Facebook uses some non alphanum characters in the session identifier which interfere with memcache stored sessions. If you want to use MemCache for storing sessions, you can override the valid session id method on memcache by placing the following code in an initializer: - -# add - as an okay key -class CGI - class Session - class MemCacheStore - def check_id(id) #:nodoc:# - /[^0-9a-zA-Z\-\._]+/ =~ id.to_s ? false : true - end - end - end -end - -== Other versions - - A facebooker port for Sinatra is available at http://github.com/jsmestad/frankie/tree/master - -== LICENSE: - -(The MIT License) - -Copyright (c) 2008-2009: - -* Chad Fowler -* Patrick Ewing -* Mike Mangino -* Shane Vitarana -* Corey Innis - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/plugins/facebooker/Rakefile b/vendor/plugins/facebooker/Rakefile deleted file mode 100644 index 8776004e8..000000000 --- a/vendor/plugins/facebooker/Rakefile +++ /dev/null @@ -1,94 +0,0 @@ -# -*- ruby -*- -# -require 'rubygems' -require 'hoe' -begin - require 'load_multi_rails_rake_tasks' -rescue LoadError - $stderr.puts "Install the multi_rails gem to run tests against multiple versions of Rails" -end - -require 'lib/facebooker/version' - -HOE = Hoe.spec('facebooker') do - self.version = Facebooker::VERSION::STRING - self.rubyforge_name = 'facebooker' - developer 'Chad Fowler', 'chad@chadfowlwer.com' - developer 'Patrick Ewing', '' - developer 'Mike Mangino', '' - developer 'Shane Vitarana', '' - developer 'Corey Innis', '' - developer 'Mike Mangino', 'mmangino@elevatedrails.com' - - self.readme_file = 'README.rdoc' - self.history_file = 'CHANGELOG.rdoc' - self.remote_rdoc_dir = '' # Release to root - self.test_globs = ['test/**/*_test.rb'] - extra_deps << ['json_pure', '>= 1.0.0'] - self.extra_rdoc_files = FileList['*.rdoc'] -end - -begin - require 'rcov/rcovtask' - - namespace :test do - namespace :coverage do - desc "Delete aggregate coverage data." - task(:clean) { rm_f "coverage.data" } - end - desc 'Aggregate code coverage for unit, functional and integration tests' - Rcov::RcovTask.new(:coverage) do |t| - t.libs << "test" - t.test_files = FileList["test/**/*_test.rb"] - t.output_dir = "coverage/" - t.verbose = true - t.rcov_opts = ['--exclude', 'test,/usr/lib/ruby,/Library/Ruby,/System/Library', '--sort', 'coverage'] - end - end -rescue LoadError - $stderr.puts "Install the rcov gem to enable test coverage analysis" -end - -namespace :gem do - task :spec do - File.open("#{HOE.name}.gemspec", 'w') do |f| - f.write(HOE.spec.to_ruby) - end - end - - namespace :spec do - task :dev do - File.open("#{HOE.name}.gemspec", 'w') do |f| - HOE.spec.version = "#{HOE.version}.#{Time.now.strftime("%Y%m%d%H%M%S")}" - f.write(HOE.spec.to_ruby) - end - end - end -end - -# vim: syntax=Ruby -# -# -# require File.dirname(__FILE__) + '/vendor/gardener/lib/gardener' -# -# require 'facebooker' -# -# namespace :doc do -# task :readme do -# puts "Readme" -# end -# end -# -# Gardener.configure do -# gem_spec do |spec| -# spec.name = 'facebooker' -# spec.version = Gem::Version.new(Facebooker::VERSION::STRING) -# spec.summary = "Pure, idiomatic Ruby wrapper for the Facebook REST API." -# spec.email = 'chad@infoether.com' -# spec.author = ['Chad Fowler', 'Patrick Ewing','Mike Mangino','Shane Vitarana'] -# spec.extra_rdoc_files = %w(COPYING) -# spec.rdoc_options = ['--title', "Gardener", -# '--main', 'README', -# '--line-numbers', '--inline-source'] -# end -# end diff --git a/vendor/plugins/facebooker/TODO.rdoc b/vendor/plugins/facebooker/TODO.rdoc deleted file mode 100644 index c9a669b3e..000000000 --- a/vendor/plugins/facebooker/TODO.rdoc +++ /dev/null @@ -1,4 +0,0 @@ -[ ] Document session handling. Come up with different cases and paths for the flow and create tests for each -[ ] Verify that sessions are used only where required. Raise exceptions on User methods that require a session when none is available -[ ] Refactor setup / Adapter loading code - diff --git a/vendor/plugins/facebooker/examples/desktop_login.rb b/vendor/plugins/facebooker/examples/desktop_login.rb deleted file mode 100644 index af1a0e681..000000000 --- a/vendor/plugins/facebooker/examples/desktop_login.rb +++ /dev/null @@ -1,14 +0,0 @@ -$: << File.join(File.dirname(__FILE__), "..", 'lib') -require 'facebooker' -load "~/.facebooker" rescue fail("You'll need to specify API_KEY and SECRET_KEY to run this example. One way to do that would be to put them in ~/.facebooker") -session = Facebooker::Session::Desktop.create(API_KEY, SECRET_KEY) -puts session.login_url -gets - -session.user.friends!.each do |user| - puts "#{user.id}:#{user.name}" -end -# This time all the data is there because friends! has already retrieved it. -session.user.friends.each do |user| - puts "#{user.id}:#{user.name}" -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/facebooker.gemspec b/vendor/plugins/facebooker/facebooker.gemspec deleted file mode 100644 index 2dfc129a3..000000000 --- a/vendor/plugins/facebooker/facebooker.gemspec +++ /dev/null @@ -1,42 +0,0 @@ -# -*- encoding: utf-8 -*- - -Gem::Specification.new do |s| - s.name = %q{facebooker} - s.version = "1.0.75" - - s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.authors = ["Chad Fowler", "Patrick Ewing", "Mike Mangino", "Shane Vitarana", "Corey Innis", "Mike Mangino"] - s.date = %q{2010-08-20} - s.description = %q{Facebooker is a Ruby wrapper over the Facebook[http://facebook.com] {REST API}[http://wiki.developers.facebook.com/index.php/API]. Its goals are: - -* Idiomatic Ruby -* No dependencies outside of the Ruby standard library (This is true with Rails 2.1. Previous Rails versions require the JSON gem) -* Concrete classes and methods modeling the Facebook data, so it's easy for a Rubyist to understand what's available -* Well tested} - s.email = ["chad@chadfowlwer.com", "", "", "", "", "mmangino@elevatedrails.com"] - s.extra_rdoc_files = ["Manifest.txt", "CHANGELOG.rdoc", "COPYING.rdoc", "README.rdoc", "TODO.rdoc"] - s.files = [".autotest", "CHANGELOG.rdoc", "COPYING.rdoc", "Manifest.txt", "README.rdoc", "Rakefile", "TODO.rdoc", "examples/desktop_login.rb", "facebooker.gemspec", "generators/facebook/facebook_generator.rb", "generators/facebook/templates/config/facebooker.yml", "generators/facebook/templates/public/javascripts/facebooker.js", "generators/facebook_controller/USAGE", "generators/facebook_controller/facebook_controller_generator.rb", "generators/facebook_controller/templates/controller.rb", "generators/facebook_controller/templates/functional_test.rb", "generators/facebook_controller/templates/helper.rb", "generators/facebook_controller/templates/view.fbml.erb", "generators/facebook_controller/templates/view.html.erb", "generators/facebook_publisher/facebook_publisher_generator.rb", "generators/facebook_publisher/templates/create_facebook_templates.rb", "generators/facebook_publisher/templates/publisher.rb", "generators/facebook_scaffold/USAGE", "generators/facebook_scaffold/facebook_scaffold_generator.rb", "generators/facebook_scaffold/templates/controller.rb", "generators/facebook_scaffold/templates/facebook_style.css", "generators/facebook_scaffold/templates/functional_test.rb", "generators/facebook_scaffold/templates/helper.rb", "generators/facebook_scaffold/templates/layout.fbml.erb", "generators/facebook_scaffold/templates/layout.html.erb", "generators/facebook_scaffold/templates/style.css", "generators/facebook_scaffold/templates/view_edit.fbml.erb", "generators/facebook_scaffold/templates/view_edit.html.erb", "generators/facebook_scaffold/templates/view_index.fbml.erb", "generators/facebook_scaffold/templates/view_index.html.erb", "generators/facebook_scaffold/templates/view_new.fbml.erb", "generators/facebook_scaffold/templates/view_new.html.erb", "generators/facebook_scaffold/templates/view_show.fbml.erb", "generators/facebook_scaffold/templates/view_show.html.erb", "generators/publisher/publisher_generator.rb", "generators/xd_receiver/templates/xd_receiver_ssl.html", "generators/xd_receiver/templates/xd_receiver.html", "generators/xd_receiver/xd_receiver_generator.rb", "init.rb", "install.rb", "lib/facebooker.rb", "lib/facebooker/adapters/adapter_base.rb", "lib/facebooker/adapters/bebo_adapter.rb", "lib/facebooker/adapters/facebook_adapter.rb", "lib/facebooker/attachment.rb", "lib/facebooker/admin.rb", "lib/facebooker/application.rb", "lib/facebooker/batch_request.rb", "lib/facebooker/data.rb", "lib/facebooker/feed.rb", "lib/facebooker/logging.rb", "lib/facebooker/mobile.rb", "lib/facebooker/mock/service.rb", "lib/facebooker/mock/session.rb", "lib/facebooker/model.rb", "lib/facebooker/models/affiliation.rb", "lib/facebooker/models/album.rb", "lib/facebooker/models/applicationproperties.rb", "lib/facebooker/models/applicationrestrictions.rb", "lib/facebooker/models/cookie.rb", "lib/facebooker/models/comment.rb", "lib/facebooker/models/education_info.rb", "lib/facebooker/models/event.rb", "lib/facebooker/models/family_relative_info.rb", "lib/facebooker/models/friend_list.rb", "lib/facebooker/models/group.rb", "lib/facebooker/models/info_item.rb", "lib/facebooker/models/info_section.rb", "lib/facebooker/models/location.rb", "lib/facebooker/models/notifications.rb", "lib/facebooker/models/page.rb", "lib/facebooker/models/photo.rb", "lib/facebooker/models/tag.rb", "lib/facebooker/models/user.rb", "lib/facebooker/models/video.rb", "lib/facebooker/models/work_info.rb", "lib/facebooker/models/message_thread.rb", "lib/facebooker/parser.rb", "lib/facebooker/rails/backwards_compatible_param_checks.rb", "lib/facebooker/rails/controller.rb", "lib/facebooker/rails/cucumber.rb", "lib/facebooker/rails/cucumber/world.rb", "lib/facebooker/rails/extensions/action_controller.rb", "lib/facebooker/rails/extensions/rack_setup.rb", "lib/facebooker/rails/extensions/routing.rb", "lib/facebooker/rails/facebook_form_builder.rb", "lib/facebooker/rails/facebook_pretty_errors.rb", "lib/facebooker/rails/facebook_request_fix.rb", "lib/facebooker/rails/facebook_request_fix_2-3.rb", "lib/facebooker/rails/facebook_session_handling.rb", "lib/facebooker/rails/facebook_url_helper.rb", "lib/facebooker/rails/facebook_url_rewriting.rb", "lib/facebooker/rails/helpers.rb", "lib/facebooker/rails/helpers/fb_connect.rb", "lib/facebooker/rails/helpers/stream_publish.rb", "lib/facebooker/rails/integration_session.rb", "lib/facebooker/rails/profile_publisher_extensions.rb", "lib/facebooker/rails/publisher.rb", "lib/facebooker/rails/routing.rb", "lib/facebooker/rails/test_helpers.rb", "lib/facebooker/rails/utilities.rb", "lib/facebooker/server_cache.rb", "lib/facebooker/service.rb", "lib/facebooker/service/base_service.rb", "lib/facebooker/service/curl_service.rb", "lib/facebooker/service/net_http_service.rb", "lib/facebooker/service/typhoeus_service.rb", "lib/facebooker/service/typhoeus_multi_service.rb", "lib/facebooker/session.rb", "lib/facebooker/stream_post.rb", "lib/facebooker/version.rb", "lib/net/http_multipart_post.rb", "lib/rack/facebook.rb", "lib/rack/facebook_session.rb", "lib/tasks/facebooker.rake", "lib/tasks/facebooker.rb", "lib/tasks/tunnel.rake", "rails/init.rb", "setup.rb", "templates/layout.erb", "test/facebooker/adapters_test.rb", "test/facebooker/admin_test.rb", "test/facebooker/application_test.rb", "test/facebooker/attachment_test.rb", "test/facebooker/batch_request_test.rb", "test/facebooker/data_test.rb", "test/facebooker/logging_test.rb", "test/facebooker/mobile_test.rb", "test/facebooker/model_test.rb", "test/facebooker/models/event_test.rb", "test/facebooker/models/page_test.rb", "test/facebooker/models/photo_test.rb", "test/facebooker/models/user_test.rb", "test/facebooker/rails/publisher_test.rb", "test/facebooker/rails_integration_test.rb", "test/facebooker/rails/facebook_request_fix_2-3_test.rb", "test/facebooker/server_cache_test.rb", "test/facebooker/session_test.rb", "test/facebooker_test.rb", "test/fixtures/multipart_post_body_with_only_parameters.txt", "test/fixtures/multipart_post_body_with_single_file.txt", "test/fixtures/multipart_post_body_with_single_file_that_has_nil_key.txt", "test/net/http_multipart_post_test.rb", "test/rack/facebook_test.rb", "test/rack/facebook_session_test.rb", "test/rails_test_helper.rb", "test/test_helper.rb", "test/facebooker/rails/facebook_url_rewriting_test.rb", "test/facebooker/rails/integration_session_test.rb", "test/facebooker/service_test.rb"] - s.homepage = %q{http://facebooker.rubyforge.org} - s.rdoc_options = ["--main", "README.rdoc"] - s.require_paths = ["lib"] - s.rubyforge_project = %q{facebooker} - s.rubygems_version = %q{1.3.5} - s.summary = %q{Facebooker is a Ruby wrapper over the Facebook[http://facebook.com] {REST API}[http://wiki.developers.facebook.com/index.php/API]} - s.test_files = ["test/facebooker/adapters_test.rb", "test/facebooker/admin_test.rb", "test/facebooker/application_test.rb", "test/facebooker/attachment_test.rb", "test/facebooker/batch_request_test.rb", "test/facebooker/data_test.rb", "test/facebooker/logging_test.rb", "test/facebooker/mobile_test.rb", "test/facebooker/model_test.rb", "test/facebooker/models/event_test.rb", "test/facebooker/models/page_test.rb", "test/facebooker/models/photo_test.rb", "test/facebooker/models/user_test.rb", "test/facebooker/rails/facebook_request_fix_2-3_test.rb", "test/facebooker/rails/facebook_url_rewriting_test.rb", "test/facebooker/rails/integration_session_test.rb", "test/facebooker/rails/publisher_test.rb", "test/facebooker/rails_integration_test.rb", "test/facebooker/server_cache_test.rb", "test/facebooker/service_test.rb", "test/facebooker/session_test.rb", "test/facebooker_test.rb", "test/net/http_multipart_post_test.rb", "test/rack/facebook_session_test.rb", "test/rack/facebook_test.rb"] - - if s.respond_to? :specification_version then - current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION - s.specification_version = 3 - - if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then - s.add_runtime_dependency(%q, [">= 1.0.0"]) - s.add_development_dependency(%q, [">= 2.4.0"]) - else - s.add_dependency(%q, [">= 1.0.0"]) - s.add_dependency(%q, [">= 2.4.0"]) - end - else - s.add_dependency(%q, [">= 1.0.0"]) - s.add_dependency(%q, [">= 2.4.0"]) - end -end diff --git a/vendor/plugins/facebooker/generators/facebook/facebook_generator.rb b/vendor/plugins/facebooker/generators/facebook/facebook_generator.rb deleted file mode 100644 index e7333c69f..000000000 --- a/vendor/plugins/facebooker/generators/facebook/facebook_generator.rb +++ /dev/null @@ -1,14 +0,0 @@ -class FacebookGenerator < Rails::Generator::Base - def manifest - record do |m| - m.file 'config/facebooker.yml', 'config/facebooker.yml' - m.file 'public/javascripts/facebooker.js', 'public/javascripts/facebooker.js' - end - end - - protected - - def banner - "Usage: #{$0} facebooker" - end -end diff --git a/vendor/plugins/facebooker/generators/facebook/templates/config/facebooker.yml b/vendor/plugins/facebooker/generators/facebook/templates/config/facebooker.yml deleted file mode 100644 index a68a197be..000000000 --- a/vendor/plugins/facebooker/generators/facebook/templates/config/facebooker.yml +++ /dev/null @@ -1,49 +0,0 @@ -# The api key, secret key, and canvas page name are required to get started -# Tunnel configuration is only needed if you are going to use the facebooker:tunnel Rake tasks -# Your callback url in Facebook should be set to http://public_host:public_port -# If you're building a Facebook connect site, -# change the value of set_asset_host_to_callback_url to false -# To develop for the new profile design, add the following key.. -# api: new -# remove the key or set it to anything else to use the old facebook design. -# This should only be necessary until the final version of the new profile is released. - -development: - api_key: - secret_key: - canvas_page_name: - callback_url: - pretty_errors: true - set_asset_host_to_callback_url: true - tunnel: - public_host_username: - public_host: - public_port: 4007 - local_port: 3000 - server_alive_interval: 0 - -test: - api_key: - secret_key: - canvas_page_name: - callback_url: - set_asset_host_to_callback_url: true - tunnel: - public_host_username: - public_host: - public_port: 4007 - local_port: 3000 - server_alive_interval: 0 - -production: - api_key: - secret_key: - canvas_page_name: - callback_url: - set_asset_host_to_callback_url: true - tunnel: - public_host_username: - public_host: - public_port: 4007 - local_port: 3000 - server_alive_interval: 0 diff --git a/vendor/plugins/facebooker/generators/facebook/templates/public/javascripts/facebooker.js b/vendor/plugins/facebooker/generators/facebook/templates/public/javascripts/facebooker.js deleted file mode 100644 index 9890ecb89..000000000 --- a/vendor/plugins/facebooker/generators/facebook/templates/public/javascripts/facebooker.js +++ /dev/null @@ -1,93 +0,0 @@ - -function $(element) { - if (typeof element == "string") { - element=document.getElementById(element); - } - if (element) - extend_instance(element,Element); - return element; -} - -function extend_instance(instance,hash) { - for (var name in hash) { - instance[name] = hash[name]; - } -} - -var Element = { - "hide": function () { - this.setStyle("display","none") - }, - "show": function () { - this.setStyle("display","block") - }, - "visible": function () { - return (this.getStyle("display") != "none"); - }, - "toggle": function () { - if (this.visible) { - this.hide(); - } else { - this.show(); - } - } -}; - -function encodeURIComponent(str) { - if (typeof(str) == "string") { - return str.replace(/=/g,'%3D').replace(/&/g,'%26'); - } - //checkboxes and radio buttons return objects instead of a string - else if(typeof(str) == "object"){ - for (prop in str) - { - return str[prop].replace(/=/g,'%3D').replace(/&/g,'%26'); - } - } -}; - -var Form = {}; -Form.serialize = function(form_element) { - return $(form_element).serialize(); -}; - -Ajax.Updater = function (container,url,options) { - this.container = container; - this.url=url; - this.ajax = new Ajax(); - this.ajax.requireLogin = 1; - if (options["onSuccess"]) { - this.ajax.responseType = Ajax.JSON; - this.ajax.ondone = options["onSuccess"]; - } else { - this.ajax.responseType = Ajax.FBML; - this.ajax.ondone = function(data) { - $(container).setInnerFBML(data); - } - } - if (options["onFailure"]) { - this.ajax.onerror = options["onFailure"]; - } - - if (!options['parameters']) { - options['parameters'] = {} - } - - // simulate other verbs over post - if (options['method']) { - options['parameters']['_method'] = options['method']; - } - - this.ajax.post(url,options['parameters']); - if (options["onLoading"]) { - options["onLoading"].call() - } -}; -Ajax.Request = function(url,options) { - Ajax.Updater('unused',url,options); -}; - -PeriodicalExecuter = function (callback, frequency) { - setTimeout(callback, frequency *1000); - setTimeout(function() { new PeriodicalExecuter(callback,frequency); }, frequency*1000); -}; diff --git a/vendor/plugins/facebooker/generators/facebook_controller/USAGE b/vendor/plugins/facebooker/generators/facebook_controller/USAGE deleted file mode 100644 index 698e343fe..000000000 --- a/vendor/plugins/facebooker/generators/facebook_controller/USAGE +++ /dev/null @@ -1,33 +0,0 @@ -Description: - Stubs out a new controller and its views. Pass the controller name, either - CamelCased or under_scored, and a list of views as arguments. - - To create a controller within a module, specify the controller name as a - path like 'parent_module/controller_name'. - - This generates a controller class in app/controllers, view templates in - app/views/controller_name, a helper class in app/helpers, and a functional - test suite in test/functional. - - Also, it generates fbml view templates. - -Example: - `./script/generate controller CreditCard open debit credit close` - - Credit card controller with URLs like /credit_card/debit. - Controller: app/controllers/credit_card_controller.rb - Views: app/views/credit_card/debit.html.erb [...], - app/views/credit_card/debit.fbml.erb [...] - Helper: app/helpers/credit_card_helper.rb - Test: test/functional/credit_card_controller_test.rb - -Modules Example: - `./script/generate controller 'admin/credit_card' suspend late_fee` - - Credit card admin controller with URLs /admin/credit_card/suspend. - Controller: app/controllers/admin/credit_card_controller.rb - Views: app/views/admin/credit_card/debit.html.erb [...], - app/views/admin/credit_card/debit.fbml.erb [...] - Helper: app/helpers/admin/credit_card_helper.rb - Test: test/functional/admin/credit_card_controller_test.rb - diff --git a/vendor/plugins/facebooker/generators/facebook_controller/facebook_controller_generator.rb b/vendor/plugins/facebooker/generators/facebook_controller/facebook_controller_generator.rb deleted file mode 100644 index b8e29960d..000000000 --- a/vendor/plugins/facebooker/generators/facebook_controller/facebook_controller_generator.rb +++ /dev/null @@ -1,40 +0,0 @@ -class FacebookControllerGenerator < Rails::Generator::NamedBase - def manifest - record do |m| - # Check for class naming collisions. - m.class_collisions class_path, "#{class_name}Controller", "#{class_name}ControllerTest", "#{class_name}Helper" - - # Controller, helper, views, and test directories. - m.directory File.join('app/controllers', class_path) - m.directory File.join('app/helpers', class_path) - m.directory File.join('app/views', class_path, file_name) - m.directory File.join('test/functional', class_path) - - # Controller class, functional test, and helper class. - m.template 'controller.rb', - File.join('app/controllers', - class_path, - "#{file_name}_controller.rb") - - m.template 'functional_test.rb', - File.join('test/functional', - class_path, - "#{file_name}_controller_test.rb") - - m.template 'helper.rb', - File.join('app/helpers', - class_path, - "#{file_name}_helper.rb") - - # View template for each action. - actions.each do |action| - html_path = File.join('app/views', class_path, file_name, "#{action}.html.erb") - m.template 'view.html.erb', html_path, - :assigns => { :action => action, :path => html_path } - fbml_path = File.join('app/views', class_path, file_name, "#{action}.fbml.erb") - m.template 'view.fbml.erb', fbml_path, - :assigns => { :action => action, :path => fbml_path } - end - end - end -end diff --git a/vendor/plugins/facebooker/generators/facebook_controller/templates/controller.rb b/vendor/plugins/facebooker/generators/facebook_controller/templates/controller.rb deleted file mode 100644 index cda2659e6..000000000 --- a/vendor/plugins/facebooker/generators/facebook_controller/templates/controller.rb +++ /dev/null @@ -1,7 +0,0 @@ -class <%= class_name %>Controller < ApplicationController -<% for action in actions -%> - def <%= action %> - end - -<% end -%> -end diff --git a/vendor/plugins/facebooker/generators/facebook_controller/templates/functional_test.rb b/vendor/plugins/facebooker/generators/facebook_controller/templates/functional_test.rb deleted file mode 100644 index 8f715e36b..000000000 --- a/vendor/plugins/facebooker/generators/facebook_controller/templates/functional_test.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'test_helper' -require File.dirname(__FILE__)+'/../../vendor/plugins/facebooker/lib/facebooker/rails/test_helpers.rb' - -class <%= class_name %>ControllerTest < ActionController::TestCase - include Facebooker::Rails::TestHelpers - - # Replace this with your real tests. - def test_truth - assert true - end -end diff --git a/vendor/plugins/facebooker/generators/facebook_controller/templates/helper.rb b/vendor/plugins/facebooker/generators/facebook_controller/templates/helper.rb deleted file mode 100644 index 3fe2ecdc7..000000000 --- a/vendor/plugins/facebooker/generators/facebook_controller/templates/helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module <%= class_name %>Helper -end diff --git a/vendor/plugins/facebooker/generators/facebook_controller/templates/view.fbml.erb b/vendor/plugins/facebooker/generators/facebook_controller/templates/view.fbml.erb deleted file mode 100644 index ad85431f9..000000000 --- a/vendor/plugins/facebooker/generators/facebook_controller/templates/view.fbml.erb +++ /dev/null @@ -1,2 +0,0 @@ -

          <%= class_name %>#<%= action %>

          -

          Find me in <%= path %>

          diff --git a/vendor/plugins/facebooker/generators/facebook_controller/templates/view.html.erb b/vendor/plugins/facebooker/generators/facebook_controller/templates/view.html.erb deleted file mode 100644 index ad85431f9..000000000 --- a/vendor/plugins/facebooker/generators/facebook_controller/templates/view.html.erb +++ /dev/null @@ -1,2 +0,0 @@ -

          <%= class_name %>#<%= action %>

          -

          Find me in <%= path %>

          diff --git a/vendor/plugins/facebooker/generators/facebook_publisher/facebook_publisher_generator.rb b/vendor/plugins/facebooker/generators/facebook_publisher/facebook_publisher_generator.rb deleted file mode 100644 index 1152a9c0d..000000000 --- a/vendor/plugins/facebooker/generators/facebook_publisher/facebook_publisher_generator.rb +++ /dev/null @@ -1,14 +0,0 @@ -class FacebookPublisherGenerator < Rails::Generator::NamedBase - def manifest - record do |m| - m.directory "app/models" - m.template "publisher.rb", "app/models/#{file_name}_publisher.rb" - migration_file_name="create_facebook_templates" - # unless m.migration_exists?(migration_file_name) - # THis should work, but it doesn't. So we re-implement it instead - if Dir.glob(File.join(RAILS_ROOT,"db","migrate","[0-9]*_*.rb")).grep(/[0-9]+_create_facebook_templates.rb$/).blank? - m.migration_template "create_facebook_templates.rb", "db/migrate", :migration_file_name=>migration_file_name - end - end - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/generators/facebook_publisher/templates/create_facebook_templates.rb b/vendor/plugins/facebooker/generators/facebook_publisher/templates/create_facebook_templates.rb deleted file mode 100644 index bd92f9de6..000000000 --- a/vendor/plugins/facebooker/generators/facebook_publisher/templates/create_facebook_templates.rb +++ /dev/null @@ -1,15 +0,0 @@ -class CreateFacebookTemplates < ActiveRecord::Migration - def self.up - create_table :facebook_templates, :force => true do |t| - t.string :template_name, :null => false - t.string :content_hash, :null => false - t.string :bundle_id, :null => true - end - add_index :facebook_templates, :template_name, :unique => true - end - - def self.down - remove_index :facebook_templates, :template_name - drop_table :facebook_templates - end -end diff --git a/vendor/plugins/facebooker/generators/facebook_publisher/templates/publisher.rb b/vendor/plugins/facebooker/generators/facebook_publisher/templates/publisher.rb deleted file mode 100644 index 620afc009..000000000 --- a/vendor/plugins/facebooker/generators/facebook_publisher/templates/publisher.rb +++ /dev/null @@ -1,3 +0,0 @@ -class <%= class_name %>Publisher < Facebooker::Rails::Publisher - -end diff --git a/vendor/plugins/facebooker/generators/facebook_scaffold/USAGE b/vendor/plugins/facebooker/generators/facebook_scaffold/USAGE deleted file mode 100644 index 69976722e..000000000 --- a/vendor/plugins/facebooker/generators/facebook_scaffold/USAGE +++ /dev/null @@ -1,27 +0,0 @@ -Description: - Scaffolds an entire resource, from model and migration to controller and - views, along with a full test suite. The resource is ready to use as a - starting point for your restful, resource-oriented application. - - Pass the name of the model, either CamelCased or under_scored, as the first - argument, and an optional list of attribute pairs. - - Attribute pairs are column_name:sql_type arguments specifying the - model's attributes. Timestamps are added by default, so you don't have to - specify them by hand as 'created_at:datetime updated_at:datetime'. - - You don't have to think up every attribute up front, but it helps to - sketch out a few so you can start working with the resource immediately. - - For example, `scaffold post title:string body:text published:boolean` - gives you a model with those three attributes, a controller that handles - the create/show/update/destroy, forms to create and edit your posts, and - an index that lists them all, as well as a map.resources :posts - declaration in config/routes.rb. - - Also, it creates Facebook view templates and functional tests. - -Examples: - `./script/generate scaffold post` # no attributes, view will be anemic - `./script/generate scaffold post title:string body:text published:boolean` - `./script/generate scaffold purchase order_id:integer amount:decimal` diff --git a/vendor/plugins/facebooker/generators/facebook_scaffold/facebook_scaffold_generator.rb b/vendor/plugins/facebooker/generators/facebook_scaffold/facebook_scaffold_generator.rb deleted file mode 100644 index 54dd1cd28..000000000 --- a/vendor/plugins/facebooker/generators/facebook_scaffold/facebook_scaffold_generator.rb +++ /dev/null @@ -1,118 +0,0 @@ -class Rails::Generator::Commands::Create - def register_fbml_mime_type_alias - path = destination_path('config/initializers/mime_types.rb') - content = "Mime::Type.register_alias 'text/html', :fbml" - unless File.read(path).match(Regexp.escape(content)) - logger.register "text/html, :fbml" - File.open(path, 'a') { |file| file.write(content) } - end - end -end -class Rails::Generator::Commands::Destroy - def register_fbml_mime_type_alias - # do nothing - end -end - -class FacebookScaffoldGenerator < Rails::Generator::NamedBase - default_options :skip_timestamps => false, :skip_migration => false - - attr_reader :controller_name, - :controller_class_path, - :controller_file_path, - :controller_class_nesting, - :controller_class_nesting_depth, - :controller_class_name, - :controller_underscore_name, - :controller_singular_name, - :controller_plural_name - alias_method :controller_file_name, :controller_underscore_name - alias_method :controller_table_name, :controller_plural_name - - def initialize(runtime_args, runtime_options = {}) - super - - @controller_name = @name.pluralize - - base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@controller_name) - @controller_class_name_without_nesting, @controller_underscore_name, @controller_plural_name = inflect_names(base_name) - @controller_singular_name=base_name.singularize - if @controller_class_nesting.empty? - @controller_class_name = @controller_class_name_without_nesting - else - @controller_class_name = "#{@controller_class_nesting}::#{@controller_class_name_without_nesting}" - end - end - - def manifest - record do |m| - # Check for class naming collisions. - m.class_collisions(controller_class_path, "#{controller_class_name}Controller", "#{controller_class_name}Helper") - m.class_collisions(class_path, "#{class_name}") - - # Controller, helper, views, test and stylesheets directories. - m.directory(File.join('app/models', class_path)) - m.directory(File.join('app/controllers', controller_class_path)) - m.directory(File.join('app/helpers', controller_class_path)) - m.directory(File.join('app/views', controller_class_path, controller_file_name)) - m.directory(File.join('app/views/layouts', controller_class_path)) - m.directory(File.join('test/functional', controller_class_path)) - m.directory(File.join('test/unit', class_path)) - m.directory(File.join('public/stylesheets', class_path)) - - for action in scaffold_views - m.template( - "view_#{action}.html.erb", - File.join('app/views', controller_class_path, controller_file_name, "#{action}.html.erb") - ) - m.template( - "view_#{action}.fbml.erb", - File.join('app/views', controller_class_path, controller_file_name, "#{action}.fbml.erb") - ) - end - - # Layout and stylesheet. - m.template('layout.html.erb', File.join('app/views/layouts', controller_class_path, "#{controller_file_name}.html.erb")) - m.template('layout.fbml.erb', File.join('app/views/layouts', controller_class_path, "#{controller_file_name}.fbml.erb")) - m.template('style.css', 'public/stylesheets/scaffold.css') - m.template('facebook_style.css', 'public/stylesheets/facebook_scaffold.css') - - m.template( - 'controller.rb', File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb") - ) - - m.template('functional_test.rb', File.join('test/functional', controller_class_path, "#{controller_file_name}_controller_test.rb")) - m.template('helper.rb', File.join('app/helpers', controller_class_path, "#{controller_file_name}_helper.rb")) - - m.route_resources controller_file_name - - m.dependency 'model', [name] + @args, :collision => :skip - - m.register_fbml_mime_type_alias - end - end - - protected - # Override with your own usage banner. - def banner - "Usage: #{$0} scaffold ModelName [field:type, field:type]" - end - - def add_options!(opt) - opt.separator '' - opt.separator 'Options:' - opt.on("--skip-timestamps", - "Don't add timestamps to the migration file for this model") { |v| options[:skip_timestamps] = v } - opt.on("--skip-migration", - "Don't generate a migration file for this model") { |v| options[:skip_migration] = v } - end - - def scaffold_views - %w[ index show new edit ] - end - - def model_name - class_name.demodulize - end - -end diff --git a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/controller.rb b/vendor/plugins/facebooker/generators/facebook_scaffold/templates/controller.rb deleted file mode 100644 index 2c12479a6..000000000 --- a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/controller.rb +++ /dev/null @@ -1,93 +0,0 @@ -class <%= controller_class_name %>Controller < ApplicationController - # GET /<%= table_name %> - # GET /<%= table_name %>.xml - def index - @<%= table_name %> = <%= class_name %>.find(:all) - - respond_to do |format| - format.html # index.html.erb - format.fbml # index.fbml.erb - format.xml { render :xml => @<%= table_name %> } - end - end - - # GET /<%= table_name %>/1 - # GET /<%= table_name %>/1.xml - def show - @<%= file_name %> = <%= class_name %>.find(params[:id]) - - respond_to do |format| - format.html # show.html.erb - format.fbml # show.fbml.erb - format.xml { render :xml => @<%= file_name %> } - end - end - - # GET /<%= table_name %>/new - # GET /<%= table_name %>/new.xml - def new - @<%= file_name %> = <%= class_name %>.new - - respond_to do |format| - format.html # new.html.erb - format.fbml # new.fbml.erb - format.xml { render :xml => @<%= file_name %> } - end - end - - # GET /<%= table_name %>/1/edit - def edit - @<%= file_name %> = <%= class_name %>.find(params[:id]) - end - - # POST /<%= table_name %> - # POST /<%= table_name %>.xml - def create - @<%= file_name %> = <%= class_name %>.new(params[:<%= file_name %>]) - - respond_to do |format| - if @<%= file_name %>.save - flash[:notice] = '<%= class_name %> was successfully created.' - format.html { redirect_to(@<%= file_name %>) } - format.fbml { redirect_to(@<%= file_name %>) } - format.xml { render :xml => @<%= file_name %>, :status => :created, :location => @<%= file_name %> } - else - format.html { render :action => "new" } - format.fbml { render :action => "new" } - format.xml { render :xml => @<%= file_name %>.errors, :status => :unprocessable_entity } - end - end - end - - # PUT /<%= table_name %>/1 - # PUT /<%= table_name %>/1.xml - def update - @<%= file_name %> = <%= class_name %>.find(params[:id]) - - respond_to do |format| - if @<%= file_name %>.update_attributes(params[:<%= file_name %>]) - flash[:notice] = '<%= class_name %> was successfully updated.' - format.html { redirect_to(@<%= file_name %>) } - format.fbml { redirect_to(@<%= file_name %>) } - format.xml { head :ok } - else - format.html { render :action => "edit" } - format.fbml { render :action => "edit" } - format.xml { render :xml => @<%= file_name %>.errors, :status => :unprocessable_entity } - end - end - end - - # DELETE /<%= table_name %>/1 - # DELETE /<%= table_name %>/1.xml - def destroy - @<%= file_name %> = <%= class_name %>.find(params[:id]) - @<%= file_name %>.destroy - - respond_to do |format| - format.html { redirect_to(<%= table_name %>_url) } - format.fbml { redirect_to(<%= table_name %>_url) } - format.xml { head :ok } - end - end -end diff --git a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/facebook_style.css b/vendor/plugins/facebooker/generators/facebook_scaffold/templates/facebook_style.css deleted file mode 100644 index 60d58aeff..000000000 --- a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/facebook_style.css +++ /dev/null @@ -1,2579 +0,0 @@ -// Taken from http://wiki.developers.facebook.com/index.php/CSS_Tips_and_Tricks. -/* ------------------------------------------------------------------------ - Facebook - ------------------------------------------------------------------------ */ - -body { - background: #fff; - font-family: "lucida grande", tahoma, verdana, arial, sans-serif; - font-size: 11px; - margin: 0px; - padding: 0px; - text-align: left; } - -h1, h2, h3, h4, h5 { - font-size: 13px; - color: #333; - margin: 0px; - padding: 0px; } - -h1 { - font-size: 14px; } - -h4, h5 { - font-size: 11px; } - -p { - font-family: "lucida grande", tahoma, verdana, arial, sans-serif; - font-size: 11px; - text-align: left; } - -a { - color: #3b5998; - text-decoration: none; } - -a:hover { - text-decoration: underline; } - -img { - border: 0px; } - -select { - border:1px solid #BDC7D8; - font-family:"lucida grande",tahoma,verdana,arial,sans-serif; - font-size:11px; - padding:2px; -} - -td, -td.label { - font-size: 11px; - text-align: left; } - -.wbr { display: block; float:left; } - -.aural { - width: 0; height: 0; - display: block; - margin-left: -5000px; - overflow: hidden; -} - -/* --------------------------| GLOBAL CLASSES |-------------------------- */ - -/* This is FBML transitional code for backwards compatibility, sorry for the mess */ - -.standard_message { - padding:10px; -} - -.standard_message.no_padding { - padding:0; -} - -.standard_message.shorten { - padding-bottom:0; -} - -.standard_message.has_padding { - padding:10px; -} - -.standard_message #error, -.standard_message .status, -.standard_message .explanation_note { - margin:0; -} - -#error, -.status, -.explanation_note { - margin: 0 10px 10px 10px; - padding: 10px; - border: solid 1px #dd3c10; - background: #ffebe8; -} - -.created { - margin: 0 20px 20px; -} - -#error p, -.status p, -.explanation_note p { - margin: 4px 0px 0px; - line-height:14px; - font-weight:normal; -} - -#error a, -.status a, -.explanation_note a { - text-decoration: none; - color: #dd3c10; -} - -.status { - border-color:#e2c822; - background: #fff9d7; -} - -.status a { - color: #3b5998; - text-decoration: none; -} - -.page_top_notice { - color: #444; - border-bottom: solid 1px #e2c822; - padding: 5px 20px 4px; - background: #fff9d7; -} - -.explanation_note { - border-color:#bdc7d8; - background:#fff; -} - -.explanation_note h1 { - font-size:11px; -} - -.pipe { - color: gray; - padding: 0px 3px; } - -.column { - float: left; } - -.center { - margin-left:auto; - margin-right:auto; -} - - -.editor_title { - background: #f7f7f7; - padding-bottom: 5px; -} - -.standard_title { - padding-bottom: 5px; -} - -.page_title { - border-bottom: 1px solid #ccc; - padding: 10px 10px 5px; - margin: 0px 10px; -} - -.standard_title .page_title { - border-bottom: none; -} - -.needs_translated { /* pgh 4ever */ - background: #85b84d; -} - -/* ----------------------------| PAGE FRAME |---------------------------- */ - -#book { - padding: 0px; - margin: 0px auto; - text-align: left; - width: 799px; } - -#sidebar { - padding: 0px 0px 0px; - font-size: 13px; /* Needed for correct text-size handling. Ask Aaron */ - width: 150px; - float: left; } - - #sidebar h3 { - margin: 0px; - font-size: 11px; - float: left; - text-align: left; - color: #333; - border: none; - background: none; - padding: 0px 0px 3px; } - - #sidebar h3 a { - color: #333; } - - #sidebar h3 a:hover { - color: #3b5998; - text-decoration: underline; } - - #sidebar a.go_home { - display: block; - background-color: #3b5998; - background-position: top left; - background-repeat: no-repeat; - height: 26px; - width: 151px; - position: absolute; - z-index: 3; - margin: 0; - padding: 29px 0px 0px; - font-size: 13px; - } - - #sidebar a.go_home:hover { - background-color: #3b5998; - background-position: bottom left; - background-repeat: no-repeat; - } - - #sidebar a.go_home h1 { - font-size: 13px; } - - #sidebar_content { - font-size: 11px; - margin: 2em 0 10px; - padding: 32px 0px 0px; - color: #000; - border-bottom: solid 1px #ddd; - background: #f7f7f7; - } - - #sidebar_signup_content { - background:#F7F7F7 none repeat scroll 0%; - border-bottom:1px solid #DDDDDD; - border-top:1px solid #DDDDDD; - font-size:11px; - line-height:28px; - margin:10px 0pt; - padding: 0px 0 10px 10px; - } - - #squicklogin { - padding: 8px 10px 10px 9px; } - - #squicklogin label { - color: #333; - display: block; - margin-bottom: 2px; - } - #squicklogin .inputtext { - margin: 0px 0px 5px 0px; - width: 123px; - } - #squicklogin .inputsubmit { - margin: 5px 0px 2px 0px; - } - #squicklogin label.persistent { - color: #333; - display: block; - font-size: 11px; - font-weight: normal; - line-height: 16px; - padding: 1px 0px 3px 0px; - } - #squicklogin label.persistent .inputcheckbox, - #loginform label.persistent .inputcheckbox { - margin: 0px 4px 0px 0px; - } - #squicklogin p { - line-height: 18px; - padding: 0px; - } - - #squicklogin #doquicklogin{ - margin:5px 0 8px 0; - } - - /* GLOBAL QUICK SEARCH */ - - /*fixes whitespace above input box in ie6*/ - #qsearchform #q { - margin: 0px; - } - - #qsearch { - padding: 8px 4px 2px 10px; - } - - #qsearch div.findfriends { - float: right; - line-height: 13px; - margin: 0px; - padding: 4px 5px 0px 0px; - } - - #qsearch a.findfriends { - line-height: 13px; - margin: 0px; - padding: 1px 0px 0px 0px; - } - - #qsearch .inputsearch { - display: block; - margin-top: 0px; - margin-right: 20px; - width: 123px; - } - - #qsearch .inputsearch.typeahead_found { - background-color: white; - } - - #qsearch.hourglass #q, - #qsearch.hourglass #nm { /* name is used for open search for gray accounts */ - float: none; - border-color: #95a5c6; - padding-left: 17px; - margin-right: 0px; - width: 108px; - } - - #qsearch h3.qsearch_header { - margin: 0px; - padding: 3px 0px 4px 3px; - } - -/* NEW GLOBAL SEARCH INPUT */ - -#qsearch_alternate { - clear: both; - margin-bottom: -4px; - padding: 0px; -} - -#qsearch_field { - float: left; - padding: 0px 0px 1px 0px; -} - -#qsearch_field .inputtext#q { - border: 1px solid #94a4c6; - border-right: 0px; - display: block; - margin: 0px; - padding: 3px; - width: 102px; -} - -#qsearch_submit { - float: left; - width: 21px; -} - -#qsearch_submit a.qsearch_button { - background: #6d84b4 url(/images/search_input_gloss.gif) top center repeat-x; - border-bottom: 1px solid #506798; - border-left: 1px solid #617aad; - border-right: 1px solid #506798; - border-top: 1px solid #5973a9; - display: block; - font-size: 11px; - margin: 0px; - padding: 0px; - text-decoration: none; - width: 19px; -} -#qsearch_submit a.qsearch_button span.qsearch_glass { - background: url(/images/search_glass.gif) 0% 70% no-repeat; - display: block; - margin: 0px; - padding: 3px 0px; -} -#qsearch_submit a.qsearch_button:active { - background: #5b75ab; - border: 1px solid #3b5998; -} -#qsearch_submit a.qsearch_button:active span.qsearch_glass { - background: url(/images/search_glass.gif) 100% 70% no-repeat; -} - - -/* SIDEBAR APPLICATION MENU */ - -#sidebar_content .separator { - border-top: solid 1px #CCCCCC; - border-bottom: solid 1px white; - margin: 0px -10px 4px -10px; } - -#sidebar .app_list { - font-size: 11px; - padding: 8px 10px 0px; } - - #sidebar .app_list h2 { - float: left; - padding-left: 2px; - } - - #sidebar .app_list h3.app_list_header { - padding-left: 3px; - } - - #sidebar .app_list_outside { - padding: 2px 14px 5px; - } - - #sidebar .browse_apps_link { - padding-top: 5px; - } - - #sidebar .edit_apps { - float: right; - text-align: right; - } - - #sidebar #app_list, #app_non_nav_list { - clear: both; - list-style: none; - margin: 0; - padding: 2px 3px 0px; } - - #sidebar #app_non_nav_list { - padding-bottom: 5px; } - - #sidebar .divider_bar { - margin-left: 22px; - border-bottom: solid 1px #d6d6d6; - line-height: 0px; - margin-bottom: 3px; - } - - #sidebar .app_list .list_item { - margin: 0px 0px 0px -35px; - padding: 0px 0px 0px 35px; } - - #sidebar .app_list_outside h2 { - margin-left: -5px; - padding: 3px 0px; - } - - #sidebar .app_list .list_item .container { - margin: 0px; - padding: 0px 0px 0px; } - - #sidebar .app_list li a { - margin: 0px; - padding: 0px; } - - #sidebar .app_list .photos_app_link { - background: url(/images/icons/photo.gif) 0px 2px no-repeat; } - #sidebar .app_list .video_app_link { - background: url(/images/icons/video.gif) 0px 2px no-repeat; } - #sidebar .app_list .notes_app_link { - background: url(/images/icons/note.gif) 0px 2px no-repeat; } - #sidebar .app_list .groups_app_link { - background: url(/images/icons/group.gif) 0px 3px no-repeat; } - #sidebar .app_list .events_app_link { - background: url(/images/icons/event.gif) 0px 2px no-repeat; } - #sidebar .app_list .book_app_link { - background: url(/images/icons/bookreview.gif) 0px 2px no-repeat; } - #sidebar .app_list .posted_app_link { - background: url(/images/icons/post.gif) 0px 2px no-repeat; } - - #sidebar .app_list .icon { - float: left; - margin: 0px; - padding: 0px; - height: 16px; - width: 16px; } - - #sidebar .app_list .link_title { - background: transparent no-repeat 0px 1px; - cursor: pointer; - display: block; - line-height: 16px; - padding: 1px 0px 3px 0px; - margin: 0px 0px 0px 0px; - overflow: hidden; } - - #sidebar .app_list .container_link { - padding-left: 22px; - } - - #sidebar .app_list .container_icon { - position: absolute; - } - - #sidebar .app_list .highlight_link { - font-weight: normal; - } - -#sidebar .more_section { - margin-top: 2px; - display: block; -} - -#sidebar .more_section:hover { - background: #ececec; -} - -#sidebar .expand_link { - text-align: left; - font-size: 9px; - font-weight: bold; - background: #ccc; - display: block; - padding: 2px 10px 2px 22px; - width: 100px; -} - -#sidebar .more_apps { - background: url(/images/down_arrow_grey_small.gif) no-repeat 10px 2px; -} - -#sidebar .less_apps { - background: url(/images/up_arrow_grey_small.gif) no-repeat 10px 2px; -} - -#sidebar .more_apps a, #sidebar .less_apps a, .more_section a { - color: #666666; -} - -#publicity { - height: 61px; - padding: 3px 3px 7px; } - - #publicity h5 { - border: none; - margin: 0px; - padding: 0px; - color: #333; - line-height: 19px; } - - #publicity h5.new { - line-height: 17px; - padding: 0px 0px 2px 36px; - background: url(/images/publicity_new.gif) no-repeat left center; } - - #publicity h5.tip { - line-height: 17px; - padding: 0px 0px 2px 29px; - background: url(/images/publicity_tip.gif) no-repeat left center; } - - #publicity p { - margin: 0px; - color: #333; - line-height: 14px; } - -#widebar { - width: 649px; - float: left; } - -#widebar_shadow { - width: 1px; - float: left; - overflow: hidden; } - -#navigator { - z-index: 20; - position: relative; - margin: 0px 1px 0px 0px; - padding: 9px 0px 4px 0px; - height: 2em; - background: url(/images/navigator_bg.gif) no-repeat left bottom; - line-height: 2em; } - - #navigator a { - text-decoration: none; - color: white; } - - #navigator ul.main_set { - font-size: 13px; - float: left; - display: block; - list-style: none; - margin: 0px; - padding: 0px; } - - #navigator .main_set li { - float: left; - display: block; - margin: 0px 10px 0px 0px; - font-weight: bold; } - - #navigator .main_set li a { - white-space: nowrap; - padding: 3px 5px; } - #navigator .main_set li a.active, - #navigator .main_set li a:hover { - background: #5c75aa; } - - #navigator .main_set li a.edit_link { - color: #d8dfea; - font-weight: normal; } - #navigator .main_set li a.edit_link:hover { - text-decoration: underline; - background: transparent; } - - #navigator ul.secondary_set { - float: right; - display: block; - list-style: none; - margin: 0px; - padding: 0px; - padding-right: 17px; - font-size: 11px; } - - #navigator .secondary_set li { - float: left; - display: block; - margin: 0px 10px 0px 0px; } - #navigator .secondary_set li a { - color: #c3cddf; } - #navigator .secondary_set li a:hover { - color: white; - text-decoration: underline; } - -/* Shortened Navigation for certain languages */ - -#navigator.shortened .main_set li { - margin: 0 0 0 4px; -} - -#navigator.shortened .main_set .navigator_menu li { - margin: 0; -} - -#navigator.shortened .main_set li.main_set_first { - margin: 0 0 0 1px; -} - -#navigator.shortened ul.secondary_set { - padding: 1px 15px 0 0; -} - -#navigator.shortened .secondary_set li { - margin: 0 8px 0 0; -} - -#navigator.shortened .main_set li a.edit_link { - font-size: 11px; -} - - -#book #content_shadow { - margin: 2px 0px 0px 1px; - background: transparent url(/images/shadow_gray.gif) repeat-y top right; - padding: 0px; - border-bottom: solid 1px #ddd; } - -#content { - margin: -2px 1px 0px -1px; - border-top: none; - border-left: solid 1px #b7b7b7; - border-right: solid 1px #b7b7b7; - border-bottom: solid 1px #3b5998; - font-size: 11px; } - -#ad_1 { - clear:both; } - -#pagefooter { - clear: both; - padding: 0px; - margin: 0px; - height: 50px; - line-height: 16px; } - -#pagefooter ul { - display: block; - list-style: none; - float: right; - margin: 0px; - padding: 8px 2px 4px;} - -#pagefooter li { - float: left; - padding: 0px;} - -#pagefooter .footer_links a { - padding: 2px 5px;} - -#pagefooter .f_links { - width: 400px; - float: right; -} - -#pagefooter .copyright_and_location { - float: left; - padding: 8px 8px 0px 10px; - margin: 0px; - width: 225px; -} - -#pagefooter .copyright_and_location #locale { - float: left; - margin-top: -1px; -} - -#pagefooter .copyright { - color: #777; - float: left; - margin-right: 10px; - padding-bottom: 5px; - } - - #pagefooter .copyright .brand { - padding: 0px 0px 2px 22px; - background: url(/images/icons/hidden.gif) no-repeat; } - -#subheader { - border-left: solid 1px #b7b7b7; - border-right: solid 1px #b7b7b7; } - - -#navigator .main_set li .with_arrow { - margin-top: 0px; -} - -.global_menu_arrow_active:focus { - outline: 0px; -} - -#navigator .main_set li a.global_menu_arrow, -#navigator .main_set li a.global_menu_arrow_active { - height: 19px; - width: 17px; - margin: 0px 0px -10px -1px; - padding: 3px 0px 3px; - background: transparent url(/images/global_menu_arrow.gif) no-repeat 0px center; } - - #navigator .main_set li a.global_menu_arrow_active, - #navigator .main_set li a.global_menu_arrow:hover, - #navigator .main_set li a.global_menu_arrow_active:hover { - background: #5c75aa url(/images/global_menu_arrow.gif) no-repeat 0px center; } - - - -/* Navigator Drop Menus ==================== */ - -#navigator .navigator_menu { - margin: -1px; - position: absolute; - z-index: 100; - background: white; - border: solid 1px #3b5998; - } - - #navigator .navigator_menu.friends { - max-width: 250px; - } - - #navigator .navigator_menu.networks { - max-width: 250px; - } - - #navigator .navigator_menu.attachments { - max-width: 250px; - } - - #navigator .navigator_menu ul { - font-size: 11px; - line-height: 1em; - font-weight: normal; - list-style: none; - padding: 5px 0px; - margin: 0px; } - - #navigator .navigator_menu li { - float: none; - cursor: pointer; - font-weight: normal; - padding: 0px; - margin: 0px; } - - #navigator .navigator_menu li.menu_divider { - display: block; - margin: 4px 10px; - font-size: 1px; - line-height: 1px; - cursor: default; - border-bottom: solid 1px #eee; } - - #navigator .navigator_menu li a { - display: block; - color: #3b5998; - border-right: solid 1px white; - border-left: solid 1px white; - padding: 4px 25px 4px 10px; } - - #navigator .navigator_menu a:hover { - text-decoration: none; - background: #3b5998; - border-right: solid 1px #6d84b4; - border-left: solid 1px #6d84b4; - color: white; } - -/* Title Header ==================== */ - -.title_header { - background: white; - padding: 20px 20px 17px; } - - .title_header h2 { - margin: 0px; - font-size: 14px; - padding: 0px 0px 0px 24px; - background: url(/images/icons/hidden.gif) no-repeat 1px 1px; } - - .title_header h2.no_icon { - padding: 0px; - background: none;} - - .title_header.add_border { - border-bottom: solid 1px #ccc; } - .title_header.gray { - background: #f7f7f7; } - .title_header.shorten { - padding-bottom: 0px; } - .title_header.no_padding { - padding: 0px; } - .title_header h4 { - color: #666; - font-size: 11px; - font-weight: normal; - padding: 3px 0 0 24px; } - .title_header h4.no_icon { - padding: 3px 0 0 0; } - - - .login_title_header { - padding: 20px 0px 10px; - margin-bottom: 10px; - border-bottom: solid 1px #ccc;} - - .login_title_header h2 { - margin: 0px; - padding: 0px; - font-size: 14px;} - - -/* Media Header ==================== */ - -.obj_media_header { - background-color: #ffffff; - padding: 0px 22px 5px 20px; } -.media_header { - background-color: #ffffff; - padding: 0px 12px 5px 20px; } - - .obj_media_header .user_info { - float: left; - padding-top: 18px; - width: 425px; } - .media_header .user_info { - float: left; - padding-top: 18px; - width: 545px } - - .obj_media_header h2 { - float: left; - font-size: 14px; - margin: 0px; - padding: 3px 0px 5px 0px; - width: 520px; } - .media_header h2 { - float: left; - font-size: 14px; - margin: 0px; - padding: 3px 0px 5px 0px; - width: 550px; } - - .obj_media_header h2 span, - .media_header h2 span { - font-weight: normal; } - - .obj_media_header .pipe, - .media_header .pipe { - color: gray; - padding: 0px 3px; } - - .obj_media_header .picture { - float: right; - margin-top: 18px; - margin-right: 0px; - position: relative; - z-index: 1; - text-align: right; - overflow: hidden; - height: 50px; - width: 150px; } - .media_header .picture { - float: left; - margin-top: 18px; - margin-right: 10px; - position: relative; - z-index: 1; - overflow: hidden; - height: 50px; - width: 50px; } - - .obj_media_header .picture img { - height: 50px; } - .media_header .picture img { - display: block; } - - .obj_media_header .media_gray_bg { - background: #f7f7f7; - z-index: 0; - border-bottom: solid 1px #cccccc; - margin: -18px -254px 7px -20px; - padding: 18px 114px 0px 20px; - width: 512px; } - .media_header .media_gray_bg { - background: #f7f7f7; - z-index: 0; - border-bottom: solid 1px #cccccc; - margin: -18px -14px 7px -80px; - padding: 18px 14px 0px 80px; - width: 552px; } - -/* Dashboard Header ==================== */ - -.dashboard_header { - padding: 10px 10px 0px; } - - .dashboard_header .dh_links { - padding: 0px 10px 5px; - border-bottom: solid 1px #ccc; } - - .dashboard_header .dh_links .dh_actions { float: left; } - .dashboard_header .dh_links .dh_help { float: right; } - .dashboard_header .dh_links .pipe { padding: 0px 7px; color: #aaa; } - .dashboard_header .dh_links form {display: inline; overflow: hidden; width: 0px;} - - .dashboard_header .dh_links .dh_actions .back_to_link { - background: url(/images/arrow_micro_back.gif) no-repeat 0% 55%; - padding-left: 10px; } - - .dashboard_header .dh_titlebar { - padding: 10px 10px 12px; } - - .dashboard_header .dh_titlebar h2 { - float: left; - font-size: 14px; - padding: 7px 0px 7px 24px; - background-repeat: no-repeat; - background-position: 1px 8px; } - .dashboard_header .dh_titlebar .dh_subtitle { - background-image: url(/images/dashboard_subtitle_arrow.gif); - background-position: 0px 5px; - background-repeat: no-repeat; - color: #000; - float: left; - font-weight: bold; - font-size: 14px; - margin: 7px 0px 0px 5px; - padding: 0px 0px 0px 13px; - } - .dashboard_header .dh_titlebar .dh_search { - float: right; - margin-top: 3px; - height: 30px; - line-height: 30px; } - .dashboard_header .dh_titlebar .dh_search input { - margin: 0px; } - .dashboard_header .dh_titlebar .dh_right { - float: right; - margin-top: 2px; - height: 30px; } - - .dashboard_header .dh_new_media_shell { - float: right; - background: url(/images/new_media_button_active.gif) no-repeat bottom -30px; - margin: 7px 0px 0px 10px; } - - /* NEW MEDIA BUTTON */ - .dh_new_media { - display: block; - float: left; - color: #777; - text-decoration: none; - background: url(/images/new_media_button.gif) no-repeat; } - - .dh_new_media .tr { background: url(/images/new_media_button.gif) no-repeat top right; } - .dh_new_media .bl { background: url(/images/new_media_button.gif) no-repeat bottom left; } - .dh_new_media .br { background: url(/images/new_media_button.gif) no-repeat bottom right; } - .dh_new_media span { - background: url(/images/new_media_button_plus.gif) no-repeat 9px center; - color: #333; - font-size: 11px; - font-weight: bold; - display: block; - padding: 3px 9px 5px 22px; - text-shadow: white 0px 1px 1px; } - - .dh_new_media:hover { - text-decoration: underline; } - .dh_new_media:active, - .dh_new_media:active .tr, - .dh_new_media:active .bl, - .dh_new_media:active .br { - background-image: url(/images/new_media_button_active.gif); } - - -/* Toolbar Buttons ===================== */ - -.dh_toolbar_buttons { - background: url(/images/buttons/toolbar_button_hover.gif) no-repeat bottom left; -} -.dh_toolbar_button { - background-image: url(/images/buttons/toolbar_button_active.gif); - color: #777; - display: block; - float: left; - text-decoration: none; -} -.dh_toolbar_button:hover { - text-decoration: none; -} - -/* LEFT BUTTON */ -.dh_toolbar_button_left { - background: url(/images/buttons/toolbar_button.gif) no-repeat top left; -} -.dh_toolbar_button_left .bottom_left { - background: url(/images/buttons/toolbar_button.gif) no-repeat bottom left; -} -.dh_toolbar_button_left .bottom_right { - background: url(/images/buttons/toolbar_button_border.gif) no-repeat bottom right; -} -.dh_toolbar_button_left:hover, -.dh_toolbar_button_left:hover .bottom_left { - background-image: url(/images/buttons/toolbar_button_hover.gif); -} -.dh_toolbar_button_left:active, -.dh_toolbar_button_left:active .bottom_left { - background-image: url(/images/buttons/toolbar_button_active.gif); -} - -/* CENTER BUTTON */ -.dh_toolbar_button_center { - background: url(/images/buttons/toolbar_button.gif) no-repeat top center; -} -.dh_toolbar_button_center .bottom_left { - background: url(/images/buttons/toolbar_button.gif) no-repeat bottom center; -} -.dh_toolbar_button_center .bottom_right { - background: url(/images/buttons/toolbar_button_border.gif) no-repeat bottom right; -} -.dh_toolbar_button_center:hover, -.dh_toolbar_button_center:hover .bottom_left { - background-image: url(/images/buttons/toolbar_button_hover.gif); -} -.dh_toolbar_button_center:active, -.dh_toolbar_button_center:active .bottom_left { - background-image: url(/images/buttons/toolbar_button_active.gif); -} - -/* RIGHT BUTTON */ -.dh_toolbar_button_right { - background: url(/images/buttons/toolbar_button.gif) no-repeat top center; -} -.dh_toolbar_button_right .top_right { - background: url(/images/buttons/toolbar_button.gif) no-repeat top right; -} -.dh_toolbar_button_right .bottom_left { - background: url(/images/buttons/toolbar_button.gif) no-repeat bottom center; -} -.dh_toolbar_button_right .bottom_right { - background: url(/images/buttons/toolbar_button.gif) no-repeat bottom right; -} -.dh_toolbar_button_right:hover, -.dh_toolbar_button_right:hover .top_right, -.dh_toolbar_button_right:hover .bottom_left, -.dh_toolbar_button_right:hover .bottom_right { - background-image: url(/images/buttons/toolbar_button_hover.gif); -} -.dh_toolbar_button_right:active, -.dh_toolbar_button_right:active .top_right, -.dh_toolbar_button_right:active .bottom_left, -.dh_toolbar_button_right:active .bottom_right { - background-image: url(/images/buttons/toolbar_button_active.gif); -} - - -/* BUTTON ICON & LABEL */ -.dh_toolbar_button span { - background-color: transparent; - background-image: url(/images/buttons/toolbar_button_plus.gif); - background-position: 9px 48%; - background-repeat: no-repeat; - color: #333; - font-size: 11px; - font-weight: bold; - display: block; - text-shadow: white 0px 1px 1px; -} -.dh_toolbar_button_left span { - background-position: 9px 48%; - padding: 3px 9px 5px 22px; -} -.dh_toolbar_button_center span { - background-position: 7px 48%; - padding: 3px 9px 5px 20px; -} -.dh_toolbar_button_right span { - background-position: 7px 48%; - padding: 3px 12px 5px 20px; -} - - -/* DEFAULT PLUS BUTTON */ -.dh_toolbar_button#toolbar_button_plus span { - background-image: url(/images/buttons/toolbar_button_plus.gif); -} -/* RECORD BUTTON */ -.dh_toolbar_button#toolbar_button_record span { - background-image: url(/images/buttons/toolbar_button_record.gif); -} -/* MESSAGE BUTTON */ -.dh_toolbar_button#toolbar_button_message span { - background-image: url(/images/buttons/toolbar_button_message.gif); -} -.dh_toolbar_button_center#toolbar_button_message span, -.dh_toolbar_button_right#toolbar_button_message span { - background-position: 6px 48%; -} - - -/* ----------------------| Empty List Message |---------------------- */ - -.empty_message { - color: #333; - font-size: 13px; - line-height: 17px; - padding: 20px 20px 50px 20px; - text-align: center; - background: #f7f7f7; } - -/* ----------------------| SWEET BLESSED CLEARFIX |---------------------- */ - -.clearfix:after { - content: "."; - display: block; - clear: both; - visibility: hidden; - line-height: 0; - height: 0; } - -.clearfix { - display: inline-block; } - -html[xmlns] .clearfix { - display: block; } - -* html .clearfix { - height: 1%; } - -/* ------------------------------| reset styles | ------------------------- */ - - /* reset things that get inheritted and apply some basic stuff we normally set in facebook.css. - * this doesn't actually prevent people from modifying the style of the related markup if they want to, but it - * does make it less likely that a page with funky css styles will accidentally make something ugly. - * see: multi friend selector - * useful for: anything in fbml that might get hijakced by developers - * example application: at the topmost parent, then style the children and they should be safe - * won't someone think of the children? */ - -.resetstyles { - padding: 0px; - border: 0px; - margin: 0px; - overflow: visible; - background: none; - border-spacing: 0; - color: #000; - cursor: auto; - direction: ltr; - font-family: "lucida grande", tahoma, verdana, arial, sans-serif; - font-size: 11px; - font-style: normal; - font-variant: normal; - font-weight: normal; - letter-spacing: normal; - text-align: left; - text-decoration: none; - text-indent: 0; - text-transform: none; - visibility: visible; - white-space: normal; - word-spacing: normal; -} - -/* ------------------------------| FORMS |------------------------------- */ - -form { - margin: 0px; - padding: 0px; } - -/* DO NOT REMOVE cursor: pointer without talking to rgrover */ -/* This is required for correct label behavior in Safari 2 */ -label { - cursor: pointer; - color: #666666; - font-weight: bold; } - -label input { - font-weight: normal; } - -.formtable { - margin: 0px auto; } - -.formtable { - margin: 0px; } - -.formtable td { - border: none; - margin: 0px; - padding: 3px 1px; } - -.formtable td.label { - color: #666666; - font-weight: bold; - padding-right: 10px; } - -.formtable tr.tallrow { - vertical-align: top; } - -.formbuttons { - text-align: center; - margin: 10px 10px; } - -.formbuttons .inputsubmit, .formbuttons .inputbutton { - margin: 2px 4px; } - -.formbuttons .securetoggle { - padding: 9px 0px 0px 0px; } - - -/* FORM INPUTS */ - -.inputtext, -.inputpassword { - border: 1px solid #bdc7d8; - font-family: "lucida grande", tahoma, verdana, arial, sans-serif; - font-size: 11px; - padding: 3px; } - -textarea { - border: 1px solid #bdc7d8; - padding: 3px; - font-size: 11px; - font-family: "lucida grande", tahoma, verdana, arial, sans-serif; } - -.inputbutton, -.inputsubmit { - padding: 2px 15px 3px 15px; - border-style: solid; - border-top-width: 1px; - border-left-width: 1px; - border-bottom-width: 1px; - border-right-width: 1px; - border-top-color: #D9DFEA; - border-left-color: #D9DFEA; - border-bottom-color: #0e1f5b; - border-right-color: #0e1f5b; - background-color: #3b5998; - color: #FFFFFF; - font-size: 11px; - font-family: "lucida grande", tahoma, verdana, arial, sans-serif; - text-align: center; } - -.inputaux { - background: #f0f0f0; - color: #000; - border-top-color: #e7e7e7; - border-right-color: #666; - border-bottom-color: #666; - border-left-color: #e7e7e7; } - -.inputcheckbox { - border: 0px; } - -.inputsearch { - background: white url(/images/magglass.png) no-repeat 3px 4px; - padding-left: 17px; } - -.transparent_png_ie6 { - display: none; -} - -#inline_flyer { - background-color: #efeded; - color: #909090; - border-bottom: 1px solid #d8dfea; - padding: 4px 10px 2px 10px; -} - -#inline_flyer .external_link a { - text-decoration: none; - border-bottom: 1px dotted #3b5998; -} - -#inline_flyer .external_link a:hover { - text-decoration: none; - border-bottom: 1px dotted #efeded; -} - -#inline_flyer #abbreviated_body { - float: left; - width: 430px; -} - -#inline_flyer #expander { - float: right; -} - -#inline_flyer_content { - background-color: #efeded; - border-bottom: 1px solid #d8dfea; - padding: 4px 10px; -} - -/* ----------------------------| ERROR PAGE |---------------------------- */ - -.error_page #content { - padding: 20px; } - -.error_page #error { - margin: 0px; } - - -/* -------------------------------| TABS |------------------------------- */ - -#tabs { - text-align: center; - padding: 4px 0px; - margin: 10px 20px 0px; - border-bottom: solid 1px #3B5998; } - -#tabs div { - display: inline; - padding: 0px; - margin: 0px; } -* html #tabs div { - margin: 0 3px; } - - -#tabs a { - margin: 0px; - padding: 4px; } - -#tabs .activetab a { - color: white; - background: #3B5998; } - -#tabs .activetab a:hover { - text-decoration: none; } - -#tabs .inactivetab a:hover { - background: #D8DFEA; - text-decoration: none; } - -#tabs .disabled { - color: #c0c0c0; - margin: 0px; - padding: 4px; - text-decoration: none; } - - -/*--| Toggle Tabs |--------------------------------------------------------------| -==================================================================================*/ - -.tabs { - padding:3px 0; - border-bottom:1px solid #898989; -} - -.tabs.top { - background: #f7f7f7; } - -.tabs .left_tabs { - padding-left:10px; - float:left; -} - -.tabs .right_tabs { - padding-right:10px; - float:right; -} - -.tabs .back_links { - padding-right: 20px; - float:right; -} - -.toggle_tabs { - margin:0; - padding:0; - list-style:none; - text-align:center; - display:inline; -} - -.toggle_tabs li { - display:inline; - padding: 2px 0px 3px; - background: #f1f1f1 url(/images/components/toggle_tab_gloss.gif) top left repeat-x; -} - -.toggle_tabs li a { - border:1px solid #898989; - border-left:0; - color:#333; - font-weight:bold; - padding:2px 8px 3px 9px; -} -.toggle_tabs li a small { - font-size: 11px; - font-weight: normal; -} - -.toggle_tabs li a:focus { - outline: 0px; -} - -.toggle_tabs li.first a { - border:1px solid #898989; -} - -/* SELECTED TAB */ - -.toggle_tabs li a.selected { - margin-left:-1px; - background:#6d84b4; - border:1px solid #3b5998; - border-left:1px solid #5973a9; - border-right:1px solid #5973a9; - color:#fff; -} - -.toggle_tabs li.last a.selected { - margin-left:-1px; - border-left:1px solid #5973a9; - border-right:1px solid #36538f; -} - -.toggle_tabs li.first a.selected { - margin:0; - border-left:1px solid #36538f; - border-right:1px solid #5973a9; -} - -.toggle_tabs li.first.last a.selected { - border:1px solid #36538f; -} - -.toggle_tabs li a.selected:hover { - text-decoration:none; -} - -/* DISABLED TAB */ - -.toggle_tabs li a.disabled { - color: #999; - cursor: default; -} - -.toggle_tabs li a.disabled:hover { - text-decoration:none; -} - - -/* -----------------------------| PAGER PRO |---------------------------- */ - -.pagerpro { - float: right; - list-style: none; - margin: 0; - padding: 0; } - -.pagerpro li { - display: inline; - float: left; } - -.pagerpro a { - display: block; - padding: 3px; - padding-bottom: 2px; } - -.pagerpro a:hover { - background: #3B5998; - border-color: #D8DFEA; - border-bottom: 1px solid #3B5998; - color: white; - text-decoration: none; } - -.pagerpro .current a, -.pagerpro .current a:hover { - background: transparent; - border-color: #3B5998; - border-bottom: 2px solid #3B5998; - color: #3B5998; - font-weight: bold; - padding-left: 2px; - padding-right: 2px; } - - -/* -------------------------------| BARS |------------------------------- */ - -.summary_bar { - border-bottom: 1px solid #D8DFEA; - clear: both; - padding: 11px 20px 0px; - color: black; - font-weight: normal; - line-height: normal; } - - .summary_bar h1, .summary_bar h2, .summary_bar h3, .summary_bar h4, .summary_bar h5 { - font-weight: normal; } - -.summary_bar .summary { - color: #333; - float: left; - padding-top: 3px; - padding-bottom: 4px; } - -.summary_bar .summary a { - font-weight: normal; } - -.tab_bar { - padding: 3px 20px 0px 10px; - border-bottom: solid 1px #3B5998; } - -.tab_bar #tabs { - margin: 0px 10px; - display: inline; - float: left; - border-bottom: none; -} - -.tab_bar .back_links { - padding: 4px 0px; - float: right; } - -.footer_bar { - border-top: 1px solid #D8DFEA; - padding: 0 20px; } - -.footer_bar .post_editor { - padding-bottom: 13px; - padding-top: 13px; } - -.footer_bar .pagerpro a { - border-top: 2px solid white; - padding-top: 1px; - padding-bottom: 0px; } - -.footer_bar .pagerpro a:hover { - border-bottom: 2px solid #3B5998; - border-top: 2px solid #3B5998; } - -.footer_bar .pagerpro .current a, -.footer_bar .pagerpro .current a:hover { - border-bottom-color: white; - border-top: 2px solid #3B5998; } - -.action_bar { - padding: 6px 20px 5px; - background: #f7f7f7; - border-top: solid 1px #b7b7b7; - border-bottom: solid 1px #ccc; } - - .action_bar label { color: #444; } - -/* ---------------------------| SHARE | HIDE |--------------------------- */ - -.share_and_hide { - font-size: 9px; } - - .s_and_h_big { - font-size: 11px; } - - .share_and_hide a { - padding: 0px 14px 1px 4px; - display: block; - float: left; - background: white url(/images/share_icon_small.gif) repeat-y right center; - border: solid 1px #7f93bc; } - - .s_and_h_big a { - background: white url(/images/share_icon.gif) repeat-y right center; - padding: 1px 18px 2px 4px; - } - - .share_and_hide a.save { - background: #eceff5; - border-left: none; } - - .share_and_hide a.with_share { - border: solid 1px #adbad4; - border-left: none; } - - .share_and_hide a:hover { - color: #fff; - border-color: #3B5998; - text-decoration: none; - background: #3B5998 url(/images/share_icon_small_hover.gif) repeat-y right center; } - - .s_and_h_big a:hover { - background: #3B5998 url(/images/share_icon_hover.gif) repeat-y right center; } - - .share_and_hide a.x_to_hide { - border: none; - width: 3px; - padding: 1px 5px 2px; - margin-left: 3px; - background: transparent url(/images/x_to_hide.gif) no-repeat center center; } - - .s_and_h_big a.x_to_hide { - padding: 2px 5px 3px; } - - .share_and_hide a.x_to_hide:hover { - background: #3B5998 url(/images/x_to_hide_hover.gif) no-repeat center center; } - - -.quail { - color: #3B5998; - float: left; - padding: 3px 0px; } - -.quail a:hover { - cursor: default; - text-decoration: none; } - - -/* --------------------------| SQUARE BULLETS |-------------------------- */ - -ul.square_bullets { - list-style: square; - padding-left: 20px; - color: #3B5998; } - -ul.square_bullets li { - padding: 1px 0px; } - -ul.square_bullets li span { - color: black; } - - -/* ----------------------------| MISCELLANY |---------------------------- */ - -/* safari won't do word break in a table without a nested div with a fixed - * width. firefox does not understand break-word at all yet, though it is - * standard css3. */ -.datawrap { word-wrap: break-word; } - -/* used in conjunction wit the wbr tag, this will cause line breaks - * in safari wherever the wbr tag would cause breaks in FF and IE. */ -.word_break { - display:block; - float:left; - margin-left: -10px; - padding: 0px; - } - -/* adjustImage goodness */ -.img_loading { - position: absolute; - left: -100000px; - top: -100000px; -} - -.two_column .right, -.two_column .left { - float: left; } - -.no_padding { - padding: 0px; } - -.see_all { - text-align: right; } - -.standard_status_element { - visibility: hidden; -} -.standard_status_element.async_saving { - visibility: visible; -} - - -/* -----------------------| WELCOME BUTTONS |---------------------------- */ - -.welcome_buttons { - display:block; - padding:8px 0 0 0; } - -.welcome_buttons a { - float:left; - display:block; - width:170px; - /* width:200px; */ - padding:5px; - margin-bottom:5px; - color:#666; - background-color:#f7f7f7; - margin-right:12px; - /* margin-right:10px; */ - border-top:1px solid #ccc; - border-bottom:1px solid #ccc; } - -html #content .welcome_buttons a:hover { - text-decoration:none; - color:#333; - background:#d8dfea; - border-top:1px solid #3b5998; - border-bottom:1px solid #3b5998; } - -.welcome_buttons a h4 { - margin:0; - padding:0 0 0 14px; - font-size:13px; - color:#333; - background:transparent url(/images/sitetour/tour_arrow.gif) no-repeat 3px 2px; - border: 0px; } - -.welcome_buttons a:hover h4 { - color:#000; } - -.welcome_buttons a p { - font-size:11px; - margin:0; - padding:3px 0 2px 14px; } - -.welcome_buttons a p span { - display:block; } - -.under_login_tour { - padding:3px 0 0 0; } - -.under_login_tour a { - width:112px; - padding:3px 4px 4px 4px; - margin:8px 0 0 0; } - -html #book .under_login_tour a:hover { - text-decoration:none; - color:#333; - background:#d8dfea; - border-top:1px solid #3b5998; - border-bottom:1px solid #3b5998; } - -.under_login_tour a h4 { - font-size:11px; - padding:0 0 0 9px; - background:transparent url(/images/sitetour/tour_arrow_micro.gif) no-repeat 2px 4px; } - -.under_login_tour a p { - padding: 3px 0 0 9px; } - -.new_feature_tag { - padding: 3px 0 0 6px; background: - url(/images/new_feature_tag.gif) no-repeat; - height: 14px; - float: left; - margin-right: 6px; - width: 32px; } - -.new_feature_tag div { - color: #fff; } - -.new_feature_tag_title { - padding-top: 1px; - margin-bottom: 3px; - font-weight: bold; } - -/* -----------------------| LINK_BUTTON STYLE |---------------------------- */ - -a.link_btn_style { - color: #fff; - font-size: 13px; - outline: none; - display:block; - height:23px; - background-repeat: no-repeat; - background-position: -1000px -1000px; -} - -html[xmlns] a.link_btn_style { - display:table; -} - -a.link_btn_style div, -a.link_btn_style span { - cursor:pointer; - float:left; - line-height:15px; - padding: 0px 0px 2px 0px; - background-repeat: no-repeat; - background-position: bottom left; -} - -a.link_btn_style div div { - padding:0px 2px 0px 0px; - background-position: top right; -} - -a.link_btn_style div div div { - padding:0px; - background-position: top left; -} - -a.link_btn_style span.btn_text { - display:inline; - margin:2px -2px -2px 2px; - padding:2px 19px 5px 17px; - background-position: bottom right; -} - -* html a.link_btn_style span { - position:relative; -} - -/* ---------------------------| SIGN UP LINK BUTTON |--------------------------- */ - -/* preload */ -a.reg_btn_style { - background-image: url(/images/welcome/btn_register_signup_active_bg.gif); -} - -a.reg_btn_style span.btn_text { - color:#fff; - font-weight:normal; -} - -a.reg_btn_style div, -a.reg_btn_style span { - background-image: url(/images/welcome/btn_register_signup_bg.gif); -} - -a.reg_btn_style:active div, -a.reg_btn_style:active span { - background-image: url(/images/welcome/btn_register_signup_active_bg.gif); -} - - -/* ---------------------------| APP SWITCHER |--------------------------- */ - -.app_switcher { - float: right; - z-index: 4; - width: 130px; - position: relative; } - - .app_switcher .app_switcher_unselected { - position: relative; - float: left; } - - .app_switcher .app_switcher_button { - cursor: pointer; - color: #555; - float: left; - display: block; - padding: 0px 5px; - margin: 0px; - font-weight: bold; - line-height: 14px; /* Shared by all divs in the link */ - text-decoration: none; - border: solid 1px #dfdfdf; } - - .app_switcher .app_switcher_button .arrow, - .app_switcher .app_switcher_button .name, - .app_switcher .app_switcher_button .icon { - float: left; } - - - .app_switcher .app_switcher_unselected .app_switcher_button:hover { - border: solid 1px #666; - background: #fff url(/images/app_switcher_hover_shadow.gif) repeat-x bottom left; } - - .app_switcher .app_switcher_selected .app_switcher_button { - color: white; - background: #6d84b4; - border: solid 1px #3B5998; } - - .app_switcher .app_switcher_button .arrow { - width: 13px; - padding: 4px 0px 3px; - margin-top: 1px; - background: transparent url(/images/app_switcher_down_arrow.gif) no-repeat -11px center; } - - .app_switcher .app_switcher_selected .app_switcher_button .arrow { - background-position: 2px center; } - - .app_switcher .app_switcher_button .name { - padding: 4px 0px 4px 6px; } - - .app_switcher .app_switcher_button .icon { - width: 16px; - padding: 4px 0px; - background-position: -16px center; - background-repeat: no-repeat; } - - .app_switcher .app_switcher_selected .app_switcher_button .icon { - background-position: 0px center; } - -.app_switcher_menu, #app_switcher_menu_toc { - position: absolute; - background: white; - border: solid 1px #3B5998; - z-index: 100; - padding: 2px 0px 6px; - width: 136px; } - - .app_switcher_menu .menu_list_header { - color: #555; - font-size: 9px; - border-bottom: solid 1px #d8dfea; - padding: 2px 7px 3px; - margin: 0px 0px 6px; } - - .app_switcher_menu a { - display: block; - line-height: 14px; - padding: 1px 7px; - } - - .app_switcher_menu a:hover { - color: white; - text-decoration: none; - background: #6D84B4; - margin: 0px -1px; - border-right: solid 1px #3B5998; - border-left: solid 1px #3B5998; - cursor: pointer; } - - .app_switcher_menu .inline_stuff:hover { - background: none; - color: #3B5998; - } - - .app_switcher_menu .inline_stuff .app_name_text { - display:inline; - } - - .app_switcher_menu .inline_stuff .app_name_text:hover { - background: none; - text-decoration: underline; - color: #3B5998; - display: inline; - } - - .app_switcher_menu .side_space, - .app_switcher_menu .app_icon, - .app_switcher_menu .name { - float: left; } - - .app_switcher_menu .side_space { - width: 7px; - padding: 4px 0px; - margin: 0px 3px; } - - .app_switcher_menu .selector_arrow { - background: url(/images/rightarrow.gif) no-repeat -7px center; } - - .app_switcher_menu a:hover .selector_arrow { - background: url(/images/rightarrow.gif) no-repeat 0px center; } - - .app_switcher_menu .app_icon { - width: 16px; - padding: 4px 0px; - margin: 0px 0px 0px 2px; - background: url(/images/icons/hidden.gif) no-repeat -16px center; } - - .app_switcher_menu a:hover .app_icon { - background-position: 0px center; } - - .app_switcher_menu .name { - padding: 4px 0px; - margin: 0px 0px 0px 5px; } - - - .app_switcher_menu .profile_box_name { - width: 95px; - } - -/* ---------------------------| GRAY HEADER |--------------------------- */ - -.grayheader { - border-bottom: 1px solid #ccc; - margin: 0px 0px 10px; - background: #f7f7f7; - padding: 15px 20px 10px -} - -.grayheader h2 { - font-size:13px; - margin: 0px 0px 2px; - padding:0px; -} - -.grayheader .left_side { - float: left; - width: 380px; -} - -.grayheader .right_side { - float: right; - width: 200px; -} - -.grayheader .right_side p { - text-align: right; -} - -.grayheader p { - display: block; - color: gray; - font-size: 11px; - margin: 2px 0px; - padding: 0px; -} - - -/* ---------------------------| FLYERS |--------------------------- */ - -#announce { - width: 134px; } - -#announce .advert { - background:#F9F9F9 none repeat scroll 0%; - border: solid 1px #D8DFEA; - border-bottom: solid 1px #3B5998; - display: block; - line-height:14px; - margin: 0px 0px 7px 14px; - width: 120px; - padding: 5px 0px 2px 0px; - -} - -#announce .advert .flyers_clickable a { - text-decoration: none; - cursor: pointer; -} - -#announce .advert .flyers_clickable a:hover { - text-decoration: none; -} - -#announce h4 { - border-bottom:1px solid #D8DFEA; - color:#3B5998; - font-size:13px; - font-weight:bold; - margin: 0px 5px 3px 5px; - padding:0px 0px 3px; - text-align:center; -} - -#announce h3 { - color:black; - font-weight:bold; - text-align:center; - padding: 0px 0px 3px; - margin: 4px 5px 3px; - font-size: 11px; -} - -#announce p { - color: #222222; - margin: 0px 0px 3px 5px; - overflow: hidden; - width: 110px; - word-wrap: break-word; -} - -.sponsors { - text-align: center; -} - -.sponsor_absolute { - position: absolute; - top: 302px; -} - -#ssponsor { - width: 120px; - font-size: 11px; - text-align: left; - padding-top: 0px; } - -.credit { - text-align: center; -} - -#ssponsor .seeall { - margin: 5px 0px; - text-align: right; -} - -#ssponsor .banner_ad { - margin-left: 14px; - margin-right: 12px; -} - - -#ssponsor .banner_ad .advert { - margin-left: 0px; -} - -.footer_ad { - clear: both; -} -.footer_ad .advertise_ads { - padding: 0 0 4px 36px; - text-align: left; -} - -/* ---------------------------| DROP-DOWN MENUS |--------------------------- */ - - -.drop_down_menu { - background: white; - position: absolute; - margin-top: -1px; - border: solid 1px #3B5998; - width: 150px; - padding: 5px 0px; -} - -.drop_down_menu .menu_element { - padding: 3px 7px; -} - -.drop_down_menu .menu_element:hover { - color: white; - background: #3B5998; - cursor: pointer; -} - -.drop_down_menu .menu_element:hover a { - color: white; - text-decoration: none; -} - - -/* ---------------------------| NOTES |--------------------------- */ - -.note_dialog { - background:#FFFFFF none repeat scroll 0%; - border:1px solid #BDC7D8; - color:#444444; - margin:0pt 10px 10px; - padding:10px; -} - -/* ---------------------------| RSS |--------------------------- */ - -.syndication_feed a { - background: url(/images/icons/feed.gif) no-repeat 0px 0.3em; - display: block; - padding: 3px 0px 4px 20px; - } - -.syndication_right { - background: url(/images/icons/feed.gif) no-repeat center right; - padding-right: 20px; -} - -#syndication_sidebar { - padding-left: 5px; -} - -#syndication_sidebar .learn_more { - font-size: 9px; - padding: 0px 0px 2px 20px; -} - -#syndication_sidebar p { - color: #333; - padding: 0px; - margin: 0px; -} - -#syndication_sidebar .syndication_explanation { - margin: 0px 15px 0px 0px; - padding: 0px; -} - -.syndication_divider { - border-top: 1px solid #CCCCCC; -} - -#syndication_sidebar h2 { - color: #333; - margin: 0px; - padding: 0px 0px 5px 0px; - font-size: 11px; -} - -/* -------------------------| MISC |--------------------------------- */ - -.findfriends_block { - text-align: left; - margin: auto; -} - - -.findfriends_block li { - line-height:18px; - list-style-image:none; - list-style-position:outside; - list-style-type:none; -} - -.clickable { - cursor: pointer; -} - -/* -------------------------| MISC FBML |------------------------------ */ - -input.request_form_submit { - background: #3B5998 url(/images/icons/request_button_icon.gif) no-repeat 8px 6px; - padding: 3px 6px 3px 26px; - overflow: visible; /* for ie: http://jehiah.cz/archive/button-width-in-ie */ -} - - -/* ----------------------| GENERIC FLASH FALLBACK |---------------------- */ - - -.flash_fallback { - background: #f4f8fc; - border: 1px solid #d8dfea; - clear: both; -} -.flash_fallback_border { - border: 1px solid white; - padding: 5px 10px 5px 10px; -} -.flash_fallback_header { - background: transparent url(/images/icons/flashplayer.gif) no-repeat center left; - color: #282c30; - font-size: 11px; - font-weight: bold; - line-height: 14px; - padding: 5px 0px 5px 22px; - text-align: left; -} -.flash_fallback_explanation { - line-height: 14px; - margin-top: -3px; - padding: 0px 0px 5px 22px; - text-align: left; -} -.flash_fallback_button { - padding: 3px 0px 5px 0px; - text-align: center; -} -.expressinstall_swf { - padding: 15px 0px 15px 0px; - text-align: center; -} - - -/* -------------------------| SOCIAL ADS |------------------------------ */ - -.ad_story { - width: 150px; - display: block; - padding: 0px; - background-color: #f7f7f7; -} - -.ad_story:hover { - text-decoration: none; -} - -.ad_story .social_ad_story { - position: relative; - z-index: 4; - border-top: solid 1px #ccc; - padding: 7px 7px; - border-bottom: solid 1px #eaeaea; - margin-bottom: -1px; -} - -.ad_story .social_ad_profile { - width: 50px; - overflow: hidden; - margin-right: 6px; - padding-top: 3px; - float: left; -} - -.ad_story .social_ad_image { - margin: 0px auto; - text-align: left; -} - -.ad_story .social_ad_text { - color: #666; - line-height: 14px; - float: left; - overflow: hidden; - width: 80px; -} - -.ad_story .social_ad_text strong { - font-weight: bold; - color: #444; -} - - -.ad_story .social_ad_advert { - z-index: 3; - position: relative; - border-top: solid 1px #ccc; - border-bottom: solid 1px #ccc; - padding: 7px 7px; -} - -/* both cascades are necessary because - #sidebar h2 will bind more tightly than - .social_ad_advert h2, yet the latter style - is used across the site */ - -#sidebar .social_ad_advert h2, -.social_ad_advert h2 { - color: #3B5998; - display: block; - float: none; - font-size: 12px; - overflow: hidden; - padding-bottom: 7px; - text-align: left; -} - -#sidebar .ad_story:hover .social_ad_advert h2, -.ad_story:hover .social_ad_advert h2 { - text-decoration: underline; -} - -#sidebar .social_ad_advert .social_ad_advert_text, -.social_ad_advert .social_ad_advert_text { - color: #333; - overflow: hidden; - padding-top: 5px; -} - -.below_social_ad { - width: 150px; - padding-top: 3px; -} - -.below_social_ad .sponsored_links { - font-size: 9px; - float: left; - padding: 2px 0px 0px 3px; - width: 105px; -} - -.below_social_ad .sponsored_question { - float: right; - padding-right: 3px; -} - -.below_social_ad .sponsored_question a { - background: transparent url(/images/question_mark_informational_off.gif) no-repeat scroll right bottom; - display: block; - height: 12px; - text-decoration:none; - width: 12px; -} - -/** ads feedback test **/ -.below_social_ad #ads_feedback { - float: right; - padding-right: 1px; -} - -.below_social_ad #ads_feedback .thumbs_up { - background: url(/images/icons/promote_t2_off.png) no-repeat scroll; - display: block; - float: left; - height: 18px; - vertical-align: top; - width: 18px; - -} - -.below_social_ad #ads_feedback .thumbs_up:hover { - background: url(/images/icons/promote_t2_hover.png) no-repeat scroll; -} - -.below_social_ad #ads_feedback .ex { - background: url(/images/x_to_hide_gray.gif) no-repeat scroll; - display: block; - float: left; - height: 18px; - margin-top: 1px; - vertical-align: top; - width: 15px; -} - -.below_social_ad #ads_feedback .ex:hover { - background: url(/images/x_to_hide_hover.gif) no-repeat scroll; -} - -.below_social_ad #ads_feedback .pipe { - display:block; - color: #ccc; - float: left; - padding: 0px 0px; - vertical-align: top; -} - -.below_social_ad #next_ad_button { - float: right; - padding: 1px 10px 0 0; -} - -.feedback_dialog select { - margin-left: 5px; -} - -/** end -- ads feedback test **/ - -.above_social_ad { - width: 130px; - line-height: 14px; - padding: 3px 7px; -} - -.above_social_ad .sponsored_links { - font-size: 9px; - float: left; - padding-top: 2px; -} - -.above_social_ad .next_link { - float: right; -} - -#sidebar .advertise_ads { - font-size: 9px; - margin: 0 0 3px 15px; - text-align: left; -} - - -/********************** INTERNAL ******************************/ - -.latest_to_production_link { - overflow: hidden; - display: block; - position: absolute; - z-index: 1000; - top: 0px; - left: 0px; -} - -#friday_snipe { - overflow: visible; - display: block; - width: 94px; - height: 94px; - margin: 0px; - padding: 0px; - position: absolute; - z-index: 1000; - top: 0px; - right: 0px; -} - -#friday_snipe .inner_image { - width: 66px; - height: 184px; - position: absolute; - margin: 0px; - padding: 0px; - display: block; - z-index: 1001; - top: 0px; - right: 0px; -} - - - -/*********************** TOOLTIP *******************************/ - -.tooltip_pro { - position: absolute; - z-index: 35; - display:none; -} - -.tooltip_pro .tooltip_text { - padding: 3px 8px 3px 9px; - text-align: center; - white-space: nowrap; - left: 0px; - color: white; - font-size: 11px; - background: #282828; - position: relative; -} - -.tooltip_pro .tooltip_pointer { - height: 4px; - width: 7px; - font-size: 1px; - margin: 0px auto 0px; - padding: 0px; - background: transparent url(/images/dark-pointer.gif) top center no-repeat; -} - -/** - * This class is added to unfocused text inputs by the Javascript - * TextInputControl class in order to give the placeholder text a placeholdery - * look while it is placeholdering in place. - * - * @author epriestley - */ -.DOMControl_placeholder { - color: #777777; -} - -/** - * Added to the "shadow div" used by TextAreaControl to calculate font metrics, - * this div needs - * - * @author epriestley - */ -.DOMControl_shadow { - position: absolute; - left: -10000px; - top: -10000px; -} - - -/* ---------------------------| ACTIONS PRO |---------------------------- */ - -.actionspro { - list-style: none; - margin: 0px; - padding: 0px; } - -.actionspro li { border-bottom: 1px solid #D8DFEA; } - -.actionspro a { - background: transparent; - display: block; - margin: 0px; - padding: 2px 3px; - text-decoration: none; } - -.actionspro a:hover { - background: #3b5998; - color: white; - text-decoration: none; } - -.actionspro .inactive { - padding: 2px 3px; - color: gray; } - - - -/* Here, take it. It's a goodbye gift. Go clean. ------------------------- */ diff --git a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/functional_test.rb b/vendor/plugins/facebooker/generators/facebook_scaffold/templates/functional_test.rb deleted file mode 100644 index ea6c071ce..000000000 --- a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/functional_test.rb +++ /dev/null @@ -1,89 +0,0 @@ -require 'test_helper' -require File.dirname(__FILE__)+'/../../vendor/plugins/facebooker/lib/facebooker/rails/test_helpers.rb' - -class <%= controller_class_name %>ControllerTest < ActionController::TestCase - include Facebooker::Rails::TestHelpers - def test_should_get_index_for_facebook - facebook_get :index - assert_response :success - assert_not_nil assigns(:<%= table_name %>) - end - - def test_should_get_new_for_facebook - facebook_get :new - assert_response :success - end - - def test_should_create_<%= file_name %>_for_facebook - assert_difference('<%= class_name %>.count') do - facebook_post :create, :<%= file_name %> => { } - end - - assert_facebook_redirect_to <%= file_name %>_path(assigns(:<%= file_name %>)) - end - - def test_should_show_<%= file_name %>_for_facebook - facebook_get :show, :id => <%= table_name %>(:one).id - assert_response :success - end - - def test_should_get_edit_for_facebook - facebook_get :edit, :id => <%= table_name %>(:one).id - assert_response :success - end - - def test_should_update_<%= file_name %>_for_facebook - facebook_put :update, :id => <%= table_name %>(:one).id, :<%= file_name %> => { } - assert_facebook_redirect_to <%= file_name %>_path(assigns(:<%= file_name %>)) - end - - def test_should_destroy_<%= file_name %>_for_facebook - assert_difference('<%= class_name %>.count', -1) do - facebook_delete :destroy, :id => <%= table_name %>(:one).id - end - - assert_facebook_redirect_to <%= table_name %>_path - end - - def test_should_get_index - get :index - assert_response :success - assert_not_nil assigns(:<%= table_name %>) - end - - def test_should_get_new - get :new - assert_response :success - end - - def test_should_create_<%= file_name %> - assert_difference('<%= class_name %>.count') do - post :create, :<%= file_name %> => { } - end - - assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>)) - end - - def test_should_show_<%= file_name %> - get :show, :id => <%= table_name %>(:one).id - assert_response :success - end - - def test_should_get_edit - get :edit, :id => <%= table_name %>(:one).id - assert_response :success - end - - def test_should_update_<%= file_name %> - put :update, :id => <%= table_name %>(:one).id, :<%= file_name %> => { } - assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>)) - end - - def test_should_destroy_<%= file_name %> - assert_difference('<%= class_name %>.count', -1) do - delete :destroy, :id => <%= table_name %>(:one).id - end - - assert_redirected_to <%= table_name %>_path - end -end diff --git a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/helper.rb b/vendor/plugins/facebooker/generators/facebook_scaffold/templates/helper.rb deleted file mode 100644 index 9bd821b1b..000000000 --- a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module <%= controller_class_name %>Helper -end diff --git a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/layout.fbml.erb b/vendor/plugins/facebooker/generators/facebook_scaffold/templates/layout.fbml.erb deleted file mode 100644 index 53c7b26fe..000000000 --- a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/layout.fbml.erb +++ /dev/null @@ -1,6 +0,0 @@ -<%%= stylesheet_link_tag 'facebook_scaffold' %> -<%%= facebook_messages %> - -
          -<%%= yield %> -
          diff --git a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/layout.html.erb b/vendor/plugins/facebooker/generators/facebook_scaffold/templates/layout.html.erb deleted file mode 100644 index 5c1f30423..000000000 --- a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/layout.html.erb +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - <%= controller_class_name %>: <%%= controller.action_name %> - <%%= stylesheet_link_tag 'scaffold' %> - - - -

          <%%= flash[:notice] %>

          - -<%%= yield %> - - - diff --git a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/style.css b/vendor/plugins/facebooker/generators/facebook_scaffold/templates/style.css deleted file mode 100644 index 879e85b36..000000000 --- a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/style.css +++ /dev/null @@ -1,74 +0,0 @@ -body { background-color: #fff; color: #333; } - -body, p, ol, ul, td { - font-family: verdana, arial, helvetica, sans-serif; - font-size: 13px; - line-height: 18px; -} - -pre { - background-color: #eee; - padding: 10px; - font-size: 11px; -} - -a { color: #000; } -a:visited { color: #666; } -a:hover { color: #fff; background-color:#000; } - -.fieldWithErrors { - padding: 2px; - background-color: red; - display: table; -} - -#errorExplanation { - width: 400px; - border: 2px solid red; - padding: 7px; - padding-bottom: 12px; - margin-bottom: 20px; - background-color: #f0f0f0; -} - -#errorExplanation h2 { - text-align: left; - font-weight: bold; - padding: 5px 5px 5px 15px; - font-size: 12px; - margin: -7px; - background-color: #c00; - color: #fff; -} - -#errorExplanation p { - color: #333; - margin-bottom: 0; - padding: 5px; -} - -#errorExplanation ul li { - font-size: 12px; - list-style: square; -} - -div.uploadStatus { - margin: 5px; -} - -div.progressBar { - margin: 5px; -} - -div.progressBar div.border { - background-color: #fff; - border: 1px solid gray; - width: 100%; -} - -div.progressBar div.background { - background-color: #333; - height: 18px; - width: 0%; -} - diff --git a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_edit.fbml.erb b/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_edit.fbml.erb deleted file mode 100644 index a730629fa..000000000 --- a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_edit.fbml.erb +++ /dev/null @@ -1,13 +0,0 @@ -

          Editing <%= singular_name %>

          - -<%% facebook_form_for(@<%= singular_name %>) do |f| %> - <%%= f.error_messages %> - -<% for attribute in attributes -%> - <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %> -<% end -%> - <%%= f.buttons "Update" %> -<%% end %> - -<%%= link_to 'Show', @<%= singular_name %> %> | -<%%= link_to 'Back', <%= plural_name %>_path %> diff --git a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_edit.html.erb b/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_edit.html.erb deleted file mode 100644 index e28997559..000000000 --- a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_edit.html.erb +++ /dev/null @@ -1,18 +0,0 @@ -

          Editing <%= singular_name %>

          - -<%% form_for(@<%= singular_name %>) do |f| %> - <%%= f.error_messages %> - -<% for attribute in attributes -%> -

          - <%%= f.label :<%= attribute.name %> %>
          - <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %> -

          -<% end -%> -

          - <%%= f.submit "Update" %> -

          -<%% end %> - -<%%= link_to 'Show', @<%= singular_name %> %> | -<%%= link_to 'Back', <%= plural_name %>_path %> diff --git a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_index.fbml.erb b/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_index.fbml.erb deleted file mode 100644 index 6875446f9..000000000 --- a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_index.fbml.erb +++ /dev/null @@ -1,24 +0,0 @@ -

          Listing <%= plural_name %>

          - - - -<% for attribute in attributes -%> - -<% end -%> - - -<%% for <%= singular_name %> in @<%= plural_name %> %> - -<% for attribute in attributes -%> - -<% end -%> - - - - -<%% end %> -
          <%= attribute.column.human_name %>
          <%%=h <%= singular_name %>.<%= attribute.name %> %><%%= link_to 'Show', <%= singular_name %> %><%%= link_to 'Edit', edit_<%= singular_name %>_path(<%= singular_name %>) %><%%= button_to 'Destroy', <%= singular_name %>, :method => :delete %>
          - -
          - -<%%= link_to 'New <%= singular_name %>', new_<%= singular_name %>_path %> diff --git a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_index.html.erb b/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_index.html.erb deleted file mode 100644 index e89757e3e..000000000 --- a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_index.html.erb +++ /dev/null @@ -1,24 +0,0 @@ -

          Listing <%= plural_name %>

          - - - -<% for attribute in attributes -%> - -<% end -%> - - -<%% for <%= singular_name %> in @<%= plural_name %> %> - -<% for attribute in attributes -%> - -<% end -%> - - - - -<%% end %> -
          <%= attribute.column.human_name %>
          <%%=h <%= singular_name %>.<%= attribute.name %> %><%%= link_to 'Show', <%= singular_name %> %><%%= link_to 'Edit', edit_<%= singular_name %>_path(<%= singular_name %>) %><%%= link_to 'Destroy', <%= singular_name %>, :confirm => 'Are you sure?', :method => :delete %>
          - -
          - -<%%= link_to 'New <%= singular_name %>', new_<%= singular_name %>_path %> diff --git a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_new.fbml.erb b/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_new.fbml.erb deleted file mode 100644 index 322a79bcc..000000000 --- a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_new.fbml.erb +++ /dev/null @@ -1,12 +0,0 @@ -

          New <%= singular_name %>

          - -<%% facebook_form_for(@<%= singular_name %>) do |f| %> - <%%= f.error_messages %> - -<% for attribute in attributes -%> - <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %> -<% end -%> - <%%= f.buttons "Create" %> -<%% end %> - -<%%= link_to 'Back', <%= plural_name %>_path %> diff --git a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_new.html.erb b/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_new.html.erb deleted file mode 100644 index c47e8117b..000000000 --- a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_new.html.erb +++ /dev/null @@ -1,17 +0,0 @@ -

          New <%= singular_name %>

          - -<%% form_for(@<%= singular_name %>) do |f| %> - <%%= f.error_messages %> - -<% for attribute in attributes -%> -

          - <%%= f.label :<%= attribute.name %> %>
          - <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %> -

          -<% end -%> -

          - <%%= f.submit "Create" %> -

          -<%% end %> - -<%%= link_to 'Back', <%= plural_name %>_path %> diff --git a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_show.fbml.erb b/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_show.fbml.erb deleted file mode 100644 index 9b6b11b02..000000000 --- a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_show.fbml.erb +++ /dev/null @@ -1,10 +0,0 @@ -<% for attribute in attributes -%> -

          - <%= attribute.column.human_name %>: - <%%=h @<%= singular_name %>.<%= attribute.name %> %> -

          - -<% end -%> - -<%%= link_to 'Edit', edit_<%= singular_name %>_path(@<%= singular_name %>) %> | -<%%= link_to 'Back', <%= plural_name %>_path %> diff --git a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_show.html.erb b/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_show.html.erb deleted file mode 100644 index 9b6b11b02..000000000 --- a/vendor/plugins/facebooker/generators/facebook_scaffold/templates/view_show.html.erb +++ /dev/null @@ -1,10 +0,0 @@ -<% for attribute in attributes -%> -

          - <%= attribute.column.human_name %>: - <%%=h @<%= singular_name %>.<%= attribute.name %> %> -

          - -<% end -%> - -<%%= link_to 'Edit', edit_<%= singular_name %>_path(@<%= singular_name %>) %> | -<%%= link_to 'Back', <%= plural_name %>_path %> diff --git a/vendor/plugins/facebooker/generators/publisher/publisher_generator.rb b/vendor/plugins/facebooker/generators/publisher/publisher_generator.rb deleted file mode 100644 index f27545e87..000000000 --- a/vendor/plugins/facebooker/generators/publisher/publisher_generator.rb +++ /dev/null @@ -1,14 +0,0 @@ -class PublisherGenerator < Rails::Generator::NamedBase - def manifest - puts banner - exit(1) - end - - def banner - <<-EOM - This generator has been renamed to facebook_publisher - please run: #{$0} facebook_publisher - EOM - end - -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/generators/xd_receiver/templates/xd_receiver.html b/vendor/plugins/facebooker/generators/xd_receiver/templates/xd_receiver.html deleted file mode 100644 index 86b2f7986..000000000 --- a/vendor/plugins/facebooker/generators/xd_receiver/templates/xd_receiver.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - Cross-Domain Receiver Page - - - - - diff --git a/vendor/plugins/facebooker/generators/xd_receiver/templates/xd_receiver_ssl.html b/vendor/plugins/facebooker/generators/xd_receiver/templates/xd_receiver_ssl.html deleted file mode 100644 index c336371b7..000000000 --- a/vendor/plugins/facebooker/generators/xd_receiver/templates/xd_receiver_ssl.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - Cross-Domain Receiver Page - - - - - diff --git a/vendor/plugins/facebooker/generators/xd_receiver/xd_receiver_generator.rb b/vendor/plugins/facebooker/generators/xd_receiver/xd_receiver_generator.rb deleted file mode 100644 index 7bceb2488..000000000 --- a/vendor/plugins/facebooker/generators/xd_receiver/xd_receiver_generator.rb +++ /dev/null @@ -1,10 +0,0 @@ -class XdReceiverGenerator < Rails::Generator::Base - def manifest - record do |m| - m.template "xd_receiver.html", "public/xd_receiver.html" - m.template "xd_receiver_ssl.html", "public/xd_receiver_ssl.html" - end - end - - -end diff --git a/vendor/plugins/facebooker/init.rb b/vendor/plugins/facebooker/init.rb deleted file mode 100644 index 18cde680e..000000000 --- a/vendor/plugins/facebooker/init.rb +++ /dev/null @@ -1,28 +0,0 @@ -# Added support to the Facebooker.yml file for switching to the new profile design.. -# Config parsing needs to happen before files are required. -facebook_config = "#{RAILS_ROOT}/config/facebooker.yml" - -require 'facebooker' -FACEBOOKER = Facebooker.load_configuration(facebook_config) - -# enable logger before including everything else, in case we ever want to log initialization -Facebooker.logger = RAILS_DEFAULT_LOGGER if Object.const_defined? :RAILS_DEFAULT_LOGGER - -require 'net/http_multipart_post' -if defined? Rails - require 'facebooker/rails/backwards_compatible_param_checks' - require 'facebooker/rails/controller' - require 'facebooker/rails/facebook_url_rewriting' - require 'facebooker/rails/facebook_session_handling' if Rails.version < '2.3' - require 'facebooker/rails/facebook_request_fix' if Rails.version < '2.3' - require 'facebooker/rails/facebook_request_fix_2-3' if Rails.version >= '2.3' - require 'facebooker/rails/routing' - require 'facebooker/rails/helpers/stream_publish' - require 'facebooker/rails/helpers/fb_connect' - require 'facebooker/rails/helpers' - require 'facebooker/rails/facebook_pretty_errors' rescue nil - require 'facebooker/rails/facebook_url_helper' - require 'facebooker/rails/extensions/rack_setup' if Rails.version > '2.3' - require 'facebooker/rails/extensions/action_controller' - require 'facebooker/rails/extensions/routing' -end diff --git a/vendor/plugins/facebooker/install.rb b/vendor/plugins/facebooker/install.rb deleted file mode 100644 index 66822f51c..000000000 --- a/vendor/plugins/facebooker/install.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'fileutils' -require 'rubygems' - -dir = File.dirname(__FILE__) -templates = File.join(dir, 'generators', 'facebook', 'templates') -config = File.join('config', 'facebooker.yml') -script = File.join('public', 'javascripts', 'facebooker.js') - -[config, script].each do |path| - FileUtils.cp File.join(templates, path), File.join(RAILS_ROOT, path) unless File.exist?(File.join(RAILS_ROOT, path)) -end -puts IO.read(File.join(dir, 'README.rdoc')) diff --git a/vendor/plugins/facebooker/lib/facebooker.rb b/vendor/plugins/facebooker/lib/facebooker.rb deleted file mode 100644 index fb6e58386..000000000 --- a/vendor/plugins/facebooker/lib/facebooker.rb +++ /dev/null @@ -1,261 +0,0 @@ -unless defined?(ActiveSupport) and defined?(ActiveSupport::JSON) - begin - require 'json' - rescue LoadError - gem "json_pure" - require "json" - end - module Facebooker - def self.json_decode(str) - JSON.parse(str) - end - - def self.json_encode(o) - JSON.dump(o) - end - end -else - module Facebooker - def self.json_decode(str) - ActiveSupport::JSON.decode(str) - end - - def self.json_encode(o) - ActiveSupport::JSON.encode(o) - end - end -end - -require 'zlib' -require 'digest/md5' - -module Facebooker - - @facebooker_configuration = {} - @raw_facebooker_configuration = {} - @current_adapter = nil - @set_asset_host_to_callback_url = true - @path_prefix = nil - @use_curl = false - - class << self - - def load_configuration(facebooker_yaml_file) - return false unless File.exist?(facebooker_yaml_file) - @raw_facebooker_configuration = YAML.load(ERB.new(File.read(facebooker_yaml_file)).result) - if defined? RAILS_ENV - @raw_facebooker_configuration = @raw_facebooker_configuration[RAILS_ENV] - end - Thread.current[:fb_api_config] = @raw_facebooker_configuration unless Thread.current[:fb_api_config] - apply_configuration(@raw_facebooker_configuration) - end - - # Sets the Facebook environment based on a hash of options. - # By default the hash passed in is loaded from facebooker.yml, but it can also be passed in - # manually every request to run multiple Facebook apps off one Rails app. - def apply_configuration(config) - ENV['FACEBOOK_API_KEY'] = config['api_key'] - ENV['FACEBOOK_SECRET_KEY'] = config['secret_key'] - ENV['FACEBOOKER_RELATIVE_URL_ROOT'] = config['canvas_page_name'] - ENV['FACEBOOKER_API'] = config['api'] - if config.has_key?('set_asset_host_to_callback_url') - Facebooker.set_asset_host_to_callback_url = config['set_asset_host_to_callback_url'] - end - if Object.const_defined?("ActionController") and Facebooker.set_asset_host_to_callback_url - ActionController::Base.asset_host = config['callback_url'] - end - Facebooker.timeout = config['timeout'] - - @facebooker_configuration = config # must be set before adapter loaded - load_adapter(:fb_sig_api_key => config['api_key']) - facebooker_config - end - - def facebooker_config - @facebooker_configuration - end - - def with_application(api_key, &block) - config = fetch_config_for( api_key ) - - unless config - self.logger.info "Can't find facebooker config: '#{api_key}'" if self.logger - yield if block_given? - return - end - - # Save the old config to handle nested activation. If no app context is - # set yet, use default app's configuration. - old = Thread.current[:fb_api_config] ? Thread.current[:fb_api_config].dup : @raw_facebooker_configuration - - if block_given? - begin - self.logger.info "Swapping facebooker config: '#{api_key}'" if self.logger - Thread.current[:fb_api_config] = apply_configuration(config) - yield - ensure - Thread.current[:fb_api_config] = old if old && !old.empty? - apply_configuration(Thread.current[:fb_api_config]) - end - end - end - - def all_api_keys - [ - @raw_facebooker_configuration['api_key'] - ] + ( - @raw_facebooker_configuration['alternative_keys'] ? - @raw_facebooker_configuration['alternative_keys'].keys : - [] - ) - end - - def with_all_applications(&block) - all_api_keys.each do |current_api_key| - with_application(current_api_key) do - block.call - end - end - end - - def fetch_config_for(api_key) - if @raw_facebooker_configuration['api_key'] == api_key - return @raw_facebooker_configuration - elsif @raw_facebooker_configuration['alternative_keys'] and - @raw_facebooker_configuration['alternative_keys'].keys.include?(api_key) - return @raw_facebooker_configuration['alternative_keys'][api_key].merge( - 'api_key' => api_key ) - end - return false - end - - # TODO: This should be converted to attr_accessor, but we need to - # get all the require statements at the top of the file to work. - - # Set the current adapter - attr_writer :current_adapter - - # Get the current adapter - def current_adapter - @current_adapter || Facebooker::AdapterBase.default_adapter - end - - def load_adapter(params) - self.current_adapter = Facebooker::AdapterBase.load_adapter(params) - end - - def facebook_path_prefix=(path) - current_adapter.facebook_path_prefix = path - end - - # Default is canvas_page_name in yml file - def facebook_path_prefix - current_adapter.facebook_path_prefix - end - - def is_for?(application_container) - current_adapter.is_for?(application_container) - end - - attr_accessor :set_asset_host_to_callback_url - attr_accessor :use_curl - alias :use_curl? :use_curl - - def timeout=(val) - @timeout = val.to_i - end - - def timeout - @timeout - end - - [:api_key,:secret_key, :www_server_base_url,:login_url_base,:install_url_base,:permission_url_base,:connect_permission_url_base,:api_rest_path,:api_server_base,:api_server_base_url,:canvas_server_base, :video_server_base].each do |delegated_method| - define_method(delegated_method){ return current_adapter.send(delegated_method)} - end - - - attr_reader :path_prefix - - - # Set the asset path to the canvas path for just this one request - # by definition, we will make this a canvas request - def with_asset_path_for_canvas - original_asset_host = ActionController::Base.asset_host - begin - ActionController::Base.asset_host = Facebooker.api_server_base_url - request_for_canvas(true) do - yield - end - ensure - ActionController::Base.asset_host = original_asset_host - end - end - - # If this request is_canvas_request - # then use the application name as the url root - def request_for_canvas(is_canvas_request) - original_path_prefix = @path_prefix - begin - @path_prefix = facebook_path_prefix if is_canvas_request - yield - ensure - @path_prefix = original_path_prefix - end - end - end -end - -require 'facebooker/attachment' -require 'facebooker/batch_request' -require 'facebooker/feed' -require 'facebooker/logging' -require 'facebooker/model' -require 'facebooker/parser' -require 'facebooker/service' -require 'facebooker/service/base_service' -#optional HTTP service adapters -begin - require 'facebooker/service/curl_service' -rescue LoadError - nil -end -begin - require 'facebooker/service/typhoeus_service' - require 'facebooker/service/typhoeus_multi_service' -rescue LoadError - nil -end - -require 'facebooker/service/net_http_service' -require 'facebooker/server_cache' -require 'facebooker/data' -require 'facebooker/admin' -require 'facebooker/application' -require 'facebooker/mobile' -require 'facebooker/session' -require 'facebooker/stream_post' -require 'facebooker/version' -require 'facebooker/models/location' -require 'facebooker/models/affiliation' -require 'facebooker/models/album' -require 'facebooker/models/comment' -require 'facebooker/models/education_info' -require 'facebooker/models/work_info' -require 'facebooker/models/event' -require 'facebooker/models/group' -require 'facebooker/models/notifications' -require 'facebooker/models/page' -require 'facebooker/models/photo' -require 'facebooker/models/cookie' -require 'facebooker/models/applicationproperties' -require 'facebooker/models/applicationrestrictions' -require 'facebooker/models/tag' -require 'facebooker/models/user' -require 'facebooker/models/info_item' -require 'facebooker/models/info_section' -require 'facebooker/models/friend_list' -require 'facebooker/models/video' -require 'facebooker/models/message_thread' -require 'facebooker/adapters/adapter_base' -require 'facebooker/adapters/facebook_adapter' -require 'facebooker/adapters/bebo_adapter' diff --git a/vendor/plugins/facebooker/lib/facebooker/adapters/adapter_base.rb b/vendor/plugins/facebooker/lib/facebooker/adapters/adapter_base.rb deleted file mode 100644 index 42fa81549..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/adapters/adapter_base.rb +++ /dev/null @@ -1,91 +0,0 @@ -module Facebooker - - class AdapterBase - class UnableToLoadAdapter < Exception; end - require 'active_support/inflector' - include ActiveSupport::CoreExtensions::String::Inflections - def facebook_path_prefix - "/" + (@facebook_path_prefix || canvas_page_name || ENV['FACEBOOK_CANVAS_PATH'] || ENV['FACEBOOKER_RELATIVE_URL_ROOT']) - end - - def facebook_path_prefix=(prefix) - @facebook_path_prefix = prefix - end - - def facebooker_config - @config - end - - def api_server_base_url - "http://" + api_server_base - end - - def is_for?(application_context) - raise "SubClassShouldDefine" - end - - def initialize(config) - @config = config - @facebook_path_prefix = nil - end - - def self.facebooker_config - Facebooker.facebooker_config - end - - - def self.load_adapter(params) - - config_key_base = params[:config_key_base] # This allows for loading of a aspecific adapter - config_key_base += "_" if config_key_base && config_key_base.length > 0 - - unless api_key = (params[:fb_sig_api_key] || facebooker_config["#{config_key_base}api_key"]) - raise Facebooker::AdapterBase::UnableToLoadAdapter - end - - unless facebooker_config - return self.default_adapter(params) - end - - facebooker_config.each do |key,value| - next unless value == api_key - - key_base = key.match(/(.*)[_]?api_key/)[1] - - adapter_class_name = if !key_base || key_base.length == 0 - "FacebookAdapter" - else - facebooker_config[key_base + "adapter"] - end - - adapter_class = "Facebooker::#{adapter_class_name}".constantize - - # Collect the rest of the configuration - adapter_config = {} - facebooker_config.each do |key,value| - if (match = key.match(/#{key_base}[_]?(.*)/)) - adapter_config[match[1]] = value - end - end - return adapter_class.new(adapter_config) - end - - return self.default_adapter(params) - - end - - def self.default_adapter(params = {}) - if facebooker_config.nil? || (facebooker_config.blank? rescue nil) - config = { "api_key" => ENV['FACEBOOK_API_KEY'], "secret_key" => ENV['FACEBOOK_SECRET_KEY']} - else - config = facebooker_config - end - FacebookAdapter.new(config) - end - - [:canvas_page_name, :api_key,:secret_key].each do |key_method| - define_method(key_method){ return facebooker_config[key_method.to_s]} - end - - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/adapters/bebo_adapter.rb b/vendor/plugins/facebooker/lib/facebooker/adapters/bebo_adapter.rb deleted file mode 100644 index 0f24b960f..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/adapters/bebo_adapter.rb +++ /dev/null @@ -1,77 +0,0 @@ -module Facebooker - class BeboAdapter < AdapterBase - - def canvas_server_base - "apps.bebo.com" - end - - def api_server_base - 'apps.bebo.com' - end - - def api_rest_path - "/restserver.php" - end - - def is_for?(application_context) - application_context == :bebo - end - - def www_server_base_url - "www.bebo.com" - end - - - def login_url_base - "http://#{www_server_base_url}/SignIn.jsp?ApiKey=#{api_key}&v=1.0" - end - - def install_url_base - "http://#{www_server_base_url}/c/apps/add?ApiKey=#{api_key}&v=1.0" - end - end -end - -# Things that don't actually work as expected in BEBO -module Facebooker - class User - def set_profile_fbml_with_bebo_adapter(profile_fbml, mobile_fbml, profile_action_fbml, profile_main = nil) - if(Facebooker.is_for?(:bebo)) - self.session.post('facebook.profile.setFBML', :uid => @id, :markup => profile_fbml) - else - set_profile_fbml_without_bebo_adapter(profile_fbml,mobile_fbml, profile_action_fbml, profile_main) - end - end - alias_method :set_profile_fbml_without_bebo_adapter, :set_profile_fbml - alias_method :set_profile_fbml, :set_profile_fbml_with_bebo_adapter - - private - - BEBO_FIELDS = FIELDS - [:meeting_sex, :wall_count, :meeting_for] - - remove_method :collect - - def collect(fields) - if(Facebooker.is_for?(:bebo) ) - BEBO_FIELDS.reject{|field_name| !fields.empty? && !fields.include?(field_name)}.join(',') - else - FIELDS.reject{|field_name| !fields.empty? && !fields.include?(field_name)}.join(',') - end - end - end - - - class PublishTemplatizedAction < Parser#:nodoc: - class < properties) == '1' - end - - # ** BETA *** - # +properties+: Hash of properties you want to view. - def get_app_properties(*properties) - json = @session.post('facebook.admin.getAppProperties', :properties => properties.to_json) - hash = Facebooker.json_decode(CGI.unescapeHTML(json)) - @properties = ApplicationProperties.from_hash(hash) - end - - # ** BETA *** - # +restrictions+: Hash of restrictions you want to set. - def set_restriction_info(restrictions) - restrictions = restrictions.respond_to?(:to_json) ? restrictions.to_json : restrictions - (@session.post 'facebook.admin.setRestrictionInfo', :restriction_str => restrictions) == '1' - end - - # ** BETA *** - def get_restriction_info(*restrictions) - json = @session.post('facebook.admin.getRestrictionInfo') - hash = Facebooker.json_decode(CGI.unescapeHTML(json)) - @restrictions = ApplicationRestrictions.from_hash(hash) - end - - # Integration points include.. - # :notifications_per_day, :requests_per_day, :emails_per_day, :email_disable_message_location - def get_allocation(integration_point) - @session.post('facebook.admin.getAllocation', :integration_point_name => integration_point.to_s).to_i - end - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/application.rb b/vendor/plugins/facebooker/lib/facebooker/application.rb deleted file mode 100644 index 37233b528..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/application.rb +++ /dev/null @@ -1,37 +0,0 @@ -module Facebooker - class Application - def initialize(session) - @session = session - end - - # +properties+: Hash of properties of the desired application. Specify exactly one of: application_id, application_api_key or application_canvas_name - # eg: application.get_public_info(:application_canvas_name => ENV['FACEBOOKER_RELATIVE_URL_ROOT']) - def get_public_info(properties) - (@session.post 'facebook.application.getPublicInfo', properties) - end - - # facebook_session.application.add_global_news [{ :message => 'Hi all users', :action_link => { :text => "Hi application", :href => 'http://facebook.er/' }}], 'http://facebook.er/icon.png' - def add_global_news(news, image=nil) - params = {} - params[:news] = news - params[:image] = image if image - @session.post('facebook.dashboard.addGlobalNews', params) - end - - # currently bugged on Facebook; returns all - # facebook_session.application.get_global_news '310354202543' - def get_global_news(*news_ids) - params = {} - params[:news_ids] = news_ids.flatten if news_ids - @session.post('facebook.dashboard.getGlobalNews', params) - end - - # facebook_session.application.clear_global_news '310354202543' - def clear_global_news(*news_ids) - params = {} - params[:news_ids] = news_ids.flatten if news_ids - @session.post('facebook.dashboard.clearGlobalNews', params) - end - - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/attachment.rb b/vendor/plugins/facebooker/lib/facebooker/attachment.rb deleted file mode 100644 index 1eebcef38..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/attachment.rb +++ /dev/null @@ -1,59 +0,0 @@ -class Facebooker::Attachment - - def initialize - @storage = {} - end - - def self.hash_populating_accessor(*names) - names.each do |name| - define_method(name) do - @storage[name] - end - define_method("#{name}=") do |val| - @storage[name]=val - end - end - end - - hash_populating_accessor :name,:href, :comments_xid, :description, :caption - - def add_media(hash) - @storage[:media]||=[] - @storage[:media] << hash - end - - def add_image(source,href) - add_media({:type=>"image",:src=>source,:href=>href}) - end - - def add_mp3(source,title=nil,artist=nil,album=nil) - params = {:src=>source,:type=>"mp3"} - params[:title] = title unless title.nil? - params[:artist] = artist unless artist.nil? - params[:album] = album unless album.nil? - add_media(params) - end - - def add_flash(swfsource, imgsource, width=nil, height=nil, expanded_width=nil, expanded_height=nil) - params={:type=>"flash",:swfsrc=>swfsource,:imgsrc=>imgsource} - params[:width] = width unless width.nil? - params[:height] = height unless height.nil? - params[:expanded_width] = expanded_width unless expanded_width.nil? - params[:expanded_height] = expanded_height unless expanded_height.nil? - add_media(params) - end - - - def add_property(key, value) - @storage[:properties] ||= {} - @storage[:properties][key] = value - end - - - - def to_hash - @storage - end - - -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/batch_request.rb b/vendor/plugins/facebooker/lib/facebooker/batch_request.rb deleted file mode 100644 index 679ee8913..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/batch_request.rb +++ /dev/null @@ -1,45 +0,0 @@ -module Facebooker - class BatchRequest - instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^respond_to\?$|^new|object_id$)/ } - attr_reader :uri - attr_reader :method - class UnexecutedRequest < StandardError; end - def initialize(params,proc) - @exception = nil - @result = nil - @method = params[:method] - @uri = params.map{|k,v| "#{k}=#{CGI.escape(v.to_s)}"}.join("&") - @proc = proc - end - - def result=(result_object) - @result = @proc.nil? ? result_object : @proc.call(result_object) - end - - def exception_raised=(ex) - @exception=ex - end - - def exception_raised? - @exception.nil? ? false : raise(@exception) - end - - def respond_to?(name) - super || @result.respond_to?(name) - end - - def ===(other) - other === @result - end - - def method_missing(name,*args,&proc) - if @exception - raise @exception - elsif @result.nil? - raise UnexecutedRequest.new("You must execute the batch before accessing the result: #{@uri}") - else - @result.send(name,*args,&proc) - end - end - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/data.rb b/vendor/plugins/facebooker/lib/facebooker/data.rb deleted file mode 100644 index d2acbf58b..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/data.rb +++ /dev/null @@ -1,57 +0,0 @@ -module Facebooker - class Data - def initialize(session) - @session = session - end - - ## - # ** BETA *** - # Sets a cookie on Facebook - # +user+ The user for whom this cookie needs to be set. - # +name+ Name of the cookie - # +value+ Value of the cookie - # Optional: - # +expires+ Time when the cookie should expire. If not specified, the cookie never expires. - # +path+ Path relative to the application's callback URL, with which the cookie should be associated. (default value is /? - def set_cookie(user, name, value, expires=nil, path=nil) - @session.post('facebook.data.setCookie', - :uid => User.cast_to_facebook_id(user), - :name => name, - :value => value, - :expires => expires, - :path => path) {|response| response == '1'} - end - - ## - # ** BETA *** - # Gets a cookie stored on Facebook - # +user+ The user from whom to get the cookies. - # Optional: - # +name+ The name of the cookie. If not specified, all the cookies for the given user get returned. - def get_cookies(user, name=nil) - @cookies = @session.post( - 'facebook.data.getCookies', :uid => User.cast_to_facebook_id(user), :name => name) do |response| - response.map do |hash| - Cookie.from_hash(hash) - end - end - end - - ## - # ** BETA *** - # Gets a preference stored on Facebook - # +pref_id+ The id of the preference to get - def get_preference(pref_id) - @session.post('facebook.data.getUserPreference', :pref_id=>pref_id) - end - - ## - # ** BETA *** - # Sets a preference on Facebook - # +pref_id+ The id of the preference to set - # +value+ The value to set for this preference - def set_preference(pref_id, value) - @session.post('facebook.data.setUserPreference', :pref_id=>pref_id, :value=>value) - end - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/feed.rb b/vendor/plugins/facebooker/lib/facebooker/feed.rb deleted file mode 100644 index 75c32360e..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/feed.rb +++ /dev/null @@ -1,78 +0,0 @@ -module Facebooker - module Feed - METHODS = {'Action' => 'facebook.feed.publishActionOfUser', 'Story' => 'facebook.feed.publishStoryToUser', - 'TemplatizedAction' => 'facebook.feed.publishTemplatizedAction' } - - class ActionBase - 1.upto(4) do |num| - attr_accessor "image_#{num}" - attr_accessor "image_#{num}_link" - end - - def add_image(image,link=nil) - 1.upto(4) do |num| - if send("image_#{num}").blank? - send("image_#{num}=",image) - send("image_#{num}_link=",link) unless link.nil? - return num - end - end - end - - - protected - def image_params - image_hash = {} - 1.upto(4) do |num| - image_attribute = "image_#{num}" - image_link_attribute = image_attribute + "_link" - self.__send__(image_attribute) ? image_hash[image_attribute] = self.__send__(image_attribute) : nil - self.__send__(image_link_attribute) ? image_hash[image_link_attribute] = self.__send__(image_link_attribute) : nil - end - image_hash - end - end - - ## - # Representation of a templatized action to be published into a user's news feed - class TemplatizedAction < ActionBase - attr_accessor :page_actor_id, :title_template, :title_data, :body_template, :body_data, :body_general, :target_ids - - def to_params - raise "Must set title_template" if self.title_template.nil? - { :page_actor_id => page_actor_id, - :title_template => title_template, - :title_data => convert_json(title_data), - :body_template => body_template, - :body_data => convert_json(body_data), - :body_general => body_general, - :target_ids => target_ids }.merge image_params - end - - def convert_json(hash_or_string) - (hash_or_string.is_a?(Hash) and hash_or_string.respond_to?(:to_json)) ? hash_or_string.to_json : hash_or_string - end - end - - ## - # Representation of a story to be published into a user's news feed. - class Story < ActionBase - attr_accessor :title, :body - - ## - # Converts Story to a Hash of its attributes for use as parameters to Facebook REST API calls - def to_params - raise "Must set title before converting" if self.title.nil? - { :title => title, :body => body }.merge image_params - end - - end - Action = Story.dup - def Action.name - "Action" - end - ## - # Representation of an action to be published into a user's news feed. Alias for Story. - class Action; end - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/logging.rb b/vendor/plugins/facebooker/lib/facebooker/logging.rb deleted file mode 100644 index c0d33c254..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/logging.rb +++ /dev/null @@ -1,44 +0,0 @@ -require 'benchmark' -module Facebooker - @@logger = nil - def self.logger=(logger) - @@logger = logger - end - def self.logger - @@logger - end - - module Logging - @skip_api_logging = nil - class << self; attr_accessor :skip_api_logging; end - - def self.log_fb_api(method, params) - message = method # might customize later - dump = format_fb_params(params) - if block_given? - result = nil - seconds = Benchmark.realtime { result = yield } - log_info(message, dump, seconds) unless skip_api_logging - result - else - log_info(message, dump) unless skip_api_logging - nil - end - rescue Exception => e - exception = "#{e.class.name}: #{e.message}: #{dump}" - log_info(message, exception) - raise - end - - def self.format_fb_params(params) - params.map { |key,value| "#{key} = #{value}" }.join(', ') - end - - def self.log_info(message, dump, seconds = 0) - return unless Facebooker.logger - log_message = "#{message} (#{seconds}) #{dump}" - Facebooker.logger.info(log_message) - end - - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/mobile.rb b/vendor/plugins/facebooker/lib/facebooker/mobile.rb deleted file mode 100644 index 07c97c6d9..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/mobile.rb +++ /dev/null @@ -1,20 +0,0 @@ -module Facebooker - class Mobile - def initialize(session) - @session = session - end - - # Used to determine whether the user identified by "uid" has enabled SMS for this application. - def can_send(user) - @session.post('facebook.sms.canSend', :uid => User.cast_to_facebook_id(user)) - end - - # Send the given message to the user. - # See http://wiki.developers.facebook.com/index.php/Mobile - def send(user, message) - @session.post('facebook.sms.send', - {:uid => User.cast_to_facebook_id(user), - :message => message}, false) - end - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/mock/service.rb b/vendor/plugins/facebooker/lib/facebooker/mock/service.rb deleted file mode 100644 index 4be1d88d9..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/mock/service.rb +++ /dev/null @@ -1,50 +0,0 @@ -require 'digest/md5' -require 'facebooker/service' - -module Facebooker - # A mock service that reads the Facebook response from fixtures - # Adapted from http://gist.github.com/44344 - # - # Facebooker::MockService.fixture_path = 'path/to/dir' - # Facebooker::Session.current = Facebooker::MockSession.create - # - class MockService < Service - class << self - attr_accessor :fixture_path - end - - def read_fixture(method, filename, original = nil) - path = fixture_path(method, filename) - File.read path - rescue Errno::ENAMETOOLONG - read_fixture(method, hash_fixture_name(filename), filename) - rescue Errno::ENOENT => e - if File.exists?(fixture_path(method, 'default')) - File.read fixture_path(method, 'default') - else - e.message << "\n(Non-hashed path is #{original})" if original - e.message << "\nFacebook API Reference: http://wiki.developers.facebook.com/index.php/#{method.sub(/^facebook\./, '')}#Example_Return_XML" - raise e - end - end - - def post(params) - method = params.delete(:method) - params.delete_if {|k,_| [:v, :api_key, :call_id, :sig].include?(k) } - Parser.parse(method, read_fixture(method, fixture_name(params))) - end - - private - def fixture_path(method, filename) - File.join(self.class.fixture_path, method, "#{filename}.xml") - end - - def hash_fixture_name(filename) - Digest::MD5.hexdigest(filename) - end - - def fixture_name(params) - params.map {|*args| args.join('=') }.sort.join('&') - end - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/mock/session.rb b/vendor/plugins/facebooker/lib/facebooker/mock/session.rb deleted file mode 100644 index 7b9aacc9a..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/mock/session.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'facebooker/session' - -module Facebooker - class MockSession < Session - def secured? - true - end - - def secure! - @uid = 1 - true - end - - def service - @service ||= MockService.new(Facebooker.api_server_base, Facebooker.api_rest_path, @api_key) - end - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/model.rb b/vendor/plugins/facebooker/lib/facebooker/model.rb deleted file mode 100644 index 97dd33d04..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/model.rb +++ /dev/null @@ -1,139 +0,0 @@ -module Facebooker - ## - # helper methods primarily supporting the management of Ruby objects which are populatable via Hashes. - # Since most Facebook API calls accept and return hashes of data (as XML), the Model module allows us to - # directly populate a model's attributes given a Hash with matching key names. - module Model - class UnboundSessionException < Exception; end - def self.included(includer) - includer.extend ClassMethods - includer.__send__(:attr_writer, :session) - includer.__send__(:attr_reader, :anonymous_fields) - end - module ClassMethods - ## - # Instantiate a new instance of the class into which we are included and populate that instance's - # attributes given the provided Hash. Key names in the Hash should map to attribute names on the model. - def from_hash(hash) - instance = new(hash) - yield instance if block_given? - instance - end - - ## - # Create a standard attr_writer and a populating_attr_reader - def populating_attr_accessor(*symbols) - attr_writer(*symbols) - populating_attr_reader(*symbols) - end - - ## - # Create a reader that will attempt to populate the model if it has not already been populated - def populating_attr_reader(*symbols) - symbols.each do |symbol| - define_method(symbol) do - populate unless populated? - instance_variable_get("@#{symbol}") - end - end - end - - def populating_hash_settable_accessor(symbol, klass) - populating_attr_reader symbol - hash_settable_writer(symbol, klass) - end - - def populating_hash_settable_list_accessor(symbol, klass) - populating_attr_reader symbol - hash_settable_list_writer(symbol, klass) - end - - # - # Declares an attribute named ::symbol:: which can be set with either an instance of ::klass:: - # or a Hash which will be used to populate a new instance of ::klass::. - def hash_settable_accessor(symbol, klass) - attr_reader symbol - hash_settable_writer(symbol, klass) - end - - def hash_settable_writer(symbol, klass) - define_method("#{symbol}=") do |value| - instance_variable_set("@#{symbol}", value.kind_of?(Hash) ? klass.from_hash(value) : value) - end - end - - # - # Declares an attribute named ::symbol:: which can be set with either a list of instances of ::klass:: - # or a list of Hashes which will be used to populate a new instance of ::klass::. - def hash_settable_list_accessor(symbol, klass) - attr_reader symbol - hash_settable_list_writer(symbol, klass) - end - - def hash_settable_list_writer(symbol, klass) - define_method("#{symbol}=") do |list| - instance_variable_set("@#{symbol}", list.map do |item| - item.kind_of?(Hash) ? klass.from_hash(item) : item - end) - end - end - - def id_is(attribute) - (file, line) = caller.first.split(':') - - class_eval(<<-EOS, file, line.to_i) - def #{attribute}=(value) - @#{attribute} = value.to_i - end - - attr_reader #{attribute.inspect} - alias :id #{attribute.inspect} - alias :id= #{"#{attribute}=".to_sym.inspect} - EOS - end - end - - ## - # Centralized, error-checked place for a model to get the session to which it is bound. - # Any Facebook API queries require a Session instance. - def session - @session || (raise UnboundSessionException, "Must bind this object to a Facebook session before querying") - end - - # - # This gets populated from FQL queries. - def anon=(value) - @anonymous_fields = value - end - - def initialize(hash = {}) - populate_from_hash!(hash) - end - - def populate - raise NotImplementedError, "#{self.class} included me and should have overriden me" - end - - def populated? - @populated - end - - ## - # Set model's attributes via Hash. Keys should map directly to the model's attribute names. - def populate_from_hash!(hash) - unless hash.nil? || hash.empty? - hash.each do |key, value| - set_attr_method = "#{key}=" - unless value.nil? - if respond_to?(set_attr_method) - self.__send__(set_attr_method, value) - else - Facebooker::Logging.log_info("**Warning**, Attempt to set non-attribute: #{key}",hash) - end - end - end - @populated = true - end - end - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/models/affiliation.rb b/vendor/plugins/facebooker/lib/facebooker/models/affiliation.rb deleted file mode 100644 index 4b0147fb4..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/affiliation.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'facebooker/model' -module Facebooker - ## - # Represents a user's affiliation, for example, which educational institutions - # the user is associated with. - class Affiliation - include Model - attr_accessor :name, :status, :type, :year, :nid - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/models/album.rb b/vendor/plugins/facebooker/lib/facebooker/models/album.rb deleted file mode 100644 index 6f656369e..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/album.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'facebooker/model' -module Facebooker - ## - # A simple representation of a photo album. - class Album - include Model - attr_accessor :aid, :cover_pid, :owner, :name, :created, - :modified, :description, :location, :link, :size, :visible - - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/models/applicationproperties.rb b/vendor/plugins/facebooker/lib/facebooker/models/applicationproperties.rb deleted file mode 100644 index c8492bdba..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/applicationproperties.rb +++ /dev/null @@ -1,39 +0,0 @@ -module Facebooker - - # application_name string The name of your application. - # callback_url string Your application's callback URL. The callback URL cannot be longer than 100 characters. - # post_install_url string The URL where a user gets redirected after installing your application. The post-install URL cannot be longer than 100 characters. - # edit_url string - # dashboard_url string - # uninstall_url string The URL where a user gets redirected after removing your application. - # ip_list string For Web-based applications, these are the IP addresses of your servers that can access Facebook's servers and serve information to your application. - # email string The email address associated with the application; the email address Facebook uses to contact you about your application. (default value is your Facebook email address.) - # description string The description of your application. - # use_iframe bool Indicates whether you render your application with FBML (0) or in an iframe (1). (default value is 1) - # desktop bool Indicates whether your application is Web-based (0) or gets installed on a user's desktop (1). (default value is 1) - # is_mobile bool Indicates whether your application can run on a mobile device (1) or not (0). (default value is 1) - # default_fbml string The default FBML code that appears in the user's profile box when he or she adds your application. - # default_column bool Indicates whether your application appears in the wide (1) or narrow (0) column of a user's Facebook profile. (default value is 1) - # message_url string For applications that can create attachments, this is the URL where you store the attachment's content. - # message_action string For applications that can create attachments, this is the label for the action that creates the attachment. It cannot be more than 20 characters. - # about_url string This is the URL to your application's About page. About pages are now Facebook Pages. - # private_install bool Indicates whether you want to disable (1) or enable (0) News Feed and Mini-Feed stories when a user installs your application. (default value is 1) - # installable bool Indicates whether a user can (1) or cannot (0) install your application. (default value is 1) - # privacy_url string The URL to your application's privacy terms. - # help_url string The URL to your application's help page. - # see_all_url string - # tos_url string The URL to your application's Terms of Service. - # dev_mode bool Indicates whether developer mode is enabled (1) or disabled (0). Only developers can install applications in developer mode. (default value is 1) - # preload_fql string A preloaded FQL query. - class ApplicationProperties - include Model - FIELDS = [ :application_name, :callback_url, :post_install_url, :edit_url, :dashboard_url, - :uninstall_url, :ip_list, :email, :description, :use_iframe, :desktop, :is_mobile, - :default_fbml, :default_column, :message_url, :message_action, :about_url, - :private_install, :installable, :privacy_url, :help_url, :see_all_url, :tos_url, - :dev_mode, :preload_fql, :icon_url, :canvas_name, :logo_url, :connect_logo_url ] - - attr_accessor(*FIELDS) - - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/models/applicationrestrictions.rb b/vendor/plugins/facebooker/lib/facebooker/models/applicationrestrictions.rb deleted file mode 100644 index d05166834..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/applicationrestrictions.rb +++ /dev/null @@ -1,10 +0,0 @@ -module Facebooker - - class ApplicationRestrictions - include Model - FIELDS = [ :age, :location, :age_distribution, :type ] - - attr_accessor(*FIELDS) - - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/models/comment.rb b/vendor/plugins/facebooker/lib/facebooker/models/comment.rb deleted file mode 100644 index cd7ae36c9..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/comment.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'facebooker/model' -module Facebooker - ## - # A simple representation of a comment - class Comment - include Model - attr_accessor :xid, :fromid, :time, :text, :id - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/models/cookie.rb b/vendor/plugins/facebooker/lib/facebooker/models/cookie.rb deleted file mode 100644 index 901335b79..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/cookie.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'facebooker/model' -module Facebooker - - ## - # A simple representation of a cookie. - class Cookie - include Model - attr_accessor :uid, :name, :value, :expires, :path - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/models/education_info.rb b/vendor/plugins/facebooker/lib/facebooker/models/education_info.rb deleted file mode 100644 index 9f98985e4..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/education_info.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Facebooker - class EducationInfo - class HighschoolInfo - include Model - attr_accessor :hs1_id, :hs2_id, :grad_year, :hs1_name, :hs2_name - end - - include Model - attr_accessor :concentrations, :name, :year, :degree, :school_type - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/models/event.rb b/vendor/plugins/facebooker/lib/facebooker/models/event.rb deleted file mode 100644 index cbd1188a5..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/event.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'facebooker/model' -module Facebooker - class Event - - ## - # The relationship between a Facebook user and an Event to which he or she has been - # invited and may or may not be attending (based on #rsvp_status) - class Attendance - include Model - attr_accessor :eid, :uid, :rsvp_status - - ## - # Get the full, populated Event object which this Attendance is associated with. - # First access will query the Facebook API (facebook.events.get). Subsequent - # calls are retrieved from in-memory cache. - def event - @event ||= Event.from_hash(session.post('facebook.events.get', :eids => [eid]).first) - end - - #TODO: implement user() method - end - - include Model - attr_accessor :pic, :pic_small, :pic_big, :name, :creator, :update_time, :description, :tagline, :venue, :host, :event_type, :nid, :location, :end_time, :start_time, :event_subtype - - id_is :eid - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/models/family_relative_info.rb b/vendor/plugins/facebooker/lib/facebooker/models/family_relative_info.rb deleted file mode 100644 index 76b3257fd..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/family_relative_info.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'facebooker/model' -module Facebooker - class FamilyRelativeInfo - include Model - attr_accessor :relationship, :uid, :name, :birthday - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/models/friend_list.rb b/vendor/plugins/facebooker/lib/facebooker/models/friend_list.rb deleted file mode 100644 index 545704777..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/friend_list.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'facebooker/model' -module Facebooker - ## - # A simple representation of a friend list. - class FriendList - include Model - attr_accessor :name - - id_is :flid - - # We need this to be an integer, so do the conversion - def flid=(f) - @flid= ( f.nil? ? nil : f.to_i) - end - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/models/group.rb b/vendor/plugins/facebooker/lib/facebooker/models/group.rb deleted file mode 100644 index 62ec8dc12..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/group.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'facebooker/model' -module Facebooker - class Group - ## - # The model of a user's relationship to a group. Users can occupy different positions within a group (e.g. 'owner') - class Membership - include Model - attr_accessor :position, :gid, :uid - end - include Model - attr_accessor :pic, :pic_small, :pic_big, :name, :creator, :recent_news, :update_time, :group_subtype, :group_type, :website, :office, :description, :venue, :nid, :privacy - - id_is :gid - - ## - # Get the full list of members as populated User objects. First time fetches group members via Facebook API call. - # Subsequent calls return cached values. - # This is a convenience method for getting all of the Membership instances and instantiating User instances for each Membership. - def members - @members ||= memberships.map do |membership| - User.new(membership.uid, session) - end - end - - ## - # Get a list of Membership instances associated with this Group. First call retrieves the Membership instances via a Facebook - # API call. Subsequent calls are retrieved from in-memory cache. - def memberships - @memberships ||= session.post('facebook.groups.getMembers', :gid => gid).map do |hash| - Membership.from_hash(hash) do |membership| - membership.gid = gid - end - end - end - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/models/info_item.rb b/vendor/plugins/facebooker/lib/facebooker/models/info_item.rb deleted file mode 100644 index 99b4024f5..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/info_item.rb +++ /dev/null @@ -1,10 +0,0 @@ -module Facebooker - class InfoItem - include Model - attr_accessor :label, :image, :description, :link, :sublabel - - def to_json - {:label => label, :image => image, :description => description, :link => link, :sublabel => sublabel}.to_json - end - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/models/info_section.rb b/vendor/plugins/facebooker/lib/facebooker/models/info_section.rb deleted file mode 100644 index 5c699f42e..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/info_section.rb +++ /dev/null @@ -1,10 +0,0 @@ -module Facebooker - class InfoSection - include Model - attr_accessor :field, :items - - def to_json - {:field => field, :items => items}.to_json - end - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/models/location.rb b/vendor/plugins/facebooker/lib/facebooker/models/location.rb deleted file mode 100644 index 05d313506..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/location.rb +++ /dev/null @@ -1,8 +0,0 @@ -module Facebooker - ## - # Representation of Location used in all places where a Location is specified. - class Location - include Model - attr_accessor :city, :zip, :country, :state - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/models/message_thread.rb b/vendor/plugins/facebooker/lib/facebooker/models/message_thread.rb deleted file mode 100644 index 317f8dffe..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/message_thread.rb +++ /dev/null @@ -1,89 +0,0 @@ -require 'facebooker/model' -module Facebooker - class MessageThread - include Model - - id_is :thread_id - attr_accessor :subject, :updated_time, :recipients - attr_accessor :parent_message_id, :parent_thread_id, :message_count - attr_accessor :snippet, :snippet_author, :object_id, :unread - - class Message - include Model - - attr_accessor :message_id, :author_id, :body, :created_time, :attachment, :thread_id - - # An attachment can be a photo, a video, or a link - class Attachment - include Model - - attr_accessor :name, :href, :icon, :caption, :description - - # The Facebook messages API is in beta, this helper method is supposed to fail anytime soon - def video? - self.href =~ /\Ahttp:\/\/www\.facebook\.com\/video\/video\.php.*/ - end - - # The Facebook messages API is in beta, this helper method is supposed to fail anytime soon - def photo? - self.href =~ /\Ahttp:\/\/www\.facebook\.com\/photo\.php.*/ - end - - # The Facebook messages API is in beta, this helper method is supposed to fail anytime soon - def link? - !video? && !photo? - end - end - hash_settable_accessor :attachment, Facebooker::MessageThread::Message::Attachment - end - hash_settable_list_accessor :messages, Facebooker::MessageThread::Message - - end -end - -# Example of attachments - -# -- Photo -- - -# -# -# http://www.facebook.com/photo.php?pid=12345&id=54321 -# -# http://b.static.ak.fbcdn.net/rsrc.php/zB010/hash/9yvl71tw.gif -# -# -# -# - -# -- Webcam video -- - -# -# -# Feb 10, 2010 1:26pm -# http://www.facebook.com/video/video.php?v=12345 -# -# http://static.ak.fbcdn.net/rsrc.php/zB010/hash/9yvl71tw.gif -# -# -# -# - -# -- Link -- - -# -# -# -# http://www.facebook.com/l.php?u=http%253A%252F%252Fwww.google.fr%252F&h=e46dd63cdbfadb74958fbe44e98f339c -# link -# http://external.ak.fbcdn.net/safe_image.php?d=dd54bba6b6e6479a89bb8084573c02c8&w=90&h=90&url=http%3A%2F%2Fwww.google.fr%2Fintl%2Ffr_fr%2Fimages%2Flogo.gif -# -# -# Google -# http://www.facebook.com/l.php?u=http%253A%252F%252Fwww.google.fr%252F&h=e46dd63cdbfadb74958fbe44e98f339c -# www.google.fr -# -# http://b.static.ak.fbcdn.net/rsrc.php/zB010/hash/9yvl71tw.gif -# -# -# -# diff --git a/vendor/plugins/facebooker/lib/facebooker/models/notifications.rb b/vendor/plugins/facebooker/lib/facebooker/models/notifications.rb deleted file mode 100644 index 030d36255..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/notifications.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Facebooker - class Notifications - include Model - attr_accessor :messages, :group_invites, :pokes, :friend_requests, :event_invites, :shares - - [:Messages, :Pokes, :Shares].each do |notification_type| - const_set(notification_type, Class.new do - include Model - attr_accessor :unread, :most_recent - end) - attribute_name = "#{notification_type.to_s.downcase}" - define_method("#{attribute_name}=") do |value| - instance_variable_set("@#{attribute_name}", value.kind_of?(Hash) ? Notifications.const_get(notification_type).from_hash(value) : value) - end - end - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/models/page.rb b/vendor/plugins/facebooker/lib/facebooker/models/page.rb deleted file mode 100644 index 2f8a252df..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/page.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'facebooker/model' -module Facebooker - class Page - - def initialize(*args) - if args.size == 1 and (args.first.is_a?(Integer) or args.first.is_a?(String)) - self.page_id=args.first - else - super - end - end - - class Genre - include Model - FIELDS = [ :dance, :party, :relax, :talk, :think, :workout, :sing, :intimate, :raunchy, :headphones ] - attr_accessor(*FIELDS) - - def initialize(*args) - super - - # convert '1'/'0' to true/false - FIELDS.each do |field| - self.send("#{field}=", self.send(field) == '1') - end - end - end - - include Model - attr_accessor :page_id,:name,:pic_small,:pic_big,:pic_square,:pic,:pic_large,:type,:website,:has_added_app,:founded,:company_overview,:mission,:products,:location,:parking,:public_transit,:hours,:attire,:payment_options,:culinary_team,:general_manager,:price_range,:restaurant_services,:restaurant_specialties,:release_date,:genre,:starring,:screenplay_by,:directed_by,:produced_by,:studio,:awards,:plot_outline,:network,:season,:schedule,:written_by,:band_members,:hometown,:current_location,:record_label,:booking_agent,:artists_we_like,:influences,:band_interests,:bio,:affiliation,:birthday,:personal_info,:personal_interests,:members,:built,:features,:mpg,:general_info,:fan_count,:page_url - attr_reader :genre - - alias_method :id, :page_id - - def genre=(value) - @genre = value.kind_of?(Hash) ? Genre.from_hash(value) : value - end - - def user_is_admin?(user) - Session.current.post('facebook.pages.isAdmin', :page_id=>self.page_id, :uid=>Facebooker::User.cast_to_facebook_id(user)) - end - - def user_is_fan?(user) - Session.current.post('facebook.pages.isFan', :page_id=>self.page_id, :uid=>Facebooker::User.cast_to_facebook_id(user)) - end - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/models/photo.rb b/vendor/plugins/facebooker/lib/facebooker/models/photo.rb deleted file mode 100644 index 22b60ab01..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/photo.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'facebooker/model' -module Facebooker - class Photo - include Model - attr_accessor :aid, :owner, :title, - :link, :caption, :created, - :src, :src_big, :src_small, - :story_fbid - - id_is :pid - - #override the generated method for id_is to use a string - def pid=(val) - @pid = val - end - - alias :id= :pid= - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/models/tag.rb b/vendor/plugins/facebooker/lib/facebooker/models/tag.rb deleted file mode 100644 index 1bde36538..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/tag.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'facebooker/model' -module Facebooker - class Tag - include Model - attr_accessor :pid, :subject, :xcoord, :ycoord, :text, :created - - def coordinates - [xcoord, ycoord] - end - - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/models/user.rb b/vendor/plugins/facebooker/lib/facebooker/models/user.rb deleted file mode 100644 index bff50b96f..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/user.rb +++ /dev/null @@ -1,760 +0,0 @@ -require 'facebooker/model' -require 'facebooker/models/affiliation' -require 'facebooker/models/work_info' -require 'facebooker/models/family_relative_info' -module Facebooker - # - # Holds attributes and behavior for a Facebook User - class User - include Model - class Status - include Model - attr_accessor :uid, :message, :time, :status_id, :source - end - FIELDS = [:status, :political, :pic_small, :name, :quotes, :is_app_user, :tv, :profile_update_time, :meeting_sex, :hs_info, :timezone, :relationship_status, :hometown_location, :about_me, :wall_count, :significant_other_id, :pic_big, :music, :work_history, :sex, :religion, :notes_count, :activities, :pic_square, :movies, :has_added_app, :education_history, :birthday, :birthday_date, :first_name, :meeting_for, :last_name, :interests, :current_location, :pic, :books, :affiliations, :locale, :profile_url, :proxied_email, :email_hashes, :allowed_restrictions, :pic_with_logo, :pic_big_with_logo, :pic_small_with_logo, :pic_square_with_logo, :online_presence, :verified, :profile_blurb, :username, :website, :is_blocked, :family, :email] - STANDARD_FIELDS = [:uid, :first_name, :last_name, :name, :timezone, :birthday, :sex, :affiliations, :locale, :profile_url, :proxied_email, :email] - populating_attr_accessor(*FIELDS) - attr_reader :affiliations - populating_hash_settable_accessor :current_location, Location - populating_hash_settable_accessor :hometown_location, Location - populating_hash_settable_accessor :hs_info, EducationInfo::HighschoolInfo - populating_hash_settable_list_accessor :affiliations, Affiliation - populating_hash_settable_list_accessor :education_history, EducationInfo - populating_hash_settable_list_accessor :work_history, WorkInfo - populating_hash_settable_list_accessor :family, FamilyRelativeInfo - - populating_attr_reader :status - - attr_accessor :request_locale - - # Can pass in these two forms: - # id, session, (optional) attribute_hash - # attribute_hash - def initialize(*args) - @friends = nil - @current_location = nil - @pic = nil - @hometown_location = nil - @populated = false - @session = nil - @id = nil - if (args.first.kind_of?(String) || args.first.kind_of?(Integer)) && args.size==1 - self.uid = args.shift - @session = Session.current - elsif (args.first.kind_of?(String) || args.first.kind_of?(Integer)) && args[1].kind_of?(Session) - self.uid = args.shift - @session = args.shift - end - if args.last.kind_of?(Hash) - populate_from_hash!(args.pop) - end - end - - id_is :uid - alias :facebook_id :id - - # Returns a user's events, params correspond to API call parameters (except UID): - # http://wiki.developers.facebook.com/index.php/Events.get - # E.g: - # @user.events(:start_time => Time.now, :end_time => 1.month.from_now) - # # => Returns events betwen now and a month from now - def events(params={}) - @events ||= {} - [:start_time,:end_time].compact.each do |key| - params[key] = params[key].to_i - end -# puts @events[params.to_s].nil? - @events[params.to_s] ||= @session.post('facebook.events.get', {:uid => self.id}.merge(params)).map do |event| - Event.from_hash(event) - end - end - - # Rsvp to an event with the eid and rsvp_status which can be 'attending', 'unsure', or 'declined'. - # http://wiki.developers.facebook.com/index.php/Events.rsvp - # E.g: - # @user.rsvp_event('100321123', 'attending') - # # => Returns true if all went well - def rsvp_event(eid, rsvp_status, options = {}) - result = @session.post('facebook.events.rsvp', options.merge(:eid => eid, :rsvp_status => rsvp_status)) - end - - # - # Set the list of friends, given an array of User objects. If the list has been retrieved previously, will not set - def friends=(list_of_friends,flid=nil) - @friends_hash ||= {} - flid=cast_to_friend_list_id(flid) - #use __blank instead of nil so that this is cached - cache_key = flid||"__blank" - - @friends_hash[cache_key] ||= list_of_friends - end - - def cast_to_friend_list_id(flid) - case flid - when String - list=friend_lists.detect {|f| f.name==flid} - raise Facebooker::Session::InvalidFriendList unless list - list.flid - when FriendList - flid.flid - else - flid - end - end - ## - # Retrieve friends - def friends(flid = nil) - @friends_hash ||= {} - flid=cast_to_friend_list_id(flid) - - #use __blank instead of nil so that this is cached - cache_key = flid||"__blank" - options = {:uid=>self.id} - options[:flid] = flid unless flid.nil? - @friends_hash[cache_key] ||= @session.post('facebook.friends.get', options,false).map do |uid| - User.new(uid, @session) - end - @friends_hash[cache_key] - end - - def friend_ids - options = {:uid => self.id} - @session.post('facebook.friends.get', options, false) - end - - ### - # Publish a post into the stream on the user's Wall and News Feed. This - # post also appears in the user's friend's streams. The +publish_stream+ - # extended permission must be granted in order to use this method. - # - # See: http://wiki.developers.facebook.com/index.php/Stream.publish - # - # +target+ can be the current user or some other user. - # - # To publish to a Page on the Page's behave, specify the page id as - # :uid and set :post_as_page to 'true', use the current user as target - # - # Example: - # # Publish a message to my own wall: - # me.publish_to(me, :message => 'hello world') - # - # # Publish to a friend's wall with an action link: - # me.publish_to(my_friend, :message => 'how are you?', :action_links => [ - # :text => 'my website', - # :href => 'http://tenderlovemaking.com/' - # ]) - def publish_to(target, options = {}) - @session.post('facebook.stream.publish', prepare_publish_to_options(target, options), false) - end - - # Prepares options for the stream.publish - def prepare_publish_to_options(target, options) - opts = {:uid => self.id, - :target_id => target.id, - :message => options[:message]} - - if a = options[:attachment] - opts[:attachment] = convert_attachment_to_json(a) - end - if (links = options[:action_links] && Facebooker.json_encode(options[:action_links])) - opts[:action_links] = links - end - unless options[:uid].nil? - opts[:uid] = options[:uid] - end - if options[:post_as_page] - opts.delete(:target_id) - end - opts - end - - def convert_attachment_to_json(attachment) - a = attachment.respond_to?(:to_hash) ? attachment.to_hash : attachment - Facebooker.json_encode(a) - end - - ### - # Publish a comment on a post - # - # See: http://wiki.developers.facebook.com/index.php/Stream.addComment - # - # +post_id+ the post_id for the post that is being commented on - # +comment+ the text of the comment - def comment_on(post_id, comment) - @session.post('facebook.stream.addComment', {:post_id=>post_id, :comment=>comment}) - end - - - ### - # Publish a comment to a specific comment set by xid - # - # See: http://wiki.developers.facebook.com/index.php/Comments.add - # - # +xid+ the xid for the set of comments - # +text+ the text of the comment - def add_comment(xid, text,title=nil,url=nil,publish_to_stream=false) - @session.post('facebook.comments.add',{:xid=>xid,:text=>text,:title=>title,:url=>url,:publish_to_stream=>publish_to_stream}) - end - - ### - # Add a like on a post - # - # See: http://wiki.developers.facebook.com/index.php/Stream.addLike - # - # +post_id+ the post_id for the post that is being commented on - def add_like_on(post_id) - @session.post('facebook.stream.addLike', {:post_id=>post_id}) - end - - ### - # Remove a like on a post - # - # See: http://wiki.developers.facebook.com/index.php/Stream.removeLike - # - # +post_id+ the post_id for the post that is being commented on - def remove_like_on(post_id) - @session.post('facebook.stream.removeLike', {:post_id=>post_id}) - end - - def friend_lists - @friend_lists ||= @session.post('facebook.friends.getLists').map do |hash| - friend_list = FriendList.from_hash(hash) - friend_list.session = session - friend_list - end - end - ### - # Retrieve friends with user info populated - # Subsequent calls will be retrieved from memory. - # Optional: list of fields to retrieve as symbols - def friends!(*fields) - @friends ||= session.post('facebook.users.getInfo', :fields => collect(fields), :uids => friends.map{|f| f.id}.join(',')).map do |hash| - User.new(hash['uid'], session, hash) - end - end - - ### - # Retrieve profile data for logged in user - # Optional: list of fields to retrieve as symbols - def populate(*fields) - arguments = {:fields => collect(fields), :uids => id} - arguments[:locale]=request_locale unless request_locale.nil? - session.post('facebook.users.getInfo', arguments) do |response| - populate_from_hash!(response.first) - end - end - - def friends_with?(user_or_id) - friends.map{|f| f.to_i}.include?(user_or_id.to_i) - end - - def friends_with_this_app - @friends_with_this_app ||= friend_ids_with_this_app.map do |uid| - User.new(uid, session) - end - end - - def friend_ids_with_this_app - @friend_ids_with_this_app ||= session.post('facebook.friends.getAppUsers') - end - - def groups(gids = []) - args = gids.empty? ? {} : {:gids => gids} - @groups ||= session.post('facebook.groups.get', args).map do |hash| - group = Group.from_hash(hash) - group.session = session - group - end - end - - ### - # Get threads in a folder - # - # See: http://wiki.developers.facebook.com/index.php/Message.getThreadsInFolder - # - # +options+ possible options are :folder_id, :limit and :offset - def threads(options = {}) - options ||= {} - @threads = session.post('facebook.message.getThreadsInFolder', options) do |response| - response.map do |hash| - MessageThread.from_hash(hash) - end - end - end - - def notifications - @notifications ||= Notifications.from_hash(session.post('facebook.notifications.get')) - end - - def publish_story(story) - publish(story) - end - - def publish_action(action) - publish(action) - end - - def publish_templatized_action(action) - publish(action) - end - - def albums - @albums ||= session.post('facebook.photos.getAlbums', :uid => self.id) do |response| - response.map do |hash| - Album.from_hash(hash) - end - end - end - - ### - # Retrieve user's facebook stream - # See http://wiki.developers.facebook.com/index.php/Stream.get for options - # - - def stream(options = {}) - @stream = session.post('facebook.stream.get', prepare_get_stream_options(options)) do |response| - response - end - end - - def create_album(params) - @album = session.post('facebook.photos.createAlbum', params) {|response| Album.from_hash(response)} - end - - def profile_photos - session.get_photos(nil, nil, profile_pic_album_id) - end - - # Upload a photo to the user's profile. - # - # In your view, create a multipart form that posts directly to your application (not through canvas): - # - # <% form_tag photos_url(:canvas => false), :html => {:multipart => true, :promptpermission => 'photo_upload'} do %> - # Photo: <%= file_field_tag 'photo' %> - # Caption: <%= text_area_tag 'caption' %> - # <%= submit_tag 'Upload Photo', :class => 'inputsubmit' %> - # <% end %> - # - # And in your controller: - # - # class PhotosController < ApplicationController - # def create - # file = Net::HTTP::MultipartPostFile.new( - # params[:photo].original_filename, - # params[:photo].content_type, - # params[:photo].read - # ) - # - # @photo = facebook_session.user.upload_photo(file, :caption => params[:caption]) - # redirect_to photos_url(:canvas => true) - # end - # end - # - # Options correspond to http://wiki.developers.facebook.com/index.php/Photos.upload - def upload_photo(multipart_post_file, options = {}) - Photo.from_hash(session.post_file('facebook.photos.upload', - options.merge(nil => multipart_post_file))) - end - - # Upload a video to the user's profile. - # - # In your view, create a multipart form that posts directly to your application (not through canvas): - # - # <% form_tag videos_url(:canvas => false), :html => {:multipart => true, :promptpermission => 'video_upload'} do %> - # Video: <%= file_field_tag 'video' %> - # Title: <%= text_area_tag 'title' %> - # Description: <%= text_area_tag 'description' %> - # <%= submit_tag 'Upload Video', :class => 'inputsubmit' %> - # <% end %> - # - # And in your controller: - # - # class VideosController < ApplicationController - # def create - # file = Net::HTTP::MultipartPostFile.new( - # params[:photo].original_filename, - # params[:photo].content_type, - # params[:photo].read - # ) - # - # @video = facebook_session.user.upload_video(file, :description => params[:description]) - # redirect_to videos_url(:canvas => true) - # end - # end - # - # Options correspond to http://wiki.developers.facebook.com/index.php/Video.upload - def upload_video(multipart_post_file, options = {}) - Video.from_hash(session.post_file('facebook.video.upload', - options.merge(nil => multipart_post_file, :base => Facebooker.video_server_base))) - end - - def profile_fbml - session.post('facebook.profile.getFBML', :uid => id) - end - - ## - # Set the profile FBML for this user - # - # This does not set profile actions, that should be done with profile_action= - def profile_fbml=(markup) - set_profile_fbml(markup, nil, nil, nil) - end - - ## - # Set the mobile profile FBML - def mobile_fbml=(markup) - set_profile_fbml(nil, markup, nil,nil) - end - - def profile_action=(markup) - set_profile_fbml(nil, nil, markup,nil) - end - - def profile_main=(markup) - set_profile_fbml(nil,nil,nil,markup) - end - - def set_profile_fbml(profile_fbml, mobile_fbml, profile_action_fbml, profile_main = nil) - parameters = {:uid => id} - parameters[:profile] = profile_fbml if profile_fbml - parameters[:profile_action] = profile_action_fbml if profile_action_fbml - parameters[:mobile_profile] = mobile_fbml if mobile_fbml - parameters[:profile_main] = profile_main if profile_main - session.post('facebook.profile.setFBML', parameters,false) - end - - ## ** NEW PROFILE DESIGN *** - # Set a info section for this user - # - # Note: using set_profile_info as I feel using user.set_info could be confused with the user.getInfo facebook method. - # Also, I feel it fits in line with user.set_profile_fbml. - def set_profile_info(title, info_fields, format = :text) - session.post('facebook.profile.setInfo', :title => title, :uid => id, - :type => format.to_s == "text" ? 1 : 5, :info_fields => info_fields.to_json) - end - - def get_profile_info - session.post('facebook.profile.getInfo', :uid => id) - end - - ## - # This DOES NOT set the status of a user on Facebook - # Use the set_status method instead - def status=(message) - case message - when String,Status - @status = message - when Hash - @status = Status.from_hash(message) - end - end - - - ## - # Return +limit+ statuses from the user - def statuses( limit = 50 ) - session.post('facebook.status.get', {:uid => uid, :limit => limit}).collect { |ret| Status.from_hash(ret) } - end - - ## - # Set the status for a user - # DOES NOT prepend "is" to the message - # - # requires extended permission. - def set_status(message) - self.status=message - session.post('facebook.users.setStatus',{:status=>message,:status_includes_verb=>1,:uid => uid}, false) do |ret| - ret - end - end - - ## - # Checks to see if the user has enabled the given extended permission - def has_permission?(ext_perm) # ext_perm = email, offline_access, status_update, photo_upload, create_listing, create_event, rsvp_event, sms - session.post('facebook.users.hasAppPermission', {:ext_perm=>ext_perm, :uid => uid}, false) == "1" - end - - ## - # Returns whether the user (either the session user or user specified by uid) has authorized the calling application - def app_user? - session.post('facebook.users.isAppUser', {:uid => self.id}, use_session_key = true) - end - - ## - # Convenience method to check multiple permissions at once - def has_permissions?(ext_perms) - ext_perms.all?{|p| has_permission?(p)} - end - - ## - ## Revoke any extended permission given by a user - def revoke_permission(ext_perm) - session.post('facebook.auth.revokeExtendedPermission', { :perm => ext_perm, :uid => uid }, false) - end - - ## - # Convenience method to send email to the current user - def send_email(subject, text=nil, fbml=nil) - session.send_email([id], subject, text, fbml) - end - - ## - # Convenience method to set cookie for the current user - def set_cookie(name, value, expires=nil, path=nil) - session.data.set_cookie(id, name, value, expires, path) - end - - ## - # Convenience method to get cookies for the current user - def get_cookies(name=nil) - session.data.get_cookies(id, name) - end - - ## - # Returns the user's id as an integer - def to_i - id - end - - def to_s - id.to_s - end - - - ### NEW DASHBOARD API STUFF - - # facebook_session.user.dashboard_count - def dashboard_count - session.post('facebook.dashboard.getCount', :uid => uid) - end - - # facebook_session.user.dashboard_count = 5 - def dashboard_count=(new_count) - session.post('facebook.dashboard.setCount', :uid => uid, :count => new_count) - end - - # facebook_session.user.dashboard_increment_count - def dashboard_increment_count - session.post('facebook.dashboard.incrementCount', :uid => uid) - end - - # facebook_session.user.dashboard_decrement_count - def dashboard_decrement_count - session.post('facebook.dashboard.decrementCount', :uid => uid) - end - - # The following methods are not bound to a specific user but do relate to Users in general, - # so I've made them into class methods. - - # Facebooker::User.dashboard_multi_get_count ['1234', '5678'] - def self.dashboard_multi_get_count(*uids) - Facebooker::Session.create.post("facebook.dashboard.multiGetCount", :uids => uids.flatten) - end - - # Facebooker::User.dashboard_multi_set_count({ '1234' => '11', '5678' => '22' }) - def self.dashboard_multi_set_count(ids) - Facebooker::Session.create.post("facebook.dashboard.multiSetCount", :ids => ids.to_json) - end - - # Facebooker::User.dashboard_multi_increment_count ['1234', '5678'] - def self.dashboard_multi_increment_count(*uids) - Facebooker::Session.create.post("facebook.dashboard.multiIncrementCount", :uids => uids.flatten.collect{ |uid| uid.to_s }.to_json) - end - - # Facebooker::User.dashboard_multi_decrement_count ['1234', '5678'] - def self.dashboard_multi_decrement_count(*uids) - Facebooker::Session.create.post("facebook.dashboard.multiDecrementCount", :uids => uids.flatten.collect{ |uid| uid.to_s }.to_json) - end - - - - - def get_news(*news_ids) - params = { :uid => uid } - params[:news_ids] = news_ids.flatten if news_ids - - session.post('facebook.dashboard.getNews', params) - end - - # facebook_session.user.add_news [{ :message => 'Hey, who are you?', :action_link => { :text => "I-I'm a test user", :href => 'http://facebook.er/' }}], 'http://facebook.er/icon.png' - def add_news(news, image=nil) - params = { :uid => uid } - params[:news] = news - params[:image] = image if image - - session.post('facebook.dashboard.addNews', params) - end - - # facebook_session.user.clear_news ['111111'] - def clear_news(*news_ids) - params = { :uid => uid } - params[:news_ids] = news_ids.flatten if news_ids - - session.post('facebook.dashboard.clearNews', params) - end - - # Facebooker::User.multi_add_news(['1234', '4321'], [{ :message => 'Hi users', :action_link => { :text => "Uh hey there app", :href => 'http://facebook.er/' }}], 'http://facebook.er/icon.png') - def self.multi_add_news(uids, news, image=nil) - params = { :uids => uids, :news => news } - params[:image] = image if image - - Facebooker::Session.create.post("facebook.dashboard.multiAddNews", params) - end - - # Facebooker::User.multi_clear_news({"1234"=>["319103117527"], "4321"=>["313954287803"]}) - def self.multi_clear_news(ids) - Facebooker::Session.create.post("facebook.dashboard.multiClearNews", :ids => ids.to_json) - end - - # Facebooker::User.multi_get_news({"1234"=>["319103117527"], "4321"=>["313954287803"]}) - def self.multi_get_news(ids) - Facebooker::Session.create.post('facebook.dashboard.multiGetNews', :ids => ids.to_json) - end - - # facebook_session.user.get_activity '123' - def get_activity(*activity_ids) - params = {} - params[:activity_ids] = activity_ids.flatten if activity_ids - - session.post('facebook.dashboard.getActivity', params) - end - - # facebook_session.user.publish_activity({ :message => '{*actor*} rolled around', :action_link => { :text => 'Roll around too', :href => 'http://facebook.er/' }}) - def publish_activity(activity) - session.post('facebook.dashboard.publishActivity', { :activity => activity.to_json }) - end - - # facebook_session.user.remove_activity ['123'] - def remove_activity(*activity_ids) - session.post('facebook.dashboard.removeActivity', { :activity_ids => activity_ids.flatten }) - end - - - ## - # Two Facebooker::User objects should be considered equal if their Facebook ids are equal - def ==(other_user) - other_user.is_a?(User) && id == other_user.id - end - - - # register a user with Facebook - # users should be a hast with at least an :email field - # you can optionally provide an :account_id field as well - - def self.register(users) - user_map={} - users=users.map do |h| - returning h.dup do |d| - if email=d.delete(:email) - hash = hash_email(email) - user_map[hash]=h - d[:email_hash]=hash - end - end - end - Facebooker::Session.create.post("facebook.connect.registerUsers",:accounts=>users.to_json) do |ret| - ret.each do |hash| - user_map.delete(hash) - end - unless user_map.empty? - e=Facebooker::Session::UserRegistrationFailed.new - e.failed_users = user_map.values - raise e - end - ret - end - end - - # Get a count of unconnected friends - def getUnconnectedFriendsCount - session.post("facebook.connect.getUnconnectedFriendsCount") - end - - - # Unregister an array of email hashes - def self.unregister(email_hashes) - Facebooker::Session.create.post("facebook.connect.unregisterUsers",:email_hashes=>email_hashes.to_json) do |ret| - ret.each do |hash| - email_hashes.delete(hash) - end - unless email_hashes.empty? - e=Facebooker::Session::UserUnRegistrationFailed.new - e.failed_users = email_hashes - raise e - end - ret - end - end - - # unregister an array of email addresses - def self.unregister_emails(emails) - emails_hash = {} - emails.each {|e| emails_hash[hash_email(e)] = e} - begin - unregister(emails_hash.keys).collect {|r| emails_hash[r]} - rescue - # re-raise with emails instead of hashes. - e = Facebooker::Session::UserUnRegistrationFailed.new - e.failed_users = $!.failed_users.collect { |f| emails_hash[f] } - raise e - end - end - - def self.hash_email(email) - email = email.downcase.strip - crc=Zlib.crc32(email) - md5=Digest::MD5.hexdigest(email) - "#{crc}_#{md5}" - end - - def self.cast_to_facebook_id(object) - if object.respond_to?(:facebook_id) - object.facebook_id - else - object - end - end - - def self.user_fields(fields = []) - valid_fields(fields) - end - - def self.standard_fields(fields = []) - valid_fields(fields,STANDARD_FIELDS) - end - - private - def publish(feed_story_or_action) - session.post(Facebooker::Feed::METHODS[feed_story_or_action.class.name.split(/::/).last], feed_story_or_action.to_params) == "1" ? true : false - end - - def self.valid_fields(fields, allowable=FIELDS) - allowable.reject{|field_name| !fields.empty? && !fields.include?(field_name)}.join(',') - end - - def collect(fields, allowable=FIELDS) - allowable.reject{|field_name| !fields.empty? && !fields.include?(field_name)}.join(',') - end - - def profile_pic_album_id - merge_aid(-3, id) - end - - def merge_aid(aid, uid) - (uid << 32) + (aid & 0xFFFFFFFF) - end - - def prepare_get_stream_options(options) - opts = {} - - opts[:viewer_id] = self.id - opts[:source_ids] = options[:source_ids] if options[:source_ids] - opts[:start_time] = options[:start_time].to_i if options[:start_time] - opts[:end_time] = options[:end_time].to_i if options[:end_time] - opts[:limit] = options[:limit] if options[:limit].is_a?(Integer) - opts[:metadata] = Facebooker.json_encode(options[:metadata]) if options[:metadata] - opts - end - - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/models/video.rb b/vendor/plugins/facebooker/lib/facebooker/models/video.rb deleted file mode 100644 index 4e1ba107f..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/video.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'facebooker/model' -module Facebooker - class Video - include Model - attr_accessor :vid, :owner, :title, - :link, :description, :created, - :story_fbid - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/models/work_info.rb b/vendor/plugins/facebooker/lib/facebooker/models/work_info.rb deleted file mode 100644 index f8527646a..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/models/work_info.rb +++ /dev/null @@ -1,10 +0,0 @@ -module Facebooker - class WorkInfo - include Model - attr_accessor :end_date, :start_date, :company_name, :description, :position - attr_reader :location - def location=(location) - @location = location.kind_of?(Hash) ? Location.from_hash(location) : location - end - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/parser.rb b/vendor/plugins/facebooker/lib/facebooker/parser.rb deleted file mode 100644 index d25db558c..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/parser.rb +++ /dev/null @@ -1,995 +0,0 @@ -require 'facebooker/session' - -begin - require 'nokogiri' -rescue Exception - require 'rexml/document' -end - -module Facebooker - class Parser - - module REXMLElementExtensions # :nodoc: - def content - self.text || '' - end - - def [] key - attributes[key] - end - - def text? - false - end - end - - module REXMLTextExtensions # :nodoc: - def text? - true - end - end - - if Object.const_defined?(:REXML) && REXML.const_defined?(:Element) - ::REXML::Element.__send__(:include, REXMLElementExtensions) - ::REXML::Text.__send__(:include, REXMLTextExtensions) - end - - def self.parse(method, data) - Errors.process(data) - parser = Parser::PARSERS[method] - raise "Can't find a parser for '#{method}'" unless parser - parser.process(data) - end - - def self.array_of(response_element, element_name) - values_to_return = [] - response_element.children.each do |element| - next if element.text? - next unless element.name == element_name - values_to_return << yield(element) - end - values_to_return - end - - def self.array_of_text_values(response_element, element_name) - array_of(response_element, element_name) do |element| - element.content.strip - end - end - - def self.array_of_hashes(response_element, element_name) - array_of(response_element, element_name) do |element| - hashinate(element) - end - end - - def self.element(name, data) - data = data.body rescue data # either data or an HTTP response - if Object.const_defined?(:Nokogiri) - xml = Nokogiri::XML(data.strip) - if node = xml.at(name) - return node - end - if xml.root.name == name - return xml.root - end - else - doc = REXML::Document.new(data) - doc.elements.each(name) do |element| - return element - end - end - raise "Element #{name} not found in #{data}" - end - - def self.hash_or_value_for(element) - if element.children.size == 1 && element.children.first.text? - element.content.strip - else - # We can have lists in not list item - if element['list'] == 'true' - element.children.reject{|c| c.text? }.map { |subchild| hash_or_value_for(subchild)} - else - hashinate(element) - end - end - end - - def self.hashinate(response_element) - response_element.children.reject{|c| c.text? }.inject({}) do |hash, child| - # If the node hasn't any child, and is not a list, we want empty strings, not empty hashes, - # except if attributes['nil'] == true - hash[child.name] = - if (child['nil'] == 'true') - nil - elsif (child.children.size == 1 && child.children.first.text?) || (child.children.size == 0 && child['list'] != 'true') - anonymous_field_from(child, hash) || child.content.strip - elsif child['list'] == 'true' - child.children.reject{|c| c.text? }.map { |subchild| hash_or_value_for(subchild)} - else - child.children.reject{|c| c.text? }.inject({}) do |subhash, subchild| - subhash[subchild.name] = hash_or_value_for(subchild) - subhash - end - end #if (child.attributes) - hash - end #do |hash, child| - end - - def self.hash_by_key_or_value_for(element, convert_1_to_true=false) - if element.children.size == 0 - { element['key'] => nil } - elsif element.children.size == 1 && element.children.first.text? - { element['key'] => (convert_1_to_true ? element.content.strip == '1' : element.content.strip) } - else - hashinate_by_key(element, convert_1_to_true) - end - end - - # A modification to hashinate. The new dashboard API returns XML in a different format than - # the other calls. What used to be the element name has become an attribute called "key". - def self.hashinate_by_key(response_element, convert_1_to_true=false) - response_element.children.reject{|c| c.text? }.inject({}) do |hash, child| - - # If the node hasn't any child, and is not a list, we want empty strings, not empty hashes, - # except if attributes['nil'] == true - hash[child['key']] = - if (child['nil'] == 'true') - nil - elsif (child.children.size == 1 && child.children.first.text?) || (child.children.size == 0 && child['list'] != 'true') - anonymous_field_from(child, hash) || (convert_1_to_true ? child.content.strip == '1' : child.content.strip) - elsif child['list'] == 'true' && child.children.reject {|subchild| subchild.text?}.all? { |subchild| !subchild.text? && subchild['key'].nil? } - child.children.reject{|c| c.text? }.map { |subchild| hash_by_key_or_value_for(subchild, convert_1_to_true)} - elsif child['list'] == 'true' - hash_by_key_or_value_for(child, convert_1_to_true) - else - child.children.reject{|c| c.text? }.inject({}) do |subhash, subchild| - subhash[subchild['key']] = hash_by_key_or_value_for(subchild, convert_1_to_true) - subhash - end - end - hash - end - end - - - - def self.booleanize(response) - response == "1" ? true : false - end - - def self.anonymous_field_from(child, hash) - if child.name == 'anon' - (hash[child.name] || []) << child.content.strip - end - end - - end - - class RevokeAuthorization < Parser#:nodoc: - def self.process(data) - booleanize(data) - end - end - - class RevokeExtendedPermission < Parser#:nodoc: - def self.process(data) - booleanize(element('auth_revokeExtendedPermission_response', data).content.strip) - end - end - - class CreateToken < Parser#:nodoc: - def self.process(data) - element('auth_createToken_response', data).content.strip - end - end - - class RegisterUsers < Parser - def self.process(data) - array_of_text_values(element("connect_registerUsers_response", data), "connect_registerUsers_response_elt") - end - end - - class UnregisterUsers < Parser - def self.process(data) - array_of_text_values(element("connect_unregisterUsers_response", data), "connect_unregisterUsers_response_elt") - end - end - - class GetUnconnectedFriendsCount < Parser - def self.process(data) - hash_or_value_for(element("connect_getUnconnectedFriendsCount_response",data)).to_i - end - end - - class GetSession < Parser#:nodoc: - def self.process(data) - hashinate(element('auth_getSession_response', data)) - end - end - - class IsAppUser < Parser#:nodoc: - def self.process(data) - element('users_isAppUser_response', data).content == '1' - end - end - - class GetFriends < Parser#:nodoc: - def self.process(data) - array_of_text_values(element('friends_get_response', data), 'uid') - end - end - - class FriendListsGet < Parser#:nodoc: - def self.process(data) - array_of_hashes(element('friends_getLists_response', data), 'friendlist') - end - end - - class UserInfo < Parser#:nodoc: - def self.process(data) - array_of_hashes(element('users_getInfo_response', data), 'user') - end - end - - class UserStandardInfo < Parser#:nodoc: - def self.process(data) - array_of_hashes(element('users_getStandardInfo_response', data), 'standard_user_info') - end - end - - class GetLoggedInUser < Parser#:nodoc: - def self.process(data) - Integer(element('users_getLoggedInUser_response', data).content.strip) - end - end - - class PagesIsAdmin < Parser#:nodoc: - def self.process(data) - element('pages_isAdmin_response', data).content.strip == '1' - end - end - - class PagesGetInfo < Parser#:nodoc: - def self.process(data) - array_of_hashes(element('pages_getInfo_response', data), 'page') - end - end - - class PagesIsFan < Parser#:nodoc: - def self.process(data) - element('pages_isFan_response', data).content.strip == '1' - end - end - - class PublishStoryToUser < Parser#:nodoc: - def self.process(data) - element('feed_publishStoryToUser_response', data).content.strip - end - end - - class StreamPublish < Parser#:nodoc: - def self.process(data) - element('stream_publish_response', data).content.strip - end - end - - class StreamAddComment < Parser#:nodoc: - def self.process(data) - element('stream_addComment_response', data).content.strip - end - end - - class StreamAddLike < Parser#:nodoc: - def self.process(data) - element('stream_addLike_response', data).content.strip - end - end - - class StreamRemoveLike < Parser#:nodoc: - def self.process(data) - booleanize(element('stream_removeLike_response', data).content.strip) - end - end - - class RegisterTemplateBundle < Parser#:nodoc: - def self.process(data) - element('feed_registerTemplateBundle_response', data).content.to_i - end - end - - class GetRegisteredTemplateBundles < Parser - def self.process(data) - array_of_hashes(element('feed_getRegisteredTemplateBundles_response',data), 'template_bundle') - end - end - - class DeactivateTemplateBundleByID < Parser#:nodoc: - def self.process(data) - element('feed_deactivateTemplateBundleByID_response', data).content.strip == '1' - end - end - - class PublishUserAction < Parser#:nodoc: - def self.process(data) - element('feed_publishUserAction_response', data).children[1].content.strip == "1" - end - end - - class UploadNativeStrings < Parser#:nodoc: - def self.process(data) - element('intl_uploadNativeStrings_response', data).content.strip - end - end - - class PublishActionOfUser < Parser#:nodoc: - def self.process(data) - element('feed_publishActionOfUser_response', data).content.strip - end - end - - class PublishTemplatizedAction < Parser#:nodoc: - def self.process(data) - element('feed_publishTemplatizedAction_response', data).children[1].content.strip - end - end - - class SetAppProperties < Parser#:nodoc: - def self.process(data) - element('admin_setAppProperties_response', data).content.strip - end - end - - class GetAppProperties < Parser#:nodoc: - def self.process(data) - element('admin_getAppProperties_response', data).content.strip - end - end - - class SetRestrictionInfo < Parser#:nodoc: - def self.process(data) - element('admin_setRestrictionInfo_response', data).content.strip - end - end - - class GetRestrictionInfo < Parser#:nodoc: - def self.process(data) - element('admin_getRestrictionInfo_response', data).content.strip - end - end - - class GetAllocation < Parser#:nodoc: - def self.process(data) - element('admin_getAllocation_response', data).content.strip - end - end - - class GetPublicInfo < Parser#:nodoc: - def self.process(data) - hashinate(element('application_getPublicInfo_response', data)) - end - end - - class CommentsAdd < Parser#:nodoc: - def self.process(data) - element('comments_add_response', data).content.strip - end - end - - class CommentsRemove < Parser#:nodoc: - def self.process(data) - booleanize(data) - end - end - - class CommentsGet < Parser#:nodoc: - def self.process(data) - array_of_hashes(element('comments_get_response', data), 'comment') - end - end - - class BatchRun < Parser #:nodoc: - class << self - def current_batch=(current_batch) - Thread.current[:facebooker_current_batch]=current_batch - end - def current_batch - Thread.current[:facebooker_current_batch] - end - end - def self.process(data) - array_of_text_values(element('batch_run_response',data),"batch_run_response_elt").each_with_index do |response,i| - batch_request=current_batch[i] - body=Struct.new(:body).new - body.body=response - begin - batch_request.result=Parser.parse(batch_request.method,body) - rescue Exception=>ex - batch_request.exception_raised=ex - end - end - end - end - - class GetAppUsers < Parser#:nodoc: - def self.process(data) - array_of_text_values(element('friends_getAppUsers_response', data), 'uid') - end - end - - class MessageGetThreadsInFolder < Parser#:nodoc: - def self.process(data) - array_of_hashes(element('message_getThreadsInFolder_response', data), 'thread') - end - end - - class NotificationsGet < Parser#:nodoc: - def self.process(data) - hashinate(element('notifications_get_response', data)) - end - end - - class NotificationsSend < Parser#:nodoc: - def self.process(data) - element('notifications_send_response', data).content.strip - end - end - - class NotificationsSendEmail < Parser#:nodoc: - def self.process(data) - element('notifications_sendEmail_response', data).content.strip - end - end - - class GetTags < Parser#nodoc: - def self.process(data) - array_of_hashes(element('photos_getTags_response', data), 'photo_tag') - end - end - - class AddTags < Parser#nodoc: - def self.process(data) - element('photos_addTag_response', data) - end - end - - class GetPhotos < Parser#nodoc: - def self.process(data) - array_of_hashes(element('photos_get_response', data), 'photo') - end - end - - class GetAlbums < Parser#nodoc: - def self.process(data) - array_of_hashes(element('photos_getAlbums_response', data), 'album') - end - end - - class GetStream < Parser #:nodoc: - def self.process(data) - response = {} - response[:albums] = array_of_hashes(element('stream_get_response/albums', data), 'album') - response[:posts] = array_of_hashes(element('stream_get_response/posts', data), 'stream_post') - response[:profile] = array_of_hashes(element('stream_get_response/profiles', data), 'profile') - response - end - end - - class CreateAlbum < Parser#:nodoc: - def self.process(data) - hashinate(element('photos_createAlbum_response', data)) - end - end - - class UploadPhoto < Parser#:nodoc: - def self.process(data) - hashinate(element('photos_upload_response', data)) - end - end - - class UploadVideo < Parser#:nodoc: - def self.process(data) - hashinate(element('video_upload_response', data)) - end - end - - class SendRequest < Parser#:nodoc: - def self.process(data) - element('notifications_sendRequest_response', data).content.strip - end - end - - class ProfileFBML < Parser#:nodoc: - def self.process(data) - element('profile_getFBML_response', data).content.strip - end - end - - class ProfileFBMLSet < Parser#:nodoc: - def self.process(data) - element('profile_setFBML_response', data).content.strip - end - end - - class ProfileInfo < Parser#:nodoc: - def self.process(data) - hashinate(element('profile_getInfo_response info_fields', data)) - end - end - - class ProfileInfoSet < Parser#:nodoc: - def self.process(data) - element('profile_setInfo_response', data).content.strip - end - end - - class FqlQuery < Parser#nodoc - def self.process(data) - root = element('fql_query_response', data) - first_child = root.children.reject{|c| c.text? }.first - first_child.nil? ? [] : [first_child.name, array_of_hashes(root, first_child.name)] - end - end - - class FqlMultiquery < Parser#nodoc - def self.process(data) - root = element('fql_multiquery_response', data) - root.children.reject { |child| child.text? }.map do |elm| - elm.children.reject { |child| child.text? }.map do |query| - if 'name' == query.name - query.text - else - list = query.children.reject { |child| child.text? } - if list.length == 0 - [] - else - [list.first.name, array_of_hashes(query, list.first.name)] - end - end - end - end - end - end - - class SetRefHandle < Parser#:nodoc: - def self.process(data) - element('fbml_setRefHandle_response', data).content.strip - end - end - - class RefreshRefURL < Parser#:nodoc: - def self.process(data) - element('fbml_refreshRefUrl_response', data).content.strip - end - end - - class RefreshImgSrc < Parser#:nodoc: - def self.process(data) - element('fbml_refreshImgSrc_response', data).content.strip - end - end - - class SetCookie < Parser#:nodoc: - def self.process(data) - element('data_setCookie_response', data).content.strip - end - end - - class GetCookies < Parser#:nodoc: - def self.process(data) - array_of_hashes(element('data_getCookies_response', data), 'cookies') - end - end - - class EventsRsvp < Parser#:nodoc: - def self.process(data) - booleanize(element('events_rsvp_response', data).content.strip) - end - end - - class EventsCreate < Parser#:nodoc: - def self.process(data) - element('events_create_response', data).content.strip - end - end - - class EventsCancel < Parser#:nodoc: - def self.process(data) - element('events_cancel_response', data).content.strip - end - end - - class EventsGet < Parser#:nodoc: - def self.process(data) - array_of_hashes(element('events_get_response', data), 'event') - end - end - - class EventsEdit < Parser#:nodoc: - def self.process(data) - booleanize(element('events_edit_response', data).content.strip) - end - end - - class EventsInvite < Parser#:nodoc: - def self.process(data) - booleanize(element('events_invite_response', data).content.strip) - end - end - - class GroupGetMembers < Parser#:nodoc: - def self.process(data) - root = element('groups_getMembers_response', data) - result = ['members', 'admins', 'officers', 'not_replied'].map do |position| - array_of(root, position) {|element| element}.map do |element| - array_of_text_values(element, 'uid').map do |uid| - {:position => position}.merge(:uid => uid) - end - end - end.flatten - end - end - - class EventMembersGet < Parser#:nodoc: - def self.process(data) - root = element('events_getMembers_response', data) - result = ['attending', 'declined', 'unsure', 'not_replied'].map do |rsvp_status| - array_of(root, rsvp_status) {|element| element}.map do |element| - array_of_text_values(element, 'uid').map do |uid| - {:rsvp_status => rsvp_status}.merge(:uid => uid) - end - end - end.flatten - end - end - - class GroupsGet < Parser#:nodoc: - def self.process(data) - array_of_hashes(element('groups_get_response', data), 'group') - end - end - - class AreFriends < Parser#:nodoc: - def self.process(data) - array_of_hashes(element('friends_areFriends_response', data), 'friend_info').inject({}) do |memo, hash| - memo[[Integer(hash['uid1']), Integer(hash['uid2'])].sort] = are_friends?(hash['are_friends']) - memo - end - end - - private - def self.are_friends?(raw_value) - if raw_value == '1' - true - elsif raw_value == '0' - false - else - nil - end - end - end - - class SetStatus < Parser - def self.process(data) - element('users_setStatus_response',data).content.strip == '1' - end - end - - class GetStatus < Parser # :nodoc: - def self.process(data) - array_of_hashes(element('status_get_response',data),'user_status') - end - end - - class GetPreference < Parser#:nodoc: - def self.process(data) - element('data_getUserPreference_response', data).content.strip - end - end - - class SetPreference < Parser#:nodoc: - def self.process(data) - element('data_setUserPreference_response', data).content.strip - end - end - - class UserHasPermission < Parser - def self.process(data) - element('users_hasAppPermission_response', data).content.strip - end - end - - class SmsSend < Parser#:nodoc: - def self.process(data) - element('sms_send_response', data).content.strip == '0' - end - end - - class SmsCanSend < Parser#:nodoc: - def self.process(data) - element('sms_canSend_response', data).content.strip - end - end - - class DashboardGetCount < Parser - def self.process(data) - element('dashboard_getCount_response', data).content.strip - end - end - - class DashboardSetCount < Parser - def self.process(data) - element('dashboard_setCount_response', data).content.strip == '1' - end - end - - class DashboardIncrementCount < Parser - def self.process(data) - element('dashboard_incrementCount_response', data).content.strip == '1' - end - end - - class DashboardDecrementCount < Parser - def self.process(data) - element('dashboard_decrementCount_response', data).content.strip == '1' - end - end - - class DashboardMultiGetCount < Parser - def self.process(data) - hashinate_by_key(element('dashboard_multiGetCount_response', data)) - end - end - - class DashboardMultiSetCount < Parser - def self.process(data) - hashinate_by_key(element('dashboard_multiSetCount_response', data), true) - end - end - - class DashboardMultiIncrementCount < Parser - def self.process(data) - hashinate_by_key(element('dashboard_multiIncrementCount_response', data), true) - end - end - - class DashboardMultiDecrementCount < Parser - def self.process(data) - hashinate_by_key(element('dashboard_multiDecrementCount_response', data), true) - end - end - - class DashboardAddGlobalNews < Parser - def self.process(data) - element('dashboard_addGlobalNews_response', data).content.strip - end - end - - # Currently, always returns all - class DashboardGetGlobalNews < Parser - def self.process(data) - hashinate_by_key(element('dashboard_getGlobalNews_response', data)) - end - end - - class DashboardClearGlobalNews < Parser - def self.process(data) - hashinate_by_key(element('dashboard_clearGlobalNews_response', data), true) - end - end - - class DashboardAddNews < Parser - def self.process(data) - element('dashboard_addNews_response', data).content.strip - end - end - - class DashboardGetNews < Parser - def self.process(data) - hashinate_by_key(element('dashboard_getNews_response', data)) - end - end - - class DashboardClearNews < Parser - def self.process(data) - hashinate_by_key(element('dashboard_clearNews_response', data), true) - end - end - - class DashboardMultiAddNews < Parser - def self.process(data) - hashinate_by_key(element('dashboard_multiAddNews_response', data)) - end - end - - class DashboardMultiClearNews < Parser - def self.process(data) - hashinate_by_key(element('dashboard_multiClearNews_response', data), true) - end - end - - class DashboardMultiGetNews < Parser - def self.process(data) - hashinate_by_key(element('dashboard_multiGetNews_response', data)) - end - end - - class DashboardPublishActivity < Parser - def self.process(data) - element('dashboard_publishActivity_response', data).content.strip - end - end - - class DashboardRemoveActivity < Parser - def self.process(data) - hashinate_by_key(element('dashboard_removeActivity_response', data), true) - end - end - - class DashboardGetActivity < Parser - def self.process(data) - hashinate_by_key(element('dashboard_getActivity_response', data)) - end - end - - - class Errors < Parser#:nodoc: - EXCEPTIONS = { - 1 => Facebooker::Session::UnknownError, - 2 => Facebooker::Session::ServiceUnavailable, - 4 => Facebooker::Session::MaxRequestsDepleted, - 5 => Facebooker::Session::HostNotAllowed, - 10 => Facebooker::Session::AppPermissionError, - 100 => Facebooker::Session::MissingOrInvalidParameter, - 101 => Facebooker::Session::InvalidAPIKey, - 102 => Facebooker::Session::SessionExpired, - 103 => Facebooker::Session::CallOutOfOrder, - 104 => Facebooker::Session::IncorrectSignature, - 120 => Facebooker::Session::InvalidAlbumId, - 200 => Facebooker::Session::PermissionError, - 250 => Facebooker::Session::ExtendedPermissionRequired, - 321 => Facebooker::Session::AlbumIsFull, - 324 => Facebooker::Session::MissingOrInvalidImageFile, - 325 => Facebooker::Session::TooManyUnapprovedPhotosPending, - 330 => Facebooker::Session::TemplateDataMissingRequiredTokens, - 340 => Facebooker::Session::TooManyUserCalls, - 341 => Facebooker::Session::TooManyUserActionCalls, - 342 => Facebooker::Session::InvalidFeedTitleLink, - 343 => Facebooker::Session::InvalidFeedTitleLength, - 344 => Facebooker::Session::InvalidFeedTitleName, - 345 => Facebooker::Session::BlankFeedTitle, - 346 => Facebooker::Session::FeedBodyLengthTooLong, - 347 => Facebooker::Session::InvalidFeedPhotoSource, - 348 => Facebooker::Session::InvalidFeedPhotoLink, - 330 => Facebooker::Session::FeedMarkupInvalid, - 360 => Facebooker::Session::FeedTitleDataInvalid, - 361 => Facebooker::Session::FeedTitleTemplateInvalid, - 362 => Facebooker::Session::FeedBodyDataInvalid, - 363 => Facebooker::Session::FeedBodyTemplateInvalid, - 364 => Facebooker::Session::FeedPhotosNotRetrieved, - 366 => Facebooker::Session::FeedTargetIdsInvalid, - 601 => Facebooker::Session::FQLParseError, - 602 => Facebooker::Session::FQLFieldDoesNotExist, - 603 => Facebooker::Session::FQLTableDoesNotExist, - 604 => Facebooker::Session::FQLStatementNotIndexable, - 605 => Facebooker::Session::FQLFunctionDoesNotExist, - 606 => Facebooker::Session::FQLWrongNumberArgumentsPassedToFunction, - 612 => Facebooker::Session::ReadMailboxExtendedPermissionRequired, - 807 => Facebooker::Session::TemplateBundleInvalid, - 1001=> Facebooker::Session::EventNameLocked - } - def self.process(data) - response_element = element('error_response', data) rescue nil - if response_element - hash = hashinate(response_element) - exception = EXCEPTIONS[Integer(hash['error_code'])] || StandardError - raise exception, hash['error_msg'] - end - end - end - - class Parser - PARSERS = { - 'facebook.auth.revokeAuthorization' => RevokeAuthorization, - 'facebook.auth.revokeExtendedPermission' => RevokeExtendedPermission, - 'facebook.auth.createToken' => CreateToken, - 'facebook.auth.getSession' => GetSession, - 'facebook.connect.registerUsers' => RegisterUsers, - 'facebook.connect.unregisterUsers' => UnregisterUsers, - 'facebook.connect.getUnconnectedFriendsCount' => GetUnconnectedFriendsCount, - 'facebook.users.getInfo' => UserInfo, - 'facebook.users.getStandardInfo' => UserStandardInfo, - 'facebook.users.setStatus' => SetStatus, - 'facebook.status.get' => GetStatus, - 'facebook.users.getLoggedInUser' => GetLoggedInUser, - 'facebook.users.hasAppPermission' => UserHasPermission, - 'facebook.users.isAppUser' => IsAppUser, - 'facebook.pages.isAdmin' => PagesIsAdmin, - 'facebook.pages.getInfo' => PagesGetInfo, - 'facebook.pages.isFan' => PagesIsFan, - 'facebook.friends.get' => GetFriends, - 'facebook.friends.getLists' => FriendListsGet, - 'facebook.friends.areFriends' => AreFriends, - 'facebook.friends.getAppUsers' => GetAppUsers, - 'facebook.feed.publishStoryToUser' => PublishStoryToUser, - 'facebook.feed.publishActionOfUser' => PublishActionOfUser, - 'facebook.feed.publishTemplatizedAction' => PublishTemplatizedAction, - 'facebook.feed.registerTemplateBundle' => RegisterTemplateBundle, - 'facebook.feed.deactivateTemplateBundleByID' => DeactivateTemplateBundleByID, - 'facebook.feed.getRegisteredTemplateBundles' => GetRegisteredTemplateBundles, - 'facebook.feed.publishUserAction' => PublishUserAction, - 'facebook.message.getThreadsInFolder' => MessageGetThreadsInFolder, - 'facebook.notifications.get' => NotificationsGet, - 'facebook.notifications.send' => NotificationsSend, - 'facebook.notifications.sendRequest' => SendRequest, - 'facebook.profile.getFBML' => ProfileFBML, - 'facebook.profile.setFBML' => ProfileFBMLSet, - 'facebook.profile.getInfo' => ProfileInfo, - 'facebook.profile.setInfo' => ProfileInfoSet, - 'facebook.fbml.setRefHandle' => SetRefHandle, - 'facebook.fbml.refreshRefUrl' => RefreshRefURL, - 'facebook.fbml.refreshImgSrc' => RefreshImgSrc, - 'facebook.data.setCookie' => SetCookie, - 'facebook.data.getCookies' => GetCookies, - 'facebook.admin.setAppProperties' => SetAppProperties, - 'facebook.admin.getAppProperties' => GetAppProperties, - 'facebook.admin.setRestrictionInfo' => SetRestrictionInfo, - 'facebook.admin.getRestrictionInfo' => GetRestrictionInfo, - 'facebook.admin.getAllocation' => GetAllocation, - 'facebook.application.getPublicInfo' => GetPublicInfo, - 'facebook.batch.run' => BatchRun, - 'facebook.fql.query' => FqlQuery, - 'facebook.fql.multiquery' => FqlMultiquery, - 'facebook.photos.get' => GetPhotos, - 'facebook.photos.getAlbums' => GetAlbums, - 'facebook.photos.createAlbum' => CreateAlbum, - 'facebook.photos.getTags' => GetTags, - 'facebook.photos.addTag' => AddTags, - 'facebook.photos.upload' => UploadPhoto, - 'facebook.stream.get' => GetStream, - 'facebook.stream.publish' => StreamPublish, - 'facebook.stream.addComment' => StreamAddComment, - 'facebook.stream.addLike' => StreamAddLike, - 'facebook.stream.removeLike' => StreamRemoveLike, - 'facebook.events.create' => EventsCreate, - 'facebook.events.cancel' => EventsCancel, - 'facebook.events.get' => EventsGet, - 'facebook.events.edit' => EventsEdit, - 'facebook.events.invite' => EventsInvite, - 'facebook.events.rsvp' => EventsRsvp, - 'facebook.groups.get' => GroupsGet, - 'facebook.events.getMembers' => EventMembersGet, - 'facebook.groups.getMembers' => GroupGetMembers, - 'facebook.notifications.sendEmail' => NotificationsSendEmail, - 'facebook.data.getUserPreference' => GetPreference, - 'facebook.data.setUserPreference' => SetPreference, - 'facebook.video.upload' => UploadVideo, - 'facebook.sms.send' => SmsSend, - 'facebook.sms.canSend' => SmsCanSend, - 'facebook.comments.add' => CommentsAdd, - 'facebook.comments.remove' => CommentsRemove, - 'facebook.comments.get' => CommentsGet, - 'facebook.dashboard.setCount' => DashboardSetCount, - 'facebook.dashboard.getCount' => DashboardGetCount, - 'facebook.dashboard.incrementCount' => DashboardIncrementCount, - 'facebook.dashboard.decrementCount' => DashboardDecrementCount, - 'facebook.dashboard.multiGetCount' => DashboardMultiGetCount, - 'facebook.dashboard.multiSetCount' => DashboardMultiSetCount, - 'facebook.dashboard.multiIncrementCount' => DashboardMultiIncrementCount, - 'facebook.dashboard.multiDecrementCount' => DashboardMultiDecrementCount, - 'facebook.dashboard.addGlobalNews' => DashboardAddGlobalNews, - 'facebook.dashboard.getGlobalNews' => DashboardGetGlobalNews, - 'facebook.dashboard.clearGlobalNews' => DashboardClearGlobalNews, - 'facebook.dashboard.addNews' => DashboardAddNews, - 'facebook.dashboard.getNews' => DashboardGetNews, - 'facebook.dashboard.clearNews' => DashboardClearNews, - 'facebook.dashboard.multiAddNews' => DashboardMultiAddNews, - 'facebook.dashboard.multiGetNews' => DashboardMultiGetNews, - 'facebook.dashboard.multiClearNews' => DashboardMultiClearNews, - 'facebook.dashboard.publishActivity' => DashboardPublishActivity, - 'facebook.dashboard.removeActivity' => DashboardRemoveActivity, - 'facebook.dashboard.getActivity' => DashboardGetActivity, - 'facebook.intl.uploadNativeStrings' => UploadNativeStrings - } - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/._helpers.rb b/vendor/plugins/facebooker/lib/facebooker/rails/._helpers.rb deleted file mode 100644 index f530009dd..000000000 Binary files a/vendor/plugins/facebooker/lib/facebooker/rails/._helpers.rb and /dev/null differ diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/backwards_compatible_param_checks.rb b/vendor/plugins/facebooker/lib/facebooker/rails/backwards_compatible_param_checks.rb deleted file mode 100644 index 272b3fa8d..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/backwards_compatible_param_checks.rb +++ /dev/null @@ -1,31 +0,0 @@ -module Facebooker::Rails::BackwardsCompatibleParamChecks - - def one_or_true( value ) - case value - when String then - value == "1" - when Numeric then - value.to_f == 1.0 - when TrueClass then - true - else - false - end - end - - def zero_or_false( value ) - case value - when String then - value.empty? || value == "0" - when Numeric then - value.to_f == 0.0 - when FalseClass then - true - when NilClass then - true - else - false - end - end - -end diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/controller.rb b/vendor/plugins/facebooker/lib/facebooker/rails/controller.rb deleted file mode 100644 index 3a99e8bf2..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/controller.rb +++ /dev/null @@ -1,357 +0,0 @@ -require 'facebooker' -require 'facebooker/rails/profile_publisher_extensions' -module Facebooker - module Rails - module Controller - include Facebooker::Rails::BackwardsCompatibleParamChecks - include Facebooker::Rails::ProfilePublisherExtensions - def self.included(controller) - controller.extend(ClassMethods) - controller.before_filter :set_facebook_request_format - controller.helper_attr :facebook_session_parameters - controller.helper_method :request_comes_from_facebook? - end - - def initialize *args - @facebook_session = nil - @installation_required = nil - super - end - - def facebook_session - @facebook_session - end - - def facebook_session_parameters - {:fb_sig_session_key=>params[:fb_sig_session_key]} - end - - def create_facebook_session - secure_with_facebook_params! || secure_with_cookies! || secure_with_token! - end - - #this is used to proxy a connection through a rails app so the facebook secret key is not needed - #iphone apps use this - def create_facebook_session_with_secret - secure_with_session_secret! - end - - def set_facebook_session - # first, see if we already have a session - session_set = session_already_secured? - # if not, see if we can load it from the environment - unless session_set - session_set = create_facebook_session - session[:facebook_session] = @facebook_session if session_set - end - if session_set - capture_facebook_friends_if_available! - Session.current = facebook_session - end - return session_set - end - - - def facebook_params - @facebook_params ||= verified_facebook_params - end - - # Redirects the top window to the given url if the content is in an iframe, otherwise performs - # a normal redirect_to call. - def top_redirect_to(*args) - if request_is_facebook_iframe? - @redirect_url = url_for(*args) - render :layout => false, :inline => <<-HTML - - - - - HTML - else - redirect_to(*args) - end - end - - def redirect_to(*args) - if request_is_for_a_facebook_canvas? and !request_is_facebook_tab? - render :text => fbml_redirect_tag(*args) - else - super - end - end - - private - - def session_already_secured? - (@facebook_session = session[:facebook_session]) && session[:facebook_session].secured? if valid_session_key_in_session? - end - - def user_has_deauthorized_application? - # if we're inside the facebook session and there is no session key, - # that means the user revoked our access - # we don't want to keep using the old expired key from the cookie. - request_comes_from_facebook? and params[:fb_sig_session_key].blank? - end - - def clear_facebook_session_information - session[:facebook_session] = nil - @facebook_session=nil - end - - def valid_session_key_in_session? - #before we access the facebook_params, make sure we have the parameters - #otherwise we will blow up trying to access the secure parameters - if user_has_deauthorized_application? - clear_facebook_session_information - false - else - !session[:facebook_session].blank? && (params[:fb_sig_session_key].blank? || session[:facebook_session].session_key == facebook_params[:session_key]) - end - end - - def clear_fb_cookies! - domain_cookie_tag = "base_domain_#{Facebooker.api_key}" - cookie_domain = ".#{cookies[domain_cookie_tag]}" if cookies[domain_cookie_tag] - fb_cookie_names.each {|name| cookies.delete(name, :domain=>cookie_domain)} - cookies.delete Facebooker.api_key - end - - def fb_cookie_prefix - "fbs_"+Facebooker.api_key - end - - def fb_cookie_names - fb_cookie_names = cookies.keys.select{|k| k && k.to_s.starts_with?(fb_cookie_prefix)} - end - - def secure_with_cookies! - parsed = {} - - fb_cookie_names.each { |key| - params = CGI.parse(cookies[key]) - params.keys.each do |pkey| - parsed[pkey] = params[pkey] - end - } - puts parsed.inspect - #returning gracefully if the cookies aren't set or have expired - return unless parsed['session_key'] && parsed['uid'] && parsed['expires'] && parsed['secret'] - return unless (Time.at(parsed['expires'].to_s.to_f) > Time.now) || (parsed['expires'] == "0") - #if we have the unexpired cookies, we'll throw an exception if the sig doesn't verify - # verify_signature(parsed,cookies[Facebooker.api_key], true) - @facebook_session = new_facebook_session - @facebook_session.secure_with!(parsed['session_key'].to_s,parsed['uid'].to_s,parsed['expires'].to_s,parsed['secret'].to_s) - @facebook_session - end - - def secure_with_token! - if params['auth_token'] - @facebook_session = new_facebook_session - @facebook_session.auth_token = params['auth_token'] - @facebook_session.secure! - @facebook_session - end - end - - def secure_with_session_secret! - if params['auth_token'] - @facebook_session = new_facebook_session - @facebook_session.auth_token = params['auth_token'] - @facebook_session.secure_with_session_secret! - @facebook_session - end - end - - def secure_with_facebook_params! - return unless request_comes_from_facebook? - - if ['user', 'session_key'].all? {|element| facebook_params[element]} - @facebook_session = new_facebook_session - @facebook_session.secure_with!(facebook_params['session_key'], facebook_params['user'], facebook_params['expires']) - @facebook_session - end - end - - #override to specify where the user should be sent after logging in - def after_facebook_login_url - nil - end - - def default_after_facebook_login_url - omit_keys = ["_method", "format"] - options = (params||{}).clone - options = options.reject{|k,v| k.to_s.match(/^fb_sig/) or omit_keys.include?(k.to_s)} - options = options.merge({:only_path => false}) - url_for(options) - end - - def create_new_facebook_session_and_redirect! - session[:facebook_session] = new_facebook_session - next_url = after_facebook_login_url || default_after_facebook_login_url - top_redirect_to session[:facebook_session].login_url({:next => next_url, :canvas=>params[:fb_sig_in_canvas]}) unless @installation_required - false - end - - def new_facebook_session - Facebooker::Session.create(Facebooker.api_key, Facebooker.secret_key) - end - - def capture_facebook_friends_if_available! - return unless request_comes_from_facebook? - if friends = facebook_params['friends'] - facebook_session.user.friends = friends.map do |friend_uid| - User.new(friend_uid, facebook_session) - end - end - end - - def verified_facebook_params - facebook_sig_params = params.inject({}) do |collection, pair| - collection[pair.first.sub(/^fb_sig_/, '')] = pair.last if pair.first[0,7] == 'fb_sig_' - collection - end - verify_signature(facebook_sig_params,params['fb_sig']) - - facebook_sig_params.inject(HashWithIndifferentAccess.new) do |collection, pair| - collection[pair.first] = facebook_parameter_conversions[pair.first].call(pair.last) - collection - end - end - - def earliest_valid_session - 48.hours.ago - end - - def verify_signature(facebook_sig_params,expected_signature,force=false) - # Don't verify the signature if rack has already done so. - unless ::Rails.version >= "2.3" and ActionController::Dispatcher.middleware.include? Rack::Facebook and !force - raw_string = facebook_sig_params.map{ |*args| args.join('=') }.sort.join - actual_sig = Digest::MD5.hexdigest([raw_string, Facebooker::Session.secret_key].join) - raise Facebooker::Session::IncorrectSignature if actual_sig != expected_signature - end - raise Facebooker::Session::SignatureTooOld if facebook_sig_params['time'] && Time.at(facebook_sig_params['time'].to_f) < earliest_valid_session - true - end - - def facebook_parameter_conversions - @facebook_parameter_conversions ||= Hash.new do |hash, key| - lambda{|value| value} - end.merge( - 'time' => lambda{|value| Time.at(value.to_f)}, - 'in_canvas' => lambda{|value| one_or_true(value)}, - 'added' => lambda{|value| one_or_true(value)}, - 'expires' => lambda{|value| zero_or_false(value) ? nil : Time.at(value.to_f)}, - 'friends' => lambda{|value| value.split(/,/)} - ) - end - - def fbml_redirect_tag(url,*args) - "" - end - - def request_comes_from_facebook? - request_is_for_a_facebook_canvas? || request_is_facebook_ajax? || request_is_fb_ping? - end - - def request_is_fb_ping? - !params['fb_sig'].blank? - end - - def request_is_for_a_facebook_canvas? - !params['fb_sig_in_canvas'].blank? - end - - def request_is_facebook_tab? - !params["fb_sig_in_profile_tab"].blank? - end - - def request_is_facebook_iframe? - !params["fb_sig_in_iframe"].blank? - end - - def request_is_facebook_ajax? - one_or_true(params["fb_sig_is_mockajax"]) || one_or_true(params["fb_sig_is_ajax"]) - end - - def xml_http_request? - request_is_facebook_ajax? || super - end - - def application_is_installed? - facebook_params['added'] - end - - def ensure_has_status_update - has_extended_permission?("status_update") || application_needs_permission("status_update") - end - def ensure_has_photo_upload - has_extended_permission?("photo_upload") || application_needs_permission("photo_upload") - end - def ensure_has_video_upload - has_extended_permission?("video_upload") || application_needs_permission("video_upload") - end - def ensure_has_create_listing - has_extended_permission?("create_listing") || application_needs_permission("create_listing") - end - def ensure_has_create_event - has_extended_permission?("create_event") || application_needs_permission("create_event") - end - - def application_needs_permission(perm) - top_redirect_to(facebook_session.permission_url(perm)) - end - - def has_extended_permission?(perm) - params["fb_sig_ext_perms"] and params["fb_sig_ext_perms"].include?(perm) - end - - def ensure_authenticated_to_facebook - set_facebook_session || create_new_facebook_session_and_redirect! - end - - def ensure_application_is_installed_by_facebook_user - @installation_required = true - returning ensure_authenticated_to_facebook && application_is_installed? do |authenticated_and_installed| - application_is_not_installed_by_facebook_user unless authenticated_and_installed - end - end - - def application_is_not_installed_by_facebook_user - next_url = after_facebook_login_url || default_after_facebook_login_url - top_redirect_to session[:facebook_session].install_url({:next => next_url}) - end - - def set_facebook_request_format - if request_is_facebook_ajax? - request.format = :fbjs - elsif request_comes_from_facebook? && !request_is_facebook_iframe? - request.format = :fbml - end - end - - - module ClassMethods - # - # Creates a filter which reqires a user to have already authenticated to - # Facebook before executing actions. Accepts the same optional options hash which - # before_filter and after_filter accept. - def ensure_authenticated_to_facebook(options = {}) - before_filter :ensure_authenticated_to_facebook, options - end - - def ensure_application_is_installed_by_facebook_user(options = {}) - before_filter :ensure_application_is_installed_by_facebook_user, options - end - - def request_comes_from_facebook? - request_is_for_a_facebook_canvas? || request_is_facebook_ajax? - end - end - end - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/cucumber.rb b/vendor/plugins/facebooker/lib/facebooker/rails/cucumber.rb deleted file mode 100644 index 71f0cdcf3..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/cucumber.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'facebooker/rails/cucumber/world' -require 'facebooker/mock/session' -require 'facebooker/mock/service' - -Facebooker::MockService.fixture_path = File.join(RAILS_ROOT, 'features', 'support', 'facebook') - -module Facebooker - class << self - # prevent Facebooker from adding canvas name as prefix to URLs - def request_for_canvas(arg) - yield - end - end - - module Rails - module Controller - # prevent Facebooker from rendering fb:redirect - def redirect_to(*args) - super - end - - # Return the mock session - def new_facebook_session - Facebooker::MockSession.create - end - end - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/cucumber/world.rb b/vendor/plugins/facebooker/lib/facebooker/rails/cucumber/world.rb deleted file mode 100644 index 88c51d044..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/cucumber/world.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'cucumber/rails/world' -require 'facebooker/rails/integration_session' - -module Facebooker - module Rails - module Cucumber - def open_session - session = Facebooker::Rails::IntegrationSession.new - - # delegate the fixture accessors back to the test instance - extras = Module.new { attr_accessor :delegate, :test_result } - if self.class.respond_to?(:fixture_table_names) - self.class.fixture_table_names.each do |table_name| - name = table_name.tr(".", "_") - next unless respond_to?(name) - extras.__send__(:define_method, name) { |*args| delegate.send(name, *args) } - end - end - - # delegate add_assertion to the test case - extras.__send__(:define_method, :add_assertion) { test_result.add_assertion } - session.extend(extras) - session.delegate = self - session.test_result = @_result - - yield session if block_given? - session - end - - def without_canvas - in_canvas = @integration_session.canvas - @integration_session.canvas = false - yield - @integration_session.canvas = in_canvas - end - end - end -end - -World(Facebooker::Rails::Cucumber) diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/extensions/action_controller.rb b/vendor/plugins/facebooker/lib/facebooker/rails/extensions/action_controller.rb deleted file mode 100644 index 89f76a376..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/extensions/action_controller.rb +++ /dev/null @@ -1,48 +0,0 @@ -module ::ActionController - class Base - def self.inherited_with_facebooker(subclass) - inherited_without_facebooker(subclass) - if subclass.to_s == "ApplicationController" - subclass.send(:include,Facebooker::Rails::Controller) - subclass.helper Facebooker::Rails::Helpers - end - end - class << self - alias_method_chain :inherited, :facebooker - end - end -end - - -# When making get requests, Facebook sends fb_sig parameters both in the query string -# and also in the post body. We want to ignore the query string ones because they are one -# request out of date -# We only do thise when there are POST parameters so that IFrame linkage still works -if Rails.version < '2.3' - class ActionController::AbstractRequest - def query_parameters_with_facebooker - if request_parameters.blank? - query_parameters_without_facebooker - else - (query_parameters_without_facebooker||{}).reject {|key,value| key.to_s =~ /^fb_sig/} - end - end - - alias_method_chain :query_parameters, :facebooker - end -else - class ActionController::Request - def query_parameters_with_facebooker - if request_parameters.blank? - query_parameters_without_facebooker - else - (query_parameters_without_facebooker||{}).reject {|key,value| key.to_s =~ /^fb_sig/} - end - end - - alias_method_chain :query_parameters, :facebooker - end -end - -Mime::Type.register_alias "text/html", :fbml -Mime::Type.register_alias "text/javascript", :fbjs \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/extensions/action_view.rb b/vendor/plugins/facebooker/lib/facebooker/rails/extensions/action_view.rb deleted file mode 100644 index 529ac8d8e..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/extensions/action_view.rb +++ /dev/null @@ -1,21 +0,0 @@ -class ActionView::PathSet - - # Try to find fbml version if the format is fbjs - def find_template_with_facebooker(original_template_path, format = nil, html_fallback = true) - find_template_without_facebooker(original_template_path, format, html_fallback) - rescue ActionView::MissingTemplate - template_path = original_template_path.sub(/^\//, '') - - each do |load_path| - if format == :fbjs && html_fallback && template = load_path["#{template_path}.#{I18n.locale}.fbml"] - return template - elsif format == :fbjs && html_fallback && template = load_path["#{template_path}.fbml"] - return template - end - - raise MissingTemplate.new(self, original_template_path, format) - end - end - - alias_method_chain :find_template, :facebooker -end diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/extensions/rack_setup.rb b/vendor/plugins/facebooker/lib/facebooker/rails/extensions/rack_setup.rb deleted file mode 100644 index 2c4e1c2a4..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/extensions/rack_setup.rb +++ /dev/null @@ -1,16 +0,0 @@ -# Somewhere in 2.3 RewindableInput was removed- rack supports it natively -require 'rack/facebook' -require 'rack/facebook_session' - -ActionController::Dispatcher.middleware.insert_before( - ActionController::ParamsParser, - Rack::Facebook -) - -#use this if you aren't using the cookie store and want to use -# the facebook session key for your session id -# ActionController::Dispatcher.middleware.insert_before( -# ActionController::Base.session_store, -# Rack::FacebookSession, -# ActionController::Base.session_options[:key] -# ) diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/extensions/routing.rb b/vendor/plugins/facebooker/lib/facebooker/rails/extensions/routing.rb deleted file mode 100644 index b303c7cd7..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/extensions/routing.rb +++ /dev/null @@ -1,15 +0,0 @@ -class ActionController::Routing::Route - def recognition_conditions_with_facebooker - defaults = recognition_conditions_without_facebooker - defaults << " env[:canvas] == conditions[:canvas] " if conditions[:canvas] - defaults - end - alias_method_chain :recognition_conditions, :facebooker -end - -# We turn off route optimization to make named routes use our code for figuring out if they should go to the session -ActionController::Base::optimise_named_routes = false - -# pull :canvas=> into env in routing to allow for conditions -ActionController::Routing::RouteSet.send :include, Facebooker::Rails::Routing::RouteSetExtensions -ActionController::Routing::RouteSet::Mapper.send :include, Facebooker::Rails::Routing::MapperExtensions diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/facebook_form_builder.rb b/vendor/plugins/facebooker/lib/facebooker/rails/facebook_form_builder.rb deleted file mode 100644 index a88da0716..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/facebook_form_builder.rb +++ /dev/null @@ -1,141 +0,0 @@ -module Facebooker - module Rails - class FacebookFormBuilder < ActionView::Helpers::FormBuilder - - - second_param = %w(password_field file_field check_box date_select datetime_select time_select) - third_param = %w(radio_button country_select select time_zone_select) - fifth_param = %w(collection_select) - - def self.create_with_offset(name,offset) - define_method name do |field,*args| - options = args[offset] || {} - build_shell(field,options.with_indifferent_access) do - super(field,*args) - end - end - end - - second_param.each do |name| - create_with_offset(name,0) - end - third_param.each do |name| - create_with_offset(name,1) - end - fifth_param.each do |name| - create_with_offset(name,3) - end - - def build_shell(field,options) - @template.content_tag "fb:editor-custom", :label=>label_for(field,options) do - yield - end - end - - def label_for(field,options) - options[:label] || field.to_s.humanize - end - - def text(string,options={}) - @template.content_tag "fb:editor-custom",string, :label=>label_for("",options) - end - - - def text_field(method, options = {}) - options = options.with_indifferent_access - options[:label] ||= label_for(method,options) - add_default_name_and_id(options,method) - options["value"] ||= value_before_type_cast(object,method) - @template.content_tag("fb:editor-text","",options) - end - - - def text_area(method, options = {}) - options[:label] ||= label_for(method,options) - add_default_name_and_id(options,method) - @template.content_tag("fb:editor-textarea",value_before_type_cast(object,method),options) - end - - # - # Build a text input area that uses typeahed - # options are like collection_select - def collection_typeahead(method,collection,value_method,text_method,options={}) - build_shell(method,options) do - collection_typeahead_internal(method,collection,value_method,text_method,options) - end - end - - def collection_typeahead_internal(method,collection,value_method,text_method,options={}) - option_values = collection.map do |item| - value=item.send(value_method) - text=item.send(text_method) - @template.content_tag "fb:typeahead-option",text,:value=>value - end.join - add_default_name_and_id(options,method) - options["value"] ||= value_before_type_cast(object,method) - @template.content_tag("fb:typeahead-input",option_values,options) - end - - def value_before_type_cast(object,method) - unless object.nil? - method_name = method.to_s - object.respond_to?(method_name + "_before_type_cast") ? - object.send(method_name + "_before_type_cast") : - object.send(method_name) - end - end - - def multi_friend_input(options={}) - build_shell(:friends,options) do - @template.content_tag("fb:multi-friend-input","",options) - end - end - - def buttons(*names) - buttons=names.map do |name| - create_button(name) - end.join - - @template.content_tag "fb:editor-buttonset",buttons - end - - def create_button(name) - @template.content_tag("fb:editor-button","",:value=>name,:name=>"commit") - end - - def add_default_name_and_id(options, method) - @method_name = method - if options.has_key?("index") - options["name"] ||= tag_name_with_index(options["index"]) - options["id"] ||= tag_id_with_index(options["index"]) - options.delete("index") - else - options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '') - options["id"] ||= "#{sanitized_object_name}_#{sanitized_method_name}" - end - end - - - private - def tag_name - "#{@object_name.to_s}[#{sanitized_method_name}]" - end - - def tag_name_with_index(index) - "#{@object_name.to_s}[#{index}][#{sanitized_method_name}]" - end - - def tag_id_with_index(index) - "#{sanitized_object_name}_#{index}_#{sanitized_method_name}" - end - - def sanitized_object_name - @object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "") - end - - def sanitized_method_name - @method_name.to_s.sub(/\?$/,"") - end - end - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/facebook_pretty_errors.rb b/vendor/plugins/facebooker/lib/facebooker/rails/facebook_pretty_errors.rb deleted file mode 100644 index 37fa4ac58..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/facebook_pretty_errors.rb +++ /dev/null @@ -1,22 +0,0 @@ -class ActionController::Base - def rescues_path_with_facebooker(template_name) - t = "#{RAILS_ROOT}/vendor/plugins/facebooker/templates/#{template_name}.erb" - if pretty_facebook_errors? && File.exist?(t) - t - else - rescues_path_without_facebooker(template_name) - end - end - alias_method_chain :rescues_path, :facebooker - - def response_code_for_rescue_with_facebooker(exception) - pretty_facebook_errors? ? 200 : response_code_for_rescue_without_facebooker(exception) - end - alias_method_chain :response_code_for_rescue, :facebooker - - - def pretty_facebook_errors? - Facebooker.facebooker_config['pretty_errors'] || - (Facebooker.facebooker_config['pretty_errors'].nil? && RAILS_ENV=="development") - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/facebook_request_fix.rb b/vendor/plugins/facebooker/lib/facebooker/rails/facebook_request_fix.rb deleted file mode 100644 index 9b3139c48..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/facebook_request_fix.rb +++ /dev/null @@ -1,28 +0,0 @@ -module ::ActionController - class AbstractRequest - include Facebooker::Rails::BackwardsCompatibleParamChecks - - def request_method_with_facebooker - if parameters[:_method].blank? - if %w{GET HEAD}.include?(parameters[:fb_sig_request_method]) - parameters[:_method] = parameters[:fb_sig_request_method] - end - end - request_method_without_facebooker - end - - if new.methods.include?("request_method") - alias_method_chain :request_method, :facebooker - end - - def xml_http_request_with_facebooker? - one_or_true(parameters["fb_sig_is_mockajax"]) || - one_or_true(parameters["fb_sig_is_ajax"]) || - xml_http_request_without_facebooker? - end - alias_method_chain :xml_http_request?, :facebooker - # we have to re-alias xhr? since it was pointing to the old method - alias :xhr? :xml_http_request? - - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/facebook_request_fix_2-3.rb b/vendor/plugins/facebooker/lib/facebooker/rails/facebook_request_fix_2-3.rb deleted file mode 100644 index d53ea0760..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/facebook_request_fix_2-3.rb +++ /dev/null @@ -1,31 +0,0 @@ -module ::ActionController - - class Request - - include Facebooker::Rails::BackwardsCompatibleParamChecks - - def request_method_with_facebooker - if parameters[:_method].blank? - if %w{GET HEAD}.include?(parameters[:fb_sig_request_method]) - parameters[:_method] = parameters[:fb_sig_request_method] - end - end - request_method_without_facebooker - end - - if new({}).methods.include?("request_method") - alias_method_chain :request_method, :facebooker - end - - def xml_http_request_with_facebooker? - one_or_true(parameters["fb_sig_is_mockajax"]) || - one_or_true(parameters["fb_sig_is_ajax"]) || - xml_http_request_without_facebooker? - end - alias_method_chain :xml_http_request?, :facebooker - - # we have to re-alias xhr? since it was pointing to the old method - alias :xhr? :xml_http_request? - - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/facebook_session_handling.rb b/vendor/plugins/facebooker/lib/facebooker/rails/facebook_session_handling.rb deleted file mode 100644 index a6db58e54..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/facebook_session_handling.rb +++ /dev/null @@ -1,68 +0,0 @@ -module ActionController - class CgiRequest - alias :initialize_aliased_by_facebooker :initialize - - def initialize(cgi, session_options = {}) - initialize_aliased_by_facebooker(cgi, session_options) - @cgi.instance_variable_set("@request_params", request_parameters.merge(query_parameters)) - end - - DEFAULT_SESSION_OPTIONS[:cookie_only] = false - end -end - -module ActionController - class RackRequest < AbstractRequest #:nodoc: - alias :initialize_aliased_by_facebooker :initialize - - def initialize(cgi, session_options = {}) - initialize_aliased_by_facebooker(cgi, session_options) - @cgi.instance_variable_set("@request_params", request_parameters.merge(query_parameters)) - end - end -end - -class CGI - class Session - alias :initialize_aliased_by_facebooker :initialize - attr_reader :request, :initialization_options - - def initialize(request, option={}) - @request = request - @initialization_options = option - option['session_id'] ||= set_session_id - initialize_aliased_by_facebooker(request, option) - end - - def set_session_id - if session_key_should_be_set_with_facebook_session_key? - request_parameters[facebook_session_key] - else - request_parameters[session_key] - end - end - - def request_parameters - request.instance_variable_get("@request_params") - end - - def session_key_should_be_set_with_facebook_session_key? - request_parameters[session_key].blank? && !request_parameters[facebook_session_key].blank? - end - - def session_key - initialization_options['session_key'] || '_session_id' - end - - def facebook_session_key - 'fb_sig_session_key' - end - - alias :create_new_id_aliased_by_facebooker :create_new_id - - def create_new_id - @new_session = true - @session_id || create_new_id_aliased_by_facebooker - end - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/facebook_url_helper.rb b/vendor/plugins/facebooker/lib/facebooker/rails/facebook_url_helper.rb deleted file mode 100644 index 2a8497dd2..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/facebook_url_helper.rb +++ /dev/null @@ -1,192 +0,0 @@ -# Extends the ActionView::Helpers::UrlHelper module. See it for details on -# the usual url helper methods: url_for, link_to, button_to, etc. -# -# Mostly, the changes sanitize javascript into facebook javascript. -# It sanitizes link_to solely by altering the private methods: -# convert_options_to_javascript!, confirm_javascript_function, and -# method_javascript_function. For button_to, it alters button_to -# itself, as well as confirm_javascript_function. No other methods -# need to be changed because of Facebook javascript. -# -# For button_to and link_to, adds alternate confirm options for facebook. -# ==== Options -# * :confirm => 'question?' - This will add a JavaScript confirm -# prompt with the question specified. -# -# Example: -# # Generates: Facebooker -# link_to("Facebooker", "http://rubyforge.org/projects/facebooker", :confirm=>"Go to Facebooker?") -# -# Alternatively, options[:confirm] may be specified. -# See the Facebook page http://wiki.developers.facebook.com/index.php/FBJS. -# These options are: -# :title:: Specifies the title of the Facebook dialog. Default is "Please Confirm". -# :content:: Specifies the title of the Facebook dialog. Default is "Are you sure?". -# -# Example: -# # Generates: Facebooker -# link_to("Facebooker", "http://rubyforge.org/projects/facebooker", :confirm=>{:title=>"the page says:", :content=>"Go to Facebooker?"}) -# -# Any other options passed are assumed to be css styles. -# Again, see the Facebook page http://wiki.developers.facebook.com/index.php/FBJS. -# -# Example: -# # Generates: Facebooker -# link_to("Facebooker", "http://rubyforge.org/projects/facebooker", :confirm=>{:title=>"the page says:, :color=>"pink", :width=>"200px"}) -module ActionView - module Helpers - module UrlHelper - # Alters one and only one line of the Rails button_to. See below. - def button_to_with_facebooker(name, options={}, html_options = {}) - if !respond_to?(:request_comes_from_facebook?) || !request_comes_from_facebook? - button_to_without_facebooker(name,options,html_options) - else - html_options = html_options.stringify_keys - convert_boolean_attributes!(html_options, %w( disabled )) - - method_tag = '' - if (method = html_options.delete('method')) && %w{put delete}.include?(method.to_s) - method_tag = tag('input', :type => 'hidden', :name => '_method', :value => method.to_s) - end - - form_method = method.to_s == 'get' ? 'get' : 'post' - - request_token_tag = '' - if form_method == 'post' && protect_against_forgery? - request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token) - end - - if confirm = html_options.delete("confirm") - # this line is the only change => html_options["onclick"] = "return #{confirm_javascript_function(confirm)}" - html_options["onclick"] = "#{confirm_javascript_function(confirm, 'a.getForm().submit();')}return false;" - end - - url = options.is_a?(String) ? options : self.url_for(options) - name ||= url - - html_options.merge!("type" => "submit", "value" => name) - - "
          " + - method_tag + tag("input", html_options) + request_token_tag + "
          " - end - end - - alias_method_chain :button_to, :facebooker - - private - - # Altered to throw an error on :popup and sanitize the javascript - # for Facebook. - def convert_options_to_javascript_with_facebooker!(html_options, url ='') - if !respond_to?(:request_comes_from_facebook?) || !request_comes_from_facebook? - convert_options_to_javascript_without_facebooker!(html_options,url) - else - confirm, popup = html_options.delete("confirm"), html_options.delete("popup") - - method, href = html_options.delete("method"), html_options['href'] - - html_options["onclick"] = case - when popup - raise ActionView::ActionViewError, "You can't use :popup" - when method # or maybe (confirm and method) - "#{method_javascript_function(method, url, href, confirm)}return false;" - when confirm # and only confirm - "#{confirm_javascript_function(confirm)}return false;" - else - html_options["onclick"] - end - end - end - - alias_method_chain :convert_options_to_javascript!, :facebooker - - - # Overrides a private method that link_to calls via convert_options_to_javascript! and - # also, button_to calls directly. For Facebook, confirm can be a hash of options to - # stylize the Facebook dialog. Takes :title, :content, :style options. See - # the Facebook page http://wiki.developers.facebook.com/index.php/FBJS for valid - # style formats like "color: 'black', background: 'white'" or like "'color','black'". - # - # == Examples == - # - # link_to("Facebooker", "http://rubyforge.org/projects/facebooker", :confirm=>"Go to Facebooker?") - # link_to("Facebooker", "http://rubyforge.org/projects/facebooker", :confirm=>{:title=>"the page says:, :content=>"Go to Facebooker?"}) - # link_to("Facebooker", "http://rubyforge.org/projects/facebooker", :confirm=>{:title=>"the page says:, :content=>"Go to Facebooker?", :color=>"pink"}) - def confirm_javascript_function_with_facebooker(confirm, fun = nil) - if !respond_to?(:request_comes_from_facebook?) || !request_comes_from_facebook? - confirm_javascript_function_without_facebooker(confirm) - else - if(confirm.is_a?(Hash)) - confirm_options = confirm.stringify_keys - title = confirm_options.delete("title") || "Please Confirm" - content = confirm_options.delete("content") || "Are you sure?" - button_confirm = confirm_options.delete("button_confirm") || "Okay" - button_cancel = confirm_options.delete("button_cancel") || "Cancel" - style = confirm_options.empty? ? "" : convert_options_to_css(confirm_options) - else - title,content,style,button_confirm,button_cancel = 'Please Confirm', confirm, "", "Okay", "Cancel" - end - "var dlg = new Dialog().showChoice('#{escape_javascript(title.to_s)}','#{escape_javascript(content.to_s)}','#{escape_javascript(button_confirm.to_s)}','#{escape_javascript(button_cancel.to_s)}').setStyle(#{style});"+ - "var a=this;dlg.onconfirm = function() { #{fun ? fun : 'document.setLocation(a.getHref());'} };" - end - end - - alias_method_chain :confirm_javascript_function, :facebooker - - def convert_options_to_css(options) - key_pair = options.shift - style = "{#{key_pair[0]}: '#{key_pair[1]}'" - for key in options.keys - style << ", #{key}: '#{options[key]}'" - end - style << "}" - end - - # Dynamically creates a form for link_to with method. Calls confirm_javascript_function if and - # only if (confirm && method) for link_to - def method_javascript_function_with_facebooker(method, url = '', href = nil, confirm = nil) - if !respond_to?(:request_comes_from_facebook?) || !request_comes_from_facebook? - method_javascript_function_without_facebooker(method,url,href) - else - action = (href && url.size > 0) ? "'#{url}'" : 'a.getHref()' - submit_function = - "var f = document.createElement('form'); f.setStyle('display','none'); " + - "a.getParentNode().appendChild(f); f.setMethod('POST'); f.setAction(#{action});" - - unless method == :post - submit_function << "var m = document.createElement('input'); m.setType('hidden'); " - submit_function << "m.setName('_method'); m.setValue('#{method}'); f.appendChild(m);" - end - - if protect_against_forgery? - submit_function << "var s = document.createElement('input'); s.setType('hidden'); " - submit_function << "s.setName('#{request_forgery_protection_token}'); s.setValue('#{escape_javascript form_authenticity_token}'); f.appendChild(s);" - end - submit_function << "f.submit();" - - if(confirm) - confirm_javascript_function(confirm, submit_function) - else - "var a=this;" + submit_function - end - end - end - - alias_method_chain :method_javascript_function, :facebooker - - end - end -end - diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/facebook_url_rewriting.rb b/vendor/plugins/facebooker/lib/facebooker/rails/facebook_url_rewriting.rb deleted file mode 100644 index 389031294..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/facebook_url_rewriting.rb +++ /dev/null @@ -1,60 +0,0 @@ -module ::ActionController - if Rails.version < '2.3' - class AbstractRequest - def relative_url_root - Facebooker.path_prefix - end - end - else - class Request - def relative_url_root - Facebooker.path_prefix - end - end - end - - class Base - class << self - alias :old_relative_url_root :relative_url_root - def relative_url_root - Facebooker.path_prefix - end - end - end - - class UrlRewriter - include Facebooker::Rails::BackwardsCompatibleParamChecks - - RESERVED_OPTIONS << :canvas - - def link_to_new_canvas? - one_or_true @request.parameters["fb_sig_in_new_facebook"] - end - - def link_to_canvas?(options) - option_override = options[:canvas] - return false if option_override == false # important to check for false. nil should use default behavior - option_override || (can_safely_access_request_parameters? && (one_or_true(@request.parameters["fb_sig_in_canvas"]) || one_or_true(@request.parameters[:fb_sig_in_canvas]) || one_or_true(@request.parameters["fb_sig_is_ajax"]) )) - end - - #rails blindly tries to merge things that may be nil into the parameters. Make sure this won't break - def can_safely_access_request_parameters? - @request.request_parameters - end - - def rewrite_url_with_facebooker(*args) - options = args.first.is_a?(Hash) ? args.first : args.last - is_link_to_canvas = link_to_canvas?(options) - if is_link_to_canvas && !options.has_key?(:host) - options[:host] = Facebooker.canvas_server_base - end - options.delete(:canvas) - Facebooker.request_for_canvas(is_link_to_canvas) do - rewrite_url_without_facebooker(*args) - end - end - - alias_method_chain :rewrite_url, :facebooker - - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/helpers.rb b/vendor/plugins/facebooker/lib/facebooker/rails/helpers.rb deleted file mode 100644 index 3ad0d81db..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/helpers.rb +++ /dev/null @@ -1,835 +0,0 @@ -require 'action_pack' - -module Facebooker - module Rails - - # Facebook specific helpers for creating FBML - # - # All helpers that take a user as a parameter will get the Facebook UID from the facebook_id attribute if it exists. - # It will use to_s if the facebook_id attribute is not present. - # - module Helpers - - include Facebooker::Rails::Helpers::FbConnect - - def versioned_concat(string,binding) - if ignore_binding? - concat(string) - else - concat(string,binding) - end - end - - # Create an fb:dialog - # id must be a unique name e.g. "my_dialog" - # cancel_button is true or false - def fb_dialog( id, cancel_button, &block ) - content = capture(&block) - cancel_button = cancel_button ? 1 : 0 unless cancel_button == 0 - versioned_concat( content_tag("fb:dialog", content, {:id => id, :cancel_button => cancel_button}), block.binding ) - end - - def fb_stream_publish(stream_post,user_message_prompt=nil,callback=nil,auto_publish=false,actor=nil) - stream_publish("Facebook.streamPublish",stream_post,user_message_prompt,callback,auto_publish,actor) - end - - - def fbjs_library - ""+ - "#{javascript_include_tag 'facebooker'}" - end - - def fb_iframe(src, options = {}) - content_tag "fb:iframe", '', options.merge({ :src => src }) - end - - def fb_swf(src, options = {}) - tag "fb:swf", options.merge(:swfsrc => src) - end - - def fb_dialog_title( title ) - content_tag "fb:dialog-title", title - end - - def fb_dialog_content( &block ) - content = capture(&block) - versioned_concat( content_tag("fb:dialog-content", content), block.binding ) - end - - def fb_dialog_button( type, value, options={} ) - options.assert_valid_keys FB_DIALOG_BUTTON_VALID_OPTION_KEYS - options.merge! :type => type, :value => value - tag "fb:dialog-button", options - end - - FB_DIALOG_BUTTON_VALID_OPTION_KEYS = [:close_dialog, :href, :form_id, :clickrewriteurl, :clickrewriteid, :clickrewriteform] - - def fb_show_feed_dialog(action, user_message = "", prompt = "", callback = nil) - update_page do |page| - page.call "Facebook.showFeedDialog",action.template_id,action.data,action.body_general,action.target_ids,callback,prompt,user_message - end - end - - - # Create an fb:request-form without a selector - # - # The block passed to this tag is used as the content of the form - # - # The message param is the name sent to content_for that specifies the body of the message - # - # For example, - # - # <% content_for("invite_message") do %> - # This gets sent in the invite. <%= fb_req_choice("with a button!",new_poke_path) %> - # <% end %> - # <% fb_request_form("Poke","invite_message",create_poke_path) do %> - # Send a poke to: <%= fb_friend_selector %>
          - # <%= fb_request_form_submit %> - # <% end %> - def fb_request_form(type,message_param,url,options={},&block) - content = capture(&block) - message = @template.instance_variable_get("@content_for_#{message_param}") - versioned_concat(content_tag("fb:request-form", content + token_tag, - {:action=>url,:method=>"post",:invite=>true,:type=>type,:content=>message}.merge(options)), - block.binding) - end - - # Create a submit button for an - # If the request is for an individual user you can optionally - # Provide the user and a label for the request button. - # For example - # <% content_for("invite_user") do %> - # This gets sent in the invite. <%= fb_req_choice("Come join us!",new_invite_path) %> - # <% end %> - # <% fb_request_form("Invite","invite_user",create_invite_path) do %> - # Invite <%= fb_name(@facebook_user.friends.first.id)%> to the party
          - # <%= fb_request_form_submit(:uid => @facebook_user.friends.first.id, :label => "Invite %n") %> - # <% end %> - # See: http://wiki.developers.facebook.com/index.php/Fb:request-form-submit for options - def fb_request_form_submit(options={}) - tag("fb:request-form-submit",stringify_vals(options)) - end - - - # Create an fb:request-form with an fb_multi_friend_selector inside - # - # The content of the block are used as the message on the form, the options hash is passed onto fb_multi_friend_selector. - # - # For example: - # <% fb_multi_friend_request("Poke","Choose some friends to Poke",create_poke_path,:exclude_ids => "123456789,987654321") do %> - # If you select some friends, they will see this message. - # <%= fb_req_choice("They will get this button, too",new_poke_path) %> - # <% end %> - def fb_multi_friend_request(type,friend_selector_message,url, fb_multi_friend_selector_options = {},&block) - content = capture(&block) - versioned_concat(content_tag("fb:request-form", - fb_multi_friend_selector(friend_selector_message, fb_multi_friend_selector_options) + token_tag, - {:action=>url,:method=>"post",:invite=>true,:type=>type,:content=>content} - ), - block.binding) - end - - # Render an element - # See: http://wiki.developers.facebook.com/index.php/Fb:friend-selector for options - # - def fb_friend_selector(options={}) - tag("fb:friend-selector",stringify_vals(options)) - end - - # Render an element - # See: http://wiki.developers.facebook.com/index.php/Fb:multi-friend-input for options - def fb_multi_friend_input(options={}) - tag "fb:multi-friend-input",stringify_vals(options) - end - - # Render an with the passed in welcome message - # Full version shows all profile pics for friends. - # See: http://wiki.developers.facebook.com/index.php/Fb:multi-friend-selector for options - # Note: I don't think the block is used here. - def fb_multi_friend_selector(message,options={},&block) - options = options.dup - tag("fb:multi-friend-selector",stringify_vals({:showborder=>false,:actiontext=>message,:max=>20}.merge(options))) - end - - # Render a condensed with the passed in welcome message - # Condensed version show checkboxes for each friend. - # See: http://wiki.developers.facebook.com/index.php/Fb:multi-friend-selector_%28condensed%29 for options - # Note: I don't think the block is used here. - def fb_multi_friend_selector_condensed(options={},&block) - options = options.dup - tag("fb:multi-friend-selector",stringify_vals(options.merge(:condensed=>true))) - end - - # Render a button in a request using the tag - # url must be an absolute url - # This should be used inside the block of an fb_multi_friend_request - def fb_req_choice(label,url) - tag "fb:req-choice",:label=>label,:url=>url - end - - # Create a facebook form using - # - # It yields a form builder that will convert the standard rails form helpers - # into the facebook specific version. - # - # Example: - # <% facebook_form_for(:poke,@poke,:url => create_poke_path) do |f| %> - # <%= f.text_field :message, :label=>"message" %> - # <%= f.buttons "Save Poke" %> - # <% end %> - # - # will generate - # - # - # - # - # - # - def facebook_form_for( record_or_name_or_array,*args, &proc) - - raise ArgumentError, "Missing block" unless block_given? - options = args.last.is_a?(Hash) ? args.pop : {} - - case record_or_name_or_array - when String, Symbol - object_name = record_or_name_or_array - when Array - object = record_or_name_or_array.last - object_name = ActionController::RecordIdentifier.singular_class_name(object) - apply_form_for_options!(record_or_name_or_array, options) - args.unshift object - else - object = record_or_name_or_array - object_name = ActionController::RecordIdentifier.singular_class_name(object) - apply_form_for_options!([object], options) - args.unshift object - end - method = (options[:html]||{})[:method] - options[:builder] ||= Facebooker::Rails::FacebookFormBuilder - editor_options={} - - action=options.delete(:url) - editor_options[:action]= action unless action.blank? - width=options.delete(:width) - editor_options[:width]=width unless width.blank? - width=options.delete(:labelwidth) - editor_options[:labelwidth]=width unless width.blank? - - versioned_concat(tag("fb:editor",editor_options,true) , proc.binding) - versioned_concat(tag(:input,{:type=>"hidden",:name=>:_method, :value=>method},false), proc.binding) unless method.blank? - versioned_concat(token_tag, proc.binding) - fields_for( object_name,*(args << options), &proc) - versioned_concat("",proc.binding) - end - - # Render an fb:application-name tag - # - # This renders the current application name via fbml. See - # http://wiki.developers.facebook.com/index.php/Fb:application-name - # for a full description. - # - def fb_application_name(options={}) - tag "fb:application-name", stringify_vals(options) - end - - # Render an fb:name tag for the given user - # This renders the name of the user specified. You can use this tag as both subject and object of - # a sentence. See http://wiki.developers.facebook.com/index.php/Fb:name for full description. - # Use this tag on FBML pages instead of retrieving the user's info and rendering the name explicitly. - # - def fb_name(user, options={}) - options = options.dup - options.transform_keys!(FB_NAME_OPTION_KEYS_TO_TRANSFORM) - options.assert_valid_keys(FB_NAME_VALID_OPTION_KEYS) - options.merge!(:uid => cast_to_facebook_id(user)) - content_tag("fb:name",nil, stringify_vals(options)) - end - - FB_NAME_OPTION_KEYS_TO_TRANSFORM = {:first_name_only => :firstnameonly, - :last_name_only => :lastnameonly, - :show_network => :shownetwork, - :use_you => :useyou, - :if_cant_see => :ifcantsee, - :subject_id => :subjectid} - FB_NAME_VALID_OPTION_KEYS = [:firstnameonly, :linked, :lastnameonly, :possessive, :reflexive, - :shownetwork, :useyou, :ifcantsee, :capitalize, :subjectid] - - # Render an tag for the specified user - # Options give flexibility for placing in any part of a sentence. - # See http://wiki.developers.facebook.com/index.php/Fb:pronoun for complete list of options. - # - def fb_pronoun(user, options={}) - options = options.dup - options.transform_keys!(FB_PRONOUN_OPTION_KEYS_TO_TRANSFORM) - options.assert_valid_keys(FB_PRONOUN_VALID_OPTION_KEYS) - options.merge!(:uid => cast_to_facebook_id(user)) - content_tag("fb:pronoun",nil, stringify_vals(options)) - end - - FB_PRONOUN_OPTION_KEYS_TO_TRANSFORM = {:use_you => :useyou, :use_they => :usethey} - FB_PRONOUN_VALID_OPTION_KEYS = [:useyou, :possessive, :reflexive, :objective, - :usethey, :capitalize] - - # Render an fb:ref tag. - # Options must contain either url or handle. - # * url The URL from which to fetch the FBML. You may need to call fbml.refreshRefUrl to refresh cache - # * handle The string previously set by fbml.setRefHandle that identifies the FBML - # See http://wiki.developers.facebook.com/index.php/Fb:ref for complete description - def fb_ref(options) - options.assert_valid_keys(FB_REF_VALID_OPTION_KEYS) - validate_fb_ref_has_either_url_or_handle(options) - validate_fb_ref_does_not_have_both_url_and_handle(options) - tag("fb:ref", stringify_vals(options)) - end - - def validate_fb_ref_has_either_url_or_handle(options) - unless options.has_key?(:url) || options.has_key?(:handle) - raise ArgumentError, "fb_ref requires :url or :handle" - end - end - - def validate_fb_ref_does_not_have_both_url_and_handle(options) - if options.has_key?(:url) && options.has_key?(:handle) - raise ArgumentError, "fb_ref may not have both :url and :handle" - end - end - - FB_REF_VALID_OPTION_KEYS = [:url, :handle] - - # Render an for the specified user. - # - # You can optionally specify the size using the :size=> option. Valid - # sizes are :thumb, :small, :normal and :square. Or, you can specify - # width and height settings instead, just like an img tag. - # - # You can optionally specify whether or not to include the facebook icon - # overlay using the :facebook_logo => true option. Default is false. - # - def fb_profile_pic(user, options={}) - options = options.dup - validate_fb_profile_pic_size(options) - options.transform_keys!(FB_PROFILE_PIC_OPTION_KEYS_TO_TRANSFORM) - options.assert_valid_keys(FB_PROFILE_PIC_VALID_OPTION_KEYS) - options.merge!(:uid => cast_to_facebook_id(user)) - content_tag("fb:profile-pic", nil,stringify_vals(options)) - end - - FB_PROFILE_PIC_OPTION_KEYS_TO_TRANSFORM = {:facebook_logo => 'facebook-logo'} - FB_PROFILE_PIC_VALID_OPTION_KEYS = [:size, :linked, 'facebook-logo', :width, :height] - - - # Render an fb:photo tag. - # photo is either a Facebooker::Photo or an id of a Facebook photo or an object that responds to photo_id. - # See: http://wiki.developers.facebook.com/index.php/Fb:photo for complete list of options. - def fb_photo(photo, options={}) - options = options.dup - options.assert_valid_keys(FB_PHOTO_VALID_OPTION_KEYS) - options.merge!(:pid => cast_to_photo_id(photo)) - validate_fb_photo_size(options) - validate_fb_photo_align_value(options) - content_tag("fb:photo",nil, stringify_vals(options)) - end - - FB_PHOTO_VALID_OPTION_KEYS = [:uid, :size, :align] - - def cast_to_photo_id(object) - object.respond_to?(:photo_id) ? object.photo_id : object - end - - VALID_FB_SHARED_PHOTO_SIZES = [:thumb, :small, :normal, :square] - VALID_FB_PHOTO_SIZES = VALID_FB_SHARED_PHOTO_SIZES - VALID_FB_PROFILE_PIC_SIZES = VALID_FB_SHARED_PHOTO_SIZES - VALID_PERMISSIONS=[:email, :offline_access, :status_update, :photo_upload, :create_listing, :create_event, :rsvp_event, :sms, :video_upload, - :publish_stream, :read_stream, :read_mailbox] - - # Render an fb:tabs tag containing some number of fb:tab_item tags. - # Example: - # <% fb_tabs do %> - # <%= fb_tab_item("Home", "home") %> - # <%= fb_tab_item("Office", "office") %> - # <% end %> - def fb_tabs(&block) - content = capture(&block) - versioned_concat(content_tag("fb:tabs", content), block.binding) - end - - # Render an fb:tab_item tag. - # Use this in conjunction with fb_tabs - # Options can contains :selected => true to indicate that a tab is the current tab. - # See: http://wiki.developers.facebook.com/index.php/Fb:tab-item for complete list of options - def fb_tab_item(title, url, options={}) - options= options.dup - options.assert_valid_keys(FB_TAB_ITEM_VALID_OPTION_KEYS) - options.merge!(:title => title, :href => url) - validate_fb_tab_item_align_value(options) - tag("fb:tab-item", stringify_vals(options)) - end - - FB_TAB_ITEM_VALID_OPTION_KEYS = [:align, :selected] - - def validate_fb_tab_item_align_value(options) - if options.has_key?(:align) && !VALID_FB_TAB_ITEM_ALIGN_VALUES.include?(options[:align].to_sym) - raise(ArgumentError, "Unknown value for align: #{options[:align]}") - end - end - - def validate_fb_photo_align_value(options) - if options.has_key?(:align) && !VALID_FB_PHOTO_ALIGN_VALUES.include?(options[:align].to_sym) - raise(ArgumentError, "Unknown value for align: #{options[:align]}") - end - end - - VALID_FB_SHARED_ALIGN_VALUES = [:left, :right] - VALID_FB_PHOTO_ALIGN_VALUES = VALID_FB_SHARED_ALIGN_VALUES - VALID_FB_TAB_ITEM_ALIGN_VALUES = VALID_FB_SHARED_ALIGN_VALUES - - - # Create a Facebook wall. It can contain fb_wall_posts - # - # For Example: - # <% fb_wall do %> - # <%= fb_wall_post(@user,"This is my message") %> - # <%= fb_wall_post(@otheruser,"This is another message") %> - # <% end %> - def fb_wall(&proc) - content = capture(&proc) - versioned_concat(content_tag("fb:wall",content,{}),proc.binding) - end - - # Render an tag - # TODO: Optionally takes a time parameter t = int The current time, which is displayed in epoch seconds. - def fb_wallpost(user,message) - content_tag("fb:wallpost",message,:uid=>cast_to_facebook_id(user)) - end - alias_method :fb_wall_post, :fb_wallpost - - # Render an tag - # If message and text are present then this will render fb:error and fb:message tag - # TODO: Optionally takes a decoration tag with value of 'no_padding' or 'shorten' - def fb_error(message, text=nil) - fb_status_msg("error", message, text) - end - - # Render an tag - # If message and text are present then this will render fb:error and fb:message tag - # TODO: Optionally takes a decoration tag with value of 'no_padding' or 'shorten' - def fb_explanation(message, text=nil) - fb_status_msg("explanation", message, text) - end - - # Render an tag - # If message and text are present then this will render fb:error and fb:message tag - # TODO: Optionally takes a decoration tag with value of 'no_padding' or 'shorten' - def fb_success(message, text=nil) - fb_status_msg("success", message, text) - end - - # Render flash values as and tags - # - # values in flash[:notice] will be rendered as an - # - # values in flash[:error] will be rendered as an - # TODO: Allow flash[:info] to render fb_explanation - def facebook_messages - message="" - unless flash[:notice].blank? - message += fb_success(*flash[:notice]) - end - unless flash[:error].blank? - message += fb_error(*flash[:error]) - end - message - end - - # Create a dashboard. It can contain fb_action, fb_help, and fb_create_button - # - # For Example: - # <% fb_dashboard do %> - # <%= APP_NAME %> - # <%= fb_action 'My Matches', search_path %> - # <%= fb_help 'Feedback', "http://www.facebook.com/apps/application.php?id=6236036681" %> - # <%= fb_create_button 'Invite Friends', main_path %> - # <% end %> - def fb_dashboard(&proc) - if block_given? - content = capture(&proc) - versioned_concat(content_tag("fb:dashboard",content,{}),proc.binding) - else - content_tag("fb:dashboard",content,{}) - end - end - - # Content for the wide profile box goes in this tag - def fb_wide(&proc) - content = capture(&proc) - versioned_concat(content_tag("fb:wide", content, {}), proc.binding) - end - - # Content for the narrow profile box goes in this tag - def fb_narrow(&proc) - content = capture(&proc) - versioned_concat(content_tag("fb:narrow", content, {}), proc.binding) - end - - # Renders an action using the tag - def fb_action(name, url) - "#{name}" - end - - # Render a tag - # For use inside - def fb_help(name, url) - "#{name}" - end - - # Render a tag - # For use inside - def fb_create_button(name, url) - "#{name}" - end - - # Create a comment area - # All the data for this content area is stored on the facebook servers. - # See: http://wiki.developers.facebook.com/index.php/Fb:comments for full details - def fb_comments(xid,canpost=true,candelete=false,numposts=5,options={}) - options = options.dup - title = (title = options.delete(:title)) ? fb_title(title) : nil - content_tag "fb:comments",title,stringify_vals(options.merge(:xid=>xid,:canpost=>canpost.to_s,:candelete=>candelete.to_s,:numposts=>numposts)) - end - - # Adds a title to the title bar - # - # Facebook | App Name | This is the canvas page window title - # - # +title+: This is the canvas page window - def fb_title(title) - "#{title}" - end - - # Create a Google Analytics tag - # - # +uacct+: Your Urchin/Google Analytics account ID. - def fb_google_analytics(uacct, options={}) - options = options.dup - tag "fb:google-analytics", stringify_vals(options.merge(:uacct => uacct)) - end - - # Render if-is-app-user tag - # This tag renders the enclosing content only if the user specified has accepted the terms of service for the application. - # Use fb_if_user_has_added_app to determine wether the user has added the app. - # Example: - # <% fb_if_is_app_user(@facebook_user) do %> - # Thanks for accepting our terms of service! - # <% fb_else do %> - # Hey you haven't agreed to our terms. <%= link_to("Please accept our terms of service.", :action => "terms_of_service") %> - # <% end %> - #<% end %> - def fb_if_is_app_user(user=nil,options={},&proc) - content = capture(&proc) - options = options.dup - options.merge!(:uid=>cast_to_facebook_id(user)) if user - versioned_concat(content_tag("fb:if-is-app-user",content,stringify_vals(options)),proc.binding) - end - - # Render if-user-has-added-app tag - # This tag renders the enclosing content only if the user specified has installed the application - # - # Example: - # <% fb_if_user_has_added_app(@facebook_user) do %> - # Hey you are an app user! - # <% fb_else do %> - # Hey you aren't an app user. <%= link_to("Add App and see the other side.", :action => "added_app") %> - # <% end %> - #<% end %> - def fb_if_user_has_added_app(user,options={},&proc) - content = capture(&proc) - options = options.dup - versioned_concat(content_tag("fb:if-user-has-added-app", content, stringify_vals(options.merge(:uid=>cast_to_facebook_id(user)))),proc.binding) - end - - # Render fb:if-is-user tag - # This tag only renders enclosing content if the user is one of those specified - # user can be a single user or an Array of users - # Example: - # <% fb_if_is_user(@check_user) do %> - # <%= fb_name(@facebook_user) %> are one of the users. <%= link_to("Check the other side", :action => "friend") %> - # <% fb_else do %> - # <%= fb_name(@facebook_user) %> are not one of the users <%= fb_name(@check_user) %> - # <%= link_to("Check the other side", :action => "you") %> - # <% end %> - # <% end %> - def fb_if_is_user(user,&proc) - content = capture(&proc) - user = [user] unless user.is_a? Array - user_list=user.map{|u| cast_to_facebook_id(u)}.join(",") - versioned_concat(content_tag("fb:if-is-user",content,{:uid=>user_list}),proc.binding) - end - - # Render fb:else tag - # Must be used within if block such as fb_if_is_user or fb_if_is_app_user . See example in fb_if_is_app_user - def fb_else(&proc) - content = capture(&proc) - versioned_concat(content_tag("fb:else",content),proc.binding) - end - - # - # Return the URL for the about page of the application - def fb_about_url - "http://#{Facebooker.www_server_base_url}/apps/application.php?api_key=#{Facebooker.api_key}" - end - - # - # Embed a discussion board named xid on the current page - # Seexid))) - end - - # Renders an 'Add to Profile' button - # The button allows a user to add condensed profile box to the main profile - def fb_add_profile_section - tag "fb:add-section-button",:section=>"profile" - end - - # Renders an 'Add to Info' button - # The button allows a user to add an application info section to her Info tab - def fb_add_info_section - tag "fb:add-section-button",:section=>"info" - end - - # Renders a link that, when clicked, initiates a dialog requesting the specified extended permission from the user. - # - # You can prompt a user with the following permissions: - # * email - # * read_stream - # * publish_stream - # * offline_access - # * status_update - # * photo_upload - # * create_event - # * rsvp_event - # * sms - # * video_upload - # * create_note - # * share_item - # * read_mailbox - # Example: - # <%= fb_prompt_permission('email', "Would you like to receive email from our application?" ) %> - # - # See http://wiki.developers.facebook.com/index.php/Fb:prompt-permission for - # more details. Correct as of 7th June 2009. - # - def fb_prompt_permission(permission,message,callback=nil) - raise(ArgumentError, "Unknown value for permission: #{permission}") unless VALID_PERMISSIONS.include?(permission.to_sym) - args={:perms=>permission} - args[:next_fbjs]=callback unless callback.nil? - content_tag("fb:prompt-permission",message,args) - end - - # Renders a link to prompt for multiple permissions at once. - # - # Example: - # <%= fb_prompt_permissions(['email','offline_access','status_update'], 'Would you like to grant some permissions?') - def fb_prompt_permissions(permissions,message,callback=nil) - permissions.each do |p| - raise(ArgumentError, "Unknown value for permission: #{p}") unless VALID_PERMISSIONS.include?(p.to_sym) - end - args={:perms=>permissions*','} - args[:next_fbjs]=callback unless callback.nil? - content_tag("fb:prompt-permission",message,args) - end - - # Renders an tag that displays the event name and links to the event's page. - def fb_eventlink(eid) - content_tag "fb:eventlink",nil,:eid=>eid - end - - # Renders an tag that displays the group name and links to the group's page. - def fb_grouplink(gid) - content_tag "fb:grouplink",nil,:gid=>gid - end - - # Returns the status of the user - def fb_user_status(user,linked=true) - content_tag "fb:user-status",nil,stringify_vals(:uid=>cast_to_facebook_id(user), :linked=>linked) - end - - # Renders a standard 'Share' button for the specified URL. - def fb_share_button(url) - content_tag "fb:share-button",nil,:class=>"url",:href=>url - end - - # Renders the FBML on a Facebook server inside an iframe. - # - # Meant to be used for a Facebook Connect site or an iframe application - def fb_serverfbml(options={},&proc) - inner = capture(&proc) - versioned_concat(content_tag("fb:serverfbml",inner,options), proc.binding) - end - - def fb_container(options={},&proc) - inner = capture(&proc) - versioned_concat(content_tag("fb:container",inner,options), proc.binding) - end - - # Renders an fb:time element - # - # Example: - # <%= fb_time(Time.now, :tz => 'America/New York', :preposition => true) %> - # - # See http://wiki.developers.facebook.com/index.php/Fb:time for - # more details - def fb_time(time, options={}) - tag "fb:time",stringify_vals({:t => time.to_i}.merge(options)) - end - - # Renders a fb:intl element - # - # Example: - # <%= fb_intl('Age', :desc => 'Label for the age form field', :delimiters => '[]') %> - # - # See http://wiki.developers.facebook.com/index.php/Fb:intl for - # more details - def fb_intl(text=nil, options={}, &proc) - raise ArgumentError, "Missing block or text" unless block_given? or text - if block_given? - versioned_concat(fb_intl(capture(&proc), options)) - else - content_tag("fb:intl", text, stringify_vals(options)) - end - end - - # Renders a fb:intl-token element - # - # Example: - # <%= fb_intl-token('number', 5) %> - # - # See http://wiki.developers.facebook.com/index.php/Fb:intl-token for - # more details - def fb_intl_token(name, text=nil, &proc) - raise ArgumentError, "Missing block or text" unless block_given? or text - if block_given? - versioned_concat(fb_intl_token(name, capture(&proc))) - else - content_tag("fb:intl-token", text, stringify_vals({:name => name})) - end - end - - # Renders a fb:date element - # - # Example: - # <%= fb_date(Time.now, :format => 'verbose', :tz => 'America/New York') %> - # - # See http://wiki.developers.facebook.com/index.php/Fb:date for - # more details - def fb_date(time, options={}) - tag "fb:date", stringify_vals({:t => time.to_i}.merge(options)) - end - - # Renders the Facebook bookmark button - # - # See http://wiki.developers.facebook.com/index.php/Fb:bookmark for - # more details - def fb_bookmark_button - content_tag "fb:bookmark" - end - - # Renders a fb:fbml-attribute element - # - # Example: - # <%= fb_fbml_attribute('title', Education) %> - # - # The options hash is passed to the fb:intl element that is generated inside this element - # and can have the keys available for the fb:intl element. - # - # See http://wiki.developers.facebook.com/index.php/Fb:fbml-attribute for - # more details - def fb_fbml_attribute(name, text, options={}) - content_tag("fb:fbml-attribute", fb_intl(text, options), stringify_vals({:name => name})) - end - - protected - - def cast_to_facebook_id(object) - Facebooker::User.cast_to_facebook_id(object) - end - - def validate_fb_profile_pic_size(options) - if options.has_key?(:size) && !VALID_FB_PROFILE_PIC_SIZES.include?(options[:size].to_sym) - raise(ArgumentError, "Unknown value for size: #{options[:size]}") - end - end - - def validate_fb_photo_size(options) - if options.has_key?(:size) && !VALID_FB_PHOTO_SIZES.include?(options[:size].to_sym) - raise(ArgumentError, "Unknown value for size: #{options[:size]}") - end - end - - private - def stringify_vals(hash) - result={} - hash.each do |key,value| - result[key]=value.to_s - end - result - end - - def fb_status_msg(type, message, text) - if text.blank? - tag("fb:#{type}", :message => message) - else - content_tag("fb:#{type}", content_tag("fb:message", message) + text) - end - end - - def token_tag - unless protect_against_forgery? - '' - else - tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token) - end - end - - def ignore_binding? - ActionPack::VERSION::MAJOR >= 2 && ActionPack::VERSION::MINOR >= 2 - end - end - end -end - -class Hash - def transform_keys!(transformation_hash) - transformation_hash.each_pair{|key, value| transform_key!(key, value)} - end - - - def transform_key!(old_key, new_key) - swapkey!(new_key, old_key) - end - - ### This method is lifted from Ruby Facets core - def swapkey!( newkey, oldkey ) - self[newkey] = self.delete(oldkey) if self.has_key?(oldkey) - self - end - - # We can allow css attributes. - FB_ALWAYS_VALID_OPTION_KEYS = [:class, :style] - def assert_valid_keys(*valid_keys) - unknown_keys = keys - [valid_keys + FB_ALWAYS_VALID_OPTION_KEYS].flatten - raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty? - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/helpers/fb_connect.rb b/vendor/plugins/facebooker/lib/facebooker/rails/helpers/fb_connect.rb deleted file mode 100644 index eb533ae5e..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/helpers/fb_connect.rb +++ /dev/null @@ -1,168 +0,0 @@ -module Facebooker - module Rails - module Helpers - module FbConnect - include Facebooker::Rails::Helpers::StreamPublish - def fb_connect_javascript_tag(options = {}) - # accept both Rails and Facebook locale formatting, i.e. "en-US" and "en_US". - lang = "/#{options[:lang].to_s.gsub('-', '_')}" if options[:lang] - # dont use the javascript_include_tag helper since it adds a .js at the end - if request.ssl? - "" - else - "" - end - end - - # - # For information on the :app_settings argument see http://wiki.developers.facebook.com/index.php/JS_API_M_FB.Facebook.Init_2 - # While it would be nice to treat :app_settings as a hash, some of the arguments do different things if they are a string vs a javascript function - # and Rails' Hash#to_json always quotes strings so there is no way to indicate when the value should be a javascript function. - # For this reason :app_settings needs to be a string that is valid JSON (including the {}'s). - # - def init_fb_connect(*required_features, &proc) - init_fb_connect_with_options({},*required_features, &proc) - end - - def init_fb_connect_with_options(options = {},*required_features, &proc) - additions = "" - if block_given? - additions = capture(&proc) - end - - # Yes, app_settings is set to a string of an empty JSON element. That's intentional. - options = options.merge({:app_settings => '{}'}) - - if required_features.last.is_a?(Hash) - options.merge!(required_features.pop.symbolize_keys) - end - - if request.ssl? - init_string = "FB.init('#{Facebooker.api_key}','/xd_receiver_ssl.html', #{options[:app_settings]});" - else - init_string = "FB.init('#{Facebooker.api_key}','/xd_receiver.html', #{options[:app_settings]});" - end - unless required_features.blank? - init_string = <<-FBML - #{case options[:js] - when :jquery then "jQuery(document).ready(" - when :dojo then "dojo.addOnLoad(" - when :mootools then "window.addEvent('domready'," - else "Event.observe(window,'load'," - end} function() { - FB_RequireFeatures(#{required_features.to_json}, function() { - #{init_string} - #{additions} - }); - }); - FBML - end - - # block_is_within_action_view? is rails 2.1.x and has been - # deprecated. rails >= 2.2.x uses block_called_from_erb? - block_tester = respond_to?(:block_is_within_action_view?) ? - :block_is_within_action_view? : :block_called_from_erb? - - if block_given? && send(block_tester, proc) - versioned_concat(javascript_tag(init_string),proc.binding) - else - javascript_tag init_string - end - end - - # - # Render an element - # - # ==== Examples - # - # <%= fb_login_button%> - # => - # - # Specifying a javascript callback - # - # <%= fb_login_button 'update_something();'%> - # => - # - # Adding options See: http://wiki.developers.facebook.com/index.php/Fb:login-button - # - # <%= fb_login_button 'update_something();', :size => :small, :background => :dark%> - # => - # - # :text option allows you to set the text value of the - # button. *A note!* This will only do what you expect it to do - # if you set :v => 2 as well. - # - # <%= fb_login_button 'update_somethign();', - # :text => 'Loginto Facebook', :v => 2 %> - # => Login to Facebook - def fb_login_button(*args) - - callback = args.first - options = args[1] || {} - options.merge!(:onlogin=>callback) if callback - - text = options.delete(:text) - - content_tag("fb:login-button",text, options) - end - - # - # Render an element, similar to - # fb_login_button. Adds a js redirect to the onlogin event via rjs. - # - # ==== Examples - # - # fb_login_and_redirect '/other_page' - # => - # - # Like #fb_login_button, this also supports the :text option - # - # fb_login_and_redirect '/other_page', :text => "Login with Facebook", :v => '2' - # => Login with Facebook - # - def fb_login_and_redirect(url, options = {}) - js = update_page do |page| - page.redirect_to url - end - - text = options.delete(:text) - - content_tag("fb:login-button",text,options.merge(:onlogin=>js)) - end - - def fb_unconnected_friends_count - content_tag "fb:unconnected-friends-count",nil - end - - def fb_logout_link(text,url,*args) - js = update_page do |page| - page.call "FB.Connect.logoutAndRedirect",url - # When session is valid, this call is meaningless, since we already redirect - # When session is invalid, it will log the user out of the system. - page.redirect_to url - end - link_to_function text, js, *args - end - - def fb_bookmark_link(text,url,*args) - js = update_page do |page| - page.call "FB.Connect.showBookmarkDialog",url - end - link_to_function text, js, *args - end - - def fb_user_action(action, user_message = nil, prompt = "", callback = nil) - defaulted_callback = callback || "null" - update_page do |page| - page.call("FB.Connect.showFeedDialog",action.template_id,action.data,action.target_ids,action.body_general,nil,page.literal("FB.RequireConnect.promptConnect"),page.literal(defaulted_callback),prompt,user_message.nil? ? nil : {:value=>user_message}) - end - end - - def fb_connect_stream_publish(stream_post,user_message_prompt=nil,callback=nil,auto_publish=false,actor=nil) - stream_publish("FB.Connect.streamPublish",stream_post,user_message_prompt,callback,auto_publish,actor) - end - - end - end - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/helpers/stream_publish.rb b/vendor/plugins/facebooker/lib/facebooker/rails/helpers/stream_publish.rb deleted file mode 100644 index 95217ec8c..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/helpers/stream_publish.rb +++ /dev/null @@ -1,22 +0,0 @@ -module Facebooker - module Rails - module Helpers - module StreamPublish - def stream_publish(js_method,stream_post,user_message_prompt=nil,callback=nil,auto_publish=false,actor=nil) - defaulted_callback = callback || "null" - update_page do |page| - page.call(js_method, - stream_post.user_message, - stream_post.attachment.to_hash, - stream_post.action_links, - Facebooker::User.cast_to_facebook_id(stream_post.target), - user_message_prompt, - page.literal(defaulted_callback), - auto_publish, - Facebooker::User.cast_to_facebook_id(actor)) - end - end - end - end - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/integration_session.rb b/vendor/plugins/facebooker/lib/facebooker/rails/integration_session.rb deleted file mode 100644 index 0e85e4cf3..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/integration_session.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'action_controller/integration' - -class Facebooker::Rails::IntegrationSession < ActionController::Integration::Session - include Facebooker::Rails::TestHelpers - attr_accessor :default_request_params, :canvas - - def process(method, path, parameters = nil, headers = nil) - if canvas - parameters = facebook_params(@default_request_params.merge(parameters || {})) - end - super method, path, parameters, headers - end - - def reset! - self.default_request_params = {:fb_sig_in_canvas => '1', :fb_sig_api_key => Facebooker::Session.api_key}.with_indifferent_access - self.canvas = true - super - end - - def get(path, parameters = nil, headers = nil) - if canvas - post path, (parameters || {}).merge('fb_sig_request_method' => 'GET'), headers - else - super path, parameters, headers - end - end - - %w(put delete).each do |method| - define_method method do |*args| - if canvas - path, parameters, headers = *args - post path, (parameters || {}).merge('_method' => method.upcase), headers - else - super *args - end - end - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/profile_publisher_extensions.rb b/vendor/plugins/facebooker/lib/facebooker/rails/profile_publisher_extensions.rb deleted file mode 100644 index 98e997d89..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/profile_publisher_extensions.rb +++ /dev/null @@ -1,42 +0,0 @@ -module Facebooker - module Rails - module ProfilePublisherExtensions - - ## - # returns true if Facebook is requesting the interface for a profile publisher - def wants_interface? - params[:method] == "publisher_getInterface" - end - - ## - # render the interface for a publisher. - # fbml is the content in string form. Use render_to_string to get the content from a template - # publish_enabled controlls whether the post form is active by default. If it isn't, you'll need to use fbjs to activate it - # comment_enabled controls whether to include a comment box - def render_publisher_interface(fbml,publish_enabled=true,comment_enabled=false) - render :json=>{:content=>{:fbml=>fbml,:publishEnabled=>publish_enabled,:commentEnabled=>comment_enabled}, - :method=>"publisher_getInterface"} - end - - # render an error while publishing the template - # This can be used for validation errors - def render_publisher_error(title,body) - render :json=>{:errorCode=>1,:errorTitle=>title,:errorMessage=>body}.to_json - end - - # render the response for a feed. This takes a user_action object like those returned from the Rails Publisher - # For instance, AttackPublisher.create_attack(@attack) - # The template must have been registered previously - def render_publisher_response(user_action) - render :json=>{:content=> { - :feed=>{ - :template_id=>user_action.template_id, - :template_data=>user_action.data - } - }, - :method=>"publisher_getFeedStory" - } - end - end - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/publisher.rb b/vendor/plugins/facebooker/lib/facebooker/rails/publisher.rb deleted file mode 100644 index db200094c..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/publisher.rb +++ /dev/null @@ -1,608 +0,0 @@ -module Facebooker - module Rails - # ActionMailer like module for publishing Facbook messages - # - # To use, create a subclass and define methods - # Each method should start by calling send_as to specify the type of message - # Valid options are :email and :notification, :user_action, :profile, :ref, :publish_stream - # - # - # Below is an example of each type - # - # class TestPublisher < Facebooker::Rails::Publisher - # # The new message templates are supported as well - # # First, create a method that contains your templates: - # # You may include multiple one line story templates and short story templates - # # but only one full story template - # # Your most specific template should be first - # # - # # Before using, you must register your template by calling register. For this example - # # You would call TestPublisher.register_publish_action - # # Registering the template will store the template id returned from Facebook in the - # # facebook_templates table that is created when you create your first publisher - # def publish_action_template - # one_line_story_template "{*actor*} did stuff with {*friend*}" - # one_line_story_template "{*actor*} did stuff" - # short_story_template "{*actor*} has a title {*friend*}", render(:partial=>"short_body") - # short_story_template "{*actor*} has a title", render(:partial=>"short_body") - # full_story_template "{*actor*} has a title {*friend*}", render(:partial=>"full_body") - # action_links action_link("My text {*template_var*}","{*link_url*}") - # end - # - # # To send a registered template, you need to create a method to set the data - # # The publisher will look up the template id from the facebook_templates table - # def publish_action(f) - # send_as :user_action - # from f - # story_size SHORT # or ONE_LINE or FULL - # data :friend=>"Mike" - # end - # - # - # # Provide a from user to send a general notification - # # if from is nil, this will send an announcement - # def notification(to,f) - # send_as :notification - # recipients to - # from f - # fbml "Not" - # end - # - # def email(to,f) - # send_as :email - # recipients to - # from f - # title "Email" - # fbml 'text' - # text fbml - # end - # # This will render the profile in /users/profile.fbml.erb - # # it will set @user to user_to_update in the template - # # The mobile profile will be rendered from the app/views/test_publisher/_mobile.erb - # # template - # def profile_update(user_to_update,user_with_session_to_use) - # send_as :profile - # from user_with_session_to_use - # recipients user_to_update - # profile render(:file=>"users/profile.fbml.erb",:assigns=>{:user=>user_to_update}) - # profile_action "A string" - # mobile_profile render(:partial=>"mobile",:assigns=>{:user=>user_to_update}) - # end - # - # # Update the given handle ref with the content from a - # # template - # def ref_update(user) - # send_as :ref - # from user - # fbml render(:file=>"users/profile",:assigns=>{:user=>user_to_update}) - # handle "a_ref_handle" - # end - # - # # Publish a post into the stream on the user's Wall and News Feed. - # def publish_stream(user_with_session_to_use, user_to_update, params) - # send_as :publish_stream - # from user_with_session_to_use - # target user_to_update - # attachment params[:attachment] - # message params[:message] - # action_links params[:action_links] - # end - # - # - # To send a message, use ActionMailer like semantics - # TestPublisher.deliver_action(@user) - # - # For testing, you may want to create an instance of the underlying message without sending it - # TestPublisher.create_action(@user) - # will create and return an instance of Facebooker::Feeds::Action - # - # Publisher makes many helpers available, including the linking and asset helpers - class Publisher - - #story sizes from the Facebooker API - ONE_LINE=1 - SHORT=2 - FULL=4 - - def initialize - @from = nil - @full_story_template = nil - @recipients = nil - @action_links = nil - @controller = PublisherController.new(self) - @action_links = nil - end - - def self.default_url_options - {:host => Facebooker.canvas_server_base + Facebooker.facebook_path_prefix} - end - - def default_url_options - self.class.default_url_options - end - - # use facebook options everywhere - def request_comes_from_facebook? - true - end - - class FacebookTemplate < ::ActiveRecord::Base - cattr_accessor :template_cache - self.template_cache = {} - - def self.inspect(*args) - "FacebookTemplate" - end - - def template_changed?(hash) - if respond_to?(:content_hash) - content_hash != hash - else - false - end - end - - def deactivate - Facebooker::Session.create.deactivate_template_bundle_by_id(self.bundle_id) - return true - rescue Facebooker::Session::TemplateBundleInvalid => e - return false - end - - - - class << self - - def register(klass,method) - publisher = setup_publisher(klass,method) - template_id = Facebooker::Session.create.register_template_bundle(publisher.one_line_story_templates,publisher.short_story_templates,publisher.full_story_template,publisher.action_links) - template = find_or_initialize_by_template_name(template_name(klass,method)) - template.deactivate if template.bundle_id # deactivate old templates to avoid exceeding templates/app limit - template.bundle_id = template_id - template.content_hash = hashed_content(klass,method) if template.respond_to?(:content_hash) - template.save! - cache(klass,method,template) - template - end - - def for_class_and_method(klass,method) - find_cached(klass,method) - end - def bundle_id_for_class_and_method(klass,method) - for_class_and_method(klass,method).bundle_id - end - - def cache(klass,method,template) - template_cache[template_name(klass,method)] = template - end - - def clear_cache! - self.template_cache = {} - end - - def find_cached(klass,method) - template_cache[template_name(klass,method)] || find_in_db(klass,method) - end - - def find_in_db(klass,method) - template = find_by_template_name(template_name(klass,method)) - - if template.nil? || template.template_changed?(hashed_content(klass, method)) - template = register(klass,method) - end - template - end - - def setup_publisher(klass,method) - publisher = klass.new - publisher.send method + '_template' - publisher - end - - def hashed_content(klass, method) - publisher = setup_publisher(klass,method) - # sort the Hash elements (in the short_story and full_story) before generating MD5 - Digest::MD5.hexdigest [publisher.one_line_story_templates, - (publisher.short_story_templates and publisher.short_story_templates.collect{|ss| ss.to_a.sort_by{|e| e[0].to_s}}), - (publisher.full_story_template and publisher.full_story_template.to_a.sort_by{|e| e[0].to_s}) - ].to_json - end - - def template_name(klass,method) - "#{Facebooker.api_key}: #{klass.name}::#{method}" - end - end - end - - class_inheritable_accessor :master_helper_module - attr_accessor :one_line_story_templates, :short_story_templates - attr_writer :action_links - - cattr_accessor :skip_registry - self.skip_registry = false - - - class InvalidSender < StandardError; end - class UnknownBodyType < StandardError; end - class UnspecifiedBodyType < StandardError; end - class Email - attr_accessor :title - attr_accessor :text - attr_accessor :fbml - end - - class Notification - attr_accessor :fbml - end - - class Profile - attr_accessor :profile - attr_accessor :profile_action - attr_accessor :mobile_profile - attr_accessor :profile_main - end - class Ref - attr_accessor :handle - attr_accessor :fbml - end - class UserAction - attr_accessor :data - attr_reader :target_ids - attr_accessor :body_general - attr_accessor :template_id - attr_accessor :template_name - attr_accessor :story_size - - def target_ids=(val) - @target_ids = val.is_a?(Array) ? val.join(",") : val - end - - def data_hash - data||{} - end - end - - class PublishStream - attr_accessor :target - attr_accessor :attachment - attr_accessor :action_links - attr_accessor :message - end - - cattr_accessor :ignore_errors - attr_accessor :_body - - def recipients(*args) - if args.size==0 - @recipients - else - @recipients=args.first - end - end - - def from(*args) - if args.size==0 - @from - else - @from=args.first - end - end - - - def send_as(option) - self._body=case option - when :action - Facebooker::Feed::Action.new - when :story - Facebooker::Feed::Story.new - when :templatized_action - Facebooker::Feed::TemplatizedAction.new - when :notification - Notification.new - when :email - Email.new - when :profile - Profile.new - when :ref - Ref.new - when :user_action - UserAction.new - when :publish_stream - StreamPost.new - else - raise UnknownBodyType.new("Unknown type to publish") - end - end - - def full_story_template(title=nil,body=nil,params={}) - if title.nil? - @full_story_template - else - @full_story_template=params.merge(:template_title=>title, :template_body=>body) - end - end - - def one_line_story_template(str) - @one_line_story_templates ||= [] - @one_line_story_templates << str - end - - def short_story_template(title,body,params={}) - @short_story_templates ||= [] - @short_story_templates << params.merge(:template_title=>title, :template_body=>body) - end - - def action_links(*links) - if self._body and self._body.respond_to?(:action_links) - self._body.send(:action_links,*links) - end - if links.blank? - @action_links - else - @action_links = *links - end - end - - def method_missing(name,*args) - if args.size==1 and self._body.respond_to?("#{name}=") - self._body.send("#{name}=",*args) - elsif self._body.respond_to?(name) - self._body.send(name,*args) - else - super - end - end - - # work around the fact that facebook cares about the order of the keys in the hash - class ImageHolder - attr_accessor :src,:href - def initialize(src,href) - self.src=src - self.href=href - end - - def ==(other) - self.src == other.src && self.href == other.href - end - - def to_json(*args) - "{\"src\":#{src.to_json}, \"href\":#{href.to_json}}" - end - end - - def image(src,target) - ImageHolder.new(image_path(src),target.respond_to?(:to_str) ? target : url_for(target)) - end - - def action_link(text,target) - {:text=>text, :href=>target} - end - - def requires_from_user?(from,body) - ! (announcement_notification?(from,body) or ref_update?(body) or profile_update?(body)) - end - - def profile_update?(body) - body.is_a?(Profile) - end - - def ref_update?(body) - body.is_a?(Ref) - end - - def announcement_notification?(from,body) - from.nil? and body.is_a?(Notification) - end - - def send_message(method) - @recipients = @recipients.is_a?(Array) ? @recipients : [@recipients] - if from.nil? and @recipients.size==1 and requires_from_user?(from,_body) - @from = @recipients.first - end - # notifications can - # omit the from address - raise InvalidSender.new("Sender must be a Facebooker::User") unless from.is_a?(Facebooker::User) || !requires_from_user?(from,_body) - case _body - when Facebooker::Feed::TemplatizedAction,Facebooker::Feed::Action - from.publish_action(_body) - when Facebooker::Feed::Story - @recipients.each {|r| r.publish_story(_body)} - when Notification - (from.nil? ? Facebooker::Session.create : from.session).send_notification(@recipients,_body.fbml) - when Email - from.session.send_email(@recipients, - _body.title, - _body.text, - _body.fbml) - when Profile - # If recipient and from aren't the same person, create a new user object using the - # userid from recipient and the session from from - @from = Facebooker::User.new(Facebooker::User.cast_to_facebook_id(@recipients.first),Facebooker::Session.create) - @from.set_profile_fbml(_body.profile, _body.mobile_profile, _body.profile_action, _body.profile_main) - when Ref - Facebooker::Session.create.server_cache.set_ref_handle(_body.handle,_body.fbml) - when UserAction - @from.session.publish_user_action(_body.template_id,_body.data_hash,_body.target_ids,_body.body_general,_body.story_size) - when Facebooker::StreamPost - @from.publish_to(_body.target, {:attachment => _body.attachment, :action_links => @action_links, :message => _body.message }) - else - raise UnspecifiedBodyType.new("You must specify a valid send_as") - end - end - - # nodoc - # needed for actionview - def logger - RAILS_DEFAULT_LOGGER - end - - # nodoc - # delegate to action view. Set up assigns and render - def render(opts) - opts = opts.dup - body = opts.delete(:assigns) || {} - initialize_template_class(body.dup.merge(:controller=>self)).render(opts) - end - - - def initialize_template_class(assigns) - template_root = "#{RAILS_ROOT}/app/views" - controller_root = File.join(template_root,self.class.controller_path) - #only do this on Rails 2.1 - if ActionController::Base.respond_to?(:append_view_path) - # only add the view path once - unless ActionController::Base.view_paths.include?(controller_root) - ActionController::Base.append_view_path(controller_root) - ActionController::Base.append_view_path(controller_root+"/..") - end - view_paths = ActionController::Base.view_paths - else - view_paths = [template_root, controller_root] - end - returning ActionView::Base.new(view_paths, assigns, self) do |template| - template.controller=self - template.extend(self.class.master_helper_module) - def template.request_comes_from_facebook? - true - end - end - end - - - self.master_helper_module = Module.new - self.master_helper_module.module_eval do - # url_helper delegates to @controller, - # so we need to define that in the template - # we make it point to the publisher - include ActionView::Helpers::UrlHelper - include ActionView::Helpers::TextHelper - include ActionView::Helpers::TagHelper - include ActionView::Helpers::FormHelper - include ActionView::Helpers::FormTagHelper - include ActionView::Helpers::AssetTagHelper - include ActionView::Helpers::NumberHelper - include Facebooker::Rails::Helpers - - #define this for the publisher views - def protect_against_forgery? - @paf ||= ActionController::Base.new.send(:protect_against_forgery?) - end - - # url_for calls in publishers tend to want full paths - def url_for(options = {}) - super(options.kind_of?(Hash) ? {:only_path => false}.update(options) : options) - end - end - ActionController::Routing::Routes.named_routes.install(self.master_helper_module) - include self.master_helper_module - class < (request.parameters[:fb_sig_in_canvas]=="1") - end - end - module MapperExtensions - - # Generates pseudo-resource routes. Since everything is a POST, routes can't be identified - # using HTTP verbs. Therefore, the action is appended to the beginning of each named route, - # except for index. - # - # Example: - # map.facebook_resources :profiles - # - # Generates the following routes: - # - # new_profile POST /profiles/new {:controller=>"profiles", :action=>"new"} - # profiles POST /profiles/index {:controller=>"profiles", :action=>"index"} - # show_profile POST /profiles/:id/show {:controller=>"profiles", :action=>"show"} - # create_profile POST /profiles/create {:controller=>"profiles", :action=>"create"} - # edit_profile POST /profiles/:id/edit {:controller=>"profiles", :action=>"edit"} - # update_profile POST /profiles/:id/update {:controller=>"profiles", :action=>"update"} - # destroy_profile POST /profiles/:id/destroy {:controller=>"profiles", :action=>"destroy"} - # - def facebook_resources(name_sym) - name = name_sym.to_s - - with_options :controller => name, :conditions => { :method => :post } do |map| - map.named_route("new_#{name.singularize}", "#{name}/new", :action => 'new') - map.named_route(name, "#{name}/index", :action => 'index') - map.named_route("show_#{name.singularize}", "#{name}/:id/show", :action => 'show', :id => /\d+/) - map.named_route("create_#{name.singularize}", "#{name}/create", :action => 'create') - map.named_route("edit_#{name.singularize}", "#{name}/:id/edit", :action => 'edit', :id => /\d+/) - map.named_route("update_#{name.singularize}", "#{name}/:id/update", :action => 'update', :id => /\d+/) - map.named_route("destroy_#{name.singularize}", "#{name}/:id/destroy", :action => 'destroy', :id => /\d+/) - end - end - end - end - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/rails/test_helpers.rb b/vendor/plugins/facebooker/lib/facebooker/rails/test_helpers.rb deleted file mode 100644 index fec3a919f..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/rails/test_helpers.rb +++ /dev/null @@ -1,68 +0,0 @@ -module Facebooker - module Rails - module TestHelpers - def assert_facebook_redirect_to(url) - assert_response :success - assert_not_nil facebook_redirect_url - assert_equal url, facebook_redirect_url - end - - def follow_facebook_redirect! - facebook_post facebook_redirect_url - end - - def facebook_get(path, params={}, session=nil, flash=nil) - facebook_verb(:get, path, params, session, flash) - end - - def facebook_post(path,params={}, session=nil, flash=nil) - facebook_verb(:post, path, params, session, flash) - end - - def facebook_put(path,params={}, session=nil, flash=nil) - facebook_verb(:put, path, params, session, flash) - end - - def facebook_delete(path,params={}, session=nil, flash=nil) - facebook_verb(:delete, path, params, session, flash) - end - - def facebook_verb(verb, path, params={}, session=nil, flash=nil) - send verb, path, facebook_params(params).reverse_merge(:canvas => true), session, flash - end - - def facebook_params(params = {}) - params = default_facebook_parameters.with_indifferent_access.merge(params || {}) - sig = generate_signature params - params.merge(:fb_sig => sig) - end - - private - - def default_facebook_parameters - { - :fb_sig_added => "1", - :fb_sig_session_key => "facebook_session_key", - :fb_sig_user => "1234", - :fb_sig_expires => "0", - :fb_sig_in_canvas => "1", - :fb_sig_time => Time.now.to_f - } - end - - def facebook_redirect_url - match = @response.body.match(/ handle_name, :fbml => fbml_source},false) == '1' - end - - ## - # Fetches and re-caches the content stored at the given URL, for use in a fb:ref FBML tag. - def refresh_ref_url(url) - (@session.post 'facebook.fbml.refreshRefUrl', {:url => url},false) == '1' - end - - def refresh_img_src(url) - (@session.post 'facebook.fbml.refreshImgSrc', {:url => url},false) == '1' - end - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/service.rb b/vendor/plugins/facebooker/lib/facebooker/service.rb deleted file mode 100644 index 074e518bc..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/service.rb +++ /dev/null @@ -1,103 +0,0 @@ -require 'facebooker/parser' -module Facebooker - class Service - def initialize(api_base, api_path, api_key) - @api_base = api_base - @api_path = api_path - @api_key = api_key - end - - @active_service = nil - def self.active_service - unless @active_service - if Facebooker.use_curl? - @active_service = Facebooker::Service::CurlService.new - else - @active_service = Facebooker::Service::NetHttpService.new - end - end - @active_service - end - - def self.active_service=(new_service) - @active_service = new_service - end - - def self.with_service(service) - old_service = active_service - self.active_service = service - begin - yield - ensure - self.active_service = old_service - end - end - - - # Process all calls to Facebook in th block asynchronously - # nil will be returned from all calls and no results will be parsed. This is mostly useful - # for sending large numbers of notifications or sending a lot of profile updates - # - # for example: - # User.find_in_batches(:batch_size => 200) do |users| - # Faceboooker::Service.with_async do - # users.each {|u| u.facebook_session.send_notification(...)} - # end - # end - # - # Note: You shouldn't make more than about 200 api calls in a with_async block - # or you might exhaust all filehandles. - # - # This functionality require the typhoeus gem - # - def self.with_async(&proc) - block_with_process = Proc.new { proc.call ; process_async} - with_service(Facebooker::Service::TyphoeusMultiService.new,&block_with_process) - end - - def self.process_async - active_service.process - end - - - # TODO: support ssl - def post(params) - attempt = 0 - if active_service.parse_results? - Parser.parse(params[:method], post_form(url,params) ) - else - post_form(url,params) - end - rescue Errno::ECONNRESET, EOFError - if attempt == 0 - attempt += 1 - retry - end - end - - def post_form(url,params) - active_service.post_form(url,params) - end - - def post_multipart_form(url,params) - active_service.post_multipart_form(url,params) - end - - def active_service - self.class.active_service - end - - def post_file(params) - service_url = url(params.delete(:base)) - result = post_multipart_form(service_url, params) - Parser.parse(params[:method], result) - end - - private - def url(base = nil) - base ||= @api_base - URI.parse('http://'+ base + @api_path) - end - - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/service/base_service.rb b/vendor/plugins/facebooker/lib/facebooker/service/base_service.rb deleted file mode 100644 index feee2776f..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/service/base_service.rb +++ /dev/null @@ -1,19 +0,0 @@ -class Facebooker::Service::BaseService - def parse_results? - true - end - - def post_params(params) - post_params = {} - params.each do |k,v| - k = k.to_s unless k.is_a?(String) - if Array === v || Hash === v - post_params[k] = Facebooker.json_encode(v) - else - post_params[k] = v - end - end - post_params - end - -end diff --git a/vendor/plugins/facebooker/lib/facebooker/service/curl_service.rb b/vendor/plugins/facebooker/lib/facebooker/service/curl_service.rb deleted file mode 100644 index 50b2f149a..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/service/curl_service.rb +++ /dev/null @@ -1,44 +0,0 @@ -require 'curb' -Facebooker.use_curl = true -class Facebooker::Service::CurlService < Facebooker::Service::BaseService - def post_form(url,params,multipart=false) - curl = Curl::Easy.new(url.to_s) do |c| - c.multipart_form_post = multipart - c.timeout = Facebooker.timeout - end - curl.http_post(*to_curb_params(params)) - curl.body_str - end - - def post_multipart_form(url,params) - post_form(url,params,true) - end - - # Net::HTTP::MultipartPostFile - def multipart_post_file?(object) - object.respond_to?(:content_type) && - object.respond_to?(:data) && - object.respond_to?(:filename) - end - - def to_curb_params(params) - parray = [] - params.each_pair do |k,v| - if multipart_post_file?(v) - # Curl doesn't like blank field names - field = Curl::PostField.file((k.blank? ? 'xxx' : k.to_s), nil, File.basename(v.filename)) - field.content_type = v.content_type - field.content = v.data - parray << field - else - parray << Curl::PostField.content( - k.to_s, - (Array === v || Hash===v) ? Facebooker.json_encode(v) : v.to_s - ) - end - end - parray - end - -end - \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/service/net_http_service.rb b/vendor/plugins/facebooker/lib/facebooker/service/net_http_service.rb deleted file mode 100644 index 2aa64baef..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/service/net_http_service.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'net/http' -class Facebooker::Service::NetHttpService lambda {|r| puts "."} - - def perform_post(url,params) - add_result(self.class.async_post(:base_uri=>url,:params=>params)) - end - - def add_result(obj) - @result_objects << obj - end - - def process - # we need to access all objects to make sure the proxy has made the request - @result_objects.each(&:nil?) - @result_objects = [] - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/service/typhoeus_service.rb b/vendor/plugins/facebooker/lib/facebooker/service/typhoeus_service.rb deleted file mode 100644 index cd95e0f1f..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/service/typhoeus_service.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'typhoeus' -class Facebooker::Service::TyphoeusService < Facebooker::Service::BaseService - include Typhoeus - def post_form(url,params) - perform_post(url.to_s,post_params(params)) - end - - def perform_post(url,params) - self.class.post(url,:params=>post_params) - end - - def post_multipart_form(url,params) - raise "Multipart not supported on Typhoeus" - end - - -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/session.rb b/vendor/plugins/facebooker/lib/facebooker/session.rb deleted file mode 100644 index 911d8e62c..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/session.rb +++ /dev/null @@ -1,809 +0,0 @@ -require 'cgi' - -module Facebooker - # - # Raised when trying to perform an operation on a user - # other than the logged in user (if that's unallowed) - class Error < StandardError; end - class NonSessionUser < Error; end - class Session - - # - # Raised when a facebook session has expired. This - # happens when the timeout is reached, or when the - # user logs out of facebook - # can be handled with: - # rescue_from Facebooker::Session::SessionExpired, :with => :some_method_name - class SessionExpired < Error; end - - class UnknownError < Error; end - class ServiceUnavailable < Error; end - class MaxRequestsDepleted < Error; end - class HostNotAllowed < Error; end - class AppPermissionError < Error; end - class MissingOrInvalidParameter < Error; end - class InvalidAPIKey < Error; end - class SessionExpired < Error; end - class CallOutOfOrder < Error; end - class IncorrectSignature < Error; end - class SignatureTooOld < Error; end - class TooManyUserCalls < Error; end - class TooManyUserActionCalls < Error; end - class InvalidFeedTitleLink < Error; end - class InvalidFeedTitleLength < Error; end - class InvalidFeedTitleName < Error; end - class BlankFeedTitle < Error; end - class FeedBodyLengthTooLong < Error; end - class InvalidFeedPhotoSource < Error; end - class InvalidFeedPhotoLink < Error; end - class TemplateDataMissingRequiredTokens < Error; end - class FeedMarkupInvalid < Error; end - class FeedTitleDataInvalid < Error; end - class FeedTitleTemplateInvalid < Error; end - class FeedBodyDataInvalid < Error; end - class FeedBodyTemplateInvalid < Error; end - class FeedPhotosNotRetrieved < Error; end - class FeedTargetIdsInvalid < Error; end - class TemplateBundleInvalid < Error; end - class ConfigurationMissing < Error; end - class FQLParseError < Error; end - class FQLFieldDoesNotExist < Error; end - class FQLTableDoesNotExist < Error; end - class FQLStatementNotIndexable < Error; end - class FQLFunctionDoesNotExist < Error; end - class FQLWrongNumberArgumentsPassedToFunction < Error; end - class PermissionError < Error; end - class InvalidAlbumId < Error; end - class AlbumIsFull < Error; end - class MissingOrInvalidImageFile < Error; end - class TooManyUnapprovedPhotosPending < Error; end - class ExtendedPermissionRequired < Error; end - class ReadMailboxExtendedPermissionRequired < Error; end - class InvalidFriendList < Error; end - class EventNameLocked < Error ; end - class UserUnRegistrationFailed < Error - attr_accessor :failed_users - end - class UserRegistrationFailed < Error - attr_accessor :failed_users - end - - API_SERVER_BASE_URL = ENV["FACEBOOKER_API"] == "new" ? "api.new.facebook.com" : "api.facebook.com" - API_PATH_REST = "/restserver.php" - WWW_SERVER_BASE_URL = ENV["FACEBOOKER_API"] == "new" ? "www.new.facebook.com" : "www.facebook.com" - WWW_PATH_LOGIN = "/login.php" - WWW_PATH_ADD = "/add.php" - WWW_PATH_INSTALL = "/install.php" - - attr_writer :auth_token - attr_reader :session_key - attr_reader :secret_from_session - - def self.create(api_key=nil, secret_key=nil) - api_key ||= self.api_key - secret_key ||= self.secret_key - raise ArgumentError unless !api_key.nil? && !secret_key.nil? - new(api_key, secret_key) - end - - def self.api_key - extract_key_from_environment(:api) || extract_key_from_configuration_file(:api) rescue report_inability_to_find_key(:api) - end - - def self.secret_key - extract_key_from_environment(:secret) || extract_key_from_configuration_file(:secret) rescue report_inability_to_find_key(:secret) - end - - def self.current - Thread.current['facebook_session'] - end - - def self.current=(session) - Thread.current['facebook_session'] = session - end - - def login_url(options={}) - options = default_login_url_options.merge(options) - "#{Facebooker.login_url_base}#{login_url_optional_parameters(options)}" - end - - def install_url(options={}) - "#{Facebooker.install_url_base}#{install_url_optional_parameters(options)}" - end - - # The url to get user to approve extended permissions - # http://wiki.developers.facebook.com/index.php/Extended_permission - # - # permissions: - # * email - # * offline_access - # * status_update - # * photo_upload - # * video_upload - # * create_listing - # * create_event - # * rsvp_event - # * sms - # * read_mailbox - def permission_url(permission,options={}) - options = default_login_url_options.merge(options) - options = add_next_parameters(options) - options << "&ext_perm=#{permission}" - "#{Facebooker.permission_url_base}#{options.join}" - end - - def connect_permission_url(permission,options={}) - options = default_login_url_options.merge(options) - options = add_next_parameters(options) - options << "&ext_perm=#{permission}" - "#{Facebooker.connect_permission_url_base}#{options.join}" - end - - def install_url_optional_parameters(options) - optional_parameters = [] - optional_parameters += add_next_parameters(options) - optional_parameters.join - end - - def add_next_parameters(options) - opts = [] - opts << "&next=#{CGI.escape(options[:next])}" if options[:next] - opts << "&next_cancel=#{CGI.escape(options[:next_cancel])}" if options[:next_cancel] - opts - end - - def login_url_optional_parameters(options) - # It is important that unused options are omitted as stuff like &canvas=false will still display the canvas. - optional_parameters = [] - optional_parameters += add_next_parameters(options) - optional_parameters << "&skipcookie=true" if options[:skip_cookie] - optional_parameters << "&hide_checkbox=true" if options[:hide_checkbox] - optional_parameters << "&canvas=true" if options[:canvas] - optional_parameters << "&fbconnect=true" if options[:fbconnect] - optional_parameters << "&return_session=true" if options[:return_session] - optional_parameters << "&session_key_only=true" if options[:session_key_only] - optional_parameters << "&req_perms=#{options[:req_perms]}" if options[:req_perms] - optional_parameters.join - end - - def default_login_url_options - {} - end - - def initialize(api_key, secret_key) - @api_key = api_key - @secret_key = secret_key - @batch_request = nil - @session_key = nil - @uid = nil - @auth_token = nil - @secret_from_session = nil - @expires = nil - end - - def secret_for_method(method_name) - @secret_key - end - - def auth_token - @auth_token ||= post 'facebook.auth.createToken' - end - - def infinite? - @expires == 0 - end - - def expired? - @expires.nil? || (!infinite? && Time.at(@expires) <= Time.now) - end - - def secured? - !@session_key.nil? && !expired? - end - - def secure!(args = {}) - response = post 'facebook.auth.getSession', :auth_token => auth_token, :generate_session_secret => args[:generate_session_secret] ? "1" : "0" - secure_with!(response['session_key'], response['uid'], response['expires'], response['secret']) - end - - def secure_with_session_secret! - self.secure!(:generate_session_secret => true) - end - - def secure_with!(session_key, uid = nil, expires = nil, secret_from_session = nil) - @session_key = session_key - @uid = uid ? Integer(uid) : post('facebook.users.getLoggedInUser', :session_key => session_key) - @expires = expires ? Integer(expires) : 0 - @secret_from_session = secret_from_session - end - - def fql_build_object(type, hash) - case type - when 'user' - user = User.new - user.session = self - user.populate_from_hash!(hash) - user - when 'photo' - Photo.from_hash(hash) - when 'album' - Album.from_hash(hash) - when 'page' - Page.from_hash(hash) - when 'page_admin' - Page.from_hash(hash) - when 'group' - Group.from_hash(hash) - when 'event' - Event.from_hash(hash) - when 'event_member' - Event::Attendance.from_hash(hash) - else - hash - end - end - - def fql_query(query, format = 'XML') - post('facebook.fql.query', :query => query, :format => format) do |response| - type = response.shift - if type.nil? - [] - else - response.shift.map do |hash| - fql_build_object(type, hash) - end - end - end - end - - def fql_multiquery(queries, format = 'XML') - results = {} - post('facebook.fql.multiquery', :queries => queries.to_json, :format => format) do |responses| - responses.each do |response| - name = response.shift - response = response.shift - type = response.shift - value = [] - unless type.nil? - value = response.shift.map do |hash| - fql_build_object(type, hash) - end - end - results[name] = value - end - end - results - end - - def user - @user ||= User.new(uid, self) - end - - # - # This one has so many parameters, a Hash seemed cleaner than a long param list. Options can be: - # :uid => Filter by events associated with a user with this uid - # :eids => Filter by this list of event ids. This is a comma-separated list of eids. - # :start_time => Filter with this UTC as lower bound. A missing or zero parameter indicates no lower bound. (Time or Integer) - # :end_time => Filter with this UTC as upper bound. A missing or zero parameter indicates no upper bound. (Time or Integer) - # :rsvp_status => Filter by this RSVP status. - def events(options = {}) - @events ||= {} - @events[options.to_s] ||= post('facebook.events.get', options) do |response| - response.map do |hash| - Event.from_hash(hash) - end - end - end - - # Creates an event with the event_info hash and an optional Net::HTTP::MultipartPostFile for the event picture. - # If ActiveSupport::TimeWithZone is installed (it's in Rails > 2.1), and start_time or end_time are given as - # ActiveSupport::TimeWithZone, then they will be assumed to represent local time for the event. They will automatically be - # converted to the expected timezone for Facebook, which is PST or PDT depending on when the event occurs. - # Returns the eid of the newly created event - # http://wiki.developers.facebook.com/index.php/Events.create - def create_event(event_info, multipart_post_file = nil) - if defined?(ActiveSupport::TimeWithZone) && defined?(ActiveSupport::TimeZone) - # Facebook expects all event local times to be in Pacific Time, so we need to take the actual local time and - # send it to Facebook as if it were Pacific Time converted to Unix epoch timestamp. Very confusing... - facebook_time = ActiveSupport::TimeZone["Pacific Time (US & Canada)"] - - start_time = event_info.delete(:start_time) || event_info.delete('start_time') - if start_time && start_time.is_a?(ActiveSupport::TimeWithZone) - event_info['start_time'] = facebook_time.parse(start_time.strftime("%Y-%m-%d %H:%M:%S")).to_i - else - event_info['start_time'] = start_time - end - - end_time = event_info.delete(:end_time) || event_info.delete('end_time') - if end_time && end_time.is_a?(ActiveSupport::TimeWithZone) - event_info['end_time'] = facebook_time.parse(end_time.strftime("%Y-%m-%d %H:%M:%S")).to_i - else - event_info['end_time'] = end_time - end - end - - post_file('facebook.events.create', :event_info => event_info.to_json, nil => multipart_post_file) - end - - def edit_event(eid, event_info, options = {}) - post('facebook.events.edit', options.merge(:eid => eid, :event_info => event_info.to_json)) - end - - # Cancel an event - # http://wiki.developers.facebook.com/index.php/Events.cancel - # E.g: - # @session.cancel_event('100321123', :cancel_message => "It's raining...") - # # => Returns true if all went well - def cancel_event(eid, options = {}) - result = post('facebook.events.cancel', options.merge(:eid => eid)) - result == '1' ? true : false - end - - # Invite users to an event - # http://wiki.developers.facebook.com/index.php/Events.invite - # E.g: - # @session.event_invite('1000321123', %w{1234 4567 1000123321}, :personal_message => "Please come!") - def event_invite(eid, uids, options = {}) - post('facebook.events.invite', options.merge(:eid => eid, :uids => uids.to_json)) - end - - # RSVP to an event - # http://wiki.developers.facebook.com/index.php/Events.rsvp - # E.g: - # @session.event_rsvp('1000321123', 'attending') - def event_rsvp(eid,rsvp_status, options = {}) - post('facebook.events.rsvp', options.merge(:eid => eid, :rsvp_status => rsvp_status)) - end - - def event_members(eid) - @members ||= {} - @members[eid] ||= post('facebook.events.getMembers', :eid => eid) do |response| - response.map do |attendee_hash| - Event::Attendance.from_hash(attendee_hash) - end - end - end - - def users_standard(user_ids, fields=[]) - post("facebook.users.getStandardInfo",:uids=>user_ids.join(","),:fields=>User.standard_fields(fields)) do |users| - users.map { |u| User.new(u)} - end - end - - def users(user_ids, fields=[]) - post("facebook.users.getInfo",:uids=>user_ids.join(","),:fields=>User.user_fields(fields)) do |users| - users.map { |u| User.new(u)} - end - end - - def pages(options = {}) - raise ArgumentError, 'fields option is mandatory' unless options.has_key?(:fields) - @pages ||= {} - @pages[options] ||= post('facebook.pages.getInfo', options) do |response| - response.map do |hash| - Page.from_hash(hash) - end - end - end - - # Takes page_id and uid, returns true if uid is a fan of the page_id - def is_fan(page_id, uid) - puts "Deprecated. Use Page#user_is_fan? instead" - Page.new(page_id).user_is_fan?(uid) - end - - - # - # Returns a proxy object for handling calls to Facebook cached items - # such as images and FBML ref handles - def server_cache - Facebooker::ServerCache.new(self) - end - - # - # Returns a proxy object for handling calls to the Facebook Data API - def data - Facebooker::Data.new(self) - end - - def admin - Facebooker::Admin.new(self) - end - - def application - Facebooker::Application.new(self) - end - - def mobile - Facebooker::Mobile.new(self) - end - - # - # Given an array like: - # [[userid, otheruserid], [yetanotherid, andanotherid]] - # returns a Hash indicating friendship of those pairs: - # {[userid, otheruserid] => true, [yetanotherid, andanotherid] => false} - # if one of the Hash values is nil, it means the facebook platform's answer is "I don't know" - def check_friendship(array_of_pairs_of_users) - uids1 = [] - uids2 = [] - array_of_pairs_of_users.each do |pair| - uids1 << pair.first - uids2 << pair.last - end - post('facebook.friends.areFriends', :uids1 => uids1.join(','), :uids2 => uids2.join(',')) - end - - def get_photos(pids = nil, subj_id = nil, aid = nil) - if [subj_id, pids, aid].all? {|arg| arg.nil?} - raise ArgumentError, "Can't get a photo without a picture, album or subject ID" - end - # We have to normalize params orherwise FB complain about signature - params = {:pids => pids, :subj_id => subj_id, :aid => aid}.delete_if { |k,v| v.nil? } - @photos = post('facebook.photos.get', params ) do |response| - response.map do |hash| - Photo.from_hash(hash) - end - end - end - - #remove a comment from a given xid stream with comment_id - def remove_comment(xid,comment_id) - post('facebook.comments.remove', :xid=>xid, :comment_id =>comment_id) - end - - #pulls comment list for a given XID - def get_comments(xid) - @comments = post('facebook.comments.get', :xid => xid) do |response| - response.map do |hash| - Comment.from_hash(hash) - end - end - end - - def get_albums(aids) - @albums = post('facebook.photos.getAlbums', :aids => aids) do |response| - response.map do |hash| - Album.from_hash(hash) - end - end - end - - ### - # Retrieve a viewer's facebook stream - # See http://wiki.developers.facebook.com/index.php/Stream.get for options - # - def get_stream(viewer_id, options = {}) - - @stream = post('facebook.stream.get', prepare_get_stream_options(viewer_id, options), true) do |response| - response - end - end - - def get_tags(pids) - @tags = post('facebook.photos.getTags', :pids => pids) do |response| - response.map do |hash| - Tag.from_hash(hash) - end - end - end - - def add_tags(pid, x, y, tag_uid = nil, tag_text = nil ) - if [tag_uid, tag_text].all? {|arg| arg.nil?} - raise ArgumentError, "Must enter a name or string for this tag" - end - @tags = post('facebook.photos.addTag', :pid => pid, :tag_uid => tag_uid, :tag_text => tag_text, :x => x, :y => y ) - end - - def send_notification(user_ids, fbml, email_fbml = nil) - params = {:notification => fbml, :to_ids => user_ids.map{ |id| User.cast_to_facebook_id(id)}.join(',')} - if email_fbml - params[:email] = email_fbml - end - params[:type]="user_to_user" - # if there is no uid, this is an announcement - unless uid? - params[:type]="app_to_user" - end - - post 'facebook.notifications.send', params,uid? - end - - ## - # Register a template bundle with Facebook. - # returns the template id to use to send using this template - def register_template_bundle(one_line_story_templates,short_story_templates=nil,full_story_template=nil, action_links=nil) - templates = ensure_array(one_line_story_templates) - parameters = {:one_line_story_templates => templates.to_json} - - unless action_links.blank? - parameters[:action_links] = action_links.to_json - end - - unless short_story_templates.blank? - templates = ensure_array(short_story_templates) - parameters[:short_story_templates] = templates.to_json - end - - unless full_story_template.blank? - parameters[:full_story_template] = full_story_template.to_json - end - - post("facebook.feed.registerTemplateBundle", parameters, false) - end - - ## - # Deactivate a template bundle with Facebook. - # Returns true if a bundle with the specified id is active and owned by this app. - # Useful to avoid exceeding the 100 templates/app limit. - def deactivate_template_bundle_by_id(template_bundle_id) - post("facebook.feed.deactivateTemplateBundleByID", {:template_bundle_id => template_bundle_id.to_s}, false) - end - - def active_template_bundles - post("facebook.feed.getRegisteredTemplateBundles",{},false) - end - - ## - # publish a previously rendered template bundle - # see http://wiki.developers.facebook.com/index.php/Feed.publishUserAction - # - def publish_user_action(bundle_id,data={},target_ids=nil,body_general=nil,story_size=nil) - parameters={:template_bundle_id=>bundle_id,:template_data=>data.to_json} - parameters[:target_ids] = target_ids unless target_ids.blank? - parameters[:body_general] = body_general unless body_general.blank? - parameters[:story_size] = story_size unless story_size.nil? - post("facebook.feed.publishUserAction", parameters) - end - - ## - # Upload strings in native locale to facebook for translations. - # - # e.g. facebook_session.upload_native_strings([:text => "Welcome {user}", :description => "Welcome message to currently logged in user."]) - # returns the number of strings uploaded - # - # See http://wiki.developers.facebook.com/index.php/Intl.uploadNativeStrings for method options - # - def upload_native_strings(native_strings) - raise ArgumentError, "You must provide strings to upload" if native_strings.nil? - - post('facebook.intl.uploadNativeStrings', :native_strings => Facebooker.json_encode(native_strings)) do |response| - response - end - end - - ## - # Send email to as many as 100 users at a time - def send_email(user_ids, subject, text, fbml = nil) - user_ids = Array(user_ids) - params = {:fbml => fbml, :recipients => user_ids.map{ |id| User.cast_to_facebook_id(id)}.join(','), :text => text, :subject => subject} - post 'facebook.notifications.sendEmail', params, false - end - - # Only serialize the bare minimum to recreate the session. - def marshal_load(variables)#:nodoc: - fields_to_serialize.each_with_index{|field, index| instance_variable_set_value(field, variables[index])} - end - - # Only serialize the bare minimum to recreate the session. - def marshal_dump#:nodoc: - fields_to_serialize.map{|field| instance_variable_value(field)} - end - - # Only serialize the bare minimum to recreate the session. - def to_yaml( opts = {} ) - YAML::quick_emit(self.object_id, opts) do |out| - out.map(taguri) do |map| - fields_to_serialize.each do |field| - map.add(field, instance_variable_value(field)) - end - end - end - end - - def instance_variable_set_value(field, value) - self.instance_variable_set("@#{field}", value) - end - - def instance_variable_value(field) - self.instance_variable_get("@#{field}") - end - - def fields_to_serialize - %w(session_key uid expires secret_from_session auth_token api_key secret_key) - end - - class Desktop < Session - def login_url - super + "&auth_token=#{auth_token}" - end - - def secret_for_method(method_name) - secret = auth_request_methods.include?(method_name) ? super : @secret_from_session - secret - end - - def post(method, params = {},use_session=false) - if method == 'facebook.profile.getFBML' || method == 'facebook.profile.setFBML' - raise NonSessionUser.new("User #{@uid} is not the logged in user.") unless @uid == params[:uid] - end - super - end - private - def auth_request_methods - ['facebook.auth.getSession', 'facebook.auth.createToken'] - end - end - - def batch_request? - @batch_request - end - - def add_to_batch(req,&proc) - batch_request = BatchRequest.new(req,proc) - Thread.current[:facebooker_current_batch_queue]<BatchRun.current_batch.map{|q| q.uri}.to_json,:serial_only=>serial_only.to_s) - ensure - @batch_request=false - BatchRun.current_batch=nil - end - - def post_without_logging(method, params = {}, use_session_key = true, &proc) - add_facebook_params(params, method) - use_session_key && @session_key && params[:session_key] ||= @session_key - final_params=params.merge(:sig => signature_for(params)) - if batch_request? - add_to_batch(final_params,&proc) - else - result = service.post(final_params) - result = yield result if block_given? - result - end - end - - def post(method, params = {}, use_session_key = true, &proc) - if batch_request? or Facebooker::Logging.skip_api_logging - post_without_logging(method, params, use_session_key, &proc) - else - Logging.log_fb_api(method, params) do - post_without_logging(method, params, use_session_key, &proc) - end - end - end - - def post_file(method, params = {}) - base = params.delete(:base) - Logging.log_fb_api(method, params) do - add_facebook_params(params, method) - @session_key && params[:session_key] ||= @session_key unless params[:uid] - service.post_file(params.merge(:base => base, :sig => signature_for(params.reject{|key, value| key.nil?}))) - end - end - - - @configuration_file_path = nil - - def self.configuration_file_path - @configuration_file_path || File.expand_path("~/.facebookerrc") - end - - def self.configuration_file_path=(path) - @configuration_file_path = path - end - - private - def add_facebook_params(hash, method) - hash[:method] = method - hash[:api_key] = @api_key - hash[:call_id] = Time.now.to_f.to_s unless method == 'facebook.auth.getSession' - hash[:v] = "1.0" - end - - # This ultimately delgates to the adapter - def self.extract_key_from_environment(key_name) - Facebooker.send(key_name.to_s + "_key") rescue nil - end - - def self.extract_key_from_configuration_file(key_name) - read_configuration_file[key_name] - end - - def self.report_inability_to_find_key(key_name) - raise ConfigurationMissing, "Could not find configuration information for #{key_name}" - end - - def self.read_configuration_file - eval(File.read(configuration_file_path)) - end - - def service - @service ||= Service.new(Facebooker.api_server_base, Facebooker.api_rest_path, @api_key) - end - - def uid - @uid || (secure!; @uid) - end - - def uid? - !! @uid - end - - def signature_for(params) - params.delete_if { |k,v| v.nil? } - raw_string = params.inject([]) do |collection, pair| - collection << pair.map { |x| - Array === x ? Facebooker.json_encode(x) : x - }.join("=") - collection - end.sort.join - Digest::MD5.hexdigest([raw_string, secret_for_method(params[:method])].join) - end - - def ensure_array(value) - value.is_a?(Array) ? value : [value] - end - - def prepare_get_stream_options(viewer_id, options) - opts = {} - - opts[:viewer_id] = viewer_id if viewer_id.is_a?(Integer) - opts[:source_ids] = options[:source_ids] if options[:source_ids] - opts[:start_time] = options[:start_time].to_i if options[:start_time] - opts[:end_time] = options[:end_time].to_i if options[:end_time] - opts[:limit] = options[:limit] if options[:limit].is_a?(Integer) - opts[:metadata] = Facebooker.json_encode(options[:metadata]) if options[:metadata] - opts - end - end - - class CanvasSession < Session - def default_login_url_options - {:canvas => true} - end - end -end diff --git a/vendor/plugins/facebooker/lib/facebooker/stream_post.rb b/vendor/plugins/facebooker/lib/facebooker/stream_post.rb deleted file mode 100644 index 423fbbca5..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/stream_post.rb +++ /dev/null @@ -1,19 +0,0 @@ -class Facebooker::StreamPost - attr_accessor :user_message, :attachment, :action_links, :target, :actor - - def initialize - self.action_links = [] - end - - alias_method :message, :user_message - alias_method :message=, :user_message= - - def action_links(*args) - if args.blank? - @action_links - else - @action_links = args.first - end - end - -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/facebooker/version.rb b/vendor/plugins/facebooker/lib/facebooker/version.rb deleted file mode 100644 index e4d39f54e..000000000 --- a/vendor/plugins/facebooker/lib/facebooker/version.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Facebooker #:nodoc: - module VERSION #:nodoc: - MAJOR = 1 - MINOR = 0 - TINY = 75 - - STRING = [MAJOR, MINOR, TINY].join('.') - end -end diff --git a/vendor/plugins/facebooker/lib/net/http_multipart_post.rb b/vendor/plugins/facebooker/lib/net/http_multipart_post.rb deleted file mode 100644 index d1a65a3e7..000000000 --- a/vendor/plugins/facebooker/lib/net/http_multipart_post.rb +++ /dev/null @@ -1,123 +0,0 @@ -require 'net/http' - -module Net - class HTTP - class << self - def post_multipart_form(url, params) - MultipartPost.new(url, params).post - end - end - - class MultipartPostFile - def initialize(filename=nil, content_type=nil, data=nil) - @filename = filename - @content_type = content_type - @data = data - end - - attr_accessor :filename - attr_accessor :content_type - attr_accessor :data - end - - class MultipartPost - def initialize(url, params) - @url = url - @multipart_post_files = extract_file_parameters_from(params) - @params = extract_non_file_parameters_from(params) - end - - def post - req = Post.new(url.path) - req.body = body - req.content_type = content_type - req.basic_auth url.user, url.password if url.user - Net::HTTP.new(url.host, url.port).start {|http| - http.request(req) - } - end - - BOUNDARY = "MichaelNiessnerIsSuperDuperAwesome" - - protected - attr_reader :url, :params, :multipart_post_files - - def extract_file_parameters_from(hash) - hash.reject{|key, value| !multipart_post_file?(value)} - end - - def extract_non_file_parameters_from(hash) - hash.reject{|key, value| multipart_post_file?(value)} - end - - def multipart_post_file?(object) - object.respond_to?(:content_type) && - object.respond_to?(:data) && - object.respond_to?(:filename) - end - - def content_type - "multipart/form-data; boundary=#{BOUNDARY}" - end - - def body - encode_parameters + encode_multipart_post_files + final_boundary - end - - def encode_multipart_post_files - return "" if multipart_post_files.empty? - if multipart_post_files.size == 1 - name = multipart_post_files.keys.first - file = multipart_post_files.values.first - encode_multipart_post_file(name, file) - else - raise "Currently more than 1 file upload is not supported." - end - end - - def encode_multipart_post_file(name, multipart_post_file) - parameter_boundary + - disposition_with_filename(name, multipart_post_file.filename) + - file_content_type(multipart_post_file.content_type) + - multipart_post_file.data + - "\r\n" - end - - def encode_parameters - params.sort_by{|key, value| key.to_s}.map{|key, value| encode_parameter(key, value)}.join - end - - def encode_parameter(key, value) - parameter_boundary + disposition_with_name(key) + value.to_s + "\r\n" - end - - def file_content_type(string) - "Content-Type: #{string}\r\n\r\n" - end - - def disposition_with_filename(name, filename) - if name.nil? - disposition("filename=\"#{filename}\"") - else - disposition("name=\"#{name}\"; filename=\"#{filename}\"") - end - end - - def disposition_with_name(name) - disposition("name=\"#{name}\"\r\n") - end - - def disposition(attribute) - "Content-Disposition: form-data; #{attribute}\r\n" - end - - def parameter_boundary - "--#{BOUNDARY}\r\n" - end - - def final_boundary - "--#{BOUNDARY}--\r\n" - end - end - end -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/rack/facebook.rb b/vendor/plugins/facebooker/lib/rack/facebook.rb deleted file mode 100644 index ae9bfa1fe..000000000 --- a/vendor/plugins/facebooker/lib/rack/facebook.rb +++ /dev/null @@ -1,89 +0,0 @@ -module Rack - # This Rack middleware checks the signature of Facebook params, and - # converts them to Ruby objects when appropiate. Also, it converts - # the request method from the Facebook POST to the original HTTP - # method used by the client. - # - # If the signature is wrong, it returns a "400 Invalid Facebook Signature". - # - # Optionally, it can take a block that receives the Rack environment - # and returns a value that evaluates to true when we want the middleware to - # be executed for the specific request. - # - # == Usage - # - # In your config.ru: - # - # require 'rack/facebook' - # use Rack::Facebook, "my_facebook_secret_key" - # - # Using a block condition: - # - # use Rack::Facebook, "my_facebook_secret_key" do |env| - # env['REQUEST_URI'] =~ /^\/facebook_only/ - # end - # - class Facebook - def initialize(app, &condition) - @app = app - @condition = condition - end - - def call(env) - return @app.call(env) unless @condition.nil? || @condition.call(env) - - request = Rack::Request.new(env) - fb_sig, fb_params = nil, nil - - [ request.POST, request.GET ].each do |params| - fb_sig, fb_params = fb_sig_and_params( params ) - break if fb_sig - end - - return @app.call(env) if fb_params.empty? - - Facebooker.with_application(fb_params['api_key']) do - unless signature_is_valid?(fb_params, fb_sig) - return Rack::Response.new(["Invalid Facebook signature"], 400).finish - end - env['REQUEST_METHOD'] = fb_params["request_method"] if fb_params["request_method"] - convert_parameters!(request.params) - @app.call(env) - end - end - - private - - def fb_sig_and_params( params ) - return nil, [] unless params['fb_sig'] - return params['fb_sig'], extract_fb_sig_params(params) - end - - def extract_fb_sig_params(params) - params.inject({}) do |collection, (param, value)| - collection[param.sub(/^fb_sig_/, '')] = value if param[0,7] == 'fb_sig_' - collection - end - end - - def signature_is_valid?(fb_params, actual_sig) - raw_string = fb_params.map{ |*args| args.join('=') }.sort.join - expected_signature = Digest::MD5.hexdigest([raw_string, Facebooker.secret_key].join) - actual_sig == expected_signature - end - - def convert_parameters!(params) - - params.each do |key, value| - case key - when 'fb_sig_added', 'fb_sig_in_canvas', 'fb_sig_in_new_facebook', 'fb_sig_position_fix', 'fb_sig_is_ajax' - params[key] = value == "1" - when 'fb_sig_expires', 'fb_sig_profile_update_time', 'fb_sig_time' - params[key] = value == "0" ? nil : Time.at(value.to_f) - when 'fb_sig_friends' - params[key] = value.split(',') - end - end - end - end -end diff --git a/vendor/plugins/facebooker/lib/rack/facebook_session.rb b/vendor/plugins/facebooker/lib/rack/facebook_session.rb deleted file mode 100644 index b7b5e17a3..000000000 --- a/vendor/plugins/facebooker/lib/rack/facebook_session.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'rack/request' - -module Rack - class FacebookSession - - FACEBOOK_SESSION_KEY = 'fb_sig_session_key' - - def initialize(app, session_key = '_session_id') - @app = app - @session_key = session_key - end - - def call(env) - req = Rack::Request.new(env) - key = req.POST[FACEBOOK_SESSION_KEY] || req.GET[FACEBOOK_SESSION_KEY] - env['HTTP_COOKIE'] = [ @session_key, key ].join('=').freeze unless key.nil? - - @app.call(env) - end - end -end diff --git a/vendor/plugins/facebooker/lib/tasks/facebooker.rake b/vendor/plugins/facebooker/lib/tasks/facebooker.rake deleted file mode 100644 index 98e92d2fc..000000000 --- a/vendor/plugins/facebooker/lib/tasks/facebooker.rake +++ /dev/null @@ -1,19 +0,0 @@ -require 'fileutils' - -namespace :facebooker do - - desc "Create a basic facebooker.yml configuration file" - task :setup => :environment do - facebook_config = File.join(RAILS_ROOT,"config","facebooker.yml") - unless File.exist?(facebook_config) - facebooker_root = File.expand_path(File.join(File.dirname(__FILE__),"..","..")) - facebook_config_tpl = File.join(facebooker_root,"generators","facebook","templates","config","facebooker.yml") - FileUtils.cp facebook_config_tpl, facebook_config - puts "Ensure 'GatewayPorts yes' is enabled in the remote development server's sshd config when using any of the facebooker:tunnel:*' rake tasks" - puts "Configuration created in #{RAILS_ROOT}/config/facebooker.yml" - else - puts "#{RAILS_ROOT}/config/facebooker.yml already exists" - end - end - -end \ No newline at end of file diff --git a/vendor/plugins/facebooker/lib/tasks/facebooker.rb b/vendor/plugins/facebooker/lib/tasks/facebooker.rb deleted file mode 100644 index 89e3b0f6b..000000000 --- a/vendor/plugins/facebooker/lib/tasks/facebooker.rb +++ /dev/null @@ -1,2 +0,0 @@ -load 'tasks/facebooker.rake' -load 'tasks/tunnel.rake' diff --git a/vendor/plugins/facebooker/lib/tasks/tunnel.rake b/vendor/plugins/facebooker/lib/tasks/tunnel.rake deleted file mode 100644 index 928fefabf..000000000 --- a/vendor/plugins/facebooker/lib/tasks/tunnel.rake +++ /dev/null @@ -1,46 +0,0 @@ -namespace :facebooker do - - tunnel_ns = namespace :tunnel do - # Courtesy of Christopher Haupt - # http://www.BuildingWebApps.com - # http://www.LearningRails.com - desc "Create a reverse ssh tunnel from a public server to a private development server." - task :start => [ :environment, :config ] do - puts @notification - system @ssh_command - end - - desc "Create a reverse ssh tunnel in the background. Requires ssh keys to be setup." - task :background_start => [ :environment, :config ] do - puts @notification - system "#{@ssh_command} > /dev/null 2>&1 &" - end - - # Adapted from Evan Weaver: http://blog.evanweaver.com/articles/2007/07/13/developing-a-facebook-app-locally/ - desc "Check if reverse tunnel is running" - task :status => [ :environment, :config ] do - if `ssh #{@public_host} -l #{@public_host_username} netstat -an | egrep "tcp.*:#{@public_port}.*LISTEN" | wc`.to_i > 0 - puts "Seems ok" - else - puts "Down" - end - end - - task :config => :environment do - facebook_config = File.join(RAILS_ROOT, 'config', 'facebooker.yml') - FACEBOOKER = YAML.load(ERB.new(File.read(facebook_config)).result)[RAILS_ENV] - @public_host_username = FACEBOOKER['tunnel']['public_host_username'] - @public_host = FACEBOOKER['tunnel']['public_host'] - @public_port = FACEBOOKER['tunnel']['public_port'] - @local_port = FACEBOOKER['tunnel']['local_port'] - @ssh_port = FACEBOOKER['tunnel']['ssh_port'] || 22 - @server_alive_interval = FACEBOOKER['tunnel']['server_alive_interval'] || 0 - @notification = "Starting tunnel #{@public_host}:#{@public_port} to 0.0.0.0:#{@local_port}" - @notification << " using SSH port #{@ssh_port}" unless @ssh_port == 22 - # "GatewayPorts yes" needs to be enabled in the remote's sshd config - @ssh_command = %Q[ssh -v -p #{@ssh_port} -nNT4 -o "ServerAliveInterval #{@server_alive_interval}" -R *:#{@public_port}:localhost:#{@local_port} #{@public_host_username}@#{@public_host}] - end - end - desc "Create a reverse ssh tunnel from a public server to a private development server." - task :tunnel => tunnel_ns[:start] -end diff --git a/vendor/plugins/facebooker/rails/init.rb b/vendor/plugins/facebooker/rails/init.rb deleted file mode 100644 index 3fd158856..000000000 --- a/vendor/plugins/facebooker/rails/init.rb +++ /dev/null @@ -1 +0,0 @@ -require File.join(File.dirname(__FILE__),'../init.rb') diff --git a/vendor/plugins/facebooker/setup.rb b/vendor/plugins/facebooker/setup.rb deleted file mode 100644 index 424a5f37c..000000000 --- a/vendor/plugins/facebooker/setup.rb +++ /dev/null @@ -1,1585 +0,0 @@ -# -# setup.rb -# -# Copyright (c) 2000-2005 Minero Aoki -# -# This program is free software. -# You can distribute/modify this program under the terms of -# the GNU LGPL, Lesser General Public License version 2.1. -# - -unless Enumerable.method_defined?(:map) # Ruby 1.4.6 - module Enumerable - alias map collect - end -end - -unless File.respond_to?(:read) # Ruby 1.6 - def File.read(fname) - open(fname) {|f| - return f.read - } - end -end - -unless Errno.const_defined?(:ENOTEMPTY) # Windows? - module Errno - class ENOTEMPTY - # We do not raise this exception, implementation is not needed. - end - end -end - -def File.binread(fname) - open(fname, 'rb') {|f| - return f.read - } -end - -# for corrupted Windows' stat(2) -def File.dir?(path) - File.directory?((path[-1,1] == '/') ? path : path + '/') -end - - -class ConfigTable - - include Enumerable - - def initialize(rbconfig) - @rbconfig = rbconfig - @items = [] - @table = {} - # options - @install_prefix = nil - @config_opt = nil - @verbose = true - @no_harm = false - end - - attr_accessor :install_prefix - attr_accessor :config_opt - - attr_writer :verbose - - def verbose? - @verbose - end - - attr_writer :no_harm - - def no_harm? - @no_harm - end - - def [](key) - lookup(key).resolve(self) - end - - def []=(key, val) - lookup(key).set val - end - - def names - @items.map {|i| i.name } - end - - def each(&block) - @items.each(&block) - end - - def key?(name) - @table.key?(name) - end - - def lookup(name) - @table[name] or setup_rb_error "no such config item: #{name}" - end - - def add(item) - @items.push item - @table[item.name] = item - end - - def remove(name) - item = lookup(name) - @items.delete_if {|i| i.name == name } - @table.delete_if {|name, i| i.name == name } - item - end - - def load_script(path, inst = nil) - if File.file?(path) - MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path - end - end - - def savefile - '.config' - end - - def load_savefile - begin - File.foreach(savefile()) do |line| - k, v = *line.split(/=/, 2) - self[k] = v.strip - end - rescue Errno::ENOENT - setup_rb_error $!.message + "\n#{File.basename($0)} config first" - end - end - - def save - @items.each {|i| i.value } - File.open(savefile(), 'w') {|f| - @items.each do |i| - f.printf "%s=%s\n", i.name, i.value if i.value? and i.value - end - } - end - - def load_standard_entries - standard_entries(@rbconfig).each do |ent| - add ent - end - end - - def standard_entries(rbconfig) - c = rbconfig - - rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT']) - - major = c['MAJOR'].to_i - minor = c['MINOR'].to_i - teeny = c['TEENY'].to_i - version = "#{major}.#{minor}" - - # ruby ver. >= 1.4.4? - newpath_p = ((major >= 2) or - ((major == 1) and - ((minor >= 5) or - ((minor == 4) and (teeny >= 4))))) - - if c['rubylibdir'] - # V > 1.6.3 - libruby = "#{c['prefix']}/lib/ruby" - librubyver = c['rubylibdir'] - librubyverarch = c['archdir'] - siteruby = c['sitedir'] - siterubyver = c['sitelibdir'] - siterubyverarch = c['sitearchdir'] - elsif newpath_p - # 1.4.4 <= V <= 1.6.3 - libruby = "#{c['prefix']}/lib/ruby" - librubyver = "#{c['prefix']}/lib/ruby/#{version}" - librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" - siteruby = c['sitedir'] - siterubyver = "$siteruby/#{version}" - siterubyverarch = "$siterubyver/#{c['arch']}" - else - # V < 1.4.4 - libruby = "#{c['prefix']}/lib/ruby" - librubyver = "#{c['prefix']}/lib/ruby/#{version}" - librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" - siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" - siterubyver = siteruby - siterubyverarch = "$siterubyver/#{c['arch']}" - end - parameterize = lambda {|path| - path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') - } - - if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } - makeprog = arg.sub(/'/, '').split(/=/, 2)[1] - else - makeprog = 'make' - end - - [ - ExecItem.new('installdirs', 'std/site/home', - 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ - {|val, table| - case val - when 'std' - table['rbdir'] = '$librubyver' - table['sodir'] = '$librubyverarch' - when 'site' - table['rbdir'] = '$siterubyver' - table['sodir'] = '$siterubyverarch' - when 'home' - setup_rb_error '$HOME was not set' unless ENV['HOME'] - table['prefix'] = ENV['HOME'] - table['rbdir'] = '$libdir/ruby' - table['sodir'] = '$libdir/ruby' - end - }, - PathItem.new('prefix', 'path', c['prefix'], - 'path prefix of target environment'), - PathItem.new('bindir', 'path', parameterize.call(c['bindir']), - 'the directory for commands'), - PathItem.new('libdir', 'path', parameterize.call(c['libdir']), - 'the directory for libraries'), - PathItem.new('datadir', 'path', parameterize.call(c['datadir']), - 'the directory for shared data'), - PathItem.new('mandir', 'path', parameterize.call(c['mandir']), - 'the directory for man pages'), - PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), - 'the directory for system configuration files'), - PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), - 'the directory for local state data'), - PathItem.new('libruby', 'path', libruby, - 'the directory for ruby libraries'), - PathItem.new('librubyver', 'path', librubyver, - 'the directory for standard ruby libraries'), - PathItem.new('librubyverarch', 'path', librubyverarch, - 'the directory for standard ruby extensions'), - PathItem.new('siteruby', 'path', siteruby, - 'the directory for version-independent aux ruby libraries'), - PathItem.new('siterubyver', 'path', siterubyver, - 'the directory for aux ruby libraries'), - PathItem.new('siterubyverarch', 'path', siterubyverarch, - 'the directory for aux ruby binaries'), - PathItem.new('rbdir', 'path', '$siterubyver', - 'the directory for ruby scripts'), - PathItem.new('sodir', 'path', '$siterubyverarch', - 'the directory for ruby extentions'), - PathItem.new('rubypath', 'path', rubypath, - 'the path to set to #! line'), - ProgramItem.new('rubyprog', 'name', rubypath, - 'the ruby program using for installation'), - ProgramItem.new('makeprog', 'name', makeprog, - 'the make program to compile ruby extentions'), - SelectItem.new('shebang', 'all/ruby/never', 'ruby', - 'shebang line (#!) editing mode'), - BoolItem.new('without-ext', 'yes/no', 'no', - 'does not compile/install ruby extentions') - ] - end - private :standard_entries - - def load_multipackage_entries - multipackage_entries().each do |ent| - add ent - end - end - - def multipackage_entries - [ - PackageSelectionItem.new('with', 'name,name...', '', 'ALL', - 'package names that you want to install'), - PackageSelectionItem.new('without', 'name,name...', '', 'NONE', - 'package names that you do not want to install') - ] - end - private :multipackage_entries - - ALIASES = { - 'std-ruby' => 'librubyver', - 'stdruby' => 'librubyver', - 'rubylibdir' => 'librubyver', - 'archdir' => 'librubyverarch', - 'site-ruby-common' => 'siteruby', # For backward compatibility - 'site-ruby' => 'siterubyver', # For backward compatibility - 'bin-dir' => 'bindir', - 'bin-dir' => 'bindir', - 'rb-dir' => 'rbdir', - 'so-dir' => 'sodir', - 'data-dir' => 'datadir', - 'ruby-path' => 'rubypath', - 'ruby-prog' => 'rubyprog', - 'ruby' => 'rubyprog', - 'make-prog' => 'makeprog', - 'make' => 'makeprog' - } - - def fixup - ALIASES.each do |ali, name| - @table[ali] = @table[name] - end - @items.freeze - @table.freeze - @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ - end - - def parse_opt(opt) - m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}" - m.to_a[1,2] - end - - def dllext - @rbconfig['DLEXT'] - end - - def value_config?(name) - lookup(name).value? - end - - class Item - def initialize(name, template, default, desc) - @name = name.freeze - @template = template - @value = default - @default = default - @description = desc - end - - attr_reader :name - attr_reader :description - - attr_accessor :default - alias help_default default - - def help_opt - "--#{@name}=#{@template}" - end - - def value? - true - end - - def value - @value - end - - def resolve(table) - @value.gsub(%r<\$([^/]+)>) { table[$1] } - end - - def set(val) - @value = check(val) - end - - private - - def check(val) - setup_rb_error "config: --#{name} requires argument" unless val - val - end - end - - class BoolItem < Item - def config_type - 'bool' - end - - def help_opt - "--#{@name}" - end - - private - - def check(val) - return 'yes' unless val - case val - when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes' - when /\An(o)?\z/i, /\Af(alse)\z/i then 'no' - else - setup_rb_error "config: --#{@name} accepts only yes/no for argument" - end - end - end - - class PathItem < Item - def config_type - 'path' - end - - private - - def check(path) - setup_rb_error "config: --#{@name} requires argument" unless path - path[0,1] == '$' ? path : File.expand_path(path) - end - end - - class ProgramItem < Item - def config_type - 'program' - end - end - - class SelectItem < Item - def initialize(name, selection, default, desc) - super - @ok = selection.split('/') - end - - def config_type - 'select' - end - - private - - def check(val) - unless @ok.include?(val.strip) - setup_rb_error "config: use --#{@name}=#{@template} (#{val})" - end - val.strip - end - end - - class ExecItem < Item - def initialize(name, selection, desc, &block) - super name, selection, nil, desc - @ok = selection.split('/') - @action = block - end - - def config_type - 'exec' - end - - def value? - false - end - - def resolve(table) - setup_rb_error "$#{name()} wrongly used as option value" - end - - undef set - - def evaluate(val, table) - v = val.strip.downcase - unless @ok.include?(v) - setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" - end - @action.call v, table - end - end - - class PackageSelectionItem < Item - def initialize(name, template, default, help_default, desc) - super name, template, default, desc - @help_default = help_default - end - - attr_reader :help_default - - def config_type - 'package' - end - - private - - def check(val) - unless File.dir?("packages/#{val}") - setup_rb_error "config: no such package: #{val}" - end - val - end - end - - class MetaConfigEnvironment - def initialize(config, installer) - @config = config - @installer = installer - end - - def config_names - @config.names - end - - def config?(name) - @config.key?(name) - end - - def bool_config?(name) - @config.lookup(name).config_type == 'bool' - end - - def path_config?(name) - @config.lookup(name).config_type == 'path' - end - - def value_config?(name) - @config.lookup(name).config_type != 'exec' - end - - def add_config(item) - @config.add item - end - - def add_bool_config(name, default, desc) - @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) - end - - def add_path_config(name, default, desc) - @config.add PathItem.new(name, 'path', default, desc) - end - - def set_config_default(name, default) - @config.lookup(name).default = default - end - - def remove_config(name) - @config.remove(name) - end - - # For only multipackage - def packages - raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer - @installer.packages - end - - # For only multipackage - def declare_packages(list) - raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer - @installer.packages = list - end - end - -end # class ConfigTable - - -# This module requires: #verbose?, #no_harm? -module FileOperations - - def mkdir_p(dirname, prefix = nil) - dirname = prefix + File.expand_path(dirname) if prefix - $stderr.puts "mkdir -p #{dirname}" if verbose? - return if no_harm? - - # Does not check '/', it's too abnormal. - dirs = File.expand_path(dirname).split(%r<(?=/)>) - if /\A[a-z]:\z/i =~ dirs[0] - disk = dirs.shift - dirs[0] = disk + dirs[0] - end - dirs.each_index do |idx| - path = dirs[0..idx].join('') - Dir.mkdir path unless File.dir?(path) - end - end - - def rm_f(path) - $stderr.puts "rm -f #{path}" if verbose? - return if no_harm? - force_remove_file path - end - - def rm_rf(path) - $stderr.puts "rm -rf #{path}" if verbose? - return if no_harm? - remove_tree path - end - - def remove_tree(path) - if File.symlink?(path) - remove_file path - elsif File.dir?(path) - remove_tree0 path - else - force_remove_file path - end - end - - def remove_tree0(path) - Dir.foreach(path) do |ent| - next if ent == '.' - next if ent == '..' - entpath = "#{path}/#{ent}" - if File.symlink?(entpath) - remove_file entpath - elsif File.dir?(entpath) - remove_tree0 entpath - else - force_remove_file entpath - end - end - begin - Dir.rmdir path - rescue Errno::ENOTEMPTY - # directory may not be empty - end - end - - def move_file(src, dest) - force_remove_file dest - begin - File.rename src, dest - rescue - File.open(dest, 'wb') {|f| - f.write File.binread(src) - } - File.chmod File.stat(src).mode, dest - File.unlink src - end - end - - def force_remove_file(path) - begin - remove_file path - rescue - end - end - - def remove_file(path) - File.chmod 0777, path - File.unlink path - end - - def install(from, dest, mode, prefix = nil) - $stderr.puts "install #{from} #{dest}" if verbose? - return if no_harm? - - realdest = prefix ? prefix + File.expand_path(dest) : dest - realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) - str = File.binread(from) - if diff?(str, realdest) - verbose_off { - rm_f realdest if File.exist?(realdest) - } - File.open(realdest, 'wb') {|f| - f.write str - } - File.chmod mode, realdest - - File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| - if prefix - f.puts realdest.sub(prefix, '') - else - f.puts realdest - end - } - end - end - - def diff?(new_content, path) - return true unless File.exist?(path) - new_content != File.binread(path) - end - - def command(*args) - $stderr.puts args.join(' ') if verbose? - system(*args) or raise RuntimeError, - "system(#{args.map{|a| a.inspect }.join(' ')}) failed" - end - - def ruby(*args) - command config('rubyprog'), *args - end - - def make(task = nil) - command(*[config('makeprog'), task].compact) - end - - def extdir?(dir) - File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") - end - - def files_of(dir) - Dir.open(dir) {|d| - return d.select {|ent| File.file?("#{dir}/#{ent}") } - } - end - - DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) - - def directories_of(dir) - Dir.open(dir) {|d| - return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT - } - end - -end - - -# This module requires: #srcdir_root, #objdir_root, #relpath -module HookScriptAPI - - def get_config(key) - @config[key] - end - - alias config get_config - - # obsolete: use metaconfig to change configuration - def set_config(key, val) - @config[key] = val - end - - # - # srcdir/objdir (works only in the package directory) - # - - def curr_srcdir - "#{srcdir_root()}/#{relpath()}" - end - - def curr_objdir - "#{objdir_root()}/#{relpath()}" - end - - def srcfile(path) - "#{curr_srcdir()}/#{path}" - end - - def srcexist?(path) - File.exist?(srcfile(path)) - end - - def srcdirectory?(path) - File.dir?(srcfile(path)) - end - - def srcfile?(path) - File.file?(srcfile(path)) - end - - def srcentries(path = '.') - Dir.open("#{curr_srcdir()}/#{path}") {|d| - return d.to_a - %w(. ..) - } - end - - def srcfiles(path = '.') - srcentries(path).select {|fname| - File.file?(File.join(curr_srcdir(), path, fname)) - } - end - - def srcdirectories(path = '.') - srcentries(path).select {|fname| - File.dir?(File.join(curr_srcdir(), path, fname)) - } - end - -end - - -class ToplevelInstaller - - Version = '3.4.1' - Copyright = 'Copyright (c) 2000-2005 Minero Aoki' - - TASKS = [ - [ 'all', 'do config, setup, then install' ], - [ 'config', 'saves your configurations' ], - [ 'show', 'shows current configuration' ], - [ 'setup', 'compiles ruby extentions and others' ], - [ 'install', 'installs files' ], - [ 'test', 'run all tests in test/' ], - [ 'clean', "does `make clean' for each extention" ], - [ 'distclean',"does `make distclean' for each extention" ] - ] - - def ToplevelInstaller.invoke - config = ConfigTable.new(load_rbconfig()) - config.load_standard_entries - config.load_multipackage_entries if multipackage? - config.fixup - klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) - klass.new(File.dirname($0), config).invoke - end - - def ToplevelInstaller.multipackage? - File.dir?(File.dirname($0) + '/packages') - end - - def ToplevelInstaller.load_rbconfig - if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } - ARGV.delete(arg) - load File.expand_path(arg.split(/=/, 2)[1]) - $".push 'rbconfig.rb' - else - require 'rbconfig' - end - ::Config::CONFIG - end - - def initialize(ardir_root, config) - @ardir = File.expand_path(ardir_root) - @config = config - # cache - @valid_task_re = nil - end - - def config(key) - @config[key] - end - - def inspect - "#<#{self.class} #{__id__()}>" - end - - def invoke - run_metaconfigs - case task = parsearg_global() - when nil, 'all' - parsearg_config - init_installers - exec_config - exec_setup - exec_install - else - case task - when 'config', 'test' - ; - when 'clean', 'distclean' - @config.load_savefile if File.exist?(@config.savefile) - else - @config.load_savefile - end - __send__ "parsearg_#{task}" - init_installers - __send__ "exec_#{task}" - end - end - - def run_metaconfigs - @config.load_script "#{@ardir}/metaconfig" - end - - def init_installers - @installer = Installer.new(@config, @ardir, File.expand_path('.')) - end - - # - # Hook Script API bases - # - - def srcdir_root - @ardir - end - - def objdir_root - '.' - end - - def relpath - '.' - end - - # - # Option Parsing - # - - def parsearg_global - while arg = ARGV.shift - case arg - when /\A\w+\z/ - setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) - return arg - when '-q', '--quiet' - @config.verbose = false - when '--verbose' - @config.verbose = true - when '--help' - print_usage $stdout - exit 0 - when '--version' - puts "#{File.basename($0)} version #{Version}" - exit 0 - when '--copyright' - puts Copyright - exit 0 - else - setup_rb_error "unknown global option '#{arg}'" - end - end - nil - end - - def valid_task?(t) - valid_task_re() =~ t - end - - def valid_task_re - @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ - end - - def parsearg_no_options - unless ARGV.empty? - task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1) - setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" - end - end - - alias parsearg_show parsearg_no_options - alias parsearg_setup parsearg_no_options - alias parsearg_test parsearg_no_options - alias parsearg_clean parsearg_no_options - alias parsearg_distclean parsearg_no_options - - def parsearg_config - evalopt = [] - set = [] - @config.config_opt = [] - while i = ARGV.shift - if /\A--?\z/ =~ i - @config.config_opt = ARGV.dup - break - end - name, value = *@config.parse_opt(i) - if @config.value_config?(name) - @config[name] = value - else - evalopt.push [name, value] - end - set.push name - end - evalopt.each do |name, value| - @config.lookup(name).evaluate value, @config - end - # Check if configuration is valid - set.each do |n| - @config[n] if @config.value_config?(n) - end - end - - def parsearg_install - @config.no_harm = false - @config.install_prefix = '' - while a = ARGV.shift - case a - when '--no-harm' - @config.no_harm = true - when /\A--prefix=/ - path = a.split(/=/, 2)[1] - path = File.expand_path(path) unless path[0,1] == '/' - @config.install_prefix = path - else - setup_rb_error "install: unknown option #{a}" - end - end - end - - def print_usage(out) - out.puts 'Typical Installation Procedure:' - out.puts " $ ruby #{File.basename $0} config" - out.puts " $ ruby #{File.basename $0} setup" - out.puts " # ruby #{File.basename $0} install (may require root privilege)" - out.puts - out.puts 'Detailed Usage:' - out.puts " ruby #{File.basename $0} " - out.puts " ruby #{File.basename $0} [] []" - - fmt = " %-24s %s\n" - out.puts - out.puts 'Global options:' - out.printf fmt, '-q,--quiet', 'suppress message outputs' - out.printf fmt, ' --verbose', 'output messages verbosely' - out.printf fmt, ' --help', 'print this message' - out.printf fmt, ' --version', 'print version and quit' - out.printf fmt, ' --copyright', 'print copyright and quit' - out.puts - out.puts 'Tasks:' - TASKS.each do |name, desc| - out.printf fmt, name, desc - end - - fmt = " %-24s %s [%s]\n" - out.puts - out.puts 'Options for CONFIG or ALL:' - @config.each do |item| - out.printf fmt, item.help_opt, item.description, item.help_default - end - out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" - out.puts - out.puts 'Options for INSTALL:' - out.printf fmt, '--no-harm', 'only display what to do if given', 'off' - out.printf fmt, '--prefix=path', 'install path prefix', '' - out.puts - end - - # - # Task Handlers - # - - def exec_config - @installer.exec_config - @config.save # must be final - end - - def exec_setup - @installer.exec_setup - end - - def exec_install - @installer.exec_install - end - - def exec_test - @installer.exec_test - end - - def exec_show - @config.each do |i| - printf "%-20s %s\n", i.name, i.value if i.value? - end - end - - def exec_clean - @installer.exec_clean - end - - def exec_distclean - @installer.exec_distclean - end - -end # class ToplevelInstaller - - -class ToplevelInstallerMulti < ToplevelInstaller - - include FileOperations - - def initialize(ardir_root, config) - super - @packages = directories_of("#{@ardir}/packages") - raise 'no package exists' if @packages.empty? - @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) - end - - def run_metaconfigs - @config.load_script "#{@ardir}/metaconfig", self - @packages.each do |name| - @config.load_script "#{@ardir}/packages/#{name}/metaconfig" - end - end - - attr_reader :packages - - def packages=(list) - raise 'package list is empty' if list.empty? - list.each do |name| - raise "directory packages/#{name} does not exist"\ - unless File.dir?("#{@ardir}/packages/#{name}") - end - @packages = list - end - - def init_installers - @installers = {} - @packages.each do |pack| - @installers[pack] = Installer.new(@config, - "#{@ardir}/packages/#{pack}", - "packages/#{pack}") - end - with = extract_selection(config('with')) - without = extract_selection(config('without')) - @selected = @installers.keys.select {|name| - (with.empty? or with.include?(name)) \ - and not without.include?(name) - } - end - - def extract_selection(list) - a = list.split(/,/) - a.each do |name| - setup_rb_error "no such package: #{name}" unless @installers.key?(name) - end - a - end - - def print_usage(f) - super - f.puts 'Inluded packages:' - f.puts ' ' + @packages.sort.join(' ') - f.puts - end - - # - # Task Handlers - # - - def exec_config - run_hook 'pre-config' - each_selected_installers {|inst| inst.exec_config } - run_hook 'post-config' - @config.save # must be final - end - - def exec_setup - run_hook 'pre-setup' - each_selected_installers {|inst| inst.exec_setup } - run_hook 'post-setup' - end - - def exec_install - run_hook 'pre-install' - each_selected_installers {|inst| inst.exec_install } - run_hook 'post-install' - end - - def exec_test - run_hook 'pre-test' - each_selected_installers {|inst| inst.exec_test } - run_hook 'post-test' - end - - def exec_clean - rm_f @config.savefile - run_hook 'pre-clean' - each_selected_installers {|inst| inst.exec_clean } - run_hook 'post-clean' - end - - def exec_distclean - rm_f @config.savefile - run_hook 'pre-distclean' - each_selected_installers {|inst| inst.exec_distclean } - run_hook 'post-distclean' - end - - # - # lib - # - - def each_selected_installers - Dir.mkdir 'packages' unless File.dir?('packages') - @selected.each do |pack| - $stderr.puts "Processing the package `#{pack}' ..." if verbose? - Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") - Dir.chdir "packages/#{pack}" - yield @installers[pack] - Dir.chdir '../..' - end - end - - def run_hook(id) - @root_installer.run_hook id - end - - # module FileOperations requires this - def verbose? - @config.verbose? - end - - # module FileOperations requires this - def no_harm? - @config.no_harm? - end - -end # class ToplevelInstallerMulti - - -class Installer - - FILETYPES = %w( bin lib ext data conf man ) - - include FileOperations - include HookScriptAPI - - def initialize(config, srcroot, objroot) - @config = config - @srcdir = File.expand_path(srcroot) - @objdir = File.expand_path(objroot) - @currdir = '.' - end - - def inspect - "#<#{self.class} #{File.basename(@srcdir)}>" - end - - def noop(rel) - end - - # - # Hook Script API base methods - # - - def srcdir_root - @srcdir - end - - def objdir_root - @objdir - end - - def relpath - @currdir - end - - # - # Config Access - # - - # module FileOperations requires this - def verbose? - @config.verbose? - end - - # module FileOperations requires this - def no_harm? - @config.no_harm? - end - - def verbose_off - begin - save, @config.verbose = @config.verbose?, false - yield - ensure - @config.verbose = save - end - end - - # - # TASK config - # - - def exec_config - exec_task_traverse 'config' - end - - alias config_dir_bin noop - alias config_dir_lib noop - - def config_dir_ext(rel) - extconf if extdir?(curr_srcdir()) - end - - alias config_dir_data noop - alias config_dir_conf noop - alias config_dir_man noop - - def extconf - ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt - end - - # - # TASK setup - # - - def exec_setup - exec_task_traverse 'setup' - end - - def setup_dir_bin(rel) - files_of(curr_srcdir()).each do |fname| - update_shebang_line "#{curr_srcdir()}/#{fname}" - end - end - - alias setup_dir_lib noop - - def setup_dir_ext(rel) - make if extdir?(curr_srcdir()) - end - - alias setup_dir_data noop - alias setup_dir_conf noop - alias setup_dir_man noop - - def update_shebang_line(path) - return if no_harm? - return if config('shebang') == 'never' - old = Shebang.load(path) - if old - $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1 - new = new_shebang(old) - return if new.to_s == old.to_s - else - return unless config('shebang') == 'all' - new = Shebang.new(config('rubypath')) - end - $stderr.puts "updating shebang: #{File.basename(path)}" if verbose? - open_atomic_writer(path) {|output| - File.open(path, 'rb') {|f| - f.gets if old # discard - output.puts new.to_s - output.print f.read - } - } - end - - def new_shebang(old) - if /\Aruby/ =~ File.basename(old.cmd) - Shebang.new(config('rubypath'), old.args) - elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby' - Shebang.new(config('rubypath'), old.args[1..-1]) - else - return old unless config('shebang') == 'all' - Shebang.new(config('rubypath')) - end - end - - def open_atomic_writer(path, &block) - tmpfile = File.basename(path) + '.tmp' - begin - File.open(tmpfile, 'wb', &block) - File.rename tmpfile, File.basename(path) - ensure - File.unlink tmpfile if File.exist?(tmpfile) - end - end - - class Shebang - def Shebang.load(path) - line = nil - File.open(path) {|f| - line = f.gets - } - return nil unless /\A#!/ =~ line - parse(line) - end - - def Shebang.parse(line) - cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ') - new(cmd, args) - end - - def initialize(cmd, args = []) - @cmd = cmd - @args = args - end - - attr_reader :cmd - attr_reader :args - - def to_s - "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}") - end - end - - # - # TASK install - # - - def exec_install - rm_f 'InstalledFiles' - exec_task_traverse 'install' - end - - def install_dir_bin(rel) - install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755 - end - - def install_dir_lib(rel) - install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644 - end - - def install_dir_ext(rel) - return unless extdir?(curr_srcdir()) - install_files rubyextentions('.'), - "#{config('sodir')}/#{File.dirname(rel)}", - 0555 - end - - def install_dir_data(rel) - install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 - end - - def install_dir_conf(rel) - # FIXME: should not remove current config files - # (rename previous file to .old/.org) - install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 - end - - def install_dir_man(rel) - install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 - end - - def install_files(list, dest, mode) - mkdir_p dest, @config.install_prefix - list.each do |fname| - install fname, dest, mode, @config.install_prefix - end - end - - def libfiles - glob_reject(%w(*.y *.output), targetfiles()) - end - - def rubyextentions(dir) - ents = glob_select("*.#{@config.dllext}", targetfiles()) - if ents.empty? - setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" - end - ents - end - - def targetfiles - mapdir(existfiles() - hookfiles()) - end - - def mapdir(ents) - ents.map {|ent| - if File.exist?(ent) - then ent # objdir - else "#{curr_srcdir()}/#{ent}" # srcdir - end - } - end - - # picked up many entries from cvs-1.11.1/src/ignore.c - JUNK_FILES = %w( - core RCSLOG tags TAGS .make.state - .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb - *~ *.old *.bak *.BAK *.orig *.rej _$* *$ - - *.org *.in .* - ) - - def existfiles - glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) - end - - def hookfiles - %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| - %w( config setup install clean ).map {|t| sprintf(fmt, t) } - }.flatten - end - - def glob_select(pat, ents) - re = globs2re([pat]) - ents.select {|ent| re =~ ent } - end - - def glob_reject(pats, ents) - re = globs2re(pats) - ents.reject {|ent| re =~ ent } - end - - GLOB2REGEX = { - '.' => '\.', - '$' => '\$', - '#' => '\#', - '*' => '.*' - } - - def globs2re(pats) - /\A(?:#{ - pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') - })\z/ - end - - # - # TASK test - # - - TESTDIR = 'test' - - def exec_test - unless File.directory?('test') - $stderr.puts 'no test in this package' if verbose? - return - end - $stderr.puts 'Running tests...' if verbose? - begin - require 'test/unit' - rescue LoadError - setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.' - end - runner = Test::Unit::AutoRunner.new(true) - runner.to_run << TESTDIR - runner.run - end - - # - # TASK clean - # - - def exec_clean - exec_task_traverse 'clean' - rm_f @config.savefile - rm_f 'InstalledFiles' - end - - alias clean_dir_bin noop - alias clean_dir_lib noop - alias clean_dir_data noop - alias clean_dir_conf noop - alias clean_dir_man noop - - def clean_dir_ext(rel) - return unless extdir?(curr_srcdir()) - make 'clean' if File.file?('Makefile') - end - - # - # TASK distclean - # - - def exec_distclean - exec_task_traverse 'distclean' - rm_f @config.savefile - rm_f 'InstalledFiles' - end - - alias distclean_dir_bin noop - alias distclean_dir_lib noop - - def distclean_dir_ext(rel) - return unless extdir?(curr_srcdir()) - make 'distclean' if File.file?('Makefile') - end - - alias distclean_dir_data noop - alias distclean_dir_conf noop - alias distclean_dir_man noop - - # - # Traversing - # - - def exec_task_traverse(task) - run_hook "pre-#{task}" - FILETYPES.each do |type| - if type == 'ext' and config('without-ext') == 'yes' - $stderr.puts 'skipping ext/* by user option' if verbose? - next - end - traverse task, type, "#{task}_dir_#{type}" - end - run_hook "post-#{task}" - end - - def traverse(task, rel, mid) - dive_into(rel) { - run_hook "pre-#{task}" - __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') - directories_of(curr_srcdir()).each do |d| - traverse task, "#{rel}/#{d}", mid - end - run_hook "post-#{task}" - } - end - - def dive_into(rel) - return unless File.dir?("#{@srcdir}/#{rel}") - - dir = File.basename(rel) - Dir.mkdir dir unless File.dir?(dir) - prevdir = Dir.pwd - Dir.chdir dir - $stderr.puts '---> ' + rel if verbose? - @currdir = rel - yield - Dir.chdir prevdir - $stderr.puts '<--- ' + rel if verbose? - @currdir = File.dirname(rel) - end - - def run_hook(id) - path = [ "#{curr_srcdir()}/#{id}", - "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } - return unless path - begin - instance_eval File.read(path), path, 1 - rescue - raise if $DEBUG - setup_rb_error "hook #{path} failed:\n" + $!.message - end - end - -end # class Installer - - -class SetupError < StandardError; end - -def setup_rb_error(msg) - raise SetupError, msg -end - -if $0 == __FILE__ - begin - ToplevelInstaller.invoke - rescue SetupError - raise if $DEBUG - $stderr.puts $!.message - $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." - exit 1 - end -end diff --git a/vendor/plugins/facebooker/templates/layout.erb b/vendor/plugins/facebooker/templates/layout.erb deleted file mode 100644 index d7f002b2a..000000000 --- a/vendor/plugins/facebooker/templates/layout.erb +++ /dev/null @@ -1,24 +0,0 @@ - - - -<%= @contents %> - - \ No newline at end of file diff --git a/log/production.log b/vendor/plugins/rails_log_stdout/.gitkeep similarity index 100% rename from log/production.log rename to vendor/plugins/rails_log_stdout/.gitkeep diff --git a/vendor/stylesheets/active_admin.css.scss b/vendor/stylesheets/active_admin.css.scss new file mode 100644 index 000000000..9b2dc9d7f --- /dev/null +++ b/vendor/stylesheets/active_admin.css.scss @@ -0,0 +1,6 @@ +// Active Admin CSS Styles +@import "active_admin/mixins"; +@import "active_admin/base"; + +// To customize the Active Admin interfaces, add your +// styles here: diff --git a/vendor/stylesheets/chosen.css b/vendor/stylesheets/chosen.css new file mode 100644 index 000000000..8b724ed1f --- /dev/null +++ b/vendor/stylesheets/chosen.css @@ -0,0 +1,368 @@ +/* @group Base */ +.chzn-container { + font-size: 13px; + position: relative; + display: inline-block; + zoom: 1; + *display: inline; +} +.chzn-container .chzn-drop { + background: #fff; + border: 1px solid #aaa; + border-top: 0; + position: absolute; + top: 29px; + left: 0; + -webkit-box-shadow: 0 4px 5px rgba(0,0,0,.15); + -moz-box-shadow : 0 4px 5px rgba(0,0,0,.15); + -o-box-shadow : 0 4px 5px rgba(0,0,0,.15); + box-shadow : 0 4px 5px rgba(0,0,0,.15); + z-index: 999; +} +/* @end */ + +/* @group Single Chosen */ +.chzn-container-single .chzn-single { + background-color: #fff; + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.5, white)); + background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 50%); + background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 50%); + background-image: -o-linear-gradient(top, #eeeeee 0%,#ffffff 50%); + background-image: -ms-linear-gradient(top, #eeeeee 0%,#ffffff 50%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff',GradientType=0 ); + background-image: linear-gradient(top, #eeeeee 0%,#ffffff 50%); + -webkit-border-radius: 4px; + -moz-border-radius : 4px; + border-radius : 4px; + -moz-background-clip : padding; + -webkit-background-clip: padding-box; + background-clip : padding-box; + border: 1px solid #aaa; + display: block; + overflow: hidden; + white-space: nowrap; + position: relative; + height: 26px; + line-height: 26px; + padding: 0 0 0 8px; + color: #444; + text-decoration: none; +} +.chzn-container-single .chzn-single span { + margin-right: 26px; + display: block; + overflow: hidden; + white-space: nowrap; + -o-text-overflow: ellipsis; + -ms-text-overflow: ellipsis; + text-overflow: ellipsis; +} +.chzn-container-single .chzn-single abbr { + display: block; + position: absolute; + right: 26px; + top: 8px; + width: 12px; + height: 13px; + font-size: 1px; + background: url(chosen-sprite.png) right top no-repeat; +} +.chzn-container-single .chzn-single abbr:hover { + background-position: right -11px; +} +.chzn-container-single .chzn-single div { + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius : 0 4px 4px 0; + border-radius : 0 4px 4px 0; + -moz-background-clip : padding; + -webkit-background-clip: padding-box; + background-clip : padding-box; + background: #ccc; + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee)); + background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%); + background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%); + background-image: -o-linear-gradient(bottom, #ccc 0%, #eee 60%); + background-image: -ms-linear-gradient(top, #cccccc 0%,#eeeeee 60%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cccccc', endColorstr='#eeeeee',GradientType=0 ); + background-image: linear-gradient(top, #cccccc 0%,#eeeeee 60%); + border-left: 1px solid #aaa; + position: absolute; + right: 0; + top: 0; + display: block; + height: 100%; + width: 18px; +} +.chzn-container-single .chzn-single div b { + background: url('chosen-sprite.png') no-repeat 0 1px; + display: block; + width: 100%; + height: 100%; +} +.chzn-container-single .chzn-search { + padding: 3px 4px; + position: relative; + margin: 0; + white-space: nowrap; +} +.chzn-container-single .chzn-search input { + background: #fff url('chosen-sprite.png') no-repeat 100% -22px; + background: url('chosen-sprite.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee)); + background: url('chosen-sprite.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%); + background: url('chosen-sprite.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%); + background: url('chosen-sprite.png') no-repeat 100% -22px, -o-linear-gradient(bottom, white 85%, #eeeeee 99%); + background: url('chosen-sprite.png') no-repeat 100% -22px, -ms-linear-gradient(top, #ffffff 85%,#eeeeee 99%); + background: url('chosen-sprite.png') no-repeat 100% -22px, -ms-linear-gradient(top, #ffffff 85%,#eeeeee 99%); + background: url('chosen-sprite.png') no-repeat 100% -22px, linear-gradient(top, #ffffff 85%,#eeeeee 99%); + margin: 1px 0; + padding: 4px 20px 4px 5px; + outline: 0; + border: 1px solid #aaa; + font-family: sans-serif; + font-size: 1em; +} +.chzn-container-single .chzn-drop { + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius : 0 0 4px 4px; + border-radius : 0 0 4px 4px; + -moz-background-clip : padding; + -webkit-background-clip: padding-box; + background-clip : padding-box; +} +/* @end */ + +.chzn-container-single-nosearch .chzn-search input { + position: absolute; + left: -9000px; +} + +/* @group Multi Chosen */ +.chzn-container-multi .chzn-choices { + background-color: #fff; + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee)); + background-image: -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%); + background-image: -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%); + background-image: -o-linear-gradient(bottom, white 85%, #eeeeee 99%); + background-image: -ms-linear-gradient(top, #ffffff 85%,#eeeeee 99%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee',GradientType=0 ); + background-image: linear-gradient(top, #ffffff 85%,#eeeeee 99%); + border: 1px solid #aaa; + margin: 0; + padding: 0; + cursor: text; + overflow: hidden; + height: auto !important; + height: 1%; + position: relative; +} +.chzn-container-multi .chzn-choices li { + float: left; + list-style: none; +} +.chzn-container-multi .chzn-choices .search-field { + white-space: nowrap; + margin: 0; + padding: 0; +} +.chzn-container-multi .chzn-choices .search-field input { + color: #666; + background: transparent !important; + border: 0 !important; + padding: 5px; + margin: 1px 0; + outline: 0; + -webkit-box-shadow: none; + -moz-box-shadow : none; + -o-box-shadow : none; + box-shadow : none; +} +.chzn-container-multi .chzn-choices .search-field .default { + color: #999; +} +.chzn-container-multi .chzn-choices .search-choice { + -webkit-border-radius: 3px; + -moz-border-radius : 3px; + border-radius : 3px; + -moz-background-clip : padding; + -webkit-background-clip: padding-box; + background-clip : padding-box; + background-color: #e4e4e4; + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #e4e4e4), color-stop(0.7, #eeeeee)); + background-image: -webkit-linear-gradient(center bottom, #e4e4e4 0%, #eeeeee 70%); + background-image: -moz-linear-gradient(center bottom, #e4e4e4 0%, #eeeeee 70%); + background-image: -o-linear-gradient(bottom, #e4e4e4 0%, #eeeeee 70%); + background-image: -ms-linear-gradient(top, #e4e4e4 0%,#eeeeee 70%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#e4e4e4', endColorstr='#eeeeee',GradientType=0 ); + background-image: linear-gradient(top, #e4e4e4 0%,#eeeeee 70%); + color: #333; + border: 1px solid #b4b4b4; + line-height: 13px; + padding: 3px 19px 3px 6px; + margin: 3px 0 3px 5px; + position: relative; +} +.chzn-container-multi .chzn-choices .search-choice span { + cursor: default; +} +.chzn-container-multi .chzn-choices .search-choice-focus { + background: #d4d4d4; +} +.chzn-container-multi .chzn-choices .search-choice .search-choice-close { + display: block; + position: absolute; + right: 3px; + top: 4px; + width: 12px; + height: 13px; + font-size: 1px; + background: url(chosen-sprite.png) right top no-repeat; +} +.chzn-container-multi .chzn-choices .search-choice .search-choice-close:hover { + background-position: right -11px; +} +.chzn-container-multi .chzn-choices .search-choice-focus .search-choice-close { + background-position: right -11px; +} +/* @end */ + +/* @group Results */ +.chzn-container .chzn-results { + margin: 0 4px 4px 0; + max-height: 190px; + padding: 0 0 0 4px; + position: relative; + overflow-x: hidden; + overflow-y: auto; +} +.chzn-container-multi .chzn-results { + margin: -1px 0 0; + padding: 0; +} +.chzn-container .chzn-results li { + display: none; + line-height: 80%; + padding: 7px 7px 8px; + margin: 0; + list-style: none; +} +.chzn-container .chzn-results .active-result { + cursor: pointer; + display: list-item; +} +.chzn-container .chzn-results .highlighted { + background: #3875d7; + color: #fff; +} +.chzn-container .chzn-results li em { + background: #feffde; + font-style: normal; +} +.chzn-container .chzn-results .highlighted em { + background: transparent; +} +.chzn-container .chzn-results .no-results { + background: #f4f4f4; + display: list-item; +} +.chzn-container .chzn-results .group-result { + cursor: default; + color: #999; + font-weight: bold; +} +.chzn-container .chzn-results .group-option { + padding-left: 20px; +} +.chzn-container-multi .chzn-drop .result-selected { + display: none; +} +/* @end */ + +/* @group Active */ +.chzn-container-active .chzn-single { + -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3); + -moz-box-shadow : 0 0 5px rgba(0,0,0,.3); + -o-box-shadow : 0 0 5px rgba(0,0,0,.3); + box-shadow : 0 0 5px rgba(0,0,0,.3); + border: 1px solid #5897fb; +} +.chzn-container-active .chzn-single-with-drop { + border: 1px solid #aaa; + -webkit-box-shadow: 0 1px 0 #fff inset; + -moz-box-shadow : 0 1px 0 #fff inset; + -o-box-shadow : 0 1px 0 #fff inset; + box-shadow : 0 1px 0 #fff inset; + background-color: #eee; + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, white), color-stop(0.5, #eeeeee)); + background-image: -webkit-linear-gradient(center bottom, white 0%, #eeeeee 50%); + background-image: -moz-linear-gradient(center bottom, white 0%, #eeeeee 50%); + background-image: -o-linear-gradient(bottom, white 0%, #eeeeee 50%); + background-image: -ms-linear-gradient(top, #ffffff 0%,#eeeeee 50%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee',GradientType=0 ); + background-image: linear-gradient(top, #ffffff 0%,#eeeeee 50%); + -webkit-border-bottom-left-radius : 0; + -webkit-border-bottom-right-radius: 0; + -moz-border-radius-bottomleft : 0; + -moz-border-radius-bottomright: 0; + border-bottom-left-radius : 0; + border-bottom-right-radius: 0; +} +.chzn-container-active .chzn-single-with-drop div { + background: transparent; + border-left: none; +} +.chzn-container-active .chzn-single-with-drop div b { + background-position: -18px 1px; +} +.chzn-container-active .chzn-choices { + -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3); + -moz-box-shadow : 0 0 5px rgba(0,0,0,.3); + -o-box-shadow : 0 0 5px rgba(0,0,0,.3); + box-shadow : 0 0 5px rgba(0,0,0,.3); + border: 1px solid #5897fb; +} +.chzn-container-active .chzn-choices .search-field input { + color: #111 !important; +} +/* @end */ + +/* @group Disabled Support */ +.chzn-disabled { + cursor: default; + opacity:0.5 !important; +} +.chzn-disabled .chzn-single { + cursor: default; +} +.chzn-disabled .chzn-choices .search-choice .search-choice-close { + cursor: default; +} + +/* @group Right to Left */ +.chzn-rtl { direction:rtl;text-align: right; } +.chzn-rtl .chzn-single { padding-left: 0; padding-right: 8px; } +.chzn-rtl .chzn-single span { margin-left: 26px; margin-right: 0; } +.chzn-rtl .chzn-single div { + left: 0; right: auto; + border-left: none; border-right: 1px solid #aaaaaa; + -webkit-border-radius: 4px 0 0 4px; + -moz-border-radius : 4px 0 0 4px; + border-radius : 4px 0 0 4px; +} +.chzn-rtl .chzn-choices li { float: right; } +.chzn-rtl .chzn-choices .search-choice { padding: 3px 6px 3px 19px; margin: 3px 5px 3px 0; } +.chzn-rtl .chzn-choices .search-choice .search-choice-close { left: 5px; right: auto; background-position: right top;} +.chzn-rtl.chzn-container-single .chzn-results { margin-left: 4px; margin-right: 0; padding-left: 0; padding-right: 4px; } +.chzn-rtl .chzn-results .group-option { padding-left: 0; padding-right: 20px; } +.chzn-rtl.chzn-container-active .chzn-single-with-drop div { border-right: none; } +.chzn-rtl .chzn-search input { + background: url('chosen-sprite.png') no-repeat -38px -22px, #ffffff; + background: url('chosen-sprite.png') no-repeat -38px -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee)); + background: url('chosen-sprite.png') no-repeat -38px -22px, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%); + background: url('chosen-sprite.png') no-repeat -38px -22px, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%); + background: url('chosen-sprite.png') no-repeat -38px -22px, -o-linear-gradient(bottom, white 85%, #eeeeee 99%); + background: url('chosen-sprite.png') no-repeat -38px -22px, -ms-linear-gradient(top, #ffffff 85%,#eeeeee 99%); + background: url('chosen-sprite.png') no-repeat -38px -22px, -ms-linear-gradient(top, #ffffff 85%,#eeeeee 99%); + background: url('chosen-sprite.png') no-repeat -38px -22px, linear-gradient(top, #ffffff 85%,#eeeeee 99%); + padding: 4px 5px 4px 20px; +} +/* @end */ \ No newline at end of file