diff --git a/.example-env b/.example-env index 3e8752fb..54039a93 100644 --- a/.example-env +++ b/.example-env @@ -1,14 +1,43 @@ -RAILS_ENV=development +export DB_USERNAME='postgres' +export DB_PASSWORD='3112' +export DB_HOST='localhost' +export DB_PORT='5432' +export DB_NAME='metamap002' -AWS_ACCESS_KEY_ID -AWS_SECRET_ACCESS_KEY -BUNDLE_GEMFILE -SMTP_DOMAIN -SMTP_PASSWORD -SMTP_PORT -SMTP_SERVER -SMTP_USERNAME -SSO_KEY +export REALTIME_SERVER='http://localhost:5001' +export MAILER_DEFAULT_URL='localhost:3000' +export DEVISE_MAILER_SENDER='team@metamaps.cc' +export DEVISE_SECRET_KEY='f71c467e526f23d614b3b08866cad4788c502bed869c282f06e73ee6c94675b62fe1f6d52fa7ba8196b33031f0d2f3b67e27ea07693c52ecebccb01700cad614' + +# # you can safely leave these blank, unless you're deploying an instance, in +# # which case you'll need to set them up +# +# export S3_BUCKET_NAME +# export AWS_ACCESS_KEY_ID +# export AWS_SECRET_ACCESS_KEY +# export SSO_KEY +# +# export SMTP_DOMAIN +# export SMTP_PASSWORD +# export SMTP_PORT +# export SMTP_SERVER +# export SMTP_USERNAME + +#ruby garbage collection stuff + +export RUBY_GC_TUNE=0 #set to 1 to enable GC test +export RUBY_GC_TOKEN=4f4380fc9a2857d1f008005a3eb86928 +export RUBY_GC_HEAP_INIT_SLOTS=186426 +export RUBY_GC_HEAP_FREE_SLOTS=559278 +export RUBY_GC_HEAP_GROWTH_FACTOR=1.03 +export RUBY_GC_HEAP_GROWTH_MAX_SLOTS=74570 +export RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR=1.4 +export RUBY_GC_MALLOC_LIMIT=32883406 +export RUBY_GC_MALLOC_LIMIT_MAX=69055153 +export RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR=1.68 +export RUBY_GC_OLDMALLOC_LIMIT=32509481 +export RUBY_GC_OLDMALLOC_LIMIT_MAX=68269910 +export RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR=1.4 ## find the ENV currently in use in the app using : ## grep -rIso -P "(?<=ENV)(\.fetch\(|\[).[A-Z_]+.(\)|\])" diff --git a/.gitignore b/.gitignore index 75aa3c26..db060afa 100644 --- a/.gitignore +++ b/.gitignore @@ -4,21 +4,21 @@ # or operating system, you probably want to add a global ignore instead: # git config --global core.excludesfile ~/.gitignore_global +#assety stuff realtime/node_modules -config/database.yml +public/assets + +#secrets and config .env -#public/assets # Ignore bundler config .bundle -# Ignore the default SQLite database. -db/*.sqlite3 - # Ignore all logfiles and tempfiles. log/*.log tmp .DS_Store - +*/.DS_Store +.DS_Store? .vagrant diff --git a/.rspec b/.rspec new file mode 100644 index 00000000..83e16f80 --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--color +--require spec_helper diff --git a/.ruby-gemset b/.ruby-gemset new file mode 100644 index 00000000..f4597680 --- /dev/null +++ b/.ruby-gemset @@ -0,0 +1 @@ +metamaps_gen002 diff --git a/.ruby-version b/.ruby-version index ac2cdeba..378bc559 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.1.3 +ruby-2.1.3 diff --git a/Gemfile b/Gemfile index 9489f21d..b4e3bdf6 100644 --- a/Gemfile +++ b/Gemfile @@ -1,15 +1,12 @@ source 'https://rubygems.org' ruby '2.1.3' -gem 'rails', '3.2.17' - -# Bundle edge Rails instead: -# gem 'rails', :git => 'git://github.com/rails/rails.git' +gem 'rails', '4.2.4' gem 'devise' gem 'redis' gem 'pg' -gem 'cancan' +gem 'cancancan' gem 'formula' gem 'formtastic' gem 'json' @@ -20,7 +17,11 @@ gem 'uservoice-ruby' gem 'dotenv' gem 'paperclip' -gem 'aws-sdk' +gem 'aws-sdk', '< 2.0' + +gem 'jquery-rails' +gem 'jquery-ui-rails' +gem 'jbuilder' #gem 'therubyracer' #optional #gem 'rb-readline' @@ -29,32 +30,30 @@ gem 'aws-sdk' # in production environments by default. group :assets do gem 'sass-rails' - gem 'coffee-rails', '~> 3.2.1' + gem 'coffee-rails' # See https://github.com/sstephenson/execjs#readme for more supported runtimes # gem 'therubyracer' - gem 'uglifier', '>= 1.0.3' + gem 'uglifier' +end + +group :test do + gem 'rspec-rails' + gem 'factory_girl_rails' + gem 'shoulda-matchers' end group :production do #this is used on heroku #gem 'rmagick' + gem 'rails_12factor' end -gem 'jquery-rails', '2.1.2' - -# To use ActiveModel has_secure_password -# gem 'bcrypt-ruby', '~> 3.0.0' - -# To use Jbuilder templates for JSON - gem 'jbuilder', '0.8.2' - -# Use unicorn as the web server -# gem 'unicorn' - -# Deploy with Capistrano -# gem 'capistrano' - -# To use debugger -# gem 'ruby-debug19', :require => 'ruby-debug' - +group :development, :test do + gem 'pry-rails' + gem 'pry-byebug' + gem 'better_errors' + gem 'binding_of_caller' + gem 'quiet_assets' + gem 'tunemygc' +end diff --git a/Gemfile.lock b/Gemfile.lock index 98a5e233..5525a8cc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,152 +1,222 @@ GEM remote: https://rubygems.org/ specs: - actionmailer (3.2.17) - actionpack (= 3.2.17) - mail (~> 2.5.4) - actionpack (3.2.17) - activemodel (= 3.2.17) - activesupport (= 3.2.17) - builder (~> 3.0.0) + actionmailer (4.2.4) + actionpack (= 4.2.4) + actionview (= 4.2.4) + activejob (= 4.2.4) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 1.0, >= 1.0.5) + actionpack (4.2.4) + actionview (= 4.2.4) + activesupport (= 4.2.4) + rack (~> 1.6) + rack-test (~> 0.6.2) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (4.2.4) + activesupport (= 4.2.4) + builder (~> 3.1) erubis (~> 2.7.0) - journey (~> 1.0.4) - rack (~> 1.4.5) - rack-cache (~> 1.2) - rack-test (~> 0.6.1) - sprockets (~> 2.2.1) - activemodel (3.2.17) - activesupport (= 3.2.17) - builder (~> 3.0.0) - activerecord (3.2.17) - activemodel (= 3.2.17) - activesupport (= 3.2.17) - arel (~> 3.0.2) - tzinfo (~> 0.3.29) - activeresource (3.2.17) - activemodel (= 3.2.17) - activesupport (= 3.2.17) - activesupport (3.2.17) - i18n (~> 0.6, >= 0.6.4) - multi_json (~> 1.0) - arel (3.0.3) - aws-sdk (1.54.0) - aws-sdk-v1 (= 1.54.0) - aws-sdk-v1 (1.54.0) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + activejob (4.2.4) + activesupport (= 4.2.4) + globalid (>= 0.3.0) + activemodel (4.2.4) + activesupport (= 4.2.4) + builder (~> 3.1) + activerecord (4.2.4) + activemodel (= 4.2.4) + activesupport (= 4.2.4) + arel (~> 6.0) + activesupport (4.2.4) + i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + arel (6.0.3) + aws-sdk (1.66.0) + aws-sdk-v1 (= 1.66.0) + aws-sdk-v1 (1.66.0) json (~> 1.4) nokogiri (>= 1.4.4) - bcrypt (3.1.7) - bcrypt (3.1.7-x86-mingw32) - best_in_place (2.1.0) - jquery-rails - rails (~> 3.1) - builder (3.0.4) - cancan (1.6.10) + bcrypt (3.1.10) + best_in_place (3.0.3) + actionpack (>= 3.2) + railties (>= 3.2) + better_errors (2.1.1) + coderay (>= 1.0.0) + erubis (>= 2.6.6) + rack (>= 0.9.0) + binding_of_caller (0.7.2) + debug_inspector (>= 0.0.1) + builder (3.2.2) + byebug (5.0.0) + columnize (= 0.9.0) + cancancan (1.13.1) climate_control (0.0.3) activesupport (>= 3.0) - cocaine (0.5.4) + cocaine (0.5.7) climate_control (>= 0.0.3, < 1.0) - coffee-rails (3.2.2) + coderay (1.1.0) + coffee-rails (4.1.0) coffee-script (>= 2.2.0) - railties (~> 3.2.0) - coffee-script (2.3.0) + railties (>= 4.0.0, < 5.0) + coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.8.0) - devise (3.4.0) + coffee-script-source (1.9.1.1) + columnize (0.9.0) + debug_inspector (0.0.2) + devise (3.5.2) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 3.2.6, < 5) responders thread_safe (~> 0.1) warden (~> 1.2.3) - dotenv (2.0.0) + diff-lcs (1.2.5) + dotenv (2.0.2) erubis (2.7.0) - execjs (2.2.1) + execjs (2.6.0) ezcrypto (0.7.2) - formtastic (3.0.0) - actionpack (>= 3.2.13) - formula (1.0.1) - rails (> 3.0.0) - hike (1.2.3) - i18n (0.6.11) - jbuilder (0.8.2) + factory_girl (4.5.0) activesupport (>= 3.0.0) - journey (1.0.4) - jquery-rails (2.1.2) - railties (>= 3.1.0, < 5.0) - thor (~> 0.14) - json (1.8.1) - kaminari (0.16.1) + factory_girl_rails (4.5.0) + factory_girl (~> 4.5.0) + railties (>= 3.0.0) + formtastic (3.1.3) + actionpack (>= 3.2.13) + formula (1.1.1) + rails (> 3.0.0) + globalid (0.3.6) + activesupport (>= 4.1.0) + i18n (0.7.0) + jbuilder (2.3.2) + activesupport (>= 3.0.0, < 5) + multi_json (~> 1.2) + jquery-rails (4.0.5) + rails-dom-testing (~> 1.0) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + jquery-ui-rails (5.0.5) + railties (>= 3.2.16) + json (1.8.3) + kaminari (0.16.3) actionpack (>= 3.0.0) activesupport (>= 3.0.0) - mail (2.5.4) - mime-types (~> 1.16) - treetop (~> 1.4.8) - mime-types (1.25.1) - mini_portile (0.6.0) - multi_json (1.10.1) - nokogiri (1.6.3.1) - mini_portile (= 0.6.0) - nokogiri (1.6.3.1-x86-mingw32) - mini_portile (= 0.6.0) + loofah (2.0.3) + nokogiri (>= 1.5.9) + mail (2.6.3) + mime-types (>= 1.16, < 3) + method_source (0.8.2) + mime-types (2.6.2) + mimemagic (0.3.0) + mini_portile (0.6.2) + minitest (5.8.2) + multi_json (1.11.2) + nokogiri (1.6.6.2) + mini_portile (~> 0.6.0) oauth (0.4.7) orm_adapter (0.5.0) - paperclip (4.2.0) - activemodel (>= 3.0.0) - activesupport (>= 3.0.0) - cocaine (~> 0.5.3) + paperclip (4.3.1) + activemodel (>= 3.2.0) + activesupport (>= 3.2.0) + cocaine (~> 0.5.5) mime-types - pg (0.17.1) - pg (0.17.1-x86-mingw32) - polyglot (0.3.5) - rack (1.4.5) - rack-cache (1.2) - rack (>= 0.4) - rack-ssl (1.3.4) - rack - rack-test (0.6.2) + mimemagic (= 0.3.0) + pg (0.18.3) + pry (0.10.3) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) + pry-byebug (3.2.0) + byebug (~> 5.0) + pry (~> 0.10) + pry-rails (0.3.4) + pry (>= 0.9.10) + quiet_assets (1.1.0) + railties (>= 3.1, < 5.0) + rack (1.6.4) + rack-test (0.6.3) rack (>= 1.0) - rails (3.2.17) - actionmailer (= 3.2.17) - actionpack (= 3.2.17) - activerecord (= 3.2.17) - activeresource (= 3.2.17) - activesupport (= 3.2.17) - bundler (~> 1.0) - railties (= 3.2.17) - rails3-jquery-autocomplete (1.0.14) - rails (>= 3.0) - railties (3.2.17) - actionpack (= 3.2.17) - activesupport (= 3.2.17) - rack-ssl (~> 1.3.2) + rails (4.2.4) + actionmailer (= 4.2.4) + actionpack (= 4.2.4) + actionview (= 4.2.4) + activejob (= 4.2.4) + activemodel (= 4.2.4) + activerecord (= 4.2.4) + activesupport (= 4.2.4) + bundler (>= 1.3.0, < 2.0) + railties (= 4.2.4) + sprockets-rails + rails-deprecated_sanitizer (1.0.3) + activesupport (>= 4.2.0.alpha) + rails-dom-testing (1.0.7) + activesupport (>= 4.2.0.beta, < 5.0) + nokogiri (~> 1.6.0) + rails-deprecated_sanitizer (>= 1.0.1) + rails-html-sanitizer (1.0.2) + loofah (~> 2.0) + rails3-jquery-autocomplete (1.0.15) + rails (>= 3.2) + rails_12factor (0.0.3) + rails_serve_static_assets + rails_stdout_logging + rails_serve_static_assets (0.0.4) + rails_stdout_logging (0.0.4) + railties (4.2.4) + actionpack (= 4.2.4) + activesupport (= 4.2.4) rake (>= 0.8.7) - rdoc (~> 3.4) - thor (>= 0.14.6, < 2.0) - rake (10.3.2) - rdoc (3.12.2) - json (~> 1.4) - redis (3.1.0) - responders (1.1.1) - railties (>= 3.2, < 4.2) - sass (3.4.5) - sass-rails (3.2.6) - railties (~> 3.2.0) - sass (>= 3.1.10) - tilt (~> 1.3) - sprockets (2.2.2) - hike (~> 1.2) - multi_json (~> 1.0) - rack (~> 1.0) - tilt (~> 1.1, != 1.3.0) + thor (>= 0.18.1, < 2.0) + rake (10.4.2) + redis (3.2.1) + responders (2.1.0) + railties (>= 4.2.0, < 5) + rspec-core (3.3.2) + rspec-support (~> 3.3.0) + rspec-expectations (3.3.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.3.0) + rspec-mocks (3.3.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.3.0) + rspec-rails (3.3.3) + actionpack (>= 3.0, < 4.3) + activesupport (>= 3.0, < 4.3) + railties (>= 3.0, < 4.3) + rspec-core (~> 3.3.0) + rspec-expectations (~> 3.3.0) + rspec-mocks (~> 3.3.0) + rspec-support (~> 3.3.0) + rspec-support (3.3.0) + sass (3.4.19) + sass-rails (5.0.4) + railties (>= 4.0.0, < 5.0) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + shoulda-matchers (3.0.1) + activesupport (>= 4.0.0) + slop (3.6.0) + sprockets (3.4.0) + rack (> 1, < 3) + sprockets-rails (2.3.3) + actionpack (>= 3.0) + activesupport (>= 3.0) + sprockets (>= 2.8, < 4.0) thor (0.19.1) - thread_safe (0.3.4) - tilt (1.4.1) - treetop (1.4.15) - polyglot - polyglot (>= 0.3.1) - tzinfo (0.3.41) - uglifier (2.5.3) + thread_safe (0.3.5) + tilt (2.0.1) + tunemygc (1.0.61) + tzinfo (1.2.2) + thread_safe (~> 0.1) + uglifier (2.7.2) execjs (>= 0.3.0) json (>= 1.8.0) uservoice-ruby (0.0.11) @@ -158,26 +228,39 @@ GEM PLATFORMS ruby - x86-mingw32 DEPENDENCIES - aws-sdk + aws-sdk (< 2.0) best_in_place - cancan - coffee-rails (~> 3.2.1) + better_errors + binding_of_caller + cancancan + coffee-rails devise dotenv + factory_girl_rails formtastic formula - jbuilder (= 0.8.2) - jquery-rails (= 2.1.2) + jbuilder + jquery-rails + jquery-ui-rails json kaminari paperclip pg - rails (= 3.2.17) + pry-byebug + pry-rails + quiet_assets + rails (= 4.2.4) rails3-jquery-autocomplete + rails_12factor redis + rspec-rails sass-rails - uglifier (>= 1.0.3) + shoulda-matchers + tunemygc + uglifier uservoice-ruby + +BUNDLED WITH + 1.10.6 diff --git a/MacInstallation.md b/MacInstallation.md deleted file mode 100644 index 2d36494e..00000000 --- a/MacInstallation.md +++ /dev/null @@ -1,42 +0,0 @@ -If you are doing an upgrade and or recent pull for changes you will need to change your default ruby package from 2.1.1 to ruby 2.1.2 - -install homebrew - - \curl -sSL https://get.rvm.io | bash -s stable --rails - - rvm install 2.1.3 --with-gcc=clang - - rvm use 2.1.3 - - gem install lunchy - -(http://www.moncefbelyamani.com/how-to-install-postgresql-on-a-mac-with-homebrew-and-lunchy/) - - - brew install postgresql - ln -sfv /usr/local/opt/postgresql/*.plist ~/Library/LaunchAgents - createuser metamaps -P -s -d - -set a password - - lunchy start postgres - - -cd into the metamaps directory - - bundle install - - -copy the database.yml.default file and rename it database.yml -make sure the username and password match the POSTGRES username and password you set - - -http://nodejs.org/ hit install, download, open, install - - - rake db:create - rake db:schema:load - rake db:fixtures:load - rails s - -( to start the server) diff --git a/README.md b/README.md index 9d3bfc90..76444337 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ Metamaps ======= [![Join the chat at https://gitter.im/metamaps/metamaps_gen002](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/metamaps/metamaps_gen002?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +![Build Status](https://jenkins.devinhoward.ca/job/metamaps_gen002.develop/badge/icon) Welcome to the Metamaps GitHub repo. @@ -72,7 +73,7 @@ Copyright (c) 2015 Connor Turland [site-blog]: http://blog.metamaps.cc [site-beta]: http://metamaps.cc [community]: https://plus.google.com/u/0/communities/115060009262157699234 -[license]: https://github.com/metamaps/metamaps_gen002/blob/master/LICENSE -[contributing]: https://github.com/metamaps/metamaps_gen002/blob/master/CONTRIBUTING.md -[contributing-issues]: https://github.com/metamaps/metamaps_gen002/blob/master/CONTRIBUTING.md#reporting-bugs-and-other-issues -[windows-installation]: https://github.com/metamaps/metamaps_gen002/blob/master/WindowsInstallation.md +[license]: https://github.com/metamaps/metamaps_gen002/blob/develop/LICENSE +[contributing]: https://github.com/metamaps/metamaps_gen002/blob/develop/doc/CONTRIBUTING.md +[contributing-issues]: https://github.com/metamaps/metamaps_gen002/blob/develop/doc/CONTRIBUTING.md#reporting-bugs-and-other-issues +[windows-installation]: https://github.com/metamaps/metamaps_gen002/blob/develop/doc/WindowsInstallation.md diff --git a/UbuntuInstallation.md b/UbuntuInstallation.md deleted file mode 100644 index 54872124..00000000 --- a/UbuntuInstallation.md +++ /dev/null @@ -1,114 +0,0 @@ -Firstly this walkthrough is done with a 14.04 32bit install of Ubuntu. - -All commands that I could are terminal based. - -Lets check if all updates for the system are installed first - -in terminal type - - sudo apt-get update - -now we need to install git - - sudo apt-get install git - -lets get our RVM installed (Ruby Version Manager) now this is fun because the package you will get from apt-get is outdated. - -so we are going to use CURL to get RVM - - sudo apt-get install curl - -then lets install RVM with curl like this - - curl -L get.rvm.io | bash -s stable - - PATH=$PATH:$HOME/.rvm/bin - - [[ -s "$HOME/.profile" ]] && source "$HOME/.profile" - - [[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm" - - source ~/.rvm/scripts/rvm - -now we can actually install RVM - - rvm requirements - -running this will check your system for requirements as well so you will need to put your system password in. - -alright now we can download metamaps from the master using git - - git clone https://github.com/metamaps/metamaps_gen002.git - -now there is a couple other things we are going to need which is nodejs, postgresql, libpq-dev and redis-server - - sudo apt-get install nodejs - - sudo apt-get install postgresql - - sudo apt-get install libpq-dev - - sudo apt-get install redis-server - -Furthermore, if you want to be able to work on profile picture uploading, or use it -you'll need ImageMagick. On Ubuntu, you can just go find ImageMagick in the Ubuntu Software Centre - -Install the specific version of ruby needed this will take some time - - rvm install ruby-2.1.3 - -Now we also need to rename your database file which is in ./config/database.default.yml to database.yml - -now run inside your metamaps_gen002 folder - - bundle install - -in your top lvl directory for metamaps this is a lengthy process so you might want to go and make a coffee or something :) - -alright now we need to make sure your postgres password is the same as it is listed in the DB file so we are going to set it by - - sudo -u postgres psql - -Select postgres like this - - \password postgres - -set the password to 3112 - -Then to quit - - \q - -now we can run the rake install and db creation - - rake db:create - - rake db:schema:load - - rake db:fixtures:load - -Execute the server: - - rails s - -and dont forget to run the other server for realtime... -open a new terminal -navigate to ./realtime and run - - sudo apt-get install npm - npm install - nodejs realtime-server.js - -Now you're all set enjoy your personal server of metamaps :) - -Navigate your browser to localhost:3000 once you have the server running - -Sign in with the default account - -email: user@user.com - -password: toolsplusconsciousness - -OR create a new account at /join, and use access code 'qwertyui' - -Start mapping and programming! diff --git a/WindowsInstallation.md b/WindowsInstallation.md deleted file mode 100644 index fed11893..00000000 --- a/WindowsInstallation.md +++ /dev/null @@ -1,45 +0,0 @@ -If you have any trouble with this process, contact us at team@metamaps.cc, and one of our developers may be able to help you out. - -First, http://railsinstaller.org/en. This will install Ruby, Rails, and Git for you. - -Once you've done that, you will need to download PostgreSQL and node.js: - - - http://nodejs.org/en/download/ - - http://www.postgresql.org/download/windows/ - -Now open a terminal, and navigate to the folder that you want to download the metamaps files to and run the following: - - git clone https://github.com/metamaps/metamaps_gen002.git --branch develop - cd metamaps_gen002 - -Now you're in the main directory. - -Install all the gems needed for Metamaps by running - - bundle install - -Setting up the database: - -1) Copy /config/database.yml.default and rename the copy to /config/database.yml then edit database.yml with your text editor and set the password to whatever you chose when you set up the PostGres database. - -2) In a terminal: - - rake db:create - rake db:schema:load - rake db:fixtures:load - -Running the server: - - rails s - -Navigate your browser to localhost:3000 once you have the server running - -Sign in with the default account - -email: user@user.com - -password: toolsplusconsciousness - -OR create a new account at /join, and use access code 'qwertyui' - -Start mapping and programming! diff --git a/app/assets/images/.DS_Store b/app/assets/images/.DS_Store index a709ceac..5dbe43d0 100644 Binary files a/app/assets/images/.DS_Store and b/app/assets/images/.DS_Store differ diff --git a/app/assets/javascripts/lib/jquery-ui-1.8.23.custom.min.js b/app/assets/javascripts/lib/jquery-ui-1.8.23.custom.min.js deleted file mode 100644 index 564759cd..00000000 --- a/app/assets/javascripts/lib/jquery-ui-1.8.23.custom.min.js +++ /dev/null @@ -1,25 +0,0 @@ -/*! jQuery UI - v1.8.23 - 2012-08-15 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.core.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){function c(b,c){var e=b.nodeName.toLowerCase();if("area"===e){var f=b.parentNode,g=f.name,h;return!b.href||!g||f.nodeName.toLowerCase()!=="map"?!1:(h=a("img[usemap=#"+g+"]")[0],!!h&&d(h))}return(/input|select|textarea|button|object/.test(e)?!b.disabled:"a"==e?b.href||c:c)&&d(b)}function d(b){return!a(b).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}a.ui=a.ui||{};if(a.ui.version)return;a.extend(a.ui,{version:"1.8.23",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}}),a.fn.extend({propAttr:a.fn.prop||a.fn.attr,_focus:a.fn.focus,focus:function(b,c){return typeof b=="number"?this.each(function(){var d=this;setTimeout(function(){a(d).focus(),c&&c.call(d)},b)}):this._focus.apply(this,arguments)},scrollParent:function(){var b;return a.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?b=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(a.curCSS(this,"position",1))&&/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0):b=this.parents().filter(function(){return/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0),/fixed/.test(this.css("position"))||!b.length?a(document):b},zIndex:function(c){if(c!==b)return this.css("zIndex",c);if(this.length){var d=a(this[0]),e,f;while(d.length&&d[0]!==document){e=d.css("position");if(e==="absolute"||e==="relative"||e==="fixed"){f=parseInt(d.css("zIndex"),10);if(!isNaN(f)&&f!==0)return f}d=d.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),a("").outerWidth(1).jquery||a.each(["Width","Height"],function(c,d){function h(b,c,d,f){return a.each(e,function(){c-=parseFloat(a.curCSS(b,"padding"+this,!0))||0,d&&(c-=parseFloat(a.curCSS(b,"border"+this+"Width",!0))||0),f&&(c-=parseFloat(a.curCSS(b,"margin"+this,!0))||0)}),c}var e=d==="Width"?["Left","Right"]:["Top","Bottom"],f=d.toLowerCase(),g={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};a.fn["inner"+d]=function(c){return c===b?g["inner"+d].call(this):this.each(function(){a(this).css(f,h(this,c)+"px")})},a.fn["outer"+d]=function(b,c){return typeof b!="number"?g["outer"+d].call(this,b):this.each(function(){a(this).css(f,h(this,b,!0,c)+"px")})}}),a.extend(a.expr[":"],{data:a.expr.createPseudo?a.expr.createPseudo(function(b){return function(c){return!!a.data(c,b)}}):function(b,c,d){return!!a.data(b,d[3])},focusable:function(b){return c(b,!isNaN(a.attr(b,"tabindex")))},tabbable:function(b){var d=a.attr(b,"tabindex"),e=isNaN(d);return(e||d>=0)&&c(b,!e)}}),a(function(){var b=document.body,c=b.appendChild(c=document.createElement("div"));c.offsetHeight,a.extend(c.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),a.support.minHeight=c.offsetHeight===100,a.support.selectstart="onselectstart"in c,b.removeChild(c).style.display="none"}),a.curCSS||(a.curCSS=a.css),a.extend(a.ui,{plugin:{add:function(b,c,d){var e=a.ui[b].prototype;for(var f in d)e.plugins[f]=e.plugins[f]||[],e.plugins[f].push([c,d[f]])},call:function(a,b,c){var d=a.plugins[b];if(!d||!a.element[0].parentNode)return;for(var e=0;e0?!0:(b[d]=1,e=b[d]>0,b[d]=0,e)},isOverAxis:function(a,b,c){return a>b&&a=9||!!b.button?this._mouseStarted?(this._mouseDrag(b),b.preventDefault()):(this._mouseDistanceMet(b)&&this._mouseDelayMet(b)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,b)!==!1,this._mouseStarted?this._mouseDrag(b):this._mouseUp(b)),!this._mouseStarted):this._mouseUp(b)},_mouseUp:function(b){return a(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,b.target==this._mouseDownEvent.target&&a.data(b.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(b)),!1},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(a){return this.mouseDelayMet},_mouseStart:function(a){},_mouseDrag:function(a){},_mouseStop:function(a){},_mouseCapture:function(a){return!0}})})(jQuery);;/*! jQuery UI - v1.8.23 - 2012-08-15 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.position.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.ui=a.ui||{};var c=/left|center|right/,d=/top|center|bottom/,e="center",f={},g=a.fn.position,h=a.fn.offset;a.fn.position=function(b){if(!b||!b.of)return g.apply(this,arguments);b=a.extend({},b);var h=a(b.of),i=h[0],j=(b.collision||"flip").split(" "),k=b.offset?b.offset.split(" "):[0,0],l,m,n;return i.nodeType===9?(l=h.width(),m=h.height(),n={top:0,left:0}):i.setTimeout?(l=h.width(),m=h.height(),n={top:h.scrollTop(),left:h.scrollLeft()}):i.preventDefault?(b.at="left top",l=m=0,n={top:b.of.pageY,left:b.of.pageX}):(l=h.outerWidth(),m=h.outerHeight(),n=h.offset()),a.each(["my","at"],function(){var a=(b[this]||"").split(" ");a.length===1&&(a=c.test(a[0])?a.concat([e]):d.test(a[0])?[e].concat(a):[e,e]),a[0]=c.test(a[0])?a[0]:e,a[1]=d.test(a[1])?a[1]:e,b[this]=a}),j.length===1&&(j[1]=j[0]),k[0]=parseInt(k[0],10)||0,k.length===1&&(k[1]=k[0]),k[1]=parseInt(k[1],10)||0,b.at[0]==="right"?n.left+=l:b.at[0]===e&&(n.left+=l/2),b.at[1]==="bottom"?n.top+=m:b.at[1]===e&&(n.top+=m/2),n.left+=k[0],n.top+=k[1],this.each(function(){var c=a(this),d=c.outerWidth(),g=c.outerHeight(),h=parseInt(a.curCSS(this,"marginLeft",!0))||0,i=parseInt(a.curCSS(this,"marginTop",!0))||0,o=d+h+(parseInt(a.curCSS(this,"marginRight",!0))||0),p=g+i+(parseInt(a.curCSS(this,"marginBottom",!0))||0),q=a.extend({},n),r;b.my[0]==="right"?q.left-=d:b.my[0]===e&&(q.left-=d/2),b.my[1]==="bottom"?q.top-=g:b.my[1]===e&&(q.top-=g/2),f.fractions||(q.left=Math.round(q.left),q.top=Math.round(q.top)),r={left:q.left-h,top:q.top-i},a.each(["left","top"],function(c,e){a.ui.position[j[c]]&&a.ui.position[j[c]][e](q,{targetWidth:l,targetHeight:m,elemWidth:d,elemHeight:g,collisionPosition:r,collisionWidth:o,collisionHeight:p,offset:k,my:b.my,at:b.at})}),a.fn.bgiframe&&c.bgiframe(),c.offset(a.extend(q,{using:b.using}))})},a.ui.position={fit:{left:function(b,c){var d=a(window),e=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft();b.left=e>0?b.left-e:Math.max(b.left-c.collisionPosition.left,b.left)},top:function(b,c){var d=a(window),e=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop();b.top=e>0?b.top-e:Math.max(b.top-c.collisionPosition.top,b.top)}},flip:{left:function(b,c){if(c.at[0]===e)return;var d=a(window),f=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft(),g=c.my[0]==="left"?-c.elemWidth:c.my[0]==="right"?c.elemWidth:0,h=c.at[0]==="left"?c.targetWidth:-c.targetWidth,i=-2*c.offset[0];b.left+=c.collisionPosition.left<0?g+h+i:f>0?g+h+i:0},top:function(b,c){if(c.at[1]===e)return;var d=a(window),f=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop(),g=c.my[1]==="top"?-c.elemHeight:c.my[1]==="bottom"?c.elemHeight:0,h=c.at[1]==="top"?c.targetHeight:-c.targetHeight,i=-2*c.offset[1];b.top+=c.collisionPosition.top<0?g+h+i:f>0?g+h+i:0}}},a.offset.setOffset||(a.offset.setOffset=function(b,c){/static/.test(a.curCSS(b,"position"))&&(b.style.position="relative");var d=a(b),e=d.offset(),f=parseInt(a.curCSS(b,"top",!0),10)||0,g=parseInt(a.curCSS(b,"left",!0),10)||0,h={top:c.top-e.top+f,left:c.left-e.left+g};"using"in c?c.using.call(b,h):d.css(h)},a.fn.offset=function(b){var c=this[0];return!c||!c.ownerDocument?null:b?a.isFunction(b)?this.each(function(c){a(this).offset(b.call(this,c,a(this).offset()))}):this.each(function(){a.offset.setOffset(this,b)}):h.call(this)}),a.curCSS||(a.curCSS=a.css),function(){var b=document.getElementsByTagName("body")[0],c=document.createElement("div"),d,e,g,h,i;d=document.createElement(b?"div":"body"),g={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},b&&a.extend(g,{position:"absolute",left:"-1000px",top:"-1000px"});for(var j in g)d.style[j]=g[j];d.appendChild(c),e=b||document.documentElement,e.insertBefore(d,e.firstChild),c.style.cssText="position: absolute; left: 10.7432222px; top: 10.432325px; height: 30px; width: 201px;",h=a(c).offset(function(a,b){return b}).offset(),d.innerHTML="",e.removeChild(d),i=h.top+h.left+(b?2e3:0),f.fractions=i>21&&i<22}()})(jQuery);;/*! jQuery UI - v1.8.23 - 2012-08-15 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.draggable.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.widget("ui.draggable",a.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1},_create:function(){this.options.helper=="original"&&!/^(?:r|a|f)/.test(this.element.css("position"))&&(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},destroy:function(){if(!this.element.data("draggable"))return;return this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy(),this},_mouseCapture:function(b){var c=this.options;return this.helper||c.disabled||a(b.target).is(".ui-resizable-handle")?!1:(this.handle=this._getHandle(b),this.handle?(c.iframeFix&&a(c.iframeFix===!0?"iframe":c.iframeFix).each(function(){a('
').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(a(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(b){var c=this.options;return this.helper=this._createHelper(b),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),a.ui.ddmanager&&(a.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(b),this.originalPageX=b.pageX,this.originalPageY=b.pageY,c.cursorAt&&this._adjustOffsetFromHelper(c.cursorAt),c.containment&&this._setContainment(),this._trigger("start",b)===!1?(this._clear(),!1):(this._cacheHelperProportions(),a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b),this._mouseDrag(b,!0),a.ui.ddmanager&&a.ui.ddmanager.dragStart(this,b),!0)},_mouseDrag:function(b,c){this.position=this._generatePosition(b),this.positionAbs=this._convertPositionTo("absolute");if(!c){var d=this._uiHash();if(this._trigger("drag",b,d)===!1)return this._mouseUp({}),!1;this.position=d.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";return a.ui.ddmanager&&a.ui.ddmanager.drag(this,b),!1},_mouseStop:function(b){var c=!1;a.ui.ddmanager&&!this.options.dropBehaviour&&(c=a.ui.ddmanager.drop(this,b)),this.dropped&&(c=this.dropped,this.dropped=!1);var d=this.element[0],e=!1;while(d&&(d=d.parentNode))d==document&&(e=!0);if(!e&&this.options.helper==="original")return!1;if(this.options.revert=="invalid"&&!c||this.options.revert=="valid"&&c||this.options.revert===!0||a.isFunction(this.options.revert)&&this.options.revert.call(this.element,c)){var f=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){f._trigger("stop",b)!==!1&&f._clear()})}else this._trigger("stop",b)!==!1&&this._clear();return!1},_mouseUp:function(b){return this.options.iframeFix===!0&&a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),a.ui.ddmanager&&a.ui.ddmanager.dragStop(this,b),a.ui.mouse.prototype._mouseUp.call(this,b)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(b){var c=!this.options.handle||!a(this.options.handle,this.element).length?!0:!1;return a(this.options.handle,this.element).find("*").andSelf().each(function(){this==b.target&&(c=!0)}),c},_createHelper:function(b){var c=this.options,d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[b])):c.helper=="clone"?this.element.clone().removeAttr("id"):this.element;return d.parents("body").length||d.appendTo(c.appendTo=="parent"?this.element[0].parentNode:c.appendTo),d[0]!=this.element[0]&&!/(fixed|absolute)/.test(d.css("position"))&&d.css("position","absolute"),d},_adjustOffsetFromHelper:function(b){typeof b=="string"&&(b=b.split(" ")),a.isArray(b)&&(b={left:+b[0],top:+b[1]||0}),"left"in b&&(this.offset.click.left=b.left+this.margins.left),"right"in b&&(this.offset.click.left=this.helperProportions.width-b.right+this.margins.left),"top"in b&&(this.offset.click.top=b.top+this.margins.top),"bottom"in b&&(this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])&&(b.left+=this.scrollParent.scrollLeft(),b.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)b={top:0,left:0};return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.element.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var b=this.options;b.containment=="parent"&&(b.containment=this.helper[0].parentNode);if(b.containment=="document"||b.containment=="window")this.containment=[b.containment=="document"?0:a(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,b.containment=="document"?0:a(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,(b.containment=="document"?0:a(window).scrollLeft())+a(b.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(b.containment=="document"?0:a(window).scrollTop())+(a(b.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(b.containment)&&b.containment.constructor!=Array){var c=a(b.containment),d=c[0];if(!d)return;var e=c.offset(),f=a(d).css("overflow")!="hidden";this.containment=[(parseInt(a(d).css("borderLeftWidth"),10)||0)+(parseInt(a(d).css("paddingLeft"),10)||0),(parseInt(a(d).css("borderTopWidth"),10)||0)+(parseInt(a(d).css("paddingTop"),10)||0),(f?Math.max(d.scrollWidth,d.offsetWidth):d.offsetWidth)-(parseInt(a(d).css("borderLeftWidth"),10)||0)-(parseInt(a(d).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(f?Math.max(d.scrollHeight,d.offsetHeight):d.offsetHeight)-(parseInt(a(d).css("borderTopWidth"),10)||0)-(parseInt(a(d).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=c}else b.containment.constructor==Array&&(this.containment=b.containment)},_convertPositionTo:function(b,c){c||(c=this.position);var d=b=="absolute"?1:-1,e=this.options,f=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())*d)}},_generatePosition:function(b){var c=this.options,d=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(d[0].tagName),f=b.pageX,g=b.pageY;if(this.originalPosition){var h;if(this.containment){if(this.relative_container){var i=this.relative_container.offset();h=[this.containment[0]+i.left,this.containment[1]+i.top,this.containment[2]+i.left,this.containment[3]+i.top]}else h=this.containment;b.pageX-this.offset.click.lefth[2]&&(f=h[2]+this.offset.click.left),b.pageY-this.offset.click.top>h[3]&&(g=h[3]+this.offset.click.top)}if(c.grid){var j=c.grid[1]?this.originalPageY+Math.round((g-this.originalPageY)/c.grid[1])*c.grid[1]:this.originalPageY;g=h?j-this.offset.click.toph[3]?j-this.offset.click.toph[2]?k-this.offset.click.left=0;k--){var l=d.snapElements[k].left,m=l+d.snapElements[k].width,n=d.snapElements[k].top,o=n+d.snapElements[k].height;if(!(l-f").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),e=document.activeElement;try{e.id}catch(f){e=document.body}return b.wrap(d),(b[0]===e||a.contains(b[0],e))&&a(e).focus(),d=b.parent(),b.css("position")=="static"?(d.css({position:"relative"}),b.css({position:"relative"})):(a.extend(c,{position:b.css("position"),zIndex:b.css("z-index")}),a.each(["top","left","bottom","right"],function(a,d){c[d]=b.css(d),isNaN(parseInt(c[d],10))&&(c[d]="auto")}),b.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),d.css(c).show()},removeWrapper:function(b){var c,d=document.activeElement;return b.parent().is(".ui-effects-wrapper")?(c=b.parent().replaceWith(b),(b[0]===d||a.contains(b[0],d))&&a(d).focus(),c):b},setTransition:function(b,c,d,e){return e=e||{},a.each(c,function(a,c){var f=b.cssUnit(c);f[0]>0&&(e[c]=f[0]*d+f[1])}),e}}),a.fn.extend({effect:function(b,c,d,e){var f=k.apply(this,arguments),g={options:f[1],duration:f[2],callback:f[3]},h=g.options.mode,i=a.effects[b];return a.fx.off||!i?h?this[h](g.duration,g.callback):this.each(function(){g.callback&&g.callback.call(this)}):i.call(this,g)},_show:a.fn.show,show:function(a){if(l(a))return this._show.apply(this,arguments);var b=k.apply(this,arguments);return b[1].mode="show",this.effect.apply(this,b)},_hide:a.fn.hide,hide:function(a){if(l(a))return this._hide.apply(this,arguments);var b=k.apply(this,arguments);return b[1].mode="hide",this.effect.apply(this,b)},__toggle:a.fn.toggle,toggle:function(b){if(l(b)||typeof b=="boolean"||a.isFunction(b))return this.__toggle.apply(this,arguments);var c=k.apply(this,arguments);return c[1].mode="toggle",this.effect.apply(this,c)},cssUnit:function(b){var c=this.css(b),d=[];return a.each(["em","px","%","pt"],function(a,b){c.indexOf(b)>0&&(d=[parseFloat(c),b])}),d}});var m={};a.each(["Quad","Cubic","Quart","Quint","Expo"],function(a,b){m[b]=function(b){return Math.pow(b,a+2)}}),a.extend(m,{Sine:function(a){return 1-Math.cos(a*Math.PI/2)},Circ:function(a){return 1-Math.sqrt(1-a*a)},Elastic:function(a){return a===0||a===1?a:-Math.pow(2,8*(a-1))*Math.sin(((a-1)*80-7.5)*Math.PI/15)},Back:function(a){return a*a*(3*a-2)},Bounce:function(a){var b,c=4;while(a<((b=Math.pow(2,--c))-1)/11);return 1/Math.pow(4,3-c)-7.5625*Math.pow((b*3-2)/22-a,2)}}),a.each(m,function(b,c){a.easing["easeIn"+b]=c,a.easing["easeOut"+b]=function(a){return 1-c(1-a)},a.easing["easeInOut"+b]=function(a){return a<.5?c(a*2)/2:c(a*-2+2)/-2+1}})}(jQuery);; \ No newline at end of file diff --git a/app/assets/javascripts/lib/typeahead.bundle.js b/app/assets/javascripts/lib/typeahead.bundle.js new file mode 100644 index 00000000..bb0c8aed --- /dev/null +++ b/app/assets/javascripts/lib/typeahead.bundle.js @@ -0,0 +1,2451 @@ +/*! + * typeahead.js 0.11.1 + * https://github.com/twitter/typeahead.js + * Copyright 2013-2015 Twitter, Inc. and other contributors; Licensed MIT + */ + +(function(root, factory) { + if (typeof define === "function" && define.amd) { + define("bloodhound", [ "jquery" ], function(a0) { + return root["Bloodhound"] = factory(a0); + }); + } else if (typeof exports === "object") { + module.exports = factory(require("jquery")); + } else { + root["Bloodhound"] = factory(jQuery); + } +})(this, function($) { + var _ = function() { + "use strict"; + return { + isMsie: function() { + return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; + }, + isBlankString: function(str) { + return !str || /^\s*$/.test(str); + }, + escapeRegExChars: function(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + }, + isString: function(obj) { + return typeof obj === "string"; + }, + isNumber: function(obj) { + return typeof obj === "number"; + }, + isArray: $.isArray, + isFunction: $.isFunction, + isObject: $.isPlainObject, + isUndefined: function(obj) { + return typeof obj === "undefined"; + }, + isElement: function(obj) { + return !!(obj && obj.nodeType === 1); + }, + isJQuery: function(obj) { + return obj instanceof $; + }, + toStr: function toStr(s) { + return _.isUndefined(s) || s === null ? "" : s + ""; + }, + bind: $.proxy, + each: function(collection, cb) { + $.each(collection, reverseArgs); + function reverseArgs(index, value) { + return cb(value, index); + } + }, + map: $.map, + filter: $.grep, + every: function(obj, test) { + var result = true; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (!(result = test.call(null, val, key, obj))) { + return false; + } + }); + return !!result; + }, + some: function(obj, test) { + var result = false; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (result = test.call(null, val, key, obj)) { + return false; + } + }); + return !!result; + }, + mixin: $.extend, + identity: function(x) { + return x; + }, + clone: function(obj) { + return $.extend(true, {}, obj); + }, + getIdGenerator: function() { + var counter = 0; + return function() { + return counter++; + }; + }, + templatify: function templatify(obj) { + return $.isFunction(obj) ? obj : template; + function template() { + return String(obj); + } + }, + defer: function(fn) { + setTimeout(fn, 0); + }, + debounce: function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments, later, callNow; + later = function() { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + } + }; + callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + } + return result; + }; + }, + throttle: function(func, wait) { + var context, args, timeout, result, previous, later; + previous = 0; + later = function() { + previous = new Date(); + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date(), remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }, + stringify: function(val) { + return _.isString(val) ? val : JSON.stringify(val); + }, + noop: function() {} + }; + }(); + var VERSION = "0.11.1"; + var tokenizers = function() { + "use strict"; + return { + nonword: nonword, + whitespace: whitespace, + obj: { + nonword: getObjTokenizer(nonword), + whitespace: getObjTokenizer(whitespace) + } + }; + function whitespace(str) { + str = _.toStr(str); + return str ? str.split(/\s+/) : []; + } + function nonword(str) { + str = _.toStr(str); + return str ? str.split(/\W+/) : []; + } + function getObjTokenizer(tokenizer) { + return function setKey(keys) { + keys = _.isArray(keys) ? keys : [].slice.call(arguments, 0); + return function tokenize(o) { + var tokens = []; + _.each(keys, function(k) { + tokens = tokens.concat(tokenizer(_.toStr(o[k]))); + }); + return tokens; + }; + }; + } + }(); + var LruCache = function() { + "use strict"; + function LruCache(maxSize) { + this.maxSize = _.isNumber(maxSize) ? maxSize : 100; + this.reset(); + if (this.maxSize <= 0) { + this.set = this.get = $.noop; + } + } + _.mixin(LruCache.prototype, { + set: function set(key, val) { + var tailItem = this.list.tail, node; + if (this.size >= this.maxSize) { + this.list.remove(tailItem); + delete this.hash[tailItem.key]; + this.size--; + } + if (node = this.hash[key]) { + node.val = val; + this.list.moveToFront(node); + } else { + node = new Node(key, val); + this.list.add(node); + this.hash[key] = node; + this.size++; + } + }, + get: function get(key) { + var node = this.hash[key]; + if (node) { + this.list.moveToFront(node); + return node.val; + } + }, + reset: function reset() { + this.size = 0; + this.hash = {}; + this.list = new List(); + } + }); + function List() { + this.head = this.tail = null; + } + _.mixin(List.prototype, { + add: function add(node) { + if (this.head) { + node.next = this.head; + this.head.prev = node; + } + this.head = node; + this.tail = this.tail || node; + }, + remove: function remove(node) { + node.prev ? node.prev.next = node.next : this.head = node.next; + node.next ? node.next.prev = node.prev : this.tail = node.prev; + }, + moveToFront: function(node) { + this.remove(node); + this.add(node); + } + }); + function Node(key, val) { + this.key = key; + this.val = val; + this.prev = this.next = null; + } + return LruCache; + }(); + var PersistentStorage = function() { + "use strict"; + var LOCAL_STORAGE; + try { + LOCAL_STORAGE = window.localStorage; + LOCAL_STORAGE.setItem("~~~", "!"); + LOCAL_STORAGE.removeItem("~~~"); + } catch (err) { + LOCAL_STORAGE = null; + } + function PersistentStorage(namespace, override) { + this.prefix = [ "__", namespace, "__" ].join(""); + this.ttlKey = "__ttl__"; + this.keyMatcher = new RegExp("^" + _.escapeRegExChars(this.prefix)); + this.ls = override || LOCAL_STORAGE; + !this.ls && this._noop(); + } + _.mixin(PersistentStorage.prototype, { + _prefix: function(key) { + return this.prefix + key; + }, + _ttlKey: function(key) { + return this._prefix(key) + this.ttlKey; + }, + _noop: function() { + this.get = this.set = this.remove = this.clear = this.isExpired = _.noop; + }, + _safeSet: function(key, val) { + try { + this.ls.setItem(key, val); + } catch (err) { + if (err.name === "QuotaExceededError") { + this.clear(); + this._noop(); + } + } + }, + get: function(key) { + if (this.isExpired(key)) { + this.remove(key); + } + return decode(this.ls.getItem(this._prefix(key))); + }, + set: function(key, val, ttl) { + if (_.isNumber(ttl)) { + this._safeSet(this._ttlKey(key), encode(now() + ttl)); + } else { + this.ls.removeItem(this._ttlKey(key)); + } + return this._safeSet(this._prefix(key), encode(val)); + }, + remove: function(key) { + this.ls.removeItem(this._ttlKey(key)); + this.ls.removeItem(this._prefix(key)); + return this; + }, + clear: function() { + var i, keys = gatherMatchingKeys(this.keyMatcher); + for (i = keys.length; i--; ) { + this.remove(keys[i]); + } + return this; + }, + isExpired: function(key) { + var ttl = decode(this.ls.getItem(this._ttlKey(key))); + return _.isNumber(ttl) && now() > ttl ? true : false; + } + }); + return PersistentStorage; + function now() { + return new Date().getTime(); + } + function encode(val) { + return JSON.stringify(_.isUndefined(val) ? null : val); + } + function decode(val) { + return $.parseJSON(val); + } + function gatherMatchingKeys(keyMatcher) { + var i, key, keys = [], len = LOCAL_STORAGE.length; + for (i = 0; i < len; i++) { + if ((key = LOCAL_STORAGE.key(i)).match(keyMatcher)) { + keys.push(key.replace(keyMatcher, "")); + } + } + return keys; + } + }(); + var Transport = function() { + "use strict"; + var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, sharedCache = new LruCache(10); + function Transport(o) { + o = o || {}; + this.cancelled = false; + this.lastReq = null; + this._send = o.transport; + this._get = o.limiter ? o.limiter(this._get) : this._get; + this._cache = o.cache === false ? new LruCache(0) : sharedCache; + } + Transport.setMaxPendingRequests = function setMaxPendingRequests(num) { + maxPendingRequests = num; + }; + Transport.resetCache = function resetCache() { + sharedCache.reset(); + }; + _.mixin(Transport.prototype, { + _fingerprint: function fingerprint(o) { + o = o || {}; + return o.url + o.type + $.param(o.data || {}); + }, + _get: function(o, cb) { + var that = this, fingerprint, jqXhr; + fingerprint = this._fingerprint(o); + if (this.cancelled || fingerprint !== this.lastReq) { + return; + } + if (jqXhr = pendingRequests[fingerprint]) { + jqXhr.done(done).fail(fail); + } else if (pendingRequestsCount < maxPendingRequests) { + pendingRequestsCount++; + pendingRequests[fingerprint] = this._send(o).done(done).fail(fail).always(always); + } else { + this.onDeckRequestArgs = [].slice.call(arguments, 0); + } + function done(resp) { + cb(null, resp); + that._cache.set(fingerprint, resp); + } + function fail() { + cb(true); + } + function always() { + pendingRequestsCount--; + delete pendingRequests[fingerprint]; + if (that.onDeckRequestArgs) { + that._get.apply(that, that.onDeckRequestArgs); + that.onDeckRequestArgs = null; + } + } + }, + get: function(o, cb) { + var resp, fingerprint; + cb = cb || $.noop; + o = _.isString(o) ? { + url: o + } : o || {}; + fingerprint = this._fingerprint(o); + this.cancelled = false; + this.lastReq = fingerprint; + if (resp = this._cache.get(fingerprint)) { + cb(null, resp); + } else { + this._get(o, cb); + } + }, + cancel: function() { + this.cancelled = true; + } + }); + return Transport; + }(); + var SearchIndex = window.SearchIndex = function() { + "use strict"; + var CHILDREN = "c", IDS = "i"; + function SearchIndex(o) { + o = o || {}; + if (!o.datumTokenizer || !o.queryTokenizer) { + $.error("datumTokenizer and queryTokenizer are both required"); + } + this.identify = o.identify || _.stringify; + this.datumTokenizer = o.datumTokenizer; + this.queryTokenizer = o.queryTokenizer; + this.reset(); + } + _.mixin(SearchIndex.prototype, { + bootstrap: function bootstrap(o) { + this.datums = o.datums; + this.trie = o.trie; + }, + add: function(data) { + var that = this; + data = _.isArray(data) ? data : [ data ]; + _.each(data, function(datum) { + var id, tokens; + that.datums[id = that.identify(datum)] = datum; + tokens = normalizeTokens(that.datumTokenizer(datum)); + _.each(tokens, function(token) { + var node, chars, ch; + node = that.trie; + chars = token.split(""); + while (ch = chars.shift()) { + node = node[CHILDREN][ch] || (node[CHILDREN][ch] = newNode()); + node[IDS].push(id); + } + }); + }); + }, + get: function get(ids) { + var that = this; + return _.map(ids, function(id) { + return that.datums[id]; + }); + }, + search: function search(query) { + var that = this, tokens, matches; + tokens = normalizeTokens(this.queryTokenizer(query)); + _.each(tokens, function(token) { + var node, chars, ch, ids; + if (matches && matches.length === 0) { + return false; + } + node = that.trie; + chars = token.split(""); + while (node && (ch = chars.shift())) { + node = node[CHILDREN][ch]; + } + if (node && chars.length === 0) { + ids = node[IDS].slice(0); + matches = matches ? getIntersection(matches, ids) : ids; + } else { + matches = []; + return false; + } + }); + return matches ? _.map(unique(matches), function(id) { + return that.datums[id]; + }) : []; + }, + all: function all() { + var values = []; + for (var key in this.datums) { + values.push(this.datums[key]); + } + return values; + }, + reset: function reset() { + this.datums = {}; + this.trie = newNode(); + }, + serialize: function serialize() { + return { + datums: this.datums, + trie: this.trie + }; + } + }); + return SearchIndex; + function normalizeTokens(tokens) { + tokens = _.filter(tokens, function(token) { + return !!token; + }); + tokens = _.map(tokens, function(token) { + return token.toLowerCase(); + }); + return tokens; + } + function newNode() { + var node = {}; + node[IDS] = []; + node[CHILDREN] = {}; + return node; + } + function unique(array) { + var seen = {}, uniques = []; + for (var i = 0, len = array.length; i < len; i++) { + if (!seen[array[i]]) { + seen[array[i]] = true; + uniques.push(array[i]); + } + } + return uniques; + } + function getIntersection(arrayA, arrayB) { + var ai = 0, bi = 0, intersection = []; + arrayA = arrayA.sort(); + arrayB = arrayB.sort(); + var lenArrayA = arrayA.length, lenArrayB = arrayB.length; + while (ai < lenArrayA && bi < lenArrayB) { + if (arrayA[ai] < arrayB[bi]) { + ai++; + } else if (arrayA[ai] > arrayB[bi]) { + bi++; + } else { + intersection.push(arrayA[ai]); + ai++; + bi++; + } + } + return intersection; + } + }(); + var Prefetch = function() { + "use strict"; + var keys; + keys = { + data: "data", + protocol: "protocol", + thumbprint: "thumbprint" + }; + function Prefetch(o) { + this.url = o.url; + this.ttl = o.ttl; + this.cache = o.cache; + this.prepare = o.prepare; + this.transform = o.transform; + this.transport = o.transport; + this.thumbprint = o.thumbprint; + this.storage = new PersistentStorage(o.cacheKey); + } + _.mixin(Prefetch.prototype, { + _settings: function settings() { + return { + url: this.url, + type: "GET", + dataType: "json" + }; + }, + store: function store(data) { + if (!this.cache) { + return; + } + this.storage.set(keys.data, data, this.ttl); + this.storage.set(keys.protocol, location.protocol, this.ttl); + this.storage.set(keys.thumbprint, this.thumbprint, this.ttl); + }, + fromCache: function fromCache() { + var stored = {}, isExpired; + if (!this.cache) { + return null; + } + stored.data = this.storage.get(keys.data); + stored.protocol = this.storage.get(keys.protocol); + stored.thumbprint = this.storage.get(keys.thumbprint); + isExpired = stored.thumbprint !== this.thumbprint || stored.protocol !== location.protocol; + return stored.data && !isExpired ? stored.data : null; + }, + fromNetwork: function(cb) { + var that = this, settings; + if (!cb) { + return; + } + settings = this.prepare(this._settings()); + this.transport(settings).fail(onError).done(onResponse); + function onError() { + cb(true); + } + function onResponse(resp) { + cb(null, that.transform(resp)); + } + }, + clear: function clear() { + this.storage.clear(); + return this; + } + }); + return Prefetch; + }(); + var Remote = function() { + "use strict"; + function Remote(o) { + this.url = o.url; + this.prepare = o.prepare; + this.transform = o.transform; + this.transport = new Transport({ + cache: o.cache, + limiter: o.limiter, + transport: o.transport + }); + } + _.mixin(Remote.prototype, { + _settings: function settings() { + return { + url: this.url, + type: "GET", + dataType: "json" + }; + }, + get: function get(query, cb) { + var that = this, settings; + if (!cb) { + return; + } + query = query || ""; + settings = this.prepare(query, this._settings()); + return this.transport.get(settings, onResponse); + function onResponse(err, resp) { + err ? cb([]) : cb(that.transform(resp)); + } + }, + cancelLastRequest: function cancelLastRequest() { + this.transport.cancel(); + } + }); + return Remote; + }(); + var oParser = function() { + "use strict"; + return function parse(o) { + var defaults, sorter; + defaults = { + initialize: true, + identify: _.stringify, + datumTokenizer: null, + queryTokenizer: null, + sufficient: 5, + sorter: null, + local: [], + prefetch: null, + remote: null + }; + o = _.mixin(defaults, o || {}); + !o.datumTokenizer && $.error("datumTokenizer is required"); + !o.queryTokenizer && $.error("queryTokenizer is required"); + sorter = o.sorter; + o.sorter = sorter ? function(x) { + return x.sort(sorter); + } : _.identity; + o.local = _.isFunction(o.local) ? o.local() : o.local; + o.prefetch = parsePrefetch(o.prefetch); + o.remote = parseRemote(o.remote); + return o; + }; + function parsePrefetch(o) { + var defaults; + if (!o) { + return null; + } + defaults = { + url: null, + ttl: 24 * 60 * 60 * 1e3, + cache: true, + cacheKey: null, + thumbprint: "", + prepare: _.identity, + transform: _.identity, + transport: null + }; + o = _.isString(o) ? { + url: o + } : o; + o = _.mixin(defaults, o); + !o.url && $.error("prefetch requires url to be set"); + o.transform = o.filter || o.transform; + o.cacheKey = o.cacheKey || o.url; + o.thumbprint = VERSION + o.thumbprint; + o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax; + return o; + } + function parseRemote(o) { + var defaults; + if (!o) { + return; + } + defaults = { + url: null, + cache: true, + prepare: null, + replace: null, + wildcard: null, + limiter: null, + rateLimitBy: "debounce", + rateLimitWait: 300, + transform: _.identity, + transport: null + }; + o = _.isString(o) ? { + url: o + } : o; + o = _.mixin(defaults, o); + !o.url && $.error("remote requires url to be set"); + o.transform = o.filter || o.transform; + o.prepare = toRemotePrepare(o); + o.limiter = toLimiter(o); + o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax; + delete o.replace; + delete o.wildcard; + delete o.rateLimitBy; + delete o.rateLimitWait; + return o; + } + function toRemotePrepare(o) { + var prepare, replace, wildcard; + prepare = o.prepare; + replace = o.replace; + wildcard = o.wildcard; + if (prepare) { + return prepare; + } + if (replace) { + prepare = prepareByReplace; + } else if (o.wildcard) { + prepare = prepareByWildcard; + } else { + prepare = idenityPrepare; + } + return prepare; + function prepareByReplace(query, settings) { + settings.url = replace(settings.url, query); + return settings; + } + function prepareByWildcard(query, settings) { + settings.url = settings.url.replace(wildcard, encodeURIComponent(query)); + return settings; + } + function idenityPrepare(query, settings) { + return settings; + } + } + function toLimiter(o) { + var limiter, method, wait; + limiter = o.limiter; + method = o.rateLimitBy; + wait = o.rateLimitWait; + if (!limiter) { + limiter = /^throttle$/i.test(method) ? throttle(wait) : debounce(wait); + } + return limiter; + function debounce(wait) { + return function debounce(fn) { + return _.debounce(fn, wait); + }; + } + function throttle(wait) { + return function throttle(fn) { + return _.throttle(fn, wait); + }; + } + } + function callbackToDeferred(fn) { + return function wrapper(o) { + var deferred = $.Deferred(); + fn(o, onSuccess, onError); + return deferred; + function onSuccess(resp) { + _.defer(function() { + deferred.resolve(resp); + }); + } + function onError(err) { + _.defer(function() { + deferred.reject(err); + }); + } + }; + } + }(); + var Bloodhound = function() { + "use strict"; + var old; + old = window && window.Bloodhound; + function Bloodhound(o) { + o = oParser(o); + this.sorter = o.sorter; + this.identify = o.identify; + this.sufficient = o.sufficient; + this.local = o.local; + this.remote = o.remote ? new Remote(o.remote) : null; + this.prefetch = o.prefetch ? new Prefetch(o.prefetch) : null; + this.index = new SearchIndex({ + identify: this.identify, + datumTokenizer: o.datumTokenizer, + queryTokenizer: o.queryTokenizer + }); + o.initialize !== false && this.initialize(); + } + Bloodhound.noConflict = function noConflict() { + window && (window.Bloodhound = old); + return Bloodhound; + }; + Bloodhound.tokenizers = tokenizers; + _.mixin(Bloodhound.prototype, { + __ttAdapter: function ttAdapter() { + var that = this; + return this.remote ? withAsync : withoutAsync; + function withAsync(query, sync, async) { + return that.search(query, sync, async); + } + function withoutAsync(query, sync) { + return that.search(query, sync); + } + }, + _loadPrefetch: function loadPrefetch() { + var that = this, deferred, serialized; + deferred = $.Deferred(); + if (!this.prefetch) { + deferred.resolve(); + } else if (serialized = this.prefetch.fromCache()) { + this.index.bootstrap(serialized); + deferred.resolve(); + } else { + this.prefetch.fromNetwork(done); + } + return deferred.promise(); + function done(err, data) { + if (err) { + return deferred.reject(); + } + that.add(data); + that.prefetch.store(that.index.serialize()); + deferred.resolve(); + } + }, + _initialize: function initialize() { + var that = this, deferred; + this.clear(); + (this.initPromise = this._loadPrefetch()).done(addLocalToIndex); + return this.initPromise; + function addLocalToIndex() { + that.add(that.local); + } + }, + initialize: function initialize(force) { + return !this.initPromise || force ? this._initialize() : this.initPromise; + }, + add: function add(data) { + this.index.add(data); + return this; + }, + get: function get(ids) { + ids = _.isArray(ids) ? ids : [].slice.call(arguments); + return this.index.get(ids); + }, + search: function search(query, sync, async) { + var that = this, local; + local = this.sorter(this.index.search(query)); + sync(this.remote ? local.slice() : local); + if (this.remote && local.length < this.sufficient) { + this.remote.get(query, processRemote); + } else if (this.remote) { + this.remote.cancelLastRequest(); + } + return this; + function processRemote(remote) { + var nonDuplicates = []; + _.each(remote, function(r) { + !_.some(local, function(l) { + return that.identify(r) === that.identify(l); + }) && nonDuplicates.push(r); + }); + async && async(nonDuplicates); + } + }, + all: function all() { + return this.index.all(); + }, + clear: function clear() { + this.index.reset(); + return this; + }, + clearPrefetchCache: function clearPrefetchCache() { + this.prefetch && this.prefetch.clear(); + return this; + }, + clearRemoteCache: function clearRemoteCache() { + Transport.resetCache(); + return this; + }, + ttAdapter: function ttAdapter() { + return this.__ttAdapter(); + } + }); + return Bloodhound; + }(); + return Bloodhound; +}); + +(function(root, factory) { + if (typeof define === "function" && define.amd) { + define("typeahead.js", [ "jquery" ], function(a0) { + return factory(a0); + }); + } else if (typeof exports === "object") { + module.exports = factory(require("jquery")); + } else { + factory(jQuery); + } +})(this, function($) { + var _ = function() { + "use strict"; + return { + isMsie: function() { + return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; + }, + isBlankString: function(str) { + return !str || /^\s*$/.test(str); + }, + escapeRegExChars: function(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + }, + isString: function(obj) { + return typeof obj === "string"; + }, + isNumber: function(obj) { + return typeof obj === "number"; + }, + isArray: $.isArray, + isFunction: $.isFunction, + isObject: $.isPlainObject, + isUndefined: function(obj) { + return typeof obj === "undefined"; + }, + isElement: function(obj) { + return !!(obj && obj.nodeType === 1); + }, + isJQuery: function(obj) { + return obj instanceof $; + }, + toStr: function toStr(s) { + return _.isUndefined(s) || s === null ? "" : s + ""; + }, + bind: $.proxy, + each: function(collection, cb) { + $.each(collection, reverseArgs); + function reverseArgs(index, value) { + return cb(value, index); + } + }, + map: $.map, + filter: $.grep, + every: function(obj, test) { + var result = true; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (!(result = test.call(null, val, key, obj))) { + return false; + } + }); + return !!result; + }, + some: function(obj, test) { + var result = false; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (result = test.call(null, val, key, obj)) { + return false; + } + }); + return !!result; + }, + mixin: $.extend, + identity: function(x) { + return x; + }, + clone: function(obj) { + return $.extend(true, {}, obj); + }, + getIdGenerator: function() { + var counter = 0; + return function() { + return counter++; + }; + }, + templatify: function templatify(obj) { + return $.isFunction(obj) ? obj : template; + function template() { + return String(obj); + } + }, + defer: function(fn) { + setTimeout(fn, 0); + }, + debounce: function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments, later, callNow; + later = function() { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + } + }; + callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + } + return result; + }; + }, + throttle: function(func, wait) { + var context, args, timeout, result, previous, later; + previous = 0; + later = function() { + previous = new Date(); + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date(), remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }, + stringify: function(val) { + return _.isString(val) ? val : JSON.stringify(val); + }, + noop: function() {} + }; + }(); + var WWW = function() { + "use strict"; + var defaultClassNames = { + wrapper: "twitter-typeahead", + input: "tt-input", + hint: "tt-hint", + menu: "tt-menu", + dataset: "tt-dataset", + suggestion: "tt-suggestion", + selectable: "tt-selectable", + empty: "tt-empty", + open: "tt-open", + cursor: "tt-cursor", + highlight: "tt-highlight" + }; + return build; + function build(o) { + var www, classes; + classes = _.mixin({}, defaultClassNames, o); + www = { + css: buildCss(), + classes: classes, + html: buildHtml(classes), + selectors: buildSelectors(classes) + }; + return { + css: www.css, + html: www.html, + classes: www.classes, + selectors: www.selectors, + mixin: function(o) { + _.mixin(o, www); + } + }; + } + function buildHtml(c) { + return { + wrapper: '', + menu: '
' + }; + } + function buildSelectors(classes) { + var selectors = {}; + _.each(classes, function(v, k) { + selectors[k] = "." + v; + }); + return selectors; + } + function buildCss() { + var css = { + wrapper: { + position: "relative", + display: "inline-block" + }, + hint: { + position: "absolute", + top: "0", + left: "0", + borderColor: "transparent", + boxShadow: "none", + opacity: "1" + }, + input: { + position: "relative", + verticalAlign: "top", + backgroundColor: "transparent" + }, + inputWithNoHint: { + position: "relative", + verticalAlign: "top" + }, + menu: { + position: "absolute", + top: "100%", + left: "0", + zIndex: "100", + display: "none" + }, + ltr: { + left: "0", + right: "auto" + }, + rtl: { + left: "auto", + right: " 0" + } + }; + if (_.isMsie()) { + _.mixin(css.input, { + backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" + }); + } + return css; + } + }(); + var EventBus = function() { + "use strict"; + var namespace, deprecationMap; + namespace = "typeahead:"; + deprecationMap = { + render: "rendered", + cursorchange: "cursorchanged", + select: "selected", + autocomplete: "autocompleted" + }; + function EventBus(o) { + if (!o || !o.el) { + $.error("EventBus initialized without el"); + } + this.$el = $(o.el); + } + _.mixin(EventBus.prototype, { + _trigger: function(type, args) { + var $e; + $e = $.Event(namespace + type); + (args = args || []).unshift($e); + this.$el.trigger.apply(this.$el, args); + return $e; + }, + before: function(type) { + var args, $e; + args = [].slice.call(arguments, 1); + $e = this._trigger("before" + type, args); + return $e.isDefaultPrevented(); + }, + trigger: function(type) { + var deprecatedType; + this._trigger(type, [].slice.call(arguments, 1)); + if (deprecatedType = deprecationMap[type]) { + this._trigger(deprecatedType, [].slice.call(arguments, 1)); + } + } + }); + return EventBus; + }(); + var EventEmitter = function() { + "use strict"; + var splitter = /\s+/, nextTick = getNextTick(); + return { + onSync: onSync, + onAsync: onAsync, + off: off, + trigger: trigger + }; + function on(method, types, cb, context) { + var type; + if (!cb) { + return this; + } + types = types.split(splitter); + cb = context ? bindContext(cb, context) : cb; + this._callbacks = this._callbacks || {}; + while (type = types.shift()) { + this._callbacks[type] = this._callbacks[type] || { + sync: [], + async: [] + }; + this._callbacks[type][method].push(cb); + } + return this; + } + function onAsync(types, cb, context) { + return on.call(this, "async", types, cb, context); + } + function onSync(types, cb, context) { + return on.call(this, "sync", types, cb, context); + } + function off(types) { + var type; + if (!this._callbacks) { + return this; + } + types = types.split(splitter); + while (type = types.shift()) { + delete this._callbacks[type]; + } + return this; + } + function trigger(types) { + var type, callbacks, args, syncFlush, asyncFlush; + if (!this._callbacks) { + return this; + } + types = types.split(splitter); + args = [].slice.call(arguments, 1); + while ((type = types.shift()) && (callbacks = this._callbacks[type])) { + syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args)); + asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args)); + syncFlush() && nextTick(asyncFlush); + } + return this; + } + function getFlush(callbacks, context, args) { + return flush; + function flush() { + var cancelled; + for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) { + cancelled = callbacks[i].apply(context, args) === false; + } + return !cancelled; + } + } + function getNextTick() { + var nextTickFn; + if (window.setImmediate) { + nextTickFn = function nextTickSetImmediate(fn) { + setImmediate(function() { + fn(); + }); + }; + } else { + nextTickFn = function nextTickSetTimeout(fn) { + setTimeout(function() { + fn(); + }, 0); + }; + } + return nextTickFn; + } + function bindContext(fn, context) { + return fn.bind ? fn.bind(context) : function() { + fn.apply(context, [].slice.call(arguments, 0)); + }; + } + }(); + var highlight = function(doc) { + "use strict"; + var defaults = { + node: null, + pattern: null, + tagName: "strong", + className: null, + wordsOnly: false, + caseSensitive: false + }; + return function hightlight(o) { + var regex; + o = _.mixin({}, defaults, o); + if (!o.node || !o.pattern) { + return; + } + o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ]; + regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly); + traverse(o.node, hightlightTextNode); + function hightlightTextNode(textNode) { + var match, patternNode, wrapperNode; + if (match = regex.exec(textNode.data)) { + wrapperNode = doc.createElement(o.tagName); + o.className && (wrapperNode.className = o.className); + patternNode = textNode.splitText(match.index); + patternNode.splitText(match[0].length); + wrapperNode.appendChild(patternNode.cloneNode(true)); + textNode.parentNode.replaceChild(wrapperNode, patternNode); + } + return !!match; + } + function traverse(el, hightlightTextNode) { + var childNode, TEXT_NODE_TYPE = 3; + for (var i = 0; i < el.childNodes.length; i++) { + childNode = el.childNodes[i]; + if (childNode.nodeType === TEXT_NODE_TYPE) { + i += hightlightTextNode(childNode) ? 1 : 0; + } else { + traverse(childNode, hightlightTextNode); + } + } + } + }; + function getRegex(patterns, caseSensitive, wordsOnly) { + var escapedPatterns = [], regexStr; + for (var i = 0, len = patterns.length; i < len; i++) { + escapedPatterns.push(_.escapeRegExChars(patterns[i])); + } + regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; + return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); + } + }(window.document); + var Input = function() { + "use strict"; + var specialKeyCodeMap; + specialKeyCodeMap = { + 9: "tab", + 27: "esc", + 37: "left", + 39: "right", + 13: "enter", + 38: "up", + 40: "down" + }; + function Input(o, www) { + o = o || {}; + if (!o.input) { + $.error("input is missing"); + } + www.mixin(this); + this.$hint = $(o.hint); + this.$input = $(o.input); + this.query = this.$input.val(); + this.queryWhenFocused = this.hasFocus() ? this.query : null; + this.$overflowHelper = buildOverflowHelper(this.$input); + this._checkLanguageDirection(); + if (this.$hint.length === 0) { + this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; + } + } + Input.normalizeQuery = function(str) { + return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " "); + }; + _.mixin(Input.prototype, EventEmitter, { + _onBlur: function onBlur() { + this.resetInputValue(); + this.trigger("blurred"); + }, + _onFocus: function onFocus() { + this.queryWhenFocused = this.query; + this.trigger("focused"); + }, + _onKeydown: function onKeydown($e) { + var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; + this._managePreventDefault(keyName, $e); + if (keyName && this._shouldTrigger(keyName, $e)) { + this.trigger(keyName + "Keyed", $e); + } + }, + _onInput: function onInput() { + this._setQuery(this.getInputValue()); + this.clearHintIfInvalid(); + this._checkLanguageDirection(); + }, + _managePreventDefault: function managePreventDefault(keyName, $e) { + var preventDefault; + switch (keyName) { + case "up": + case "down": + preventDefault = !withModifier($e); + break; + + default: + preventDefault = false; + } + preventDefault && $e.preventDefault(); + }, + _shouldTrigger: function shouldTrigger(keyName, $e) { + var trigger; + switch (keyName) { + case "tab": + trigger = !withModifier($e); + break; + + default: + trigger = true; + } + return trigger; + }, + _checkLanguageDirection: function checkLanguageDirection() { + var dir = (this.$input.css("direction") || "ltr").toLowerCase(); + if (this.dir !== dir) { + this.dir = dir; + this.$hint.attr("dir", dir); + this.trigger("langDirChanged", dir); + } + }, + _setQuery: function setQuery(val, silent) { + var areEquivalent, hasDifferentWhitespace; + areEquivalent = areQueriesEquivalent(val, this.query); + hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false; + this.query = val; + if (!silent && !areEquivalent) { + this.trigger("queryChanged", this.query); + } else if (!silent && hasDifferentWhitespace) { + this.trigger("whitespaceChanged", this.query); + } + }, + bind: function() { + var that = this, onBlur, onFocus, onKeydown, onInput; + onBlur = _.bind(this._onBlur, this); + onFocus = _.bind(this._onFocus, this); + onKeydown = _.bind(this._onKeydown, this); + onInput = _.bind(this._onInput, this); + this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); + if (!_.isMsie() || _.isMsie() > 9) { + this.$input.on("input.tt", onInput); + } else { + this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { + if (specialKeyCodeMap[$e.which || $e.keyCode]) { + return; + } + _.defer(_.bind(that._onInput, that, $e)); + }); + } + return this; + }, + focus: function focus() { + this.$input.focus(); + }, + blur: function blur() { + this.$input.blur(); + }, + getLangDir: function getLangDir() { + return this.dir; + }, + getQuery: function getQuery() { + return this.query || ""; + }, + setQuery: function setQuery(val, silent) { + this.setInputValue(val); + this._setQuery(val, silent); + }, + hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() { + return this.query !== this.queryWhenFocused; + }, + getInputValue: function getInputValue() { + return this.$input.val(); + }, + setInputValue: function setInputValue(value) { + this.$input.val(value); + this.clearHintIfInvalid(); + this._checkLanguageDirection(); + }, + resetInputValue: function resetInputValue() { + this.setInputValue(this.query); + }, + getHint: function getHint() { + return this.$hint.val(); + }, + setHint: function setHint(value) { + this.$hint.val(value); + }, + clearHint: function clearHint() { + this.setHint(""); + }, + clearHintIfInvalid: function clearHintIfInvalid() { + var val, hint, valIsPrefixOfHint, isValid; + val = this.getInputValue(); + hint = this.getHint(); + valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; + isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow(); + !isValid && this.clearHint(); + }, + hasFocus: function hasFocus() { + return this.$input.is(":focus"); + }, + hasOverflow: function hasOverflow() { + var constraint = this.$input.width() - 2; + this.$overflowHelper.text(this.getInputValue()); + return this.$overflowHelper.width() >= constraint; + }, + isCursorAtEnd: function() { + var valueLength, selectionStart, range; + valueLength = this.$input.val().length; + selectionStart = this.$input[0].selectionStart; + if (_.isNumber(selectionStart)) { + return selectionStart === valueLength; + } else if (document.selection) { + range = document.selection.createRange(); + range.moveStart("character", -valueLength); + return valueLength === range.text.length; + } + return true; + }, + destroy: function destroy() { + this.$hint.off(".tt"); + this.$input.off(".tt"); + this.$overflowHelper.remove(); + this.$hint = this.$input = this.$overflowHelper = $("
"); + } + }); + return Input; + function buildOverflowHelper($input) { + return $('').css({ + position: "absolute", + visibility: "hidden", + whiteSpace: "pre", + fontFamily: $input.css("font-family"), + fontSize: $input.css("font-size"), + fontStyle: $input.css("font-style"), + fontVariant: $input.css("font-variant"), + fontWeight: $input.css("font-weight"), + wordSpacing: $input.css("word-spacing"), + letterSpacing: $input.css("letter-spacing"), + textIndent: $input.css("text-indent"), + textRendering: $input.css("text-rendering"), + textTransform: $input.css("text-transform") + }).insertAfter($input); + } + function areQueriesEquivalent(a, b) { + return Input.normalizeQuery(a) === Input.normalizeQuery(b); + } + function withModifier($e) { + return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; + } + }(); + var Dataset = function() { + "use strict"; + var keys, nameGenerator; + keys = { + val: "tt-selectable-display", + obj: "tt-selectable-object" + }; + nameGenerator = _.getIdGenerator(); + function Dataset(o, www) { + o = o || {}; + o.templates = o.templates || {}; + o.templates.notFound = o.templates.notFound || o.templates.empty; + if (!o.source) { + $.error("missing source"); + } + if (!o.node) { + $.error("missing node"); + } + if (o.name && !isValidName(o.name)) { + $.error("invalid dataset name: " + o.name); + } + www.mixin(this); + this.highlight = !!o.highlight; + this.name = o.name || nameGenerator(); + this.limit = o.limit || 5; + this.displayFn = getDisplayFn(o.display || o.displayKey); + this.templates = getTemplates(o.templates, this.displayFn); + this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source; + this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async; + this._resetLastSuggestion(); + this.$el = $(o.node).addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name); + } + Dataset.extractData = function extractData(el) { + var $el = $(el); + if ($el.data(keys.obj)) { + return { + val: $el.data(keys.val) || "", + obj: $el.data(keys.obj) || null + }; + } + return null; + }; + _.mixin(Dataset.prototype, EventEmitter, { + _overwrite: function overwrite(query, suggestions) { + suggestions = suggestions || []; + if (suggestions.length) { + this._renderSuggestions(query, suggestions); + } else if (this.async && this.templates.pending) { + this._renderPending(query); + } else if (!this.async && this.templates.notFound) { + this._renderNotFound(query); + } else { + this._empty(); + } + this.trigger("rendered", this.name, suggestions, false); + }, + _append: function append(query, suggestions) { + suggestions = suggestions || []; + if (suggestions.length && this.$lastSuggestion.length) { + this._appendSuggestions(query, suggestions); + } else if (suggestions.length) { + this._renderSuggestions(query, suggestions); + } else if (!this.$lastSuggestion.length && this.templates.notFound) { + this._renderNotFound(query); + } + this.trigger("rendered", this.name, suggestions, true); + }, + _renderSuggestions: function renderSuggestions(query, suggestions) { + var $fragment; + $fragment = this._getSuggestionsFragment(query, suggestions); + this.$lastSuggestion = $fragment.children().last(); + this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions)); + }, + _appendSuggestions: function appendSuggestions(query, suggestions) { + var $fragment, $lastSuggestion; + $fragment = this._getSuggestionsFragment(query, suggestions); + $lastSuggestion = $fragment.children().last(); + this.$lastSuggestion.after($fragment); + this.$lastSuggestion = $lastSuggestion; + }, + _renderPending: function renderPending(query) { + var template = this.templates.pending; + this._resetLastSuggestion(); + template && this.$el.html(template({ + query: query, + dataset: this.name + })); + }, + _renderNotFound: function renderNotFound(query) { + var template = this.templates.notFound; + this._resetLastSuggestion(); + template && this.$el.html(template({ + query: query, + dataset: this.name + })); + }, + _empty: function empty() { + this.$el.empty(); + this._resetLastSuggestion(); + }, + _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) { + var that = this, fragment; + fragment = document.createDocumentFragment(); + _.each(suggestions, function getSuggestionNode(suggestion) { + var $el, context; + context = that._injectQuery(query, suggestion); + $el = $(that.templates.suggestion(context)).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable); + fragment.appendChild($el[0]); + }); + this.highlight && highlight({ + className: this.classes.highlight, + node: fragment, + pattern: query + }); + return $(fragment); + }, + _getFooter: function getFooter(query, suggestions) { + return this.templates.footer ? this.templates.footer({ + query: query, + suggestions: suggestions, + dataset: this.name + }) : null; + }, + _getHeader: function getHeader(query, suggestions) { + return this.templates.header ? this.templates.header({ + query: query, + suggestions: suggestions, + dataset: this.name + }) : null; + }, + _resetLastSuggestion: function resetLastSuggestion() { + this.$lastSuggestion = $(); + }, + _injectQuery: function injectQuery(query, obj) { + return _.isObject(obj) ? _.mixin({ + _query: query + }, obj) : obj; + }, + update: function update(query) { + var that = this, canceled = false, syncCalled = false, rendered = 0; + this.cancel(); + this.cancel = function cancel() { + canceled = true; + that.cancel = $.noop; + that.async && that.trigger("asyncCanceled", query); + }; + this.source(query, sync, async); + !syncCalled && sync([]); + function sync(suggestions) { + if (syncCalled) { + return; + } + syncCalled = true; + suggestions = (suggestions || []).slice(0, that.limit); + rendered = suggestions.length; + that._overwrite(query, suggestions); + if (rendered < that.limit && that.async) { + that.trigger("asyncRequested", query); + } + } + function async(suggestions) { + suggestions = suggestions || []; + if (!canceled && rendered < that.limit) { + that.cancel = $.noop; + rendered += suggestions.length; + that._append(query, suggestions.slice(0, that.limit - rendered)); + that.async && that.trigger("asyncReceived", query); + } + } + }, + cancel: $.noop, + clear: function clear() { + this._empty(); + this.cancel(); + this.trigger("cleared"); + }, + isEmpty: function isEmpty() { + return this.$el.is(":empty"); + }, + destroy: function destroy() { + this.$el = $("
"); + } + }); + return Dataset; + function getDisplayFn(display) { + display = display || _.stringify; + return _.isFunction(display) ? display : displayFn; + function displayFn(obj) { + return obj[display]; + } + } + function getTemplates(templates, displayFn) { + return { + notFound: templates.notFound && _.templatify(templates.notFound), + pending: templates.pending && _.templatify(templates.pending), + header: templates.header && _.templatify(templates.header), + footer: templates.footer && _.templatify(templates.footer), + suggestion: templates.suggestion || suggestionTemplate + }; + function suggestionTemplate(context) { + return $("
").text(displayFn(context)); + } + } + function isValidName(str) { + return /^[_a-zA-Z0-9-]+$/.test(str); + } + }(); + var Menu = function() { + "use strict"; + function Menu(o, www) { + var that = this; + o = o || {}; + if (!o.node) { + $.error("node is required"); + } + www.mixin(this); + this.$node = $(o.node); + this.query = null; + this.datasets = _.map(o.datasets, initializeDataset); + function initializeDataset(oDataset) { + var node = that.$node.find(oDataset.node).first(); + oDataset.node = node.length ? node : $("
").appendTo(that.$node); + return new Dataset(oDataset, www); + } + } + _.mixin(Menu.prototype, EventEmitter, { + _onSelectableClick: function onSelectableClick($e) { + this.trigger("selectableClicked", $($e.currentTarget)); + }, + _onRendered: function onRendered(type, dataset, suggestions, async) { + this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); + this.trigger("datasetRendered", dataset, suggestions, async); + }, + _onCleared: function onCleared() { + this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); + this.trigger("datasetCleared"); + }, + _propagate: function propagate() { + this.trigger.apply(this, arguments); + }, + _allDatasetsEmpty: function allDatasetsEmpty() { + return _.every(this.datasets, isDatasetEmpty); + function isDatasetEmpty(dataset) { + return dataset.isEmpty(); + } + }, + _getSelectables: function getSelectables() { + return this.$node.find(this.selectors.selectable); + }, + _removeCursor: function _removeCursor() { + var $selectable = this.getActiveSelectable(); + $selectable && $selectable.removeClass(this.classes.cursor); + }, + _ensureVisible: function ensureVisible($el) { + var elTop, elBottom, nodeScrollTop, nodeHeight; + elTop = $el.position().top; + elBottom = elTop + $el.outerHeight(true); + nodeScrollTop = this.$node.scrollTop(); + nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10); + if (elTop < 0) { + this.$node.scrollTop(nodeScrollTop + elTop); + } else if (nodeHeight < elBottom) { + this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight)); + } + }, + bind: function() { + var that = this, onSelectableClick; + onSelectableClick = _.bind(this._onSelectableClick, this); + this.$node.on("click.tt", this.selectors.selectable, onSelectableClick); + _.each(this.datasets, function(dataset) { + dataset.onSync("asyncRequested", that._propagate, that).onSync("asyncCanceled", that._propagate, that).onSync("asyncReceived", that._propagate, that).onSync("rendered", that._onRendered, that).onSync("cleared", that._onCleared, that); + }); + return this; + }, + isOpen: function isOpen() { + return this.$node.hasClass(this.classes.open); + }, + open: function open() { + this.$node.addClass(this.classes.open); + }, + close: function close() { + this.$node.removeClass(this.classes.open); + this._removeCursor(); + }, + setLanguageDirection: function setLanguageDirection(dir) { + this.$node.attr("dir", dir); + }, + selectableRelativeToCursor: function selectableRelativeToCursor(delta) { + var $selectables, $oldCursor, oldIndex, newIndex; + $oldCursor = this.getActiveSelectable(); + $selectables = this._getSelectables(); + oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1; + newIndex = oldIndex + delta; + newIndex = (newIndex + 1) % ($selectables.length + 1) - 1; + newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex; + return newIndex === -1 ? null : $selectables.eq(newIndex); + }, + setCursor: function setCursor($selectable) { + this._removeCursor(); + if ($selectable = $selectable && $selectable.first()) { + $selectable.addClass(this.classes.cursor); + this._ensureVisible($selectable); + } + }, + getSelectableData: function getSelectableData($el) { + return $el && $el.length ? Dataset.extractData($el) : null; + }, + getActiveSelectable: function getActiveSelectable() { + var $selectable = this._getSelectables().filter(this.selectors.cursor).first(); + return $selectable.length ? $selectable : null; + }, + getTopSelectable: function getTopSelectable() { + var $selectable = this._getSelectables().first(); + return $selectable.length ? $selectable : null; + }, + update: function update(query) { + var isValidUpdate = query !== this.query; + if (isValidUpdate) { + this.query = query; + _.each(this.datasets, updateDataset); + } + return isValidUpdate; + function updateDataset(dataset) { + dataset.update(query); + } + }, + empty: function empty() { + _.each(this.datasets, clearDataset); + this.query = null; + this.$node.addClass(this.classes.empty); + function clearDataset(dataset) { + dataset.clear(); + } + }, + destroy: function destroy() { + this.$node.off(".tt"); + this.$node = $("
"); + _.each(this.datasets, destroyDataset); + function destroyDataset(dataset) { + dataset.destroy(); + } + } + }); + return Menu; + }(); + var DefaultMenu = function() { + "use strict"; + var s = Menu.prototype; + function DefaultMenu() { + Menu.apply(this, [].slice.call(arguments, 0)); + } + _.mixin(DefaultMenu.prototype, Menu.prototype, { + open: function open() { + !this._allDatasetsEmpty() && this._show(); + return s.open.apply(this, [].slice.call(arguments, 0)); + }, + close: function close() { + this._hide(); + return s.close.apply(this, [].slice.call(arguments, 0)); + }, + _onRendered: function onRendered() { + if (this._allDatasetsEmpty()) { + this._hide(); + } else { + this.isOpen() && this._show(); + } + return s._onRendered.apply(this, [].slice.call(arguments, 0)); + }, + _onCleared: function onCleared() { + if (this._allDatasetsEmpty()) { + this._hide(); + } else { + this.isOpen() && this._show(); + } + return s._onCleared.apply(this, [].slice.call(arguments, 0)); + }, + setLanguageDirection: function setLanguageDirection(dir) { + this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl); + return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0)); + }, + _hide: function hide() { + this.$node.hide(); + }, + _show: function show() { + this.$node.css("display", "block"); + } + }); + return DefaultMenu; + }(); + var Typeahead = function() { + "use strict"; + function Typeahead(o, www) { + var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged; + o = o || {}; + if (!o.input) { + $.error("missing input"); + } + if (!o.menu) { + $.error("missing menu"); + } + if (!o.eventBus) { + $.error("missing event bus"); + } + www.mixin(this); + this.eventBus = o.eventBus; + this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; + this.input = o.input; + this.menu = o.menu; + this.enabled = true; + this.active = false; + this.input.hasFocus() && this.activate(); + this.dir = this.input.getLangDir(); + this._hacks(); + this.menu.bind().onSync("selectableClicked", this._onSelectableClicked, this).onSync("asyncRequested", this._onAsyncRequested, this).onSync("asyncCanceled", this._onAsyncCanceled, this).onSync("asyncReceived", this._onAsyncReceived, this).onSync("datasetRendered", this._onDatasetRendered, this).onSync("datasetCleared", this._onDatasetCleared, this); + onFocused = c(this, "activate", "open", "_onFocused"); + onBlurred = c(this, "deactivate", "_onBlurred"); + onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed"); + onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed"); + onEscKeyed = c(this, "isActive", "_onEscKeyed"); + onUpKeyed = c(this, "isActive", "open", "_onUpKeyed"); + onDownKeyed = c(this, "isActive", "open", "_onDownKeyed"); + onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed"); + onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed"); + onQueryChanged = c(this, "_openIfActive", "_onQueryChanged"); + onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged"); + this.input.bind().onSync("focused", onFocused, this).onSync("blurred", onBlurred, this).onSync("enterKeyed", onEnterKeyed, this).onSync("tabKeyed", onTabKeyed, this).onSync("escKeyed", onEscKeyed, this).onSync("upKeyed", onUpKeyed, this).onSync("downKeyed", onDownKeyed, this).onSync("leftKeyed", onLeftKeyed, this).onSync("rightKeyed", onRightKeyed, this).onSync("queryChanged", onQueryChanged, this).onSync("whitespaceChanged", onWhitespaceChanged, this).onSync("langDirChanged", this._onLangDirChanged, this); + } + _.mixin(Typeahead.prototype, { + _hacks: function hacks() { + var $input, $menu; + $input = this.input.$input || $("
"); + $menu = this.menu.$node || $("
"); + $input.on("blur.tt", function($e) { + var active, isActive, hasActive; + active = document.activeElement; + isActive = $menu.is(active); + hasActive = $menu.has(active).length > 0; + if (_.isMsie() && (isActive || hasActive)) { + $e.preventDefault(); + $e.stopImmediatePropagation(); + _.defer(function() { + $input.focus(); + }); + } + }); + $menu.on("mousedown.tt", function($e) { + $e.preventDefault(); + }); + }, + _onSelectableClicked: function onSelectableClicked(type, $el) { + this.select($el); + }, + _onDatasetCleared: function onDatasetCleared() { + this._updateHint(); + }, + _onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) { + this._updateHint(); + this.eventBus.trigger("render", suggestions, async, dataset); + }, + _onAsyncRequested: function onAsyncRequested(type, dataset, query) { + this.eventBus.trigger("asyncrequest", query, dataset); + }, + _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) { + this.eventBus.trigger("asynccancel", query, dataset); + }, + _onAsyncReceived: function onAsyncReceived(type, dataset, query) { + this.eventBus.trigger("asyncreceive", query, dataset); + }, + _onFocused: function onFocused() { + this._minLengthMet() && this.menu.update(this.input.getQuery()); + }, + _onBlurred: function onBlurred() { + if (this.input.hasQueryChangedSinceLastFocus()) { + this.eventBus.trigger("change", this.input.getQuery()); + } + }, + _onEnterKeyed: function onEnterKeyed(type, $e) { + var $selectable; + if ($selectable = this.menu.getActiveSelectable()) { + this.select($selectable) && $e.preventDefault(); + } + }, + _onTabKeyed: function onTabKeyed(type, $e) { + var $selectable; + if ($selectable = this.menu.getActiveSelectable()) { + this.select($selectable) && $e.preventDefault(); + } else if ($selectable = this.menu.getTopSelectable()) { + this.autocomplete($selectable) && $e.preventDefault(); + } + }, + _onEscKeyed: function onEscKeyed() { + this.close(); + }, + _onUpKeyed: function onUpKeyed() { + this.moveCursor(-1); + }, + _onDownKeyed: function onDownKeyed() { + this.moveCursor(+1); + }, + _onLeftKeyed: function onLeftKeyed() { + if (this.dir === "rtl" && this.input.isCursorAtEnd()) { + this.autocomplete(this.menu.getTopSelectable()); + } + }, + _onRightKeyed: function onRightKeyed() { + if (this.dir === "ltr" && this.input.isCursorAtEnd()) { + this.autocomplete(this.menu.getTopSelectable()); + } + }, + _onQueryChanged: function onQueryChanged(e, query) { + this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty(); + }, + _onWhitespaceChanged: function onWhitespaceChanged() { + this._updateHint(); + }, + _onLangDirChanged: function onLangDirChanged(e, dir) { + if (this.dir !== dir) { + this.dir = dir; + this.menu.setLanguageDirection(dir); + } + }, + _openIfActive: function openIfActive() { + this.isActive() && this.open(); + }, + _minLengthMet: function minLengthMet(query) { + query = _.isString(query) ? query : this.input.getQuery() || ""; + return query.length >= this.minLength; + }, + _updateHint: function updateHint() { + var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match; + $selectable = this.menu.getTopSelectable(); + data = this.menu.getSelectableData($selectable); + val = this.input.getInputValue(); + if (data && !_.isBlankString(val) && !this.input.hasOverflow()) { + query = Input.normalizeQuery(val); + escapedQuery = _.escapeRegExChars(query); + frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i"); + match = frontMatchRegEx.exec(data.val); + match && this.input.setHint(val + match[1]); + } else { + this.input.clearHint(); + } + }, + isEnabled: function isEnabled() { + return this.enabled; + }, + enable: function enable() { + this.enabled = true; + }, + disable: function disable() { + this.enabled = false; + }, + isActive: function isActive() { + return this.active; + }, + activate: function activate() { + if (this.isActive()) { + return true; + } else if (!this.isEnabled() || this.eventBus.before("active")) { + return false; + } else { + this.active = true; + this.eventBus.trigger("active"); + return true; + } + }, + deactivate: function deactivate() { + if (!this.isActive()) { + return true; + } else if (this.eventBus.before("idle")) { + return false; + } else { + this.active = false; + this.close(); + this.eventBus.trigger("idle"); + return true; + } + }, + isOpen: function isOpen() { + return this.menu.isOpen(); + }, + open: function open() { + if (!this.isOpen() && !this.eventBus.before("open")) { + this.menu.open(); + this._updateHint(); + this.eventBus.trigger("open"); + } + return this.isOpen(); + }, + close: function close() { + if (this.isOpen() && !this.eventBus.before("close")) { + this.menu.close(); + this.input.clearHint(); + this.input.resetInputValue(); + this.eventBus.trigger("close"); + } + return !this.isOpen(); + }, + setVal: function setVal(val) { + this.input.setQuery(_.toStr(val)); + }, + getVal: function getVal() { + return this.input.getQuery(); + }, + select: function select($selectable) { + var data = this.menu.getSelectableData($selectable); + if (data && !this.eventBus.before("select", data.obj)) { + this.input.setQuery(data.val, true); + this.eventBus.trigger("select", data.obj); + this.close(); + return true; + } + return false; + }, + autocomplete: function autocomplete($selectable) { + var query, data, isValid; + query = this.input.getQuery(); + data = this.menu.getSelectableData($selectable); + isValid = data && query !== data.val; + if (isValid && !this.eventBus.before("autocomplete", data.obj)) { + this.input.setQuery(data.val); + this.eventBus.trigger("autocomplete", data.obj); + return true; + } + return false; + }, + moveCursor: function moveCursor(delta) { + var query, $candidate, data, payload, cancelMove; + query = this.input.getQuery(); + $candidate = this.menu.selectableRelativeToCursor(delta); + data = this.menu.getSelectableData($candidate); + payload = data ? data.obj : null; + cancelMove = this._minLengthMet() && this.menu.update(query); + if (!cancelMove && !this.eventBus.before("cursorchange", payload)) { + this.menu.setCursor($candidate); + if (data) { + this.input.setInputValue(data.val); + } else { + this.input.resetInputValue(); + this._updateHint(); + } + this.eventBus.trigger("cursorchange", payload); + return true; + } + return false; + }, + destroy: function destroy() { + this.input.destroy(); + this.menu.destroy(); + } + }); + return Typeahead; + function c(ctx) { + var methods = [].slice.call(arguments, 1); + return function() { + var args = [].slice.call(arguments); + _.each(methods, function(method) { + return ctx[method].apply(ctx, args); + }); + }; + } + }(); + (function() { + "use strict"; + var old, keys, methods; + old = $.fn.typeahead; + keys = { + www: "tt-www", + attrs: "tt-attrs", + typeahead: "tt-typeahead" + }; + methods = { + initialize: function initialize(o, datasets) { + var www; + datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); + o = o || {}; + www = WWW(o.classNames); + return this.each(attach); + function attach() { + var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, MenuConstructor; + _.each(datasets, function(d) { + d.highlight = !!o.highlight; + }); + $input = $(this); + $wrapper = $(www.html.wrapper); + $hint = $elOrNull(o.hint); + $menu = $elOrNull(o.menu); + defaultHint = o.hint !== false && !$hint; + defaultMenu = o.menu !== false && !$menu; + defaultHint && ($hint = buildHintFromInput($input, www)); + defaultMenu && ($menu = $(www.html.menu).css(www.css.menu)); + $hint && $hint.val(""); + $input = prepInput($input, www); + if (defaultHint || defaultMenu) { + $wrapper.css(www.css.wrapper); + $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint); + $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null); + } + MenuConstructor = defaultMenu ? DefaultMenu : Menu; + eventBus = new EventBus({ + el: $input + }); + input = new Input({ + hint: $hint, + input: $input + }, www); + menu = new MenuConstructor({ + node: $menu, + datasets: datasets + }, www); + typeahead = new Typeahead({ + input: input, + menu: menu, + eventBus: eventBus, + minLength: o.minLength + }, www); + $input.data(keys.www, www); + $input.data(keys.typeahead, typeahead); + } + }, + isEnabled: function isEnabled() { + var enabled; + ttEach(this.first(), function(t) { + enabled = t.isEnabled(); + }); + return enabled; + }, + enable: function enable() { + ttEach(this, function(t) { + t.enable(); + }); + return this; + }, + disable: function disable() { + ttEach(this, function(t) { + t.disable(); + }); + return this; + }, + isActive: function isActive() { + var active; + ttEach(this.first(), function(t) { + active = t.isActive(); + }); + return active; + }, + activate: function activate() { + ttEach(this, function(t) { + t.activate(); + }); + return this; + }, + deactivate: function deactivate() { + ttEach(this, function(t) { + t.deactivate(); + }); + return this; + }, + isOpen: function isOpen() { + var open; + ttEach(this.first(), function(t) { + open = t.isOpen(); + }); + return open; + }, + open: function open() { + ttEach(this, function(t) { + t.open(); + }); + return this; + }, + close: function close() { + ttEach(this, function(t) { + t.close(); + }); + return this; + }, + select: function select(el) { + var success = false, $el = $(el); + ttEach(this.first(), function(t) { + success = t.select($el); + }); + return success; + }, + autocomplete: function autocomplete(el) { + var success = false, $el = $(el); + ttEach(this.first(), function(t) { + success = t.autocomplete($el); + }); + return success; + }, + moveCursor: function moveCursoe(delta) { + var success = false; + ttEach(this.first(), function(t) { + success = t.moveCursor(delta); + }); + return success; + }, + val: function val(newVal) { + var query; + if (!arguments.length) { + ttEach(this.first(), function(t) { + query = t.getVal(); + }); + return query; + } else { + ttEach(this, function(t) { + t.setVal(newVal); + }); + return this; + } + }, + destroy: function destroy() { + ttEach(this, function(typeahead, $input) { + revert($input); + typeahead.destroy(); + }); + return this; + } + }; + $.fn.typeahead = function(method) { + if (methods[method]) { + return methods[method].apply(this, [].slice.call(arguments, 1)); + } else { + return methods.initialize.apply(this, arguments); + } + }; + $.fn.typeahead.noConflict = function noConflict() { + $.fn.typeahead = old; + return this; + }; + function ttEach($els, fn) { + $els.each(function() { + var $input = $(this), typeahead; + (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input); + }); + } + function buildHintFromInput($input, www) { + return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop("readonly", true).removeAttr("id name placeholder required").attr({ + autocomplete: "off", + spellcheck: "false", + tabindex: -1 + }); + } + function prepInput($input, www) { + $input.data(keys.attrs, { + dir: $input.attr("dir"), + autocomplete: $input.attr("autocomplete"), + spellcheck: $input.attr("spellcheck"), + style: $input.attr("style") + }); + $input.addClass(www.classes.input).attr({ + autocomplete: "off", + spellcheck: false + }); + try { + !$input.attr("dir") && $input.attr("dir", "auto"); + } catch (e) {} + return $input; + } + function getBackgroundStyles($el) { + return { + backgroundAttachment: $el.css("background-attachment"), + backgroundClip: $el.css("background-clip"), + backgroundColor: $el.css("background-color"), + backgroundImage: $el.css("background-image"), + backgroundOrigin: $el.css("background-origin"), + backgroundPosition: $el.css("background-position"), + backgroundRepeat: $el.css("background-repeat"), + backgroundSize: $el.css("background-size") + }; + } + function revert($input) { + var www, $wrapper; + www = $input.data(keys.www); + $wrapper = $input.parent().filter(www.selectors.wrapper); + _.each($input.data(keys.attrs), function(val, key) { + _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); + }); + $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input); + if ($wrapper.length) { + $input.detach().insertAfter($wrapper); + $wrapper.remove(); + } + } + function $elOrNull(obj) { + var isValid, $el; + isValid = _.isJQuery(obj) || _.isElement(obj); + $el = isValid ? $(obj).first() : []; + return $el.length ? $el : null; + } + })(); +}); \ No newline at end of file diff --git a/app/assets/javascripts/lib/typeahead.js b/app/assets/javascripts/lib/typeahead.js deleted file mode 100644 index 3a413d68..00000000 --- a/app/assets/javascripts/lib/typeahead.js +++ /dev/null @@ -1,1165 +0,0 @@ -/*! - * typeahead.js 0.9.3 - * https://github.com/twitter/typeahead - * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT - */ - -(function($) { - var VERSION = "0.9.3"; - var utils = { - isMsie: function() { - var match = /(msie) ([\w.]+)/i.exec(navigator.userAgent); - return match ? parseInt(match[2], 10) : false; - }, - isBlankString: function(str) { - return !str || /^\s*$/.test(str); - }, - escapeRegExChars: function(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); - }, - isString: function(obj) { - return typeof obj === "string"; - }, - isNumber: function(obj) { - return typeof obj === "number"; - }, - isArray: $.isArray, - isFunction: $.isFunction, - isObject: $.isPlainObject, - isUndefined: function(obj) { - return typeof obj === "undefined"; - }, - bind: $.proxy, - bindAll: function(obj) { - var val; - for (var key in obj) { - $.isFunction(val = obj[key]) && (obj[key] = $.proxy(val, obj)); - } - }, - indexOf: function(haystack, needle) { - for (var i = 0; i < haystack.length; i++) { - if (haystack[i] === needle) { - return i; - } - } - return -1; - }, - each: $.each, - map: $.map, - filter: $.grep, - every: function(obj, test) { - var result = true; - if (!obj) { - return result; - } - $.each(obj, function(key, val) { - if (!(result = test.call(null, val, key, obj))) { - return false; - } - }); - return !!result; - }, - some: function(obj, test) { - var result = false; - if (!obj) { - return result; - } - $.each(obj, function(key, val) { - if (result = test.call(null, val, key, obj)) { - return false; - } - }); - return !!result; - }, - mixin: $.extend, - getUniqueId: function() { - var counter = 0; - return function() { - return counter++; - }; - }(), - defer: function(fn) { - setTimeout(fn, 0); - }, - debounce: function(func, wait, immediate) { - var timeout, result; - return function() { - var context = this, args = arguments, later, callNow; - later = function() { - timeout = null; - if (!immediate) { - result = func.apply(context, args); - } - }; - callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) { - result = func.apply(context, args); - } - return result; - }; - }, - throttle: function(func, wait) { - var context, args, timeout, result, previous, later; - previous = 0; - later = function() { - previous = new Date(); - timeout = null; - result = func.apply(context, args); - }; - return function() { - var now = new Date(), remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0) { - clearTimeout(timeout); - timeout = null; - previous = now; - result = func.apply(context, args); - } else if (!timeout) { - timeout = setTimeout(later, remaining); - } - return result; - }; - }, - tokenizeQuery: function(str) { - return $.trim(str).toLowerCase().split(/[\s]+/); - }, - tokenizeText: function(str) { - return $.trim(str).toLowerCase().split(/[\s\-_]+/); - }, - getProtocol: function() { - return location.protocol; - }, - noop: function() {} - }; - var EventTarget = function() { - var eventSplitter = /\s+/; - return { - on: function(events, callback) { - var event; - if (!callback) { - return this; - } - this._callbacks = this._callbacks || {}; - events = events.split(eventSplitter); - while (event = events.shift()) { - this._callbacks[event] = this._callbacks[event] || []; - this._callbacks[event].push(callback); - } - return this; - }, - trigger: function(events, data) { - var event, callbacks; - if (!this._callbacks) { - return this; - } - events = events.split(eventSplitter); - while (event = events.shift()) { - if (callbacks = this._callbacks[event]) { - for (var i = 0; i < callbacks.length; i += 1) { - callbacks[i].call(this, { - type: event, - data: data - }); - } - } - } - return this; - } - }; - }(); - var EventBus = function() { - var namespace = "typeahead:"; - function EventBus(o) { - if (!o || !o.el) { - $.error("EventBus initialized without el"); - } - this.$el = $(o.el); - } - utils.mixin(EventBus.prototype, { - trigger: function(type) { - var args = [].slice.call(arguments, 1); - this.$el.trigger(namespace + type, args); - } - }); - return EventBus; - }(); - var PersistentStorage = function() { - var ls, methods; - try { - ls = window.localStorage; - ls.setItem("~~~", "!"); - ls.removeItem("~~~"); - } catch (err) { - ls = null; - } - function PersistentStorage(namespace) { - this.prefix = [ "__", namespace, "__" ].join(""); - this.ttlKey = "__ttl__"; - this.keyMatcher = new RegExp("^" + this.prefix); - } - if (ls && window.JSON) { - methods = { - _prefix: function(key) { - return this.prefix + key; - }, - _ttlKey: function(key) { - return this._prefix(key) + this.ttlKey; - }, - get: function(key) { - if (this.isExpired(key)) { - this.remove(key); - } - return decode(ls.getItem(this._prefix(key))); - }, - set: function(key, val, ttl) { - if (utils.isNumber(ttl)) { - ls.setItem(this._ttlKey(key), encode(now() + ttl)); - } else { - ls.removeItem(this._ttlKey(key)); - } - return ls.setItem(this._prefix(key), encode(val)); - }, - remove: function(key) { - ls.removeItem(this._ttlKey(key)); - ls.removeItem(this._prefix(key)); - return this; - }, - clear: function() { - var i, key, keys = [], len = ls.length; - for (i = 0; i < len; i++) { - if ((key = ls.key(i)).match(this.keyMatcher)) { - keys.push(key.replace(this.keyMatcher, "")); - } - } - for (i = keys.length; i--; ) { - this.remove(keys[i]); - } - return this; - }, - isExpired: function(key) { - var ttl = decode(ls.getItem(this._ttlKey(key))); - return utils.isNumber(ttl) && now() > ttl ? true : false; - } - }; - } else { - methods = { - get: utils.noop, - set: utils.noop, - remove: utils.noop, - clear: utils.noop, - isExpired: utils.noop - }; - } - utils.mixin(PersistentStorage.prototype, methods); - return PersistentStorage; - function now() { - return new Date().getTime(); - } - function encode(val) { - return JSON.stringify(utils.isUndefined(val) ? null : val); - } - function decode(val) { - return JSON.parse(val); - } - }(); - var RequestCache = function() { - function RequestCache(o) { - utils.bindAll(this); - o = o || {}; - this.sizeLimit = o.sizeLimit || 10; - this.cache = {}; - this.cachedKeysByAge = []; - } - utils.mixin(RequestCache.prototype, { - get: function(url) { - return this.cache[url]; - }, - set: function(url, resp) { - var requestToEvict; - if (this.cachedKeysByAge.length === this.sizeLimit) { - requestToEvict = this.cachedKeysByAge.shift(); - delete this.cache[requestToEvict]; - } - this.cache[url] = resp; - this.cachedKeysByAge.push(url); - } - }); - return RequestCache; - }(); - var Transport = function() { - var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests, requestCache; - function Transport(o) { - utils.bindAll(this); - o = utils.isString(o) ? { - url: o - } : o; - requestCache = requestCache || new RequestCache(); - maxPendingRequests = utils.isNumber(o.maxParallelRequests) ? o.maxParallelRequests : maxPendingRequests || 6; - this.url = o.url; - this.wildcard = o.wildcard || "%QUERY"; - this.filter = o.filter; - this.replace = o.replace; - this.ajaxSettings = { - type: "get", - cache: o.cache, - timeout: o.timeout, - dataType: o.dataType || "json", - beforeSend: o.beforeSend - }; - this._get = (/^throttle$/i.test(o.rateLimitFn) ? utils.throttle : utils.debounce)(this._get, o.rateLimitWait || 300); - } - utils.mixin(Transport.prototype, { - _get: function(url, cb) { - var that = this; - if (belowPendingRequestsThreshold()) { - this._sendRequest(url).done(done); - } else { - this.onDeckRequestArgs = [].slice.call(arguments, 0); - } - function done(resp) { - var data = that.filter ? that.filter(resp) : resp; - cb && cb(data); - requestCache.set(url, resp); - } - }, - _sendRequest: function(url) { - var that = this, jqXhr = pendingRequests[url]; - if (!jqXhr) { - incrementPendingRequests(); - jqXhr = pendingRequests[url] = $.ajax(url, this.ajaxSettings).always(always); - } - return jqXhr; - function always() { - decrementPendingRequests(); - pendingRequests[url] = null; - if (that.onDeckRequestArgs) { - that._get.apply(that, that.onDeckRequestArgs); - that.onDeckRequestArgs = null; - } - } - }, - get: function(query, cb) { - var that = this, encodedQuery = encodeURIComponent(query || ""), url, resp; - cb = cb || utils.noop; - url = this.replace ? this.replace(this.url, encodedQuery) : this.url.replace(this.wildcard, encodedQuery); - if (resp = requestCache.get(url)) { - utils.defer(function() { - cb(that.filter ? that.filter(resp) : resp); - }); - } else { - this._get(url, cb); - } - return !!resp; - } - }); - return Transport; - function incrementPendingRequests() { - pendingRequestsCount++; - } - function decrementPendingRequests() { - pendingRequestsCount--; - } - function belowPendingRequestsThreshold() { - return pendingRequestsCount < maxPendingRequests; - } - }(); - var Dataset = function() { - var keys = { - thumbprint: "thumbprint", - protocol: "protocol", - itemHash: "itemHash", - adjacencyList: "adjacencyList" - }; - function Dataset(o) { - utils.bindAll(this); - if (utils.isString(o.template) && !o.engine) { - $.error("no template engine specified"); - } - if (!o.local && !o.prefetch && !o.remote) { - $.error("one of local, prefetch, or remote is required"); - } - this.name = o.name || utils.getUniqueId(); - this.limit = o.limit || 5; - this.minLength = o.minLength || 1; - this.header = o.header; - this.footer = o.footer; - this.valueKey = o.valueKey || "value"; - this.template = compileTemplate(o.template, o.engine, this.valueKey); - this.local = o.local; - this.prefetch = o.prefetch; - this.remote = o.remote; - this.itemHash = {}; - this.adjacencyList = {}; - this.storage = o.name ? new PersistentStorage(o.name) : null; - } - utils.mixin(Dataset.prototype, { - _processLocalData: function(data) { - this._mergeProcessedData(this._processData(data)); - }, - _loadPrefetchData: function(o) { - var that = this, thumbprint = VERSION + (o.thumbprint || ""), storedThumbprint, storedProtocol, storedItemHash, storedAdjacencyList, isExpired, deferred; - if (this.storage) { - storedThumbprint = this.storage.get(keys.thumbprint); - storedProtocol = this.storage.get(keys.protocol); - storedItemHash = this.storage.get(keys.itemHash); - storedAdjacencyList = this.storage.get(keys.adjacencyList); - } - isExpired = storedThumbprint !== thumbprint || storedProtocol !== utils.getProtocol(); - o = utils.isString(o) ? { - url: o - } : o; - o.ttl = utils.isNumber(o.ttl) ? o.ttl : 24 * 60 * 60 * 1e3; - if (storedItemHash && storedAdjacencyList && !isExpired) { - this._mergeProcessedData({ - itemHash: storedItemHash, - adjacencyList: storedAdjacencyList - }); - deferred = $.Deferred().resolve(); - } else { - deferred = $.getJSON(o.url).done(processPrefetchData); - } - return deferred; - function processPrefetchData(data) { - var filteredData = o.filter ? o.filter(data) : data, processedData = that._processData(filteredData), itemHash = processedData.itemHash, adjacencyList = processedData.adjacencyList; - if (that.storage) { - that.storage.set(keys.itemHash, itemHash, o.ttl); - that.storage.set(keys.adjacencyList, adjacencyList, o.ttl); - that.storage.set(keys.thumbprint, thumbprint, o.ttl); - that.storage.set(keys.protocol, utils.getProtocol(), o.ttl); - } - that._mergeProcessedData(processedData); - } - }, - _transformDatum: function(datum) { - var value = utils.isString(datum) ? datum : datum[this.valueKey], tokens = datum.tokens || utils.tokenizeText(value), item = { - value: value, - tokens: tokens - }; - if (utils.isString(datum)) { - item.datum = {}; - item.datum[this.valueKey] = datum; - } else { - item.datum = datum; - } - item.tokens = utils.filter(item.tokens, function(token) { - return !utils.isBlankString(token); - }); - item.tokens = utils.map(item.tokens, function(token) { - return token.toLowerCase(); - }); - return item; - }, - _processData: function(data) { - var that = this, itemHash = {}, adjacencyList = {}; - utils.each(data, function(i, datum) { - var item = that._transformDatum(datum), id = utils.getUniqueId(item.value); - itemHash[id] = item; - utils.each(item.tokens, function(i, token) { - var character = token.charAt(0), adjacency = adjacencyList[character] || (adjacencyList[character] = [ id ]); - !~utils.indexOf(adjacency, id) && adjacency.push(id); - }); - }); - return { - itemHash: itemHash, - adjacencyList: adjacencyList - }; - }, - _mergeProcessedData: function(processedData) { - var that = this; - utils.mixin(this.itemHash, processedData.itemHash); - utils.each(processedData.adjacencyList, function(character, adjacency) { - var masterAdjacency = that.adjacencyList[character]; - that.adjacencyList[character] = masterAdjacency ? masterAdjacency.concat(adjacency) : adjacency; - }); - }, - _getLocalSuggestions: function(terms) { - var that = this, firstChars = [], lists = [], shortestList, suggestions = []; - utils.each(terms, function(i, term) { - var firstChar = term.charAt(0); - !~utils.indexOf(firstChars, firstChar) && firstChars.push(firstChar); - }); - utils.each(firstChars, function(i, firstChar) { - var list = that.adjacencyList[firstChar]; - if (!list) { - return false; - } - lists.push(list); - if (!shortestList || list.length < shortestList.length) { - shortestList = list; - } - }); - if (lists.length < firstChars.length) { - return []; - } - utils.each(shortestList, function(i, id) { - var item = that.itemHash[id], isCandidate, isMatch; - isCandidate = utils.every(lists, function(list) { - return ~utils.indexOf(list, id); - }); - isMatch = isCandidate && utils.every(terms, function(term) { - return utils.some(item.tokens, function(token) { - return token.indexOf(term) === 0; - }); - }); - isMatch && suggestions.push(item); - }); - return suggestions; - }, - initialize: function() { - var deferred; - this.local && this._processLocalData(this.local); - this.transport = this.remote ? new Transport(this.remote) : null; - deferred = this.prefetch ? this._loadPrefetchData(this.prefetch) : $.Deferred().resolve(); - this.local = this.prefetch = this.remote = null; - this.initialize = function() { - return deferred; - }; - return deferred; - }, - getSuggestions: function(query, cb) { - var that = this, terms, suggestions, cacheHit = false; - if (query.length < this.minLength) { - return; - } - terms = utils.tokenizeQuery(query); - suggestions = this._getLocalSuggestions(terms).slice(0, this.limit); - if (suggestions.length < this.limit && this.transport) { - cacheHit = this.transport.get(query, processRemoteData); - } - !cacheHit && cb && cb(suggestions); - function processRemoteData(data) { - suggestions = suggestions.slice(0); - utils.each(data, function(i, datum) { - var item = that._transformDatum(datum), isDuplicate; - isDuplicate = utils.some(suggestions, function(suggestion) { - //return item.value === suggestion.value; - return false; - }); - !isDuplicate && suggestions.push(item); - return suggestions.length < that.limit; - }); - cb && cb(suggestions); - } - } - }); - return Dataset; - function compileTemplate(template, engine, valueKey) { - var renderFn, compiledTemplate; - if (utils.isFunction(template)) { - renderFn = template; - } else if (utils.isString(template)) { - compiledTemplate = engine.compile(template); - renderFn = utils.bind(compiledTemplate.render, compiledTemplate); - } else { - renderFn = function(context) { - return "

" + context[valueKey] + "

"; - }; - } - return renderFn; - } - }(); - var InputView = function() { - function InputView(o) { - var that = this; - utils.bindAll(this); - this.specialKeyCodeMap = { - // START METAMAPS CODE - //9: "tab", - // END METAMAPS CODE - 27: "esc", - 37: "left", - 39: "right", - 13: "enter", - 38: "up", - 40: "down" - }; - this.$hint = $(o.hint); - this.$input = $(o.input).on("blur.tt", this._handleBlur).on("focus.tt", this._handleFocus).on("keydown.tt", this._handleSpecialKeyEvent); - if (!utils.isMsie()) { - this.$input.on("input.tt", this._compareQueryToInputValue); - } else { - this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { - if (that.specialKeyCodeMap[$e.which || $e.keyCode]) { - return; - } - utils.defer(that._compareQueryToInputValue); - }); - } - this.query = this.$input.val(); - this.$overflowHelper = buildOverflowHelper(this.$input); - } - utils.mixin(InputView.prototype, EventTarget, { - _handleFocus: function() { - this.trigger("focused"); - }, - _handleBlur: function() { - this.trigger("blured"); - }, - _handleSpecialKeyEvent: function($e) { - var keyName = this.specialKeyCodeMap[$e.which || $e.keyCode]; - keyName && this.trigger(keyName + "Keyed", $e); - }, - _compareQueryToInputValue: function() { - var inputValue = this.getInputValue(), isSameQuery = compareQueries(this.query, inputValue), isSameQueryExceptWhitespace = isSameQuery ? this.query.length !== inputValue.length : false; - if (isSameQueryExceptWhitespace) { - this.trigger("whitespaceChanged", { - value: this.query - }); - } else if (!isSameQuery) { - this.trigger("queryChanged", { - value: this.query = inputValue - }); - } - }, - destroy: function() { - this.$hint.off(".tt"); - this.$input.off(".tt"); - this.$hint = this.$input = this.$overflowHelper = null; - }, - focus: function() { - this.$input.focus(); - }, - blur: function() { - this.$input.blur(); - }, - getQuery: function() { - return this.query; - }, - setQuery: function(query) { - this.query = query; - }, - getInputValue: function() { - return this.$input.val(); - }, - setInputValue: function(value, silent) { - this.$input.val(value); - !silent && this._compareQueryToInputValue(); - }, - getHintValue: function() { - return this.$hint.val(); - }, - setHintValue: function(value) { - this.$hint.val(value); - }, - getLanguageDirection: function() { - return (this.$input.css("direction") || "ltr").toLowerCase(); - }, - isOverflow: function() { - this.$overflowHelper.text(this.getInputValue()); - return this.$overflowHelper.width() > this.$input.width(); - }, - isCursorAtEnd: function() { - var valueLength = this.$input.val().length, selectionStart = this.$input[0].selectionStart, range; - if (utils.isNumber(selectionStart)) { - return selectionStart === valueLength; - } else if (document.selection) { - range = document.selection.createRange(); - range.moveStart("character", -valueLength); - return valueLength === range.text.length; - } - return true; - } - }); - return InputView; - function buildOverflowHelper($input) { - return $("").css({ - position: "absolute", - left: "-9999px", - visibility: "hidden", - whiteSpace: "nowrap", - fontFamily: $input.css("font-family"), - fontSize: $input.css("font-size"), - fontStyle: $input.css("font-style"), - fontVariant: $input.css("font-variant"), - fontWeight: $input.css("font-weight"), - wordSpacing: $input.css("word-spacing"), - letterSpacing: $input.css("letter-spacing"), - textIndent: $input.css("text-indent"), - textRendering: $input.css("text-rendering"), - textTransform: $input.css("text-transform") - }).insertAfter($input); - } - function compareQueries(a, b) { - a = (a || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); - b = (b || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); - return a === b; - } - }(); - var DropdownView = function() { - var html = { - suggestionsList: '' - }, css = { - suggestionsList: { - display: "block" - }, - suggestion: { - whiteSpace: "nowrap", - cursor: "pointer" - }, - suggestionChild: { - whiteSpace: "normal" - } - }; - function DropdownView(o) { - utils.bindAll(this); - this.isOpen = false; - this.isEmpty = true; - this.isMouseOverDropdown = false; - this.$menu = $(o.menu).on("mouseenter.tt", this._handleMouseenter).on("mouseleave.tt", this._handleMouseleave).on("click.tt", ".tt-suggestion", this._handleSelection).on("mouseover.tt", ".tt-suggestion", this._handleMouseover); - } - utils.mixin(DropdownView.prototype, EventTarget, { - _handleMouseenter: function() { - this.isMouseOverDropdown = true; - }, - _handleMouseleave: function() { - this.isMouseOverDropdown = false; - - // START METAMAPS CODE - this._getSuggestions().removeClass("tt-is-under-cursor"); - this._getSuggestions().removeClass("tt-is-under-mouse-cursor"); - // END METAMAPS CODE - }, - _handleMouseover: function($e) { - var $suggestion = $($e.currentTarget); - this._getSuggestions().removeClass("tt-is-under-cursor"); - // START METAMAPS CODE - this._getSuggestions().removeClass("tt-is-under-mouse-cursor"); - $suggestion.addClass("tt-is-under-mouse-cursor"); - // ORIGINAL CODE $suggestion.addClass("tt-is-under-cursor"); - }, - _handleSelection: function($e) { - var $suggestion = $($e.currentTarget); - this.trigger("suggestionSelected", extractSuggestion($suggestion)); - }, - _show: function() { - this.$menu.css("display", "block"); - }, - _hide: function() { - this.$menu.hide(); - }, - _moveCursor: function(increment) { - var $suggestions, $cur, nextIndex, $underCursor; - if (!this.isVisible()) { - return; - } - $suggestions = this._getSuggestions(); - $cur = $suggestions.filter(".tt-is-under-cursor"); - $cur.removeClass("tt-is-under-cursor"); - nextIndex = $suggestions.index($cur) + increment; - nextIndex = (nextIndex + 1) % ($suggestions.length + 1) - 1; - if (nextIndex === -1) { - this.trigger("cursorRemoved"); - return; - } else if (nextIndex < -1) { - nextIndex = $suggestions.length - 1; - } - $underCursor = $suggestions.eq(nextIndex).addClass("tt-is-under-cursor"); - this._ensureVisibility($underCursor); - this.trigger("cursorMoved", extractSuggestion($underCursor)); - }, - _getSuggestions: function() { - return this.$menu.find(".tt-suggestions > .tt-suggestion"); - }, - _ensureVisibility: function($el) { - var menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10), menuScrollTop = this.$menu.scrollTop(), elTop = $el.position().top, elBottom = elTop + $el.outerHeight(true); - if (elTop < 0) { - this.$menu.scrollTop(menuScrollTop + elTop); - } else if (menuHeight < elBottom) { - this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); - } - }, - destroy: function() { - this.$menu.off(".tt"); - this.$menu = null; - }, - isVisible: function() { - return this.isOpen && !this.isEmpty; - }, - closeUnlessMouseIsOverDropdown: function() { - if (!this.isMouseOverDropdown) { - this.close(); - } - }, - close: function() { - if (this.isOpen) { - this.isOpen = false; - this.isMouseOverDropdown = false; - this._hide(); - this.$menu.find(".tt-suggestions > .tt-suggestion").removeClass("tt-is-under-cursor"); - this.trigger("closed"); - } - }, - open: function() { - if (!this.isOpen) { - this.isOpen = true; - !this.isEmpty && this._show(); - this.trigger("opened"); - } - }, - setLanguageDirection: function(dir) { - var ltrCss = { - left: "0", - right: "auto" - }, rtlCss = { - left: "auto", - right: " 0" - }; - dir === "ltr" ? this.$menu.css(ltrCss) : this.$menu.css(rtlCss); - }, - moveCursorUp: function() { - this._moveCursor(-1); - }, - moveCursorDown: function() { - this._moveCursor(+1); - }, - getSuggestionUnderCursor: function() { - var $suggestion = this._getSuggestions().filter(".tt-is-under-cursor").first(); - return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; - }, - getFirstSuggestion: function() { - var $suggestion = this._getSuggestions().first(); - return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; - }, - renderSuggestions: function(dataset, suggestions) { - var datasetClassName = "tt-dataset-" + dataset.name, wrapper = '
%body
', compiledHtml, $suggestionsList, $dataset = this.$menu.find("." + datasetClassName), elBuilder, fragment, $el; - if ($dataset.length === 0) { - $suggestionsList = $(html.suggestionsList).css(css.suggestionsList); - $dataset = $("
").addClass(datasetClassName).append(dataset.header).append($suggestionsList).append(dataset.footer).appendTo(this.$menu); - } - if (suggestions.length > 0) { - this.isEmpty = false; - this.isOpen && this._show(); - elBuilder = document.createElement("div"); - fragment = document.createDocumentFragment(); - utils.each(suggestions, function(i, suggestion) { - suggestion.dataset = dataset.name; - compiledHtml = dataset.template(suggestion.datum); - elBuilder.innerHTML = wrapper.replace("%body", compiledHtml); - $el = $(elBuilder.firstChild).css(css.suggestion).data("suggestion", suggestion); - $el.children().each(function() { - $(this).css(css.suggestionChild); - }); - fragment.appendChild($el[0]); - }); - $dataset.show().find(".tt-suggestions").html(fragment); - } else { - this.clearSuggestions(dataset.name); - } - this.trigger("suggestionsRendered"); - }, - clearSuggestions: function(datasetName) { - var $datasets = datasetName ? this.$menu.find(".tt-dataset-" + datasetName) : this.$menu.find('[class^="tt-dataset-"]'), $suggestions = $datasets.find(".tt-suggestions"); - $datasets.hide(); - $suggestions.empty(); - if (this._getSuggestions().length === 0) { - this.isEmpty = true; - this._hide(); - } - } - }); - return DropdownView; - function extractSuggestion($el) { - return $el.data("suggestion"); - } - }(); - var TypeaheadView = function() { - var html = { - wrapper: '', - hint: '', - dropdown: '' - }, css = { - wrapper: { - position: "relative", - display: "inline-block" - }, - hint: { - position: "absolute", - top: "0", - left: "0", - borderColor: "transparent", - boxShadow: "none" - }, - query: { - position: "relative", - verticalAlign: "top", - backgroundColor: "transparent" - }, - dropdown: { - position: "absolute", - top: "100%", - left: "0", - zIndex: "100", - display: "none" - } - }; - if (utils.isMsie()) { - utils.mixin(css.query, { - backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" - }); - } - if (utils.isMsie() && utils.isMsie() <= 7) { - utils.mixin(css.wrapper, { - display: "inline", - zoom: "1" - }); - utils.mixin(css.query, { - marginTop: "-1px" - }); - } - function TypeaheadView(o) { - var $menu, $input, $hint; - utils.bindAll(this); - this.$node = buildDomStructure(o.input); - this.datasets = o.datasets; - this.dir = null; - this.eventBus = o.eventBus; - $menu = this.$node.find(".tt-dropdown-menu"); - $input = this.$node.find(".tt-query"); - $hint = this.$node.find(".tt-hint"); - this.dropdownView = new DropdownView({ - menu: $menu - }).on("suggestionSelected", this._handleSelection).on("cursorMoved", this._clearHint).on("cursorMoved", this._setInputValueToSuggestionUnderCursor).on("cursorRemoved", this._setInputValueToQuery).on("cursorRemoved", this._updateHint).on("suggestionsRendered", this._updateHint).on("opened", this._updateHint).on("closed", this._clearHint).on("opened closed", this._propagateEvent); - // START METAMAPS CODE - this.dropdownView.on('suggestionsRendered', this._suggestionsRendered); - // END METAMAPS CODE - - this.inputView = new InputView({ - input: $input, - hint: $hint - }).on("focused", this._openDropdown).on("blured", this._closeDropdown).on("blured", this._setInputValueToQuery).on("enterKeyed tabKeyed", this._handleSelection).on("queryChanged", this._clearHint).on("queryChanged", this._clearSuggestions).on("queryChanged", this._getSuggestions).on("whitespaceChanged", this._updateHint).on("queryChanged whitespaceChanged", this._openDropdown).on("queryChanged whitespaceChanged", this._setLanguageDirection).on("escKeyed", this._closeDropdown).on("escKeyed", this._setInputValueToQuery).on("tabKeyed upKeyed downKeyed", this._managePreventDefault).on("upKeyed downKeyed", this._moveDropdownCursor).on("upKeyed downKeyed", this._openDropdown).on("tabKeyed leftKeyed rightKeyed", this._autocomplete); - // START METAMAPS CODE - this.inputView.on('queryChanged', this._queryChanged); - // END METAMAPS CODE - } - utils.mixin(TypeaheadView.prototype, EventTarget, { - _managePreventDefault: function(e) { - var $e = e.data, hint, inputValue, preventDefault = false; - switch (e.type) { - case "tabKeyed": - hint = this.inputView.getHintValue(); - inputValue = this.inputView.getInputValue(); - preventDefault = hint && hint !== inputValue; - break; - - case "upKeyed": - case "downKeyed": - preventDefault = !$e.shiftKey && !$e.ctrlKey && !$e.metaKey; - break; - } - preventDefault && $e.preventDefault(); - }, - _setLanguageDirection: function() { - var dir = this.inputView.getLanguageDirection(); - if (dir !== this.dir) { - this.dir = dir; - this.$node.css("direction", dir); - this.dropdownView.setLanguageDirection(dir); - } - }, - // START METAMAPS CODE - _suggestionsRendered: function() { - this.eventBus.trigger('suggestionsRendered'); - }, - _queryChanged: function() { - this.eventBus.trigger('queryChanged'); - }, - // END METAMAPS CODE - _updateHint: function() { - var suggestion = this.dropdownView.getFirstSuggestion(), hint = suggestion ? suggestion.value : null, dropdownIsVisible = this.dropdownView.isVisible(), inputHasOverflow = this.inputView.isOverflow(), inputValue, query, escapedQuery, beginsWithQuery, match; - if (hint && dropdownIsVisible && !inputHasOverflow) { - inputValue = this.inputView.getInputValue(); - query = inputValue.replace(/\s{2,}/g, " ").replace(/^\s+/g, ""); - escapedQuery = utils.escapeRegExChars(query); - beginsWithQuery = new RegExp("^(?:" + escapedQuery + ")(.*$)", "i"); - match = beginsWithQuery.exec(hint); - this.inputView.setHintValue(inputValue + (match ? match[1] : "")); - } - }, - _clearHint: function() { - this.inputView.setHintValue(""); - }, - _clearSuggestions: function() { - this.dropdownView.clearSuggestions(); - }, - _setInputValueToQuery: function() { - this.inputView.setInputValue(this.inputView.getQuery()); - }, - _setInputValueToSuggestionUnderCursor: function(e) { - var suggestion = e.data; - this.inputView.setInputValue(suggestion.value, true); - }, - _openDropdown: function() { - this.dropdownView.open(); - }, - _closeDropdown: function(e) { - this.dropdownView[e.type === "blured" ? "closeUnlessMouseIsOverDropdown" : "close"](); - }, - _moveDropdownCursor: function(e) { - var $e = e.data; - if (!$e.shiftKey && !$e.ctrlKey && !$e.metaKey) { - this.dropdownView[e.type === "upKeyed" ? "moveCursorUp" : "moveCursorDown"](); - } - }, - _handleSelection: function(e) { - var byClick = e.type === "suggestionSelected", suggestion = byClick ? e.data : this.dropdownView.getSuggestionUnderCursor(); - if (suggestion) { - this.inputView.setInputValue(suggestion.value); - byClick ? this.inputView.focus() : e.data.preventDefault(); - byClick && utils.isMsie() ? utils.defer(this.dropdownView.close) : this.dropdownView.close(); - this.eventBus.trigger("selected", suggestion.datum, suggestion.dataset); - } - }, - _getSuggestions: function() { - var that = this, query = this.inputView.getQuery(); - if (utils.isBlankString(query)) { - return; - } - utils.each(this.datasets, function(i, dataset) { - dataset.getSuggestions(query, function(suggestions) { - if (query === that.inputView.getQuery()) { - that.dropdownView.renderSuggestions(dataset, suggestions); - } - }); - }); - }, - _autocomplete: function(e) { - var isCursorAtEnd, ignoreEvent, query, hint, suggestion; - if (e.type === "rightKeyed" || e.type === "leftKeyed") { - isCursorAtEnd = this.inputView.isCursorAtEnd(); - ignoreEvent = this.inputView.getLanguageDirection() === "ltr" ? e.type === "leftKeyed" : e.type === "rightKeyed"; - if (!isCursorAtEnd || ignoreEvent) { - return; - } - } - query = this.inputView.getQuery(); - hint = this.inputView.getHintValue(); - if (hint !== "" && query !== hint) { - suggestion = this.dropdownView.getFirstSuggestion(); - this.inputView.setInputValue(suggestion.value); - this.eventBus.trigger("autocompleted", suggestion.datum, suggestion.dataset); - } - }, - _propagateEvent: function(e) { - this.eventBus.trigger(e.type); - }, - destroy: function() { - this.inputView.destroy(); - this.dropdownView.destroy(); - destroyDomStructure(this.$node); - this.$node = null; - }, - setQuery: function(query) { - this.inputView.setQuery(query); - this.inputView.setInputValue(query); - this._clearHint(); - this._clearSuggestions(); - this._getSuggestions(); - } - }); - return TypeaheadView; - function buildDomStructure(input) { - var $wrapper = $(html.wrapper), $dropdown = $(html.dropdown), $input = $(input), $hint = $(html.hint); - $wrapper = $wrapper.css(css.wrapper); - $dropdown = $dropdown.css(css.dropdown); - $hint.css(css.hint).css({ - backgroundAttachment: $input.css("background-attachment"), - backgroundClip: $input.css("background-clip"), - backgroundColor: $input.css("background-color"), - backgroundImage: $input.css("background-image"), - backgroundOrigin: $input.css("background-origin"), - backgroundPosition: $input.css("background-position"), - backgroundRepeat: $input.css("background-repeat"), - backgroundSize: $input.css("background-size") - }); - $input.data("ttAttrs", { - dir: $input.attr("dir"), - autocomplete: $input.attr("autocomplete"), - spellcheck: $input.attr("spellcheck"), - style: $input.attr("style") - }); - $input.addClass("tt-query").attr({ - autocomplete: "off", - spellcheck: false - }).css(css.query); - try { - !$input.attr("dir") && $input.attr("dir", "auto"); - } catch (e) {} - return $input.wrap($wrapper).parent().prepend($hint).append($dropdown); - } - function destroyDomStructure($node) { - var $input = $node.find(".tt-query"); - utils.each($input.data("ttAttrs"), function(key, val) { - utils.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); - }); - $input.detach().removeData("ttAttrs").removeClass("tt-query").insertAfter($node); - $node.remove(); - } - }(); - (function() { - var cache = {}, viewKey = "ttView", methods; - methods = { - initialize: function(datasetDefs) { - var datasets; - datasetDefs = utils.isArray(datasetDefs) ? datasetDefs : [ datasetDefs ]; - if (datasetDefs.length === 0) { - $.error("no datasets provided"); - } - datasets = utils.map(datasetDefs, function(o) { - var dataset = cache[o.name] ? cache[o.name] : new Dataset(o); - if (o.name) { - cache[o.name] = dataset; - } - return dataset; - }); - return this.each(initialize); - function initialize() { - var $input = $(this), deferreds, eventBus = new EventBus({ - el: $input - }); - deferreds = utils.map(datasets, function(dataset) { - return dataset.initialize(); - }); - $input.data(viewKey, new TypeaheadView({ - input: $input, - eventBus: eventBus = new EventBus({ - el: $input - }), - datasets: datasets - })); - $.when.apply($, deferreds).always(function() { - utils.defer(function() { - eventBus.trigger("initialized"); - }); - }); - } - }, - destroy: function() { - return this.each(destroy); - function destroy() { - var $this = $(this), view = $this.data(viewKey); - if (view) { - view.destroy(); - $this.removeData(viewKey); - } - } - }, - setQuery: function(query) { - return this.each(setQuery); - function setQuery() { - var view = $(this).data(viewKey); - view && view.setQuery(query); - } - } - }; - jQuery.fn.typeahead = function(method) { - if (methods[method]) { - return methods[method].apply(this, [].slice.call(arguments, 1)); - } else { - return methods.initialize.apply(this, arguments); - } - }; - })(); -})(window.jQuery); diff --git a/app/assets/javascripts/src/JIT.js b/app/assets/javascripts/src/JIT.js.erb similarity index 96% rename from app/assets/javascripts/src/JIT.js rename to app/assets/javascripts/src/JIT.js.erb index 79954271..81cc687d 100644 --- a/app/assets/javascripts/src/JIT.js +++ b/app/assets/javascripts/src/JIT.js.erb @@ -3127,7 +3127,7 @@ var Canvas; ctx = base.getCtx(), scale = base.scaleOffsetX; //var pattern = new Image(); - //pattern.src = "/assets/cubes.png"; + //pattern.src = "<%= asset_path('cubes.png') %>"; //var ptrn = ctx.createPattern(pattern, 'repeat'); //ctx.fillStyle = ptrn; ctx.fillStyle = Metamaps.Settings.colors.background; diff --git a/app/assets/javascripts/src/Metamaps.Backbone.js b/app/assets/javascripts/src/Metamaps.Backbone.js.erb similarity index 100% rename from app/assets/javascripts/src/Metamaps.Backbone.js rename to app/assets/javascripts/src/Metamaps.Backbone.js.erb diff --git a/app/assets/javascripts/src/Metamaps.GlobalUI.js b/app/assets/javascripts/src/Metamaps.GlobalUI.js.erb similarity index 79% rename from app/assets/javascripts/src/Metamaps.GlobalUI.js rename to app/assets/javascripts/src/Metamaps.GlobalUI.js.erb index 46a34b5c..fc2ba6fc 100644 --- a/app/assets/javascripts/src/Metamaps.GlobalUI.js +++ b/app/assets/javascripts/src/Metamaps.GlobalUI.js.erb @@ -459,7 +459,7 @@ Metamaps.GlobalUI.Search = { $('.sidebarSearch .twitter-typeahead, .sidebarSearch .tt-hint, .sidebarSearchField').animate({ width: '0' }, 300, function () { - $('.sidebarSearchField').typeahead('setQuery', ''); + $('.sidebarSearchField').typeahead('val', ''); $('.sidebarSearchField').blur(); self.changing = false; self.isOpen = false; @@ -477,95 +477,109 @@ Metamaps.GlobalUI.Search = { var topics = { name: 'topics', limit: 9999, - dupChecker: function (datum1, datum2) { - return false; - }, - template: $('#topicSearchTemplate').html(), - remote: { - url: '/search/topics?term=%QUERY', - replace: function () { - var q = '/search/topics?term=' + $('.sidebarSearchField').val(); - if (Metamaps.Active.Mapper && $("#limitTopicsToMe").is(':checked')) { - q += "&user=" + Metamaps.Active.Mapper.id.toString(); - } - return q; + + display: function(s) { return s.label; }, + templates: { + notFound: function(s) { + return Hogan.compile($('#topicSearchTemplate').html()).render({ + value: "No results", + label: "No results", + typeImageURL: "<%= asset_path('icons/wildcard.png') %>", + rtype: "noresult" + }); + }, + header: topicheader, + suggestion: function(s) { + return Hogan.compile($('#topicSearchTemplate').html()).render(s); }, - filter: function (dataset) { - if (dataset.length == 0) { - dataset.push({ - value: "No results", - label: "No results", - typeImageURL: "/assets/icons/wildcard.png", - rtype: "noresult" - }); - } - return dataset; - } }, - engine: Hogan, - header: topicheader + source: new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + url: '/search/topics', + prepare: function(query, settings) { + settings.url += '?term=' + query; + if (Metamaps.Active.Mapper && $("#limitTopicsToMe").is(':checked')) { + settings.url += "&user=" + Metamaps.Active.Mapper.id.toString(); + } + return settings; + }, + }, + }), }; var maps = { name: 'maps', limit: 9999, - dupChecker: function (datum1, datum2) { - return false; - }, - template: $('#mapSearchTemplate').html(), - remote: { - url: '/search/maps?term=%QUERY', - replace: function () { - var q = '/search/maps?term=' + $('.sidebarSearchField').val(); - if (Metamaps.Active.Mapper && $("#limitMapsToMe").is(':checked')) { - q += "&user=" + Metamaps.Active.Mapper.id.toString(); - } - return q; + display: function(s) { return s.label; }, + templates: { + notFound: function(s) { + return Hogan.compile($('#mapSearchTemplate').html()).render({ + value: "No results", + label: "No results", + rtype: "noresult" + }); + }, + header: mapheader, + suggestion: function(s) { + return Hogan.compile($('#mapSearchTemplate').html()).render(s); }, - filter: function (dataset) { - if (dataset.length == 0) { - dataset.push({ - value: "No results", - label: "No results", - rtype: "noresult" - }); - } - return dataset; - } }, - engine: Hogan, - header: mapheader + source: new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + url: '/search/maps', + prepare: function(query, settings) { + settings.url += '?term=' + query; + if (Metamaps.Active.Mapper && $("#limitMapsToMe").is(':checked')) { + settings.url += "&user=" + Metamaps.Active.Mapper.id.toString(); + } + return settings; + }, + }, + }), }; var mappers = { name: 'mappers', limit: 9999, - dupChecker: function (datum1, datum2) { - return false; + display: function(s) { return s.label; }, + templates: { + notFound: function(s) { + return Hogan.compile($('#mapperSearchTemplate').html()).render({ + value: "No results", + label: "No results", + rtype: "noresult", + profile: "<%= asset_path('user.png') %>", + }); + }, + header: mapperheader, + suggestion: function(s) { + return Hogan.compile($('#mapperSearchTemplate').html()).render(s); + }, }, - template: $('#mapperSearchTemplate').html(), - remote: { - url: '/search/mappers?term=%QUERY', - filter: function (dataset) { - if (dataset.length == 0) { - dataset.push({ - profile: "/assets/user.png", - - value: "No results", - label: "No results", - rtype: "noresult" - }); - } - return dataset; - } - }, - engine: Hogan, - header: mapperheader + source: new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + url: '/search/mappers?term=%QUERY', + wildcard: '%QUERY', + }, + }), }; - $('.sidebarSearchField').typeahead([topics, maps, mappers]); + + // Take all that crazy setup data and put it together into one beautiful typeahead call! + $('.sidebarSearchField').typeahead( + { + highlight: true, + }, + [topics, maps, mappers] + ); //Set max height of the search results box to prevent it from covering bottom left footer - $('.sidebarSearchField').bind('typeahead:suggestionsRendered', function (event) { + $('.sidebarSearchField').bind('typeahead:render', function (event) { self.initSearchOptions(); self.hideLoader(); var h = $(window).height(); @@ -577,7 +591,7 @@ Metamaps.GlobalUI.Search = { }); // tell the autocomplete to launch a new tab with the topic, map, or mapper you clicked on - $('.sidebarSearchField').bind('typeahead:selected', self.handleResultClick); + $('.sidebarSearchField').bind('typeahead:select', self.handleResultClick); // don't do it, if they clicked on a 'addToMap' button $('.sidebarSearch button.addToMap').click(function (event) { @@ -585,7 +599,7 @@ Metamaps.GlobalUI.Search = { }); // make sure that when you click on 'limit to me' or 'toggle section' it works - $('.sidebarSearchField').bind('typeahead:queryChanged', function(){ + $('.sidebarSearchField').bind('typeahead:change', function(){ if ($(this).val() === "") { self.hideLoader(); } @@ -600,14 +614,14 @@ Metamaps.GlobalUI.Search = { self.hideLoader(); - if (datum.rtype != "noresult") { + if (["topic", "map", "mapper"].indexOf(datum.rtype) !== -1) { self.close(0, true); var win; - if (dataset == "topics") { + if (datum.rtype == "topic") { Metamaps.Router.topics(datum.id); - } else if (dataset == "maps") { + } else if (datum.rtype == "map") { Metamaps.Router.maps(datum.id); - } else if (dataset == "mappers") { + } else if (datum.rtype == "mapper") { Metamaps.Router.explore("mapper", datum.id); } } @@ -616,7 +630,7 @@ Metamaps.GlobalUI.Search = { var self = Metamaps.GlobalUI.Search; function toggleResultSet(set) { - var s = $('.tt-dataset-' + set + ' .tt-suggestions'); + var s = $('.tt-dataset-' + set + ' .tt-dataset'); if (s.css('height') == '0px') { s.css({ 'height': 'auto', @@ -635,7 +649,7 @@ Metamaps.GlobalUI.Search = { $('.limitToMe').unbind().bind("change", function (e) { // set the value of the search equal to itself to retrigger the autocomplete event self.isOpen = false; - $('.sidebarSearchField').typeahead('setQuery', $('.sidebarSearchField').val()); + $('.sidebarSearchField').typeahead('val', $('.sidebarSearchField').val()); setTimeout(function () { self.isOpen = true; }, 2000); @@ -658,4 +672,4 @@ Metamaps.GlobalUI.Search = { showLoader: function () { $('#searchLoading').show(); } -}; \ No newline at end of file +}; diff --git a/app/assets/javascripts/src/Metamaps.JIT.js b/app/assets/javascripts/src/Metamaps.JIT.js.erb similarity index 97% rename from app/assets/javascripts/src/Metamaps.JIT.js rename to app/assets/javascripts/src/Metamaps.JIT.js.erb index bef4ad59..38a2de5b 100644 --- a/app/assets/javascripts/src/Metamaps.JIT.js +++ b/app/assets/javascripts/src/Metamaps.JIT.js.erb @@ -29,10 +29,10 @@ Metamaps.JIT = { $(".takeScreenshot").click(Metamaps.Map.exportImage); self.topicDescImage = new Image(); - self.topicDescImage.src = '/assets/topic_description_signifier.png'; + self.topicDescImage.src = '<%= asset_path('topic_description_signifier.png') %>'; self.topicLinkImage = new Image(); - self.topicLinkImage.src = '/assets/topic_link_signifier.png'; + self.topicLinkImage.src = '<%= asset_path('topic_link_signifier.png') %>'; }, /** * convert our topic JSON into something JIT can use diff --git a/app/assets/javascripts/src/Metamaps.Router.js b/app/assets/javascripts/src/Metamaps.Router.js.erb similarity index 100% rename from app/assets/javascripts/src/Metamaps.Router.js rename to app/assets/javascripts/src/Metamaps.Router.js.erb diff --git a/app/assets/javascripts/src/Metamaps.Views.js b/app/assets/javascripts/src/Metamaps.Views.js.erb similarity index 100% rename from app/assets/javascripts/src/Metamaps.Views.js rename to app/assets/javascripts/src/Metamaps.Views.js.erb diff --git a/app/assets/javascripts/src/Metamaps.js b/app/assets/javascripts/src/Metamaps.js.erb similarity index 93% rename from app/assets/javascripts/src/Metamaps.js rename to app/assets/javascripts/src/Metamaps.js.erb index 2a378623..09bd1f97 100644 --- a/app/assets/javascripts/src/Metamaps.js +++ b/app/assets/javascripts/src/Metamaps.js.erb @@ -37,7 +37,7 @@ Metamaps.Settings = { background: '#18202E', text: '#DDD' } - } + }, }; Metamaps.Touch = { @@ -157,7 +157,7 @@ Metamaps.Backbone.init = function () { this.on('saved', this.savedEvent); this.on('nowPrivate', function(){ var removeTopicData = { - topicid: this.id + mappableid: this.id }; $(document).trigger(Metamaps.JIT.events.removeTopic, [removeTopicData]); @@ -165,7 +165,7 @@ Metamaps.Backbone.init = function () { this.on('noLongerPrivate', function(){ var newTopicData = { mappingid: this.getMapping().id, - topicid: this.id + mappableid: this.id }; $(document).trigger(Metamaps.JIT.events.newTopic, [newTopicData]); @@ -194,7 +194,8 @@ Metamaps.Backbone.init = function () { return Metamaps.Mappings.findWhere({ map_id: Metamaps.Active.Map.id, - topic_id: this.isNew() ? this.cid : this.id + mappable_type: "Topic", + mappable_id: this.isNew() ? this.cid : this.id }); }, createNode: function () { @@ -320,14 +321,14 @@ Metamaps.Backbone.init = function () { this.on('noLongerPrivate', function(){ var newSynapseData = { mappingid: this.getMapping().id, - synapseid: this.id + mappableid: this.id }; $(document).trigger(Metamaps.JIT.events.newSynapse, [newSynapseData]); }); this.on('nowPrivate', function(){ $(document).trigger(Metamaps.JIT.events.removeSynapse, [{ - synapseid: this.id + mappableid: this.id }]); }); @@ -336,7 +337,7 @@ Metamaps.Backbone.init = function () { prepareLiForFilter: function () { var li = ''; li += '
  • ';       - li += '"'; li += ' alt="synapse icon" />';       li += '

    ' + this.get('desc') + '

  • '; return li; @@ -370,7 +371,8 @@ Metamaps.Backbone.init = function () { return Metamaps.Mappings.findWhere({ map_id: Metamaps.Active.Map.id, - synapse_id: this.isNew() ? this.cid : this.id + mappable_type: "Synapse", + mappable_id: this.isNew() ? this.cid : this.id }); }, createEdge: function () { @@ -457,11 +459,11 @@ Metamaps.Backbone.init = function () { return Metamaps.Map.get(this.get('map_id')); }, getTopic: function () { - if (this.get('category') === 'Topic') return Metamaps.Topic.get(this.get('topic_id')); + if (this.get('mappable_type') === 'Topic') return Metamaps.Topic.get(this.get('mappable_id')); else return false; }, getSynapse: function () { - if (this.get('category') === 'Synapse') return Metamaps.Synapse.get(this.get('synapse_id')); + if (this.get('mappable_type') === 'Synapse') return Metamaps.Synapse.get(this.get('mappable_id')); else return false; } }); @@ -654,7 +656,7 @@ Metamaps.Create = { self.newSelectedMetacodeNames = self.selectedMetacodeNames.slice(0); self.newSelectedMetacodes = self.selectedMetacodes.slice(0); } - $('#metacodeSwitchTabs').tabs("select", self.selectedMetacodeSetIndex); + $('#metacodeSwitchTabs').tabs("option", "active", self.selectedMetacodeSetIndex); $('#topic_name').focus(); }, newTopic: { @@ -664,21 +666,36 @@ Metamaps.Create = { Metamaps.Create.newTopic.name = $(this).val(); }); + var topicBloodhound = new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + url: '/topics/autocomplete_topic?term=%QUERY', + wildcard: '%QUERY', + }, + }); + // initialize the autocomplete results for the metacode spinner - $('#topic_name').typeahead([ + $('#topic_name').typeahead( { + highlight: true, + minLength: 2, + }, + [{ name: 'topic_autocomplete', limit: 8, - template: $('#topicAutocompleteTemplate').html(), - remote: { - url: '/topics/autocomplete_topic?term=%QUERY' + display: function (s) { return s.label; }, + templates: { + suggestion: function(s) { + return Hogan.compile($('#topicAutocompleteTemplate').html()).render(s); + }, }, - engine: Hogan - } - ]); + source: topicBloodhound, + }] + ); // tell the autocomplete to submit the form with the topic you clicked on if you pick from the autocomplete - $('#topic_name').bind('typeahead:selected', function (event, datum, dataset) { + $('#topic_name').bind('typeahead:select', function (event, datum, dataset) { Metamaps.Topic.getTopicFromAutocomplete(datum.id); }); @@ -711,7 +728,7 @@ Metamaps.Create = { }, hide: function () { $('#new_topic').fadeOut('fast'); - $("#topic_name").typeahead('setQuery', ''); + $("#topic_name").typeahead('val', ''); Metamaps.Create.newTopic.beingCreated = false; } }, @@ -723,32 +740,62 @@ Metamaps.Create = { Metamaps.Create.newSynapse.description = $(this).val(); }); - // initialize the autocomplete results for synapse creation - $('#synapse_desc').typeahead([ - { - name: 'synapse_autocomplete', - template: "
    {{label}}
    ", - remote: { - url: '/search/synapses?term=%QUERY' + var synapseBloodhound = new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + url: '/search/synapses?term=%QUERY', + wildcard: '%QUERY', + }, + }); + var existingSynapseBloodhound = new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + url: '/search/synapses?topic1id=%TOPIC1&topic2id=%TOPIC2', + prepare: function(query, settings) { + var self = Metamaps.Create.newSynapse; + if (Metamaps.Selected.Nodes.length < 2) { + settings.url = settings.url.replace("%TOPIC1", self.topic1id).replace("%TOPIC2", self.topic2id); + return settings; + } else { + return null; + } }, - engine: Hogan + }, + }); + + // initialize the autocomplete results for synapse creation + $('#synapse_desc').typeahead( + { + highlight: true, + minLength: 2, + }, + [{ + name: 'synapse_autocomplete', + display: function(s) { return s.label; }, + templates: { + suggestion: function(s) { + return Hogan.compile("
    {{label}}
    ").render(s); + }, + }, + source: synapseBloodhound, }, { name: 'existing_synapses', limit: 50, - template: $('#synapseAutocompleteTemplate').html(), - remote: { - url: '/search/synapses', - replace: function () { - return self.getSearchQuery(); - } + display: function(s) { return s.label; }, + templates: { + suggestion: function(s) { + return Hogan.compile($('#synapseAutocompleteTemplate').html()).render(s); + }, + header: "

    Existing synapses

    " }, - engine: Hogan, - header: "

    Existing synapses

    " - } - ]); + source: existingSynapseBloodhound, + }] + ); - $('#synapse_desc').bind('typeahead:selected', function (event, datum, dataset) { + $('#synapse_desc').bind('typeahead:select', function (event, datum, dataset) { if (datum.id) { // if they clicked on an existing synapse get it Metamaps.Synapse.getSynapseFromAutocomplete(datum.id); } @@ -771,7 +818,7 @@ Metamaps.Create = { }, hide: function () { $('#new_synapse').fadeOut('fast'); - $("#synapse_desc").typeahead('setQuery', ''); + $("#synapse_desc").typeahead('val', ''); Metamaps.Create.newSynapse.beingCreated = false; Metamaps.Create.newTopic.addSynapse = false; Metamaps.Create.newSynapse.topic1id = 0; @@ -779,13 +826,6 @@ Metamaps.Create = { Metamaps.Mouse.synapseStartCoordinates = []; Metamaps.Visualize.mGraph.plot(); }, - getSearchQuery: function () { - var self = Metamaps.Create.newSynapse; - - if (Metamaps.Selected.Nodes.length < 2) { - return '/search/synapses?topic1id=' + self.topic1id + '&topic2id=' + self.topic2id; - } else return ''; - } } }; // end Metamaps.Create @@ -888,8 +928,11 @@ Metamaps.TopicCard = { setTimeout(function () { var text = $(element).val(); if (event.type=="paste" || (event.type=="keyup" && event.which==13)){ - if (text.slice(0, 4) !== 'http') { - text='http://'+text; + // TODO evaluate converting this to '//' no matter what (infer protocol) + if (text.slice(0, 7) !== 'http://' && + text.slice(0, 8) !== 'https://' && + text.slice(0, 2) !== '//') { + text='//'+text; } topic.save({ link: text @@ -1528,8 +1571,6 @@ Metamaps.SynapseCard = { ////////////////////// END TOPIC AND SYNAPSE CARDS ////////////////////////////////// - - /* * * VISUALIZE @@ -1859,7 +1900,7 @@ Metamaps.Util = { return (url.match(/\.(jpeg|jpg|gif|png)$/) != null); }, checkURLisYoutubeVideo: function (url) { - return (url.match(/^http:\/\/(?:www\.)?youtube.com\/watch\?(?=[^?]*v=\w+)(?:[^\s?]+)?$/) != null); + return (url.match(/^https?:\/\/(?:www\.)?youtube.com\/watch\?(?=[^?]*v=\w+)(?:[^\s?]+)?$/) != null); } }; // end Metamaps.Util @@ -1869,9 +1910,6 @@ Metamaps.Util = { * */ Metamaps.Realtime = { - stringForLocalhost: 'http://localhost:5001', - stringForMetamaps: 'http://metamaps.cc:5001', - stringForHeroku: 'http://gentle-savannah-1303.herokuapp.com', socket: null, isOpen: false, changing: false, @@ -1895,9 +1933,7 @@ Metamaps.Realtime = { }); $('body').click(self.close); - var railsEnv = $('body').data('env'); - var whichToConnect = railsEnv === 'development' ? self.stringForLocalhost : self.stringForHeroku; - self.socket = io.connect(whichToConnect); + self.socket = io.connect('<%= ENV['REALTIME_SERVER'] %>'); self.socket.on('connect', function () { self.startActiveMap(); }); @@ -2505,12 +2541,12 @@ Metamaps.Realtime = { if (!self.status) return; - function test() { + function waitThenRenderTopic() { if (topic && mapping && mapper) { Metamaps.Topic.renderTopic(mapping, topic, false, false); } else if (!cancel) { - setTimeout(test, 10); + setTimeout(waitThenRenderTopic, 10); } } @@ -2523,7 +2559,7 @@ Metamaps.Realtime = { Metamaps.Mapper.get(data.mapperid, mapperCallback); } $.ajax({ - url: "/topics/" + data.topicid + ".json", + url: "/topics/" + data.mappableid + ".json", success: function (response) { Metamaps.Topics.add(response); topic = Metamaps.Topics.get(response.id); @@ -2543,7 +2579,7 @@ Metamaps.Realtime = { } }); - test(); + waitThenRenderTopic(); }, // removeTopic sendDeleteTopic: function (data) { @@ -2570,7 +2606,7 @@ Metamaps.Realtime = { if (!self.status) return; - var topic = Metamaps.Topics.get(data.topicid); + var topic = Metamaps.Topics.get(data.mappableid); if (topic) { var node = topic.get('node'); var mapping = topic.getMapping(); @@ -2598,7 +2634,7 @@ Metamaps.Realtime = { if (!self.status) return; - function test() { + function waitThenRenderSynapse() { if (synapse && mapping && mapper) { topic1 = synapse.getTopic1(); node1 = topic1.get('node'); @@ -2608,7 +2644,7 @@ Metamaps.Realtime = { Metamaps.Synapse.renderSynapse(mapping, synapse, node1, node2, false); } else if (!cancel) { - setTimeout(test, 10); + setTimeout(waitThenRenderSynapse, 10); } } @@ -2621,7 +2657,7 @@ Metamaps.Realtime = { Metamaps.Mapper.get(data.mapperid, mapperCallback); } $.ajax({ - url: "/synapses/" + data.synapseid + ".json", + url: "/synapses/" + data.mappableid + ".json", success: function (response) { Metamaps.Synapses.add(response); synapse = Metamaps.Synapses.get(response.id); @@ -2640,7 +2676,7 @@ Metamaps.Realtime = { cancel = true; } }); - test(); + waitThenRenderSynapse(); }, // deleteSynapse sendDeleteSynapse: function (data) { @@ -2668,7 +2704,7 @@ Metamaps.Realtime = { if (!self.status) return; - var synapse = Metamaps.Synapses.get(data.synapseid); + var synapse = Metamaps.Synapses.get(data.mappableid); if (synapse) { var edge = synapse.get('edge'); var mapping = synapse.getMapping(); @@ -2778,12 +2814,12 @@ Metamaps.Control = { var permToDelete = Metamaps.Active.Mapper.id === topic.get('user_id') || Metamaps.Active.Mapper.get('admin'); if (permToDelete) { - var topicid = topic.id; + var mappableid = topic.id; var mapping = node.getData('mapping'); topic.destroy(); Metamaps.Mappings.remove(mapping); $(document).trigger(Metamaps.JIT.events.deleteTopic, [{ - topicid: topicid + mappableid: mappableid }]); Metamaps.Control.hideNode(nodeid); } else { @@ -2822,12 +2858,12 @@ Metamaps.Control = { } var topic = node.getData('topic'); - var topicid = topic.id; + var mappableid = topic.id; var mapping = node.getData('mapping'); mapping.destroy(); Metamaps.Topics.remove(topic); $(document).trigger(Metamaps.JIT.events.removeTopic, [{ - topicid: topicid + mappableid: mappableid }]); Metamaps.Control.hideNode(nodeid); }, @@ -2951,7 +2987,7 @@ Metamaps.Control = { Metamaps.Control.hideEdge(edge); } - var synapseid = synapse.id; + var mappableid = synapse.id; synapse.destroy(); // the server will destroy the mapping, we just need to remove it here @@ -2962,7 +2998,7 @@ Metamaps.Control = { delete edge.data.$displayIndex; } $(document).trigger(Metamaps.JIT.events.deleteSynapse, [{ - synapseid: synapseid + mappableid: mappableid }]); } else { Metamaps.GlobalUI.notifyUser('Only synapses you created can be deleted'); @@ -3007,7 +3043,7 @@ Metamaps.Control = { var synapse = edge.getData("synapses")[index]; var mapping = edge.getData("mappings")[index]; - var synapseid = synapse.id; + var mappableid = synapse.id; mapping.destroy(); Metamaps.Synapses.remove(synapse); @@ -3018,7 +3054,7 @@ Metamaps.Control = { delete edge.data.$displayIndex; } $(document).trigger(Metamaps.JIT.events.removeSynapse, [{ - synapseid: synapseid + mappableid: mappableid }]); }, hideSelectedEdges: function () { @@ -3262,26 +3298,34 @@ Metamaps.Filter = { // the first option enables us to accept // ['Topics', 'Synapses'] as 'collection' if (typeof collection === "object") { - Metamaps[collection[0]].each(function(model) { - var prop = model.get(propertyToCheck) ? model.get(propertyToCheck).toString() : false; - if (prop && newList.indexOf(prop) === -1) { - newList.push(prop); - } - }); - Metamaps[collection[1]].each(function(model) { - var prop = model.get(propertyToCheck) ? model.get(propertyToCheck).toString() : false; - if (prop && newList.indexOf(prop) === -1) { - newList.push(prop); - } - }); - } - else if (typeof collection === "string") { - Metamaps[collection].each(function(model) { - var prop = model.get(propertyToCheck) ? model.get(propertyToCheck).toString() : false; - if (prop && newList.indexOf(prop) === -1) { - newList.push(prop); - } - }); + Metamaps[collection[0]].each(function(model) { + var prop = model.get(propertyToCheck); + if (prop !== null) { + prop = prop.toString(); + if (newList.indexOf(prop) === -1) { + newList.push(prop); + } + } + }); + Metamaps[collection[1]].each(function(model) { + var prop = model.get(propertyToCheck); + if (prop !== null) { + prop = prop.toString(); + if (newList.indexOf(prop) === -1) { + newList.push(prop); + } + } + }); + } else if (typeof collection === "string") { + Metamaps[collection].each(function(model) { + var prop = model.get(propertyToCheck); + if (prop !== null) { + prop = prop.toString(); + if (newList.indexOf(prop) === -1) { + newList.push(prop); + } + } + }); } removed = _.difference(self.filters[filtersToUse], newList); @@ -4018,14 +4062,14 @@ Metamaps.Topic = { var mappingSuccessCallback = function (mappingModel, response) { var newTopicData = { mappingid: mappingModel.id, - topicid: mappingModel.get('topic_id') + mappableid: mappingModel.get('mappable_id') }; $(document).trigger(Metamaps.JIT.events.newTopic, [newTopicData]); }; var topicSuccessCallback = function (topicModel, response) { if (Metamaps.Active.Map) { - mapping.save({ topic_id: topicModel.id }, { + mapping.save({ mappable_id: topicModel.id }, { success: mappingSuccessCallback, error: function (model, response) { console.log('error saving mapping to database'); @@ -4075,10 +4119,10 @@ Metamaps.Topic = { Metamaps.Topics.add(topic); var mapping = new Metamaps.Backbone.Mapping({ - category: "Topic", xloc: Metamaps.Create.newTopic.x, yloc: Metamaps.Create.newTopic.y, - topic_id: topic.cid + mappable_id: topic.cid, + mappable_type: "Topic", }); Metamaps.Mappings.add(mapping); @@ -4097,10 +4141,10 @@ Metamaps.Topic = { var topic = self.get(id); var mapping = new Metamaps.Backbone.Mapping({ - category: "Topic", xloc: Metamaps.Create.newTopic.x, yloc: Metamaps.Create.newTopic.y, - topic_id: topic.id + mappable_type: "Topic", + mappable_id: topic.id, }); Metamaps.Mappings.add(mapping); @@ -4115,10 +4159,10 @@ Metamaps.Topic = { var nextCoords = Metamaps.Map.getNextCoord(); var mapping = new Metamaps.Backbone.Mapping({ - category: "Topic", xloc: nextCoords.x, yloc: nextCoords.y, - topic_id: topic.id + mappable_type: "Topic", + mappable_id: topic.id, }); Metamaps.Mappings.add(mapping); @@ -4189,14 +4233,14 @@ Metamaps.Synapse = { var mappingSuccessCallback = function (mappingModel, response) { var newSynapseData = { mappingid: mappingModel.id, - synapseid: mappingModel.get('synapse_id') + mappableid: mappingModel.get('mappable_id') }; $(document).trigger(Metamaps.JIT.events.newSynapse, [newSynapseData]); }; var synapseSuccessCallback = function (synapseModel, response) { if (Metamaps.Active.Map) { - mapping.save({ synapse_id: synapseModel.id }, { + mapping.save({ mappable_id: synapseModel.id }, { success: mappingSuccessCallback }); } @@ -4246,15 +4290,15 @@ Metamaps.Synapse = { node1 = synapsesToCreate[i]; topic1 = node1.getData('topic'); synapse = new Metamaps.Backbone.Synapse({ - desc: Metamaps.Create.newSynapse.description, + desc: Metamaps.Create.newSynapse.description,// || "", node1_id: topic1.isNew() ? topic1.cid : topic1.id, node2_id: topic2.isNew() ? topic2.cid : topic2.id, }); Metamaps.Synapses.add(synapse); mapping = new Metamaps.Backbone.Mapping({ - category: "Synapse", - synapse_id: synapse.cid + mappable_type: "Synapse", + mappable_id: synapse.cid, }); Metamaps.Mappings.add(mapping); @@ -4274,8 +4318,8 @@ Metamaps.Synapse = { var synapse = self.get(id); var mapping = new Metamaps.Backbone.Mapping({ - category: "Synapse", - synapse_id: synapse.id + mappable_type: "Synapse", + mappable_id: synapse.id, }); Metamaps.Mappings.add(mapping); @@ -4755,7 +4799,7 @@ Metamaps.Map.InfoBox = { obj["map_creator_tip"] = isCreator ? self.changePermissionText : ""; obj["contributors_class"] = Metamaps.Mappers.length > 1 ? "multiple" : ""; obj["contributors_class"] += Metamaps.Mappers.length === 2 ? " mTwo" : ""; - obj["contributor_image"] = Metamaps.Mappers.length > 0 ? Metamaps.Mappers.models[0].get("image") : "/assets/user.png"; + obj["contributor_image"] = Metamaps.Mappers.length > 0 ? Metamaps.Mappers.models[0].get("image") : "<%= asset_path('user.png') %>"; obj["contributor_list"] = self.createContributorList(); obj["user_name"] = isCreator ? "You" : map.get("user_name"); obj["created_at"] = map.get("created_at_clean"); @@ -4852,7 +4896,7 @@ Metamaps.Map.InfoBox = { if (Metamaps.Mappers.length === 2) contributors_class = "multiple mTwo"; else if (Metamaps.Mappers.length > 2) contributors_class = "multiple"; - var contributors_image = "/assets/user.png"; + var contributors_image = "<%= asset_path('user.png') %>"; if (Metamaps.Mappers.length > 0) { // get the first contributor and use their image contributors_image = Metamaps.Mappers.models[0].get("image"); @@ -5033,7 +5077,7 @@ Metamaps.Account = { var self = Metamaps.Account; $('.userImageDiv canvas').remove(); - $('.userImageDiv img').attr('src', '/assets/user.png').show(); + $('.userImageDiv img').attr('src', '<%= asset_path('user.png') %>').show(); $('.userImageMenu').hide(); var input = $('#user_image'); diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css.erb similarity index 90% rename from app/assets/stylesheets/application.css rename to app/assets/stylesheets/application.css.erb index 392b2d2c..6c036de1 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css.erb @@ -78,7 +78,7 @@ html { } body { - background: #d8d9da url(shattered_@2X.png); + background: #d8d9da url(<%= asset_data_uri('shattered_@2X.png') %>); font-family: 'din-medium', helvetica, sans-serif; color: #424242; -moz-osx-font-smoothing: grayscale; @@ -300,7 +300,7 @@ input[type="submit"]:active { display: none; width: 40px; height: 40px; - background-image: url(photo.png); + background-image: url(<%= asset_data_uri('photo.png') %>); position: absolute; top: 22px; left: 22px; @@ -413,7 +413,7 @@ input[type="submit"]:active { } .accountName:hover .nameEdit:after { - background:url(edit.png)no-repeat; + background: url(<%= asset_data_uri('edit.png') %>) no-repeat; content:" "; position:absolute; width:25px; @@ -547,7 +547,7 @@ input[type="submit"]:active { display: block; height: 16px; width: 16px; - background-image: url(metacodesettings_sprite.png); + background-image: url(<%= asset_data_uri('metacodesettings_sprite.png') %>); position: absolute; z-index: 2; top: 20px; @@ -652,7 +652,7 @@ label { box-shadow: 6px 6px 8px rgba(0, 0, 0, 0.4); } .headertop .tab { - background: url(tab.png) no-repeat 0 0; + background: url('tab.png') no-repeat 0 0; position: absolute; top: -11px; right: -2px; @@ -719,7 +719,7 @@ label { .accountInnerArrow { width:16px; height:16px; - background-image: url(arrowdown_sprite.png); + background-image: url(<%= asset_data_uri('arrowdown_sprite.png') %>); background-repeat: no-repeat; position:absolute; top: 8px; @@ -783,7 +783,7 @@ label { position:absolute; pointer-events:none; background-repeat:no-repeat; - background-image: url(user_sprite.png); + background-image: url(<%= asset_data_uri('user_sprite.png') %>); } .accountSettings .accountIcon { background-position: 0 0; @@ -813,7 +813,7 @@ li.accountInvite span { padding: 9px 0 9px 62px; } .accountImage { - background-image: url(user.png); + background-image: url(<%= asset_data_uri 'user.png' %>); background-size: 84px 84px; background-repeat: no-repeat; height:84px; @@ -1145,7 +1145,7 @@ h3.realtimeBoxTitle { position: absolute; top: 4px; right: 0; - background-image: url('junto24_sprite.png'); + background-image: url(<%= asset_data_uri('junto24_sprite.png') %>); } .realtimeMapperList .littleRtOff .littleJuntoIcon { background-position: 0 0; @@ -1222,7 +1222,7 @@ h3.realtimeBoxTitle { position: absolute; top: 0; left: 4px; - background-image: url(context_sprite.png); + background-image: url(<%= asset_data_uri('context_sprite.png') %>); background-repeat: no-repeat; width: 24px; height: 24px; @@ -1238,7 +1238,7 @@ h3.realtimeBoxTitle { } .rightclickmenu .rc-center .rc-icon { - background-image: url(context_topicview_sprite.png); + background-image: url(<%= asset_data_uri('context_topicview_sprite.png') %>); } .rightclickmenu .rc-popout .rc-icon { background-position: 0 -72px; @@ -1251,7 +1251,7 @@ h3.realtimeBoxTitle { } .rightclickmenu .rc-siblings .rc-icon { background-position: 0 -24px; - background-image: url(context_topicview_sprite.png); + background-image: url(<%= asset_data_uri('context_topicview_sprite.png') %>); } .rightclickmenu .expandLi { position: absolute; @@ -1259,7 +1259,7 @@ h3.realtimeBoxTitle { right: 8px; width: 16px; height: 16px; - background-image: url(arrowright_sprite.png); + background-image: url(<%= asset_data_uri('arrowright_sprite.png') %>); background-repeat: no-repeat; background-position: 0 -32px; } @@ -1294,7 +1294,7 @@ h3.realtimeBoxTitle { position: absolute; top: 0; left: 4px; - background-image: url(permissions32_sprite.png); + background-image: url(<%= asset_data_uri('permissions32_sprite.png') %>); background-size: 72px 48px; background-repeat: no-repeat; width: 24px; @@ -1339,7 +1339,7 @@ h3.realtimeBoxTitle { right: 4px; width: 16px; height: 16px; - background-image: url(arrowright_sprite.png); + background-image: url(<%= asset_data_uri('arrowright_sprite.png') %>); background-repeat: no-repeat; background-position: 0 -32px; } @@ -1373,13 +1373,14 @@ h3.realtimeBoxTitle { /* topic and synapse autocomplete */ -#new_topic .tt-suggestion.tt-is-under-cursor, -#new_topic .tt-suggestion.tt-is-under-mouse-cursor, -#new_synapse .tt-suggestion.tt-is-under-cursor, -#new_synapse .tt-suggestion.tt-is-under-mouse-cursor { +#new_topic .tt-suggestion:hover, +#new_topic .tt-suggestion.tt-cursor, +#new_synapse .tt-suggestion:hover, +#new_synapse .tt-suggestion.tt-cursor { background: #E0E0E0; } #new_topic .tt-suggestion, +#new_synapse .tt-dataset h3, #new_synapse .tt-suggestion { background: #F5F5F5; position: relative; @@ -1418,15 +1419,15 @@ h3.realtimeBoxTitle { top: 8px; right: 8px; background-repeat: no-repeat; - background-image: url(arrowright_sprite.png); + background-image: url(<%= asset_data_uri('arrowright_sprite.png') %>); background-position: 0 -32px; } -#new_topic .tt-suggestion.tt-is-under-cursor .expandTopicMetadata, -#new_topic .tt-suggestion.tt-is-under-mouse-cursor .expandTopicMetadata { +#new_topic .tt-suggestion:hover .expandTopicMetadata, +#new_topic .tt-suggestion.tt-cursor .expandTopicMetadata { display: block; } -#new_topic .tt-suggestion.tt-is-under-cursor .topicMetadata, -#new_topic .tt-suggestion.tt-is-under-mouse-cursor .topicMetadata { +#new_topic .tt-suggestion:hover .topicMetadata, +#new_topic .tt-suggestion.tt-cursor .topicMetadata { display: block; } #new_topic .topicMetadata { @@ -1444,7 +1445,7 @@ h3.realtimeBoxTitle { width: 32px; height: 32px; background-repeat: no-repeat; - background-image: url(permissions32_sprite.png); + background-image: url(<%= asset_data_uri('permissions32_sprite.png') %>); position: absolute; bottom: 4px; right: 4px; @@ -1464,7 +1465,7 @@ h3.realtimeBoxTitle { #new_topic .topicNumMaps { height: 14px; padding: 1px 0 1px 32px; - background-image: url(metamap16.png); + background-image: url(<%= asset_data_uri('metamap16.png') %>); background-repeat: no-repeat; background-position: 8px 0; position: absolute; @@ -1473,7 +1474,7 @@ h3.realtimeBoxTitle { #new_topic .topicNumSynapses { height: 14px; padding: 1px 0 1px 32px; - background-image: url(synapse16.png); + background-image: url(<%= asset_data_uri('synapse16.png') %>); background-repeat: no-repeat; background-position: 8px 0; position: absolute; @@ -1563,7 +1564,7 @@ h3.realtimeBoxTitle { width: 32px; height: 32px; background-repeat: no-repeat; - background-image: url(permissions32_sprite.png); + background-image: url(<%= asset_data_uri('permissions32_sprite.png') %>); } /* map info box */ /* map info box */ @@ -1603,7 +1604,7 @@ h3.realtimeBoxTitle { resize: none; } .mapInfoBox.canEdit #mapInfoName:hover { - background-image: url(edit.png); + background-image: url(<%= asset_data_uri('edit.png') %>); background-repeat: no-repeat; background-position: bottom right; cursor: text; @@ -1757,11 +1758,11 @@ h3.realtimeBoxTitle { } .mapTopics { - background-image: url(topic32.png); + background-image: url(<%= asset_data_uri('topic32.png') %>); background-position: 13px center; } .mapSynapses { - background-image: url(synapse32padded.png); + background-image: url(<%= asset_data_uri('synapse32padded.png') %>); background-position: 13px center; } .mapInfoBox .mapPermission { @@ -1771,7 +1772,7 @@ h3.realtimeBoxTitle { padding: 0; margin: 8px 30px 8px 10px; position: relative; - background-image: url(permissions32_sprite.png); + background-image: url(<%= asset_data_uri('permissions32_sprite.png') %>); } .mapInfoBox .mapPermission.commons { background-position: 0 0; @@ -1783,12 +1784,12 @@ h3.realtimeBoxTitle { background-position: -32px 0; } .yourMap .mapPermission:hover { - background-image: url(arrowperms_sprite.png); + background-image: url(<%= asset_data_uri('arrowperms_sprite.png') %>); cursor: pointer; background-position: -32px 0; } .yourMap .mapPermission.minimize { - background-image: url(arrowperms_sprite.png) !important; + background-image: url(<%= asset_data_uri('arrowperms_sprite.png') %>) !important; cursor: pointer; background-position: 0 0; } @@ -1803,7 +1804,7 @@ h3.realtimeBoxTitle { width: 32px; height: 32px; background-repeat: no-repeat; - background-image: url(permissions32_sprite.png); + background-image: url(<%= asset_data_uri('permissions32_sprite.png') %>); } .mapInfoBox .mapPermission .permissionSelect .commons { background-position: 0 0; @@ -1850,7 +1851,7 @@ h3.realtimeBoxTitle { width: 340px; } .mapInfoBox.canEdit .mapInfoDesc:hover { - background-image: url(edit.png); + background-image: url(<%= asset_data_uri('edit.png') %>); background-position: top right; background-repeat: no-repeat; cursor: text; @@ -1895,7 +1896,7 @@ h3.realtimeBoxTitle { .mapInfoShareIcon { width: 24px; height: 24px; - background-image: url(share_sprite_mapinfo.png); + background-image: url(<%= asset_data_uri('share_sprite_mapinfo.png') %>); background-repeat: no-repeat; margin: 4px auto 0; background-position: 0 -24px; @@ -1916,7 +1917,7 @@ and it won't be important on password protected instances */ width: 16px; height: 16px; margin: 8px auto 0; - background-image: url(remove_mapinfo_sprite.png); + background-image: url(<%= asset_data_uri('remove_mapinfo_sprite.png') %>); background-repeat: no-repeat; background-position: -16px 0; } @@ -1977,7 +1978,7 @@ and it won't be important on password protected instances */ border: solid 2px #000; } #lightbox_overlay #lightbox_main a#lightbox_close { - background-image: url(xlightbox.png); + background-image: url(<%= asset_data_uri('xlightbox.png') %>); cursor: pointer; height: 32px; outline-style: none; @@ -2076,7 +2077,7 @@ and it won't be important on password protected instances */ color: #00bcd4; } .lightbox_links .lightboxAboutIcon { - background-image: url(about_sprite.png); + background-image: url(<%= asset_data_uri('about_sprite.png') %>); background-repeat: no-repeat; width:32px; height:32px; @@ -2145,6 +2146,7 @@ and it won't be important on password protected instances */ display: block; width: 100%; padding: 4px 0 !important; + outline: none; } .ui-tabs-vertical .ui-tabs-panel { padding: 0 !important; @@ -2181,6 +2183,7 @@ and it won't be important on password protected instances */ } #metacodeSwitchTabs li.ui-state-active a { color: #00BCD4; + cursor: pointer; } .metacodeSwitchTab { max-height: 300px; @@ -2351,15 +2354,15 @@ and it won't be important on password protected instances */ opacity: 0.7; } #chromeIcon { - background: url(/assets/browser_icons.png) no-repeat; + background: url(<%= asset_data_uri 'browser_icons.png' %>) no-repeat; } #fireFoxIcon { - background: url(/assets/browser_icons.png) no-repeat -105px 0; + background: url(<%= asset_data_uri 'browser_icons.png' %>) no-repeat -105px 0; } #safariIcon { - background: url(/assets/browser_icons.png) no-repeat -220px 0; + background: url(<%= asset_data_uri 'browser_icons.png' %>) no-repeat -220px 0; } #chromeIcon:hover{ @@ -2496,7 +2499,7 @@ and it won't be important on password protected instances */ position: relative; } .new_map .mapPermIcon { - background-image: url(permissions64sprite.png); + background-image: url(<%= asset_data_uri('permissions64sprite.png') %>); background-repeat: no-repeat; width:64px; height:64px; @@ -2611,7 +2614,7 @@ and it won't be important on password protected instances */ } #zoomIn { - background: #424242 url(zoom_sprite.png) no-repeat; + background: #424242 url(<%= asset_data_uri('zoom_sprite.png') %>) no-repeat; width: 22px; height: 22px; display: inline-block; @@ -2623,7 +2626,7 @@ and it won't be important on password protected instances */ } #zoomOut { - background: #424242 url(zoom_sprite.png) no-repeat; + background: #424242 url(<%= asset_data_uri('zoom_sprite.png') %>) no-repeat; width: 22px; height: 22px; display: inline-block; @@ -2635,7 +2638,7 @@ and it won't be important on password protected instances */ } #centerMap { - background: #424242 url(extents_sprite.png) no-repeat; + background: #424242 url(<%= asset_data_uri('extents_sprite.png') %>) no-repeat; width: 22px; height: 22px; display: inline-block; @@ -2940,7 +2943,7 @@ and it won't be important on password protected instances */ .compassArrow { display: none; background-repeat: no-repeat; - background-image: url(compass_arrow.png); + background-image: url(<%= asset_data_uri('compass_arrow.png') %>); width: 48px; height: 32px; position: absolute; diff --git a/app/assets/stylesheets/base.css b/app/assets/stylesheets/base.css.erb similarity index 88% rename from app/assets/stylesheets/base.css rename to app/assets/stylesheets/base.css.erb index 2e0073a0..216fa04d 100644 --- a/app/assets/stylesheets/base.css +++ b/app/assets/stylesheets/base.css.erb @@ -85,7 +85,7 @@ padding: 0 16px; } .canEdit #titleActivator:hover { - background-image: url(edit.png); + background-image: url(<%= asset_data_uri('edit.png') %>); background-repeat: no-repeat; background-position: bottom right; cursor: text; @@ -148,7 +148,7 @@ margin-right: 8px; } .canEdit .CardOnGraph .best_in_place_desc:hover { - background-image: url(edit.png); + background-image: url(<%= asset_data_uri('edit.png') %>); background-position: top right; background-repeat: no-repeat; cursor: text; @@ -244,7 +244,7 @@ left: 0; width: 32px; height: 32px; - background-image: url(map32_sprite.png); + background-image: url(<%= asset_data_uri('map32_sprite.png') %>); background-repeat: no-repeat; background-position: 0 0; cursor: pointer; @@ -335,7 +335,7 @@ left: 0; width: 32px; height: 32px; - background-image: url(synapse32_sprite.png); + background-image: url(<%= asset_data_uri('synapse32_sprite.png') %>); background-repeat: no-repeat; background-position: 0 0; } @@ -382,7 +382,7 @@ min-width: 32px; margin-top: 8px; margin-left: 8px; - background-image: url(permissions32_sprite.png); + background-image: url(<%= asset_data_uri('permissions32_sprite.png') %>); background-position: 0 0; } .mapPerm.co { @@ -396,12 +396,12 @@ } .yourTopic .mapPerm:hover, .yourEdge .mapPerm:hover { - background-image: url(arrowperms_sprite.png); + background-image: url(<%= asset_data_uri('arrowperms_sprite.png') %>); background-position: -32px 0; cursor:pointer; } .yourTopic .mapPerm.minimize, .yourEdge .mapPerm.minimize { - background-image: url(arrowperms_sprite.png) !important; + background-image: url(<%= asset_data_uri('arrowperms_sprite.png') %>) !important; background-position: 0 0; cursor: pointer; } @@ -417,7 +417,7 @@ cursor: pointer; height: 32px; background-repeat: no-repeat; background-position: 0 0; - background-image: url(permissions32_sprite.png); + background-image: url(<%= asset_data_uri('permissions32_sprite.png') %>); } .mapPerm .permissionSelect .commons { background-position: 0 0; @@ -462,7 +462,7 @@ cursor: pointer; right: 16px; width: 16px; height: 16px; - background-image: url(arrowright_sprite.png); + background-image: url(<%= asset_data_uri('arrowright_sprite.png') %>); background-repeat: no-repeat; background-position: 0 -32px; } @@ -542,7 +542,7 @@ background-color: #E0E0E0; right: 8px; width: 16px; height: 16px; - background-image: url(arrowright_sprite.png); + background-image: url(<%= asset_data_uri('arrowright_sprite.png') %>); background-repeat: no-repeat; background-position: 0 -32px; } @@ -648,10 +648,10 @@ background-color: #E0E0E0; left: 12px; } #linkIcon { - background-image: url(link_sprite.png); + background-image: url(<%= asset_data_uri('link_sprite.png') %>); } #uploadIcon { - background-image: url(upload_sprite.png); + background-image: url(<%= asset_data_uri('upload_sprite.png') %>); } #addlink:hover #linkIcon, #addupload:hover #uploadIcon { background-position: 0 -24px; @@ -690,7 +690,7 @@ font-family: 'din-regular', helvetica, sans-serif; height: 24px; background-repeat: no-repeat; background-position: 0 0; - background-image: url(link_sprite.png); + background-image: url(<%= asset_data_uri('link_sprite.png') %>); pointer-events: none; z-index: 1; } @@ -703,7 +703,7 @@ font-family: 'din-regular', helvetica, sans-serif; height: 32px; cursor: pointer; float:none; - background-image: url(remove.png); + background-image: url(<%= asset_data_uri('remove.png') %>); background-repeat: no-repeat; background-position: center center; } @@ -740,7 +740,7 @@ font-family: 'din-regular', helvetica, sans-serif; } #linkremove { - background-image: url(/assets/remove.png); + background-image: url(<%= asset_data_uri 'remove.png' %>); background-repeat: no-repeat; background-position: center center; width: 24px; @@ -781,7 +781,7 @@ font-family: 'din-regular', helvetica, sans-serif; } #editSynUpperBar { - background: #FFFFFF url(synapse32.png) no-repeat 8px center; + background: #FFFFFF url(<%= asset_data_uri('synapse32.png') %>) no-repeat 8px center; min-height: 48px; height: 48px; border-bottom: 1px solid #222222; @@ -814,7 +814,7 @@ font-family: 'din-regular', helvetica, sans-serif; } .canEdit #edit_synapse_desc:hover { - background-image: url(edit.png); + background-image: url(<%= asset_data_uri('edit.png') %>); background-repeat: no-repeat; background-position: 164px center; cursor: text; @@ -841,7 +841,7 @@ font-family: 'din-regular', helvetica, sans-serif; height: 24px; top: 12px; right: 8px; - background-image: url(arrowdown_sprite.png); + background-image: url(<%= asset_data_uri('arrowdown_sprite.png') %>); background-repeat: no-repeat; background-position: 4px -12px; } @@ -923,11 +923,11 @@ font-family: 'din-regular', helvetica, sans-serif; background-repeat: no-repeat; } #edit_synapse_right { - background-image: url(synapsedirectionright_sprite.png); + background-image: url(<%= asset_data_uri('synapsedirectionright_sprite.png') %>); right: 16px; } #edit_synapse_left { - background-image: url(synapsedirectionleft_sprite.png); + background-image: url(<%= asset_data_uri('synapsedirectionleft_sprite.png') %>); right: 56px; } #edit_synapse_left.checked, #edit_synapse_right.checked { @@ -1103,7 +1103,7 @@ font-family: 'din-regular', helvetica, sans-serif; } .mapperMetadata { - background: url(profile_card_sprite.png) no-repeat center 0; + background: url(<%= asset_data_uri('profile_card_sprite.png') %>) no-repeat center 0; height: 64px; width: 160px; margin: 16px auto 0; @@ -1126,4 +1126,4 @@ font-family: 'din-regular', helvetica, sans-serif; } .mapperMetadata .metadataSynapses { color: #DAB539; -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/clean.css b/app/assets/stylesheets/clean.css.erb similarity index 54% rename from app/assets/stylesheets/clean.css rename to app/assets/stylesheets/clean.css.erb index 4ecd3120..8c3bd7dd 100644 --- a/app/assets/stylesheets/clean.css +++ b/app/assets/stylesheets/clean.css.erb @@ -1,20 +1,20 @@ @font-face { font-family: 'din-medium'; - src: url('/assets/Fonts/din.eot'); - src: url('/assets/Fonts/din.eot?#iefix') format('embedded-opentype'), - url('/assets/Fonts/din.woff') format('woff'), - url('/assets/Fonts/din.ttf') format('truetype'), - url('/assets/Fonts/din.svg#din-medium') format('svg'); + src: url(<%= asset_path 'Fonts/din.eot' %>); + src: url(<%= asset_path 'Fonts/din.eot?#iefix' %>) format('embedded-opentype'), + url(<%= asset_path 'Fonts/din.woff' %>) format('woff'), + url(<%= asset_path 'Fonts/din.ttf' %>) format('truetype'), + url(<%= asset_path 'Fonts/din.svg#din-medium' %>) format('svg'); font-weight: normal; font-style: normal; } @font-face { font-family: 'din-regular'; - src: url('/assets/Fonts/din-reg.eot'); - src: url('/assets/Fonts/din-reg.eot?#iefix') format('embedded-opentype'), - url('/assets/Fonts/din-reg.woff') format('woff'), - url('/assets/Fonts/din-reg.ttf') format('truetype'), - url('/assets/Fonts/din-reg.svg#din-reg') format('svg'); + src: url(<%= asset_path 'Fonts/din-reg.eot' %>); + src: url(<%= asset_path 'Fonts/din-reg.eot?#iefix' %>) format('embedded-opentype'), + url(<%= asset_path 'Fonts/din-reg.woff' %>) format('woff'), + url(<%= asset_path 'Fonts/din-reg.ttf' %>) format('truetype'), + url(<%= asset_path 'Fonts/din-reg.svg#din-reg' %>) format('svg'); font-weight: normal; font-style: normal; } @@ -134,7 +134,7 @@ width: 40px; height: 32px; background-color: #757575; - background-image: url(home_light.png); + background-image: url(<%= asset_data_uri('home_light.png') %>); background-repeat: no-repeat; background-position: center center; border-top-left-radius: 2px; @@ -142,7 +142,7 @@ float:left; } .homeButton:hover { - background-image: url(home_light.png); + background-image: url(<%= asset_data_uri('home_light.png') %>); } .homeButton a { display:block; @@ -150,470 +150,6 @@ height: 32px; } -/* search */ - -.sidebarSearch { - float:left; - height: 32px; - position: relative; -} - -#searchLoading { - height: 24px; - width: 24px; - position: absolute; - top: 4px; - right: 76px; - display: none; -} - -.unauthenticated .homePage .sidebarSearchIcon { - border-radius: 2px; -} -.sidebarSearchIcon { - float: left; - width: 72px; - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; - height: 32px; - background: #4fb5c0 url('search.png') no-repeat center center; - background-size: 32px 32px; - cursor: pointer; -} -.sidebarSearch .twitter-typeahead, .sidebarSearch .sidebarSearchField { - float: left; -} - -.unauthenticated .homePage .sidebarSearchField, -.unauthenticated .homePage .sidebarSearch .tt-hint { - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; -} -.explorePage .sidebarSearchField, -.explorePage .sidebarSearch .tt-hint { - width: 380px; - padding: 5px 10px 5px 10px; -} - -.sidebarSearchField { - color: #424242; -} -.sidebarSearch .tt-hint { - color: transparent; -} -.sidebarSearchField, -.sidebarSearch .tt-hint { - height: 20px; - border-top: 1px solid #BDBDBD; - border-bottom: 1px solid #BDBDBD; - border-left: none; - border-right: none; - padding: 5px 0 5px 0; - width: 0px; - margin: 0; - outline: none; - font-size: 14px; - line-height: 14px; - background: #F5F5F5; - font-family: 'din-medium', helvetica, sans-serif; -} -.sidebarSearch .tt-dropdown-menu { - top: 40px !important; - background: #F5F5F5; - width: 472px; - overflow-y: auto; - overflow-x: visible; - box-shadow: 0 10px 10px rgba(0,0,0,0.19), 0 6px 3px rgba(0,0,0,0.23); -} - -.autoOptions #mapContribs { - width: 15px; - height: 15px; - border: 1px solid #424242; - margin-top: 4px; - margin-left: 4px; -} - -.mapContributorsIcon span { - margin-left: 5px; -} - -.mapContributorsIcon li span { - margin-left: 10px; -} - - -.searchHeader { - height: 42px; - width: 100%; - position: relative; -} -.searchTopicsHeader { - background: #4fc4a8; -} -.searchMapsHeader { - background: #994fc0; -} -.searchMappersHeader { - background: #c04f4f; -} -.sidebarSearch .tt-dropdown-menu h3 { - text-transform: uppercase; - color: #F5F5F5; - font-size: 18px; - line-height: 18px; - margin: 12px 0 3px 16px; - float: left; -} -.sidebarSearch .tt-dropdown-menu .limitToMe { - float: left; - width: 12px; - height: 12px; - border: 1px; - color: #000000; - position: absolute; - top: 15px; - left: 136px; -} -.sidebarSearch .tt-dropdown-menu .limitToMeLabel { - float: left; - font-family: 'din-medium', helvetica, sans-serif; - font-size: 12px; - color: #f5f5f5; - margin: 0; - position: absolute; - top: 15px; - left: 156px; -} -.sidebarSearch .tt-dropdown-menu .minimizeResults, .sidebarSearch .tt-dropdown-menu .maximizeResults { - width: 32px; - height: 32px; - background-image: url(arrowpermswhite_sprite.png); - background-repeat: no-repeat; - cursor: pointer; - position: absolute; - top: 5px; - left: 410px; -} -.sidebarSearch .tt-dropdown-menu .minimizeResults { - background-position: 0 0; -} -.sidebarSearch .tt-dropdown-menu .maximizeResults { - background-position: -32px 0; -} -.sidebarSearch .tt-suggestions { - overflow: visible; -} -.sidebarSearch .tt-suggestion { - position: relative; - background: #FFF; - padding: 8px 0; -} -.sidebarSearch .tt-is-under-cursor, -.sidebarSearch .tt-is-under-mouse-cursor { - background: #E0E0E0; -} - -.resultmap, .resulttopic, .resultmapper, .resultnoresult { - min-height: 48px; - display: table; -} -/*.sidebarSearch .tt-dataset-maps .tt-is-under-cursor .resultmap, -.sidebarSearch .tt-dataset-maps .tt-is-under-mouse-cursor .resultmap, -.sidebarSearch .tt-dataset-topics .tt-is-under-cursor .resulttopic, -.sidebarSearch .tt-dataset-topics .tt-is-under-mouse-cursor .resulttopic { - min-height: 48px; -}*/ -.sidebarSearch .tt-suggestion .searchResIconWrapper { - display: table-cell; - vertical-align: middle; - height: 32px; - padding: 0 18px 0 28px; -} -.sidebarSearch .tt-suggestion .icon { - width: 32px; - height: 32px; - border-radius:16px; -} -.sidebarSearch .topicMetacode { - display: table-cell; - vertical-align: middle; - padding: 0 0 0 8px; - width: 70px; -} -.sidebarSearch .tt-dataset-topics .topicIcon { - width: 32px; - height: 32px; - margin: 0 auto; -} -.sidebarSearch .tt-dataset-topics .metacodeTip { - display: none; - margin: 0 auto; -} -.sidebarSearch .tt-dataset-topics .tt-is-under-cursor .metacodeTip, -.sidebarSearch .tt-dataset-topics .tt-is-under-mouse-cursor .metacodeTip { - display: block; - font-family: 'vinyl'; - text-transform: uppercase; - font-style: italic; - font-size: 13px; - margin: 0 5px 0 2px; - text-align: center; -} -.sidebarSearch .tt-dataset-mappers .tt-suggestion .icon { - margin: 0px 0px 0px 0px; -} -.sidebarSearch .tt-dataset-mappers .resultText { - width: 150px; -} - -.sidebarSearch .resultText { - width: 260px; - display: table-cell; - padding-left: 8px; - vertical-align: middle; - word-wrap: break-word; -} -.sidebarSearch .resultTitle { - font-weight: normal; - font-size: 16px; - line-height: 20px; - width: 100%; - font-family: 'din-regular', helvetica, sans-serif; -} -.sidebarSearch .resultDesc { - font-size: 12px; - line-height: 16px; - width: 100%; - font-style: italic; - font-family: helvetica, sans-serif; -} -.sidebarSearch .tip { - display: none; -} -.sidebarSearch div.autoOptions { - width: 114px; - height: 48px; - position: absolute; - display: none; - top: 8px; - right: 0; -} -.tt-dataset-maps div.autoOptions { - width: 84px; -} -.sidebarSearch .tt-dataset-mappers .autoOptions { - width: 235px; -} -.sidebarSearch .tt-is-under-cursor .autoOptions, -.sidebarSearch .tt-is-under-mouse-cursor .autoOptions { - display: block; -} -.sidebarSearch .tt-suggestion .resultnoresult .autoOptions { - display: none; -} -.sidebarSearch .autoOptions button, -.sidebarSearch .autoOptions a, -.sidebarSearch .autoOptions div { - position: absolute; - padding: 0; - margin: 0; - border: none; - outline: none; -} -.sidebarSearch button.addToMap { - display:none; - width: 24px; - height: 24px; - background: url(addtopic_sprite.png); - background-repeat: no-repeat; - background-size: 48px 24px; - top: 12px; - left: 80px; - cursor: pointer; -} -.canEditMap button.addToMap { - display: block; -} -.sidebarSearch button.addToMap:hover { - background-position: -24px; -} - -.sidebarSearch div.topicCount { - width: 24px; - height: 24px; - background: url(topic16.png); - background-repeat: no-repeat; - background-position: 0 center; - top: 0; - left: 0; - padding-left: 18px; - font-size: 12px; - line-height: 24px; -} - -.sidebarSearch div.mapCount { - width: 24px; - height: 24px; - background: url(metamap16.png); - background-repeat: no-repeat; - background-position: 0 center; - left: 0; - padding-left: 20px; - font-size: 12px; - line-height: 24px; -} -.sidebarSearch div.synapseCount { - width: 24px; - height: 24px; - background: url(synapse16.png); - background-repeat: no-repeat; - background-position: 0 center; - top: 24px; - left: 0; - padding-left: 20px; - font-size: 12px; - line-height: 24px; -} -.sidebarSearch div.topicOriginatorIcon { - width: 18px; - height: 18px; - padding: 3px; - top: 0; - left: 44px; -} -.sidebarSearch .topicOriginatorIcon img { - border-radius: 9px; -} - -.sidebarSearch .topicOriginatorIcon .tip { - right: 30px; - top: 1px; -} -.sidebarSearch .tip { - position: absolute; - background: #424242; - width: auto; - top: 2px; - right: 25px; - color: white; - white-space: nowrap; - border-radius: 2px; - font-size: 12px !important; - font-family: 'din-regular'; - line-height: 12px; - padding: 4px 4px 4px; - z-index: 100; -} -.sidebarSearch .hoverForTip:hover .tip { - display: block; -} - -.sidebarSearch .mapContributorsIcon .tip { - right: 40px; - top: -5px; - padding-top: 5px; - padding-bottom: 5px; -} - -.sidebarSearch .hoverForTip .tip li { - padding-left: 28px; - padding-top: 4px; -} - -.tipUserImage { - position: absolute; - top: 0px; - left: 7px; - border-radius: 14px; -} - -.sidebarSearch .hoverForTip .tip:before { - content: ''; - position: absolute; - width: 0; - height: 0; - border-left: 4px solid #424242; - border-top: 5px solid transparent; - border-bottom: 5px solid transparent; -} - -.sidebarSearch .hoverForTip.addToMap .tip { - right: 30px; -} -.sidebarSearch .hoverForTip.addToMap .tip:before { - right: -4px; -} - -.sidebarSearch .mapContributorsIcon .tip:before { - top: 12px; - right: -4px; -} - -.sidebarSearch .topicOriginatorIcon .tip:before { - top: 5px; - right: -4px; -} - -.sidebarSearch .mapContributorsIcon .mapContributors { - top: auto; - right: 0; - bottom: 21px; - white-space: normal; - width: 200px; -} -.sidebarSearch div.mapContributorsIcon { - height: 24px; - top: 0; - left: 44px; - font-size: 12px; - line-height: 24px; -} -.sidebarSearch div.topicPermission, -.sidebarSearch div.mapPermission { - width: 24px; - height: 24px; - background-image: url(permissions32_sprite.png); - background-repeat: no-repeat; - background-size: 72px 48px !important; - top: 24px; - left: 44px; -} -.sidebarSearch div.topicPermission.commons, -.sidebarSearch div.mapPermission.commons { - background-position: 0 0; -} -.sidebarSearch div.topicPermission.public, -.sidebarSearch div.mapPermission.public { - background-position: -48px 0; -} -.sidebarSearch div.topicPermission.private, -.sidebarSearch div.mapPermission.private { - background-position: -24px 0; -} - -.sidebarSearch .tt-dataset-mappers div.mapCount { - top: 8px; - left: 170px; -} -.sidebarSearch .tt-dataset-mappers div.mapperCreated { - left: 0px; - padding-left: 0px; - font-size: 12px; - font-family: 'din-medium', helvetica, sans-serif; - line-height: 24px; -} -.sidebarSearch .tt-dataset-mappers div.mapperGeneration { - top: 20px; - left: 0px; - padding-left: 0px; - font-size: 12px; - font-family: 'din-medium', helvetica, sans-serif; - line-height: 24px; -} - -/* end search */ - /* end upperLeftUI */ /* upperRightUI */ @@ -665,7 +201,7 @@ .upperRightIcon { width: 32px; height: 32px; - background-image: url('topright_sprite.png'); + background-image: url(<%= asset_data_uri('topright_sprite.png') %>); background-repeat: no-repeat; cursor: pointer; } @@ -816,12 +352,12 @@ } .fullWidthWrapper.withPartners { - background: url(homepage_bg_fade.png) no-repeat center -300px; + background: url(<%= asset_data_uri('homepage_bg_fade.png') %>) no-repeat center -300px; } .homeWrapper.homePartners { padding: 64px 0 280px; height: 96px; - background: url(partner_logos.png) no-repeat 0 64px; + background: url(<%= asset_data_uri('partner_logos.png') %>) no-repeat 0 64px; } .github-fork-ribbon-wrapper { @@ -863,7 +399,7 @@ cursor: pointer; } .openCheatsheet { - background-image: url('help_sprite.png'); + background-image: url(<%= asset_data_uri('help_sprite.png') %>); background-repeat:no-repeat; } .openCheatsheet:hover { @@ -872,7 +408,7 @@ .mapInfoIcon { position: relative; top: 56px; /* puts it just offscreen */ - background-image: url('mapinfo_sprite.png'); + background-image: url(<%= asset_data_uri('mapinfo_sprite.png') %>); background-repeat:no-repeat; } .mapInfoIcon:hover { @@ -918,7 +454,7 @@ .takeScreenshot { margin-bottom: 5px; border-radius: 2px; - background-image: url(screenshot_sprite.png); + background-image: url(<%= asset_data_uri 'screenshot_sprite.png' %>); display: none; } .takeScreenshot:hover { @@ -931,7 +467,7 @@ .zoomExtents { margin-bottom:5px; border-radius: 2px; - background-image: url(extents_sprite.png); + background-image: url(<%= asset_data_uri('extents_sprite.png') %>); } .zoomExtents:hover { @@ -1076,7 +612,7 @@ } .zoomIn { - background-image: url(zoom_sprite.png); + background-image: url(<%= asset_data_uri('zoom_sprite.png') %>); background-position: 0 /…0; border-top-left-radius: 2px; border-top-right-radius: 2px; @@ -1085,7 +621,7 @@ background-position: -32px 0; } .zoomOut { - background-image: url(zoom_sprite.png); + background-image: url(<%= asset_data_uri('zoom_sprite.png') %>); background-position:0 -32px; border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; @@ -1163,15 +699,15 @@ left:5px; } .exploreMapsCenter .myMaps .exploreMapsIcon { - background-image: url(exploremaps_sprite.png); + background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>); background-position: 0 0; } .exploreMapsCenter .activeMaps .exploreMapsIcon { - background-image: url(exploremaps_sprite.png); + background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>); background-position: -32px 0; } .exploreMapsCenter .featuredMaps .exploreMapsIcon { - background-image: url(exploremaps_sprite.png); + background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>); background-position: -64px 0; } .myMaps:hover .exploreMapsIcon, .myMaps.active .exploreMapsIcon { @@ -1212,7 +748,7 @@ /* feedback */ body a#barometer_tab { -background-image: url(feedback_sprite.png); +background-image: url(<%= asset_path 'feedback_sprite.png' %>); background-position: 0 0; background-color: transparent; height: 110px; @@ -1227,4 +763,4 @@ box-shadow: 0px 1px 1.5px rgba(0,0,0,0.12), 0 1px 1px rgba(0,0,0,0.24); body a#barometer_tab:hover { background-position: 0 -110px; -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/jquery-ui.css b/app/assets/stylesheets/jquery-ui.css.erb similarity index 88% rename from app/assets/stylesheets/jquery-ui.css rename to app/assets/stylesheets/jquery-ui.css.erb index c89a95ff..88113a6e 100644 --- a/app/assets/stylesheets/jquery-ui.css +++ b/app/assets/stylesheets/jquery-ui.css.erb @@ -243,25 +243,25 @@ body .ui-tooltip { border-width: 2px; } .ui-widget { font-family: Verdana,Arial,sans-serif/*{ffDefault}*/; font-size: 1.1em/*{fsDefault}*/; } .ui-widget .ui-widget { font-size: 1em; } .ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif/*{ffDefault}*/; font-size: 1em; } -.ui-widget-content { border: 1px solid #aaaaaa/*{borderColorContent}*/; background: #ffffff/*{bgColorContent}*/ url(ui-bg_flat_75_ffffff_40x100.png)/*{bgImgUrlContent}*/ 50%/*{bgContentXPos}*/ 50%/*{bgContentYPos}*/ repeat-x/*{bgContentRepeat}*/; color: #222222/*{fcContent}*/; } +.ui-widget-content { border: 1px solid #aaaaaa/*{borderColorContent}*/; background: #ffffff/*{bgColorContent}*/ url(<%= asset_data_uri('ui-bg_flat_75_ffffff_40x100.png') %>)/*{bgImgUrlContent}*/ 50%/*{bgContentXPos}*/ 50%/*{bgContentYPos}*/ repeat-x/*{bgContentRepeat}*/; color: #222222/*{fcContent}*/; } .ui-widget-content a { color: #222222/*{fcContent}*/; } -.ui-widget-header { border: 1px solid #aaaaaa/*{borderColorHeader}*/; background: #cccccc/*{bgColorHeader}*/ url(images/ui-bg_highlight-soft_75_cccccc_1x100.png)/*{bgImgUrlHeader}*/ 50%/*{bgHeaderXPos}*/ 50%/*{bgHeaderYPos}*/ repeat-x/*{bgHeaderRepeat}*/; color: #222222/*{fcHeader}*/; font-weight: bold; } +.ui-widget-header { border: 1px solid #aaaaaa/*{borderColorHeader}*/; background: #cccccc/*{bgColorHeader}*/ url(<%= asset_data_uri('images/ui-bg_highlight-soft_75_cccccc_1x100.png') %>)/*{bgImgUrlHeader}*/ 50%/*{bgHeaderXPos}*/ 50%/*{bgHeaderYPos}*/ repeat-x/*{bgHeaderRepeat}*/; color: #222222/*{fcHeader}*/; font-weight: bold; } .ui-widget-header a { color: #222222/*{fcHeader}*/; } /* Interaction states ----------------------------------*/ -.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3/*{borderColorDefault}*/; background: #e6e6e6/*{bgColorDefault}*/ url(images/ui-bg_glass_75_e6e6e6_1x400.png)/*{bgImgUrlDefault}*/ 50%/*{bgDefaultXPos}*/ 50%/*{bgDefaultYPos}*/ repeat-x/*{bgDefaultRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #555555/*{fcDefault}*/; } +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3/*{borderColorDefault}*/; background: #e6e6e6/*{bgColorDefault}*/ url(<%= asset_data_uri('images/ui-bg_glass_75_e6e6e6_1x400.png') %>)/*{bgImgUrlDefault}*/ 50%/*{bgDefaultXPos}*/ 50%/*{bgDefaultYPos}*/ repeat-x/*{bgDefaultRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #555555/*{fcDefault}*/; } .ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555/*{fcDefault}*/; text-decoration: none; } -.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 0px solid #999999/*{borderColorHover}*/; background: #dadada/*{bgColorHover}*/ url(images/ui-bg_glass_75_dadada_1x400.png)/*{bgImgUrlHover}*/ 50%/*{bgHoverXPos}*/ 50%/*{bgHoverYPos}*/ repeat-x/*{bgHoverRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #212121/*{fcHover}*/; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 0px solid #999999/*{borderColorHover}*/; background: #dadada/*{bgColorHover}*/ url(<%= asset_data_uri('images/ui-bg_glass_75_dadada_1x400.png') %>)/*{bgImgUrlHover}*/ 50%/*{bgHoverXPos}*/ 50%/*{bgHoverYPos}*/ repeat-x/*{bgHoverRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #212121/*{fcHover}*/; } .ui-state-hover a, .ui-state-hover a:hover, .ui-state-hover a:link, .ui-state-hover a:visited { color: #212121/*{fcHover}*/; text-decoration: none; } -.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa/*{borderColorActive}*/; background: #ffffff/*{bgColorActive}*/ url(images/ui-bg_glass_65_ffffff_1x400.png)/*{bgImgUrlActive}*/ 50%/*{bgActiveXPos}*/ 50%/*{bgActiveYPos}*/ repeat-x/*{bgActiveRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #212121/*{fcActive}*/; } +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa/*{borderColorActive}*/; background: #ffffff/*{bgColorActive}*/ url(<%= asset_data_uri('images/ui-bg_glass_65_ffffff_1x400.png') %>)/*{bgImgUrlActive}*/ 50%/*{bgActiveXPos}*/ 50%/*{bgActiveYPos}*/ repeat-x/*{bgActiveRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #212121/*{fcActive}*/; } .ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121/*{fcActive}*/; text-decoration: none; } /* Interaction Cues ----------------------------------*/ -.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fcefa1/*{borderColorHighlight}*/; background: #fbf9ee/*{bgColorHighlight}*/ url(images/ui-bg_glass_55_fbf9ee_1x400.png)/*{bgImgUrlHighlight}*/ 50%/*{bgHighlightXPos}*/ 50%/*{bgHighlightYPos}*/ repeat-x/*{bgHighlightRepeat}*/; color: #363636/*{fcHighlight}*/; } +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fcefa1/*{borderColorHighlight}*/; background: #fbf9ee/*{bgColorHighlight}*/ url(<%= asset_data_uri('images/ui-bg_glass_55_fbf9ee_1x400.png') %>)/*{bgImgUrlHighlight}*/ 50%/*{bgHighlightXPos}*/ 50%/*{bgHighlightYPos}*/ repeat-x/*{bgHighlightRepeat}*/; color: #363636/*{fcHighlight}*/; } .ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636/*{fcHighlight}*/; } -.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a/*{borderColorError}*/; background: #fef1ec/*{bgColorError}*/ url(images/ui-bg_glass_95_fef1ec_1x400.png)/*{bgImgUrlError}*/ 50%/*{bgErrorXPos}*/ 50%/*{bgErrorYPos}*/ repeat-x/*{bgErrorRepeat}*/; color: #cd0a0a/*{fcError}*/; } +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a/*{borderColorError}*/; background: #fef1ec/*{bgColorError}*/ url(<%= asset_data_uri('images/ui-bg_glass_95_fef1ec_1x400.png') %>)/*{bgImgUrlError}*/ 50%/*{bgErrorXPos}*/ 50%/*{bgErrorYPos}*/ repeat-x/*{bgErrorRepeat}*/; color: #cd0a0a/*{fcError}*/; } .ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a/*{fcError}*/; } .ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a/*{fcError}*/; } .ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } @@ -273,14 +273,14 @@ body .ui-tooltip { border-width: 2px; } ----------------------------------*/ /* states and images */ -.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png)/*{iconsContent}*/; } -.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png)/*{iconsContent}*/; } -.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png)/*{iconsHeader}*/; } -.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png)/*{iconsDefault}*/; } -.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png)/*{iconsHover}*/; } -.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png)/*{iconsActive}*/; } -.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png)/*{iconsHighlight}*/; } -.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png)/*{iconsError}*/; } +.ui-icon { width: 16px; height: 16px; background-image: url(<%= asset_data_uri('images/ui-icons_222222_256x240.png') %>)/*{iconsContent}*/; } +.ui-widget-content .ui-icon {background-image: url(<%= asset_data_uri('images/ui-icons_222222_256x240.png') %>)/*{iconsContent}*/; } +.ui-widget-header .ui-icon {background-image: url(<%= asset_data_uri('images/ui-icons_222222_256x240.png') %>)/*{iconsHeader}*/; } +.ui-state-default .ui-icon { background-image: url(<%= asset_data_uri('images/ui-icons_888888_256x240.png') %>)/*{iconsDefault}*/; } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(<%= asset_data_uri('images/ui-icons_454545_256x240.png') %>)/*{iconsHover}*/; } +.ui-state-active .ui-icon {background-image: url(<%= asset_data_uri('images/ui-icons_454545_256x240.png') %>)/*{iconsActive}*/; } +.ui-state-highlight .ui-icon {background-image: url(<%= asset_data_uri('images/ui-icons_2e83ff_256x240.png') %>)/*{iconsHighlight}*/; } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(<%= asset_data_uri('images/ui-icons_cd0a0a_256x240.png') %>)/*{iconsError}*/; } /* positioning */ .ui-icon-carat-1-n { background-position: 0 0; } @@ -470,5 +470,5 @@ body .ui-tooltip { border-width: 2px; } .ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px/*{cornerRadius}*/; -webkit-border-bottom-right-radius: 4px/*{cornerRadius}*/; -khtml-border-bottom-right-radius: 4px/*{cornerRadius}*/; border-bottom-right-radius: 4px/*{cornerRadius}*/; } /* Overlays */ -.ui-widget-overlay { background: #aaaaaa/*{bgColorOverlay}*/ url(images/ui-bg_flat_0_aaaaaa_40x100.png)/*{bgImgUrlOverlay}*/ 50%/*{bgOverlayXPos}*/ 50%/*{bgOverlayYPos}*/ repeat-x/*{bgOverlayRepeat}*/; opacity: .3;filter:Alpha(Opacity=30)/*{opacityOverlay}*/; } -.ui-widget-shadow { margin: -8px/*{offsetTopShadow}*/ 0 0 -8px/*{offsetLeftShadow}*/; padding: 8px/*{thicknessShadow}*/; background: #aaaaaa/*{bgColorShadow}*/ url(images/ui-bg_flat_0_aaaaaa_40x100.png)/*{bgImgUrlShadow}*/ 50%/*{bgShadowXPos}*/ 50%/*{bgShadowYPos}*/ repeat-x/*{bgShadowRepeat}*/; opacity: .3;filter:Alpha(Opacity=30)/*{opacityShadow}*/; -moz-border-radius: 8px/*{cornerRadiusShadow}*/; -khtml-border-radius: 8px/*{cornerRadiusShadow}*/; -webkit-border-radius: 8px/*{cornerRadiusShadow}*/; border-radius: 8px/*{cornerRadiusShadow}*/; } +.ui-widget-overlay { background: #aaaaaa/*{bgColorOverlay}*/ url(<%= asset_data_uri('images/ui-bg_flat_0_aaaaaa_40x100.png') %>)/*{bgImgUrlOverlay}*/ 50%/*{bgOverlayXPos}*/ 50%/*{bgOverlayYPos}*/ repeat-x/*{bgOverlayRepeat}*/; opacity: .3;filter:Alpha(Opacity=30)/*{opacityOverlay}*/; } +.ui-widget-shadow { margin: -8px/*{offsetTopShadow}*/ 0 0 -8px/*{offsetLeftShadow}*/; padding: 8px/*{thicknessShadow}*/; background: #aaaaaa/*{bgColorShadow}*/ url(images/<%= asset_data_uri('ui-bg_flat_0_aaaaaa_40x100.png') %>)/*{bgImgUrlShadow}*/ 50%/*{bgShadowXPos}*/ 50%/*{bgShadowYPos}*/ repeat-x/*{bgShadowRepeat}*/; opacity: .3;filter:Alpha(Opacity=30)/*{opacityShadow}*/; -moz-border-radius: 8px/*{cornerRadiusShadow}*/; -khtml-border-radius: 8px/*{cornerRadiusShadow}*/; -webkit-border-radius: 8px/*{cornerRadiusShadow}*/; border-radius: 8px/*{cornerRadiusShadow}*/; } diff --git a/app/assets/stylesheets/jquery.mCustomScrollbar.css b/app/assets/stylesheets/jquery.mCustomScrollbar.css.erb similarity index 96% rename from app/assets/stylesheets/jquery.mCustomScrollbar.css rename to app/assets/stylesheets/jquery.mCustomScrollbar.css.erb index e318b29c..e8588f35 100644 --- a/app/assets/stylesheets/jquery.mCustomScrollbar.css +++ b/app/assets/stylesheets/jquery.mCustomScrollbar.css.erb @@ -162,7 +162,6 @@ .mCSB_scrollTools .mCSB_buttonDown, .mCSB_scrollTools .mCSB_buttonLeft, .mCSB_scrollTools .mCSB_buttonRight{ - background-image:url(mCSB_buttons.png); background-repeat:no-repeat; opacity:0.4; filter:"alpha(opacity=40)"; -ms-filter:"alpha(opacity=40)"; /* old ie */ @@ -471,4 +470,4 @@ } .mCS-dark-thin>.mCSB_scrollTools .mCSB_buttonRight{ background-position:-80px -56px; -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/search.scss.erb b/app/assets/stylesheets/search.scss.erb new file mode 100644 index 00000000..0f806f96 --- /dev/null +++ b/app/assets/stylesheets/search.scss.erb @@ -0,0 +1,492 @@ +#searchLoading { + height: 24px; + width: 24px; + position: absolute; + top: 4px; + right: 76px; + display: none; +} + +.unauthenticated { + .homePage .sidebarSearchIcon { + border-radius: 2px; + } + + .homePage .sidebarSearchField, + .homePage .sidebarSearch .tt-hint { + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; + } +} + +.sidebarSearchIcon { + float: left; + width: 72px; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + height: 32px; + background: #4fb5c0 url(<%= asset_data_uri('search.png') %>) no-repeat center center; + background-size: 32px 32px; + cursor: pointer; +} + +.autoOptions #mapContribs { + width: 15px; + height: 15px; + border: 1px solid #424242; + margin-top: 4px; + margin-left: 4px; +} + +.mapContributorsIcon span { + margin-left: 5px; +} + +.mapContributorsIcon li span { + margin-left: 10px; +} + +.searchHeader { + height: 42px; + width: 100%; + position: relative; +} +.searchTopicsHeader { + background: #4fc4a8; +} +.searchMapsHeader { + background: #994fc0; +} +.searchMappersHeader { + background: #c04f4f; +} + +.resultmap, .resulttopic, .resultmapper, .resultnoresult { + min-height: 48px; + display: table; + width: 100%; +} + +.canEditMap button.addToMap { + display: block; +} + +.tipUserImage { + position: absolute; + top: 0px; + left: 7px; + border-radius: 14px; +} + +/* main search selector */ + +.sidebarSearch { + float:left; + height: 32px; + position: relative; + + .twitter-typeahead { + float: left; + } + + .sidebarSearchField { + float: left; + height: 20px; + border-top: 1px solid #BDBDBD; + border-bottom: 1px solid #BDBDBD; + border-left: none; + border-right: none; + padding: 5px 0 5px 0; + width: 0px; + margin: 0; + outline: none; + font-size: 14px; + line-height: 14px; + background: #F5F5F5; + font-family: 'din-medium', helvetica, sans-serif; + color: #424242; + } + + .tt-hint { + color: transparent; + height: 20px; + border-top: 1px solid #BDBDBD; + border-bottom: 1px solid #BDBDBD; + border-left: none; + border-right: none; + padding: 5px 0 5px 0; + width: 0px; + margin: 0; + outline: none; + font-size: 14px; + line-height: 14px; + background: #F5F5F5; + font-family: 'din-medium', helvetica, sans-serif; + } + + .tt-menu { + top: 40px !important; + background: #F5F5F5; + width: 472px; + overflow-y: auto; + overflow-x: visible; + box-shadow: 0 10px 10px rgba(0,0,0,0.19), 0 6px 3px rgba(0,0,0,0.23); + + h3 { + text-transform: uppercase; + color: #F5F5F5; + font-size: 18px; + line-height: 18px; + margin: 12px 0 3px 16px; + float: left; + } + + .limitToMe { + float: left; + width: 12px; + height: 12px; + border: 1px; + color: #000000; + position: absolute; + top: 15px; + left: 136px; + } + + .limitToMeLabel { + float: left; + font-family: 'din-medium', helvetica, sans-serif; + font-size: 12px; + color: #f5f5f5; + margin: 0; + position: absolute; + top: 15px; + left: 156px; + } + + .minimizeResults, + .maximizeResults { + width: 32px; + height: 32px; + background-image: url(<%= asset_data_uri('arrowpermswhite_sprite.png') %>); + background-repeat: no-repeat; + cursor: pointer; + position: absolute; + top: 5px; + left: 410px; + } + .minimizeResults { + background-position: 0 0; + } + .maximizeResults { + background-position: -32px 0; + } + }/* tt-menu */ + + .tt-suggestion { + position: relative; + background: #FFF; + padding: 8px 0; + + > div { + display: table-cell; + } + + &:hover { + background: #E0E0E0; + } + .icon { + width: 32px; + height: 32px; + border-radius:16px; + } + .resultText { + width: 260px; + padding-left: 8px; + vertical-align: middle; + word-wrap: break-word; + } + .resultTitle { + font-weight: normal; + font-size: 16px; + line-height: 20px; + width: 100%; + font-family: 'din-regular', helvetica, sans-serif; + } + .resultDesc { + font-size: 12px; + line-height: 16px; + width: 100%; + font-style: italic; + font-family: helvetica, sans-serif; + } + + .topicMetacode, + .searchResIconWrapper { + vertical-align: middle; + padding: 0 0 0 8px; + width: 70px; + } + }/* tt-suggestion */ + + .tt-dataset { + overflow: visible; + } + + .tt-dataset-maps { + .autoOptions { + width: 84px; + } + }/* .tt-dataset-maps */ + + .tt-dataset-topics { + .topicIcon { + width: 32px; + height: 32px; + margin: 0 auto; + } + .metacodeTip { + display: none; + margin: 0 auto; + } + .tt-cursor .metacodeTip, + .tt-suggestion:hover .metacodeTip { + display: block; + font-family: 'vinyl'; + text-transform: uppercase; + font-style: italic; + font-size: 13px; + margin: 0 5px 0 2px; + text-align: center; + } + }/* tt-dataset-topics */ + + .tt-dataset-mappers { + .icon { + margin: 0px 0px 0px 0px; + } + .mappers .resultText { + width: 150px; + } + .autoOptions { + width: 235px; + } + .mapCount { + top: 8px; + left: 170px; + } + .mapperCreated { + left: 0px; + padding-left: 0px; + font-size: 12px; + font-family: 'din-medium', helvetica, sans-serif; + line-height: 24px; + } + .mapperGeneration { + top: 20px; + left: 0px; + padding-left: 0px; + font-size: 12px; + font-family: 'din-medium', helvetica, sans-serif; + line-height: 24px; + } + }/* tt-dataset-mappers */ + + .autoOptions { + width: 114px; + height: 48px; + position: absolute; + display: none; + top: 8px; + right: 0; + + a, + div, + button { + position: absolute; + margin: 0; + border: none; + outline: none; + } + }/* .autoOptions */ + + .tt-cursor .autoOptions, + .tt-suggestion:hover .autoOptions { + display: block; + } + .tt-suggestion .resultnoresult .autoOptions { + display: none; + } + + .addToMap { + display:none; + width: 24px; + height: 24px; + background: url(<%= asset_data_uri('addtopic_sprite.png') %>); + background-repeat: no-repeat; + background-size: 48px 24px; + top: 12px; + left: 80px; + cursor: pointer; + + &:hover { + background-position: -24px; + } + }/* .addToMap */ + + .topicCount { + width: 24px; + height: 24px; + background: url(<%= asset_data_uri('topic16.png') %>); + background-repeat: no-repeat; + background-position: 0 center; + top: 0; + left: 0; + padding-left: 18px; + font-size: 12px; + line-height: 24px; + } + + .mapCount { + width: 24px; + height: 24px; + background: url(<%= asset_data_uri('metamap16.png') %>); + background-repeat: no-repeat; + background-position: 0 center; + left: 0; + padding-left: 20px; + font-size: 12px; + line-height: 24px; + } + + .synapseCount { + width: 24px; + height: 24px; + background: url(<%= asset_data_uri('synapse16.png') %>); + background-repeat: no-repeat; + background-position: 0 center; + top: 24px; + left: 0; + padding-left: 20px; + font-size: 12px; + line-height: 24px; + } + + .tip { + display: none; + position: absolute; + background: #424242; + width: auto; + top: 2px; + right: 25px; + color: white; + white-space: nowrap; + border-radius: 2px; + font-size: 12px !important; + font-family: 'din-regular'; + line-height: 12px; + padding: 4px 4px 4px; + z-index: 100; + } + + .topicOriginatorIcon { + width: 18px; + height: 18px; + padding: 3px; + top: 0; + left: 44px; + + img { + border-radius: 9px; + } + + .tip { + right: 30px; + top: 1px; + } + + .tip:before { + top: 5px; + right: -4px; + } + }/* .topicOriginatorIcon */ + + .mapContributorsIcon .tip { + right: 40px; + top: -5px; + padding-top: 5px; + padding-bottom: 5px; + } + + .hoverForTip{ + &:hover .tip { + display: block; + } + + .tip li { + padding-left: 28px; + padding-top: 4px; + } + + .tip:before { + content: ''; + position: absolute; + width: 0; + height: 0; + border-left: 4px solid #424242; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + } + + .tip { + right: 30px; + } + .tip:before { + right: -4px; + } + }/* .hoverForTip */ + + .mapContributorsIcon { + height: 24px; + top: 0; + left: 44px; + font-size: 12px; + line-height: 24px; + + .tip:before { + top: 12px; + right: -4px; + } + + .mapContributors { + top: auto; + right: 0; + bottom: 21px; + white-space: normal; + width: 200px; + } + }/* .mapContributorsIcon */ + + .topicPermission, + .mapPermission { + width: 24px; + height: 24px; + background-image: url(<%= asset_data_uri('permissions32_sprite.png') %>); + background-repeat: no-repeat; + background-size: 72px 48px !important; + top: 24px; + left: 44px; + + .commons, + .commons { + background-position: 0 0; + } + .public, + .public { + background-position: -48px 0; + } + .private, + .private { + background-position: -24px 0; + } + }/* .topicPermission, .mapPermission */ +}/* .sidebarSearch */ diff --git a/app/assets/stylesheets/uservoice.css b/app/assets/stylesheets/uservoice.css.erb similarity index 85% rename from app/assets/stylesheets/uservoice.css rename to app/assets/stylesheets/uservoice.css.erb index 02b92b9e..f633c367 100644 --- a/app/assets/stylesheets/uservoice.css +++ b/app/assets/stylesheets/uservoice.css.erb @@ -6,7 +6,7 @@ } div.uv-icon.uv-bottom-left { - background-image:url(feedback_sprite.png); + background-image: url(<%= asset_data_uri 'feedback_sprite.png' %>); background-repeat: no-repeat; color:#FFFFFF; cursor:pointer; @@ -22,4 +22,4 @@ div.uv-icon.uv-bottom-left { div.uv-icon.uv-bottom-left:hover { background-position: 0 -110px; -} \ No newline at end of file +} diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 511ab723..74d56340 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,7 @@ class ApplicationController < ActionController::Base protect_from_forgery + + before_filter :get_invite_link # this is for global login include ContentHelper @@ -9,7 +11,13 @@ class ApplicationController < ActionController::Base helper_method :admin? def after_sign_in_path_for(resource) - sign_in_url = url_for(:action => 'new', :controller => 'sessions', :only_path => false, :protocol => 'http') + unsafe_uri = request.env["REQUEST_URI"] + if unsafe_uri.starts_with?('http') && !unsafe_uri.starts_with?('https') + protocol = 'http' + else + protocol = 'https' + end + sign_in_url = url_for(:action => 'new', :controller => 'sessions', :only_path => false, :protocol => protocol) if request.referer == sign_in_url super @@ -37,7 +45,7 @@ private end def require_admin - unless authenticated? && user.admin + unless authenticated? && admin? redirect_to root_url, notice: "You need to be an admin for that." return false end @@ -47,13 +55,18 @@ private current_user end - def authenticated? current_user end def admin? - current_user && current_user.admin + authenticated? && current_user.admin + end + + def get_invite_link + unsafe_uri = request.env["REQUEST_URI"] || 'https://metamaps.cc' + valid_url = /^https?:\/\/([\w\.-]+)(:\d{1,5})?\/?$/ + safe_uri = (unsafe_uri.match(valid_url)) ? unsafe_uri : '//metamaps.cc/' + @invite_link = "#{safe_uri}join" + (current_user ? "?code=#{current_user.code}" : "") end - end diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb index 46747f71..1cd0f577 100644 --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -26,8 +26,6 @@ class MainController < ApplicationController # get /search/topics?term=SOMETERM def searchtopics - @current = current_user - term = params[:term] user = params[:user] ? params[:user] : false @@ -68,25 +66,20 @@ class MainController < ApplicationController else search = term.downcase + '%' - if !user - @topics = Topic.where('LOWER("name") like ?', search).where('metacode_id = ?', filterByMetacode.id).order('"name"') - @topics2 = Topic.where('LOWER("name") like ?', '%' + search).where('metacode_id = ?', filterByMetacode.id).order('"name"') - @topics3 = Topic.where('LOWER("desc") like ?', '%' + search).where('metacode_id = ?', filterByMetacode.id).order('"name"') - @topics4 = Topic.where('LOWER("link") like ?', '%' + search).where('metacode_id = ?', filterByMetacode.id).order('"name"') - @topics = @topics + (@topics2 - @topics) - @topics = @topics + (@topics3 - @topics) - @topics = @topics + (@topics4 - @topics) - - elsif user - @topics = Topic.where('LOWER("name") like ?', search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"') - @topics2 = Topic.where('LOWER("name") like ?', '%' + search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"') - @topics3 = Topic.where('LOWER("desc") like ?', '%' + search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"') - @topics4 = Topic.where('LOWER("link") like ?', '%' + search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"') - @topics = @topics + (@topics2 - @topics) - @topics = @topics + (@topics3 - @topics) - @topics = @topics + (@topics4 - @topics) - + if user + @topics = Set.new(Topic.where('LOWER("name") like ?', search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"')) + @topics2 = Set.new(Topic.where('LOWER("name") like ?', '%' + search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"')) + @topics3 = Set.new(Topic.where('LOWER("desc") like ?', '%' + search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"')) + @topics4 = Set.new(Topic.where('LOWER("link") like ?', '%' + search).where('metacode_id = ? AND user_id = ?', filterByMetacode.id, user).order('"name"')) + else + @topics = Set.new(Topic.where('LOWER("name") like ?', search).where('metacode_id = ?', filterByMetacode.id).order('"name"')) + @topics2 = Set.new(Topic.where('LOWER("name") like ?', '%' + search).where('metacode_id = ?', filterByMetacode.id).order('"name"')) + @topics3 = Set.new(Topic.where('LOWER("desc") like ?', '%' + search).where('metacode_id = ?', filterByMetacode.id).order('"name"')) + @topics4 = Set.new(Topic.where('LOWER("link") like ?', '%' + search).where('metacode_id = ?', filterByMetacode.id).order('"name"')) end + + #get unique elements only through the magic of Sets + @topics = (@topics + @topics2 + @topics3 + @topics4).to_a end elsif desc search = '%' + term.downcase + '%' @@ -127,15 +120,13 @@ class MainController < ApplicationController end #read this next line as 'delete a topic if its private and you're either 1. logged out or 2. logged in but not the topic creator - @topics.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } + @topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && current_user.id != t.user_id)) } render json: autocomplete_array_json(@topics) end # get /search/maps?term=SOMETERM def searchmaps - @current = current_user - term = params[:term] user = params[:user] ? params[:user] : nil @@ -163,15 +154,13 @@ class MainController < ApplicationController end #read this next line as 'delete a map if its private and you're either 1. logged out or 2. logged in but not the map creator - @maps.delete_if {|m| m.permission == "private" && (!authenticated? || (authenticated? && @current.id != m.user_id)) } + @maps.to_a.delete_if {|m| m.permission == "private" && (!authenticated? || (authenticated? && current_user.id != m.user_id)) } render json: autocomplete_map_array_json(@maps) end # get /search/mappers?term=SOMETERM def searchmappers - @current = current_user - term = params[:term] if term && !term.empty? && term.downcase[0..3] != "map:" && term.downcase[0..5] != "topic:" && term.downcase != "mapper:" @@ -187,19 +176,17 @@ class MainController < ApplicationController # get /search/synapses?term=SOMETERM OR # get /search/synapses?topic1id=SOMEID&topic2id=SOMEID def searchsynapses - @current = current_user - term = params[:term] topic1id = params[:topic1id] topic2id = params[:topic2id] - + if term && !term.empty? - @synapses = Synapse.select('DISTINCT "desc"').where('LOWER("desc") like ?', '%' + term.downcase + '%').order('"desc"') + @synapses = Synapse.where('LOWER("desc") like ?', '%' + term.downcase + '%').order('"desc"') # remove any duplicate synapse types that just differ by # leading or trailing whitespaces collectedDesc = [] - @synapses.delete_if {|s| + @synapses.to_a.uniq(&:desc).delete_if {|s| desc = s.desc == nil || s.desc == "" ? "" : s.desc.strip if collectedDesc.index(desc) == nil collectedDesc.push(desc) @@ -211,23 +198,20 @@ class MainController < ApplicationController #limit to 5 results @synapses = @synapses.slice(0,5) - - render json: autocomplete_synapse_generic_json(@synapses) - elsif topic1id && !topic1id.empty? @one = Synapse.where('node1_id = ? AND node2_id = ?', topic1id, topic2id) @two = Synapse.where('node2_id = ? AND node1_id = ?', topic1id, topic2id) @synapses = @one + @two - @synapses.sort! {|s1,s2| s1.desc <=> s2.desc } + @synapses.sort! {|s1,s2| s1.desc <=> s2.desc }.to_a - #read this next line as 'delete a synapse if its private and you're either 1. logged out or 2. logged in but not the synapse creator - @synapses.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } - - render json: autocomplete_synapse_array_json(@synapses) + #permissions + @synapses.delete_if {|s| s.permission == "private" && !authenticated? } + @synapses.delete_if {|s| s.permission == "private" && authenticated? && current_user.id != s.user_id } else @synapses = [] - render json: autocomplete_synapse_array_json(@synapses) end + + render json: autocomplete_synapse_array_json(@synapses) end end diff --git a/app/controllers/mappings_controller.rb b/app/controllers/mappings_controller.rb index b28c7638..27567eb4 100644 --- a/app/controllers/mappings_controller.rb +++ b/app/controllers/mappings_controller.rb @@ -13,7 +13,7 @@ class MappingsController < ApplicationController # POST /mappings.json def create - @mapping = Mapping.new(params[:mapping]) + @mapping = Mapping.new(mapping_params) @mapping.map.touch(:updated_at) @@ -30,7 +30,7 @@ class MappingsController < ApplicationController @mapping.map.touch(:updated_at) - if @mapping.update_attributes(params[:mapping]) + if @mapping.update_attributes(mapping_params) head :no_content else render json: @mapping.errors, status: :unprocessable_entity @@ -48,4 +48,10 @@ class MappingsController < ApplicationController head :no_content end + + private + # Never trust parameters from the scary internet, only allow the white list through. + def mapping_params + params.require(:mapping).permit(:id, :xloc, :yloc, :mappable_id, :mappable_type, :map_id, :user_id) + end end diff --git a/app/controllers/maps_controller.rb b/app/controllers/maps_controller.rb index 8f0ced9b..add8a0c1 100644 --- a/app/controllers/maps_controller.rb +++ b/app/controllers/maps_controller.rb @@ -72,14 +72,10 @@ class MapsController < ApplicationController respond_to do |format| format.html { @allmappers = @map.contributors - @alltopics = @map.topics.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } - @allsynapses = @map.synapses.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } - @allmappings = @map.mappings.delete_if {|m| - if m.category == "Synapse" - object = m.synapse - elsif m.category == "Topic" - object = m.topic - end + @alltopics = @map.topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } + @allsynapses = @map.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } + @allmappings = @map.mappings.to_a.delete_if {|m| + object = m.mappable !object || (object.permission == "private" && (!authenticated? || (authenticated? && @current.id != object.user_id))) } @@ -100,14 +96,10 @@ class MapsController < ApplicationController end @allmappers = @map.contributors - @alltopics = @map.topics.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } - @allsynapses = @map.synapses.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } - @allmappings = @map.mappings.delete_if {|m| - if m.category == "Synapse" - object = m.synapse - elsif m.category == "Topic" - object = m.topic - end + @alltopics = @map.topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } + @allsynapses = @map.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } + @allmappings = @map.mappings.to_a.delete_if {|m| + object = m.mappable !object || (object.permission == "private" && (!authenticated? || (authenticated? && @current.id != object.user_id))) } @@ -141,10 +133,9 @@ class MapsController < ApplicationController @all.each do |topic| topic = topic.split('/') @mapping = Mapping.new() - @mapping.category = "Topic" @mapping.user = @user @mapping.map = @map - @mapping.topic = Topic.find(topic[0]) + @mapping.mappable = Topic.find(topic[0]) @mapping.xloc = topic[1] @mapping.yloc = topic[2] @mapping.save @@ -155,10 +146,9 @@ class MapsController < ApplicationController @synAll = @synAll.split(',') @synAll.each do |synapse_id| @mapping = Mapping.new() - @mapping.category = "Synapse" @mapping.user = @user @mapping.map = @map - @mapping.synapse = Synapse.find(synapse_id) + @mapping.mappable = Synapse.find(synapse_id) @mapping.save end end @@ -180,7 +170,7 @@ class MapsController < ApplicationController respond_to do |format| if !@map format.json { render json: "unauthorized" } - elsif @map.update_attributes(params[:map]) + elsif @map.update_attributes(map_params) format.json { head :no_content } else format.json { render json: @map.errors, status: :unprocessable_entity } @@ -218,15 +208,7 @@ class MapsController < ApplicationController @map = Map.find(params[:id]).authorize_to_delete(@current) - if @map - @mappings = @map.mappings - - @mappings.each do |mapping| - mapping.delete - end - - @map.delete - end + @map.delete if @map respond_to do |format| format.json { @@ -238,4 +220,11 @@ class MapsController < ApplicationController } end end + + private + + # Never trust parameters from the scary internet, only allow the white list through. + def map_params + params.require(:map).permit(:id, :name, :arranged, :desc, :permission, :user_id) + end end diff --git a/app/controllers/metacode_sets_controller.rb b/app/controllers/metacode_sets_controller.rb index 4542f6dd..720076c1 100644 --- a/app/controllers/metacode_sets_controller.rb +++ b/app/controllers/metacode_sets_controller.rb @@ -1,7 +1,7 @@ class MetacodeSetsController < ApplicationController before_filter :require_admin - + # GET /metacode_sets # GET /metacode_sets.json def index @@ -45,7 +45,7 @@ class MetacodeSetsController < ApplicationController # POST /metacode_sets.json def create @user = current_user - @metacode_set = MetacodeSet.new(params[:metacode_set]) + @metacode_set = MetacodeSet.new(metacode_set_params) @metacode_set.user_id = @user.id respond_to do |format| @@ -70,7 +70,7 @@ class MetacodeSetsController < ApplicationController @metacode_set = MetacodeSet.find(params[:id]) respond_to do |format| - if @metacode_set.update_attributes(params[:metacode_set]) + if @metacode_set.update_attributes(metacode_set_params) # build an array of the IDs of the metacodes currently in the set @currentMetacodes = @metacode_set.metacodes.map{ |m| m.id.to_s } @@ -116,4 +116,11 @@ class MetacodeSetsController < ApplicationController format.json { head :no_content } end end + + private + + def metacode_set_params + params.require(:metacode_set).permit(:desc, :mapperContributed, :name) + end + end diff --git a/app/controllers/metacodes_controller.rb b/app/controllers/metacodes_controller.rb index 810981dc..5d1e1367 100644 --- a/app/controllers/metacodes_controller.rb +++ b/app/controllers/metacodes_controller.rb @@ -1,12 +1,13 @@ class MetacodesController < ApplicationController - before_filter :require_admin, except: [:index] # GET /metacodes # GET /metacodes.json def index - @metacodes = Metacode.order("name").all + @metacodes.map do |metacode| + metacode.icon = ActionController::Base.helpers.asset_path(metacode.icon) + end respond_to do |format| format.html { @@ -51,7 +52,7 @@ class MetacodesController < ApplicationController # POST /metacodes # POST /metacodes.json def create - @metacode = Metacode.new(params[:metacode]) + @metacode = Metacode.new(metacode_params) respond_to do |format| if @metacode.save @@ -70,7 +71,7 @@ class MetacodesController < ApplicationController @metacode = Metacode.find(params[:id]) respond_to do |format| - if @metacode.update_attributes(params[:metacode]) + if @metacode.update_attributes(metacode_params) format.html { redirect_to metacodes_url, notice: 'Metacode was successfully updated.' } format.json { head :no_content } else @@ -93,4 +94,11 @@ class MetacodesController < ApplicationController # format.json { head :no_content } # end # end + + private + + # Never trust parameters from the scary internet, only allow the white list through. + def metacode_params + params.require(:metacode).permit(:id, :name, :icon, :color) + end end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb deleted file mode 100644 index 0f0be9ba..00000000 --- a/app/controllers/registrations_controller.rb +++ /dev/null @@ -1,10 +0,0 @@ -class Users::RegistrationsController < Devise::RegistrationsController - protected - def after_sign_up_path_for(resource) - signed_in_root_path(resource) - end - - def after_update_path_for(resource) - signed_in_root_path(resource) - end -end \ No newline at end of file diff --git a/app/controllers/synapses_controller.rb b/app/controllers/synapses_controller.rb index 6ff1537b..2c3c08d0 100644 --- a/app/controllers/synapses_controller.rb +++ b/app/controllers/synapses_controller.rb @@ -21,7 +21,8 @@ class SynapsesController < ApplicationController # POST /synapses # POST /synapses.json def create - @synapse = Synapse.new(params[:synapse]) + @synapse = Synapse.new(synapse_params) + @synapse.update_attribute :desc, "" if @synapse.desc.nil? respond_to do |format| if @synapse.save @@ -36,9 +37,10 @@ class SynapsesController < ApplicationController # PUT /synapses/1.json def update @synapse = Synapse.find(params[:id]) + @synapse.update_attribute :desc, "" if @synapse.desc.nil? respond_to do |format| - if @synapse.update_attributes(params[:synapse]) + if @synapse.update_attributes(synapse_params) format.json { head :no_content } else format.json { render json: @synapse.errors, status: :unprocessable_entity } @@ -48,20 +50,17 @@ class SynapsesController < ApplicationController # DELETE synapses/:id def destroy - @current = current_user - @synapse = Synapse.find(params[:id]).authorize_to_delete(@current) - - if @synapse - @synapse.mappings.each do |m| - m.map.touch(:updated_at) - m.delete - end - - @synapse.delete - end + @synapse = Synapse.find(params[:id]).authorize_to_delete(current_user) + @synapse.delete if @synapse respond_to do |format| format.json { head :no_content } end end + + private + + def synapse_params + params.require(:synapse).permit(:id, :desc, :category, :weight, :permission, :node1_id, :node2_id, :user_id) + end end diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index ca24d1fb..d73be190 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -10,12 +10,11 @@ class TopicsController < ApplicationController @current = current_user term = params[:term] if term && !term.empty? - # !connor term here needs to have .downcase @topics = Topic.where('LOWER("name") like ?', term.downcase + '%').order('"name"') #read this next line as 'delete a topic if its private and you're either #1. logged out or 2. logged in but not the topic creator - @topics.delete_if {|t| t.permission == "private" && + @topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } else @topics = [] @@ -35,7 +34,7 @@ class TopicsController < ApplicationController respond_to do |format| format.html { @alltopics = ([@topic] + @topic.relatives).delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } # should limit to topics visible to user - @allsynapses = @topic.synapses.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } + @allsynapses = @topic.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } @allcreators = [] @alltopics.each do |t| @@ -64,8 +63,8 @@ class TopicsController < ApplicationController redirect_to root_url, notice: "Access denied. That topic is private." and return end - @alltopics = @topic.relatives.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } - @allsynapses = @topic.synapses.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } + @alltopics = @topic.relatives.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id)) } + @allsynapses = @topic.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && @current.id != s.user_id)) } @allcreators = [] @allcreators.push(@topic.user) @alltopics.each do |t| @@ -101,7 +100,7 @@ class TopicsController < ApplicationController @topicsAlreadyHas = params[:network] ? params[:network].split(',') : [] - @alltopics = @topic.relatives.delete_if {|t| + @alltopics = @topic.relatives.to_a.delete_if {|t| @topicsAlreadyHas.index(t.id.to_s) != nil || (t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id))) } @@ -133,7 +132,7 @@ class TopicsController < ApplicationController @topicsAlreadyHas = params[:network] ? params[:network].split(',') : [] - @alltopics = @topic.relatives.delete_if {|t| + @alltopics = @topic.relatives.to_a.delete_if {|t| @topicsAlreadyHas.index(t.id.to_s) != nil || (params[:metacode] && t.metacode_id.to_s != params[:metacode]) || (t.permission == "private" && (!authenticated? || (authenticated? && @current.id != t.user_id))) @@ -141,7 +140,7 @@ class TopicsController < ApplicationController @alltopics.uniq! - @allsynapses = @topic.synapses.delete_if {|s| + @allsynapses = @topic.synapses.to_a.delete_if {|s| (s.topic1 == @topic && @alltopics.index(s.topic2) == nil) || (s.topic2 == @topic && @alltopics.index(s.topic1) == nil) } @@ -172,7 +171,7 @@ class TopicsController < ApplicationController # POST /topics # POST /topics.json def create - @topic = Topic.new(params[:topic]) + @topic = Topic.new(topic_params) respond_to do |format| if @topic.save @@ -189,7 +188,7 @@ class TopicsController < ApplicationController @topic = Topic.find(params[:id]) respond_to do |format| - if @topic.update_attributes(params[:topic]) + if @topic.update_attributes(topic_params) format.json { head :no_content } else format.json { render json: @topic.errors, status: :unprocessable_entity } @@ -201,36 +200,16 @@ class TopicsController < ApplicationController def destroy @current = current_user @topic = Topic.find(params[:id]).authorize_to_delete(@current) - - if @topic - @synapses = @topic.synapses - @mappings = @topic.mappings - - @synapses.each do |synapse| - synapse.mappings.each do |m| - - @map = m.map - @map.touch(:updated_at) - - m.delete - end - - synapse.delete - end - - @mappings.each do |mapping| - - @map = mapping.map - @map.touch(:updated_at) - - mapping.delete - end - - @topic.delete - end + @topic.delete if @topic respond_to do |format| format.json { head :no_content } end end + + private + + def topic_params + params.require(:topic).permit(:id, :name, :desc, :link, :permission, :user_id, :metacode_id) + end end diff --git a/app/controllers/users/passwords_controller.rb b/app/controllers/users/passwords_controller.rb index d1405f6a..ae5517e8 100644 --- a/app/controllers/users/passwords_controller.rb +++ b/app/controllers/users/passwords_controller.rb @@ -3,4 +3,4 @@ class Users::PasswordsController < Devise::PasswordsController def after_resetting_password_path_for(resource) signed_in_root_path(resource) end -end \ No newline at end of file +end diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb index 5fff2f1c..c77edb50 100644 --- a/app/controllers/users/registrations_controller.rb +++ b/app/controllers/users/registrations_controller.rb @@ -1,4 +1,7 @@ class Users::RegistrationsController < Devise::RegistrationsController + before_filter :configure_sign_up_params, only: [:create] + before_filter :configure_account_update_params, only: [:update] + protected def after_sign_up_path_for(resource) signed_in_root_path(resource) @@ -7,4 +10,14 @@ class Users::RegistrationsController < Devise::RegistrationsController def after_update_path_for(resource) signed_in_root_path(resource) end + + private + def configure_sign_up_params + devise_parameter_sanitizer.for(:sign_up) << [:name, :joinedwithcode] + end + + def configure_account_update_params + puts devise_parameter_sanitizer_for(:account_update) + devise_parameter_sanitizer.for(:account_update) << [:image] + end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 58ef2d96..063ab866 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,9 +1,8 @@ class UsersController < ApplicationController - before_filter :require_user, only: [:edit, :update, :updatemetacodes] respond_to :html, :json - + # GET /users/1.json def show @user = User.find(params[:id]) @@ -14,7 +13,6 @@ class UsersController < ApplicationController # GET /users/:id/edit def edit @user = current_user - respond_with(@user) end @@ -22,9 +20,9 @@ class UsersController < ApplicationController def update @user = current_user - if params[:user][:password] == "" && params[:user][:password_confirmation] == "" + if user_params[:password] == "" && user_params[:password_confirmation] == "" # not trying to change the password - if @user.update_attributes(params[:user]) + if @user.update_attributes(user_params.except(:password, :password_confirmation)) if params[:remove_image] == "1" @user.image = nil end @@ -43,7 +41,7 @@ class UsersController < ApplicationController # trying to change the password correct_pass = @user.valid_password?(params[:current_password]) - if correct_pass && @user.update_attributes(params[:user]) + if correct_pass && @user.update_attributes(user_params) if params[:remove_image] == "1" @user.image = nil end @@ -98,4 +96,9 @@ class UsersController < ApplicationController end end + private + + def user_params + params.require(:user).permit(:name, :email, :image, :password, :password_confirmation) + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 95f6ba29..3db990db 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,2 +1,18 @@ module ApplicationHelper + def get_metacodeset + @m = user.settings.metacodes + set = @m[0].include?("metacodeset") ? MetacodeSet.find(@m[0].sub("metacodeset-","").to_i) : false + return set + end + + def user_metacodes + @m = user.settings.metacodes + set = get_metacodeset + if set + @metacodes = set.metacodes.to_a + else + @metacodes = Metacode.where(id: @m).to_a + end + @metacodes.sort! {|m1,m2| m2.name.downcase <=> m1.name.downcase }.rotate!(-1) + end end diff --git a/app/helpers/maps_helper.rb b/app/helpers/maps_helper.rb index 0c9b3a08..169ee4b9 100644 --- a/app/helpers/maps_helper.rb +++ b/app/helpers/maps_helper.rb @@ -16,7 +16,7 @@ module MapsHelper map['rtype'] = "map" contributorTip = '' - firstContributorImage = '/assets/user.png' + firstContributorImage = 'https://s3.amazonaws.com/metamaps-assets/site/user.png' if m.contributors.count > 0 firstContributorImage = m.contributors[0].image.url(:thirtytwo) m.contributors.each_with_index do |c, index| diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 977e5709..69335da1 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -1,22 +1,10 @@ module UsersHelper - - ## this one is for building our custom JSON autocomplete format for typeahead + # build custom json autocomplete for typeahead def autocomplete_user_array_json(users) - temp = [] - users.each do |u| - user = {} - user['id'] = u.id - user['label'] = u.name - user['value'] = u.name - user['profile'] = u.image.url(:sixtyfour) - user['mapCount'] = u.maps.count - user['generation'] = u.generation - user['created_at'] = u.created_at.strftime("%m/%d/%Y") - user['rtype'] = "mapper" - - temp.push user + json_users = [] + users.each do |user| + json_users.push user.as_json_for_autocomplete end - return temp + json_users end - end diff --git a/app/models/in_metacode_set.rb b/app/models/in_metacode_set.rb index 3f608587..117033d6 100644 --- a/app/models/in_metacode_set.rb +++ b/app/models/in_metacode_set.rb @@ -1,5 +1,4 @@ class InMetacodeSet < ActiveRecord::Base belongs_to :metacode, :class_name => "Metacode", :foreign_key => "metacode_id" belongs_to :metacode_set, :class_name => "MetacodeSet", :foreign_key => "metacode_set_id" - # attr_accessible :title, :body end diff --git a/app/models/map.rb b/app/models/map.rb index 3bf5a4a6..87019d2a 100644 --- a/app/models/map.rb +++ b/app/models/map.rb @@ -2,18 +2,21 @@ class Map < ActiveRecord::Base belongs_to :user - has_many :topicmappings, :class_name => 'Mapping', :conditions => {:category => 'Topic'} - has_many :synapsemappings, :class_name => 'Mapping', :conditions => {:category => 'Synapse'} - - has_many :topics, :through => :topicmappings - has_many :synapses, :through => :synapsemappings + has_many :topicmappings, -> { Mapping.topicmapping }, class_name: :Mapping, dependent: :destroy + has_many :synapsemappings, -> { Mapping.synapsemapping }, class_name: :Mapping, dependent: :destroy + has_many :topics, through: :topicmappings, source: :mappable, source_type: "Topic" + has_many :synapses, through: :synapsemappings, source: :mappable, source_type: "Synapse" # This method associates the attribute ":image" with a file attachment has_attached_file :screenshot, :styles => { :thumb => ['188x126#', :png] #:full => ['940x630#', :png] }, - :default_url => "/assets/missing-map.png" + :default_url => 'https://s3.amazonaws.com/metamaps-assets/site/missing-map.png' + validates :name, presence: true + validates :arranged, inclusion: { in: [true, false] } + validates :permission, presence: true + validates :permission, inclusion: { in: Perm::ISSIONS.map(&:to_s) } # Validate the attached image is image/jpg, image/png, etc validates_attachment_content_type :screenshot, :content_type => /\Aimage\/.*\Z/ @@ -23,13 +26,7 @@ class Map < ActiveRecord::Base end def mk_permission - if self.permission == "commons" - "co" - elsif self.permission == "public" - "pu" - elsif self.permission == "private" - "pr" - end + Perm.short(permission) end #return an array of the contributors to the map @@ -110,14 +107,6 @@ class Map < ActiveRecord::Base end return self end - - # returns Boolean if user allowed to view Topic, Synapse, or Map - def authorize_to_view(user) - if (self.permission == "private" && self.user != user) - return false - end - return true - end def decode_base64(imgBase64) decoded_data = Base64.decode64(imgBase64) diff --git a/app/models/mapping.rb b/app/models/mapping.rb index dc50730c..318aa5cf 100644 --- a/app/models/mapping.rb +++ b/app/models/mapping.rb @@ -1,7 +1,10 @@ class Mapping < ActiveRecord::Base - belongs_to :topic, :class_name => "Topic", :foreign_key => "topic_id" - belongs_to :synapse, :class_name => "Synapse", :foreign_key => "synapse_id" + scope :topicmapping, -> { where(mappable_type: :Topic) } + scope :synapsemapping, -> { where(mappable_type: :Synapse) } + + belongs_to :mappable, polymorphic: true + belongs_to :map, :class_name => "Map", :foreign_key => "map_id" belongs_to :user diff --git a/app/models/metacode.rb b/app/models/metacode.rb index 315f7800..03b0f0c0 100644 --- a/app/models/metacode.rb +++ b/app/models/metacode.rb @@ -13,5 +13,4 @@ class Metacode < ActiveRecord::Base return true if self.metacode_sets.include? metacode_set return false end - -end \ No newline at end of file +end diff --git a/app/models/metacode_set.rb b/app/models/metacode_set.rb index 6798e568..d06de1e4 100644 --- a/app/models/metacode_set.rb +++ b/app/models/metacode_set.rb @@ -1,6 +1,5 @@ class MetacodeSet < ActiveRecord::Base belongs_to :user - attr_accessible :desc, :mapperContributed, :name has_many :in_metacode_sets has_many :metacodes, :through => :in_metacode_sets end diff --git a/app/models/synapse.rb b/app/models/synapse.rb index bf3bdab2..225485e5 100644 --- a/app/models/synapse.rb +++ b/app/models/synapse.rb @@ -5,9 +5,14 @@ class Synapse < ActiveRecord::Base belongs_to :topic1, :class_name => "Topic", :foreign_key => "node1_id" belongs_to :topic2, :class_name => "Topic", :foreign_key => "node2_id" - has_many :mappings + has_many :mappings, as: :mappable, dependent: :destroy has_many :maps, :through => :mappings + validates :desc, length: { minimum: 0, allow_nil: false } + + validates :permission, presence: true + validates :permission, inclusion: { in: Perm::ISSIONS.map(&:to_s) } + def user_name self.user.name end @@ -46,13 +51,4 @@ class Synapse < ActiveRecord::Base end return false end - - # returns Boolean if user allowed to view Topic, Synapse, or Map - def authorize_to_view(user) - if (self.permission == "private" && self.user != user) - return false - end - return true - end - end diff --git a/app/models/topic.rb b/app/models/topic.rb index 078c633e..e80d3df7 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -3,13 +3,16 @@ class Topic < ActiveRecord::Base belongs_to :user - has_many :synapses1, :class_name => 'Synapse', :foreign_key => 'node1_id' - has_many :synapses2, :class_name => 'Synapse', :foreign_key => 'node2_id' + has_many :synapses1, :class_name => 'Synapse', :foreign_key => 'node1_id', dependent: :destroy + has_many :synapses2, :class_name => 'Synapse', :foreign_key => 'node2_id', dependent: :destroy has_many :topics1, :through => :synapses2, :source => :topic1 has_many :topics2, :through => :synapses1, :source => :topic2 - has_many :mappings + has_many :mappings, as: :mappable, dependent: :destroy has_many :maps, :through => :mappings + + validates :permission, presence: true + validates :permission, inclusion: { in: Perm::ISSIONS.map(&:to_s) } # This method associates the attribute ":image" with a file attachment has_attached_file :image @@ -39,27 +42,27 @@ class Topic < ActiveRecord::Base belongs_to :metacode def user_name - self.user.name + user.name end def user_image - self.user.image.url + user.image.url end def map_count - self.maps.count + maps.count end def synapse_count - self.synapses.count + synapses.count end def inmaps - self.maps.map(&:name) + maps.map(&:name) end def inmapsLinks - self.maps.map(&:id) + maps.map(&:id) end def as_json(options={}) @@ -71,24 +74,18 @@ class Topic < ActiveRecord::Base end def mk_permission - if self.permission == "commons" - "co" - elsif self.permission == "public" - "pu" - elsif self.permission == "private" - "pr" - end + Perm.short(permission) end # has no viewable synapses helper function def has_viewable_synapses(current) result = false - self.synapses.each do |synapse| - if synapse.authorize_to_view(current) + synapses.each do |synapse| + if synapse.authorize_to_show(current) result = true end end - return result + result end ##### PERMISSIONS ###### @@ -117,12 +114,4 @@ class Topic < ActiveRecord::Base end return false end - - # returns Boolean if user allowed to view Topic, Synapse, or Map - def authorize_to_view(user) - if (self.permission == "private" && self.user != user) - return false - end - return true - end end diff --git a/app/models/user.rb b/app/models/user.rb index 2e738134..bd3bf477 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -7,12 +7,10 @@ class User < ActiveRecord::Base has_many :maps has_many :mappings - before_create :generate_code + after_create :generate_code devise :database_authenticatable, :recoverable, :rememberable, :trackable, :registerable - attr_accessible :name, :email, :image, :password, :password_confirmation, :code, :joinedwithcode, :remember_me - serialize :settings, UserPreference validates :password, :presence => true, @@ -37,11 +35,12 @@ class User < ActiveRecord::Base :ninetysix => ['96x96#', :png], :onetwentyeight => ['128x128#', :png] }, - :default_url => "/assets/user.png" + :default_url => 'https://s3.amazonaws.com/metamaps-assets/site/user.png' # Validate the attached image is image/jpg, image/png, etc validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/ + # override default as_json def as_json(options={}) { :id => self.id, :name => self.name, @@ -49,27 +48,32 @@ class User < ActiveRecord::Base :admin => self.admin } end + + def as_json_for_autocomplete + json = {} + json['id'] = id + json['label'] = name + json['value'] = name + json['profile'] = image.url(:sixtyfour) + json['mapCount'] = maps.count + json['generation'] = generation + json['created_at'] = created_at.strftime("%m/%d/%Y") + json['rtype'] = "mapper" + json + end + #generate a random 8 letter/digit code that they can use to invite people def generate_code - #generate a random 8 letter/digit code that they can use to invite people - self.code = rand(36**8).to_s(36) - + self.code ||= rand(36**8).to_s(36) $codes.push(self.code) - - self.generation = self.get_generation + self.generation = get_generation! end - def get_generation - if self.joinedwithcode == self.code - # if your joinedwithcode equals your code you must be GEN 0 - gen = 0 - elsif self.generation - # if your generation has already been calculated then just return that value - gen = self.generation + def get_generation! + if code == joinedwithcode + update(generation: 0) else - # if your generation hasn't been calculated, base it off the - # generation of the person whose code you joined with + 1 - gen = User.find_by_code(self.joinedwithcode).get_generation + 1 + update(generation: User.find_by_code(joinedwithcode).generation + 1) end end @@ -77,13 +81,12 @@ class User < ActiveRecord::Base # make sure we always return a UserPreference instance if read_attribute(:settings).nil? write_attribute :settings, UserPreference.new - read_attribute :settings - else - read_attribute :settings end + read_attribute :settings end def settings=(val) write_attribute :settings, val end + end diff --git a/app/services/perm.rb b/app/services/perm.rb new file mode 100644 index 00000000..c18c26b7 --- /dev/null +++ b/app/services/perm.rb @@ -0,0 +1,37 @@ +class Perm + # e.g. Perm::ISSIONS + ISSIONS = [:commons, :public, :private] + + class << self + + def short(permission) + case permission + when :commons + "co" + when :public + "pu" + when :private + "pr" + else + fail "Invalid permission" + end + end + + def long(perm) + case perm + when "co" + :commons + when "pu" + :public + when "pr" + :private + else + fail "Invalid short permission" + end + end + + def valid?(permission) + ISSIONS.include? permission + end + end +end diff --git a/app/views/layouts/_lightboxes.html.erb b/app/views/layouts/_lightboxes.html.erb index e8b688d5..00b923f6 100644 --- a/app/views/layouts/_lightboxes.html.erb +++ b/app/views/layouts/_lightboxes.html.erb @@ -18,7 +18,7 @@

    PRIVATE BETA

    -

    2.6

    +

    2.7

    Nov 29, 2014

    @@ -46,6 +46,7 @@
  • + <% # TODO enable ssl on blog.metamaps.cc %>
    blog @@ -58,7 +59,8 @@
  • - + <% # TODO switch to https:// once we're confident %> +
    terms
    @@ -115,7 +117,7 @@
  • Google Plus *
  • Hackpad *
  • Loomio *
  • -
  • Twitter *
  • +
  • Twitter *
  • Hipchat
  • Google Hangouts
  • Vimeo
  • @@ -140,7 +142,7 @@
    • Google Analytics

    Support

    -
    • Uservoice *
    +
    • Uservoice *
    @@ -152,7 +154,8 @@ Chrome Firefox Safari -

    While it's downloading, explore our blog,
    watch the tutorials, or visit our knowledge base! + <% # TODO https for blog.metamaps.cc %> +

    While it's downloading, explore our blog,
    watch the tutorials, or visit our knowledge base!

    @@ -200,13 +203,14 @@

    Want to help with design, code, community building, or communications for Metamaps? We're an open value network, which for us means we want to invite and empower peers to participate in creating value together. + <% # TODO change this link to https once it works %>

    To be a USER, request an invite! We'll expect you to abide by our terms of service.

    To be a CONTRIBUTOR, simply enter our spaces and join the conversation! We'll expect you to follow some guidelines.

    MEMBERS are contributors who act as stewards and guardians of the collective and are responsible for maintaining our culture and purpose. We're working on a set of agreements to formalize what it means to be a member.

    -

    We welcome people of all skillsets to create value within our network. There's open possibilities for designers, researchers, academics, strategists, and facilitators to contribute. If you're a developer, help us advance the code and take a look around our Github.

    +

    We welcome people of all skillsets to create value within our network. There's open possibilities for designers, researchers, academics, strategists, and facilitators to contribute. If you're a developer, help us advance the code and take a look around our Github.

    Thanks for your interest in helping out with Metamaps! Hopefully this gets you pointed in the right direction to get involved. The next step is to read the links on this page and then contribute!

    @@ -231,9 +235,8 @@

    As a valued beta tester, you have the ability to invite your peers, colleagues and collaborators onto the platform.

    Below is a personal invite link containing your unique access code, which can be used multiple times.

    - <% mapper = current_user %> -

    http://metamaps.cc/join?code=<%= mapper.code %>

    - +

    <%= @invite_link %> +

    diff --git a/app/views/layouts/_templates.html.erb b/app/views/layouts/_templates.html.erb index afd9de52..4218bfda 100644 --- a/app/views/layouts/_templates.html.erb +++ b/app/views/layouts/_templates.html.erb @@ -129,7 +129,7 @@ @@ -234,7 +236,7 @@
    {{{metacode_select}}}
    - +
    {{username}}
    @@ -267,4 +269,4 @@
    -
    \ No newline at end of file +
    diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 9911466a..e21a91c3 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -16,12 +16,12 @@ <%= javascript_include_tag "application" %> - + @@ -66,7 +66,7 @@ -"> +"> <% if devise_error_messages? %>

    <%= devise_error_messages! %>

    @@ -141,16 +141,12 @@ // set up uservoice with signed in user <% if authenticated? && ENV['SSO_KEY'] %> - USERVOICE.load(Metamaps.Active.Mapper.name, Metamaps.Active.Mapper.id, "<%= user.email %>", "<%= current_sso_token %>"); + USERVOICE.load(Metamaps.Active.Mapper.name, Metamaps.Active.Mapper.id, "<%= user.email %>", "<%= current_sso_token %>"); <% else %> - USERVOICE.load(); + USERVOICE.load(); <% end %> -<% if !authenticated? %> - -<% end %> - <%= render :partial => 'layouts/googleanalytics' if Rails.env.production? %> diff --git a/app/views/main/home.html.erb b/app/views/main/home.html.erb index 2590f2f5..3c1319e0 100644 --- a/app/views/main/home.html.erb +++ b/app/views/main/home.html.erb @@ -47,7 +47,7 @@ Metamaps.Maps.Active = <%= @maps.to_json.html_safe %>; Metamaps.currentSection = ""; Metamaps.currentPage = ""; - Metamaps.GlobalUI.Search.isOpen = true; + Metamaps.GlobalUI.Search.open(); Metamaps.GlobalUI.Search.lock(); -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/maps/_mapinfobox.html.erb b/app/views/maps/_mapinfobox.html.erb index 4305b3a7..1fa6da02 100644 --- a/app/views/maps/_mapinfobox.html.erb +++ b/app/views/maps/_mapinfobox.html.erb @@ -8,12 +8,12 @@ <%= @map && @map.permission != 'private' ? " shareable" : "" %>"> <% if @map %> -
    <%= best_in_place @map, :name, :type => :textarea, :activator => "#mapInfoName", :classes => 'best_in_place_name' %>
    +
    <%= best_in_place @map, :name, :as => :textarea, :activator => "#mapInfoName", :class => 'best_in_place_name' %>
    <% if @map.contributors.count == 0 %> - + <% elsif @map.contributors.count == 1 %> <% elsif @map.contributors.count == 2 %> @@ -23,7 +23,7 @@ <% end %> <%= @map.contributors.count %>
    @@ -42,7 +42,7 @@
    <% if (authenticated? && @map.authorize_to_edit(user)) || (!authenticated? && @map.desc != "" && @map.desc != nil )%> - <%= best_in_place @map, :desc, :activator => "#mapInfoDesc", :type => :textarea, :nil => "Click to add description...", :classes => 'best_in_place_desc' %> + <%= best_in_place @map, :desc, :activator => "#mapInfoDesc", :as => :textarea, :placeholder => "Click to add description...", :class => 'best_in_place_desc' %> <% end %>
    @@ -61,4 +61,4 @@
    <% end %> -
    \ No newline at end of file +
    diff --git a/app/views/maps/_newtopic.html.erb b/app/views/maps/_newtopic.html.erb index f22920bb..e5263d76 100644 --- a/app/views/maps/_newtopic.html.erb +++ b/app/views/maps/_newtopic.html.erb @@ -1,19 +1,10 @@ <%= form_for Topic.new, url: topics_url, remote: true do |form| %>
    - <% @m = user.settings.metacodes %> - <% set = @m[0].include?("metacodeset") ? MetacodeSet.find(@m[0].sub("metacodeset-","").to_i) : false %> - <% if set %> - <% @metacodes = set.metacodes %> - <% else %> - <% @metacodes = [] %> - <% @m.each do |m| %> - <% @metacodes.push(Metacode.find(m.to_i)) %> - <% end %> - <% end %> - <% @metacodes.sort! {|m1,m2| m2.name.downcase <=> m1.name.downcase }.rotate!(-1) %> + <% @metacodes = user_metacodes() %> + <% set = get_metacodeset() %> <% @metacodes.each do |metacode| %> - <%= metacode.name %> + <%= metacode.name %> <% end %>
    <%= form.text_field :name, :maxlength => 140, :placeholder => "title..." %> diff --git a/app/views/metacode_sets/_form.html.erb b/app/views/metacode_sets/_form.html.erb index 3a5089b9..f7ef60c6 100644 --- a/app/views/metacode_sets/_form.html.erb +++ b/app/views/metacode_sets/_form.html.erb @@ -37,7 +37,7 @@ <% while $i < (Metacode.all.length / 4) do %>
  • class="toggledOff"<% end %> onclick="Metamaps.Admin.liClickHandler.call(this);"> - <%= @m[$i].name %> + <%= @m[$i].name %>

    <%= @m[$i].name.downcase %>

  • @@ -48,7 +48,7 @@ <% while $i < (Metacode.all.length / 4 * 2) do %>
  • class="toggledOff"<% end %> onclick="Metamaps.Admin.liClickHandler.call(this);"> - <%= @m[$i].name %> + <%= @m[$i].name %>

    <%= @m[$i].name.downcase %>

  • @@ -59,7 +59,7 @@ <% while $i < (Metacode.all.length / 4 * 3) do %>
  • class="toggledOff"<% end %> onclick="Metamaps.Admin.liClickHandler.call(this);"> - <%= @m[$i].name %> + <%= @m[$i].name %>

    <%= @m[$i].name.downcase %>

  • @@ -70,7 +70,7 @@ <% while $i < Metacode.all.length do %>
  • class="toggledOff"<% end %> onclick="Metamaps.Admin.liClickHandler.call(this);"> - <%= @m[$i].name %> + <%= @m[$i].name %>

    <%= @m[$i].name.downcase %>

  • @@ -86,4 +86,4 @@ { :class => 'button', 'data-bypass' => 'true' } %> <%= f.submit :class => 'add', :onclick => "return Metamaps.Admin.validate();" %> -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/metacode_sets/index.html.erb b/app/views/metacode_sets/index.html.erb index 7a93d97f..8f6aa810 100644 --- a/app/views/metacode_sets/index.html.erb +++ b/app/views/metacode_sets/index.html.erb @@ -23,7 +23,7 @@ <%= metacode_set.desc %> <% metacode_set.metacodes.each_with_index do |metacode, index| %> - + <% if (index+1)%4 == 0 %>
    <% end %> @@ -34,4 +34,4 @@ <% end %> - \ No newline at end of file + diff --git a/app/views/metacodes/index.html.erb b/app/views/metacodes/index.html.erb index c99634d4..4f3563f1 100644 --- a/app/views/metacodes/index.html.erb +++ b/app/views/metacodes/index.html.erb @@ -14,7 +14,7 @@ <% @metacodes.each do |metacode| %> <%= metacode.name %> - <%= metacode.icon %> + <%= asset_path metacode.icon %> <% if metacode.color %> <%= metacode.color %> @@ -22,10 +22,10 @@ <% else %> <% end %> - + <%= link_to 'Edit', edit_metacode_path(metacode), :data => { :bypass => 'true'} %> <% end %> - \ No newline at end of file + diff --git a/app/views/metacodes/new.html.erb b/app/views/metacodes/new.html.erb index 8520bb6c..e10f28d1 100644 --- a/app/views/metacodes/new.html.erb +++ b/app/views/metacodes/new.html.erb @@ -2,4 +2,4 @@
    <%= render 'form' %>
    - \ No newline at end of file + diff --git a/app/views/shared/_cheatsheet.html.erb b/app/views/shared/_cheatsheet.html.erb index f0a2ffbc..1f57e6f9 100644 --- a/app/views/shared/_cheatsheet.html.erb +++ b/app/views/shared/_cheatsheet.html.erb @@ -38,7 +38,7 @@
    Esc: Hides auto-suggestion results
    Enter: create a new topic
    Gear Icon: open up metacode settings
    -

    Learn More
    +

    Learn More
    @@ -61,7 +61,7 @@ Change Topic permission: Click on 'Permission' icon (only for topic creator)
    - Open Topic view: Click on icon within topic card bar + Open Topic view: Click on icon within topic card bar
    Close 'Topic' card: Click on canvas @@ -70,7 +70,7 @@ Open 'Context Menu': Right-click/alt+click on topic icon or synapse
    *Hide/Remove/Delete topic within context menu
    -

    Learn More
    +

    Learn More
    @@ -81,7 +81,7 @@
    Create new Topic with Synapse: Right-click + drag from topic to open canvas
    Enter: Create topic
    Enter: Create synapse
    -

    Learn More
    +

    Learn More
    @@ -94,7 +94,7 @@
    Browse synapses / change visible synapse click on arrow icon and select desired synapse
    Open 'Context Menu': Right-click/alt-click on Synapse
    *Hide/Remove/Delete synapse within context menu
    -

    Learn More
    +

    Learn More
    @@ -102,7 +102,7 @@
    Move around Canvas: Click and drag
    Zoom in/out: Scroll OR click on
    &
    Zoom to see all: Click
    OR Ctrl + E
    -

    Learn More
    +

    Learn More
    @@ -151,42 +151,42 @@

    For more information about Metamaps.cc, visit our Knowledge Base or skip directly to a section by clicking on one of the categories below.

    - Feedback Forums + Feedback Forums
    - KNOWLEDGE BASE + KNOWLEDGE BASE
    diff --git a/app/views/shared/_filterBox.html.erb b/app/views/shared/_filterBox.html.erb index b01f6eca..9ea4926b 100644 --- a/app/views/shared/_filterBox.html.erb +++ b/app/views/shared/_filterBox.html.erb @@ -70,13 +70,13 @@ @metacodes.each_with_index do |metacode, index| @metacodelist += '
  • ' - @metacodelist += '' + metacode.name + '' + @metacodelist += '' + metacode.name + '' @metacodelist += '

    ' + metacode.name.downcase + '

  • ' end @synapses.each_with_index do |synapse, index| d = synapse.desc || "" @synapselist += '
  • ' - @synapselist += 'synapse icon

    ' + d + @synapselist += 'synapse icon

    ' + d @synapselist += '

  • ' end @mappers.each_with_index do |mapper, index| diff --git a/app/views/shared/_metacodeoptions.html.erb b/app/views/shared/_metacodeoptions.html.erb index f45a1aa6..a6092c3e 100644 --- a/app/views/shared/_metacodeoptions.html.erb +++ b/app/views/shared/_metacodeoptions.html.erb @@ -12,7 +12,7 @@ -
    \ No newline at end of file + diff --git a/app/views/shared/_switchmetacodes.html.erb b/app/views/shared/_switchmetacodes.html.erb index fb3962ef..e0fcd036 100644 --- a/app/views/shared/_switchmetacodes.html.erb +++ b/app/views/shared/_switchmetacodes.html.erb @@ -19,16 +19,16 @@
    <% allMetacodeSets.each_with_index do |m, localindex| %>
    <% @list = '' %> <% m.metacodes.sort{|x,y| x.name <=> y.name }.each_with_index do |m, index| %> - <% @list += '
  • ' + m.name + '

    ' + m.name.downcase + '

  • ' %> + <% @list += '
  • ' + m.name + '

    ' + m.name.downcase + '

  • ' %> <% end %>

    <%= m.desc %>

    @@ -53,7 +53,7 @@ <% else %> <% mClass = "toggledOff" %> <% end %> - <% @list += '
  • ' + m.name + '

    ' + m.name.downcase + '

  • ' %> + <% @list += '
  • ' + m.name + '

    ' + m.name.downcase + '

  • ' %> <% end %>
    @@ -73,4 +73,4 @@ \ No newline at end of file + diff --git a/app/views/topics/_new.html.erb b/app/views/topics/_new.html.erb index 7aa13eb3..49b6a9a8 100644 --- a/app/views/topics/_new.html.erb +++ b/app/views/topics/_new.html.erb @@ -20,7 +20,7 @@ <% end %> <% @metacodes.sort! {|m1,m2| m2.name.downcase <=> m1.name.downcase }.rotate!(-1) %> <% @metacodes.each do |metacode| %> - <%= metacode.name %> + <%= metacode.name %> <% end %>
    <%= form.text_field :name, :maxlength => 140, :placeholder => "title..." %> diff --git a/bin/jenkins-test.sh b/bin/jenkins-test.sh new file mode 100755 index 00000000..193b109f --- /dev/null +++ b/bin/jenkins-test.sh @@ -0,0 +1,21 @@ +#!/bin/bash -l + +#prerequisites +#sudo aptitude -q -y install libpq-dev + +source "$HOME/.rvm/scripts/rvm" +rvm install $(cat .ruby-version) +rvm gemset use $(cat .ruby-gemset) +gem install bundler + +set -x + +#configure environment +export RAILS_ENV=test +cp .example-env .env +sed -i -e "s/DB_USERNAME='.*'/DB_USERNAME='jenkins'/" .env + +#test +bundle install +rake db:create db:test:prepare +bundle exec rspec diff --git a/config/application.rb b/config/application.rb index d9f5f0f1..399b32c9 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,13 +1,12 @@ require File.expand_path('../boot', __FILE__) require 'rails/all' +require 'dotenv' -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 +Bundler.require(*Rails.groups) + +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" +Dotenv.load ".env.#{ENV["RAILS_ENV"]}", '.env' module Metamaps class Application < Rails::Application @@ -16,7 +15,7 @@ module Metamaps # -- 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) + config.autoload_paths << Rails.root.join('app', 'services') # 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. @@ -44,17 +43,15 @@ module Metamaps # like if you have constraints or database-specific column types # config.active_record.schema_format = :sql - # Enforce whitelist mode for mass assignment. - # This will create an empty whitelist of attributes available for mass-assignment for all models - # in your app. As such, your models will need to explicitly whitelist or blacklist accessible - # parameters by using an attr_accessible or attr_protected declaration. - # config.active_record.whitelist_attributes = true - # Enable the asset pipeline - config.assets.enabled = true config.assets.initialize_on_precompile = false # Version of your assets, change this if you want to expire all your assets config.assets.version = '2.0' + + config.generators do |g| + g.test_framework :rspec + end + config.active_record.raise_in_transactional_callbacks = true end end diff --git a/config/boot.rb b/config/boot.rb index 4489e586..0f0d7c60 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,4 +1,12 @@ require 'rubygems' +require 'rails/commands/server' +module Rails + class Server + def default_options + super.merge(Host: '0.0.0.0', Port: 3000) + end + end +end # Set up gems listed in the Gemfile. ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 00000000..469ede34 --- /dev/null +++ b/config/database.yml @@ -0,0 +1,21 @@ +default: &default + min_messages: WARNING + encoding: unicode + pool: 5 + adapter: postgresql + host: <%= ENV['DB_HOST'] %> + port: <%= ENV['DB_PORT'] %> + username: <%= ENV['DB_USERNAME'] %> + password: <%= ENV['DB_PASSWORD'] %> + +development: + <<: *default + database: <%= ENV['DB_NAME'] %>_development + +test: + <<: *default + database: <%= ENV['DB_NAME'] %>_test + +production: + <<: *default + database: <%= ENV['DB_NAME'] %>_production diff --git a/config/database.yml.default b/config/database.yml.default deleted file mode 100644 index 06bfae01..00000000 --- a/config/database.yml.default +++ /dev/null @@ -1,10 +0,0 @@ -development: - min_messages: WARNING - adapter: postgresql - host: 127.0.0.1 - port: 5432 - encoding: unicode - database: metamap002_development - pool: 5 - username: postgres - password: "3112" \ No newline at end of file diff --git a/config/environments/development.rb b/config/environments/development.rb index 74431de7..593fbd3c 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,26 +1,27 @@ Metamaps::Application.configure do # Settings specified here will take precedence over those in config/application.rb + config.log_level = :info + config.eager_load = 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 web server 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.consider_all_requests_local = true config.action_controller.perform_caching = false # S3 file storage config.paperclip_defaults = { - :storage => :s3, - :s3_credentials => { - :bucket => ENV['S3_BUCKET_NAME'], - :access_key_id => ENV['AWS_ACCESS_KEY_ID'], - :secret_access_key => ENV['AWS_SECRET_ACCESS_KEY'] - } + storage: :s3, + s3_credentials: { + bucket: ENV['S3_BUCKET_NAME'], + access_key_id: ENV['AWS_ACCESS_KEY_ID'], + secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'] + }, + s3_protocol: 'https' } config.action_mailer.delivery_method = :smtp @@ -40,19 +41,6 @@ Metamaps::Application.configure do # 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 - - # Raise exception on mass assignment protection for Active Record models - config.active_record.mass_assignment_sanitizer = :strict - - # Log the query plan for queries taking more than this (works - # with SQLite, MySQL, and PostgreSQL) - config.active_record.auto_explain_threshold_in_seconds = 0.5 - - # Do not compress assets - config.assets.compress = false - # Expands the lines which load the assets config.assets.debug = true end diff --git a/config/environments/production.rb b/config/environments/production.rb index 2186327f..9654962a 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,6 +1,10 @@ Metamaps::Application.configure do # Settings specified here will take precedence over those in config/application.rb + config.log_level = :warn + config.eager_load = true + config.assets.js_compressor = :uglifier + # Code is not reloaded between requests config.cache_classes = true @@ -9,7 +13,9 @@ Metamaps::Application.configure do config.action_controller.perform_caching = true # Disable Rails's static asset server (Apache or nginx will already do this) - config.serve_static_assets = false + config.serve_static_files = true + + config.assets.compile = false # Compress JavaScripts and CSS config.assets.compress = true @@ -18,11 +24,12 @@ Metamaps::Application.configure do # S3 file storage config.paperclip_defaults = { :storage => :s3, - :s3_credentials => { - :bucket => ENV['S3_BUCKET_NAME'], - :access_key_id => ENV['AWS_ACCESS_KEY_ID'], - :secret_access_key => ENV['AWS_SECRET_ACCESS_KEY'] - } + s3_credentials: { + bucket: ENV['S3_BUCKET_NAME'], + access_key_id: ENV['AWS_ACCESS_KEY_ID'], + secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'] + }, + s3_protocol: 'https' } config.action_mailer.delivery_method = :smtp @@ -35,7 +42,7 @@ Metamaps::Application.configure do authentication: 'plain', enable_starttls_auto: true, openssl_verify_mode: 'none' } - config.action_mailer.default_url_options = { :host => 'metamaps.herokuapp.com' } + config.action_mailer.default_url_options = { :host => ENV['MAILER_DEFAULT_URL'] } # Don't care if the mailer can't send config.action_mailer.raise_delivery_errors = true diff --git a/config/environments/test.rb b/config/environments/test.rb index 8a7b408c..be83469c 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,6 +1,8 @@ Metamaps::Application.configure do # Settings specified here will take precedence over those in config/application.rb + config.eager_load = false + # 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 @@ -8,12 +10,9 @@ Metamaps::Application.configure do config.cache_classes = true # Configure static asset server for tests with Cache-Control for performance - config.serve_static_assets = true + config.serve_static_files = true config.static_cache_control = "public, max-age=3600" - # Log error messages when you accidentally call methods on nil - config.whiny_nils = true - # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false @@ -29,9 +28,12 @@ Metamaps::Application.configure do # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test - # Raise exception on mass assignment protection for Active Record models - config.active_record.mass_assignment_sanitizer = :strict - # Print deprecation notices to the stderr config.active_support.deprecation = :stderr + + #assets config + config.assets.compile = true + config.assets.compress = false + config.assets.debug = false + config.assets.digest = false end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 2924b11e..6590fddf 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -1,21 +1,23 @@ # Use this hook to configure devise mailer, warden hooks and so forth. # Many of these configuration options can be set straight in your model. Devise.setup do |config| + # The secret key used by Devise. Devise uses this key to generate + # random tokens. Changing this key will render invalid all existing + # confirmation, reset password and unlock tokens in the database. + # Devise will use the `secret_key_base` on Rails 4+ applications as its `secret_key` + # by default. You can change it below and use your own secret key. + # config.secret_key = '4d38a819bcea6314ffccb156a8e84b1b52c51ed446d11877c973791b3cd88449e9dbd7990cbc6e7f37d84702168ec36391467000c842ed5bed4f0b05df2b9507' + # ==> Mailer Configuration # Configure the e-mail address which will be shown in Devise::Mailer, # note that it will be overwritten if you use your own mailer class with default "from" parameter. - config.mailer_sender = "team@metamaps.cc" + config.mailer_sender = ENV['DEVISE_MAILER_SENDER'] # Configure the class responsible to send e-mails. # config.mailer = "Devise::Mailer" - if Rails.env.development? # this is for Connors localhost - config.secret_key = 'f71c467e526f23d614b3b08866cad4788c502bed869c282f06e73ee6c94675b62fe1f6d52fa7ba8196b33031f0d2f3b67e27ea07693c52ecebccb01700cad614' - end - if Rails.env.production? # this is for the heroku staging environment - config.secret_key = 'd91ba0da95749174ee2b8922034783cbde4945409ed28b13383e18e72844beb74467f8199e9e216f0687cd2290c6e46bf74da24486d14bba3671d76c5b10c753' - end + config.secret_key = ENV['DEVISE_SECRET_KEY'] # ==> ORM configuration # Load and configure the ORM. Supports :active_record (default) and @@ -121,6 +123,9 @@ Devise.setup do |config| # The time the user will be remembered without asking for credentials again. config.remember_for = 2.weeks + # Invalidates all the remember me tokens when the user signs out. + config.expire_all_remember_me_on_sign_out = true + # If true, extends the user's remember period when remembered via cookie. # config.extend_remember_period = false @@ -142,9 +147,6 @@ Devise.setup do |config| # time the user will be asked for credentials again. Default is 30 minutes. # config.timeout_in = 30.minutes - # If true, expires auth token on session timeout. - # config.expire_auth_token_on_timeout = false - # ==> 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. @@ -152,7 +154,7 @@ Devise.setup do |config| # config.lock_strategy = :failed_attempts # Defines which key will be used when locking and unlocking an account - # config.unlock_keys = [ :email ] + # config.unlock_keys = [:email] # Defines which strategy will be used to unlock an account. # :email = Sends an unlock link to the user email @@ -168,16 +170,23 @@ Devise.setup do |config| # Time interval to unlock the account if :time is enabled as unlock_strategy. # config.unlock_in = 1.hour + # Warn on the last attempt before the account is locked. + # config.last_attempt_warning = true + # ==> Configuration for :recoverable # # Defines which key will be used when recovering the password for an account - # config.reset_password_keys = [ :email ] + # 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 = 24.hours + # When set to false, does not sign a user in automatically after their password is + # reset. Defaults to true, so a user is signed in automatically after a reset. + # config.sign_in_after_reset_password = true + # ==> 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, @@ -186,10 +195,6 @@ Devise.setup do |config| # REST_AUTH_SITE_KEY to pepper) # config.encryptor = :sha512 - # ==> Configuration for :token_authenticatable - # Defines name of the authentication token params key - # config.token_authentication_key = :auth_token - # ==> 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 @@ -237,12 +242,12 @@ Devise.setup do |config| # is mountable, there are some extra configurations to be taken into account. # The following options are available, assuming the engine is mounted as: # - # mount MyEngine, at: "/my_engine" + # mount MyEngine, at: '/my_engine' # # The router that invoked `devise_for`, in the example above, would be: # config.router_name = :my_engine # - # When using omniauth, Devise cannot automatically set Omniauth path, + # When using OmniAuth, Devise cannot automatically set OmniAuth path, # so you need to do it manually. For the users scope, it would be: - # config.omniauth_path_prefix = "/my_engine/users/auth" + # config.omniauth_path_prefix = '/my_engine/users/auth' end diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb index b9f46222..9c1fb05e 100644 --- a/config/initializers/secret_token.rb +++ b/config/initializers/secret_token.rb @@ -4,4 +4,4 @@ # 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. -Metamaps::Application.config.secret_token = '267c8a84f63963282f45bc3010eaddf027abfab58fc759d6e239c8005f85ee99d6d01b1ab6394cdee9ca7f8c9213a0cf91d3d8d3350f096123e2caccbcc0924f' +Metamaps::Application.config.secret_key_base = '267c8a84f63963282f45bc3010eaddf027abfab58fc759d6e239c8005f85ee99d6d01b1ab6394cdee9ca7f8c9213a0cf91d3d8d3350f096123e2caccbcc0924f' diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index d01f375c..26a10f29 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -3,49 +3,50 @@ en: devise: confirmations: - confirmed: "Your account was successfully confirmed. You are now signed in." - send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes." - send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions about how to confirm your account in a few minutes." + confirmed: "Your email address has been successfully confirmed." + send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." failure: already_authenticated: "You are already signed in." - inactive: "Your account was not activated yet." - invalid: "Invalid email or password." - invalid_token: "Invalid authentication token." + inactive: "Your account is not activated yet." + invalid: "Invalid %{authentication_keys} or password." locked: "Your account is locked." - not_found_in_database: "Invalid email or password." - timeout: "Your session expired, please sign in again to continue." + last_attempt: "You have one more attempt before your account is locked." + not_found_in_database: "Invalid %{authentication_keys} or password." + timeout: "Your session expired. Please sign in again to continue." unauthenticated: "You need to sign in or sign up before continuing." - unconfirmed: "You have to confirm your account before continuing." + unconfirmed: "You have to confirm your email address before continuing." mailer: confirmation_instructions: subject: "Confirmation instructions" reset_password_instructions: subject: "Reset password instructions" unlock_instructions: - subject: "Unlock Instructions" + subject: "Unlock instructions" omniauth_callbacks: failure: "Could not authenticate you from %{kind} because \"%{reason}\"." success: "Successfully authenticated from %{kind} account." passwords: no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." - send_instructions: "You will receive an email with instructions about how to reset your password in a few minutes." + send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." - updated: "Your password was changed successfully. You are now signed in." - updated_not_active: "Your password was changed successfully." + updated: "Your password has been changed successfully. You are now signed in." + updated_not_active: "Your password has been changed successfully." registrations: - destroyed: "Bye! Your account was successfully cancelled. We hope to see you again soon." + destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." signed_up: "Welcome! You have signed up successfully." signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." - signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please open the link to activate your account." - update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and click on the confirm link to finalize confirming your new email address." - updated: "You updated your account successfully." + signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." + update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address." + updated: "Your account has been updated successfully." sessions: signed_in: "Signed in successfully." signed_out: "Signed out successfully." + already_signed_out: "Signed out successfully." unlocks: - send_instructions: "You will receive an email with instructions about how to unlock your account in a few minutes." - send_paranoid_instructions: "If your account exists, you will receive an email with instructions about how to unlock it in a few minutes." + send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." + send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." unlocked: "Your account has been unlocked successfully. Please sign in to continue." errors: messages: diff --git a/config/routes.rb b/config/routes.rb index 494992a6..a3ab6e3a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,12 +2,12 @@ Metamaps::Application.routes.draw do root to: 'main#home', via: :get - match 'request', to: 'main#requestinvite', via: :get, as: :request + get 'request', to: 'main#requestinvite', as: :request - match 'search/topics', to: 'main#searchtopics', via: :get, as: :searchtopics - match 'search/maps', to: 'main#searchmaps', via: :get, as: :searchmaps - match 'search/mappers', to: 'main#searchmappers', via: :get, as: :searchmappers - match 'search/synapses', to: 'main#searchsynapses', via: :get, as: :searchsynapses + get 'search/topics', to: 'main#searchtopics', as: :searchtopics + get 'search/maps', to: 'main#searchmaps', as: :searchmaps + get 'search/mappers', to: 'main#searchmappers', as: :searchmappers + get 'search/synapses', to: 'main#searchsynapses', as: :searchsynapses resources :mappings, except: [:index, :new, :edit] resources :metacode_sets, :except => [:show] @@ -16,28 +16,28 @@ Metamaps::Application.routes.draw do resources :topics, except: [:index, :new, :edit] do get :autocomplete_topic, :on => :collection end - match 'topics/:id/network', to: 'topics#network', via: :get, as: :network - match 'topics/:id/relative_numbers', to: 'topics#relative_numbers', via: :get, as: :relative_numbers - match 'topics/:id/relatives', to: 'topics#relatives', via: :get, as: :relatives + get 'topics/:id/network', to: 'topics#network', as: :network + get 'topics/:id/relative_numbers', to: 'topics#relative_numbers', as: :relative_numbers + get 'topics/:id/relatives', to: 'topics#relatives', as: :relatives - match 'explore/active', to: 'maps#index', via: :get, as: :activemaps - match 'explore/featured', to: 'maps#index', via: :get, as: :featuredmaps - match 'explore/mine', to: 'maps#index', via: :get, as: :mymaps - match 'explore/mapper/:id', to: 'maps#index', via: :get, as: :usermaps + get 'explore/active', to: 'maps#index', as: :activemaps + get 'explore/featured', to: 'maps#index', as: :featuredmaps + get 'explore/mine', to: 'maps#index', as: :mymaps + get 'explore/mapper/:id', to: 'maps#index', as: :usermaps resources :maps, except: [:new, :edit] - match 'maps/:id/contains', to: 'maps#contains', via: :get, as: :contains - match 'maps/:id/upload_screenshot', to: 'maps#screenshot', via: :post, as: :screenshot + get 'maps/:id/contains', to: 'maps#contains', as: :contains + post 'maps/:id/upload_screenshot', to: 'maps#screenshot', as: :screenshot - devise_for :users, controllers: { registrations: 'users/registrations', passwords: 'users/passwords', sessions: 'devise/sessions' }, :skip => [:sessions] + devise_for :users, controllers: { registrations: 'users/registrations', passwords: 'users/passwords', sessions: 'devise/sessions' }, :skip => :sessions devise_scope :user do get 'login' => 'devise/sessions#new', :as => :new_user_session post 'login' => 'devise/sessions#create', :as => :user_session get 'logout' => 'devise/sessions#destroy', :as => :destroy_user_session - get 'join' => 'devise/registrations#new', :as => :new_user_registration + get 'join' => 'devise/registrations#new', :as => :new_user_registration_path end - match 'users/:id/details', to: 'users#details', via: :get, as: :details - match 'user/updatemetacodes', to: 'users#updatemetacodes', via: :post, as: :updatemetacodes + get 'users/:id/details', to: 'users#details', as: :details + post 'user/updatemetacodes', to: 'users#updatemetacodes', as: :updatemetacodes resources :users, except: [:index, :destroy] end diff --git a/configure.sh b/configure.sh index 13e18244..481ca907 100755 --- a/configure.sh +++ b/configure.sh @@ -11,7 +11,7 @@ vagrant ssh --command "cd /vagrant; gem install bundler"; vagrant ssh --command "cd /vagrant; bundle install"; # copy the db config -vagrant ssh --command "cd /vagrant; cp config/database.yml.default config/database.yml"; +vagrant ssh --command "cd /vagrant; cp .example-env .env"; # Rake all the things vagrant ssh --command "cd /vagrant; rake db:create; rake db:schema:load; rake db:fixtures:load" diff --git a/db/migrate/20151001024122_mapping_polymorphism.rb b/db/migrate/20151001024122_mapping_polymorphism.rb new file mode 100644 index 00000000..e6bbd5fc --- /dev/null +++ b/db/migrate/20151001024122_mapping_polymorphism.rb @@ -0,0 +1,36 @@ +class MappingPolymorphism < ActiveRecord::Migration + def up + add_column :mappings, :mappable_id, :integer + add_column :mappings, :mappable_type, :string + add_index :mappings, [:mappable_id, :mappable_type] + + Mapping.find_each do |mapping| + if mapping.synapse_id.nil? and mapping.topic_id.nil? + puts "Mapping id=#{mapping.id} has no valid id, skipping!" + next + end + if not mapping.synapse_id.nil? and not mapping.topic_id.nil? + puts "Mapping id=#{mapping.id} has both topic and synapse ids, skipping!" + next + end + + unless mapping.synapse_id.nil? + mapping.mappable = Synapse.find_by(id: mapping.synapse_id) + else + mapping.mappable = Topic.find_by(id: mapping.topic_id) + end + + if mapping.mappable.nil? + mapping.delete + else + mapping.save + end + end + end + + def down + remove_index :mappings, [:mappable_id, :mappable_type] + remove_column :mappings, :mappable_id, :integer + remove_column :mappings, :mappable_type, :string + end +end diff --git a/db/migrate/20151023143719_add_missing_indexes.rb b/db/migrate/20151023143719_add_missing_indexes.rb new file mode 100644 index 00000000..551f2319 --- /dev/null +++ b/db/migrate/20151023143719_add_missing_indexes.rb @@ -0,0 +1,16 @@ +class AddMissingIndexes < ActiveRecord::Migration + def change + add_index :topics, :user_id + add_index :topics, :metacode_id + add_index :synapses, [:node2_id, :node2_id] + add_index :synapses, [:node1_id, :node1_id] + add_index :synapses, :user_id + add_index :synapses, :node1_id + add_index :synapses, :node2_id + add_index :mappings, [:map_id, :topic_id] + add_index :mappings, [:map_id, :synapse_id] + add_index :mappings, :map_id + add_index :mappings, :user_id + add_index :maps, :user_id + end +end diff --git a/db/migrate/20160120061513_metacode_asset_path_update.rb b/db/migrate/20160120061513_metacode_asset_path_update.rb new file mode 100644 index 00000000..a254597c --- /dev/null +++ b/db/migrate/20160120061513_metacode_asset_path_update.rb @@ -0,0 +1,15 @@ +class Metacode < ActiveRecord::Base +end + +class MetacodeAssetPathUpdate < ActiveRecord::Migration + def change + Metacode.first(50).each do |metacode| + if metacode.icon.start_with?("/assets/icons/") + metacode.icon = metacode.icon.sub(/^\/assets\/icons/, "https://s3.amazonaws.com/metamaps-assets/metacodes") + metacode.icon = metacode.icon.sub('blueprint_96px', 'blueprint/96px') + metacode.icon = metacode.icon.sub('generics_96px', 'generics/96px') + metacode.save + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index fc6b7335..83cecc24 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -9,21 +9,24 @@ # 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. +# It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(:version => 20141121204712) do +ActiveRecord::Schema.define(version: 20151028061513) do - create_table "in_metacode_sets", :force => true do |t| + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "in_metacode_sets", force: :cascade do |t| t.integer "metacode_id" t.integer "metacode_set_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "in_metacode_sets", ["metacode_id"], :name => "index_in_metacode_sets_on_metacode_id" - add_index "in_metacode_sets", ["metacode_set_id"], :name => "index_in_metacode_sets_on_metacode_set_id" + add_index "in_metacode_sets", ["metacode_id"], name: "index_in_metacode_sets_on_metacode_id", using: :btree + add_index "in_metacode_sets", ["metacode_set_id"], name: "index_in_metacode_sets_on_metacode_set_id", using: :btree - create_table "mappings", :force => true do |t| + create_table "mappings", force: :cascade do |t| t.text "category" t.integer "xloc" t.integer "yloc" @@ -31,18 +34,26 @@ ActiveRecord::Schema.define(:version => 20141121204712) do t.integer "synapse_id" t.integer "map_id" t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "mappable_id" + t.string "mappable_type" end - create_table "maps", :force => true do |t| + add_index "mappings", ["map_id", "synapse_id"], name: "index_mappings_on_map_id_and_synapse_id", using: :btree + add_index "mappings", ["map_id", "topic_id"], name: "index_mappings_on_map_id_and_topic_id", using: :btree + add_index "mappings", ["map_id"], name: "index_mappings_on_map_id", using: :btree + add_index "mappings", ["mappable_id", "mappable_type"], name: "index_mappings_on_mappable_id_and_mappable_type", using: :btree + add_index "mappings", ["user_id"], name: "index_mappings_on_user_id", using: :btree + + create_table "maps", force: :cascade do |t| t.text "name" t.boolean "arranged" t.text "desc" t.text "permission" t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.boolean "featured" t.string "screenshot_file_name" t.string "screenshot_content_type" @@ -50,26 +61,28 @@ ActiveRecord::Schema.define(:version => 20141121204712) do t.datetime "screenshot_updated_at" end - create_table "metacode_sets", :force => true do |t| + add_index "maps", ["user_id"], name: "index_maps_on_user_id", using: :btree + + create_table "metacode_sets", force: :cascade do |t| t.string "name" t.text "desc" t.integer "user_id" t.boolean "mapperContributed" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "metacode_sets", ["user_id"], :name => "index_metacode_sets_on_user_id" + add_index "metacode_sets", ["user_id"], name: "index_metacode_sets_on_user_id", using: :btree - create_table "metacodes", :force => true do |t| + create_table "metacodes", force: :cascade do |t| t.text "name" t.string "icon" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "color" end - create_table "synapses", :force => true do |t| + create_table "synapses", force: :cascade do |t| t.text "desc" t.text "category" t.text "weight" @@ -77,19 +90,25 @@ ActiveRecord::Schema.define(:version => 20141121204712) do t.integer "node1_id" t.integer "node2_id" t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - create_table "topics", :force => true do |t| + add_index "synapses", ["node1_id", "node1_id"], name: "index_synapses_on_node1_id_and_node1_id", using: :btree + add_index "synapses", ["node1_id"], name: "index_synapses_on_node1_id", using: :btree + add_index "synapses", ["node2_id", "node2_id"], name: "index_synapses_on_node2_id_and_node2_id", using: :btree + add_index "synapses", ["node2_id"], name: "index_synapses_on_node2_id", using: :btree + add_index "synapses", ["user_id"], name: "index_synapses_on_user_id", using: :btree + + create_table "topics", force: :cascade do |t| t.text "name" t.text "desc" t.text "link" t.text "permission" t.integer "user_id" t.integer "metacode_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "image_file_name" t.string "image_content_type" t.integer "image_file_size" @@ -100,25 +119,28 @@ ActiveRecord::Schema.define(:version => 20141121204712) do t.datetime "audio_updated_at" end - create_table "users", :force => true do |t| + add_index "topics", ["metacode_id"], name: "index_topics_on_metacode_id", using: :btree + add_index "topics", ["user_id"], name: "index_topics_on_user_id", using: :btree + + create_table "users", force: :cascade do |t| t.string "name" t.string "email" t.text "settings" - t.string "code", :limit => 8 - t.string "joinedwithcode", :limit => 8 + t.string "code", limit: 8 + t.string "joinedwithcode", limit: 8 t.string "crypted_password" t.string "password_salt" t.string "persistence_token" t.string "perishable_token" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "encrypted_password", :limit => 128, :default => "" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "encrypted_password", limit: 128, default: "" t.string "remember_token" t.datetime "remember_created_at" t.string "reset_password_token" t.datetime "last_sign_in_at" t.string "last_sign_in_ip" - t.integer "sign_in_count", :default => 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.string "current_sign_in_ip" t.datetime "reset_password_sent_at" @@ -130,6 +152,6 @@ ActiveRecord::Schema.define(:version => 20141121204712) do t.integer "generation" end - add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true + add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree end diff --git a/db/seeds.rb b/db/seeds.rb index 4edb1e85..51aa01d9 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,7 +1,306 @@ # This file should contain all the record creation needed to seed the database with its default values. # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). -# -# Examples: -# -# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) -# Mayor.create(name: 'Emanuel', city: cities.first) + +## USERS +User.new({ + name: 'user', + email: 'user@user.com', + password: 'toolsplusconsciousness', + code: 'qwertyui', + joinedwithcode: 'qwertyui', + admin: 'false' +}).save(validate: false) + +User.new({ + name: 'admin', + email: 'admin@admin.com', + password: 'toolsplusconsciousness', + code: 'iuytrewq', + joinedwithcode: 'iuytrewq', + admin: 'true' +}).save(validate: false) +## END USERS + +## METACODES +Metacode.create({ + name: 'Action', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_action.png', + color: '#BD6C85' +}) + +Metacode.create({ + name: 'Activity', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_activity.png', + color: '#6EBF65' +}) + +Metacode.create({ + name: 'Catalyst', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_catalyst.png', + color: '#EF8964', +}) + +Metacode.create({ + name: 'Closed', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_closedissue.png', + color: '#ABB49F', +}) + +Metacode.create({ + name: 'Process', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_process.png', + color: '#BDB25E', +}) + +Metacode.create({ + name: 'Future', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_futuredev.png', + color: '#25A17F', +}) + +Metacode.create({ + name: 'Group', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_group.png', + color: '#7076BC', +}) + +Metacode.create({ + name: 'Implication', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_implication.png', + color: '#83DECA', +}) + +Metacode.create({ + name: 'Insight', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_insight.png', + color: '#B074AD', +}) + +Metacode.create({ + name: 'Intention', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_intention.png', + color: '#BAEAFF', +}) + +Metacode.create({ + name: 'Knowledge', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_knowledge.png', + color: '#60ACF7', +}) + +Metacode.create({ + name: 'Location', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_location.png', + color: '#ABD9A7', +}) + +Metacode.create({ + name: 'Need', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_need.png', + color: '#D2A7D4', +}) + +Metacode.create({ + name: 'Open', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_openissue.png', + color: '#9BBF71', +}) + +Metacode.create({ + name: 'Opportunity', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_opportunity.png', + color: '#889F64', +}) + +Metacode.create({ + name: 'Person', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_person.png', + color: '#DE925F', +}) + +Metacode.create({ + name: 'Platform', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_platform.png', + color: '#21C8FE', +}) + +Metacode.create({ + name: 'Problem', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_problem.png', + color: '#99CFC4', +}) + +Metacode.create({ + name: 'Resource', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_resource.png', + color: '#C98C63', +}) + +Metacode.create({ + name: 'Role', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_role.png', + color: '#A8595D', +}) + +Metacode.create({ + name: 'Task', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_task.png', + color: '#3397C4', +}) + +Metacode.create({ + name: 'Trajectory', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_trajectory.png', + color: '#D3AA4C', +}) + +Metacode.create({ + name: 'Argument', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_argument.png', + color: '#7FAEFD', +}) + +Metacode.create({ + name: 'Con', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_con.png', + color: '#CF7C74', +}) + +Metacode.create({ + name: 'Subject', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_subject.png', + color: '#8293D8', +}) + +Metacode.create({ + name: 'Decision', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_decision.png', + color: '#CCA866', +}) + +Metacode.create({ + name: 'Event', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_event.png', + color: '#F5854B', +}) + +Metacode.create({ + name: 'Example', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_example.png', + color: '#618C61', +}) + +Metacode.create({ + name: 'Experience', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_experience.png', + color: '#BE995F', +}) + +Metacode.create({ + name: 'Feedback', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_feedback.png', + color: '#54A19D', +}) + +Metacode.create({ + name: 'Aim', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_aim.png', + color: '#B0B0B0', +}) + +Metacode.create({ + name: 'Good', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_goodpractice.png', + color: '#BD9E86', +}) + +Metacode.create({ + name: 'Idea', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_idea.png', + color: '#C4BC5E', +}) + +Metacode.create({ + name: 'List', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_list.png', + color: '#B7A499', +}) + +Metacode.create({ + name: 'Media', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_media.png', + color: '#6D94CC', +}) + +Metacode.create({ + name: 'Metamap', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_metamap.png', + color: '#AEA9FD', +}) + +Metacode.create({ + name: 'Model', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_model.png', + color: '#B385BA', +}) + +Metacode.create({ + name: 'Note', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_note.png', + color: '#A389A1', +}) + +Metacode.create({ + name: 'Perspective', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_perspective.png', + color: '#2EB6CC', +}) + +Metacode.create({ + name: 'Pro', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_pro.png', + color: '#89B879', +}) + +Metacode.create({ + name: 'Project', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_project.png', + color: '#85A050', +}) + +Metacode.create({ + name: 'Question', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_question.png', + color: '#5CB3B3', +}) + +Metacode.create({ + name: 'Reference', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_reference.png', + color: '#A7A7A7', +}) + +Metacode.create({ + name: 'Research', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_research.png', + color: '#CD8E89', +}) + +Metacode.create({ + name: 'Status', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_status.png', + color: '#EFA7C0', +}) + +Metacode.create({ + name: 'Tool', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_tool.png', + color: '#828282', +}) + +Metacode.create({ + name: 'Wildcard', + icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_wildcard.png', + color: '#73C7DE', +}) +## END METACODES diff --git a/CHANGELOG.md b/doc/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to doc/CHANGELOG.md diff --git a/CONTRIBUTING.md b/doc/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to doc/CONTRIBUTING.md diff --git a/doc/MacInstallation.md b/doc/MacInstallation.md new file mode 100644 index 00000000..2881f585 --- /dev/null +++ b/doc/MacInstallation.md @@ -0,0 +1,43 @@ +# OSX Install + +If you are doing an upgrade and or recent pull for changes you will need to change your default ruby package from 2.1.1 to ruby 2.1.2 + +Some of these steps are pulled from http://www.moncefbelyamani.com/how-to-install-postgresql-on-a-mac-with-homebrew-and-lunchy/ + +Install homebrew + + \curl -sSL https://get.rvm.io | bash -s stable --rails + rvm install 2.1.3 --with-gcc=clang + rvm use 2.1.3 + gem install lunchy + +Now install homebrew. + + ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + +Then install nodejs + + brew install nodejs + +And postgresql: + + brew install postgresql + ln -sfv /usr/local/opt/postgresql/*.plist ~/Library/LaunchAgents + createuser metamaps -P -s -d + +Set a password, then start the service: + + lunchy start postgres + +Change directory to the metamaps git repository, and run: + + bundle install + +Copy the .example-env file and rename it to .env. Then modify the DB_USERNAME and DB_PASSWORD values to match the postgres username and password you set + + rake db:create + rake db:schema:load + rake db:fixtures:load + rails server + +Now open a browser to http://localhost:3000! diff --git a/doc/README_FOR_APP b/doc/README_FOR_APP deleted file mode 100644 index fe41f5cc..00000000 --- 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/doc/RspecTesting.md b/doc/RspecTesting.md new file mode 100644 index 00000000..82cf74f7 --- /dev/null +++ b/doc/RspecTesting.md @@ -0,0 +1,53 @@ +## Testing with RSpec + +RSpec is a ruby gem that allows you to test your code base. This is great - +every time you make a change, you can do some basic sanity checks to make sure +you didn't break anything. + +To test Metamaps, run + + rspec + +in the top level directory. It will automatically search the `spec` directory +for files called `*_spec.rb`, and run them as tests. When it's done testing, it +will print a report telling you how many tests failed. With luck, the number +will be 0. + +Note that if your test database doesn't exist yet, you'll need to create it +first: + + RAILS_ENV=test rake db:create + +At the time of writing, there are four directories in the spec folder. One, +`support`, is for helper functions. `rails_helper.rb` and `spec_helper.rb` are +also for helper functions. + +`factories` is for a gem called [factory-girl][factory-girl]. This gem lets you +use the `create` and `build` functions to quickly create the simplest possible +valid version of a given model. For instance: + + let(:map1) { create :map } + let(:ronald) { create :user, name: "Ronald" } + let(:map2) { create :map, user: ronald } + +As you can see, you can also customize the factories. You can read the full +documentation at the link above or check the existing specs to see how it works. +It is worth reading through the factories to see how they are defined. If you +add a model to `app/models`, please also create a factory for it that defines +the minimum valid state for that model. + +Finally, `models` and `controllers` have the actual spec files. Writing specs is +usually fairly simple but you do need to understand the syntax. You can read +more at [rspec.info][rspec-docs]. + +If you modify the metamaps codebase, please consider adding tests verifying that +the added code works. This will help in a few ways: + + - Unrelated changes in the future that break your code will be spotted earlier + - Your changes will be more easily understood, since the *purpose* will be + described by the spec + +Happy testing! + +[factory-girl]: https://github.com/thoughtbot/factory_girl +[rspec-docs]: http://rspec.info diff --git a/doc/UbuntuInstallation.md b/doc/UbuntuInstallation.md new file mode 100644 index 00000000..77e2cb56 --- /dev/null +++ b/doc/UbuntuInstallation.md @@ -0,0 +1,98 @@ +Firstly this walkthrough is done with a 14.04 32bit install of Ubuntu. +Let's check if all updates for the system are installed first. In a +terminal type: + + sudo apt-get update + +Now we need to install git: + + sudo apt-get install git + +Now let's get our RVM installed (Ruby Version Manager). Now this is fun +because the package you will get from apt-get is outdated. So we are going +to use CURL to get RVM + + sudo apt-get install curl + +Then lets install RVM with curl like this + + gpg --keyserver hkp://keys.gnupg.net \ + --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 + \curl -sSL https://get.rvm.io | bash -s stable + PATH=$PATH:$HOME/.rvm/bin + [[ -s "$HOME/.profile" ]] && source "$HOME/.profile" + [[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm" + source ~/.rvm/scripts/rvm + +Now we can actually install RVM + + rvm requirements + +Running this will check your system for requirements as well so you will need to put your system password in. + +All right now we can download metamaps from the master using git + + git clone https://github.com/metamaps/metamaps_gen002.git + +Now there is a couple other things we are going to need which is nodejs, postgresql, libpq-dev and redis-server + + sudo apt-get install nodejs npm + sudo ln -s /usr/bin/nodejs /usr/bin/node + sudo apt-get install postgresql + sudo apt-get install libpq-dev + sudo apt-get install redis-server + +Furthermore, if you want to be able to work on profile picture uploading, +or use it you'll need ImageMagick. On Ubuntu, you can just go find +ImageMagick in the Ubuntu Software Centre + +Install the specific version of ruby needed this will take some time + + rvm install $(cat metamaps_gen002/.ruby-version) + +Now we also need to copy .example-env to a new file named .env. Review the +configuration in here to see if you need any changes. + + cp .example-env .env + +Now run inside your metamaps_gen002 folder: + + gem install bundle + bundle install + +in your top level directory for metamaps. This is a lengthy process so you might want to go and make a coffee or something :) + +All right now we need to make sure your postgres password is the same as +it is listed in the .env file so we are going to set it by + + sudo -u postgres psql + +Use these commands to set the password to 3112 and then quit: + + \password postgres + \q + +now we can use rake to create, load the schema into, and load db/seeds.rb +into the postgres database: + + rake db:setup + +Open a new terminal, navigate to the metamaps directory, and execute the +server: + + rails s + +and dont forget to run the other server for realtime... + + cd realtime + npm install + node realtime-server.js + +Now you're all set enjoy your personal server of metamaps :) Navigate your browser to localhost:3000 once you have the server running. Sign in with the default account + + email: user@user.com + password: toolsplusconsciousness + +OR create a new account at /join, and use access code 'qwertyui' + +Start mapping and programming! diff --git a/doc/WindowsInstallation.md b/doc/WindowsInstallation.md new file mode 100644 index 00000000..59e21298 --- /dev/null +++ b/doc/WindowsInstallation.md @@ -0,0 +1,54 @@ +Before you begin, you'll need to install stuff: + +Ruby, Git, and Rails: http://railsinstaller.org/en +PostgreSQL 9.2: http://www.enterprisedb.com/products-services-training/pgdownload +nodejs: http://nodejs.org/download + +During the installation of the PostgreSQL database, you'll need to choose a +database password. Anything is fine, just note down what you choose. + +Once you are ready, create a new folder to hold this and any other git +repositories. As an example, let's pretend you've chose C:\git, and made that +folder writable by your user account. + +Now you are ready to clone the Metamaps git repository: + + git clone https://github.com/metamaps/metamaps_gen002.git --branch develop + cd metamaps_gen002 + bundle install + +The third `bundle install` command downloads and installs the rubygem +dependencies of Metamaps. + +At this point you should be in C:\git\metamaps_gen002, or whatever equivalent +directory you've chosen. The next step is to set up your database +configuration. From the metamaps_gen002 directory, run + + start config + +This command will open a Windows Explorer window of the "config" directory of +Metamaps. Copy `.example-env`, and rename the copy to `.env`. Edit the file and +set the DB_PASSWORD to be whatever you set up with postgres earlier. Once +you're done, then move back into the command prompt. The next few commands will +fail unless `.env` is correctly configured and Postgres is running. + + rake db:create + rake db:schema:load + rake db:fixtures:load + +And you're set up! At this point, you should be able to run the server at any +time with only one command; you don't need to repeat any of the previous steps +again. The command to run the server is: + + rails s + +Navigate your browser to localhost:3000 once you have the server running + +Sign in with the default account + + email: user@user.com + password: toolsplusconsciousness + +OR create a new account at /join, and use access code 'qwertyui' + +Start mapping and programming! diff --git a/doc/metamaps-qa-steps.md b/doc/metamaps-qa-steps.md new file mode 100644 index 00000000..a3f136ca --- /dev/null +++ b/doc/metamaps-qa-steps.md @@ -0,0 +1,39 @@ +# Metamaps Tests + +Run these tests to be reasonably sure that your code changes haven't broken anything. + +### Users & Accounts + + - Create an account using your join code + - Log in to the interface + - Check your user's "generation" + - Edit your profile picture, email, name, and password + - Remove your profile picture + +### Maps, Topics, Synapses, and Permissions + + - Create three maps: private, public, and another public + - Change the last map's permissions to commons + - Change a map's name + - Create a topic on map #1 + - Verify (in a private window or another browser) that the second user can't acccess map #1 + - Create a topic on map #2 + - Verify that the second user **can't** edit map #2 + - Create a topic on map #3 + - Verify that the second user **can** edit map #3 + - Pull a topic from map #1 to map #3 + - Create a private topic on map #1 + - Verify that the private topic can be pulled from map #1 by the same user + - Verify that the private topic can't be pulled from map #1 by another user + +### Mappings + + - Add a number of topics to one of your maps. Reload to see if they are still there. + - Add a number of synapses to one of your maps. Reload to see if they are still there. + - Rearrange one of your maps and save the layout. Reload to see if the layout is preserved. + +### Misc + + - Login as admin. Change metacode sets. + - Set the screenshot for one of your maps, and verify the index of maps is updated. + - Open two browsers on map #3 and verify that realtime editing works (you'll need to be running the realtime server for this to work). diff --git a/lib/tasks/heroku.rake b/lib/tasks/heroku.rake index af2dddf9..3e35fcbf 100644 --- a/lib/tasks/heroku.rake +++ b/lib/tasks/heroku.rake @@ -1,6 +1,8 @@ +require 'dotenv/tasks' + namespace :heroku do desc "Generate the Heroku gems manifest from gem dependencies" - task :gems do + task :gems => :dotenv do RAILS_ENV='production' Rake::Task[:environment].invoke list = Rails.configuration.gems.collect do |g| @@ -12,4 +14,4 @@ namespace :heroku do f.write(list) end end -end \ No newline at end of file +end diff --git a/public/404.html b/public/404.html index 643a5ded..da435ea6 100644 --- a/public/404.html +++ b/public/404.html @@ -26,7 +26,7 @@ } body { - background: #d8d9da url(/assets/shattered_@2X.png); + background: #d8d9da url(https://s3.amazonaws.com/metamaps-assets/site/shattered_%402X.png); font-family: 'din-regular', helvetica, sans-serif; color: #424242; text-align: justify; @@ -61,7 +61,7 @@ border-radius: 225px; -webkit-border-radius: 225px; -moz-border-radius: 225px; - background: url(/assets/monkeyselfie.jpg) no-repeat; + background: url(https://s3.amazonaws.com/metamaps-assets/site/monkeyselfie.jpg) no-repeat; float: left; background-position:50% 20%; background-size: 100%; diff --git a/realtime/README.md b/realtime/README.md index 468f526e..5c81979b 100644 --- a/realtime/README.md +++ b/realtime/README.md @@ -1 +1,18 @@ -'Real-Time' +## Node.js realtime server + +To run the server, you need to install the dependencies and run the server. +Please ensure you have followed the OS-specific instructions in doc/ to +install NodeJS. Once you have node, then you can proceed to install the +node packages for the realtime server: + + cd realtime + npm install #creates node_modules directory + node realtime-server.js + +That's it! + +To run the server as a daemon that will be re-run if it crashes, you can +use the forever node package. + + sudo npm install -g forever + forever start realtime-server.js diff --git a/realtime/package.json b/realtime/package.json index 1a11284a..0bedc1c4 100644 --- a/realtime/package.json +++ b/realtime/package.json @@ -2,6 +2,7 @@ "name": "RoR-real-time", "description": "providing real-time sychronization for ruby on rails", "version": "0.0.1", + "private": true, "dependencies": { "socket.io": "0.9.12" } diff --git a/spec/controllers/mappings_controller_spec.rb b/spec/controllers/mappings_controller_spec.rb new file mode 100644 index 00000000..8a7acda5 --- /dev/null +++ b/spec/controllers/mappings_controller_spec.rb @@ -0,0 +1,160 @@ +require 'rails_helper' + +# This spec was generated by rspec-rails when you ran the scaffold generator. +# It demonstrates how one might use RSpec to specify the controller code that +# was generated by Rails when you ran the scaffold generator. +# +# It assumes that the implementation code is generated by the rails scaffold +# generator. If you are using any extension libraries to generate different +# controller code, this generated spec may or may not pass. +# +# It only uses APIs available in rails and/or rspec-rails. There are a number +# of tools you can use to make these specs even more expressive, but we're +# sticking to rails and rspec-rails APIs to keep things simple and stable. +# +# Compared to earlier versions of this generator, there is very limited use of +# stubs and message expectations in this spec. Stubs are only used when there +# is no simpler way to get a handle on the object needed for the example. +# Message expectations are only used when there is no simpler way to specify +# that an instance is receiving a specific message. + +RSpec.describe MappingsController, type: :controller do + # This should return the minimal set of attributes required to create a valid + # Mapping. As you add validations to Mapping, be sure to + # adjust the attributes here as well. + let(:valid_attributes) do + skip('Add a hash of attributes valid for your model') + end + + let(:invalid_attributes) do + skip('Add a hash of attributes invalid for your model') + end + + # This should return the minimal set of values that should be in the session + # in order to pass any filters (e.g. authentication) defined in + # MappingsController. Be sure to keep this updated too. + let(:valid_session) { {} } + + describe 'GET #index' do + it 'assigns all mappings as @mappings' do + mapping = Mapping.create! valid_attributes + get :index, {}, valid_session + expect(assigns(:mappings)).to eq([mapping]) + end + end + + describe 'GET #show' do + it 'assigns the requested mapping as @mapping' do + mapping = Mapping.create! valid_attributes + get :show, { id: mapping.to_param }, valid_session + expect(assigns(:mapping)).to eq(mapping) + end + end + + describe 'GET #edit' do + it 'assigns the requested mapping as @mapping' do + mapping = Mapping.create! valid_attributes + get :edit, { id: mapping.to_param }, valid_session + expect(assigns(:mapping)).to eq(mapping) + end + end + + describe 'POST #create' do + context 'with valid params' do + it 'creates a new Mapping' do + expect do + post :create, { mapping: valid_attributes }, valid_session + end.to change(Mapping, :count).by(1) + end + + it 'assigns a newly created mapping as @mapping' do + post :create, { mapping: valid_attributes }, valid_session + expect(assigns(:mapping)).to be_a(Mapping) + expect(assigns(:mapping)).to be_persisted + end + + it 'redirects to the created mapping' do + post :create, { mapping: valid_attributes }, valid_session + expect(response).to redirect_to(Mapping.last) + end + end + + context 'with invalid params' do + it 'assigns a newly created but unsaved mapping as @mapping' do + post :create, { mapping: invalid_attributes }, valid_session + expect(assigns(:mapping)).to be_a_new(Mapping) + end + + it "re-renders the 'new' template" do + post :create, { mapping: invalid_attributes }, valid_session + expect(response).to render_template('new') + end + end + end + + describe 'PUT #update' do + context 'with valid params' do + let(:new_attributes) do + skip('Add a hash of attributes valid for your model') + end + + it 'updates the requested mapping' do + mapping = Mapping.create! valid_attributes + put :update, + { id: mapping.to_param, mapping: new_attributes }, + valid_session + mapping.reload + skip('Add assertions for updated state') + end + + it 'assigns the requested mapping as @mapping' do + mapping = Mapping.create! valid_attributes + put :update, + { id: mapping.to_param, mapping: valid_attributes }, + valid_session + expect(assigns(:mapping)).to eq(mapping) + end + + it 'redirects to the mapping' do + mapping = Mapping.create! valid_attributes + put :update, + { id: mapping.to_param, mapping: valid_attributes }, + valid_session + expect(response).to redirect_to(mapping) + end + end + + context 'with invalid params' do + it 'assigns the mapping as @mapping' do + mapping = Mapping.create! valid_attributes + put :update, + { id: mapping.to_param, mapping: invalid_attributes }, + valid_session + expect(assigns(:mapping)).to eq(mapping) + end + + it "re-renders the 'edit' template" do + mapping = Mapping.create! valid_attributes + put :update, + { id: mapping.to_param, mapping: invalid_attributes }, + valid_session + expect(response).to render_template('edit') + end + end + end + + describe 'DELETE #destroy' do + it 'destroys the requested mapping' do + mapping = Mapping.create! valid_attributes + expect do + delete :destroy, { id: mapping.to_param }, valid_session + end.to change(Mapping, :count).by(-1) + end + + it 'redirects to the mappings list' do + mapping = Mapping.create! valid_attributes + delete :destroy, { id: mapping.to_param }, valid_session + expect(response).to redirect_to(mappings_url) + end + end +end diff --git a/spec/controllers/maps_controller_spec.rb b/spec/controllers/maps_controller_spec.rb new file mode 100644 index 00000000..fdaa064a --- /dev/null +++ b/spec/controllers/maps_controller_spec.rb @@ -0,0 +1,160 @@ +require 'rails_helper' + +# This spec was generated by rspec-rails when you ran the scaffold generator. +# It demonstrates how one might use RSpec to specify the controller code that +# was generated by Rails when you ran the scaffold generator. +# +# It assumes that the implementation code is generated by the rails scaffold +# generator. If you are using any extension libraries to generate different +# controller code, this generated spec may or may not pass. +# +# It only uses APIs available in rails and/or rspec-rails. There are a number +# of tools you can use to make these specs even more expressive, but we're +# sticking to rails and rspec-rails APIs to keep things simple and stable. +# +# Compared to earlier versions of this generator, there is very limited use of +# stubs and message expectations in this spec. Stubs are only used when there +# is no simpler way to get a handle on the object needed for the example. +# Message expectations are only used when there is no simpler way to specify +# that an instance is receiving a specific message. + +RSpec.describe MapsController, type: :controller do + # This should return the minimal set of attributes required to create a valid + # Map. As you add validations to Map, be sure to + # adjust the attributes here as well. + let(:valid_attributes) do + skip('Add a hash of attributes valid for your model') + end + + let(:invalid_attributes) do + skip('Add a hash of attributes invalid for your model') + end + + # This should return the minimal set of values that should be in the session + # in order to pass any filters (e.g. authentication) defined in + # MapsController. Be sure to keep this updated too. + let(:valid_session) { {} } + + describe 'GET #index' do + it 'assigns all maps as @maps' do + map = Map.create! valid_attributes + get :index, {}, valid_session + expect(assigns(:maps)).to eq([map]) + end + end + + describe 'GET #show' do + it 'assigns the requested map as @map' do + map = Map.create! valid_attributes + get :show, { id: map.to_param }, valid_session + expect(assigns(:map)).to eq(map) + end + end + + describe 'GET #edit' do + it 'assigns the requested map as @map' do + map = Map.create! valid_attributes + get :edit, { id: map.to_param }, valid_session + expect(assigns(:map)).to eq(map) + end + end + + describe 'POST #create' do + context 'with valid params' do + it 'creates a new Map' do + expect do + post :create, { map: valid_attributes }, valid_session + end.to change(Map, :count).by(1) + end + + it 'assigns a newly created map as @map' do + post :create, { map: valid_attributes }, valid_session + expect(assigns(:map)).to be_a(Map) + expect(assigns(:map)).to be_persisted + end + + it 'redirects to the created map' do + post :create, { map: valid_attributes }, valid_session + expect(response).to redirect_to(Map.last) + end + end + + context 'with invalid params' do + it 'assigns a newly created but unsaved map as @map' do + post :create, { map: invalid_attributes }, valid_session + expect(assigns(:map)).to be_a_new(Map) + end + + it "re-renders the 'new' template" do + post :create, { map: invalid_attributes }, valid_session + expect(response).to render_template('new') + end + end + end + + describe 'PUT #update' do + context 'with valid params' do + let(:new_attributes) do + skip('Add a hash of attributes valid for your model') + end + + it 'updates the requested map' do + map = Map.create! valid_attributes + put :update, + { id: map.to_param, map: new_attributes }, + valid_session + map.reload + skip('Add assertions for updated state') + end + + it 'assigns the requested map as @map' do + map = Map.create! valid_attributes + put :update, + { id: map.to_param, map: valid_attributes }, + valid_session + expect(assigns(:map)).to eq(map) + end + + it 'redirects to the map' do + map = Map.create! valid_attributes + put :update, + { id: map.to_param, map: valid_attributes }, + valid_session + expect(response).to redirect_to(map) + end + end + + context 'with invalid params' do + it 'assigns the map as @map' do + map = Map.create! valid_attributes + put :update, + { id: map.to_param, map: invalid_attributes }, + valid_session + expect(assigns(:map)).to eq(map) + end + + it "re-renders the 'edit' template" do + map = Map.create! valid_attributes + put :update, + { id: map.to_param, map: invalid_attributes }, + valid_session + expect(response).to render_template('edit') + end + end + end + + describe 'DELETE #destroy' do + it 'destroys the requested map' do + map = Map.create! valid_attributes + expect do + delete :destroy, { id: map.to_param }, valid_session + end.to change(Map, :count).by(-1) + end + + it 'redirects to the maps list' do + map = Map.create! valid_attributes + delete :destroy, { id: map.to_param }, valid_session + expect(response).to redirect_to(maps_url) + end + end +end diff --git a/spec/controllers/metacodes_controller_spec.rb b/spec/controllers/metacodes_controller_spec.rb new file mode 100644 index 00000000..6e1ba2b9 --- /dev/null +++ b/spec/controllers/metacodes_controller_spec.rb @@ -0,0 +1,172 @@ +require 'rails_helper' + +# This spec was generated by rspec-rails when you ran the scaffold generator. +# It demonstrates how one might use RSpec to specify the controller code that +# was generated by Rails when you ran the scaffold generator. +# +# It assumes that the implementation code is generated by the rails scaffold +# generator. If you are using any extension libraries to generate different +# controller code, this generated spec may or may not pass. +# +# It only uses APIs available in rails and/or rspec-rails. There are a number +# of tools you can use to make these specs even more expressive, but we're +# sticking to rails and rspec-rails APIs to keep things simple and stable. +# +# Compared to earlier versions of this generator, there is very limited use of +# stubs and message expectations in this spec. Stubs are only used when there +# is no simpler way to get a handle on the object needed for the example. +# Message expectations are only used when there is no simpler way to specify +# that an instance is receiving a specific message. + +RSpec.describe MetacodesController, type: :controller do + before :each do + @user = create(:user, admin: true) + sign_in @user + end + + # This should return the minimal set of attributes required to create a valid + # Metacode. As you add validations to Metacode, be sure to + # adjust the attributes here as well. + let(:valid_attributes) do + skip('Add a hash of attributes valid for your model') + end + + let(:invalid_attributes) do + skip('Add a hash of attributes invalid for your model') + end + + # This should return the minimal set of values that should be in the session + # in order to pass any filters (e.g. authentication) defined in + # MetacodesController. Be sure to keep this updated too. + let(:valid_session) { {} } + + describe 'GET #index' do + it 'assigns all metacodes as @metacodes' do + metacode = Metacode.create! valid_attributes + get :index, {}, valid_session + expect(assigns(:metacodes)).to eq([metacode]) + end + end + + describe 'GET #show' do + it 'assigns the requested metacode as @metacode' do + metacode = Metacode.create! valid_attributes + get :show, { id: metacode.to_param }, valid_session + expect(assigns(:metacode)).to eq(metacode) + end + end + + describe 'GET #new' do + it 'assigns a new metacode as @metacode' do + get :new, {}, valid_session + expect(assigns(:metacode)).to be_a_new(Metacode) + end + end + + describe 'GET #edit' do + it 'assigns the requested metacode as @metacode' do + metacode = Metacode.create! valid_attributes + get :edit, { id: metacode.to_param }, valid_session + expect(assigns(:metacode)).to eq(metacode) + end + end + + describe 'POST #create' do + context 'with valid params' do + it 'creates a new Metacode' do + expect do + post :create, { metacode: valid_attributes }, valid_session + end.to change(Metacode, :count).by(1) + end + + it 'assigns a newly created metacode as @metacode' do + post :create, { metacode: valid_attributes }, valid_session + expect(assigns(:metacode)).to be_a(Metacode) + expect(assigns(:metacode)).to be_persisted + end + + it 'redirects to the created metacode' do + post :create, { metacode: valid_attributes }, valid_session + expect(response).to redirect_to(Metacode.last) + end + end + + context 'with invalid params' do + it 'assigns a newly created but unsaved metacode as @metacode' do + post :create, { metacode: invalid_attributes }, valid_session + expect(assigns(:metacode)).to be_a_new(Metacode) + end + + it "re-renders the 'new' template" do + post :create, { metacode: invalid_attributes }, valid_session + expect(response).to render_template('new') + end + end + end + + describe 'PUT #update' do + context 'with valid params' do + let(:new_attributes) do + skip('Add a hash of attributes valid for your model') + end + + it 'updates the requested metacode' do + metacode = Metacode.create! valid_attributes + put :update, + { id: metacode.to_param, metacode: new_attributes }, + valid_session + metacode.reload + skip('Add assertions for updated state') + end + + it 'assigns the requested metacode as @metacode' do + metacode = Metacode.create! valid_attributes + put :update, + { id: metacode.to_param, metacode: valid_attributes }, + valid_session + expect(assigns(:metacode)).to eq(metacode) + end + + it 'redirects to the metacode' do + metacode = Metacode.create! valid_attributes + put :update, + { id: metacode.to_param, metacode: valid_attributes }, + valid_session + expect(response).to redirect_to(metacode) + end + end + + context 'with invalid params' do + it 'assigns the metacode as @metacode' do + metacode = Metacode.create! valid_attributes + put :update, + { id: metacode.to_param, metacode: invalid_attributes }, + valid_session + expect(assigns(:metacode)).to eq(metacode) + end + + it "re-renders the 'edit' template" do + metacode = Metacode.create! valid_attributes + put :update, + { id: metacode.to_param, metacode: invalid_attributes }, + valid_session + expect(response).to render_template('edit') + end + end + end + + describe 'DELETE #destroy' do + it 'destroys the requested metacode' do + metacode = Metacode.create! valid_attributes + expect do + delete :destroy, { id: metacode.to_param }, valid_session + end.to change(Metacode, :count).by(-1) + end + + it 'redirects to the metacodes list' do + metacode = Metacode.create! valid_attributes + delete :destroy, { id: metacode.to_param }, valid_session + expect(response).to redirect_to(metacodes_url) + end + end +end diff --git a/spec/controllers/synapses_controller_spec.rb b/spec/controllers/synapses_controller_spec.rb new file mode 100644 index 00000000..ff05ea6f --- /dev/null +++ b/spec/controllers/synapses_controller_spec.rb @@ -0,0 +1,160 @@ +require 'rails_helper' + +# This spec was generated by rspec-rails when you ran the scaffold generator. +# It demonstrates how one might use RSpec to specify the controller code that +# was generated by Rails when you ran the scaffold generator. +# +# It assumes that the implementation code is generated by the rails scaffold +# generator. If you are using any extension libraries to generate different +# controller code, this generated spec may or may not pass. +# +# It only uses APIs available in rails and/or rspec-rails. There are a number +# of tools you can use to make these specs even more expressive, but we're +# sticking to rails and rspec-rails APIs to keep things simple and stable. +# +# Compared to earlier versions of this generator, there is very limited use of +# stubs and message expectations in this spec. Stubs are only used when there +# is no simpler way to get a handle on the object needed for the example. +# Message expectations are only used when there is no simpler way to specify +# that an instance is receiving a specific message. + +RSpec.describe SynapsesController, type: :controller do + # This should return the minimal set of attributes required to create a valid + # Synapse. As you add validations to Synapse, be sure to + # adjust the attributes here as well. + let(:valid_attributes) do + skip('Add a hash of attributes valid for your model') + end + + let(:invalid_attributes) do + skip('Add a hash of attributes invalid for your model') + end + + # This should return the minimal set of values that should be in the session + # in order to pass any filters (e.g. authentication) defined in + # SynapsesController. Be sure to keep this updated too. + let(:valid_session) { {} } + + describe 'GET #index' do + it 'assigns all synapses as @synapses' do + synapse = Synapse.create! valid_attributes + get :index, {}, valid_session + expect(assigns(:synapses)).to eq([synapse]) + end + end + + describe 'GET #show' do + it 'assigns the requested synapse as @synapse' do + synapse = Synapse.create! valid_attributes + get :show, { id: synapse.to_param }, valid_session + expect(assigns(:synapse)).to eq(synapse) + end + end + + describe 'GET #edit' do + it 'assigns the requested synapse as @synapse' do + synapse = Synapse.create! valid_attributes + get :edit, { id: synapse.to_param }, valid_session + expect(assigns(:synapse)).to eq(synapse) + end + end + + describe 'POST #create' do + context 'with valid params' do + it 'creates a new Synapse' do + expect do + post :create, { synapse: valid_attributes }, valid_session + end.to change(Synapse, :count).by(1) + end + + it 'assigns a newly created synapse as @synapse' do + post :create, { synapse: valid_attributes }, valid_session + expect(assigns(:synapse)).to be_a(Synapse) + expect(assigns(:synapse)).to be_persisted + end + + it 'redirects to the created synapse' do + post :create, { synapse: valid_attributes }, valid_session + expect(response).to redirect_to(Synapse.last) + end + end + + context 'with invalid params' do + it 'assigns a newly created but unsaved synapse as @synapse' do + post :create, { synapse: invalid_attributes }, valid_session + expect(assigns(:synapse)).to be_a_new(Synapse) + end + + it "re-renders the 'new' template" do + post :create, { synapse: invalid_attributes }, valid_session + expect(response).to render_template('new') + end + end + end + + describe 'PUT #update' do + context 'with valid params' do + let(:new_attributes) do + skip('Add a hash of attributes valid for your model') + end + + it 'updates the requested synapse' do + synapse = Synapse.create! valid_attributes + put :update, + { id: synapse.to_param, synapse: new_attributes }, + valid_session + synapse.reload + skip('Add assertions for updated state') + end + + it 'assigns the requested synapse as @synapse' do + synapse = Synapse.create! valid_attributes + put :update, + { id: synapse.to_param, synapse: valid_attributes }, + valid_session + expect(assigns(:synapse)).to eq(synapse) + end + + it 'redirects to the synapse' do + synapse = Synapse.create! valid_attributes + put :update, + { id: synapse.to_param, synapse: valid_attributes }, + valid_session + expect(response).to redirect_to(synapse) + end + end + + context 'with invalid params' do + it 'assigns the synapse as @synapse' do + synapse = Synapse.create! valid_attributes + put :update, + { id: synapse.to_param, synapse: invalid_attributes }, + valid_session + expect(assigns(:synapse)).to eq(synapse) + end + + it "re-renders the 'edit' template" do + synapse = Synapse.create! valid_attributes + put :update, + { id: synapse.to_param, synapse: invalid_attributes }, + valid_session + expect(response).to render_template('edit') + end + end + end + + describe 'DELETE #destroy' do + it 'destroys the requested synapse' do + synapse = Synapse.create! valid_attributes + expect do + delete :destroy, { id: synapse.to_param }, valid_session + end.to change(Synapse, :count).by(-1) + end + + it 'redirects to the synapses list' do + synapse = Synapse.create! valid_attributes + delete :destroy, { id: synapse.to_param }, valid_session + expect(response).to redirect_to(synapses_url) + end + end +end diff --git a/spec/controllers/topics_controller_spec.rb b/spec/controllers/topics_controller_spec.rb new file mode 100644 index 00000000..35b2156e --- /dev/null +++ b/spec/controllers/topics_controller_spec.rb @@ -0,0 +1,136 @@ +require 'rails_helper' + +RSpec.describe TopicsController, type: :controller do + let(:valid_attributes) do + skip('Add a hash of attributes valid for your model') + end + + let(:invalid_attributes) do + skip('Add a hash of attributes invalid for your model') + end + + let(:valid_session) { {} } + + describe 'GET #index' do + it 'assigns all topics as @topics' do + topic = Topic.create! valid_attributes + get :index, {}, valid_session + expect(assigns(:topics)).to eq([topic]) + end + end + + describe 'GET #show' do + it 'assigns the requested topic as @topic' do + topic = Topic.create! valid_attributes + get :show, { id: topic.to_param }, valid_session + expect(assigns(:topic)).to eq(topic) + end + end + + describe 'GET #edit' do + it 'assigns the requested topic as @topic' do + topic = Topic.create! valid_attributes + get :edit, { id: topic.to_param }, valid_session + expect(assigns(:topic)).to eq(topic) + end + end + + describe 'POST #create' do + context 'with valid params' do + it 'creates a new Topic' do + expect do + post :create, { topic: valid_attributes }, valid_session + end.to change(Topic, :count).by(1) + end + + it 'assigns a newly created topic as @topic' do + post :create, { topic: valid_attributes }, valid_session + expect(assigns(:topic)).to be_a(Topic) + expect(assigns(:topic)).to be_persisted + end + + it 'redirects to the created topic' do + post :create, { topic: valid_attributes }, valid_session + expect(response).to redirect_to(Topic.last) + end + end + + context 'with invalid params' do + it 'assigns a newly created but unsaved topic as @topic' do + post :create, { topic: invalid_attributes }, valid_session + expect(assigns(:topic)).to be_a_new(Topic) + end + + it "re-renders the 'new' template" do + post :create, { topic: invalid_attributes }, valid_session + expect(response).to render_template('new') + end + end + end + + describe 'PUT #update' do + context 'with valid params' do + let(:new_attributes) do + skip('Add a hash of attributes valid for your model') + end + + it 'updates the requested topic' do + topic = Topic.create! valid_attributes + put :update, + { id: topic.to_param, topic: new_attributes }, + valid_session + topic.reload + skip('Add assertions for updated state') + end + + it 'assigns the requested topic as @topic' do + topic = Topic.create! valid_attributes + put :update, + { id: topic.to_param, topic: valid_attributes }, + valid_session + expect(assigns(:topic)).to eq(topic) + end + + it 'redirects to the topic' do + topic = Topic.create! valid_attributes + put :update, + { id: topic.to_param, topic: valid_attributes }, + valid_session + expect(response).to redirect_to(topic) + end + end + + context 'with invalid params' do + it 'assigns the topic as @topic' do + topic = Topic.create! valid_attributes + put :update, + { id: topic.to_param, topic: invalid_attributes }, + valid_session + expect(assigns(:topic)).to eq(topic) + end + + it "re-renders the 'edit' template" do + topic = Topic.create! valid_attributes + put :update, + { id: topic.to_param, topic: invalid_attributes }, + valid_session + expect(response).to render_template('edit') + end + end + end + + describe 'DELETE #destroy' do + it 'destroys the requested topic' do + topic = Topic.create! valid_attributes + expect do + delete :destroy, { id: topic.to_param }, valid_session + end.to change(Topic, :count).by(-1) + end + + it 'redirects to the topics list' do + topic = Topic.create! valid_attributes + delete :destroy, { id: topic.to_param }, valid_session + expect(response).to redirect_to(topics_url) + end + end +end diff --git a/spec/factories/maps.rb b/spec/factories/maps.rb new file mode 100644 index 00000000..a786d109 --- /dev/null +++ b/spec/factories/maps.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :map do + sequence(:name) { |n| "Cool Map ##{n}" } + permission :commons + arranged { false } + user + end +end diff --git a/spec/factories/metacodes.rb b/spec/factories/metacodes.rb new file mode 100644 index 00000000..5cfb38f6 --- /dev/null +++ b/spec/factories/metacodes.rb @@ -0,0 +1,4 @@ +FactoryGirl.define do + factory :metacode do + end +end diff --git a/spec/factories/synapses.rb b/spec/factories/synapses.rb new file mode 100644 index 00000000..b83a0073 --- /dev/null +++ b/spec/factories/synapses.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + factory :synapse do + sequence(:desc) { |n| "Cool synapse ##{n}" } + category :to + permission :commons + association :topic1, factory: :topic + association :topic2, factory: :topic + end +end diff --git a/spec/factories/topics.rb b/spec/factories/topics.rb new file mode 100644 index 00000000..17c69a25 --- /dev/null +++ b/spec/factories/topics.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :topic do + sequence(:name) { |n| "Cool Topic ##{n}" } + permission :commons + user + metacode + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb new file mode 100644 index 00000000..91da1d1c --- /dev/null +++ b/spec/factories/users.rb @@ -0,0 +1,29 @@ +# +# This file supports three factories, because code and joinedwithcode +# make things complicated! +# +# Generally, use :simple_user to generate users. It's simplest. +# +# If you want to test code generation, or need a setup with users that +# have actual codes, you'll need to specify one simple_user and then you +# can specify other :code_user users based on the pre-existing user's code. + +FactoryGirl.define do + factory :code_user, class: User do + sequence(:name) { |n| "Cool User ##{n}" } + sequence(:email) { |n| "cooluser#{n}@cooldomain.com" } + password 'omgwtfbbq' + + factory :simple_user, aliases: [:user] do + joinedwithcode { "qwertyui" } + code { "qwertyui" } + + transient do + validate false + end + + # bypass validation of the joinedwithcode + to_create { |instance| instance.save(validate: instance.validate) } + end + end +end diff --git a/spec/models/map_spec.rb b/spec/models/map_spec.rb new file mode 100644 index 00000000..d1486192 --- /dev/null +++ b/spec/models/map_spec.rb @@ -0,0 +1,38 @@ +require 'rails_helper' + +RSpec.describe Map, type: :model do + it { is_expected.to belong_to :user } + it { is_expected.to validate_presence_of :name } + it { is_expected.to validate_presence_of :permission } + it { is_expected.to validate_inclusion_of(:permission).in_array Perm::ISSIONS.map(&:to_s) } + + context 'permissions' do + let(:owner) { create :user } + let(:other_user) { create :user } + let(:map) { create :map, user: owner, permission: :commons } + let(:private_map) { create :map, user: owner, permission: :private } + let(:public_map) { create :map, user: owner, permission: :public } + + it 'prevents deletion by non-owner' do + expect(map.authorize_to_delete(other_user)).to eq false + expect(map.authorize_to_delete(owner)).to eq map + end + + it 'prevents visibility if private' do + expect(map.authorize_to_show(other_user)).to eq map + expect(map.authorize_to_show(owner)).to eq map + expect(private_map.authorize_to_show(owner)).to eq private_map + expect(private_map.authorize_to_show(other_user)).to eq false + end + + it 'only allows editing if commons or owned' do + expect(map.authorize_to_edit(other_user)).to eq map + expect(map.authorize_to_edit(owner)).to eq map + expect(private_map.authorize_to_edit(other_user)).to eq false + expect(private_map.authorize_to_edit(owner)).to eq private_map + expect(public_map.authorize_to_edit(other_user)).to eq false + expect(public_map.authorize_to_edit(owner)).to eq public_map + end + end +end + diff --git a/spec/models/mapping_spec.rb b/spec/models/mapping_spec.rb new file mode 100644 index 00000000..32d34796 --- /dev/null +++ b/spec/models/mapping_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Mapping, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/metacode_spec.rb b/spec/models/metacode_spec.rb new file mode 100644 index 00000000..10571a81 --- /dev/null +++ b/spec/models/metacode_spec.rb @@ -0,0 +1,6 @@ +require 'rails_helper' + +RSpec.describe Metacode, type: :model do + pending "add some examples to (or delete) #{__FILE__}" + it { is_expected.to have_many(:topics) } +end diff --git a/spec/models/synapse_spec.rb b/spec/models/synapse_spec.rb new file mode 100644 index 00000000..a1069805 --- /dev/null +++ b/spec/models/synapse_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +RSpec.describe Synapse, type: :model do + it { is_expected.to belong_to :user } + it { is_expected.to belong_to :topic1 } + it { is_expected.to belong_to :topic2 } + it { is_expected.to have_many :maps } + it { is_expected.to have_many :mappings } + it { is_expected.to validate_presence_of :permission } + it { is_expected.to validate_inclusion_of(:permission).in_array Perm::ISSIONS.map(&:to_s) } + it { is_expected.to validate_length_of(:desc).is_at_least(0) } # TODO don't allow nil + + context 'permissions' do + let(:owner) { create :user } + let(:other_user) { create :user } + let(:synapse) { create :synapse, user: owner, permission: :commons } + let(:private_synapse) { create :synapse, user: owner, permission: :private } + let(:public_synapse) { create :synapse, user: owner, permission: :public } + + it 'prevents deletion by non-owner' do + expect(synapse.authorize_to_delete(other_user)).to eq false + expect(synapse.authorize_to_delete(owner)).to eq synapse + end + + it 'prevents visibility if private' do + expect(synapse.authorize_to_show(other_user)).to eq synapse + expect(synapse.authorize_to_show(owner)).to eq synapse + expect(private_synapse.authorize_to_show(owner)).to eq private_synapse + expect(private_synapse.authorize_to_show(other_user)).to eq false + end + + it 'only allows editing if commons or owned' do + expect(synapse.authorize_to_edit(other_user)).to eq synapse + expect(synapse.authorize_to_edit(owner)).to eq synapse + expect(private_synapse.authorize_to_edit(other_user)).to eq false + expect(private_synapse.authorize_to_edit(owner)).to eq private_synapse + expect(public_synapse.authorize_to_edit(other_user)).to eq false + expect(public_synapse.authorize_to_edit(owner)).to eq public_synapse + end + end +end diff --git a/spec/models/topic_spec.rb b/spec/models/topic_spec.rb new file mode 100644 index 00000000..b499daac --- /dev/null +++ b/spec/models/topic_spec.rb @@ -0,0 +1,81 @@ +require 'rails_helper' + +RSpec.describe Topic, type: :model do + it { is_expected.to belong_to :user } + it { is_expected.to belong_to :metacode } + it { is_expected.to have_many :maps } + it { is_expected.to have_many :mappings } + it { is_expected.to validate_presence_of :permission } + it { is_expected.to validate_inclusion_of(:permission).in_array Perm::ISSIONS.map(&:to_s) } + + context 'has_viewable_synapses function' do + let (:user) { create(:user) } + let (:other_user) { create(:user) } + + context 'topic with no synapses' do + let (:topic) { create(:topic) } + + it 'returns false' do + expect(topic.has_viewable_synapses(user)).to eq false + end + end + + context 'topic with one unpermitted synapse' do + let (:synapse) { create(:synapse, permission: :private, user: other_user) } + let (:topic) { create(:topic, synapses1: [synapse]) } + + it 'returns false' do + expect(topic.has_viewable_synapses(user)).to eq false + end + end + + context 'topic with one permitted synapse' do + let (:synapse) { create(:synapse, permission: :private, user: user) } + let(:topic) { create(:topic, synapses1: [synapse]) } + + it 'returns true' do + expect(topic.has_viewable_synapses(user)).to eq true + end + end + + context 'topic with one unpermitted, one permitted synapse' do + let (:synapse1) { create(:synapse, permission: :private, user: other_user) } + let (:synapse2) { create(:synapse, permission: :private, user: user) } + let (:topic) { create(:topic, synapses1: [synapse1, synapse2]) } + + it 'returns true' do + expect(topic.synapses.count).to eq 2 + expect(topic.has_viewable_synapses(user)).to eq true + end + end + end + + context 'permssions' do + let(:owner) { create :user } + let(:other_user) { create :user } + let(:topic) { create :topic, user: owner, permission: :commons } + let(:private_topic) { create :topic, user: owner, permission: :private } + let(:public_topic) { create :topic, user: owner, permission: :public } + + it 'prevents deletion by non-owner' do + expect(topic.authorize_to_delete(other_user)).to eq false + expect(topic.authorize_to_delete(owner)).to eq topic + end + + it 'prevents visibility if private' do + expect(topic.authorize_to_show(other_user)).to eq topic + expect(topic.authorize_to_show(owner)).to eq topic + expect(private_topic.authorize_to_show(owner)).to eq private_topic + expect(private_topic.authorize_to_show(other_user)).to eq false + end + + it 'only allows editing if commons or owned' do + expect(topic.authorize_to_edit(other_user)).to eq topic + expect(topic.authorize_to_edit(owner)).to eq topic + expect(private_topic.authorize_to_edit(other_user)).to eq false + expect(private_topic.authorize_to_edit(owner)).to eq private_topic + expect(public_topic.authorize_to_edit(other_user)).to eq false + expect(public_topic.authorize_to_edit(owner)).to eq public_topic + end + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb new file mode 100644 index 00000000..23f21101 --- /dev/null +++ b/spec/rails_helper.rb @@ -0,0 +1,45 @@ +# This file is copied to spec/ when you run 'rails generate rspec:install' +ENV['RAILS_ENV'] ||= 'test' +require File.expand_path('../../config/environment', __FILE__) + +# Prevent database truncation if the environment is production +if Rails.env.production? + abort('The Rails environment is running in production mode!') +end + +require 'spec_helper' +require 'rspec/rails' +# Add additional requires below this line. Rails is not loaded until this point! + +# require all support files +Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } + +RSpec.configure do |config| + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + config.fixture_path = "#{::Rails.root}/spec/fixtures" + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + # RSpec Rails can automatically mix in different behaviours to your tests + # based on their file location, for example enabling you to call `get` and + # `post` in specs under `spec/controllers`. + # + # You can disable this behaviour by removing the line below, and instead + # explicitly tag your specs with their type, e.g.: + # + # RSpec.describe UsersController, :type => :controller do + # # ... + # end + # + # The different available types are documented in the features, such as in + # https://relishapp.com/rspec/rspec-rails/docs + config.infer_spec_type_from_file_location! + + config.include Devise::TestHelpers, type: :controller + config.include ControllerHelpers, type: :controller + config.include Shoulda::Matchers::ActiveModel, type: :model + config.include Shoulda::Matchers::ActiveRecord, type: :model +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 00000000..a2b164b2 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,9 @@ +RSpec.configure do |config| + config.expect_with :rspec do |expectations| + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + end +end diff --git a/spec/support/controller_helpers.rb b/spec/support/controller_helpers.rb new file mode 100644 index 00000000..5fe34854 --- /dev/null +++ b/spec/support/controller_helpers.rb @@ -0,0 +1,17 @@ +# https://github.com/plataformatec/devise/wiki/How-To:-Stub-authentication-in-controller-specs +module ControllerHelpers + # rubocop:disable Metrics/AbcSize + def sign_in(user = create(:user)) + if user.nil? # simulate unauthenticated + allow(request.env['warden']).to( + receive(:authenticate!).and_throw(:warden, scope: :user) + ) + else # simulate authenticated + allow(request.env['warden']).to( + receive(:authenticate!).and_return(user) + ) + end + allow(controller).to receive(:current_user).and_return(user) + end + # rubocop:enable Metrics/AbcSize +end diff --git a/spec/support/factory_girl.rb b/spec/support/factory_girl.rb new file mode 100644 index 00000000..afae617a --- /dev/null +++ b/spec/support/factory_girl.rb @@ -0,0 +1,4 @@ +# lets you type create(:user) instead of FactoryGirl.create(:user) +RSpec.configure do |config| + config.include FactoryGirl::Syntax::Methods +end diff --git a/test/fixtures/.gitkeep b/test/fixtures/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/test/fixtures/in_metacode_sets.yml b/test/fixtures/in_metacode_sets.yml deleted file mode 100644 index 9879c73b..00000000 --- a/test/fixtures/in_metacode_sets.yml +++ /dev/null @@ -1,9 +0,0 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html - -#one: -# metacode: -# metacode_set: - -#two: -# metacode: -# metacode_set: diff --git a/test/fixtures/mappings.yml b/test/fixtures/mappings.yml deleted file mode 100644 index 9cfe87b6..00000000 --- a/test/fixtures/mappings.yml +++ /dev/null @@ -1,11 +0,0 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html - -# This model initially had no columns defined. If you add columns to the -# model remove the '{}' from the fixture names and add the columns immediately -# below each fixture, per the syntax in the comments below -# -#one: {} -# column: value -# -#two: {} -# column: value diff --git a/test/fixtures/metacode_sets.yml b/test/fixtures/metacode_sets.yml deleted file mode 100644 index 56bda62e..00000000 --- a/test/fixtures/metacode_sets.yml +++ /dev/null @@ -1,13 +0,0 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html - -#one: -# name: MyString -# desc: MyText -# user: -# mapperContributed: false - -#two: -# name: MyString -# desc: MyText -# user: -# mapperContributed: false diff --git a/test/fixtures/metacodes.yml b/test/fixtures/metacodes.yml deleted file mode 100644 index 02ec0663..00000000 --- a/test/fixtures/metacodes.yml +++ /dev/null @@ -1,240 +0,0 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html - -# This model initially had no columns defined. If you add columns to the -# model remove the '{}' from the fixture names and add the columns immediately -# below each fixture, per the syntax in the comments below -# -one: - name: Action - icon: /assets/icons/blueprint_96px/bp_action.png - color: #BD6C85 - -two: - name: Activity - icon: /assets/icons/blueprint_96px/bp_activity.png - color: #6EBF65 - -three: - name: Catalyst - icon: /assets/icons/blueprint_96px/bp_catalyst.png - color: #EF8964 - -four: - name: Closed - icon: /assets/icons/blueprint_96px/bp_closedissue.png - color: #ABB49F - -five: - name: Process - icon: /assets/icons/blueprint_96px/bp_process.png - color: #BDB25E - -six: - name: Future Dev - icon: /assets/icons/blueprint_96px/bp_futuredev.png - color: #25A17F - -seven: - name: Group - icon: /assets/icons/blueprint_96px/bp_group.png - color: #7076BC - -eight: - name: Implication - icon: /assets/icons/blueprint_96px/bp_implication.png - color: #83DECA - -nine: - name: Insight - icon: /assets/icons/blueprint_96px/bp_insight.png - color: #B074AD - -ten: - name: Intention - icon: /assets/icons/blueprint_96px/bp_intention.png - color: #BAEAFF - -eleven: - name: Knowledge - icon: /assets/icons/blueprint_96px/bp_knowledge.png - color: #60ACF7 - -twelve: - name: Location - icon: /assets/icons/blueprint_96px/bp_location.png - color: #ABD9A7 - -thirteen: - name: Need - icon: /assets/icons/blueprint_96px/bp_need.png - color: #D2A7D4 - -fourteen: - name: Open Issue - icon: /assets/icons/blueprint_96px/bp_openissue.png - color: #9BBF71 - -fifteen: - name: Opportunity - icon: /assets/icons/blueprint_96px/bp_opportunity.png - color: #889F64 - -sixteen: - name: Person - icon: /assets/icons/blueprint_96px/bp_person.png - color: #DE925F - -seventeen: - name: Platform - icon: /assets/icons/blueprint_96px/bp_platform.png - color: #21C8FE - -eighteen: - name: Problem - icon: /assets/icons/blueprint_96px/bp_problem.png - color: #99CFC4 - -nineteen: - name: Resource - icon: /assets/icons/blueprint_96px/bp_resource.png - color: #C98C63 - -twenty: - name: Role - icon: /assets/icons/blueprint_96px/bp_role.png - color: #A8595D - -twenty-one: - name: Task - icon: /assets/icons/blueprint_96px/bp_task.png - color: #3397C4 - -twenty-two: - name: Trajectory - icon: /assets/icons/blueprint_96px/bp_trajectory.png - color: #D3AA4C - -twenty-three: - name: Argument - icon: /assets/icons/generics_96px/gen_argument.png - color: #7FAEFD - -twenty-four: - name: Con - icon: /assets/icons/generics_96px/gen_con.png - color: #CF7C74 - -twenty-five: - name: Subject - icon: /assets/icons/generics_96px/gen_subject.png - color: #8293D8 - -twenty-six: - name: Decision - icon: /assets/icons/generics_96px/gen_decision.png - color: #CCA866 - -twenty-seven: - name: Event - icon: /assets/icons/generics_96px/gen_event.png - color: #F5854B - -twenty-eight: - name: Example - icon: /assets/icons/generics_96px/gen_example.png - color: #618C61 - -twenty-nine: - name: Experience - icon: /assets/icons/generics_96px/gen_experience.png - color: #BE995F - -thirty: - name: Feedback - icon: /assets/icons/generics_96px/gen_feedback.png - color: #54A19D - -thirty-one: - name: Aim - icon: /assets/icons/generics_96px/gen_aim.png - color: #B0B0B0 - -thirty-two: - name: Good Practice - icon: /assets/icons/generics_96px/gen_goodpractice.png - color: #BD9E86 - -thirty-three: - name: Idea - icon: /assets/icons/generics_96px/gen_idea.png - color: #C4BC5E - -thirty-four: - name: List - icon: /assets/icons/generics_96px/gen_list.png - color: #B7A499 - -thirty-five: - name: Media - icon: /assets/icons/generics_96px/gen_media.png - color: #6D94CC - -thirty-six: - name: Metamap - icon: /assets/icons/generics_96px/gen_metamap.png - color: #AEA9FD - -thirty-seven: - name: Model - icon: /assets/icons/generics_96px/gen_model.png - color: #B385BA - -thirty-eight: - name: Note - icon: /assets/icons/generics_96px/gen_note.png - color: #A389A1 - -thirty-nine: - name: Perspective - icon: /assets/icons/generics_96px/gen_perspective.png - color: #2EB6CC - -forty: - name: Pro - icon: /assets/icons/generics_96px/gen_pro.png - color: #89B879 - -forty-one: - name: Project - icon: /assets/icons/generics_96px/gen_project.png - color: #85A050 - -forty-two: - name: Question - icon: /assets/icons/generics_96px/gen_question.png - color: #5CB3B3 - -forty-three: - name: Reference - icon: /assets/icons/generics_96px/gen_reference.png - color: #A7A7A7 - -forty-four: - name: Research - icon: /assets/icons/generics_96px/gen_research.png - color: #CD8E89 - -forty-five: - name: Status update - icon: /assets/icons/generics_96px/gen_status.png - color: #EFA7C0 - -forty-six: - name: Tool - icon: /assets/icons/generics_96px/gen_tool.png - color: #828282 - -forty-seven: - name: Wildcard - icon: /assets/icons/generics_96px/gen_wildcard.png - color: #73C7DE diff --git a/test/fixtures/topics.yml b/test/fixtures/topics.yml deleted file mode 100644 index 9cfe87b6..00000000 --- a/test/fixtures/topics.yml +++ /dev/null @@ -1,11 +0,0 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html - -# This model initially had no columns defined. If you add columns to the -# model remove the '{}' from the fixture names and add the columns immediately -# below each fixture, per the syntax in the comments below -# -#one: {} -# column: value -# -#two: {} -# column: value diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml deleted file mode 100644 index 8a461a50..00000000 --- a/test/fixtures/users.yml +++ /dev/null @@ -1,17 +0,0 @@ -#password: toolsplusconsciousness - -user: - name: user - email: user@user.com - encrypted_password: $2a$10$psR68SWYNy5ZKQPs9FrFM.HuRMrTXO/YFzv.HaUmdCsQZsQrG1XAW - code: qwertyui - joinedwithcode: qwertyui - admin: false - -admin: - name: admin - email: admin@admin.com - encrypted_password: $2a$10$psR68SWYNy5ZKQPs9FrFM.HuRMrTXO/YFzv.HaUmdCsQZsQrG1XAW - code: iuytrewq - joinedwithcode: iuytrewq - admin: true diff --git a/test/functional/.gitkeep b/test/functional/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/test/functional/in_metacode_sets_controller_test.rb b/test/functional/in_metacode_sets_controller_test.rb deleted file mode 100644 index f78ad458..00000000 --- a/test/functional/in_metacode_sets_controller_test.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'test_helper' - -class InMetacodeSetsControllerTest < ActionController::TestCase - setup do - @in_metacode_set = in_metacode_sets(:one) - end - - test "should get index" do - get :index - assert_response :success - assert_not_nil assigns(:in_metacode_sets) - end - - test "should get new" do - get :new - assert_response :success - end - - test "should create in_metacode_set" do - assert_difference('InMetacodeSet.count') do - post :create, in_metacode_set: { } - end - - assert_redirected_to in_metacode_set_path(assigns(:in_metacode_set)) - end - - test "should show in_metacode_set" do - get :show, id: @in_metacode_set - assert_response :success - end - - test "should get edit" do - get :edit, id: @in_metacode_set - assert_response :success - end - - test "should update in_metacode_set" do - put :update, id: @in_metacode_set, in_metacode_set: { } - assert_redirected_to in_metacode_set_path(assigns(:in_metacode_set)) - end - - test "should destroy in_metacode_set" do - assert_difference('InMetacodeSet.count', -1) do - delete :destroy, id: @in_metacode_set - end - - assert_redirected_to in_metacode_sets_path - end -end diff --git a/test/functional/main_controller_test.rb b/test/functional/main_controller_test.rb deleted file mode 100644 index b7ec6bfc..00000000 --- a/test/functional/main_controller_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class MainControllerTest < ActionController::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/functional/mapping_controller_test.rb b/test/functional/mapping_controller_test.rb deleted file mode 100644 index 59fc534a..00000000 --- a/test/functional/mapping_controller_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'test_helper' - -class MappingControllerTest < ActionController::TestCase - test "should get new" do - get :new - assert_response :success - end - -end diff --git a/test/functional/maps_controller_test.rb b/test/functional/maps_controller_test.rb deleted file mode 100644 index 9517ea55..00000000 --- a/test/functional/maps_controller_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class MapsControllerTest < ActionController::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/functional/metacode_sets_controller_test.rb b/test/functional/metacode_sets_controller_test.rb deleted file mode 100644 index ec06c1e4..00000000 --- a/test/functional/metacode_sets_controller_test.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'test_helper' - -class MetacodeSetsControllerTest < ActionController::TestCase - setup do - @metacode_set = metacode_sets(:one) - end - - test "should get index" do - get :index - assert_response :success - assert_not_nil assigns(:metacode_sets) - end - - test "should get new" do - get :new - assert_response :success - end - - test "should create metacode_set" do - assert_difference('MetacodeSet.count') do - post :create, metacode_set: { desc: @metacode_set.desc, mapperContributed: @metacode_set.mapperContributed, name: @metacode_set.name } - end - - assert_redirected_to metacode_set_path(assigns(:metacode_set)) - end - - test "should show metacode_set" do - get :show, id: @metacode_set - assert_response :success - end - - test "should get edit" do - get :edit, id: @metacode_set - assert_response :success - end - - test "should update metacode_set" do - put :update, id: @metacode_set, metacode_set: { desc: @metacode_set.desc, mapperContributed: @metacode_set.mapperContributed, name: @metacode_set.name } - assert_redirected_to metacode_set_path(assigns(:metacode_set)) - end - - test "should destroy metacode_set" do - assert_difference('MetacodeSet.count', -1) do - delete :destroy, id: @metacode_set - end - - assert_redirected_to metacode_sets_path - end -end diff --git a/test/functional/metacodes_controller_test.rb b/test/functional/metacodes_controller_test.rb deleted file mode 100644 index c15d468f..00000000 --- a/test/functional/metacodes_controller_test.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'test_helper' - -class MetacodesControllerTest < ActionController::TestCase - setup do - @metacode = metacodes(:one) - end - - test "should get index" do - get :index - assert_response :success - assert_not_nil assigns(:metacodes) - end - - test "should get new" do - get :new - assert_response :success - end - - test "should create metacode" do - assert_difference('Metacode.count') do - post :create, metacode: { icon: @metacode.icon, name: @metacode.name } - end - - assert_redirected_to metacode_path(assigns(:metacode)) - end - - test "should show metacode" do - get :show, id: @metacode - assert_response :success - end - - test "should get edit" do - get :edit, id: @metacode - assert_response :success - end - - test "should update metacode" do - put :update, id: @metacode, metacode: { icon: @metacode.icon, name: @metacode.name } - assert_redirected_to metacode_path(assigns(:metacode)) - end - - test "should destroy metacode" do - assert_difference('Metacode.count', -1) do - delete :destroy, id: @metacode - end - - assert_redirected_to metacodes_path - end -end diff --git a/test/functional/sessions_controller_test.rb b/test/functional/sessions_controller_test.rb deleted file mode 100644 index d30ebc38..00000000 --- a/test/functional/sessions_controller_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class SessionsControllerTest < ActionController::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/functional/synapses_controller_test.rb b/test/functional/synapses_controller_test.rb deleted file mode 100644 index 42d864b0..00000000 --- a/test/functional/synapses_controller_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class SynapsesControllerTest < ActionController::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/functional/topics_controller_test.rb b/test/functional/topics_controller_test.rb deleted file mode 100644 index dd41edd9..00000000 --- a/test/functional/topics_controller_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class TopicsControllerTest < ActionController::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/functional/users_controller_test.rb b/test/functional/users_controller_test.rb deleted file mode 100644 index d23f1829..00000000 --- a/test/functional/users_controller_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class UsersControllerTest < ActionController::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/integration/.gitkeep b/test/integration/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/test/performance/browsing_test.rb b/test/performance/browsing_test.rb deleted file mode 100644 index 3fea27b9..00000000 --- a/test/performance/browsing_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'test_helper' -require 'rails/performance_test_help' - -class BrowsingTest < ActionDispatch::PerformanceTest - # Refer to the documentation for all available options - # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory] - # :output => 'tmp/performance', :formats => [:flat] } - - def test_homepage - get '/' - end -end diff --git a/test/test_helper.rb b/test/test_helper.rb deleted file mode 100644 index 8bf1192f..00000000 --- a/test/test_helper.rb +++ /dev/null @@ -1,13 +0,0 @@ -ENV["RAILS_ENV"] = "test" -require File.expand_path('../../config/environment', __FILE__) -require 'rails/test_help' - -class ActiveSupport::TestCase - # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. - # - # Note: You'll currently still have to declare fixtures explicitly in integration tests - # -- they do not yet inherit this setting - fixtures :all - - # Add more helper methods to be used by all tests here... -end diff --git a/test/unit/.gitkeep b/test/unit/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/test/unit/helpers/in_metacode_sets_helper_test.rb b/test/unit/helpers/in_metacode_sets_helper_test.rb deleted file mode 100644 index 64eb689a..00000000 --- a/test/unit/helpers/in_metacode_sets_helper_test.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'test_helper' - -class InMetacodeSetsHelperTest < ActionView::TestCase -end diff --git a/test/unit/helpers/main_helper_test.rb b/test/unit/helpers/main_helper_test.rb deleted file mode 100644 index 22da3c40..00000000 --- a/test/unit/helpers/main_helper_test.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'test_helper' - -class MainHelperTest < ActionView::TestCase -end diff --git a/test/unit/helpers/mapping_helper_test.rb b/test/unit/helpers/mapping_helper_test.rb deleted file mode 100644 index 5768ae1b..00000000 --- a/test/unit/helpers/mapping_helper_test.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'test_helper' - -class MappingHelperTest < ActionView::TestCase -end diff --git a/test/unit/helpers/maps_helper_test.rb b/test/unit/helpers/maps_helper_test.rb deleted file mode 100644 index 0a0efccd..00000000 --- a/test/unit/helpers/maps_helper_test.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'test_helper' - -class MapsHelperTest < ActionView::TestCase -end diff --git a/test/unit/helpers/metacode_sets_helper_test.rb b/test/unit/helpers/metacode_sets_helper_test.rb deleted file mode 100644 index d1983654..00000000 --- a/test/unit/helpers/metacode_sets_helper_test.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'test_helper' - -class MetacodeSetsHelperTest < ActionView::TestCase -end diff --git a/test/unit/helpers/metacodes_helper_test.rb b/test/unit/helpers/metacodes_helper_test.rb deleted file mode 100644 index 2837d55f..00000000 --- a/test/unit/helpers/metacodes_helper_test.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'test_helper' - -class MetacodesHelperTest < ActionView::TestCase -end diff --git a/test/unit/helpers/sessions_helper_test.rb b/test/unit/helpers/sessions_helper_test.rb deleted file mode 100644 index 7d44e096..00000000 --- a/test/unit/helpers/sessions_helper_test.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'test_helper' - -class SessionsHelperTest < ActionView::TestCase -end diff --git a/test/unit/helpers/synapses_helper_test.rb b/test/unit/helpers/synapses_helper_test.rb deleted file mode 100644 index c7a530a0..00000000 --- a/test/unit/helpers/synapses_helper_test.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'test_helper' - -class SynapsesHelperTest < ActionView::TestCase -end diff --git a/test/unit/helpers/topics_helper_test.rb b/test/unit/helpers/topics_helper_test.rb deleted file mode 100644 index 12cc0b4f..00000000 --- a/test/unit/helpers/topics_helper_test.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'test_helper' - -class TopicsHelperTest < ActionView::TestCase -end diff --git a/test/unit/helpers/users_helper_test.rb b/test/unit/helpers/users_helper_test.rb deleted file mode 100644 index 96af37a8..00000000 --- a/test/unit/helpers/users_helper_test.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'test_helper' - -class UsersHelperTest < ActionView::TestCase -end diff --git a/test/unit/in_metacode_set_test.rb b/test/unit/in_metacode_set_test.rb deleted file mode 100644 index 1873f3d2..00000000 --- a/test/unit/in_metacode_set_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class InMetacodeSetTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/unit/map_test.rb b/test/unit/map_test.rb deleted file mode 100644 index 033cf0ff..00000000 --- a/test/unit/map_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class MapTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/unit/mapping_test.rb b/test/unit/mapping_test.rb deleted file mode 100644 index 20115809..00000000 --- a/test/unit/mapping_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class MappingTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/unit/metacode_set_test.rb b/test/unit/metacode_set_test.rb deleted file mode 100644 index f92bc573..00000000 --- a/test/unit/metacode_set_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class MetacodeSetTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/unit/metacode_test.rb b/test/unit/metacode_test.rb deleted file mode 100644 index 7bfd318d..00000000 --- a/test/unit/metacode_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class MetacodeTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/unit/session_test.rb b/test/unit/session_test.rb deleted file mode 100644 index 2d1bc1b0..00000000 --- a/test/unit/session_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class SessionTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/unit/synapse_test.rb b/test/unit/synapse_test.rb deleted file mode 100644 index 50a6fe66..00000000 --- a/test/unit/synapse_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class SynapseTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/unit/topic_test.rb b/test/unit/topic_test.rb deleted file mode 100644 index 90522ee5..00000000 --- a/test/unit/topic_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class TopicTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb deleted file mode 100644 index 82f61e01..00000000 --- a/test/unit/user_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class UserTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end