From d190caf7599027608ea709a3ad9c4f42b8f9ab89 Mon Sep 17 00:00:00 2001 From: Willy Mene Date: Thu, 28 Apr 2011 16:01:17 -0700 Subject: [PATCH] Initial commit --- .gitignore | 9 + .rvmrc | 1 + COPYRIGHT | 16 + Gemfile | 26 + Gemfile.lock | 108 + LICENSE | 14 + README.textile | 67 + Rakefile | 31 + app/controllers/application_controller.rb | 66 + app/controllers/catalog_controller.rb | 241 + app/controllers/content_files_controller.rb | 25 + app/controllers/content_upload.rb | 39 + app/controllers/eems_controller.rb | 130 + app/controllers/log_controller.rb | 24 + app/controllers/parts_controller.rb | 45 + .../permission_files_controller.rb | 110 + .../submit_to_tech_services_controller.rb | 37 + app/helpers/application_helper.rb | 84 + app/helpers/eems_helper.rb | 218 + app/models/content_file.rb | 11 + app/models/eem.rb | 70 + app/models/part.rb | 67 + app/models/permission_file.rb | 39 + app/views/catalog/_constraints.html.erb | 0 app/views/catalog/_default.html.erb | 11 + .../_detail_partials/_eem_readonly.html.erb | 38 + app/views/catalog/_document_list.html.erb | 34 + app/views/catalog/_facets.html.erb | 42 + app/views/catalog/_home_text.html.erb | 14 + .../catalog/_index_partials/_default.html.erb | 17 + app/views/catalog/_search_form.html.erb | 11 + .../_show_partials/_creator_name.html.erb | 1 + .../catalog/_show_partials/_default.html.erb | 17 + app/views/catalog/_sort_and_per_page.html.erb | 13 + app/views/catalog/index.html.erb | 35 + app/views/catalog/show.html.erb | 93 + .../_detail_partials/_action_log.html.erb | 13 + .../eems/_detail_partials/_editable.html.erb | 79 + .../_permission_files.html.erb | 32 + .../eems/_error/_not_authorized.html.erb | 26 + app/views/eems/_payment_fund.html.erb | 5 + app/views/eems/_select_options.html.erb | 46 + app/views/eems/_source_url.html.erb | 6 + app/views/eems/_top_nav_links.html.erb | 15 + .../eems/_widgets/_browser_widget.html.erb | 46 + .../eems/_widgets/_desktop_widget.html.erb | 51 + app/views/eems/new.html.erb | 40 + app/views/eems/show.html.erb | 93 + app/views/layouts/application.html.erb | 51 + app/views/layouts/eems.html.erb | 17 + app/views/layouts/eems_show.html.erb | 30 + app/views/layouts/login.html.erb | 15 + app/views/login/new.html.erb | 4 + config/boot.rb | 124 + config/cucumber.yml | 2 + config/database.yml | 28 + config/environment.rb | 44 + config/environments/cucumber.rb | 33 + config/environments/culerity.rb | 27 + config/environments/development.rb | 26 + config/environments/production.rb | 42 + config/environments/test.rb | 41 + .../initializers/accession_workflow_config.rb | 17 + config/initializers/backtrace_silencers.rb | 7 + config/initializers/blacklight_config.rb | 177 + config/initializers/delayed_job_config.rb | 4 + config/initializers/fedora_repository.rb | 3 + config/initializers/inflections.rb | 10 + config/initializers/mime_types.rb | 5 + config/initializers/new_rails_defaults.rb | 19 + config/initializers/overrides.rb | 71 + config/initializers/session_store.rb | 15 + config/initializers/sulair_config.rb | 15 + config/locales/en.yml | 5 + config/preinitializer.rb | 22 + config/routes.rb | 68 + config/setup_load_paths.rb | 21 + config/solr.yml | 10 + db/migrate/20090127164730_create_users.rb | 16 + db/migrate/20090127164740_create_bookmarks.rb | 17 + ...090127164750_acts_as_taggable_migration.rb | 26 + db/migrate/20090428182620_create_searches.rb | 15 + ...529161304_add_authlogic_fields_to_users.rb | 15 + .../20100225054037_create_content_files.rb | 15 + .../20100225055824_create_delayed_jobs.rb | 20 + ...227011147_add_filepath_to_content_files.rb | 9 + ...227030213_add_part_pid_to_content_files.rb | 9 + ...702215015_add_attempts_to_content_files.rb | 9 + ..._add_user_display_name_to_content_files.rb | 9 + db/schema.rb | 85 + doc/README_FOR_APP | 2 + features/eems_widget.feature | 34 + features/step_definitions/culerity_steps.rb | 183 + features/step_definitions/widget_steps.rb | 37 + features/support/cucumber.env.rb | 57 + features/support/env.rb | 9 + features/support/paths.rb | 30 + lib/dor/action_log_datastream.rb | 72 + lib/dor/download_job.rb | 89 + lib/dor/utils.rb | 10 + lib/dor/workflow_service.rb | 115 + lib/eem_model.rb | 5 + lib/eem_model/eem.rb | 36 + lib/eem_model/eem_accession.rb | 309 ++ lib/eem_model/part.rb | 21 + lib/eems_user.rb | 30 + lib/tasks/cucumber.rake | 53 + lib/tasks/culerity.rake | 38 + lib/tasks/rspec.rake | 144 + public/404.html | 30 + public/422.html | 30 + public/500.html | 30 + public/favicon.ico | 0 public/images/bgTopBar_1x48.png | Bin 0 -> 2990 bytes public/images/c_loader.gif | Bin 0 -> 1153 bytes public/images/date-picker/backstripes.gif | Bin 0 -> 234 bytes public/images/date-picker/bg_header.jpg | Bin 0 -> 1792 bytes public/images/date-picker/bullet1.gif | Bin 0 -> 55 bytes public/images/date-picker/bullet2.gif | Bin 0 -> 262 bytes public/images/date-picker/cal-grey.gif | Bin 0 -> 170 bytes public/images/date-picker/cal.gif | Bin 0 -> 127 bytes .../date-picker/gradient-e5e5e5-ffffff.gif | Bin 0 -> 526 bytes public/images/eems-dev-widget-button.png | Bin 0 -> 2907 bytes public/images/eems-test-widget-button.png | Bin 0 -> 2822 bytes public/images/eems-widget-button.png | Bin 0 -> 2442 bytes public/images/error.png | Bin 0 -> 588 bytes public/images/h_loading.gif | Bin 0 -> 5267 bytes public/images/icon_dashboard_sort_asc.png | Bin 0 -> 162 bytes public/images/icon_dashboard_sort_desc.png | Bin 0 -> 161 bytes public/images/icon_edit_10x10.png | Bin 0 -> 3025 bytes public/images/icon_list_hide.png | Bin 0 -> 160 bytes public/images/icon_list_show.png | Bin 0 -> 167 bytes public/images/icon_save_10x10.png | Bin 0 -> 3011 bytes public/images/progress_bar.png | Bin 0 -> 126 bytes public/images/rails.png | Bin 0 -> 6646 bytes public/images/sulair.png | Bin 0 -> 7689 bytes public/images/widget_tab_bg.png | Bin 0 -> 147 bytes public/javascripts/browser-widget-dialog.js | 161 + public/javascripts/browser-widget.js | 445 ++ public/javascripts/controls.js | 963 ++++ public/javascripts/culerity.js | 31 + public/javascripts/date.format.js | 127 + public/javascripts/dragdrop.js | 973 ++++ public/javascripts/eems_common.js | 115 + public/javascripts/eems_detail.js | 316 ++ public/javascripts/eems_detail_actions.js | 124 + public/javascripts/effects.js | 1128 +++++ public/javascripts/jquery-1.4.2.min.js | 154 + public/javascripts/jquery.autocomplete.js | 762 +++ public/javascripts/jquery.form.js | 772 +++ public/javascripts/prototype.js | 4320 +++++++++++++++++ public/plugin_assets/README | 5 + public/plugin_assets/blacklight/images/bg.png | Bin 0 -> 280 bytes .../blacklight/images/border.png | Bin 0 -> 210 bytes .../blacklight/images/bul_sq_gry.gif | Bin 0 -> 45 bytes .../blacklight/images/checkmark.gif | Bin 0 -> 574 bytes .../plugin_assets/blacklight/images/logo.png | Bin 0 -> 906 bytes .../blacklight/images/magnifying_glass.gif | Bin 0 -> 601 bytes .../blacklight/images/remove.gif | Bin 0 -> 111 bytes .../blacklight/images/separator.gif | Bin 0 -> 112 bytes .../blacklight/images/start_over.gif | Bin 0 -> 419 bytes .../blacklight/javascripts/accordion.js | 15 + .../blacklight/javascripts/application.js | 9 + .../blacklight/javascripts/blacklight.js | 3 + .../javascripts/jquery-1.3.1.min.js | 19 + .../blacklight/javascripts/lightbox.js | 17 + .../blacklight/stylesheets/application.css | 203 + .../blacklight/stylesheets/yui.css | 31 + public/robots.txt | 5 + public/stylesheets/application.css | 333 ++ public/stylesheets/eems_detail.css | 192 + public/stylesheets/jquery.autocomplete.css | 49 + public/stylesheets/site.css | 231 + script/about | 4 + script/autospec | 6 + script/console | 3 + script/cucumber | 10 + script/dbconsole | 3 + script/delayed_job | 5 + script/destroy | 3 + script/generate | 3 + script/performance/benchmarker | 3 + script/performance/profiler | 3 + script/plugin | 3 + script/runner | 3 + script/server | 3 + script/spec | 10 + solr/derby.log | 10 + solr/schema.xml | 267 + solr/solrconfig.xml | 842 ++++ .../content_files_controller_spec.rb | 37 + .../eems_controller_desktop_upload_spec.rb | 88 + .../eems_controller_no_pdf_spec.rb | 64 + spec/controllers/eems_controller_spec.rb | 155 + spec/controllers/log_controller_spec.rb | 75 + spec/controllers/parts_controller_spec.rb | 91 + .../permission_files_controller_spec.rb | 116 + ...submit_to_tech_services_controller_spec.rb | 114 + spec/helpers/eems_helper_spec.rb | 74 + spec/lib/action_log_datastream_spec.rb | 153 + spec/lib/download_job_spec.rb | 117 + spec/lib/eems_user_spec.rb | 81 + spec/models/content_file_spec.rb | 9 + spec/models/eem_accession_spec.rb | 87 + spec/models/eem_spec.rb | 149 + spec/models/part_spec.rb | 110 + spec/models/permission_file_spec.rb | 72 + spec/rcov.opts | 3 + spec/spec.opts | 4 + spec/spec_helper.rb | 54 + spec/views/eems/show.html.erb_spec.rb | 54 + spec/views/login/new.html.erb_spec.rb | 10 + startjob.sh | 9 + stopjob.sh | 9 + test/fixtures/content_files.yml | 11 + test/performance/browsing_test.rb | 9 + test/test_helper.rb | 38 + test/unit/content_file_test.rb | 8 + vendor/plugins/blacklight/.gitignore | 15 + vendor/plugins/blacklight/.gitmodules | 6 + vendor/plugins/blacklight/LICENSE | 14 + vendor/plugins/blacklight/README.rdoc | 28 + vendor/plugins/blacklight/Rakefile | 24 + .../app/controllers/application_controller.rb | 59 + .../app/controllers/bookmarks_controller.rb | 59 + .../app/controllers/catalog_controller.rb | 200 + .../app/controllers/feedback_controller.rb | 37 + .../controllers/saved_searches_controller.rb | 44 + .../controllers/search_history_controller.rb | 25 + .../controllers/user_sessions_controller.rb | 43 + .../app/controllers/users_controller.rb | 13 + .../app/helpers/application_helper.rb | 387 ++ .../app/helpers/bookmarks_helper.rb | 3 + .../blacklight/app/helpers/catalog_helper.rb | 38 + .../blacklight/app/helpers/feedback_helper.rb | 2 + .../app/helpers/saved_searches_helper.rb | 2 + .../app/helpers/search_history_helper.rb | 2 + .../app/helpers/user_sessions_helper.rb | 2 + .../blacklight/app/helpers/users_helper.rb | 2 + .../plugins/blacklight/app/models/bookmark.rb | 7 + .../blacklight/app/models/record_mailer.rb | 30 + .../plugins/blacklight/app/models/search.rb | 12 + .../blacklight/app/models/solr_document.rb | 6 + vendor/plugins/blacklight/app/models/user.rb | 46 + .../blacklight/app/models/user_session.rb | 4 + .../blacklight/app/views/_flash_msg.html.erb | 6 + .../app/views/_user_util_links.html.erb | 11 + .../app/views/bookmarks/edit.html.erb | 33 + .../app/views/bookmarks/index.html.erb | 56 + .../app/views/bookmarks/new.html.erb | 1 + .../app/views/bookmarks/show.html.erb | 18 + .../views/catalog/_bookmark_control.html.erb | 12 + .../app/views/catalog/_citation.html.erb | 17 + .../app/views/catalog/_constraints.html.erb | 59 + .../app/views/catalog/_did_you_mean.html.erb | 10 + .../app/views/catalog/_document_list.html.erb | 32 + .../app/views/catalog/_email_form.html.erb | 14 + .../views/catalog/_facet_pagination.html.erb | 6 + .../app/views/catalog/_facets.html.erb | 26 + .../views/catalog/_hidden_filters.html.erb | 7 + .../app/views/catalog/_home.html.erb | 6 + .../app/views/catalog/_home_text.html.erb | 6 + .../catalog/_index_partials/_default.html.erb | 11 + .../app/views/catalog/_marc_view.html.erb | 38 + .../views/catalog/_previous_next_doc.html.erb | 7 + .../catalog/_search_constraints.html.erb | 23 + .../app/views/catalog/_search_form.html.erb | 15 + .../catalog/_show_partials/_default.html.erb | 18 + .../app/views/catalog/_show_tools.html.erb | 22 + .../app/views/catalog/_sms_form.html.erb | 26 + .../app/views/catalog/_solr_request.html.erb | 5 + .../views/catalog/_sort_and_per_page.html.erb | 25 + .../app/views/catalog/citation.html.erb | 1 + .../blacklight/app/views/catalog/email.erb | 1 + .../app/views/catalog/facet.html.erb | 11 + .../app/views/catalog/index.html.erb | 35 + .../app/views/catalog/index.rss.builder | 20 + .../app/views/catalog/opensearch.json.erb | 0 .../app/views/catalog/opensearch.xml.erb | 10 + .../app/views/catalog/send_email_record.erb | 0 .../app/views/catalog/show.endnote.erb | 1 + .../app/views/catalog/show.html.erb | 44 + .../app/views/catalog/show.refworks.erb | 1 + .../blacklight/app/views/catalog/sms.erb | 1 + .../app/views/feedback/complete.html.erb | 3 + .../app/views/feedback/show.html.erb | 20 + .../app/views/layouts/application.html.erb | 53 + .../app/views/record_mailer/email_record.erb | 9 + .../app/views/record_mailer/sms_record.erb | 7 + .../app/views/saved_searches/index.html.erb | 27 + .../app/views/search_history/index.html.erb | 23 + .../views/user_sessions/_login_form.html.erb | 5 + .../app/views/user_sessions/index.html.erb | 7 + .../app/views/user_sessions/new.html.erb | 1 + .../blacklight/app/views/users/new.html.erb | 11 + .../blacklight/app/views/users/show.html.erb | 27 + .../plugins/blacklight/assets/images/bg.png | Bin 0 -> 280 bytes .../blacklight/assets/images/border.png | Bin 0 -> 210 bytes .../blacklight/assets/images/bul_sq_gry.gif | Bin 0 -> 45 bytes .../blacklight/assets/images/checkmark.gif | Bin 0 -> 574 bytes .../plugins/blacklight/assets/images/logo.png | Bin 0 -> 906 bytes .../assets/images/magnifying_glass.gif | Bin 0 -> 601 bytes .../blacklight/assets/images/remove.gif | Bin 0 -> 111 bytes .../blacklight/assets/images/separator.gif | Bin 0 -> 112 bytes .../blacklight/assets/images/start_over.gif | Bin 0 -> 419 bytes .../assets/javascripts/accordion.js | 15 + .../assets/javascripts/application.js | 9 + .../assets/javascripts/blacklight.js | 3 + .../assets/javascripts/jquery-1.3.1.min.js | 19 + .../blacklight/assets/javascripts/lightbox.js | 17 + .../assets/stylesheets/application.css | 203 + .../blacklight/assets/stylesheets/yui.css | 31 + .../plugins/blacklight/config/CONFIG_README | 15 + .../config/SolrMarc/config.properties | 19 + .../config/SolrMarc/demoserver.properties | 19 + .../config/SolrMarc/index.properties | 97 + vendor/plugins/blacklight/config/boot.rb | 109 + vendor/plugins/blacklight/config/cucumber.yml | 7 + vendor/plugins/blacklight/config/database.yml | 20 + .../blacklight/config/demo_config.properties | 13 + .../blacklight/config/demo_index.properties | 97 + .../plugins/blacklight/config/environment.rb | 104 + .../config/environments/cucumber.rb | 22 + .../config/environments/development.rb | 19 + .../blacklight/config/environments/test.rb | 24 + .../config/initializers/blacklight_config.rb | 185 + .../config/initializers/inflections.rb | 10 + .../config/initializers/mime_types.rb | 5 + .../config/initializers/new_rails_defaults.rb | 17 + .../plugins/blacklight/config/locales/en.yml | 5 + vendor/plugins/blacklight/config/routes.rb | 7 + vendor/plugins/blacklight/config/solr.yml | 6 + .../db/migrate/20090127164730_create_users.rb | 16 + .../20090127164740_create_bookmarks.rb | 17 + ...090127164750_acts_as_taggable_migration.rb | 26 + .../migrate/20090428182620_create_searches.rb | 15 + ...529161304_add_authlogic_fields_to_users.rb | 15 + .../plugins/blacklight/doc/CUSTOMIZING.rdoc | 35 + .../blacklight/doc/PRE-REQUISITES.rdoc | 96 + vendor/plugins/blacklight/doc/README_FOR_APP | 1 + vendor/plugins/blacklight/doc/README_GIT.rdoc | 207 + .../blacklight/doc/README_INSTALLATION.rdoc | 57 + .../blacklight/doc/README_PRODUCTION.rdoc | 21 + .../plugins/blacklight/doc/README_SOLR.rdoc | 226 + .../blacklight/features/bookmarks.feature | 58 + .../blacklight/features/did_you_mean.feature | 129 + .../blacklight/features/errors.feature | 16 + .../blacklight/features/record_view.feature | 35 + .../features/saved_searches.feature | 51 + .../blacklight/features/search.feature | 81 + .../features/search_filters.feature | 123 + .../features/search_history.feature | 98 + .../blacklight/features/search_sort.feature | 17 + .../step_definitions/bookmarks_steps.rb | 4 + .../features/step_definitions/error_steps.rb | 4 + .../step_definitions/general_steps.rb | 4 + .../step_definitions/record_view_steps.rb | 7 + .../step_definitions/saved_searches_steps.rb | 14 + .../step_definitions/search_history_steps.rb | 8 + .../features/step_definitions/search_steps.rb | 86 + .../features/step_definitions/user_steps.rb | 4 + .../features/step_definitions/web_steps.rb | 263 + .../blacklight/features/support/env.rb | 53 + .../blacklight/features/support/paths.rb | 50 + .../blacklight/features/user_account.feature | 63 + vendor/plugins/blacklight/init.rb | 51 + vendor/plugins/blacklight/install.rb | 0 vendor/plugins/blacklight/install/solr.yml | 8 + vendor/plugins/blacklight/lib/blacklight.rb | 75 + .../blacklight/lib/blacklight/configurable.rb | 46 + .../blacklight/lib/blacklight/core_ext.rb | 50 + .../plugins/blacklight/lib/blacklight/marc.rb | 41 + .../lib/blacklight/marc/citation.rb | 246 + .../blacklight/lib/blacklight/routes.rb | 35 + .../lib/blacklight/search_fields.rb | 89 + .../plugins/blacklight/lib/blacklight/solr.rb | 6 + .../lib/blacklight/solr/document.rb | 64 + .../lib/blacklight/solr/document/marc.rb | 22 + .../blacklight/lib/blacklight/solr/facets.rb | 45 + .../blacklight/lib/blacklight/solr_helper.rb | 242 + vendor/plugins/blacklight/lib/colorize.rb | 196 + .../blacklight/lib/taggable_pagination.rb | 31 + .../blacklight/lib/tasks/blacklight.rake | 59 + .../blacklight/lib/tasks/cucumber.rake | 46 + .../plugins/blacklight/lib/tasks/rspec.rake | 188 + .../blacklight/lib/tasks/solr_marc.rake | 128 + .../lib/will_paginate_link_renderer.rb | 20 + vendor/plugins/blacklight/rerun.txt | 1 + vendor/plugins/blacklight/script/about | 4 + vendor/plugins/blacklight/script/autospec | 6 + vendor/plugins/blacklight/script/console | 3 + vendor/plugins/blacklight/script/cucumber | 10 + vendor/plugins/blacklight/script/dbconsole | 3 + vendor/plugins/blacklight/script/destroy | 3 + vendor/plugins/blacklight/script/generate | 3 + .../blacklight/script/performance/benchmarker | 3 + .../blacklight/script/performance/profiler | 3 + .../blacklight/script/performance/request | 3 + vendor/plugins/blacklight/script/plugin | 3 + .../blacklight/script/process/inspector | 3 + .../plugins/blacklight/script/process/reaper | 3 + .../plugins/blacklight/script/process/spawner | 3 + vendor/plugins/blacklight/script/runner | 3 + vendor/plugins/blacklight/script/server | 3 + vendor/plugins/blacklight/script/spec | 10 + vendor/plugins/blacklight/script/spec_server | 9 + .../plugins/blacklight/solr_marc/SolrMarc.jar | Bin 0 -> 4741075 bytes .../controllers/catalog_controller_spec.rb | 309 ++ .../saved_searches_controller_spec.rb | 26 + .../search_history_controller_spec.rb | 29 + .../blacklight/spec/data/test_data.utf8.mrc | 1 + .../spec/helpers/application_helper_spec.rb | 167 + .../helpers/search_history_helper_spec.rb | 11 + .../spec/helpers/solr_helper_spec.rb | 593 +++ .../spec/lib/blacklight_marc_spec.rb | 82 + .../blacklight/spec/lib/blacklight_spec.rb | 28 + .../blacklight/spec/lib/configurable_spec.rb | 97 + .../core_ext_deep_merge_unless_blank_spec.rb | 37 + .../blacklight/spec/lib/marc_citation_spec.rb | 261 + .../blacklight/spec/lib/search_fields_spec.rb | 69 + .../spec/lib/tasks/solr_marc_task_spec.rb | 39 + .../blacklight/spec/lib/test_solr_server.rb | 130 + .../blacklight/spec/models/bookmark_spec.rb | 37 + .../spec/models/record_mailer_spec.rb | 81 + .../blacklight/spec/models/search_spec.rb | 30 + .../spec/models/solr_document_spec.rb | 135 + .../blacklight/spec/models/user_spec.rb | 124 + vendor/plugins/blacklight/spec/rcov.opts | 3 + vendor/plugins/blacklight/spec/spec.opts | 4 + vendor/plugins/blacklight/spec/spec_helper.rb | 53 + .../catalog/_document_list.html.erb_spec.rb | 45 + .../views/catalog/_facets.html.erb_spec.rb | 182 + .../_index_partials/_default.html.erb_spec.rb | 61 + .../_show_partials/_default.html.erb_spec.rb | 53 + .../spec/views/catalog/show.html.erb_spec.rb | 103 + vendor/plugins/blacklight/super_template.rb | 288 ++ .../blacklight/tasks/blacklight_tasks.rake | 4 + vendor/plugins/blacklight/template.rb | 188 + vendor/plugins/blacklight/uninstall.rb | 1 + .../acts_as_taggable_on_steroids/CHANGELOG | 182 + .../acts_as_taggable_on_steroids/MIT-LICENSE | 20 + .../acts_as_taggable_on_steroids/README | 146 + .../acts_as_taggable_on_steroids/Rakefile | 22 + .../acts_as_taggable_migration_generator.rb | 11 + .../templates/migration.rb | 26 + .../acts_as_taggable_on_steroids/init.rb | 1 + .../lib/acts_as_taggable.rb | 205 + .../acts_as_taggable_on_steroids/lib/tag.rb | 71 + .../lib/tag_counts_extension.rb | 3 + .../lib/tag_list.rb | 108 + .../lib/tagging.rb | 12 + .../lib/tags_helper.rb | 13 + .../test/abstract_unit.rb | 97 + .../test/acts_as_taggable_test.rb | 364 ++ .../test/database.yml | 10 + .../test/fixtures/magazine.rb | 3 + .../test/fixtures/magazines.yml | 7 + .../test/fixtures/photo.rb | 8 + .../test/fixtures/photos.yml | 24 + .../test/fixtures/post.rb | 7 + .../test/fixtures/posts.yml | 34 + .../test/fixtures/special_post.rb | 2 + .../test/fixtures/subscription.rb | 4 + .../test/fixtures/subscriptions.yml | 3 + .../test/fixtures/taggings.yml | 149 + .../test/fixtures/tags.yml | 19 + .../test/fixtures/user.rb | 7 + .../test/fixtures/users.yml | 7 + .../test/schema.rb | 37 + .../test/tag_list_test.rb | 119 + .../test/tag_test.rb | 44 + .../test/tagging_test.rb | 13 + .../test/tags_helper_test.rb | 28 + .../vendor/plugins/blacklight/init.rb | 0 .../vendor/plugins/engines/CHANGELOG | 274 ++ .../vendor/plugins/engines/MIT-LICENSE | 21 + .../blacklight/vendor/plugins/engines/README | 83 + .../vendor/plugins/engines/Rakefile | 226 + .../vendor/plugins/engines/about.yml | 7 + .../blacklight/vendor/plugins/engines/boot.rb | 17 + .../engines/generators/plugin_migration/USAGE | 45 + .../plugin_migration_generator.rb | 98 + .../templates/plugin_migration.erb | 13 + .../blacklight/vendor/plugins/engines/init.rb | 5 + .../vendor/plugins/engines/lib/engines.rb | 174 + .../plugins/engines/lib/engines/assets.rb | 36 + .../plugins/engines/lib/engines/plugin.rb | 97 + .../engines/lib/engines/plugin/list.rb | 30 + .../engines/lib/engines/plugin/loader.rb | 11 + .../engines/lib/engines/plugin/locator.rb | 11 + .../engines/lib/engines/plugin/migrator.rb | 41 + .../engines/rails_extensions/asset_helpers.rb | 119 + .../engines/rails_extensions/dependencies.rb | 138 + .../rails_extensions/form_tag_helpers.rb | 37 + .../engines/rails_extensions/migrations.rb | 133 + .../lib/engines/rails_extensions/rails.rb | 11 + .../plugins/engines/lib/engines/testing.rb | 101 + .../vendor/plugins/engines/tasks/engines.rake | 280 ++ .../controllers/app_and_plugin_controller.rb | 5 + .../namespace/app_and_plugin_controller.rb | 5 + .../engines/test/app/helpers/mail_helper.rb | 5 + .../test/app/models/app_and_plugin_model.rb | 3 + .../engines/test/app/models/notify_mail.rb | 26 + .../plugins/engines/test/app/things/thing.rb | 3 + .../app/views/app_and_plugin/a_view.html.erb | 1 + .../namespace/app_and_plugin/a_view.html.erb | 1 + .../implicit_multipart.text.html.erb | 1 + .../implicit_multipart.text.plain.erb | 1 + .../views/notify_mail/multipart_html.html.erb | 1 + .../notify_mail/multipart_plain.html.erb | 1 + .../views/notify_mail/signup.text.plain.erb | 5 + ...n_with_application_template.text.plain.erb | 1 + ...n_with_application_template_plain.html.erb | 1 + .../functional/controller_loading_test.rb | 51 + ...ception_notification_compatibility_test.rb | 29 + .../test/functional/locale_loading_test.rb | 26 + .../engines/test/functional/routes_test.rb | 29 + .../test/functional/view_helpers_test.rb | 37 + .../test/functional/view_loading_test.rb | 60 + .../test/lib/app_and_plugin_lib_model.rb | 3 + .../engines/test/lib/engines_test_helper.rb | 42 + .../engines/test/lib/render_information.rb | 7 + .../controllers/alpha_plugin_controller.rb | 8 + .../controllers/app_and_plugin_controller.rb | 5 + .../namespace/alpha_plugin_controller.rb | 5 + .../namespace/app_and_plugin_controller.rb | 5 + .../namespace/shared_plugin_controller.rb | 5 + .../controllers/shared_plugin_controller.rb | 5 + .../app/models/alpha_plugin_model.rb | 3 + .../app/models/app_and_plugin_model.rb | 7 + .../app/models/shared_plugin_model.rb | 3 + .../app/views/alpha_plugin/a_view.html.erb | 1 + .../app/views/app_and_plugin/a_view.html.erb | 1 + .../app/views/layouts/plugin_layout.erb | 1 + .../namespace/alpha_plugin/a_view.html.erb | 1 + .../namespace/app_and_plugin/a_view.html.erb | 1 + .../namespace/shared_plugin/a_view.html.erb | 1 + .../app/views/shared_plugin/a_view.html.erb | 1 + .../lib/alpha_plugin_lib_model.rb | 3 + .../lib/app_and_plugin_lib_model.rb | 7 + .../test/plugins/alpha_plugin/locales/en.yml | 3 + .../controllers/app_and_plugin_controller.rb | 5 + .../namespace/shared_plugin_controller.rb | 5 + .../controllers/shared_plugin_controller.rb | 5 + .../app/models/shared_plugin_model.rb | 3 + .../namespace/shared_plugin/a_view.html.erb | 1 + .../app/views/shared_plugin/a_view.html.erb | 1 + .../engines/test/plugins/beta_plugin/init.rb | 1 + .../test/plugins/beta_plugin/locales/en.yml | 3 + .../app/controllers/assets_controller.rb | 2 + .../app/views/assets/index.html.erb | 4 + .../app/views/layouts/assets.html.erb | 3 + .../engines/test/plugins/test_assets/init.rb | 0 .../assets/file.txt | 0 .../assets/subfolder/file_in_subfolder.txt | 0 .../test_assets_with_assets_directory/init.rb | 0 .../assets/file.txt | 0 .../test_assets_with_no_subdirectory/init.rb | 0 .../test_code_mixing/app/things/thing.rb | 3 + .../test/plugins/test_code_mixing/init.rb | 1 + .../test/plugins/test_load_path/init.rb | 0 .../db/migrate/001_create_tests.rb | 11 + .../db/migrate/002_create_others.rb | 11 + .../db/migrate/003_create_extras.rb | 11 + .../test/plugins/test_migration/init.rb | 0 .../app/models/plugin_mail.rb | 26 + .../views/plugin_mail/mail_from_plugin.erb | 1 + .../multipart_from_plugin_html.html.erb | 1 + .../multipart_from_plugin_plain.html.erb | 1 + ...in_with_application_template_html.html.erb | 1 + ...n_with_application_template_plain.html.erb | 1 + .../test/plugins/test_plugin_mailing/init.rb | 0 .../namespace/test_routing_controller.rb | 5 + .../controllers/test_routing_controller.rb | 9 + .../plugins/test_routing/config/routes.rb | 4 + .../engines/test/plugins/test_routing/init.rb | 0 .../test/plugins/test_testing/app/README.txt | 1 + .../engines/test/plugins/test_testing/init.rb | 0 .../test/fixtures/testing_fixtures.yml | 0 .../test_testing/test/unit/override_test.rb | 13 + .../engines/test/unit/action_mailer_test.rb | 54 + .../test/unit/arbitrary_code_mixing_test.rb | 41 + .../plugins/engines/test/unit/assets_test.rb | 52 + .../test/unit/backwards_compat_test.rb | 8 + .../engines/test/unit/load_path_test.rb | 58 + .../engines/test/unit/migration_test.rb | 63 + .../engines/test/unit/model_and_lib_test.rb | 37 + .../plugins/engines/test/unit/plugins_test.rb | 11 + .../test/unit/test_testing/override_test.rb | 7 + .../plugins/engines/test/unit/testing_test.rb | 19 + .../plugins/resource_controller/LICENSE | 22 + .../plugins/resource_controller/README.rdoc | 330 ++ .../plugins/resource_controller/Rakefile | 46 + .../vendor/plugins/resource_controller/TODO | 0 .../plugins/resource_controller/VERSION.yml | 4 + .../generators/scaffold_resource/USAGE | 29 + .../scaffold_resource_generator.rb | 179 + .../scaffold_resource/templates/controller.rb | 2 + .../scaffold_resource/templates/fixtures.yml | 10 + .../templates/functional_test.rb | 57 + .../scaffold_resource/templates/helper.rb | 2 + .../scaffold_resource/templates/migration.rb | 15 + .../scaffold_resource/templates/model.rb | 2 + .../templates/old_migration.rb | 13 + .../templates/rspec/functional_spec.rb | 255 + .../templates/rspec/helper_spec.rb | 11 + .../templates/rspec/routing_spec.rb | 61 + .../templates/rspec/unit_spec.rb | 11 + .../templates/rspec/views/edit_spec.rb | 28 + .../templates/rspec/views/index_spec.rb | 26 + .../templates/rspec/views/new_spec.rb | 30 + .../templates/rspec/views/show_spec.rb | 25 + .../templates/shoulda_functional_test.rb | 19 + .../scaffold_resource/templates/unit_test.rb | 7 + .../templates/view__form.erb | 6 + .../templates/view__form.haml | 5 + .../scaffold_resource/templates/view_edit.erb | 16 + .../templates/view_edit.haml | 11 + .../templates/view_index.erb | 22 + .../templates/view_index.haml | 19 + .../scaffold_resource/templates/view_new.erb | 12 + .../scaffold_resource/templates/view_new.haml | 9 + .../scaffold_resource/templates/view_show.erb | 9 + .../templates/view_show.haml | 9 + .../plugins/resource_controller/init.rb | 1 + .../plugins/resource_controller/install.rb | 1 + .../lib/resource_controller.rb | 26 + .../lib/resource_controller/accessors.rb | 77 + .../lib/resource_controller/action_options.rb | 40 + .../lib/resource_controller/actions.rb | 75 + .../lib/resource_controller/base.rb | 15 + .../lib/resource_controller/class_methods.rb | 24 + .../lib/resource_controller/controller.rb | 69 + .../failable_action_options.rb | 25 + .../lib/resource_controller/helpers.rb | 28 + .../helpers/current_objects.rb | 73 + .../resource_controller/helpers/internal.rb | 80 + .../lib/resource_controller/helpers/nested.rb | 67 + .../helpers/singleton_customizations.rb | 64 + .../lib/resource_controller/helpers/urls.rb | 132 + .../resource_controller/response_collector.rb | 27 + .../lib/resource_controller/singleton.rb | 15 + .../resource_controller/lib/urligence.rb | 50 + .../plugins/resource_controller/rails/init.rb | 6 + .../resource_controller.gemspec | 30 + .../plugins/resource_controller/uninstall.rb | 1 + 646 files changed, 36211 insertions(+) create mode 100644 .gitignore create mode 100644 .rvmrc create mode 100644 COPYRIGHT create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 LICENSE create mode 100644 README.textile create mode 100644 Rakefile create mode 100644 app/controllers/application_controller.rb create mode 100644 app/controllers/catalog_controller.rb create mode 100644 app/controllers/content_files_controller.rb create mode 100644 app/controllers/content_upload.rb create mode 100644 app/controllers/eems_controller.rb create mode 100644 app/controllers/log_controller.rb create mode 100644 app/controllers/parts_controller.rb create mode 100644 app/controllers/permission_files_controller.rb create mode 100644 app/controllers/submit_to_tech_services_controller.rb create mode 100644 app/helpers/application_helper.rb create mode 100644 app/helpers/eems_helper.rb create mode 100644 app/models/content_file.rb create mode 100644 app/models/eem.rb create mode 100644 app/models/part.rb create mode 100644 app/models/permission_file.rb create mode 100644 app/views/catalog/_constraints.html.erb create mode 100644 app/views/catalog/_default.html.erb create mode 100644 app/views/catalog/_detail_partials/_eem_readonly.html.erb create mode 100644 app/views/catalog/_document_list.html.erb create mode 100644 app/views/catalog/_facets.html.erb create mode 100644 app/views/catalog/_home_text.html.erb create mode 100644 app/views/catalog/_index_partials/_default.html.erb create mode 100644 app/views/catalog/_search_form.html.erb create mode 100644 app/views/catalog/_show_partials/_creator_name.html.erb create mode 100644 app/views/catalog/_show_partials/_default.html.erb create mode 100644 app/views/catalog/_sort_and_per_page.html.erb create mode 100644 app/views/catalog/index.html.erb create mode 100644 app/views/catalog/show.html.erb create mode 100644 app/views/eems/_detail_partials/_action_log.html.erb create mode 100644 app/views/eems/_detail_partials/_editable.html.erb create mode 100644 app/views/eems/_detail_partials/_permission_files.html.erb create mode 100644 app/views/eems/_error/_not_authorized.html.erb create mode 100644 app/views/eems/_payment_fund.html.erb create mode 100644 app/views/eems/_select_options.html.erb create mode 100644 app/views/eems/_source_url.html.erb create mode 100644 app/views/eems/_top_nav_links.html.erb create mode 100644 app/views/eems/_widgets/_browser_widget.html.erb create mode 100644 app/views/eems/_widgets/_desktop_widget.html.erb create mode 100644 app/views/eems/new.html.erb create mode 100644 app/views/eems/show.html.erb create mode 100644 app/views/layouts/application.html.erb create mode 100644 app/views/layouts/eems.html.erb create mode 100644 app/views/layouts/eems_show.html.erb create mode 100644 app/views/layouts/login.html.erb create mode 100644 app/views/login/new.html.erb create mode 100644 config/boot.rb create mode 100644 config/cucumber.yml create mode 100644 config/database.yml create mode 100644 config/environment.rb create mode 100644 config/environments/cucumber.rb create mode 100644 config/environments/culerity.rb create mode 100644 config/environments/development.rb create mode 100644 config/environments/production.rb create mode 100644 config/environments/test.rb create mode 100644 config/initializers/accession_workflow_config.rb create mode 100644 config/initializers/backtrace_silencers.rb create mode 100644 config/initializers/blacklight_config.rb create mode 100644 config/initializers/delayed_job_config.rb create mode 100644 config/initializers/fedora_repository.rb create mode 100644 config/initializers/inflections.rb create mode 100644 config/initializers/mime_types.rb create mode 100644 config/initializers/new_rails_defaults.rb create mode 100644 config/initializers/overrides.rb create mode 100644 config/initializers/session_store.rb create mode 100644 config/initializers/sulair_config.rb create mode 100644 config/locales/en.yml create mode 100644 config/preinitializer.rb create mode 100644 config/routes.rb create mode 100644 config/setup_load_paths.rb create mode 100644 config/solr.yml create mode 100644 db/migrate/20090127164730_create_users.rb create mode 100644 db/migrate/20090127164740_create_bookmarks.rb create mode 100644 db/migrate/20090127164750_acts_as_taggable_migration.rb create mode 100644 db/migrate/20090428182620_create_searches.rb create mode 100644 db/migrate/20090529161304_add_authlogic_fields_to_users.rb create mode 100644 db/migrate/20100225054037_create_content_files.rb create mode 100644 db/migrate/20100225055824_create_delayed_jobs.rb create mode 100644 db/migrate/20100227011147_add_filepath_to_content_files.rb create mode 100644 db/migrate/20100227030213_add_part_pid_to_content_files.rb create mode 100644 db/migrate/20100702215015_add_attempts_to_content_files.rb create mode 100644 db/migrate/20101029180439_add_user_display_name_to_content_files.rb create mode 100644 db/schema.rb create mode 100644 doc/README_FOR_APP create mode 100644 features/eems_widget.feature create mode 100644 features/step_definitions/culerity_steps.rb create mode 100644 features/step_definitions/widget_steps.rb create mode 100644 features/support/cucumber.env.rb create mode 100644 features/support/env.rb create mode 100644 features/support/paths.rb create mode 100644 lib/dor/action_log_datastream.rb create mode 100644 lib/dor/download_job.rb create mode 100644 lib/dor/utils.rb create mode 100644 lib/dor/workflow_service.rb create mode 100644 lib/eem_model.rb create mode 100644 lib/eem_model/eem.rb create mode 100644 lib/eem_model/eem_accession.rb create mode 100644 lib/eem_model/part.rb create mode 100644 lib/eems_user.rb create mode 100644 lib/tasks/cucumber.rake create mode 100644 lib/tasks/culerity.rake create mode 100644 lib/tasks/rspec.rake create mode 100644 public/404.html create mode 100644 public/422.html create mode 100644 public/500.html create mode 100644 public/favicon.ico create mode 100644 public/images/bgTopBar_1x48.png create mode 100644 public/images/c_loader.gif create mode 100755 public/images/date-picker/backstripes.gif create mode 100755 public/images/date-picker/bg_header.jpg create mode 100755 public/images/date-picker/bullet1.gif create mode 100755 public/images/date-picker/bullet2.gif create mode 100755 public/images/date-picker/cal-grey.gif create mode 100755 public/images/date-picker/cal.gif create mode 100755 public/images/date-picker/gradient-e5e5e5-ffffff.gif create mode 100644 public/images/eems-dev-widget-button.png create mode 100644 public/images/eems-test-widget-button.png create mode 100644 public/images/eems-widget-button.png create mode 100644 public/images/error.png create mode 100644 public/images/h_loading.gif create mode 100644 public/images/icon_dashboard_sort_asc.png create mode 100644 public/images/icon_dashboard_sort_desc.png create mode 100644 public/images/icon_edit_10x10.png create mode 100644 public/images/icon_list_hide.png create mode 100644 public/images/icon_list_show.png create mode 100644 public/images/icon_save_10x10.png create mode 100644 public/images/progress_bar.png create mode 100644 public/images/rails.png create mode 100644 public/images/sulair.png create mode 100644 public/images/widget_tab_bg.png create mode 100644 public/javascripts/browser-widget-dialog.js create mode 100644 public/javascripts/browser-widget.js create mode 100644 public/javascripts/controls.js create mode 100644 public/javascripts/culerity.js create mode 100644 public/javascripts/date.format.js create mode 100644 public/javascripts/dragdrop.js create mode 100644 public/javascripts/eems_common.js create mode 100644 public/javascripts/eems_detail.js create mode 100644 public/javascripts/eems_detail_actions.js create mode 100644 public/javascripts/effects.js create mode 100644 public/javascripts/jquery-1.4.2.min.js create mode 100644 public/javascripts/jquery.autocomplete.js create mode 100644 public/javascripts/jquery.form.js create mode 100644 public/javascripts/prototype.js create mode 100644 public/plugin_assets/README create mode 100644 public/plugin_assets/blacklight/images/bg.png create mode 100644 public/plugin_assets/blacklight/images/border.png create mode 100644 public/plugin_assets/blacklight/images/bul_sq_gry.gif create mode 100644 public/plugin_assets/blacklight/images/checkmark.gif create mode 100644 public/plugin_assets/blacklight/images/logo.png create mode 100644 public/plugin_assets/blacklight/images/magnifying_glass.gif create mode 100644 public/plugin_assets/blacklight/images/remove.gif create mode 100644 public/plugin_assets/blacklight/images/separator.gif create mode 100644 public/plugin_assets/blacklight/images/start_over.gif create mode 100644 public/plugin_assets/blacklight/javascripts/accordion.js create mode 100644 public/plugin_assets/blacklight/javascripts/application.js create mode 100644 public/plugin_assets/blacklight/javascripts/blacklight.js create mode 100644 public/plugin_assets/blacklight/javascripts/jquery-1.3.1.min.js create mode 100644 public/plugin_assets/blacklight/javascripts/lightbox.js create mode 100644 public/plugin_assets/blacklight/stylesheets/application.css create mode 100644 public/plugin_assets/blacklight/stylesheets/yui.css create mode 100644 public/robots.txt create mode 100644 public/stylesheets/application.css create mode 100644 public/stylesheets/eems_detail.css create mode 100644 public/stylesheets/jquery.autocomplete.css create mode 100644 public/stylesheets/site.css create mode 100755 script/about create mode 100755 script/autospec create mode 100755 script/console create mode 100755 script/cucumber create mode 100755 script/dbconsole create mode 100755 script/delayed_job create mode 100755 script/destroy create mode 100755 script/generate create mode 100755 script/performance/benchmarker create mode 100755 script/performance/profiler create mode 100755 script/plugin create mode 100755 script/runner create mode 100755 script/server create mode 100755 script/spec create mode 100644 solr/derby.log create mode 100644 solr/schema.xml create mode 100644 solr/solrconfig.xml create mode 100644 spec/controllers/content_files_controller_spec.rb create mode 100644 spec/controllers/eems_controller_desktop_upload_spec.rb create mode 100644 spec/controllers/eems_controller_no_pdf_spec.rb create mode 100644 spec/controllers/eems_controller_spec.rb create mode 100644 spec/controllers/log_controller_spec.rb create mode 100644 spec/controllers/parts_controller_spec.rb create mode 100644 spec/controllers/permission_files_controller_spec.rb create mode 100644 spec/controllers/submit_to_tech_services_controller_spec.rb create mode 100644 spec/helpers/eems_helper_spec.rb create mode 100644 spec/lib/action_log_datastream_spec.rb create mode 100644 spec/lib/download_job_spec.rb create mode 100644 spec/lib/eems_user_spec.rb create mode 100644 spec/models/content_file_spec.rb create mode 100644 spec/models/eem_accession_spec.rb create mode 100644 spec/models/eem_spec.rb create mode 100644 spec/models/part_spec.rb create mode 100644 spec/models/permission_file_spec.rb create mode 100644 spec/rcov.opts create mode 100644 spec/spec.opts create mode 100644 spec/spec_helper.rb create mode 100644 spec/views/eems/show.html.erb_spec.rb create mode 100644 spec/views/login/new.html.erb_spec.rb create mode 100755 startjob.sh create mode 100755 stopjob.sh create mode 100644 test/fixtures/content_files.yml create mode 100644 test/performance/browsing_test.rb create mode 100644 test/test_helper.rb create mode 100644 test/unit/content_file_test.rb create mode 100644 vendor/plugins/blacklight/.gitignore create mode 100644 vendor/plugins/blacklight/.gitmodules create mode 100644 vendor/plugins/blacklight/LICENSE create mode 100644 vendor/plugins/blacklight/README.rdoc create mode 100644 vendor/plugins/blacklight/Rakefile create mode 100644 vendor/plugins/blacklight/app/controllers/application_controller.rb create mode 100644 vendor/plugins/blacklight/app/controllers/bookmarks_controller.rb create mode 100644 vendor/plugins/blacklight/app/controllers/catalog_controller.rb create mode 100644 vendor/plugins/blacklight/app/controllers/feedback_controller.rb create mode 100644 vendor/plugins/blacklight/app/controllers/saved_searches_controller.rb create mode 100644 vendor/plugins/blacklight/app/controllers/search_history_controller.rb create mode 100644 vendor/plugins/blacklight/app/controllers/user_sessions_controller.rb create mode 100644 vendor/plugins/blacklight/app/controllers/users_controller.rb create mode 100644 vendor/plugins/blacklight/app/helpers/application_helper.rb create mode 100644 vendor/plugins/blacklight/app/helpers/bookmarks_helper.rb create mode 100644 vendor/plugins/blacklight/app/helpers/catalog_helper.rb create mode 100644 vendor/plugins/blacklight/app/helpers/feedback_helper.rb create mode 100644 vendor/plugins/blacklight/app/helpers/saved_searches_helper.rb create mode 100644 vendor/plugins/blacklight/app/helpers/search_history_helper.rb create mode 100644 vendor/plugins/blacklight/app/helpers/user_sessions_helper.rb create mode 100644 vendor/plugins/blacklight/app/helpers/users_helper.rb create mode 100644 vendor/plugins/blacklight/app/models/bookmark.rb create mode 100644 vendor/plugins/blacklight/app/models/record_mailer.rb create mode 100644 vendor/plugins/blacklight/app/models/search.rb create mode 100644 vendor/plugins/blacklight/app/models/solr_document.rb create mode 100644 vendor/plugins/blacklight/app/models/user.rb create mode 100644 vendor/plugins/blacklight/app/models/user_session.rb create mode 100644 vendor/plugins/blacklight/app/views/_flash_msg.html.erb create mode 100644 vendor/plugins/blacklight/app/views/_user_util_links.html.erb create mode 100644 vendor/plugins/blacklight/app/views/bookmarks/edit.html.erb create mode 100644 vendor/plugins/blacklight/app/views/bookmarks/index.html.erb create mode 100644 vendor/plugins/blacklight/app/views/bookmarks/new.html.erb create mode 100644 vendor/plugins/blacklight/app/views/bookmarks/show.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_bookmark_control.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_citation.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_constraints.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_did_you_mean.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_document_list.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_email_form.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_facet_pagination.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_facets.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_hidden_filters.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_home.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_home_text.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_index_partials/_default.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_marc_view.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_previous_next_doc.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_search_constraints.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_search_form.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_show_partials/_default.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_show_tools.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_sms_form.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_solr_request.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/_sort_and_per_page.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/citation.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/email.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/facet.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/index.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/index.rss.builder create mode 100644 vendor/plugins/blacklight/app/views/catalog/opensearch.json.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/opensearch.xml.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/send_email_record.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/show.endnote.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/show.html.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/show.refworks.erb create mode 100644 vendor/plugins/blacklight/app/views/catalog/sms.erb create mode 100644 vendor/plugins/blacklight/app/views/feedback/complete.html.erb create mode 100644 vendor/plugins/blacklight/app/views/feedback/show.html.erb create mode 100644 vendor/plugins/blacklight/app/views/layouts/application.html.erb create mode 100644 vendor/plugins/blacklight/app/views/record_mailer/email_record.erb create mode 100644 vendor/plugins/blacklight/app/views/record_mailer/sms_record.erb create mode 100644 vendor/plugins/blacklight/app/views/saved_searches/index.html.erb create mode 100644 vendor/plugins/blacklight/app/views/search_history/index.html.erb create mode 100644 vendor/plugins/blacklight/app/views/user_sessions/_login_form.html.erb create mode 100644 vendor/plugins/blacklight/app/views/user_sessions/index.html.erb create mode 100644 vendor/plugins/blacklight/app/views/user_sessions/new.html.erb create mode 100644 vendor/plugins/blacklight/app/views/users/new.html.erb create mode 100644 vendor/plugins/blacklight/app/views/users/show.html.erb create mode 100644 vendor/plugins/blacklight/assets/images/bg.png create mode 100644 vendor/plugins/blacklight/assets/images/border.png create mode 100644 vendor/plugins/blacklight/assets/images/bul_sq_gry.gif create mode 100644 vendor/plugins/blacklight/assets/images/checkmark.gif create mode 100644 vendor/plugins/blacklight/assets/images/logo.png create mode 100644 vendor/plugins/blacklight/assets/images/magnifying_glass.gif create mode 100644 vendor/plugins/blacklight/assets/images/remove.gif create mode 100644 vendor/plugins/blacklight/assets/images/separator.gif create mode 100644 vendor/plugins/blacklight/assets/images/start_over.gif create mode 100644 vendor/plugins/blacklight/assets/javascripts/accordion.js create mode 100644 vendor/plugins/blacklight/assets/javascripts/application.js create mode 100644 vendor/plugins/blacklight/assets/javascripts/blacklight.js create mode 100644 vendor/plugins/blacklight/assets/javascripts/jquery-1.3.1.min.js create mode 100644 vendor/plugins/blacklight/assets/javascripts/lightbox.js create mode 100644 vendor/plugins/blacklight/assets/stylesheets/application.css create mode 100644 vendor/plugins/blacklight/assets/stylesheets/yui.css create mode 100644 vendor/plugins/blacklight/config/CONFIG_README create mode 100644 vendor/plugins/blacklight/config/SolrMarc/config.properties create mode 100644 vendor/plugins/blacklight/config/SolrMarc/demoserver.properties create mode 100644 vendor/plugins/blacklight/config/SolrMarc/index.properties create mode 100644 vendor/plugins/blacklight/config/boot.rb create mode 100644 vendor/plugins/blacklight/config/cucumber.yml create mode 100644 vendor/plugins/blacklight/config/database.yml create mode 100644 vendor/plugins/blacklight/config/demo_config.properties create mode 100644 vendor/plugins/blacklight/config/demo_index.properties create mode 100644 vendor/plugins/blacklight/config/environment.rb create mode 100644 vendor/plugins/blacklight/config/environments/cucumber.rb create mode 100644 vendor/plugins/blacklight/config/environments/development.rb create mode 100644 vendor/plugins/blacklight/config/environments/test.rb create mode 100644 vendor/plugins/blacklight/config/initializers/blacklight_config.rb create mode 100644 vendor/plugins/blacklight/config/initializers/inflections.rb create mode 100644 vendor/plugins/blacklight/config/initializers/mime_types.rb create mode 100644 vendor/plugins/blacklight/config/initializers/new_rails_defaults.rb create mode 100644 vendor/plugins/blacklight/config/locales/en.yml create mode 100644 vendor/plugins/blacklight/config/routes.rb create mode 100644 vendor/plugins/blacklight/config/solr.yml create mode 100644 vendor/plugins/blacklight/db/migrate/20090127164730_create_users.rb create mode 100644 vendor/plugins/blacklight/db/migrate/20090127164740_create_bookmarks.rb create mode 100644 vendor/plugins/blacklight/db/migrate/20090127164750_acts_as_taggable_migration.rb create mode 100644 vendor/plugins/blacklight/db/migrate/20090428182620_create_searches.rb create mode 100644 vendor/plugins/blacklight/db/migrate/20090529161304_add_authlogic_fields_to_users.rb create mode 100644 vendor/plugins/blacklight/doc/CUSTOMIZING.rdoc create mode 100644 vendor/plugins/blacklight/doc/PRE-REQUISITES.rdoc create mode 100644 vendor/plugins/blacklight/doc/README_FOR_APP create mode 100644 vendor/plugins/blacklight/doc/README_GIT.rdoc create mode 100644 vendor/plugins/blacklight/doc/README_INSTALLATION.rdoc create mode 100644 vendor/plugins/blacklight/doc/README_PRODUCTION.rdoc create mode 100644 vendor/plugins/blacklight/doc/README_SOLR.rdoc create mode 100644 vendor/plugins/blacklight/features/bookmarks.feature create mode 100644 vendor/plugins/blacklight/features/did_you_mean.feature create mode 100644 vendor/plugins/blacklight/features/errors.feature create mode 100644 vendor/plugins/blacklight/features/record_view.feature create mode 100644 vendor/plugins/blacklight/features/saved_searches.feature create mode 100644 vendor/plugins/blacklight/features/search.feature create mode 100644 vendor/plugins/blacklight/features/search_filters.feature create mode 100644 vendor/plugins/blacklight/features/search_history.feature create mode 100644 vendor/plugins/blacklight/features/search_sort.feature create mode 100644 vendor/plugins/blacklight/features/step_definitions/bookmarks_steps.rb create mode 100644 vendor/plugins/blacklight/features/step_definitions/error_steps.rb create mode 100644 vendor/plugins/blacklight/features/step_definitions/general_steps.rb create mode 100644 vendor/plugins/blacklight/features/step_definitions/record_view_steps.rb create mode 100644 vendor/plugins/blacklight/features/step_definitions/saved_searches_steps.rb create mode 100644 vendor/plugins/blacklight/features/step_definitions/search_history_steps.rb create mode 100644 vendor/plugins/blacklight/features/step_definitions/search_steps.rb create mode 100644 vendor/plugins/blacklight/features/step_definitions/user_steps.rb create mode 100644 vendor/plugins/blacklight/features/step_definitions/web_steps.rb create mode 100644 vendor/plugins/blacklight/features/support/env.rb create mode 100644 vendor/plugins/blacklight/features/support/paths.rb create mode 100644 vendor/plugins/blacklight/features/user_account.feature create mode 100644 vendor/plugins/blacklight/init.rb create mode 100644 vendor/plugins/blacklight/install.rb create mode 100644 vendor/plugins/blacklight/install/solr.yml create mode 100644 vendor/plugins/blacklight/lib/blacklight.rb create mode 100644 vendor/plugins/blacklight/lib/blacklight/configurable.rb create mode 100644 vendor/plugins/blacklight/lib/blacklight/core_ext.rb create mode 100644 vendor/plugins/blacklight/lib/blacklight/marc.rb create mode 100644 vendor/plugins/blacklight/lib/blacklight/marc/citation.rb create mode 100644 vendor/plugins/blacklight/lib/blacklight/routes.rb create mode 100644 vendor/plugins/blacklight/lib/blacklight/search_fields.rb create mode 100644 vendor/plugins/blacklight/lib/blacklight/solr.rb create mode 100644 vendor/plugins/blacklight/lib/blacklight/solr/document.rb create mode 100644 vendor/plugins/blacklight/lib/blacklight/solr/document/marc.rb create mode 100644 vendor/plugins/blacklight/lib/blacklight/solr/facets.rb create mode 100644 vendor/plugins/blacklight/lib/blacklight/solr_helper.rb create mode 100644 vendor/plugins/blacklight/lib/colorize.rb create mode 100644 vendor/plugins/blacklight/lib/taggable_pagination.rb create mode 100644 vendor/plugins/blacklight/lib/tasks/blacklight.rake create mode 100644 vendor/plugins/blacklight/lib/tasks/cucumber.rake create mode 100644 vendor/plugins/blacklight/lib/tasks/rspec.rake create mode 100644 vendor/plugins/blacklight/lib/tasks/solr_marc.rake create mode 100644 vendor/plugins/blacklight/lib/will_paginate_link_renderer.rb create mode 100644 vendor/plugins/blacklight/rerun.txt create mode 100755 vendor/plugins/blacklight/script/about create mode 100755 vendor/plugins/blacklight/script/autospec create mode 100755 vendor/plugins/blacklight/script/console create mode 100755 vendor/plugins/blacklight/script/cucumber create mode 100755 vendor/plugins/blacklight/script/dbconsole create mode 100755 vendor/plugins/blacklight/script/destroy create mode 100755 vendor/plugins/blacklight/script/generate create mode 100755 vendor/plugins/blacklight/script/performance/benchmarker create mode 100755 vendor/plugins/blacklight/script/performance/profiler create mode 100755 vendor/plugins/blacklight/script/performance/request create mode 100755 vendor/plugins/blacklight/script/plugin create mode 100755 vendor/plugins/blacklight/script/process/inspector create mode 100755 vendor/plugins/blacklight/script/process/reaper create mode 100755 vendor/plugins/blacklight/script/process/spawner create mode 100755 vendor/plugins/blacklight/script/runner create mode 100755 vendor/plugins/blacklight/script/server create mode 100755 vendor/plugins/blacklight/script/spec create mode 100755 vendor/plugins/blacklight/script/spec_server create mode 100644 vendor/plugins/blacklight/solr_marc/SolrMarc.jar create mode 100644 vendor/plugins/blacklight/spec/controllers/catalog_controller_spec.rb create mode 100644 vendor/plugins/blacklight/spec/controllers/saved_searches_controller_spec.rb create mode 100644 vendor/plugins/blacklight/spec/controllers/search_history_controller_spec.rb create mode 100644 vendor/plugins/blacklight/spec/data/test_data.utf8.mrc create mode 100644 vendor/plugins/blacklight/spec/helpers/application_helper_spec.rb create mode 100644 vendor/plugins/blacklight/spec/helpers/search_history_helper_spec.rb create mode 100644 vendor/plugins/blacklight/spec/helpers/solr_helper_spec.rb create mode 100644 vendor/plugins/blacklight/spec/lib/blacklight_marc_spec.rb create mode 100644 vendor/plugins/blacklight/spec/lib/blacklight_spec.rb create mode 100644 vendor/plugins/blacklight/spec/lib/configurable_spec.rb create mode 100644 vendor/plugins/blacklight/spec/lib/core_ext_deep_merge_unless_blank_spec.rb create mode 100644 vendor/plugins/blacklight/spec/lib/marc_citation_spec.rb create mode 100644 vendor/plugins/blacklight/spec/lib/search_fields_spec.rb create mode 100644 vendor/plugins/blacklight/spec/lib/tasks/solr_marc_task_spec.rb create mode 100644 vendor/plugins/blacklight/spec/lib/test_solr_server.rb create mode 100644 vendor/plugins/blacklight/spec/models/bookmark_spec.rb create mode 100644 vendor/plugins/blacklight/spec/models/record_mailer_spec.rb create mode 100644 vendor/plugins/blacklight/spec/models/search_spec.rb create mode 100644 vendor/plugins/blacklight/spec/models/solr_document_spec.rb create mode 100644 vendor/plugins/blacklight/spec/models/user_spec.rb create mode 100644 vendor/plugins/blacklight/spec/rcov.opts create mode 100644 vendor/plugins/blacklight/spec/spec.opts create mode 100644 vendor/plugins/blacklight/spec/spec_helper.rb create mode 100644 vendor/plugins/blacklight/spec/views/catalog/_document_list.html.erb_spec.rb create mode 100644 vendor/plugins/blacklight/spec/views/catalog/_facets.html.erb_spec.rb create mode 100644 vendor/plugins/blacklight/spec/views/catalog/_index_partials/_default.html.erb_spec.rb create mode 100644 vendor/plugins/blacklight/spec/views/catalog/_show_partials/_default.html.erb_spec.rb create mode 100644 vendor/plugins/blacklight/spec/views/catalog/show.html.erb_spec.rb create mode 100644 vendor/plugins/blacklight/super_template.rb create mode 100644 vendor/plugins/blacklight/tasks/blacklight_tasks.rake create mode 100644 vendor/plugins/blacklight/template.rb create mode 100644 vendor/plugins/blacklight/uninstall.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/CHANGELOG create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/MIT-LICENSE create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/README create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/Rakefile create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/generators/acts_as_taggable_migration/acts_as_taggable_migration_generator.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/generators/acts_as_taggable_migration/templates/migration.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/init.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/lib/acts_as_taggable.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/lib/tag.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/lib/tag_counts_extension.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/lib/tag_list.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/lib/tagging.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/lib/tags_helper.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/abstract_unit.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/acts_as_taggable_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/database.yml create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/fixtures/magazine.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/fixtures/magazines.yml create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/fixtures/photo.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/fixtures/photos.yml create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/fixtures/post.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/fixtures/posts.yml create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/fixtures/special_post.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/fixtures/subscription.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/fixtures/subscriptions.yml create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/fixtures/taggings.yml create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/fixtures/tags.yml create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/fixtures/user.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/fixtures/users.yml create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/schema.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/tag_list_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/tag_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/tagging_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/acts_as_taggable_on_steroids/test/tags_helper_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/blacklight/init.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/CHANGELOG create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/MIT-LICENSE create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/README create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/Rakefile create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/about.yml create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/boot.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/generators/plugin_migration/USAGE create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/generators/plugin_migration/plugin_migration_generator.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/generators/plugin_migration/templates/plugin_migration.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/init.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/lib/engines.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/lib/engines/assets.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/lib/engines/plugin.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/lib/engines/plugin/list.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/lib/engines/plugin/loader.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/lib/engines/plugin/locator.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/lib/engines/plugin/migrator.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/lib/engines/rails_extensions/asset_helpers.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/lib/engines/rails_extensions/dependencies.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/lib/engines/rails_extensions/form_tag_helpers.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/lib/engines/rails_extensions/migrations.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/lib/engines/rails_extensions/rails.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/lib/engines/testing.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/tasks/engines.rake create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/app/controllers/app_and_plugin_controller.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/app/controllers/namespace/app_and_plugin_controller.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/app/helpers/mail_helper.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/app/models/app_and_plugin_model.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/app/models/notify_mail.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/app/things/thing.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/app/views/app_and_plugin/a_view.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/app/views/namespace/app_and_plugin/a_view.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/app/views/notify_mail/implicit_multipart.text.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/app/views/notify_mail/implicit_multipart.text.plain.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/app/views/notify_mail/multipart_html.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/app/views/notify_mail/multipart_plain.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/app/views/notify_mail/signup.text.plain.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/app/views/plugin_mail/mail_from_plugin_with_application_template.text.plain.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/app/views/plugin_mail/multipart_from_plugin_with_application_template_plain.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/functional/controller_loading_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/functional/exception_notification_compatibility_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/functional/locale_loading_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/functional/routes_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/functional/view_helpers_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/functional/view_loading_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/lib/app_and_plugin_lib_model.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/lib/engines_test_helper.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/lib/render_information.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/app/controllers/alpha_plugin_controller.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/app/controllers/app_and_plugin_controller.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/app/controllers/namespace/alpha_plugin_controller.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/app/controllers/namespace/app_and_plugin_controller.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/app/controllers/namespace/shared_plugin_controller.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/app/controllers/shared_plugin_controller.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/app/models/alpha_plugin_model.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/app/models/app_and_plugin_model.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/app/models/shared_plugin_model.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/app/views/alpha_plugin/a_view.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/app/views/app_and_plugin/a_view.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/app/views/layouts/plugin_layout.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/app/views/namespace/alpha_plugin/a_view.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/app/views/namespace/app_and_plugin/a_view.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/app/views/namespace/shared_plugin/a_view.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/app/views/shared_plugin/a_view.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/lib/alpha_plugin_lib_model.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/lib/app_and_plugin_lib_model.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/alpha_plugin/locales/en.yml create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/beta_plugin/app/controllers/app_and_plugin_controller.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/beta_plugin/app/controllers/namespace/shared_plugin_controller.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/beta_plugin/app/controllers/shared_plugin_controller.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/beta_plugin/app/models/shared_plugin_model.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/beta_plugin/app/views/namespace/shared_plugin/a_view.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/beta_plugin/app/views/shared_plugin/a_view.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/beta_plugin/init.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/beta_plugin/locales/en.yml create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_assets/app/controllers/assets_controller.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_assets/app/views/assets/index.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_assets/app/views/layouts/assets.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_assets/init.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_assets_with_assets_directory/assets/file.txt create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_assets_with_assets_directory/assets/subfolder/file_in_subfolder.txt create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_assets_with_assets_directory/init.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_assets_with_no_subdirectory/assets/file.txt create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_assets_with_no_subdirectory/init.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_code_mixing/app/things/thing.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_code_mixing/init.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_load_path/init.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_migration/db/migrate/001_create_tests.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_migration/db/migrate/002_create_others.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_migration/db/migrate/003_create_extras.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_migration/init.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_plugin_mailing/app/models/plugin_mail.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_plugin_mailing/app/views/plugin_mail/mail_from_plugin.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_plugin_mailing/app/views/plugin_mail/multipart_from_plugin_html.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_plugin_mailing/app/views/plugin_mail/multipart_from_plugin_plain.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_plugin_mailing/app/views/plugin_mail/multipart_from_plugin_with_application_template_html.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_plugin_mailing/app/views/plugin_mail/multipart_from_plugin_with_application_template_plain.html.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_plugin_mailing/init.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_routing/app/controllers/namespace/test_routing_controller.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_routing/app/controllers/test_routing_controller.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_routing/config/routes.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_routing/init.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_testing/app/README.txt create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_testing/init.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_testing/test/fixtures/testing_fixtures.yml create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/plugins/test_testing/test/unit/override_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/unit/action_mailer_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/unit/arbitrary_code_mixing_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/unit/assets_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/unit/backwards_compat_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/unit/load_path_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/unit/migration_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/unit/model_and_lib_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/unit/plugins_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/unit/test_testing/override_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/engines/test/unit/testing_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/LICENSE create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/README.rdoc create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/Rakefile create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/TODO create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/VERSION.yml create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/USAGE create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/scaffold_resource_generator.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/controller.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/fixtures.yml create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/functional_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/helper.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/migration.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/model.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/old_migration.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/rspec/functional_spec.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/rspec/helper_spec.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/rspec/routing_spec.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/rspec/unit_spec.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/rspec/views/edit_spec.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/rspec/views/index_spec.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/rspec/views/new_spec.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/rspec/views/show_spec.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/shoulda_functional_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/unit_test.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/view__form.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/view__form.haml create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/view_edit.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/view_edit.haml create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/view_index.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/view_index.haml create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/view_new.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/view_new.haml create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/view_show.erb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/generators/scaffold_resource/templates/view_show.haml create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/init.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/install.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/lib/resource_controller.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/lib/resource_controller/accessors.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/lib/resource_controller/action_options.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/lib/resource_controller/actions.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/lib/resource_controller/base.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/lib/resource_controller/class_methods.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/lib/resource_controller/controller.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/lib/resource_controller/failable_action_options.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/lib/resource_controller/helpers.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/lib/resource_controller/helpers/current_objects.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/lib/resource_controller/helpers/internal.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/lib/resource_controller/helpers/nested.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/lib/resource_controller/helpers/singleton_customizations.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/lib/resource_controller/helpers/urls.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/lib/resource_controller/response_collector.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/lib/resource_controller/singleton.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/lib/urligence.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/rails/init.rb create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/resource_controller.gemspec create mode 100644 vendor/plugins/blacklight/vendor/plugins/resource_controller/uninstall.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..11850ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +log/*.log +tmp/* +db/*.sqlite3 +coverage/* +coverage.data +public/workspace/* +public/workspace +workspace/* +.bundle diff --git a/.rvmrc b/.rvmrc new file mode 100644 index 0000000..9fdac77 --- /dev/null +++ b/.rvmrc @@ -0,0 +1 @@ +rvm use 1.8.7@eems --create \ No newline at end of file diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..c7c5224 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,16 @@ +Copyright (c) 2011. The Board of Trustees of the Leland Stanford Junior University. All rights reserved. + +Redistribution and use of this distribution in source and binary forms, with or without modification, are permitted +provided that: The above copyright notice and this permission notice appear in all copies and supporting documentation; +The name, identifiers, and trademarks of The Board of Trustees of the Leland Stanford Junior University are not used in +advertising or publicity without the express prior written permission of The Board of Trustees of the Leland Stanford +Junior University; Recipients acknowledge that this distribution is made available as a research courtesy, "as is", +potentially with defects, without any obligation on the part of The Board of Trustees of the Leland Stanford Junior +University to provide support, services, or repair; + +THE BOARD OF TRUSTEES OF THE LELAND STANFORD JUNIOR UNIVERSITY DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD +TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE, AND IN NO EVENT SHALL THE BOARD OF TRUSTEES OF THE LELAND STANFORD JUNIOR UNIVERSITY BE LIABLE FOR ANY SPECIAL, +INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION WITH +THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..d868fc3 --- /dev/null +++ b/Gemfile @@ -0,0 +1,26 @@ +source "http://rubygems.org" + +gem 'active-fedora', '1.2.8' +gem 'authlogic', '2.1.2' +gem 'curb' +gem 'delayed_job', '2.0.3' +gem "facets", "2.8.4" +gem 'json' +gem "marc", "0.3.0" +gem "rails", "2.3.10" +gem "rsolr", "0.12.1" +gem "rsolr-ext", "0.12.0" +gem "sqlite3-ruby", "1.2.5" +gem "will_paginate", "2.3.11" + +group :development do + gem "awesome_print" + gem "mongrel" + gem "ruby-debug" +end + +group :test do + gem 'rcov' + gem 'rspec-rails', "< 2.0" + gem 'rspec', "< 2.0" +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..974619a --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,108 @@ +GEM + remote: http://rubygems.org/ + specs: + actionmailer (2.3.10) + actionpack (= 2.3.10) + actionpack (2.3.10) + activesupport (= 2.3.10) + rack (~> 1.1.0) + active-fedora (1.2.8) + activeresource (< 3.0.0) + mime-types (>= 1.16) + multipart-post + nokogiri + om (>= 1.0) + solr-ruby (>= 0.0.6) + solrizer (>= 0.3.0) + xml-simple (>= 1.0.12) + activerecord (2.3.10) + activesupport (= 2.3.10) + activeresource (2.3.10) + activesupport (= 2.3.10) + activesupport (2.3.10) + authlogic (2.1.2) + activesupport + awesome_print (0.3.1) + builder (3.0.0) + cgi_multipart_eof_fix (2.5.0) + columnize (0.3.2) + curb (0.7.10) + daemons (1.1.0) + delayed_job (2.0.3) + daemons + facets (2.8.4) + fastthread (1.0.7) + gem_plugin (0.2.3) + json (1.4.6) + linecache (0.43) + marc (0.3.0) + mediashelf-loggable (0.4.0) + mime-types (1.16) + mongrel (1.1.5) + cgi_multipart_eof_fix (>= 2.4) + daemons (>= 1.0.3) + fastthread (>= 1.0.1) + gem_plugin (>= 0.2.3) + multipart-post (1.1.0) + nokogiri (1.4.3.1) + om (1.0.2) + facets + facets + nokogiri + nokogiri (>= 1.4.2) + rack (1.1.0) + rails (2.3.10) + actionmailer (= 2.3.10) + actionpack (= 2.3.10) + activerecord (= 2.3.10) + activeresource (= 2.3.10) + activesupport (= 2.3.10) + rake (>= 0.8.3) + rake (0.8.7) + rcov (0.9.9) + rsolr (0.12.1) + builder (>= 2.1.2) + rsolr-ext (0.12.0) + rsolr (>= 0.12.1) + rspec (1.3.1) + rspec-rails (1.3.3) + rack (>= 1.0.0) + rspec (= 1.3.1) + ruby-debug (0.10.4) + columnize (>= 0.1) + ruby-debug-base (~> 0.10.4.0) + ruby-debug-base (0.10.4) + linecache (>= 0.3) + solr-ruby (0.0.8) + solrizer (0.3.2) + mediashelf-loggable + nokogiri + nokogiri + om + solr-ruby + sqlite3-ruby (1.2.5) + will_paginate (2.3.11) + xml-simple (1.0.13) + +PLATFORMS + ruby + +DEPENDENCIES + active-fedora (= 1.2.8) + authlogic (= 2.1.2) + awesome_print + curb + delayed_job (= 2.0.3) + facets (= 2.8.4) + json + marc (= 0.3.0) + mongrel + rails (= 2.3.10) + rcov + rsolr (= 0.12.1) + rsolr-ext (= 0.12.0) + rspec (< 2.0) + rspec-rails (< 2.0) + ruby-debug + sqlite3-ruby (= 1.2.5) + will_paginate (= 2.3.11) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9bda72b --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ +########################################################################################################### +# Copyright (c) 2011. The Board of Trustees of the Leland Stanford Junior University. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. \ No newline at end of file diff --git a/README.textile b/README.textile new file mode 100644 index 0000000..ebc5ac3 --- /dev/null +++ b/README.textile @@ -0,0 +1,67 @@ + +h1. EEMs - Everyday Electronic Materials + +A pre-Hydra, ActiveFedora/Blacklight Ruby on Rails application + +h2. Installation + +h3. Prerequisites + +* rvm with MRI 1.8.7 +* bundler +* packages and header files in order to build the following gems +** curb - needs curl and curl header files +** nokogiri - libxml2 +* Fedora +* Solr + + +h3. Instructions to get development instance running + +* source the .rvmrc - It will create an rvm gemset _1.8.7@eems_, you can change this if you want +** @$ source .rvmrc@ +* Use bundler to install gems - install any missing packages if needed +** @$ bundle install@ +* Point to your Fedora instance: modify the *FEDORA_URL* constant in @conf/environments/development.rb@ +* Point to your Solr instance: modify @config/solr.yml@ and @config/initializers/fedora_repository.rb@ +* Copy @solr/schema.xml@ and @solr/solrconfig.xml@ to *$SOLR_HOME/conf* +* Create the development sqlite database +** @$ rake db:migrate@ +* Create the test sqlite database +** @$ rake db:test:clone@ +* Make sure all the tests pass +** @$ rake spec@ +* Start delayed_job +** @$ startjob.sh development@ +* Start the app +** @$ script/server@ + +h2. Typical Use Case + +h3. Create an Eem using the browser widget to point to the URL of a pdf + +* Login to the app by opening http://localhost:3000 in your browser +* Create bookmark/browser button by dragging the "EEMs-dev Widget" button to your browser's bookmark toolbar +* Navigate to a web page with a freely downloadable pdf, not behind a password. e.g. http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm +* Click the "EEMs Widget" browser button +* Fill in details. In particular, drag the link for "1-column for viewing online" to the "Direct link to file:" input field. The widget should populate the input field with the URL to the PDF +* Click "Send to Dashboard" + +You should now see a facet with your username in the Selector facet. You can drill down to the details of the Eem you created. The Eem is editable until the "Send to Technical Services" Action link is clicked. This should kick off background accessioning of the Eem. + +h2. High-level Code Overview during EEMs creation + +* An EemsUser is created and 'logged-in' via ApplicationController#set_current_user. For demonstration purposes, it is naively creating an eems_demo_user. You should replace/rewrite this method with your with institution specific authorization/authentication scheme +* The browser widget is rendered from @public/javascripts/browser-widget-dialog.js@ +* Clicking the Send or Save buttons in the browser widget POSTs a json request with the form data to EemsController#create +* EemsController#create +** Creates Eem and Part objects in Fedora +** Creates a ContentFile object to temporarily keep track of pdf download progress. +** Creates a Dor::DownloadJob object which does the work of fetching the pdf from the specified url using the curb gem on the server side. +* DelayedJob then calls the Dor::DownloadJob#perform method to do the work in the backgroud. It updates download progress via ContentFile#update_progress +* The widget displays download progress by calling ContentController#show, passing the id of the ContentFile +* When the download finishes, a content datastream is created on the Part object, pointing to the downloaded pdf, logged to the ActionLog datastream in the Eem object. + + + + diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..602290e --- /dev/null +++ b/Rakefile @@ -0,0 +1,31 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require(File.join(File.dirname(__FILE__), 'config', 'boot')) + +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +require 'tasks/rails' + +begin + require 'delayed/tasks' +rescue LoadError + STDERR.puts "Run `rake gems:install` to install delayed_job" +end + +task :clean do + puts 'Cleaning old coverage.data' + FileUtils.rm('coverage.data') if(File.exists? 'coverage.data') +end + +Spec::Rake::SpecTask.new(:rcov) do |spec| + spec.libs << 'lib' << 'spec' + spec.pattern = 'spec/**/*_spec.rb' + spec.rcov = true + spec.rcov_opts = %w{--rails --exclude spec\/*,gems\/*,ruby\/* --aggregate coverage.data} +end + +task :default => [:clean, :rcov] + diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb new file mode 100644 index 0000000..0fa6767 --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,66 @@ +require 'vendor/plugins/blacklight/app/controllers/application_controller.rb' +# Filters added to this controller apply to all controllers in the application. +# Likewise, all the methods added will be available for all controllers. + +class ApplicationController < ActionController::Base + + before_filter :set_current_user + + helper :all # include all helpers, all the time + protect_from_forgery # See ActionController::RequestForgeryProtection for details + + protected + + # For the purposes of distribution, this method will create an eems_demo_user for logging in. + # It will be up to the developer to re-write this method + def set_current_user + # !!!!!!!!!!!!!!!!!!!!!!!! Setting a demo user + # !!!!!!!!!!!!!!!!!!!!!!!! Replace these lines with institution specific authorization/authentication scheme + unless EemsUser.user_authenticated?(session) + user = EemsUser.new("Eems Demo User created in ApplicationController", 'eems_demo_user', 'privgroup unused') + user.save_to_session(session) + end + end + + # Scrub sensitive parameters from your log + # filter_parameter_logging :password + private + def require_fedora + Fedora::Repository.register(FEDORA_URL, session[:user]) + return true + end + def require_solr + ActiveFedora::SolrService.register(SOLR_URL) + end + + # underscores are escaped w/ + signs, which are unescaped by rails to spaces + # html tags are escaped by converting < and > to < and > + def unescape_keys(attrs) + h=Hash.new + attrs.each do |k,v| + v = v.gsub(//, '>') + h[k.gsub(/ /, '_')] = v + end + h + end + + def escape_keys(attrs) + h=Hash.new + attrs.each do |k,v| + h[k.gsub(/_/, '+')]=v + end + h + end + + # !!!!!!!!!!!!!!!!!!!!!!!! These filters should be re-written/removed for institution specfic authentication/authorization + def user_required + true + end + + # + def authorized_user + true + end + +end diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb new file mode 100644 index 0000000..b4a8e43 --- /dev/null +++ b/app/controllers/catalog_controller.rb @@ -0,0 +1,241 @@ +class CatalogController < ApplicationController + + include Blacklight::SolrHelper + + before_filter :require_fedora, :require_solr, :search_session, :history_session + before_filter :user_required + before_filter :authorized_user + before_filter :delete_or_assign_search_session_params, :only=>:index + after_filter :set_additional_search_session_values, :only=>:index + + # Whenever an action raises SolrHelper::InvalidSolrID, this block gets executed. + # Hint: the SolrHelper #get_solr_response_for_doc_id method raises this error, + # which is used in the #show action here. + # rescue_from InvalidSolrID, :with => lambda { + # # when a request for /catalog/BAD_SOLR_ID is made, this method is executed... + # flash[:notice] = "Sorry, you seem to have encountered an error." + # redirect_to catalog_index_path + # } + # + # # When RSolr::RequestError is raised, this block is executed. + # # The index action will more than likely throw this one. + # # Example, when the standard query parser is used, and a user submits a "bad" query. + # rescue_from RSolr::RequestError, :with => lambda { + # # when solr (RSolr) throws an error (RSolr::RequestError), this method is executed. + # flash[:notice] = "Sorry, I don't understand your search." + # redirect_to catalog_index_path + # } + + # get search results from the solr index + def index + @extra_controller_params ||= {} + q = build_eems_solr_query(params[:q]) + + (@response, @document_list) = get_search_results(@extra_controller_params.merge!(:q=>q)) + @filters = params[:f] || [] + @user = EemsUser.load_from_session(session) + + if @user.nil? + #render :text => "Sorry, you are not authorized to view this page." + render :partial => "eems/_error/not_authorized" + return + end + + respond_to do |format| + format.html { save_current_search_params } + format.rss { render :layout => false } + end + end + + # get single document from the solr index + def show + @response, @document = get_solr_response_for_doc_id + @eem = Eem.find(@document[:id].to_s) + @parts = @eem.parts unless (@eem.parts.nil?) + @log = @eem.datastreams['actionLog'] + @user = EemsUser.load_from_session(session) + + respond_to do |format| + format.html {setup_next_and_previous_documents} + format.xml {render :xml => @document.marc.to_xml} + format.refworks + format.endnote + end + end + + # updates the search counter (allows the show view to paginate) + def update + session[:search][:counter] = params[:counter] + redirect_to :action => "show" + end + + # displays values and pagination links for a single facet field + def facet + @pagination = get_facet_pagination(params[:id], params) + end + + # single document image resource + def image + end + + # single document availability status (true/false) + def status + end + + # single document availability info + def availability + end + + # collection/search UI via Google maps + def map + end + + # method to serve up XML OpenSearch description and JSON autocomplete response + def opensearch + respond_to do |format| + format.xml do + render :layout => false + end + format.json do + render :json => get_opensearch_response + end + end + end + + # citation action + def citation + @response, @document = get_solr_response_for_doc_id + end + # Email Action (this will only be accessed when the Email link is clicked by a non javascript browser) + def email + @response, @document = get_solr_response_for_doc_id + end + # SMS action (this will only be accessed when the SMS link is clicked by a non javascript browser) + def sms + @response, @document = get_solr_response_for_doc_id + end + + # action for sending email. This is meant to post from the form and to do processing + def send_email_record + @response, @document = get_solr_response_for_doc_id + if params[:to] + from = request.host # host w/o port for From address (from address cannot have port#) + host = request.host + host << ":#{request.port}" unless request.port.nil? # host w/ port for linking + case params[:style] + when 'sms' + if !params[:carrier].blank? + if params[:to].length != 10 + flash[:error] = "You must enter a valid 10 digit phone number" + else + email = RecordMailer.create_sms_record(@document, {:to => params[:to], :carrier => params[:carrier]}, from, host) + end + else + flash[:error] = "You must select a carrier" + end + when 'email' + if params[:to].match(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/) + email = RecordMailer.create_email_record(@document, {:to => params[:to], :message => params[:message]}, from, host) + else + flash[:error] = "You must enter a valid email address" + end + end + RecordMailer.deliver(email) unless flash[:error] + redirect_to catalog_path(@document[:id]) + else + flash[:error] = "You must enter a recipient in order to send this message" + end + end + + protected + + # + # non-routable methods -> + # + + # calls setup_previous_document then setup_next_document. + # used in the show action for single view pagination. + def setup_next_and_previous_documents + setup_previous_document + setup_next_document + end + + # gets a document based on its position within a resultset + def setup_document_by_counter(counter) + return if counter < 1 || session[:search].blank? + search = session[:search] || {} + get_single_doc_via_search(search.merge({:page => counter})) + end + + def setup_previous_document + @previous_document = session[:search][:counter] ? setup_document_by_counter(session[:search][:counter].to_i - 1) : nil + end + + def setup_next_document + @next_document = session[:search][:counter] ? setup_document_by_counter(session[:search][:counter].to_i + 1) : nil + end + + # sets up the session[:search] hash if it doesn't already exist + def search_session + session[:search] ||= {} + end + + # sets up the session[:history] hash if it doesn't already exist. + # assigns all Search objects (that match the searches in session[:history]) to a variable @searches. + def history_session + session[:history] ||= [] + @searches = searches_from_history # <- in ApplicationController + end + + # This method will remove certain params from the session[:search] hash + # if the values are blank? (nil or empty string) + # if the values aren't blank, they are saved to the session in the :search hash. + def delete_or_assign_search_session_params + [:q, :qt, :search_field, :f, :per_page, :page, :sort].each do |pname| + #if(pname == :q && params[pname].blank?) + # session[:search][pname] = "" + #else + params[pname].blank? ? session[:search].delete(pname) : session[:search][pname] = params[pname] + #end + end + end + + # Saves the current search (if it does not already exist) as a models/search object + # then adds the id of the serach object to session[:history] + def save_current_search_params + return if search_session[:q].blank? && search_session[:f].blank? + params_copy = search_session.clone # don't think we need a deep copy for this + params_copy.delete(:page) + unless @searches.collect { |search| search.query_params }.include?(params_copy) + new_search = Search.create(:query_params => params_copy) + session[:history].unshift(new_search.id) + end + end + + # sets some additional search metadata so that the show view can display it. + def set_additional_search_session_values + unless @response.nil? + search_session[:total] = @response.total + end + end + + # a solr query method + # this is used when selecting a search result: we have a query and a + # position in the search results and possibly some facets + def get_single_doc_via_search(extra_controller_params={}) + solr_params = solr_search_params(extra_controller_params) + solr_params[:per_page] = 1 + solr_params[:fl] = '*' + Blacklight.solr.find(solr_params.merge(:q => build_eems_solr_query(extra_controller_params[:q]))).docs.first + end + + def build_eems_solr_query(query) + q = "" + # start query of with user supplied query term + q << "_query_:\"{!dismax qf=$qf_dismax pf=$pf_dismax}#{query}\"" + + # Append the exclusion of FileAssets + q << " AND _query_:\"Eem\"" + end + +end \ No newline at end of file diff --git a/app/controllers/content_files_controller.rb b/app/controllers/content_files_controller.rb new file mode 100644 index 0000000..ab076fd --- /dev/null +++ b/app/controllers/content_files_controller.rb @@ -0,0 +1,25 @@ +require 'delayed_job' + +class ContentFilesController < ApplicationController + before_filter :find_model + + def show + pdone = {'percent_done' => @cf.percent_done.to_s} + if(@cf.attempts == Delayed::Worker.max_attempts + 1) + pdone['attempts'] = 'failed' + else + pdone['attempts'] = @cf.attempts.to_s + end + + Rails.logger.debug("pdone: #{pdone.inspect}") + render :json => pdone.to_json + + end + + private + def find_model + ContentFile.uncached do + @cf = ContentFile.find(params[:id]) if params[:id] + end + end +end diff --git a/app/controllers/content_upload.rb b/app/controllers/content_upload.rb new file mode 100644 index 0000000..0a87bec --- /dev/null +++ b/app/controllers/content_upload.rb @@ -0,0 +1,39 @@ + +# Contains common methods used by the EemsController and PartsController when uploading content +module ContentUpload + + def create_content_dir + @content_dir = File.join(Sulair::WORKSPACE_DIR, @eem.pid) + FileUtils.mkdir(@content_dir) unless (File.exists?(@content_dir)) + end + + # Assumes params[:content_upload], @content_dir, @log, and @eem, and @user are defined + # Create a new Part if it doesn't exist already + # Otherwise, a previous upload attempt failed, so use the existing Part + def create_part_from_upload_and_log + content_file = params[:content_upload] + + if(@eem.parts.size == 0) + part = Part.from_params() + part.add_relationship(:is_part_of, @eem) + part.save + Rails.logger.info("Creating new Part: #{part.pid}") + else + part = @eem.parts.first + Rails.logger.info("Using existing Part: #{part.pid}") + + props_ds = part.datastreams['properties'] + Rails.logger.warn("!!!!! Replacing existing Part content !!!!!!") if(props_ds.done_values.first =~ /true/i) + end + + filename = Part.normalize_filename(content_file.original_filename) + File.open(File.join(@content_dir,filename), "wb") { |f| f.write(content_file.read) } + + part.create_content_datastream(filename) + part.download_done + + @log.log("File uploaded by #{@user.display_name}") + @log.save + end + +end \ No newline at end of file diff --git a/app/controllers/eems_controller.rb b/app/controllers/eems_controller.rb new file mode 100644 index 0000000..2c902ce --- /dev/null +++ b/app/controllers/eems_controller.rb @@ -0,0 +1,130 @@ +require 'dor/workflow_service' + +class EemsController < ApplicationController + include ContentUpload + + before_filter :require_fedora + before_filter :require_solr + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # These filters are defined in the local ApplicationController + # Either they should be re-written for institution specfic authentication/authorization + before_filter :user_required, :except => :index + before_filter :authorized_user, :except => :index + + #GET /eems + def index + render :text => 'OK' + end + + #GET /eems/{:id} + def show + @user = EemsUser.load_from_session(session) + @eem = Eem.find(params[:id]) + @parts = @eem.parts + + render :layout => "eems_show" + end + + #GET /eems/new + #render the page to create a new Eem + def new + @user = EemsUser.load_from_session(session) + end + + #POST /eems + #Handles submit from /eems/new + #Assume we receive the standard Rails hash of all the form params + def create + create_eem_and_log + create_content_dir + + # The file was uploaded with the POST + if(!params[:content_upload].nil?) + create_part_from_upload_and_log + + #render_creation_response(@eem.pid, part.pid) + res = 'eem_pid=' + @eem.pid + render :text => res.to_s, :content_type => 'text/plain' + + # We will download the file in the background + else + cf = ContentFile.new + cf.url = params[:contentUrl] + + cf.filepath = @content_dir + cf.attempts = 1 + cf.user_display_name = @user.display_name + cf.save + + part = Part.from_params(:url => params[:contentUrl], :content_file_id => cf.id) + part.add_relationship(:is_part_of, @eem) + part.save + cf.part_pid = part.pid + cf.save + + job = Dor::DownloadJob.new(cf.id) + Delayed::Job.enqueue(job) + + render_creation_response(@eem.pid, part.pid, cf.id) + end + + end + + #POST /eems/no_pdf + def no_pdf + create_eem_and_log + render_creation_response(@eem.pid) + end + + # Taken from Salt project + # Uses the update_indexed_attributes method provided by ActiveFedora::Base + # This should behave pretty much like the ActiveRecord update_indexed_attributes method + # For more information, see the ActiveFedora docs. + # + # Examples + # put :update, :id=>"_PID_", "document"=>{"subject"=>{"-1"=>"My Topic"}} + # Appends a new "subject" value of "My Topic" to any appropriate datasreams in the _PID_ document. + # put :update, :id=>"_PID_", "document"=>{"medium"=>{"1"=>"Paper Document", "2"=>"Image"}} + # Sets the 1st and 2nd "medium" values on any appropriate datasreams in the _PID_ document, overwriting any existing values. + def update + @eem = Eem.find(params[:id]) + attrs = unescape_keys(params[:eem]) + logger.debug("attributes submitted: #{attrs.inspect}") + ###@eem.update_indexed_attributes(attrs) + @eem.update_attributes(attrs) + @eem.save + + ###response = attrs.keys.map{|x| escape_keys({x=>attrs[x].values})} + response = {'eem' => attrs} + logger.debug("returning #{response.inspect}") + render :json => response + end + + protected + def create_eem_and_log + @eem = Eem.from_params(params[:eem]) + attrs = unescape_keys(params[:eem]) + @eem.update_attributes(attrs) + + @user = EemsUser.load_from_session(session) + #Add actionLog datastream + @log = Dor::ActionLogDatastream.new + @log.log("Request created by #{@user.display_name}") + @eem.add_datastream(@log) + @eem.save + + #Create the workflow datastream + #It's defined in config/initializers/accession_workflow_config + Dor::WorkflowService.create_workflow('dor', @eem.pid, 'eemsAccessionWF', ACCESSION_WF_XML) + end + + def render_creation_response(eem_pid, part_pid=nil, content_file_id=nil) + resp = {'eem_pid' => @eem.pid} + resp['part_pid'] = part_pid unless(part_pid.nil?) + resp['content_file_id'] = content_file_id unless(content_file_id.nil?) + + render :json => resp.to_json + end + +end \ No newline at end of file diff --git a/app/controllers/log_controller.rb b/app/controllers/log_controller.rb new file mode 100644 index 0000000..f9e56e2 --- /dev/null +++ b/app/controllers/log_controller.rb @@ -0,0 +1,24 @@ +class LogController < ApplicationController + before_filter :require_fedora + before_filter :require_solr + + # It can handle json + # {"entry": "My log entry text", "comment": "My comment about this entry"} + # + # Or xml + # + # My xml entry + # My comment about this entry + # + # + # Or application/x-www-form-urlencoded + # entry=My%20text + def create + eem = Eem.find(params[:eems_id]) + log = eem.datastreams['actionLog'] + log.log(params[:entry], params[:comment]) + log.save + + render :text => 'logged' + end +end \ No newline at end of file diff --git a/app/controllers/parts_controller.rb b/app/controllers/parts_controller.rb new file mode 100644 index 0000000..7fafaae --- /dev/null +++ b/app/controllers/parts_controller.rb @@ -0,0 +1,45 @@ + +class PartsController < ApplicationController + include ContentUpload + + before_filter :require_fedora + before_filter :require_solr + before_filter :user_required + before_filter :authorized_user + + # Handles POST to /eems/:eems_id/parts + def create + @eem = Eem.find(params[:eem_id]) + @log = @eem.datastreams['actionLog'] + @user = EemsUser.load_from_session(session) + + create_content_dir + + # The file was uploaded with the POST + if(!params[:content_upload].nil?) + create_part_from_upload_and_log + + redirect_to "/view/#{@eem.pid}" + else + # We shouldn't get here, so log an error if we + Rails.logger.error("!!!!! There was no content uploaded !!!!!!") + end + + end + + # Handles PUT to /eems/:eems_id/parts/:id + def update + @eem = Eem.find(params[:eem_id]) + part = @eem.parts.first + attrs = unescape_keys(params[:part]) + + logger.debug("attributes submitted: #{attrs.inspect}") + part.update_attributes(attrs) + part.save + + response = {'part' => attrs} + logger.debug("returning #{response.inspect}") + render :json => response + end + +end \ No newline at end of file diff --git a/app/controllers/permission_files_controller.rb b/app/controllers/permission_files_controller.rb new file mode 100644 index 0000000..72c10b1 --- /dev/null +++ b/app/controllers/permission_files_controller.rb @@ -0,0 +1,110 @@ + +class PermissionFilesController < ApplicationController + include ApplicationHelper + + before_filter :require_fedora + before_filter :require_solr + before_filter :user_required + before_filter :authorized_user + + #before_filter :duplicate_file_check, :only => [:create, :create_supplemental, :create_permission] + #before_filter :upload_size_check, :only => [:create, :create_supplemental, :create_permission] + + attr_accessor :file, :file_name, :eem + + # Handles http POST + def create + @file= PermissionFile.new() + @eem = Eem.find(params[:eem_id]) + @file.add_relationship(:is_dependent_of, @eem) + @file.set_permission_file_titles + process_file(params, "Permission File") + + log_uploaded + response = create_response + + redirect_to "/view/#{@eem.pid}" + end + + # Handles http DELETE + def destroy + part = PermissionFile.find(params[:id]) + if(part.nil?) + render :status => 404, :text => "Cannot find object with id: #{params[:id]}" + else + part.delete + @eem = Eem.find(params[:eem_id]) + log_deleted(part) + render :text => 'OK' + end + end + + #for stubbing + def create_new_datastream(attrs) + ds = ActiveFedora::Datastream.new(attrs) + end + + def create_response + {:file_name => @file_name} + end + + def process_file(params, label = "") + @file_name = params[:file].original_filename + props_ds = @file.datastreams['properties'] + props_ds.file_name_values = [@file_name] + mime_type = MIME::Types.type_for(@file_name).to_s + props_ds.comment_values = [params[:comment]] if(params[:comment]) + @file.save + + #save file to local disk. Directory is pid of parent object + dir = Sulair::WORKSPACE_DIR + "/#{@eem.pid}" + Dir.mkdir(dir) unless (File.exists?(dir)) + @path = File.join(dir, @file_name) + File.open(@path, "wb") { |f| f.write(params[:file].read) } + + #create externally managed Fedora Datastream + url = Sulair::WORKSPACE_URL + "/#{@eem.pid}/#{@file_name}" + #save file as a datastream for this new child object + attrs = {:pid => @file.pid, :dsID => 'content', :dsLocation => url, + :mimeType => mime_type, :dsLabel => label, :checksumType => 'MD5' } + @datastream = ActiveFedora::Datastream.new(attrs) + @datastream.control_group = 'E' + @datastream.versionable = false + @datastream.save + end + + def log_uploaded + log('uploaded', params[:file].original_filename) + end + + def log_deleted(permfile) + log('deleted', permfile.datastreams['properties'].file_name_values.first) + end + + def log(action, file_name) + user = EemsUser.load_from_session(session) + log = @eem.datastreams['actionLog'] + log.log("Permission file: #{file_name} #{action} by #{user.display_name}") + log.save + end + + + def upload_size_check + if(params[:file].size.to_i > MAX_UPLOADED_FILE_SIZE) + render :status => 400, :text => 'error-400-file-too-large' + return false + end + true + end + + def duplicate_file_check + if(File.exists?(WORKSPACE_DIR + '/' + params[:submit_id] + '/' + params[:file].original_filename)) + Rails.logger.error('File already exists') + render :status => 400, :text => 'error-400-file-exists' + return false + end + true + end + + +end diff --git a/app/controllers/submit_to_tech_services_controller.rb b/app/controllers/submit_to_tech_services_controller.rb new file mode 100644 index 0000000..55fe32a --- /dev/null +++ b/app/controllers/submit_to_tech_services_controller.rb @@ -0,0 +1,37 @@ +require 'dor/workflow_service' + +class SubmitToTechServicesController < ApplicationController + before_filter :require_fedora + before_filter :require_solr + + def create + @eem = Eem.find(params[:eems_id]) + + @props_ds = @eem.datastreams['eemsProperties'] + @props_ds.status_values = ['Submitted'] + now = Time.new.xmlschema + @props_ds.statusDatetime_values = [now] + @props_ds.requestDatetime_values = [now] + + user = EemsUser.load_from_session(session) + action_log = @eem.datastreams['actionLog'] + action_log.log("Request submitted by #{user.display_name}", params[:comment]) + action_log.save + + @eem.update_identity_metadata_object_label + + @eem.save + + Dor::WorkflowService.update_workflow_status('dor', params[:eems_id], 'eemsAccessionWF', 'submit-tech-services', 'completed') + + render :text => 'true' + rescue Exception => e + msg = e.message + unless(e.backtrace.nil?) + msg << "\n" << e.backtrace.join("\n") + end + + Rails.logger.error("SubmitToTechServices failed:\n" << msg) + render :status => 500, :text => "false" + end +end \ No newline at end of file diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000..4b4eddc --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,84 @@ +# Methods added to this helper will be available to all templates in the application. +module ApplicationHelper + require_dependency 'vendor/plugins/blacklight/app/helpers/application_helper.rb' + + def application_name + 'EEMs' + end + + def render_document_heading + '

' + document_heading.to_s + '

' + end + + def eem_title_heading + @document['title_t'].first + end + + # Get value for a given eem field + def print_solr_field(name, msg = '') + value = eval("@document[:#{name.to_s}]") + + if (value.nil? || value.empty?) + value = msg + end + + value + end + + # Get facet display name + def get_facet_display_value(fname, value) + if (fname == 'language_facet') + value = get_language_name(value) + end + + value + end + + # Get formatted timestamp for search results + def format_search_results_timestamp(timestamp) + if !timestamp.nil? + time = Time.parse(timestamp) + return time.strftime("%d-%b-%Y") + end + + return '' + end + + # Get sort values for header element in dashboard -> search results + def get_sort_params(field) + sort = params["sort"] + css_class = "" + field = field.sub(/_t$/, ''); + + if sort =~ /#{field}_sort[\s\+]asc/ + sort = sort.sub(/#{field}_sort[\s\+]asc/, "#{field}_sort desc") + css_class = "searchResultsHdrSortDesc" + + elsif sort =~ /#{field}_sort[\s\+]desc/ + sort = sort.sub(/#{field}_sort[\s\+]desc/, "#{field}_sort asc") + css_class = "searchResultsHdrSortAsc" + + else + sort = get_default_sort(field) + end + + return [sort, css_class] + end + + # Get default sort string for solr fields + def get_default_sort(field) + default_sort = { + "title" => "title_sort asc", + "system_create_dt" => "system_create_dt_sort desc", + "requestDatetime" => "requestDatetime_sort desc", + "selectorName" => "selectorName_sort asc", + "copyrightStatus" => "copyrightStatus_sort asc", + "status" => "status_sort asc" + } + + return default_sort[field] || '' + + end + +end + diff --git a/app/helpers/eems_helper.rb b/app/helpers/eems_helper.rb new file mode 100644 index 0000000..e1e5f8a --- /dev/null +++ b/app/helpers/eems_helper.rb @@ -0,0 +1,218 @@ + +module EemsHelper + + # Get text field tag for a property + def eems_text_field_tag(prop, value = '', options = {}) + text_field_tag "eem[#{prop.to_s}]", value, options + end + + # Get value for a given eem field + def print_eems_field(name, msg = '') + if (!@eem.nil?) + value = eval("@eem.fields[:#{name.to_s}][:values].first") + end + + if (value.nil? || value.empty?) + value = msg + end + + return value + end + + # Get value for a given eem part field + def print_parts_field(name, msg = '') + value = '' + + if (!@parts[0].nil?) + value = eval("@parts[0].datastreams['properties'].#{name.to_s}_values.first") + end + + if (value.nil? || value.empty?) + value = msg + end + + return value + end + + # Print the url decoded + def print_url_decoded + url = print_parts_field('url') + URI::decode(url) + end + + # Get source URL from referrer (or return empty string if nil) + def get_source_url(referrer) + value = '' + + if !referrer.nil? && !referrer.empty? + value = referrer + end + + return value + end + + # Get shortened source URL (max length = 50 characters) + def shorten_url(url) + max_length = 40 + + if url.length > max_length + url = url[0, max_length] + '...' + end + + return url + end + + # Get creator name (value from either creatorOrg or creatorPerson) + def get_creator_name + value = '' + creator_person = @eem.fields[:creatorPerson][:values].first + creator_org = @eem.fields[:creatorOrg][:values].first + + if (!creator_person.nil? && !creator_person.empty?) + value = creator_person + end + + if (!creator_org.nil? && !creator_org.empty?) + value = creator_org + end + + return value + end + + # Get creator type (either 'person' or 'organization') + def get_creator_type + value = 'organization' + creator_person = @eem.fields[:creatorPerson][:values].first + + if (!creator_person.nil? && !creator_person.empty?) + value = 'person' + end + + return value + end + + # Get locally saved filename + def get_local_filename + fname = '' + + if (!@parts[0].nil?) + value = @parts[0].datastreams['properties'].filename_values.first + fname = value unless(value.nil?) + end + + return fname + end + + # Get URL/path to the locally saved filename + def get_local_file_path + value = 'unknown' + filename = URI::escape(get_local_filename()) + + if (!filename.empty?) + value = Sulair::WORKSPACE_URL + '/' + @eem.pid + '/' + filename + end + + return value + end + + # Get payment fund (or empty string if nil) + def get_payment_fund + value = '' + payment_type = print_eems_field('paymentType') + + if (payment_type == 'Paid') + value = print_eems_field('paymentFund') + end + + return value + end + + # escape html tags (<, >) + def escape_tags(value) + value = value.gsub(/>/, '>') + value = value.gsub(/ 'Arabic', 'chi' => 'Chinese', 'eng' => 'English', 'fre' => 'French', + 'ger' => 'German', 'heb' => 'Hebrew', 'ita' => 'Italian', 'jpn' => 'Japanese', + 'kor' => 'Korean', 'rus' => 'Russian', 'spa' => 'Spanish', '|||' => 'Other' + } + + return language[code] || code + end + + # format action log timestamp + def format_action_log_timestamp(timestamp) + if !timestamp.nil? + return timestamp.strftime("%d-%b-%Y %I:%M %p") + end + + return '' + end + + # format action log message + def format_action_log_message(msg) + if (!msg.nil? && msg =~ /^(.*? (by|for) )(.*)$/) + msg = $1 + '' + $3 + '' + end + + return msg || '' + end + + # format download date timestamp to store in part child object + def format_download_date_timestamp(timestamp) + if !timestamp.nil? + return timestamp.strftime("%Y-%m-%dT%H:%M%z") + end + + return '' + end + +end diff --git a/app/models/content_file.rb b/app/models/content_file.rb new file mode 100644 index 0000000..39afb49 --- /dev/null +++ b/app/models/content_file.rb @@ -0,0 +1,11 @@ +class ContentFile < ActiveRecord::Base + + #needs to save percent done to object in db + def update_progress(dl_total, dl_now) + done_now = (Float(dl_now) / Float(dl_total) * 100).round + update_attribute(:percent_done, done_now) if(done_now != percent_done) + rescue FloatDomainError => e + # ignore the initial 0.0/0.0 + end + +end diff --git a/app/models/eem.rb b/app/models/eem.rb new file mode 100644 index 0000000..fe0840f --- /dev/null +++ b/app/models/eem.rb @@ -0,0 +1,70 @@ +require "active_fedora" +require 'dor/workflow_service' +require 'dor/action_log_datastream' + +# defined in lib directory +require 'eem_model' + + +class Eem < EemModel::Eem + include EemModel::EemAccession + + has_relationship "permission_files", :is_dependent_of, :inbound => true + + has_metadata :name => 'actionLog', :type => Dor::ActionLogDatastream do |m| + #nada + end + + #TODO this differs from Etd, but I'm not sure why we would need to declare Eem.new outside of this method + def self.from_params(params_hash) + e = Eem.new + e.set_properties(params_hash) + id_xml = e.generate_initial_identity_metadata_xml + attrs = { :dsID => 'identityMetadata', :mimeType => 'application/xml', :dsLabel => 'Identity Metadata'} + datastream = ActiveFedora::Datastream.new(attrs) + datastream.content = id_xml + datastream.control_group = 'X' + datastream.versionable = false + e.add_datastream(datastream) + e.save + e + end + + def set_properties(params_hash) + props = datastreams['eemsProperties'] + + params_hash.each_pair do |field, value| + case field + when 'creatorType' + if(value =~ /person/) + props.creatorPerson_values = [params_hash['creatorName']] + else + props.creatorOrg_values = [params_hash['creatorName']] + end + when 'creatorName' + #skip + else + eval("props.#{field.to_s}_values = [#{value.inspect}]") + end + end + + set_dc_and_fedora_metadata(props) + end + + def set_dc_and_fedora_metadata(props_ds) + dc = datastreams['DC'] + dc.title_values = props_ds.title_values + dc.identifier_append self.pid + if(props_ds.creatorPerson_values != []) + dc.creator_values = props_ds.creatorPerson_values + else + dc.creator_values = props_ds.creatorOrg_values + end + + title = props_ds.title_values.first + if(title) + self.label = 'EEMs: ' + title.gsub(/[\s\v\b]+/, " ") + end + end + +end diff --git a/app/models/part.rb b/app/models/part.rb new file mode 100644 index 0000000..c32c421 --- /dev/null +++ b/app/models/part.rb @@ -0,0 +1,67 @@ +# Part +# properties +# :url +# :done +# :content_file_id +# :size? +# +# -:done initialized to false +# +# content Datastream +# -points to workspace/eems-druid/content.pdf (we can create this even if the job isn't done) +require 'cgi' +require 'eem_model' + +class Part < EemModel::Part + + def self.from_params(params={}) + p = Part.new + props = p.datastreams['properties'] + props.url_values = [params[:url]] unless(params[:content_file_id].nil?) + props.content_file_id_values = [params[:content_file_id].to_s] unless(params[:content_file_id].nil?) + props.done_values = ['false'] + p + end + + def create_content_datastream(filename) + return if(datastreams.has_key?('content')) + + props_ds = datastreams['properties'] + props_ds.filename_values = [filename] + props_ds.save + mime_type = MIME::Types.type_for(filename).to_s + url = Sulair::WORKSPACE_URL + '/' + parent_pid + '/' + filename + + attrs = {:dsID => 'content', :dsLocation => url, + :mimeType => mime_type, :dsLabel => 'Content File: ' + filename, :checksumType => 'MD5', :versionable => 'false' } + ds = ActiveFedora::Datastream.new(attrs) + ds.control_group = 'E' + add_datastream(ds) + ds.save + end + + def parent_pid + if(!parents(:response_format => :id_array).empty?) + parents(:response_format => :id_array)[0] + end + end + + def download_done + props_ds = datastreams['properties'] + props_ds.done_values= ['true'] + props_ds.download_date_values = (Time.now).strftime("%Y-%m-%dT%H:%M%z") + props_ds.save + end + + def log_download_complete(display_name) + e = Eem.find(parent_pid) + action_log = e.datastreams['actionLog'] + action_log.log("File uploaded by #{display_name}") + action_log.save + end + + def Part.normalize_filename(filename) + URI::decode(filename) + end + +end diff --git a/app/models/permission_file.rb b/app/models/permission_file.rb new file mode 100644 index 0000000..dacb9bd --- /dev/null +++ b/app/models/permission_file.rb @@ -0,0 +1,39 @@ + +class PermissionFile < ActiveFedora::Base + + has_relationship "permission_file_for", :is_dependent_of # relationship between permission file and parent etd + + has_metadata :name => "properties", :type => ActiveFedora::MetadataDatastream do |m| + m.field "file_name", :string + m.field "comment", :string + end + + has_metadata :name => "DC", :type => ActiveFedora::QualifiedDublinCoreDatastream do |m| + end + + def parent_pid + permission_file_for(:response_format => :id_array)[0] + end + + def path_to_file + file_name = datastreams['properties'].file_name_values.first + File.join(Sulair::WORKSPACE_DIR, parent_pid, file_name) + end + + def delete + super + FileUtils.rm(path_to_file) + Dir.rmdir( Sulair::WORKSPACE_DIR + "/#{parent_pid}" ) if(Dir.glob(Sulair::WORKSPACE_DIR + "/#{parent_pid}/*").empty?) + end + + def set_permission_file_titles + set_dc_and_fedora_metadata('Permission file for Eem ' << parent_pid) + end + + private + def set_dc_and_fedora_metadata(title) + self.label = title + datastreams['DC'].title_values = title + end + +end \ No newline at end of file diff --git a/app/views/catalog/_constraints.html.erb b/app/views/catalog/_constraints.html.erb new file mode 100644 index 0000000..e69de29 diff --git a/app/views/catalog/_default.html.erb b/app/views/catalog/_default.html.erb new file mode 100644 index 0000000..383cb97 --- /dev/null +++ b/app/views/catalog/_default.html.erb @@ -0,0 +1,11 @@ +<%# default partial to display solr document fields in catalog index view -%> +
+ + <% index_field_names.each do |solr_fname| -%> + <% if document.get solr_fname %> +
<%= h index_field_labels[solr_fname]%>
+
<%= h document.get(solr_fname) %>
+ <% end -%> + <% end -%> + +
diff --git a/app/views/catalog/_detail_partials/_eem_readonly.html.erb b/app/views/catalog/_detail_partials/_eem_readonly.html.erb new file mode 100644 index 0000000..ec0ca2a --- /dev/null +++ b/app/views/catalog/_detail_partials/_eem_readonly.html.erb @@ -0,0 +1,38 @@ +

<%= eem_title_heading %>

+ +
+
Id :
<%= @document[:id].to_s %>
+ +
Catalog key :
<%= get_catkey() %>
+ +
PURL :
+ +
Found at this site :
+
<%= link_to @document[:sourceUrl_t].to_s, @document[:sourceUrl_t].to_s, {:target => '_blank'} %>
+ +
Language :
+
<%= get_language_name(@document[:language_t].first.to_s) %>
+ +
Author :
+
<%= render :partial => 'catalog/_show_partials/creator_name', :locals => {:document => @document} %>
+ +
Direct link to file :
+
<%= link_to print_url_decoded, print_parts_field('url'), {:id => 'link_url', :target => '_blank'} %>
+ +
Local copy of file :
+
<%= link_to get_local_filename(), get_local_file_path(), {:id => 'link_local_file'} %>
+ +
Citation/comments :
+
<%= @document[:note_t].to_s %>
+ +
Copyright :
+
<%= @document[:copyrightStatus_t].to_s %> <%= @document[:copyrightDate_t].to_s %>
+ +
Copyright permission :
+
+ <%= render :partial => 'eems/_detail_partials/permission_files', :locals => {:readOnly => true} %> +
+ +
Purchase :
+
<%= @document[:paymentType_t].to_s %> <%= @document[:paymentFund_t].to_s %>
+
diff --git a/app/views/catalog/_document_list.html.erb b/app/views/catalog/_document_list.html.erb new file mode 100644 index 0000000..900fd3c --- /dev/null +++ b/app/views/catalog/_document_list.html.erb @@ -0,0 +1,34 @@ + +<% # container for all documents in index view -%> +
+ + + + <% index_field_names.each do |solr_fname| -%> + <% sort_params = get_sort_params(solr_fname) %> + + + <% end -%> + + + <% # loop thru each doc -%> + <% @document_list.each_with_index do |document, counter| %> + <% rowBgColor = (counter % 2) == 1 ? "searchResultsRow1" : "searchResultsRow2" %> + + <% # main container for doc partial view -%> + <%= render_document_partial document, :index %> + + <% end %> +
+ <%= h index_field_labels[solr_fname]%> + <% form_tag catalog_index_path, :method=>:get do %> + <%= hidden_field_tag :sort, sort_params[0], :id => 'sort' %> + <%= hidden_field_tag :q, params[:q], :id => 'sort_q' %> + <%= hidden_field_tag(:qt, params[:qt], :id => 'sort_qt') %> + <%= hidden_field_tag(:per_page, params[:per_page], :id => 'sort_per_page') %> + <%= render 'catalog/hidden_filters' %> + + <% end %> +
+ +
diff --git a/app/views/catalog/_facets.html.erb b/app/views/catalog/_facets.html.erb new file mode 100644 index 0000000..6ce0b32 --- /dev/null +++ b/app/views/catalog/_facets.html.erb @@ -0,0 +1,42 @@ +<% unless params[:q].blank? and params[:f].blank? %> + +<% end %> + +<% # main container for facets/limits menu -%> +
+

Filter by

+ <% facet_field_names.each do |solr_fname| %> + +
+ <% display_facet = @response.facets.detect {|f| f.name == solr_fname} -%> + + <% if display_facet.items.length > 0 %> +

<%= facet_field_labels[solr_fname] -%>

+
    + <% display_facet.items.each do |item| -%> + + <% if !item.value.empty? %> + <% display_value = get_facet_display_value(solr_fname, h(item.value)) %> + + <% if facet_in_params? solr_fname, item.value %> +
  • + <%= display_value %> (<%= format_num item.hits %>) + [<%= link_to 'remove', remove_facet_params(solr_fname, item.value, params), :class=>'remove' %>] +
  • + <% else %> +
  • + <%= link_to display_value, add_facet_params(solr_fname, item.value) %> (<%= format_num item.hits %>) +
  • + <% end %> + + <% end %> + + <% end %> +
+ + <% end %> +
+ <% end %> +
diff --git a/app/views/catalog/_home_text.html.erb b/app/views/catalog/_home_text.html.erb new file mode 100644 index 0000000..675a30d --- /dev/null +++ b/app/views/catalog/_home_text.html.erb @@ -0,0 +1,14 @@ +<% + base_url = Sulair::WORKSPACE_URL.sub(/\/workspace\/?$/, "") + widget_name = WIDGET_NAME || 'EEMs Widget' + widget_suffix = WIDGET_SUFFIX || '' +%> + +

EEMS<%= widget_suffix %> Bookmark Widget

+ +
+ Drag this button to the bookmarks toolbar in your web browser   + + <%= widget_name %> + +
\ No newline at end of file diff --git a/app/views/catalog/_index_partials/_default.html.erb b/app/views/catalog/_index_partials/_default.html.erb new file mode 100644 index 0000000..7bd84df --- /dev/null +++ b/app/views/catalog/_index_partials/_default.html.erb @@ -0,0 +1,17 @@ +<%# default partial to display solr document fields in catalog index view -%> +<% index_field_names.each do |solr_fname| -%> + + <% if document.get solr_fname %> + + <% if solr_fname == 'title_t' %> + <%= link_to document['title_t'].first, :controller => "catalog", :action => "show", + :id => document.get('id').gsub(/^druid:/, '')%> + <% elsif solr_fname == 'system_create_dt' || solr_fname == 'requestDatetime_t' %> + <%= format_search_results_timestamp(document.get(solr_fname)) %> + <% else %> + <%= h document.get(solr_fname) %> + <% end %> + + <% end -%> + +<% end -%> diff --git a/app/views/catalog/_search_form.html.erb b/app/views/catalog/_search_form.html.erb new file mode 100644 index 0000000..540fdf9 --- /dev/null +++ b/app/views/catalog/_search_form.html.erb @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/app/views/catalog/_show_partials/_creator_name.html.erb b/app/views/catalog/_show_partials/_creator_name.html.erb new file mode 100644 index 0000000..2dc9db0 --- /dev/null +++ b/app/views/catalog/_show_partials/_creator_name.html.erb @@ -0,0 +1 @@ +<%= get_creator_name() %> \ No newline at end of file diff --git a/app/views/catalog/_show_partials/_default.html.erb b/app/views/catalog/_show_partials/_default.html.erb new file mode 100644 index 0000000..17dc2c7 --- /dev/null +++ b/app/views/catalog/_show_partials/_default.html.erb @@ -0,0 +1,17 @@ +<%# default partial to display solr document fields in catalog show view -%> +
+ <% document_show_fields.each do |solr_fname| -%> + <% if document[solr_fname] %> + <% if document[solr_fname].is_a?(Array) %> +
<%= document_show_field_labels[solr_fname]%>
+ <% document[solr_fname].each do |val| %> + <%= val %><%= "
" unless val == document[solr_fname].last %> + <% end %> +
+ <% else %> +
<%= document_show_field_labels[solr_fname]%>
+
<%= document[solr_fname] %>
+ <% end -%> + <% end -%> + <% end -%> +
\ No newline at end of file diff --git a/app/views/catalog/_sort_and_per_page.html.erb b/app/views/catalog/_sort_and_per_page.html.erb new file mode 100644 index 0000000..deeb533 --- /dev/null +++ b/app/views/catalog/_sort_and_per_page.html.erb @@ -0,0 +1,13 @@ +
+ + <% form_tag catalog_index_path, :method=>:get, :class=>'per_page' do %> + <%= label_tag(:per_page, "Show " + select_tag(:per_page, options_for_select(['10', '20', '50', '100'], h(params[:per_page])), :title => "Number of results to display per page", :onchange => 'this.form.submit()') + " per page") %> + <%= hidden_field_tag :q, params[:q], :id => 'per_page_q' %> + <%= hidden_field_tag(:qt, params[:qt], :id => 'per_page_qt') %> + <%= hidden_field_tag(:sort, params[:sort], :id => 'per_page_sort') %> + <%= render 'catalog/hidden_filters' %> + + <% end %> + +

Click on column headers to sort

+
\ No newline at end of file diff --git a/app/views/catalog/index.html.erb b/app/views/catalog/index.html.erb new file mode 100644 index 0000000..0020835 --- /dev/null +++ b/app/views/catalog/index.html.erb @@ -0,0 +1,35 @@ +<% if params[:q].blank? and params[:f].blank? %> + <%# if there are no input/search related params, display the "home" partial -%> + <%= render 'home' %> +<% else %> + + <% @page_title = application_name + ": Search Results" %> + + <%= render :partial => 'search_form' %> + + <%= render :partial => 'did_you_mean' %> + + <%= render 'constraints', :localized_params=>params %> + + + + <%= render 'sort_and_per_page' %> + + <%= render :partial=>'document_list' %> + + <% sidebar_items << capture do %> + <%= render :partial=>'facets' %> + <% end %> + + + + <%= render :partial=>'solr_request' %> + +<% end %> \ No newline at end of file diff --git a/app/views/catalog/show.html.erb b/app/views/catalog/show.html.erb new file mode 100644 index 0000000..76a6edd --- /dev/null +++ b/app/views/catalog/show.html.erb @@ -0,0 +1,93 @@ +
+
+
+ « <%= link_to "Start Over", :controller => "catalog", :action => "index" %>
+ « <%= link_back_to_catalog(opts={:label=>'Back to search result'}) %> +
+
+ <%= render 'previous_next_doc' %> +
+
+
+

Status

+ <%= print_eems_field('status') %>

+ +

Actions

+
    + <% if is_eem_editable(print_eems_field('status')) %> +
  • + <%= link_to 'Send to Technical Services', '#', {:id => 'link_send_to_tech_services', :class => 'action_box_show'} %> +
    + +
    + + <%= link_to 'Cancel', '#', :id => 'send_to_tech_services_cancel' %> +
    +
    +
  • + <% end %> + + <% if !is_eem_canceled(print_eems_field('status')) %> +
  • + <%= link_to 'Cancel this request', '#', {:id => 'link_cancel_this_request', :class => 'action_box_show'} %> +
    + +
    + + <%= link_to 'Cancel', '#', :id => 'cancel_this_request_cancel' %> +
    +
    +
  • + <% end %> + +
  • + <%= link_to 'Upload copyright attachment', '#', {:id => 'link_upload_copyright_attachment', :class => 'action_box_show'} %> + <% form_tag("/eems/#{@eem.pid}/permission_files", :method => 'post', :enctype => 'multipart/form-data', :id => 'formlet_upload_copyright_attachment', :class => 'comment_formlet') do %> +
    +
    <%= label_tag 'permission_file_upload', 'Select file (.doc, .pdf, .txt, etc.)' %>
    +
    <%= file_field '', '', {:size => 18, :id => 'permission_file_upload', :name => 'file'} %>
    +
    <%= label_tag 'permission_desc', 'Description' %>
    +
    +
    +
    + <%= image_tag '/images/c_loader.gif', :id => 'permission_files_upload_loader', :class => 'c_loader', :style => 'display: none;' %> + + <%= link_to 'Cancel', '#', :id => 'upload_copyright_attachment_cancel' %> +
    + <% end %> +
  • + +
  • + <%= link_to 'Question/comment to selector', '#', {:id => 'link_comment_to_selector', :class => 'action_box_show'} %> +
    + +
    + + <%= link_to 'Cancel', '#', :id => 'comment_to_selector_cancel' %> +
    +
    +
  • +
+
+ +
+ <% if is_eem_editable(print_eems_field('status')) %> + <%= render :partial => 'eems/_detail_partials/editable' %> + <% else %> + <%= render :partial => 'catalog/_detail_partials/eem_readonly' %> + <% end %> + +
+

Action log

+ <%= render :partial => 'eems/_detail_partials/action_log' %> +
+ +
+
+ + +<%= javascript_tag "window._pid = '#{@eem.pid}'" %> +<%= javascript_tag "window._part_pid = '" + get_part_pid() + "'" %> diff --git a/app/views/eems/_detail_partials/_action_log.html.erb b/app/views/eems/_detail_partials/_action_log.html.erb new file mode 100644 index 0000000..02d09a1 --- /dev/null +++ b/app/views/eems/_detail_partials/_action_log.html.erb @@ -0,0 +1,13 @@ +<% if (!@log.nil?) %> +
+ <% @log.each_entry do |timestamp, entry, comment| %> +
<%= format_action_log_timestamp(timestamp) %> :
+
+ <%= format_action_log_message(entry) %> + <% if (!comment.nil? && !comment.empty?)%> +
<%= comment %>
+ <% end %> +
+ <% end %> +
+<% end %> \ No newline at end of file diff --git a/app/views/eems/_detail_partials/_editable.html.erb b/app/views/eems/_detail_partials/_editable.html.erb new file mode 100644 index 0000000..5129032 --- /dev/null +++ b/app/views/eems/_detail_partials/_editable.html.erb @@ -0,0 +1,79 @@ +

<%= print_eems_field('title', @document['title_t'].first) %>

+ <%= text_field_tag "title", print_eems_field('title', @document['title_t'].first), {:size => 80, :class => "mainTitle", :style => "display: none", :id => 'input_title'} %> + +
+
Id :
<%= @eem.pid %>
+ +
Catalog key :
<%= get_catkey() %>
+ +
PURL :
+ +
Found at this site :
+
+ <%= link_to print_eems_field('sourceUrl'), print_eems_field('sourceUrl'), {:id => 'link_sourceUrl', :target => '_blank'} %> +
+ +
Language :
+
<%= render :partial => 'eems/select_options', :locals => {:field => 'language', :id => 'eem_language', :selected => print_eems_field('language')} %>
+ +
Author :
+
+
<%= get_creator_name() %>
+ <%= text_field_tag "creatorName", get_creator_name(), {:size => 30, :style => "display: none", :id => 'input_creatorName'} %> + <%= render :partial => 'eems/select_options', :locals => {:field => 'creatorType', :id => 'eem_creatorType', :selected => get_creator_type()} %> +
+ +
Direct link to file :
+
+ <% if (!print_parts_field('url').nil? && !print_parts_field('url').empty?) %> + + <%= link_to print_url_decoded, print_parts_field('url'), {:id => 'linkUrl', :target => '_blank'} %> + <%= text_field_tag "linkUrl", print_parts_field('url'), {:size => 60, :style => "display: none", :id => 'input_linkUrl'} %> + + <% else %> +
Click to add link
+ <%= link_to print_url_decoded, print_parts_field('url'), {:id => 'linkUrl', :target => '_blank', :style => "display: none;"} %> + <%= text_field_tag "linkUrl", print_parts_field('url'), {:size => 60, :style => "display: none", :id => 'input_linkUrl'} %> + + <% end %> +
+ +
Local copy of file :
+
+ <% if get_local_file_path() != 'unknown' %> + <%= link_to get_local_filename(), get_local_file_path(), {:id => 'link_local_file'} %> + <% else %> + <% form_tag("/eems/#{@eem.pid}/parts", :method => 'post', :id => 'detail_file_upload', :name => 'detail_file_upload', :enctype => 'multipart/form-data') do %> + <%= file_field '', '', {:size => 31, :id => 'content_upload', :name => 'content_upload'} %>
+ + <%= image_tag '/images/c_loader.gif', :id => 'detail_file_upload_loader', :class => 'c_loader', :style => 'display: none;' %> + <% end %> + <% end %> +
+ +
Citation/comments :
+
+
<%= escape_tags(print_eems_field('note')) %>
+ +
+ +
Copyright :
+
+ <%= render :partial => 'eems/select_options', :locals => {:field => 'copyrightStatusDetailPage', :name => 'copyrightStatus', :id => 'eem_copyrightStatus', :selected => print_eems_field('copyrightStatus')} %> +
+ +
Copyright permission :
+
+ <%= render :partial => 'eems/_detail_partials/permission_files', :locals => {:readOnly => false} %> +
+ +
Purchase :
+
+ <%= render :partial => 'eems/select_options', :locals => {:field => 'paymentType', :id => 'eem_paymentType', :selected => print_eems_field('paymentType')} %> + <%= render :partial => 'eems/payment_fund' %> +
+
+ + + + diff --git a/app/views/eems/_detail_partials/_permission_files.html.erb b/app/views/eems/_detail_partials/_permission_files.html.erb new file mode 100644 index 0000000..5ea5701 --- /dev/null +++ b/app/views/eems/_detail_partials/_permission_files.html.erb @@ -0,0 +1,32 @@ +<% + pfiles = @eem.permission_files +%> + + \ No newline at end of file diff --git a/app/views/eems/_error/_not_authorized.html.erb b/app/views/eems/_error/_not_authorized.html.erb new file mode 100644 index 0000000..1c85327 --- /dev/null +++ b/app/views/eems/_error/_not_authorized.html.erb @@ -0,0 +1,26 @@ + + + + + + + <%= h(@page_title || application_name) %> + <%= stylesheet_link_tag 'site' %> + + + + +
+

Sorry, access to the EEMs tool is limited to selected users.

+

What now?

+ +

To inquire about getting access, contact eems-feedback@lists.stanford.edu.

+

If you think you have access, but are unable to login, contact HelpSU.

+ +

Protect our web assets

+ +

Remember to quit your browser when you are finished with this browser session. Quitting the browser is the only sure way to discard the keys to this and other secured Stanford websites that you received when you logged into WebLogin.

+
+ + + \ No newline at end of file diff --git a/app/views/eems/_payment_fund.html.erb b/app/views/eems/_payment_fund.html.erb new file mode 100644 index 0000000..a77a49a --- /dev/null +++ b/app/views/eems/_payment_fund.html.erb @@ -0,0 +1,5 @@ +<% if (print_eems_field('paymentType') == 'Paid')%> + <%= eems_text_field_tag :paymentFund, print_eems_field('paymentFund'), {:size => 25, :id => 'eem_payment_fund', :class => 'marginLeft_14px'} %> +<% else %> + <%= eems_text_field_tag :paymentFund, "(Fund name)", {:size => 25, :id => 'eem_payment_fund', :class => 'marginLeft_14px', :style => 'display: none;'} %> +<% end %> diff --git a/app/views/eems/_select_options.html.erb b/app/views/eems/_select_options.html.erb new file mode 100644 index 0000000..cbb62bc --- /dev/null +++ b/app/views/eems/_select_options.html.erb @@ -0,0 +1,46 @@ +<% + list_options = { + 'language' => [ + {'ara' => 'Arabic'}, {'chi' => 'Chinese'}, {'eng' => 'English'}, {'fre' => 'French'}, + {'ger' => 'German'}, {'heb' => 'Hebrew'}, {'ita' => 'Italian'}, {'jpn' => 'Japanese'}, + {'kor' => 'Korean'}, {'rus' => 'Russian'}, {'spa' => 'Spanish'}, {'|||' => 'Other'} + ], + 'creatorType' => [ + {'organization' => 'organization'}, {'person' => 'person'} + ], + 'copyrightStatusWidget' => [ + {'Public access OK' => 'Public access OK'}, + {'Stanford access OK' => 'Stanford access OK'}, + {'Requires request' => 'Requires request'} + ], + 'copyrightStatusDetailPage' => [ + {'Public access OK' => 'Public access OK'}, + {'Stanford access OK' => 'Stanford access OK'}, + {'Requires request' => 'Requires request'}, + {'Request sent' => 'Request sent'}, + {'Permission denied' => 'Permission denied'}, + {'No response received' => 'No response received'} + ], + 'paymentType' => [ + {'Free' => 'Free'}, + {'Paid' => 'On fund...'} + ] + } + + id = field if id.nil? + name = field if name.nil? +%> + + diff --git a/app/views/eems/_source_url.html.erb b/app/views/eems/_source_url.html.erb new file mode 100644 index 0000000..c5eeab1 --- /dev/null +++ b/app/views/eems/_source_url.html.erb @@ -0,0 +1,6 @@ +<% if !get_source_url(params[:referrer]).empty? %> + <%= shorten_url(get_source_url(params[:referrer])) %> + <%= hidden_field_tag 'eem[sourceUrl]', get_source_url(params[:referrer]), {:id => 'eem_bw_sourceUrl'} %> +<% else %> + <%= eems_text_field_tag :sourceUrl, get_source_url(params[:referrer]), {:size => 41, :name => 'eem[sourceUrl]', :id => 'eem_bw_sourceUrl'} %> +<% end %> \ No newline at end of file diff --git a/app/views/eems/_top_nav_links.html.erb b/app/views/eems/_top_nav_links.html.erb new file mode 100644 index 0000000..87dd850 --- /dev/null +++ b/app/views/eems/_top_nav_links.html.erb @@ -0,0 +1,15 @@ +<% + @user = EemsUser.load_from_session(session) + widget_name = WIDGET_NAME || 'EEMs Widget' +%> + + + + diff --git a/app/views/eems/_widgets/_browser_widget.html.erb b/app/views/eems/_widgets/_browser_widget.html.erb new file mode 100644 index 0000000..51e31d7 --- /dev/null +++ b/app/views/eems/_widgets/_browser_widget.html.erb @@ -0,0 +1,46 @@ +<% form_tag({:action => :create}, :id => 'eems-browser-widget', :name => 'browser_widget') do %> + <%= hidden_field_tag 'eem[selectorSunetid]', "#{@user.sunetid}"%> + <%= hidden_field_tag 'eem[selectorName]', "#{@user.display_name}"%> + +
+
<%= label_tag 'eem[sourceUrl]', 'Found on this site:' %>
+
<%= render :partial => 'eems/source_url' %>
+ +
Language:
+
<%= render :partial => 'eems/select_options', :locals => {:field => 'language', :id => 'eem_bw_language', :selected => 'eng'} %>
+ +
<%= label_tag 'eem[title]', 'Title of work:' %>
+
<%= eems_text_field_tag :title, '', {:size => 41, :id => 'eem_bw_title'} %>
+ +
<%= label_tag 'eem[creatorName]', 'Author:' %>
+
+ <%= eems_text_field_tag :creatorName, '', {:size => 24, :id => 'eem_bw_creatorName'} %>   + <%= render :partial => 'eems/select_options', :locals => {:field => 'creatorType', :id => 'eem_bw_creatorType', :selected => ''} %> +
+ +
<%= label_tag 'contentUrl', 'Direct link to file:' %>
+
+ <%= text_field_tag :contentUrl, '', {:size => 41, :name => 'contentUrl', :id => 'contentUrl'} %>
+ ↑ Drag and drop the link of the file you want to capture +
+ +
+
<%= text_area_tag 'eem[note]', '(Citation, comments, copyright notes, etc.)', :id => 'eem_bw_note', :rows => 5, :cols => 56 %>
+ +
<%= label_tag 'eem[copyrightStatus]', 'Copyright:' %>
+
+ <%= render :partial => 'eems/select_options', :locals => {:field => 'copyrightStatusWidget', :name => 'copyrightStatus', :id => 'eem_bw_copyrightStatus', :selected => 'Requires request'} %> +
+ +
<%= label_tag 'eem[paymentType]', 'Purchase:' %>
+
+ <%= render :partial => 'eems/select_options', :locals => {:field => 'paymentType', :id => 'eem_bw_paymentType', :selected => ''} %> + <%= eems_text_field_tag :paymentFund, '(Fund name)', {:size => 25, :id => 'eem_bw_payment_fund', :class => 'marginLeft_14px', :style => 'display: none;'} %> +
+
+ +
+ + +
+<% end %> \ No newline at end of file diff --git a/app/views/eems/_widgets/_desktop_widget.html.erb b/app/views/eems/_widgets/_desktop_widget.html.erb new file mode 100644 index 0000000..d8a6e47 --- /dev/null +++ b/app/views/eems/_widgets/_desktop_widget.html.erb @@ -0,0 +1,51 @@ +<% form_tag('/eems', :method => 'post', :id => 'eems-desktop-widget', :name => 'desktop_widget', :enctype => 'multipart/form-data', :target =>"upload_target", :style => "display: none;") do %> + <%= hidden_field_tag 'eem[selectorSunetid]', "#{@user.sunetid}"%> + <%= hidden_field_tag 'eem[selectorName]', "#{@user.display_name}"%> + <%= hidden_field_tag 'authenticity_token', "#{form_authenticity_token}", :id => 'authenticity_token' %> + <%= hidden_field_tag 'eem[statusDatetime]', '', :id => 'eem_dw_statusDatetime' %> + <%= hidden_field_tag 'eem[copyrightStatusDate]', '', :id => 'eem_dw_copyrightStatusDate' %> + <%= hidden_field_tag 'eem[requestDatetime]', '', :id => 'eem_dw_requestDatetime' %> + <%= hidden_field_tag 'eem[status]', 'Created', :id => 'eem_dw_status' %> + +
+
<%= label_tag 'eem[sourceUrl]', 'Found on this site:' %>
+
<%= eems_text_field_tag :sourceUrl, '', {:size => 41, :name => 'eem[sourceUrl]', :id => 'eem_dw_sourceUrl'} %>
+ +
Language:
+
<%= render :partial => 'eems/select_options', :locals => {:field => 'language', :id => 'eem_dw_language', :selected => 'eng'} %>
+ +
<%= label_tag 'eem[title]', 'Title of work:' %>
+
<%= eems_text_field_tag :title, '', {:size => 41, :id => 'eem_dw_title'} %>
+ +
<%= label_tag 'eem[creatorName]', 'Author:' %>
+
+ <%= eems_text_field_tag :creatorName, '', {:size => 24, :id => 'eem_dw_creatorName'} %>   + <%= render :partial => 'eems/select_options', :locals => {:field => 'creatorType', :id => 'eem_dw_creatorType', :selected => ''} %> +
+ +
<%= label_tag 'content_upload', 'File:' %>
+
+ <%= file_field '', '', {:size => 31, :id => 'content_upload', :name => 'content_upload'} %> +
+ +
+
<%= text_area_tag 'eem[note]', '(Citation, comments, copyright notes, etc.)', :id => 'eem_dw_note', :rows => 5, :cols => 56 %>
+ +
<%= label_tag 'eem[copyrightStatus]', 'Copyright:' %>
+
+ <%= render :partial => 'eems/select_options', :locals => {:field => 'copyrightStatusWidget', :name => 'copyrightStatus', :id => 'eem_dw_copyrightStatus', :selected => 'Requires request'} %> +
+ +
<%= label_tag 'eem[paymentType]', 'Purchase:' %>
+
+ <%= render :partial => 'eems/select_options', :locals => {:field => 'paymentType', :id => 'eem_dw_paymentType', :selected => ''} %> + <%= eems_text_field_tag :paymentFund, '(Fund name)', {:size => 25, :id => 'eem_dw_payment_fund', :class => 'marginLeft_14px', :style => 'display: none;'} %> +
+
+ +
+ + +
+ +<% end %> diff --git a/app/views/eems/new.html.erb b/app/views/eems/new.html.erb new file mode 100644 index 0000000..c7e248f --- /dev/null +++ b/app/views/eems/new.html.erb @@ -0,0 +1,40 @@ +<% + widget_suffix = WIDGET_SUFFIX || '' +%> + +
+ EEMs<%= widget_suffix %> Selection Widget + +
+ +
+ <%= link_to "Capture from this site", "#", :id => "eems-browser-widget-toggle" %> + <%= link_to "Upload from desktop", "#", :id => "eems-desktop-widget-toggle" %> +
+
+ +<%= render :partial => 'eems/_widgets/browser_widget' %> +<%= render :partial => 'eems/_widgets/desktop_widget' %> + + + + + + + + + diff --git a/app/views/eems/show.html.erb b/app/views/eems/show.html.erb new file mode 100644 index 0000000..c19f736 --- /dev/null +++ b/app/views/eems/show.html.erb @@ -0,0 +1,93 @@ + +
+

<%= print_eems_field('title') %>

+ <%= text_field_tag "title", print_eems_field('title'), {:size => 30, :class => "mainTitle", :style => "display: none", :id => 'input_title'} %> + +
+
Id :
<%= @eem.pid %>
+ +
PURL :
+ +
Found at this site :
+
+ <%= link_to print_eems_field('sourceUrl'), print_eems_field('sourceUrl'), {:id => 'link_sourceUrl', :target => '_blank'} %> +
+ +
Language :
+
<%= render :partial => 'eems/select_options', :locals => {:field => 'language', :selected => print_eems_field('language')} %>
+ +
Created by :
+
+
<%= get_creator_name() %>
+ <%= text_field_tag "creatorName", get_creator_name(), {:size => 30, :style => "display: none", :id => 'input_creatorName'} %> + <%= render :partial => 'eems/select_options', :locals => {:field => 'creatorType', :selected => get_creator_type()} %> +
+ +
Direct link to file :
+
+ <%= link_to print_url_decoded, print_parts_field('url'), {:id => 'link_url', :target => '_blank'} %> +
+ +
Local copy of file :
+
+ <%= link_to get_local_filename(), get_local_file_path(), {:id => 'link_local_file'} %> +
+ +
Citation/comments :
+
+
<%= escape_tags(print_eems_field('note')) %>
+ +
+ +
Copyright :
+
+ <%= render :partial => 'eems/select_options', :locals => {:field => 'copyrightStatusDetailPage', :id => 'copyrightStatus', :selected => print_eems_field('copyrightStatus')} %> +
+ +
Purchase :
+
+ <%= render :partial => 'eems/select_options', :locals => {:field => 'paymentType', :selected => print_eems_field('paymentType')} %> + <%= render :partial => 'eems/payment_fund' %> +
+
+ +
+

Action log

+
+
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000..54fce99 --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,51 @@ + + + + + + + + <%= h(@page_title || application_name) %> + + <%= render_js_includes %> + <%= render_stylesheet_includes %> + <%= javascript_tag "window._token = '#{form_authenticity_token}'" %> + <%= stylesheet_link_tag 'application', 'jquery.autocomplete' %> + <%= javascript_include_tag 'eems_common', 'eems_detail_actions', 'jquery.autocomplete', 'date.format' %> + + <% if params[:q].to_s.empty? and params[:f].to_s.empty? and params[:id].nil? %> + + <% else %> + + <% end %> + + <%= render :partial => 'eems/top_nav_links' %> +
+ +
+
+
+
+
+
+ +
+ +
+
+ <%= yield %> +
+
+ +
<%= sidebar_items.join('') %>
+ +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/app/views/layouts/eems.html.erb b/app/views/layouts/eems.html.erb new file mode 100644 index 0000000..ae7e5b4 --- /dev/null +++ b/app/views/layouts/eems.html.erb @@ -0,0 +1,17 @@ + + + + + + + <%= h(@page_title || application_name) %> + <%= javascript_tag "window._token = '#{form_authenticity_token}'" %> + <%= javascript_include_tag 'jquery-1.4.2.min', 'jquery.autocomplete', 'jquery.form', 'date.format', 'eems_common', 'browser-widget' %> + <%= stylesheet_link_tag 'site', 'jquery.autocomplete' %> + + + + <%= yield %> + + + \ No newline at end of file diff --git a/app/views/layouts/eems_show.html.erb b/app/views/layouts/eems_show.html.erb new file mode 100644 index 0000000..22a3455 --- /dev/null +++ b/app/views/layouts/eems_show.html.erb @@ -0,0 +1,30 @@ + + + + + + + <%= h(@page_title || application_name) %> + <%= javascript_tag "window._token = '#{form_authenticity_token}'" %> + <%= javascript_tag "window._pid = '#{@eem.pid}'" %> + <%= javascript_include_tag 'jquery-1.4.2.min', 'jquery.autocomplete', 'common', 'eems_detail' %> + <%= stylesheet_link_tag 'site', 'jquery.autocomplete', 'eems_detail' %> + + + + + +
+ <%= render :partial => 'eems/user_name_logout' %> +
+
+ « <%= link_to "Back to dashboard", "/view" %> +
+
+ <%= yield %> +
+
+
+ + + \ No newline at end of file diff --git a/app/views/layouts/login.html.erb b/app/views/layouts/login.html.erb new file mode 100644 index 0000000..b8d9108 --- /dev/null +++ b/app/views/layouts/login.html.erb @@ -0,0 +1,15 @@ + + + + + + + <%= h(@page_title || application_name) %> + <%= stylesheet_link_tag 'site' %> + + + + <%= yield %> + + + \ No newline at end of file diff --git a/app/views/login/new.html.erb b/app/views/login/new.html.erb new file mode 100644 index 0000000..e76616d --- /dev/null +++ b/app/views/login/new.html.erb @@ -0,0 +1,4 @@ +
+

You must login before using the tool


+ Please click to login +
diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000..84559a8 --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,124 @@ +# Don't change this file! +# Configure your app in config/environment.rb and config/environments/*.rb + +RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) + +module Rails + class << self + def boot! + unless booted? + preinitialize + pick_boot.run + end + end + + def booted? + defined? Rails::Initializer + end + + def pick_boot + (vendor_rails? ? VendorBoot : GemBoot).new + end + + def vendor_rails? + File.exist?("#{RAILS_ROOT}/vendor/rails") + end + + def preinitialize + load(preinitializer_path) if File.exist?(preinitializer_path) + end + + def preinitializer_path + "#{RAILS_ROOT}/config/preinitializer.rb" + end + end + + class Boot + def run + load_initializer + Rails::Initializer.run(:set_load_path) + end + end + + class VendorBoot < Boot + def load_initializer + require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" + Rails::Initializer.run(:install_gem_spec_stubs) + Rails::GemDependency.add_frozen_gem_path + end + end + + class GemBoot < Boot + def load_initializer + self.class.load_rubygems + load_rails_gem + require 'initializer' + end + + def load_rails_gem + if version = self.class.gem_version + gem 'rails', version + else + gem 'rails' + end + rescue Gem::LoadError => load_error + $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.) + exit 1 + end + + class << self + def rubygems_version + Gem::RubyGemsVersion rescue nil + end + + def gem_version + if defined? RAILS_GEM_VERSION + RAILS_GEM_VERSION + elsif ENV.include?('RAILS_GEM_VERSION') + ENV['RAILS_GEM_VERSION'] + else + parse_gem_version(read_environment_rb) + end + end + + def load_rubygems + require 'rubygems' + min_version = '1.3.1' + unless rubygems_version >= min_version + $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) + exit 1 + end + + rescue LoadError + $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) + exit 1 + end + + def parse_gem_version(text) + $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/ + end + + private + def read_environment_rb + File.read("#{RAILS_ROOT}/config/environment.rb") + end + end + end +end + +class Rails::Boot + def run + load_initializer + + Rails::Initializer.class_eval do + def load_gems + @bundler_loaded ||= Bundler.require :default, Rails.env + end + end + + Rails::Initializer.run(:set_load_path) + end +end + +# All that for this: +Rails.boot! diff --git a/config/cucumber.yml b/config/cucumber.yml new file mode 100644 index 0000000..96e7d2b --- /dev/null +++ b/config/cucumber.yml @@ -0,0 +1,2 @@ +default: --format pretty --color +wip: --tags @wip:3 --wip features diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 0000000..1c03183 --- /dev/null +++ b/config/database.yml @@ -0,0 +1,28 @@ +# SQLite version 3.x +# gem install sqlite3-ruby (not necessary on OS X Leopard) +development: + adapter: sqlite3 + database: db/development.sqlite3 + pool: 5 + timeout: 5000 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: &TEST + adapter: sqlite3 + database: db/test.sqlite3 + pool: 5 + timeout: 5000 + +production: + adapter: sqlite3 + database: db/production.sqlite3 + pool: 5 + timeout: 5000 + +cucumber: &CUCUMBER + <<: *TEST + +culerity: + <<: *CUCUMBER \ No newline at end of file diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000..0113ff4 --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,44 @@ +# Be sure to restart your server when you modify this file + +# Specifies gem version of Rails to use when vendor/rails is not present +RAILS_GEM_VERSION = '2.3.10' unless defined? RAILS_GEM_VERSION + +# Bootstrap the Rails environment, frameworks, and default configuration +require File.join(File.dirname(__FILE__), 'boot') +require File.join(File.dirname(__FILE__), '../vendor/plugins/blacklight/vendor/plugins/engines/boot') + +Rails::Initializer.run do |config| + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + + # Add additional load paths for your own custom dirs + # config.load_paths += %W( #{RAILS_ROOT}/extras ) + + # Specify gems that this application depends on and have them installed with rake gems:install + # config.gem "bj" + # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net" + # config.gem "sqlite3-ruby", :lib => "sqlite3" + # config.gem "aws-s3", :lib => "aws/s3" + + # Only load the plugins named here, in the order given (default is alphabetical). + # :all can be used as a placeholder for all plugins not explicitly named + # config.plugins = [ :exception_notification, :ssl_requirement, :all ] + config.plugin_paths += ["#{RAILS_ROOT}/vendor/plugins/blacklight/vendor/plugins"] + + # Skip frameworks you're not going to use. To use Rails without a database, + # you must remove the Active Record framework. + # config.frameworks -= [ :active_record, :active_resource, :action_mailer ] + + # Activate observers that should always be running + # config.active_record.observers = :cacher, :garbage_collector, :forum_observer + + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. + # Run "rake -D time" for a list of tasks for finding time zone names. + config.time_zone = 'UTC' + + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')] + # config.i18n.default_locale = :de + +end diff --git a/config/environments/cucumber.rb b/config/environments/cucumber.rb new file mode 100644 index 0000000..9fe9008 --- /dev/null +++ b/config/environments/cucumber.rb @@ -0,0 +1,33 @@ +# Edit at your own peril - it's recommended to regenerate this file +# in the future when you upgrade to a newer version of Cucumber. + +# IMPORTANT: Setting config.cache_classes to false is known to +# break Cucumber's use_transactional_fixtures method. +# For more information see https://rspec.lighthouseapp.com/projects/16211/tickets/165 +config.cache_classes = true + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false + +# Disable request forgery protection in test environment +config.action_controller.allow_forgery_protection = false + +# Tell Action Mailer not to deliver emails to the real world. +# The :test delivery method accumulates sent emails in the +# ActionMailer::Base.deliveries array. +config.action_mailer.delivery_method = :test + +config.gem 'cucumber-rails', :lib => false, :version => '>=0.3.0' unless File.directory?(File.join(Rails.root, 'vendor/plugins/cucumber-rails')) +config.gem 'database_cleaner', :lib => false, :version => '>=0.5.0' unless File.directory?(File.join(Rails.root, 'vendor/plugins/database_cleaner')) +config.gem 'webrat', :lib => false, :version => '>=0.7.0' unless File.directory?(File.join(Rails.root, 'vendor/plugins/webrat')) + +FEDORA_URL = 'http://fedoraAdmin:fedoraAdmin@127.0.0.1:8080/fedora' + +module Sulair + WORKSPACE_URL = "https://eems-unit.stanford.edu/workspace" +end + diff --git a/config/environments/culerity.rb b/config/environments/culerity.rb new file mode 100644 index 0000000..4275173 --- /dev/null +++ b/config/environments/culerity.rb @@ -0,0 +1,27 @@ +config.cache_classes = true + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false +config.action_view.cache_template_loading = false + +# Disable request forgery protection in test environment +config.action_controller.allow_forgery_protection = false + +# Tell Action Mailer not to deliver emails to the real world. +# The :test delivery method accumulates sent emails in the +# ActionMailer::Base.deliveries array. +config.action_mailer.delivery_method = :persistent + +config.after_initialize do + require 'culerity/persistent_delivery' +end + +FEDORA_URL = 'http://fedoraAdmin:fedoraAdmin@127.0.0.1:8080/fedora' + +module Sulair + WORKSPACE_URL = "https://eems-unit.stanford.edu/workspace" +end \ No newline at end of file diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 0000000..ed767bb --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,26 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# In the development environment your application's code is reloaded on +# every request. This slows down response time but is perfect for development +# since you don't have to restart the webserver when you make code changes. +config.cache_classes = false + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_view.debug_rjs = true +config.action_controller.perform_caching = false + +# Don't care if the mailer can't send +config.action_mailer.raise_delivery_errors = false + +FEDORA_URL = 'http://fedoraAdmin:fedoraAdmin@127.0.0.1:8080/fedora' +WIDGET_NAME = 'Dev EEMs Widget' +WIDGET_SUFFIX = '-dev' + +module Sulair + WORKSPACE_URL = 'http://localhost:8080/workspace' +end + diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 0000000..1418a90 --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,42 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The production environment is meant for finished, "live" apps. +# Code is not reloaded between requests +config.cache_classes = true + +# Full error reports are disabled and caching is turned on +config.action_controller.consider_all_requests_local = false +config.action_controller.perform_caching = true +config.action_view.cache_template_loading = true + +# See everything in the log (default is :info) +# config.log_level = :debug + +# Use a different logger for distributed setups +# config.logger = SyslogLogger.new + +# Use a different cache store in production +# config.cache_store = :mem_cache_store + +# Enable serving of images, stylesheets, and javascripts from an asset server +# config.action_controller.asset_host = "http://assets.example.com" + +# Disable delivery errors, bad email addresses will be ignored +# config.action_mailer.raise_delivery_errors = false + +# Enable threaded mode +# config.threadsafe! + +FEDORA_URL = 'https://fedoraAdmin:fedoraAdmin@fedora.yourinstitute.edu/fedora' + +WIDGET_NAME = 'EEMs Widget' +WIDGET_SUFFIX = '' + +module Sulair + WORKSPACE_URL = 'https://eems-server.yourinstitute.edu/workspace' +end + +module Dor + CREATE_WORKFLOW = true + WF_URI = 'http://lyberservices-prod.stanford.edu/workflow' +end diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 0000000..274a7ef --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,41 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! +config.cache_classes = true + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false +config.action_view.cache_template_loading = true + +# Disable request forgery protection in test environment +config.action_controller.allow_forgery_protection = false + +# Tell Action Mailer not to deliver emails to the real world. +# The :test delivery method accumulates sent emails in the +# ActionMailer::Base.deliveries array. +config.action_mailer.delivery_method = :test + +# Use SQL instead of Active Record's schema dumper when creating the test database. +# This is necessary if your schema can't be completely dumped by the schema dumper, +# like if you have constraints or database-specific column types +# config.active_record.schema_format = :sql + +config.gem 'rspec-rails', :version => '>= 1.3.2', :lib => false unless File.directory?(File.join(Rails.root, 'vendor/plugins/rspec-rails')) + +FEDORA_URL = 'http://fedoraAdmin:fedoraAdmin@127.0.0.1:8080/fedora' +WIDGET_NAME = 'Dev EEMs Widget' + +module Sulair + WORKSPACE_URL = "https://eems-unit.stanford.edu/workspace" +end + +module Dor + MINT_SURI_IDS = false +end \ No newline at end of file diff --git a/config/initializers/accession_workflow_config.rb b/config/initializers/accession_workflow_config.rb new file mode 100644 index 0000000..be1d600 --- /dev/null +++ b/config/initializers/accession_workflow_config.rb @@ -0,0 +1,17 @@ +ACCESSION_WF_XML = <<-EOXML + + + + + + + + + + + + + + + +EOXML diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb new file mode 100644 index 0000000..c2169ed --- /dev/null +++ b/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying do debug a problem that might steem from framework code. +# Rails.backtrace_cleaner.remove_silencers! \ No newline at end of file diff --git a/config/initializers/blacklight_config.rb b/config/initializers/blacklight_config.rb new file mode 100644 index 0000000..793a301 --- /dev/null +++ b/config/initializers/blacklight_config.rb @@ -0,0 +1,177 @@ +# You can configure Blacklight from here. +# +# Blacklight.configure(:environment) do |config| end +# +# :shared (or leave it blank) is used by all environments. +# You can override a shared key by using that key in a particular +# environment's configuration. +# +# If you have no configuration beyond :shared for an environment, you +# do not need to call configure() for that envirnoment. +# +# For specific environments: +# +# Blacklight.configure(:test) {} +# Blacklight.configure(:development) {} +# Blacklight.configure(:production) {} +# + +Blacklight.configure(:shared) do |config| + + # FIXME: is this duplicated below? + SolrDocument.marc_source_field = :marc_display + SolrDocument.marc_format_type = :marc21 + + # default params for the SolrDocument.search method + SolrDocument.default_params[:search] = { + :qt=>:search, + :per_page => 10, + :facets => {:fields=> + ["format", + "language_facet", + "lc_1letter_facet", + "lc_alpha_facet", + "lc_b4cutter_facet", + "language_facet", + "pub_date", + "subject_era_facet", + "subject_geo_facet", + "subject_topic_facet"] + } + } + + # default params for the SolrDocument.find_by_id method + SolrDocument.default_params[:find_by_id] = {:qt => :document} + + + ############################## + + + config[:default_qt] = "search_eems" + + + # solr field values given special treatment in the show (single result) view + config[:show] = { + :html_title => "title_t", + :heading => "title_t", + :display_type => "format" + } + + # solr fld values given special treatment in the index (search results) view + config[:index] = { + :show_link => "title_t", + :num_per_page => 10, + :record_display_type => "format" + } + + # solr fields that will be treated as facets by the blacklight application + # The ordering of the field names is the order of the display + config[:facet] = { + :field_names => [ + "selectorName_facet", + "status_facet", + "paymentType_facet", + "paymentFund_facet", + "copyrightStatus_facet", + "language_facet", + "creatorOrg_facet", + "creatorPerson_facet" + ], + :labels => { + "selectorName_facet" => "Selector", + "status_facet" => "Status", + "paymentType_facet" => "Purchase", + "paymentFund_facet" => "Fund", + "copyrightStatus_facet" => "Copyright", + "language_facet" => "Language", + "creatorOrg_facet" => "Creator (organization)", + "creatorPerson_facet" => "Creator (person)" + } + } + + # solr fields to be displayed in the index (search results) view + # The ordering of the field names is the order of the display + config[:index_fields] = { + :field_names => [ + "title_t", + "system_create_dt", + "requestDatetime_t", + "selectorName_t", + "copyrightStatus_t", + "status_t" + ], + :labels => { + "title_t" => "Title", + "system_create_dt" => "Created", + "requestDatetime_t" => "Submitted", + "selectorName_t" => "Selector", + "copyrightStatus_t" => "Copyright", + "status_t" => "Status" + } + } + + # solr fields to be displayed in the show (single result) view + # The ordering of the field names is the order of the display + config[:show_fields] = { + :field_names => [ + "title_t", + "sourceUrl_t", + "language_t", + "creatorPerson_t", + "creatorOrg_t", + "url_t", + "note_t", + "copyrightStatus_t", + "copyrightStatusDate_t", + "paymentType_t", + "paymentFund_t", + "selectorSunetid_t", + "id" + ], + :labels => { + "title_t" => "Title", + "sourceUrl_t" => "Found at this site", + "language_t" => "Language", + "creatorPerson_t" => "Creator", + "creatorOrg_t" => "Creator", + "url_t" => "Direct link to PDF", + "note_t" => "Citation/Comments", + "copyrightStatus_t" => "Copyright", + "copyrightStatusDate_t" => "Copyright date", + "paymentType_t" => "Purchase", + "paymentFund_t" => "Payment fund", + "selectorSunetid_t" => "Selector SUNet id", + "id" => "Id" + } + } + +# FIXME: is this now redundant with above? + # type of raw data in index. Currently marcxml and marc21 are supported. + config[:raw_storage_type] = "marc21" + # name of solr field containing raw data + config[:raw_storage_field] = "marc_display" + + # "fielded" search configuration. Used by pulldown among other places. + # For supported keys in hash, see rdoc for Blacklight::SearchFields + config[:search_fields] ||= [] + config[:search_fields] << {:display_label => 'All Fields', :qt => 'search_eems'} + config[:search_fields] << {:display_label => 'Title', :qt => 'title_search'} + config[:search_fields] << {:display_label =>'Author', :qt => 'author_search'} + config[:search_fields] << {:display_label => 'Subject', :qt=> 'subject_search'} + + # "sort results by" select (pulldown) + # label in pulldown is followed by the name of the SOLR field to sort by and + # whether the sort is ascending or descending (it must be asc or desc + # except in the relevancy case). + # label is key, solr field is value + config[:sort_fields] ||= [] + config[:sort_fields] << ['title', 'title_sort asc, requestDatetime_sort desc'] + config[:sort_fields] << ['date requested', 'requestDatetime_sort desc, title_sort asc'] + config[:sort_fields] << ['created', 'system_create_dt_sort asc, title_sort asc'] + config[:sort_fields] << ['status', 'status_sort asc, title_sort asc'] + + # If there are more than this many search results, no spelling ("did you + # mean") suggestion is offered. + config[:spell_max] = 5 +end + diff --git a/config/initializers/delayed_job_config.rb b/config/initializers/delayed_job_config.rb new file mode 100644 index 0000000..dc6ca55 --- /dev/null +++ b/config/initializers/delayed_job_config.rb @@ -0,0 +1,4 @@ +require 'delayed_job' + +Delayed::Worker.max_attempts = 4 +Delayed::Worker.max_run_time = 5.minutes diff --git a/config/initializers/fedora_repository.rb b/config/initializers/fedora_repository.rb new file mode 100644 index 0000000..d1a9fb3 --- /dev/null +++ b/config/initializers/fedora_repository.rb @@ -0,0 +1,3 @@ +require "active_fedora" + +SOLR_URL = 'http://127.0.0.1:8080/solr/' diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb new file mode 100644 index 0000000..d531b8b --- /dev/null +++ b/config/initializers/inflections.rb @@ -0,0 +1,10 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format +# (all these examples are active by default): +# ActiveSupport::Inflector.inflections do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb new file mode 100644 index 0000000..72aca7e --- /dev/null +++ b/config/initializers/mime_types.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf +# Mime::Type.register_alias "text/html", :iphone diff --git a/config/initializers/new_rails_defaults.rb b/config/initializers/new_rails_defaults.rb new file mode 100644 index 0000000..8ec3186 --- /dev/null +++ b/config/initializers/new_rails_defaults.rb @@ -0,0 +1,19 @@ +# Be sure to restart your server when you modify this file. + +# These settings change the behavior of Rails 2 apps and will be defaults +# for Rails 3. You can remove this initializer when Rails 3 is released. + +if defined?(ActiveRecord) + # Include Active Record class name as root for JSON serialized output. + ActiveRecord::Base.include_root_in_json = true + + # Store the full class name (including module namespace) in STI type column. + ActiveRecord::Base.store_full_sti_class = true +end + +# Use ISO 8601 format for JSON serialized times and dates. +ActiveSupport.use_standard_json_time_format = true + +# Don't escape HTML entities in JSON, leave that for the #json_escape helper. +# if you're including raw json in an HTML page. +ActiveSupport.escape_html_entities_in_json = false \ No newline at end of file diff --git a/config/initializers/overrides.rb b/config/initializers/overrides.rb new file mode 100644 index 0000000..b4f35ac --- /dev/null +++ b/config/initializers/overrides.rb @@ -0,0 +1,71 @@ + + +require 'fedora/base' +require 'fedora/repository' +require 'net/http' +require 'net/https' + +class Fedora::Datastream < Fedora::BaseObject + def versionable + @attributes[:versionable] + end + + def versionable=(new_val) + @attributes[:versionable]=new_val + end + +end + +module ActiveFedora + class Base + def delete + Fedora::Repository.instance.delete(@inner_object) + SolrService.instance.conn.delete(self.pid) if ENABLE_SOLR_UPDATES + end + end + + class MetadataDatastream < Datastream + + #constructor, calls up to ActiveFedora::Datastream's constructor + def initialize(attrs=nil) + super + @fields={} + self.versionable = false + end + + + #this is to order the xml into an alphabetical order. + def to_xml(xml = Nokogiri::XML::Document.parse("")) #:nodoc: + if xml.instance_of?(Nokogiri::XML::Document) + xml_insertion_point = xml.root + else + xml_insertion_point = xml.sort + end + builder = Nokogiri::XML::Builder.with(xml_insertion_point) do |xml| + f = fields.sort {|a,b| a[0].to_s <=> b[0].to_s} + f.each do |value| + field = value[0] + field_info = value[1] + element_attrs = field_info[:element_attrs].nil? ? {} : field_info[:element_attrs] + field_info[:values].each do |val| + builder_arg = "xml.#{field}(val, element_attrs)" + puts builder_arg.inspect + eval(builder_arg) + end #do |val| + end #each do |value| + end #builder + return builder.to_xml + end #def to_xml + + end #class +end #module + +#Patch after upgrade to Rails 2.3.2 +#See http://groups.google.com/group/rack-devel/browse_thread/thread/4bce411e5a389856 +module Mime + class Type + def split(*args) + to_s.split(*args) + end + end +end diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb new file mode 100644 index 0000000..22a6743 --- /dev/null +++ b/config/initializers/session_store.rb @@ -0,0 +1,15 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key for verifying cookie session data integrity. +# If you change this key, all old sessions will become invalid! +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +ActionController::Base.session = { + :key => '_eems_session', + :secret => '51utj3pgrl24lawzhgzspd6301ndf47qvnqcvafksqnsrykmensbdt3pb3lqsjln0n8ubgtnt81aqkut15m30w650d3ig3xfzyxf' +} + +# Use the database for sessions instead of the cookie-based default, +# which shouldn't be used to store highly confidential information +# (create the session table with "rake db:sessions:create") +# ActionController::Base.session_store = :active_record_store diff --git a/config/initializers/sulair_config.rb b/config/initializers/sulair_config.rb new file mode 100644 index 0000000..62a2e5e --- /dev/null +++ b/config/initializers/sulair_config.rb @@ -0,0 +1,15 @@ +require 'dor/download_job' + +module Sulair + WORKSPACE_DIR = File.join(RAILS_ROOT, 'workspace') + AUTHORIZED_EEMS_PRIVGROUP = 'some:group' +end + +unless(File.exists?(Sulair::WORKSPACE_DIR)) + FileUtils.mkdir(Sulair::WORKSPACE_DIR) + FileUtils.ln_s(Sulair::WORKSPACE_DIR, File.join(RAILS_ROOT, 'public', 'workspace')) +end + +unless(File.exists?(File.join(RAILS_ROOT, 'tmp'))) + FileUtils.mkdir(File.join(RAILS_ROOT, 'tmp')) +end \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 0000000..f265c06 --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,5 @@ +# Sample localization file for English. Add more files in this directory for other locales. +# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. + +en: + hello: "Hello world" \ No newline at end of file diff --git a/config/preinitializer.rb b/config/preinitializer.rb new file mode 100644 index 0000000..44c2ab6 --- /dev/null +++ b/config/preinitializer.rb @@ -0,0 +1,22 @@ +# Use bundler for depencency management + +begin + require "rubygems" + require "bundler" +rescue LoadError + raise "Could not load the bundler gem. Install it with `gem install bundler`." +end + +if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.24") + raise RuntimeError, "Your bundler version is too old for Rails 2.3." + + "Run `gem install bundler` to upgrade." +end + +begin + # Set up load paths for all bundled gems + ENV["BUNDLE_GEMFILE"] = File.expand_path("../../Gemfile", __FILE__) + Bundler.setup +rescue Bundler::GemNotFound + raise RuntimeError, "Bundler couldn't find some gems." + + "Did you run `bundle install`?" +end diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..e71d78e --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,68 @@ +ActionController::Routing::Routes.draw do |map| + map.login "login", :controller => 'login', :action => 'new' + + # Copied from vendor/plugins/blacklight/lib/blacklight/routes.rb and modified for /view + map.resources(:catalog, :as => 'view', + :only => [:index, :show, :update], + # /catalog/:id/image <- for ajax cover requests + # /catalog/:id/status + # /catalog/:id/availability + :member=>{:image=>:get, :status=>:get, :availability=>:get, :citation=>:get, :send_email_record=>:post, :email=>:get, :sms=>:get}, + # /catalog/map + :collection => {:map => :get, :opensearch=>:get} + ) + + # The priority is based upon order of creation: first created -> highest priority. + Blacklight::Routes.build map + + # Sample of regular route: + # map.connect 'products/:id', :controller => 'catalog', :action => 'view' + # Keep in mind you can assign values other than :controller and :action + + # Sample of named route: + # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase' + # This route can be invoked with purchase_url(:id => product.id) + + # Sample resource route (maps HTTP verbs to controller actions automatically): + # map.resources :products + + # Sample resource route with options: + # map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get } + + # Sample resource route with sub-resources: + # map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller + + # Sample resource route with more complex sub-resources + # map.resources :products do |products| + # products.resources :comments + # products.resources :sales, :collection => { :recent => :get } + # end + + # Sample resource route within a namespace: + # map.namespace :admin do |admin| + # # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb) + # admin.resources :products + # end + + # You can have the root of your site routed with map.root -- just remember to delete public/index.html. + # map.root :controller => "welcome" + + # See how all your routes lay out with "rake routes" + + # Install the default routes as the lowest priority. + # Note: These default routes make all actions in every controller accessible via GET requests. You should + # consider removing the them or commenting them out if you're using named routes and resources. + map.connect 'eems/:eems_id/log', :controller => 'log', :action => 'create' + map.connect 'eems/:eems_id/submit_to_tech_services', :controller => 'submit_to_tech_services', :action => 'create' + map.connect 'eems/no_pdf', :controller => 'eems', :action => 'no_pdf' + + map.resources :eems do |s| + s.resources :permission_files + s.resources :parts + end + map.resources :content_files + + + map.connect ':controller/:action/:id' + map.connect ':controller/:action/:id.:format' +end diff --git a/config/setup_load_paths.rb b/config/setup_load_paths.rb new file mode 100644 index 0000000..a42f0cd --- /dev/null +++ b/config/setup_load_paths.rb @@ -0,0 +1,21 @@ +# Enables app to use rvm gemset defined in .rvmrc file + +if ENV['MY_RUBY_HOME'] && ENV['MY_RUBY_HOME'].include?('rvm') + begin + rvm_path = File.dirname(File.dirname(ENV['MY_RUBY_HOME'])) + rvm_lib_path = File.join(rvm_path, 'lib') + $LOAD_PATH.unshift rvm_lib_path + require 'rvm' + RVM.use_from_path! File.dirname(File.dirname(__FILE__)) + rescue LoadError + # RVM is unavailable at this point. + raise "RVM ruby lib is currently unavailable." + end +end + +# Select the correct item for which you use below. +# If you're not using bundler, remove it completely. + +# If we're using a Bundler 1.0 beta +ENV['BUNDLE_GEMFILE'] = File.expand_path('../Gemfile', File.dirname(__FILE__)) +require 'bundler/setup' diff --git a/config/solr.yml b/config/solr.yml new file mode 100644 index 0000000..b700172 --- /dev/null +++ b/config/solr.yml @@ -0,0 +1,10 @@ +development: + url: http://127.0.0.1:8080/solr +production: + url: http://127.0.0.1:8080/solr +test: + url: http://127.0.0.1:8080/solr +cucumber: + url: http://127.0.0.1:8888/solr +culerity: + url: http://127.0.0.1:8888/solr diff --git a/db/migrate/20090127164730_create_users.rb b/db/migrate/20090127164730_create_users.rb new file mode 100644 index 0000000..e3410f6 --- /dev/null +++ b/db/migrate/20090127164730_create_users.rb @@ -0,0 +1,16 @@ +class CreateUsers < ActiveRecord::Migration + def self.up + create_table :users do |t| + t.string :login, :unique=>true, :null=>false + t.string :email, :unique=>true + t.string :crypted_password + t.text :last_search_url + t.datetime :last_login + t.timestamps + end + end + + def self.down + drop_table :users + end +end \ No newline at end of file diff --git a/db/migrate/20090127164740_create_bookmarks.rb b/db/migrate/20090127164740_create_bookmarks.rb new file mode 100644 index 0000000..46aab33 --- /dev/null +++ b/db/migrate/20090127164740_create_bookmarks.rb @@ -0,0 +1,17 @@ +class CreateBookmarks < ActiveRecord::Migration + def self.up + create_table :bookmarks do |t| + t.integer :user_id, :null=>false + t.text :url + t.string :document_id + t.string :title + t.text :notes + t.timestamps + end + end + + def self.down + drop_table :bookmarks + end + +end \ No newline at end of file diff --git a/db/migrate/20090127164750_acts_as_taggable_migration.rb b/db/migrate/20090127164750_acts_as_taggable_migration.rb new file mode 100644 index 0000000..3be83c4 --- /dev/null +++ b/db/migrate/20090127164750_acts_as_taggable_migration.rb @@ -0,0 +1,26 @@ +class ActsAsTaggableMigration < ActiveRecord::Migration + def self.up + create_table :tags do |t| + t.column :name, :string + end + + create_table :taggings do |t| + t.column :tag_id, :integer + t.column :taggable_id, :integer + + # You should make sure that the column created is + # long enough to store the required class names. + t.column :taggable_type, :string + + t.column :created_at, :datetime + end + + add_index :taggings, :tag_id + add_index :taggings, [:taggable_id, :taggable_type] + end + + def self.down + drop_table :taggings + drop_table :tags + end +end \ No newline at end of file diff --git a/db/migrate/20090428182620_create_searches.rb b/db/migrate/20090428182620_create_searches.rb new file mode 100644 index 0000000..07629cb --- /dev/null +++ b/db/migrate/20090428182620_create_searches.rb @@ -0,0 +1,15 @@ +class CreateSearches < ActiveRecord::Migration + def self.up + create_table :searches do |t| + t.text :query_params + t.integer :user_id + + t.timestamps + end + add_index :searches, :user_id + end + + def self.down + drop_table :searches + end +end diff --git a/db/migrate/20090529161304_add_authlogic_fields_to_users.rb b/db/migrate/20090529161304_add_authlogic_fields_to_users.rb new file mode 100644 index 0000000..bfe4ef0 --- /dev/null +++ b/db/migrate/20090529161304_add_authlogic_fields_to_users.rb @@ -0,0 +1,15 @@ +class AddAuthlogicFieldsToUsers < ActiveRecord::Migration + def self.up + add_column :users, :password_salt, :string + add_column :users, :persistence_token, :string + add_column :users, :current_login_at, :datetime + rename_column :users, :last_login, :last_login_at + end + + def self.down + remove_column :users, :current_login_at + rename_column :users, :last_login_at, :last_login + remove_column :users, :persistence_token + remove_column :users, :password_salt + end +end diff --git a/db/migrate/20100225054037_create_content_files.rb b/db/migrate/20100225054037_create_content_files.rb new file mode 100644 index 0000000..10264e7 --- /dev/null +++ b/db/migrate/20100225054037_create_content_files.rb @@ -0,0 +1,15 @@ +class CreateContentFiles < ActiveRecord::Migration + def self.up + create_table :content_files do |t| + t.string :url + t.integer :percent_done + t.string :path + + t.timestamps + end + end + + def self.down + drop_table :content_files + end +end diff --git a/db/migrate/20100225055824_create_delayed_jobs.rb b/db/migrate/20100225055824_create_delayed_jobs.rb new file mode 100644 index 0000000..d6414e3 --- /dev/null +++ b/db/migrate/20100225055824_create_delayed_jobs.rb @@ -0,0 +1,20 @@ +class CreateDelayedJobs < ActiveRecord::Migration + def self.up + create_table :delayed_jobs, :force => true do |table| + table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue + table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually. + table.text :handler # YAML-encoded string of the object that will do work + table.text :last_error # reason for last failure (See Note below) + table.datetime :run_at # When to run. Could be Time.now for immediately, or sometime in the future. + table.datetime :locked_at # Set when a client is working on this object + table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead) + table.string :locked_by # Who is working on this object (if locked) + table.timestamps + end + + end + + def self.down + drop_table :delayed_jobs + end +end \ No newline at end of file diff --git a/db/migrate/20100227011147_add_filepath_to_content_files.rb b/db/migrate/20100227011147_add_filepath_to_content_files.rb new file mode 100644 index 0000000..59c0e25 --- /dev/null +++ b/db/migrate/20100227011147_add_filepath_to_content_files.rb @@ -0,0 +1,9 @@ +class AddFilepathToContentFiles < ActiveRecord::Migration + def self.up + add_column :content_files, :filepath, :string + end + + def self.down + remove_column :content_files, :filepath + end +end diff --git a/db/migrate/20100227030213_add_part_pid_to_content_files.rb b/db/migrate/20100227030213_add_part_pid_to_content_files.rb new file mode 100644 index 0000000..7b36bd7 --- /dev/null +++ b/db/migrate/20100227030213_add_part_pid_to_content_files.rb @@ -0,0 +1,9 @@ +class AddPartPidToContentFiles < ActiveRecord::Migration + def self.up + add_column :content_files, :part_pid, :string + end + + def self.down + remove_column :content_files, :part_pid + end +end diff --git a/db/migrate/20100702215015_add_attempts_to_content_files.rb b/db/migrate/20100702215015_add_attempts_to_content_files.rb new file mode 100644 index 0000000..b4d08da --- /dev/null +++ b/db/migrate/20100702215015_add_attempts_to_content_files.rb @@ -0,0 +1,9 @@ +class AddAttemptsToContentFiles < ActiveRecord::Migration + def self.up + add_column :content_files, :attempts, :integer + end + + def self.down + remove_column :content_files, :attempts + end +end diff --git a/db/migrate/20101029180439_add_user_display_name_to_content_files.rb b/db/migrate/20101029180439_add_user_display_name_to_content_files.rb new file mode 100644 index 0000000..f089756 --- /dev/null +++ b/db/migrate/20101029180439_add_user_display_name_to_content_files.rb @@ -0,0 +1,9 @@ +class AddUserDisplayNameToContentFiles < ActiveRecord::Migration + def self.up + add_column :content_files, :user_display_name, :string + end + + def self.down + remove_column :content_files, :user_display_name + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..50b2ad5 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,85 @@ +# This file is auto-generated from the current state of the database. Instead of editing this file, +# please use the migrations feature of Active Record to incrementally modify your database, and +# then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your database schema. If you need +# to create the application database on another system, you should be using db:schema:load, not running +# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended to check this file into your version control system. + +ActiveRecord::Schema.define(:version => 20101029180439) do + + create_table "bookmarks", :force => true do |t| + t.integer "user_id", :null => false + t.text "url" + t.string "document_id" + t.string "title" + t.text "notes" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "content_files", :force => true do |t| + t.string "url" + t.integer "percent_done" + t.string "path" + t.datetime "created_at" + t.datetime "updated_at" + t.string "filepath" + t.string "part_pid" + t.integer "attempts" + t.string "user_display_name" + end + + create_table "delayed_jobs", :force => true do |t| + t.integer "priority", :default => 0 + t.integer "attempts", :default => 0 + t.text "handler" + t.text "last_error" + t.datetime "run_at" + t.datetime "locked_at" + t.datetime "failed_at" + t.string "locked_by" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "searches", :force => true do |t| + t.text "query_params" + t.integer "user_id" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "searches", ["user_id"], :name => "index_searches_on_user_id" + + create_table "taggings", :force => true do |t| + t.integer "tag_id" + t.integer "taggable_id" + t.string "taggable_type" + t.datetime "created_at" + end + + add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id" + add_index "taggings", ["taggable_id", "taggable_type"], :name => "index_taggings_on_taggable_id_and_taggable_type" + + create_table "tags", :force => true do |t| + t.string "name" + end + + create_table "users", :force => true do |t| + t.string "login", :null => false + t.string "email" + t.string "crypted_password" + t.text "last_search_url" + t.datetime "last_login_at" + t.datetime "created_at" + t.datetime "updated_at" + t.string "password_salt" + t.string "persistence_token" + t.datetime "current_login_at" + end + +end diff --git a/doc/README_FOR_APP b/doc/README_FOR_APP new file mode 100644 index 0000000..fe41f5c --- /dev/null +++ b/doc/README_FOR_APP @@ -0,0 +1,2 @@ +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/features/eems_widget.feature b/features/eems_widget.feature new file mode 100644 index 0000000..cfd1215 --- /dev/null +++ b/features/eems_widget.feature @@ -0,0 +1,34 @@ +Feature: New EEMs + In order to create a new EEM + As a selector + I want to select an item to collect + + Scenario: Widget Exists + Given I am on the widget + Then I should see a "label" with text "Found on this site:" + Then I should see a text input field for "eem_sourceUrl" + Then I should see a "dt" with text "Language:" + Then I should see a select box for "eem_language" + Then I should see a "label" with text "Title of work:" + Then I should see a text input field for "eem_title" + Then I should see a "label" with text "Created by:" + Then I should see a text input field for "eem_creatorName" + Then I should see a select box for "eem_creatorType" + Then I should see a "label" with text "Direct link to PDF:" + Then I should see a text input field for "contentUrl" + Then I should see a text input field for "eem_note" + Then I should see a "dt" with text "Copyright:" + Then I should see a select box for "eem_copyrightStatus" + Then I should see a "dt" with text "Purchase:" + Then I should see a select box for "eem_paymentType" + Then I should see a text input field for "eem_payment_fund" + + Scenario: Upload Confirmation + Given I am on the widget + When I fill in "https://dlib.stanford.edu:6521/text/temp/SampleEEMs" for "eem_sourceUrl" + And I type in "Proceedings of the Sardine Symposium 2000" for "eem_title" and fire event "onchange" + And I fill in "Cucumber, Tester" for "eem_creatorName" + And I type in "https://dlib.stanford.edu:6521/text/temp/SampleEEMs/pdf/sardine1.pdf" for "contentUrl" and fire event "onhover" + And I wait for "Save to dashboard" button to be enabled + Then "Save to dashboard" button should be enabled + diff --git a/features/step_definitions/culerity_steps.rb b/features/step_definitions/culerity_steps.rb new file mode 100644 index 0000000..7483cac --- /dev/null +++ b/features/step_definitions/culerity_steps.rb @@ -0,0 +1,183 @@ +require 'culerity' + +Before do + $rails_server_pid ||= Culerity::run_rails(:environment => 'culerity', :port => 3001) + $server ||= Culerity::run_server + unless $browser + $browser = Culerity::RemoteBrowserProxy.new $server, { + :browser => :firefox3, + :javascript_enabled => true, + :javascript_exceptions => true, + :resynchronize => true, + :status_code_exceptions => true + } + $browser.log_level = :all + end + @host = 'http://localhost:3001' +end + +After do + $server.clear_proxies + $browser.clear_cookies +end + +at_exit do + $browser.exit if $browser + $server.close if $server + Process.kill(6, $rails_server_pid) if $rails_server_pid + exit(0) +end + +Given /^(?:|I )am on (.+)$/ do |page_name| + $browser.goto @host + path_to(page_name) +end + +When /I follow "([^\"]*)"/ do |link| + _link = [ + [:text, /^#{Regexp.escape(link)}$/ ], + [:id, link], + [:title, link], + [:text, /#{Regexp.escape(link)}/ ], + ].map{|args| $browser.link(*args)}.find{|__link| __link.exist?} + raise "link \"#{link}\" not found" unless _link + _link.click + assert_successful_response +end + +When /I press "([^\"]*)"/ do |button| + $browser.button(:text, button).click + assert_successful_response +end + +When /I fill in "([^\"]*)" with "([^\"]*)"/ do |field, value| + find_by_label_or_id(:text_field, field).set(value) +end + +When /I fill in "([^\"]*)" for "([^\"]*)"/ do |value, field| + find_by_label_or_id(:text_field, field).set(value) +end + +When /I check "([^\"]*)"/ do |field| + find_by_label_or_id(:check_box, field).set(true) +end + +When /^I uncheck "([^\"]*)"$/ do |field| + find_by_label_or_id(:check_box, field).set(false) +end + +When /I select "([^\"]*)" from "([^\"]*)"/ do |value, field| + find_by_label_or_id(:select_list, field).select value +end + +When /I choose "([^\"]*)"/ do |field| + find_by_label_or_id(:radio, field).set(true) +end + +When /I go to (.+)/ do |path| + $browser.goto @host + path_to(path) + assert_successful_response +end + +When /^I wait for the AJAX call to finish$/ do + $browser.wait_while do + begin + count = $browser.execute_script("window.running_ajax_calls").to_i + count.to_i > 0 + rescue => e + if e.message.include?('HtmlunitCorejsJavascript::Undefined') + raise "For 'I wait for the AJAX call to finish' to work please include culerity.js after including jQuery. If you don't use jQuery please rewrite culerity.js accordingly." + else + raise(e) + end + end + end +end + +Then /^(?:|I )should be on (.+)$/ do |page_name| + current_path = URI.parse($browser.url) + expected_path = URI.parse(path_to(page_name)) + + # If our expected path doesn't specify a query-string, ignore any query string + # in the current path + current_path, expected_path = if expected_path.query.nil? + [ current_path.path, expected_path.path ] + else + [ current_path.select(:path, :query).compact.join('?'), path_to(page_name) ] + end + + if defined?(Spec::Rails::Matchers) + current_path.should == path_to(page_name) + else + assert_equal path_to(page_name), current_path + end +end + +Then /^the "([^\"]*)" field should contain "([^\"]*)"$/ do |field, value| + f = find_by_label_or_id(:text_field, field) + if defined?(Spec::Rails::Matchers) + f.text.should =~ /#{Regexp::escape(value)}/ + else + assert_match(/#{Regexp::escape(value)}/, f.text) + end +end + +Then /^the "([^\"]*)" field should not contain "([^\"]*)"$/ do |field, value| + f = find_by_label_or_id(:text_field, field) + if defined?(Spec::Rails::Matchers) + f.text.should_not =~ /#{Regexp::escape(value)}/ + else + assert_no_match(/#{Regexp::escape(value)}/, f.text) + end +end + +Then /^the "([^\"]*)" checkbox should be checked$/ do |label| + f = find_by_label_or_id(:check_box, label) + if defined?(Spec::Rails::Matchers) + f.should be_checked + else + assert f.checked? + end +end + +Then /^the "([^\"]*)" checkbox should not be checked$/ do |label| + f = find_by_label_or_id(:check_box, label) + if defined?(Spec::Rails::Matchers) + f.should_not be_checked + else + assert !f.checked? + end +end + +Then /I should see "([^\"]*)"/ do |text| + # if we simply check for the browser.html content we don't find content that has been added dynamically, e.g. after an ajax call + div = $browser.div(:text, /#{Regexp::escape(text)}/) + div.should be_exist +end + +Then /I should not see "([^\"]*)"/ do |text| + div = $browser.div(:text, /#{Regexp::escape(text)}/) + div.should_not be_exist +end + +def find_by_label_or_id(element, attribute) + matchers = [[attribute, :id], [attribute, :name]] + matchers << [$browser.label(:text, attribute).for, :id] if $browser.label(:text, attribute).exist? + field = matchers.map{|_field, matcher| $browser.send(element, matcher, _field)}.find(&:exist?) || raise("#{element} not found using \"#{attribute}\"") +end + +def assert_successful_response + status = $browser.page.web_response.status_code + if(status == 302 || status == 301) + location = $browser.page.web_response.get_response_header_value('Location') + puts "Being redirected to #{location}" + $browser.goto location + assert_successful_response + elsif status != 200 + filename = "culerity-#{Time.now.to_i}.html" + File.open(RAILS_ROOT + "/tmp/#{filename}", "w") do |f| + f.write $browser.html + end + `open tmp/#{filename}` + raise "Browser returned Response Code #{$browser.page.web_response.status_code}" + end +end diff --git a/features/step_definitions/widget_steps.rb b/features/step_definitions/widget_steps.rb new file mode 100644 index 0000000..a07a4ba --- /dev/null +++ b/features/step_definitions/widget_steps.rb @@ -0,0 +1,37 @@ +require 'culerity' + +When /^I wait for "(.*)" button to be enabled/ do |value| + #$browser.button(:text, value).enabled = :true + #b = $browser.button(:text, value).set(:disabled, false) + $browser.wait_while { $browser.button(:text, value).enabled? } +end + +When /I type in "([^\"]*)" for "([^\"]*)" and fire event "(.*)"/ do |value, field, evt| + $browser.text_field(:id, field).set(value) + $browser.text_field(:id, field).fire_event(evt) +end + +Then /^I should see a "(.*)" with text "(.*)"$/ do |elem, text| + l = eval("$browser.#{elem}(:text, /#{text}/)") + l.should be_exist +end + +Then /^I should see a text input field for "(.*)"/ do |field| + f = find_by_label_or_id(:text_field, field) + f.should be_exist +end + +Then /^I should see a select box for "(.*)"/ do |field| + f = find_by_label_or_id(:select_list, field) + f.should be_exist +end + +Then /^"(.*)" button should be enabled/ do |value| + f = $browser.button(:text, value) + + if defined?(Spec::Rails::Matchers) + f.should be_enabled + else + assert f.enabled? + end +end diff --git a/features/support/cucumber.env.rb b/features/support/cucumber.env.rb new file mode 100644 index 0000000..8d1b046 --- /dev/null +++ b/features/support/cucumber.env.rb @@ -0,0 +1,57 @@ +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + +ENV["RAILS_ENV"] ||= "cucumber" +require File.expand_path(File.dirname(__FILE__) + '/../../config/environment') + +require 'cucumber/formatter/unicode' # Remove this line if you don't want Cucumber Unicode support +require 'cucumber/rails/world' +require 'cucumber/rails/active_record' +require 'cucumber/web/tableish' + +require 'webrat' +require 'webrat/core/matchers' + +Webrat.configure do |config| + config.mode = :rails + config.open_error_files = false # Set to true if you want error pages to pop up in the browser +end + + +# If you set this to false, any error raised from within your app will bubble +# up to your step definition and out to cucumber unless you catch it somewhere +# on the way. You can make Rails rescue errors and render error pages on a +# per-scenario basis by tagging a scenario or feature with the @allow-rescue tag. +# +# If you set this to true, Rails will rescue all errors and render error +# pages, more or less in the same way your application would behave in the +# default production environment. It's not recommended to do this for all +# of your scenarios, as this makes it hard to discover errors in your application. +ActionController::Base.allow_rescue = false + +# If you set this to true, each scenario will run in a database transaction. +# You can still turn off transactions on a per-scenario basis, simply tagging +# a feature or scenario with the @no-txn tag. If you are using Capybara, +# tagging with @culerity or @javascript will also turn transactions off. +# +# If you set this to false, transactions will be off for all scenarios, +# regardless of whether you use @no-txn or not. +# +# Beware that turning transactions off will leave data in your database +# after each scenario, which can lead to hard-to-debug failures in +# subsequent scenarios. If you do this, we recommend you create a Before +# block that will explicitly put your database in a known state. +Cucumber::Rails::World.use_transactional_fixtures = true + +# How to clean your database when transactions are turned off. See +# http://github.com/bmabey/database_cleaner for more info. +if defined?(ActiveRecord::Base) + begin + require 'database_cleaner' + DatabaseCleaner.strategy = :truncation + rescue LoadError => ignore_if_database_cleaner_not_present + end +end diff --git a/features/support/env.rb b/features/support/env.rb new file mode 100644 index 0000000..48767d9 --- /dev/null +++ b/features/support/env.rb @@ -0,0 +1,9 @@ +ENV["RAILS_ENV"] ||= "culerity" +require File.expand_path(File.dirname(__FILE__) + '/../../config/environment') +require 'cucumber/rails/world' + +Cucumber::Rails::World.use_transactional_fixtures = false +ActionController::Base.allow_rescue = false + +require 'cucumber/formatter/unicode' +require 'cucumber/rails/rspec' \ No newline at end of file diff --git a/features/support/paths.rb b/features/support/paths.rb new file mode 100644 index 0000000..50e0766 --- /dev/null +++ b/features/support/paths.rb @@ -0,0 +1,30 @@ +module NavigationHelpers + # Maps a name to a path. Used by the + # + # When /^I go to (.+)$/ do |page_name| + # + # step definition in web_steps.rb + # + def path_to(page_name) + case page_name + + when /the home\s?page/ + '/' + + when /the widget/ + '/eems/new?wau=jchris' + + # Add more mappings here. + # Here is an example that pulls values out of the Regexp: + # + # when /^(.*)'s profile page$/i + # user_profile_path(User.find_by_login($1)) + + else + raise "Can't find mapping from \"#{page_name}\" to a path.\n" + + "Now, go and add a mapping in #{__FILE__}" + end + end +end + +World(NavigationHelpers) diff --git a/lib/dor/action_log_datastream.rb b/lib/dor/action_log_datastream.rb new file mode 100644 index 0000000..6c6b3e3 --- /dev/null +++ b/lib/dor/action_log_datastream.rb @@ -0,0 +1,72 @@ +require 'active_fedora' +require 'nokogiri' +require 'time' + +module Dor + class ActionLogDatastream < ActiveFedora::Datastream + attr_accessor :entries + + def initialize(attrs=nil) + super + self.label = "Action Log" + self.dsid = "actionLog" + self.control_group = 'X' + self.versionable = false + @entries = [] + end + + def save + self.blob = self.to_xml + super + end + + def log(action, comment = nil) + entry = {:timestamp => Time.new, :action => action} + entry[:comment] = comment unless(comment.nil?) + @entries << entry + end + + def each_entry(&block) + @entries.each do |entry| + block.call(entry[:timestamp], entry[:action], entry[:comment]) + end + end + + ######################################################################## + # Fedora datastream marshalling + + # Build an ActionLog from the Fedora persisted instance + # tmpl ActiveFedora::Datastream + # node Nokogiri::Node + def self.from_xml(tmpl, node) + entries = [] + node.xpath("./foxml:datastreamVersion[last()]/foxml:xmlContent/actionLog/entry").each do |entry_node| + entry = {:timestamp => Time.parse(entry_node["timestamp"]), + :action => entry_node.at_xpath("./action").text} + comment = entry_node.at_xpath("./comment") + entry[:comment] = comment.text if(comment) + entries << entry + end + + #TODO maybe sort by timestamp? + tmpl.entries = entries + tmpl + end + + def to_xml + builder = Nokogiri::XML::Builder.new do |xml| + xml.actionLog { + self.each_entry do |ts, action, comment| + xml.entry(:timestamp => ts.xmlschema) { + xml.action action + xml.comment comment unless(comment.nil?) + } + end + } + end + builder.to_xml + end + + + end +end \ No newline at end of file diff --git a/lib/dor/download_job.rb b/lib/dor/download_job.rb new file mode 100644 index 0000000..1b492e3 --- /dev/null +++ b/lib/dor/download_job.rb @@ -0,0 +1,89 @@ +require 'curl' +require 'tempfile' + +ActiveFedora::SolrService.register(SOLR_URL) +Fedora::Repository.register(FEDORA_URL) + +module Dor + class DownloadJob < Struct.new(:content_file_id) + + #TODO figure out if it isn't an http GET + #TODO if job fails, dj retries perform? Should test if file exists + def perform + @cf = ContentFile.find(content_file_id) + + @c = Curl::Easy.new(@cf.url) + @filename = nil + + tmpdl = Tempfile.new('dlfile', File.join(RAILS_ROOT,'tmp')) + File.open(tmpdl.path, "wb") do |output| + @c.on_header do |hdr| + if(hdr =~ /content-disposition.*filename\s*=\s*\"?([^\"]*)\"?/i) + @filename = $1 + end + Rails.logger.info("Response header: #{hdr}") + hdr.length + end + + @c.on_body do |data| + output << data + data.length + end + + @c.on_progress do |dl_total, dl_now, ut, un| + @cf.update_progress(dl_total, dl_now) + true + end + + @c.follow_location = true + + @c.on_complete do |curb| + code = curb.response_code + unless(code < 400) + raise "Curb Download Error- #{code.to_s}" + end + end + + @c.on_success do |curb| + Rails.logger.info("Download Successful: #{curb.response_code.to_s} Bytes: #{curb.downloaded_bytes}") + @cf.update_progress(curb.downloaded_bytes, curb.downloaded_bytes) + end + + @c.perform + end + + # If filename wasn't sent in the content-disposition header, we assume it is the last part of the url + unless(@filename) + @filename = @cf.url.split(/\?/).first.split(/\//).last + end + + @filename = Part.normalize_filename(@filename) + + FileUtils.cp(tmpdl.path, File.join(@cf.filepath,@filename)) + FileUtils.chmod(0644, File.join(@cf.filepath,@filename)) + tmpdl.delete + + part = Part.find(@cf.part_pid) + part.create_content_datastream(@filename) + part.download_done + + # Log that the download completed + part.log_download_complete(@cf.user_display_name) + + rescue Exception => e + msg = e.message + unless(e.backtrace.nil?) + msg << "\n" << e.backtrace.join("\n") + end + + if( @cf.attempts < Delayed::Worker.max_attempts + 1) + @cf.attempts = @cf.attempts + 1 + @cf.save + end + + Rails.logger.error("DownloadJob Failed: " + msg) + raise e + end + + end +end diff --git a/lib/dor/utils.rb b/lib/dor/utils.rb new file mode 100644 index 0000000..f97eedf --- /dev/null +++ b/lib/dor/utils.rb @@ -0,0 +1,10 @@ +require 'active_fedora' + +module Dor + class Utils + def self.register_local + Fedora::Repository.register(FEDORA_URL) + ActiveFedora::SolrService.register(SOLR_URL) + end + end +end \ No newline at end of file diff --git a/lib/dor/workflow_service.rb b/lib/dor/workflow_service.rb new file mode 100644 index 0000000..d7baa74 --- /dev/null +++ b/lib/dor/workflow_service.rb @@ -0,0 +1,115 @@ +require 'nokogiri' + +module Dor + + # Methods to create and update workflow + # Calls to LyberCore::Connection have been commented out, since it pertains to Stanford's WorkDo service + # + # ==== Required Constants + # - Dor::CREATE_WORKFLOW : true or false. Can be used to turn of workflow in a particular environment, like development + # - Dor::WF_URI : The URI to the workflow service. + module WorkflowService + + # Creates a workflow for a given object in the repository. If this particular workflow for this objects exists, + # it will replace the old workflow with wf_xml passed to this method. + # Returns true on success. Caller must handle any exceptions + # + # == Parameters + # - repo - The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment + # - druid - The id of the object + # - workflow_name - The name of the workflow you want to create + # - wf_xml - The xml that represents the workflow + # + def WorkflowService.create_workflow(repo, druid, workflow_name, wf_xml) + return true # unless(Dor::CREATE_WORKFLOW) + # + # full_uri = '' + # full_uri << Dor::WF_URI << '/' << repo << '/objects/' << druid << '/workflows/' << workflow_name + # + # # On success, an empty body is sent + # LyberCore::Connection.put(full_uri, wf_xml){|response| true} + end + + # Updates the status of one step in a workflow. + # Returns true on success. Caller must handle any exceptions + # + # == Required Parameters + # - repo - The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment + # - druid - The id of the object + # - workflow_name - The name of the workflow + # - status - The status that you want to set. Typical statuses are 'waiting', 'completed', 'error', but could be any string + # + # == Optional Parameters + # - elapsed - The number of seconds it took to complete this step. Can have a decimal. Is set to 0 if not passed in. + # - lifecycle - Bookeeping label for this particular workflow step. Examples are: 'registered', 'shelved' + # + # == Http Call + # The method does an HTTP PUT to the URL defined in Dor::WF_URI. As an example: + # PUT "/dor/objects/pid:123/workflows/GoogleScannedWF/convert" + # " + def WorkflowService.update_workflow_status(repo, druid, workflow, process, status, elapsed = 0, lifecycle = nil) + return true # unless(Dor::CREATE_WORKFLOW) + # + # uri = '' + # uri << Dor::WF_URI << '/' << repo << '/objects/' << druid << '/workflows/' << workflow << '/' << process + # + # attrs = {:process => process, :status => status, :elapsed => elapsed.to_s} + # attrs[:lifecycle] = lifecycle if(lifecycle) + # + # builder = Nokogiri::XML::Builder.new {|xml| xml.process(attrs)} + # + # # On success, an empty body is sent + # LyberCore::Connection.put(uri, builder.to_xml) {|response| true} + end + + # + # Retrieves the process status of the given workflow for the given object identifier + # + def WorkflowService.get_workflow_status(repo, druid, workflow, process) + # uri = '' + # uri << Dor::WF_URI << '/' << repo << '/objects/' << druid << '/workflows/' << workflow + # workflow_md = LyberCore::Connection.get(uri) + # + # doc = Nokogiri::XML(workflow_md) + # raise Exception.new("Unable to parse response:\n#{workflow_md}") if(doc.root.nil?) + # + # status = doc.root.at_xpath("//process[@name='#{process}']/@status").content + # return status + end + + def WorkflowService.get_workflow_xml(repo, druid, workflow) + # uri = '' + # uri << Dor::WF_URI << '/' << repo << '/objects/' << druid << '/workflows/' << workflow + # workflow_md = LyberCore::Connection.get(uri) + end + + # Updates the status of one step in a workflow to error. + # Returns true on success. Caller must handle any exceptions + # + # == Required Parameters + # - repo - The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment + # - druid - The id of the object + # - workflow_name - The name of the workflow + # - error_msg - The error message. Ideally, this is a brief message describing the error + # + # == Optional Parameters + # - error_txt - A slot to hold more information about the error, like a full stacktrace + # + # == Http Call + # The method does an HTTP PUT to the URL defined in Dor::WF_URI. As an example: + # PUT "/dor/objects/pid:123/workflows/GoogleScannedWF/convert" + # " + def WorkflowService.update_workflow_error_status(repo, druid, workflow, process, error_msg, error_txt = nil) + return true + # uri = '' + # uri << Dor::WF_URI << '/' << repo << '/objects/' << druid << '/workflows/' << workflow << '/' << process + # process_xml = '' + # + # # On success, an empty body is sent + # LyberCore::Connection.put(uri, process_xml) {|response| true} + end + + end +end diff --git a/lib/eem_model.rb b/lib/eem_model.rb new file mode 100644 index 0000000..082ea30 --- /dev/null +++ b/lib/eem_model.rb @@ -0,0 +1,5 @@ +# Copied from internal Stanford gem +require 'eem_model/eem' +require 'eem_model/part' +require 'eem_model/eem_accession' + diff --git a/lib/eem_model/eem.rb b/lib/eem_model/eem.rb new file mode 100644 index 0000000..d7b3cee --- /dev/null +++ b/lib/eem_model/eem.rb @@ -0,0 +1,36 @@ +# Copied from internal Stanford gem + +module EemModel + + class Eem < ActiveFedora::Base + + has_relationship "parts", :is_part_of, :inbound => true #content files + + has_metadata :name => 'eemsProperties', :type => ActiveFedora::MetadataDatastream do |m| + m.label = "eemsProperties" + + m.field "copyrightStatusDate", :string, :multiple => false #mutiple doesn't do anything + m.field "copyrightStatus", :string + m.field "creatorOrg", :string + m.field "creatorPerson", :string + m.field "language", :string + m.field "note", :string + m.field "paymentType", :string + m.field "paymentFund", :string + m.field "selectorName", :string + m.field "selectorSunetid", :string + m.field "title", :string + m.field "sourceUrl", :string + m.field "requestDatetime", :string + m.field "status", :string + m.field "statusDatetime", :string + m.field "downloadDate", :string + end + + has_metadata :name => "DC", :type => ActiveFedora::QualifiedDublinCoreDatastream do |m| + end + + end + +end + diff --git a/lib/eem_model/eem_accession.rb b/lib/eem_model/eem_accession.rb new file mode 100644 index 0000000..53e93d2 --- /dev/null +++ b/lib/eem_model/eem_accession.rb @@ -0,0 +1,309 @@ +# Copied from internal Stanford gem + +require 'nokogiri' + +module EemModel + module EemAccession + + # create a datastream in the repository for the given eem object + def populate_datastream(ds_name) + label = case ds_name + when "identityMetadata" then 'Identity Metadata' + when "contentMetadata" then 'Content Metadata' + when "rightsMetadata" then 'Rights Metadata' + else '' + end + metadata = case ds_name + when "identityMetadata" then generate_identity_metadata_xml + when "contentMetadata" then generate_content_metadata_xml + when "rightsMetadata" then generate_rights_metadata_xml + else nil + end + unless( metadata.nil? ) + populate_datastream_in_repository(ds_name,label,metadata) + end + end + + # create a datastream for the given eem object with the given datastream name, label, and metadata blob + def populate_datastream_in_repository(ds_name,label,metadata) + attrs = { :pid => pid, :dsID => ds_name, :mimeType => 'application/xml', :dsLabel => label, :blob => metadata } + datastream = ActiveFedora::Datastream.new(attrs) + datastream.control_group = 'M' + datastream.versionable = false + datastream.save + end + + # create the identity metadata xml datastream + # + # + # druid:rt923jk342 <-- Fedora PID + # item <-- Supplied fixed value + # value from Fedora header <-- from Fedora header + # DOR <-- Supplied fixed value + # title, from DC:title <-- updated after Symphony record is created + # creator, from DC:creator <-- updated after Symphony record is created + # 0000000012 <-- per Registrar, from EEM propertied + # 129483625 <-- added after Symphony record is created. Can be found in DC + # 7f3da130-7b02-11de-8a39-0800200c9a66 <-- DOR assigned (omit if not present) + # druid:ct692vv3660 <-- fixed pre-assigned value, use the value shown here for EEMs + # EEM : Dissertation | Thesis <-- set of tags * + # + # + # == Options + # - :add_label If set to true, will add objectLabel to the xml, based on the Fedora object label. Default is false + def generate_initial_identity_metadata_xml(options={:add_label => false}) + builder = Nokogiri::XML::Builder.new do |xml| + dc_ds = datastreams['DC'] + props_ds = datastreams['eemsProperties'] + xml.identityMetadata { + xml.objectId { + xml.text(pid) + } + xml.objectType { + xml.text("item") + } + if(options[:add_label]) + xml.objectLabel{ + xml.text(self.label) + } + end + xml.objectAdminClass { + xml.text("EEMs") + } + xml.agreementId { + xml.text("some:objectId") + } + xml.tag { + xml.text("EEM : 1.0" ) + } + } + end + builder.to_xml + end + + def update_identity_metadata_object_label + id_ds = datastreams['identityMetadata'] + id_xml = id_ds.content + if(!id_xml.nil? && !id_xml.empty?) + id_doc = Nokogiri::XML(id_xml) + object_label = id_doc.at_xpath('//objectLabel') + # create a new one if it doesn't exist + if(object_label.nil?) + object_label = Nokogiri::XML::Node.new('objectLabel', id_doc) + object_label.content = self.label + object_type = id_doc.at_xpath('//objectType') + if(object_type) + object_type.add_next_sibling(object_label) + else + id_doc.root << object_label + end + # else replace the old label with the new label + elsif(object_label.content != self.label) + object_label.content = self.label + end + id_ds.content = id_doc.to_xml + + # Create identityXml datastream since it doesn't exist + else + id_ds.content = generate_initial_identity_metadata_xml(:add_label => true) + end + + id_ds.save + + end + + # create the content metadata xml datastream + # + # + # + # Body of dissertation (as submitted) + # + # + # + # Body of dissertation + # + # https://stacks.stanford.edu/file/druid:rt923jk342/mydissertation-augmented.pdf + # + # + # + # Full experimental data + # + # https://stacks.stanford.edu/file/druid:rt923jk342/datafile.xls + # + # + # + # Permission from the artist + # + # + # + # + def generate_content_metadata_xml + builder = Nokogiri::XML::Builder.new do |xml| + xml.contentMetadata(:type => "eem", :objectId => pid) { + # main pdf + props_ds = main_pdf.datastreams['properties'] + main_pdf_file_name = props_ds.file_name_values.first + main_pdf_file_size = props_ds.size_values.first + xml.resource(:id => "main", :type => "main-original", :data => "content") { + xml.attr(:name => "label") { + xml.text("Body of dissertation (as submitted)") + } + xml.file(:id => main_pdf_file_name, :mimetype => "application/pdf", :size => main_pdf_file_size, :shelve => "yes", :deliver => "no", :preserve => "yes") + } + # augmented pdf + props_ds = main_pdf.datastreams['properties'] + main_pdf_file_name = props_ds.file_name_values.first + main_pdf_file_size = props_ds.size_values.first + augmented_pdf_file_name = main_pdf_file_name.gsub(/\.pdf/,'-augmented.pdf') + augmented_pdf_file_size = File.size?(File.join(WORKSPACE_DIR, pid, augmented_pdf_file_name)) + xml.resource(:id => "main", :type => "main-augmented", :data => "content", :objectId => main_pdf.pid) { + xml.attr(:name => "label") { + xml.text("Body of dissertation") + } + xml.file(:id => augmented_pdf_file_name, :mimetype => "application/pdf", :size => augmented_pdf_file_size, :shelve => "yes", :deliver => "yes", :preserve => "yes") { + xml.location(:type => "url") { + xml.text("https://stacks.stanford.edu/file/#{pid}/#{augmented_pdf_file_name}") + } + } + } + # supplemental files + supplemental_files.each_with_index do |supplemental_file, sequence| + props_ds = supplemental_file.datastreams['properties'] + supplemental_file_name = props_ds.file_name_values.first + supplemental_file_mimetype = MIME::Types.type_for(supplemental_file_name).first.content_type + supplemental_file_label = props_ds.label_values.first + supplemental_file_size = props_ds.size_values.first + xml.resource(:id => "supplement", :type => "supplement", :data => "content", :sequence => sequence+1, :objectId => supplemental_file.pid) { + xml.attr(:name => "label") { + xml.text(supplemental_file_label) + } + xml.file(:id => supplemental_file_name, :mimetype => supplemental_file_mimetype, :size => supplemental_file_size, :shelve => "yes", :deliver => "yes", :preserve => "yes") { + xml.location(:type => "url") { + xml.text("https://stacks.stanford.edu/file/#{pid}/#{supplemental_file_name}") + } + } + } + end + # permission files + permission_files.each_with_index do |permission_file, sequence| + props_ds = permission_file.datastreams['properties'] + permission_file_name = props_ds.file_name_values.first + permission_file_mimetype = MIME::Types.type_for(permission_file_name).first.content_type + permission_file_label = props_ds.label_values.first + permission_file_size = props_ds.size_values.first + xml.resource(:id => "permissions", :type => "permissions", :data => "content", :objectId => permission_file.pid) { + xml.attr(:name => "label") { + xml.text(permission_file_label) + } + xml.file(:id => permission_file_name, :mimetype => permission_file_mimetype, :size => permission_file_size, :shelve => "yes", :deliver => "no", :preserve => "yes") + } + end + } + end + builder.to_xml + end + + # create the rights metadata xml datastream + # + # + # + # (c) Copyright [conferral year] by [student name] + # + # <--- this block is static across EEMs; all EEMs are discoverable + # + # + # + # + # <--- include this block after an object has been "released" + # + # stanford:stanford -OR- <--- for Stanford-only access or world/public visibility + # 2011-03-01 <--- if embargoed, date calculated from release date + # + # + # + # value <--- if a license is selected + # + # + # + def generate_rights_metadata_xml + + # determine which parts of the rights metadata should be displayed based on the workflow lifecycle + is_shelved = false + shelve_status = Dor::WorkflowService.get_workflow_status(pid,'eemAccessionWF','shelve') + if( "#{shelve_status}".eql? "completed" ) + is_shelved = true + end + + builder = Nokogiri::XML::Builder.new do |xml| + xml.rightsMetadata(:objectId => pid) { + props_ds = datastreams['properties'] + conferral_year = props_ds.degreeconfyr_values.first + student_name = props_ds.name_values.first + xml.copyright { + xml.human { + formatted_student_name = Dor::Util.parse_name(student_name) + xml.text("(c) Copyright #{conferral_year} by #{formatted_student_name}") + } + } + xml.access(:type => "discover") { + xml.machine { + xml.world + } + } + if( is_shelved ) + release_date = get_embargo_date + visibility = props_ds.external_visibility_values.first + generate_rights_access_block(xml,release_date,visibility) + end + cc_license_type = props_ds.cclicensetype_values.first + cc_code = props_ds.cclicense_values.first + cc_license = case cc_code + when "1" then 'by' + when "2" then 'by-sa' + when "3" then 'by-nd' + when "4" then 'by-nc' + when "5" then 'by-nc-sa' + when "6" then 'by-nc-nd' + else 'none' + end + unless( cc_license.nil? and cc_license_type.nil? ) + xml.use { + xml.machine(:type => "creativeCommons") { + xml.text(cc_license) + } + xml.human(:type => "creativeCommons") { + xml.text(cc_license_type) + } + } + end + } + end + builder.to_xml + end + + def generate_rights_access_block(xml,release_date,visibility) + access_type = 'stanford' + if( visibility == '100' and (!release_date.nil? and release_date.past?) ) + access_type = 'world' + end + xml.access(:type => "read") { + xml.machine { + if( access_type.eql? 'stanford' ) + xml.group { + xml.text("stanford") + } + if( !release_date.nil? && release_date.future? ) + xml.embargoReleaseDate { + xml.text(release_date.strftime("%Y-%m-%d")) + } + end + else + xml.world + end + } + } + end + + end +end + diff --git a/lib/eem_model/part.rb b/lib/eem_model/part.rb new file mode 100644 index 0000000..94b92ab --- /dev/null +++ b/lib/eem_model/part.rb @@ -0,0 +1,21 @@ +# Copied from internal Stanford gem + +module EemModel + + class Part < ActiveFedora::Base + + has_relationship "parents", :is_part_of #relationship between content file and parent Eem + + has_metadata :name => 'properties', :type => ActiveFedora::MetadataDatastream do |m| + m.label = "properties" + m.field "url", :string + m.field "done", :string + m.field "content_file_id", :string + m.field "filename", :string + m.field "download_date", :string + end + + end + +end + diff --git a/lib/eems_user.rb b/lib/eems_user.rb new file mode 100644 index 0000000..f0cc4cb --- /dev/null +++ b/lib/eems_user.rb @@ -0,0 +1,30 @@ +class EemsUser + + attr_reader :display_name, :login, :privgroup + + def initialize(display_name, login, privgroup=nil) + @display_name = display_name + @login = login + @privgroup = privgroup + end + + def save_to_session(session) + u = {:display_name => @display_name, :login => @login, :privgroup => @privgroup} + session[:eems_user] = u + end + + # TODO what to do when there is no :eems_user in the session + def EemsUser.load_from_session(session) + u = EemsUser.new(session[:eems_user][:display_name], session[:eems_user][:login], session[:eems_user][:privgroup]) + u + end + + def EemsUser.user_authenticated?(session) + return true unless(session[:eems_user].nil?) + false + end + + #For backwards compatibility + alias :sunetid :login + +end \ No newline at end of file diff --git a/lib/tasks/cucumber.rake b/lib/tasks/cucumber.rake new file mode 100644 index 0000000..7db1a55 --- /dev/null +++ b/lib/tasks/cucumber.rake @@ -0,0 +1,53 @@ +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + + +unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks + +vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first +$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? + +begin + require 'cucumber/rake/task' + + namespace :cucumber do + Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t| + t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. + t.fork = true # You may get faster startup if you set this to false + t.profile = 'default' + end + + Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'wip' + end + + Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'rerun' + end + + desc 'Run all features' + task :all => [:ok, :wip] + end + desc 'Alias for cucumber:ok' + task :cucumber => 'cucumber:ok' + + task :default => :cucumber + + task :features => :cucumber do + STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" + end +rescue LoadError + desc 'cucumber rake task not available (cucumber not installed)' + task :cucumber do + abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' + end +end + +end diff --git a/lib/tasks/culerity.rake b/lib/tasks/culerity.rake new file mode 100644 index 0000000..46a84f2 --- /dev/null +++ b/lib/tasks/culerity.rake @@ -0,0 +1,38 @@ +namespace 'culerity' do + namespace 'rails' do + desc "Starts a rails server for cucumber/culerity tests" + task :start do + port = ENV['PORT'] || 3001 + environment = ENV["RAILS_ENV"] || 'culerity' + pid_file = RAILS_ROOT + "/tmp/culerity_rails_server.pid" + if File.exists?(pid_file) + puts "culerity rails server already running; if not, delete tmp/culerity_rails_server.pid and try again" + exit 1 + end + rails_server = IO.popen("script/server -e #{environment} -p #{port}", 'r+') + File.open(pid_file, "w") { |file| file << rails_server.pid } + end + + desc "Stops the running rails server for cucumber/culerity tests" + task :stop do + pid_file = RAILS_ROOT + "/tmp/culerity_rails_server.pid" + if File.exists?(pid_file) + pid = File.read(pid_file).to_i + Process.kill(6, pid) + File.delete(pid_file) + else + puts "No culerity rails server running. Doing nothing." + end + end + + desc "Restarts the rails server for cucumber/culerity tests" + task :restart => [:stop, :start] + end + + desc "Install required gems into jruby" + task :install do + jgem_cmd = `which jruby`.strip + raise "ERROR: You need to install jruby to use culerity and celerity." if jgem_cmd.blank? + sh "#{jgem_cmd} -S gem install celerity" + end +end diff --git a/lib/tasks/rspec.rake b/lib/tasks/rspec.rake new file mode 100644 index 0000000..dba3ffc --- /dev/null +++ b/lib/tasks/rspec.rake @@ -0,0 +1,144 @@ +gem 'test-unit', '1.2.3' if RUBY_VERSION.to_f >= 1.9 +rspec_gem_dir = nil +Dir["#{RAILS_ROOT}/vendor/gems/*"].each do |subdir| + rspec_gem_dir = subdir if subdir.gsub("#{RAILS_ROOT}/vendor/gems/","") =~ /^(\w+-)?rspec-(\d+)/ && File.exist?("#{subdir}/lib/spec/rake/spectask.rb") +end +rspec_plugin_dir = File.expand_path(File.dirname(__FILE__) + '/../../vendor/plugins/rspec') + +if rspec_gem_dir && (test ?d, rspec_plugin_dir) + raise "\n#{'*'*50}\nYou have rspec installed in both vendor/gems and vendor/plugins\nPlease pick one and dispose of the other.\n#{'*'*50}\n\n" +end + +if rspec_gem_dir + $LOAD_PATH.unshift("#{rspec_gem_dir}/lib") +elsif File.exist?(rspec_plugin_dir) + $LOAD_PATH.unshift("#{rspec_plugin_dir}/lib") +end + +# Don't load rspec if running "rake gems:*" +unless ARGV.any? {|a| a =~ /^gems/} + +begin + require 'spec/rake/spectask' +rescue MissingSourceFile + module Spec + module Rake + class SpecTask + def initialize(name) + task name do + # if rspec-rails is a configured gem, this will output helpful material and exit ... + require File.expand_path(File.join(File.dirname(__FILE__),"..","..","config","environment")) + + # ... otherwise, do this: + raise <<-MSG + +#{"*" * 80} +* You are trying to run an rspec rake task defined in +* #{__FILE__}, +* but rspec can not be found in vendor/gems, vendor/plugins or system gems. +#{"*" * 80} +MSG + end + end + end + end + end +end + +Rake.application.instance_variable_get('@tasks').delete('default') + +spec_prereq = File.exist?(File.join(RAILS_ROOT, 'config', 'database.yml')) ? "db:test:prepare" : :noop +task :noop do +end + +task :default => :spec +task :stats => "spec:statsetup" + +desc "Run all specs in spec directory (excluding plugin specs)" +Spec::Rake::SpecTask.new(:spec => spec_prereq) do |t| + t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""] + t.spec_files = FileList['spec/**/*_spec.rb'] +end + +namespace :spec do + desc "Run all specs in spec directory with RCov (excluding plugin specs)" + Spec::Rake::SpecTask.new(:rcov) do |t| + t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""] + t.spec_files = FileList['spec/**/*_spec.rb'] + t.rcov = true + t.rcov_opts = lambda do + IO.readlines("#{RAILS_ROOT}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten + end + end + + desc "Print Specdoc for all specs (excluding plugin specs)" + Spec::Rake::SpecTask.new(:doc) do |t| + t.spec_opts = ["--format", "specdoc", "--dry-run"] + t.spec_files = FileList['spec/**/*_spec.rb'] + end + + desc "Print Specdoc for all plugin examples" + Spec::Rake::SpecTask.new(:plugin_doc) do |t| + t.spec_opts = ["--format", "specdoc", "--dry-run"] + t.spec_files = FileList['vendor/plugins/**/spec/**/*_spec.rb'].exclude('vendor/plugins/rspec/*') + end + + [:models, :controllers, :views, :helpers, :lib, :integration].each do |sub| + desc "Run the code examples in spec/#{sub}" + Spec::Rake::SpecTask.new(sub => spec_prereq) do |t| + t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""] + t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"] + end + end + + desc "Run the code examples in vendor/plugins (except RSpec's own)" + Spec::Rake::SpecTask.new(:plugins => spec_prereq) do |t| + t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""] + t.spec_files = FileList['vendor/plugins/**/spec/**/*_spec.rb'].exclude('vendor/plugins/rspec/*').exclude("vendor/plugins/rspec-rails/*") + end + + namespace :plugins do + desc "Runs the examples for rspec_on_rails" + Spec::Rake::SpecTask.new(:rspec_on_rails) do |t| + t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""] + t.spec_files = FileList['vendor/plugins/rspec-rails/spec/**/*_spec.rb'] + end + end + + # Setup specs for stats + task :statsetup do + require 'code_statistics' + ::STATS_DIRECTORIES << %w(Model\ specs spec/models) if File.exist?('spec/models') + ::STATS_DIRECTORIES << %w(View\ specs spec/views) if File.exist?('spec/views') + ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers) if File.exist?('spec/controllers') + ::STATS_DIRECTORIES << %w(Helper\ specs spec/helpers) if File.exist?('spec/helpers') + ::STATS_DIRECTORIES << %w(Library\ specs spec/lib) if File.exist?('spec/lib') + ::STATS_DIRECTORIES << %w(Routing\ specs spec/routing) if File.exist?('spec/routing') + ::STATS_DIRECTORIES << %w(Integration\ specs spec/integration) if File.exist?('spec/integration') + ::CodeStatistics::TEST_TYPES << "Model specs" if File.exist?('spec/models') + ::CodeStatistics::TEST_TYPES << "View specs" if File.exist?('spec/views') + ::CodeStatistics::TEST_TYPES << "Controller specs" if File.exist?('spec/controllers') + ::CodeStatistics::TEST_TYPES << "Helper specs" if File.exist?('spec/helpers') + ::CodeStatistics::TEST_TYPES << "Library specs" if File.exist?('spec/lib') + ::CodeStatistics::TEST_TYPES << "Routing specs" if File.exist?('spec/routing') + ::CodeStatistics::TEST_TYPES << "Integration specs" if File.exist?('spec/integration') + end + + namespace :db do + namespace :fixtures do + desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z." + task :load => :environment do + ActiveRecord::Base.establish_connection(Rails.env) + base_dir = File.join(Rails.root, 'spec', 'fixtures') + fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir + + require 'active_record/fixtures' + (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/).map {|f| File.join(fixtures_dir, f) } : Dir.glob(File.join(fixtures_dir, '*.{yml,csv}'))).each do |fixture_file| + Fixtures.create_fixtures(File.dirname(fixture_file), File.basename(fixture_file, '.*')) + end + end + end + end +end + +end diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..eff660b --- /dev/null +++ b/public/404.html @@ -0,0 +1,30 @@ + + + + + + + The page you were looking for doesn't exist (404) + + + + + +
+

The page you were looking for doesn't exist.

+

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

+
+ + \ No newline at end of file diff --git a/public/422.html b/public/422.html new file mode 100644 index 0000000..b54e4a3 --- /dev/null +++ b/public/422.html @@ -0,0 +1,30 @@ + + + + + + + The change you wanted was rejected (422) + + + + + +
+

The change you wanted was rejected.

+

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

+
+ + \ No newline at end of file diff --git a/public/500.html b/public/500.html new file mode 100644 index 0000000..ec3bbf0 --- /dev/null +++ b/public/500.html @@ -0,0 +1,30 @@ + + + + + + + We're sorry, but something went wrong (500) + + + + + +
+

We're sorry, but something went wrong.

+

We've been notified about this issue and we'll take a look at it shortly.

+
+ + diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/public/images/bgTopBar_1x48.png b/public/images/bgTopBar_1x48.png new file mode 100644 index 0000000000000000000000000000000000000000..1bb355426e63e385028fd8eea6c1d86d62d2b9c0 GIT binary patch literal 2990 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 Ull9^Ff1V03(7lBu={QFJ2O4#ea{vGU literal 0 HcmV?d00001 diff --git a/public/images/c_loader.gif b/public/images/c_loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..358ab485eb2ed2ada2392408aaac8b693dee2d1e GIT binary patch literal 1153 zcmZ?wbhEHb6krfwXklQOGi%P7b7!_}+w$hsn>DM}+_`t>$e|-&zI|D;Xvv*hclPYt z^X1c*Et|GHdG_SYsWX56{h2dw&Xr47_Uzj8=G~h$>()GZ^yJ8~BY%GVS+Z;i12&-e zpWDwhB-q(8z|~04fSD1fM)4;L2OE&q0jUQ$gMl^ufpT9;=Dai~0SUoaw^lcYw=!D` zW_s~P@H4Y0Xz*qoYRE9qnZUzvbg_V>Wrqsag2Y)21}cZHZDCOGuze6Dd7w{=;e^8# z1_^-%El;8S2eU=g#F+>|3czGpyCHORyttU^MsEBEI3nT!5z6XW@(Dy(;0eue) z17HALxpoEUdtevQkmSoH|iM%f`uy>$r&_!(%J< z1@9TAJ!@cH`r(3Z0B@}05{8&*qCxL8vRJg*np;d5O@ai~ug}g>TEg7Q%q+>uz#O#2 z(c3_f!J2cEwgyv$aH^{fgD{(34Zk=CPnDjic&@nytFAdA-_ax<(-XjHG(%a&wa_Oi zXexWcedZR46kZ+SHvSJYIwj1QG!v>nxNK$3{FEUT+Oj-j(u-4W!ahvRnxX4@51n;v zV$e|O^0f*I5&pPLc@c*K1FNbCGqbrlYX+}?k_59jKXbh1i6%3jrZY@!q z6r#7K&6U03gG=}QcMoSYXsexK*;H{w^C@HI#tkn#nNs^LB$$8^ N*r7RnhMFRSH2~KX>52dV literal 0 HcmV?d00001 diff --git a/public/images/date-picker/backstripes.gif b/public/images/date-picker/backstripes.gif new file mode 100755 index 0000000000000000000000000000000000000000..63f14035142b4ba489635a75c9907ed7e5173f51 GIT binary patch literal 234 zcmVMOLc**Mu zTYR$8d#1J<^Y2|?M~-GVoh7Qa4-2odo5u8QJNTUQTE7dzgg&Ch2uzZc$YQ7o%|)G5 z3G-TgVy7s9hQxeut|7Z^A=xTjM{h{y6RJQ$+ijji8tC5N?H1qN_Ps`y0%L6iWqxUD>mzytGdg2sd$SM ktkjD%+*XX0YR-tZb|xz{bJG4g_o*9GqNST$~&{V890g0&u{{ z%)-LP#>T_J!6U@S!zUyHk`WOE%L_s%0VoC6gTnZKfI*OhL79nznNg5|Nsy6Qkn#T! zh6)BoFaWv}1{hhGnK&V$j4&}E&Bz3FumDgw2P4q^KzWFqFk|9HCg#a3q9KNkjf#P+ zf`t)sdG=REXwFh_;TAQ zXY)=|Jzv2yau2kvUJji!pLXA=q# zNMMr@An8Peb^1T51_NnswV^_~Ae`iI1OxVTk@iz-zyHKj>DQM=&A;uNE#Hy+cK&z! zZKZKh#T(PRbV3((i&iW;8m9r`L_X>k{g$!l=sYkl6vz{aSbbDX3&s@#s*HlEgmMC1 zyMU%V>IQN5R)Gx^`|=8#BB1FYML?^Exrgc z9M0AG=-Ra>FliCY7`XHA)rBp;{M1Mzz3ie$yp%lf$F~l zWgLvaYQw6Vj@CNn-Pirk5N-bV)qjRuwfzG3!G7SiRG+}}`U-nl!zb2H3>>U)SQwu$ zTQR)7!ahNpL(%yJ8)F3H0tepT4fzflg7XVB1Q-~2Ua@8fFxNeZPf(vA=di%v&SA|v zmiz`y7L7`Va}Cy|j8C|L1|ca~`+|kx{tmtaL5wb+80S_syt`mlv;A+*{qL*)ZaKGK z;N`df48^~vIr}+ture__FuY-4PwM=^G{IVggMmr?!Kx)p4GiiJxGn*0X1v3|_f~^} z;Q|8(gKdNH0tN=jJB!Q@h;TxcGcbJ9FkxU~;7{Or!MKz`wnMgy#bK2}Lx95nn*d13 B)&>9o literal 0 HcmV?d00001 diff --git a/public/images/date-picker/bullet1.gif b/public/images/date-picker/bullet1.gif new file mode 100755 index 0000000000000000000000000000000000000000..ae352c2b697b3309d21f667749a7fd4a2d695655 GIT binary patch literal 55 zcmZ?wbhEHb zn7DSR*ytGc7%6$hIB9taREfFC38UEw8fx+>T53van%aq~+G@$_8tVuvTU$tLo9lFKS96bdsUA;tYoh^i|-QC0O9bO|&URQqJ1dhHQ$nKu? M9{(**KT`kzJ6L&uY5)KL literal 0 HcmV?d00001 diff --git a/public/images/date-picker/cal-grey.gif b/public/images/date-picker/cal-grey.gif new file mode 100755 index 0000000000000000000000000000000000000000..484e977efa626e55778f4fe7c9e307e8b15f3050 GIT binary patch literal 170 zcmZ?wbhEHb6krfw*v!F@ot+&U8+-2Dx%u+|z`((v z1Cj)p!N3wOaMDv-DTU`u`8ig>o|FffQ4y17vhy8JJn~ubLLuX6i}&qpjfoN_41EoW z8fBjulg=N$6yEJt>ZBw%)8s|f*{Iwo?(YqUz8K8@8+35z+@HF-H{SoKQDkDU1^@sf BKAZpm literal 0 HcmV?d00001 diff --git a/public/images/date-picker/cal.gif b/public/images/date-picker/cal.gif new file mode 100755 index 0000000000000000000000000000000000000000..8526cf5d19a915aa8073cf344873c4505491970d GIT binary patch literal 127 zcmZ?wbhEHb6krfwSj51v)Yr?)cd3`J*V1K6uU)?O9}E~67!-f9FfuT(G3WrDe9t*l+9<3>gnm}=;-L@=jZ0;=H=z( z|Ns900000000000A^8LW000~SEC2ui044xi000I5;3ke_X`X1Ru59bRa4gSSZQppV z?|kq7z#uO}EEin3mE$jQpf%+1ct4$;!n)YaD4(GA+$+}+;a z;Njxq;tb~J=;`X~?CtLF?hEqs^!4`l`1$(#`U?L400RmfNU)&6g9Z~QT*$DY!-o(f zN}MPWfyIj$Giuz(v7^V29uJ5dNwTELlPFV*s$9vkrOT8CV#=IJv!>0PICJXES)ixS zpFo2O9ZIyQ(V_&BDqYI7sne%Wqe`7hwJOvBShH%~%C)Q4uVBN99ZUABfU{`Rs$I*r zt=qS7(BTJr4xw7TUm@{kM Q%(=7Y&!9t#P8X1^@s69OWTt0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU+{YgYYRCwC#S_y1b#}&QrdxO*qZ7DTi zz|aCw0|b&n%VL(O2}XtnBpRwLC22yCXbAyfB0y=NNxA?93L#QT#1<({6DdHEpg4^e z%u)#022vo!;03}C22;TP|G)Qo?);g@{PDAKz!XqC(&%k7bMKvV@40urZMm*X&F$;e zEEDv<4IJ=&D<&qz3Iyto##k}_&Nxb}yoT?wJ?beIOL)`woPt0Om-+l zPB2JzZ7p~E#Z#wCd_n(fzrLw~*x2;|ydOGQmKDA5od^A%ZjEJBecv0kp2R}{j_v?( zn*mp#*VOR!OFaMVrP|;QJ$hK$+vxoo0@?$-8T2Lr-Vb2+tF9+;5r8AgMdwIY(H`mT z5&a)62v}XFJ8m#mTOo@nf>;sTj#^YK%oL)7-`ws7FY>VQs9gaaiEz=wZ~)}Q_CWOq zm{sC|j*0OCa9%`R<$@mQ>b@EDn+NH7m^aCKDyKV=+6BEc&*ER64e03soICLbpn!&mAtM-Sg*r`aQX)h)!mrM>dFF+K@a0u2V7FSS0qM7It$mM z+5=rVfXwNQq4q5^XVTgI`^5dZ{rjm+uU_K*p-SeF|ET=dpbVyH*yV#)Wlmo7CZH$fW{3I!<$8L^mb8Q;E2`*Q16O6}iY=+~xaPfAKlqXYmEgkCZD z-fqAE+WCjy)4})Or?@q1AhTB35in#1gCVNL9NzTgjveW8Wn~?jPcq)8Kl&(jdtw|N zSQBw9%ja~ZAJ-J%3hZ!$pv%oCJ<8d8RHSR5HzubaD=wlkM4B@GTcOBrDu;4&g>Xv> zXBw)8k*$05pt#gjs=qlVM0~21Pj)Mo&hnd5vEu4Jp<{a@&ff%yG6li%LjmycQ z)^Hxx3SXH)hu(XS#%#X+{?myQ>FmC}G;G=1;(1clMfx~1lTH+FsYCAy9~)N%uwPe* z1x$K(6woq9xZ1f-zxU3BKAqc7=FO*P0K(JgFI8e+zUQ`TIB0<4(el%+~U4mV2LFR603+wYeFdaK4+mbtdx^Vbi zix*SRNt1+)tUtdWg#9^LR75-8c%3RC>&^IhD$UFF)lcMd18@_3$FGo!I+|$*eLs3{ zOc8ggq*xfu^k=anNYKmk^Mt2eF0T;p)tbetR#E>cQ|Q3TmDFX_DBAS$6nbRA0?2mg zLPfclJ2vMN+Piu+?O(l$x(pdg=|hJI#yFl|AU5(p>(OLpOhTg@5Dr*$F-^|A-7$| zV8J+#Eq|9r9Swe*z7a_&+P)7i`TdU3JgKV81471IBcMx%eiyBw^bO=4mlE_cLs1Bu+IAD zo)vRdIWfvB34M9oAvla9f*~?CB!l9=7cWwn?*VLN9T)qu zlKR5Ar=Pzd7-G}Zm&JHj1VlbcO8|)NdiSOrY!jWtw#!zr#4#+}2p z8oe#_y$YN8dkg8fmbPEIJf(BE0b19CG!O+kgM9t>TJhH!o9W7^217UK++N7dqc;)8 zbJ5dbi~{F(czzvyj^ldDs*TzTz_=9yfRN@Sy%6EqpwmgkB_ajv0=-}5>R%p9xEEq> zD>%n2j2pXl9bE(+v#!UmvAJ{SQr5ynLZ8|A-X0su8+Z^A%B=Bk$O~%GV%o#&Fl%u; zjB7~h%)m166_8o~2foZ4fYUu7OPj%HyWinldq_WsJYEGf}dxm%p~d?uJ)R$pTUYNSWS zX>_R+R@823;~mKWAt^6Gehj$@*N8`eeg#*~vD_bi>aF@XRd6 zb9wwXFpsI;lJ+ISnN!d!@h(n})OzUMJgO<|uwKTyj=R;PcaH4(%6`Y7=o^6^HX=sl za92}C85C5M9Y|GMk@wZ9!CW&&+fUN$HiN`5g4C`X61m##uMwk;Xs)Nf@zO9xegU4~LDa z)%Gdm=nhBx-Q27j07Ug1yKIfZZ^n__28wMNmPx(~A#zyLU?FBVwWA-Vtn002ovPDHLk FV1jw-mZ$&# literal 0 HcmV?d00001 diff --git a/public/images/eems-test-widget-button.png b/public/images/eems-test-widget-button.png new file mode 100644 index 0000000000000000000000000000000000000000..03f673fefd6592f85f765708265d4e6c5bc8d027 GIT binary patch literal 2822 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 literal 0 HcmV?d00001 diff --git a/public/images/eems-widget-button.png b/public/images/eems-widget-button.png new file mode 100644 index 0000000000000000000000000000000000000000..9cd24da7237e6871e6aae0005e066a45ea098709 GIT binary patch literal 2442 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 a00RK_^zIw85}(lk0000Li literal 0 HcmV?d00001 diff --git a/public/images/h_loading.gif b/public/images/h_loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..bd548bbfcd7d4640a2109dda838fbeb656e2d8b5 GIT binary patch literal 5267 zcmds*dpOkj9>;$_%rH#Mg~23dh7dw!yWs5d`;F3_RNMBbP3dNJQ7WO_#xP?>VrXa# zn#sL#ElP#PmYS?ecim9g*6Ha;?Y3*rp3lsrI_K;k`#k6Tl|MWl&yQz*@7MSB`+VNN zIdf;voE0`7AtC(;qT0_Jy4O28JknEIJ@R5mB@~T392os|w70H7mCPG@*gN{q(cY5{ z$k-SA50ga!OG9w~pn$3Fp86>Kp8d{%L_|ywH_bJge~TDoZSS00b#~@YSI_*xim~Db zSQ&ni*OR}fN>HgBofqZPk1w&JitWNosM zlfmj5Y2<8=b5ClO6C@{%bk(rab^_wre zXB>8;I~5d7Rm>_OmdeT%?BgeDYU}E)sP8|d^~Vp%IEr6evl~S*RmrT4w7=ZFQf<&LZ}1Zt43Z9^;A~3 zY>kGbB}|8bb1Dc=oh=#a>XA+Ng0ZMw&5uV$AX5k*>s%Vh|;k7##2}``^?#flTD{v z>b`HaXjhROQmDCU6%`qWP_&3r8Ya5`ptP_Le<)L+*8P&lp;Tj|uyuk>JNE9feEdr^ zzHRi?FLb@E#pe=!d*Lgv7qs&dS90db0&CZHu-{4-9vgAGSl#|e%!99IAXNLqGB z127GS%WB|My*yOr;s%_Q-o5|>4l|I06AoP5_*YzrvFE(oZ_Zsk7SLRlX=Py+FCa zpmg_@)Majxz&R^Az1)RamQg4&JA%zm(hKz|!<8^evonOhK=iKjz4GTvEl zOAcp|ttm>3vml}j3Vta6aA8r2v>^OwF?OWFb24iSYoh0*bW4^AY3o^+MRXe*G{e&b zZTG<2E_7V%ywa}1ez3WWsc(2XrVB-x<4yEfNP4a(1yxj{rqzSxyH*#z#3}z6dV~F( zKkRHep5seNZdVg5BZ6w8^fww2%@SLTF8BMdF7`Kdy_X*ts3=UgU`4mC2;dXURxPOv z?W{X`c?==*&k*WV12!L213!QqWDSV}JjfULLGHj3m;p$j3ygKn0Xz^(XWoDC10;Y) z$4`SfPX^$D99RhOfH&|1#o<8wMQopIaE3<194e>cHZoLad|$T(hz9!0Gj9F31?Ikuh5%nX5HX9`qzLod4j_E8=kPL;US<)V?s=UrV;vu-o<7&UZq$ zqlO(aZ^GjNlGWfNJ7N4Nxh=8wN)8sqitdg90~xjT5irtaae(o2 ze~p0UJAHh283kZ-Tt|}A(gZ&>Qj)nv3o0I5LJ%77mW?g_9d~WG#=oVQMzy%qG|Ze} z7o+h|vIjJb*+ZMe&cY&!)9yWcNKA7rId#_= zW~1w=^{LcSvy9`4R5U%i*zpj#lO~p{Xm&@G{rJO*duZ>-qb=*d-50JH9kZ+wWau>IILrMZ(bRH9dZ7jI9GK-6GY2bs*sEvF=mWb zLa9c=7=?Z?=yymz6{B%bs&R1dRhRISHcc2X#ID`g#W$_; zq(~~z2l!)gxTmAEKql8ps0@Ba9L(x}_;zqt66p7i?qz_tx|&Ys#;^CaZ+EicCV()8JVQ@Z{z*n z`4_n!F)`An)h6t~kxogUn2fOeBX4fh>bFd*UjmPh!^1K)4IGTuI4F$+2jk&7YXQL=Wh{m=AfADXs&cAISTZVWm(=tJ`mdX>rLPTk)zocMHz?C*=Nll#% z{bapv;+<-PVJO##Sjn2=n4okCzj!(eHI;&Y2l!h5;`$$gb&ks7BTA%H?{(FZp~1L_ zFx%e%UfU}VajQQewOBg`Q+K;Jb*Jc|6?(B0Jzjh5T*8cPtZ0fffS>9<0mk1q4o08j z^n3p4^z$yd5^}ITcy4N;r7G&C%$vt4x$!2sBfM|M0-Hm(RTormRvP)|9_U-UEgYtx z(GuqSk~_FE1%9GP6+5h-7fx!frojt7*o~gMtew?iugxo9I6_{22dT9iX5s z4}}KzzB=6M)zoZlX@E^13IuHWP!V*SzV0oDt~Wq2{7@I5=s^kiSQMaVK#hlbSRjMi zp(}&X58xHRr@R0E_%rdkU&e>(5b0eM#J)7MJuHTBRj~br$#ZBZ@mp5hzJ#Qd*!cZeVls~}W*iVQ*i-}|&>5x$1(qZR9i>oA zanmxiP+3}DS$+IySp`-_IfgaXHq|p&OzV7WLxx@c<*!pwT8^#YAc}Si6m4-g@oQNb z_pt|gCeR-zJVng!gl$^i_8q?J&W5$a_9L&}Vy|%$&64OP#V(qr`3BQm7U=a&Ffy}x zJ6RSOJa=rtqDL8OSqI^*w%VVhA@9u~YUL}6dFXBpW*3t11}&?#nZ(xq~cam z0t*wHn*_r~2?>b_J9v0F?3i2F9~&QFoWU)Sz`%ZiL2p%pvLsMFgQu&X%Q~loCIE4I BEpPw; literal 0 HcmV?d00001 diff --git a/public/images/icon_dashboard_sort_desc.png b/public/images/icon_dashboard_sort_desc.png new file mode 100644 index 0000000000000000000000000000000000000000..d327e1ff2b43abdc60dd6b66f60221c97fdc5bc5 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^d_c^~!3-pw0(BLDlw^r(L`iUdT1k0gQ7VIDN`6wR zf@f}GdTLN=VoGJ<$y6JlBDMgZ5ZC`;5L)v!2Pnc+666>BpW*3t11}&?+0(@_q~cam zLIb0lKth7cGsc}g9yS8g1rIV8OFp!5YhYkzV69-#`}ImF0H~b7)78&qol`;+0IBCN A4FCWD literal 0 HcmV?d00001 diff --git a/public/images/icon_edit_10x10.png b/public/images/icon_edit_10x10.png new file mode 100644 index 0000000000000000000000000000000000000000..0a270f3d58b30bfad0f742065e5a5b6c0d820bb6 GIT binary patch literal 3025 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 T9Z<@Z00000NkvXXu0mjffPt6s literal 0 HcmV?d00001 diff --git a/public/images/icon_list_hide.png b/public/images/icon_list_hide.png new file mode 100644 index 0000000000000000000000000000000000000000..c95a63b4c504f551b42ccac37ce3edccabcc31c8 GIT binary patch literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>3?#4ne^UZdk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*A&x0(?ST|NsAQY-|i9f5_#B0tJ~%g8YL2|7W;FVdQ&MBb@04pFd AjQ{`u literal 0 HcmV?d00001 diff --git a/public/images/icon_list_show.png b/public/images/icon_list_show.png new file mode 100644 index 0000000000000000000000000000000000000000..2448d86dca760dfbaeb85363bd2fff6994860db6 GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>3?#4ne^UZdk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*A&x0(?ST|NsAQY-|i9f5_#B0tJ~%g8YL2|7W;iffF38W=oX{an^L HB{Ts5Qh71< literal 0 HcmV?d00001 diff --git a/public/images/icon_save_10x10.png b/public/images/icon_save_10x10.png new file mode 100644 index 0000000000000000000000000000000000000000..8a9c2186b6f9bf9daf6a512c0e4ab0594b75313b GIT binary patch literal 3011 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 AQ~&?~ literal 0 HcmV?d00001 diff --git a/public/images/sulair.png b/public/images/sulair.png new file mode 100644 index 0000000000000000000000000000000000000000..7738b9fc5b40ea7533fd281e145b7acd34e2986c GIT binary patch literal 7689 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 DWG?}c literal 0 HcmV?d00001 diff --git a/public/images/widget_tab_bg.png b/public/images/widget_tab_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..8815e1cbf8afa07f460d4e27ba10a41841a973eb GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^Oh7Ed0V0oZ{G0)#BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrH1%nR>c7hE&{2irJba%FN9C`2YX^jUuN%KR@p%A}lQ2k(83+ s^625>4Xa#4TRv?FRH)?AXkcJwc>IMUBrIdbW}pEKp00i_>zopr00uKK7XSbN literal 0 HcmV?d00001 diff --git a/public/javascripts/browser-widget-dialog.js b/public/javascripts/browser-widget-dialog.js new file mode 100644 index 0000000..418f985 --- /dev/null +++ b/public/javascripts/browser-widget-dialog.js @@ -0,0 +1,161 @@ + +var EEMsWidget = { + popup_width : 490, + popup_height : 510, + + showPopOver : function(baseUrl){ + if (document.getElementById('eems_popup') != null) { + document.getElementById('eems_popup').style.display='inline'; + return; + } + + var popup = document.createElement('div'); + var popup_style = 'overflow: auto; top: 10px; position: fixed; text-align: left; ' + + 'font: normal normal normal 9px/1.5 Tahoma,Arial; z-index: 100000; border-color: 1px solid blue'; + popup.id = 'eems-popup'; + popup.setAttribute('style', popup_style); + popup.style.left = (document.body.clientWidth - this.popup_width - 10) + 'px'; + popup.style.width = this.popup_width + 'px'; + popup.style.height = this.popup_height + 'px'; + popup.onmouseover = function() { popup.style.cursor = 'move'; } + popup.onmousedown = function() { popup.style.cursor = '-moz-grabbing'; } + popup.onmouseup = function() { popup.style.cursor = ''; } + + var popup_container = document.createElement('div'); + var popup_container_style = 'position: absolute; top: 0; left: 0; ' + + 'overflow: auto; background-color: #660000; opacity: 0.6; filter: alpha(opacity=60); ' + + '-moz-border-radius: 10px; -webkit-border-radius: 10px'; + popup_container.setAttribute('style', popup_container_style); + popup_container.style.width = this.popup_width + 'px'; + popup_container.style.height = this.popup_height + 'px'; + + var popup_content = document.createElement('div'); + var popup_content_style = 'top: 15px; left: 15px; clear: both; position: absolute; margin: 0;' + + 'border: 1px solid #666; -moz-border-radius: 5px; -webkit-border-radius: 5px; background-color: #f6f2f6;'; + popup_content.id = 'popup_content'; + popup_content.setAttribute('style', popup_content_style); + popup_content.style.width = (this.popup_width - 32) + 'px'; + popup_content.style.height = (this.popup_height - 32) + 'px'; + popup_content.style.backgroundColor = '#fff'; + + var close_link = document.createElement('a'); + var close_link_style = 'background-color: #a46666; color: #fff; font-weight: bold; position: absolute; -moz-border-radius: 3px; -webkit-border-radius: 3px;' + + 'right: 6px; top: 6px; padding: 0 5px 2px 5px; text-decoration: none; border-width: 0;'; + close_link.setAttribute('style', close_link_style); + close_link.setAttribute('href', '#'); + close_link.setAttribute('onmouseover', "this.style.backgroundColor = '#770000';"); + close_link.setAttribute('onmouseout', "this.style.backgroundColor = '#a46666';"); + close_link.setAttribute('onclick', "javascript:(function(){elemWidget=document.getElementById('eems-popup');elemWidget.parentNode.removeChild(elemWidget);}())"); + + close_link.innerHTML = 'x'; + popup_content.appendChild(close_link); + + var iframeForm = document.createElement('iframe'); + iframeForm.style.width = (this.popup_width - 50) + 'px'; + iframeForm.style.height = (this.popup_height - 40) + 'px'; + iframeForm.style.overflow = "hidden"; + iframeForm.style.marginLeft = '5px'; + iframeForm.style.borderWidth = 0; + iframeForm.id = 'iframeForm'; + iframeForm.name = 'iframeForm'; + iframeForm.src = baseUrl + '/eems/new?referrer=' + parent.location.href; + + popup_content.appendChild(iframeForm); + + popup.appendChild(popup_container); + popup.appendChild(popup_content); + + if (document.getElementsByTagName('body')[0] != null && document.getElementsByTagName('body')[0] != undefined) { + document.getElementsByTagName('body')[0].appendChild(popup); + } else { + alert('Sorry, EEMs widget cannot be opened in this page'); + return; + } + + this.setupWidgetDragging(); + }, + + setupWidgetDragging : function() { + var attrs = { + isDragged : false, + left : 0, + top : 0, + drag : { left : 0, top : 0 }, + start : { left : 0, top : 0 } + }; + + var dragLimits = { + left : { min : 0, max: (this.f_clientWidth() - this.popup_width) }, + top : { min : 0, max: (this.f_clientHeight() - this.popup_height) } + }; + + var elemPopup = document.getElementById('eems-popup'); + + var startWidgetMove = function(event) { + if (!event) event = window.event; // required for IE + + attrs.drag.left = event.clientX; + attrs.drag.top = event.clientY; + attrs.start.left = parseFloat(elemPopup.style.left.replace(/px/, '')); + attrs.start.top = parseFloat(elemPopup.style.top.replace(/px/, '')); + attrs.isDragged = true; + }; + + var processWidgetMove = function(event) { + if (!event) event = window.event; // required for IE + + if (attrs.isDragged) { + attrs.left = attrs.start.left + (event.clientX - attrs.drag.left); + attrs.top = attrs.start.top + (event.clientY - attrs.drag.top); + + if (attrs.left < dragLimits.left.min) attrs.left = dragLimits.left.min; + if (attrs.left > dragLimits.left.max) attrs.left = dragLimits.left.max; + if (attrs.top < dragLimits.top.min) attrs.top = dragLimits.top.min; + if (attrs.top > dragLimits.top.max) attrs.top = dragLimits.top.max; + + if (attrs.top < 0) attrs.top = 10; + if (attrs.left < 0) attrs.left = 10; + + elemPopup.style.cursor = '-moz-grabbing'; + elemPopup.style.left = attrs.left + 'px'; + elemPopup.style.top = attrs.top + 'px'; + } + }; + + var stopWidgetMove = function(event) { + attrs.isDragged = false; + elemPopup.style.cursor = ''; + }; + + elemPopup.onmousedown = startWidgetMove; + elemPopup.onmousemove = processWidgetMove; + elemPopup.onmouseup = stopWidgetMove; + elemPopup.onmouseleave = stopWidgetMove; + elemPopup.onmouseout = stopWidgetMove; + elemPopup.ondragstart = function() { return false; } // for IE + }, + + // From -- http://www.softcomplex.com/docs/get_window_size_and_scrollbar_position.html + f_clientWidth : function() { + return this.f_filterResults ( + window.innerWidth ? window.innerWidth : 0, + document.documentElement ? document.documentElement.clientWidth : 0, + document.body ? document.body.clientWidth : 0 + ); + }, + + f_clientHeight : function() { + return this.f_filterResults ( + window.innerHeight ? window.innerHeight : 0, + document.documentElement ? document.documentElement.clientHeight : 0, + document.body ? document.body.clientHeight : 0 + ); + }, + + f_filterResults : function(n_win, n_docel, n_body) { + var n_result = n_win ? n_win : 0; + if (n_docel && (!n_result || (n_result > n_docel))) n_result = n_docel; + return n_body && (!n_result || (n_result > n_body)) ? n_body : n_result; + } + +} diff --git a/public/javascripts/browser-widget.js b/public/javascripts/browser-widget.js new file mode 100644 index 0000000..2870806 --- /dev/null +++ b/public/javascripts/browser-widget.js @@ -0,0 +1,445 @@ +var token = window._token; +var content_file_id; +var timeoutId = 0; +var delayedJobTimeout = 0; +var data = "ABBASI ABBOTT ABERCROMB ABRAHAMSW ACKERMANR AFRICA AFRICADOC AHNE ANTHRO AREASTREF ARONSONB ARSGIFT ARTARCH ASHENR ASUL ASULMEM ASULRES AVERYC BACKERM BACONR BAILEYT BALDRIDGE BALLARDE BANDETH BARCHASS BARKSDALE BARNESJ BARONJ BELLD BENDERA BENDERRM BENNETT BERNSTEIN BIOLOGY BIOLOGY2 BOOKEXCH BOYLESE BRANGIFT BRASCHF BREERC BRICKERF BROOKSC BRUSHIST BRUSHISTI BRUSLIT BRUSLITI BURDJ BURNELLG BURROWSD CAFAROC CAGLEA CAMPBELLR CANFIELDD CARNOCHAN CELEBFUND CHAMBERLA CHEM CHEMGIFT CHICANO CHINAHV CHINART CHINEXCHG CHJPDOC CHUCKF CLASS1965 CLASS1970 CLASSICS CLEBSCH CLEBSCHW CLEMENTM COCHRANS COFFINJ COHNA COMM COMPRACEC CONNOLLYG COTRELLE CRAIGW CRARYG CROWL CULTSTUD CUSICKJ DATAFILE DATAFILE2 DAVESD DAVISJ DENNINGR DIGICORP DINSMOREM DIROFF DOBBSMELT DONNELLD DUPSOLD DUTTONR EALJAPAN EALMOORE EARTHSCI EARTHSCIM EASTASIA EASTASTVI EBOOKS ECON EDGRENR EDUC EEURDOC EGGERE ELIOTM ENG ENGGIFT ENVIROPOP ESBENSHAD FALCONERF FEHRENBAC FELTONC FEMINIST FIELD FILMPERF FITGERWIL FITZHUGHW FLAHERTYM FORDJ FORDOC FORLANGI FOXS FRANKENST FRANKLHUM FRIENDE FRITDOC FRYBERGER GALLAGHER GALTCOBL GAYLES GEBALLET GENACQ GENGIFT GENREF GENSER GERMANIC GIFTSPPRO GOLDMANR GOLDSTEIN GOODANB GORDONA GOVINFOI GREENC GREENW GROMMONA GUNSTM GWAUDOC HADERLIEE HAEFNERJ HAMMONDA HANNA HANNAP HARRASSOW HARRISJ HARRISL HAUGH HAWES HAYFER HEARST HENMULLER HERRICK HERTLEINL HERTLEINM HESS HILLMAN HIRSCHMAN HISTSCI HMSHO HOBSON HOPKINS HORTON HOWELL1 HOWELL2 HUMINST HUMREF HVACQ INFOACC INFOACCDS INSTSOFTW INTLDOC JACKMANC JACKMAND JACOBSONM JAGELS JAPANHV JAY JEWELFUND JEWISH JOHNSONJ JOHNSONW JONESR JONESW JORDAN KARLE KATSEV KATZEVA KATZEVS KAY KAYSTEEVE KEMBLE KENNEDY KIBLER KIM KIMBALL KITTLE KLEINH KLEINM KLINEROET KNAPPI KNAPPJ KOOPERMAN KOREAN KORFDNGR KUMM KUMMA KUMMFF KUMMFG KUMMH KUMMM KUMMS KWOKB KWOKL LANEWEST LANZ LATAM LATAMIDOC LEITER LEWIS LINDER LINGUIST LIS LITFRANKL LITTAUER LITTPOET LMBSP LOWENTHAL LYMAN LYMANAWAR LYNCH MAHARAM MARKUS MARTINEAU MASON MATHCOMP MCCAMENT MCCULLOUG MCDOWELL MEDIAACQ MEDIAINST MEDIARES MEDIEVAL MEMFUND MEYERBORE MEYERRES MEYERRES2 MGATES MIDEAST MILNE MIRRIELEE MIZOTA MONOORD MORGAN MORRISON MOSER MOULTON MUFFLEY MULLERMCC MUNGER MUNRO MUSIC MUSICARS MUSICSCO MUSICSOU MYERS NIELSEN NISHINO NOFUND NUGGETS OBERLIN OFFEN PAGE PAYNEB PAYNER PECK PERFARTS PERRETTE PERRY PETERSONG PHILBRICK PHILOSOPH PHYSGIFT PHYSICS POLISCI PORTWOOD POYET PRICE PROTHRO PSYCH QUILLEN RASMUSSEN REHNBORGC REHNBORGJ REINERT RELIGION RESEVAL RESMAT RESMATBK RESMATCEN RESMATEND RESMATNEY RESMATOPP RITTER RIXON ROBUSTELL ROMANCE ROSE ROSENBAUM ROSENBERG ROSENBLAT ROSS RUEEUR SAMSON SASIA SASIABUDD SCHIRESON SCITECHI SEASIAPAC SHAFTEL SHARP SHARPS SHAVER SHELDON SHOUP SIEVERS SILIGENE SIMPSON SKINNER SMALL SMORTCHEV SNEDDEN SOARES SOC SOCSCII SOCSCIREF SOWERS SPAPOR SPEC SPECGIFT STAFFREF STANDISH STARLING STATEDOC STEEL STEINBERG STEINMETZ STEVENS STRUBLE SUNDERMEY TANENBAUM TANNERMEM TANNERREL TARBELL TAXACCREV THOMASC THOMASR THOMPSONP THOMPSONW THORPE TIERNAN UARCH UARCHGIFT UKCANDOC ULIBRES USDOC VANWYCK VICKERS1 VICKERS2 VONSCHLE WALLER WARREN WATT WEBB WEBSTER WEISS WEURSOC WEYBURN WHITEHEAD WICKERSHA WIEL WIGGINS WILSON WOOD WOODBURN WOODYATT WREDENB WYATT YOUNGH YOUNGHMAP ZALK ZENOFF".split(" "); +var allowedFileTypes = "pdf doc docx xls xlsx ppt ppts txt"; +var defaultValues = { + note: "(Citation, comments, copyright notes, etc.)", + payment_fund: "(Fund name)" +}; +var dateFormatMask = "yyyy-mm-dd'T'HH:MM:sso"; +var msgInvalidFileType = "File to upload is of invalid type.\n Allowed file types are - \n.pdf, .doc, .docx, .xls, .xlsx, .ppt, .ppts, .txt"; + +$(document).ready(function() { + // payment fund + $('#eem_bw_payment_fund') + .autocomplete(data) + .change(function() { toggleSendToTechServices('#eems-browser-widget'); }) + .focus(function() { + if ($('#eem_bw_payment_fund').val() == defaultValues.payment_fund) { + $('#eem_bw_payment_fund').val(''); + } + }); + + $('#eem_dw_payment_fund') + .autocomplete(data) + .change(function() { toggleSendToTechServices('#eems-desktop-widget'); }) + .focus(function() { + if ($('#eem_dw_payment_fund').val() == defaultValues.payment_fund) { + $('#eem_dw_payment_fund').val(''); + } + }); + + // payment type + $('#eem_bw_paymentType').change(function() { + if ($('#eem_bw_paymentType').val() == 'Paid') { $('#eem_bw_payment_fund').show(); } + else { $('#eem_bw_payment_fund').hide(); } + + + toggleSendToTechServices('eems-browser-widget'); + }); + + $('#eem_dw_paymentType').change(function() { + if ($('#eem_dw_paymentType').val() == 'Paid') { $('#eem_dw_payment_fund').show(); } + else { $('#eem_dw_payment_fund').hide(); } + + toggleSendToTechServices('eems-browser-widget'); + }); + + // note + $('#eem_bw_note').focus(function() { + if ($('#eem_bw_note').val() == defaultValues.note) { + $('#eem_bw_note').val(''); + } + }); + + $('#eem_dw_note').focus(function() { + if ($('#eem_dw_note').val() == defaultValues.note) { + $('#eem_dw_note').val(''); + } + }); + + // title + $('#eem_bw_title').change(function() { + toggleSaveToDashboard('eems-browser-widget'); + toggleSendToTechServices('eems-browser-widget'); + }); + + $('#eem_dw_title').change(function() { + toggleSaveToDashboard('eems-desktop-widget'); + toggleSendToTechServices('eems-desktop-widget'); + }); + + // contentURL (for browser widget) + $('#contentUrl') + .hover(function() { + toggleSaveToDashboard('eems-browser-widget'); + toggleSendToTechServices('eems-browser-widget'); + }) + .change(function() { + toggleSaveToDashboard('eems-browser-widget'); + toggleSendToTechServices('eems-browser-widget'); + }); + + // content_upload (for desktop widget) + $('#content_upload').change(function() { + toggleSaveToDashboard('eems-desktop-widget'); + toggleSendToTechServices('eems-desktop-widget'); + }); + + // copyright status + $('#eem_bw_copyrightStatus').change(function() { + toggleSendToTechServices('eems-browser-widget'); + }); + + $('#eem_dw_copyrightStatus').change(function() { + toggleSendToTechServices('eems-desktop-widget'); + }); + + // payment fund + $('#eem_bw_payment_fund').change(function() { + toggleSaveToDashboard('eems-browser-widget'); + toggleSendToTechServices('eems-browser-widget'); + }); + + $('#eem_dw_payment_fund').change(function() { + toggleSaveToDashboard('eems-desktop-widget'); + toggleSendToTechServices('eems-desktop-widget'); + }); + + // save to dashboard - browser widget + $('#eem_bw_save_to_dashboard').click(function() { + var selectorName = $('#eem_selectorName').val(); + var logMsg = 'Request created by ' + selectorName; + var pars = 'eem[status]=Created'; + submitEEM(pars, logMsg); + }); + + // send to tech services - browser widget + $('#eem_bw_send_to_tech_services').click(function() { + var selectorName = $('#eem_selectorName').val(); + var logMsg = 'Request submitted by ' + selectorName; + //var pars = 'eem[requestDatetime]=' + dateFormat(dateFormatMask); + var pars; + submitEEM(pars, logMsg); + }); + + // save to dashboard - desktop widget + $('#eem_dw_save_to_dashboard').click(function() { + var selectorName = $('#eem_selectorName').val(); + var logMsg = 'Request created by ' + selectorName; + var pars = 'eem[status]=Created'; + submitEEMDesktopUpload(pars, logMsg); + }); + + // send to tech services - desktop widget + $('#eem_dw_send_to_tech_services').click(function() { + var selectorName = $('#eem_selectorName').val(); + var logMsg = 'Request submitted by ' + selectorName; + var pars = 'eem[requestDatetime]=' + dateFormat(dateFormatMask); + submitEEMDesktopUpload(pars, logMsg); + }); + +}); + +// submit EEM object (desktop upload) +function submitEEMDesktopUpload(pars, logMsg) { + var selectorName = $('#eem_selectorName').val(); + + var options = { + target: '#upload_target', + timeout: 180000, + beforeSubmit: function() { + $('#eems-desktop-widget').fadeOut('slow'); + $('#eems-loader').show(); + + if ($('#eem_dw_note').val() == defaultValues.note) { $('#eem_dw_note').val(''); } + if ($('#eem_dw_payment_fund').val() == defaultValues.payment_fund) { $('#eem_dw_payment_fund').val(''); } + + $('#eem_dw_statusDatetime').val(dateFormat(dateFormatMask)); + $('#eem_dw_copyrightStatusDate').val(dateFormat(dateFormatMask)); + $('#eem_dw_requestDatetime').val(dateFormat(dateFormatMask)); + }, + success: function(data) { + var data = stripHTMLTags(data); + var eem_pid = data.replace(/^eem_pid=/, ''); + + if (eem_pid != null && eem_pid != undefined && (/^druid:\w{11}/.test(eem_pid))) { + window._pid = eem_pid; + $('#eems-loader').hide(); + $('#details-link').attr('href', '/view/' + trimmedId(eem_pid)); + $('#eems-success').show(); + $('#eems-links').show(); + + // if 'Send to Technical Services' button is pressed + if (/Request submitted by/.test(logMsg)) { + sendToTechServices(eem_pid); + } + } + else { + showPDFErrorMsg(); + } + }, + error: function() { showEEMsErrorMsg(); } + }; + + $('#eems-desktop-widget').target = 'upload_target'; + + if (isValidFileType($('#content_upload').val())) { + $('#eems-desktop-widget').ajaxSubmit(options); + } + else { + alert(msgInvalidFileType); + } + + return false; + +} + +// submit EEM object (browser upload) +function submitEEM(pars, logMsg) { + pars = pars + '&eem[statusDatetime]=' + dateFormat(dateFormatMask); + pars = pars + '&eem[copyrightStatusDate]=' + dateFormat(dateFormatMask); + + if ($('#eem_bw_note').val() == defaultValues.note) { $('#eem_bw_note').val(''); } + if ($('#eem_bw_payment_fund').val() == defaultValues.payment_fund) { $('#eem_bw_payment_fund').val(''); } + + var eem_data = $('#eems-browser-widget').serialize() + '&' + pars; + + if ($('#contentUrl').val() == '') { + showLoader(); + createEEM_WithoutPDF(eem_data, logMsg); + } + else { + if (isValidFileType($('#contentUrl').val())) { + showLoader(); + createEEM_WithPDF(eem_data, logMsg); + } + else { + alert(msgInvalidFileType); + } + } + + return false; + } + +// create EEM with PDF +function createEEM_WithPDF(eem_data, logMsg) { + $.ajax({ + url: '/eems', + type: 'POST', + datatype: 'json', + timeout: 10000, + data: eem_data, + success: function(eem) { + $('#eems-loader').hide(); + $('#eems-upload-progress').show(); + + if (eem != null && eem.eem_pid != null && (/^druid:\w{11}/.test(eem.eem_pid))) { + window._pid = eem.eem_pid; + + var selectorName = $('#eem_selectorName').val(); + + $('#details-link').attr('href', '/view/' + trimmedId(eem.eem_pid)); + content_file_id = eem.content_file_id; + + update(); + + // if 'Send to Technical Services' button is pressed + if (/Request submitted by/.test(logMsg) && eem.content_file_id != undefined) { + sendToTechServices(eem.eem_pid); + } + } + else { + showPDFErrorMsg(); + } + }, + error: function() { showEEMsErrorMsg(); }, + }); +} + +// create EEM without a PDF +function createEEM_WithoutPDF(eem_data, logMsg) { + $.ajax({ + url: '/eems/no_pdf', + type: 'POST', + datatype: 'json', + timeout: 10000, + data: eem_data, + success: function(eem) { + if (eem != null && eem.eem_pid != null && (/^druid:\w{11}/.test(eem.eem_pid))) { + $('#details-link').attr('href', '/view/' + trimmedId(eem.eem_pid)); + $('#eems-loader').hide(); + $('#eems-success').show(); + $('#eems-links').show(); + } + }, + error: function() { showEEMsErrorMsg(); }, + }); +} + +function showLoader() { + $('#eems-browser-widget').fadeOut('slow'); + $('#eems-loader').show(); +} + +// Error creating an EEM +function showEEMsErrorMsg() { + $('#eems-loader').hide(); + $('#eems-upload-progress').hide(); + $('#eems-error').html("Error creating EEM.").show(); +} + +// Error uploading PDF +function showPDFErrorMsg() { + var eem_pid = window._pid; + + $('#eems-loader').hide(); + $('#eems-upload-progress').hide(); + + var msg = + "Unable to upload the requested file. The EEMs record has been created and the request should not be tried again." + + "

What you can do:

" + + "
  1. Download the file to your desktop from the web site. It is likely that the way in which the web site delivers " + + "the file to the user makes it impossible for the EEMs application to upload it on your behalf.
  2. " + + "
  3. Upload the file into the existing request by going to the EEMs Dashboard, finding the "; + + if (eem_pid != undefined) { + msg = msg + "record of this request"; + } else { + msg = msg + "record of this request"; + } + + msg = msg + " and using the \'Browse\' button " + + "next to the empty \"Local copy of this file\" field.
  4. " + + $('#eems-error').html("
    " + msg + "
    ").show(); + + if (eem_pid != undefined) { + var pars1 = { 'authenticity_token': window._token, 'eem[status]': 'Created', 'eem[requestDatetime]': '' }; + eemUpdate(eem_pid, pars1); + + var selectorName = $('#eem_selectorName').val(); + var logMsg = 'File upload failure for ' + selectorName; + var pars2 = { 'authenticity_token': window._token, 'entry': logMsg }; + addLogEntry(eem_pid, pars2, false); + } + +} + +// Update file upload progress bar +function update() { + $.getJSON('/content_files/' + content_file_id, function(data) { + + if (data == null || data.attempts == 'failed') { + showPDFErrorMsg(); + return; + } + + var percent = parseInt(data.percent_done); + delayedJobTimeout += 500; // increment delayed job time by 0.5 sec + + if (!isNaN(percent)) { + $('#eems-percent-done').html(percent + ' %'); + $('#eems-progress-bar').css({'width' : (percent*3) + 'px', 'height' : '10px' }); + + if (percent == 100) { + $('#upload-progress-text').hide(); + $('#upload-complete-text').show(); + $('#eems-links').show(); + clearTimeout(timeoutId); + return; + } + } else { + // if delayedJobTimeout >= 10 sec, quit uploader process + if (delayedJobTimeout >= 10000) { + showPDFErrorMsg(); + clearTimeout(timeoutId); + return; + } + } + }); + + timeoutId = setTimeout(update, 500); +} + +// Submit to tech services +function sendToTechServices(pid) { + var pars = { 'authenticity_token': window._token }; + + $.ajax({ + url: '/eems/' + pid + '/submit_to_tech_services', + type: 'PUT', + data: pars, + success: function(status) { + }, + }); +} + +// Enable/disable 'Save to dashboard' button +function toggleSaveToDashboard(widgetName) { + if (widgetName == 'eems-browser-widget') { + if ($('#eem_bw_title').val() != '' && $('#contentUrl').val() != '' ) { + $('#eem_bw_save_to_dashboard').attr("disabled", false); + } + else { + $('#eem_bw_save_to_dashboard').attr("disabled", true); + } + } + + if (widgetName == 'eems-desktop-widget') { + if ($('#eem_dw_title').val() != '' && $('#content_upload').val() != '' ) { + $('#eem_dw_save_to_dashboard').attr("disabled", false); + } + else { + $('#eem_dw_save_to_dashboard').attr("disabled", true); + } + } +} + +// Enable/disable 'Send to Tech Services' button +function toggleSendToTechServices(widgetName) { + if (widgetName == 'eems-browser-widget') { + if ($('#eem_bw_title').val() != '' && $('#contentUrl').val() != '' && + (($('#eem_bw_paymentType').val() == 'Free' && ( $('#eem_bw_copyrightStatus').val() == 'Public access OK' || $('#eem_bw_copyrightStatus').val() == 'Stanford access OK')) || + ($('#eem_bw_paymentType').val() == 'Paid' && $('#eem_bw_payment_fund').val() != '' && $('#eem_bw_payment_fund').val() != defaultValues.payment_fund))) { + $('#eem_bw_send_to_tech_services').removeAttr("disabled"); + } + else { + $('#eem_bw_send_to_tech_services').attr("disabled", true); + } + } + + if (widgetName == 'eems-desktop-widget') { + if ($('#eem_dw_title').val() != '' && $('#content_upload').val() != '' && + (($('#eem_dw_paymentType').val() == 'Free' && ( $('#eem_dw_copyrightStatus').val() == 'Public access OK' || $('#eem_dw_copyrightStatus').val() == 'Stanford access OK')) || + ($('#eem_dw_paymentType').val() == 'Paid' && $('#eem_dw_payment_fund').val() != '' && $('#eem_dw_payment_fund').val() != defaultValues.payment_fund))) { + $('#eem_dw_send_to_tech_services').removeAttr("disabled"); + } + else { + $('#eem_dw_send_to_tech_services').attr("disabled", true); + } + } +} + +function trimmedId(id) { + if (id != null && id != undefined) { + return id.replace(/^druid:/, ''); + } + else { + return ''; + } +} + +function isValidFileType(url) { + var strRegex = allowedFileTypes.replace(/ /g, "|"); + strRegex = strRegex.replace(/\|$/, ""); + strRegex = "\\.(" + strRegex + ")$"; + + var regex = new RegExp(strRegex, "i"); + + if (regex.test(url)) { return true; } + + return false; +} diff --git a/public/javascripts/controls.js b/public/javascripts/controls.js new file mode 100644 index 0000000..ca29aef --- /dev/null +++ b/public/javascripts/controls.js @@ -0,0 +1,963 @@ +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005-2008 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +if(typeof Effect == 'undefined') + throw("controls.js requires including script.aculo.us' effects.js library"); + +var Autocompleter = { }; +Autocompleter.Base = Class.create({ + baseInitialize: function(element, update, options) { + element = $(element); + this.element = element; + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + this.oldElementValue = this.element.value; + + if(this.setOptions) + this.setOptions(options); + else + this.options = options || { }; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, { + setHeight: false, + offsetTop: element.offsetHeight + }); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if(typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + // Force carriage returns as token delimiters anyway + if (!this.options.tokens.include('\n')) + this.options.tokens.push('\n'); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (Prototype.Browser.IE) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); + }, + + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.selectEntry(); + this.hide(); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.hasFocus = false; + this.active = false; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + if(this.index > 0) this.index--; + else this.index = this.entryCount-1; + this.getEntry(this.index).scrollIntoView(true); + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++; + else this.index = 0; + this.getEntry(this.index).scrollIntoView(false); + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = $(selectedElement).select('.' + this.options.select) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var bounds = this.getTokenBounds(); + if (bounds[0] != -1) { + var newValue = this.element.value.substr(0, bounds[0]); + var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value + this.element.value.substr(bounds[1]); + } else { + this.element.value = value; + } + this.oldElementValue = this.element.value; + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.down()); + + if(this.update.firstChild && this.update.down().childNodes) { + this.entryCount = + this.update.down().childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + this.index = 0; + + if(this.entryCount==1 && this.options.autoSelect) { + this.selectEntry(); + this.hide(); + } else { + this.render(); + } + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + this.tokenBounds = null; + if(this.getToken().length>=this.options.minChars) { + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + this.oldElementValue = this.element.value; + }, + + getToken: function() { + var bounds = this.getTokenBounds(); + return this.element.value.substring(bounds[0], bounds[1]).strip(); + }, + + getTokenBounds: function() { + if (null != this.tokenBounds) return this.tokenBounds; + var value = this.element.value; + if (value.strip().empty()) return [-1, 0]; + var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); + var offset = (diff == this.oldElementValue.length ? 1 : 0); + var prevTokenPos = -1, nextTokenPos = value.length; + var tp; + for (var index = 0, l = this.options.tokens.length; index < l; ++index) { + tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); + if (tp > prevTokenPos) prevTokenPos = tp; + tp = value.indexOf(this.options.tokens[index], diff + offset); + if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; + } + return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); + } +}); + +Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { + var boundary = Math.min(newS.length, oldS.length); + for (var index = 0; index < boundary; ++index) + if (newS[index] != oldS[index]) + return index; + return boundary; +}; + +Ajax.Autocompleter = Class.create(Autocompleter.Base, { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + this.startIndicator(); + + var entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(Autocompleter.Base, { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
  5. " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  6. "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  7. " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  8. "); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)); + return "
      " + ret.join('') + "
    "; + } + }, options || { }); + } +}); + +// AJAX in-place editor and collection editor +// Full rewrite by Christophe Porteneuve (April 2007). + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +}; + +Ajax.InPlaceEditor = Class.create({ + initialize: function(element, url, options) { + this.url = url; + this.element = element = $(element); + this.prepareOptions(); + this._controls = { }; + arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! + Object.extend(this.options, options || { }); + if (!this.options.formId && this.element.id) { + this.options.formId = this.element.id + '-inplaceeditor'; + if ($(this.options.formId)) + this.options.formId = ''; + } + if (this.options.externalControl) + this.options.externalControl = $(this.options.externalControl); + if (!this.options.externalControl) + this.options.externalControlOnly = false; + this._originalBackground = this.element.getStyle('background-color') || 'transparent'; + this.element.title = this.options.clickToEditText; + this._boundCancelHandler = this.handleFormCancellation.bind(this); + this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); + this._boundFailureHandler = this.handleAJAXFailure.bind(this); + this._boundSubmitHandler = this.handleFormSubmission.bind(this); + this._boundWrapperHandler = this.wrapUp.bind(this); + this.registerListeners(); + }, + checkForEscapeOrReturn: function(e) { + if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; + if (Event.KEY_ESC == e.keyCode) + this.handleFormCancellation(e); + else if (Event.KEY_RETURN == e.keyCode) + this.handleFormSubmission(e); + }, + createControl: function(mode, handler, extraClasses) { + var control = this.options[mode + 'Control']; + var text = this.options[mode + 'Text']; + if ('button' == control) { + var btn = document.createElement('input'); + btn.type = 'submit'; + btn.value = text; + btn.className = 'editor_' + mode + '_button'; + if ('cancel' == mode) + btn.onclick = this._boundCancelHandler; + this._form.appendChild(btn); + this._controls[mode] = btn; + } else if ('link' == control) { + var link = document.createElement('a'); + link.href = '#'; + link.appendChild(document.createTextNode(text)); + link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; + link.className = 'editor_' + mode + '_link'; + if (extraClasses) + link.className += ' ' + extraClasses; + this._form.appendChild(link); + this._controls[mode] = link; + } + }, + createEditField: function() { + var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); + var fld; + if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { + fld = document.createElement('input'); + fld.type = 'text'; + var size = this.options.size || this.options.cols || 0; + if (0 < size) fld.size = size; + } else { + fld = document.createElement('textarea'); + fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); + fld.cols = this.options.cols || 40; + } + fld.name = this.options.paramName; + fld.value = text; // No HTML breaks conversion anymore + fld.className = 'editor_field'; + if (this.options.submitOnBlur) + fld.onblur = this._boundSubmitHandler; + this._controls.editor = fld; + if (this.options.loadTextURL) + this.loadExternalText(); + this._form.appendChild(this._controls.editor); + }, + createForm: function() { + var ipe = this; + function addText(mode, condition) { + var text = ipe.options['text' + mode + 'Controls']; + if (!text || condition === false) return; + ipe._form.appendChild(document.createTextNode(text)); + }; + this._form = $(document.createElement('form')); + this._form.id = this.options.formId; + this._form.addClassName(this.options.formClassName); + this._form.onsubmit = this._boundSubmitHandler; + this.createEditField(); + if ('textarea' == this._controls.editor.tagName.toLowerCase()) + this._form.appendChild(document.createElement('br')); + if (this.options.onFormCustomization) + this.options.onFormCustomization(this, this._form); + addText('Before', this.options.okControl || this.options.cancelControl); + this.createControl('ok', this._boundSubmitHandler); + addText('Between', this.options.okControl && this.options.cancelControl); + this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); + addText('After', this.options.okControl || this.options.cancelControl); + }, + destroy: function() { + if (this._oldInnerHTML) + this.element.innerHTML = this._oldInnerHTML; + this.leaveEditMode(); + this.unregisterListeners(); + }, + enterEditMode: function(e) { + if (this._saving || this._editing) return; + this._editing = true; + this.triggerCallback('onEnterEditMode'); + if (this.options.externalControl) + this.options.externalControl.hide(); + this.element.hide(); + this.createForm(); + this.element.parentNode.insertBefore(this._form, this.element); + if (!this.options.loadTextURL) + this.postProcessEditField(); + if (e) Event.stop(e); + }, + enterHover: function(e) { + if (this.options.hoverClassName) + this.element.addClassName(this.options.hoverClassName); + if (this._saving) return; + this.triggerCallback('onEnterHover'); + }, + getText: function() { + return this.element.innerHTML.unescapeHTML(); + }, + handleAJAXFailure: function(transport) { + this.triggerCallback('onFailure', transport); + if (this._oldInnerHTML) { + this.element.innerHTML = this._oldInnerHTML; + this._oldInnerHTML = null; + } + }, + handleFormCancellation: function(e) { + this.wrapUp(); + if (e) Event.stop(e); + }, + handleFormSubmission: function(e) { + var form = this._form; + var value = $F(this._controls.editor); + this.prepareSubmission(); + var params = this.options.callback(form, value) || ''; + if (Object.isString(params)) + params = params.toQueryParams(); + params.editorId = this.element.id; + if (this.options.htmlResponse) { + var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); + Object.extend(options, { + parameters: params, + onComplete: this._boundWrapperHandler, + onFailure: this._boundFailureHandler + }); + new Ajax.Updater({ success: this.element }, this.url, options); + } else { + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: params, + onComplete: this._boundWrapperHandler, + onFailure: this._boundFailureHandler + }); + new Ajax.Request(this.url, options); + } + if (e) Event.stop(e); + }, + leaveEditMode: function() { + this.element.removeClassName(this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this._originalBackground; + this.element.show(); + if (this.options.externalControl) + this.options.externalControl.show(); + this._saving = false; + this._editing = false; + this._oldInnerHTML = null; + this.triggerCallback('onLeaveEditMode'); + }, + leaveHover: function(e) { + if (this.options.hoverClassName) + this.element.removeClassName(this.options.hoverClassName); + if (this._saving) return; + this.triggerCallback('onLeaveHover'); + }, + loadExternalText: function() { + this._form.addClassName(this.options.loadingClassName); + this._controls.editor.disabled = true; + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + this._form.removeClassName(this.options.loadingClassName); + var text = transport.responseText; + if (this.options.stripLoadedTextTags) + text = text.stripTags(); + this._controls.editor.value = text; + this._controls.editor.disabled = false; + this.postProcessEditField(); + }.bind(this), + onFailure: this._boundFailureHandler + }); + new Ajax.Request(this.options.loadTextURL, options); + }, + postProcessEditField: function() { + var fpc = this.options.fieldPostCreation; + if (fpc) + $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); + }, + prepareOptions: function() { + this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); + Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); + [this._extraDefaultOptions].flatten().compact().each(function(defs) { + Object.extend(this.options, defs); + }.bind(this)); + }, + prepareSubmission: function() { + this._saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + registerListeners: function() { + this._listeners = { }; + var listener; + $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { + listener = this[pair.value].bind(this); + this._listeners[pair.key] = listener; + if (!this.options.externalControlOnly) + this.element.observe(pair.key, listener); + if (this.options.externalControl) + this.options.externalControl.observe(pair.key, listener); + }.bind(this)); + }, + removeForm: function() { + if (!this._form) return; + this._form.remove(); + this._form = null; + this._controls = { }; + }, + showSaving: function() { + this._oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + this.element.addClassName(this.options.savingClassName); + this.element.style.backgroundColor = this._originalBackground; + this.element.show(); + }, + triggerCallback: function(cbName, arg) { + if ('function' == typeof this.options[cbName]) { + this.options[cbName](this, arg); + } + }, + unregisterListeners: function() { + $H(this._listeners).each(function(pair) { + if (!this.options.externalControlOnly) + this.element.stopObserving(pair.key, pair.value); + if (this.options.externalControl) + this.options.externalControl.stopObserving(pair.key, pair.value); + }.bind(this)); + }, + wrapUp: function(transport) { + this.leaveEditMode(); + // Can't use triggerCallback due to backward compatibility: requires + // binding + direct element + this._boundComplete(transport, this.element); + } +}); + +Object.extend(Ajax.InPlaceEditor.prototype, { + dispose: Ajax.InPlaceEditor.prototype.destroy +}); + +Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { + initialize: function($super, element, url, options) { + this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions; + $super(element, url, options); + }, + + createEditField: function() { + var list = document.createElement('select'); + list.name = this.options.paramName; + list.size = 1; + this._controls.editor = list; + this._collection = this.options.collection || []; + if (this.options.loadCollectionURL) + this.loadCollection(); + else + this.checkForExternalText(); + this._form.appendChild(this._controls.editor); + }, + + loadCollection: function() { + this._form.addClassName(this.options.loadingClassName); + this.showLoadingText(this.options.loadingCollectionText); + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + var js = transport.responseText.strip(); + if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check + throw('Server returned an invalid collection representation.'); + this._collection = eval(js); + this.checkForExternalText(); + }.bind(this), + onFailure: this.onFailure + }); + new Ajax.Request(this.options.loadCollectionURL, options); + }, + + showLoadingText: function(text) { + this._controls.editor.disabled = true; + var tempOption = this._controls.editor.firstChild; + if (!tempOption) { + tempOption = document.createElement('option'); + tempOption.value = ''; + this._controls.editor.appendChild(tempOption); + tempOption.selected = true; + } + tempOption.update((text || '').stripScripts().stripTags()); + }, + + checkForExternalText: function() { + this._text = this.getText(); + if (this.options.loadTextURL) + this.loadExternalText(); + else + this.buildOptionList(); + }, + + loadExternalText: function() { + this.showLoadingText(this.options.loadingText); + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + this._text = transport.responseText.strip(); + this.buildOptionList(); + }.bind(this), + onFailure: this.onFailure + }); + new Ajax.Request(this.options.loadTextURL, options); + }, + + buildOptionList: function() { + this._form.removeClassName(this.options.loadingClassName); + this._collection = this._collection.map(function(entry) { + return 2 === entry.length ? entry : [entry, entry].flatten(); + }); + var marker = ('value' in this.options) ? this.options.value : this._text; + var textFound = this._collection.any(function(entry) { + return entry[0] == marker; + }.bind(this)); + this._controls.editor.update(''); + var option; + this._collection.each(function(entry, index) { + option = document.createElement('option'); + option.value = entry[0]; + option.selected = textFound ? entry[0] == marker : 0 == index; + option.appendChild(document.createTextNode(entry[1])); + this._controls.editor.appendChild(option); + }.bind(this)); + this._controls.editor.disabled = false; + Field.scrollFreeActivate(this._controls.editor); + } +}); + +//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! **** +//**** This only exists for a while, in order to let **** +//**** users adapt to the new API. Read up on the new **** +//**** API and convert your code to it ASAP! **** + +Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) { + if (!options) return; + function fallback(name, expr) { + if (name in options || expr === undefined) return; + options[name] = expr; + }; + fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' : + options.cancelLink == options.cancelButton == false ? false : undefined))); + fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' : + options.okLink == options.okButton == false ? false : undefined))); + fallback('highlightColor', options.highlightcolor); + fallback('highlightEndColor', options.highlightendcolor); +}; + +Object.extend(Ajax.InPlaceEditor, { + DefaultOptions: { + ajaxOptions: { }, + autoRows: 3, // Use when multi-line w/ rows == 1 + cancelControl: 'link', // 'link'|'button'|false + cancelText: 'cancel', + clickToEditText: 'Click to edit', + externalControl: null, // id|elt + externalControlOnly: false, + fieldPostCreation: 'activate', // 'activate'|'focus'|false + formClassName: 'inplaceeditor-form', + formId: null, // id|elt + highlightColor: '#ffff99', + highlightEndColor: '#ffffff', + hoverClassName: '', + htmlResponse: true, + loadingClassName: 'inplaceeditor-loading', + loadingText: 'Loading...', + okControl: 'button', // 'link'|'button'|false + okText: 'ok', + paramName: 'value', + rows: 1, // If 1 and multi-line, uses autoRows + savingClassName: 'inplaceeditor-saving', + savingText: 'Saving...', + size: 0, + stripLoadedTextTags: false, + submitOnBlur: false, + textAfterControls: '', + textBeforeControls: '', + textBetweenControls: '' + }, + DefaultCallbacks: { + callback: function(form) { + return Form.serialize(form); + }, + onComplete: function(transport, element) { + // For backward compatibility, this one is bound to the IPE, and passes + // the element directly. It was too often customized, so we don't break it. + new Effect.Highlight(element, { + startcolor: this.options.highlightColor, keepBackgroundImage: true }); + }, + onEnterEditMode: null, + onEnterHover: function(ipe) { + ipe.element.style.backgroundColor = ipe.options.highlightColor; + if (ipe._effect) + ipe._effect.cancel(); + }, + onFailure: function(transport, ipe) { + alert('Error communication with the server: ' + transport.responseText.stripTags()); + }, + onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls. + onLeaveEditMode: null, + onLeaveHover: function(ipe) { + ipe._effect = new Effect.Highlight(ipe.element, { + startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor, + restorecolor: ipe._originalBackground, keepBackgroundImage: true + }); + } + }, + Listeners: { + click: 'enterEditMode', + keydown: 'checkForEscapeOrReturn', + mouseover: 'enterHover', + mouseout: 'leaveHover' + } +}); + +Ajax.InPlaceCollectionEditor.DefaultOptions = { + loadingCollectionText: 'Loading options...' +}; + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create({ + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}); \ No newline at end of file diff --git a/public/javascripts/culerity.js b/public/javascripts/culerity.js new file mode 100644 index 0000000..71cbe65 --- /dev/null +++ b/public/javascripts/culerity.js @@ -0,0 +1,31 @@ +// this allows culerity to wait until all ajax requests have finished +jQuery(function($) { + var original_ajax = $.ajax; + var count_down = function(callback) { + return function() { + try { + if(callback) { + callback.apply(this, arguments); + }; + } catch(e) { + window.running_ajax_calls -= 1; + throw(e); + } + window.running_ajax_calls -= 1; + }; + }; + window.running_ajax_calls = 0; + + var ajax_with_count = function(options) { + if(options.async == false) { + return(original_ajax(options)); + } else { + window.running_ajax_calls += 1; + options.success = count_down(options.success); + options.error = count_down(options.error); + return original_ajax(options); + } + }; + + $.ajax = ajax_with_count; +}); \ No newline at end of file diff --git a/public/javascripts/date.format.js b/public/javascripts/date.format.js new file mode 100644 index 0000000..ac71bc3 --- /dev/null +++ b/public/javascripts/date.format.js @@ -0,0 +1,127 @@ +/* +* Date Format 1.2.3 +* (c) 2007-2009 Steven Levithan +* MIT license +* +* Includes enhancements by Scott Trenda +* and Kris Kowal +* +* Accepts a date, a mask, or a date and a mask. +* Returns a formatted version of the given date. +* The date defaults to the current date/time. +* The mask defaults to dateFormat.masks.default. +*/ + +var dateFormat = function () { + var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, + timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, + timezoneClip = /[^-+\dA-Z]/g, + pad = function (val, len) { + val = String(val); + len = len || 2; + while (val.length < len) val = "0" + val; + return val; + }; + + // Regexes and supporting functions are cached through closure + return function (date, mask, utc) { + var dF = dateFormat; + + // You can't provide utc if you skip other args (use the "UTC:" mask prefix) + if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { + mask = date; + date = undefined; + } + + // Passing date through Date applies Date.parse, if necessary + date = date ? new Date(date) : new Date; + if (isNaN(date)) throw SyntaxError("invalid date"); + + mask = String(dF.masks[mask] || mask || dF.masks["default"]); + + // Allow setting the utc argument via the mask + if (mask.slice(0, 4) == "UTC:") { + mask = mask.slice(4); + utc = true; + } + + var _ = utc ? "getUTC" : "get", + d = date[_ + "Date"](), + D = date[_ + "Day"](), + m = date[_ + "Month"](), + y = date[_ + "FullYear"](), + H = date[_ + "Hours"](), + M = date[_ + "Minutes"](), + s = date[_ + "Seconds"](), + L = date[_ + "Milliseconds"](), + o = utc ? 0 : date.getTimezoneOffset(), + flags = { + d: d, + dd: pad(d), + ddd: dF.i18n.dayNames[D], + dddd: dF.i18n.dayNames[D + 7], + m: m + 1, + mm: pad(m + 1), + mmm: dF.i18n.monthNames[m], + mmmm: dF.i18n.monthNames[m + 12], + yy: String(y).slice(2), + yyyy: y, + h: H % 12 || 12, + hh: pad(H % 12 || 12), + H: H, + HH: pad(H), + M: M, + MM: pad(M), + s: s, + ss: pad(s), + l: pad(L, 3), + L: pad(L > 99 ? Math.round(L / 10) : L), + t: H < 12 ? "a" : "p", + tt: H < 12 ? "am" : "pm", + T: H < 12 ? "A" : "P", + TT: H < 12 ? "AM" : "PM", + Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), + o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), + S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] + }; + + return mask.replace(token, function ($0) { + return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); + }); + }; + }(); + + // Some common format strings + dateFormat.masks = { + "default": "ddd mmm dd yyyy HH:MM:ss", + shortDate: "m/d/yy", + mediumDate: "mmm d, yyyy", + longDate: "mmmm d, yyyy", + fullDate: "dddd, mmmm d, yyyy", + shortTime: "h:MM TT", + mediumTime: "h:MM:ss TT", + longTime: "h:MM:ss TT Z", + isoDate: "yyyy-mm-dd", + isoTime: "HH:MM:ss", + isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", + isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" + }; + + // Internationalization strings + dateFormat.i18n = { + dayNames: [ + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" + ], + monthNames: [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" + ] + }; + + // For convenience... + Date.prototype.format = function (mask, utc) { + return dateFormat(this, mask, utc); + }; + + diff --git a/public/javascripts/dragdrop.js b/public/javascripts/dragdrop.js new file mode 100644 index 0000000..07229f9 --- /dev/null +++ b/public/javascripts/dragdrop.js @@ -0,0 +1,973 @@ +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +if(Object.isUndefined(Effect)) + throw("dragdrop.js requires including script.aculo.us' effects.js library"); + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || { }); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if(Object.isArray(containment)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + this.drops.push(options); + }, + + findDeepestChild: function(drops) { + deepest = drops[0]; + + for (i = 1; i < drops.length; ++i) + if (Element.isParent(drops[i].element, deepest.element)) + deepest = drops[i]; + + return deepest; + }, + + isContained: function(element, drop) { + var containmentNode; + if(drop.tree) { + containmentNode = element.treeNode; + } else { + containmentNode = element.parentNode; + } + return drop._containers.detect(function(c) { return containmentNode == c }); + }, + + isAffected: function(point, element, drop) { + return ( + (drop.element!=element) && + ((!drop._containers) || + this.isContained(element, drop)) && + ((!drop.accept) || + (Element.classNames(element).detect( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + var drop, affected = []; + + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) + affected.push(drop); + }); + + if(affected.length>0) + drop = Droppables.findDeepestChild(affected); + + if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); + if (drop) { + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + if (drop != this.last_active) Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) + if (this.last_active.onDrop) { + this.last_active.onDrop(element, this.last_active.element, event); + return true; + } + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +}; + +var Draggables = { + drags: [], + observers: [], + + register: function(draggable) { + if(this.drags.length == 0) { + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.updateDrag.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + } + this.drags.push(draggable); + }, + + unregister: function(draggable) { + this.drags = this.drags.reject(function(d) { return d==draggable }); + if(this.drags.length == 0) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + Event.stopObserving(document, "keypress", this.eventKeypress); + } + }, + + activate: function(draggable) { + if(draggable.options.delay) { + this._timeout = setTimeout(function() { + Draggables._timeout = null; + window.focus(); + Draggables.activeDraggable = draggable; + }.bind(this), draggable.options.delay); + } else { + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari + this.activeDraggable = draggable; + } + }, + + deactivate: function() { + this.activeDraggable = null; + }, + + updateDrag: function(event) { + if(!this.activeDraggable) return; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + // Mozilla-based browsers fire successive mousemove events with + // the same coordinates, prevent needless redrawing (moz bug?) + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; + this._lastPointer = pointer; + + this.activeDraggable.updateDrag(event, pointer); + }, + + endDrag: function(event) { + if(this._timeout) { + clearTimeout(this._timeout); + this._timeout = null; + } + if(!this.activeDraggable) return; + this._lastPointer = null; + this.activeDraggable.endDrag(event); + this.activeDraggable = null; + }, + + keyPress: function(event) { + if(this.activeDraggable) + this.activeDraggable.keyPress(event); + }, + + addObserver: function(observer) { + this.observers.push(observer); + this._cacheObserverCallbacks(); + }, + + removeObserver: function(element) { // element instead of observer fixes mem leaks + this.observers = this.observers.reject( function(o) { return o.element==element }); + this._cacheObserverCallbacks(); + }, + + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' + if(this[eventName+'Count'] > 0) + this.observers.each( function(o) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + if(draggable.options[eventName]) draggable.options[eventName](draggable, event); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +}; + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create({ + initialize: function(element) { + var defaults = { + handle: false, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, + queue: {scope:'_draggable', position:'end'} + }); + }, + endeffect: function(element) { + var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + queue: {scope:'_draggable', position:'end'}, + afterFinish: function(){ + Draggable._dragging[element] = false + } + }); + }, + zindex: 1000, + revert: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } + delay: 0 + }; + + if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) + Object.extend(defaults, { + starteffect: function(element) { + element._opacity = Element.getOpacity(element); + Draggable._dragging[element] = true; + new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); + } + }); + + var options = Object.extend(defaults, arguments[1] || { }); + + this.element = $(element); + + if(options.handle && Object.isString(options.handle)) + this.handle = this.element.down('.'+options.handle, 0); + + if(!this.handle) this.handle = $(options.handle); + if(!this.handle) this.handle = this.element; + + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { + options.scroll = $(options.scroll); + this._isScrollChild = Element.childOf(this.element, options.scroll); + } + + Element.makePositioned(this.element); // fix IE + + this.options = options; + this.dragging = false; + + this.eventMouseDown = this.initDrag.bindAsEventListener(this); + Event.observe(this.handle, "mousedown", this.eventMouseDown); + + Draggables.register(this); + }, + + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + Draggables.unregister(this); + }, + + currentDelta: function() { + return([ + parseInt(Element.getStyle(this.element,'left') || '0'), + parseInt(Element.getStyle(this.element,'top') || '0')]); + }, + + initDrag: function(event) { + if(!Object.isUndefined(Draggable._dragging[this.element]) && + Draggable._dragging[this.element]) return; + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if((tag_name = src.tagName.toUpperCase()) && ( + tag_name=='INPUT' || + tag_name=='SELECT' || + tag_name=='OPTION' || + tag_name=='BUTTON' || + tag_name=='TEXTAREA')) return; + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); + + Draggables.activate(this); + Event.stop(event); + } + }, + + startDrag: function(event) { + this.dragging = true; + if(!this.delta) + this.delta = this.currentDelta(); + + if(this.options.zindex) { + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + this.element.style.zIndex = this.options.zindex; + } + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); + if (!this._originallyAbsolute) + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + if(this.options.scroll) { + if (this.options.scroll == window) { + var where = this._getWindowScroll(this.options.scroll); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + + if(!this.options.quiet){ + Position.prepare(); + Droppables.show(pointer, this.element); + } + + Draggables.notify('onDrag', this, event); + + this.draw(pointer); + if(this.options.change) this.options.change(this); + + if(this.options.scroll) { + this.stopScrolling(); + + var p; + if (this.options.scroll == window) { + with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft + Position.deltaX; + p[1] += this.options.scroll.scrollTop + Position.deltaY; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(Prototype.Browser.WebKit) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.quiet){ + Position.prepare(); + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + Droppables.show(pointer, this.element); + } + + if(this.options.ghosting) { + if (!this._originallyAbsolute) + Position.relativize(this.element); + delete this._originallyAbsolute; + Element.remove(this._clone); + this._clone = null; + } + + var dropped = false; + if(success) { + dropped = Droppables.fire(event, this.element); + if (!dropped) dropped = false; + } + if(dropped && this.options.onDropped) this.options.onDropped(this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && Object.isFunction(revert)) revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + if (dropped == 0 || revert != 'failure') + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + if(this.options.endeffect) + this.options.endeffect(this.element); + + Draggables.deactivate(this); + Droppables.reset(); + }, + + keyPress: function(event) { + if(event.keyCode!=Event.KEY_ESC) return; + this.finishDrag(event, false); + Event.stop(event); + }, + + endDrag: function(event) { + if(!this.dragging) return; + this.stopScrolling(); + this.finishDrag(event, true); + Event.stop(event); + }, + + draw: function(point) { + var pos = Position.cumulativeOffset(this.element); + if(this.options.ghosting) { + var r = Position.realOffset(this.element); + pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; + } + + var d = this.currentDelta(); + pos[0] -= d[0]; pos[1] -= d[1]; + + if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { + pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; + pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; + } + + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) + }.bind(this)); + + if(this.options.snap) { + if(Object.isFunction(this.options.snap)) { + p = this.options.snap(p[0],p[1],this); + } else { + if(Object.isArray(this.options.snap)) { + p = p.map( function(v, i) { + return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); + } else { + p = p.map( function(v) { + return (v/this.options.snap).round()*this.options.snap }.bind(this)); + } + }} + + var style = this.element.style; + if((!this.options.constraint) || (this.options.constraint=='horizontal')) + style.left = p[0] + "px"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = p[1] + "px"; + + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + }, + + stopScrolling: function() { + if(this.scrollInterval) { + clearInterval(this.scrollInterval); + this.scrollInterval = null; + Draggables._lastScrollPointer = null; + } + }, + + startScrolling: function(speed) { + if(!(speed[0] || speed[1])) return; + this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; + this.lastScrolled = new Date(); + this.scrollInterval = setInterval(this.scroll.bind(this), 10); + }, + + scroll: function() { + var current = new Date(); + var delta = current - this.lastScrolled; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + if (this._isScrollChild) { + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + Draggables._lastScrollPointer[1] = 0; + this.draw(Draggables._lastScrollPointer); + } + + if(this.options.change) this.options.change(this); + }, + + _getWindowScroll: function(w) { + var T, L, W, H; + with (w.document) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight; + } + } + return { top: T, left: L, width: W, height: H }; + } +}); + +Draggable._dragging = { }; + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create({ + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +}); + +var Sortable = { + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, + + sortables: { }, + + _findRootElement: function(element) { + while (element.tagName.toUpperCase() != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + element = $(element); + var s = Sortable.sortables[element.id]; + + if(s) { + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + + delete Sortable.sortables[s.element.id]; + } + }, + + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + delay: 0, + hoverclass: null, + ghosting: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: this.SERIALIZE_RULE, + + // these take arrays of elements or ids and can be + // used for better initialization performance + elements: false, + handles: false, + + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || { }); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + quiet: options.quiet, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + delay: options.delay, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + tree: options.tree, + hoverclass: options.hoverclass, + onHover: Sortable.onHover + }; + + var options_for_tree = { + onHover: Sortable.onEmptyHover, + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass + }; + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (options.elements || this.findElements(element, options) || []).each( function(e,i) { + var handle = options.handles ? $(options.handles[i]) : + (options.handle ? $(e).select('.' + options.handle)[0] : e); + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + if(options.tree) e.treeNode = element; + options.droppables.push(e); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.id] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.tag); + }, + + findTreeElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.treeTag); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, overlap) { + var oldParentNode = element.parentNode; + var droponOptions = Sortable.options(dropon); + + if(!Element.isParent(dropon, element)) { + var index; + + var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); + var child = null; + + if(children) { + var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); + + for (index = 0; index < children.length; index += 1) { + if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { + offset -= Element.offsetSize (children[index], droponOptions.overlap); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Sortable._marker.hide(); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = + ($('dropmarker') || Element.extend(document.createElement('DIV'))). + hide().addClassName('dropmarker').setStyle({position:'absolute'}); + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); + + if(position=='after') + if(sortable.overlap == 'horizontal') + Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); + else + Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); + + Sortable._marker.show(); + }, + + _tree: function(element, options, parent) { + var children = Sortable.findElements(element, options) || []; + + for (var i = 0; i < children.length; ++i) { + var match = children[i].id.match(options.format); + + if (!match) continue; + + var child = { + id: encodeURIComponent(match ? match[1] : null), + element: element, + parent: parent, + children: [], + position: parent.children.length, + container: $(children[i]).down(options.treeTag) + }; + + /* Get the element containing the children and recurse over it */ + if (child.container) + this._tree(child.container, options, child); + + parent.children.push (child); + } + + return parent; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format + }, arguments[1] || { }); + + var root = { + id: null, + parent: null, + children: [], + container: element, + position: 0 + }; + + return Sortable._tree(element, options, root); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || { }); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || { }); + + var nodeMap = { }; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var options = Object.extend(Sortable.options(element), arguments[1] || { }); + var name = encodeURIComponent( + (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); + + if (options.tree) { + return Sortable.tree(element, arguments[1]).children.map( function (item) { + return [name + Sortable._constructIndex(item) + "[id]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).join('&'); + } + } +}; + +// Returns true if child is contained within element +Element.isParent = function(child, element) { + if (!child.parentNode || child == element) return false; + if (child.parentNode == element) return true; + return Element.isParent(child.parentNode, element); +}; + +Element.findChildren = function(element, only, recursive, tagName) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +}; + +Element.offsetSize = function (element, type) { + return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; +}; \ No newline at end of file diff --git a/public/javascripts/eems_common.js b/public/javascripts/eems_common.js new file mode 100644 index 0000000..65027c9 --- /dev/null +++ b/public/javascripts/eems_common.js @@ -0,0 +1,115 @@ +var dateFormatMask = "yyyy-mm-dd'T'HH:MM:sso"; + +$(document).ready(function(){ + $("#searchResultsHeader td").click(function() { + $(this).find('form').submit(); + }); +}); + +function addLogEntry(pid, pars, reload) { + if (pid == undefined) { pid = window._pid; } + + $.ajax({ + url: '/eems/' + pid + '/log', + type: 'POST', + datatype: 'json', + data: pars, + success: function() { + if (reload) { + window.location.reload(); + } + }, + }); +} + +function unescapeTags(value) { + value = value.replace(/>/gi, ">"); + value = value.replace(/"); + value = value.replace(/</gi, "<"); + value = value.replace(//gi, "\r"); + return value; +} + +// Ajax updater for eem +function eemUpdate(pid, pars) { + if (pid == undefined) { pid = window._pid; } + pars['pid'] = pid; + + $.ajax({ + url: '/eems/' + pid + '.json', + type: 'PUT', + data: pars, + success: function(eem) { + }, + }); + + return false; +} + +// Ajax updater for content file +function partUpdate(pid, pars) { + var partPid = window._part_pid; + + if (pid == undefined) { pid = window._pid; } + pars['pid'] = pid; + + if (pid == undefined || partPid == undefined || partPid == '') { + return; + } + + $.ajax({ + url: '/eems/' + pid + '/parts/' + partPid, + type: 'PUT', + data: pars, + success: function(response) { + }, + }); + + return false; +} + +function stripHTMLTags(strInputCode){ + strInputCode = strInputCode.replace(/&(lt|gt);/g, function (strMatch, p1){ + return (p1 == "lt")? "<" : ">"; + }); + + var strTagStrippedText = strInputCode.replace(/<\/?[^>]+(>|$)/g, ""); + return strTagStrippedText; +} + + +function showBrowserWidget() { + $('#eems-browser-widget-toggle').addClass('eems-widget-tab-active'); + $('#eems-desktop-widget-toggle').removeClass('eems-widget-tab-active'); + $('#eems-desktop-widget').fadeOut(); + $('#eems-browser-widget').fadeIn(); + $('#eems-upload-progress').hide(); + $('#eems-links').hide(); +} + +function showDesktopWidget() { + $('#eems-desktop-widget-toggle').addClass('eems-widget-tab-active'); + $('#eems-browser-widget-toggle').removeClass('eems-widget-tab-active'); + $('#eems-browser-widget').fadeOut(); + $('#eems-desktop-widget').fadeIn(); + $('#eems-upload-progress').hide(); + $('#eems-links').hide(); +} + + +$(document).ready(function() { + $('#eems-browser-widget-toggle').click(function() { + showBrowserWidget(); + }); + + $('#eems-desktop-widget-toggle').click(function() { + showDesktopWidget(); + }); + + showBrowserWidget(); +}); diff --git a/public/javascripts/eems_detail.js b/public/javascripts/eems_detail.js new file mode 100644 index 0000000..114a74c --- /dev/null +++ b/public/javascripts/eems_detail.js @@ -0,0 +1,316 @@ +var token = window._token; +var pid = window._pid; +var listLanguages = "Arabic Chinese English French German Hebrew Italian Japanese Korean Russian Spanish Other".split(" "); +var data = "ABBASI ABBOTT ABERCROMB ABRAHAMSW ACKERMANR AFRICA AFRICADOC AHNE ANTHRO AREASTREF ARONSONB ARSGIFT ARTARCH ASHENR ASUL ASULMEM ASULRES AVERYC BACKERM BACONR BAILEYT BALDRIDGE BALLARDE BANDETH BARCHASS BARKSDALE BARNESJ BARONJ BELLD BENDERA BENDERRM BENNETT BERNSTEIN BIOLOGY BIOLOGY2 BOOKEXCH BOYLESE BRANGIFT BRASCHF BREERC BRICKERF BROOKSC BRUSHIST BRUSHISTI BRUSLIT BRUSLITI BURDJ BURNELLG BURROWSD CAFAROC CAGLEA CAMPBELLR CANFIELDD CARNOCHAN CELEBFUND CHAMBERLA CHEM CHEMGIFT CHICANO CHINAHV CHINART CHINEXCHG CHJPDOC CHUCKF CLASS1965 CLASS1970 CLASSICS CLEBSCH CLEBSCHW CLEMENTM COCHRANS COFFINJ COHNA COMM COMPRACEC CONNOLLYG COTRELLE CRAIGW CRARYG CROWL CULTSTUD CUSICKJ DATAFILE DATAFILE2 DAVESD DAVISJ DENNINGR DIGICORP DINSMOREM DIROFF DOBBSMELT DONNELLD DUPSOLD DUTTONR EALJAPAN EALMOORE EARTHSCI EARTHSCIM EASTASIA EASTASTVI EBOOKS ECON EDGRENR EDUC EEURDOC EGGERE ELIOTM ENG ENGGIFT ENVIROPOP ESBENSHAD FALCONERF FEHRENBAC FELTONC FEMINIST FIELD FILMPERF FITGERWIL FITZHUGHW FLAHERTYM FORDJ FORDOC FORLANGI FOXS FRANKENST FRANKLHUM FRIENDE FRITDOC FRYBERGER GALLAGHER GALTCOBL GAYLES GEBALLET GENACQ GENGIFT GENREF GENSER GERMANIC GIFTSPPRO GOLDMANR GOLDSTEIN GOODANB GORDONA GOVINFOI GREENC GREENW GROMMONA GUNSTM GWAUDOC HADERLIEE HAEFNERJ HAMMONDA HANNA HANNAP HARRASSOW HARRISJ HARRISL HAUGH HAWES HAYFER HEARST HENMULLER HERRICK HERTLEINL HERTLEINM HESS HILLMAN HIRSCHMAN HISTSCI HMSHO HOBSON HOPKINS HORTON HOWELL1 HOWELL2 HUMINST HUMREF HVACQ INFOACC INFOACCDS INSTSOFTW INTLDOC JACKMANC JACKMAND JACOBSONM JAGELS JAPANHV JAY JEWELFUND JEWISH JOHNSONJ JOHNSONW JONESR JONESW JORDAN KARLE KATSEV KATZEVA KATZEVS KAY KAYSTEEVE KEMBLE KENNEDY KIBLER KIM KIMBALL KITTLE KLEINH KLEINM KLINEROET KNAPPI KNAPPJ KOOPERMAN KOREAN KORFDNGR KUMM KUMMA KUMMFF KUMMFG KUMMH KUMMM KUMMS KWOKB KWOKL LANEWEST LANZ LATAM LATAMIDOC LEITER LEWIS LINDER LINGUIST LIS LITFRANKL LITTAUER LITTPOET LMBSP LOWENTHAL LYMAN LYMANAWAR LYNCH MAHARAM MARKUS MARTINEAU MASON MATHCOMP MCCAMENT MCCULLOUG MCDOWELL MEDIAACQ MEDIAINST MEDIARES MEDIEVAL MEMFUND MEYERBORE MEYERRES MEYERRES2 MGATES MIDEAST MILNE MIRRIELEE MIZOTA MONOORD MORGAN MORRISON MOSER MOULTON MUFFLEY MULLERMCC MUNGER MUNRO MUSIC MUSICARS MUSICSCO MUSICSOU MYERS NIELSEN NISHINO NOFUND NUGGETS OBERLIN OFFEN PAGE PAYNEB PAYNER PECK PERFARTS PERRETTE PERRY PETERSONG PHILBRICK PHILOSOPH PHYSGIFT PHYSICS POLISCI PORTWOOD POYET PRICE PROTHRO PSYCH QUILLEN RASMUSSEN REHNBORGC REHNBORGJ REINERT RELIGION RESEVAL RESMAT RESMATBK RESMATCEN RESMATEND RESMATNEY RESMATOPP RITTER RIXON ROBUSTELL ROMANCE ROSE ROSENBAUM ROSENBERG ROSENBLAT ROSS RUEEUR SAMSON SASIA SASIABUDD SCHIRESON SCITECHI SEASIAPAC SHAFTEL SHARP SHARPS SHAVER SHELDON SHOUP SIEVERS SILIGENE SIMPSON SKINNER SMALL SMORTCHEV SNEDDEN SOARES SOC SOCSCII SOCSCIREF SOWERS SPAPOR SPEC SPECGIFT STAFFREF STANDISH STARLING STATEDOC STEEL STEINBERG STEINMETZ STEVENS STRUBLE SUNDERMEY TANENBAUM TANNERMEM TANNERREL TARBELL TAXACCREV THOMASC THOMASR THOMPSONP THOMPSONW THORPE TIERNAN UARCH UARCHGIFT UKCANDOC ULIBRES USDOC VANWYCK VICKERS1 VICKERS2 VONSCHLE WALLER WARREN WATT WEBB WEBSTER WEISS WEURSOC WEYBURN WHITEHEAD WICKERSHA WIEL WIGGINS WILSON WOOD WOODBURN WOODYATT WREDENB WYATT YOUNGH YOUNGHMAP ZALK ZENOFF".split(" "); +var defaultValues = { + "title" : "Click to add title", + "creatorName" : "Click to add creator name", + "note" : "Click to add citations, comments, copyright notes, etc.", + "payment_fund" : "(Fund name)", + "linkUrl" : "Click to add link" +}; +var dateFormatMask = "yyyy-mm-dd'T'HH:MM:sso"; + +$(document).ready(function() { + + $('#eem_payment_fund').autocomplete(data); + + $.each(['creatorName', 'note'], function(index, name) { + if ($('#text_' + name).html() == '') { + $('#text_' + name).html("" + defaultValues[name] + ""); + } + }); + + $('#eem_payment_fund').focus(function() { + if ($('#eem_payment_fund').val() == defaultValues.payment_fund) { + $('#eem_payment_fund').val(''); + } + }); + + toggleSendToTechServices(); + + $('#btn_detail_file_upload').click(function() { + $(this).attr('disabled', 'disabled'); + $('#detail_file_upload_loader').show(); + $('#detail_file_upload').submit(); + }); + + $('#content_upload').change(function() { + if ($('#content_upload').val() != '') { + $('#btn_detail_file_upload').removeAttr('disabled'); + } else { + $('#btn_detail_file_upload').attr('disabled', 'disabled'); + } + }); + + // send to tech services + $('#send_to_tech_services_ok').click(function() { + $('#send_to_tech_services_ok').attr('disabled', 'disabled'); + sendToTechServices(); + }); + + // Title - inline edit + $('#text_title').click(function() { + editTextOnClick('title'); + }); + + $('#input_title').blur(function() { + editTextOnBlur('title'); + var pars = {"eem[title]" : $('#input_title').val(), 'authenticity_token': token }; + eemUpdate(pid, pars); + toggleSendToTechServices(); + }); + + // Creator Name - inline edit + $('#text_creatorName').click(function() { + editTextOnClick('creatorName'); + }); + + $('#input_creatorName').blur(function() { + editTextOnBlur('creatorName'); + updateCreator(); + }); + + // Direct link to file - inline edit + $('#btn_linkUrl').click(function() { + editLinkUrlOnClick(); + }); + + $('#text_linkUrl').click(function() { + editLinkUrlOnClick(); + }); + + $('#input_linkUrl').blur(function() { + var pars = {'authenticity_token': token }; + + if ($('#input_linkUrl').attr('value') == '') { + $('#text_linkUrl').show(); + $('#linkUrl').attr('href', ''); + $('#linkUrl').text(''); + $('#linkUrl').hide(); + } + else { + $('#linkUrl').attr('href', unescapeTags($('#input_linkUrl').attr('value'))); + $('#linkUrl').text(unescapeTags($('#input_linkUrl').attr('value'))); + $('#text_linkUrl').hide(); + $('#btn_linkUrl').show(); + $('#linkUrl').show(); + pars['part[url]'] = unescapeTags($('#input_linkUrl').attr('value')); + partUpdate(pid, pars); + } + + $('#input_linkUrl').hide(); + }); + + // Creator Type + $('#eem_creatorType').change(function() { + updateCreator(); + }); + + // Note/Comments - inline edit + $('#text_note').click(function() { + editTextOnClick('note'); + }); + + $('#input_note').blur(function() { + editTextOnBlur('note'); + + var noteValue = $('#input_note').attr('value'); + + if (noteValue.match(new RegExp(defaultValues['note'], 'i')) != null) { + noteValue = ''; + } + + var pars = {"eem[note]" : noteValue, 'authenticity_token': token }; + eemUpdate(pid, pars); + }); + + // Language + $('#eem_language').change(function() { + var pars = {"eem[language]" : $('#eem_language').val(), 'authenticity_token': token }; + eemUpdate(pid, pars); + }); + + // Copyright + $('#eem_copyrightStatus').change(function() { + var pars = {}; + pars["eem[copyrightStatus]"] = $('#eem_copyrightStatus').val(); + pars["eem[copyrightStatusDate]"] = dateFormat(dateFormatMask); + pars["authenticity_token"] = token; + + eemUpdate(pid, pars); + toggleSendToTechServices(); + }); + + // Payment Status + $('#eem_paymentType').change(function() { + var pars = {}; + pars["eem[paymentType]"] = $('#eem_paymentType').val(); + pars["authenticity_token"] = token; + + if ($('#eem_paymentType').val() == 'Paid') { + var value = $('#eem_payment_fund').val(); + + if (value != defaultValues.payment_fund) { + pars["eem[paymentFund]"] = value; + } + + $('#eem_payment_fund').show(); + } + else { + pars["eem[paymentFund]"] = ''; + $('#eem_payment_fund').val(defaultValues.payment_fund); + $('#eem_payment_fund').hide(); + } + + eemUpdate(pid, pars); + toggleSendToTechServices(); + }); + + // Payment fund + $('#eem_payment_fund').result(function(event, data, formatted) { + var value = !data ? "" : formatted; + + if (value != '' && value != defaultValues.payment_fund) { + var pars = { "eem[paymentFund]" : value, 'authenticity_token': token }; + eemUpdate(pid, pars); + } + + toggleSendToTechServices(); + }); + + $('#eem_payment_fund').change(function() { + var value = $('#eem_payment_fund').val(); + + if (value != '' && value != defaultValues.payment_fund) { + var pars = { "eem[paymentFund]" : value, 'authenticity_token': token }; + eemUpdate(pid, pars); + } + + toggleSendToTechServices(); + }); + +}); + +function updateCreator() { + var pars = { 'authenticity_token': token }; + var creatorValue = $('#input_creatorName').attr('value'); + + if (creatorValue.match(new RegExp(defaultValues['creatorName'], 'i')) != null) { + creatorValue = ''; + } + + if ($('#eem_creatorType').val() == 'person') { + pars["eem[creatorPerson]"] = creatorValue; + pars["eem[creatorOrg]"] = ''; + } else { + pars["eem[creatorOrg]"] = creatorValue; + pars["eem[creatorPerson]"] = ''; + } + + eemUpdate(pid, pars); +} + +// inline edit functions +function editTextOnClick(name) { + if ($('#text_' + name).html().match(new RegExp(defaultValues[name], 'i')) != null) { + $('#text_' + name).html(''); + } + + $('#input_' + name).attr('value', escapeTags($('#text_' + name).html())); + $('#text_' + name).hide(); + + $('#input_' + name).show(); + $('#input_' + name).focus(); +} + +function editTextOnBlur(name) { + if ($('#input_' + name).attr('value') == '') { + $('#input_' + name).attr('value', "" + defaultValues[name] + ""); + $('#text_' + name).html($('#input_' + name).attr('value')); + } + else { + $('#text_' + name).html(unescapeTags($('#input_' + name).attr('value'))); + } + + $('#input_' + name).hide(); + $('#text_' + name).show(); +} + +function editLinkUrlOnClick() { + if ($('#linkUrl').attr('href').match(new RegExp(defaultValues['linkUrl'], 'i')) != null) { + $('#linkUrl').attr('href', ''); + $('#linkUrl').text(''); + } + + $('#input_linkUrl').attr('value', escapeTags($('#linkUrl').attr('href'))); + + $('#linkUrl').hide(); + $('#btn_linkUrl').hide(); + $('#text_linkUrl').hide(); + $('#input_linkUrl').show(); + $('#input_linkUrl').focus(); +} + +function updateCopyrightDate(data) { + var dateValue = data["mm"] + '/' + data["dd"] + '/' + data["yyyy"]; + var pars = { "eem[copyrightDate]" : dateValue, 'authenticity_token': token }; + eemUpdate(pid, pars); +} + +function toggleSendToTechServices() { + if ($('#input_title').val() != '' && $('#input_title').val() != 'Click to add title' && status != 'Submitted' && $('#detail_file_upload').length == 0 && + (($('#eem_paymentType').val() == 'Free' && ( $('#eem_copyrightStatus').val() == 'Public access OK' || $('#eem_copyrightStatus').val() == 'Stanford access OK')) || + ($('#eem_paymentType').val() == 'Paid' && $('#eem_payment_fund').val() != '' && $('#eem_payment_fund').val() != '(Fund name)'))) { + $('#send_to_tech_services_ok').removeAttr('disabled'); + $('#text_send_to_acquistions').val(''); + $('#text_send_to_acquistions').removeAttr('disabled'); + } + else { + $('#send_to_tech_services_ok').attr('disabled', true); + $('#text_send_to_acquistions').val('This item can\'t be sent to Technical Services until copyright permission is received.'); + $('#text_send_to_acquistions').attr('disabled', true); + } + + // no local pdf file exists. hence, #detail_file_upload form element exists + if ($('#detail_file_upload').length != 0) { + $('#text_send_to_acquistions').val('This item can\'t be sent to Technical Services until a local copy of the file is uploaded.'); + } +} + +function sendToTechServices() { + var comment = unescapeTags($('#text_send_to_acquistions').val()); + var pars = {}; + if (pid == undefined) { pid = window._pid; } + pars['authenticity_token'] = token; + + if (/\S/.test(comment)) { + pars['comment'] = comment; + } + + $.ajax({ + url: '/eems/' + pid + '/submit_to_tech_services', + type: 'PUT', + data: pars, + success: function(status) { + if (status) { + window.location.reload(); + } + }, + }); +} + +function deletePermissionFile(eem_pid, file_pid) { + var uniq_id = file_pid.replace('druid:', ''); + + $('#delete_' + uniq_id).hide(); + $('#loader_' + uniq_id).show(); + + $.ajax({ + url: '/eems/' + eem_pid + '/permission_files/' + file_pid, + type: 'DELETE', + success: function(status) { + if (status == 'OK') { window.location.reload(); } + } + }); +} diff --git a/public/javascripts/eems_detail_actions.js b/public/javascripts/eems_detail_actions.js new file mode 100644 index 0000000..831b409 --- /dev/null +++ b/public/javascripts/eems_detail_actions.js @@ -0,0 +1,124 @@ + +$(document).ready(function() { + acquisitionsActionsBindToggle('send_to_tech_services'); + acquisitionsActionsBindToggle('cancel_this_request'); + acquisitionsActionsBindToggle('upload_copyright_attachment'); + acquisitionsActionsBindToggle('comment_to_selector'); + + $('#text_comment_to_selector').val(''); + $('#text_cancel_this_request').val(''); + + // cancel this request + $('#cancel_this_request_ok').click(function() { + cancelThisRequest(); + }); + + // upload permission file + $('#upload_copyright_attachment_ok').click(function() { + $('#upload_copyright_attachment_ok').hide(); + $('#permission_files_upload_loader').show(); + $('#formlet_upload_copyright_attachment').submit(); + }); + + $('#permission_file_upload').change(function() { + if ($('#permission_file_upload').val() != '') { + $('#upload_copyright_attachment_ok').removeAttr('disabled'); + } else { + $('#upload_copyright_attachment_ok').attr('disabled', 'disabled'); + } + }); + + // question/comment to selector + $('#comment_to_selector_ok').click(function() { + questionOrCommentToSelector(); + }); + + $('#text_comment_to_selector').keyup(function() { + var txt = $('#text_comment_to_selector').val(); + + if (/\S/.test(txt)) { + $('#comment_to_selector_ok').removeAttr('disabled'); + } else { + $('#comment_to_selector_ok').attr('disabled', 'disabled'); + } + }); + + $('#send_to_tech_services_cancel').click(function() { + cancelAction('send_to_tech_services'); + }); + + $('#cancel_this_request_cancel').click(function() { + cancelAction('cancel_this_request'); + }); + + $('#cancel_this_request_ok').removeAttr('disabled'); + + $('#upload_copyright_attachment_cancel').click(function() { + $('#formlet_upload_copyright_attachment')[0].reset(); + $('#permission_files_upload_loader').hide(); + $('upload_copyright_attachment_ok').show(); + $('#link_upload_copyright_attachment').click(); + $('#permission_file_upload').change(); + }); + + $('#comment_to_selector_cancel').click(function() { + cancelAction('comment_to_selector'); + $('#text_comment_to_selector').keyup(); + }); + +}); + +function acquisitionsActionsBindToggle(name) { + if ($('#link_' + name).length > 0) { + $('#link_' + name).toggle( + function() { + $('#formlet_' + name).show(); + $('#link_' + name).removeClass('action_box_show').addClass('action_box_hide'); + }, + function() { + $('#formlet_' + name).hide(); + $('#link_' + name).removeClass('action_box_hide').addClass('action_box_show'); + } + ); + } +} + +function cancelThisRequest() { + var cancelComment = unescapeTags($('#text_cancel_this_request').val()); + var pid = window._pid; + var parsUpdate = { + 'eem[status]': 'Canceled', + 'eem[statusDatetime]': dateFormat(dateFormatMask), + 'authenticity_token': window._token + }; + + eemUpdate(pid, parsUpdate); + + var parsLog = { + 'entry': 'Request canceled by ' + selectorName, + 'comment': cancelComment, + 'authenticity_token': window._token + }; + + addLogEntry(pid, parsLog, true); +} + +function questionOrCommentToSelector() { + var questionOrComment = unescapeTags($('#text_comment_to_selector').val()); + + if (/\S/.test(questionOrComment)) { + var pars = { + 'entry': 'Question/comment by ' + selectorName, + 'comment': questionOrComment, + 'authenticity_token': window._token + }; + + addLogEntry(window._pid, pars, true); + } +} + +function cancelAction(name) { + $('#text_' + name).val(''); + $('#link_' + name).click(); +} + diff --git a/public/javascripts/effects.js b/public/javascripts/effects.js new file mode 100644 index 0000000..5a639d2 --- /dev/null +++ b/public/javascripts/effects.js @@ -0,0 +1,1128 @@ +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if (this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if (this.slice(0,1) == '#') { + if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if (this.length==7) color = this.toLowerCase(); + } + } + return (color.length==7 ? color : (arguments[0] || this)); +}; + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +}; + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +}; + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); + if (Prototype.Browser.WebKit) window.scrollBy(0,0); + return element; +}; + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +}; + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + Transitions: { + linear: Prototype.K, + sinoidal: function(pos) { + return (-Math.cos(pos*Math.PI)/2) + .5; + }, + reverse: function(pos) { + return 1-pos; + }, + flicker: function(pos) { + var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4; + return pos > 1 ? 1 : pos; + }, + wobble: function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5; + }, + pulse: function(pos, pulses) { + return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5; + }, + spring: function(pos) { + return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); + }, + none: function(pos) { + return 0; + }, + full: function(pos) { + return 1; + } + }, + DefaultOptions: { + duration: 1.0, // seconds + fps: 100, // 100= assume 66fps max. + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' + }, + tagifyText: function(element) { + var tagifyStyle = 'position:relative'; + if (Prototype.Browser.IE) tagifyStyle += ';zoom:1'; + + element = $(element); + $A(element.childNodes).each( function(child) { + if (child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + new Element('span', {style: tagifyStyle}).update( + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if (((typeof element == 'object') || + Object.isFunction(element)) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || { }); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || { }); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(Enumerable, { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = Object.isString(effect.options.queue) ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'with-last': + timestamp = this.effects.pluck('startOn').max() || timestamp; + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + + if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) + this.effects.push(effect); + + if (!this.interval) + this.interval = setInterval(this.loop.bind(this), 15); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if (this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + for(var i=0, len=this.effects.length;i= this.startOn) { + if (timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if (this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / this.totalTime, + frame = (pos * this.totalFrames).round(); + if (frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + cancel: function() { + if (!this.options.sync) + Effect.Queues.get(Object.isString(this.options.queue) ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if (this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + var data = $H(); + for(property in this) + if (!Object.isFunction(this[property])) data.set(property, this[property]); + return '#'; + } +}); + +Effect.Parallel = Class.create(Effect.Base, { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if (effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Tween = Class.create(Effect.Base, { + initialize: function(object, from, to) { + object = Object.isString(object) ? $(object) : object; + var args = $A(arguments), method = args.last(), + options = args.length == 5 ? args[3] : null; + this.method = Object.isFunction(method) ? method.bind(object) : + Object.isFunction(object[method]) ? object[method].bind(object) : + function(value) { object[method] = value }; + this.start(Object.extend({ from: from, to: to }, options || { })); + }, + update: function(position) { + this.method(position); + } +}); + +Effect.Event = Class.create(Effect.Base, { + initialize: function() { + this.start(Object.extend({ duration: 0 }, arguments[0] || { })); + }, + update: Prototype.emptyFunction +}); + +Effect.Opacity = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || { }); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if (this.options.mode == 'absolute') { + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: (this.options.x * position + this.originalLeft).round() + 'px', + top: (this.options.y * position + this.originalTop).round() + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); +}; + +Effect.Scale = Class.create(Effect.Base, { + initialize: function(element, percent) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or { } with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || { }); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = { }; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if (fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if (this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if (/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if (!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if (this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = { }; + if (this.options.scaleX) d.width = width.round() + 'px'; + if (this.options.scaleY) d.height = height.round() + 'px'; + if (this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if (this.elementPositioning == 'absolute') { + if (this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if (this.options.scaleY) d.top = -topd + 'px'; + if (this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if (this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { }; + if (!this.options.keepBackgroundImage) { + this.oldStyle.backgroundImage = this.element.getStyle('background-image'); + this.element.setStyle({backgroundImage: 'none'}); + } + if (!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if (!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = function(element) { + var options = arguments[1] || { }, + scrollOffsets = document.viewport.getScrollOffsets(), + elementOffsets = $(element).cumulativeOffset(); + + if (options.offset) elementOffsets[1] += options.offset; + + return new Effect.Tween(null, + scrollOffsets.top, + elementOffsets[1], + options, + function(p){ scrollTo(scrollOffsets.left, p.round()); } + ); +}; + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if (effect.options.to!=0) return; + effect.element.hide().setStyle({opacity: oldOpacity}); + } + }, arguments[1] || { }); + return new Effect.Opacity(element,options); +}; + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + effect.element.setOpacity(effect.options.from).show(); + }}, arguments[1] || { }); + return new Effect.Opacity(element,options); +}; + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { + opacity: element.getInlineOpacity(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height + }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().setStyle(oldStyle); } + }, arguments[1] || { }) + ); +}; + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }, arguments[1] || { }) + ); +}; + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || { })); +}; + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); + } + }); + } + }, arguments[1] || { })); +}; + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + effect.effects[0].element.makePositioned(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); + } + }, arguments[1] || { })); +}; + +Effect.Shake = function(element) { + element = $(element); + var options = Object.extend({ + distance: 20, + duration: 0.5 + }, arguments[1] || {}); + var distance = parseFloat(options.distance); + var split = parseFloat(options.duration) / 10.0; + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { + effect.element.undoPositioned().setStyle(oldStyle); + }}); }}); }}); }}); }}); }}); +}; + +Effect.SlideDown = function(element) { + element = $(element).cleanWhitespace(); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if (window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || { }) + ); +}; + +Effect.SlideUp = function(element) { + element = $(element).cleanWhitespace(); + var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, window.opera ? 0 : 1, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if (window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); + } + }, arguments[1] || { }) + ); +}; + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, { + restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }); +}; + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide().makeClipping().makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + } + }, options) + ); + } + }); +}; + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } + }, options) + ); +}; + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || { }, + oldOpacity = element.getInlineOpacity(), + transition = options.transition || Effect.Transitions.linear, + reverser = function(pos){ + return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5); + }; + + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 2.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +}; + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + element.makeClipping(); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().setStyle(oldStyle); + } }); + }}, arguments[1] || { })); +}; + +Effect.Morph = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + style: { } + }, arguments[1] || { }); + + if (!Object.isString(options.style)) this.style = $H(options.style); + else { + if (options.style.include(':')) + this.style = options.style.parseStyle(); + else { + this.element.addClassName(options.style); + this.style = $H(this.element.getStyles()); + this.element.removeClassName(options.style); + var css = this.element.getStyles(); + this.style = this.style.reject(function(style) { + return style.value == css[style.key]; + }); + options.afterFinishInternal = function(effect) { + effect.element.addClassName(effect.options.style); + effect.transforms.each(function(transform) { + effect.element.style[transform.style] = ''; + }); + }; + } + } + this.start(options); + }, + + setup: function(){ + function parseColor(color){ + if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; + color = color.parseColor(); + return $R(0,2).map(function(i){ + return parseInt( color.slice(i*2+1,i*2+3), 16 ); + }); + } + this.transforms = this.style.map(function(pair){ + var property = pair[0], value = pair[1], unit = null; + + if (value.parseColor('#zzzzzz') != '#zzzzzz') { + value = value.parseColor(); + unit = 'color'; + } else if (property == 'opacity') { + value = parseFloat(value); + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + } else if (Element.CSS_LENGTH.test(value)) { + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); + value = parseFloat(components[1]); + unit = (components.length == 3) ? components[2] : null; + } + + var originalValue = this.element.getStyle(property); + return { + style: property.camelize(), + originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), + targetValue: unit=='color' ? parseColor(value) : value, + unit: unit + }; + }.bind(this)).reject(function(transform){ + return ( + (transform.originalValue == transform.targetValue) || + ( + transform.unit != 'color' && + (isNaN(transform.originalValue) || isNaN(transform.targetValue)) + ) + ); + }); + }, + update: function(position) { + var style = { }, transform, i = this.transforms.length; + while(i--) + style[(transform = this.transforms[i]).style] = + transform.unit=='color' ? '#'+ + (Math.round(transform.originalValue[0]+ + (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + + (Math.round(transform.originalValue[1]+ + (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + + (Math.round(transform.originalValue[2]+ + (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : + (transform.originalValue + + (transform.targetValue - transform.originalValue) * position).toFixed(3) + + (transform.unit === null ? '' : transform.unit); + this.element.setStyle(style, true); + } +}); + +Effect.Transform = Class.create({ + initialize: function(tracks){ + this.tracks = []; + this.options = arguments[1] || { }; + this.addTracks(tracks); + }, + addTracks: function(tracks){ + tracks.each(function(track){ + track = $H(track); + var data = track.values().first(); + this.tracks.push($H({ + ids: track.keys().first(), + effect: Effect.Morph, + options: { style: data } + })); + }.bind(this)); + return this; + }, + play: function(){ + return new Effect.Parallel( + this.tracks.map(function(track){ + var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options'); + var elements = [$(ids) || $$(ids)].flatten(); + return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) }); + }).flatten(), + this.options + ); + } +}); + +Element.CSS_PROPERTIES = $w( + 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + + 'fontSize fontWeight height left letterSpacing lineHeight ' + + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ + 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + + 'right textIndent top width wordSpacing zIndex'); + +Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; + +String.__parseStyleElement = document.createElement('div'); +String.prototype.parseStyle = function(){ + var style, styleRules = $H(); + if (Prototype.Browser.WebKit) + style = new Element('div',{style:this}).style; + else { + String.__parseStyleElement.innerHTML = '
    '; + style = String.__parseStyleElement.childNodes[0].style; + } + + Element.CSS_PROPERTIES.each(function(property){ + if (style[property]) styleRules.set(property, style[property]); + }); + + if (Prototype.Browser.IE && this.include('opacity')) + styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]); + + return styleRules; +}; + +if (document.defaultView && document.defaultView.getComputedStyle) { + Element.getStyles = function(element) { + var css = document.defaultView.getComputedStyle($(element), null); + return Element.CSS_PROPERTIES.inject({ }, function(styles, property) { + styles[property] = css[property]; + return styles; + }); + }; +} else { + Element.getStyles = function(element) { + element = $(element); + var css = element.currentStyle, styles; + styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) { + results[property] = css[property]; + return results; + }); + if (!styles.opacity) styles.opacity = element.getOpacity(); + return styles; + }; +} + +Effect.Methods = { + morph: function(element, style) { + element = $(element); + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { })); + return element; + }, + visualEffect: function(element, effect, options) { + element = $(element); + var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[klass](element, options); + return element; + }, + highlight: function(element, options) { + element = $(element); + new Effect.Highlight(element, options); + return element; + } +}; + +$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ + 'pulsate shake puff squish switchOff dropOut').each( + function(effect) { + Effect.Methods[effect] = function(element, options){ + element = $(element); + Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); + return element; + }; + } +); + +$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( + function(f) { Effect.Methods[f] = Element[f]; } +); + +Element.addMethods(Effect.Methods); \ No newline at end of file diff --git a/public/javascripts/jquery-1.4.2.min.js b/public/javascripts/jquery-1.4.2.min.js new file mode 100644 index 0000000..7c24308 --- /dev/null +++ b/public/javascripts/jquery-1.4.2.min.js @@ -0,0 +1,154 @@ +/*! + * jQuery JavaScript Library v1.4.2 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Sat Feb 13 22:33:48 2010 -0500 + */ +(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, +Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& +(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, +a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== +"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, +function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
    a"; +var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected, +parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent= +false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n= +s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true, +applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando]; +else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this, +a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b=== +w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i, +cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected= +c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); +a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g, +function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split("."); +k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a), +C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B=0){a.type= +e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&& +f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive; +if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data", +e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a, +"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a, +d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, +e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift(); +t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D|| +g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, +CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, +g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, +text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, +setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= +h[3];l=0;for(m=h.length;l=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== +"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, +h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& +q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; +if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="

    ";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); +(function(){var g=s.createElement("div");g.innerHTML="
    ";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: +function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= +{},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== +"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", +d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? +a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== +1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"},F={option:[1,""],legend:[1,"
    ","
    "],thead:[1,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],col:[2,"","
    "],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
    ","
    "];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= +c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, +wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, +prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, +this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); +return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, +""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); +return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", +""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= +c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? +c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= +function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= +Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, +"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= +a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= +a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=//gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!== +"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("
    ").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this}, +serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), +function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href, +global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&& +e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)? +"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache=== +false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B= +false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since", +c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E|| +d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x); +g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status=== +1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b=== +"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional; +if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration=== +"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]|| +c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start; +this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now= +this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem, +e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b
    "; +a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b); +c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a, +d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top- +f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset": +"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in +e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window); diff --git a/public/javascripts/jquery.autocomplete.js b/public/javascripts/jquery.autocomplete.js new file mode 100644 index 0000000..a1a6537 --- /dev/null +++ b/public/javascripts/jquery.autocomplete.js @@ -0,0 +1,762 @@ +/* + * Autocomplete - jQuery plugin 1.1pre + * + * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Revision: $Id: jquery.autocomplete.js 5785 2008-07-12 10:37:33Z joern.zaefferer $ + * + */ + +;(function($) { + +$.fn.extend({ + autocomplete: function(urlOrData, options) { + var isUrl = typeof urlOrData == "string"; + options = $.extend({}, $.Autocompleter.defaults, { + url: isUrl ? urlOrData : null, + data: isUrl ? null : urlOrData, + delay: isUrl ? $.Autocompleter.defaults.delay : 10, + max: options && !options.scroll ? 10 : 150 + }, options); + + // if highlight is set to false, replace it with a do-nothing function + options.highlight = options.highlight || function(value) { return value; }; + + // if the formatMatch option is not specified, then use formatItem for backwards compatibility + options.formatMatch = options.formatMatch || options.formatItem; + + return this.each(function() { + new $.Autocompleter(this, options); + }); + }, + result: function(handler) { + return this.bind("result", handler); + }, + search: function(handler) { + return this.trigger("search", [handler]); + }, + flushCache: function() { + return this.trigger("flushCache"); + }, + setOptions: function(options){ + return this.trigger("setOptions", [options]); + }, + unautocomplete: function() { + return this.trigger("unautocomplete"); + } +}); + +$.Autocompleter = function(input, options) { + + var KEY = { + UP: 38, + DOWN: 40, + DEL: 46, + TAB: 9, + RETURN: 13, + ESC: 27, + COMMA: 188, + PAGEUP: 33, + PAGEDOWN: 34, + BACKSPACE: 8 + }; + + // Create $ object for input element + var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); + + var timeout; + var previousValue = ""; + var cache = $.Autocompleter.Cache(options); + var hasFocus = 0; + var lastKeyPressCode; + var config = { + mouseDownOnSelect: false + }; + var select = $.Autocompleter.Select(options, input, selectCurrent, config); + + var blockSubmit; + + // prevent form submit in opera when selecting with return key + $.browser.opera && $(input.form).bind("submit.autocomplete", function() { + if (blockSubmit) { + blockSubmit = false; + return false; + } + }); + + // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all + $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { + // track last key pressed + lastKeyPressCode = event.keyCode; + switch(event.keyCode) { + + case KEY.UP: + event.preventDefault(); + if ( select.visible() ) { + select.prev(); + } else { + onChange(0, true); + } + break; + + case KEY.DOWN: + event.preventDefault(); + if ( select.visible() ) { + select.next(); + } else { + onChange(0, true); + } + break; + + case KEY.PAGEUP: + event.preventDefault(); + if ( select.visible() ) { + select.pageUp(); + } else { + onChange(0, true); + } + break; + + case KEY.PAGEDOWN: + event.preventDefault(); + if ( select.visible() ) { + select.pageDown(); + } else { + onChange(0, true); + } + break; + + // matches also semicolon + case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: + case KEY.TAB: + case KEY.RETURN: + if( selectCurrent() ) { + // stop default to prevent a form submit, Opera needs special handling + event.preventDefault(); + blockSubmit = true; + return false; + } + break; + + case KEY.ESC: + select.hide(); + break; + + default: + clearTimeout(timeout); + timeout = setTimeout(onChange, options.delay); + break; + } + }).focus(function(){ + // track whether the field has focus, we shouldn't process any + // results if the field no longer has focus + hasFocus++; + }).blur(function() { + hasFocus = 0; + if (!config.mouseDownOnSelect) { + hideResults(); + } + }).click(function() { + // show select when clicking in a focused field + if ( hasFocus++ > 1 && !select.visible() ) { + onChange(0, true); + } + }).bind("search", function() { + // TODO why not just specifying both arguments? + var fn = (arguments.length > 1) ? arguments[1] : null; + function findValueCallback(q, data) { + var result; + if( data && data.length ) { + for (var i=0; i < data.length; i++) { + if( data[i].result.toLowerCase() == q.toLowerCase() ) { + result = data[i]; + break; + } + } + } + if( typeof fn == "function" ) fn(result); + else $input.trigger("result", result && [result.data, result.value]); + } + $.each(trimWords($input.val()), function(i, value) { + request(value, findValueCallback, findValueCallback); + }); + }).bind("flushCache", function() { + cache.flush(); + }).bind("setOptions", function() { + $.extend(options, arguments[1]); + // if we've updated the data, repopulate + if ( "data" in arguments[1] ) + cache.populate(); + }).bind("unautocomplete", function() { + select.unbind(); + $input.unbind(); + $(input.form).unbind(".autocomplete"); + }); + + + function selectCurrent() { + var selected = select.selected(); + if( !selected ) + return false; + + var v = selected.result; + previousValue = v; + + if ( options.multiple ) { + var words = trimWords($input.val()); + if ( words.length > 1 ) { + v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v; + } + v += options.multipleSeparator; + } + + $input.val(v); + hideResultsNow(); + $input.trigger("result", [selected.data, selected.value]); + return true; + } + + function onChange(crap, skipPrevCheck) { + if( lastKeyPressCode == KEY.DEL ) { + select.hide(); + return; + } + + var currentValue = $input.val(); + + if ( !skipPrevCheck && currentValue == previousValue ) + return; + + previousValue = currentValue; + + currentValue = lastWord(currentValue); + if ( currentValue.length >= options.minChars) { + $input.addClass(options.loadingClass); + if (!options.matchCase) + currentValue = currentValue.toLowerCase(); + request(currentValue, receiveData, hideResultsNow); + } else { + stopLoading(); + select.hide(); + } + }; + + function trimWords(value) { + if ( !value ) { + return [""]; + } + var words = value.split( options.multipleSeparator ); + var result = []; + $.each(words, function(i, value) { + if ( $.trim(value) ) + result[i] = $.trim(value); + }); + return result; + } + + function lastWord(value) { + if ( !options.multiple ) + return value; + var words = trimWords(value); + return words[words.length - 1]; + } + + // fills in the input box w/the first match (assumed to be the best match) + // q: the term entered + // sValue: the first matching result + function autoFill(q, sValue){ + // autofill in the complete box w/the first match as long as the user hasn't entered in more data + // if the last user key pressed was backspace, don't autofill + if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) { + // fill in the value (keep the case the user has typed) + $input.val($input.val() + sValue.substring(lastWord(previousValue).length)); + // select the portion of the value not typed by the user (so the next character will erase) + $.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length); + } + }; + + function hideResults() { + clearTimeout(timeout); + timeout = setTimeout(hideResultsNow, 200); + }; + + function hideResultsNow() { + var wasVisible = select.visible(); + select.hide(); + clearTimeout(timeout); + stopLoading(); + if (options.mustMatch) { + // call search and run callback + $input.search( + function (result){ + // if no value found, clear the input box + if( !result ) { + if (options.multiple) { + var words = trimWords($input.val()).slice(0, -1); + $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") ); + } + else + $input.val( "" ); + } + } + ); + } + if (wasVisible) + // position cursor at end of input field + $.Autocompleter.Selection(input, input.value.length, input.value.length); + }; + + function receiveData(q, data) { + if ( data && data.length && hasFocus ) { + stopLoading(); + select.display(data, q); + autoFill(q, data[0].value); + select.show(); + } else { + hideResultsNow(); + } + }; + + function request(term, success, failure) { + if (!options.matchCase) + term = term.toLowerCase(); + var data = cache.load(term); + // recieve the cached data + if (data && data.length) { + success(term, data); + // if an AJAX url has been supplied, try loading the data now + } else if( (typeof options.url == "string") && (options.url.length > 0) ){ + + var extraParams = { + timestamp: +new Date() + }; + $.each(options.extraParams, function(key, param) { + extraParams[key] = typeof param == "function" ? param() : param; + }); + + $.ajax({ + // try to leverage ajaxQueue plugin to abort previous requests + mode: "abort", + // limit abortion to this input + port: "autocomplete" + input.name, + dataType: options.dataType, + url: options.url, + data: $.extend({ + q: lastWord(term), + limit: options.max + }, extraParams), + success: function(data) { + var parsed = options.parse && options.parse(data) || parse(data); + cache.add(term, parsed); + success(term, parsed); + } + }); + } else { + // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match + select.emptyList(); + failure(term); + } + }; + + function parse(data) { + var parsed = []; + var rows = data.split("\n"); + for (var i=0; i < rows.length; i++) { + var row = $.trim(rows[i]); + if (row) { + row = row.split("|"); + parsed[parsed.length] = { + data: row, + value: row[0], + result: options.formatResult && options.formatResult(row, row[0]) || row[0] + }; + } + } + return parsed; + }; + + function stopLoading() { + $input.removeClass(options.loadingClass); + }; + +}; + +$.Autocompleter.defaults = { + inputClass: "ac_input", + resultsClass: "ac_results", + loadingClass: "ac_loading", + minChars: 1, + delay: 400, + matchCase: false, + matchSubset: true, + matchContains: false, + cacheLength: 10, + max: 100, + mustMatch: false, + extraParams: {}, + selectFirst: true, + formatItem: function(row) { return row[0]; }, + formatMatch: null, + autoFill: false, + width: 0, + multiple: false, + multipleSeparator: ", ", + highlight: function(value, term) { + return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); + }, + scroll: true, + scrollHeight: 60 +}; + +$.Autocompleter.Cache = function(options) { + + var data = {}; + var length = 0; + + function matchSubset(s, sub) { + if (!options.matchCase) + s = s.toLowerCase(); + var i = s.indexOf(sub); + if (options.matchContains == "word"){ + i = s.toLowerCase().search("\\b" + sub.toLowerCase()); + } + if (i == -1) return false; + return i == 0 || options.matchContains; + }; + + function add(q, value) { + if (length > options.cacheLength){ + flush(); + } + if (!data[q]){ + length++; + } + data[q] = value; + } + + function populate(){ + if( !options.data ) return false; + // track the matches + var stMatchSets = {}, + nullData = 0; + + // no url was specified, we need to adjust the cache length to make sure it fits the local data store + if( !options.url ) options.cacheLength = 1; + + // track all options for minChars = 0 + stMatchSets[""] = []; + + // loop through the array and create a lookup structure + for ( var i = 0, ol = options.data.length; i < ol; i++ ) { + var rawValue = options.data[i]; + // if rawValue is a string, make an array otherwise just reference the array + rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; + + var value = options.formatMatch(rawValue, i+1, options.data.length); + if ( value === false ) + continue; + + var firstChar = value.charAt(0).toLowerCase(); + // if no lookup array for this character exists, look it up now + if( !stMatchSets[firstChar] ) + stMatchSets[firstChar] = []; + + // if the match is a string + var row = { + value: value, + data: rawValue, + result: options.formatResult && options.formatResult(rawValue) || value + }; + + // push the current match into the set list + stMatchSets[firstChar].push(row); + + // keep track of minChars zero items + if ( nullData++ < options.max ) { + stMatchSets[""].push(row); + } + }; + + // add the data items to the cache + $.each(stMatchSets, function(i, value) { + // increase the cache size + options.cacheLength++; + // add to the cache + add(i, value); + }); + } + + // populate any existing data + setTimeout(populate, 25); + + function flush(){ + data = {}; + length = 0; + } + + return { + flush: flush, + add: add, + populate: populate, + load: function(q) { + if (!options.cacheLength || !length) + return null; + /* + * if dealing w/local data and matchContains than we must make sure + * to loop through all the data collections looking for matches + */ + if( !options.url && options.matchContains ){ + // track all matches + var csub = []; + // loop through all the data grids for matches + for( var k in data ){ + // don't search through the stMatchSets[""] (minChars: 0) cache + // this prevents duplicates + if( k.length > 0 ){ + var c = data[k]; + $.each(c, function(i, x) { + // if we've got a match, add it to the array + if (matchSubset(x.value, q)) { + csub.push(x); + } + }); + } + } + return csub; + } else + // if the exact item exists, use it + if (data[q]){ + return data[q]; + } else + if (options.matchSubset) { + for (var i = q.length - 1; i >= options.minChars; i--) { + var c = data[q.substr(0, i)]; + if (c) { + var csub = []; + $.each(c, function(i, x) { + if (matchSubset(x.value, q)) { + csub[csub.length] = x; + } + }); + return csub; + } + } + } + return null; + } + }; +}; + +$.Autocompleter.Select = function (options, input, select, config) { + var CLASSES = { + ACTIVE: "ac_over" + }; + + var listItems, + active = -1, + data, + term = "", + needsInit = true, + element, + list; + + // Create results + function init() { + if (!needsInit) + return; + element = $("
    ") + .hide() + .addClass(options.resultsClass) + .css("position", "absolute") + .appendTo(document.body); + + list = $("
      ").appendTo(element).mouseover( function(event) { + if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') { + active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event)); + $(target(event)).addClass(CLASSES.ACTIVE); + } + }).click(function(event) { + $(target(event)).addClass(CLASSES.ACTIVE); + select(); + // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus + input.focus(); + return false; + }).mousedown(function() { + config.mouseDownOnSelect = true; + }).mouseup(function() { + config.mouseDownOnSelect = false; + }); + + if( options.width > 0 ) + element.css("width", options.width); + + needsInit = false; + } + + function target(event) { + var element = event.target; + while(element && element.tagName != "LI") + element = element.parentNode; + // more fun with IE, sometimes event.target is empty, just ignore it then + if(!element) + return []; + return element; + } + + function moveSelect(step) { + listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE); + movePosition(step); + var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE); + if(options.scroll) { + var offset = 0; + listItems.slice(0, active).each(function() { + offset += this.offsetHeight; + }); + if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) { + list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight()); + } else if(offset < list.scrollTop()) { + list.scrollTop(offset); + } + } + }; + + function movePosition(step) { + active += step; + if (active < 0) { + active = listItems.size() - 1; + } else if (active >= listItems.size()) { + active = 0; + } + } + + function limitNumberOfItems(available) { + return options.max && options.max < available + ? options.max + : available; + } + + function fillList() { + list.empty(); + var max = limitNumberOfItems(data.length); + for (var i=0; i < max; i++) { + if (!data[i]) + continue; + var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term); + if ( formatted === false ) + continue; + var li = $("
    • ").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0]; + $.data(li, "ac_data", data[i]); + } + listItems = list.find("li"); + if ( options.selectFirst ) { + listItems.slice(0, 1).addClass(CLASSES.ACTIVE); + active = 0; + } + // apply bgiframe if available + if ( $.fn.bgiframe ) + list.bgiframe(); + } + + return { + display: function(d, q) { + init(); + data = d; + term = q; + fillList(); + }, + next: function() { + moveSelect(1); + }, + prev: function() { + moveSelect(-1); + }, + pageUp: function() { + if (active != 0 && active - 8 < 0) { + moveSelect( -active ); + } else { + moveSelect(-8); + } + }, + pageDown: function() { + if (active != listItems.size() - 1 && active + 8 > listItems.size()) { + moveSelect( listItems.size() - 1 - active ); + } else { + moveSelect(8); + } + }, + hide: function() { + element && element.hide(); + listItems && listItems.removeClass(CLASSES.ACTIVE); + active = -1; + }, + visible : function() { + return element && element.is(":visible"); + }, + current: function() { + return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]); + }, + show: function() { + var offset = $(input).offset(); + element.css({ + width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(), + top: offset.top + input.offsetHeight, + left: offset.left + }).show(); + if(options.scroll) { + list.scrollTop(0); + list.css({ + maxHeight: options.scrollHeight, + overflow: 'auto' + }); + + if($.browser.msie && typeof document.body.style.maxHeight === "undefined") { + var listHeight = 0; + listItems.each(function() { + listHeight += this.offsetHeight; + }); + var scrollbarsVisible = listHeight > options.scrollHeight; + list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight ); + if (!scrollbarsVisible) { + // IE doesn't recalculate width when scrollbar disappears + listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) ); + } + } + + } + }, + selected: function() { + var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE); + return selected && selected.length && $.data(selected[0], "ac_data"); + }, + emptyList: function (){ + list && list.empty(); + }, + unbind: function() { + element && element.remove(); + } + }; +}; + +$.Autocompleter.Selection = function(field, start, end) { + if( field.createTextRange ){ + var selRange = field.createTextRange(); + selRange.collapse(true); + selRange.moveStart("character", start); + selRange.moveEnd("character", end); + selRange.select(); + } else if( field.setSelectionRange ){ + field.setSelectionRange(start, end); + } else { + if( field.selectionStart ){ + field.selectionStart = start; + field.selectionEnd = end; + } + } + field.focus(); +}; + +})(jQuery); \ No newline at end of file diff --git a/public/javascripts/jquery.form.js b/public/javascripts/jquery.form.js new file mode 100644 index 0000000..791ece7 --- /dev/null +++ b/public/javascripts/jquery.form.js @@ -0,0 +1,772 @@ +/*! + * jQuery Form Plugin + * version: 2.45 (09-AUG-2010) + * @requires jQuery v1.3.2 or later + * + * Examples and documentation at: http://malsup.com/jquery/form/ + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ +;(function($) { + +/* + Usage Note: + ----------- + Do not use both ajaxSubmit and ajaxForm on the same form. These + functions are intended to be exclusive. Use ajaxSubmit if you want + to bind your own submit handler to the form. For example, + + $(document).ready(function() { + $('#myForm').bind('submit', function() { + $(this).ajaxSubmit({ + target: '#output' + }); + return false; // <-- important! + }); + }); + + Use ajaxForm when you want the plugin to manage all the event binding + for you. For example, + + $(document).ready(function() { + $('#myForm').ajaxForm({ + target: '#output' + }); + }); + + When using ajaxForm, the ajaxSubmit function will be invoked for you + at the appropriate time. +*/ + +/** + * ajaxSubmit() provides a mechanism for immediately submitting + * an HTML form using AJAX. + */ +$.fn.ajaxSubmit = function(options) { + // fast fail if nothing selected (http://dev.jquery.com/ticket/2752) + if (!this.length) { + log('ajaxSubmit: skipping submit process - no element selected'); + return this; + } + + if (typeof options == 'function') { + options = { success: options }; + } + + var url = $.trim(this.attr('action')); + if (url) { + // clean url (don't include hash vaue) + url = (url.match(/^([^#]+)/)||[])[1]; + } + url = url || window.location.href || ''; + + options = $.extend(true, { + url: url, + type: this.attr('method') || 'GET', + iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank' + }, options); + + // hook for manipulating the form data before it is extracted; + // convenient for use with rich editors like tinyMCE or FCKEditor + var veto = {}; + this.trigger('form-pre-serialize', [this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-pre-serialize trigger'); + return this; + } + + // provide opportunity to alter form data before it is serialized + if (options.beforeSerialize && options.beforeSerialize(this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSerialize callback'); + return this; + } + + var n,v,a = this.formToArray(options.semantic); + if (options.data) { + options.extraData = options.data; + for (n in options.data) { + if(options.data[n] instanceof Array) { + for (var k in options.data[n]) { + a.push( { name: n, value: options.data[n][k] } ); + } + } + else { + v = options.data[n]; + v = $.isFunction(v) ? v() : v; // if value is fn, invoke it + a.push( { name: n, value: v } ); + } + } + } + + // give pre-submit callback an opportunity to abort the submit + if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSubmit callback'); + return this; + } + + // fire vetoable 'validate' event + this.trigger('form-submit-validate', [a, this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-submit-validate trigger'); + return this; + } + + var q = $.param(a); + + if (options.type.toUpperCase() == 'GET') { + options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q; + options.data = null; // data is null for 'get' + } + else { + options.data = q; // data is the query string for 'post' + } + + var $form = this, callbacks = []; + if (options.resetForm) { + callbacks.push(function() { $form.resetForm(); }); + } + if (options.clearForm) { + callbacks.push(function() { $form.clearForm(); }); + } + + // perform a load on the target only if dataType is not provided + if (!options.dataType && options.target) { + var oldSuccess = options.success || function(){}; + callbacks.push(function(data) { + var fn = options.replaceTarget ? 'replaceWith' : 'html'; + $(options.target)[fn](data).each(oldSuccess, arguments); + }); + } + else if (options.success) { + callbacks.push(options.success); + } + + options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg + var context = options.context || options; // jQuery 1.4+ supports scope context + for (var i=0, max=callbacks.length; i < max; i++) { + callbacks[i].apply(context, [data, status, xhr || $form, $form]); + } + }; + + // are there files to upload? + var fileInputs = $('input:file', this).length > 0; + var mp = 'multipart/form-data'; + var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp); + + // options.iframe allows user to force iframe mode + // 06-NOV-09: now defaulting to iframe mode if file input is detected + if (options.iframe !== false && (fileInputs || options.iframe || multipart)) { + // hack to fix Safari hang (thanks to Tim Molendijk for this) + // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d + if (options.closeKeepAlive) { + $.get(options.closeKeepAlive, fileUpload); + } + else { + fileUpload(); + } + } + else { + $.ajax(options); + } + + // fire 'notify' event + this.trigger('form-submit-notify', [this, options]); + return this; + + + // private function for handling file uploads (hat tip to YAHOO!) + function fileUpload() { + var form = $form[0]; + + if ($(':input[name=submit],:input[id=submit]', form).length) { + // if there is an input with a name or id of 'submit' then we won't be + // able to invoke the submit fn on the form (at least not x-browser) + alert('Error: Form elements must not have name or id of "submit".'); + return; + } + + var s = $.extend(true, {}, $.ajaxSettings, options); + s.context = s.context || s; + var id = 'jqFormIO' + (new Date().getTime()); + var $io = $('