diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..bf8d6f2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +.bundle +.convox +.env +.git +!.git/logs/HEAD +db/*.sqlite3 +db/*.sqlite3-journal +log/* +tmp/* +!tmp/.gitkeep +sample-assets* +dump.rdb diff --git a/.env b/.env new file mode 100644 index 0000000..86b8784 --- /dev/null +++ b/.env @@ -0,0 +1,14 @@ +#POSTGRES_DB=goldenseal +#POSTGRES_HOST=postgres +#POSTGRES_PASSWORD=DatabaseFTW +#POSTGRES_USER=postgres +DEFAULT_HOST=localhost:8080 +DEPLOY_HOOK=CHANGEME +DOCKER_PORT=8000:80 +PASSENGER_APP_ENV=development +REGISTRY_HOST=registry.gitlab.com +REGISTRY_URI=/notch8/washington-goldenseal +SITE_URI=web.goldenseal.staging.notch8network.com +SOLR_URL=http://localhost:8983/solr/development +TAG=dev +TEST_DB=goldenseal_test diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..5f6c3dd --- /dev/null +++ b/.env.development @@ -0,0 +1,4 @@ +VIRTUAL_PORT: 80 +VIRTUAL_HOST: goldenseal.docker +SKIP_LDAP: true + diff --git a/.gitignore b/.gitignore index f8f8453..5c359ec 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ /db/*.sqlite3 /db/*.sqlite3-journal +public/uploads + /log/* !/log/.keep /tmp @@ -23,3 +25,11 @@ config/solr.yml config/redis.yml config/resque-pool.yml config/ldap.yml + +public/uploads +sample-assets +sample_assets.tgz +**/*.swo +**/*.swp + +ops/fits-1.0.5/* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..e35a8f6 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,66 @@ +image: docker:17.04 +services: + - docker:dind + +stages: + - build + - review + +variables: + DOCKER_DRIVER: overlay + DOCKER_PORT: 80 + PASSENGER_APP_ENV: development + RAILS_ENV: development + REGISTRY_HOST: registry.gitlab.com + REGISTRY_URI: /notch8/washington-goldenseal + TEST_DB: test + SECRET_KEY_BASE: 729ac8fd6f2ad97b45d9bdf6cbc6a83e01c53fcc0e7b3c2afac1dfb2906deccfd39b83e534938d4e2729e3787a72043794eb045568f089efddce51c17cc4160d + SKIP_LDAP: 'true' + +build: + stage: build + script: + - docker login -u "gitlab-ci-token" -p "$CI_BUILD_TOKEN" $CI_REGISTRY + - docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_PROJECT_NAME-$CI_BUILD_REF_SLUG" . + - docker push "$CI_REGISTRY_IMAGE:$CI_PROJECT_NAME-$CI_BUILD_REF_SLUG" + variables: + GIT_STRATEGY: fetch + tags: + - docker + +review: + stage: review + type: deploy + environment: + name: review/$CI_PROJECT_NAME-$CI_BUILD_REF_SLUG + url: http://web.$CI_PROJECT_NAME-$CI_BUILD_REF_SLUG.staging.notch8network.com + on_stop: stop_review + only: + - branches + except: + - master + script: + - wget https://github.com/rancher/cli/releases/download/v0.4.1/rancher-linux-amd64-v0.4.1.tar.gz + - tar zxfv rancher-linux-amd64-v0.4.1.tar.gz + - export RANCHER_ENVIRONMENT=staging + - export TAG=$CI_PROJECT_NAME-$CI_BUILD_REF_SLUG + - export SITE_URI=web.$CI_PROJECT_NAME-$CI_BUILD_REF_SLUG.staging.notch8network.com + - ./rancher-v0.4.1/rancher up -p -d -u -c -s $TAG -f docker-compose.ci.yml + variables: + GIT_STRATEGY: fetch + tags: + - docker + +stop_review: + stage: review + script: + - wget https://github.com/rancher/cli/releases/download/v0.4.1/rancher-linux-amd64-v0.4.1.tar.gz + - tar zxfv rancher-linux-amd64-v0.4.1.tar.gz + - export RANCHER_ENVIRONMENT=staging + - ./rancher-v0.4.1/rancher rm $CI_PROJECT_NAME-$CI_BUILD_REF_SLUG + variables: + GIT_STRATEGY: none + when: manual + environment: + name: review/$CI_PROJECT_NAME-$CI_BUILD_REF_SLUG + action: stop diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1da95f3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM registry.gitlab.com/notch8/washington-goldenseal/base:latest + +ADD https://time.is/just build-time +COPY ops/nginx.sh /etc/service/nginx/run +COPY ops/webapp.conf /etc/nginx/sites-enabled/webapp.conf +COPY ops/env.conf /etc/nginx/main.d/env.conf + +COPY . $APP_HOME + +ADD https://projects.iq.harvard.edu/files/fits/files/fits-1.0.5.zip /opt/ +RUN unzip /opt/fits-1.0.5.zip -d /opt +RUN chmod a+x /opt/fits-1.0.5/fits.sh +ENV PATH="/opt/fits-1.0.5:${PATH}" + +RUN bundle check || bundle install +RUN chown -R app:app ./jetty + +CMD ["/sbin/my_init"] diff --git a/Dockerfile.base b/Dockerfile.base new file mode 100644 index 0000000..ba34378 --- /dev/null +++ b/Dockerfile.base @@ -0,0 +1,60 @@ +FROM phusion/passenger-ruby23:0.9.27 + +RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ + echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ + curl -sL https://deb.nodesource.com/setup_8.x | bash - && \ + echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | debconf-set-selections && \ + apt-get install -y software-properties-common && \ + apt-add-repository -y ppa:ansible/ansible && \ + add-apt-repository -y ppa:webupd8team/java && \ + apt-get update -qq && \ + apt-get install -y build-essential nodejs yarn pv ansible libsasl2-dev libpq-dev postgresql-client \ + oracle-java8-installer zip unzip imagemagick tzdata redis-server && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ + yarn config set no-progress && \ + yarn config set silent + +ENV JAVA_HOME /usr/lib/jvm/java-8-oracle + + +RUN rm /etc/nginx/sites-enabled/default +COPY ops/webapp.conf /etc/nginx/sites-enabled/webapp.conf +COPY ops/env.conf /etc/nginx/main.d/env.conf + +ENV APP_HOME /home/app/webapp +RUN mkdir $APP_HOME +WORKDIR $APP_HOME + +ENV BUNDLE_GEMFILE=$APP_HOME/Gemfile \ + BUNDLE_JOBS=4 + +COPY Gemfile* $APP_HOME/ +RUN bundle check || bundle install +#RUN yarn install + +RUN touch /var/log/worker.log && chmod 666 /var/log/worker.log +RUN mkdir /etc/service/worker +COPY ops/worker.sh /etc/service/worker/run +RUN chmod +x /etc/service/worker/run + +RUN mkdir /etc/service/redis +COPY ops/redis.sh /etc/service/redis/run +RUN chmod +x /etc/service/redis/run + + +COPY . $APP_HOME +RUN chown -R app $APP_HOME + +RUN /sbin/setuser app ./bin/setup && \ + /sbin/setuser app rake jetty:clean && \ + /sbin/setuser app rake curation_concerns:jetty:config +RUN touch /var/log/jetty.log && chmod 666 /var/log/jetty.log + + +# Asset complie and migrate if prod, otherwise just start nginx +COPY ops/nginx.sh /etc/service/nginx/run +RUN chmod +x /etc/service/nginx/run +RUN rm -f /etc/service/nginx/down + +CMD ["/sbin/my_init"] \ No newline at end of file diff --git a/Gemfile b/Gemfile index dd23da6..b15b491 100644 --- a/Gemfile +++ b/Gemfile @@ -39,6 +39,8 @@ gem 'riiif', '0.2.0' gem 'openseadragon', '~> 0.2.1' gem 'angularjs-rails', '~> 1.4.4' gem 'ldp', '~> 0.4.1' +gem 'blacklight_oai_provider', git: 'https://github.com/projectblacklight/blacklight_oai_provider.git', branch: 'v5.1' +gem 'blacklight-spotlight', git: 'https://github.com/projectblacklight/spotlight.git', tag: 'v0.17.1' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' @@ -71,3 +73,12 @@ gem 'rsolr', '~> 1.0.6' gem 'devise' gem 'devise_ldap_authenticatable' gem 'devise-guests', '~> 0.3' + +gem 'font-awesome-rails' + +gem 'friendly_id' +gem 'sitemap_generator' +gem 'blacklight-gallery', '>= 0.3.0' +gem 'blacklight-oembed' +gem 'social-share-button' +gem 'devise_invitable' diff --git a/Gemfile.lock b/Gemfile.lock index 8f76fdd..140d79a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,51 @@ +GIT + remote: https://github.com/projectblacklight/blacklight_oai_provider.git + revision: 3ec965543710f9702bf532e2123618fbf24af6b6 + branch: v5.1 + specs: + blacklight_oai_provider (5.0.0) + blacklight (~> 5.13) + oai (~> 0.4) + rails (~> 4.0) + +GIT + remote: https://github.com/projectblacklight/spotlight.git + revision: 10713b6084b68e3af15d1418715149b66299cd60 + tag: v0.17.1 + specs: + blacklight-spotlight (0.17.1) + acts-as-taggable-on (~> 3.5) + autoprefixer-rails + blacklight (~> 5.16) + blacklight-gallery (>= 0.3.0) + blacklight-oembed (>= 0.0.3) + bootstrap_form (~> 2.2) + breadcrumbs_on_rails (~> 2.3.0) + cancancan + carrierwave + carrierwave-crop + devise (~> 3.0) + devise_invitable (~> 1.5) + faraday + faraday_middleware + friendly_id (~> 5.1.0) + github-markup + google-api-client (~> 0.8.0) + legato + lodash-rails + mail_form (~> 1.5, >= 1.5.1) + mini_magick + nokogiri + oauth2 + openseadragon + paper_trail (~> 4.0) + rails (~> 4.0, >= 4.2.0) + roar-rails + sir_trevor_rails (~> 0.5.0b1) + social-share-button (~> 0.1.5) + tophat + underscore-rails (~> 1.6) + GEM remote: https://rubygems.org/ specs: @@ -57,20 +105,26 @@ GEM activemodel (= 4.2.5) activesupport (= 4.2.5) arel (~> 6.0) - activerecord-import (0.10.0) - activerecord (>= 3.0) + activerecord-import (0.22.0) + activerecord (>= 3.2) activesupport (4.2.5) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) + acts-as-taggable-on (3.5.0) + activerecord (>= 3.2, < 5) addressable (2.4.0) angularjs-rails (1.4.4) arel (6.0.3) ast (2.1.0) astrolabe (1.3.1) parser (~> 2.2) + autoparse (0.3.3) + addressable (>= 2.3.1) + extlib (>= 0.9.15) + multi_json (>= 1.0.0) autoprefixer-rails (6.2.3) execjs json @@ -84,6 +138,16 @@ GEM nokogiri (~> 1.6) rails (>= 3.2.6, < 5) rsolr (~> 1.0.11) + blacklight-gallery (0.4.1) + blacklight (>= 5.12, < 7) + bootstrap-sass (~> 3.0) + openseadragon (>= 0.2.0) + rails + blacklight-oembed (0.2.0) + blacklight (>= 5.0, < 7) + bootstrap-sass (~> 3.0) + rails + ruby-oembed blacklight_advanced_search (5.2.1) blacklight (~> 5.15) parslet @@ -91,6 +155,7 @@ GEM bootstrap-sass (3.3.6) autoprefixer-rails (>= 5.2.1) sass (>= 3.3.4) + bootstrap_form (2.7.0) breadcrumbs_on_rails (2.3.1) builder (3.2.2) byebug (6.0.2) @@ -113,6 +178,15 @@ GEM rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) + carrierwave (0.10.0) + activemodel (>= 3.2.0) + activesupport (>= 3.2.0) + json (>= 1.7) + mime-types (>= 1.16) + carrierwave-crop (0.1.2) + carrierwave (>= 0.8.0, < 0.11.0) + jquery-rails + rails (>= 3.2) childprocess (0.5.8) ffi (~> 1.0, >= 1.0.11) coffee-rails (4.1.0) @@ -145,6 +219,8 @@ GEM resque (~> 1.23) resque-pool (~> 0.3) daemons (1.2.3) + declarative (0.0.10) + declarative-option (0.1.0) deprecation (0.2.2) activesupport devise (3.5.2) @@ -156,12 +232,14 @@ GEM warden (~> 1.2.3) devise-guests (0.3.3) devise + devise_invitable (1.6.1) + actionmailer (>= 3.2.6) + devise (>= 3.2.0) devise_ldap_authenticatable (0.8.5) devise (>= 3.4.1) net-ldap (>= 0.6.0, <= 0.11) diff-lcs (1.2.5) - domain_name (0.5.25) - unf (>= 0.0.5, < 1.0.0) + diffy (3.2.0) ebnf (1.0.0) rdf (~> 1.1) sxp (~> 0.1, >= 0.1.3) @@ -169,6 +247,7 @@ GEM nokogiri (>= 1.4.3) erubis (2.7.0) execjs (2.6.0) + extlib (0.9.16) factory_girl (4.5.0) activesupport (>= 3.0.0) factory_girl_rails (4.5.0) @@ -176,14 +255,38 @@ GEM railties (>= 3.0.0) faraday (0.9.2) multipart-post (>= 1.2, < 3) + faraday_middleware (0.12.2) + faraday (>= 0.7.4, < 1.0) ffi (1.9.10) + font-awesome-rails (4.5.0.0) + railties (>= 3.2, < 5.0) + friendly_id (5.1.0) + activerecord (>= 4.0.0) + github-markup (2.0.0) globalid (0.3.6) activesupport (>= 4.1.0) + google-api-client (0.8.7) + activesupport (>= 3.2, < 5.0) + addressable (~> 2.3) + autoparse (~> 0.3) + extlib (~> 0.9) + faraday (~> 0.9) + googleauth (~> 0.3) + launchy (~> 2.4) + multi_json (~> 1.10) + retriable (~> 1.4) + signet (~> 0.6) + googleauth (0.5.1) + faraday (~> 0.9) + jwt (~> 1.4) + logging (~> 2.0) + memoist (~> 0.12) + multi_json (~> 1.11) + os (~> 0.9) + signet (~> 0.7) haml (4.0.7) tilt htmlentities (4.3.4) - http-cookie (1.0.2) - domain_name (~> 0.5) http_logger (0.5.1) hydra-access-controls (9.5.0) active-fedora (~> 9.0) @@ -248,14 +351,19 @@ GEM json-ld (1.99.0) multi_json (~> 1.11) rdf (~> 1.99) + jwt (1.5.6) kaminari (0.16.3) actionpack (>= 3.0.0) activesupport (>= 3.0.0) + launchy (2.4.3) + addressable (~> 2.3) ldp (0.4.1) faraday http_logger linkeddata (>= 1.1) slop + legato (0.7.0) + multi_json less (2.6.0) commonjs (~> 0.2.7) less-rails (2.7.0) @@ -286,24 +394,34 @@ GEM rdf-xsd (~> 1.1, >= 1.1.5) sparql (~> 1.99) sparql-client (~> 1.99) + little-plugger (1.1.4) + lodash-rails (4.17.5) + railties (>= 3.1) logger (1.2.8) + logging (2.2.2) + little-plugger (~> 1.1) + multi_json (~> 1.10) loofah (2.0.3) nokogiri (>= 1.5.9) mail (2.6.3) mime-types (>= 1.16, < 3) + mail_form (1.7.1) + actionmailer (>= 3.2, < 6) + activemodel (>= 3.2, < 6) + memoist (0.16.0) mime-types (2.99) mini_magick (4.3.6) mini_portile2 (2.0.0) minitest (5.8.3) mono_logger (1.1.0) multi_json (1.11.2) + multi_xml (0.6.0) multipart-post (2.0.0) net-http-persistent (2.9.4) net-ldap (0.11) net-scp (1.2.1) net-ssh (>= 2.6.5) net-ssh (3.0.1) - netrc (0.11.0) noid (0.8.0) nokogiri (1.6.7.1) mini_portile2 (~> 2.0.0.rc2) @@ -311,6 +429,16 @@ GEM activesupport (>= 3.2.18) i18n nokogiri + oai (0.4.0) + builder (>= 3.1.0) + faraday + faraday_middleware + oauth2 (1.4.0) + faraday (>= 0.8, < 0.13) + jwt (~> 1.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) om (3.1.0) activemodel activesupport @@ -319,6 +447,11 @@ GEM openseadragon (0.2.1) rails (> 3.2.0) orm_adapter (0.5.0) + os (0.9.6) + paper_trail (4.2.0) + activerecord (>= 3.0, < 6.0) + activesupport (>= 3.0, < 6.0) + request_store (~> 1.1) parser (2.2.2.6) ast (>= 1.1, < 3.0) parslet (1.7.1) @@ -326,12 +459,12 @@ GEM pg (0.18.3) posix-spawn (0.3.11) powerpack (0.1.1) - qa (0.5.0) + qa (0.11.1) activerecord-import deprecation - nokogiri (~> 1.6.0) - rails (>= 3.2.13, < 5.0) - rest-client + faraday + nokogiri (~> 1.6) + rails (>= 4.2.0, < 6.0) rack (1.6.4) rack-protection (1.5.3) rack @@ -416,13 +549,20 @@ GEM rdf (~> 1.1, >= 1.1.10) rdf-xsd (1.99.0) rdf (~> 1.99) - rdoc (4.2.0) + rdoc (4.3.0) + redcarpet (3.4.0) redis (3.2.2) redis-namespace (1.5.2) redis (~> 3.0, >= 3.0.4) redlock (0.1.5) redis (~> 3, >= 3.0.5) ref (2.0.0) + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) + request_store (1.4.1) + rack (>= 1.4) responders (2.1.0) railties (>= 4.2.0, < 5) resque (1.25.2) @@ -440,12 +580,18 @@ GEM resque sass-rails twitter-bootstrap-rails - rest-client (1.8.0) - http-cookie (>= 1.0.2, < 2.0) - mime-types (>= 1.16, < 3.0) - netrc (~> 0.7) + retriable (1.4.1) riiif (0.2.0) rails (> 3.2.0) + roar (1.1.0) + representable (~> 3.0.0) + roar-rails (1.1.0) + actionpack + railties (>= 3.0.0) + responders + roar (~> 1.1.0) + test_xml (>= 0.1.6) + uber (< 0.2.0) rsolr (1.0.13) builder (>= 2.1.2) rspec-core (3.3.2) @@ -471,6 +617,7 @@ GEM powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.4) + ruby-oembed (0.12.0) ruby-progressbar (1.7.5) rubyzip (1.1.7) sass (3.4.20) @@ -483,6 +630,11 @@ GEM sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) + signet (0.8.1) + addressable (~> 2.3) + faraday (~> 0.9) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) simple_form (3.1.1) actionpack (~> 4.0) activemodel (~> 4.0) @@ -490,7 +642,17 @@ GEM rack (~> 1.4) rack-protection (~> 1.4) tilt (>= 1.3, < 3) + sir_trevor_rails (0.5.1) + multi_json (~> 1.0) + rails (>= 3, < 6) + redcarpet (>= 2.0.1, < 4) + twitter-text (~> 1.4) + sitemap_generator (6.0.1) + builder (~> 3.0) slop (4.2.1) + social-share-button (0.1.10) + coffee-rails + sass-rails solrizer (3.3.0) activesupport daemons @@ -523,12 +685,17 @@ GEM net-ssh (>= 2.8.0) stomp (1.3.4) sxp (0.1.5) + test_xml (0.1.8) + diffy (~> 3.0) + nokogiri (>= 1.3.2) therubyracer (0.12.2) libv8 (~> 3.16.14.0) ref thor (0.19.1) thread_safe (0.3.5) tilt (2.0.1) + tophat (2.3.1) + actionpack (>= 3.0.0) turbolinks (2.5.3) coffee-rails twitter-bootstrap-rails (3.2.2) @@ -536,14 +703,18 @@ GEM execjs (>= 2.2.2, >= 2.2) less-rails (>= 2.5.0) railties (>= 3.1) + twitter-text (1.14.7) + unf (~> 0.1.0) tzinfo (1.2.2) thread_safe (~> 0.1) + uber (0.1.0) uglifier (2.7.2) execjs (>= 0.3.0) json (>= 1.8.0) + underscore-rails (1.8.3) unf (0.1.4) unf_ext - unf_ext (0.0.7.1) + unf_ext (0.0.7.5) vegas (0.1.11) rack (>= 1.0.0) warden (1.2.3) @@ -557,6 +728,10 @@ PLATFORMS DEPENDENCIES angularjs-rails (~> 1.4.4) + blacklight-gallery (>= 0.3.0) + blacklight-oembed + blacklight-spotlight! + blacklight_oai_provider! byebug capistrano capistrano-bundler @@ -567,8 +742,11 @@ DEPENDENCIES curation_concerns (~> 0.5.0) devise devise-guests (~> 0.3) + devise_invitable devise_ldap_authenticatable factory_girl_rails + font-awesome-rails + friendly_id hydra-file_characterization (= 0.3.3) jbuilder (~> 2.0) jettywrapper @@ -586,6 +764,8 @@ DEPENDENCIES rubocop sass-rails (~> 5.0) sdoc (~> 0.4.0) + sitemap_generator + social-share-button spring sqlite3 therubyracer @@ -593,4 +773,4 @@ DEPENDENCIES uglifier (>= 1.3.0) BUNDLED WITH - 1.11.2 + 1.16.0 diff --git a/README.md b/README.md index 1b43a76..e01aaf8 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,22 @@ To start worker(s) to run the jobs: QUEUE=* VERBOSE=1 rake resque:work ``` +## Jetty and Solr +Just like in production, Solr is run in the same container as the main app in dev and staging environments. Consistency between environments trumps Docker best practices here, so it is what it is. + +Jetty and Solr are started automatically by the container by my_init. See ```/etc/services/jetty/run``` for how this happens. Blacklight, Curation Concerns, Spotlight, and Jetty all provide rake tasks to do this. The one we use is Jetty: ```rake jetty:start```. There are a few more rake tasks for managing the process that you can see by running ```rake --tasks```. Solr runs on port 8983. + +## Docker and the base image +In development and the staging environments, we use the phusion/passenger-ruby Docker image. ```Dockerfile.base``` is used to setup the container from scratch, including all the packages, assets, and startup scripts. ```Dockerfile``` is used during deployment, pulling the cached base image, and loading the code for the application quickly. + +### Some important file locations +* Anything in /etc/services/**/run will be run by the ```/sbin/my_init``` script that phusion/passenger-ruby uses to boot +* The home directory for the app is ```/home/app/webapp``` +* In the code base, you'll find dev ops stuff in ```/ops``` +* ```docker-compose.yml``` is used to define your development environment +* ```docker-compose-ci.yml``` is used to define review environment on staging +* ```.gitlab-ci.yml``` defines the pipeline on gitlab to build and deploy test and staging environments + ## Run the test suite * Make sure jetty is running @@ -58,7 +74,7 @@ QUEUE=* VERBOSE=1 rake resque:work ## Production Installation -We recommend using Ansible to create production instances of Goldenseal. Download https://github.com/acozine/sufia-centos/releases/tag/0.1 and symlink the roles subdirectory of the sufia-centos code into the ansible subudirectory of the Goldenseal code: +We recommend using Ansible to create production instances of Goldenseal. Download https://github.com/acozine/sufia-centos and symlink the roles subdirectory of the sufia-centos code into the ansible subudirectory of the Goldenseal code: ``` sudo ln -s /path/to/sufia-centos/roles /path/to/goldenseal/ansible/roles ``` @@ -89,6 +105,8 @@ We have tested Goldenseal with the following versions of its dependencies: ## Importing Records ### Importing records with TEI or VRA files +NOTE: This happens automatically on staging servers + TEI files can be parsed to create Audio, Video, or Text records. VRA records can be parsed to produce Image records. The instructions are basically the same either way. * Create a directory and add all the TEI (or VRA) files to that directory. @@ -127,3 +145,12 @@ The `tmp/uploads` directory is where uploaded files are temporarily stored befor You must be sure that all the background jobs have finished using the files before you delete them, so rather than deleting everything under `tmp/uploads`, we recommend that you only delete files and directories that are more than a few days old. For a little more background [see story #199](https://github.com/curationexperts/goldenseal/issues/199) + +## Spotlight Notes +### Workflow +- Create Spotlight Exhibits from all Admin Sets +- Update search of index CC works to work with SL Exhibits +- autocomplete + - Set autocmplete_default_param in new Spotlight initializer +- Associate AdminSet to Exhibit +- Add "spotlight_exhibit_slug_xxxxxxx_bsi" to solr_params for works when associated to collection diff --git a/ansible/ansible-ec2.yml b/ansible/ansible-ec2.yml index 3348465..dd03076 100644 --- a/ansible/ansible-ec2.yml +++ b/ansible/ansible-ec2.yml @@ -25,8 +25,8 @@ - { role: ec2, sudo: yes } - { role: housekeeping, sudo: yes } - { role: ruby } - - { role: hydra-stack } - { role: services } + - { role: hydra-stack } - { role: passenger, sudo: yes } - { role: ffmpeg } - { role: imagemagick } diff --git a/ansible/ansible_vars.yml b/ansible/ansible_vars.yml index 53d76e1..ee5e960 100644 --- a/ansible/ansible_vars.yml +++ b/ansible/ansible_vars.yml @@ -3,10 +3,10 @@ ## uncomment and fill in other variables as desired # ## public ec2-specific variables -# ec2_region: # default is us-east-1 -# ec2_zone: # default is us-east-1d -# ec2_instance_type: # default is m3.medium, a moderate instance size -# ec2_image: # default is ami-96a818fe, CentOS 7 64-bit +ec2_region: us-west-2 # default is us-east-1 +ec2_zone: us-west-2b # default is us-east-1d +ec2_instance_type: m4.large # default is m3.medium, a moderate instance size +ec2_image: ami-a042f4d8 # default is ami-96a818fe, CentOS 7 64-bit # ec2_opt_vol_size: # default is 40 # ec2_vol_1: # default is "{{ hostvars['localhost'].ec2_vol }}" # @@ -18,17 +18,19 @@ ec2_secret_key: ec2_access_key: ## NO default - the Secret Access Key from your IAM credentials - starts with AKIA ec2_key: -## NO default - the key from EC2 that will authenticate your ssh connection +## NO default - the key name from EC2 that will authenticate your ssh connection ec2_security_group: ## NO default - looks like sg-xxxxxxx - the security group your instance will start in ec2_vpc: ## NO default - the VPC your instance will start in +vpc_subnet_id: +## NO default - the subnet id starts with subnet- ## if using EC2 Classic, comment out this variable and remove the line "assign_public_ip: yes" ## from the launch_ec2 role's tasks/main.yml # ## project-specific variables project_name: goldenseal -repo: https://github.com/curationexperts/goldenseal.git +repo: https://github.com/curationexperts/goldenseal.git #https://gitlab.com/notch8/washington-goldenseal.git rails_env: production # server_name: # FQDN, for example: wustl.curationexperts.com; default is 127.0.0.1 # install_path: # default is /opt/install @@ -48,7 +50,7 @@ hydra_db: ## name of the Postgresql database for the Rails app - insecure default hydra_pg_user: ## name of the Postgresql user for the Rails app - insecure default -hydra_pg_pass: +hydra_pg_pass: ## password for the Postgresql user for the Rails app - insecure default # ## fits variables diff --git a/app/assets/images/header-shield.svg b/app/assets/images/header-shield.svg new file mode 100644 index 0000000..68cefad --- /dev/null +++ b/app/assets/images/header-shield.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/loading.gif b/app/assets/images/loading.gif new file mode 100644 index 0000000..18b455c Binary files /dev/null and b/app/assets/images/loading.gif differ diff --git a/app/assets/images/wustl-4-color-rev-shield.svg b/app/assets/images/wustl-4-color-rev-shield.svg new file mode 100644 index 0000000..28a7210 --- /dev/null +++ b/app/assets/images/wustl-4-color-rev-shield.svg @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 9cdcd24..857782c 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -18,6 +18,7 @@ //= require openseadragon //= require angular //= require tei_viewer +//= require spotlight // ableplayer has a dependency on jquery.cookie //= require jquery.cookie diff --git a/app/assets/javascripts/blacklight_oembed.js b/app/assets/javascripts/blacklight_oembed.js new file mode 100644 index 0000000..72acb3e --- /dev/null +++ b/app/assets/javascripts/blacklight_oembed.js @@ -0,0 +1,5 @@ +//= require 'blacklight_oembed/jquery.oembed.js' + +Blacklight.onLoad(function() { + $('[data-embed-url]').oEmbed(); +}); \ No newline at end of file diff --git a/app/assets/javascripts/spotlight.js b/app/assets/javascripts/spotlight.js new file mode 100644 index 0000000..04ac194 --- /dev/null +++ b/app/assets/javascripts/spotlight.js @@ -0,0 +1 @@ +//= require spotlight/application \ No newline at end of file diff --git a/app/assets/javascripts/spotlight/blocks/browse_block.js b/app/assets/javascripts/spotlight/blocks/browse_block.js new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/javascripts/spotlight/blocks/pages_block.js b/app/assets/javascripts/spotlight/blocks/pages_block.js new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/app/assets/javascripts/spotlight/blocks/pages_block.js @@ -0,0 +1 @@ + diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 82b5b05..75ee4c4 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -15,8 +15,10 @@ *= require_self */ - @import 'curation_concerns/curation_concerns'; @import 'openseadragon/openseadragon'; @import 'tei_viewer'; @import 'video'; +@import 'font-awesome'; +@import 'wustl'; +@import 'spotlight'; diff --git a/app/assets/stylesheets/blacklight_oembed.css.scss b/app/assets/stylesheets/blacklight_oembed.css.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/stylesheets/rights.scss b/app/assets/stylesheets/rights.scss new file mode 100644 index 0000000..5301e13 --- /dev/null +++ b/app/assets/stylesheets/rights.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the rights controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/spotlight.scss b/app/assets/stylesheets/spotlight.scss new file mode 100644 index 0000000..3ba194a --- /dev/null +++ b/app/assets/stylesheets/spotlight.scss @@ -0,0 +1,11 @@ +/* +*= require social-share-button +*= require bootstrap-tagsinput +*= require jquery.jcrop +*/ +@import 'spotlight/variables_bootstrap'; +@import 'bootstrap-sprockets'; +@import 'sir-trevor'; +@import 'sir-trevor-icons'; +@import 'bootstrap'; +@import 'spotlight/spotlight'; \ No newline at end of file diff --git a/app/assets/stylesheets/tei_viewer.scss b/app/assets/stylesheets/tei_viewer.scss index e3d2a63..f07e75a 100644 --- a/app/assets/stylesheets/tei_viewer.scss +++ b/app/assets/stylesheets/tei_viewer.scss @@ -1,6 +1,7 @@ #tei-container { - height: 620px; - overflow: auto; + // height: 620px; + // overflow: auto; + border: 1px solid #ddd; border-radius: 5px; background-color: #f5f5f5; @@ -9,6 +10,7 @@ &:after { clear: both; } + } ol.images { @@ -29,7 +31,6 @@ border-bottom: 1px solid $gray-light; list-style-type: none; .image, .text { - @include make-sm-column(6); min-height: 640px; padding-bottom: 20px; padding-top: 20px; @@ -80,3 +81,16 @@ #display-label { margin-left: 20px; } + +.opener { + font-weight: bold; +} + +@media screen and (max-width: 767px) { + #tei-container li.page .image, #tei-container li.page .text { + min-height: 1px; + } + ol#pages.both { + padding-left: 0; + } +} diff --git a/app/assets/stylesheets/wustl.scss b/app/assets/stylesheets/wustl.scss new file mode 100644 index 0000000..c767a4a --- /dev/null +++ b/app/assets/stylesheets/wustl.scss @@ -0,0 +1,450 @@ +body { + padding: 0; + font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif; + h1, h2, h3, h4, h5, h6 { + font-family: 'Libre Baskerville', 'Times New Roman', serif; + } + header#banner hgroup { + div#brand-bar-wrapper { + background-color: #a51417; + background-image: none; + border-bottom: none; + border-top: #62121b 5px solid; + padding: 0px 0px 5px 0px; + margin: 0px; + div.container { + margin: auto; + max-width: 940px; + padding: 0px; + div#brand-bar { + max-width: 940px; + margin: auto; + div#university { + margin: 0px; + padding: 0px; + } + div#division { + margin: 0px; + padding: 0px; + } + } + } + } + div#product-bar-wrapper { + border: none; + div.container { + position: relative; + background: transparent url(image-path('header-shield.svg')) scroll left center no-repeat; + padding: 0px 70px; + max-width: 940px; + right: 70px; + div#product-bar { + height: 135px; + line-height: 135px; + margin: auto; + font-family: 'Libre Baskerville', 'Times New Roman', serif; + font-size: 50px; + a { + color: #000; + } + } + } + } + div#title-bar-wrapper { + padding: 5px 0px; + background-color: #555; + background-image: none; + border: 0; + margin: 0; + div.container { + margin: auto; + padding: 0px; + max-width: 940px; + div#title-bar { + max-width: 940px; + margin: auto; + div#site-search { + margin: 0px; + padding: 0px; + form { + margin: 0px; + padding: 0px; + } + .glyphicon { + line-height: 1.42857143; + } + } + nav#site-actions { + padding: 0px; + margin: 0px; + } + } + } + } + } + div#main { + max-width: 940px; + margin: auto; + padding: 0px; + } + footer#footer { + h3 { + margin-top: 0px; + } + color: #fff; + text-align: left; + padding: 0px; + margin: 20px 0px 0px 0px; + font-size: .866667em; + .fa { + line-height: 40px; + } + a.fa-icon { + display: inline-block; + height: 40px; + width: 40px; + text-align: center; + font-size: 16px; + line-height: 40px; + text-decoration: none; + span.screenreader-fallback-text { + position: absolute; + height: 1px; + width: 1px; + overflow: hidden; + } + } + div#footer-top-wrapper { + position: relative; + background-color: #555; + padding: 40px 0px 45px 0px; + div#footer-top { + margin: auto; + max-width: 940px; + div.col-md-3 { + padding: 0px 0px 0px 20px; + h3 { + font-size: 20px; + } + ul { + padding: 0px; + li { + list-style-type: none; + margin-bottom: 0.5em; + } + } + div.social { + + margin-top: 19px; + a { + border: 1px solid #777; + } + a:hover { + background-color: #a51417; + border: 1px solid #a51417; + } + } + a img { + max-width: 100%; + } + } + div.col-md-3:first-of-type { + padding-left: 0px; + } + } + } + div#footer-top-wrapper:after { + content: ''; + position: absolute; + top: -5px; + left: 50%; + z-index: 22; + margin-left: -6px; + width: 0; + height: 0; + border-right: 6px solid transparent; + border-bottom: 5px solid #555; + border-left: 6px solid transparent; + } + div#footer-bottom-wrapper { + background-color: #a51417; + border-top: 5px solid #721218; + div#footer-bottom { + margin: auto; + padding: 10px 0px; + max-width: 940px; + div.copyright { + font-size: 12px; + min-height: 40px; + line-height: 40px; + padding: 0px; + margin: 0px; + } + div.back-to-top { + text-align: right; + padding: 0px; + a.back-to-top { + font-size: 13px; + margin-right: 10px; + } + a.fa-icon { + box-shadow: inset 0px 0px 0px 1px #b5202a; + } + a.fa-icon:hover { + background-color: rgba(255, 255, 255, 0.15); + } + } + } + } + a { + color: #fff; + text-decoration: underline; + } + a:hover { + text-decoration: none; + } + + } + span.label-and-count>label { + display: inline; + font-weight: normal; + overflow: hidden; + } + + span.facet-count { + float: right; + } + + .facet-values .facet-label { + padding-left: 5px !important; + text-indent: 0 !important; + display:inline; + } + .submit-buttons { + padding-top: 30px; + } + ul.advanced-help { + padding-left: 20px; + } + .advanced-help-panel { + background-color: #eee; + padding: 10px; + } + h3.query-criteria-heading { + margin-top: 0; + font-size: 20px; + } + h3.limit-criteria-heading { + margin-top: 2px; + } + h5.panel-title a { + color: #002b5b; + font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif; + } + .sort-submit-buttons { + border-top: 1px solid #eeeeee; + } + #set-access-controls .form-group .radio { + margin-left: 15px; + } + .search-results-list{ + .truncate{ + position: relative; + margin-bottom: 2em; + + a{ + position: absolute; + bottom: -1em; + right: 0; + font-size: .8em; + } + + .inner-value{ + max-height: 8em; + overflow: hidden; + } + + .inner-value:after { + content : ""; + position : absolute; + z-index : 1; + bottom : 0; + left : 0; + pointer-events : none; + background-image : linear-gradient(to bottom, + rgba(255,255,255, 0), + rgba(255,255,255, 1) 90%); + width : 100%; + height : 2em; + } + } + } + #download-actions{ + .dropdown-toggle{ + img{ + height: 1em; + display: none; + } + } + } +} + +@media screen and (max-width: 767px) { + body header#banner hgroup div#brand-bar-wrapper { + padding: 0 15px 5px 15px; + } + body header#banner hgroup div#product-bar-wrapper div.container { + padding:0; + right: 0; + max-width: 100%; + } + body header#banner hgroup div#product-bar-wrapper div.container div#product-bar { + font-size: 32px; + padding:0 20px; + height: 90px; + line-height: 100px; + } + body header#banner hgroup div#title-bar-wrapper { + padding: 10px; + } + body header#banner hgroup div#title-bar-wrapper div.container div#title-bar div#site-search form select, + body header#banner hgroup div#title-bar-wrapper div.container div#title-bar div#site-search form input { + margin-bottom: 5px; + } + body header#banner hgroup div#title-bar-wrapper div.container div#title-bar div#site-search form { + text-align: left; + margin-bottom: 5px; + } + #site-actions { + text-align: left; + } + body div#main { + padding:0 15px; + } + + body footer#footer div#footer-top-wrapper div#footer-top div.col-md-3, + body footer#footer div#footer-top-wrapper div#footer-top div.col-md-3:first-of-type { + padding:0 20px 20px 20px; + } + body footer#footer div#footer-top-wrapper div#footer-top div.col-md-3 div.social { + text-align: left; + + } + body footer#footer div#footer-bottom-wrapper div#footer-bottom { + padding: 10px 20px; + } + body footer#footer a.fa-icon { + + margin: 4px 4px 2px 0; + font-size: 16px; + height: 40px; + width: 40px; + text-align: center; + text-decoration: none; + line-height: 40px; + border: 1px solid #777; + background-color: transparent; + box-shadow: none; + } + body footer#footer ul { + list-style-type: none; + padding:0; + } + body footer#footer div#footer-bottom-wrapper div#footer-bottom div.back-to-top a.fa-icon { + display: block; + font-size: 16px; + height: 40px; + width: 40px; + float:right; + text-align: center; + line-height: 40px; + box-shadow: inset 0px 0px 0px 1px #b5202a; + text-decoration: none; + border:none; + } + .row.no-neg-margin { + margin-left:0; + margin-right:0; + } + input#page-number { + display: inline-block; + width: auto; + vertical-align: middle; + } + #display-label { + display:block; + margin: 10px 0 0 0; + } + #tei-container { + margin: 10px; + } + + a.add-to-collection.btn.btn-primary { + margin-top: 16px; + } + + tr.file_set.attributes > td.thumbnail { + width: 50px; + } + +} + +@media screen and (min-width: 768px) { + body { + width:101%; + } + body div#main { + padding:0 15px; + } + body header#banner hgroup div#product-bar-wrapper div.container div#product-bar { + font-size: 50px; + } + tr.file_set.attributes > td.thumbnail { + width: 80px; + } + body footer#footer h3 { + font-size: 20px; + } + body footer#footer a.fa-icon { + + margin: 4px 4px 2px 0; + font-size: 16px; + height: 40px; + width: 40px; + text-align: center; + text-decoration: none; + line-height: 40px; + border: 1px solid #777; + background-color: transparent; + box-shadow: none; + } + body footer#footer ul { + list-style-type: none; + padding:0; + } + body footer#footer ul li { + margin-bottom: 10px; + } + body footer#footer div#footer-bottom-wrapper div#footer-bottom { + padding: 0 15px 15px; + } + body footer#footer div#footer-bottom-wrapper div#footer-bottom div.copyright { + margin-top: 10px; + } + body footer#footer div#footer-bottom-wrapper div#footer-bottom div.back-to-top { + line-height: 40px; + margin-top: 10px; + } + body footer#footer div#footer-bottom-wrapper div#footer-bottom div.back-to-top a.fa-icon { + display: block; + font-size: 16px; + height: 40px; + width: 40px; + float:right; + text-align: center; + line-height: 40px; + box-shadow: inset 0px 0px 0px 1px #b5202a; + text-decoration: none; + border:none; + } +} diff --git a/app/controllers/admin_sets_controller.rb b/app/controllers/admin_sets_controller.rb index 1c4f93c..756e5a1 100644 --- a/app/controllers/admin_sets_controller.rb +++ b/app/controllers/admin_sets_controller.rb @@ -1,6 +1,7 @@ class AdminSetsController < ApplicationController include Blacklight::Base include Hydra::Controller::SearchBuilder + include Downloads copy_blacklight_config_from(CatalogController) load_and_authorize_resource except: :show @@ -25,6 +26,7 @@ def show end def edit + @spotlight_exhibit = Spotlight::Exhibit.where(admin_set_id: @admin_set.identifier) @form = AdminSetForm.new(@admin_set) end @@ -45,8 +47,17 @@ def destroy redirect_to root_path, notice: "#{@admin_set.title} has been queued for removal. This may take several minutes." end - private + def allow_downloads + toggle_prevent_download(@admin_set, false, params[:file_type]) + redirect_to @admin_set, notice: 'Collection was successfully updated.' + end + def prevent_downloads + toggle_prevent_download(@admin_set, true, params[:file_type]) + redirect_to @admin_set, notice: 'Collection was successfully updated.' + end + + private def admin_set_params AdminSetForm.model_attributes(params[:admin_set]) end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index cd917f0..de54932 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,10 +1,29 @@ class ApplicationController < ActionController::Base + before_action :fake_sign_in + before_action :set_default_url_options + + def fake_sign_in + if Rails.env.development? && ENV['SKIP_LDAP'] + sign_in(:user, User.first) + end + end + + def set_default_url_options + ActionMailer::Base.default_url_options = {:host => request.host_with_port} + Rails.application.routes.default_url_options = { + host: request.host_with_port, + protocol: request.protocol + } + end + rescue_from DeviseLdapAuthenticatable::LdapException do |exception| render text: exception, status: 500 end helper Openseadragon::OpenseadragonHelper # Adds a few additional behaviors into the application controller include Blacklight::Controller + include Spotlight::Controller + # Adds CurationConcerns behaviors to the application controller. include Hydra::Controller::ControllerBehavior diff --git a/app/controllers/blacklight_advanced_search/advanced_controller.rb b/app/controllers/blacklight_advanced_search/advanced_controller.rb new file mode 100644 index 0000000..7a5d638 --- /dev/null +++ b/app/controllers/blacklight_advanced_search/advanced_controller.rb @@ -0,0 +1,23 @@ +class BlacklightAdvancedSearch::AdvancedController < CatalogController + include Hydra::Controller::SearchBuilder + + def index + unless request.method==:post + @response = get_advanced_search_facets + end + end + + protected + def get_advanced_search_facets + # We want to find the facets available for the current search, but: + # * IGNORING current query (add in facets_for_advanced_search_form filter) + # * IGNORING current advanced search facets (remove add_advanced_search_to_solr filter) + response, _ = search_results(params, search_params_logic) do |search_builder| + search_builder.except(:add_advanced_search_to_solr).append(:facets_for_advanced_search_form) + search_builder.current_ability = current_ability + search_builder + end + + return response + end +end diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb index 1f42e27..5264e7d 100644 --- a/app/controllers/catalog_controller.rb +++ b/app/controllers/catalog_controller.rb @@ -1,11 +1,13 @@ class CatalogController < ApplicationController include CurationConcerns::CatalogController + include BlacklightOaiProvider::Controller def self.search_config super.merge('qf' => %w( title_tesim contributor_tesim description_tesim subject_tesim + tei_json_tesi id )) end @@ -16,23 +18,31 @@ def self.search_config config.default_solr_params = { qf: search_config['qf'], qt: search_config['qt'], - rows: search_config['rows'] + rows: search_config['rows'], + + "hl.simple.pre": '', + "hl.simple.post": '', + "hl.alternateField": "dd", + hl: true } + + # solr field configuration for search results/index views config.index.title_field = solr_name('title', :stored_searchable) config.index.display_type_field = solr_name('has_model', :symbol) - + config.index.thumbnail_field = 'thumbnail_path_ss' config.index.partials += [:action_menu] # solr field configuration for document/show views # config.show.title_field = solr_name("title", :stored_searchable) # config.show.display_type_field = solr_name("has_model", :symbol) + config.show.prevent_download_field = solr_name("prevent_download_bsi", :stored_searchable) # 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.add_facet_field solr_name('human_readable_type', :facetable) + config.add_facet_field solr_name('human_readable_type', :facetable), label: "Item Type" config.add_facet_field solr_name('creator', :facetable), limit: 5 config.add_facet_field solr_name('tag', :facetable), limit: 10 config.add_facet_field solr_name('subject', :facetable), limit: 10 @@ -50,20 +60,21 @@ def self.search_config # solr fields to be displayed in the index (search results) view # The ordering of the field names is the order of the display - config.add_index_field solr_name('description', :stored_searchable), helper_method: :index_description, length: 300 - config.add_index_field solr_name('tag', :stored_searchable) - config.add_index_field solr_name('subject', :stored_searchable) - config.add_index_field solr_name('creator', :stored_searchable) - config.add_index_field solr_name('contributor', :stored_searchable) - config.add_index_field solr_name('publisher', :stored_searchable) - config.add_index_field solr_name('based_near', :stored_searchable) - config.add_index_field solr_name('language', :stored_searchable) + config.add_index_field solr_name('description', :stored_searchable), helper_method: :index_description, length: 300, highlight: true + config.add_index_field solr_name('tag', :stored_searchable), highlight: true + config.add_index_field solr_name('subject', :stored_searchable), highlight: true + config.add_index_field solr_name('creator', :stored_searchable), highlight: true + config.add_index_field solr_name('contributor', :stored_searchable), highlight: true + config.add_index_field solr_name('publisher', :stored_searchable), highlight: true + config.add_index_field solr_name('based_near', :stored_searchable), highlight: true + config.add_index_field solr_name('language', :stored_searchable), highlight: true config.add_index_field uploaded_field, helper_method: :formatted_time, label: 'Date Uploaded' config.add_index_field modified_field, helper_method: :formatted_time, label: 'Date Modified' config.add_index_field 'date_issued_dtsi', helper_method: :formatted_time, label: 'Date Issued' - config.add_index_field 'rights_label_ss', label: 'Content License' - config.add_index_field solr_name('human_readable_type', :stored_searchable) - config.add_index_field solr_name('format', :stored_searchable) + config.add_index_field 'rights_label_ss', label: 'Content License', highlight: true + config.add_index_field solr_name('human_readable_type', :stored_searchable), label: 'Item Type', highlight: true + config.add_index_field solr_name('format', :stored_searchable), highlight: true + config.add_index_field solr_name('prevent_download_bsi', :stored_searchable), label: 'Prevent Downloads' # "fielded" search configuration. Used by pulldown among other places. # For supported keys in hash, see rdoc for Blacklight::SearchFields @@ -89,20 +100,6 @@ def self.search_config # of Solr search fields. # creator, title, description, publisher, date_created, # subject, language, resource_type, format, identifier, based_near, - config.add_search_field('contributor') do |field| - field.include_in_simple_select = false - # solr_parameters hash are sent to Solr as ordinary url query params. - - # :solr_local_parameters will be sent using Solr LocalParams - # syntax, as eg {! qf=$title_qf }. This is neccesary to use - # Solr parameter de-referencing like $title_qf. - # See: http://wiki.apache.org/solr/LocalParams - solr_name = solr_name('contributor', :stored_searchable, type: :string) - field.solr_local_parameters = { - qf: solr_name, - pf: solr_name - } - end config.add_search_field('title') do |field| solr_name = solr_name('title', :stored_searchable, type: :string) @@ -113,6 +110,7 @@ def self.search_config end config.add_search_field('creator') do |field| + field.include_in_advanced_search = false solr_name = solr_name('creator', :stored_searchable, type: :string) field.solr_local_parameters = { qf: solr_name, @@ -129,7 +127,17 @@ def self.search_config } end + config.add_search_field('tei_json') do |field| + field.label = 'Content' + solr_name = 'tei_json_tesi' + field.solr_local_parameters = { + qf: solr_name, + pf: solr_name + } + end + config.add_search_field('publisher') do |field| + field.include_in_advanced_search = false field.include_in_simple_select = false solr_name = solr_name('publisher', :stored_searchable, type: :string) field.solr_local_parameters = { @@ -140,6 +148,7 @@ def self.search_config config.add_search_field('date_created') do |field| field.include_in_simple_select = false + field.include_in_advanced_search = false solr_name = solr_name('created', :stored_searchable, type: :string) field.solr_local_parameters = { qf: solr_name, @@ -157,6 +166,7 @@ def self.search_config end config.add_search_field('subject') do |field| + field.include_in_advanced_search = false solr_name = solr_name('subject', :stored_searchable, type: :string) field.solr_local_parameters = { qf: solr_name, @@ -165,6 +175,7 @@ def self.search_config end config.add_search_field('language') do |field| + field.include_in_advanced_search = false field.include_in_simple_select = false solr_name = solr_name('language', :stored_searchable, type: :string) field.solr_local_parameters = { @@ -174,6 +185,8 @@ def self.search_config end config.add_search_field('human_readable_type') do |field| + field.label = "Item Type" + # field.include_in_advanced_search = false field.include_in_simple_select = false solr_name = solr_name('human_readable_type', :stored_searchable, type: :string) field.solr_local_parameters = { @@ -204,6 +217,7 @@ def self.search_config config.add_search_field('tag') do |field| field.include_in_simple_select = false + field.include_in_advanced_search = false solr_name = solr_name('tag', :stored_searchable, type: :string) field.solr_local_parameters = { qf: solr_name, @@ -211,18 +225,27 @@ def self.search_config } end - config.add_search_field('depositor') do |field| + config.add_search_field('contributor') do |field| field.include_in_simple_select = false - solr_name = solr_name('depositor', :stored_searchable, type: :string) + field.include_in_advanced_search = false + # solr_parameters hash are sent to Solr as ordinary url query params. + + # :solr_local_parameters will be sent using Solr LocalParams + # syntax, as eg {! qf=$title_qf }. This is neccesary to use + # Solr parameter de-referencing like $title_qf. + # See: http://wiki.apache.org/solr/LocalParams + solr_name = solr_name('contributor', :stored_searchable, type: :string) field.solr_local_parameters = { qf: solr_name, pf: solr_name } end - config.add_search_field('rights') do |field| + + config.add_search_field('depositor') do |field| field.include_in_simple_select = false - solr_name = solr_name('rights', :stored_searchable, type: :string) + field.include_in_advanced_search = false + solr_name = solr_name('depositor', :stored_searchable, type: :string) field.solr_local_parameters = { qf: solr_name, pf: solr_name @@ -252,5 +275,23 @@ def self.search_config # If there are more than this many search results, no spelling ("did you # mean") suggestion is offered. config.spell_max = 5 + config.add_field_configuration_to_solr_request! + + # OAI + config.oai = { + provider: { + repository_name: 'Washington University Goldenseal', + repository_url: 'http://repository.wustl.edu/catalog/oai', + record_prefix: 'oai:goldenseal', + admin_email: 'library.webmaster@wumail.wustl.edu', + sample_id: 'f1881k888' + }, + document: { + limit: 25, # number of records returned with each request, default: 15 + set_fields: [ # ability to define ListSets, optional, default: nil + { label: 'collection', solr_field: 'isPartOf_ssim' } + ] + } + } end end diff --git a/app/controllers/collections_controller.rb b/app/controllers/collections_controller.rb new file mode 100644 index 0000000..2608b24 --- /dev/null +++ b/app/controllers/collections_controller.rb @@ -0,0 +1,8 @@ +class CollectionsController < ApplicationController + include CurationConcerns::CollectionsControllerBehavior + include CurationConcerns::CollectionsControllerBehaviorEnhancements + + def presenter_class + CollectionPresenter + end +end diff --git a/app/controllers/concerns/curation_concerns/collections_controller_behavior_enhancements.rb b/app/controllers/concerns/curation_concerns/collections_controller_behavior_enhancements.rb new file mode 100644 index 0000000..48cbf3b --- /dev/null +++ b/app/controllers/concerns/curation_concerns/collections_controller_behavior_enhancements.rb @@ -0,0 +1,23 @@ +module CurationConcerns + module CollectionsControllerBehaviorEnhancements + extend ActiveSupport::Concern + included do + before_action :filter_docs_with_read_access!, except: [:show, :prevent_downloads, :allow_downloads] + skip_load_and_authorize_resource only: [:show, :prevent_downloads, :allow_downloads] + end + + def allow_downloads + file_type = params[:file_type] + collection = Collection.find(params[:id]) + toggle_prevent_download(collection, false, file_type) + redirect_to collection_path(collection), notice: 'Collection was successfully updated.' + end + + def prevent_downloads + file_type = params[:file_type] + collection = Collection.find(params[:id]) + toggle_prevent_download(collection, true, file_type) + redirect_to collection_path(collection), notice: 'Collection was successfully updated.' + end + end +end diff --git a/app/controllers/concerns/curation_concerns/curation_concern_controller.rb b/app/controllers/concerns/curation_concerns/curation_concern_controller.rb new file mode 100644 index 0000000..0050812 --- /dev/null +++ b/app/controllers/concerns/curation_concerns/curation_concern_controller.rb @@ -0,0 +1,175 @@ +module CurationConcerns::CurationConcernController + extend ActiveSupport::Concern + include Blacklight::Base + include Hydra::Controller::SearchBuilder + + included do + copy_blacklight_config_from(CatalogController) + include CurationConcerns::ThemedLayoutController + with_themed_layout '1_column' + helper CurationConcerns::AbilityHelper + + class_attribute :curation_concern_type + attr_accessor :curation_concern + helper_method :curation_concern + end + + module ClassMethods + def set_curation_concern_type(curation_concern_type) + load_and_authorize_resource class: curation_concern_type, instance_name: :curation_concern, except: :show + self.curation_concern_type = curation_concern_type + end + + def cancan_resource_class + CurationConcerns::ControllerResource + end + end + + def new + build_form + end + + def create + # return unless verify_acceptance_of_user_agreement! + if actor.create + after_create_response + else + setup_form + build_form + respond_to do |wants| + wants.html { render 'new', status: :unprocessable_entity } + wants.json { render_json_response(response_type: :unprocessable_entity, options: { errors: curation_concern.errors }) } + end + end + end + + # Finds a solr document matching the id and sets @presenter + # @raises CanCan::AccessDenied if the document is not found + # or the user doesn't have access to it. + def show + respond_to do |wants| + wants.html { presenter } + wants.json do + # load and authorize @curation_concern manually because it's skipped for html + # This has to use #find instead of #load_instance_from_solr because + # we want to return values like file_set_ids in the json + @curation_concern = curation_concern_type.find(params[:id]) unless curation_concern + authorize! :show, @curation_concern + render :show, status: :ok + end + additional_response_formats(wants) + end + end + + def edit + build_form + end + + def update + if actor.update + after_update_response + else + setup_form + respond_to do |wants| + wants.html do + build_form + render 'edit', status: :unprocessable_entity + end + wants.json { render_json_response(response_type: :unprocessable_entity, options: { errors: curation_concern.errors }) } + end + end + end + + def destroy + title = curation_concern.to_s + curation_concern.destroy + after_destroy_response(title) + end + + attr_writer :actor + + protected + + # Gives the class of the show presenter. Override this if you want + # to use a different presenter. + def show_presenter + CurationConcerns::WorkShowPresenter + end + + # Gives the class of the form. Override this if you want + # to use a different form. + def form_class + CurationConcerns.const_get("#{self.class.curation_concern_type.to_s.demodulize}Form") + end + + def build_form + @form = form_class.new(curation_concern, current_ability) + end + + def actor + @actor ||= CurationConcerns::CurationConcern.actor(curation_concern, current_user, attributes_for_actor) + end + + def presenter + @presenter ||= show_presenter.new(curation_concern_from_search_results, current_ability) + end + + # Override setup_form in concrete controllers to get the form ready for display + def setup_form + return unless curation_concern.respond_to?(:contributor) && curation_concern.contributor.blank? + curation_concern.contributor << current_user.user_key + end + + def _prefixes + @_prefixes ||= super + ['curation_concerns/base'] + end + + def after_create_response + respond_to do |wants| + wants.html { redirect_to [main_app, curation_concern] } + wants.json { render :show, status: :created, location: polymorphic_path([main_app, curation_concern]) } + end + end + + def after_update_response + # TODO: visibility or lease/embargo status + if actor.visibility_changed? && curation_concern.file_sets.present? + redirect_to main_app.confirm_curation_concerns_permission_path(curation_concern) + else + respond_to do |wants| + wants.html { redirect_to [main_app, curation_concern] } + wants.json { render :show, status: :ok, location: polymorphic_path([main_app, curation_concern]) } + end + end + end + + def after_destroy_response(title) + flash[:notice] = "Deleted #{title}" + respond_to do |wants| + wants.html { redirect_to main_app.catalog_index_path } + wants.json { render_json_response(response_type: :deleted, message: "Deleted #{curation_concern.id}") } + end + end + + def attributes_for_actor + form_class.model_attributes(params[hash_key_for_curation_concern]) + end + + def hash_key_for_curation_concern + self.class.curation_concern_type.model_name.param_key + end + + # Override this method to add additional response + # formats to your local app + def additional_response_formats(_) + # nop + end + + private + + def curation_concern_from_search_results + _, document_list = search_results(params, CatalogController.search_params_logic + [:find_one]) + raise CanCan::AccessDenied.new(nil, :show) if document_list.empty? + document_list.first + end +end diff --git a/app/controllers/concerns/custom_metadata.rb b/app/controllers/concerns/custom_metadata.rb new file mode 100644 index 0000000..280a1b1 --- /dev/null +++ b/app/controllers/concerns/custom_metadata.rb @@ -0,0 +1,17 @@ +module CustomMetadata + extend ActiveSupport::Concern + + def build_form + super + if @form.model.admin_set + existing_custom_metadatas = @form.model.custom_metadatas.collect{|d| d.title.titleize} + if @form.model.admin_set.spotlight_exhibit.present? + @form.model.admin_set.spotlight_exhibit.custom_fields.each do |custom_field| + unless existing_custom_metadatas.include? custom_field.configuration["label"] + @form.model.custom_metadatas.build(title: custom_field.slug.humanize) + end + end + end + end + end +end diff --git a/app/controllers/concerns/downloads.rb b/app/controllers/concerns/downloads.rb new file mode 100644 index 0000000..9d5e90e --- /dev/null +++ b/app/controllers/concerns/downloads.rb @@ -0,0 +1,29 @@ +# This module needs to be included after CurationConcerns::CurationConcernController +module Downloads + extend ActiveSupport::Concern + + def toggle_prevent_download(object, value, file_type) + authorize! :edit, object + case file_type + when 'video' + works = object.members.select{ |work| work.class == Video } + when 'text' + works = object.members.select{ |work| work.class == Text } + when 'audio' + works = object.members.select{ |work| work.class == Audio } + when 'image' + works = object.members.select{ |work| work.class == Image } + when 'all' + works = object.members + end + + works.each do |work| + # map through the file set of each work and find the file set that is the representative media, update its prevent_download attribute + rep_media = work.file_sets.select { |fs| fs.id == work.representative_id }.first + if rep_media.present? + rep_media.update_attributes( prevent_download: value ) + end + end + end +end + diff --git a/app/controllers/concerns/spotlight/catalog/access_controls_enforcement.rb b/app/controllers/concerns/spotlight/catalog/access_controls_enforcement.rb new file mode 100644 index 0000000..bfc8912 --- /dev/null +++ b/app/controllers/concerns/spotlight/catalog/access_controls_enforcement.rb @@ -0,0 +1,54 @@ +gem_dir = Gem::Specification.find_by_name("blacklight-spotlight").gem_dir +require "#{gem_dir}/app/controllers/concerns/spotlight/catalog/access_controls_enforcement.rb" + +Spotlight::Catalog.send :remove_const, :AccessControlsEnforcement + +module Spotlight + module Catalog + ## + # Enforce exhibit visibility for index queries + module AccessControlsEnforcement + extend ActiveSupport::Concern + + included do + # access control handled by CC + #self.search_params_logic += [:apply_permissive_visibility_filter, :apply_exhibit_resources_filter] + self.search_params_logic += [:apply_exhibit_resources_filter] + end + + ## + # SearchBuilder mixin + module SearchBuilder + extend ActiveSupport::Concern + + included do + self.default_processor_chain += [:apply_permissive_visibility_filter, :apply_exhibit_resources_filter] + end + + # Adds a filter that excludes resources that have been marked as not-visible + def apply_permissive_visibility_filter(solr_params) + return unless current_exhibit + return if scope.respond_to?(:can?) && scope.can?(:curate, current_exhibit) && !blacklight_params[:public] + + solr_params.append_filter_query "-#{blacklight_config.document_model.visibility_field(current_exhibit)}:false" + end + + def apply_exhibit_resources_filter(solr_params) + return unless current_exhibit + + current_exhibit.solr_data.each do |facet_field, values| + Array(values).each do |value| + solr_params.append_filter_query send(:facet_value_to_fq_string, facet_field, value) + end + end + end + + private + + def current_exhibit + scope.current_exhibit + end + end + end + end +end diff --git a/app/controllers/concerns/spotlight/controller.rb b/app/controllers/concerns/spotlight/controller.rb new file mode 100644 index 0000000..ff633e2 --- /dev/null +++ b/app/controllers/concerns/spotlight/controller.rb @@ -0,0 +1,82 @@ +module Spotlight + ## + # Spotlight controller helpers + module Controller + extend ActiveSupport::Concern + include Blacklight::Controller + include Spotlight::Config + + included do + helper_method :current_site, :current_exhibit, :current_masthead, :exhibit_masthead?, :resource_masthead? + end + + def current_site + @current_site ||= Spotlight::Site.instance + end + + def current_exhibit + @exhibit + end + + def current_masthead + @masthead ||= if resource_masthead? + # TODO: is there a way to get this generically, instead of requiring controllers + # to override #current_masthead or set it explicitly?. In the meantime, `nil` is + # hopefully less confusing than a wrong value. + nil + elsif current_exhibit + current_exhibit.masthead if exhibit_masthead? + elsif current_site.masthead && current_site.masthead.display? + current_site.masthead + end + end + + def current_masthead=(masthead) + @masthead = masthead + end + + def resource_masthead? + false + end + + def exhibit_masthead? + current_exhibit && current_exhibit.masthead && current_exhibit.masthead.display? + end + + # overwrites Blacklight::Controller#blacklight_config + def blacklight_config + if current_exhibit + exhibit_specific_blacklight_config + else + default_catalog_controller.blacklight_config + end + end + + def search_action_url(*args) + main_app.catalog_index_url(*args) + end + + def search_facet_url(*args) + main_app.catalog_facet_url(*args) + end + + def exhibit_search_action_url(*args) + options = args.extract_options! + only_path = options[:only_path] + options.except! :exhibit_id, :only_path + + if only_path + spotlight.exhibit_catalog_index_path(current_exhibit, *args, options) + else + spotlight.exhibit_catalog_index_url(current_exhibit, *args, options) + end + end + + def exhibit_search_facet_url(*args) + options = args.extract_options! + options = params.merge(options).except(:exhibit_id, :only_path) + + spotlight.exhibit_catalog_facet_url(current_exhibit, *args, options) + end + end +end diff --git a/app/controllers/curation_concerns/audios_controller.rb b/app/controllers/curation_concerns/audios_controller.rb index 0451dbd..6a08ad2 100644 --- a/app/controllers/curation_concerns/audios_controller.rb +++ b/app/controllers/curation_concerns/audios_controller.rb @@ -3,13 +3,15 @@ class CurationConcerns::AudiosController < ApplicationController include CurationConcerns::CurationConcernController - + include CustomMetadata include AttachFiles + before_filter :add_attachments_to_files, only: :create set_curation_concern_type Audio def show_presenter - ::WorkShowPresenter + ::AudioPresenter end + end diff --git a/app/controllers/curation_concerns/collections_controller_behavior.rb b/app/controllers/curation_concerns/collections_controller_behavior.rb new file mode 100644 index 0000000..4fa6465 --- /dev/null +++ b/app/controllers/curation_concerns/collections_controller_behavior.rb @@ -0,0 +1,80 @@ +module CurationConcerns + module CollectionsControllerBehavior + extend ActiveSupport::Concern + include Hydra::CollectionsControllerBehavior + include Hydra::Controller::SearchBuilder + include Downloads + + def new + super + form + end + + def edit + super + form + end + + def show + presenter + super + end + + # overriding the method in Hydra::Collections so the search builder can find the collection + def collection + action_name == 'show' ? @presenter : @collection + end + + protected + def filter_docs_with_read_access! + super + flash.delete(:notice) if flash.notice == 'Select something first' + end + + def presenter + @presenter ||= begin + _, document_list = search_results(params, self.class.search_params_logic + [:find_one]) + curation_concern = document_list.first + raise CanCan::AccessDenied unless curation_concern + presenter_class.new(curation_concern, current_ability) + end + end + + def presenter_class + CurationConcerns::CollectionPresenter + end + + def collection_member_search_builder_class + CurationConcerns::SearchBuilder + end + + def collection_params + form_class.model_attributes(params[:collection]) + end + + def query_collection_members + params[:q] = params[:cq] + super + end + + def after_destroy(id) + respond_to do |format| + format.html { redirect_to collections_path, notice: 'Collection was successfully deleted.' } + format.json { render json: { id: id }, status: :destroyed, location: @collection } + end + end + + def form + @form ||= form_class.new(@collection) + end + + def form_class + CurationConcerns::Forms::CollectionEditForm + end + + # Include 'catalog' and 'curation_concerns/base' in the search path for views + def _prefixes + @_prefixes ||= super + ['catalog', 'curation_concerns/base'] + end + end +end diff --git a/app/controllers/curation_concerns/documents_controller.rb b/app/controllers/curation_concerns/documents_controller.rb index a70e836..7e0dbc7 100644 --- a/app/controllers/curation_concerns/documents_controller.rb +++ b/app/controllers/curation_concerns/documents_controller.rb @@ -3,6 +3,8 @@ class CurationConcerns::DocumentsController < ApplicationController include CurationConcerns::CurationConcernController + include CustomMetadata + set_curation_concern_type Document def show_presenter diff --git a/app/controllers/curation_concerns/file_sets_controller.rb b/app/controllers/curation_concerns/file_sets_controller.rb index 177de8e..0d7c0b6 100644 --- a/app/controllers/curation_concerns/file_sets_controller.rb +++ b/app/controllers/curation_concerns/file_sets_controller.rb @@ -7,6 +7,8 @@ def additional_response_formats(format) format.vtt { render_vtt } end + delegate :prevent_download, to: :solr_document + protected # Overridden to use a local presenter implementation @@ -24,5 +26,7 @@ def render_vtt fail ActiveFedora::ObjectNotFoundError end end + + delegate :prevent_download, to: :solr_document end end diff --git a/app/controllers/curation_concerns/images_controller.rb b/app/controllers/curation_concerns/images_controller.rb index 99429d3..af223e3 100644 --- a/app/controllers/curation_concerns/images_controller.rb +++ b/app/controllers/curation_concerns/images_controller.rb @@ -1,8 +1,11 @@ class CurationConcerns::ImagesController < ApplicationController include CurationConcerns::CurationConcernController + include CustomMetadata + set_curation_concern_type Image def show_presenter - ::WorkShowPresenter + ::ImagePresenter end + end diff --git a/app/controllers/curation_concerns/texts_controller.rb b/app/controllers/curation_concerns/texts_controller.rb index 1e4f45a..60e123f 100644 --- a/app/controllers/curation_concerns/texts_controller.rb +++ b/app/controllers/curation_concerns/texts_controller.rb @@ -3,6 +3,7 @@ class CurationConcerns::TextsController < ApplicationController include CurationConcerns::CurationConcernController + include CustomMetadata include AttachFiles before_filter :add_attachments_to_files, only: :create @@ -14,4 +15,5 @@ class CurationConcerns::TextsController < ApplicationController def show_presenter TextPresenter end + end diff --git a/app/controllers/curation_concerns/videos_controller.rb b/app/controllers/curation_concerns/videos_controller.rb index 012cecd..c11f6ef 100644 --- a/app/controllers/curation_concerns/videos_controller.rb +++ b/app/controllers/curation_concerns/videos_controller.rb @@ -1,12 +1,14 @@ class CurationConcerns::VideosController < ApplicationController include CurationConcerns::CurationConcernController - + include CustomMetadata include AttachFiles + before_filter :add_attachments_to_files, only: :create set_curation_concern_type Video def show_presenter - ::WorkShowPresenter + ::VideoPresenter end + end diff --git a/app/controllers/rights_controller.rb b/app/controllers/rights_controller.rb new file mode 100644 index 0000000..6393c1a --- /dev/null +++ b/app/controllers/rights_controller.rb @@ -0,0 +1,78 @@ +class RightsController < ApplicationController + authorize_resource class: "Qa::LocalAuthorityEntry" + before_action :set_right, only: [:show, :edit, :update, :destroy] + + # GET /rights + # GET /rights.json + def index + authority = Qa::LocalAuthority.where(name: 'rights').first + @rights = authority.local_authority_entries + end + + # GET /rights/1 + # GET /rights/1.json + def show + end + + # GET /rights/new + def new + authority = Qa::LocalAuthority.where(name: 'rights').first + @right = authority.local_authority_entries.new + end + + # GET /rights/1/edit + def edit + end + + # POST /rights + # POST /rights.json + def create + authority = Qa::LocalAuthority.where(name: 'rights').first + @right = authority.local_authority_entries.new(right_params) + + respond_to do |format| + if @right.save + format.html { redirect_to rights_path, notice: 'Right was successfully created.' } + format.json { render :show, status: :created, location: @right } + else + format.html { render :new } + format.json { render json: @right.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /rights/1 + # PATCH/PUT /rights/1.json + def update + respond_to do |format| + if @right.update(right_params) + format.html { redirect_to rights_path, notice: 'Right was successfully updated.' } + format.json { render :show, status: :ok, location: @right } + else + format.html { render :edit } + format.json { render json: @right.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /rights/1 + # DELETE /rights/1.json + def destroy + @right.destroy + respond_to do |format| + format.html { redirect_to rights_path, notice: 'Right was successfully destroyed.' } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_right + @right = Qa::LocalAuthorityEntry.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def right_params + params[:local_authority_entry].permit(:local_authority_id, :label, :uri) + end +end diff --git a/app/controllers/spotlight/exhibits_controller.rb b/app/controllers/spotlight/exhibits_controller.rb new file mode 100644 index 0000000..7619408 --- /dev/null +++ b/app/controllers/spotlight/exhibits_controller.rb @@ -0,0 +1,112 @@ +module Spotlight + ## + # Administrative CRUD actions for an exhibit + class ExhibitsController < Spotlight::ApplicationController + before_action :authenticate_user!, except: [:index] + include Blacklight::SearchHelper + load_and_authorize_resource except: [:index] + load_resource only: :index + + def index + @exhibits = Spotlight::Exhibit + @published_exhibits = @exhibits.published.page(params[:page]) + @published_exhibits = @published_exhibits.tagged_with(params[:tag]) if params[:tag] + + if @published_exhibits.one? + redirect_to @published_exhibits.first + else + render layout: 'spotlight/home' + end + end + + def new + build_initial_exhibit_contact_emails + end + + def process_import + @exhibit.import(JSON.parse(import_exhibit_params.read)) + if @exhibit.save && @exhibit.reindex_later + redirect_to spotlight.exhibit_dashboard_path(@exhibit), notice: t(:'helpers.submit.exhibit.updated', model: @exhibit.class.model_name.human.downcase) + else + render action: :import + end + end + + def create + @exhibit.attributes = exhibit_params + + if @exhibit.save + @exhibit.roles.create user: current_user, role: 'admin' if current_user + redirect_to spotlight.exhibit_dashboard_path(@exhibit), notice: t(:'helpers.submit.exhibit.created', model: @exhibit.class.model_name.human.downcase) + else + render action: :new + end + end + + def show + respond_to do |format| + format.json do + authorize! :export, @exhibit + send_data JSON.pretty_generate(Spotlight::ExhibitExportSerializer.new(@exhibit).as_json), + type: 'application/json', + disposition: 'attachment', + filename: "#{@exhibit.friendly_id}-export.json" + end + end + end + + def edit + add_breadcrumb t(:'spotlight.exhibits.breadcrumb', title: @exhibit.title), @exhibit + add_breadcrumb t(:'spotlight.configuration.sidebar.header'), exhibit_dashboard_path(@exhibit) + add_breadcrumb t(:'spotlight.configuration.sidebar.settings'), edit_exhibit_path(@exhibit) + build_initial_exhibit_contact_emails + end + + def update + if @exhibit.update(exhibit_params) + redirect_to edit_exhibit_path(@exhibit), notice: t(:'helpers.submit.exhibit.updated', model: @exhibit.class.model_name.human.downcase) + else + flash[:alert] = @exhibit.errors.full_messages.join('
'.html_safe) + render action: :edit + end + end + + def destroy + @exhibit.destroy + + redirect_to main_app.root_url, notice: t(:'helpers.submit.exhibit.destroyed', model: @exhibit.class.model_name.human.downcase) + end + + protected + + def current_exhibit + @exhibit if @exhibit && @exhibit.persisted? + end + + def exhibit_params + params.require(:exhibit).permit( + :title, + :subtitle, + :description, + :published, + :tag_list, + contact_emails_attributes: [:id, :email] + ) + end + + def create_params + params.require(:exhibit).permit( + :title, + :slug + ).reject { |_k, v| v.blank? } + end + + def import_exhibit_params + params.require(:file) + end + + def build_initial_exhibit_contact_emails + @exhibit.contact_emails.build unless @exhibit.contact_emails.present? + end + end +end diff --git a/app/controllers/spotlight/home_pages_controller.rb b/app/controllers/spotlight/home_pages_controller.rb new file mode 100644 index 0000000..a152023 --- /dev/null +++ b/app/controllers/spotlight/home_pages_controller.rb @@ -0,0 +1,54 @@ +module Spotlight + ## + # CRUD actions for the exhibit home page + class HomePagesController < Spotlight::PagesController + include Blacklight::SearchHelper + include Spotlight::Catalog + + load_and_authorize_resource through: :exhibit, singleton: true, instance_name: 'page' + + before_action :attach_breadcrumbs, except: :show + + def edit + add_breadcrumb t(:'spotlight.curation.sidebar.feature_pages'), exhibit_feature_pages_path(@exhibit) + add_breadcrumb @page.title, [:edit, @exhibit, @page] + super + end + + def index + redirect_to exhibit_feature_pages_path(@exhibit) + end + + def show + @response, @document_list = search_results({}, search_params_logic) if @page.display_sidebar? + + if @page.nil? || !@page.published? + render '/catalog/index' + else + render 'show' + end + end + + private + + # Calling off spotlight search urls + #alias search_action_url exhibit_search_action_url + #alias search_facet_url exhibit_search_facet_url + def search_action_url options={} + if options['exhibit_id'] && @exhibit + options = options.except('exhibit_id') + + exhibitable_klass = @exhibit.exhibitable_type.try(:constantize) + exhibitable = exhibitable_klass.find(@exhibit.exhibitable_id) if exhibitable_klass + if exhibitable + options["f"].merge!({exhibitable.default_filter_field => [exhibitable.title]}) + end + end + main_app.catalog_index_url(options.except('format')) + end + + def allowed_page_params + super.concat [:display_title, :display_sidebar] + end + end +end diff --git a/app/forms/admin_set_form.rb b/app/forms/admin_set_form.rb index 81dc7a9..08a5216 100644 --- a/app/forms/admin_set_form.rb +++ b/app/forms/admin_set_form.rb @@ -1,7 +1,16 @@ class AdminSetForm include HydraEditor::Form - self.terms = [:title, :identifier, :description, :thumbnail_id, :creator, :contributor, - :subject, :publisher, :language] + self.terms = [ + :contributor, + :creator, + :description, + :identifier, + :language, + :publisher, + :subject, + :thumbnail_id, + :title + ] self.model_class = ::AdminSet diff --git a/app/forms/curation_concerns/common_form.rb b/app/forms/curation_concerns/common_form.rb index 73d8dc0..07fb91f 100644 --- a/app/forms/curation_concerns/common_form.rb +++ b/app/forms/curation_concerns/common_form.rb @@ -1,9 +1,18 @@ module CurationConcerns class CommonForm < CurationConcerns::Forms::WorkForm include OnCampusAccess - self.terms += [:admin_set_id, :editor, :sponsor, :funder, :researcher, :identifier, :series, :extent, :note, :description_standard, :publication_place, :date_issued] + self.terms += [:admin_set_id, :editor, :sponsor, :funder, :researcher, :source, :identifier, :series, :extent, :note, :description_standard, :publication_place, :date_issued, :custom_metadatas] - delegate :read_groups, to: :model + delegate :read_groups, :custom_metadatas, :custom_metadatas_attributes=, to: :model + + + protected + def self.build_permitted_params + permitted = super + permitted.delete({ custom_metadatas_attributes: [] }) + permitted << { custom_metadatas_attributes: [:id, :title, :value] } + permitted + end end end diff --git a/app/forms/curation_concerns/forms/collection_edit_form.rb b/app/forms/curation_concerns/forms/collection_edit_form.rb new file mode 100644 index 0000000..e22323c --- /dev/null +++ b/app/forms/curation_concerns/forms/collection_edit_form.rb @@ -0,0 +1,37 @@ +module CurationConcerns + module Forms + class CollectionEditForm + include HydraEditor::Form + self.model_class = ::Collection + + delegate :human_readable_type, :member_ids, to: :model + + self.terms = [:resource_type, :title, :creator, :contributor, :description, + :tag, :rights, :publisher, :date_created, :subject, :language, + :representative_id, :thumbnail_id, :identifier, :based_near, + :related_url, :visibility, :rights] + + # Test to see if the given field is required + # @param [Symbol] key a field + # @return [Boolean] is it required or not + def required?(key) + model_class.validators_on(key).any? { |v| v.is_a? ActiveModel::Validations::PresenceValidator } + end + + # @return [Hash] All generic files in the collection, file.to_s is the key, file.id is the value + def select_files + Hash[all_files] + end + + private + + def all_files + member_presenters.flat_map(&:file_presenters).map { |x| [x.to_s, x.id] } + end + + def member_presenters + PresenterFactory.build_presenters(model.member_ids, WorkShowPresenter, nil) + end + end + end +end diff --git a/app/forms/curation_concerns/forms/file_set_edit_form.rb b/app/forms/curation_concerns/forms/file_set_edit_form.rb new file mode 100644 index 0000000..18e0630 --- /dev/null +++ b/app/forms/curation_concerns/forms/file_set_edit_form.rb @@ -0,0 +1,15 @@ +module CurationConcerns::Forms + class FileSetEditForm + include HydraEditor::Form + self.required_fields = [:title, :creator, :tag, :rights] + + self.model_class = ::FileSet + + self.terms = [:resource_type, :title, :creator, :contributor, :description, + :tag, :rights, :publisher, :date_created, :subject, :language, + :identifier, :based_near, :related_url, + :visibility_during_embargo, :visibility_after_embargo, :embargo_release_date, + :visibility_during_lease, :visibility_after_lease, :lease_expiration_date, + :visibility, :prevent_download] + end +end \ No newline at end of file diff --git a/app/helpers/curation_concerns_helper.rb b/app/helpers/curation_concerns_helper.rb index 7d31d43..86f2925 100644 --- a/app/helpers/curation_concerns_helper.rb +++ b/app/helpers/curation_concerns_helper.rb @@ -6,7 +6,25 @@ module CurationConcernsHelper def url_for_document(doc, _options = {}) case doc.fetch('has_model_ssim').first when AdminSet.to_class_uri - admin_set_path doc.id + exhibit = Spotlight::Exhibit + .where(exhibitable_id: doc.id) + .where(exhibitable_type: 'AdminSet') + .first + if exhibit + spotlight.exhibit_root_path exhibit + else + admin_set_path doc.id + end + when Collection.to_class_uri + exhibit = Spotlight::Exhibit + .where(exhibitable_id: doc.id) + .where(exhibitable_type: 'Collection') + .first + if exhibit + spotlight.exhibit_root_path exhibit + else + super + end else super end @@ -15,4 +33,20 @@ def url_for_document(doc, _options = {}) def track_admin_set_path(*args) track_solr_document_path(*args) end + + def allow_download_link(presenter, query) + if presenter.kind_of? CurationConcerns::CollectionPresenter + allow_downloads_path(presenter, query) + elsif presenter.kind_of? AdminSetPresenter + admin_set_allow_downloads_path(presenter, query) + end + end + + def prevent_download_link(presenter, query) + if presenter.kind_of? CurationConcerns::CollectionPresenter + prevent_downloads_path(presenter, query) + elsif presenter.kind_of? AdminSetPresenter + admin_set_prevent_downloads_path(presenter, query) + end + end end diff --git a/app/helpers/rights_helper.rb b/app/helpers/rights_helper.rb new file mode 100644 index 0000000..357e8cf --- /dev/null +++ b/app/helpers/rights_helper.rb @@ -0,0 +1,2 @@ +module RightsHelper +end diff --git a/app/helpers/spotlight/application_helper.rb b/app/helpers/spotlight/application_helper.rb new file mode 100644 index 0000000..2abf132 --- /dev/null +++ b/app/helpers/spotlight/application_helper.rb @@ -0,0 +1,156 @@ +module Spotlight + ## + # General spotlight application helpers + module ApplicationHelper + include CrudLinkHelpers + include TitleHelper + include JcropHelper + include CurationConcerns::CatalogHelper + + ## + # Give the application name a chance to include the exhibit title + def application_name + name = current_site.title if current_site.title.present? + name ||= super + + if current_exhibit + t :'spotlight.application_name', exhibit: current_exhibit.title, application_name: name + else + name + end + end + + # Can search for named routes directly in the main app, omitting + # the "main_app." prefix + def method_missing(method, *args, &block) + if main_app_url_helper?(method) + main_app.send(method, *args) + else + super + end + end + + def respond_to_missing?(method, *args) + main_app_url_helper?(method) || super + end + + ## + # Override the Blacklight #url_for_document helper to add + # the current exhibit context + def url_for_document(document) + super + end + + ## + # Override Blacklight's #document_action_path helper to add + # the current exhibit context + def document_action_path(action_opts, url_opts = nil) + if current_exhibit + spotlight.send(action_opts.path || "exhibit_#{action_opts.key}_#{controller_name}_path", url_opts) + else + super + end + end + + ## + # Helper to turn tag data into facets + def url_to_tag_facet(tag) + if current_exhibit + search_action_url(add_facet_params(blacklight_config.document_model.solr_field_for_tagger(current_exhibit), tag, {})) + else + search_action_url(q: tag) + end + end + + ## + # Override Blacklight's #render_document_class to inject a private class + def render_document_class(document = @document) + types = super || '' + types << " #{document_class_prefix}private" if document.private?(current_exhibit) + types + end + + # Return a copy of the blacklight configuration + # that only includes views conifgured by our block + def blacklight_view_config_for_search_block(block) + # Reject any views that aren't configured to display for this block + blacklight_config.view.select do |view, _| + block.view.include? view.to_s + end + end + + def block_document_index_view_type(block) + views = blacklight_view_config_for_search_block(block) + + if views.key? document_index_view_type + document_index_view_type + else + views.keys.first + end + end + + # Return the list of views that are configured to display for a block + def selected_search_block_views(block) + block.as_json[:data].select do |_key, value| + value == 'on' + end.keys.map(&:to_s) + end + + def select_deselect_button + button_tag( + t(:".deselect_all"), + class: 'btn btn-default btn-xs metadata-select', + data: { + behavior: 'metadata-select', + 'deselect-text' => t(:".deselect_all"), + 'select-text' => t(:".select_all") + } + ) + end + + def add_exhibit_twitter_card_content + twitter_card('summary') do |card| + card.url exhibit_root_url(current_exhibit) + card.title current_exhibit.title + card.description current_exhibit.subtitle + card.image carrierwave_url(current_exhibit.thumbnail.image.thumb) if current_exhibit.thumbnail + end + end + + def carrierwave_url(upload) + # Carrierwave's #url returns either a full url (if asset path was configured) + # or just the path to the image. We'll try to normalize it to a url. + url = upload.url + + if url.nil? || url.starts_with?('http') + url + else + (URI.parse(Rails.application.config.asset_host || root_url) + url).to_s + end + end + + def render_save_this_search? + (current_exhibit && can?(:curate, current_exhibit)) && + !(params[:controller] == 'spotlight/catalog' && params[:action] == 'admin') + end + + def uploaded_field_label(config) + solr_field = Array(config.solr_field || config.field_name).first.to_s + config.label || blacklight_config.index_fields[solr_field].try(:label) || t(".#{solr_field}") + end + + def view_label(view) + t(:"blacklight.search.view.#{view}", default: blacklight_config.view[view].title || view.to_s) + end + + def available_view_fields + current_exhibit.blacklight_configuration.default_blacklight_config.view.to_h.reject { |_k, v| v.if == false } + end + + private + + def main_app_url_helper?(method) + method.to_s.end_with?('_path', '_url') && main_app.respond_to?(method) + end + end +end diff --git a/app/helpers/spotlight_helper.rb b/app/helpers/spotlight_helper.rb new file mode 100644 index 0000000..0426d0f --- /dev/null +++ b/app/helpers/spotlight_helper.rb @@ -0,0 +1,12 @@ +## +# Global Spotlight helpers +module SpotlightHelper + include ::BlacklightHelper + include Spotlight::MainAppHelpers + + def admin_set_link opts={} + if @exhibit.exhibitable + link_to t(:'spotlight.curration.sidebar.collection'), @exhibit.exhibitable + end + end +end diff --git a/app/indexers/base_work_indexer.rb b/app/indexers/base_work_indexer.rb index 2a715d6..a24fcd2 100644 --- a/app/indexers/base_work_indexer.rb +++ b/app/indexers/base_work_indexer.rb @@ -1,15 +1,46 @@ class BaseWorkIndexer < CurationConcerns::WorkIndexer + TEI_JSON = 'tei_json_tesi' + include Rails.application.routes.url_helpers + def generate_solr_document + relative_thumb_path = CurationConcerns::ThumbnailPathService.call(object) super do |solr_doc| + solr_doc[TEI_JSON] = ActionView::Base.full_sanitizer.sanitize(tei) if tei + solr_doc['oai_identifier_ssm'] = [ + url_for(object), + "http://#{Rails.application.routes.default_url_options[:host]}#{relative_thumb_path}", + object.identifier + ] solr_doc['rights_label_ss'] = rights_labels.first solr_doc['admin_set_ssi'] = object.admin_set.try(:title) + solr_doc['custom_metadata_fields_ssm'] = object.custom_metadatas.collect{|datapoint| datapoint.title.parameterize.underscore } + object.custom_metadatas.each do |datapoint| + next if datapoint.title.blank? + solr_doc["#{datapoint.title.parameterize.underscore}_ssi"] = datapoint.value + end yield(solr_doc) if block_given? end end private - def rights_labels object.rights.map { |r| RightsService.label(r) } end + + def tei_to_json + as_json = tei_as_json + return unless as_json + JSON.generate(as_json) + end + + def tei + @tei ||= object.try(:tei).try(:original_file).try(:content) + end + + def tei_as_json + # OPTIMIZE: this could be indexed on the FileSet which + # so that every index call wouldn't have to load the tei file. + return unless tei + TEIConverter.new(tei, object).as_json + end end diff --git a/app/indexers/text_indexer.rb b/app/indexers/text_indexer.rb index 63f3533..8f309e9 100644 --- a/app/indexers/text_indexer.rb +++ b/app/indexers/text_indexer.rb @@ -1,23 +1,2 @@ class TextIndexer < BaseWorkIndexer - TEI_JSON = 'tei_json_ss' - - # def generate_solr_document - # super do |solr_doc| - # solr_doc[TEI_JSON] = tei_to_json if object.tei - # end - # end - # - # def tei_to_json - # as_json = tei_as_json - # return unless as_json - # JSON.generate(as_json) - # end - # - # def tei_as_json - # # OPTIMIZE: this could be indexed on the FileSet which - # # so that every index call wouldn't have to load the tei file. - # tei = object.tei.original_file.try(:content) - # return unless tei - # TEIConverter.new(tei, object).as_json - # end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 6bf05c8..f78f4e6 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -6,8 +6,15 @@ class Ability def custom_permissions if admin? can [:confirm_delete], ActiveFedora::Base + can [:allow_downloads, :prevent_downloads], AdminSet + + can :manage, Spotlight::HomePage + can :manage, Spotlight::Exhibit end + can :read, Spotlight::HomePage + can :read, Spotlight::Exhibit + # Limits creating new objects to a specific group # # if user_groups.include? 'special_group' diff --git a/app/models/admin_set.rb b/app/models/admin_set.rb index 8cfcee1..09afd21 100644 --- a/app/models/admin_set.rb +++ b/app/models/admin_set.rb @@ -2,6 +2,7 @@ class AdminSet < ActiveFedora::Base include Hydra::AccessControls::Permissions include CurationConcerns::HumanReadableType include CurationConcerns::HasRepresentative + include SpotlightExhibitable self.human_readable_type = 'Administrative Collection' @@ -46,12 +47,28 @@ class AdminSet < ActiveFedora::Base end before_create :assign_access + before_destroy :destroy_spotlight_exhibit def assign_access self.read_groups += ['public'] end + def destroy_spotlight_exhibit + spotlight_exhibit_query.destroy_all + end + def self.indexer AdminSetIndexer end + + def default_filter_field + "admin_set_ssi" + end + + private + def spotlight_exhibit_query + Spotlight::Exhibit + .where(exhibitable_id: self.id) + .where(exhibitable_type: 'AdminSet') + end end diff --git a/app/models/audio.rb b/app/models/audio.rb index 41bc1e2..564454b 100644 --- a/app/models/audio.rb +++ b/app/models/audio.rb @@ -5,6 +5,7 @@ class Audio < ActiveFedora::Base include InAdminSet include OnCampusAccess include DrawTemplate + include SpotlightAttributes validates :title, presence: { message: 'Your work must have a title.' } diff --git a/app/models/collection.rb b/app/models/collection.rb index 06e73e7..ba9ff60 100644 --- a/app/models/collection.rb +++ b/app/models/collection.rb @@ -1,5 +1,16 @@ class Collection < ActiveFedora::Base include ::CurationConcerns::CollectionBehavior - + include SpotlightExhibitable self.human_readable_type = 'Personal Collection' + + def default_filter_field + "member_ids_ssim" + end + + private + def spotlight_exhibit_query + Spotlight::Exhibit + .where(exhibitable_id: self.id) + .where(exhibitable_type: 'Collection') + end end diff --git a/app/models/concerns/metadata.rb b/app/models/concerns/metadata.rb index 9306959..6cc499d 100644 --- a/app/models/concerns/metadata.rb +++ b/app/models/concerns/metadata.rb @@ -6,6 +6,10 @@ module Metadata index.as :stored_sortable end + property :oai_identifier, predicate: ::RDF::Vocab::DC.identifier, multiple: true do |index| + index.as :stored_searchable + end + property :extent, predicate: ::RDF::Vocab::DC.extent do |index| index.as :symbol end @@ -41,5 +45,9 @@ module Metadata property :researcher, predicate: ::RDF::Vocab::MARCRelators.res do |index| index.as :stored_searchable end + + property :source, predicate: ::RDF::Vocab::MARCRelators.org do |index| + index.as :stored_searchable + end end end diff --git a/app/models/concerns/spotlight/exhibit_defaults.rb b/app/models/concerns/spotlight/exhibit_defaults.rb new file mode 100644 index 0000000..e70cb6d --- /dev/null +++ b/app/models/concerns/spotlight/exhibit_defaults.rb @@ -0,0 +1,59 @@ +module Spotlight + # Mixin for adding default configuration to exhibits + module ExhibitDefaults + extend ActiveSupport::Concern + + included do + before_create :build_home_page + before_create :add_site_reference + after_create :initialize_config + after_create :initialize_browse + after_create :initialize_main_navigation + after_create :initialize_filter + end + + protected + + def initialize_filter + return unless Spotlight::Engine.config.filter_resources_by_exhibit + + filters.create field: default_filter_field, value: default_filter_value + end + + def initialize_config + self.blacklight_configuration ||= Spotlight::BlacklightConfiguration.create! + end + + def initialize_browse + return unless searches.blank? + + searches.create title: 'All Exhibit Items', + long_description: 'All items in this exhibit.' + end + + def initialize_main_navigation + default_main_navigations.each_with_index do |nav_type, weight| + main_navigations.create nav_type: nav_type, weight: weight + end + end + + def add_site_reference + self.site ||= Spotlight::Site.instance + end + + def default_filter_field + raise "'default_filter_field' should be implemented in class including this module" + end + + # Return a string to work around any ActiveRecord type-casting + def default_filter_value + self.exhibitable.title + end + + private + + def default_main_navigations + Spotlight::Engine.config.exhibit_main_navigation.dup + end + end +end diff --git a/app/models/concerns/spotlight_attributes.rb b/app/models/concerns/spotlight_attributes.rb new file mode 100644 index 0000000..3810ead --- /dev/null +++ b/app/models/concerns/spotlight_attributes.rb @@ -0,0 +1,26 @@ +module SpotlightAttributes + extend ActiveSupport::Concern + + # TODO: this needs paperclip like behavior + # from spotlight: app/controllers/concerns/spotlight/base.rb + # thumbnails: doc.spotlight_image_versions.try(:thumb) || doc[blacklight_config.index.thumbnail_field], + # full_image_url: doc.spotlight_image_versions.try(:full).try(:first), + # full_images: doc.spotlight_image_versions.try(:full), + # image_versions: doc.spotlight_image_versions.image_versions(:thumb, :full), + included do + klass_name = self.name + has_and_belongs_to_many :custom_metadatas, predicate: ::RDF::Vocab::DCAT.CatalogRecord, class_name: "#{klass_name}::CustomMetadata", inverse_of: klass_name.downcase.pluralize + accepts_nested_attributes_for :custom_metadatas + + property :spotlight_image_versions, predicate: ::RDF::Vocab::DC.abstract, multiple: true do |index| + index.as :stored_sortable + end + + self::CustomMetadata = Class.new(ActiveFedora::Base) do + type ::RDF::URI('http://example.org/terms/custom_metadata_set') + has_many klass_name.downcase.pluralize.intern, inverse_of: :custom_metadatas, class_name: klass_name + property :title, predicate: ::RDF::Vocab::DC.title, class_name: ::RDF::Literal, multiple: false + property :value, predicate: ::RDF::Vocab::DC.description, class_name: ::RDF::Literal, multiple: false + end + end +end diff --git a/app/models/concerns/spotlight_exhibitable.rb b/app/models/concerns/spotlight_exhibitable.rb new file mode 100644 index 0000000..a12b088 --- /dev/null +++ b/app/models/concerns/spotlight_exhibitable.rb @@ -0,0 +1,36 @@ +module SpotlightExhibitable + extend ActiveSupport::Concern + + included do + after_create :create_spotlight_exhibit + before_save :update_spotlight_exhibit + after_destroy :remove_spotlight_exhibit + end + + def spotlight_exhibit + @spotlight_exhibit ||= spotlight_exhibit_query.first + end + + def create_spotlight_exhibit + spotlight_exhibit_query.first_or_create do |exhibit| + exhibit.title = self.title + exhibit.published = true + end + end + + def update_spotlight_exhibit + if self.valid? && self.title_changed? && spotlight_exhibit + spotlight_exhibit.update_attributes({ + title: self.title + }) + end + end + + def remove_spotlight_exhibit + spotlight_exhibit.destroy if spotlight_exhibit + end + + def spotlight_exhibit_query + raise "This method must be implimented when this module is included." + end +end diff --git a/app/models/file_set.rb b/app/models/file_set.rb index a89f745..77654af 100644 --- a/app/models/file_set.rb +++ b/app/models/file_set.rb @@ -6,4 +6,13 @@ class FileSet < ActiveFedora::Base def human_readable_type 'File' end + + property :prevent_download, predicate: ::RDF::Vocab::DC.Policy, multiple: false do |index| + index.as :stored_searchable + end + + def prevent_download= value + super value.to_s.downcase == "true" + end + end diff --git a/app/models/image.rb b/app/models/image.rb index 77c50fa..392e80e 100644 --- a/app/models/image.rb +++ b/app/models/image.rb @@ -5,10 +5,13 @@ class Image < ActiveFedora::Base include InAdminSet include OnCampusAccess include DrawTemplate + include SpotlightAttributes + validates :title, presence: { message: 'Your work must have a title.' } def self.indexer ImageIndexer end + end diff --git a/app/models/qa.rb b/app/models/qa.rb new file mode 100644 index 0000000..58fe5cd --- /dev/null +++ b/app/models/qa.rb @@ -0,0 +1,5 @@ +module Qa + def self.table_name_prefix + 'qa_' + end +end diff --git a/app/models/qa/local_authority.rb b/app/models/qa/local_authority.rb new file mode 100644 index 0000000..6285a09 --- /dev/null +++ b/app/models/qa/local_authority.rb @@ -0,0 +1,3 @@ +class Qa::LocalAuthority < ActiveRecord::Base + has_many :local_authority_entries +end diff --git a/app/models/qa/local_authority_entry.rb b/app/models/qa/local_authority_entry.rb new file mode 100644 index 0000000..a51fb39 --- /dev/null +++ b/app/models/qa/local_authority_entry.rb @@ -0,0 +1,9 @@ +class Qa::LocalAuthorityEntry < ActiveRecord::Base + belongs_to :local_authority + + validates :uri, format: { with: /\Ahttp[s]?:\/\/.+/, message: 'should start with "http://"' } + + def uri_editable? + self.id.blank? + end +end diff --git a/app/models/resource_base.rb b/app/models/resource_base.rb new file mode 100644 index 0000000..f44d0b5 --- /dev/null +++ b/app/models/resource_base.rb @@ -0,0 +1,92 @@ +class ResourceBase < ActiveFedora::Base + include CurationConcerns::WorkBehavior + include CurationConcerns::BasicMetadata + include Metadata + include InAdminSet + include OnCampusAccess + include DrawTemplate + + #Spotlight + #include Spotlight::SolrDocument::AtomicUpdates + #include ActiveSupport::Benchmarkable + + # these break indexing for CurationConcern based works + #include Spotlight::Resources::GeneratingSolrDocuments + #include Spotlight::Resources::Indexing + + class_attribute :weight + + #serialize :data, Hash + # store :metadata, accessors: [ + # :last_indexed_estimate, + # :last_indexed_count, + # :last_index_elapsed_time, + # :last_indexed_finished], coder: JSON + + #enum index_status: [:waiting, :completed, :errored] + + ## + # Persist the record, and trigger a reindex to solr + # + # @param [Hash] All arguments will be passed through to ActiveFedora's #save method + def save_and_index(*args) + save(*args) && reindex + end + + # Spotlight attributes + # exhibit_id + # type + # url + # data + # indexed_at + # created_at + # updated_at + # metadata + # index_status + + # Spotlight methods + # reindex_later + # save_and_index + # concerning :GeneratingSolrDocuments + # to_solr + # - exists + # documents_to_index + # existing_solr_doc_hash + # unique_key + # exhibit_specific_solr_data + # spotlight_resource_metadata_for_solr + # document_modal + # concerning :Indexing + # reindex + # reindex_with_logging + # blacklight_solr + # connection_config + # batch_size + # write_to_index + # commit + + property :exhibit_name, predicate: ::RDF::Vocab::DC.abstract, multiple: true do |index| + index.as :stored_searchable + end + + property :indexed_at, predicate: ::RDF::Vocab::DC.abstract, multiple: false + + #Need to be part of metadata + property :last_indexed_estimate, predicate: ::RDF::Vocab::DC.abstract, multiple: false + property :last_indexed_count, predicate: ::RDF::Vocab::DC.abstract, multiple: false + property :last_index_elapsed_time, predicate: ::RDF::Vocab::DC.abstract, multiple: false + property :last_indexed_finished, predicate: ::RDF::Vocab::DC.abstract, multiple: false + + # property :data, predicate: ::RDF::DC.description, multiple: false do |index| + # index.as :stored + # end + + alias_method :index, :update_index + alias_method :to_global_id, :id + + def exhibit + if self.exhibit_name + Spotlight::Exhibit.where(title: self.exhibit_name).first + end + end +end diff --git a/app/models/solr_document.rb b/app/models/solr_document.rb index 7ca159e..497482e 100644 --- a/app/models/solr_document.rb +++ b/app/models/solr_document.rb @@ -1,11 +1,24 @@ # -*- encoding : utf-8 -*- class SolrDocument include Blacklight::Solr::Document + include Blacklight::Document::DublinCore + include BlacklightOaiProvider::SolrDocument + include Spotlight::SolrDocument # Adds CurationConcerns behaviors to the SolrDocument. include CurationConcerns::SolrDocumentBehavior # Do content negotiation for AF models. use_extension(Hydra::ContentNegotiation) + def to_openseadragon(*_args) + [self[Spotlight::Engine.config.full_image_field]].flatten.each_with_index.map do |image_url, index| + { Spotlight::SolrDocument::UploadedResource::LegacyImagePyramidTileSource.new( + image_url, + width: 300, + height: 300 + ) => {} + } + end + end def height self['height_is'] @@ -63,6 +76,14 @@ def filename self['label_ssi'] end + def custom_metadata_fields + self['custom_metadata_fields_ssm'] + end + + def thumbnail_path + self['thumbnail_path_ss'] + end + def thumbnail_id fetch('hasRelatedImage_ssim', []).first end @@ -70,4 +91,27 @@ def thumbnail_id def on_campus? read_groups.include? OnCampusAccess::OnCampus end + + def source + self['source_tesim'] + end + + def prevent_download + self['prevent_download_bsi'] + end + + field_semantics.merge!( + contributor: 'contributor_tesim', + creator: 'creator_sim', + date: 'date_issued_dtsi', + description: 'description_tesim', + format: 'human_readable_type_tesim', + identifier: 'oai_identifier_ssm', + language: 'language_tesim', + publisher: 'publisher_tesim', + rights: 'rights_label_ss', + subject: 'subject_tesim', + title: 'title_tesim', + type: 'human_readable_type_tesim' + ) end diff --git a/app/models/spotlight/blacklight_configuration.rb b/app/models/spotlight/blacklight_configuration.rb new file mode 100644 index 0000000..4d0ccd2 --- /dev/null +++ b/app/models/spotlight/blacklight_configuration.rb @@ -0,0 +1,347 @@ +require 'blacklight/utils' + +module Spotlight + ## + # Exhibit-specific blacklight configuration model + # rubocop:disable Metrics/ClassLength + class BlacklightConfiguration < ActiveRecord::Base + belongs_to :exhibit, touch: true + serialize :facet_fields, Hash + serialize :index_fields, Hash + serialize :search_fields, Hash + serialize :sort_fields, Hash + serialize :default_solr_params, Hash + serialize :show, Hash + serialize :index, Hash + serialize :per_page, Array + serialize :document_index_view_types, Array + + include Spotlight::BlacklightConfigurationDefaults + include Spotlight::ImageDerivatives + + delegate :document_model, to: :default_blacklight_config + + # get rid of empty values + before_validation do |model| + model.index_fields.each do |_k, v| + v[:enabled] ||= v.any? { |_k1, v1| !v1.blank? } + + default_blacklight_config.view.keys.each do |view| + v[view] &&= value_to_boolean(v[view]) + end + + v[:show] &&= value_to_boolean(v[:show]) + v.reject! { |_k, v1| v1.blank? && v1 != false } + end if model.index_fields + + model.facet_fields.each do |_k, v| + v[:show] &&= value_to_boolean(v[:show]) + v[:show] ||= true if v[:show].nil? + v.reject! { |_k, v1| v1.blank? && v1 != false } + end if model.facet_fields + + model.search_fields.each do |k, v| + v[:enabled] &&= value_to_boolean(v[:enabled]) + v[:enabled] ||= true if v[:enabled].nil? + v[:label] = default_blacklight_config.search_fields[k][:label] if default_blacklight_config.search_fields[k] && !v[:label].present? + v.reject! { |_k, v1| v1.blank? && v1 != false } + end if model.search_fields + + model.sort_fields.each do |k, v| + v[:enabled] &&= value_to_boolean(v[:enabled]) + v[:enabled] ||= true if v[:enabled].nil? + v[:label] = default_blacklight_config.sort_fields[k][:label] if default_blacklight_config.sort_fields[k] && !v[:label].present? + v.reject! { |_k, v1| v1.blank? && v1 != false } + end if model.sort_fields + + model.per_page.reject!(&:blank?) if model.per_page + model.document_index_view_types.reject!(&:blank?) if model.document_index_view_types + end + + ## + # Serialize this configuration to a Blacklight::Configuration object + # appropriate to the current view. If a value isn't set in this record, + # it will use the configuration set upstream (in default_blacklight_config) + # @param [String] view the configuration may be different depending on the index view selected + # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize + def blacklight_config + @blacklight_config ||= begin + # Create a new config based on the defaults + config = default_blacklight_config.inheritable_copy + + config.show.merge! show unless show.blank? + config.index.merge! index unless index.blank? + + config.index.thumbnail_field ||= Spotlight::Engine.config.thumbnail_field + + config.add_results_collection_tool 'save_search', if: :render_save_this_search? + + config.default_solr_params = config.default_solr_params.merge(default_solr_params) + + config.view.embed.partials ||= ['openseadragon'] + config.view.embed.if = false + config.view.embed.locals ||= { osd_container_class: '' } + + # Add any custom fields + config.index_fields.merge! custom_index_fields + config.index_fields = Hash[config.index_fields.sort_by { |k, _v| field_weight(index_fields, k) }] + config.index_fields.reject! { |_k, v| v.if == false } + + # Update with customizations + config.index_fields.each do |k, v| + if index_fields[k] + v.merge! index_fields[k].symbolize_keys + elsif custom_index_fields[k] + set_custom_field_defaults(v) + else + set_index_field_defaults(v) + end + v.upstream_if = v.if unless v.if.nil? + v.if = :field_enabled? + + v.normalize! config + v.validate! + end + + config.show_fields.reject! { |_k, v| v.if == false } + + config.show_fields.reject { |k, _v| config.index_fields[k] }.each do |k, v| + config.index_fields[k] = v + + if index_fields[k] + v.merge! index_fields[k].symbolize_keys + else + set_show_field_defaults(v) + end + + v.upstream_if = v.if unless v.if.nil? + v.if = :field_enabled? + + v.normalize! config + v.validate! + end + + config.show_fields = config.index_fields + + unless search_fields.blank? + config.search_fields = Hash[config.search_fields.sort_by { |k, _v| field_weight(search_fields, k) }] + + config.search_fields.each do |k, v| + v.upstream_if = v.if unless v.if.nil? + v.if = :field_enabled? + next if search_fields[k].blank? + + v.merge! search_fields[k].symbolize_keys + v.normalize! config + v.validate! + end + end + + unless sort_fields.blank? + config.sort_fields = Hash[config.sort_fields.sort_by { |k, _v| field_weight(sort_fields, k) }] + + config.sort_fields.each do |k, v| + v.upstream_if = v.if unless v.if.nil? + v.if = :field_enabled? + next if sort_fields[k].blank? + + v.merge! sort_fields[k].symbolize_keys + v.normalize! config + v.validate! + end + end + + config.facet_fields.merge! custom_facet_fields + unless facet_fields.blank? + config.facet_fields = Hash[config.facet_fields.sort_by { |k, _v| field_weight(facet_fields, k) }] + + config.facet_fields.each do |k, v| + next if facet_fields[k].blank? + + v.merge! facet_fields[k].symbolize_keys + v.upstream_if = v.if unless v.if.nil? + v.enabled = v.show + v.if = :field_enabled? + v.normalize! config + v.validate! + end + end + + config.per_page = (config.per_page & per_page) unless per_page.blank? + + if default_per_page + config.per_page.delete(default_per_page) + config.per_page.unshift(default_per_page) + end + + config.view.each do |k, v| + v.key = k + v.upstream_if = v.if unless v.if.nil? + v.if = :enabled_in_spotlight_view_type_configuration? + end unless document_index_view_types.blank? + + if config.search_fields.blank? + config.navbar.partials[:saved_searches].if = false if config.navbar.partials.key? :saved_searches + config.navbar.partials[:search_history].if = false if config.navbar.partials.key? :search_history + end + + config + end + end + # rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity + + def custom_index_fields + Hash[exhibit.custom_fields.map do |x| + field = Blacklight::Configuration::IndexField.new x.configuration.merge( + key: x.field, field: x.solr_field + ) + [x.field, field] + end] + end + + def custom_facet_fields + Hash[exhibit.custom_fields.vocab.map do |x| + field = Blacklight::Configuration::FacetField.new x.configuration.merge( + key: x.field, field: x.solr_field, show: false + ) + [x.field, field] + end] + end + + ## + # Get the "upstream" blacklight configuration to use + def default_blacklight_config + @default_blacklight_config ||= begin + config = Spotlight::Engine.blacklight_config.deep_copy + add_exhibit_specific_fields(config) + config + end + end + + # Parse params checkbox arrays into simple arrays. + # A group of checkboxes on a form returns values like this: + # {"list"=>"1", "gallery"=>"1", "map"=>"0"} + # where, "list" and "gallery" are selected and "map" is not. This function + # digests that hash into a list of selected values. e.g.: + # ["list", "gallery"] + def document_index_view_types=(hash_or_array) + if hash_or_array.is_a? Hash + super(hash_or_array.select { |_, checked| checked == '1' }.keys) + else + super(hash_or_array) + end + end + + # @return [OpenStructWithHashAccess] keys are view types; value is 1 if enabled + # A group of checkboxes on a form needs values like this: + # {"list"=>"1", "gallery"=>"1", "map"=>"0"} + # where, "list" and "gallery" are selected and "map" is not. This function + # takes ["list", "gallery"] and turns it into the above. + def document_index_view_types_selected_hash + selected_view_types = document_index_view_types + avail_view_types = default_blacklight_config.view.select { |_k, v| v.if != false }.keys + Blacklight::OpenStructWithHashAccess.new.tap do |s| + avail_view_types.each do |k| + s[k] = selected_view_types.include?(k.to_s) + end + end + end + + protected + + def add_exhibit_specific_fields(config) + #add_exhibit_tags_fields(config) + add_uploaded_resource_fields(config) + add_autocomplete_field(config) + end + + def add_exhibit_tags_fields(config) + # rubocop:disable Style/GuardClause + unless config.show_fields.include? :exhibit_tags + config.add_show_field :exhibit_tags, field: config.document_model.solr_field_for_tagger(exhibit), link_to_search: true + end + + unless config.facet_fields.include? :exhibit_tags + config.add_facet_field :exhibit_tags, field: config.document_model.solr_field_for_tagger(exhibit) + end + # rubocop:enable Style/GuardClause + end + + def add_uploaded_resource_fields(config) + exhibit.uploaded_resource_fields.each do |f| + add_uploaded_resource_field(config, f) + end + end + + def add_uploaded_resource_field(config, f) + key = Array(f.solr_field || f.field_name).first.to_s + + return if config.index_fields.any? { |_k, v| v.field == key } + + options = f.blacklight_options || {} + options[:label] = f.label if f.label + + config.add_index_field key, options + end + + def add_autocomplete_field(config) + return unless Spotlight::Engine.config.autocomplete_search_field && !config.search_fields[Spotlight::Engine.config.autocomplete_search_field] + + config.add_search_field(Spotlight::Engine.config.autocomplete_search_field) do |field| + field.include_in_simple_select = false + field.solr_parameters = Spotlight::Engine.config.default_autocomplete_params.deep_dup + field.solr_parameters[:fl] ||= default_autocomplete_field_list(config) + end + end + + def default_autocomplete_field_list(config) + "#{config.document_model.unique_key} #{config.view_config(:show).title_field} #{spotlight_image_version_fields.join(' ')}" + end + + def spotlight_image_version_fields + spotlight_image_derivatives.map do |version| + version[:field] + end + end + + # rubocop:disable Style/AccessorMethodName + def set_index_field_defaults(field) + return unless index_fields.blank? + + views = default_blacklight_config.view.keys | [:show, :enabled] + field.merge! Hash[views.map { |v| [v, true] }] + end + + def set_show_field_defaults(field) + return unless index_fields.blank? + views = default_blacklight_config.view.keys + field.merge! Hash[views.map { |v| [v, false] }] + field.enabled = true + field.show = true + end + + def set_custom_field_defaults(field) + field.show = true + field.enabled = true + end + # rubocop:enable Style/AccessorMethodName + + # @return [Integer] the weight (sort order) for this field + def field_weight(fields, index) + if fields[index] && fields[index][:weight] + fields[index][:weight].to_i + else + 100 + (fields.keys.index(index) || fields.keys.length) + end + end + + def value_to_boolean(v) + if defined? ActiveRecord::Type + # Rails 4.2+ + ActiveRecord::Type::Boolean.new.type_cast_from_database v + else + ActiveRecord::ConnectionAdapters::Column.value_to_boolean v + end + end + end +end diff --git a/app/models/spotlight/exhibit.rb b/app/models/spotlight/exhibit.rb new file mode 100644 index 0000000..8d2c6bf --- /dev/null +++ b/app/models/spotlight/exhibit.rb @@ -0,0 +1,133 @@ +require 'mail' +module Spotlight + ## + # Spotlight exhibit + class Exhibit < ActiveRecord::Base + include Spotlight::ExhibitAnalytics + include Spotlight::ExhibitDefaults + include Spotlight::ExhibitDocuments + + scope :published, -> { where(published: true) } + scope :unpublished, -> { where(published: false) } + + paginates_per 50 + + extend FriendlyId + friendly_id :title, use: [:slugged, :finders] + validates :title, presence: true + validates :slug, uniqueness: true + + default_scope { order('weight ASC') } + + acts_as_tagger + acts_as_taggable + delegate :blacklight_config, to: :blacklight_configuration + delegate :description, to: :exhibitable, allow_nil: true + serialize :facets, Array + + # Note: friendly id associations need to be 'destroy'ed to reap the slug history + has_many :about_pages, extend: FriendlyId::FinderMethods + has_many :attachments, dependent: :destroy + has_many :contact_emails, dependent: :delete_all # These are the contacts who get "Contact us" emails + has_many :contacts, dependent: :delete_all # These are the contacts who appear in the sidebar + has_many :custom_fields, dependent: :delete_all + has_many :feature_pages, extend: FriendlyId::FinderMethods + has_many :main_navigations, dependent: :delete_all + has_many :owned_taggings, class_name: 'ActsAsTaggableOn::Tagging', as: :tagger + #has_many :resources + has_many :roles, as: :resource, dependent: :delete_all + has_many :searches, dependent: :destroy, extend: FriendlyId::FinderMethods + has_many :solr_document_sidecars, dependent: :delete_all + has_many :users, through: :roles, class_name: Spotlight::Engine.config.user_class + has_many :pages, dependent: :destroy + has_many :filters, dependent: :delete_all + + has_one :blacklight_configuration, class_name: 'Spotlight::BlacklightConfiguration', dependent: :delete + has_one :home_page + + belongs_to :site + belongs_to :masthead, dependent: :destroy + belongs_to :thumbnail, class_name: 'Spotlight::FeaturedImage', dependent: :destroy + + #accepts_nested_attributes_for :about_pages, :attachments, :contacts, :custom_fields, :feature_pages, + # :main_navigations, :owned_taggings, :resources, :searches, :solr_document_sidecars + accepts_nested_attributes_for :about_pages, :attachments, :contacts, :custom_fields, :feature_pages, + :main_navigations, :owned_taggings, :searches, :solr_document_sidecars + + accepts_nested_attributes_for :blacklight_configuration, :home_page, :masthead, :thumbnail, :filters, update_only: true + accepts_nested_attributes_for :contact_emails, reject_if: proc { |attr| attr['email'].blank? } + accepts_nested_attributes_for :roles, allow_destroy: true, reject_if: proc { |attr| attr['user_key'].blank? && attr['id'].blank? } + + before_save :sanitize_description, if: :description_changed? + include Spotlight::DefaultThumbnailable + + def title + exhibitable.title if exhibitable + end + + def exhibitable + return nil if self.exhibitable_id.blank? || self.exhibitable_type.blank? + @exhibitable = self.exhibitable_type.constantize.find self.exhibitable_id + rescue + return nil + end + + def main_about_page + @main_about_page ||= about_pages.published.first + end + + def browse_categories? + searches.published.any? + end + + def to_s + title + end + + def import(hash) + Spotlight::ExhibitExportSerializer.prepare(self).from_hash(hash) + self + end + + def solr_data + filters.each_with_object({}) do |filter, hash| + hash.merge! filter.to_hash + end + end + + def reindex_later + Spotlight::ReindexJob.perform_later(self) + end + + def uploaded_resource_fields + Spotlight::Engine.config.upload_fields + end + + def searchable? + blacklight_config.search_fields.any? { |_k, v| v.enabled && v.include_in_simple_select != false } + end + + def set_default_thumbnail + self.thumbnail ||= searches.first.try(:thumbnail) + end + + def requested_by + roles.first.user if roles.first + end + + def reindex_progress + return nil + @reindex_progress ||= ReindexProgress.new(resources.order('updated_at')) if resources + end + + def default_filter_field + exhibitable.default_filter_field if exhibitable + end + + protected + + def sanitize_description + self.description = ::Rails::Html::FullSanitizer.new.sanitize(description) + end + end +end diff --git a/app/models/text.rb b/app/models/text.rb index c2d50c8..ab181c7 100644 --- a/app/models/text.rb +++ b/app/models/text.rb @@ -5,6 +5,7 @@ class Text < ActiveFedora::Base include InAdminSet include OnCampusAccess include DrawTemplate + include SpotlightAttributes validates :title, presence: { message: 'Your work must have a title.' } diff --git a/app/models/user.rb b/app/models/user.rb index 646f414..e8a90f9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -12,7 +12,14 @@ class User < ActiveRecord::Base devise :ldap_authenticatable, :rememberable, :trackable # Fetch groups from LDAP. Must come after `devise` call. - include WithLdapGroups + if ENV['SKIP_LDAP'] && !Rails.env.production? + def groups + ['admin'] + end + else + include WithLdapGroups + end + # Method added by Blacklight; Blacklight uses #to_s on your # user class to get a user-displayable login/identifier for @@ -20,4 +27,8 @@ class User < ActiveRecord::Base def to_s username end + + def exhibits + [] + end end diff --git a/app/models/video.rb b/app/models/video.rb index 5c6fba2..22435cf 100644 --- a/app/models/video.rb +++ b/app/models/video.rb @@ -5,6 +5,7 @@ class Video < ActiveFedora::Base include InAdminSet include OnCampusAccess include DrawTemplate + include SpotlightAttributes validates :title, presence: { message: 'Your work must have a title.' } diff --git a/app/presenters/admin_set_presenter.rb b/app/presenters/admin_set_presenter.rb index 769ca11..3a724d1 100644 --- a/app/presenters/admin_set_presenter.rb +++ b/app/presenters/admin_set_presenter.rb @@ -1,16 +1,43 @@ class AdminSetPresenter include CurationConcerns::ModelProxy include CurationConcerns::PresentsAttributes + include ExhibitPresenter attr_reader :solr_document # Metadata Methods - delegate :title, :description, :creator, :contributor, :subject, :publisher, :language, - :embargo_release_date, :lease_expiration_date, :rights, :human_readable_type, - :representative_id, - to: :solr_document + delegate :contributor, + :creator, + :description, + :embargo_release_date, + :human_readable_type, + :language, + :lease_expiration_date, + :publisher, + :representative_id, + :rights, + :subject, + :title, + to: :solr_document # @param [SolrDocument] solr_document def initialize(solr_document) @solr_document = solr_document end + + def attribute_to_html(field, options={}) + case field + when :spotlight_exhibit + SpotlightExhibitRenderer.new(:spotlight, [spotlight_exhibit_title], link_path: edit_exhibit_path).render if spotlight_exhibit + else + super + end + end + + def spotlight_exhibit + @spotlight_exhibit ||= Spotlight::Exhibit + .where(exhibitable_id: @solr_document['id']) + .where(exhibitable_type: 'AdminSet') + .first + end + end diff --git a/app/presenters/audio_presenter.rb b/app/presenters/audio_presenter.rb new file mode 100644 index 0000000..0ca7f81 --- /dev/null +++ b/app/presenters/audio_presenter.rb @@ -0,0 +1,3 @@ +class AudioPresenter < WorkShowPresenter + include DisplayFields +end diff --git a/app/presenters/blacklight/document_presenter.rb b/app/presenters/blacklight/document_presenter.rb new file mode 100644 index 0000000..0ac61aa --- /dev/null +++ b/app/presenters/blacklight/document_presenter.rb @@ -0,0 +1,220 @@ +module Blacklight + class DocumentPresenter + include ActionView::Helpers::OutputSafetyHelper + include ActionView::Helpers::TagHelper + extend Deprecation + + # @param [SolrDocument] document + # @param [ActionController::Base] controller scope for linking and generating urls + # @param [Blacklight::Configuration] configuration + def initialize(document, controller, configuration = controller.blacklight_config) + @document = document + @configuration = configuration + @controller = controller + end + + ## + # Get the value of the document's "title" field, or a placeholder + # value (if empty) + # + # @param [SolrDocument] document + # @return [String] + def document_heading + fields = Array(@configuration.view_config(:show).title_field) + f = fields.find { |field| @document.has? field } + + if f.nil? + render_field_value(@document.id) + else + render_field_value(@document[f]) + end + end + + ## + # Create links from a documents dynamically + # provided export formats. Returns empty string if no links available. + # + # @params [SolrDocument] document + # @params [Hash] options + # @option options [Boolean] :unique ensures only one link is output for every + # content type, e.g. as required by atom + # @option options [Array] :exclude array of format shortnames to not include in the output + def link_rel_alternates(options = {}) + options = { unique: false, exclude: [] }.merge(options) + + seen = Set.new + + safe_join(@document.export_formats.map do |format, spec| + next if options[:exclude].include?(format) || (options[:unique] && seen.include?(spec[:content_type])) + + seen.add(spec[:content_type]) + + tag(:link, rel: "alternate", title: format, type: spec[:content_type], href: @controller.polymorphic_url(@document, format: format)) + end.compact, "\n") + end + + ## + # Get the document's "title" to display in the element. + # (by default, use the #document_heading) + # + # @see #document_heading + # @return [String] + def document_show_html_title + if @configuration.view_config(:show).html_title_field + fields = Array(@configuration.view_config(:show).html_title_field) + f = fields.find { |field| @document.has? field } + + if f.nil? + render_field_value(@document.id) + else + render_field_value(@document[f]) + end + else + document_heading + end + end + + ## + # Render a value (or array of values) from a field + # + # @param [String] value or list of values to display + # @param [Blacklight::Solr::Configuration::Field] solr field configuration + # @return [String] + def render_field_value value=nil, field_config=nil + safe_values = Array(value).collect { |x| x.respond_to?(:force_encoding) ? x.force_encoding("UTF-8") : x } + + if field_config and field_config.itemprop + safe_values = safe_values.map { |x| content_tag :span, x, :itemprop => field_config.itemprop } + end + + safe_join(safe_values, (field_config.separator if field_config) || field_value_separator) + end + + ## + # Render the document index heading + # + # @param [Hash] opts (Deprecated) + # @option opts [Symbol] :label Render the given field from the document + # @option opts [Proc] :label Evaluate the given proc + # @option opts [String] :label Render the given string + # @param [Symbol, Proc, String] field Render the given field or evaluate the proc or render the given string + def render_document_index_label field, opts ={} + if field.is_a? Hash + Deprecation.warn DocumentPresenter, "Calling render_document_index_label with a hash is deprecated" + field = field[:label] + end + label = case field + when Symbol + @document[field] + when Proc + field.call(@document, opts) + when String + field + end + render_field_value label || @document.id + end + + ## + # Render the index field label for a document + # + # Allow an extention point where information in the document + # may drive the value of the field + # @param [String] field + # @param [Hash] opts + # @options opts [String] :value + def render_index_field_value field, options = {} + field_config = @configuration.index_fields[field] + value = options[:value] || get_field_values(field, field_config, options) + + render_field_value value, field_config + end + + ## + # Render the show field value for a document + # + # Allow an extention point where information in the document + # may drive the value of the field + # @param [String] field + # @param [Hash] options + # @options opts [String] :value + def render_document_show_field_value field, options={} + field_config = @configuration.show_fields[field] + value = options[:value] || get_field_values(field, field_config, options) + + render_field_value value, field_config + end + + ## + # Get the value for a document's field, and prepare to render it. + # - highlight_field + # - accessor + # - solr field + # + # Rendering: + # - helper_method + # - link_to_search + # TODO : maybe this should be merged with render_field_value, and the ugly signature + # simplified by pushing some of this logic into the "model" + # @param [SolrDocument] document + # @param [String] field name + # @param [Blacklight::Solr::Configuration::Field] solr field configuration + # @param [Hash] options additional options to pass to the rendering helpers + def get_field_values field, field_config, options = {} + # retrieving values + value = case + when (field_config and field_config.highlight) + # retrieve the document value from the highlighting response + @document.highlight_field(field_config.field).map(&:html_safe) if @document.has_highlight_field? field_config.field + when (field_config and field_config.accessor) + # implicit method call + if field_config.accessor === true + @document.send(field) + # arity-1 method call (include the field name in the call) + elsif !field_config.accessor.is_a?(Array) && @document.method(field_config.accessor).arity != 0 + @document.send(field_config.accessor, field) + # chained method calls + else + Array(field_config.accessor).inject(@document) do |result, method| + result.send(method) + end + end + when field_config + # regular document field + if field_config.default and field_config.default.is_a? Proc + @document.fetch(field_config.field, &field_config.default) + else + @document.fetch(field_config.field, field_config.default) + end + when field + @document[field] + end + + # rendering values + case + when (field_config and field_config.helper_method) + @controller.send(field_config.helper_method, options.merge(document: @document, field: field, config: field_config, value: value)) + when (field_config and field_config.link_to_search) + link_field = if field_config.link_to_search === true + field_config.key + else + field_config.link_to_search + end + + Array(value).map do |v| + @controller.link_to render_field_value(v, field_config), @controller.search_action_path(@controller.add_facet_params(link_field, v, {})) + end if field + else + value + end + end + + ## + # Default separator to use in #render_field_value + # + # @return [String] + def field_value_separator + ', ' + end + + end +end diff --git a/app/presenters/collection_presenter.rb b/app/presenters/collection_presenter.rb new file mode 100644 index 0000000..1aad9e8 --- /dev/null +++ b/app/presenters/collection_presenter.rb @@ -0,0 +1,10 @@ +class CollectionPresenter < CurationConcerns::CollectionPresenter + include ExhibitPresenter + + def spotlight_exhibit + @spotlight_exhibit ||= Spotlight::Exhibit + .where(exhibitable_id: @solr_document['id']) + .where(exhibitable_type: 'Collection') + .first + end +end diff --git a/app/presenters/concerns/exhibit_presenter.rb b/app/presenters/concerns/exhibit_presenter.rb new file mode 100644 index 0000000..953f746 --- /dev/null +++ b/app/presenters/concerns/exhibit_presenter.rb @@ -0,0 +1,19 @@ +module ExhibitPresenter + extend ActiveSupport::Concern + + def edit_exhibit_path + Spotlight::Engine.routes.url_helpers.edit_exhibit_path(spotlight_exhibit) if spotlight_exhibit + end + + def spotlight_exhibit_title + spotlight_exhibit.title if spotlight_exhibit + end + + def custom_metadata_fields + [] + end + + def spotlight_exhibit + raise "You should impliment this method when ExhibitPresenter is included" + end +end diff --git a/app/presenters/display_fields.rb b/app/presenters/display_fields.rb index 6fb6440..4556db6 100644 --- a/app/presenters/display_fields.rb +++ b/app/presenters/display_fields.rb @@ -2,10 +2,26 @@ module DisplayFields extend ActiveSupport::Concern included do - delegate :identifier, :series, :date_issued, :note, :extent, - :description_standard, :publication_place, :editor, :sponsor, - :funder, :researcher, :height, :width, :mime_type, :filename, - :representative_id, :thumbnail_id, to: :solr_document + delegate :identifier, + :custom_metadata_fields, + :date_issued, + :description_standard, + :editor, + :extent, + :filename, + :funder, + :height, + :mime_type, + :note, + :publication_place, + :representative_id, + :researcher, + :series, + :source, + :sponsor, + :thumbnail_id, + :width, + to: :solr_document end def permission_badge_class diff --git a/app/presenters/file_set_presenter.rb b/app/presenters/file_set_presenter.rb index 341ff0c..7ca6560 100644 --- a/app/presenters/file_set_presenter.rb +++ b/app/presenters/file_set_presenter.rb @@ -2,4 +2,6 @@ class FileSetPresenter < CurationConcerns::FileSetPresenter def permission_badge_class ::PermissionBadge end + + delegate :prevent_download, to: :solr_document end diff --git a/app/presenters/image_presenter.rb b/app/presenters/image_presenter.rb new file mode 100644 index 0000000..b176b43 --- /dev/null +++ b/app/presenters/image_presenter.rb @@ -0,0 +1,3 @@ +class ImagePresenter < WorkShowPresenter + include DisplayFields +end diff --git a/app/presenters/video_presenter.rb b/app/presenters/video_presenter.rb new file mode 100644 index 0000000..980fb83 --- /dev/null +++ b/app/presenters/video_presenter.rb @@ -0,0 +1,3 @@ +class VideoPresenter < WorkShowPresenter + include DisplayFields +end diff --git a/app/presenters/work_show_presenter.rb b/app/presenters/work_show_presenter.rb index 02f19c5..bb855cb 100644 --- a/app/presenters/work_show_presenter.rb +++ b/app/presenters/work_show_presenter.rb @@ -15,8 +15,20 @@ def attribute_to_html(field, options = {}) end end - private + def respond_to?(method) + has_native = super + return true if has_native + + # look for custom metadata + key = "#{method}_ssi" + return false unless solr_document.keys.include?(key) + self.class.send :define_method, method do + solr_document[key] + end + end + + private def admin_set_title ERB::Util.h(solr_document['admin_set_ssi']) end @@ -28,4 +40,5 @@ def admin_set_id def admin_set_path Rails.application.routes.url_helpers.admin_set_path(admin_set_id) end + end diff --git a/app/renderers/spotlight_exhibit_renderer.rb b/app/renderers/spotlight_exhibit_renderer.rb new file mode 100644 index 0000000..ea7f769 --- /dev/null +++ b/app/renderers/spotlight_exhibit_renderer.rb @@ -0,0 +1,5 @@ +class SpotlightExhibitRenderer < CurationConcerns::AttributeRenderer + def li_value(value) + link_to ERB::Util.h(value), options.fetch(:link_path) + end +end diff --git a/app/search_builders/search_builder.rb b/app/search_builders/search_builder.rb index 9d59612..824df35 100644 --- a/app/search_builders/search_builder.rb +++ b/app/search_builders/search_builder.rb @@ -1,4 +1,6 @@ class SearchBuilder < CurationConcerns::SearchBuilder + include Spotlight::Catalog::AccessControlsEnforcement::SearchBuilder + def collection_clauses return [] if blacklight_params.key?(:f) && Array(blacklight_params[:f][:generic_type_sim]).include?('Work') ["{!terms f=has_model_ssim}#{::Collection.to_class_uri},#{AdminSet.to_class_uri}"] diff --git a/app/services/rights_service.rb b/app/services/rights_service.rb new file mode 100644 index 0000000..58cd100 --- /dev/null +++ b/app/services/rights_service.rb @@ -0,0 +1,15 @@ +module RightsService + mattr_accessor :authority + self.authority = Qa::Authorities::Local.subauthority_for('rights') + + def self.select_options + authority.all.map do |element| + [element[:label], element[:id]] + end + end + + def self.label(id) + record = authority.find(id) + record.fetch(:label) if record + end +end diff --git a/app/views/_user_util_links.html.erb b/app/views/_user_util_links.html.erb new file mode 100644 index 0000000..32bde31 --- /dev/null +++ b/app/views/_user_util_links.html.erb @@ -0,0 +1,30 @@ +<div class="navbar-right"> + + <ul class="nav navbar-nav"> + <%= render_nav_actions do |config, action|%> + <li><%= action %></li> + <% end %> + </ul> + + <ul class="nav navbar-nav"> + <% if current_user %> + <li class="dropdown"> + <a href="#" class="dropdown-toggle" data-toggle="dropdown"><%=current_user%> <b class="caret"></b></a> + <ul class="dropdown-menu"> + <li> + <%= link_to t('spotlight.header_links.logout'), main_app.destroy_user_session_path %> + </li> + </ul> + </li> + <% else %> + <li> + <%= link_to t('spotlight.header_links.login'), main_app.new_user_session_path %> + </li> + <% end %> + <% if current_exhibit and show_contact_form? %> + <li> + <%= link_to t('spotlight.header_links.contact'), spotlight.new_exhibit_contact_form_path(current_exhibit), data: {behavior: 'contact-link', target: 'report-problem-form' } %> + </li> + <% end %> + </ul> +</div> diff --git a/app/views/admin_sets/show.html.erb b/app/views/admin_sets/show.html.erb index 0a80d97..476fb13 100644 --- a/app/views/admin_sets/show.html.erb +++ b/app/views/admin_sets/show.html.erb @@ -1,7 +1,28 @@ <h2><%= @presenter.title %></h2> - <%= render_thumbnail_tag @presenter.solr_document %> + +<h2 class="non lower">Actions</h2> +<div class='btn-group' role='group'> + <% if can? :edit, @presenter %> + <%= link_to "Edit Metadata", + [:edit, @presenter], + class: 'btn btn-default' %> + <% end %> + + <%= link_to "View Members", + catalog_index_path(add_facet_params('admin_set_ssi', @presenter.title)), + class: 'btn btn-default' %> + + <% if can? :edit, @presenter %> + <%= render 'shared/download_actions' %> + + <%= link_to "Delete Collection", + [:confirm_delete, @presenter], + class: 'btn btn-default pull-right' %> + <% end %> +</div> + <table class="table table-striped <%= dom_class(@presenter) %> attributes"> <caption class="table-heading"><h2>Attributes</h2></caption> <thead> @@ -24,6 +45,13 @@ <%= link_to "Edit Metadata", [:edit, @presenter], class: 'btn btn-default' %> + + <% if @presenter.edit_exhibit_path %> + <%= link_to "Customize", + @presenter.edit_exhibit_path, + class: 'btn btn-default' %> + <% end %> + <% end %> <%= link_to "View Members", diff --git a/app/views/advanced/_advanced_search_facets.html.erb b/app/views/advanced/_advanced_search_facets.html.erb new file mode 100644 index 0000000..2eb47f0 --- /dev/null +++ b/app/views/advanced/_advanced_search_facets.html.erb @@ -0,0 +1,16 @@ +<%# used to render facets with checkboxes on advanced search form, + we pretty much just use the built-in blacklight render_facet_partials + helper. + + But we've provided a local override of the _facet_limit + partial in our own `views/advanced/_facet_limit.html.erb`, + that is written to include checkboxes for form selection. + + This is the default display of facets, but you can + also choose to use _advanced_search_facets_as_select, + for a chosen.js-compatible multi-select. +%> + +<div class="advanced-facet-limits panel-group"> + <%= render_facet_partials facet_field_names %> +</div> diff --git a/app/views/advanced/_advanced_search_facets_as_select.html.erb b/app/views/advanced/_advanced_search_facets_as_select.html.erb new file mode 100644 index 0000000..6c3380e --- /dev/null +++ b/app/views/advanced/_advanced_search_facets_as_select.html.erb @@ -0,0 +1,30 @@ +<%# alternate version of facets on form that renders using multi-select. + Has to copy and paste more code from blacklight than default, making + it somewhat more fragile. + + Logic taken from facets_helper_behavior.rb, #render_facet_partials and + #render_facet_limit. +%> + +<% facets_from_request(facet_field_names).each do |display_facet| %> + <% if should_render_facet?(display_facet) %> + <div class="form-group advanced-search-facet"> + <%= label_tag display_facet.name.parameterize, :class => "col-sm-3 control-label" do %> + <%= facet_field_label(display_facet.name) %> + <% end %> + + <div class="col-sm-9"> + <%= content_tag(:select, :multiple => true, + :name => "f_inclusive[#{display_facet.name}][]", + :id => display_facet.name.parameterize, + :class => "form-control advanced-search-facet-select") do %> + <% display_facet.items.each do |facet_item| %> + <%= content_tag :option, :value => facet_item.value, :selected => facet_value_checked?(display_facet.name, facet_item.value) do %> + <%= facet_item.label %>  (<%= number_with_delimiter facet_item.hits %>) + <% end %> + <% end %> + <% end %> + </div> + </div> + <% end %> +<% end %> \ No newline at end of file diff --git a/app/views/advanced/_advanced_search_fields.html.erb b/app/views/advanced/_advanced_search_fields.html.erb new file mode 100644 index 0000000..a79206e --- /dev/null +++ b/app/views/advanced/_advanced_search_fields.html.erb @@ -0,0 +1,12 @@ +<%- search_fields_for_advanced_search.each do |key, field_def| -%> + <div class="form-group advanced-search-field"> + <%= label_tag key, "#{field_def.label }", :class => "col-sm-3 control-label" %> + <div class="col-sm-9"> + <% if key == "human_readable_type"%> + <%= select_tag key, raw("<option>Video</option><option>Text</option><option>Audio</option><option>Image</option><option>Personal Collection</option><option>Administrative Collection</option><option>Document</option>"), :include_blank => true, :class => 'form-control' %> + <% else %> + <%= text_field_tag key, label_tag_default_for(key), :class => 'form-control' %> + <% end %> + </div> + </div> +<%- end -%> diff --git a/app/views/advanced/_advanced_search_form.html.erb b/app/views/advanced/_advanced_search_form.html.erb new file mode 100644 index 0000000..572b653 --- /dev/null +++ b/app/views/advanced/_advanced_search_form.html.erb @@ -0,0 +1,47 @@ + <% unless (search_context_str = render_search_to_s( advanced_search_context)).blank? %> + <div class="constraints well search_history"> + <h4><%= t 'blacklight_advanced_search.form.search_context' %></h4> + <%= search_context_str %> + </div> + <% end %> + +<%= form_tag catalog_index_path, :class => 'advanced form-horizontal', :method => :get do %> + + <%= render_hash_as_hidden_fields(params_for_search(advanced_search_context, {})) %> + + <div class="input-criteria"> + <div class="col-sm-4"> + <div class="limit-criteria"> + <h3 class="limit-criteria-heading">  </h3> + + <div id="advanced_search_facets" class="limit_input"> + <% if blacklight_config.try(:advanced_search).try {|h| h[:form_facet_partial] } %> + <%= render blacklight_config.advanced_search[:form_facet_partial] %> + <% else %> + <%= render 'advanced_search_facets' %> + <% end %> + </div> + </div> + </div> + <div class="col-sm-8"> + <div class="query-criteria"> + <h3 class="query-criteria-heading"> + <%= t('blacklight_advanced_search.form.query_criteria_heading_html', :select_menu => select_menu_for_field_operator ) %> + </h3> + + <div id="advanced_search"> + <%= render 'advanced/advanced_search_fields' %> + </div> + </div> + </div> + + </div> + <hr> + <div class="row "> + + <div class="col-sm-12 sort-submit-buttons clearfix"> + <%= render 'advanced_search_submit_btns' %> + </div> + + </div> +<% end %> diff --git a/app/views/advanced/_advanced_search_help.html.erb b/app/views/advanced/_advanced_search_help.html.erb new file mode 100644 index 0000000..64c1349 --- /dev/null +++ b/app/views/advanced/_advanced_search_help.html.erb @@ -0,0 +1,24 @@ +<div class="advanced-help-panel"> + <h4>Search tips</h4> + + <ul class="advanced-help"> + <li>Select "match all" to require all fields. + </li> + + <li>Select "match any" to find at least one field. + </li> + + <li>Combine keywords and attributes to find specific items. + </li> + + <li>Use quotation marks to search as a phrase. + + <li>Use "+" before a term to make it required. (Otherwise results matching only some of your terms may be included).</li> + + <li>Use "-" before a word or phrase to exclude. + + <li>Use "OR", "AND", and "NOT" to create complex boolean logic. You can use parentheses in your complex expressions. </li> + <li>Truncation and wildcards are not supported - word-stemming is done automatically.</li> + </ul> +</div> + \ No newline at end of file diff --git a/app/views/advanced/_advanced_search_submit_btns.html.erb b/app/views/advanced/_advanced_search_submit_btns.html.erb new file mode 100644 index 0000000..a622865 --- /dev/null +++ b/app/views/advanced/_advanced_search_submit_btns.html.erb @@ -0,0 +1,15 @@ + <div class="row"> + + <div class="sort-buttons col-sm-6"> + <%= label_tag(:sort, t('blacklight_advanced_search.form.sort_label'), :class => "control-label") %> + + <%= select_tag(:sort, options_for_select(sort_fields, h(params[:sort])), :class => "form-control sort-select") %> + <%= hidden_field_tag(:search_field, blacklight_config.advanced_search[:url_key]) %> + </div> + + <div class="submit-buttons col-sm-6 text-right"> + <%= link_to t('blacklight_advanced_search.form.start_over'), advanced_search_path, :class =>"btn btn-default advanced-search-start-over" %> + + <%= submit_tag t('blacklight_advanced_search.form.search_btn'), :class=>'btn btn-primary advanced-search-submit', :id => "advanced-search-submit" %> + </div> + </div> \ No newline at end of file diff --git a/app/views/advanced/_facet_limit.html.erb b/app/views/advanced/_facet_limit.html.erb new file mode 100644 index 0000000..1834a87 --- /dev/null +++ b/app/views/advanced/_facet_limit.html.erb @@ -0,0 +1,15 @@ +<ul class="facet-values list-unstyled blacklight-advanced-facet-select"> + <% display_facet.items.each do |item| -%> + <li> + <span class="facet-checkbox"> + <%= check_box_tag "f_inclusive[#{solr_field}][]", item.value.to_sym, facet_value_checked?(solr_field, item.value), :id => "f_inclusive_#{solr_field}_#{item.value.parameterize}"%> + </span> + + <span class="label-and-count"> + <%= label_tag "f_inclusive_#{solr_field}_#{item.value.parameterize}" do %> + <%= render_facet_value(solr_field, item, :suppress_link => true) %> + <% end %> + <span> + </li> + <% end -%> +</ul> \ No newline at end of file diff --git a/app/views/advanced/index.html.erb b/app/views/advanced/index.html.erb new file mode 100644 index 0000000..891c7d0 --- /dev/null +++ b/app/views/advanced/index.html.erb @@ -0,0 +1,19 @@ +<% @page_title = "More Search Options - #{application_name}" %> + +<div class="advanced-search-form col-sm-12"> + + <h1 class="advanced page-header"> + <%= t('blacklight_advanced_search.form.title') %> + <%= link_to t('blacklight_advanced_search.form.start_over'), advanced_search_path, :class =>"btn btn-default pull-right advanced-search-start-over" %> + </h1> + + <div class="row"> + <div class="col-md-9"> + <%= render 'advanced_search_form' %> + </div> + <div class="col-md-3"> + <%= render "advanced_search_help" %> + </div> + </div> + +</div> \ No newline at end of file diff --git a/app/views/catalog/_index_default.html.erb b/app/views/catalog/_index_default.html.erb new file mode 100644 index 0000000..155cb74 --- /dev/null +++ b/app/views/catalog/_index_default.html.erb @@ -0,0 +1,19 @@ +<%# default partial to display solr document fields in catalog index view -%> +<dl class="document-metadata dl-horizontal dl-invert"> + + <% index_fields(document).each do |field_name, field| -%> + <% if should_render_index_field? document, field %> + <% field_value = render_index_field_value(document, field: field_name) %> + <dt class="blacklight-<%= field_name.parameterize %>"><%= render_index_field_label document, field: field_name %></dt> + <dd class="<%= 'truncate' if field_value.length > 120 %> blacklight-<%= field_name.parameterize %>"> + <% if field_value.length > 120 %> + <%= link_to_document document, "...more" %> + <% end %> + <div class='inner-value'> + <%= field_value %> + </div> + </dd> + <% end -%> + <% end -%> + +</dl> diff --git a/app/views/catalog/_index_header_list_default.html.erb b/app/views/catalog/_index_header_list_default.html.erb new file mode 100644 index 0000000..eb32e9c --- /dev/null +++ b/app/views/catalog/_index_header_list_default.html.erb @@ -0,0 +1,7 @@ + <% # header bar for doc items in index view -%> + <div class="documentHeader"> + <h4 class="index_title"> + <% counter = document_counter_with_offset(document_counter) %> + <%= link_to_document document, document_show_link_field(document), counter: counter %> + </h4> + </div> diff --git a/app/views/collections/_form.html.erb b/app/views/collections/_form.html.erb new file mode 100644 index 0000000..aba068b --- /dev/null +++ b/app/views/collections/_form.html.erb @@ -0,0 +1,26 @@ +<%= simple_form_for [collections, @form] do |f| %> + + <% if f.error_notification %> + <div class="alert alert-danger fade in"> + <strong>Wait don't go!</strong> There was a problem with your submission. Please review the errors below: + <a class="close" data-dismiss="alert" href="#">×</a> + </div> + <% end %> + + <%= render 'form_descriptive_fields', f: f %> + <%= render 'form_permission', f: f %> + <%= render "form_rights", f: f %> + <%= render "curation_concerns/base/form_media", f: f %> + + <div class="row"> + <div class="col-md-12 form-actions"> + <%= f.submit class: 'btn btn-primary require-contributor-agreement' %> + <% if action_name == 'new' %> + <%= link_to 'Cancel', main_app.root_path, class: 'btn btn-link' %> + <% else %> + <%= link_to 'Cancel', collections.collection_path(@form), class: 'btn btn-link' %> + <% end %> + </div> + </div> + +<% end %> diff --git a/app/views/collections/_form_rights.html.erb b/app/views/collections/_form_rights.html.erb new file mode 100644 index 0000000..79b0175 --- /dev/null +++ b/app/views/collections/_form_rights.html.erb @@ -0,0 +1,19 @@ +<div class="row with-headroom"> + <div class="col-md-12"> + <fieldset id="set-license"> + <legend>Choose a License for your Collection</legend> + <p> + What do you want others to be able to do with your work? + </p> + <p> + <a href="http://creativecommons.org/licenses/" target="_blank">Here's some help</a> if you don't know which license to choose. + </p> + <div class="form-group select optional collection_rights"> + <label class="select optional control-label" for="collection_rights">Rights</label> + <div> + <%= select_tag "collection[rights][]", options_for_select(RightsService.select_options, f.object.rights), class: 'form-control' %> + </div> + </div> + </fieldset> + </div> +</div> diff --git a/app/views/collections/_show_actions.html.erb b/app/views/collections/_show_actions.html.erb new file mode 100644 index 0000000..86ffe75 --- /dev/null +++ b/app/views/collections/_show_actions.html.erb @@ -0,0 +1,14 @@ +<% if can? :edit, @presenter.id %> + <h2 class="non lower">Actions</h2> + <div class="btn-group" role="group"> + <%= link_to "Edit", collections.edit_collection_path, class: 'btn btn-default' %>    + <% if @presenter.edit_exhibit_path %> + <%= link_to "Customize", + @presenter.edit_exhibit_path, + class: 'btn btn-default' %>   + <% end %> + <%= link_to "Add files from your dashboard", search_path_for_my_works, class: 'btn btn-default' %>   + <%= render 'shared/download_actions' %> + </div> +<%end %> + diff --git a/app/views/curation_concerns/base/_attribute_partial.html.erb b/app/views/curation_concerns/base/_attribute_partial.html.erb new file mode 100644 index 0000000..adf5103 --- /dev/null +++ b/app/views/curation_concerns/base/_attribute_partial.html.erb @@ -0,0 +1,38 @@ +<% is_not_faceted = false unless defined?(is_not_faceted) %> +<% if value.present? %> + <tr> + <th><%= name %></th> + <td> + <ul class="tabular"> + <li class="attribute <%= name %>"> + + <% if value.is_a?(Array) %> + + <% if is_not_faceted %> + <% value.each do |e| %> + <% if e == '1' %> + <span><%= sanitize('true', tags: %w[a], attributes: %w[href target]) %></span> + <% elsif e == '0' %> + <span><%= sanitize('false', tags: %w[a], attributes: %w[href target]) %></span> + <% else %> + <span><%= sanitize(e, tags: %w[a], attributes: %w[href target]) %></span> + <% end %> + <% end %> + <% else %> + <% value.each do |e| %> + <a href="<%= faceted_search_path(field, e) %>"><%= e %></a> + <% end %> + <% end %> + + <% else %> + <% if is_not_faceted %> + <span><%= sanitize(value, tags: %w[a], attributes: %w[href target]) %></span> + <% else %> + <a href="<%= faceted_search_path(field, value) %>"><%= value %></a> + <% end %> + <% end %> + </li> + </ul> + </td> + </tr> +<% end %> diff --git a/app/views/curation_concerns/base/_attribute_rows.html.erb b/app/views/curation_concerns/base/_attribute_rows.html.erb index 4e4f2cc..886b60f 100644 --- a/app/views/curation_concerns/base/_attribute_rows.html.erb +++ b/app/views/curation_concerns/base/_attribute_rows.html.erb @@ -1,3 +1,4 @@ + <%= @presenter.attribute_to_html(:description) %> <%= @presenter.attribute_to_html(:subject, catalog_search_link: true) %> <%= @presenter.attribute_to_html(:creator, catalog_search_link: true ) %> @@ -7,6 +8,7 @@ <%= @presenter.attribute_to_html(:funder) %> <%= @presenter.attribute_to_html(:researcher) %> <%= @presenter.attribute_to_html(:publisher) %> + <%= @presenter.attribute_to_html(:source) %> <%= @presenter.attribute_to_html(:language) %> <%= @presenter.attribute_to_html(:identifier) %> <%= @presenter.attribute_to_html(:series) %> @@ -20,4 +22,8 @@ <%= @presenter.attribute_to_html(:mime_type) %> <%= @presenter.attribute_to_html(:filename) %> <%= @presenter.attribute_to_html(:admin_set) %> - + <% if @presenter.respond_to? :custom_metadata_fields %> + <% @presenter.custom_metadata_fields.to_a.uniq.each do |field| %> + <%= @presenter.attribute_to_html(field.intern) %> + <% end %> + <% end %> diff --git a/app/views/curation_concerns/base/_form_additional_information.html.erb b/app/views/curation_concerns/base/_form_additional_information.html.erb index 3d674ac..b8c86c0 100644 --- a/app/views/curation_concerns/base/_form_additional_information.html.erb +++ b/app/views/curation_concerns/base/_form_additional_information.html.erb @@ -14,5 +14,11 @@ <%= f.input :note, as: :multi_value, input_html: { class: 'form-control' } %> <%= f.input :description_standard, as: :multi_value, input_html: { class: 'form-control' } %> <%= f.input :publication_place, as: :multi_value, input_html: { class: 'form-control' } %> - <%= f.input :date_issued, wrapper: :inline, input_html: { value: f.object.date_issued, class: 'datepicker' } %> + <%= f.input :date_issued, input_html: { value: f.object.date_issued, class: 'datepicker' } %> + <%= f.fields_for :custom_metadatas, f.object.custom_metadatas.to_a do |custom_metadata_fields| %> + <%= custom_metadata_fields.hidden_field :id %> + <%= custom_metadata_fields.hidden_field :title %> + <%= custom_metadata_fields.input :value, label: custom_metadata_fields.object.title.titleize, input_html: { class: 'form-control'} %> + <% end %> </fieldset> + diff --git a/app/views/curation_concerns/base/_related_files.html.erb b/app/views/curation_concerns/base/_related_files.html.erb new file mode 100644 index 0000000..a578470 --- /dev/null +++ b/app/views/curation_concerns/base/_related_files.html.erb @@ -0,0 +1,24 @@ +<% if presenter.file_presenters.present? %> + <div class="panel panel-default related_files"> + <div class="panel-heading"> + <h2>Files</h2> + </div> + <table class="table table-striped table-responsive"> + <thead> + <tr> + <th>File</th> + <th>Filename</th> + <th>Date Uploaded</th> + <th>Visibility</th> + <th>Actions</th> + </tr> + </thead> + <tbody> + <%= render presenter.file_presenters %> + </tbody> + </table> + </div> +<% elsif can? :edit, presenter.id %> + <h2>Files</h2> + <p class="center"><em>This <%= presenter.human_readable_type %> has no files associated with it. You can add one using the "Attach a File" button below.</em></p> +<% end %> diff --git a/app/views/curation_concerns/file_sets/_actions.html.erb b/app/views/curation_concerns/file_sets/_actions.html.erb new file mode 100644 index 0000000..7ac4280 --- /dev/null +++ b/app/views/curation_concerns/file_sets/_actions.html.erb @@ -0,0 +1,18 @@ +<% if can?(:edit, file_set.id) %> + <%= link_to( 'Edit', edit_polymorphic_path([main_app, file_set]), + { class: 'btn btn-default', title: "Edit #{file_set}" }) %> + <%= link_to( 'Rollback', main_app.versions_curation_concerns_file_set_path(file_set), + { class: 'btn btn-default', title: "Rollback to previous version" }) %> +<% end %> +<% if can?(:destroy, file_set.id) %> + <%= link_to( 'Delete', polymorphic_path([main_app, file_set]), + class: 'btn btn-default', method: :delete, title: "Delete #{file_set}", + data: {confirm: "Deleting #{file_set} from #{t('curation_concerns.product_name')} is permanent. Click OK to delete this from #{t('curation_concerns.product_name')}, or Cancel to cancel this operation"} + )%> +<% end %> +<% if can?(:read, file_set.id) %> + <% unless file_set.prevent_download.present? && file_set.prevent_download == true %> + <%= link_to 'Download', main_app.download_path(file_set), + class: 'btn btn-default', title: "Download #{file_set.to_s.inspect}", target: "_blank" %> + <% end %> +<% end %> diff --git a/app/views/curation_concerns/file_sets/_form.html.erb b/app/views/curation_concerns/file_sets/_form.html.erb new file mode 100644 index 0000000..060ae54 --- /dev/null +++ b/app/views/curation_concerns/file_sets/_form.html.erb @@ -0,0 +1,39 @@ +<%= simple_form_for [main_app, curation_concern], + html: { multipart: true }, + wrapper_mappings: { multifile: :horizontal_file_input } do |f| %> + <div class="row"> + <div class="col-md-6"> + <fieldset class="required"> + <legend>Your File’s Title</legend> + <span class="control-label"> + <%= label_tag 'file_set[title][]', 'Title', class: "string optional" %> + </span> + <%= text_field_tag 'file_set[title][]', curation_concern.title.first, class: 'form-control' %> + </fieldset> + <fieldset class="required"> + <legend> + Attach Your File + </legend> + <%= f.input :files, as: :multifile %><br /> + + <label> + <%= f.check_box :prevent_download, { input_html: { class: 'form-control ' }}, 'true', 'false' %>  Prevent downloading for this file + </label> + </fieldset> + </div> + + <div class="col-md-6"> + <%= render "form_permission", f: f %> + </div> + </div> + + <div class="row"> + <div class="col-md-12 form-actions"> + <%= f.submit( + (curation_concern.persisted? ? "Update Attached File" : %(Attach to #{parent.human_readable_type})), + class: 'btn btn-primary' + ) %> + <%= link_to 'Cancel', parent_path(parent), class: 'btn btn-link' %> + </div> + </div> +<% end %> diff --git a/app/views/curation_concerns/file_sets/media_display/_default.html.erb b/app/views/curation_concerns/file_sets/media_display/_default.html.erb new file mode 100644 index 0000000..0a43453 --- /dev/null +++ b/app/views/curation_concerns/file_sets/media_display/_default.html.erb @@ -0,0 +1,8 @@ +<% unless file_set.prevent_download.present? && file_set.prevent_download == true %> + <%= link_to main_app.download_path(file_set), target: "_new", title: "Download the document" do %> + <figure> + <%= image_tag "default.png", alt: "No preview available", class: "img-responsive" %> + <figcaption>Download the document</figcaption> + </figure> + <% end %> +<% end %> \ No newline at end of file diff --git a/app/views/curation_concerns/file_sets/media_display/_video.html.erb b/app/views/curation_concerns/file_sets/media_display/_video.html.erb index 921bf0c..305c59d 100644 --- a/app/views/curation_concerns/file_sets/media_display/_video.html.erb +++ b/app/views/curation_concerns/file_sets/media_display/_video.html.erb @@ -1,5 +1,5 @@ <% if defined?(transcript_id) && transcript_id %> - <div id="player" class="col-md-6"> + <div id="player" class="col-md-7"> <video width="480" height="360" preload="auto" data-able-player data-transcript-div="transcript" data-use-transcript-button="false" data-translation-path="/" data-include-transcript="true"> <source src="<%= main_app.download_path(file_set, file: 'webm') %>" type="video/webm" /> <source src="<%= main_app.download_path(file_set, file: 'mp4') %>" type="video/mp4" /> @@ -7,7 +7,7 @@ Your browser does not support the video tag. </video> </div> - <div id="transcript" class="col-md-6"></div> + <div id="transcript" class="col-md-5"></div> <% else %> <video width="480" height="360" controls="controls" class="video-js vjs-default-skin" data-setup="{}" preload="auto"> <source src="<%= main_app.download_path(file_set, file: 'webm') %>" type="video/webm" /> diff --git a/app/views/curation_concerns/file_sets/show.html.erb b/app/views/curation_concerns/file_sets/show.html.erb new file mode 100644 index 0000000..d257dac --- /dev/null +++ b/app/views/curation_concerns/file_sets/show.html.erb @@ -0,0 +1,18 @@ +<% provide :page_title, @presenter.page_title %> +<% provide :page_header do %> + <h1>File Details <small><%= @presenter.title %></small></h1> +<% end %> + +<%= media_display @presenter %> +<%= render "attributes", curation_concern: @presenter %> + + <div class="form-actions"> + <% unless @presenter.prevent_download.present? && @presenter.prevent_download == true %> + <%= link_to "Download this File", main_app.download_path(@presenter), class: 'btn btn-default' %> + <% end %> + <% if can? :edit, @presenter.id %> + <%= link_to "Edit this File", edit_polymorphic_path([main_app, @presenter]), class: 'btn btn-default' %> + <% end %> + + <%= link_to "Back to #{parent.human_readable_type}", parent_path(parent), class: 'btn btn-default' %> + </div> diff --git a/app/views/curation_concerns/texts/_tei_viewer.html.erb b/app/views/curation_concerns/texts/_tei_viewer.html.erb index 4ae37c4..5faf77a 100644 --- a/app/views/curation_concerns/texts/_tei_viewer.html.erb +++ b/app/views/curation_concerns/texts/_tei_viewer.html.erb @@ -1,11 +1,11 @@ <script type="text/ng-template" id="tei_viewer.html"> <form role="form" class="form-inline"> - <div class="row"> + <div class="row no-neg-margin"> <label for="page-number">Page: </label> <input type="text" size="3" ng-model="formNumber" id="page-number" ng-model="currentPage" class="form-control" ng-blur="textUpdated()"> <button class="btn btn-default" ng-click="previousPage()"><i class="glyphicon glyphicon-step-backward"></i><span class="sr-only">Previous Page</span></button> <button class="btn btn-default" ng-click="nextPage()"><i class="glyphicon glyphicon-step-forward"></i><span class="sr-only">Next Page</span></button> - + <label for="display" id="display-label">Display: </label> <select name="display" class="form-control" ng-model="display"> <option value="both">Images and Text</option> @@ -15,9 +15,9 @@ </form> <div id="tei-container" class="row"> <ol id="pages" class="{{display}}"> - <li ng-repeat="page in pages" class="page"> - <div ng-bind-html="to_trusted(page.image)" class="image"></div> - <div ng-bind-html="to_trusted(page.html)" class="text"></div> + <li ng-repeat="page in pages" class="page"> + <div ng-if="page.image" ng-bind-html="to_trusted(page.image)" ng-class="page.html ? 'image col-sm-6' : 'image col-sm-12' "></div> + <div ng-if="page.html" ng-bind-html="to_trusted(page.html)" ng-class="page.image ? 'text col-sm-6' : 'text col-sm-12' "></div> </li> </ol> </div> diff --git a/app/views/layouts/_google_analytics.html.erb b/app/views/layouts/_google_analytics.html.erb new file mode 100644 index 0000000..6fd1d2c --- /dev/null +++ b/app/views/layouts/_google_analytics.html.erb @@ -0,0 +1,7 @@ +<!-- Google Tag Manager --> +<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': +new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], +j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= +'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); +})(window,document,'script','dataLayer','GTM-MP5DXX7');</script> +<!-- End Google Tag Manager --> \ No newline at end of file diff --git a/app/views/layouts/_google_analytics_body.html.erb b/app/views/layouts/_google_analytics_body.html.erb new file mode 100644 index 0000000..3577f0b --- /dev/null +++ b/app/views/layouts/_google_analytics_body.html.erb @@ -0,0 +1,4 @@ +<!-- Google Tag Manager (noscript) --> + <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-MP5DXX7" + height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> + <!-- End Google Tag Manager (noscript) --> \ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index a123b41..7f2650b 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,12 +1,15 @@ <!DOCTYPE html> <html> <head> + <%= render :partial => 'layouts/google_analytics' if Rails.env == 'production' %> + <title>Goldenseal <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> <%= csrf_meta_tags %> + <%= render :partial => 'layouts/google_analytics_body' if Rails.env == 'production' %> <%= yield %> diff --git a/app/views/layouts/boilerplate.html.erb b/app/views/layouts/boilerplate.html.erb new file mode 100644 index 0000000..1dd51a4 --- /dev/null +++ b/app/views/layouts/boilerplate.html.erb @@ -0,0 +1,24 @@ + + + + + + + <%= render :partial => 'layouts/google_analytics' if Rails.env == 'production' %> + + + + <%= content_for?(:page_title) ? yield(:page_title) : default_page_title %> + + <%= csrf_meta_tag %> + <%= stylesheet_link_tag 'application' %> + <%= javascript_include_tag 'application' %> + + + + <%= render :partial => 'layouts/google_analytics_body' if Rails.env == 'production' %> + <%= content_for?(:body) ? yield(:body) : yield %> + + <%= render 'shared/ajax_modal' %> + + diff --git a/app/views/layouts/spotlight/spotlight.html.erb b/app/views/layouts/spotlight/spotlight.html.erb new file mode 100644 index 0000000..39f5943 --- /dev/null +++ b/app/views/layouts/spotlight/spotlight.html.erb @@ -0,0 +1,49 @@ + + + <%= render :partial => 'layouts/google_analytics' if Rails.env == 'production' %> + + + + + + + + + + + <%= h(@page_title || application_name) %> + + <%= favicon_link_tag 'favicon.ico' %> + <%= stylesheet_link_tag "application" %> + <%= javascript_include_tag "application" %> + <%= csrf_meta_tags %> + <%= content_for(:head) %> + <%= description %> + <%= twitter_card %> + <%= opengraph %> + + + + + + + <%= render :partial => 'layouts/google_analytics_body' if Rails.env == 'production' %> + <%= render partial: 'shared/header' %> + <%= render partial: 'shared/masthead' %> + <%= render partial: 'shared/ajax_modal' %> + +
+ <%= render partial: '/flash_msg', layout: 'shared/flash_messages' %> + +
+ <%= content_for?(:content) ? yield(:content) : yield %> +
+
+ + <%= render :partial => 'shared/footer' %> + + diff --git a/app/views/rights/_form.html.erb b/app/views/rights/_form.html.erb new file mode 100644 index 0000000..909b26d --- /dev/null +++ b/app/views/rights/_form.html.erb @@ -0,0 +1,30 @@ +<% url = @right.id.nil? ? rights_path : right_path(@right) %> +<%= simple_form_for(@right, url: url, html: {class: "form-horizontal"}) do |f| %> + <% if @right.errors.any? %> +
+

<%= pluralize(@right.errors.count, "error") %> prohibited this right from being saved:

+ +
+ <% end %> + +
+ <%= f.label :label, "Rights Statement Label"%> +
+
+ <%= f.input :label, label: false %> +
+
+ <%= f.label :uri, "URI" %> +
+
+ <%= f.input :uri, label: false, disabled: !f.object.uri_editable? %> +
+
+ <%= f.submit "Save", :class => 'btn btn-primary' %>  + <%= link_to 'Back', rights_path, :class => 'btn btn-default' %> +
+<% end %> diff --git a/app/views/rights/edit.html.erb b/app/views/rights/edit.html.erb new file mode 100644 index 0000000..3bc2261 --- /dev/null +++ b/app/views/rights/edit.html.erb @@ -0,0 +1,3 @@ +

Edit Rights Statement

+ +<%= render 'form' %> diff --git a/app/views/rights/index.html.erb b/app/views/rights/index.html.erb new file mode 100644 index 0000000..d010ea5 --- /dev/null +++ b/app/views/rights/index.html.erb @@ -0,0 +1,26 @@ +

Rights Statements

+ + + + + + + + + + + + <% @rights.each do |right| %> + + + + + + + <% end %> + +
Rights Statement LabelURI
<%= right.label %><%= right.uri %><%= link_to 'Edit', edit_right_path(right), :class => "btn btn-xs btn-primary" %><%= link_to 'Destroy', right_path(right), method: :delete, data: { confirm: 'Are you sure?' }, :class => "btn btn-xs btn-default" %>
+ +
+ +<%= link_to 'Add New Rights Statement', new_right_path, :class => 'btn btn-primary' %> diff --git a/app/views/rights/index.json.jbuilder b/app/views/rights/index.json.jbuilder new file mode 100644 index 0000000..2d5402a --- /dev/null +++ b/app/views/rights/index.json.jbuilder @@ -0,0 +1,4 @@ +json.array!(@rights) do |right| + json.extract! right, :id + json.url right_url(right, format: :json) +end diff --git a/app/views/rights/new.html.erb b/app/views/rights/new.html.erb new file mode 100644 index 0000000..2396140 --- /dev/null +++ b/app/views/rights/new.html.erb @@ -0,0 +1,3 @@ +

New Rights Statement

+ +<%= render 'form' %> diff --git a/app/views/rights/show.json.jbuilder b/app/views/rights/show.json.jbuilder new file mode 100644 index 0000000..92a20ad --- /dev/null +++ b/app/views/rights/show.json.jbuilder @@ -0,0 +1 @@ +json.extract! @right, :id, :created_at, :updated_at diff --git a/app/views/shared/_brand_bar.html.erb b/app/views/shared/_brand_bar.html.erb new file mode 100644 index 0000000..ff144e3 --- /dev/null +++ b/app/views/shared/_brand_bar.html.erb @@ -0,0 +1,20 @@ +
+ +
diff --git a/app/views/shared/_download_actions.html.erb b/app/views/shared/_download_actions.html.erb new file mode 100644 index 0000000..a2e1cd9 --- /dev/null +++ b/app/views/shared/_download_actions.html.erb @@ -0,0 +1,34 @@ + + + + diff --git a/app/views/shared/_footer.html.erb b/app/views/shared/_footer.html.erb new file mode 100644 index 0000000..3852fde --- /dev/null +++ b/app/views/shared/_footer.html.erb @@ -0,0 +1,71 @@ + diff --git a/app/views/shared/_header.html.erb b/app/views/shared/_header.html.erb new file mode 100644 index 0000000..1b8ceee --- /dev/null +++ b/app/views/shared/_header.html.erb @@ -0,0 +1,13 @@ + diff --git a/app/views/shared/_masthead.html.erb b/app/views/shared/_masthead.html.erb new file mode 100644 index 0000000..898324e --- /dev/null +++ b/app/views/shared/_masthead.html.erb @@ -0,0 +1,32 @@ +<%= render 'spotlight/shared/report_a_problem' if show_contact_form? %> + +
+ <% if current_masthead %> + + + <% end %> + + <%# <%= render 'shared/exhibit_navbar' if current_exhibit && resource_masthead? %> + +
+
+ <% if content_for? :masthead %> + <%= content_for :masthead %> + <% elsif current_exhibit %> + <%= current_exhibit.title %> + <% if current_exhibit.subtitle.present? %> + <%= current_exhibit.subtitle %> + <% end %> + <% else %> + <%= application_name %> + <% if current_site.subtitle.present? %> + <%= current_site.subtitle %> + <% end %> + <% end %> +
+
+ + <%# <%= render 'shared/exhibit_navbar' if current_exhibit && !resource_masthead? %> +
+ +<%= render 'shared/breadcrumbs' unless resource_masthead? %> diff --git a/app/views/shared/_my_actions.html.erb b/app/views/shared/_my_actions.html.erb index 815815d..86f4d0e 100644 --- a/app/views/shared/_my_actions.html.erb +++ b/app/views/shared/_my_actions.html.erb @@ -22,6 +22,16 @@
  • <%= link_to 'Jobs', main_app.resque_web_path, role: 'menuitem' %>
  • <% end %> + <% if current_exhibit and can? :curate, current_exhibit %> +
  • + <%= link_to t('spotlight.dashboards.show.header'), spotlight.exhibit_dashboard_path(current_exhibit) %> +
  • + <% end %> + + <% if can? :discover, Hydra::AccessControls::Permission %> +
  • <%= link_to 'Rights', main_app.rights_path, role: 'menuitem' %>
  • + <% end %> + <% if include_works_link || include_collections_link %>
  • <% end %> diff --git a/app/views/shared/_product_bar.html.erb b/app/views/shared/_product_bar.html.erb new file mode 100644 index 0000000..ac1a661 --- /dev/null +++ b/app/views/shared/_product_bar.html.erb @@ -0,0 +1,5 @@ +
    + +
    diff --git a/app/views/shared/_site_search.html.erb b/app/views/shared/_site_search.html.erb index e5cad1e..42fa9b5 100644 --- a/app/views/shared/_site_search.html.erb +++ b/app/views/shared/_site_search.html.erb @@ -10,8 +10,13 @@ <%= render_hash_as_hidden_fields(params_for_search().except(:q, :search_field, :qt, :page, :utf8)) %> <%= text_field_tag(:q, params[:q], class: "q search-query form-control", id: "catalog_search", placeholder: t('curation_concerns.search.form.q.placeholder'), tabindex: "1", type: "search") %> - + <%= + link_to_unless_current("Advanced Search", main_app.advanced_search_path(params), :class => "btn btn-primary") do + "" + end + %> <% end %> diff --git a/app/views/shared/_title_bar.html.erb b/app/views/shared/_title_bar.html.erb index 506afdb..79dbf39 100644 --- a/app/views/shared/_title_bar.html.erb +++ b/app/views/shared/_title_bar.html.erb @@ -1,16 +1,10 @@ -
    -
    - - -
    -

    <%= link_to t('curation_concerns.product_name'), main_app.root_path, id: 'home-link' %>

    -
    - - -
    -
    - +
    +
    + + +
    +
    diff --git a/app/views/spotlight/custom_fields/_form.html.erb b/app/views/spotlight/custom_fields/_form.html.erb new file mode 100644 index 0000000..b5556e8 --- /dev/null +++ b/app/views/spotlight/custom_fields/_form.html.erb @@ -0,0 +1,12 @@ +<%= bootstrap_form_for @custom_field.new_record? ? [current_exhibit, @custom_field] : [@custom_field.exhibit, @custom_field], layout: :horizontal, label_col: 'col-md-3', control_col: 'col-sm-5', html: {class: 'col-md-9', id: 'edit-search'} do |f| %> + + <%= f.text_field :label %> + <%= f.text_area :short_description %> + +
    +
    + <%= link_to t(:"cancel"), edit_exhibit_metadata_configuration_path(current_exhibit), class: "btn btn-link" %> + <%= f.submit nil, class: 'btn btn-primary' %> +
    +
    +<% end %> diff --git a/app/views/spotlight/exhibits/_exhibit_card_back.html.erb b/app/views/spotlight/exhibits/_exhibit_card_back.html.erb new file mode 100644 index 0000000..e14231e --- /dev/null +++ b/app/views/spotlight/exhibits/_exhibit_card_back.html.erb @@ -0,0 +1,23 @@ +

    + <%= exhibit.title %> +

    + +

    + <%= exhibit.subtitle %> +

    + +<% if exhibit.description %> +

    + <%= exhibit.description.truncate(168, omission: "") %> + <% if exhibit.description.length > 168 %> + <%= content_tag(:span,"…".html_safe, class: "visible-sm visible-md") %> + + <% end %> +

    +<% end %> + +
    + <%= link_to t(:'.visit_exhibit'), exhibit, class: "btn btn-primary", role: "button" %> +
    diff --git a/app/views/spotlight/exhibits/_exhibits.html.erb b/app/views/spotlight/exhibits/_exhibits.html.erb new file mode 100644 index 0000000..6a1b64a --- /dev/null +++ b/app/views/spotlight/exhibits/_exhibits.html.erb @@ -0,0 +1,5 @@ +<% exhibits.each_slice(4).each do |row| %> +
    + <%= render collection: row, partial: 'exhibit_card', as: 'exhibit' %> +
    +<% end %> \ No newline at end of file diff --git a/app/views/spotlight/exhibits/edit.html.erb b/app/views/spotlight/exhibits/edit.html.erb new file mode 100644 index 0000000..89782a4 --- /dev/null +++ b/app/views/spotlight/exhibits/edit.html.erb @@ -0,0 +1,91 @@ +<%= render 'spotlight/shared/exhibit_sidebar' %> +
    + <%= configuration_page_title %> + +<%= bootstrap_form_for @exhibit, url: spotlight.exhibit_appearance_path(@exhibit), layout: :horizontal, label_col: 'col-md-3 col-sm-3', control_col: 'col-md-5 col-sm-5', html: {data: { autocomplete_exhibit_catalog_index_path: spotlight.autocomplete_exhibit_catalog_index_path(current_exhibit, q: "%QUERY", format: "json") } } do |f| %> + <% if @exhibit.errors.any? %> +
    +

    <%= pluralize(@exhibit.errors.count, "error") %> prohibited this page from being saved:

    + +
      + <% @exhibit.errors.full_messages.each do |msg| %> +
    • <%= msg %>
    • + <% end %> +
    +
    + <% end %> +
    + +
    +
    +

    <%= t(:'.site_masthead.help') %>

    + <%= f.fields_for(:masthead, current_exhibit.masthead || current_exhibit.build_masthead) do |m| %> + <%= render '/spotlight/featured_images/form', f: m, jcrop_options: default_masthead_jcrop_options %> + <% end %> +
    + +
    +

    <%= t(:'.site_thumbnail.help') %>

    + <%= f.fields_for(:thumbnail, current_exhibit.thumbnail || current_exhibit.build_thumbnail) do |m| %> + <%= render '/spotlight/featured_images/form', f: m, jcrop_options: default_site_thumbnail_jcrop_options %> + <% end %> +
    + + +
    +
    + +
    +
    + <%= f.submit nil, class: 'btn btn-primary' %> +
    +
    +<% end %> +
    diff --git a/app/views/spotlight/exhibits/index.html.erb b/app/views/spotlight/exhibits/index.html.erb new file mode 100644 index 0000000..3824580 --- /dev/null +++ b/app/views/spotlight/exhibits/index.html.erb @@ -0,0 +1,48 @@ +
    + + <% if (current_user && current_user.exhibits.any?) || can?(:manage, Spotlight::Exhibit) %> + + <% end %> + +
    +
    + <% if @exhibits.published.none? %> + <%= render 'missing_exhibits' %> + <% else %> + <%= render 'exhibits', exhibits: @published_exhibits %> + + <% if @published_exhibits.total_count > @published_exhibits.size %> + + <% end %> + <% end %> + +
    + + <% if @exhibits.unpublished.accessible_by(current_ability).any? %> +
    + <%= render 'exhibits', exhibits: @exhibits.unpublished.accessible_by(current_ability) %> +
    + <% end %> + + <% if current_user && current_user.exhibits.any? %> +
    + <%= render 'exhibits', exhibits: current_user.exhibits %> +
    + <% end %> +
    +
    + + diff --git a/app/views/spotlight/metadata_configurations/_metadata_field.html.erb b/app/views/spotlight/metadata_configurations/_metadata_field.html.erb new file mode 100644 index 0000000..9284bfd --- /dev/null +++ b/app/views/spotlight/metadata_configurations/_metadata_field.html.erb @@ -0,0 +1,16 @@ + + <%= f.fields_for key do |field| %> + + <%= field.hidden_field :weight, 'data-property' => 'weight' %> +
    +
    <%= t :drag %>
    + <%= index_field_label(nil, key) %> + <%= field.hidden_field :label, value: index_field_label(nil, key), class: 'form-control input-sm', data: {:"edit-field-target" => 'true'} %> +
    + + <%= field.check_box :show, inline: true, checked: config.show, label: "" %> + <% available_view_fields.keys.each do |type| %> + <%= field.check_box type, inline: true, checked: config.send(type), label: "" %> + <% end %> + <% end %> + \ No newline at end of file diff --git a/app/views/spotlight/metadata_configurations/edit.html.erb b/app/views/spotlight/metadata_configurations/edit.html.erb new file mode 100644 index 0000000..7f253f4 --- /dev/null +++ b/app/views/spotlight/metadata_configurations/edit.html.erb @@ -0,0 +1,29 @@ +<%= render 'spotlight/shared/exhibit_sidebar' %> +
    +<%= configuration_page_title %> + +

    <%= t(:'.exhibit_specific.instructions') %>

    + + + + <% @exhibit.custom_fields.each do |field| %> + + + + + <% end %> + + +
    +
    <%= field.label %>
    +
    + <%= exhibit_edit_link field, class: 'btn btn-link' %> · + <%= exhibit_delete_link field, class: 'btn btn-link' %> +
    +
    + <%= field.short_description %> +
    + + +<%= exhibit_create_link Spotlight::CustomField.new, class: 'btn btn-primary' %> +
    diff --git a/app/views/spotlight/pages/edit.html.erb b/app/views/spotlight/pages/edit.html.erb new file mode 100644 index 0000000..009d1cb --- /dev/null +++ b/app/views/spotlight/pages/edit.html.erb @@ -0,0 +1,4 @@ +<%= curation_page_title %> +<%= link_to "Edit Metadata", spotlight.edit_exhibit_path(@page.exhibit) %> + +<%= render 'form' %> diff --git a/app/views/spotlight/pages/show.html.erb b/app/views/spotlight/pages/show.html.erb new file mode 100644 index 0000000..17f4314 --- /dev/null +++ b/app/views/spotlight/pages/show.html.erb @@ -0,0 +1,26 @@ +<% set_html_page_title @page.title if @page.should_display_title? %> +<% render 'tophat' %> +<%= render 'sidebar' if @page.display_sidebar? %> + +
    +
    + <%= link_to "View Members", + catalog_index_path("f[admin_set_ssi][]": @page.exhibit.title), + class: 'btn btn-primary' %> + + <%= render 'edit_page_link' if can? :edit, @page %> + <% if @page.should_display_title? %> +

    + <%= @page.title %> +

    + <% end %> +
    +
    + <% if @page.content? %> + <%= render @page.content %> + <% else %> + <%= render 'empty' %> + <% end %> +
    +
    + diff --git a/app/views/spotlight/shared/_configuration_sidebar.html.erb b/app/views/spotlight/shared/_configuration_sidebar.html.erb new file mode 100644 index 0000000..538e356 --- /dev/null +++ b/app/views/spotlight/shared/_configuration_sidebar.html.erb @@ -0,0 +1,10 @@ + + diff --git a/app/views/spotlight/shared/_exhibit_sidebar.html.erb b/app/views/spotlight/shared/_exhibit_sidebar.html.erb new file mode 100644 index 0000000..e8a0ab6 --- /dev/null +++ b/app/views/spotlight/shared/_exhibit_sidebar.html.erb @@ -0,0 +1,8 @@ + diff --git a/app/views/spotlight/sir_trevor/_sir_trevor_block_array.html.erb b/app/views/spotlight/sir_trevor/_sir_trevor_block_array.html.erb new file mode 100644 index 0000000..cdaf71e --- /dev/null +++ b/app/views/spotlight/sir_trevor/_sir_trevor_block_array.html.erb @@ -0,0 +1 @@ +<%= render sir_trevor_block_array.to_a %> \ No newline at end of file diff --git a/app/views/spotlight/sir_trevor/blocks/_browse_block.html.erb b/app/views/spotlight/sir_trevor/blocks/_browse_block.html.erb new file mode 100644 index 0000000..9b1df8d --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/_browse_block.html.erb @@ -0,0 +1,23 @@ +<% browse_block.with_solr_helper(self) %> + +<% if browse_block.searches? %> +
    + <% browse_block.searches.each_with_index do |search, index| %> +
    + <%= link_to spotlight.exhibit_browse_path(search.exhibit, search) do %> +
    +
    +

    + <%= search.title %> +

    + + <% if browse_block.display_item_counts? %> + <%= t(:'.items', count: search.documents.size) %> + <% end %> +
    +
    + <% end %> +
    + <% end %> +
    +<% end %> \ No newline at end of file diff --git a/app/views/spotlight/sir_trevor/blocks/_featured_pages_block.html.erb b/app/views/spotlight/sir_trevor/blocks/_featured_pages_block.html.erb new file mode 100644 index 0000000..b166184 --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/_featured_pages_block.html.erb @@ -0,0 +1,17 @@ +<% if featured_pages_block.pages? %> + +<% end %> diff --git a/app/views/spotlight/sir_trevor/blocks/_heading_block.html.erb b/app/views/spotlight/sir_trevor/blocks/_heading_block.html.erb new file mode 100644 index 0000000..ac25663 --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/_heading_block.html.erb @@ -0,0 +1 @@ +

    <%= sir_trevor_markdown heading_block.text %>

    diff --git a/app/views/spotlight/sir_trevor/blocks/_iframe_block.html.erb b/app/views/spotlight/sir_trevor/blocks/_iframe_block.html.erb new file mode 100644 index 0000000..c490029 --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/_iframe_block.html.erb @@ -0,0 +1 @@ +<%= sanitize iframe_block.code, tags: %w(iframe), attributes: %w(id width height marginwidth marginheight src frameborder allowfullscreen sandbox seamless srcdoc scrolling) %> \ No newline at end of file diff --git a/app/views/spotlight/sir_trevor/blocks/_image_block.html.erb b/app/views/spotlight/sir_trevor/blocks/_image_block.html.erb new file mode 100644 index 0000000..264bd20 --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/_image_block.html.erb @@ -0,0 +1,5 @@ +<% if image_block.file && image_block.file[:url] -%> +
    + <%= image_tag image_block.file[:url] %> +
    +<% end -%> diff --git a/app/views/spotlight/sir_trevor/blocks/_list_block.html.erb b/app/views/spotlight/sir_trevor/blocks/_list_block.html.erb new file mode 100644 index 0000000..7ebe556 --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/_list_block.html.erb @@ -0,0 +1,3 @@ +
    + <%= sir_trevor_markdown list_block.text %> +
    diff --git a/app/views/spotlight/sir_trevor/blocks/_oembed_block.html.erb b/app/views/spotlight/sir_trevor/blocks/_oembed_block.html.erb new file mode 100644 index 0000000..bc3c66f --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/_oembed_block.html.erb @@ -0,0 +1,14 @@ +
    +
    + <%= render_oembed_tag oembed_block.url %> +
    + + <% if oembed_block.text? %> +
    + <% unless oembed_block.title.blank? %> +

    <%= oembed_block.title %>

    + <% end %> + <%= sir_trevor_markdown oembed_block.text %> +
    + <% end %> +
    diff --git a/app/views/spotlight/sir_trevor/blocks/_quote_block.html.erb b/app/views/spotlight/sir_trevor/blocks/_quote_block.html.erb new file mode 100644 index 0000000..895d366 --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/_quote_block.html.erb @@ -0,0 +1,13 @@ +
    +
    +
    + <%= sir_trevor_markdown quote_block.text %> +
    + + <% if quote_block.cite.present? %> +
    + – <%= quote_block.cite %> +
    + <% end %> +
    +
    diff --git a/app/views/spotlight/sir_trevor/blocks/_rule_block.html.erb b/app/views/spotlight/sir_trevor/blocks/_rule_block.html.erb new file mode 100644 index 0000000..1d6667d --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/_rule_block.html.erb @@ -0,0 +1 @@ +
    \ No newline at end of file diff --git a/app/views/spotlight/sir_trevor/blocks/_search_results_block.html.erb b/app/views/spotlight/sir_trevor/blocks/_search_results_block.html.erb new file mode 100644 index 0000000..81d772f --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/_search_results_block.html.erb @@ -0,0 +1,25 @@ +<% if search_results_block.searches? %> +
    + <% response, document_list = get_search_widget_search_results(search_results_block) %> + <%- unless document_list.present? %> +
    + No items to display. There are currently no items in this exhibit that match the curator's search criteria. +
    + <%- else %> + <% @response, @document_list = [response, document_list] %> + + <% views = blacklight_view_config_for_search_block(search_results_block) %> + <% if views.length > 1 -%> +
    +
    + <%= render partial: 'view_type_group', locals: { block: search_results_block } %> +
    +
    + <% end %> + + <%= render_document_index_with_view(block_document_index_view_type(search_results_block), document_list) %> + <%= render 'results_pagination' %> +
    + <%- end %> + +<% end %> \ No newline at end of file diff --git a/app/views/spotlight/sir_trevor/blocks/_solr_documents_block.html.erb b/app/views/spotlight/sir_trevor/blocks/_solr_documents_block.html.erb new file mode 100644 index 0000000..e151952 --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/_solr_documents_block.html.erb @@ -0,0 +1,39 @@ +<% solr_documents_block.with_solr_helper(self) %> + +
    + <% if solr_documents_block.documents? %> + +
    "> + <% solr_documents_block.each_document do |block_options, document| %> +
    +
    + <% if block_thumb = block_options[:thumbnail_image_url] %> +
    <%= link_to_document(document, image_tag(block_thumb), counter: -1) %>
    + <% elsif has_thumbnail? document %> +
    <%= render_thumbnail_tag(document, {}, document_counter: -1) %>
    + <% end %> + <% if solr_documents_block.primary_caption? %> +
    + <%= render_index_field_value document, solr_documents_block.primary_caption_field %> +
    + <% end %> + <% if solr_documents_block.secondary_caption? %> +
    + <%= render_index_field_value document, solr_documents_block.secondary_caption_field %> +
    + <% end %> +
    +
    + <% end %> +
    + <% end %> + + <% if solr_documents_block.text? %> +
    + <% unless solr_documents_block.title.blank? %> +

    <%= solr_documents_block.title %>

    + <% end %> + <%= sir_trevor_markdown solr_documents_block.text %> +
    + <% end %> +
    \ No newline at end of file diff --git a/app/views/spotlight/sir_trevor/blocks/_solr_documents_carousel_block.html.erb b/app/views/spotlight/sir_trevor/blocks/_solr_documents_carousel_block.html.erb new file mode 100644 index 0000000..ade72d8 --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/_solr_documents_carousel_block.html.erb @@ -0,0 +1,48 @@ +<% solr_documents_carousel_block.with_solr_helper(self) %> +<% indicators = [] %> +<% html_id = "carousel-#{solr_documents_carousel_block.object_id}" %> + diff --git a/app/views/spotlight/sir_trevor/blocks/_solr_documents_embed_block.html.erb b/app/views/spotlight/sir_trevor/blocks/_solr_documents_embed_block.html.erb new file mode 100644 index 0000000..061d4e2 --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/_solr_documents_embed_block.html.erb @@ -0,0 +1,24 @@ +<% solr_documents_embed_block.with_solr_helper(self) %> + +
    + + <% if solr_documents_embed_block.documents? %> + +
    "> + <% solr_documents_embed_block.each_document do |block_options, document| %> +
    + <%= render_document_partials document, blacklight_config.view.embed.partials, (blacklight_config.view.embed.locals || {}).reverse_merge(block: solr_documents_embed_block) %> +
    + <% end %> +
    + <% end %> + + <% if solr_documents_embed_block.text? %> +
    + <% unless solr_documents_embed_block.title.blank? %> +

    <%= solr_documents_embed_block.title %>

    + <% end %> + <%= sir_trevor_markdown solr_documents_embed_block.text %> +
    + <% end %> +
    \ No newline at end of file diff --git a/app/views/spotlight/sir_trevor/blocks/_solr_documents_features_block.html.erb b/app/views/spotlight/sir_trevor/blocks/_solr_documents_features_block.html.erb new file mode 100644 index 0000000..ae3dbc2 --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/_solr_documents_features_block.html.erb @@ -0,0 +1,40 @@ +<% solr_documents_features_block.with_solr_helper(self) %> +<% indicators = [] %> +<% html_id = "carousel-#{solr_documents_features_block.object_id}" %> + +
    + <% if solr_documents_features_block.documents? %> + + <% end %> +
    diff --git a/app/views/spotlight/sir_trevor/blocks/_solr_documents_grid_block.html.erb b/app/views/spotlight/sir_trevor/blocks/_solr_documents_grid_block.html.erb new file mode 100644 index 0000000..003f2e7 --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/_solr_documents_grid_block.html.erb @@ -0,0 +1,26 @@ +<% solr_documents_grid_block.with_solr_helper(self) %> +
    +
    + <% if solr_documents_grid_block.documents? %> + <% solr_documents_grid_block.each_document.each_with_index do |(block_options, document), index| %> +
    + <% if block_thumb = block_options[:thumbnail_image_url] %> +
    <%= link_to_document(document, image_tag(block_thumb), counter: -1) %>
    + <% elsif has_thumbnail? document %> +
    <%= render_thumbnail_tag(document, {}, document_counter: -1) %>
    + <% end %> +
    + <% end %> + <% end %> +
    + + + <% if solr_documents_grid_block.text? %> +
    + <% unless solr_documents_grid_block.title.blank? %> +

    <%= solr_documents_grid_block.title %>

    + <% end %> + <%= sir_trevor_markdown solr_documents_grid_block.text %> +
    + <% end %> +
    diff --git a/app/views/spotlight/sir_trevor/blocks/_text_block.html.erb b/app/views/spotlight/sir_trevor/blocks/_text_block.html.erb new file mode 100644 index 0000000..d174437 --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/_text_block.html.erb @@ -0,0 +1,3 @@ +
    + <%= sir_trevor_markdown text_block.text %> +
    diff --git a/app/views/spotlight/sir_trevor/blocks/_tweet_block.html.erb b/app/views/spotlight/sir_trevor/blocks/_tweet_block.html.erb new file mode 100644 index 0000000..e88482e --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/_tweet_block.html.erb @@ -0,0 +1,9 @@ +
    + <%= link_to image_tag(tweet_block.profile_image_url, class: 'img'), tweet_block.screen_name %> +

    + <%= tweet_block.render_tweet_body %> +

    + From <%= link_to tweet_block.at_name, tweet_block.screen_name %> on Twitter: + +
    + diff --git a/app/views/spotlight/sir_trevor/blocks/_uploaded_items_block.html.erb b/app/views/spotlight/sir_trevor/blocks/_uploaded_items_block.html.erb new file mode 100644 index 0000000..deada96 --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/_uploaded_items_block.html.erb @@ -0,0 +1,22 @@ +
    +
    + <% if uploaded_items_block.files.present? %> + <% uploaded_items_block.files.each do |file| %> +
    +
    +
    + <%= file[:title] %> +
    +
    +
    + <% end %> + <% end %> +
    + + <% if uploaded_items_block.text? %> +
    + <%= content_tag(:h3, uploaded_items_block.title) if uploaded_items_block.title.present? %> + <%= sir_trevor_markdown uploaded_items_block.text %> +
    + <% end %> +
    diff --git a/app/views/spotlight/sir_trevor/blocks/videos/_vimeo.html.erb b/app/views/spotlight/sir_trevor/blocks/videos/_vimeo.html.erb new file mode 100644 index 0000000..f59febd --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/videos/_vimeo.html.erb @@ -0,0 +1,4 @@ +
    + +
    diff --git a/app/views/spotlight/sir_trevor/blocks/videos/_youtube.html.erb b/app/views/spotlight/sir_trevor/blocks/videos/_youtube.html.erb new file mode 100644 index 0000000..314833d --- /dev/null +++ b/app/views/spotlight/sir_trevor/blocks/videos/_youtube.html.erb @@ -0,0 +1,4 @@ +
    + +
    diff --git a/bin/setup b/bin/setup index 2bf7d4a..2fd33f7 100755 --- a/bin/setup +++ b/bin/setup @@ -4,14 +4,17 @@ require 'pathname' # path to your application root. APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) -Dir.chdir APP_ROOT do + +def bundle # This script is a starting point to setup your application. # Add necessary setup steps to this file: puts "== Installing dependencies ==" system "gem install bundler --conservative" system "bundle check || bundle install" +end +def copy_configs puts "\n== Copying sample files ==" unless File.exist?("config/database.yml") system "cp config/database.yml.template config/database.yml" @@ -23,14 +26,32 @@ Dir.chdir APP_ROOT do system "cp config/resque-pool.yml.template config/resque-pool.yml" system "cp config/ldap.yml.template config/ldap.yml" end +end +def prep_db puts "\n== Preparing database ==" system "bin/rake db:setup" +end +def remove_logs puts "\n== Removing old logs and tempfiles ==" system "rm -f log/*" system "rm -rf tmp/cache" +end +def restart puts "\n== Restarting application server ==" system "touch tmp/restart.txt" end + +Dir.chdir APP_ROOT do + if ARGV[0] && ARGV[0].length > 1 + send(ARGV[0]) + else + bundle + copy_configs + prep_db + remove_logs + restart + end +end diff --git a/config/application.rb b/config/application.rb index d22f3a5..13369b1 100644 --- a/config/application.rb +++ b/config/application.rb @@ -12,6 +12,8 @@ class Application < Rails::Application g.test_framework :rspec, spec: true end + config.autoload_paths << "#{Rails.root}/app/presenters/concerns" + # 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. @@ -29,3 +31,4 @@ class Application < Rails::Application config.active_job.queue_adapter = :resque end end + diff --git a/config/authorities/.gitkeep b/config/authorities/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/config/authorities/rights.yml b/config/authorities/rights.yml deleted file mode 100644 index 22467c8..0000000 --- a/config/authorities/rights.yml +++ /dev/null @@ -1,19 +0,0 @@ -terms: - - id: http://creativecommons.org/licenses/by/3.0/us/ - term: Attribution 3.0 United States - - id: http://creativecommons.org/licenses/by-sa/3.0/us/ - term: Attribution-ShareAlike 3.0 United States - - id: http://creativecommons.org/licenses/by-nc/3.0/us/ - term: Attribution-NonCommercial 3.0 United States - - id: http://creativecommons.org/licenses/by-nd/3.0/us/ - term: Attribution-NoDerivs 3.0 United States - - id: http://creativecommons.org/licenses/by-nc-nd/3.0/us/ - term: Attribution-NonCommercial-NoDerivs 3.0 United States - - id: http://creativecommons.org/licenses/by-nc-sa/3.0/us/ - term: Attribution-NonCommercial-ShareAlike 3.0 United States - - id: http://creativecommons.org/publicdomain/mark/1.0/ - term: Public Domain Mark 1.0 - - id: http://creativecommons.org/publicdomain/zero/1.0/ - term: CC0 1.0 Universal - - id: http://www.europeana.eu/portal/rights/rr-r.html - term: All rights reserved diff --git a/config/deploy.rb b/config/deploy.rb index c9bb445..f7a8343 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -2,7 +2,8 @@ lock '3.4.0' set :application, 'goldenseal' -set :repo_url, 'https://github.com/curationexperts/goldenseal.git' +set :repo_url, 'https://github.com/wulib-wustl-edu/goldenseal.git' +#set :repo_url, "git@gitlab.com:notch8/washington-goldenseal.git" set :passenger_restart_with_touch, true ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp set :deploy_to, '/opt/goldenseal' diff --git a/config/deploy/production.rb b/config/deploy/production.rb index a66a65c..9fcf370 100644 --- a/config/deploy/production.rb +++ b/config/deploy/production.rb @@ -1,3 +1,8 @@ +set :stage, :production +set :rails_env, 'production' +set :branch, ENV['BRANCH'] || 'master' +server 'hydraprod.wulib.wustl.edu', user: 'deploy', roles: [:web, :app, :db, :resque_pool] + # server-based syntax # ====================== # Defines a single server with a list of roles and multiple properties. diff --git a/config/environments/development.rb b/config/environments/development.rb index b55e214..4ba47a6 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -38,4 +38,6 @@ # Raises error for missing translations # config.action_view.raise_on_missing_translations = true + end + diff --git a/config/environments/production.rb b/config/environments/production.rb index 40ebbf6..b20a7f5 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -77,3 +77,4 @@ # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false end + diff --git a/config/environments/test.rb b/config/environments/test.rb index 1c19f08..654354a 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -40,3 +40,4 @@ # Raises error for missing translations # config.action_view.raise_on_missing_translations = true end + diff --git a/config/fedora.yml b/config/fedora.yml index c108513..06b563b 100644 --- a/config/fedora.yml +++ b/config/fedora.yml @@ -6,7 +6,7 @@ development: test: user: fedoraAdmin password: fedoraAdmin - url: http://localhost:8983/fedora/rest + url: http://127.0.0.1:8983/fedora/rest base_path: /test production: user: fedoraAdmin diff --git a/config/initializers/authorities.rb b/config/initializers/authorities.rb new file mode 100644 index 0000000..a812723 --- /dev/null +++ b/config/initializers/authorities.rb @@ -0,0 +1 @@ +Qa::Authorities::Local.register_subauthority('rights', 'Qa::Authorities::Local::TableBasedAuthority') diff --git a/config/initializers/curation_concerns.rb b/config/initializers/curation_concerns.rb index a383224..5ed0b9b 100644 --- a/config/initializers/curation_concerns.rb +++ b/config/initializers/curation_concerns.rb @@ -99,7 +99,11 @@ # config.noid_template = ".reeddeeddk" # Store identifier minter's state in a file for later replayability - config.minter_statefile = '/opt/goldenseal/shared/minter-state' if Rails.env.production? + if Rails.env.production? + config.minter_statefile = '/opt/goldenseal/shared/minter-state' + else + config.minter_statefile = './tmp/minter-state' + end # Specify the prefix for Redis keys: # config.redis_namespace = "curation_concerns" diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index be567b2..8d903fa 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -9,7 +9,7 @@ # config.ldap_check_group_membership = false # config.ldap_check_group_membership_without_admin = false # config.ldap_check_attributes = false - # config.ldap_use_admin_to_bind = false + config.ldap_use_admin_to_bind = true # config.ldap_ad_group_check = false # The secret key used by Devise. Devise uses this key to generate diff --git a/config/initializers/friendly_id.rb b/config/initializers/friendly_id.rb new file mode 100644 index 0000000..f064f1e --- /dev/null +++ b/config/initializers/friendly_id.rb @@ -0,0 +1,88 @@ +# FriendlyId Global Configuration +# +# Use this to set up shared configuration options for your entire application. +# Any of the configuration options shown here can also be applied to single +# models by passing arguments to the `friendly_id` class method or defining +# methods in your model. +# +# To learn more, check out the guide: +# +# http://norman.github.io/friendly_id/file.Guide.html + +FriendlyId.defaults do |config| + # ## Reserved Words + # + # Some words could conflict with Rails's routes when used as slugs, or are + # undesirable to allow as slugs. Edit this list as needed for your app. + config.use :reserved + + config.reserved_words = %w(new edit index session login logout users admin + stylesheets assets javascripts images) + + # ## Friendly Finders + # + # Uncomment this to use friendly finders in all models. By default, if + # you wish to find a record by its friendly id, you must do: + # + # MyModel.friendly.find('foo') + # + # If you uncomment this, you can do: + # + # MyModel.find('foo') + # + # This is significantly more convenient but may not be appropriate for + # all applications, so you must explicity opt-in to this behavior. You can + # always also configure it on a per-model basis if you prefer. + # + # Something else to consider is that using the :finders addon boosts + # performance because it will avoid Rails-internal code that makes runtime + # calls to `Module.extend`. + # + # config.use :finders + # + # ## Slugs + # + # Most applications will use the :slugged module everywhere. If you wish + # to do so, uncomment the following line. + # + # config.use :slugged + # + # By default, FriendlyId's :slugged addon expects the slug column to be named + # 'slug', but you can change it if you wish. + # + # config.slug_column = 'slug' + # + # When FriendlyId can not generate a unique ID from your base method, it appends + # a UUID, separated by a single dash. You can configure the character used as the + # separator. If you're upgrading from FriendlyId 4, you may wish to replace this + # with two dashes. + # + # config.sequence_separator = '-' + # + # ## Tips and Tricks + # + # ### Controlling when slugs are generated + # + # As of FriendlyId 5.0, new slugs are generated only when the slug field is + # nil, but if you're using a column as your base method can change this + # behavior by overriding the `should_generate_new_friendly_id` method that + # FriendlyId adds to your model. The change below makes FriendlyId 5.0 behave + # more like 4.0. + # + # config.use Module.new { + # def should_generate_new_friendly_id? + # slug.blank? || _changed? + # end + # } + # + # FriendlyId uses Rails's `parameterize` method to generate slugs, but for + # languages that don't use the Roman alphabet, that's not usually sufficient. + # Here we use the Babosa library to transliterate Russian Cyrillic slugs to + # ASCII. If you use this, don't forget to add "babosa" to your Gemfile. + # + # config.use Module.new { + # def normalize_friendly_id(text) + # text.to_slug.normalize! :transliterations => [:russian, :latin] + # end + # } +end diff --git a/config/initializers/oembed_providers.rb b/config/initializers/oembed_providers.rb new file mode 100644 index 0000000..332ebae --- /dev/null +++ b/config/initializers/oembed_providers.rb @@ -0,0 +1,3 @@ +require 'oembed' + +OEmbed::Providers.register_all \ No newline at end of file diff --git a/config/initializers/routes.rb b/config/initializers/routes.rb new file mode 100644 index 0000000..448002e --- /dev/null +++ b/config/initializers/routes.rb @@ -0,0 +1 @@ +Rails.application.routes.default_url_options[:host] = ENV['DEFAULT_HOST'] || 'repository.wustl.edu' diff --git a/config/initializers/social_share_button.rb b/config/initializers/social_share_button.rb new file mode 100644 index 0000000..d8b6e52 --- /dev/null +++ b/config/initializers/social_share_button.rb @@ -0,0 +1,3 @@ +SocialShareButton.configure do |config| + config.allow_sites = %w(twitter facebook google_plus) +end diff --git a/config/initializers/spotlight.rb b/config/initializers/spotlight.rb new file mode 100644 index 0000000..423c5ca --- /dev/null +++ b/config/initializers/spotlight.rb @@ -0,0 +1,8 @@ +Spotlight::Engine.config.default_autocomplete_params = { + qf: 'id^1000 title_tesim^100 id_ng title_ng', + facet: false, + 'facet.field' => [] +} + +Spotlight::Engine.config.thumbnail_field = "thumbnail_path_ss" +Spotlight::Engine.config.full_image_field = "thumbnail_path_ss" diff --git a/config/ldap.yml.template b/config/ldap.yml.template index f1aa0b9..5382f1f 100644 --- a/config/ldap.yml.template +++ b/config/ldap.yml.template @@ -1,23 +1,20 @@ ## Environment default: &default - attribute: uid - base: ou=people,dc=dce,dc=com - group_base: ou=groups,dc=dce,dc=com + group_base: OU=User-Groups,DC=wulib,DC=wustl,DC=edu + attribute: sAMAccountName + base: CN=Users,DC=wulib,DC=wustl,DC=edu + admin_user: CN=ldap,CN=Users,DC=wulib,DC=wustl,DC=edu + admin_password: <%= ENV['LDAP_PASSWORD'] %> + ssl: false development: - host: ldap.curationexperts.com - port: 389 - admin_user: cn=admin,dc=test,dc=com - admin_password: admin_password - ssl: false + host: ldap + port: 3389 <<: *default test: - host: ldap.curationexperts.com + host: ldap port: 3389 - admin_user: cn=admin,dc=test,dc=com - admin_password: admin_password - ssl: simple_tls <<: *default production: diff --git a/config/locales/blacklight.en.yml b/config/locales/blacklight.en.yml index ff834b2..c945aaa 100644 --- a/config/locales/blacklight.en.yml +++ b/config/locales/blacklight.en.yml @@ -1,3 +1,3 @@ en: blacklight: - application_name: 'Blacklight' \ No newline at end of file + application_name: 'Washington University - Digital Gateway' diff --git a/config/locales/en.yml b/config/locales/en.yml index 05d5e3e..7f7cd6f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -37,3 +37,7 @@ en: admin_set: update: "Update Metadata" create: "Create" + curation_concerns: + product_name: "Digital Gateway" + institution: + name: "Washington University" diff --git a/config/locales/spotlight.en.yml b/config/locales/spotlight.en.yml new file mode 100644 index 0000000..374bf71 --- /dev/null +++ b/config/locales/spotlight.en.yml @@ -0,0 +1,658 @@ +en: + cancel: "Cancel" + drag: "Drag" + toggle_nav: "Toggle navigation" + helpers: + action: + new: Add a new %{model} + view: View + edit: Edit + edit_long: "Edit this %{model}" + destroy: Delete + cancel: Cancel + destroy_are_you_sure: "Are you sure you want to delete this %{model}?" + update_all: "Save changes" + spotlight/search: + destroy: "Delete" + edit_long: "Edit this saved search" + acts_as_taggable_on/tag: + destroy: "Delete tag" + spotlight/role: + create: "Add a new user" + destroy: "Remove from site" + spotlight/custom_field: + create: "Add new field" + spotlight/contact: + create: "Add contact" + spotlight/exhibit: + create: "Create Exhibit" + destroy: "Delete this exhibit" + submit: + spotlight_default: &spotlight_default + create: "Save" + submit: "Save changes" + update: "Save changes" + created: "The %{model} was created." + updated: "The %{model} was successfully updated." + batch_updated: "%{model} were successfully updated." + batch_error: "There was an error updating the requested %{model}." + destroyed: "The %{model} was deleted." + contact: + create: "Save" + created: "The %{model} was created." + destroyed: "The %{model} was destroyed." + update: "Save" + updated: "The %{model} was successfully updated." + batch_updated: "Contacts were successfully updated." + page: &spotlight_page + create: "Add new page" + submit: "Save changes" + update: "Save changes" + created: "The %{model} was created." + updated: "The %{model} was successfully updated." + batch_updated: "%{model} were successfully updated." + batch_error: "There was an error updating the requested pages." + destroyed: "The %{model} was deleted." + exhibit: *spotlight_default + filter: *spotlight_default + search: *spotlight_default + site: *spotlight_default + contact_form: + create: "Send" + created: "Thanks. Your feedback has been sent." + feature_page: *spotlight_page + about_page: *spotlight_page + home_page: *spotlight_page + blacklight_configuration: + create: "Save" + submit: "Save changes" + update: "Save changes" + updated: "The exhibit was successfully updated." + solr_document: *spotlight_default + users: *spotlight_default + custom_field: *spotlight_default + role: + updated: "User has been updated." + destroyed: "User has been removed." + batch_error: "There was a problem saving the user(s)." + invite: + invited: 'User has been invited.' + label: + solr_document: + exhibit_tag_list: "Tags" + spotlight/exhibit: + tag_list: 'Tags' + spotlight/filter: + field: 'Field' + value: 'Value' + activerecord: + models: + spotlight: + page: Page + attributes: + "spotlight/contact": + avatar: "Photo" + "spotlight/exhibit": + published: "Published?" + "spotlight/page": + display_sidebar?: "Show sidebar" + display_title: "Show title" + "spotlight/masthead": + display: "Show background image in masthead" + help: + spotlight/exhibit: + tag_list: "Enter tags separated by commas." + blacklight: + search: + fields: + facet: + exhibit_tag: Exhibit Tags + spotlight: + application_name: "%{exhibit} - %{application_name}" + html_title: "%{title} | %{application_name}" + html_admin_title: "%{section} - %{title}" + configuration: + sidebar: + header: Configuration + settings: General + appearance: Appearance + users: Users + metadata: Metadata + search_configuration: Search + edit_homepage: Collection Homepage + header: "Configuration" + page_title: "Page title" + settings: + header: "Settings" + admin_users: + create: + success: Added user as an exhibits adminstrator + error: There was a problem adding the user as an exhibits adminstrator + destroy: + success: User removed from site adminstrator role + error: There was a problem removing the user from the site adminstrator role + index: + section: Manage exhibits + page_title: Manage administrators + instructions: Existing exhibits administrators + add: Add new administrator + destroy: Remove from role + save: Add role + pending: pending + exhibits: + edit: + header: Appearance + exhibit_style: + heading: "Exhibit style" + main_navigation: + menu: Main menu + help: > + Select the menu items you want to be displayed in the main navigation menu + (menu items are only displayed when published pages exist for that item). + Click a menu item to change its display label. Drag and drop a menu item + to change their order in the menu. + restore_default: "Restore default" + thumbnail: + small: Small + medium: Medium + large: Large + contact_forms: + new: + header: "Feedback" + curation: + sidebar: + header: Curation + dashboard: Dashboard + collection: Collection + my_collections: My Collections + my_works: My Works + analytics: Analytics + items: Items + tags: Tags + browse: Browse + feature_pages: Feature pages + about_pages: About pages + nav: + home: "Home" + header: "Curation" + search_configurations: + document_index_view_types: + label: Result page types + default_per_page: + label: Default per page + edit: + header: "Search" + tab: + options: "Options" + facets: "Facets" + results: "Results" + search_fields: + header: "Field-based search" + help: > + If the search box is displayed, you can also enable field-based search. + Field-based search adds a dropdown menu to your exhibit site's search box that + provides the user with an option to restrict a search query to a single metadata field. + instructions: > + If enabled, you can select below the metadata fields that are available for searching. Click + a field name to edit its display label. Drag and drop fields to specify the order they + are displayed in the search box dropdown menu. + enable_feature: Display search box + facets: + help: > + If the sidebar is visible, users can use + the facets shown in the sidebar to limit a search. You can select the facets + that are available for searching below. Click a facet field name to edit its display label. + Drag and drop facets to specify the order they are displayed in the sidebar. + sort_by: + label: "Sort by:" + count: Frequency + index: Value + facet_metadata: + document_count: + one: "%{count} item" + other: "%{count} items" + too_many_values_count: "%{count}+ unique values" + value_count: + one: "%{count} unique value" + other: "%{count} unique values" + sort: + header: "Sort fields" + help: > + Select the fields you want to be available to users for sorting results. + Click a field title to change its display label. Drag and drop fields to + change their order in the sort dropdown menu. The field listed first is + the default sort field. + keys: + asc: ascending + desc: descending + score: relevancy score + sort_date_dtsi: date + sort_title_ssi: title + sort_type_ssi: type + sort_source_ssi: source + id: id + metadata_configurations: + edit: + field: + label: "Field name" + deselect_all: "Deselect all" + select_all: "Select all" + header: "Metadata" + order_header: "Display and Order Metadata Fields" + exhibit_specific: + header: "Collection-Specific Fields" + instructions: + "You can add metadata fields to supplement the metadata fields that are part of the repository item record." + view: + show: "Item Details" + instructions: > + Select metadata fields to display on each type of page. Click a field name + to edit its display label. Drag and drop fields to specify the order in which they + are displayed. + catalog: + breadcrumb: + index: 'Search Results' + edit_default: + blank_field_warning_html: > + This field is currently hidden on all pages. You can make it visible on the Curation > %{link} page. + full_title_tesim: "Title" + url-field: + help: "Valid file types: %{extensions}" + facets: + exhibit_visibility: + label: "Item Visibility" + private: "Private" + fields: + title: "Title" + date_added: "Date Added" + visibility: Public + document_visibility_control: + make_public: + label: "" + button: Make Public + make_private: + label: "" + button: Make Private + inprogress: + label: "" + private: "Private" + admin: + title: Curation - Items + header: Items + admin_header: + reindex: "Reindex items" + new_resource: "Add items" + new: + header: Import items + reindex_progress_panel: + heading: Reindexing status + begin_html: "Began reindexing a total of items." + completed_html: "Reindexed of items." + error: 'An error occured while reindexing. Contact your exhibits administrator for more information.' + invitation_mailer: + invitation_instructions: + hello: "Hello %{email}!" + someone_invited_you: "The Exhibits Administrator has invited you to help work on the \"%{exhibit_name}\" exhibit at %{url}. You can accept this invitation by clicking the link below." + accept: "Accept invitation" + ignore_html: "If you don't want to accept the invitation, please ignore this email. Your account won't be created until you access the link above." + exhibits_admin_invitation_mailer: + invitation_instructions: + hello: "Hello %{email}!" + someone_invited_you: "The Exhibits Administrator has invited you to help manage exhibits. You can accept this invitation by clicking the link below." + accept: "Accept invitation" + ignore_html: "If you don't want to accept the invitation, please ignore this email. Your exhibits administrator account won't be created until you access the link above." + confirmation_mailer: + confirmation_instructions: + welcome: "Welcome %{email}!" + instructions: "You can confirm your account email through the link below:" + confirm: Confirm my account + contact_form: + new: + header: "Feedback" + custom_fields: + edit: + header: Edit Exhibit-Specific Field + new: + header: Add Exhibit-Specific Field + form: + field_type: + label: "Type" + vocab: "Controlled vocabulary" + text: "Free text" + dashboards: + show: + header: Dashboard + page_activity: + header: Recent Site Building Activity + new_feature_page: "Add new Feature page" + new_about_page: "Add new About page" + field: + title: Title + last_edited_by: User + updated_at: Last Edited + solr_document_activity: + header: Recently Updated Items + no_documents: There are no documents in this exhibit + analytics: + header: "Analytics" + monthly_header: "User Activity Over the Past Month" + pageviews: "page views" + users: "unique visits" + sessions: "visitors" + pagetitle: "page title" + pages: + header: "Most popular pages" + exhibits: + breadcrumb: Home + index: + published: Published exhibits + user: Your exhibits + unpublished: Unpublished exhibits + delete: + heading: Delete exhibit + warning_html: > + This action is irreversible. Be sure to back up the exhibit settings and content using + the %{export_link} feature before proceeding. + edit: + header: General + basic_settings: + heading: Basic settings + site_masthead: + heading: Site masthead + help: > + You can select and crop an image to use as a background in your exhibit site's + masthead. To use an image as a masthead background, you should use an image that + is at least 120 pixels tall and 1200 pixels wide. For best results use an image at + least 1800 pixels wide. You can crop larger images using the cropping tool below. + site_thumbnail: + heading: Site thumbnail + help: "You can select and crop an image to visually represent this exhibit." + + form: + fields: + contact_emails: + help_block: Each contact email will receive feedback submissions + published: + label: "Published" + help_block: "" + new_exhibit_form: + fields: + title: + label: Title + help_block: This can be changed later. + slug: + label: URL slug + help_block: A hyphenated name to be displayed in the URL for the exhibit (e.g., "maps-of-africa"). + exhibit_card_front: + unpublished: Unpublished + exhibit_card_back: + visit_exhibit: "Visit" + new: + header: Create a new exhibit + filter: + heading: Filter items + import: + heading: Import data + instructions: You can import an exhibit JSON file exported from this application to use that data file to define this exhibit. + button: Import data + export: + heading: Export data + instructions: > + You can create a backup of this exhibit by exporting the data that defines it to a JSON file. + You can then import that JSON file into new exhibit to restore the exhibit data or to use as a starting point for a new exhibit. + download: Export data + confirmation_status: + confirmed: Confirmed. + confirmation_sent: Confirmation sent. + not_validated: Not validated. + resend: Resend confirmation? + tags: + all: All + + main_navigation: + about: "About" + browse: "Browse" + curated_features: "Curated Features" + pages: + order_pages: + pages_header: Custom Pages + instructions: Add new pages below. Drag and drop pages to change the order in which they are displayed in the sidebar. + new_page: "Add new page" + save: "Save" + cancel: "Cancel" + index: + home_pages: + title: Exhibit Home + header: Exhibit Home + feature_pages: + title: Feature Pages + header: Feature Pages + home_pages_header: Homepage + about_pages: + title: About Pages + header: About Pages + new: + header: New page + edit: + header: Edit page + locked: "This page is currently being edited by %{user} (%{created_at})" + form: + title_placeholder: "Title" + page_content: "Content" + page_options: "Options" + page_thumbnail: "Thumbnail" + thumbnail: + help: > + You can select and crop an image to visually represent this page. It will be + used as the thumbnail image if you include this page using the 'Highlight Featured Pages' widget. + sir_trevor: + blocks: + browse_block: + items: + one: "%{count} item" + other: "%{count} items" + contacts: + edit: + header: "Edit Contact" + new: + header: "Add Exhibit Contact" + form: + name: + placeholder: First and last name + email: + placeholder: Valid email address + title: + placeholder: Job title most relevant to this exhibit + location: + placeholder: Name of library or other physical location + telephone: + placeholder: Telephone number (optional) + about_pages: + contacts_form: + header: Contacts + contact: + legend: Contact Details + instructions: > + Enter details for each librarian, curator or other contact person for this exhibit. + Select the contacts you want to be show in the sidebar of the About Pages. Drag and + drop contacts to specify the order in which they are shown in the sidebar. + nav_link: About + page_options: + published: "Publish" # Possibly no longer used + sidebar: + nav_link: About + contacts: + header: "Contacts" + feature_pages: + nav_link: Curated Features + page_options: + published: "Publish" + featured_images: + form: &featured_images_form + source: + header: "Image source" + exhibit: + label: "From this exhibit" + help: "To find an image, start typing the title of an exhibit item." + remote: + label: "Upload an image" + header: "Cropped image" + help: > + Adjust the cropping box to cover the area of the image you want to use + as the thumbnail image. Click "Save changes" to save the cropped area. + upload_form: *featured_images_form + + resources: + new: + header: "Add items" + form: + needs_provider: "Could not find an appropriate importer" + has_provider: "Ready to import" + upload: + csv: + success: "'%{file_name}' has been uploaded. An email will be sent to you once indexing is complete." + new: + single_item_form: "Single item" + multi_item_form: "Multiple items using CSV file" + error: "There was a problem uploading your object." + success: "Object uploaded successfully." + form: + title: "Upload item" + add_item: "Add item" + add_item_and_continue: "Add item and continue adding" + full_title_tesim: "Title" + url-field: + help: "Valid file types: %{extensions}" + csv_upload: + form: + title: "Upload multiple items" + add_item: "Add item" + help_html: "%{link}" + template: "Download template" + file_label: "CSV File" + bookmarklet: + instructions: "Drag this button to the bookmarks toolbar in your web browser" + bookmarklet: "%{application_name} widget" + reindexing_in_progress: "Reindexing all resources" + external_resources_form: + title: "From external resource" + roles: + edit_fields: + invite_html: "This user does not yet exist. Would you like to send them an %{link}?" + invite_link: invite + index: + title: Site Configuration - Users + invite_pending: pending + header: Users + name: Username + email: "Email Address" + role: "Role" + actions: "Actions" + sites: + new: + section: Manage exhibits + page_title: Create a new exhibit + edit: + section: Manage exhibits + page_title: Customize appearance + basic_settings: + heading: Title + site_masthead: + heading: Site masthead + help: > + You can select and crop an image to use as a background for your home page masthead. + + To use an image as a masthead background, you should use an image that is at least + 120 pixels tall and 1200 pixels wide. For best results use an image at least 1800 pixels wide. + You can crop larger images using the cropping tool below. + edit_exhibits: + section: Manage exhibits + page_title: Order exhibits + instructions: Drag and drop the exhibits below to specify the order in which they are displayed on the exhibits homepage. + searches: &search + nav_link: "Browse" + index: + header: "Browse" + title: "Curation - Browse" + categories_header: "Browse Categories" + instructions: > + Use the Save search button on a search results page to create a new browse category. + Select the categories you want to be displayed on the browse landing page. Drag and + drop categories to change the order in which they appear on that page." + no_saved_searches: > + You can save search results while in curation mode to create browse categories, + which will be displayed here. + not_searchable_html: > + This exhibit is not currently searchable. To perform searches that can be saved as + additional browse categories, temporarily turn on the Display search box + option in the Options section of the Configuration > %{href} page. + edit: + header: "Edit Browse Category" + title: "Curation - Browse" + query_params: "Active search constraints" + search: + item_count: + one: "%{count} item" + other: "%{count} items" + missing_description_html: "%{link} to add a description." + form: + search_description: "Description" + search_masthead: "Masthead" + search_thumbnail: "Thumbnail" + masthead: + help: > + You can select and crop an image to use as a browse category-specific masthead, instead + of the default site masthead, for this browse category's detail page. + help_secondary: > + To create a browse category-specific masthead, you should use an image that is at least + 120 pixels tall and 1200 pixels wide. For best results use an image at least 1800 pixels wide. + You can crop larger images using the cropping tool below. + thumbnail: + help: "You can select and crop an image to visually represent this search." + browse: + nav_link: "Browse" + index: + header: "Browse Exhibit" + search: + item_count: + one: "%{count} item" + other: "%{count} items" + tags: + index: + title: "Curation - Tags" + header: "Tags" + name: "Tag name" + taggings: + count: "Items tagged" + actions: "Actions" + no_tags: "No items in this exhibit have been tagged. You can add tags to an item on the Item Details page while in curation mode." + role: + admin: "Admin" + curator: "Curator" + header_links: + login: "Sign in" + logout: "Sign out" + contact: "Feedback" + saved_search: + label: "Save this search" + title: "Saved search title" + shared: + report_a_problem: + title: Contact Us + indexing_complete_mailer: + documents_indexed: + title: "Your CSV file has just finished being processed." + body: + one: "%{count} document has been indexed from the CSV file and added to the exhibit %{title}." + other: "%{count} documents have been indexed from the CSV file and added to the exhibit %{title}." + versions: + undo: Undo changes + redo: Redo changes + undo_error: Unable to undo changes + shared: + share_follow: + share_follow: "Share & Follow" + site_sidebar: + header: Actions + create_exhibit: 'Create a new exhibit' + documentation: 'Curator documentation' diff --git a/config/routes.rb b/config/routes.rb index bcad15b..0c9c3b6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,12 +1,15 @@ ALLOW_DOTS ||= /[^\/]+(?=\.(html|json|ttl)\z)|[^\/]+/ Rails.application.routes.draw do + mount Blacklight::Oembed::Engine, at: 'oembed' + mount Spotlight::Engine, at: 'spotlight' + resources :rights blacklight_for :catalog devise_for :users mount Hydra::Collections::Engine => '/' mount CurationConcerns::Engine, at: '/' resources :welcome, only: :index - root to: "welcome#index" + root to: 'welcome#index' iiif_for 'riiif/image', at: '/image-service' curation_concerns_collections curation_concerns_basic_routes @@ -25,4 +28,9 @@ constraints resque_web_constraint do mount ResqueWeb::Engine => "/resque" end + + get 'collections/:id/allow_downloads' => 'collections#allow_downloads', as: :allow_downloads + get 'collections/:id/prevent_downloads' => 'collections#prevent_downloads', as: :prevent_downloads + get 'admin_sets/:id/allow_downloads' => 'admin_sets#allow_downloads', as: :admin_set_allow_downloads + get 'admin_sets/:id/prevent_downloads' => 'admin_sets#prevent_downloads', as: :admin_set_prevent_downloads end diff --git a/config/sitemap.rb b/config/sitemap.rb new file mode 100644 index 0000000..3107783 --- /dev/null +++ b/config/sitemap.rb @@ -0,0 +1,11 @@ +require 'sitemap_generator' + +# TODO: Update the default host to match your deployment environment +SitemapGenerator::Sitemap.default_host = 'http://localhost/' + +SitemapGenerator::Interpreter.send :include, Rails.application.routes.url_helpers +SitemapGenerator::Interpreter.send :include, Spotlight::Engine.routes.url_helpers + +SitemapGenerator::Sitemap.create do + Spotlight::Sitemap.add_all_exhibits(self) +end diff --git a/db/migrate/20180306025845_create_qa_local_authorities.rb b/db/migrate/20180306025845_create_qa_local_authorities.rb new file mode 100644 index 0000000..b874119 --- /dev/null +++ b/db/migrate/20180306025845_create_qa_local_authorities.rb @@ -0,0 +1,10 @@ +class CreateQaLocalAuthorities < ActiveRecord::Migration + def change + create_table :qa_local_authorities do |t| + t.string :name + + t.timestamps null: false + end + add_index :qa_local_authorities, :name, unique: true + end +end diff --git a/db/migrate/20180306025848_create_qa_local_authority_entries.rb b/db/migrate/20180306025848_create_qa_local_authority_entries.rb new file mode 100644 index 0000000..49b4580 --- /dev/null +++ b/db/migrate/20180306025848_create_qa_local_authority_entries.rb @@ -0,0 +1,14 @@ +class CreateQaLocalAuthorityEntries < ActiveRecord::Migration + def change + create_table :qa_local_authority_entries do |t| + t.integer :local_authority_id + t.string :label + t.string :uri + + t.timestamps null: false + end + add_index :qa_local_authority_entries, :uri + add_index "qa_local_authority_entries", ["local_authority_id"], name: "index_qa_local_authority_entries_on_local_authority_id" + + end +end diff --git a/db/migrate/20180306163029_load_rights.rb b/db/migrate/20180306163029_load_rights.rb new file mode 100644 index 0000000..9901c1f --- /dev/null +++ b/db/migrate/20180306163029_load_rights.rb @@ -0,0 +1,5 @@ +class LoadRights < ActiveRecord::Migration + def change + Rake::Task['data:rights'].invoke + end +end diff --git a/db/migrate/20180319193148_create_friendly_id_slugs.rb b/db/migrate/20180319193148_create_friendly_id_slugs.rb new file mode 100644 index 0000000..770f626 --- /dev/null +++ b/db/migrate/20180319193148_create_friendly_id_slugs.rb @@ -0,0 +1,15 @@ +class CreateFriendlyIdSlugs < ActiveRecord::Migration + def change + create_table :friendly_id_slugs do |t| + t.string :slug, :null => false + t.integer :sluggable_id, :null => false + t.string :sluggable_type, :limit => 50 + t.string :scope + t.datetime :created_at + end + add_index :friendly_id_slugs, :sluggable_id + add_index :friendly_id_slugs, [:slug, :sluggable_type] + add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], :unique => true + add_index :friendly_id_slugs, :sluggable_type + end +end diff --git a/db/migrate/20180319193215_create_versions.rb b/db/migrate/20180319193215_create_versions.rb new file mode 100644 index 0000000..ff794f9 --- /dev/null +++ b/db/migrate/20180319193215_create_versions.rb @@ -0,0 +1,34 @@ +class CreateVersions < ActiveRecord::Migration + + # The largest text column available in all supported RDBMS is + # 1024^3 - 1 bytes, roughly one gibibyte. We specify a size + # so that MySQL will use `longtext` instead of `text`. Otherwise, + # when serializing very large objects, `text` might not be big enough. + TEXT_BYTES = 1_073_741_823 + + def change + create_table :versions do |t| + t.string :item_type, :null => false + t.integer :item_id, :null => false + t.string :event, :null => false + t.string :whodunnit + t.text :object, :limit => TEXT_BYTES + + # Known issue in MySQL: fractional second precision + # ------------------------------------------------- + # + # MySQL timestamp columns do not support fractional seconds unless + # defined with "fractional seconds precision". MySQL users should manually + # add fractional seconds precision to this migration, specifically, to + # the `created_at` column. + # (https://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html) + # + # MySQL users should also upgrade to rails 4.2, which is the first + # version of ActiveRecord with support for fractional seconds in MySQL. + # (https://github.com/rails/rails/pull/14359) + # + t.datetime :created_at + end + add_index :versions, [:item_type, :item_id] + end +end diff --git a/db/migrate/20180319193612_create_spotlight_pages.spotlight.rb b/db/migrate/20180319193612_create_spotlight_pages.spotlight.rb new file mode 100644 index 0000000..c28c195 --- /dev/null +++ b/db/migrate/20180319193612_create_spotlight_pages.spotlight.rb @@ -0,0 +1,24 @@ +# This migration comes from spotlight (originally 20131119213142) +class CreateSpotlightPages < ActiveRecord::Migration + def change + create_table :spotlight_pages do |t| + t.string :title + t.string :type + t.string :slug + t.string :scope + t.text :content + t.integer :weight, default: 50 + t.boolean :published + t.references :exhibit + t.integer :created_by_id + t.integer :last_edited_by_id + t.timestamps + t.integer :parent_page_id + t.boolean :display_sidebar + t.boolean :display_title + end + add_index :spotlight_pages, :exhibit_id + add_index :spotlight_pages, :parent_page_id + add_index :spotlight_pages, [:slug,:scope], unique: true + end +end diff --git a/db/migrate/20180319193613_create_spotlight_attachments.spotlight.rb b/db/migrate/20180319193613_create_spotlight_attachments.spotlight.rb new file mode 100644 index 0000000..a09cc80 --- /dev/null +++ b/db/migrate/20180319193613_create_spotlight_attachments.spotlight.rb @@ -0,0 +1,13 @@ +# This migration comes from spotlight (originally 20131120172811) +class CreateSpotlightAttachments < ActiveRecord::Migration + def change + create_table :spotlight_attachments do |t| + t.string :name + t.string :file + t.string :uid + t.references :exhibit + + t.timestamps + end + end +end diff --git a/db/migrate/20180319193614_create_exhibits.spotlight.rb b/db/migrate/20180319193614_create_exhibits.spotlight.rb new file mode 100644 index 0000000..15ecb45 --- /dev/null +++ b/db/migrate/20180319193614_create_exhibits.spotlight.rb @@ -0,0 +1,14 @@ +# This migration comes from spotlight (originally 20140128155151) +class CreateExhibits < ActiveRecord::Migration + def change + create_table :spotlight_exhibits do |t| + t.string :title, null: false + t.string :subtitle + t.string :slug + t.text :description + t.timestamps + end + + add_index :spotlight_exhibits, :slug, unique: true + end +end diff --git a/db/migrate/20180319193615_create_roles.spotlight.rb b/db/migrate/20180319193615_create_roles.spotlight.rb new file mode 100644 index 0000000..a932856 --- /dev/null +++ b/db/migrate/20180319193615_create_roles.spotlight.rb @@ -0,0 +1,12 @@ +# This migration comes from spotlight (originally 20140128155152) +class CreateRoles < ActiveRecord::Migration + def change + create_table :spotlight_roles do |t| + t.references :exhibit + t.references :user + t.string :role + end + + add_index :spotlight_roles, [:exhibit_id, :user_id], unique: true + end +end diff --git a/db/migrate/20180319193616_create_spotlight_searches.spotlight.rb b/db/migrate/20180319193616_create_spotlight_searches.spotlight.rb new file mode 100644 index 0000000..2f337e4 --- /dev/null +++ b/db/migrate/20180319193616_create_spotlight_searches.spotlight.rb @@ -0,0 +1,21 @@ +# This migration comes from spotlight (originally 20140130155151) +class CreateSpotlightSearches < ActiveRecord::Migration + def change + create_table :spotlight_searches do |t| + t.string :title + t.string :slug + t.string :scope + t.text :short_description + t.text :long_description + t.text :query_params + t.integer :weight + t.boolean :on_landing_page + t.string :featured_image + t.references :exhibit + t.timestamps + end + + add_index :spotlight_searches, :exhibit_id + add_index :spotlight_searches, [:slug,:scope], unique: true + end +end diff --git a/db/migrate/20180319193617_create_spotlight_blacklight_configurations.spotlight.rb b/db/migrate/20180319193617_create_spotlight_blacklight_configurations.spotlight.rb new file mode 100644 index 0000000..ab32729 --- /dev/null +++ b/db/migrate/20180319193617_create_spotlight_blacklight_configurations.spotlight.rb @@ -0,0 +1,21 @@ +# This migration comes from spotlight (originally 20140130215634) +class CreateSpotlightBlacklightConfigurations < ActiveRecord::Migration + def change + create_table :spotlight_blacklight_configurations do |t| + t.references :exhibit + t.text :facet_fields + t.text :index_fields + t.text :search_fields + t.text :sort_fields + t.text :default_solr_params + t.text :show + t.text :index + t.integer :default_per_page + t.text :per_page + t.text :document_index_view_types + t.string :thumbnail_size + + t.timestamps + end + end +end diff --git a/db/migrate/20180319193618_acts_as_taggable_on_migration.spotlight.rb b/db/migrate/20180319193618_acts_as_taggable_on_migration.spotlight.rb new file mode 100644 index 0000000..7e77f4a --- /dev/null +++ b/db/migrate/20180319193618_acts_as_taggable_on_migration.spotlight.rb @@ -0,0 +1,33 @@ +# This migration comes from spotlight (originally 20140206152809) +# This migration comes from acts_as_taggable_on_engine (originally 1) +class ActsAsTaggableOnMigration < ActiveRecord::Migration + def self.up + create_table :tags do |t| + t.string :name + end + + create_table :taggings do |t| + t.references :tag + + # You should make sure that the column created is + # long enough to store the required class names. + t.string :taggable_id + t.string :taggable_type + t.references :tagger, :polymorphic => true + + # Limit is created to prevent MySQL error on index + # length for MyISAM table type: http://bit.ly/vgW2Ql + t.string :context, :limit => 128 + + t.datetime :created_at + end + + add_index :taggings, :tag_id + add_index :taggings, [:taggable_id, :taggable_type, :context] + end + + def self.down + drop_table :taggings + drop_table :tags + end +end diff --git a/db/migrate/20180319193619_add_missing_unique_indices.spotlight.rb b/db/migrate/20180319193619_add_missing_unique_indices.spotlight.rb new file mode 100644 index 0000000..a28f405 --- /dev/null +++ b/db/migrate/20180319193619_add_missing_unique_indices.spotlight.rb @@ -0,0 +1,23 @@ +# This migration comes from spotlight (originally 20140206152810) +# This migration comes from acts_as_taggable_on_engine (originally 2) +class AddMissingUniqueIndices < ActiveRecord::Migration + + def self.up + add_index :tags, :name, unique: true + + remove_index :taggings, :tag_id + remove_index :taggings, [:taggable_id, :taggable_type, :context] + add_index :taggings, + [:tag_id, :taggable_id, :taggable_type, :context, :tagger_id, :tagger_type], + unique: true, name: 'taggings_idx' + end + + def self.down + remove_index :tags, :name + + remove_index :taggings, name: 'tagging_idx' + add_index :taggings, :tag_id + add_index :taggings, [:taggable_id, :taggable_type, :context] + end + +end diff --git a/db/migrate/20180319193620_create_spotlight_custom_fields.spotlight.rb b/db/migrate/20180319193620_create_spotlight_custom_fields.spotlight.rb new file mode 100644 index 0000000..e72b6e2 --- /dev/null +++ b/db/migrate/20180319193620_create_spotlight_custom_fields.spotlight.rb @@ -0,0 +1,13 @@ +# This migration comes from spotlight (originally 20140211203403) +class CreateSpotlightCustomFields < ActiveRecord::Migration + def change + create_table :spotlight_custom_fields do |t| + t.references :exhibit + t.string :slug + t.string :field + t.text :configuration + + t.timestamps + end + end +end diff --git a/db/migrate/20180319193621_create_spotlight_solr_document_sidecars.spotlight.rb b/db/migrate/20180319193621_create_spotlight_solr_document_sidecars.spotlight.rb new file mode 100644 index 0000000..ddba07f --- /dev/null +++ b/db/migrate/20180319193621_create_spotlight_solr_document_sidecars.spotlight.rb @@ -0,0 +1,13 @@ +# This migration comes from spotlight (originally 20140211212626) +class CreateSpotlightSolrDocumentSidecars < ActiveRecord::Migration + def change + create_table :spotlight_solr_document_sidecars do |t| + t.references :exhibit, index: true + t.string :solr_document_id, index: true + t.boolean :public, default: true + t.text :data + + t.timestamps + end + end +end diff --git a/db/migrate/20180319193622_create_contacts.spotlight.rb b/db/migrate/20180319193622_create_contacts.spotlight.rb new file mode 100644 index 0000000..9ad139a --- /dev/null +++ b/db/migrate/20180319193622_create_contacts.spotlight.rb @@ -0,0 +1,19 @@ +# This migration comes from spotlight (originally 20140218155151) +class CreateContacts < ActiveRecord::Migration + def change + create_table :spotlight_contacts do |t| + t.string :slug + t.string :name + t.string :email + t.string :title + t.string :location + t.string :telephone + t.boolean :show_in_sidebar + t.integer :weight, default: 50 + t.references :exhibit + t.timestamps + end + + add_index :spotlight_contacts, :exhibit_id + end +end diff --git a/db/migrate/20180319193623_create_contact_emails.spotlight.rb b/db/migrate/20180319193623_create_contact_emails.spotlight.rb new file mode 100644 index 0000000..8eaaf4f --- /dev/null +++ b/db/migrate/20180319193623_create_contact_emails.spotlight.rb @@ -0,0 +1,17 @@ +# This migration comes from spotlight (originally 20140225180948) +class CreateContactEmails < ActiveRecord::Migration + def change + create_table(:spotlight_contact_emails) do |t| + t.references :exhibit + t.string :email, :null => false, :default => "" + t.string :confirmation_token + t.datetime :confirmed_at + t.datetime :confirmation_sent_at + t.string :unconfirmed_email + t.timestamps + end + + add_index :spotlight_contact_emails, [:email, :exhibit_id], :unique => true + add_index :spotlight_contact_emails, :confirmation_token, :unique => true + end +end diff --git a/db/migrate/20180319193624_create_resources.spotlight.rb b/db/migrate/20180319193624_create_resources.spotlight.rb new file mode 100644 index 0000000..835aa48 --- /dev/null +++ b/db/migrate/20180319193624_create_resources.spotlight.rb @@ -0,0 +1,13 @@ +# This migration comes from spotlight (originally 20140228131207) +class CreateResources < ActiveRecord::Migration + def change + create_table(:spotlight_resources) do |t| + t.references :exhibit + t.string :type + t.string :url + t.text :data + t.datetime :indexed_at + t.timestamps + end + end +end diff --git a/db/migrate/20180319193625_change_featured_image_to_featured_image_id.spotlight.rb b/db/migrate/20180319193625_change_featured_image_to_featured_image_id.spotlight.rb new file mode 100644 index 0000000..12521bc --- /dev/null +++ b/db/migrate/20180319193625_change_featured_image_to_featured_image_id.spotlight.rb @@ -0,0 +1,7 @@ +# This migration comes from spotlight (originally 20140401232956) +class ChangeFeaturedImageToFeaturedImageId < ActiveRecord::Migration + def change + remove_column :spotlight_searches, :featured_image, :string + add_column :spotlight_searches, :featured_item_id, :string + end +end diff --git a/db/migrate/20180319193626_create_spotlight_main_navigations.spotlight.rb b/db/migrate/20180319193626_create_spotlight_main_navigations.spotlight.rb new file mode 100644 index 0000000..a033433 --- /dev/null +++ b/db/migrate/20180319193626_create_spotlight_main_navigations.spotlight.rb @@ -0,0 +1,13 @@ +# This migration comes from spotlight (originally 20140403180324) +class CreateSpotlightMainNavigations < ActiveRecord::Migration + def change + create_table :spotlight_main_navigations do |t| + t.string :label + t.integer :weight, default: 20 + t.string :nav_type + t.references :exhibit + t.timestamps + end + add_index :spotlight_main_navigations, :exhibit_id + end +end diff --git a/db/migrate/20180319193627_create_locks.spotlight.rb b/db/migrate/20180319193627_create_locks.spotlight.rb new file mode 100644 index 0000000..c4b5174 --- /dev/null +++ b/db/migrate/20180319193627_create_locks.spotlight.rb @@ -0,0 +1,12 @@ +# This migration comes from spotlight (originally 20141117111311) +class CreateLocks < ActiveRecord::Migration + def change + create_table :spotlight_locks do |t| + t.references :on, polymorphic: true + t.references :by, polymorphic: true + t.timestamps + end + + add_index :spotlight_locks, [:on_id, :on_type], unique: true + end +end diff --git a/db/migrate/20180319193628_change_contact_details.spotlight.rb b/db/migrate/20180319193628_change_contact_details.spotlight.rb new file mode 100644 index 0000000..1be0805 --- /dev/null +++ b/db/migrate/20180319193628_change_contact_details.spotlight.rb @@ -0,0 +1,41 @@ +# This migration comes from spotlight (originally 20141118233735) +class ChangeContactDetails < ActiveRecord::Migration + def up + add_column :spotlight_contacts, :contact_info, :text + + Spotlight::Contact.find_each do |contact| + migrated_contact_info = {} + attributes.each do |attribute| + if (value = contact.send(attribute)).present? + migrated_contact_info[attribute] = value + end + end + contact.contact_info = migrated_contact_info + contact.save! + end + + attributes.each do |col| + remove_column :spotlight_contacts, col, :string if Spotlight::Contact.column_names.include? col + end + end + def down + attributes.each do |attribute| + add_column :spotlight_contacts, col, :string + end + + Spotlight::Contact.find_each do |contact| + attributes.each do |attribute| + if (value = contact.contact_info[attribute]).present? + contact.send("#{attribute}=".to_sym, value) + end + end + contact.save! + end + + remove_column :spotlight_contacts, :contact_info, :text + end + private + def attributes + [:email, :title, :location, :telephone] + end +end diff --git a/db/migrate/20180319193629_add_avatar_to_contacts.spotlight.rb b/db/migrate/20180319193629_add_avatar_to_contacts.spotlight.rb new file mode 100644 index 0000000..e573e29 --- /dev/null +++ b/db/migrate/20180319193629_add_avatar_to_contacts.spotlight.rb @@ -0,0 +1,10 @@ +# This migration comes from spotlight (originally 20141126231820) +class AddAvatarToContacts < ActiveRecord::Migration + def change + add_column :spotlight_contacts, :avatar, :string + add_column :spotlight_contacts, :avatar_crop_x, :integer + add_column :spotlight_contacts, :avatar_crop_y, :integer + add_column :spotlight_contacts, :avatar_crop_w, :integer + add_column :spotlight_contacts, :avatar_crop_h, :integer + end +end diff --git a/db/migrate/20180319193630_add_layout_options_to_exhibit.spotlight.rb b/db/migrate/20180319193630_add_layout_options_to_exhibit.spotlight.rb new file mode 100644 index 0000000..7921fea --- /dev/null +++ b/db/migrate/20180319193630_add_layout_options_to_exhibit.spotlight.rb @@ -0,0 +1,7 @@ +# This migration comes from spotlight (originally 20141205005902) +class AddLayoutOptionsToExhibit < ActiveRecord::Migration + def change + add_column :spotlight_exhibits, :searchable, :boolean, default: true + add_column :spotlight_exhibits, :layout, :string + end +end diff --git a/db/migrate/20180319193631_add_published_to_exhibit.spotlight.rb b/db/migrate/20180319193631_add_published_to_exhibit.spotlight.rb new file mode 100644 index 0000000..349ef5a --- /dev/null +++ b/db/migrate/20180319193631_add_published_to_exhibit.spotlight.rb @@ -0,0 +1,16 @@ +# This migration comes from spotlight (originally 20150116161616) +class AddPublishedToExhibit < ActiveRecord::Migration + def change + add_column :spotlight_exhibits, :published, :boolean, default: false + add_column :spotlight_exhibits, :published_at, :datetime + + reversible do |dir| + dir.up do + Spotlight::Exhibit.find_each do |e| + e.published = true + e.save! + end + end + end + end +end diff --git a/db/migrate/20180319193632_add_featured_image_to_exhibit.spotlight.rb b/db/migrate/20180319193632_add_featured_image_to_exhibit.spotlight.rb new file mode 100644 index 0000000..e910fd2 --- /dev/null +++ b/db/migrate/20180319193632_add_featured_image_to_exhibit.spotlight.rb @@ -0,0 +1,6 @@ +# This migration comes from spotlight (originally 20150127173245) +class AddFeaturedImageToExhibit < ActiveRecord::Migration + def change + add_column :spotlight_exhibits, :featured_image, :string + end +end diff --git a/db/migrate/20180319193633_add_polymorphic_document_to_sidecars.spotlight.rb b/db/migrate/20180319193633_add_polymorphic_document_to_sidecars.spotlight.rb new file mode 100644 index 0000000..6c9ba12 --- /dev/null +++ b/db/migrate/20180319193633_add_polymorphic_document_to_sidecars.spotlight.rb @@ -0,0 +1,27 @@ +# This migration comes from spotlight (originally 20150217111511) +class AddPolymorphicDocumentToSidecars < ActiveRecord::Migration + def change + add_column :spotlight_solr_document_sidecars, :document_id, :string + add_column :spotlight_solr_document_sidecars, :document_type, :string + + reversible do |dir| + dir.up do + Spotlight::SolrDocumentSidecar.find_each do |e| + e.document = SolrDocument.new(id: e.solr_document_id) + e.save! + end + end + + dir.down do + Spotlight::SolrDocumentSidecar.find_each do |e| + e.solr_document_id = e.document_id + e.save! + end + end + end + + remove_column :spotlight_solr_document_sidecars, :solr_document_id + + add_index :bookmarks, [:document_type, :document_id] + end +end diff --git a/db/migrate/20180319193634_add_spotlight_featured_images.spotlight.rb b/db/migrate/20180319193634_add_spotlight_featured_images.spotlight.rb new file mode 100644 index 0000000..98579c4 --- /dev/null +++ b/db/migrate/20180319193634_add_spotlight_featured_images.spotlight.rb @@ -0,0 +1,17 @@ +# This migration comes from spotlight (originally 20150304071512) +class AddSpotlightFeaturedImages < ActiveRecord::Migration + def change + create_table :spotlight_featured_images do |t| + t.string :type + t.boolean :display + t.string :image + t.string :source + t.string :document_global_id + t.integer :image_crop_x + t.integer :image_crop_y + t.integer :image_crop_w + t.integer :image_crop_h + t.timestamps + end + end +end diff --git a/db/migrate/20180319193635_add_featured_image_to_spotlight_classes.spotlight.rb b/db/migrate/20180319193635_add_featured_image_to_spotlight_classes.spotlight.rb new file mode 100644 index 0000000..4059fe6 --- /dev/null +++ b/db/migrate/20180319193635_add_featured_image_to_spotlight_classes.spotlight.rb @@ -0,0 +1,11 @@ +# This migration comes from spotlight (originally 20150304111111) +class AddFeaturedImageToSpotlightClasses < ActiveRecord::Migration + + def change + add_column :spotlight_searches, :masthead_id, :integer + add_column :spotlight_searches, :thumbnail_id, :integer + add_column :spotlight_exhibits, :masthead_id, :integer + add_column :spotlight_exhibits, :thumbnail_id, :integer + add_column :spotlight_pages, :thumbnail_id, :integer + end +end \ No newline at end of file diff --git a/db/migrate/20180319193636_add_display_to_spotlight_main_navigations.spotlight.rb b/db/migrate/20180319193636_add_display_to_spotlight_main_navigations.spotlight.rb new file mode 100644 index 0000000..059e39a --- /dev/null +++ b/db/migrate/20180319193636_add_display_to_spotlight_main_navigations.spotlight.rb @@ -0,0 +1,12 @@ +# This migration comes from spotlight (originally 20150306202300) +class AddDisplayToSpotlightMainNavigations < ActiveRecord::Migration + def up + add_column :spotlight_main_navigations, :display, :boolean, default: true + + Spotlight::MainNavigation.reset_column_information + Spotlight::MainNavigation.update_all display: true + end + def down + remove_column :spotlight_main_navigations, :display + end +end diff --git a/db/migrate/20180319193637_add_field_type_to_custom_fields.spotlight.rb b/db/migrate/20180319193637_add_field_type_to_custom_fields.spotlight.rb new file mode 100644 index 0000000..21b5f8a --- /dev/null +++ b/db/migrate/20180319193637_add_field_type_to_custom_fields.spotlight.rb @@ -0,0 +1,12 @@ +# This migration comes from spotlight (originally 20150313175432) +class AddFieldTypeToCustomFields < ActiveRecord::Migration + def up + add_column :spotlight_custom_fields, :field_type, :string + + Spotlight::CustomField.reset_column_information + Spotlight::CustomField.update_all field_type: 'text' + end + def down + remove_column :spotlight_custom_fields, :field_type + end +end diff --git a/db/migrate/20180319193638_add_taggings_counter_cache_to_tags.spotlight.rb b/db/migrate/20180319193638_add_taggings_counter_cache_to_tags.spotlight.rb new file mode 100644 index 0000000..7308092 --- /dev/null +++ b/db/migrate/20180319193638_add_taggings_counter_cache_to_tags.spotlight.rb @@ -0,0 +1,16 @@ +# This migration comes from spotlight (originally 20150410180014) +# This migration comes from acts_as_taggable_on_engine (originally 3) +class AddTaggingsCounterCacheToTags < ActiveRecord::Migration + def self.up + add_column :tags, :taggings_count, :integer, default: 0 + + ActsAsTaggableOn::Tag.reset_column_information + ActsAsTaggableOn::Tag.find_each do |tag| + ActsAsTaggableOn::Tag.reset_counters(tag.id, :taggings) + end + end + + def self.down + remove_column :tags, :taggings_count + end +end diff --git a/db/migrate/20180319193639_add_missing_taggable_index.spotlight.rb b/db/migrate/20180319193639_add_missing_taggable_index.spotlight.rb new file mode 100644 index 0000000..507135d --- /dev/null +++ b/db/migrate/20180319193639_add_missing_taggable_index.spotlight.rb @@ -0,0 +1,11 @@ +# This migration comes from spotlight (originally 20150410180015) +# This migration comes from acts_as_taggable_on_engine (originally 4) +class AddMissingTaggableIndex < ActiveRecord::Migration + def self.up + add_index :taggings, [:taggable_id, :taggable_type, :context] + end + + def self.down + remove_index :taggings, [:taggable_id, :taggable_type, :context] + end +end diff --git a/db/migrate/20180319193640_change_collation_for_tag_names.spotlight.rb b/db/migrate/20180319193640_change_collation_for_tag_names.spotlight.rb new file mode 100644 index 0000000..a7155b7 --- /dev/null +++ b/db/migrate/20180319193640_change_collation_for_tag_names.spotlight.rb @@ -0,0 +1,11 @@ +# This migration comes from spotlight (originally 20150410180016) +# This migration comes from acts_as_taggable_on_engine (originally 5) +# This migration is added to circumvent issue #623 and have special characters +# work properly +class ChangeCollationForTagNames < ActiveRecord::Migration + def up + if ActsAsTaggableOn::Utils.using_mysql? + execute("ALTER TABLE tags MODIFY name varchar(255) CHARACTER SET utf8 COLLATE utf8_bin;") + end + end +end diff --git a/db/migrate/20180319193641_change_spotlight_searches_to_published.spotlight.rb b/db/migrate/20180319193641_change_spotlight_searches_to_published.spotlight.rb new file mode 100644 index 0000000..306036c --- /dev/null +++ b/db/migrate/20180319193641_change_spotlight_searches_to_published.spotlight.rb @@ -0,0 +1,6 @@ +# This migration comes from spotlight (originally 20150713160101) +class ChangeSpotlightSearchesToPublished < ActiveRecord::Migration + def up + rename_column :spotlight_searches, :on_landing_page, :published + end +end diff --git a/db/migrate/20180319193642_remove_searchable_from_exhibit.spotlight.rb b/db/migrate/20180319193642_remove_searchable_from_exhibit.spotlight.rb new file mode 100644 index 0000000..ead84d1 --- /dev/null +++ b/db/migrate/20180319193642_remove_searchable_from_exhibit.spotlight.rb @@ -0,0 +1,21 @@ +# This migration comes from spotlight (originally 20151016092343) +class RemoveSearchableFromExhibit < ActiveRecord::Migration + def up + Spotlight::Exhibit.where(searchable: false).find_each do |e| + e.home_page.update(display_sidebar: false) + end + + Spotlight::Exhibit.where(searchable: true).find_each do |e| + key = e.blacklight_configuration.default_blacklight_config.default_search_field.key + + e.blacklight_configuration.search_fields[key] ||= {} + e.blacklight_configuration.search_fields[key][:enabled] = true + end + + remove_column :spotlight_exhibits, :searchable + end + + def down + add_column :spotlight_exhibits, :searchable, :boolean, default: true + end +end diff --git a/db/migrate/20180319193643_add_metadata_to_spotlight_resource.spotlight.rb b/db/migrate/20180319193643_add_metadata_to_spotlight_resource.spotlight.rb new file mode 100644 index 0000000..64621ea --- /dev/null +++ b/db/migrate/20180319193643_add_metadata_to_spotlight_resource.spotlight.rb @@ -0,0 +1,10 @@ +# This migration comes from spotlight (originally 20151110082345) +class AddMetadataToSpotlightResource < ActiveRecord::Migration + def up + add_column :spotlight_resources, :metadata, :binary + end + + def down + remove_column :spotlight_resources, :metadata + end +end diff --git a/db/migrate/20180319193644_change_spotlight_exhibit_published_default.spotlight.rb b/db/migrate/20180319193644_change_spotlight_exhibit_published_default.spotlight.rb new file mode 100644 index 0000000..3398557 --- /dev/null +++ b/db/migrate/20180319193644_change_spotlight_exhibit_published_default.spotlight.rb @@ -0,0 +1,6 @@ +# This migration comes from spotlight (originally 20151117153210) +class ChangeSpotlightExhibitPublishedDefault < ActiveRecord::Migration + def up + change_column :spotlight_exhibits, :published, :boolean, default: false + end +end \ No newline at end of file diff --git a/db/migrate/20180319193645_remove_default_from_spotlight_exhibit.spotlight.rb b/db/migrate/20180319193645_remove_default_from_spotlight_exhibit.spotlight.rb new file mode 100644 index 0000000..06898c0 --- /dev/null +++ b/db/migrate/20180319193645_remove_default_from_spotlight_exhibit.spotlight.rb @@ -0,0 +1,13 @@ +# This migration comes from spotlight (originally 20151124101123) +class RemoveDefaultFromSpotlightExhibit < ActiveRecord::Migration + def up + return unless Spotlight::Exhibit.column_names.include? 'default' + + remove_column :spotlight_exhibits, :default + end + + def down + add_column :spotlight_exhibits, :default, :boolean, default: true + add_index :spotlight_exhibits, :default, unique: true + end +end \ No newline at end of file diff --git a/db/migrate/20180319193646_update_custom_field_names.spotlight.rb b/db/migrate/20180319193646_update_custom_field_names.spotlight.rb new file mode 100644 index 0000000..70a0eb1 --- /dev/null +++ b/db/migrate/20180319193646_update_custom_field_names.spotlight.rb @@ -0,0 +1,32 @@ +# This migration comes from spotlight (originally 20151124105543) +class UpdateCustomFieldNames < ActiveRecord::Migration + def up + fields = {} + + Spotlight::CustomField.find_each do |f| + f.update(field: f.send(:field_name)) + fields[f.solr_field] = f + end + + Spotlight::SolrDocumentSidecar.find_each do |f| + f.data.select { |k, v| fields.has_key? k }.each do |k, v| + f.data[fields[k].send(:field_name)] = f.data.delete(k) + end + end + end + + def down + fields = {} + + Spotlight::CustomField.find_each do |f| + fields[f.field] = f + f.update(field: f.send(:solr_field)) + end + + Spotlight::SolrDocumentSidecar.find_each do |f| + f.data.select { |k, v| fields.has_key? k }.each do |k, v| + f.data[fields[k].send(:solr_field)] = f.data.delete(k) + end + end + end +end \ No newline at end of file diff --git a/db/migrate/20180319193647_add_weight_to_exhibits.spotlight.rb b/db/migrate/20180319193647_add_weight_to_exhibits.spotlight.rb new file mode 100644 index 0000000..e870bd9 --- /dev/null +++ b/db/migrate/20180319193647_add_weight_to_exhibits.spotlight.rb @@ -0,0 +1,6 @@ +# This migration comes from spotlight (originally 20151208085432) +class AddWeightToExhibits < ActiveRecord::Migration + def up + add_column :spotlight_exhibits, :weight, :integer, default: 50 + end +end diff --git a/db/migrate/20180319193648_create_spotlight_site.spotlight.rb b/db/migrate/20180319193648_create_spotlight_site.spotlight.rb new file mode 100644 index 0000000..0f255e7 --- /dev/null +++ b/db/migrate/20180319193648_create_spotlight_site.spotlight.rb @@ -0,0 +1,10 @@ +# This migration comes from spotlight (originally 20151210073829) +class CreateSpotlightSite < ActiveRecord::Migration + def change + create_table :spotlight_sites do |t| + t.string :title + t.string :subtitle + t.references :masthead + end + end +end diff --git a/db/migrate/20180319193649_add_site_to_spotlight_exhibits.spotlight.rb b/db/migrate/20180319193649_add_site_to_spotlight_exhibits.spotlight.rb new file mode 100644 index 0000000..2215f65 --- /dev/null +++ b/db/migrate/20180319193649_add_site_to_spotlight_exhibits.spotlight.rb @@ -0,0 +1,24 @@ +# This migration comes from spotlight (originally 20151211131415) +class AddSiteToSpotlightExhibits < ActiveRecord::Migration + def up + add_column :spotlight_exhibits, :site_id, :integer + add_index :spotlight_exhibits, :site_id + + add_default_site_to_exhibits + end + + def down + remove_column :spotlight_exhibits, :site_id, :integer + end + + private + + def add_default_site_to_exhibits + Spotlight::Site.reset_column_information + Spotlight::Exhibit.reset_column_information + + Spotlight::Exhibit.find_each do |e| + e.site = Spotlight::Site.instance + end + end +end diff --git a/db/migrate/20180319193650_change_roles_to_support_polymorphic_associations.spotlight.rb b/db/migrate/20180319193650_change_roles_to_support_polymorphic_associations.spotlight.rb new file mode 100644 index 0000000..6c7c787 --- /dev/null +++ b/db/migrate/20180319193650_change_roles_to_support_polymorphic_associations.spotlight.rb @@ -0,0 +1,42 @@ +# This migration comes from spotlight (originally 20151215141516) +class ChangeRolesToSupportPolymorphicAssociations < ActiveRecord::Migration + def up + add_column :spotlight_roles, :resource_id, :integer + add_column :spotlight_roles, :resource_type, :string + + migrate_role_data_to_polymorphic_resource + + remove_index :spotlight_roles, [:exhibit_id, :user_id] + remove_column :spotlight_roles, :exhibit_id + + add_index :spotlight_roles, [:resource_type, :resource_id, :user_id], unique: true, name: 'index_spotlight_roles_on_resource_and_user_id' + end + + def down + add_column :spotlight_roles, :exhibit_id, :integer + add_index(:spotlight_roles, [:exhibit_id]) + + Spotlight::Role.reset_column_information + + Spotlight::Role.find_each do |e| + e.update(exhibit_id: e.resource_id) if e.exhibit_id.nil? && e.resource_type == 'Spotlight::Exhibit' + end + + remove_index :spotlight_roles, name: 'index_spotlight_roles_on_resource_and_user_id' + + remove_column :spotlight_roles, :resource_id + remove_column :spotlight_roles, :resource_type + + add_index :spotlight_roles, [:exhibit_id, :user_id], unique: true + end + + private + + def migrate_role_data_to_polymorphic_resource + Spotlight::Role.reset_column_information + + Spotlight::Role.find_each do |e| + e.update(resource_id: e.exhibit_id, resource_type: 'Spotlight::Exhibit') unless e.resource_id + end + end +end diff --git a/db/migrate/20180319193651_add_index_status_to_resources.spotlight.rb b/db/migrate/20180319193651_add_index_status_to_resources.spotlight.rb new file mode 100644 index 0000000..3651351 --- /dev/null +++ b/db/migrate/20180319193651_add_index_status_to_resources.spotlight.rb @@ -0,0 +1,7 @@ +# This migration comes from spotlight (originally 20151215192845) +class AddIndexStatusToResources < ActiveRecord::Migration + def change + add_column :spotlight_resources, :index_status, :integer + add_index :spotlight_resources, :index_status + end +end diff --git a/db/migrate/20180319193652_create_spotlight_exhibit_filters.spotlight.rb b/db/migrate/20180319193652_create_spotlight_exhibit_filters.spotlight.rb new file mode 100644 index 0000000..aa31538 --- /dev/null +++ b/db/migrate/20180319193652_create_spotlight_exhibit_filters.spotlight.rb @@ -0,0 +1,18 @@ +# This migration comes from spotlight (originally 20151217211019) +class CreateSpotlightExhibitFilters < ActiveRecord::Migration + def change + create_table :spotlight_filters do |t| + t.string :field + t.string :value + t.references :exhibit, index: true + + t.timestamps null: false + end + + reversible do |change| + change.up do + Spotlight::Exhibit.all.each { |exhibit| exhibit.send(:initialize_filter) } + end + end + end +end diff --git a/db/migrate/20180419170009_add_exhibits_for_collections.rb b/db/migrate/20180419170009_add_exhibits_for_collections.rb new file mode 100644 index 0000000..d7137cf --- /dev/null +++ b/db/migrate/20180419170009_add_exhibits_for_collections.rb @@ -0,0 +1,8 @@ +class AddExhibitsForCollections < ActiveRecord::Migration + def change + add_column :spotlight_exhibits, :exhibitable_id, :string + add_column :spotlight_exhibits, :exhibitable_type, :string + add_index :spotlight_exhibits, :exhibitable_type + add_index :spotlight_exhibits, :exhibitable_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 71e07ff..0a4538c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151007130042) do +ActiveRecord::Schema.define(version: 20180419170009) do create_table "bookmarks", force: :cascade do |t| t.integer "user_id", null: false @@ -23,6 +23,7 @@ t.string "document_type" end + add_index "bookmarks", ["document_type", "document_id"], name: "index_bookmarks_on_document_type_and_document_id" add_index "bookmarks", ["user_id"], name: "index_bookmarks_on_user_id" create_table "checksum_audit_logs", force: :cascade do |t| @@ -38,6 +39,38 @@ add_index "checksum_audit_logs", ["file_set_id", "file_id"], name: "by_generic_file_id_and_file_id" + create_table "friendly_id_slugs", force: :cascade do |t| + t.string "slug", null: false + t.integer "sluggable_id", null: false + t.string "sluggable_type", limit: 50 + t.string "scope" + t.datetime "created_at" + end + + add_index "friendly_id_slugs", ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true + add_index "friendly_id_slugs", ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type" + add_index "friendly_id_slugs", ["sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_id" + add_index "friendly_id_slugs", ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type" + + create_table "qa_local_authorities", force: :cascade do |t| + t.string "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "qa_local_authorities", ["name"], name: "index_qa_local_authorities_on_name", unique: true + + create_table "qa_local_authority_entries", force: :cascade do |t| + t.integer "local_authority_id" + t.string "label" + t.string "uri" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "qa_local_authority_entries", ["local_authority_id"], name: "index_qa_local_authority_entries_on_local_authority_id" + add_index "qa_local_authority_entries", ["uri"], name: "index_qa_local_authority_entries_on_uri" + create_table "searches", force: :cascade do |t| t.text "query_params" t.integer "user_id" @@ -48,6 +81,253 @@ add_index "searches", ["user_id"], name: "index_searches_on_user_id" + create_table "spotlight_attachments", force: :cascade do |t| + t.string "name" + t.string "file" + t.string "uid" + t.integer "exhibit_id" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "spotlight_blacklight_configurations", force: :cascade do |t| + t.integer "exhibit_id" + t.text "facet_fields" + t.text "index_fields" + t.text "search_fields" + t.text "sort_fields" + t.text "default_solr_params" + t.text "show" + t.text "index" + t.integer "default_per_page" + t.text "per_page" + t.text "document_index_view_types" + t.string "thumbnail_size" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "spotlight_contact_emails", force: :cascade do |t| + t.integer "exhibit_id" + t.string "email", default: "", null: false + t.string "confirmation_token" + t.datetime "confirmed_at" + t.datetime "confirmation_sent_at" + t.string "unconfirmed_email" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "spotlight_contact_emails", ["confirmation_token"], name: "index_spotlight_contact_emails_on_confirmation_token", unique: true + add_index "spotlight_contact_emails", ["email", "exhibit_id"], name: "index_spotlight_contact_emails_on_email_and_exhibit_id", unique: true + + create_table "spotlight_contacts", force: :cascade do |t| + t.string "slug" + t.string "name" + t.string "email" + t.string "title" + t.string "location" + t.string "telephone" + t.boolean "show_in_sidebar" + t.integer "weight", default: 50 + t.integer "exhibit_id" + t.datetime "created_at" + t.datetime "updated_at" + t.text "contact_info" + t.string "avatar" + t.integer "avatar_crop_x" + t.integer "avatar_crop_y" + t.integer "avatar_crop_w" + t.integer "avatar_crop_h" + end + + add_index "spotlight_contacts", ["exhibit_id"], name: "index_spotlight_contacts_on_exhibit_id" + + create_table "spotlight_custom_fields", force: :cascade do |t| + t.integer "exhibit_id" + t.string "slug" + t.string "field" + t.text "configuration" + t.datetime "created_at" + t.datetime "updated_at" + t.string "field_type" + end + + create_table "spotlight_exhibits", force: :cascade do |t| + t.string "title", null: false + t.string "subtitle" + t.string "slug" + t.text "description" + t.datetime "created_at" + t.datetime "updated_at" + t.string "layout" + t.boolean "published", default: false + t.datetime "published_at" + t.string "featured_image" + t.integer "masthead_id" + t.integer "thumbnail_id" + t.integer "weight", default: 50 + t.integer "site_id" + t.string "exhibitable_id" + t.string "exhibitable_type" + end + + add_index "spotlight_exhibits", ["exhibitable_id"], name: "index_spotlight_exhibits_on_exhibitable_id" + add_index "spotlight_exhibits", ["exhibitable_type"], name: "index_spotlight_exhibits_on_exhibitable_type" + add_index "spotlight_exhibits", ["site_id"], name: "index_spotlight_exhibits_on_site_id" + add_index "spotlight_exhibits", ["slug"], name: "index_spotlight_exhibits_on_slug", unique: true + + create_table "spotlight_featured_images", force: :cascade do |t| + t.string "type" + t.boolean "display" + t.string "image" + t.string "source" + t.string "document_global_id" + t.integer "image_crop_x" + t.integer "image_crop_y" + t.integer "image_crop_w" + t.integer "image_crop_h" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "spotlight_filters", force: :cascade do |t| + t.string "field" + t.string "value" + t.integer "exhibit_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "spotlight_filters", ["exhibit_id"], name: "index_spotlight_filters_on_exhibit_id" + + create_table "spotlight_locks", force: :cascade do |t| + t.integer "on_id" + t.string "on_type" + t.integer "by_id" + t.string "by_type" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "spotlight_locks", ["on_id", "on_type"], name: "index_spotlight_locks_on_on_id_and_on_type", unique: true + + create_table "spotlight_main_navigations", force: :cascade do |t| + t.string "label" + t.integer "weight", default: 20 + t.string "nav_type" + t.integer "exhibit_id" + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "display", default: true + end + + add_index "spotlight_main_navigations", ["exhibit_id"], name: "index_spotlight_main_navigations_on_exhibit_id" + + create_table "spotlight_pages", force: :cascade do |t| + t.string "title" + t.string "type" + t.string "slug" + t.string "scope" + t.text "content" + t.integer "weight", default: 50 + t.boolean "published" + t.integer "exhibit_id" + t.integer "created_by_id" + t.integer "last_edited_by_id" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "parent_page_id" + t.boolean "display_sidebar" + t.boolean "display_title" + t.integer "thumbnail_id" + end + + add_index "spotlight_pages", ["exhibit_id"], name: "index_spotlight_pages_on_exhibit_id" + add_index "spotlight_pages", ["parent_page_id"], name: "index_spotlight_pages_on_parent_page_id" + add_index "spotlight_pages", ["slug", "scope"], name: "index_spotlight_pages_on_slug_and_scope", unique: true + + create_table "spotlight_resources", force: :cascade do |t| + t.integer "exhibit_id" + t.string "type" + t.string "url" + t.text "data" + t.datetime "indexed_at" + t.datetime "created_at" + t.datetime "updated_at" + t.binary "metadata" + t.integer "index_status" + end + + add_index "spotlight_resources", ["index_status"], name: "index_spotlight_resources_on_index_status" + + create_table "spotlight_roles", force: :cascade do |t| + t.integer "user_id" + t.string "role" + t.integer "resource_id" + t.string "resource_type" + end + + add_index "spotlight_roles", ["resource_type", "resource_id", "user_id"], name: "index_spotlight_roles_on_resource_and_user_id", unique: true + + create_table "spotlight_searches", force: :cascade do |t| + t.string "title" + t.string "slug" + t.string "scope" + t.text "short_description" + t.text "long_description" + t.text "query_params" + t.integer "weight" + t.boolean "published" + t.integer "exhibit_id" + t.datetime "created_at" + t.datetime "updated_at" + t.string "featured_item_id" + t.integer "masthead_id" + t.integer "thumbnail_id" + end + + add_index "spotlight_searches", ["exhibit_id"], name: "index_spotlight_searches_on_exhibit_id" + add_index "spotlight_searches", ["slug", "scope"], name: "index_spotlight_searches_on_slug_and_scope", unique: true + + create_table "spotlight_sites", force: :cascade do |t| + t.string "title" + t.string "subtitle" + t.integer "masthead_id" + end + + create_table "spotlight_solr_document_sidecars", force: :cascade do |t| + t.integer "exhibit_id" + t.boolean "public", default: true + t.text "data" + t.datetime "created_at" + t.datetime "updated_at" + t.string "document_id" + t.string "document_type" + end + + add_index "spotlight_solr_document_sidecars", ["exhibit_id"], name: "index_spotlight_solr_document_sidecars_on_exhibit_id" + + create_table "taggings", force: :cascade do |t| + t.integer "tag_id" + t.string "taggable_id" + t.string "taggable_type" + t.integer "tagger_id" + t.string "tagger_type" + t.string "context", limit: 128 + t.datetime "created_at" + end + + add_index "taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true + add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context" + + create_table "tags", force: :cascade do |t| + t.string "name" + t.integer "taggings_count", default: 0 + end + + add_index "tags", ["name"], name: "index_tags_on_name", unique: true + create_table "users", force: :cascade do |t| t.string "username", default: "", null: false t.string "encrypted_password", default: "", null: false @@ -75,4 +355,15 @@ t.datetime "updated_at" end + create_table "versions", force: :cascade do |t| + t.string "item_type", null: false + t.integer "item_id", null: false + t.string "event", null: false + t.string "whodunnit" + t.text "object", limit: 1073741823 + t.datetime "created_at" + end + + add_index "versions", ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id" + end diff --git a/db/seeds.rb b/db/seeds.rb index 4edb1e8..746a4fa 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -5,3 +5,6 @@ # # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) # Mayor.create(name: 'Emanuel', city: cities.first) + +User.where(username: 'test').first_or_create +Rake::Task['data:rights'].invoke diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml new file mode 100644 index 0000000..f8f35b1 --- /dev/null +++ b/docker-compose.ci.yml @@ -0,0 +1,22 @@ +version: '2' +services: + web: + image: "${REGISTRY_HOST}${REGISTRY_URI}:${TAG}" + environment: + - DOCKER_PORT + - PASSENGER_APP_ENV + - RAILS_ENV + - REGISTRY_HOST + - REGISTRY_URI + - TEST_DB + - SECRET_KEY_BASE + - SKIP_LDAP + command: bash -c "mkdir -p /home/app/webapp/tmp && chmod -R 1777 /home/app/webapp/tmp && /sbin/my_init" + ports: + - "80" + labels: + rap.host: web.${TAG}.staging.notch8network.com + rap.le_host: web.${TAG}.staging.notch8network.com + rap.le_test: true + io.rancher.container.pull_image: always + io.rancher.scheduler.affinity:host_label: target=moishe diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..09e4cf0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,46 @@ +version: '2' +services: + base: + build: + context: ./ + dockerfile: Dockerfile.base + image: "${REGISTRY_HOST}${REGISTRY_URI}/base:latest" + env_file: + - .env + - .env.development + + web: + build: + context: ./ + dockerfile: Dockerfile + image: "${REGISTRY_HOST}${REGISTRY_URI}:${TAG}" + volumes: + - .:/home/app/webapp + env_file: + - .env + - .env.development + # Keep the stdin open, so we can attach to our app container's process + # and do things such as byebug, etc: + stdin_open: true + # Enable sending signals (CTRL+C, CTRL+P + CTRL+Q) into the container: + tty: true + ports: + - "8080:80" + - "3003:3000" + depends_on: + - ldap + + ldap: + image: kingsquare/tunnel + command: "*:3389:lib-lswv125.wulib.wustl.edu:389 rob@old.notch8.com" + env_file: + - .env + - .env.development + volumes: + - "${HOME}/.ssh:/root/.ssh" + ports: + - '3389:3389' + +volumes: + ldap: + slapd: diff --git a/fits.log b/fits.log new file mode 100644 index 0000000..e69de29 diff --git a/lib/import/common_importer.rb b/lib/import/common_importer.rb index 85b81a3..62a338d 100644 --- a/lib/import/common_importer.rb +++ b/lib/import/common_importer.rb @@ -87,7 +87,7 @@ def create_record(attributes) attributes[:rights] = transform_rights(attributes) unless attributes[:rights].blank? - record = record_class.create!(attributes) do |r| + record = record_class.new(attributes) do |r| r.apply_depositor_metadata(user) end @@ -99,7 +99,10 @@ def create_record(attributes) } puts " Adding in order took %0.2fs" % time.real unless Rails.env.test? set_representative(record) + record.save! + record.reload + record.update_index end def attach_files(record, metadata_file, files) diff --git a/lib/tasks/ci.rake b/lib/tasks/ci.rake index 0794740..e8bc6c9 100644 --- a/lib/tasks/ci.rake +++ b/lib/tasks/ci.rake @@ -1,10 +1,25 @@ -require 'jettywrapper' +#require 'jettywrapper' -desc "Run the ci build" -task ci: ['jetty:clean', 'jetty:config'] do - jetty_params = Jettywrapper.load_config - Jettywrapper.wrap(jetty_params) do - # run the tests - Rake::Task["spec"].invoke +#desc "Run the ci build" +#task ci: ['jetty:clean', 'jetty:config'] do +# jetty_params = Jettywrapper.load_config +# Jettywrapper.wrap(jetty_params) do +# # run the tests +# Rake::Task["spec"].invoke +# end +#end +namespace :ci do + desc 'loads some sample data for review branches' + task :load_sample => :environment do + if(!File.exists?(Rails.root.join('sample-assets'))) + sh('wget https://s3-us-west-2.amazonaws.com/washington-u/sample-assets.tgz') + sh('tar zxfv sample-assets.tgz') + end + + set = AdminSet.last + set = AdminSet.create(title: 'sample', identifier: 'sample') unless set + sh("script/import -t text -a #{set.id} -p sample-assets/text") + sh("script/import -t video -a #{set.id} -p sample-assets/video") + sh("script/import -t image -a #{set.id} -p sample-assets/image") end end diff --git a/lib/tasks/data.rake b/lib/tasks/data.rake new file mode 100644 index 0000000..917e2e4 --- /dev/null +++ b/lib/tasks/data.rake @@ -0,0 +1,20 @@ +namespace :data do + desc "add existing licenses to database" + task :rights => :environment do + authority = Qa::LocalAuthority.where(name: 'rights').first_or_create + + [["http://creativecommons.org/licenses/by/3.0/us/","Attribution 3.0 United States"], + ["http://creativecommons.org/licenses/by-sa/3.0/us/","Attribution-ShareAlike 3.0 United States"], + ["http://creativecommons.org/licenses/by-nc/3.0/us/","Attribution-NonCommercial 3.0 United States"], + ["http://creativecommons.org/licenses/by-nd/3.0/us/","Attribution-NoDerivs 3.0 United States"], + ["http://creativecommons.org/licenses/by-nc-nd/3.0/us/","Attribution-NonCommercial-NoDerivs 3.0 United States"], + ["http://creativecommons.org/licenses/by-nc-sa/3.0/us/","Attribution-NonCommercial-ShareAlike 3.0 United States"], + ["http://creativecommons.org/publicdomain/mark/1.0/","Public Domain Mark 1.0"], + ["http://creativecommons.org/publicdomain/zero/1.0/","CC0 1.0 Universal"], + ["http://www.europeana.eu/portal/rights/rr-r.html","All rights reserved"]].each do |record| + Qa::LocalAuthorityEntry.where(local_authority: authority, + label: record[1], + uri: record[0]).first_or_create + end + end +end diff --git a/lib/tasks/jetty.rake b/lib/tasks/jetty.rake new file mode 100644 index 0000000..d6e8d39 --- /dev/null +++ b/lib/tasks/jetty.rake @@ -0,0 +1,19 @@ +begin + require 'jettywrapper' + + namespace :jetty do + desc "Copies the application's solr config into jetty" + task configure_solr: ['jetty:clean'] do + FileList['solr_conf/conf/*'].each do |f| + cp(f.to_s, 'jetty/solr/blacklight-core/conf/', verbose: true) + end + end + end +rescue LoadError + namespace :jetty do + desc "Copies the application's solr config into jetty (requires jettywrapper)" + task :configure_solr do + # no-op + end + end +end diff --git a/lib/xslt/goldenseal-eop-SSD-2.xslt b/lib/xslt/goldenseal-eop-SSD-2.xslt new file mode 100644 index 0000000..27a62f0 --- /dev/null +++ b/lib/xslt/goldenseal-eop-SSD-2.xslt @@ -0,0 +1,103 @@ + + + + + + +WEBVTT +kind: captions +lang: en + + + + + + + + + + .000 --> .000 + + + + <v > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/xslt/goldenseal-eop-SSD.xslt b/lib/xslt/goldenseal-eop-SSD.xslt new file mode 100644 index 0000000..7ee22c8 --- /dev/null +++ b/lib/xslt/goldenseal-eop-SSD.xslt @@ -0,0 +1,42 @@ + + + + + + +WEBVTT +kind: captions +lang: en + + + + + + + + + + .000 --> .000 + + + + <v > + + + + + + + + + + + + + + + diff --git a/lib/xslt/tei.diff b/lib/xslt/tei.diff new file mode 100644 index 0000000..72cc68a --- /dev/null +++ b/lib/xslt/tei.diff @@ -0,0 +1,23 @@ +3a4 +> xmlns:smil="http://www.w3.org/2001/SMIL20/" +10c11 +< WEBVTT +--- +> WEBVTT +13d13 +< +30,37c30 +< +< +< +< +< +< +< +< +--- +> +41d33 +< +101a94 +> diff --git a/lib/xslt/transcript_tei_to_vtt.orig.xslt b/lib/xslt/transcript_tei_to_vtt.orig.xslt new file mode 100644 index 0000000..3fe46d1 --- /dev/null +++ b/lib/xslt/transcript_tei_to_vtt.orig.xslt @@ -0,0 +1,94 @@ + + + + + + + WEBVTT +kind: captions +lang: en + + + + + + + + + .000 --> .000 + + + + <v > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/xslt/transcript_tei_to_vtt.smil.xslt b/lib/xslt/transcript_tei_to_vtt.smil.xslt new file mode 100644 index 0000000..c9ac576 --- /dev/null +++ b/lib/xslt/transcript_tei_to_vtt.smil.xslt @@ -0,0 +1,95 @@ + + + + + + + WEBVTT +kind: captions +lang: en + + + + + + + + + .000 --> .000 + + + + <v > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/xslt/transcript_tei_to_vtt.xslt b/lib/xslt/transcript_tei_to_vtt.xslt index 0989f95..7ee22c8 100644 --- a/lib/xslt/transcript_tei_to_vtt.xslt +++ b/lib/xslt/transcript_tei_to_vtt.xslt @@ -1,94 +1,42 @@ - WEBVTT +WEBVTT kind: captions lang: en + - + - .000 --> .000 + .000 --> .000 <v > - - - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - diff --git a/ops/deploy.retry b/ops/deploy.retry new file mode 100644 index 0000000..78a257a --- /dev/null +++ b/ops/deploy.retry @@ -0,0 +1 @@ +staging.CHANGEME diff --git a/ops/deploy.yml b/ops/deploy.yml new file mode 100644 index 0000000..99e84c2 --- /dev/null +++ b/ops/deploy.yml @@ -0,0 +1,14 @@ +--- +- name: Deploy + hosts: all + gather_facts: False + tasks: + - name: deploy app + delegate_to: 127.0.0.1 + uri: + url: "{{ deploy_url }}" + method: POST + return_content: yes + body: { "push_data": { "tag": "{{ tag }}" }, "repository": { "repo_name":"registry.gitlab.com/notch8/ror-site/{{ app_name | regex_replace('-staging', '') }}" }} + body_format: json + status_code: 200 diff --git a/ops/env.conf b/ops/env.conf new file mode 100644 index 0000000..2688622 --- /dev/null +++ b/ops/env.conf @@ -0,0 +1,23 @@ +env BUILD_DIR; +env ELASTICSEARCH_URL; +env MYSQL_DATABASE; +env MYSQL_HOST; +env MYSQL_PASSWORD; +env MYSQL_USER; +env PASSENGER_APP_ENV; +env RAILS_LOG_TO_STDOUT; +env REGISTRY_HOST; +env REGISTRY_URI; +env SECRET_KEY_BASE; +env SITE_URI; +env TAG; +env TEST_DB; +env VIRTUAL_PORT; +env FACEBOOK_APP_ID; +env FACEBOOK_SECRET; +env GOOGLE_OAUTH2_CLIENT_ID; +env GOOGLE_OAUTH2_CLIENT_SECRET; +env STRIPE_PLATFORM_CLIENT_ID; +env STRIPE_SECRET_KEY; +env STRIPE_PUBLISHABLE_KEY; +env SKIP_LDAP; \ No newline at end of file diff --git a/ops/hosts b/ops/hosts new file mode 100644 index 0000000..1fa0184 --- /dev/null +++ b/ops/hosts @@ -0,0 +1,29 @@ +[staging] +staging.tilma ansible_user=centos + +[production] +tilma ansible_user=ubuntu + +[servers:children] +staging +production + +[servers:vars] +registry_host=registry.gitlab.com +compose_dir=/var/www +project_name=ror-site + +[staging:vars] +tag=staging-latest +rancher_env=glass-canvas +deploy_env=staging +deploy_url=https://rancher.notch8.com/v1-webhooks/endpoint?key=6mNqSnMDJSR0KphZvEYbNhJ1jFwfKYChZv4bWFZs&projectId=1a17575 +app_name=tilma-staging + +[production:vars] +tag=production-latest +rancher_env=glass-canvas +environment=production +deploy_env=production +deploy_url=https://rancher.notch8.com/v1-webhooks/endpoint?key=uo55jpIzYTMpZ4VctNFmL02yyW9D1d0ZLeA66nwa&projectId=1a17575 +app_name=tilma \ No newline at end of file diff --git a/ops/nginx.sh b/ops/nginx.sh new file mode 100755 index 0000000..dcf21f6 --- /dev/null +++ b/ops/nginx.sh @@ -0,0 +1,32 @@ +#!/bin/bash +set -e +if [[ ! -e /var/log/nginx/error.log ]]; then + # The Nginx log forwarder might be sleeping and waiting + # until the error log becomes available. We restart it in + # 1 second so that it picks up the new log file quickly. + (sleep 1 && sv restart /etc/service/nginx-log-forwarder) +fi + +/sbin/setuser app /bin/bash -l -c 'cd /home/app/webapp && bundle exec rake jetty:start' + +if [ -z $PASSENGER_APP_ENV ] +then + export PASSENGER_APP_ENV=development +fi + +if [[ $PASSENGER_APP_ENV == "development" ]] +then + /sbin/setuser app /bin/bash -l -c 'cd /home/app/webapp && ./bin/setup copy_configs' +fi + +if [[ $PASSENGER_APP_ENV == "production" ]] || [[ $PASSENGER_APP_ENV == "staging" ]] +then + /sbin/setuser app /bin/bash -l -c 'cd /home/app/webapp && bundle exec rake db:migrate' +fi + +if [[ $PASSENGER_APP_ENV == "staging" ]] +then + /sbin/setuser app /bin/bash -l -c 'cd /home/app/webapp && bundle exec rake db:seed ci:load_sample' +fi + +exec /usr/sbin/nginx diff --git a/ops/provision.retry b/ops/provision.retry new file mode 100644 index 0000000..339f8b1 --- /dev/null +++ b/ops/provision.retry @@ -0,0 +1 @@ +tilma diff --git a/ops/provision.yml b/ops/provision.yml new file mode 100644 index 0000000..a42580e --- /dev/null +++ b/ops/provision.yml @@ -0,0 +1,14 @@ +--- +- name: Provision + hosts: all + gather_facts: False + tasks: + - name: Provision via rancher + shell: "cd .. && rancher up -p -d -u -s {{ app_name }} -f docker-compose.{{ deploy_env }}.yml -e .env.{{ app_name | regex_replace('-staging', '') }}.{{ deploy_env }}" + register: result + delegate_to: 127.0.0.1 + environment: + APP_NAME: "{{ app_name | regex_replace('-staging', '') }}" + TAG: "{{ tag }}" + RANCHER_ENVIRONMENT: "{{ rancher_env }}" + - debug: var=result.stdout_lines diff --git a/ops/redis.sh b/ops/redis.sh new file mode 100644 index 0000000..83efb2f --- /dev/null +++ b/ops/redis.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# `/sbin/setuser memcache` runs the given command as the user `memcache`. +# If you omit that part, the command will be run as root. + +exec redis-server diff --git a/ops/webapp.conf b/ops/webapp.conf new file mode 100644 index 0000000..dd739d9 --- /dev/null +++ b/ops/webapp.conf @@ -0,0 +1,15 @@ +server { + listen 80; + server_name _; + root /home/app/webapp/public; + client_body_in_file_only clean; + client_body_buffer_size 32K; + + client_max_body_size 0; + + sendfile on; + send_timeout 300s; + # The following deploys your Ruby/Python/Node.js/Meteor app on Passenger. + passenger_enabled on; + passenger_user app; +} diff --git a/ops/worker.sh b/ops/worker.sh new file mode 100644 index 0000000..8779d5f --- /dev/null +++ b/ops/worker.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# `/sbin/setuser memcache` runs the given command as the user `memcache`. +# If you omit that part, the command will be run as root. + +exec /sbin/setuser app /bin/bash -l -c 'cd /home/app/webapp && QUEUE=* VERBOSE=1 rake resque:work >>/var/log/worker.log 2>&1' diff --git a/public/assets/.sprockets-manifest-a3d960f9bec9532f8d93ff666b241756.json b/public/assets/.sprockets-manifest-a3d960f9bec9532f8d93ff666b241756.json new file mode 100644 index 0000000..93794be --- /dev/null +++ b/public/assets/.sprockets-manifest-a3d960f9bec9532f8d93ff666b241756.json @@ -0,0 +1 @@ +{"files":{"favicon-b4185c7dd08c0b4f2142c58bbcca322b3a01f50adc3413438b98be2b664bb4b6.ico":{"logical_path":"favicon.ico","mtime":"2018-01-24T19:57:32+00:00","size":1406,"digest":"b4185c7dd08c0b4f2142c58bbcca322b3a01f50adc3413438b98be2b664bb4b6","integrity":"sha256-tBhcfdCMC08hQsWLvMoyKzoB9QrcNBNDi5i+K2ZLtLY="},"spotlight/default_thumbnail-bbab04087becfd4290ed6cae8bb3ef4fdf2b86389fc11a59c14950cff0b6f256.jpg":{"logical_path":"spotlight/default_thumbnail.jpg","mtime":"2018-04-05T23:23:24+00:00","size":12228,"digest":"bbab04087becfd4290ed6cae8bb3ef4fdf2b86389fc11a59c14950cff0b6f256","integrity":"sha256-u6sECHvs/UKQ7Wyui7PvT98rhjifwRpZwUlQz/C28lY="},"Jcrop-752309673bbd9d9b2ba0bd58d0a7071a2c59c30e2824d85bcd2a3bc0a07ef1f1.gif":{"logical_path":"Jcrop.gif","mtime":"2018-04-05T23:23:30+00:00","size":329,"digest":"752309673bbd9d9b2ba0bd58d0a7071a2c59c30e2824d85bcd2a3bc0a07ef1f1","integrity":"sha256-dSMJZzu9nZsroL1Y0KcHGixZww4oJNhbzSo7wKB+8fE="},"header-shield-78e456088b25329419d1f98d7bb42e02c7f040fa5d1f51b4531418ec2d6c48d1.svg":{"logical_path":"header-shield.svg","mtime":"2018-02-07T22:44:10+00:00","size":6876,"digest":"78e456088b25329419d1f98d7bb42e02c7f040fa5d1f51b4531418ec2d6c48d1","integrity":"sha256-eORWCIslMpQZ0fmNe7QuAsfwQPpdH1G0UxQY7C1sSNE="},"wustl-4-color-rev-shield-10e4f3d11d829fc3609da279e5433a53e80d038c2cacfe9238e15f8bfb7a61d5.svg":{"logical_path":"wustl-4-color-rev-shield.svg","mtime":"2018-02-07T22:44:10+00:00","size":23119,"digest":"10e4f3d11d829fc3609da279e5433a53e80d038c2cacfe9238e15f8bfb7a61d5","integrity":"sha256-EOTz0R2Cn8NgnaJ55UM6U+gNA4wsrP6SOOFfi/t6YdU="},"application-2094a97c58f7b56052cadd89c9a44465dc78d23906e01558800790a76795a489.js":{"logical_path":"application.js","mtime":"2018-04-20T13:06:19+00:00","size":1134107,"digest":"2094a97c58f7b56052cadd89c9a44465dc78d23906e01558800790a76795a489","integrity":"sha256-IJSpfFj3tWBSyt2JyaREZdx40jkG4BVYgAeQp2eVpIk="},"application-9ee086d5c5c19d50bd12aa6c220a7817b0265ad1ed80a1b9ae93284535831732.css":{"logical_path":"application.css","mtime":"2018-04-20T13:06:19+00:00","size":555949,"digest":"9ee086d5c5c19d50bd12aa6c220a7817b0265ad1ed80a1b9ae93284535831732","integrity":"sha256-nuCG1cXBnVC9EqpsIgp4F7AmWtHtgKG5rpMoRTWDFzI="},"able-c95c94605ee1482ee47ffe411e4637dc77162ef1974e3fcb8c28569ec9224868.eot":{"logical_path":"able.eot","mtime":"2018-01-24T22:28:51+00:00","size":6104,"digest":"c95c94605ee1482ee47ffe411e4637dc77162ef1974e3fcb8c28569ec9224868","integrity":"sha256-yVyUYF7hSC7kf/5BHkY33HcWLvGXTj/LjChWnskiSGg="},"able-8627fd16fdeb04867dc51283898f117bd202e5a22ebfeea4167ade4655e0297b.svg":{"logical_path":"able.svg","mtime":"2018-01-24T22:28:51+00:00","size":15061,"digest":"8627fd16fdeb04867dc51283898f117bd202e5a22ebfeea4167ade4655e0297b","integrity":"sha256-hif9Fv3rBIZ9xRKDiY8Re9IC5aIuv+6kFnreRlXgKXs="},"able-e340a2f0b62e717f59993409cda5642c72d01609d8669ab344d571d27b2ae039.ttf":{"logical_path":"able.ttf","mtime":"2018-01-24T22:28:51+00:00","size":5952,"digest":"e340a2f0b62e717f59993409cda5642c72d01609d8669ab344d571d27b2ae039","integrity":"sha256-40Ci8LYucX9ZmTQJzaVkLHLQFgnYZpqzRNVx0nsq4Dk="},"able-5e30bdbb24182ec0f9b4c29faf64e441849042daf1be9062477d6cf7b4b621c4.woff":{"logical_path":"able.woff","mtime":"2018-01-24T22:28:51+00:00","size":4808,"digest":"5e30bdbb24182ec0f9b4c29faf64e441849042daf1be9062477d6cf7b4b621c4","integrity":"sha256-XjC9uyQYLsD5tMKfr2TkQYSQQtrxvpBiR31s97S2IcQ="},"blacklight/gallery/view-icon-masonry-94cb2f417ca00047d6e4603644bebaa67037ef9b3efcc9c95b42229c1c571345.png":{"logical_path":"blacklight/gallery/view-icon-masonry.png","mtime":"2018-04-05T23:23:31+00:00","size":1107,"digest":"94cb2f417ca00047d6e4603644bebaa67037ef9b3efcc9c95b42229c1c571345","integrity":"sha256-lMsvQXygAEfW5GA2RL66pnA375s+/MnJW0IinBxXE0U="},"blacklight/gallery/view-icon-masonry@2x-da61b9e430c6b3f007aaa8073c062b147c909746c254f0adb81eaf476dc1a26d.png":{"logical_path":"blacklight/gallery/view-icon-masonry@2x.png","mtime":"2018-04-05T23:23:31+00:00","size":1098,"digest":"da61b9e430c6b3f007aaa8073c062b147c909746c254f0adb81eaf476dc1a26d","integrity":"sha256-2mG55DDGs/AHqqgHPAYrFHyQl0bCVPCtuB6vR23Bom0="},"FontAwesome-7ed24c05432403117372891543f0cb6a7922100919e7ae077c1f3faf67658dc2.otf":{"logical_path":"FontAwesome.otf","mtime":"2018-03-07T18:05:34+00:00","size":109688,"digest":"7ed24c05432403117372891543f0cb6a7922100919e7ae077c1f3faf67658dc2","integrity":"sha256-ftJMBUMkAxFzcokVQ/DLankiEAkZ564HfB8/r2dljcI="},"fontawesome-webfont-e219ece8f4d3e4ac455ef31cd3a7c7b5057ea68a109937fc26b03c6e99ee9322.eot":{"logical_path":"fontawesome-webfont.eot","mtime":"2018-03-07T18:05:34+00:00","size":70807,"digest":"e219ece8f4d3e4ac455ef31cd3a7c7b5057ea68a109937fc26b03c6e99ee9322","integrity":"sha256-4hns6PTT5KxFXvMc06fHtQV+pooQmTf8JrA8bpnukyI="},"fontawesome-webfont-d67041fe5d50eef9ef671643968f7ce6b130eaaaaa2ce4d496b18d0a33aeb87b.svg":{"logical_path":"fontawesome-webfont.svg","mtime":"2018-03-07T18:05:34+00:00","size":365616,"digest":"d67041fe5d50eef9ef671643968f7ce6b130eaaaaa2ce4d496b18d0a33aeb87b","integrity":"sha256-1nBB/l1Q7vnvZxZDlo985rEw6qqqLOTUlrGNCjOuuHs="},"fontawesome-webfont-7b5a4320fba0d4c8f79327645b4b9cc875a2ec617a557e849b813918eb733499.ttf":{"logical_path":"fontawesome-webfont.ttf","mtime":"2018-03-07T18:05:34+00:00","size":142072,"digest":"7b5a4320fba0d4c8f79327645b4b9cc875a2ec617a557e849b813918eb733499","integrity":"sha256-e1pDIPug1Mj3kydkW0ucyHWi7GF6VX6Em4E5GOtzNJk="},"fontawesome-webfont-c812ddc9e475d3e65d68a6b3b589ce598a2a5babb7afc55477d59215c4a38a40.woff":{"logical_path":"fontawesome-webfont.woff","mtime":"2018-03-07T18:05:34+00:00","size":83588,"digest":"c812ddc9e475d3e65d68a6b3b589ce598a2a5babb7afc55477d59215c4a38a40","integrity":"sha256-yBLdyeR10+ZdaKaztYnOWYoqW6u3r8VUd9WSFcSjikA="},"fontawesome-webfont-ff82aeed6b9bb6701696c84d1b223d2e682eb78c89117a438ce6cfea8c498995.woff2":{"logical_path":"fontawesome-webfont.woff2","mtime":"2018-03-07T18:05:34+00:00","size":66624,"digest":"ff82aeed6b9bb6701696c84d1b223d2e682eb78c89117a438ce6cfea8c498995","integrity":"sha256-/4Ku7WubtnAWlshNGyI9Lmgut4yJEXpDjObP6oxJiZU="},"sprites/social-share-button-01bd4e9a7782620aab986c55abc6ecb9ee88d50bb51f28c2f00fe36d87a0944c.png":{"logical_path":"sprites/social-share-button.png","mtime":"2018-04-05T23:23:30+00:00","size":8006,"digest":"01bd4e9a7782620aab986c55abc6ecb9ee88d50bb51f28c2f00fe36d87a0944c","integrity":"sha256-Ab1OmneCYgqrmGxVq8bsue6I1Qu1HyjC8A/jbYeglEw="},"sprites/social-share-button@2x-a597120b30fe6ae4ac5b4f0f45f307c0f5519851529b3010a7c584eb3ed186be.png":{"logical_path":"sprites/social-share-button@2x.png","mtime":"2018-04-05T23:23:30+00:00","size":17215,"digest":"a597120b30fe6ae4ac5b4f0f45f307c0f5519851529b3010a7c584eb3ed186be","integrity":"sha256-pZcSCzD+auSsW08PRfMHwPVRmFFSmzAQp8WE6z7Rhr4="},"spotlight/blocks/browse-on-8f7e560fe4b2ee7e8762f477821ae746af698ce9b91bdfa9e19e9d45e7bb06a1.png":{"logical_path":"spotlight/blocks/browse-on.png","mtime":"2018-04-05T23:23:24+00:00","size":681,"digest":"8f7e560fe4b2ee7e8762f477821ae746af698ce9b91bdfa9e19e9d45e7bb06a1","integrity":"sha256-j35WD+Sy7n6HYvR3ghrnRq9pjOm5G9+p4Z6dRee7BqE="},"spotlight/blocks/browse-8b0b9cd7cd3905826df76c7c6abbd84a5aed6a62c6e660ed4933a765253b6489.png":{"logical_path":"spotlight/blocks/browse.png","mtime":"2018-04-05T23:23:24+00:00","size":645,"digest":"8b0b9cd7cd3905826df76c7c6abbd84a5aed6a62c6e660ed4933a765253b6489","integrity":"sha256-iwuc1805BYJt92x8arvYSlrtamLG5mDtSTOnZSU7ZIk="},"spotlight/blocks/features-on-33817abee1a540e7f2a8729a3dc27b7c6c8379fa7777ab5f6119e942af263291.png":{"logical_path":"spotlight/blocks/features-on.png","mtime":"2018-04-05T23:23:24+00:00","size":592,"digest":"33817abee1a540e7f2a8729a3dc27b7c6c8379fa7777ab5f6119e942af263291","integrity":"sha256-M4F6vuGlQOfyqHKaPcJ7fGyDefp3d6tfYRnpQq8mMpE="},"spotlight/blocks/features-eadadd56461f682fd4c6cd787748540d474f432af0cd682f2a1684fba0f48ed0.png":{"logical_path":"spotlight/blocks/features.png","mtime":"2018-04-05T23:23:24+00:00","size":553,"digest":"eadadd56461f682fd4c6cd787748540d474f432af0cd682f2a1684fba0f48ed0","integrity":"sha256-6trdVkYfaC/Uxs14d0hUDUdPQyrwzWgvKhaE+6D0jtA="},"spotlight/blocks/item-carousel-on-a26f4a2bb6b8b502b84fc621c5d6afae9f5e646ae7c2d6d6dccd8e973e754613.png":{"logical_path":"spotlight/blocks/item-carousel-on.png","mtime":"2018-04-05T23:23:24+00:00","size":988,"digest":"a26f4a2bb6b8b502b84fc621c5d6afae9f5e646ae7c2d6d6dccd8e973e754613","integrity":"sha256-om9KK7a4tQK4T8Yhxdavrp9eZGrnwtbW3M2Olz51RhM="},"spotlight/blocks/item-carousel-33724f9604397c7be1d43271f96645909240f7db1e9e7c7fb7928898667fe738.png":{"logical_path":"spotlight/blocks/item-carousel.png","mtime":"2018-04-05T23:23:24+00:00","size":993,"digest":"33724f9604397c7be1d43271f96645909240f7db1e9e7c7fb7928898667fe738","integrity":"sha256-M3JPlgQ5fHvh1DJx+WZFkJJA99sennx/t5KImGZ/5zg="},"spotlight/blocks/item-embed-on-5448bd656f848d0c13f5333af9a88938cf05c470dd60aa4a07452493a644d6fd.png":{"logical_path":"spotlight/blocks/item-embed-on.png","mtime":"2018-04-05T23:23:24+00:00","size":885,"digest":"5448bd656f848d0c13f5333af9a88938cf05c470dd60aa4a07452493a644d6fd","integrity":"sha256-VEi9ZW+EjQwT9TM6+aiJOM8FxHDdYKpKB0Ukk6ZE1v0="},"spotlight/blocks/item-embed-730e3245393053e34def3835e5a9bc19a81e943a005c9d6139f340137d1eeaad.png":{"logical_path":"spotlight/blocks/item-embed.png","mtime":"2018-04-05T23:23:24+00:00","size":886,"digest":"730e3245393053e34def3835e5a9bc19a81e943a005c9d6139f340137d1eeaad","integrity":"sha256-cw4yRTkwU+NN7zg15am8GagelDoAXJ1hOfNAE30e6q0="},"spotlight/blocks/item-grid-on-6a6ed8e9048e9c3e541e5ffe4efd55cef6e5be6b5852ba499c47a8bf3c77a99d.png":{"logical_path":"spotlight/blocks/item-grid-on.png","mtime":"2018-04-05T23:23:24+00:00","size":229,"digest":"6a6ed8e9048e9c3e541e5ffe4efd55cef6e5be6b5852ba499c47a8bf3c77a99d","integrity":"sha256-am7Y6QSOnD5UHl/+Tv1VzvblvmtYUrpJnEeovzx3qZ0="},"spotlight/blocks/item-grid-a859821315998fa5248a63d99062b0dfaf3e939b5df343d9c423dbe1cad805e5.png":{"logical_path":"spotlight/blocks/item-grid.png","mtime":"2018-04-05T23:23:24+00:00","size":229,"digest":"a859821315998fa5248a63d99062b0dfaf3e939b5df343d9c423dbe1cad805e5","integrity":"sha256-qFmCExWZj6UkimPZkGKw368+k5td80PZxCPb4crYBeU="},"spotlight/blocks/item-row-on-88c16d2a584c982285694ae46cd6fd7398db1c1bab06d8fe6150c184ed2b4968.png":{"logical_path":"spotlight/blocks/item-row-on.png","mtime":"2018-04-05T23:23:24+00:00","size":498,"digest":"88c16d2a584c982285694ae46cd6fd7398db1c1bab06d8fe6150c184ed2b4968","integrity":"sha256-iMFtKlhMmCKFaUrkbNb9c5jbHBurBtj+YVDBhO0rSWg="},"spotlight/blocks/item-row-abc07b0fed0b6fe5ddacc0d1de17b9b6bf6b5a76fceaad412433b9a5ef2376f7.png":{"logical_path":"spotlight/blocks/item-row.png","mtime":"2018-04-05T23:23:24+00:00","size":517,"digest":"abc07b0fed0b6fe5ddacc0d1de17b9b6bf6b5a76fceaad412433b9a5ef2376f7","integrity":"sha256-q8B7D+0Lb+XdrMDR3he5tr9rWnb86q1BJDO5pe8jdvc="},"spotlight/blocks/item-slideshow-on-2a62fb7bbb4ad1c1319839c58a84af866fb7d9f987b2ca01e0e7a286ad880978.png":{"logical_path":"spotlight/blocks/item-slideshow-on.png","mtime":"2018-04-05T23:23:24+00:00","size":830,"digest":"2a62fb7bbb4ad1c1319839c58a84af866fb7d9f987b2ca01e0e7a286ad880978","integrity":"sha256-KmL7e7tK0cExmDnFioSvhm+32fmHssoB4Oeihq2ICXg="},"spotlight/blocks/item-slideshow-a260145061a6ec0ea767138044b89ea9d3b60ecdfa1061e298ef256f08c65985.png":{"logical_path":"spotlight/blocks/item-slideshow.png","mtime":"2018-04-05T23:23:24+00:00","size":838,"digest":"a260145061a6ec0ea767138044b89ea9d3b60ecdfa1061e298ef256f08c65985","integrity":"sha256-omAUUGGm7A6nZxOARLieqdO2Ds36EGHimO8lbwjGWYU="},"spotlight/blocks/results-on-7372bc50631094ed9565af8939bd0e84c79eac601256bf7bb12c934f3801304e.png":{"logical_path":"spotlight/blocks/results-on.png","mtime":"2018-04-05T23:23:24+00:00","size":227,"digest":"7372bc50631094ed9565af8939bd0e84c79eac601256bf7bb12c934f3801304e","integrity":"sha256-c3K8UGMQlO2VZa+JOb0OhMeerGASVr97sSyTTzgBME4="},"spotlight/blocks/results-018ba18b7ef629ac3d13e8ea40e59635048b120ea883df661a36f3807f78ffa2.png":{"logical_path":"spotlight/blocks/results.png","mtime":"2018-04-05T23:23:24+00:00","size":227,"digest":"018ba18b7ef629ac3d13e8ea40e59635048b120ea883df661a36f3807f78ffa2","integrity":"sha256-AYuhi372Kaw9E+jqQOWWNQSLEg6og99mGjbzgH94/6I="},"spotlight/blocks/rule-on-1e16507d085e86ae0ed26435f3264c6091b8559263492b293ce887c9f1ac80bb.png":{"logical_path":"spotlight/blocks/rule-on.png","mtime":"2018-04-05T23:23:24+00:00","size":98,"digest":"1e16507d085e86ae0ed26435f3264c6091b8559263492b293ce887c9f1ac80bb","integrity":"sha256-HhZQfQhehq4O0mQ18yZMYJG4VZJjSSspPOiHyfGsgLs="},"spotlight/blocks/rule-054dc438cdb28345f0d7f2c4caaf9a1483f3a6d68e3cb89f6d9906dccf03bb4c.png":{"logical_path":"spotlight/blocks/rule.png","mtime":"2018-04-05T23:23:24+00:00","size":98,"digest":"054dc438cdb28345f0d7f2c4caaf9a1483f3a6d68e3cb89f6d9906dccf03bb4c","integrity":"sha256-BU3EOM2yg0Xw1/LEyq+aFIPzptaOPLifbZkG3M8Du0w="},"spotlight/blocks/text-on-a5166a120f28bd7bcbb7b2e2ef4bc9c2ae2280fe4d57b192d48438b31e2b751e.png":{"logical_path":"spotlight/blocks/text-on.png","mtime":"2018-04-05T23:23:24+00:00","size":169,"digest":"a5166a120f28bd7bcbb7b2e2ef4bc9c2ae2280fe4d57b192d48438b31e2b751e","integrity":"sha256-pRZqEg8ovXvLt7Li70vJwq4igP5NV7GS1IQ4sx4rdR4="},"spotlight/blocks/text-e216b456f53225e8b8bbd26baf5e842f14841ffaa34715cd1e84c83d23062dd3.png":{"logical_path":"spotlight/blocks/text.png","mtime":"2018-04-05T23:23:24+00:00","size":167,"digest":"e216b456f53225e8b8bbd26baf5e842f14841ffaa34715cd1e84c83d23062dd3","integrity":"sha256-4ha0VvUyJei4u9Jrr16ELxSEH/qjRxXNHoTIPSMGLdM="},"spotlight/application-465d645b877c64cfd8235c7bdfc645df1ddb3e6b404b023d0ee4a4d6aae6217d.js":{"logical_path":"spotlight/application.js","mtime":"2018-04-20T13:06:19+00:00","size":559636,"digest":"465d645b877c64cfd8235c7bdfc645df1ddb3e6b404b023d0ee4a4d6aae6217d","integrity":"sha256-Rl1kW4d8ZM/YI1x738ZF3x3bPmtASwI9DuSk1qrmIX0="},"spotlight/application-bad33562d35ae6246b441c0f4c630c9605c6fb27d534acea17ca6715b0f0636b.css":{"logical_path":"spotlight/application.css","mtime":"2018-04-20T13:06:19+00:00","size":265270,"digest":"bad33562d35ae6246b441c0f4c630c9605c6fb27d534acea17ca6715b0f0636b","integrity":"sha256-utM1YtNa5iRrRBwPTGMMlgXG+yfVNKzqF8pnFbDwY2s="},"openseadragon/fullpage_grouphover-8d9b60badcdd45f9c538b4014fa52130f91fdf7ee8d4cc43d72b7cfc5b475469.png":{"logical_path":"openseadragon/fullpage_grouphover.png","mtime":"2018-01-24T19:57:33+00:00","size":4907,"digest":"8d9b60badcdd45f9c538b4014fa52130f91fdf7ee8d4cc43d72b7cfc5b475469","integrity":"sha256-jZtgutzdRfnFOLQBT6UhMPkf337o1MxD1yt8/FtHVGk="},"openseadragon/fullpage_hover-64ceaf34eac330c7d6b3e71a4830cdda50db9705d84c6bbf080dfbcc2173c439.png":{"logical_path":"openseadragon/fullpage_hover.png","mtime":"2018-01-24T19:57:33+00:00","size":5214,"digest":"64ceaf34eac330c7d6b3e71a4830cdda50db9705d84c6bbf080dfbcc2173c439","integrity":"sha256-ZM6vNOrDMMfWs+caSDDN2lDblwXYTGu/CA37zCFzxDk="},"openseadragon/fullpage_pressed-0e745969d9ca01c2aa519d6c3f3d2dc5bc5fa466602f2034aa6e678960db71ba.png":{"logical_path":"openseadragon/fullpage_pressed.png","mtime":"2018-01-24T19:57:33+00:00","size":5213,"digest":"0e745969d9ca01c2aa519d6c3f3d2dc5bc5fa466602f2034aa6e678960db71ba","integrity":"sha256-DnRZadnKAcKqUZ1sPz0txbxfpGZgLyA0qm5niWDbcbo="},"openseadragon/fullpage_rest-4d29556b0d6220897ce665f683e17235603019826d0eccc573f1e1d04c08477e.png":{"logical_path":"openseadragon/fullpage_rest.png","mtime":"2018-01-24T19:57:33+00:00","size":5155,"digest":"4d29556b0d6220897ce665f683e17235603019826d0eccc573f1e1d04c08477e","integrity":"sha256-TSlVaw1iIIl85mX2g+FyNWAwGYJtDszFc/Hh0EwIR34="},"openseadragon/home_grouphover-9252742700d9f302a270f1942291a2a355e69da7a85118542f012001e943ef0e.png":{"logical_path":"openseadragon/home_grouphover.png","mtime":"2018-01-24T19:57:33+00:00","size":4808,"digest":"9252742700d9f302a270f1942291a2a355e69da7a85118542f012001e943ef0e","integrity":"sha256-klJ0JwDZ8wKicPGUIpGio1XmnaeoURhULwEgAelD7w4="},"openseadragon/home_hover-09e5b9167e7a4ab330e598df0b69a4795e8cdefbd79f35b62b1b57c10c83ea97.png":{"logical_path":"openseadragon/home_hover.png","mtime":"2018-01-24T19:57:33+00:00","size":5107,"digest":"09e5b9167e7a4ab330e598df0b69a4795e8cdefbd79f35b62b1b57c10c83ea97","integrity":"sha256-CeW5Fn56SrMw5ZjfC2mkeV6M3vvXnzW2KxtXwQyD6pc="},"openseadragon/home_pressed-d4abc088b08320251dc2d0df8c2665907abd347d2123123748551e29ea59f633.png":{"logical_path":"openseadragon/home_pressed.png","mtime":"2018-01-24T19:57:33+00:00","size":5138,"digest":"d4abc088b08320251dc2d0df8c2665907abd347d2123123748551e29ea59f633","integrity":"sha256-1KvAiLCDICUdwtDfjCZlkHq9NH0hIxI3SFUeKepZ9jM="},"openseadragon/home_rest-8e280d6b63992033b781d1b1779fee5eef9c0d6666297ba1ef8bc5dfa58bf26f.png":{"logical_path":"openseadragon/home_rest.png","mtime":"2018-01-24T19:57:33+00:00","size":5061,"digest":"8e280d6b63992033b781d1b1779fee5eef9c0d6666297ba1ef8bc5dfa58bf26f","integrity":"sha256-jigNa2OZIDO3gdGxd5/uXu+cDWZmKXuh74vF36WL8m8="},"openseadragon/next_grouphover-20b59567ad9b95237afdf4f7e0a996c88f2f293053fdc9a78d19ff473eec420d.png":{"logical_path":"openseadragon/next_grouphover.png","mtime":"2018-01-24T19:57:33+00:00","size":3004,"digest":"20b59567ad9b95237afdf4f7e0a996c88f2f293053fdc9a78d19ff473eec420d","integrity":"sha256-ILWVZ62blSN6/fT34KmWyI8vKTBT/cmnjRn/Rz7sQg0="},"openseadragon/next_hover-0ff290cd4ea121e4fe08f3a8a06339568bb0fa1cb4c0031b88fe9b58fc278407.png":{"logical_path":"openseadragon/next_hover.png","mtime":"2018-01-24T19:57:33+00:00","size":3433,"digest":"0ff290cd4ea121e4fe08f3a8a06339568bb0fa1cb4c0031b88fe9b58fc278407","integrity":"sha256-D/KQzU6hIeT+CPOooGM5Vouw+hy0wAMbiP6bWPwnhAc="},"openseadragon/next_pressed-c6091636705e293569ff695484214879e6c04f9cd9fa56ed603b803446e8127f.png":{"logical_path":"openseadragon/next_pressed.png","mtime":"2018-01-24T19:57:33+00:00","size":3503,"digest":"c6091636705e293569ff695484214879e6c04f9cd9fa56ed603b803446e8127f","integrity":"sha256-xgkWNnBeKTVp/2lUhCFIeebAT5zZ+lbtYDuANEboEn8="},"openseadragon/next_rest-d71177afcd7ed4baccfeae321c345e39cda547477555db18b42d361009fbaf06.png":{"logical_path":"openseadragon/next_rest.png","mtime":"2018-01-24T19:57:33+00:00","size":3061,"digest":"d71177afcd7ed4baccfeae321c345e39cda547477555db18b42d361009fbaf06","integrity":"sha256-1xF3r81+1LrM/q4yHDReOc2lR0d1VdsYtC02EAn7rwY="},"openseadragon/previous_grouphover-d88299bd9f4175c83a620ab91e4db134eaebcbbe8e5a6cf3ea3de8ba7878851c.png":{"logical_path":"openseadragon/previous_grouphover.png","mtime":"2018-01-24T19:57:33+00:00","size":2987,"digest":"d88299bd9f4175c83a620ab91e4db134eaebcbbe8e5a6cf3ea3de8ba7878851c","integrity":"sha256-2IKZvZ9Bdcg6Ygq5Hk2xNOrry76OWmzz6j3ounh4hRw="},"openseadragon/previous_hover-53287f3ced64ec0cafbd02eebf2253936d0ae51832ba92392d6ea62ab9072f70.png":{"logical_path":"openseadragon/previous_hover.png","mtime":"2018-01-24T19:57:33+00:00","size":3461,"digest":"53287f3ced64ec0cafbd02eebf2253936d0ae51832ba92392d6ea62ab9072f70","integrity":"sha256-Uyh/PO1k7AyvvQLuvyJTk20K5RgyupI5LW6mKrkHL3A="},"openseadragon/previous_pressed-319b4e58132403c9c88c113127d86ce50e8284d1b4caad303ce20c53537ff795.png":{"logical_path":"openseadragon/previous_pressed.png","mtime":"2018-01-24T19:57:33+00:00","size":3499,"digest":"319b4e58132403c9c88c113127d86ce50e8284d1b4caad303ce20c53537ff795","integrity":"sha256-MZtOWBMkA8nIjBExJ9hs5Q6ChNG0yq0wPOIMU1N/95U="},"openseadragon/previous_rest-aecdb486d75814f721c1eb3830a2f8a12780227c8cd76d2c16ba9af5912dba42.png":{"logical_path":"openseadragon/previous_rest.png","mtime":"2018-01-24T19:57:33+00:00","size":3064,"digest":"aecdb486d75814f721c1eb3830a2f8a12780227c8cd76d2c16ba9af5912dba42","integrity":"sha256-rs20htdYFPchwes4MKL4oSeAInyM120sFrqa9ZEtukI="},"openseadragon/rotateleft_grouphover-39647145d396d1c41c9921d5e322b43c44ff124fcbdd03063bbb7d6c4a02b9a0.png":{"logical_path":"openseadragon/rotateleft_grouphover.png","mtime":"2018-01-24T19:57:33+00:00","size":1902,"digest":"39647145d396d1c41c9921d5e322b43c44ff124fcbdd03063bbb7d6c4a02b9a0","integrity":"sha256-OWRxRdOW0cQcmSHV4yK0PET/Ek/L3QMGO7t9bEoCuaA="},"openseadragon/rotateleft_hover-fe8c9d624686f71582acfae033a985cf91423fedbe966c4c2a62ca729b2f2dea.png":{"logical_path":"openseadragon/rotateleft_hover.png","mtime":"2018-01-24T19:57:33+00:00","size":2289,"digest":"fe8c9d624686f71582acfae033a985cf91423fedbe966c4c2a62ca729b2f2dea","integrity":"sha256-/oydYkaG9xWCrPrgM6mFz5FCP+2+lmxMKmLKcpsvLeo="},"openseadragon/rotateleft_pressed-e48f1502559b90bb3f76afcde211bd3bd77c8cf0493ba529fa9221ae4680801c.png":{"logical_path":"openseadragon/rotateleft_pressed.png","mtime":"2018-01-24T19:57:33+00:00","size":2242,"digest":"e48f1502559b90bb3f76afcde211bd3bd77c8cf0493ba529fa9221ae4680801c","integrity":"sha256-5I8VAlWbkLs/dq/N4hG9O9d8jPBJO6Up+pIhrkaAgBw="},"openseadragon/rotateleft_rest-a4abb092fd829ca5ff951404c96adecc4cb7238e2e3f03408fc4593a4e00132a.png":{"logical_path":"openseadragon/rotateleft_rest.png","mtime":"2018-01-24T19:57:33+00:00","size":1943,"digest":"a4abb092fd829ca5ff951404c96adecc4cb7238e2e3f03408fc4593a4e00132a","integrity":"sha256-pKuwkv2CnKX/lRQEyWrezEy3I44uPwNAj8RZOk4AEyo="},"openseadragon/rotateright_grouphover-46b13721e6b60052ce39ce6eb695e8172252f1e640466bfe37c3fd4099aa9ee8.png":{"logical_path":"openseadragon/rotateright_grouphover.png","mtime":"2018-01-24T19:57:33+00:00","size":1970,"digest":"46b13721e6b60052ce39ce6eb695e8172252f1e640466bfe37c3fd4099aa9ee8","integrity":"sha256-RrE3Iea2AFLOOc5utpXoFyJS8eZARmv+N8P9QJmqnug="},"openseadragon/rotateright_hover-8f2c8885273e8e53b228c60c3793c4dba9a0b718058c4eb72f3170a04c99869d.png":{"logical_path":"openseadragon/rotateright_hover.png","mtime":"2018-01-24T19:57:33+00:00","size":2338,"digest":"8f2c8885273e8e53b228c60c3793c4dba9a0b718058c4eb72f3170a04c99869d","integrity":"sha256-jyyIhSc+jlOyKMYMN5PE26mgtxgFjE63LzFwoEyZhp0="},"openseadragon/rotateright_pressed-401e698877b40bcc77ede57153d7a43de0512afe2020a5ba4803cebb067f44b9.png":{"logical_path":"openseadragon/rotateright_pressed.png","mtime":"2018-01-24T19:57:33+00:00","size":2228,"digest":"401e698877b40bcc77ede57153d7a43de0512afe2020a5ba4803cebb067f44b9","integrity":"sha256-QB5piHe0C8x37eVxU9ekPeBRKv4gIKW6SAPOuwZ/RLk="},"openseadragon/rotateright_rest-fdbb33173a802d6d41d76e6ba6f9b77d1ed796f4cd5375e3ba554d5584fc2458.png":{"logical_path":"openseadragon/rotateright_rest.png","mtime":"2018-01-24T19:57:33+00:00","size":1961,"digest":"fdbb33173a802d6d41d76e6ba6f9b77d1ed796f4cd5375e3ba554d5584fc2458","integrity":"sha256-/bszFzqALW1B125rpvm3fR7XlvTNU3XjulVNVYT8JFg="},"openseadragon/zoomin_grouphover-e3e58ff9290bc54ad24947257e4c3e75fb5616aa8f46b4f2eb66e2982c93eec0.png":{"logical_path":"openseadragon/zoomin_grouphover.png","mtime":"2018-01-24T19:57:33+00:00","size":4794,"digest":"e3e58ff9290bc54ad24947257e4c3e75fb5616aa8f46b4f2eb66e2982c93eec0","integrity":"sha256-4+WP+SkLxUrSSUclfkw+dftWFqqPRrTy62bimCyT7sA="},"openseadragon/zoomin_hover-398e5e0b4386b8569cd3efa4efd846aab552a272a050dfb218b2bb792fed0920.png":{"logical_path":"openseadragon/zoomin_hover.png","mtime":"2018-01-24T19:57:33+00:00","size":5126,"digest":"398e5e0b4386b8569cd3efa4efd846aab552a272a050dfb218b2bb792fed0920","integrity":"sha256-OY5eC0OGuFac0++k79hGqrVSonKgUN+yGLK7eS/tCSA="},"openseadragon/zoomin_pressed-2adc36279b0d5f7baebc0e347c03e204b599d8e5733963674a5b209b6f0a7b11.png":{"logical_path":"openseadragon/zoomin_pressed.png","mtime":"2018-01-24T19:57:33+00:00","size":5172,"digest":"2adc36279b0d5f7baebc0e347c03e204b599d8e5733963674a5b209b6f0a7b11","integrity":"sha256-Ktw2J5sNX3uuvA40fAPiBLWZ2OVzOWNnSlsgm28KexE="},"openseadragon/zoomin_rest-0cfe30647a634c60a68fa868d67c488f60e6f78ce2bf61effce1c25c13f1d0bc.png":{"logical_path":"openseadragon/zoomin_rest.png","mtime":"2018-01-24T19:57:33+00:00","size":5041,"digest":"0cfe30647a634c60a68fa868d67c488f60e6f78ce2bf61effce1c25c13f1d0bc","integrity":"sha256-DP4wZHpjTGCmj6ho1nxIj2Dm94ziv2Hv/OHCXBPx0Lw="},"openseadragon/zoomout_grouphover-935cf9421f818373b50d1c07c1600a46e5abe0030aeb29ad9069eee552483c0d.png":{"logical_path":"openseadragon/zoomout_grouphover.png","mtime":"2018-01-24T19:57:33+00:00","size":4596,"digest":"935cf9421f818373b50d1c07c1600a46e5abe0030aeb29ad9069eee552483c0d","integrity":"sha256-k1z5Qh+Bg3O1DRwHwWAKRuWr4AMK6ymtkGnu5VJIPA0="},"openseadragon/zoomout_hover-c0f132422ffb01bd8d6da0558ddf2d5c5a9248d6a8657c977f37c183fc0a041c.png":{"logical_path":"openseadragon/zoomout_hover.png","mtime":"2018-01-24T19:57:33+00:00","size":4931,"digest":"c0f132422ffb01bd8d6da0558ddf2d5c5a9248d6a8657c977f37c183fc0a041c","integrity":"sha256-wPEyQi/7Ab2NbaBVjd8tXFqSSNaoZXyXfzfBg/wKBBw="},"openseadragon/zoomout_pressed-a7091b00617fa6be61cce72ad9921f7432516232ccbb39214e8c62be4c2cb390.png":{"logical_path":"openseadragon/zoomout_pressed.png","mtime":"2018-01-24T19:57:33+00:00","size":5007,"digest":"a7091b00617fa6be61cce72ad9921f7432516232ccbb39214e8c62be4c2cb390","integrity":"sha256-pwkbAGF/pr5hzOcq2ZIfdDJRYjLMuzkhToxivkwss5A="},"openseadragon/zoomout_rest-ee46500bf6f400d6cc2e3841bcab6e6ff07f2538cfda9d44ba5978edb6071631.png":{"logical_path":"openseadragon/zoomout_rest.png","mtime":"2018-01-24T19:57:33+00:00","size":4811,"digest":"ee46500bf6f400d6cc2e3841bcab6e6ff07f2538cfda9d44ba5978edb6071631","integrity":"sha256-7kZQC/b0ANbMLjhBvKtub/B/JTjP2p1Eull47bYHFjE="},"resque_web/idle-18173b94ca2aa4f55f4c89159496ff2224d143b8256a91f30947a45aa847fd3b.png":{"logical_path":"resque_web/idle.png","mtime":"2018-01-24T19:57:34+00:00","size":661,"digest":"18173b94ca2aa4f55f4c89159496ff2224d143b8256a91f30947a45aa847fd3b","integrity":"sha256-GBc7lMoqpPVfTIkVlJb/IiTRQ7glapHzCUekWqhH/Ts="},"resque_web/lifebuoy-581621c4214cf4d08ac8fa7ac426c330ac9e52040fa4a1cc089e1295f6016c00.png":{"logical_path":"resque_web/lifebuoy.png","mtime":"2018-01-24T19:57:34+00:00","size":5361,"digest":"581621c4214cf4d08ac8fa7ac426c330ac9e52040fa4a1cc089e1295f6016c00","integrity":"sha256-WBYhxCFM9NCKyPp6xCbDMKyeUgQPpKHMCJ4SlfYBbAA="},"resque_web/poll-2138e7c844e6f8d2c14d4e39384744827ed3e6cc4574045a6f72bf8df13e11a8.png":{"logical_path":"resque_web/poll.png","mtime":"2018-01-24T19:57:34+00:00","size":627,"digest":"2138e7c844e6f8d2c14d4e39384744827ed3e6cc4574045a6f72bf8df13e11a8","integrity":"sha256-ITjnyETm+NLBTU45OEdEgn7T5sxFdARab3K/jfE+Eag="},"resque_web/rails-322506f9917889126e81df2833a6eecdf2e394658d53dad347e9882dd4dbf28e.png":{"logical_path":"resque_web/rails.png","mtime":"2018-01-24T19:57:34+00:00","size":6646,"digest":"322506f9917889126e81df2833a6eecdf2e394658d53dad347e9882dd4dbf28e","integrity":"sha256-MiUG+ZF4iRJugd8oM6buzfLjlGWNU9rTR+mILdTb8o4="},"resque_web/working-6d2c6a179add2f39a67fe52b2159c17834c7587034cba3e3d54f532341805c6e.png":{"logical_path":"resque_web/working.png","mtime":"2018-01-24T19:57:34+00:00","size":792,"digest":"6d2c6a179add2f39a67fe52b2159c17834c7587034cba3e3d54f532341805c6e","integrity":"sha256-bSxqF5rdLzmmf+UrIVnBeDTHWHA0y6Pj1U9TI0GAXG4="},"resque_web/application-0f14b3b1c07c6c76749947709c0766cc8565f503558bf24138302df4c734192f.js":{"logical_path":"resque_web/application.js","mtime":"2018-04-20T13:06:19+00:00","size":144091,"digest":"0f14b3b1c07c6c76749947709c0766cc8565f503558bf24138302df4c734192f","integrity":"sha256-DxSzscB8bHZ0mUdwnAdmzIVl9QNVi/JBODAt9Mc0GS8="},"resque_web/application-420695733631afe856cb7842099b74346bdb7a183d3985c02bbac0199ecab0a3.css":{"logical_path":"resque_web/application.css","mtime":"2018-04-20T13:06:19+00:00","size":139531,"digest":"420695733631afe856cb7842099b74346bdb7a183d3985c02bbac0199ecab0a3","integrity":"sha256-QgaVczYxr+hWy3hCCZt0NGvbehg9OYXAK7rAGZ7KsKM="},"glyphicons-halflings-regular-62fcbc4796f99217282f30c654764f572d9bfd9df7de9ce1e37922fa3caf8124.eot":{"logical_path":"glyphicons-halflings-regular.eot","mtime":"2018-01-24T19:57:32+00:00","size":20290,"digest":"62fcbc4796f99217282f30c654764f572d9bfd9df7de9ce1e37922fa3caf8124","integrity":"sha256-Yvy8R5b5khcoLzDGVHZPVy2b/Z333pzh43ki+jyvgSQ="},"glyphicons-halflings-regular-cef3dffcef386be2c8d1307761717e2eb9f43c151f2da9f1647e9d454abf13a3.svg":{"logical_path":"glyphicons-halflings-regular.svg","mtime":"2018-01-24T19:57:32+00:00","size":62850,"digest":"cef3dffcef386be2c8d1307761717e2eb9f43c151f2da9f1647e9d454abf13a3","integrity":"sha256-zvPf/O84a+LI0TB3YXF+Lrn0PBUfLanxZH6dRUq/E6M="},"glyphicons-halflings-regular-e27b969ef04fed3b39000b7b977e602d6e6a2b1c8c0d618bebf6dd875243ea3c.ttf":{"logical_path":"glyphicons-halflings-regular.ttf","mtime":"2018-01-24T19:57:32+00:00","size":41236,"digest":"e27b969ef04fed3b39000b7b977e602d6e6a2b1c8c0d618bebf6dd875243ea3c","integrity":"sha256-4nuWnvBP7Ts5AAt7l35gLW5qKxyMDWGL6/bdh1JD6jw="},"glyphicons-halflings-regular-63faf0af44a428f182686f0d924bb30e369a9549630c7b98a969394f58431067.woff":{"logical_path":"glyphicons-halflings-regular.woff","mtime":"2018-01-24T19:57:32+00:00","size":23292,"digest":"63faf0af44a428f182686f0d924bb30e369a9549630c7b98a969394f58431067","integrity":"sha256-Y/rwr0SkKPGCaG8NkkuzDjaalUljDHuYqWk5T1hDEGc="},"audio-5133b642ee875760dbd85bfab48649d009efd4bd29db1165f891b48a90b4f37e.png":{"logical_path":"audio.png","mtime":"2018-01-24T19:57:36+00:00","size":31665,"digest":"5133b642ee875760dbd85bfab48649d009efd4bd29db1165f891b48a90b4f37e","integrity":"sha256-UTO2Qu6HV2Db2Fv6tIZJ0Anv1L0p2xFl+JG0ipC0834="},"default-c6018ff301250c55bcc663293e1816c7daece4159cbc93fc03d9b35dbc3db30d.png":{"logical_path":"default.png","mtime":"2018-01-24T19:57:36+00:00","size":27869,"digest":"c6018ff301250c55bcc663293e1816c7daece4159cbc93fc03d9b35dbc3db30d","integrity":"sha256-xgGP8wElDFW8xmMpPhgWx9rs5BWcvJP8A9mzXbw9sw0="},"loading-b84750cc5a395288fcfd0cf42e3a60d6135e2f14db83fce05e97e5abacc2f9b4.gif":{"logical_path":"loading.gif","mtime":"2018-01-24T19:57:36+00:00","size":3897,"digest":"b84750cc5a395288fcfd0cf42e3a60d6135e2f14db83fce05e97e5abacc2f9b4","integrity":"sha256-uEdQzFo5Uoj8/Qz0Ljpg1hNeLxTbg/zgXpflq6zC+bQ="},"nope-cf8beaaa1b7d23e413def42da79a8ce9752d511312f6e656e902b4fbb90186d4.png":{"logical_path":"nope.png","mtime":"2018-01-24T19:57:36+00:00","size":8507,"digest":"cf8beaaa1b7d23e413def42da79a8ce9752d511312f6e656e902b4fbb90186d4","integrity":"sha256-z4vqqht9I+QT3vQtp5qM6XUtURMS9uZW6QK0+7kBhtQ="},"progressbar-a45ab53b619988cefd45f49cfacc604dd499b93f53a96927cf7b2d965f421f48.gif":{"logical_path":"progressbar.gif","mtime":"2018-01-24T19:57:36+00:00","size":3323,"digest":"a45ab53b619988cefd45f49cfacc604dd499b93f53a96927cf7b2d965f421f48","integrity":"sha256-pFq1O2GZiM79RfSc+sxgTdSZuT9TqWknz3stll9CH0g="},"curation_concerns/application-5afde1e59226b982514fd55d22584023c689472b0257f50e7baa500986db5ab8.js":{"logical_path":"curation_concerns/application.js","mtime":"2018-04-05T23:23:31+00:00","size":86903,"digest":"5afde1e59226b982514fd55d22584023c689472b0257f50e7baa500986db5ab8","integrity":"sha256-Wv3h5ZImuYJRT9VdIlhAI8aJRysCV/UOe6pQCYbbWrg="},"ui-bg_glass_100_fdf5ce_1x400-f88e1b423d5b2de7044f448bb83dfc1324b8132da3c589157e349e9125f9fbca.png":{"logical_path":"ui-bg_glass_100_fdf5ce_1x400.png","mtime":"2018-01-24T19:57:36+00:00","size":153,"digest":"f88e1b423d5b2de7044f448bb83dfc1324b8132da3c589157e349e9125f9fbca","integrity":"sha256-+I4bQj1bLecET0SLuD38EyS4Ey2jxYkVfjSekSX5+8o="},"ui-bg_highlight-soft_100_eeeeee_1x100-41ff65fb4f9b6f2fa9c9d025c2e9b0c9e09a2aee6f32266d19ee93c8af4dacbf.png":{"logical_path":"ui-bg_highlight-soft_100_eeeeee_1x100.png","mtime":"2018-01-24T19:57:36+00:00","size":90,"digest":"41ff65fb4f9b6f2fa9c9d025c2e9b0c9e09a2aee6f32266d19ee93c8af4dacbf","integrity":"sha256-Qf9l+0+bby+pydAlwumwyeCaKu5vMiZtGe6TyK9NrL8="},"qa/application-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.js":{"logical_path":"qa/application.js","mtime":"2018-01-24T19:57:32+00:00","size":0,"digest":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","integrity":"sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="},"qa/application-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css":{"logical_path":"qa/application.css","mtime":"2018-01-24T19:57:32+00:00","size":0,"digest":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","integrity":"sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="},"jquery-ui/ui-bg_flat_0_aaaaaa_40x100-9a8492a580bf85d3e98ae8861fbd45567e5a1f83eeafcf9574da0399d5f602ab.png":{"logical_path":"jquery-ui/ui-bg_flat_0_aaaaaa_40x100.png","mtime":"2018-01-24T19:57:31+00:00","size":180,"digest":"9a8492a580bf85d3e98ae8861fbd45567e5a1f83eeafcf9574da0399d5f602ab","integrity":"sha256-moSSpYC/hdPpiuiGH71FVn5aH4Pur8+VdNoDmdX2Aqs="},"jquery-ui/ui-bg_flat_75_ffffff_40x100-39ab7ccd9f4e82579da78a9241265df288d8eb65dbbd7cf48aed2d0129887df5.png":{"logical_path":"jquery-ui/ui-bg_flat_75_ffffff_40x100.png","mtime":"2018-01-24T19:57:31+00:00","size":178,"digest":"39ab7ccd9f4e82579da78a9241265df288d8eb65dbbd7cf48aed2d0129887df5","integrity":"sha256-Oat8zZ9Ogledp4qSQSZd8ojY62XbvXz0iu0tASmIffU="},"jquery-ui/ui-bg_glass_55_fbf9ee_1x400-691597e8a40a891ea94d3589976ecfc33e6145c49422443b00ac2b5a0022964c.png":{"logical_path":"jquery-ui/ui-bg_glass_55_fbf9ee_1x400.png","mtime":"2018-01-24T19:57:31+00:00","size":120,"digest":"691597e8a40a891ea94d3589976ecfc33e6145c49422443b00ac2b5a0022964c","integrity":"sha256-aRWX6KQKiR6pTTWJl27Pwz5hRcSUIkQ7AKwrWgAilkw="},"jquery-ui/ui-bg_glass_65_ffffff_1x400-f0e6cd91b837d5c5644d026e5ffeccd907953317cd5c0f689901733afda260b2.png":{"logical_path":"jquery-ui/ui-bg_glass_65_ffffff_1x400.png","mtime":"2018-01-24T19:57:31+00:00","size":105,"digest":"f0e6cd91b837d5c5644d026e5ffeccd907953317cd5c0f689901733afda260b2","integrity":"sha256-8ObNkbg31cVkTQJuX/7M2QeVMxfNXA9omQFzOv2iYLI="},"jquery-ui/ui-bg_glass_75_dadada_1x400-c108f5cbf2dd9ec07a26530695ddd95e1664597ce6c056ae44c162cc2e28cec4.png":{"logical_path":"jquery-ui/ui-bg_glass_75_dadada_1x400.png","mtime":"2018-01-24T19:57:31+00:00","size":111,"digest":"c108f5cbf2dd9ec07a26530695ddd95e1664597ce6c056ae44c162cc2e28cec4","integrity":"sha256-wQj1y/LdnsB6JlMGld3ZXhZkWXzmwFauRMFizC4ozsQ="},"jquery-ui/ui-bg_glass_75_e6e6e6_1x400-ddf5dd4e0ef2b185e8bb0af7b6e90ebe74a84384cb4700658e76e754c8bfe550.png":{"logical_path":"jquery-ui/ui-bg_glass_75_e6e6e6_1x400.png","mtime":"2018-01-24T19:57:31+00:00","size":110,"digest":"ddf5dd4e0ef2b185e8bb0af7b6e90ebe74a84384cb4700658e76e754c8bfe550","integrity":"sha256-3fXdTg7ysYXouwr3tukOvnSoQ4TLRwBljnbnVMi/5VA="},"jquery-ui/ui-bg_glass_95_fef1ec_1x400-f6f1c1bedf1a0f37cfef81d12f5f012869d1ee7c984775a569827a1784d34f5c.png":{"logical_path":"jquery-ui/ui-bg_glass_95_fef1ec_1x400.png","mtime":"2018-01-24T19:57:31+00:00","size":119,"digest":"f6f1c1bedf1a0f37cfef81d12f5f012869d1ee7c984775a569827a1784d34f5c","integrity":"sha256-9vHBvt8aDzfP74HRL18BKGnR7nyYR3WlaYJ6F4TTT1w="},"jquery-ui/ui-bg_highlight-soft_75_cccccc_1x100-54270656df079c4da5182629a080fc633b6f84b87985eb016d25a560e2c38d4a.png":{"logical_path":"jquery-ui/ui-bg_highlight-soft_75_cccccc_1x100.png","mtime":"2018-01-24T19:57:31+00:00","size":101,"digest":"54270656df079c4da5182629a080fc633b6f84b87985eb016d25a560e2c38d4a","integrity":"sha256-VCcGVt8HnE2lGCYpoID8YztvhLh5hesBbSWlYOLDjUo="},"jquery-ui/ui-icons_222222_256x240-57adb0d65f4e91dacfee975d9574422bee7486c8a182d60133728c672f2cdbbc.png":{"logical_path":"jquery-ui/ui-icons_222222_256x240.png","mtime":"2018-01-24T19:57:31+00:00","size":4369,"digest":"57adb0d65f4e91dacfee975d9574422bee7486c8a182d60133728c672f2cdbbc","integrity":"sha256-V62w1l9OkdrP7pddlXRCK+50hsihgtYBM3KMZy8s27w="},"jquery-ui/ui-icons_2e83ff_256x240-20f8c6667afc48aa433ee9eb6d8a0584bdbd6b4a4a9091ff1e6b3adb31e63bd9.png":{"logical_path":"jquery-ui/ui-icons_2e83ff_256x240.png","mtime":"2018-01-24T19:57:31+00:00","size":4369,"digest":"20f8c6667afc48aa433ee9eb6d8a0584bdbd6b4a4a9091ff1e6b3adb31e63bd9","integrity":"sha256-IPjGZnr8SKpDPunrbYoFhL29a0pKkJH/Hms62zHmO9k="},"jquery-ui/ui-icons_454545_256x240-07460e843c3e59aaadbb34231e699e856a2980753c7a47b66447da5d9f93fb7f.png":{"logical_path":"jquery-ui/ui-icons_454545_256x240.png","mtime":"2018-01-24T19:57:31+00:00","size":4369,"digest":"07460e843c3e59aaadbb34231e699e856a2980753c7a47b66447da5d9f93fb7f","integrity":"sha256-B0YOhDw+WaqtuzQjHmmehWopgHU8eke2ZEfaXZ+T+38="},"jquery-ui/ui-icons_888888_256x240-ea2e29625de3463465e93b002b065f5833e05b97f7a052b1c141e754d62e1a8b.png":{"logical_path":"jquery-ui/ui-icons_888888_256x240.png","mtime":"2018-01-24T19:57:31+00:00","size":4369,"digest":"ea2e29625de3463465e93b002b065f5833e05b97f7a052b1c141e754d62e1a8b","integrity":"sha256-6i4pYl3jRjRl6TsAKwZfWDPgW5f3oFKxwUHnVNYuGos="},"jquery-ui/ui-icons_cd0a0a_256x240-1e32c6dbf5d3fd342f27a78aa881550d6412aa207f48468724a6a15402b6041b.png":{"logical_path":"jquery-ui/ui-icons_cd0a0a_256x240.png","mtime":"2018-01-24T19:57:31+00:00","size":4369,"digest":"1e32c6dbf5d3fd342f27a78aa881550d6412aa207f48468724a6a15402b6041b","integrity":"sha256-HjLG2/XT/TQvJ6eKqIFVDWQSqiB/SEaHJKahVAK2BBs="},"blacklight/logo-e368f4faad4c5310352e175e1fba8220b57aab2e29cbe0f3ca1189a2ef085f9b.png":{"logical_path":"blacklight/logo.png","mtime":"2018-01-24T19:57:32+00:00","size":3861,"digest":"e368f4faad4c5310352e175e1fba8220b57aab2e29cbe0f3ca1189a2ef085f9b","integrity":"sha256-42j0+q1MUxA1LhdeH7qCILV6qy4py+DzyhGJou8IX5s="},"bootstrap/glyphicons-halflings-regular-13634da87d9e23f8c3ed9108ce1724d183a39ad072e73e1b3d8cbf646d2d0407.eot":{"logical_path":"bootstrap/glyphicons-halflings-regular.eot","mtime":"2018-01-24T19:56:47+00:00","size":20127,"digest":"13634da87d9e23f8c3ed9108ce1724d183a39ad072e73e1b3d8cbf646d2d0407","integrity":"sha256-E2NNqH2eI/jD7ZEIzhck0YOjmtBy5z4bPYy/ZG0tBAc="},"bootstrap/glyphicons-halflings-regular-42f60659d265c1a3c30f9fa42abcbb56bd4a53af4d83d316d6dd7a36903c43e5.svg":{"logical_path":"bootstrap/glyphicons-halflings-regular.svg","mtime":"2018-01-24T19:56:47+00:00","size":108738,"digest":"42f60659d265c1a3c30f9fa42abcbb56bd4a53af4d83d316d6dd7a36903c43e5","integrity":"sha256-QvYGWdJlwaPDD5+kKry7Vr1KU69Ng9MW1t16NpA8Q+U="},"bootstrap/glyphicons-halflings-regular-e395044093757d82afcb138957d06a1ea9361bdcf0b442d06a18a8051af57456.ttf":{"logical_path":"bootstrap/glyphicons-halflings-regular.ttf","mtime":"2018-01-24T19:56:47+00:00","size":45404,"digest":"e395044093757d82afcb138957d06a1ea9361bdcf0b442d06a18a8051af57456","integrity":"sha256-45UEQJN1fYKvyxOJV9BqHqk2G9zwtELQahioBRr1dFY="},"bootstrap/glyphicons-halflings-regular-a26394f7ede100ca118eff2eda08596275a9839b959c226e15439557a5a80742.woff":{"logical_path":"bootstrap/glyphicons-halflings-regular.woff","mtime":"2018-01-24T19:56:47+00:00","size":23424,"digest":"a26394f7ede100ca118eff2eda08596275a9839b959c226e15439557a5a80742","integrity":"sha256-omOU9+3hAMoRjv8u2ghZYnWpg5uVnCJuFUOVV6WoB0I="},"bootstrap/glyphicons-halflings-regular-fe185d11a49676890d47bb783312a0cda5a44c4039214094e7957b4c040ef11c.woff2":{"logical_path":"bootstrap/glyphicons-halflings-regular.woff2","mtime":"2018-01-24T19:56:47+00:00","size":18028,"digest":"fe185d11a49676890d47bb783312a0cda5a44c4039214094e7957b4c040ef11c","integrity":"sha256-/hhdEaSWdokNR7t4MxKgzaWkTEA5IUCU55V7TAQO8Rw="}},"assets":{"favicon.ico":"favicon-b4185c7dd08c0b4f2142c58bbcca322b3a01f50adc3413438b98be2b664bb4b6.ico","spotlight/default_thumbnail.jpg":"spotlight/default_thumbnail-bbab04087becfd4290ed6cae8bb3ef4fdf2b86389fc11a59c14950cff0b6f256.jpg","Jcrop.gif":"Jcrop-752309673bbd9d9b2ba0bd58d0a7071a2c59c30e2824d85bcd2a3bc0a07ef1f1.gif","header-shield.svg":"header-shield-78e456088b25329419d1f98d7bb42e02c7f040fa5d1f51b4531418ec2d6c48d1.svg","wustl-4-color-rev-shield.svg":"wustl-4-color-rev-shield-10e4f3d11d829fc3609da279e5433a53e80d038c2cacfe9238e15f8bfb7a61d5.svg","application.js":"application-2094a97c58f7b56052cadd89c9a44465dc78d23906e01558800790a76795a489.js","application.css":"application-9ee086d5c5c19d50bd12aa6c220a7817b0265ad1ed80a1b9ae93284535831732.css","able.eot":"able-c95c94605ee1482ee47ffe411e4637dc77162ef1974e3fcb8c28569ec9224868.eot","able.svg":"able-8627fd16fdeb04867dc51283898f117bd202e5a22ebfeea4167ade4655e0297b.svg","able.ttf":"able-e340a2f0b62e717f59993409cda5642c72d01609d8669ab344d571d27b2ae039.ttf","able.woff":"able-5e30bdbb24182ec0f9b4c29faf64e441849042daf1be9062477d6cf7b4b621c4.woff","blacklight/gallery/view-icon-masonry.png":"blacklight/gallery/view-icon-masonry-94cb2f417ca00047d6e4603644bebaa67037ef9b3efcc9c95b42229c1c571345.png","blacklight/gallery/view-icon-masonry@2x.png":"blacklight/gallery/view-icon-masonry@2x-da61b9e430c6b3f007aaa8073c062b147c909746c254f0adb81eaf476dc1a26d.png","FontAwesome.otf":"FontAwesome-7ed24c05432403117372891543f0cb6a7922100919e7ae077c1f3faf67658dc2.otf","fontawesome-webfont.eot":"fontawesome-webfont-e219ece8f4d3e4ac455ef31cd3a7c7b5057ea68a109937fc26b03c6e99ee9322.eot","fontawesome-webfont.svg":"fontawesome-webfont-d67041fe5d50eef9ef671643968f7ce6b130eaaaaa2ce4d496b18d0a33aeb87b.svg","fontawesome-webfont.ttf":"fontawesome-webfont-7b5a4320fba0d4c8f79327645b4b9cc875a2ec617a557e849b813918eb733499.ttf","fontawesome-webfont.woff":"fontawesome-webfont-c812ddc9e475d3e65d68a6b3b589ce598a2a5babb7afc55477d59215c4a38a40.woff","fontawesome-webfont.woff2":"fontawesome-webfont-ff82aeed6b9bb6701696c84d1b223d2e682eb78c89117a438ce6cfea8c498995.woff2","sprites/social-share-button.png":"sprites/social-share-button-01bd4e9a7782620aab986c55abc6ecb9ee88d50bb51f28c2f00fe36d87a0944c.png","sprites/social-share-button@2x.png":"sprites/social-share-button@2x-a597120b30fe6ae4ac5b4f0f45f307c0f5519851529b3010a7c584eb3ed186be.png","spotlight/blocks/browse-on.png":"spotlight/blocks/browse-on-8f7e560fe4b2ee7e8762f477821ae746af698ce9b91bdfa9e19e9d45e7bb06a1.png","spotlight/blocks/browse.png":"spotlight/blocks/browse-8b0b9cd7cd3905826df76c7c6abbd84a5aed6a62c6e660ed4933a765253b6489.png","spotlight/blocks/features-on.png":"spotlight/blocks/features-on-33817abee1a540e7f2a8729a3dc27b7c6c8379fa7777ab5f6119e942af263291.png","spotlight/blocks/features.png":"spotlight/blocks/features-eadadd56461f682fd4c6cd787748540d474f432af0cd682f2a1684fba0f48ed0.png","spotlight/blocks/item-carousel-on.png":"spotlight/blocks/item-carousel-on-a26f4a2bb6b8b502b84fc621c5d6afae9f5e646ae7c2d6d6dccd8e973e754613.png","spotlight/blocks/item-carousel.png":"spotlight/blocks/item-carousel-33724f9604397c7be1d43271f96645909240f7db1e9e7c7fb7928898667fe738.png","spotlight/blocks/item-embed-on.png":"spotlight/blocks/item-embed-on-5448bd656f848d0c13f5333af9a88938cf05c470dd60aa4a07452493a644d6fd.png","spotlight/blocks/item-embed.png":"spotlight/blocks/item-embed-730e3245393053e34def3835e5a9bc19a81e943a005c9d6139f340137d1eeaad.png","spotlight/blocks/item-grid-on.png":"spotlight/blocks/item-grid-on-6a6ed8e9048e9c3e541e5ffe4efd55cef6e5be6b5852ba499c47a8bf3c77a99d.png","spotlight/blocks/item-grid.png":"spotlight/blocks/item-grid-a859821315998fa5248a63d99062b0dfaf3e939b5df343d9c423dbe1cad805e5.png","spotlight/blocks/item-row-on.png":"spotlight/blocks/item-row-on-88c16d2a584c982285694ae46cd6fd7398db1c1bab06d8fe6150c184ed2b4968.png","spotlight/blocks/item-row.png":"spotlight/blocks/item-row-abc07b0fed0b6fe5ddacc0d1de17b9b6bf6b5a76fceaad412433b9a5ef2376f7.png","spotlight/blocks/item-slideshow-on.png":"spotlight/blocks/item-slideshow-on-2a62fb7bbb4ad1c1319839c58a84af866fb7d9f987b2ca01e0e7a286ad880978.png","spotlight/blocks/item-slideshow.png":"spotlight/blocks/item-slideshow-a260145061a6ec0ea767138044b89ea9d3b60ecdfa1061e298ef256f08c65985.png","spotlight/blocks/results-on.png":"spotlight/blocks/results-on-7372bc50631094ed9565af8939bd0e84c79eac601256bf7bb12c934f3801304e.png","spotlight/blocks/results.png":"spotlight/blocks/results-018ba18b7ef629ac3d13e8ea40e59635048b120ea883df661a36f3807f78ffa2.png","spotlight/blocks/rule-on.png":"spotlight/blocks/rule-on-1e16507d085e86ae0ed26435f3264c6091b8559263492b293ce887c9f1ac80bb.png","spotlight/blocks/rule.png":"spotlight/blocks/rule-054dc438cdb28345f0d7f2c4caaf9a1483f3a6d68e3cb89f6d9906dccf03bb4c.png","spotlight/blocks/text-on.png":"spotlight/blocks/text-on-a5166a120f28bd7bcbb7b2e2ef4bc9c2ae2280fe4d57b192d48438b31e2b751e.png","spotlight/blocks/text.png":"spotlight/blocks/text-e216b456f53225e8b8bbd26baf5e842f14841ffaa34715cd1e84c83d23062dd3.png","spotlight/application.js":"spotlight/application-465d645b877c64cfd8235c7bdfc645df1ddb3e6b404b023d0ee4a4d6aae6217d.js","spotlight/application.css":"spotlight/application-bad33562d35ae6246b441c0f4c630c9605c6fb27d534acea17ca6715b0f0636b.css","openseadragon/fullpage_grouphover.png":"openseadragon/fullpage_grouphover-8d9b60badcdd45f9c538b4014fa52130f91fdf7ee8d4cc43d72b7cfc5b475469.png","openseadragon/fullpage_hover.png":"openseadragon/fullpage_hover-64ceaf34eac330c7d6b3e71a4830cdda50db9705d84c6bbf080dfbcc2173c439.png","openseadragon/fullpage_pressed.png":"openseadragon/fullpage_pressed-0e745969d9ca01c2aa519d6c3f3d2dc5bc5fa466602f2034aa6e678960db71ba.png","openseadragon/fullpage_rest.png":"openseadragon/fullpage_rest-4d29556b0d6220897ce665f683e17235603019826d0eccc573f1e1d04c08477e.png","openseadragon/home_grouphover.png":"openseadragon/home_grouphover-9252742700d9f302a270f1942291a2a355e69da7a85118542f012001e943ef0e.png","openseadragon/home_hover.png":"openseadragon/home_hover-09e5b9167e7a4ab330e598df0b69a4795e8cdefbd79f35b62b1b57c10c83ea97.png","openseadragon/home_pressed.png":"openseadragon/home_pressed-d4abc088b08320251dc2d0df8c2665907abd347d2123123748551e29ea59f633.png","openseadragon/home_rest.png":"openseadragon/home_rest-8e280d6b63992033b781d1b1779fee5eef9c0d6666297ba1ef8bc5dfa58bf26f.png","openseadragon/next_grouphover.png":"openseadragon/next_grouphover-20b59567ad9b95237afdf4f7e0a996c88f2f293053fdc9a78d19ff473eec420d.png","openseadragon/next_hover.png":"openseadragon/next_hover-0ff290cd4ea121e4fe08f3a8a06339568bb0fa1cb4c0031b88fe9b58fc278407.png","openseadragon/next_pressed.png":"openseadragon/next_pressed-c6091636705e293569ff695484214879e6c04f9cd9fa56ed603b803446e8127f.png","openseadragon/next_rest.png":"openseadragon/next_rest-d71177afcd7ed4baccfeae321c345e39cda547477555db18b42d361009fbaf06.png","openseadragon/previous_grouphover.png":"openseadragon/previous_grouphover-d88299bd9f4175c83a620ab91e4db134eaebcbbe8e5a6cf3ea3de8ba7878851c.png","openseadragon/previous_hover.png":"openseadragon/previous_hover-53287f3ced64ec0cafbd02eebf2253936d0ae51832ba92392d6ea62ab9072f70.png","openseadragon/previous_pressed.png":"openseadragon/previous_pressed-319b4e58132403c9c88c113127d86ce50e8284d1b4caad303ce20c53537ff795.png","openseadragon/previous_rest.png":"openseadragon/previous_rest-aecdb486d75814f721c1eb3830a2f8a12780227c8cd76d2c16ba9af5912dba42.png","openseadragon/rotateleft_grouphover.png":"openseadragon/rotateleft_grouphover-39647145d396d1c41c9921d5e322b43c44ff124fcbdd03063bbb7d6c4a02b9a0.png","openseadragon/rotateleft_hover.png":"openseadragon/rotateleft_hover-fe8c9d624686f71582acfae033a985cf91423fedbe966c4c2a62ca729b2f2dea.png","openseadragon/rotateleft_pressed.png":"openseadragon/rotateleft_pressed-e48f1502559b90bb3f76afcde211bd3bd77c8cf0493ba529fa9221ae4680801c.png","openseadragon/rotateleft_rest.png":"openseadragon/rotateleft_rest-a4abb092fd829ca5ff951404c96adecc4cb7238e2e3f03408fc4593a4e00132a.png","openseadragon/rotateright_grouphover.png":"openseadragon/rotateright_grouphover-46b13721e6b60052ce39ce6eb695e8172252f1e640466bfe37c3fd4099aa9ee8.png","openseadragon/rotateright_hover.png":"openseadragon/rotateright_hover-8f2c8885273e8e53b228c60c3793c4dba9a0b718058c4eb72f3170a04c99869d.png","openseadragon/rotateright_pressed.png":"openseadragon/rotateright_pressed-401e698877b40bcc77ede57153d7a43de0512afe2020a5ba4803cebb067f44b9.png","openseadragon/rotateright_rest.png":"openseadragon/rotateright_rest-fdbb33173a802d6d41d76e6ba6f9b77d1ed796f4cd5375e3ba554d5584fc2458.png","openseadragon/zoomin_grouphover.png":"openseadragon/zoomin_grouphover-e3e58ff9290bc54ad24947257e4c3e75fb5616aa8f46b4f2eb66e2982c93eec0.png","openseadragon/zoomin_hover.png":"openseadragon/zoomin_hover-398e5e0b4386b8569cd3efa4efd846aab552a272a050dfb218b2bb792fed0920.png","openseadragon/zoomin_pressed.png":"openseadragon/zoomin_pressed-2adc36279b0d5f7baebc0e347c03e204b599d8e5733963674a5b209b6f0a7b11.png","openseadragon/zoomin_rest.png":"openseadragon/zoomin_rest-0cfe30647a634c60a68fa868d67c488f60e6f78ce2bf61effce1c25c13f1d0bc.png","openseadragon/zoomout_grouphover.png":"openseadragon/zoomout_grouphover-935cf9421f818373b50d1c07c1600a46e5abe0030aeb29ad9069eee552483c0d.png","openseadragon/zoomout_hover.png":"openseadragon/zoomout_hover-c0f132422ffb01bd8d6da0558ddf2d5c5a9248d6a8657c977f37c183fc0a041c.png","openseadragon/zoomout_pressed.png":"openseadragon/zoomout_pressed-a7091b00617fa6be61cce72ad9921f7432516232ccbb39214e8c62be4c2cb390.png","openseadragon/zoomout_rest.png":"openseadragon/zoomout_rest-ee46500bf6f400d6cc2e3841bcab6e6ff07f2538cfda9d44ba5978edb6071631.png","resque_web/idle.png":"resque_web/idle-18173b94ca2aa4f55f4c89159496ff2224d143b8256a91f30947a45aa847fd3b.png","resque_web/lifebuoy.png":"resque_web/lifebuoy-581621c4214cf4d08ac8fa7ac426c330ac9e52040fa4a1cc089e1295f6016c00.png","resque_web/poll.png":"resque_web/poll-2138e7c844e6f8d2c14d4e39384744827ed3e6cc4574045a6f72bf8df13e11a8.png","resque_web/rails.png":"resque_web/rails-322506f9917889126e81df2833a6eecdf2e394658d53dad347e9882dd4dbf28e.png","resque_web/working.png":"resque_web/working-6d2c6a179add2f39a67fe52b2159c17834c7587034cba3e3d54f532341805c6e.png","resque_web/application.js":"resque_web/application-0f14b3b1c07c6c76749947709c0766cc8565f503558bf24138302df4c734192f.js","resque_web/application.css":"resque_web/application-420695733631afe856cb7842099b74346bdb7a183d3985c02bbac0199ecab0a3.css","glyphicons-halflings-regular.eot":"glyphicons-halflings-regular-62fcbc4796f99217282f30c654764f572d9bfd9df7de9ce1e37922fa3caf8124.eot","glyphicons-halflings-regular.svg":"glyphicons-halflings-regular-cef3dffcef386be2c8d1307761717e2eb9f43c151f2da9f1647e9d454abf13a3.svg","glyphicons-halflings-regular.ttf":"glyphicons-halflings-regular-e27b969ef04fed3b39000b7b977e602d6e6a2b1c8c0d618bebf6dd875243ea3c.ttf","glyphicons-halflings-regular.woff":"glyphicons-halflings-regular-63faf0af44a428f182686f0d924bb30e369a9549630c7b98a969394f58431067.woff","audio.png":"audio-5133b642ee875760dbd85bfab48649d009efd4bd29db1165f891b48a90b4f37e.png","default.png":"default-c6018ff301250c55bcc663293e1816c7daece4159cbc93fc03d9b35dbc3db30d.png","loading.gif":"loading-b84750cc5a395288fcfd0cf42e3a60d6135e2f14db83fce05e97e5abacc2f9b4.gif","nope.png":"nope-cf8beaaa1b7d23e413def42da79a8ce9752d511312f6e656e902b4fbb90186d4.png","progressbar.gif":"progressbar-a45ab53b619988cefd45f49cfacc604dd499b93f53a96927cf7b2d965f421f48.gif","curation_concerns/application.js":"curation_concerns/application-5afde1e59226b982514fd55d22584023c689472b0257f50e7baa500986db5ab8.js","ui-bg_glass_100_fdf5ce_1x400.png":"ui-bg_glass_100_fdf5ce_1x400-f88e1b423d5b2de7044f448bb83dfc1324b8132da3c589157e349e9125f9fbca.png","ui-bg_highlight-soft_100_eeeeee_1x100.png":"ui-bg_highlight-soft_100_eeeeee_1x100-41ff65fb4f9b6f2fa9c9d025c2e9b0c9e09a2aee6f32266d19ee93c8af4dacbf.png","qa/application.js":"qa/application-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.js","qa/application.css":"qa/application-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css","jquery-ui/ui-bg_flat_0_aaaaaa_40x100.png":"jquery-ui/ui-bg_flat_0_aaaaaa_40x100-9a8492a580bf85d3e98ae8861fbd45567e5a1f83eeafcf9574da0399d5f602ab.png","jquery-ui/ui-bg_flat_75_ffffff_40x100.png":"jquery-ui/ui-bg_flat_75_ffffff_40x100-39ab7ccd9f4e82579da78a9241265df288d8eb65dbbd7cf48aed2d0129887df5.png","jquery-ui/ui-bg_glass_55_fbf9ee_1x400.png":"jquery-ui/ui-bg_glass_55_fbf9ee_1x400-691597e8a40a891ea94d3589976ecfc33e6145c49422443b00ac2b5a0022964c.png","jquery-ui/ui-bg_glass_65_ffffff_1x400.png":"jquery-ui/ui-bg_glass_65_ffffff_1x400-f0e6cd91b837d5c5644d026e5ffeccd907953317cd5c0f689901733afda260b2.png","jquery-ui/ui-bg_glass_75_dadada_1x400.png":"jquery-ui/ui-bg_glass_75_dadada_1x400-c108f5cbf2dd9ec07a26530695ddd95e1664597ce6c056ae44c162cc2e28cec4.png","jquery-ui/ui-bg_glass_75_e6e6e6_1x400.png":"jquery-ui/ui-bg_glass_75_e6e6e6_1x400-ddf5dd4e0ef2b185e8bb0af7b6e90ebe74a84384cb4700658e76e754c8bfe550.png","jquery-ui/ui-bg_glass_95_fef1ec_1x400.png":"jquery-ui/ui-bg_glass_95_fef1ec_1x400-f6f1c1bedf1a0f37cfef81d12f5f012869d1ee7c984775a569827a1784d34f5c.png","jquery-ui/ui-bg_highlight-soft_75_cccccc_1x100.png":"jquery-ui/ui-bg_highlight-soft_75_cccccc_1x100-54270656df079c4da5182629a080fc633b6f84b87985eb016d25a560e2c38d4a.png","jquery-ui/ui-icons_222222_256x240.png":"jquery-ui/ui-icons_222222_256x240-57adb0d65f4e91dacfee975d9574422bee7486c8a182d60133728c672f2cdbbc.png","jquery-ui/ui-icons_2e83ff_256x240.png":"jquery-ui/ui-icons_2e83ff_256x240-20f8c6667afc48aa433ee9eb6d8a0584bdbd6b4a4a9091ff1e6b3adb31e63bd9.png","jquery-ui/ui-icons_454545_256x240.png":"jquery-ui/ui-icons_454545_256x240-07460e843c3e59aaadbb34231e699e856a2980753c7a47b66447da5d9f93fb7f.png","jquery-ui/ui-icons_888888_256x240.png":"jquery-ui/ui-icons_888888_256x240-ea2e29625de3463465e93b002b065f5833e05b97f7a052b1c141e754d62e1a8b.png","jquery-ui/ui-icons_cd0a0a_256x240.png":"jquery-ui/ui-icons_cd0a0a_256x240-1e32c6dbf5d3fd342f27a78aa881550d6412aa207f48468724a6a15402b6041b.png","blacklight/logo.png":"blacklight/logo-e368f4faad4c5310352e175e1fba8220b57aab2e29cbe0f3ca1189a2ef085f9b.png","bootstrap/glyphicons-halflings-regular.eot":"bootstrap/glyphicons-halflings-regular-13634da87d9e23f8c3ed9108ce1724d183a39ad072e73e1b3d8cbf646d2d0407.eot","bootstrap/glyphicons-halflings-regular.svg":"bootstrap/glyphicons-halflings-regular-42f60659d265c1a3c30f9fa42abcbb56bd4a53af4d83d316d6dd7a36903c43e5.svg","bootstrap/glyphicons-halflings-regular.ttf":"bootstrap/glyphicons-halflings-regular-e395044093757d82afcb138957d06a1ea9361bdcf0b442d06a18a8051af57456.ttf","bootstrap/glyphicons-halflings-regular.woff":"bootstrap/glyphicons-halflings-regular-a26394f7ede100ca118eff2eda08596275a9839b959c226e15439557a5a80742.woff","bootstrap/glyphicons-halflings-regular.woff2":"bootstrap/glyphicons-halflings-regular-fe185d11a49676890d47bb783312a0cda5a44c4039214094e7957b4c040ef11c.woff2"}} \ No newline at end of file diff --git a/public/assets/FontAwesome-7ed24c05432403117372891543f0cb6a7922100919e7ae077c1f3faf67658dc2.otf b/public/assets/FontAwesome-7ed24c05432403117372891543f0cb6a7922100919e7ae077c1f3faf67658dc2.otf new file mode 100644 index 0000000..3ed7f8b Binary files /dev/null and b/public/assets/FontAwesome-7ed24c05432403117372891543f0cb6a7922100919e7ae077c1f3faf67658dc2.otf differ diff --git a/public/assets/Jcrop-752309673bbd9d9b2ba0bd58d0a7071a2c59c30e2824d85bcd2a3bc0a07ef1f1.gif b/public/assets/Jcrop-752309673bbd9d9b2ba0bd58d0a7071a2c59c30e2824d85bcd2a3bc0a07ef1f1.gif new file mode 100644 index 0000000..72ea7cc Binary files /dev/null and b/public/assets/Jcrop-752309673bbd9d9b2ba0bd58d0a7071a2c59c30e2824d85bcd2a3bc0a07ef1f1.gif differ diff --git a/public/assets/able-5e30bdbb24182ec0f9b4c29faf64e441849042daf1be9062477d6cf7b4b621c4.woff b/public/assets/able-5e30bdbb24182ec0f9b4c29faf64e441849042daf1be9062477d6cf7b4b621c4.woff new file mode 100644 index 0000000..5c53071 Binary files /dev/null and b/public/assets/able-5e30bdbb24182ec0f9b4c29faf64e441849042daf1be9062477d6cf7b4b621c4.woff differ diff --git a/public/assets/able-8627fd16fdeb04867dc51283898f117bd202e5a22ebfeea4167ade4655e0297b.svg b/public/assets/able-8627fd16fdeb04867dc51283898f117bd202e5a22ebfeea4167ade4655e0297b.svg new file mode 100644 index 0000000..b9e83b1 --- /dev/null +++ b/public/assets/able-8627fd16fdeb04867dc51283898f117bd202e5a22ebfeea4167ade4655e0297b.svg @@ -0,0 +1,34 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/assets/able-8627fd16fdeb04867dc51283898f117bd202e5a22ebfeea4167ade4655e0297b.svg.gz b/public/assets/able-8627fd16fdeb04867dc51283898f117bd202e5a22ebfeea4167ade4655e0297b.svg.gz new file mode 100644 index 0000000..ecf1e75 Binary files /dev/null and b/public/assets/able-8627fd16fdeb04867dc51283898f117bd202e5a22ebfeea4167ade4655e0297b.svg.gz differ diff --git a/public/assets/able-c95c94605ee1482ee47ffe411e4637dc77162ef1974e3fcb8c28569ec9224868.eot b/public/assets/able-c95c94605ee1482ee47ffe411e4637dc77162ef1974e3fcb8c28569ec9224868.eot new file mode 100644 index 0000000..472a1f4 Binary files /dev/null and b/public/assets/able-c95c94605ee1482ee47ffe411e4637dc77162ef1974e3fcb8c28569ec9224868.eot differ diff --git a/public/assets/able-e340a2f0b62e717f59993409cda5642c72d01609d8669ab344d571d27b2ae039.ttf b/public/assets/able-e340a2f0b62e717f59993409cda5642c72d01609d8669ab344d571d27b2ae039.ttf new file mode 100644 index 0000000..1c2cfb2 Binary files /dev/null and b/public/assets/able-e340a2f0b62e717f59993409cda5642c72d01609d8669ab344d571d27b2ae039.ttf differ diff --git a/public/assets/application-2094a97c58f7b56052cadd89c9a44465dc78d23906e01558800790a76795a489.js b/public/assets/application-2094a97c58f7b56052cadd89c9a44465dc78d23906e01558800790a76795a489.js new file mode 100644 index 0000000..b7edf49 --- /dev/null +++ b/public/assets/application-2094a97c58f7b56052cadd89c9a44465dc78d23906e01558800790a76795a489.js @@ -0,0 +1,1560 @@ +/*! + * jQuery JavaScript Library v1.11.3 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2015-04-28T16:19Z + */ +function TeiViewer(t,e){t.currentPage=1,t.formNumber=1,t.maxPage="1",t.pages=[],t.imagesOnly=!1,t.display="both",t.build=function(e){t.pages=e.pages,t.maxPage=t.pages.length},t.to_trusted=function(t){return e.trustAsHtml(t)},t.previousPage=function(){t.currentPage>1&&(t.currentPage--,t.formNumber=t.currentPage)},t.nextPage=function(){t.currentPage{{#if thumbnail}}
    {{/if}}{{title}}
      {{description}}'}function addAutocompletetoMastheadUpload(){$("[data-masthead-typeahead]").length>0&&$("[data-masthead-typeahead]").spotlightSearchTypeAhead({bloodhound:itemsBloodhound(),template:itemsTemplate()}).on("click",function(){$(this).select()}).on("typeahead:selected typeahead:autocompleted",function(t,e){var n=$($(this).data("remoteUrlField")),i=$($(this).data("target-panel"));swapInputForPanel($(this),i,e),$($(this).data("id-field")).val(e.global_id),n.val(e.full_images[0]).trigger("change"),$(this).attr("type","text"),$(".thumbs-list li",i).on("click.masthead",function(){var t=$(".thumbs-list li").index($(this));n.val(e.full_images[t]).trigger("change")})})}function addAutocompletetoFeaturedImage(){$("[data-featured-item-typeahead]").length>0&&$("[data-featured-item-typeahead]").spotlightSearchTypeAhead({bloodhound:itemsBloodhound(),template:itemsTemplate()}).on("click",function(){$(this).select()}).on("change",function(){$($(this).data("id-field")).val("")}).on("typeahead:selected typeahead:autocompleted",function(t,e){$($(this).data("id-field")).val(e.id)})}function swapInputForPanel(t,e,n){$(".pic.thumbnail img",e).attr("src",n.thumbnail).show(),$("[data-item-grid-thumbnail]",e).attr("value",n.thumbnail),$("[data-panel-title]",e).text(n.title),n["private"]&&e.addClass("blacklight-private"),$("[data-panel-id-display]",e).text(n.id),$(t.data("id_field")).val(n.id),e.multiImageSelector(n.image_versions),$(t.data("checkbox_field")).prop("checked",!0),t.attr("type","hidden"),e.show()}function addRemoveAutocompletedPanelBehavior(){$("[data-item-grid-panel-remove]").on("click",function(t){t.preventDefault();var e=$(this).closest("li.dd-item"),n=$("[data-target-panel='#"+e.attr("id")+"']");$("input[type='hidden']",e).prop("value",""),n.attr("value",""),n.attr("type","text"),e.hide()})}function replaceName(t,e){t.prop("name",t.prop("name").replace(/\d/,e))}!function(t,e){"object"==typeof module&&"object"==typeof module.exports?module.exports=t.document?e(t,!0):function(t){if(!t.document)throw new Error("jQuery requires a window with a document");return e(t)}:e(t)}("undefined"!=typeof window?window:this,function(t,e){function n(t){var e="length"in t&&t.length,n=rt.type(t);return"function"===n||rt.isWindow(t)?!1:1===t.nodeType&&e?!0:"array"===n||0===e||"number"==typeof e&&e>0&&e-1 in t}function i(t,e,n){if(rt.isFunction(e))return rt.grep(t,function(t,i){return!!e.call(t,i,t)!==n});if(e.nodeType)return rt.grep(t,function(t){return t===e!==n});if("string"==typeof e){if(dt.test(e))return rt.filter(e,t,n);e=rt.filter(e,t)}return rt.grep(t,function(t){return rt.inArray(t,e)>=0!==n})}function r(t,e){do t=t[e];while(t&&1!==t.nodeType);return t}function o(t){var e=wt[t]={};return rt.each(t.match(bt)||[],function(t,n){e[n]=!0}),e}function a(){ft.addEventListener?(ft.removeEventListener("DOMContentLoaded",s,!1),t.removeEventListener("load",s,!1)):(ft.detachEvent("onreadystatechange",s),t.detachEvent("onload",s))}function s(){(ft.addEventListener||"load"===event.type||"complete"===ft.readyState)&&(a(),rt.ready())}function l(t,e,n){if(void 0===n&&1===t.nodeType){var i="data-"+e.replace(St,"-$1").toLowerCase();if(n=t.getAttribute(i),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:$t.test(n)?rt.parseJSON(n):n}catch(r){}rt.data(t,e,n)}else n=void 0}return n}function u(t){var e;for(e in t)if(("data"!==e||!rt.isEmptyObject(t[e]))&&"toJSON"!==e)return!1;return!0}function c(t,e,n,i){if(rt.acceptData(t)){var r,o,a=rt.expando,s=t.nodeType,l=s?rt.cache:t,u=s?t[a]:t[a]&&a;if(u&&l[u]&&(i||l[u].data)||void 0!==n||"string"!=typeof e)return u||(u=s?t[a]=G.pop()||rt.guid++:a),l[u]||(l[u]=s?{}:{toJSON:rt.noop}),("object"==typeof e||"function"==typeof e)&&(i?l[u]=rt.extend(l[u],e):l[u].data=rt.extend(l[u].data,e)),o=l[u],i||(o.data||(o.data={}),o=o.data),void 0!==n&&(o[rt.camelCase(e)]=n),"string"==typeof e?(r=o[e],null==r&&(r=o[rt.camelCase(e)])):r=o,r}}function h(t,e,n){if(rt.acceptData(t)){var i,r,o=t.nodeType,a=o?rt.cache:t,s=o?t[rt.expando]:rt.expando;if(a[s]){if(e&&(i=n?a[s]:a[s].data)){rt.isArray(e)?e=e.concat(rt.map(e,rt.camelCase)):e in i?e=[e]:(e=rt.camelCase(e),e=e in i?[e]:e.split(" ")),r=e.length;for(;r--;)delete i[e[r]];if(n?!u(i):!rt.isEmptyObject(i))return}(n||(delete a[s].data,u(a[s])))&&(o?rt.cleanData([t],!0):nt.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}function d(){return!0}function p(){return!1}function f(){try{return ft.activeElement}catch(t){}}function m(t){var e=Bt.split("|"),n=t.createDocumentFragment();if(n.createElement)for(;e.length;)n.createElement(e.pop());return n}function g(t,e){var n,i,r=0,o=typeof t.getElementsByTagName!==kt?t.getElementsByTagName(e||"*"):typeof t.querySelectorAll!==kt?t.querySelectorAll(e||"*"):void 0;if(!o)for(o=[],n=t.childNodes||t;null!=(i=n[r]);r++)!e||rt.nodeName(i,e)?o.push(i):rt.merge(o,g(i,e));return void 0===e||e&&rt.nodeName(t,e)?rt.merge([t],o):o}function v(t){At.test(t.type)&&(t.defaultChecked=t.checked)}function y(t,e){return rt.nodeName(t,"table")&&rt.nodeName(11!==e.nodeType?e:e.firstChild,"tr")?t.getElementsByTagName("tbody")[0]||t.appendChild(t.ownerDocument.createElement("tbody")):t}function b(t){return t.type=(null!==rt.find.attr(t,"type"))+"/"+t.type,t}function w(t){var e=Kt.exec(t.type);return e?t.type=e[1]:t.removeAttribute("type"),t}function x(t,e){for(var n,i=0;null!=(n=t[i]);i++)rt._data(n,"globalEval",!e||rt._data(e[i],"globalEval"))}function _(t,e){if(1===e.nodeType&&rt.hasData(t)){var n,i,r,o=rt._data(t),a=rt._data(e,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(i=0,r=s[n].length;r>i;i++)rt.event.add(e,n,s[n][i])}a.data&&(a.data=rt.extend({},a.data))}}function k(t,e){var n,i,r;if(1===e.nodeType){if(n=e.nodeName.toLowerCase(),!nt.noCloneEvent&&e[rt.expando]){r=rt._data(e);for(i in r.events)rt.removeEvent(e,i,r.handle);e.removeAttribute(rt.expando)}"script"===n&&e.text!==t.text?(b(e).text=t.text,w(e)):"object"===n?(e.parentNode&&(e.outerHTML=t.outerHTML),nt.html5Clone&&t.innerHTML&&!rt.trim(e.innerHTML)&&(e.innerHTML=t.innerHTML)):"input"===n&&At.test(t.type)?(e.defaultChecked=e.checked=t.checked,e.value!==t.value&&(e.value=t.value)):"option"===n?e.defaultSelected=e.selected=t.defaultSelected:("input"===n||"textarea"===n)&&(e.defaultValue=t.defaultValue)}}function $(e,n){var i,r=rt(n.createElement(e)).appendTo(n.body),o=t.getDefaultComputedStyle&&(i=t.getDefaultComputedStyle(r[0]))?i.display:rt.css(r[0],"display");return r.detach(),o}function S(t){var e=ft,n=Jt[t];return n||(n=$(t,e),"none"!==n&&n||(Qt=(Qt||rt("'},youtube:{regex:/(?:http[s]?:\/\/)?(?:www.)?(?:(?:youtube.com\/watch\?(?:.*)(?:v=))|(?:youtu.be\/))([^&].+)/,html:''}},type:"video",title:function(){return i18n.t("blocks:video:title")},droppable:!0,pastable:!0,icon_name:"video",loadData:function(t){if(this.providers.hasOwnProperty(t.source)){var e=this.providers[t.source],n="file:"===window.location.protocol?"http:":window.location.protocol,r=e.square?"with-square-media":"with-sixteen-by-nine-media";this.$editor.addClass("st-block__editor--"+r).html(i.template(e.html,{protocol:n,remote_id:t.remote_id,width:this.$editor.width()}))}},onContentPasted:function(t){this.handleDropPaste(t.target.value)},matchVideoProvider:function(t,e,n){var r=t.regex.exec(n);return null==r||i.isUndefined(r[1])?{}:{source:e,remote_id:r[1]}},handleDropPaste:function(t){if(r.isURI(t))for(var e in this.providers)this.providers.hasOwnProperty(e)&&this.setAndLoadData(this.matchVideoProvider(this.providers[e],e,t))},onDrop:function(t){var e=t.getData("text/plain");this.handleDropPaste(e)}})},{"../block":146,"../lodash":178,"../utils":184}],162:[function(t,e,n){"use strict";var i={html:['
    ','<%= _.result(block, "icon_name") %>','

    <%= i18n.t("general:drop", { block: "" + _.result(block, "title") + "" }) %>',"

    "].join("\n"),re_render_on_reorder:!1},r={html:['"',' class="st-block__paste-input st-paste-block">'].join("")},o={html:['
    ','','',"
    "].join("\n")};e.exports={debug:!1,scribeDebug:!1,skipValidation:!1,version:"0.4.0",language:"en",instances:[],defaults:{defaultType:!1,spinner:{className:"st-spinner",lines:9,length:8,width:3,radius:6,color:"#000",speed:1.4,trail:57,shadow:!1,left:"50%",top:"50%"},Block:{drop_options:i,paste_options:r,upload_options:o},blockLimit:0,blockTypeLimits:{},required:[],uploadUrl:"/attachments",baseImageUrl:"/sir-trevor-uploads/",errorsContainer:void 0,convertToMarkdown:!1,convertFromMarkdown:!0,formatBar:{commands:[{name:"Bold",title:"bold",cmd:"bold",keyCode:66,text:"B"},{name:"Italic",title:"italic",cmd:"italic",keyCode:73,text:"i"},{name:"Link",title:"link",iconName:"link",cmd:"linkPrompt",text:"link"},{name:"Unlink",title:"unlink",iconName:"link",cmd:"unlink",text:"link"}]}}}},{}],163:[function(t,e,n){(function(n){"use strict";var i=t("./lodash"),r="undefined"!=typeof window?window.$:"undefined"!=typeof n?n.$:null,o=t("./config"),a=t("./utils"),s=t("./events"),l=t("./event-bus"),u=t("./form-events"),c=t("./block-controls"),h=t("./block-manager"),d=t("./floating-block-controls"),p=t("./format-bar"),f=t("./extensions/editor-store"),m=t("./error-handler"),g=function(t){this.initialize(t)};Object.assign(g.prototype,t("./function-bind"),t("./events"),{bound:["onFormSubmit","hideAllTheThings","changeBlockPosition","removeBlockDragOver","renderBlock","resetBlockControls","blockLimitReached"],events:{"block:reorder:dragend":"removeBlockDragOver","block:content:dropped":"removeBlockDragOver"},initialize:function(t){return a.log("Init SirTrevor.Editor"),this.options=Object.assign({},o.defaults,t||{}),this.ID=i.uniqueId("st-editor-"),this._ensureAndSetElements()?(!i.isUndefined(this.options.onEditorRender)&&i.isFunction(this.options.onEditorRender)&&(this.onEditorRender=this.options.onEditorRender),this.mediator=Object.assign({},s),this._bindFunctions(),o.instances.push(this),this.build(),void u.bindFormSubmit(this.$form)):!1},build:function(){this.$el.hide(),this.errorHandler=new m(this.$outer,this.mediator,this.options.errorsContainer),this.store=new f(this.$el.val(),this.mediator),this.block_manager=new h(this.options,this.ID,this.mediator),this.block_controls=new c(this.block_manager.blockTypes,this.mediator),this.fl_block_controls=new d(this.$wrapper,this.ID,this.mediator),this.formatBar=new p(this.options.formatBar,this.mediator),this.mediator.on("block:changePosition",this.changeBlockPosition),this.mediator.on("block-controls:reset",this.resetBlockControls),this.mediator.on("block:limitReached",this.blockLimitReached),this.mediator.on("block:render",this.renderBlock),this.dataStore="Please use store.retrieve();",this._setEvents(),this.$wrapper.prepend(this.fl_block_controls.render().$el),r(document.body).append(this.formatBar.render().$el),this.$outer.append(this.block_controls.render().$el),r(window).bind("click",this.hideAllTheThings),this.createBlocks(),this.$wrapper.addClass("st-ready"),i.isUndefined(this.onEditorRender)||this.onEditorRender()},createBlocks:function(){var t=this.store.retrieve();t.data.length>0?t.data.forEach(function(t){this.mediator.trigger("block:create",t.type,t.data)},this):this.options.defaultType!==!1&&this.mediator.trigger("block:create",this.options.defaultType,{})},destroy:function(){this.formatBar.destroy(),this.fl_block_controls.destroy(),this.block_controls.destroy(),this.blocks.forEach(function(t){this.mediator.trigger("block:remove",this.block.blockID)},this),this.mediator.stopListening(),this.stopListening(),o.instances=o.instances.filter(function(t){return t.ID!==this.ID},this),this.store.reset(),this.$outer.replaceWith(this.$el.detach())},reinitialize:function(t){this.destroy(),this.initialize(t||this.options)},resetBlockControls:function(){this.block_controls.renderInContainer(this.$wrapper),this.block_controls.hide()},blockLimitReached:function(t){this.$wrapper.toggleClass("st--block-limit-reached",t)},_setEvents:function(){Object.keys(this.events).forEach(function(t){l.on(t,this[this.events[t]],this)},this)},hideAllTheThings:function(t){this.block_controls.hide(),this.formatBar.hide()},store:function(t,e){return a.log("The store method has been removed, please call store[methodName]"),this.store[t].call(this,e||{})},renderBlock:function(t){this._renderInPosition(t.render().$el),this.hideAllTheThings(),this.scrollTo(t.$el),t.trigger("onRender")},scrollTo:function(t){r("html, body").animate({scrollTop:t.position().top},300,"linear")},removeBlockDragOver:function(){this.$outer.find(".st-drag-over").removeClass("st-drag-over")},changeBlockPosition:function(t,e){e-=1;var n=this.getBlockPosition(t),i=this.$wrapper.find(".st-block").eq(e),r=n>e?"Before":"After";i&&i.attr("id")!==t.attr("id")&&(this.hideAllTheThings(),t["insert"+r](i),this.scrollTo(t))},_renderInPosition:function(t){this.block_controls.currentContainer?this.block_controls.currentContainer.after(t):this.$wrapper.append(t)},validateAndSaveBlock:function(t,e){if((!o.skipValidation||e)&&!t.valid())return this.mediator.trigger("errors:add",{text:i.result(t,"validationFailMsg")}),void a.log("Block "+t.blockID+" failed validation");var n=t.getData();a.log("Adding data for block "+t.blockID+" to block store:",n),this.store.addData(n)},onFormSubmit:function(t){return t=t===!1?!1:!0,a.log("Handling form submission for Editor "+this.ID),this.mediator.trigger("errors:reset"),this.store.reset(),this.validateBlocks(t),this.block_manager.validateBlockTypesExist(t),this.mediator.trigger("errors:render"),this.$el.val(this.store.toString()),this.errorHandler.errors.length},validateBlocks:function(t){var e=this;this.$wrapper.find(".st-block").each(function(n,o){var a=e.block_manager.findBlockById(r(o).attr("id"));i.isUndefined(a)||e.validateAndSaveBlock(a,t)})},findBlockById:function(t){return this.block_manager.findBlockById(t)},getBlocksByType:function(t){return this.block_manager.getBlocksByType(t)},getBlocksByIDs:function(t){return this.block_manager.getBlocksByIDs(t)},getBlockPosition:function(t){return this.$wrapper.find(".st-block").index(t)},_ensureAndSetElements:function(){if(i.isUndefined(this.options.el)||i.isEmpty(this.options.el))return a.log("You must provide an el"),!1;this.$el=this.options.el,this.el=this.options.el[0],this.$form=this.$el.parents("form");var t=r("
    ").attr({id:this.ID,"class":"st-outer",dropzone:"copy link move"}),e=r("
    ").attr({"class":"st-blocks"});return this.$el.wrap(t).wrap(e),this.$outer=this.$form.find("#"+this.ID),this.$wrapper=this.$outer.find(".st-blocks"),!0}}),e.exports=g}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./block-controls":139,"./block-manager":141,"./config":162,"./error-handler":164,"./event-bus":165,"./events":166,"./extensions/editor-store":167,"./floating-block-controls":170,"./form-events":171,"./format-bar":172,"./function-bind":173,"./lodash":178,"./utils":184}],164:[function(t,e,n){(function(n){"use strict";var i=t("./lodash"),r="undefined"!=typeof window?window.$:"undefined"!=typeof n?n.$:null,o=function(t,e,n){this.$wrapper=t,this.mediator=e,this.$el=n,i.isUndefined(this.$el)&&(this._ensureElement(),this.$wrapper.prepend(this.$el)),this.$el.hide(),this._bindFunctions(),this._bindMediatedEvents(),this.initialize()};Object.assign(o.prototype,t("./function-bind"),t("./mediated-events"),t("./renderable"),{errors:[],className:"st-errors",eventNamespace:"errors",mediatedEvents:{reset:"reset",add:"addMessage",render:"render"},initialize:function(){var t=r("
      ");this.$el.append("

      "+i18n.t("errors:title")+"

      ").append(t),this.$list=t},render:function(){return 0===this.errors.length?!1:(this.errors.forEach(this.createErrorItem,this),void this.$el.show())},createErrorItem:function(t){var e=r("
    • ",{"class":"st-errors__msg",html:t.text});this.$list.append(e)},addMessage:function(t){this.errors.push(t)},reset:function(){return 0===this.errors.length?!1:(this.errors=[],this.$list.html(""),void this.$el.hide())}}),e.exports=o}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./function-bind":173,"./lodash":178,"./mediated-events":179,"./renderable":180}],165:[function(t,e,n){"use strict";e.exports=Object.assign({},t("./events"))},{"./events":166}],166:[function(t,e,n){"use strict";e.exports=t("eventablejs")},{eventablejs:3}],167:[function(t,e,n){"use strict";var i=t("../lodash"),r=t("../utils"),o=function(t,e){this.mediator=e,this.initialize(t?t.trim():"")};Object.assign(o.prototype,{initialize:function(t){this.store=this._parseData(t)||{data:[]}},retrieve:function(){return this.store},toString:function(t){return JSON.stringify(this.store,void 0,t)},reset:function(){r.log("Resetting the EditorStore"),this.store={data:[]}},addData:function(t){return this.store.data.push(t),this.store},_parseData:function(t){var e;if(0===t.length)return e;try{var n=JSON.parse(t);i.isUndefined(n.data)||(e=n)}catch(r){this.mediator.trigger("errors:add",{text:i18n.t("errors:load_fail")}),this.mediator.trigger("errors:render"),console.log("Sorry there has been a problem with parsing the JSON"),console.log(r)}return e}}),e.exports=o},{"../lodash":178,"../utils":184}],168:[function(t,e,n){(function(n){"use strict";var i=t("../lodash"),r="undefined"!=typeof window?window.$:"undefined"!=typeof n?n.$:null,o=t("../config"),a=t("../utils"),s=t("../event-bus");e.exports=function(t,e,n,l){s.trigger("onUploadStart");var u=[t.blockID,(new Date).getTime(),"raw"].join("-"),c=new FormData;c.append("attachment[name]",e.name),c.append("attachment[file]",e),c.append("attachment[uid]",u),t.resetMessages();var h=function(e){a.log("Upload callback called"),s.trigger("onUploadStop"),!i.isUndefined(n)&&i.isFunction(n)&&n.apply(t,arguments)},d=function(e,n,r){a.log("Upload callback error called"),s.trigger("onUploadStop"),!i.isUndefined(l)&&i.isFunction(l)&&l.call(t,n)},p=r.ajax({url:o.defaults.uploadUrl,data:c,cache:!1,contentType:!1,processData:!1,dataType:"json",type:"POST"});return t.addQueuedItem(u,p),p.done(h).fail(d).always(t.removeQueuedItem.bind(t,u)),p}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../config":162,"../event-bus":165,"../lodash":178,"../utils":184}],169:[function(t,e,n){(function(n){"use strict";var i="undefined"!=typeof window?window.$:"undefined"!=typeof n?n.$:null,r=t("../utils"),o=t("../event-bus"),a=function(t){this.$form=t,this.intialize()};Object.assign(a.prototype,{intialize:function(){this.submitBtn=this.$form.find("input[type='submit']");var t=[];this.submitBtn.each(function(e,n){t.push(i(n).attr("value"))}),this.submitBtnTitles=t,this.canSubmit=!0,this.globalUploadCount=0,this._bindEvents()},setSubmitButton:function(t,e){this.submitBtn.attr("value",e)},resetSubmitButton:function(){var t=this.submitBtnTitles;this.submitBtn.each(function(e,n){i(n).attr("value",t[e])})},onUploadStart:function(t){this.globalUploadCount++,r.log("onUploadStart called "+this.globalUploadCount),1===this.globalUploadCount&&this._disableSubmitButton()},onUploadStop:function(t){this.globalUploadCount=this.globalUploadCount<=0?0:this.globalUploadCount-1,r.log("onUploadStop called "+this.globalUploadCount),0===this.globalUploadCount&&this._enableSubmitButton()},onError:function(t){r.log("onError called"),this.canSubmit=!1},_disableSubmitButton:function(t){this.setSubmitButton(null,t||i18n.t("general:wait")),this.submitBtn.attr("disabled","disabled").addClass("disabled")},_enableSubmitButton:function(){this.resetSubmitButton(),this.submitBtn.removeAttr("disabled").removeClass("disabled")},_events:{disableSubmitButton:"_disableSubmitButton",enableSubmitButton:"_enableSubmitButton",setSubmitButton:"setSubmitButton",resetSubmitButton:"resetSubmitButton",onError:"onError",onUploadStart:"onUploadStart",onUploadStop:"onUploadStop"},_bindEvents:function(){Object.keys(this._events).forEach(function(t){o.on(t,this[this._events[t]],this)},this)}}),e.exports=a}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../event-bus":165,"../utils":184}],170:[function(t,e,n){(function(n){"use strict";var i=t("./lodash"),r="undefined"!=typeof window?window.$:"undefined"!=typeof n?n.$:null,o=t("./event-bus"),a=function(t,e,n){this.$wrapper=t,this.instance_id=e,this.mediator=n,this._ensureElement(),this._bindFunctions(),this.initialize()};Object.assign(a.prototype,t("./function-bind"),t("./renderable"),t("./events"),{className:"st-block-controls__top",attributes:function(){return{"data-icon":"add"}},bound:["handleBlockMouseOut","handleBlockMouseOver","handleBlockClick","onDrop"],initialize:function(){this.$el.on("click",this.handleBlockClick).dropArea().bind("drop",this.onDrop),this.$wrapper.on("mouseover",".st-block",this.handleBlockMouseOver).on("mouseout",".st-block",this.handleBlockMouseOut).on("click",".st-block--with-plus",this.handleBlockClick)},onDrop:function(t){t.preventDefault();var e=this.$el,n=t.originalEvent.dataTransfer.getData("text/plain"),a=r("#"+n);i.isUndefined(n)||i.isEmpty(a)||e.attr("id")===n||this.instance_id!==a.attr("data-instance")||e.after(a),o.trigger("block:reorder:dropped",n)},handleBlockMouseOver:function(t){var e=r(t.currentTarget);e.hasClass("st-block--with-plus")||e.addClass("st-block--with-plus")},handleBlockMouseOut:function(t){var e=r(t.currentTarget);e.hasClass("st-block--with-plus")&&e.removeClass("st-block--with-plus")},handleBlockClick:function(t){t.stopPropagation(),this.mediator.trigger("block-controls:render",r(t.currentTarget))}}),e.exports=a}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./event-bus":165,"./events":166,"./function-bind":173,"./lodash":178,"./renderable":180}],171:[function(t,e,n){"use strict";var i=t("./config"),r=t("./utils"),o=t("./event-bus"),a=t("./extensions/submittable"),s=!1,l={bindFormSubmit:function(t){s||(new a(t),t.bind("submit",this.onFormSubmit),s=!0)},onBeforeSubmit:function(t){var e=0;return i.instances.forEach(function(n,i){e+=n.onFormSubmit(t)}),r.log("Total errors: "+e),e},onFormSubmit:function(t){var e=l.onBeforeSubmit();e>0&&(o.trigger("onError"),t.preventDefault())}};e.exports=l},{"./config":162,"./event-bus":165,"./extensions/submittable":169,"./utils":184}],172:[function(t,e,n){(function(n){"use strict";var i=t("./lodash"),r="undefined"!=typeof window?window.$:"undefined"!=typeof n?n.$:null,o=t("./config"),a=t("./utils"),s=function(t,e){this.options=Object.assign({},o.defaults.formatBar,t||{}),this.commands=this.options.commands,this.mediator=e,this._ensureElement(),this._bindFunctions(),this._bindMediatedEvents(),this.initialize.apply(this,arguments)};Object.assign(s.prototype,t("./function-bind"),t("./mediated-events"),t("./events"),t("./renderable"),{className:"st-format-bar",bound:["onFormatButtonClick","renderBySelection","hide"],eventNamespace:"formatter",mediatedEvents:{position:"renderBySelection",show:"show",hide:"hide"},initialize:function(){this.$btns=[],this.commands.forEach(function(t){var e=r("',collapseBtnHTML:'',group:0,maxDepth:5,threshold:20,reject:[],dropCallback:null,cloneNodeOnDrag:!1,dragOutsideToDelete:!1};r.prototype={init:function(){var n=this;n.reset(),n.el.data("nestable-group",this.options.group),n.placeEl=t('
      '),t.each(this.el.find(n.options.itemNodeName),function(e,i){n.setParent(t(i))}),n.el.on("click","button",function(e){if(!n.dragEl&&(o||0===e.button)){var i=t(e.currentTarget),r=i.data("action"),a=i.parent(n.options.itemNodeName);"collapse"===r&&n.collapseItem(a),"expand"===r&&n.expandItem(a)}});var i=function(e){var i=t(e.target);if(n.nestableCopy=i.closest("."+n.options.rootClass).clone(!0),!i.hasClass(n.options.handleClass)){if(i.closest("."+n.options.noDragClass).length)return;i=i.closest("."+n.options.handleClass)}!i.length||n.dragEl||!o&&1!==e.which||o&&1!==e.touches.length||(e.preventDefault(),n.dragStart(o?e.touches[0]:e))},r=function(t){n.dragEl&&(t.preventDefault(),n.dragMove(o?t.touches[0]:t))},a=function(t){n.dragEl&&(t.preventDefault(),n.dragStop(o?t.touches[0]:t))};o?(n.el[0].addEventListener(s,i,!1),e.addEventListener(l,r,!1),e.addEventListener(u,a,!1),e.addEventListener(c,a,!1)):(n.el.on(s,i),n.w.on(l,r),n.w.on(u,a));var h=function(){o?(n.el[0].removeEventListener(s,i,!1),e.removeEventListener(l,r,!1),e.removeEventListener(u,a,!1),e.removeEventListener(c,a,!1)):(n.el.off(s,i),n.w.off(l,r),n.w.off(u,a)),n.el.off("click"),n.el.unbind("destroy-nestable"),n.el.data("nestable",null);var h=n.el[0].getElementsByTagName("button");t(h).remove()};n.el.bind("destroy-nestable",h)},destroy:function(){this.expandAll(),this.el.trigger("destroy-nestable")},serialize:function(){var e,n=0,i=this;step=function(e,n){var r=[],o=e.children(i.options.itemNodeName);return o.each(function(){var e=t(this),o=t.extend({},e.data()),a=e.children(i.options.listNodeName);a.length&&(o.children=step(a,n+1)),r.push(o)}),r};var r;return r=i.el.is(i.options.listNodeName)?i.el:i.el.find(i.options.listNodeName).first(),e=step(r,n)},reset:function(){this.mouse={offsetX:0,offsetY:0,startX:0,startY:0,lastX:0,lastY:0,nowX:0,nowY:0,distX:0,distY:0,dirAx:0,dirX:0,dirY:0,lastDirX:0,lastDirY:0,distAxX:0,distAxY:0},this.moving=!1,this.dragEl=null,this.dragRootEl=null,this.dragDepth=0,this.dragItem=null,this.hasNewRoot=!1,this.pointEl=null,this.sourceRoot=null,this.isOutsideRoot=!1},expandItem:function(t){t.removeClass(this.options.collapsedClass),t.children('[data-action="expand"]').hide(),t.children('[data-action="collapse"]').show(),t.children(this.options.listNodeName).show(),this.el.trigger("expand",[t]),t.trigger("expand")},collapseItem:function(t){var e=t.children(this.options.listNodeName);e.length&&(t.addClass(this.options.collapsedClass),t.children('[data-action="collapse"]').hide(),t.children('[data-action="expand"]').show(),t.children(this.options.listNodeName).hide()),this.el.trigger("collapse",[t]),t.trigger("collapse")},expandAll:function(){var e=this;e.el.find(e.options.itemNodeName).each(function(){e.expandItem(t(this))})},collapseAll:function(){var e=this;e.el.find(e.options.itemNodeName).each(function(){e.collapseItem(t(this))})},setParent:function(e){e.children(this.options.listNodeName).length&&(e.prepend(t(this.options.expandBtnHTML)),e.prepend(t(this.options.collapseBtnHTML))),(" "+e[0].className+" ").indexOf(" "+h.collapsedClass+" ")>-1?e.children('[data-action="collapse"]').hide():e.children('[data-action="expand"]').hide()},unsetParent:function(t){t.removeClass(this.options.collapsedClass),t.children("[data-action]").remove(),t.children(this.options.listNodeName).remove()},dragStart:function(e){var r=this.mouse,o=t(e.target),a=o.closest("."+this.options.handleClass).closest(this.options.itemNodeName);this.sourceRoot=o.closest("."+this.options.rootClass),this.dragItem=a,this.placeEl.css("height",a.height()),r.offsetX=e.offsetX!==i?e.offsetX:e.pageX-o.offset().left,r.offsetY=e.offsetY!==i?e.offsetY:e.pageY-o.offset().top,r.startX=r.lastX=e.pageX,r.startY=r.lastY=e.pageY,this.dragRootEl=this.el,this.dragEl=t(n.createElement(this.options.listNodeName)).addClass(this.options.listClass+" "+this.options.dragClass),this.dragEl.css("width",a.width()),this.options.cloneNodeOnDrag?a.after(a.clone()):a.after(this.placeEl),a[0].parentNode.removeChild(a[0]),a.appendTo(this.dragEl),t(n.body).append(this.dragEl),this.dragEl.css({left:e.pageX-r.offsetX,top:e.pageY-r.offsetY});var s,l,u=this.dragEl.find(this.options.itemNodeName);for(s=0;sthis.dragDepth&&(this.dragDepth=l)},dragStop:function(e){var n=this.dragEl.children(this.options.itemNodeName).first();if(n[0].parentNode.removeChild(n[0]),this.isOutsideRoot&&this.options.dragOutsideToDelete){var i=this.placeEl.parent();this.placeEl.remove(),i.children().length||this.unsetParent(i.parent()),this.dragRootEl.find(this.options.itemNodeName).length||this.dragRootEl.append('
      ')}else this.placeEl.replaceWith(n);this.moving||t(this.dragItem).trigger("click");var r,o=!1;for(r=0;r0?1:-1,h.dirY=0===h.distY?0:h.distY>0?1:-1;var d=Math.abs(h.distX)>Math.abs(h.distY)?1:0;if(!this.moving)return h.dirAx=d,void(this.moving=!0);h.dirAx!==d?(h.distAxX=0,h.distAxY=0):(h.distAxX+=Math.abs(h.distX),0!==h.dirX&&h.dirX!==h.lastDirX&&(h.distAxX=0),h.distAxY+=Math.abs(h.distY),0!==h.dirY&&h.dirY!==h.lastDirY&&(h.distAxY=0)),h.dirAx=d,h.dirAx&&h.distAxX>=c.threshold&&(h.distAxX=0,s=this.placeEl.prev(c.itemNodeName),h.distX>0&&s.length&&!s.hasClass(c.collapsedClass)&&!s.hasClass(c.noChildrenClass)&&(r=s.find(c.listNodeName).last(),u=this.placeEl.parents(c.listNodeName).length,u+this.dragDepth<=c.maxDepth&&(r.length?(r=s.children(c.listNodeName).last(),r.append(this.placeEl)):(r=t("<"+c.listNodeName+"/>").addClass(c.listClass),r.append(this.placeEl),s.append(r),this.setParent(s)))),h.distX<0&&(l=this.placeEl.next(c.itemNodeName),l.length||(o=this.placeEl.parent(),this.placeEl.closest(c.itemNodeName).after(this.placeEl),o.children().length||this.unsetParent(o.parent()))));var p=!1;a||(this.dragEl[0].style.visibility="hidden"),this.pointEl=t(n.elementFromPoint(i.pageX-n.documentElement.scrollLeft,i.pageY-(e.pageYOffset||n.documentElement.scrollTop))),this.dragRootEl.has(this.pointEl).length?(this.isOutsideRoot=!1,this.dragEl[0].style.opacity=1):(this.isOutsideRoot=!0,this.dragEl[0].style.opacity=.5);var f=this.pointEl.closest("."+c.rootClass),m=this.dragRootEl.data("nestable-id")!==f.data("nestable-id");if(this.isOutsideRoot=!f.length,a||(this.dragEl[0].style.visibility="visible"),this.pointEl.hasClass(c.handleClass)&&(this.pointEl=this.pointEl.closest(c.itemNodeName)),1!=c.maxDepth||this.pointEl.hasClass(c.itemClass)||(this.pointEl=this.pointEl.closest("."+c.itemClass)),this.pointEl.hasClass(c.emptyClass))p=!0;else if(!this.pointEl.length||!this.pointEl.hasClass(c.itemClass))return;if(!h.dirAx||m||p){if(m&&c.group!==f.data("nestable-group"))return;if(u=this.dragDepth-1+this.pointEl.parents(c.listNodeName).length,u>c.maxDepth)return;var g=i.pageY'),this.dragRootEl=f,m&&(this.hasNewRoot=this.el[0]!==this.dragRootEl[0])}}},t.fn.nestable=function(e){var n=this,i=this,o=function(t){function e(){return(65536*(1+Math.random())|0).toString(16).substring(1)}var n=t||"-";return e()+e()+n+e()+n+e()+n+e()+n+e()+e()+e()};return n.each(function(){var n=t(this).data("nestable");n?"string"==typeof e&&"function"==typeof n[e]&&(i=n[e]()):(t(this).data("nestable",new r(this,e)),t(this).data("nestable-id",o()))}),i||n}}(window.jQuery||window.Zepto,window,document);var LATIN_MAP={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xc6":"AE","\xc7":"C","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xd0":"D","\xd1":"N","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\u0150":"O","\xd8":"O","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\u0170":"U","\xdd":"Y","\xde":"TH","\xdf":"ss","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xe6":"ae","\xe7":"c","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xf0":"d","\xf1":"n","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\u0151":"o","\xf8":"o","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\u0171":"u","\xfd":"y","\xfe":"th","\xff":"y"},LATIN_SYMBOLS_MAP={"\xa9":"(c)"},GREEK_MAP={"\u03b1":"a","\u03b2":"b","\u03b3":"g","\u03b4":"d","\u03b5":"e","\u03b6":"z","\u03b7":"h","\u03b8":"8","\u03b9":"i","\u03ba":"k","\u03bb":"l","\u03bc":"m","\u03bd":"n","\u03be":"3","\u03bf":"o","\u03c0":"p","\u03c1":"r","\u03c3":"s","\u03c4":"t","\u03c5":"y","\u03c6":"f","\u03c7":"x","\u03c8":"ps","\u03c9":"w","\u03ac":"a","\u03ad":"e","\u03af":"i","\u03cc":"o","\u03cd":"y","\u03ae":"h","\u03ce":"w","\u03c2":"s","\u03ca":"i","\u03b0":"y","\u03cb":"y","\u0390":"i","\u0391":"A","\u0392":"B","\u0393":"G","\u0394":"D","\u0395":"E","\u0396":"Z","\u0397":"H","\u0398":"8","\u0399":"I","\u039a":"K","\u039b":"L","\u039c":"M","\u039d":"N","\u039e":"3","\u039f":"O","\u03a0":"P","\u03a1":"R","\u03a3":"S","\u03a4":"T","\u03a5":"Y","\u03a6":"F","\u03a7":"X","\u03a8":"PS","\u03a9":"W","\u0386":"A","\u0388":"E","\u038a":"I","\u038c":"O","\u038e":"Y","\u0389":"H","\u038f":"W","\u03aa":"I","\u03ab":"Y"},TURKISH_MAP={"\u015f":"s","\u015e":"S","\u0131":"i","\u0130":"I","\xe7":"c","\xc7":"C","\xfc":"u","\xdc":"U","\xf6":"o","\xd6":"O","\u011f":"g","\u011e":"G"},RUSSIAN_MAP={"\u0430":"a","\u0431":"b","\u0432":"v","\u0433":"g","\u0434":"d","\u0435":"e","\u0451":"yo","\u0436":"zh","\u0437":"z","\u0438":"i","\u0439":"j","\u043a":"k","\u043b":"l","\u043c":"m","\u043d":"n","\u043e":"o","\u043f":"p","\u0440":"r","\u0441":"s","\u0442":"t","\u0443":"u","\u0444":"f","\u0445":"h","\u0446":"c","\u0447":"ch","\u0448":"sh","\u0449":"sh","\u044a":"","\u044b":"y","\u044c":"","\u044d":"e","\u044e":"yu","\u044f":"ya","\u0410":"A","\u0411":"B","\u0412":"V","\u0413":"G","\u0414":"D","\u0415":"E","\u0401":"Yo","\u0416":"Zh","\u0417":"Z","\u0418":"I","\u0419":"J","\u041a":"K","\u041b":"L","\u041c":"M","\u041d":"N","\u041e":"O","\u041f":"P","\u0420":"R","\u0421":"S","\u0422":"T","\u0423":"U","\u0424":"F","\u0425":"H","\u0426":"C","\u0427":"Ch","\u0428":"Sh","\u0429":"Sh","\u042a":"","\u042b":"Y","\u042c":"","\u042d":"E","\u042e":"Yu","\u042f":"Ya"},UKRAINIAN_MAP={"\u0404":"Ye","\u0406":"I","\u0407":"Yi","\u0490":"G","\u0454":"ye","\u0456":"i","\u0457":"yi","\u0491":"g"},CZECH_MAP={"\u010d":"c","\u010f":"d","\u011b":"e","\u0148":"n","\u0159":"r","\u0161":"s","\u0165":"t","\u016f":"u","\u017e":"z","\u010c":"C","\u010e":"D","\u011a":"E","\u0147":"N","\u0158":"R","\u0160":"S","\u0164":"T","\u016e":"U","\u017d":"Z"},POLISH_MAP={"\u0105":"a","\u0107":"c","\u0119":"e","\u0142":"l","\u0144":"n","\xf3":"o","\u015b":"s","\u017a":"z","\u017c":"z","\u0104":"A","\u0106":"C","\u0118":"e","\u0141":"L","\u0143":"N","\xd3":"o","\u015a":"S","\u0179":"Z","\u017b":"Z"},LATVIAN_MAP={"\u0101":"a","\u010d":"c","\u0113":"e","\u0123":"g","\u012b":"i","\u0137":"k","\u013c":"l","\u0146":"n","\u0161":"s","\u016b":"u","\u017e":"z","\u0100":"A","\u010c":"C","\u0112":"E","\u0122":"G","\u012a":"i","\u0136":"k","\u013b":"L","\u0145":"N","\u0160":"S","\u016a":"u","\u017d":"Z"},ALL_DOWNCODE_MAPS=new Array;ALL_DOWNCODE_MAPS[0]=LATIN_MAP,ALL_DOWNCODE_MAPS[1]=LATIN_SYMBOLS_MAP,ALL_DOWNCODE_MAPS[2]=GREEK_MAP,ALL_DOWNCODE_MAPS[3]=TURKISH_MAP,ALL_DOWNCODE_MAPS[4]=RUSSIAN_MAP,ALL_DOWNCODE_MAPS[5]=UKRAINIAN_MAP,ALL_DOWNCODE_MAPS[6]=CZECH_MAP,ALL_DOWNCODE_MAPS[7]=POLISH_MAP,ALL_DOWNCODE_MAPS[8]=LATVIAN_MAP;var Downcoder=new Object;Downcoder.Initialize=function(){if(!Downcoder.map){Downcoder.map={},Downcoder.chars="";for(var t in ALL_DOWNCODE_MAPS){var e=ALL_DOWNCODE_MAPS[t];for(var n in e)Downcoder.map[n]=e[n],Downcoder.chars+=n}Downcoder.regex=new RegExp("["+Downcoder.chars+"]|[^"+Downcoder.chars+"]+","g")}},downcode=function(t){Downcoder.Initialize();var e="",n=t.match(Downcoder.regex);if(n)for(var i=0;ithis.$items.length-1||0>t?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){e.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",this.$items.eq(t))},n.prototype.pause=function(e){return e||(this.paused=!0),this.$element.find(".next, .prev").length&&t.support.transition&&(this.$element.trigger(t.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},n.prototype.next=function(){return this.sliding?void 0:this.slide("next")},n.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},n.prototype.slide=function(e,i){var r=this.$element.find(".item.active"),o=i||this.getItemForDirection(e,r),a=this.interval,s="next"==e?"left":"right",l=this;if(o.hasClass("active"))return this.sliding=!1;var u=o[0],c=t.Event("slide.bs.carousel",{relatedTarget:u,direction:s});if(this.$element.trigger(c),!c.isDefaultPrevented()){if(this.sliding=!0,a&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var h=t(this.$indicators.children()[this.getItemIndex(o)]);h&&h.addClass("active")}var d=t.Event("slid.bs.carousel",{relatedTarget:u,direction:s});return t.support.transition&&this.$element.hasClass("slide")?(o.addClass(e),o[0].offsetWidth,r.addClass(s),o.addClass(s),r.one("bsTransitionEnd",function(){o.removeClass([e,s].join(" ")).addClass("active"),r.removeClass(["active",s].join(" ")),l.sliding=!1,setTimeout(function(){l.$element.trigger(d)},0)}).emulateTransitionEnd(n.TRANSITION_DURATION)):(r.removeClass("active"),o.addClass("active"),this.sliding=!1,this.$element.trigger(d)),a&&this.cycle(),this}};var i=t.fn.carousel;t.fn.carousel=e,t.fn.carousel.Constructor=n,t.fn.carousel.noConflict=function(){return t.fn.carousel=i,this};var r=function(n){var i,r=t(this),o=t(r.attr("data-target")||(i=r.attr("href"))&&i.replace(/.*(?=#[^\s]+$)/,""));if(o.hasClass("carousel")){var a=t.extend({},o.data(),r.data()),s=r.attr("data-slide-to");s&&(a.interval=!1),e.call(o,a),s&&o.data("bs.carousel").to(s),n.preventDefault()}};t(document).on("click.bs.carousel.data-api","[data-slide]",r).on("click.bs.carousel.data-api","[data-slide-to]",r),t(window).on("load",function(){t('[data-ride="carousel"]').each(function(){var n=t(this);e.call(n,n.data())})})}(jQuery),/* The MIT License (MIT) + +Copyright (c) 2013 Tim Schlechter + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +function(t){"use strict";function e(e,n){this.itemsArray=[],this.$element=t(e),this.$element.hide(),this.isSelect="SELECT"===e.tagName,this.multiple=this.isSelect&&e.hasAttribute("multiple"),this.objectItems=n&&n.itemValue,this.placeholderText=e.hasAttribute("placeholder")?this.$element.attr("placeholder"):"",this.inputSize=Math.max(1,this.placeholderText.length),this.$container=t('
      '),this.$input=t('').appendTo(this.$container),this.$element.after(this.$container),this.build(n)}function n(t,e){if("function"!=typeof t[e]){var n=t[e];t[e]=function(t){return t[n]}}}function i(t,e){if("function"!=typeof t[e]){var n=t[e];t[e]=function(){return n}}}function r(t){return t?s.text(t).html():""}function o(t){var e=0;if(document.selection){t.focus();var n=document.selection.createRange();n.moveStart("character",-t.value.length),e=n.text.length}else(t.selectionStart||"0"==t.selectionStart)&&(e=t.selectionStart);return e}var a={tagClass:function(t){return"label label-info"},itemValue:function(t){return t?t.toString():t},itemText:function(t){return this.itemValue(t)},freeInput:!0,maxTags:void 0,confirmKeys:[13],onTagExists:function(t,e){e.hide().fadeIn()}};e.prototype={constructor:e,add:function(e,n){var i=this;if(!(i.options.maxTags&&i.itemsArray.length>=i.options.maxTags)&&(e===!1||e)){if("object"==typeof e&&!i.objectItems)throw"Can't add objects when itemValue option is not set";if(!e.toString().match(/^\s*$/)){if(i.isSelect&&!i.multiple&&i.itemsArray.length>0&&i.remove(i.itemsArray[0]),"string"==typeof e&&"INPUT"===this.$element[0].tagName){var o=e.split(",");if(o.length>1){for(var a=0;a'+r(l)+'');if(d.data("item",e),i.findInputWrapper().before(d),d.after(" "),i.isSelect&&!t('option[value="'+escape(s)+'"]',i.$element)[0]){var p=t("");p.data("item",e),p.attr("value",s),i.$element.append(p)}n||i.pushVal(),i.options.maxTags===i.itemsArray.length&&i.$container.addClass("bootstrap-tagsinput-max"),i.$element.trigger(t.Event("itemAdded",{item:e}))}}}},remove:function(e,n){var i=this;i.objectItems&&(e="object"==typeof e?t.grep(i.itemsArray,function(t){return i.options.itemValue(t)==i.options.itemValue(e)})[0]:t.grep(i.itemsArray,function(t){return i.options.itemValue(t)==e})[0]),e&&(t(".tag",i.$container).filter(function(){return t(this).data("item")===e}).remove(),t("option",i.$element).filter(function(){return t(this).data("item")===e}).remove(),i.itemsArray.splice(t.inArray(e,i.itemsArray),1)),n||i.pushVal(),i.options.maxTags>i.itemsArray.length&&i.$container.removeClass("bootstrap-tagsinput-max"),i.$element.trigger(t.Event("itemRemoved",{item:e}))},removeAll:function(){var e=this;for(t(".tag",e.$container).remove(),t("option",e.$element).remove();e.itemsArray.length>0;)e.itemsArray.pop();e.pushVal(),e.options.maxTags&&!this.isEnabled()&&this.enable()},refresh:function(){var e=this;t(".tag",e.$container).each(function(){var n=t(this),i=n.data("item"),o=e.options.itemValue(i),a=e.options.itemText(i),s=e.options.tagClass(i);if(n.attr("class",null),n.addClass("tag "+r(s)),n.contents().filter(function(){return 3==this.nodeType})[0].nodeValue=r(a),e.isSelect){var l=t("option",e.$element).filter(function(){return t(this).data("item")===i});l.attr("value",o)}})},items:function(){return this.itemsArray},pushVal:function(){var e=this,n=t.map(e.items(),function(t){return e.options.itemValue(t).toString()});e.$element.val(n,!0).trigger("change")},build:function(e){var r=this;r.options=t.extend({},a,e);var s=r.options.typeahead||{};r.objectItems&&(r.options.freeInput=!1),n(r.options,"itemValue"),n(r.options,"itemText"),n(r.options,"tagClass"),r.options.source&&(s.source=r.options.source),s.source&&t.fn.typeahead&&(i(s,"source"),r.$input.typeahead({source:function(e,n){function i(t){for(var e=[],i=0;i$1
      ")}})),r.$container.on("click",t.proxy(function(t){r.$input.focus()},r)),r.$container.on("keydown","input",t.proxy(function(e){var n=t(e.target),i=r.findInputWrapper();switch(e.which){case 8:if(0===o(n[0])){var a=i.prev();a&&r.remove(a.data("item"))}break;case 46:if(0===o(n[0])){var s=i.next();s&&r.remove(s.data("item"))}break;case 37:var l=i.prev();0===n.val().length&&l[0]&&(l.before(i),n.focus());break;case 39:var u=i.next();0===n.val().length&&u[0]&&(u.after(i),n.focus());break;default:r.options.freeInput&&t.inArray(e.which,r.options.confirmKeys)>=0&&(r.add(n.val()),n.val(""),e.preventDefault())}n.attr("size",Math.max(this.inputSize,n.val().length))},r)),r.$container.on("click","[data-role=remove]",t.proxy(function(e){r.remove(t(e.target).closest(".tag").data("item"))},r)),r.options.itemValue===a.itemValue&&("INPUT"===r.$element[0].tagName?r.add(r.$element.val()):t("option",r.$element).each(function(){r.add(t(this).attr("value"),!0)}))},destroy:function(){var t=this;t.$container.off("keypress","input"),t.$container.off("click","[role=remove]"),t.$container.remove(),t.$element.removeData("tagsinput"),t.$element.show()},focus:function(){this.$input.focus()},input:function(){return this.$input},findInputWrapper:function(){for(var e=this.$input[0],n=this.$container[0];e&&e.parentNode!==n;)e=e.parentNode;return t(e)}},t.fn.tagsinput=function(n,i){var r=[];return this.each(function(){var o=t(this).data("tagsinput");if(o){var a=o[n](i);void 0!==a&&r.push(a)}else o=new e(this,n),t(this).data("tagsinput",o),r.push(o),"SELECT"===this.tagName&&t("option",t(this)).attr("selected","selected"),t(this).val(t(this).val())}),"string"==typeof n?r.length>1?r:r[0]:r},t.fn.tagsinput.Constructor=e;var s=t("
      ");t(function(){t("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput()})}(window.jQuery),function(){window.SocialShareButton={openUrl:function(t,e){return"true"===e?window.open(t,"popup","height=500,width=500"):(window.open(t),!1)},share:function(t){var e,n,i,r,o,a,s,l,u,c,h,d;switch(s=$(t).data("site"),n=$(t).data("appkey")||"",e=$(t).parent(),l=encodeURIComponent($(t).data(s+"-title")||e.data("title")||""),o=encodeURIComponent(e.data("img")||""),c=encodeURIComponent(e.data("url")||""),h=encodeURIComponent(e.data("via")||""),i=encodeURIComponent(e.data("desc")||" "),a=encodeURIComponent(e.data("popup")||"false"),0===c.length&&(c=encodeURIComponent(location.href)),s){case"email":location.href="mailto:?to=&subject="+l+"&body="+c;break;case"weibo":SocialShareButton.openUrl("http://service.weibo.com/share/share.php?url="+c+"&type=3&pic="+o+"&title="+l+"&appkey="+n,a);break;case"twitter":d="",h.length>0&&(d="&via="+h),SocialShareButton.openUrl("https://twitter.com/intent/tweet?url="+c+"&text="+l+d,a);break;case"douban":SocialShareButton.openUrl("http://shuo.douban.com/!service/share?href="+c+"&name="+l+"&image="+o+"&sel="+i,a);break;case"facebook":SocialShareButton.openUrl("http://www.facebook.com/sharer.php?u="+c,a);break;case"qq":SocialShareButton.openUrl("http://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url="+c+"&title="+l+"&pics="+o+"&summary="+i+"&site="+n,a);break;case"tqq":SocialShareButton.openUrl("http://share.v.t.qq.com/index.php?c=share&a=index&url="+c+"&title="+l+"&pic="+o+"&appkey="+n,a);break;case"baidu":SocialShareButton.openUrl("http://hi.baidu.com/pub/show/share?url="+c+"&title="+l+"&content="+i,a);break;case"kaixin001":SocialShareButton.openUrl("http://www.kaixin001.com/rest/records.php?url="+c+"&content="+l+"&style=11&pic="+o+"&aid="+n,a);break;case"renren":SocialShareButton.openUrl("http://widget.renren.com/dialog/share?resourceUrl="+c+"&srcUrl="+c+"&title="+l+"&pic="+o+"&description="+i,a);break;case"google_plus":SocialShareButton.openUrl("https://plus.google.com/share?url="+c,a);break;case"google_bookmark":SocialShareButton.openUrl("https://www.google.com/bookmarks/mark?op=edit&output=popup&bkmk="+c+"&title="+l,a);break;case"delicious":SocialShareButton.openUrl("http://www.delicious.com/save?url="+c+"&title="+l+"&jump=yes&pic="+o,a);break;case"plurk":SocialShareButton.openUrl("http://www.plurk.com/?status="+l+": "+c+"&qualifier=shares",a);break;case"pinterest":SocialShareButton.openUrl("http://www.pinterest.com/pin/create/button/?url="+c+"&media="+o+"&description="+l,a);break;case"tumblr":r=function(e){var n;return n=$(t).attr("data-"+e),n?encodeURIComponent(n):void 0},u=function(){var t,e,n,i;return e=r("type")||"link",t=function(){switch(e){case"text":return l=r("title")||l,"title="+l;case"photo":return l=r("caption")||l,i=r("source")||o,"caption="+l+"&source="+i;case"quote":return n=r("quote")||l,i=r("source")||"","quote="+n+"&source="+i;default:return l=r("title")||l,c=r("url")||c,"name="+l+"&url="+c}}(),"/"+e+"?"+t},SocialShareButton.openUrl("http://www.tumblr.com/share"+u(),a)}return!1}}}.call(this),/*! + SerializeJSON jQuery plugin. + https://github.com/marioizquierdo/jquery.serializeJSON + version 2.4.2 (Oct, 2014) + + Copyright (c) 2014 Mario Izquierdo + Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) + and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. +*/ +function(t){"use strict";t.fn.serializeJSON=function(e){var n,i,r,o,a,s,l;return s=t.serializeJSON,l=s.optsWithDefaults(e),s.validateOptions(l),i=this.serializeArray(),s.readCheckboxUncheckedValues(i,this,l),n={},t.each(i,function(t,e){r=s.splitInputNameIntoKeysArray(e.name),o=r.pop(),"skip"!==o&&(a=s.parseValue(e.value,o,l),l.parseWithFunction&&"_"===o&&(a=l.parseWithFunction(a,e.name)),s.deepSet(n,r,a,l))}),n},t.serializeJSON={defaultOptions:{parseNumbers:!1,parseBooleans:!1,parseNulls:!1,parseAll:!1,parseWithFunction:null,checkboxUncheckedValue:void 0,useIntKeysAsArrayIndex:!1},optsWithDefaults:function(e){var n,i;return null==e&&(e={}),n=t.serializeJSON,i=n.optWithDefaults("parseAll",e),{parseNumbers:i||n.optWithDefaults("parseNumbers",e),parseBooleans:i||n.optWithDefaults("parseBooleans",e),parseNulls:i||n.optWithDefaults("parseNulls",e),parseWithFunction:n.optWithDefaults("parseWithFunction",e),checkboxUncheckedValue:n.optWithDefaults("checkboxUncheckedValue",e),useIntKeysAsArrayIndex:n.optWithDefaults("useIntKeysAsArrayIndex",e)}},optWithDefaults:function(e,n){return n[e]!==!1&&""!==n[e]&&(n[e]||t.serializeJSON.defaultOptions[e])},validateOptions:function(t){var e,n;n=["parseNumbers","parseBooleans","parseNulls","parseAll","parseWithFunction","checkboxUncheckedValue","useIntKeysAsArrayIndex"];for(e in t)if(-1===n.indexOf(e))throw new Error("serializeJSON ERROR: invalid option '"+e+"'. Please use one of "+n.join(","))},parseValue:function(e,n,i){var r;return r=t.serializeJSON,"string"==n?e:"number"==n||i.parseNumbers&&r.isNumeric(e)?Number(e):"boolean"==n||i.parseBooleans&&("true"===e||"false"===e)?-1===["false","null","undefined","","0"].indexOf(e):"null"==n||i.parseNulls&&"null"==e?-1!==["false","null","undefined","","0"].indexOf(e)?null:e:"array"==n||"object"==n?JSON.parse(e):"auto"==n?r.parseValue(e,null,{parseNumbers:!0,parseBooleans:!0,parseNulls:!0}):e},isObject:function(t){return t===Object(t)},isUndefined:function(t){return void 0===t},isValidArrayIndex:function(t){return/^[0-9]+$/.test(String(t))},isNumeric:function(t){return t-parseFloat(t)>=0},splitInputNameIntoKeysArray:function(e){var n,i,r,o,a;return a=t.serializeJSON,o=a.extractTypeFromInputName(e),i=o[0],r=o[1],n=i.split("["),n=t.map(n,function(t){return t.replace(/]/g,"")}),""===n[0]&&n.shift(),n.push(r),n},extractTypeFromInputName:function(e){var n,i;if(i=t.serializeJSON,n=e.match(/(.*):([^:]+)$/)){var r=["string","number","boolean","null","array","object","skip","auto"];if(-1!==r.indexOf(n[2]))return[n[1],n[2]];throw new Error("serializeJSON ERROR: Invalid type "+n[2]+" found in input name '"+e+"', please use one of "+r.join(", "))}return[e,"_"]},deepSet:function(e,n,i,r){var o,a,s,l,u,c;if(null==r&&(r={}),c=t.serializeJSON,c.isUndefined(e))throw new Error("ArgumentError: param 'o' expected to be an object or array, found undefined");if(!n||0===n.length)throw new Error("ArgumentError: param 'keys' expected to be an array with least one element");o=n[0],1===n.length?""===o?e.push(i):e[o]=i:(a=n[1],""===o&&(l=e.length-1,u=e[l],o=c.isObject(u)&&(c.isUndefined(u[a])||n.length>2)?l:l+1),c.isUndefined(e[o])&&(""===a?e[o]=[]:r.useIntKeysAsArrayIndex&&c.isValidArrayIndex(a)?e[o]=[]:e[o]={}),s=n.slice(1),c.deepSet(e[o],s,i,r))},readCheckboxUncheckedValues:function(e,n,i){var r,o,a,s,l;null==i&&(i={}),l=t.serializeJSON,r="input[type=checkbox][name]:not(:checked,[disabled])",o=n.find(r).add(n.filter(r)),o.each(function(n,r){a=t(r),s=a.attr("data-unchecked-value"),s?e.push({name:r.name,value:s}):l.isUndefined(i.checkboxUncheckedValue)||e.push({name:r.name,value:i.checkboxUncheckedValue})})}}}(window.jQuery||window.Zepto||window.$),function(t){t.fn.addNewPageButton=function(e){function n(n){function i(){n.outerWidth()<=h+5&&n.animate({width:a.animate_width+"px"},a.speed,function(){s.show(0,function(){c.focus(),n.width("auto"),n.width(n.width())})})}function r(){s.hide(),n.animate({width:h+"px"},a.speed)}function o(){return""==t.trim(c.val())}var a=t.extend({speed:n.data("speed")||450,animate_width:n.data("animate_width")||425},e),s=t(n.data("field-target")),l=t("input[data-behavior='save']",s),u=t("input[data-behavior='cancel']",s),c=t("input[type='text']",s),h=n.outerWidth();n.on("mouseenter focus",function(){i()}),l.on("click",function(){return o()?!1:void 0}),u.on("click",function(t){t.preventDefault(),c.val(""),r()}),c.on("blur",function(){o()&&r()})}t.each(this,function(){n(t(this))})}}(jQuery),Spotlight.onLoad(function(){$("[data-expanded-add-button]").addNewPageButton()}),Spotlight.onLoad(function(){addRestoreDefaultBehavior()}),Spotlight.onLoad(function(){addCheckboxToggleBehavior(),addEnableToggleBehavior()}),function(t){SirTrevor.BlockMixins.Autocompleteable={mixinName:"Autocompleteable",preload:!0,initializeAutocompleteable:function(){this.on("onRender",this.addAutocompletetoSirTrevorForm),_.isUndefined(this.autocomplete_url)&&(this.autocomplete_url=function(){return t("form[data-autocomplete-url]").data("autocomplete-url").replace("%25QUERY","%QUERY")}),_.isUndefined(this.autocomplete_template)&&(this.autocomplete_url=function(){return'
      {{#if thumbnail}}
      {{/if}}{{title}}
        {{description}}
      '}),_.isUndefined(this.transform_autocomplete_results)&&(this.transform_autocomplete_results=_.identity)},autocomplete_control:function(){return'"/>'},addAutocompletetoSirTrevorForm:function(){this.$("[data-twitter-typeahead]").spotlightSearchTypeAhead({bloodhound:this.bloodhound(),template:this.autocomplete_template()}).on("typeahead:selected typeahead:autocompleted",this.autocompletedHandler()).on("focus",function(){""===t(this).val()&&t(this).data().ttTypeahead.input.trigger("queryChanged","")})},autocompletedHandler:function(e,n){var i=this;return function(e,n){t(this).typeahead("val",""),t(this).val(""),i.createItemPanel(t.extend(n,{display:"true"}))}},bloodhound:function(){var t=this,e=new Bloodhound({datumTokenizer:function(t){return Bloodhound.tokenizers.whitespace(t.title)},queryTokenizer:Bloodhound.tokenizers.whitespace,limit:10,remote:{url:t.autocomplete_url(),filter:t.transform_autocomplete_results}});return e.initialize(),e}},SirTrevor.Block.prototype.availableMixins.push("autocompleteable")}(jQuery),function(t){SirTrevor.BlockMixins.Formable={mixinName:"Formable",preload:!0,initializeFormable:function(){_.isUndefined(this.afterLoadData)&&(this.afterLoadData=function(t){})},formId:function(t){return this.blockID+"_"+t},_serializeData:function(){var e=this.$(":input,textarea,select").not(":input:radio").serializeJSON();return this.$(":input:radio:checked").each(function(n,i){var r=t(i).data("key")||i.getAttribute("name");r.match("\\[")||(e[r]=t(i).val())}),this.hasTextBlock()&&(e.text=this.getTextBlockHTML(),e.text.length>0&&this.options.convertToMarkdown&&(e.text=stToMarkdown(e.text,this.type))),e},loadData:function(t){this.hasTextBlock()&&this.getTextBlock().html(SirTrevor.toHTML(t.text,this.type)),this.loadFormDataByKey(t),this.afterLoadData(t)},loadFormDataByKey:function(e){this.$(":input").not("button,:input[type=hidden]").each(function(n,i){var r=t(i).data("key")||i.getAttribute("name");if(r){r.match("\\[\\]$")&&(r=r.replace("[]",""));var o=e[r];o instanceof Array||(o=[o]),t(this).val(o)}})}},SirTrevor.Block.prototype.availableMixins.push("formable")}(jQuery),function(t){SirTrevor.PreviewButton=function(){this._ensureElement(),this._bindFunctions()},SirTrevor.PreviewButton.prototype=Object.create(SirTrevor.BlockDeletion.prototype),SirTrevor.PreviewButton.prototype.attributes={html:"preview","data-icon":"preview"},SirTrevor.PreviewButton.prototype.className="st-block-ui-btn st-block-ui-btn--preview st-icon",SirTrevor.EditButton=function(){this._ensureElement(),this._bindFunctions()},SirTrevor.EditButton.prototype=Object.create(SirTrevor.BlockDeletion.prototype),SirTrevor.EditButton.prototype.attributes={html:"edit","data-icon":"edit"},SirTrevor.EditButton.prototype.className="st-block-ui-btn st-block-ui-btn--edit st-icon",SirTrevor.BlockMixins.Previewable={mixinName:"Previewable",initializePreviewable:function(){this.on("onRender",this.addPreviewButton),_.isUndefined(this.afterPreviewLoad)&&(this.afterPreviewLoad=function(){})},addPreviewButton:function(){this._prependUIComponent(new SirTrevor.PreviewButton,".st-block-ui-btn--preview",this.previewHandler())},_prependUIComponent:function(t,e,n){this.$ui.prepend(t.render().$el),e&&n&&this.$ui.on("click",e,n)},previewButton:'',previewUrl:function(t){return t.$el.closest("[data-preview-url]").data("preview-url")},previewHandler:function(){var e=this;return function(n){n.stopPropagation();var i=t(this);i.attr("disabled","disabled"),t.post(e.previewUrl(e),{block:JSON.stringify(e.getData())},function(t){e.renderPreview(t).insertAfter(e.$inner),e.$inner.hide(),e.afterPreviewLoad()})}},renderPreview:function(e){var n=(new SirTrevor.EditButton).render().$el;n.on("click",this.editHandler(n));var i=t('
      ').append(n),r=t('
      ');return r.append(e).append(i)},editHandler:function(e){var n=this,i=e;return function(e){e.stopPropagation(),n.$inner.show(),t(this).closest(".preview").remove(),i.removeAttr("disabled")}}},SirTrevor.Block.prototype.availableMixins.push("previewable"),SirTrevor.Block.prototype.previewable=!0}(jQuery),function(t){SirTrevor.BlockMixins.Textable={mixinName:"Textable",preload:!0,initializeTextable:function(){_.isUndefined(this.formId)&&this.withMixin(SirTrevor.BlockMixins.Formable),_.isUndefined(this.show_heading)&&(this.show_heading=!0)},align_key:"text-align",text_key:"item-text",heading_key:"title",text_area:function(){return _.template(['
      ','
      ','
      ',this.heading(),'
      ','','
      ',"
      ","
      ","
      ",'
      ','
      ','

      <%= i18n.t("blocks:textable:align:title") %>

      ','" value="left" checked="true">','','" value="right">','',"
      ","
      ","
      "].join("\n"))(this)},heading:function(){return this.show_heading?['
      ','','',"
      "].join("\n"):""}},SirTrevor.Block.prototype.availableMixins.push("textable")}(jQuery),function(t){SirTrevor.BlockMixins.Titleable={mixinName:"Titleable",initializeTitleable:function(){this.$inner.append("
      "+this.title()+"
      ")}},SirTrevor.Block.prototype.availableMixins.push("titleable"),SirTrevor.Block.prototype.titleable=!0}(jQuery),function(t){Spotlight.Block=SirTrevor.Block.extend({formable:!0,editorHTML:function(){return _.template(this.template)(this)},beforeBlockRender:function(){this.availableMixins.forEach(function(t){this[t]&&SirTrevor.BlockMixins[_.capitalize(t)].preload&&this.withMixin(SirTrevor.BlockMixins[_.capitalize(t)])},this)},$instance:function(){return t("#"+this.instanceID)}})}(jQuery),Spotlight.Block.Resources=function(){return Spotlight.Block.extend({type:"resources",formable:!0,autocompleteable:!0,show_heading:!0,title:function(){return i18n.t("blocks:"+this.type+":title")},description:function(){return i18n.t("blocks:"+this.type+":description")},icon_name:"resources",blockGroup:function(){return i18n.t("blocks:group:items")},primary_field_key:"primary-caption-field",show_primary_field_key:"show-primary-caption",secondary_field_key:"secondary-caption-field",show_secondary_field_key:"show-secondary-caption",display_checkbox:"display-checkbox",globalIndex:0,_itemPanel:function(t){var e,n="item_"+this.globalIndex++;e="true"==t.display?"checked='checked'":"";var i=['
    • ','','','','','','
      <%= i18n.t("blocks:resources:panel:drag") %>
      ','
      ','
      ','
      ','','','',"
      ",'
      ','',"
      ",'
      ','
      '+t.title+"
      ","
      "+(t.slug||t.id)+"
      ",'
      ',"
      ",'","
      ","
      ","
    • "].join("\n"),r=$(_.template(i)(this)),o=this;return $(".remove a",r).on("click",function(t){t.preventDefault(),$(this).closest(".field").remove(),o.afterPanelDelete()}),this.afterPanelRender(t,r),r},afterPanelRender:function(t,e){},afterPanelDelete:function(){},createItemPanel:function(t){var e=this._itemPanel(t);$(e).appendTo(this.$el.find(".panels > ol")),this.$el.find('[data-behavior="nestable"]').trigger("change")},item_options:function(){return""},content:function(){var t=[this.items_selector()];return this.textable&&t.push(this.text_area()),_.template(t.join("
      \n"))(this)},items_selector:function(){return['
      ','
      ','
      ','
        ',this.autocomplete_control(),"
        ","
        ",'
        ',this.item_options(),"
        ","
        "].join("\n")},template:['
        ','
        ',"<%= description() %>","
        ","<%= content() %>","
        "].join("\n"),onBlockRender:function(){SpotlightNestable.init(this.$el.find('[data-behavior="nestable"]')),this.$el.find("[data-input-select-target]").selectRelatedInput()},afterLoadData:function(t){var e=this;$.each(Object.keys(t.item||{}).map(function(e){return t.item[e]}).sort(function(t,e){return t.weight-e.weight}),function(t,n){e.createItemPanel(n)})}})}(),SirTrevor.Blocks.Browse=function(){return Spotlight.Block.Resources.extend({type:"browse",icon_name:"pages",autocomplete_url:function(){return this.$el.closest("form[data-autocomplete-exhibit-searches-path]").data("autocomplete-exhibit-searches-path").replace("%25QUERY","%QUERY")},autocomplete_template:function(){return'
        {{#if thumbnail_image_url}}
        {{/if}}{{title}}
          {{description}}
        '},item_options:function(){return[""].join("\n")}})}(),SirTrevor.Blocks.Iframe=function(){return SirTrevor.Block.extend({type:"Iframe",formable:!0,title:function(){return i18n.t("blocks:iframe:title")},description:function(){return i18n.t("blocks:iframe:description")},icon_name:"iframe",editorHTML:function(){return _.template(this.template,this)(this)},template:['
        ','
        ',"<%= description() %>","
        ",'',"
        "].join("\n")})}(),SirTrevor.Blocks.Oembed=function(){return Spotlight.Block.extend({textable:!0,id_key:"url",type:"oembed",title:function(){return i18n.t("blocks:oembed:title")},description:function(){return i18n.t("blocks:oembed:description")},icon_name:"oembed",show_heading:!1,template:['
        ','
        ',"<%= description() %>","
        ",'
        ','
        ','','',"
        ","
        ","<%= text_area() %>","
        "].join("\n")})}(),SirTrevor.Blocks.FeaturedPages=function(){return Spotlight.Block.Resources.extend({type:"featured_pages",icon_name:"pages",autocomplete_url:function(){return this.$el.closest("form[data-autocomplete-exhibit-feature-pages-path]").data("autocomplete-exhibit-feature-pages-path").replace("%25QUERY","%QUERY")},autocomplete_template:function(){return'
        {{log "Look at me"}}{{log thumbnail_image_url}}{{#if thumbnail_image_url}}
        {{/if}}{{title}}
          {{description}}
        '}})}(),SirTrevor.Blocks.Rule=function(){return SirTrevor.Block.extend({type:"rule",title:function(){return i18n.t("blocks:rule:title")},icon_name:"rule",previewable:!1,editorHTML:function(){return _.template(this.template,this)(this)},template:"
        "})}(),SirTrevor.Blocks.SearchResults=function(){return SirTrevor.Blocks.Browse.extend({type:"search_results",icon_name:"search_results",searches_key:"slug",view_key:"view",textable:!1,content:function(){return _.template([this.items_selector()].join("
        \n"))(this)},item_options:function(){var t=this,e=$("[data-blacklight-configuration-search-views]").data("blacklight-configuration-search-views");return $.map(e,function(e){return checkbox="",checkbox+="
        ",checkbox+="",checkbox+="
        ",checkbox}).join("\n")},afterPanelRender:function(t,e){this.$el.find(".item-input-field").attr("disabled","disabled")},afterPanelDelete:function(){this.$el.find(".item-input-field").removeAttr("disabled")}})}(),SirTrevor.Blocks.SolrDocuments=function(){return Spotlight.Block.Resources.extend({type:"solr_documents",textable:!0,icon_name:"items",autocomplete_url:function(){return this.$instance().closest("form[data-autocomplete-exhibit-catalog-index-path]").data("autocomplete-exhibit-catalog-index-path").replace("%25QUERY","%QUERY")},autocomplete_template:function(){return'
        {{#if thumbnail}}
        {{/if}}{{title}}
          {{description}}
        '},transform_autocomplete_results:function(t){return $.map(t.docs,function(t){return t})},caption_option_values:function(){var t=$("[data-blacklight-configuration-index-fields]").data("blacklight-configuration-index-fields");return $.map(t,function(t){return $(""}),e},addCarouselMaxHeightOptions:function(t){var e="",n=this;return $.each(t.values,function(i,r){var o=i===t.selected?"checked":"",a=n.formId(n.max_height_key);e+='",e+='"}),e},afterPreviewLoad:function(t){this.$el.find(".carousel").carousel();var e=function(t){var e,n=$(this),i=$(n.attr("data-target")||(e=n.attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,""));if(i.hasClass("carousel")){var r=$.extend({},i.data(),n.data()),o=n.attr("data-slide-to");o&&(r.interval=!1),$.fn.carousel.call(i,r),o&&i.data("bs.carousel").to(o),t.preventDefault()}};this.$el.find(".carousel").on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e)}})}(),SirTrevor.Blocks.SolrDocumentsEmbed=function(){return SirTrevor.Blocks.SolrDocuments.extend({type:"solr_documents_embed",icon_name:"item_embed",item_options:function(){return""},afterPreviewLoad:function(t){this.$el.find("picture[data-openseadragon]").openseadragon()}})}(),SirTrevor.Blocks.SolrDocumentsFeatures=function(){return SirTrevor.Blocks.SolrDocuments.extend({textable:!1,type:"solr_documents_features",icon_name:"item_features",afterPreviewLoad:function(t){this.$el.find(".carousel").carousel();var e=function(t){var e,n=$(this),i=$(n.attr("data-target")||(e=n.attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,""));if(i.hasClass("carousel")){var r=$.extend({},i.data(),n.data()),o=n.attr("data-slide-to");o&&(r.interval=!1),$.fn.carousel.call(i,r),o&&i.data("bs.carousel").to(o),t.preventDefault()}};this.$el.find(".carousel").on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e)}})}(),SirTrevor.Blocks.SolrDocumentsGrid=function(){return SirTrevor.Blocks.SolrDocuments.extend({type:"solr_documents_grid",icon_name:"item_grid",item_options:function(){return""}})}(),SirTrevor.Blocks.UploadedItems=function(){return Spotlight.Block.Resources.extend({textable:!0,uploadable:!0,autocompleteable:!1,id_key:"file",type:"uploaded_items",icon_name:"items",blockGroup:"undefined",upload_options:{html:""},fileInput:function(){return this.$el.find('input[type="file"]')},onBlockRender:function(){SpotlightNestable.init(this.$el.find('[data-behavior="nestable"]')),this.fileInput().on("change",function(t){this.onDrop(t.currentTarget)}.bind(this))},onDrop:function(t){var e=t.files[0];"undefined"!=typeof URL?URL:"undefined"!=typeof webkitURL?webkitURL:null;/image/.test(e.type)&&(this.loading(),this.uploader(e,function(t){this.createItemPanel(t),this.fileInput().val(""),this.ready()},function(t){this.addMessage(i18n.t("blocks:image:upload_error")),this.ready()}))},title:function(){return i18n.t("blocks:uploaded_items:title")},description:function(){return i18n.t("blocks:uploaded_items:description")},globalIndex:0,_itemPanel:function(t){var e="file_"+this.globalIndex++,n='checked="checked"';"false"==t.display&&(n="");var i=t.id||t.uid,r=t.title||t.name,o=t.url||t.file.url,a=['
      1. ','','','','','
        <%= i18n.t("blocks:resources:panel:drag") %>
        ','
        ','
        ','
        ','','','',"
        ",'
        ','',"
        ",'
        ','
        '+r+"
        ","
        ",'","
        ","
        ","
      2. "].join("\n"),s=$(_.template(a)(this)),l=this;return $(".remove a",s).on("click",function(t){t.preventDefault(),$(this).closest(".field").remove(),l.afterPanelDelete()}),this.afterPanelRender(t,s),s},template:['
        ','
        ',"<%= description() %>","
        ",'
        ','
        ','
        ','
          ',"
        ","
        ",'',"
        ","
        ","<%= text_area() %>","
        "].join("\n")})}(),Spotlight.onLoad(function(){if($("#solr_document_exhibit_tag_list").length>0){$("#solr_document_exhibit_tag_list").tagsinput();var t=new Bloodhound({datumTokenizer:function(t){return Bloodhound.tokenizers.whitespace(t.name)},queryTokenizer:Bloodhound.tokenizers.whitespace,limit:10,prefetch:{url:$("#solr_document_exhibit_tag_list").data("autocomplete_url"),ttl:1,filter:function(t){return $.map(t,function(t){return{name:t}})}}});t.initialize(),$("#solr_document_exhibit_tag_list").tagsinput("input").typeahead({highlight:!0,hint:!1},{name:"tags",displayKey:"name",source:t.ttAdapter()}).bind("typeahead:selected",$.proxy(function(t,e){$("#solr_document_exhibit_tag_list").tagsinput("add",e.name),$("#solr_document_exhibit_tag_list").tagsinput("input").typeahead("val","")})).bind("blur",function(){$("#solr_document_exhibit_tag_list").tagsinput("add",$("#solr_document_exhibit_tag_list").tagsinput("input").typeahead("val")),$("#solr_document_exhibit_tag_list").tagsinput("input").typeahead("val","")})}$(".visiblity_toggle").bl_checkbox_submit({css_class:"toggle_visibility",success:function(t){var e=$($(this).data("label-toggle-target"));t?e.removeClass("blacklight-private"):e.addClass("blacklight-private")}})}),function(t){t.fn.spotlightCheckUserExistence=function(){function e(){d=t(this),""!==d.val()&&c()[0].checkValidity()?t.ajax(h()).success(i).fail(r):i()}function n(){""===t(this).val()&&i()}function i(){l().hide(),u().prop("disabled",!1)}function r(){o(),l().show(),s().on("change",o),u().prop("disabled",!0)}function o(){var t=l().find("a"),e=t.data("inviteUrl"),n=d.val();t.attr("href",e+"?user="+encodeURIComponent(n)+"&role="+encodeURIComponent(a()))}function a(){return s().length>0?s().val():d.closest("tr").find("[data-user-role]").data("userRole")}function s(){return d.closest("tr").find("select")}function l(){return c().find('[data-behavior="no-user-note"]')}function u(){return c().find('input[type="submit"]')}function c(){return d.closest("form")}function h(){return t("[data-user-exists-url]").data("userExistsUrl")+"?user="+d.val()}var d,p=this;return t(p).each(function(){t(this).on("blur",e),t(this).on("change",n)}),this}}(jQuery),Spotlight.onLoad(function(){$('[data-behavior="check-user-existence"]').spotlightCheckUserExistence()}),/** +* @license +* Copyright (c) 2014 The Polymer Project Authors. All rights reserved. +* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt +* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt +* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt +* Code distributed by Google as part of the polymer project is also +* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt +*/ +Function.prototype.bind||(Function.prototype.bind=function(t){var e=this,n=Array.prototype.slice.call(arguments,1);return function(){var i=n.slice();return i.push.apply(i,arguments),e.apply(t,i)}}),function(t){function e(t){w.push(t),b||(b=!0,m(i))}function n(t){return window.ShadowDOMPolyfill&&window.ShadowDOMPolyfill.wrapIfNeeded(t)||t}function i(){b=!1;var t=w;w=[],t.sort(function(t,e){return t.uid_-e.uid_});var e=!1;t.forEach(function(t){var n=t.takeRecords();r(t),n.length&&(t.callback_(n,t),e=!0)}),e&&i()}function r(t){t.nodes_.forEach(function(e){var n=g.get(e);n&&n.forEach(function(e){e.observer===t&&e.removeTransientObservers()})})}function o(t,e){for(var n=t;n;n=n.parentNode){var i=g.get(n);if(i)for(var r=0;r>>0)+(e++ +"__")};n.prototype={set:function(e,n){var i=e[this.name];return i&&i[0]===e?i[1]=n:t(e,this.name,{value:[e,n],writable:!0}),this},get:function(t){var e;return(e=t[this.name])&&e[0]===t?e[1]:void 0},"delete":function(t){var e=t[this.name];return e&&e[0]===t?(e[0]=e[1]=void 0,!0):!1},has:function(t){var e=t[this.name];return e?e[0]===t:!1}},window.WeakMap=n}();var m,g=new WeakMap;if(/Trident|Edge/.test(navigator.userAgent))m=setTimeout;else if(window.setImmediate)m=window.setImmediate;else{var v=[],y=String(Math.random());window.addEventListener("message",function(t){if(t.data===y){var e=v;v=[],e.forEach(function(t){t()})}}),m=function(t){v.push(t),window.postMessage(y,"*")}}var b=!1,w=[],x=0;a.prototype={observe:function(t,e){if(t=n(t),!e.childList&&!e.attributes&&!e.characterData||e.attributeOldValue&&!e.attributes||e.attributeFilter&&e.attributeFilter.length&&!e.attributes||e.characterDataOldValue&&!e.characterData)throw new SyntaxError;var i=g.get(t);i||g.set(t,i=[]);for(var r,o=0;o0){var r=n[i-1],o=p(r,t);if(o)return void(n[i-1]=o)}else e(this.observer);n[i]=t},addListeners:function(){this.addListeners_(this.target)},addListeners_:function(t){var e=this.options;e.attributes&&t.addEventListener("DOMAttrModified",this,!0),e.characterData&&t.addEventListener("DOMCharacterDataModified",this,!0),e.childList&&t.addEventListener("DOMNodeInserted",this,!0),(e.childList||e.subtree)&&t.addEventListener("DOMNodeRemoved",this,!0)},removeListeners:function(){this.removeListeners_(this.target)},removeListeners_:function(t){var e=this.options;e.attributes&&t.removeEventListener("DOMAttrModified",this,!0),e.characterData&&t.removeEventListener("DOMCharacterDataModified",this,!0),e.childList&&t.removeEventListener("DOMNodeInserted",this,!0),(e.childList||e.subtree)&&t.removeEventListener("DOMNodeRemoved",this,!0)},addTransientObserver:function(t){if(t!==this.target){this.addListeners_(t),this.transientObservedNodes.push(t);var e=g.get(t);e||g.set(t,e=[]),e.push(this)}},removeTransientObservers:function(){var t=this.transientObservedNodes;this.transientObservedNodes=[],t.forEach(function(t){this.removeListeners_(t);for(var e=g.get(t),n=0;n0&&(u[0].src=t.target.result),e.setSelect(d.setSelect)}}(i),r.readAsDataURL(n)}else{var o=t(this).attr("value");e.setImage(o),l.css({width:"",height:""}),l[0].src=o,u.length>0&&(u[0].src=o),e.setSelect(d.setSelect)}l.closest(".missing-croppable").removeClass("missing-croppable")})}),this}}(jQuery),Spotlight.onLoad(function(){$("[data-in-place-edit-target]").spotlightEditInPlace()}),function(t){t.fn.spotlightEditInPlace=function(){var e=this;return t(e).each(function(){t(this).on("click.inplaceedit",function(){var e=t(this).find(t(this).data("in-place-edit-target")),n=t(this).find(t(this).data("in-place-edit-field-target"));return t(this).addClass("hide-edit-icon"),e.hide(),n.val(e.text()),n.attr("type","text"),n.select(),n.focus(),n.on("keypress",function(t){return 13==t.which?(n.trigger("blur.inplaceedit"),!1):void 0}),n.on("blur.inplaceedit",function(){return e.text(n.val()),e.show(),n.attr("type","hidden"),t("[data-in-place-edit-target]").removeClass("hide-edit-icon"),!1}),!1})}),this}}(jQuery),Spotlight.onLoad(function(){$("#new_exhibit").each(function(){$("#exhibit_title").on("change keyup",function(){$("#exhibit_slug").attr("placeholder",URLify($(this).val(),$(this).val().length))}),$("#exhibit_slug").on("focus",function(){""===$(this).val()&&$(this).val($(this).attr("placeholder"))})}),$("#another-email").on("click",function(){var t=$(this).closest(".form-group"),e=t.find(".contact"),n=e.first().clone();n.find("input").each(function(){$(this).val(""),$(this).attr("id",$(this).attr("id").replace("0",e.length)),$(this).attr("name",$(this).attr("name").replace("0",e.length))}),n.find(".first-row-only").remove(),n.find(".input-group input:only-child").closest(".input-group").removeClass("input-group"),$(n).insertAfter(e.last())}),$(".btn-with-tooltip").tooltip(),$("#save-modal").on("shown.bs.modal",function(){$("#search_title").focus()})}),Spotlight.onLoad(function(){serializeObservedForms(observedForms())}),$(window).on("beforeunload page:before-change",function(t){if(observedFormsStatusHasChanged()){var e="You have unsaved changes. Are you sure you want to leave this page?";return"beforeunload"==t.type?e:confirm(e)}}),/** + * jquery.Jcrop.js v0.9.12 + * jQuery Image Cropping Plugin - released under MIT License + * Author: Kelly Hallman + * http://github.com/tapmodo/Jcrop + * Copyright (c) 2008-2013 Tapmodo Interactive LLC {{{ + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * }}} + * + * This is a fork of the above mentioned code which is available at https://github.com/sul-dlss/Jcrop + */ +function(t){t.Jcrop=function(e,n){function i(t){return Math.round(t)+"px"}function r(t){return M.baseClass+"-"+t}function o(){return t.fx.step.hasOwnProperty("backgroundColor")}function a(e){var n=t(e).offset();return[n.left,n.top]}function s(t){return[t.pageX-I[0],t.pageY-I[1]]}function l(e){"object"!=typeof e&&(e={}),M=t.extend(M,e),t.each(["onChange","onSelect","onRelease","onDblClick"],function(t,e){"function"!=typeof M[e]&&(M[e]=function(){})})}function u(t,e,n){if(I=a(U),ft.setCursor("move"===t?t:t+"-resize"),"move"===t)return ft.activateHandlers(h(e),g,n);var i=ht.getFixed(),r=d(t),o=ht.getCorner(d(r));ht.setPressed(ht.getCorner(r)),ht.setCurrent(o),ft.activateHandlers(c(t,i),g,n)}function c(t,e){return function(n){if(M.aspectRatio)switch(t){case"e":n[1]=e.y+1;break;case"w":n[1]=e.y+1;break;case"n":n[0]=e.x+1;break;case"s":n[0]=e.x+1}else switch(t){case"e":n[1]=e.y2;break;case"w":n[1]=e.y2;break;case"n":n[0]=e.x2;break;case"s":n[0]=e.x2}ht.setCurrent(n),pt.update()}}function h(t){var e=t;return mt.watchKeys(),function(t){ht.moveOffset([t[0]-e[0],t[1]-e[1]]),e=t,pt.update()}}function d(t){switch(t){case"n":return"sw";case"s":return"nw";case"e":return"nw";case"w":return"ne";case"ne":return"sw";case"nw":return"se";case"se":return"nw";case"sw":return"ne"}}function p(t){return function(e){return M.disabled?!1:"move"!==t||M.allowMove?(I=a(U),it=!0,u(t,s(e)),e.stopPropagation(),e.preventDefault(),!1):!1}}function f(t,e,n){var i=t.width(),r=t.height();i>e&&e>0&&(i=e,r=e/t.width()*t.height()),r>n&&n>0&&(r=n,i=n/t.height()*t.width()),et=t.width()/i,nt=t.height()/r,t.width(i).height(r)}function m(t){return{x:t.x*et,y:t.y*nt,x2:t.x2*et,y2:t.y2*nt,w:t.w*et,h:t.h*nt}}function g(t){var e=ht.getFixed();e.w>M.minSelect[0]&&e.h>M.minSelect[1]?(pt.enableHandles(),pt.done()):pt.release(),ft.setCursor(M.allowSelect?"crosshair":"default")}function v(t){if(!M.disabled&&M.allowSelect){it=!0,I=a(U),pt.disableHandles(),ft.setCursor("crosshair");var e=s(t);return ht.setPressed(e),pt.update(),ft.activateHandlers(y,g,"touch"===t.type.substring(0,5)),mt.watchKeys(),t.stopPropagation(),t.preventDefault(),!1}}function y(t){ht.setCurrent(t),pt.update()}function b(){var e=t("
        ").addClass(r("tracker"));return R&&e.css({opacity:0,backgroundColor:"white"}),e}function w(t){W.removeClass().addClass(r("holder")).addClass(t)}function x(t,e){function n(){window.setTimeout(y,h)}var i=t[0]/et,r=t[1]/nt,o=t[2]/et,a=t[3]/nt;if(!rt){var s=ht.flipCoords(i,r,o,a),l=ht.getFixed(),u=[l.x,l.y,l.x2,l.y2],c=u,h=M.animationDelay,d=s[0]-u[0],p=s[1]-u[1],f=s[2]-u[2],m=s[3]-u[3],g=0,v=M.swingSpeed;i=c[0],r=c[1],o=c[2],a=c[3],pt.animMode(!0);var y=function(){return function(){g+=(100-g)/v,c[0]=Math.round(i+g/100*d),c[1]=Math.round(r+g/100*p),c[2]=Math.round(o+g/100*f),c[3]=Math.round(a+g/100*m),g>=99.8&&(g=100),100>g?(k(c),n()):(pt.done(),pt.animMode(!1),"function"==typeof e&&e.call(gt))}}();n()}}function _(t){k([t[0]/et,t[1]/nt,t[2]/et,t[3]/nt]),M.onSelect.call(gt,m(ht.getFixed())),pt.enableHandles()}function k(t){ht.setPressed([t[0],t[1]]),ht.setCurrent([t[2],t[3]]),pt.update()}function $(){return m(ht.getFixed())}function S(){return ht.getFixed()}function T(t){l(t),O()}function E(){M.disabled=!0,pt.disableHandles(),pt.setCursor("default"),ft.setCursor("default")}function C(){M.disabled=!1,O()}function P(){pt.done(),ft.activateHandlers(null,null)}function A(){W.remove(),F.show(),F.css("visibility","visible"),t(e).removeData("Jcrop")}function D(t,e){pt.release(),E();var n=new Image;n.onload=function(){var i=n.width,r=n.height,o=M.boxWidth,a=M.boxHeight;U.width(i).height(r),U.attr("src",t),K.attr("src",t),f(U,o,a),q=U.width(),V=U.height(),K.width(q).height(V),st.width(q+2*at).height(V+2*at),W.width(q).height(V),dt.resize(q,V),C(),"function"==typeof e&&e.call(gt)},n.src=t}function N(t,e,n){var i=e||M.bgColor;M.bgFade&&o()&&M.fadeTime&&!n?t.animate({backgroundColor:i},{queue:!1,duration:M.fadeTime}):t.css("backgroundColor",i)}function O(t){M.allowResize?t?pt.enableOnly():pt.enableHandles():pt.disableHandles(),ft.setCursor(M.allowSelect?"crosshair":"default"),pt.setCursor(M.allowMove?"move":"default"),M.hasOwnProperty("trueSize")&&(et=M.trueSize[0]/q,nt=M.trueSize[1]/V),M.hasOwnProperty("setSelect")&&(_(M.setSelect),pt.done(),delete M.setSelect),dt.refresh(),M.bgColor!=lt&&(N(M.shade?dt.getShades():W,M.shade?M.shadeColor||M.bgColor:M.bgColor),lt=M.bgColor),ut!=M.bgOpacity&&(ut=M.bgOpacity,M.shade?dt.refresh():pt.setBgOpacity(ut)),Z=M.maxSize[0]||0,Q=M.maxSize[1]||0,J=M.minSize[0]||0,tt=M.minSize[1]||0,M.hasOwnProperty("outerImage")&&(U.attr("src",M.outerImage),delete M.outerImage),pt.refresh()}var I,M=t.extend({},t.Jcrop.defaults),B=navigator.userAgent.toLowerCase(),R=/msie/.test(B),j=/msie [1-6]\./.test(B);"object"!=typeof e&&(e=t(e)[0]),"object"!=typeof n&&(n={}),l(n);var L={border:"none",visibility:"visible",margin:0,padding:0,position:"absolute",top:0,left:0},F=t(e),H=!0;if("IMG"==e.tagName){if(0!=F[0].width&&0!=F[0].height)F.width(F[0].width),F.height(F[0].height);else{var z=new Image;z.src=F[0].src,F.width(z.width),F.height(z.height)}var U=F.clone().removeAttr("id").css(L).show();U.width(F.width()),U.height(F.height()),F.after(U).hide()}else U=F.css(L).show(),H=!1,null===M.shade&&(M.shade=!0);f(U,M.boxWidth,M.boxHeight);var q=U.width(),V=U.height(),W=t("
        ").width(q).height(V).addClass(r("holder")).css({position:"relative",backgroundColor:M.bgColor}).insertAfter(F).append(U);M.addClass&&W.addClass(M.addClass);var K=t("
        "),G=t("
        ").width("100%").height("100%").css({zIndex:310,position:"absolute",overflow:"hidden"}),X=t("
        ").width("100%").height("100%").css("zIndex",320),Y=t("
        ").css({position:"absolute",zIndex:600}).dblclick(function(){var t=ht.getFixed();M.onDblClick.call(gt,t)}).insertBefore(U).append(G,X);H&&(K=t("").attr("src",U.attr("src")).css(L).width(q).height(V),G.append(K)),j&&Y.css({overflowY:"hidden"});var Z,Q,J,tt,et,nt,it,rt,ot,at=M.boundary,st=b().width(q+2*at).height(V+2*at).css({position:"absolute",top:i(-at),left:i(-at),zIndex:290}).mousedown(v),lt=M.bgColor,ut=M.bgOpacity;I=a(U);var ct=function(){function t(){var t,e={},n=["touchstart","touchmove","touchend"],i=document.createElement("div");try{for(t=0;td+e&&(e-=e+d),0>p+n&&(n-=n+p),m+n>V&&(n+=V-(m+n)),f+e>q&&(e+=q-(f+e)),d+=e,f+=e,p+=n,m+=n}function r(t){var e=o();switch(t){case"ne":return[e.x2,e.y];case"nw":return[e.x,e.y];case"se":return[e.x2,e.y2];case"sw":return[e.x,e.y2]}}function o(){if(!M.aspectRatio)return l();var t,e,n,i,r=M.aspectRatio,o=M.minSize[0]/et,a=M.maxSize[0]/et,c=M.maxSize[1]/nt,h=f-d,g=m-p,v=Math.abs(h),y=Math.abs(g),b=v/y;return 0===a&&(a=10*q),0===c&&(c=10*V),r>b?(e=m,n=y*r,t=0>h?d-n:n+d,0>t?(t=0,i=Math.abs((t-d)/r),e=0>g?p-i:i+p):t>q&&(t=q,i=Math.abs((t-d)/r),e=0>g?p-i:i+p)):(t=f,i=v/r,e=0>g?p-i:p+i,0>e?(e=0,n=Math.abs((e-p)*r),t=0>h?d-n:n+d):e>V&&(e=V,n=Math.abs(e-p)*r,t=0>h?d-n:n+d)),t>d?(o>t-d?t=d+o:t-d>a&&(t=d+a),e=e>p?p+(t-d)/r:p-(t-d)/r):d>t&&(o>d-t?t=d-o:d-t>a&&(t=d-a),e=e>p?p+(d-t)/r:p-(d-t)/r),0>t?(d-=t,t=0):t>q&&(d-=t-q,t=q),0>e?(p-=e,e=0):e>V&&(p-=e-V,e=V),u(s(d,p,t,e))}function a(t){return t[0]<0&&(t[0]=0),t[1]<0&&(t[1]=0),t[0]>q&&(t[0]=q),t[1]>V&&(t[1]=V),[Math.round(t[0]),Math.round(t[1])]}function s(t,e,n,i){var r=t,o=n,a=e,s=i;return t>n&&(r=n,o=t),e>i&&(a=i,s=e),[r,a,o,s]}function l(){var t,e=f-d,n=m-p;return Z&&Math.abs(e)>Z/et&&(f=e>0?d+Z/et:d-Z/et),Q&&Math.abs(n)>Q/nt&&(m=n>0?p+Q/nt:p-Q/nt),tt/nt&&Math.abs(n)0?p+tt/nt:p-tt/nt),J/et&&Math.abs(e)0?d+J/et:d-J/et),0>d&&(f-=d,d-=d),0>p&&(m-=p,p-=p),0>f&&(d-=f,f-=f),0>m&&(p-=m,m-=m),f>q&&(t=f-q,d-=t,f-=t),m>V&&(t=m-V,p-=t,m-=t),d>q&&(t=d-V,m-=t,p-=t),p>V&&(t=p-V,m-=t,p-=t),u(s(d,p,f,m))}function u(t){return{x:t[0],y:t[1],x2:t[2],y2:t[3],w:t[2]-t[0],h:t[3]-t[1]}}var c,h,d=0,p=0,f=0,m=0;return{flipCoords:s,setPressed:t,setCurrent:e,getOffset:n,moveOffset:i,getCorner:r,getFixed:o}}(),dt=function(){function e(t,e){f.left.css({height:i(e)}),f.right.css({height:i(e)})}function n(){return r(ht.getFixed())}function r(t){f.top.css({left:i(t.x),width:i(t.w),height:i(t.y)}),f.bottom.css({top:i(t.y2),left:i(t.x),width:i(t.w),height:i(V-t.y2)}),f.right.css({left:i(t.x2),width:i(q-t.x2)}),f.left.css({width:i(t.x)})}function o(){return t("
        ").css({position:"absolute",backgroundColor:M.shadeColor||M.bgColor}).appendTo(p)}function a(){d||(d=!0,p.insertBefore(U),n(),pt.setBgOpacity(1,0,1),K.hide(),s(M.shadeColor||M.bgColor,1),pt.isAwake()?u(M.bgOpacity,1):u(1,1))}function s(t,e){N(h(),t,e)}function l(){d&&(p.remove(),K.show(),d=!1,pt.isAwake()?pt.setBgOpacity(M.bgOpacity,1,1):(pt.setBgOpacity(1,1,1),pt.disableHandles()),N(W,0,1))}function u(t,e){d&&(M.bgFade&&!e?p.animate({opacity:1-t},{queue:!1,duration:M.fadeTime}):p.css({opacity:1-t}))}function c(){M.shade?a():l(),pt.isAwake()&&u(M.bgOpacity)}function h(){return p.children()}var d=!1,p=t("
        ").css({position:"absolute",zIndex:240,opacity:0}),f={top:o(),left:o().height(V),right:o().height(V),bottom:o()};return{update:n,updateRaw:r,getShades:h,setBgColor:s,enable:a,disable:l,resize:e,refresh:c,opacity:u}}(),pt=function(){function e(e){var n=t("
        ").css({position:"absolute",opacity:M.borderOpacity}).addClass(r(e));return G.append(n),n}function n(e,n){var i=t("
        ").mousedown(p(e)).css({cursor:e+"-resize",position:"absolute",zIndex:n}).addClass("ord-"+e);return ct.support&&i.bind("touchstart.jcrop",ct.createDragger(e)),X.append(i),i}function o(t){var e=M.handleSize,i=n(t,E++).css({opacity:M.handleOpacity}).addClass(r("handle"));return e&&i.width(e).height(e),i}function a(t){return n(t,E++).addClass("jcrop-dragbar")}function s(t){var e;for(e=0;e').css({position:"fixed",left:"-120px",width:"12px"}).addClass("jcrop-keymgr"),a=t("
        ").css({position:"absolute",overflow:"hidden"}).append(o);return M.keySupport&&(o.keydown(r).blur(n),j||!M.fixedSupport?(o.css({position:"absolute",left:"-20px"}),a.append(o).insertBefore(U)):o.insertBefore(U)),{watchKeys:e}}();ct.support&&st.bind("touchstart.jcrop",ct.newSelection),X.hide(),O(!0);var gt={setImage:D,animateTo:x,setSelect:_,setOptions:T,tellSelect:$,tellScaled:S,setClass:w,disable:E,enable:C,cancel:P,release:pt.release,destroy:A,focus:mt.watchKeys,getBounds:function(){return[q*et,V*nt]},getWidgetSize:function(){return[q,V]},getScaleFactor:function(){return[et,nt]},getOptions:function(){return M},ui:{holder:W,selection:Y}};return R&&W.bind("selectstart",function(){return!1}),F.data("Jcrop",gt),gt},t.fn.Jcrop=function(e,n){var i;return this.each(function(){if(t(this).data("Jcrop")){if("api"===e)return t(this).data("Jcrop");t(this).data("Jcrop").setOptions(e)}else"IMG"==this.tagName?t.Jcrop.Loader(this,function(){t(this).css({display:"block",visibility:"hidden"}),i=t.Jcrop(this,e),t.isFunction(n)&&n.call(i)}):(t(this).css({display:"block",visibility:"hidden"}),i=t.Jcrop(this,e),t.isFunction(n)&&n.call(i))}),this},t.Jcrop.Loader=function(e,n,i){function r(){a.complete?(o.unbind(".jcloader"),t.isFunction(n)&&n.call(a)):window.setTimeout(r,50)}var o=t(e),a=o[0];o.bind("load.jcloader",r).bind("error.jcloader",function(e){o.unbind(".jcloader"),t.isFunction(i)&&i.call(a)}),a.complete&&t.isFunction(n)&&(o.unbind(".jcloader"),n.call(a))},t.Jcrop.defaults={allowSelect:!0,allowMove:!0,allowResize:!0,trackDocument:!0,baseClass:"jcrop",addClass:null,bgColor:"black",bgOpacity:.6,bgFade:!1,borderOpacity:.4,handleOpacity:.5,handleSize:null,aspectRatio:0,keySupport:!0,createHandles:["n","s","e","w","nw","ne","se","sw"],createDragbars:["n","s","e","w"],createBorders:["n","s","e","w"],drawBorders:!0,dragEdges:!0,fixedSupport:!0,touchSupport:null,shade:null,boxWidth:0,boxHeight:0,boundary:2,fadeTime:400,animationDelay:20,swingSpeed:3,minSelect:[0,0],maxSize:[0,0],minSize:[0,0],onChange:function(){},onSelect:function(){},onDblClick:function(){},onRelease:function(){}}}(jQuery),Spotlight.onLoad(function(){function t(t){$.ajax({url:$(t).data("lock"),type:"POST",data:{_method:"delete"},async:!1}),$(t).removeAttr("data-lock")}$("[data-lock]").on("click",function(e){t(this)})}),function(){$.fn.multiImageSelector=function(t){function e(e){f=e,t&&t.length>1&&(n(),c())}function n(){$("[data-panel-image-pagination]",f).html("Image "+r(i())+" of "+t.length).show().append(m),o()}function i(){return $("[data-item-grid-thumbnail]",f).attr("value")}function r(t){return(index=y.indexOf(t))>-1?index+1:1}function o(){m.on("click",function(){g.slideToggle(),a(),l(),u(),s(),d($(this))})}function a(){var t=0;$("li",v).each(function(){t+=$(this).outerWidth()}),v.width(t+5)}function s(){var t=g.width(),e=0;$("li",v).each(function(){var n=$(this),i=$("img",n),r=e+=n.width();position=v.position().left+r-n.width(),position>=0&&position');n.on("click",function(){var t=$("img",$(this)).attr("src");$("li",v).removeClass("active"),$(this).addClass("active"),$(".pic.thumbnail img",f).attr("src",t),$("[data-item-grid-thumbnail]",f).attr("value",t),$("[data-item-grid-full-image]",f).attr("value",$("img",$(this)).data("full-image")),$("[data-panel-image-pagination] [data-current-image]",f).text(r(i())),u()}),$("img",n).on("load",function(){a()}),v.append(n)})}var f,m=$(" Change"),g=$(""),v=$("
          "),y=$.map(t,function(t){return t.thumb});return e(this)}}(jQuery),jQuery.fn.scrollStop=function(t){$(this).scroll(function(){var e=this,n=$(e);n.data("scrollTimeout")&&clearTimeout(n.data("scrollTimeout")),n.data("scrollTimeout",setTimeout(t,250,e))})},Spotlight.onLoad(function(){SirTrevor.setDefaults({uploadUrl:$("[data-attachment-endpoint]").data("attachment-endpoint")});var t=$(".js-st-instance").first();if(t.length){var e=new SirTrevor.Editor({el:t,onEditorRender:function(){serializeObservedForms(observedForms())},blockTypeLimits:{SearchResults:1,Tweet:-1}});new Spotlight.BlockLimits(e).enforceLimits()}$(".carousel").carousel()}),Spotlight.onLoad(function(){$("input[type='checkbox'][data-readonly]").on("click",function(t){t.preventDefault()})}),Spotlight.onLoad(function(){$('[data-behavior="reindex-monitor"]').reindexMonitor()}),function(t){t.fn.reindexMonitor=function(){function e(e){t.ajax(e).success(n).fail(i)}function n(t){t.in_progress?(u().show(),r(t)):u().hide()}function i(){u().hide()}function r(t){c().text(t.started),h().text(t.updated_at),p().text(t.completed),s(t),a(t),o(t)}function o(t){var e=l(t);f().attr("aria-valuemax",t.total).attr("aria-valuenow",e).css("width",e+"%").text(e+"%")}function a(t){t.errored?m().show():m().hide()}function s(e){d().each(function(){t(this).text(e.total)})}function l(t){return Math.floor(t.completed/t.total*100)}function u(){return g.find(".index-status")}function c(){return u().find('[data-behavior="monitor-start"]').find('[data-behavior="date"]')}function h(){return u().find('[data-behavior="monitor-current"]').find('[data-behavior="date"]')}function d(){return u().find('[data-behavior="total"]')}function p(){return u().find('[data-behavior="monitor-current"]').find('[data-behavior="completed"]')}function f(){return u().find(".progress-bar")}function m(){return u().find('[data-behavior="monitor-error"]')}var g,v=this,y=3e3;return t(v).each(function(){g=t(this);var n=g.data("monitorUrl"),i=g.data("refreshRate")||y;setInterval(function(){e(n)},i)}),this}}(jQuery),function(t){t.fn.reportProblem=function(e){function n(){target_val=o.attr("data-target"),target_val&&(a=t("#"+target_val),o.on("click",i),a.find('[data-behavior="cancel-link"]').on("click",r))}function i(t){t.preventDefault(),a.slideToggle("slow")}function r(t){t.preventDefault(),a.slideUp("fast")}var o,a;t.extend({},e);return this.each(function(){o=t(this),n()})}}(jQuery),Spotlight.onLoad(function(){$('[data-behavior="contact-link"]').reportProblem()}),/*! + * typeahead.js 0.10.2 + * https://github.com/twitter/typeahead.js + * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT + */ +!function(t){var e={isMsie:function(){return/(msie|trident)/i.test(navigator.userAgent)?navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2]:!1},isBlankString:function(t){return!t||/^\s*$/.test(t)},escapeRegExChars:function(t){return t.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},isString:function(t){return"string"==typeof t},isNumber:function(t){return"number"==typeof t},isArray:t.isArray,isFunction:t.isFunction,isObject:t.isPlainObject,isUndefined:function(t){return"undefined"==typeof t},bind:t.proxy,each:function(e,n){function i(t,e){return n(e,t)}t.each(e,i)},map:t.map,filter:t.grep,every:function(e,n){var i=!0;return e?(t.each(e,function(t,r){return(i=n.call(null,r,t,e))?void 0:!1}),!!i):i},some:function(e,n){var i=!1;return e?(t.each(e,function(t,r){return(i=n.call(null,r,t,e))?!1:void 0}),!!i):i},mixin:t.extend,getUniqueId:function(){var t=0;return function(){return t++}}(),templatify:function(e){function n(){return String(e)}return t.isFunction(e)?e:n},defer:function(t){setTimeout(t,0)},debounce:function(t,e,n){var i,r;return function(){var o,a,s=this,l=arguments;return o=function(){i=null,n||(r=t.apply(s,l))},a=n&&!i,clearTimeout(i),i=setTimeout(o,e),a&&(r=t.apply(s,l)),r}},throttle:function(t,e){var n,i,r,o,a,s;return a=0,s=function(){a=new Date,r=null,o=t.apply(n,i)},function(){var l=new Date,u=e-(l-a);return n=this,i=arguments,0>=u?(clearTimeout(r),r=null,a=l,o=t.apply(n,i)):r||(r=setTimeout(s,u)),o}},noop:function(){}},n="0.10.2",i=function(){function t(t){return t.split(/\s+/)}function e(t){return t.split(/\W+/)}function n(t){return function(e){return function(n){return t(n[e])}}}return{nonword:e,whitespace:t,obj:{nonword:n(e),whitespace:n(t)}}}(),r=function(){function t(t){this.maxSize=t||100,this.size=0,this.hash={},this.list=new n}function n(){this.head=this.tail=null}function i(t,e){this.key=t,this.val=e,this.prev=this.next=null}return e.mixin(t.prototype,{set:function(t,e){var n,r=this.list.tail;this.size>=this.maxSize&&(this.list.remove(r),delete this.hash[r.key]),(n=this.hash[t])?(n.val=e,this.list.moveToFront(n)):(n=new i(t,e),this.list.add(n),this.hash[t]=n,this.size++)},get:function(t){var e=this.hash[t];return e?(this.list.moveToFront(e),e.val):void 0}}),e.mixin(n.prototype,{add:function(t){this.head&&(t.next=this.head,this.head.prev=t),this.head=t,this.tail=this.tail||t},remove:function(t){t.prev?t.prev.next=t.next:this.head=t.next,t.next?t.next.prev=t.prev:this.tail=t.prev},moveToFront:function(t){this.remove(t),this.add(t)}}),t}(),o=function(){function t(t){this.prefix=["__",t,"__"].join(""),this.ttlKey="__ttl__",this.keyMatcher=new RegExp("^"+this.prefix)}function n(){return(new Date).getTime()}function i(t){return JSON.stringify(e.isUndefined(t)?null:t)}function r(t){return JSON.parse(t)}var o,a;try{o=window.localStorage,o.setItem("~~~","!"),o.removeItem("~~~")}catch(s){o=null}return a=o&&window.JSON?{_prefix:function(t){return this.prefix+t},_ttlKey:function(t){return this._prefix(t)+this.ttlKey},get:function(t){return this.isExpired(t)&&this.remove(t),r(o.getItem(this._prefix(t)))},set:function(t,r,a){return e.isNumber(a)?o.setItem(this._ttlKey(t),i(n()+a)):o.removeItem(this._ttlKey(t)),o.setItem(this._prefix(t),i(r))},remove:function(t){return o.removeItem(this._ttlKey(t)),o.removeItem(this._prefix(t)),this},clear:function(){var t,e,n=[],i=o.length;for(t=0;i>t;t++)(e=o.key(t)).match(this.keyMatcher)&&n.push(e.replace(this.keyMatcher,""));for(t=n.length;t--;)this.remove(n[t]);return this},isExpired:function(t){var i=r(o.getItem(this._ttlKey(t)));return e.isNumber(i)&&n()>i?!0:!1}}:{get:e.noop,set:e.noop,remove:e.noop,clear:e.noop,isExpired:e.noop},e.mixin(t.prototype,a),t}(),a=function(){function n(e){e=e||{},this._send=e.transport?i(e.transport):t.ajax,this._get=e.rateLimiter?e.rateLimiter(this._get):this._get}function i(n){return function(i,r){function o(t){e.defer(function(){s.resolve(t)})}function a(t){e.defer(function(){s.reject(t)})}var s=t.Deferred();return n(i,r,o,a),s}}var o=0,a={},s=6,l=new r(10);return n.setMaxPendingRequests=function(t){s=t},n.resetCache=function(){l=new r(10)},e.mixin(n.prototype,{_get:function(t,e,n){function i(e){n&&n(null,e),l.set(t,e)}function r(){n&&n(!0)}function u(){o--,delete a[t],h.onDeckRequestArgs&&(h._get.apply(h,h.onDeckRequestArgs),h.onDeckRequestArgs=null)}var c,h=this;(c=a[t])?c.done(i).fail(r):s>o?(o++,a[t]=this._send(t,e).done(i).fail(r).always(u)):this.onDeckRequestArgs=[].slice.call(arguments,0)},get:function(t,n,i){var r;return e.isFunction(n)&&(i=n,n={}),(r=l.get(t))?e.defer(function(){i&&i(null,r)}):this._get(t,n,i),!!r}}),n}(),s=function(){function n(e){e=e||{},e.datumTokenizer&&e.queryTokenizer||t.error("datumTokenizer and queryTokenizer are both required"),this.datumTokenizer=e.datumTokenizer,this.queryTokenizer=e.queryTokenizer,this.reset()}function i(t){return t=e.filter(t,function(t){return!!t}),t=e.map(t,function(t){return t.toLowerCase()})}function r(){return{ids:[],children:{}}}function o(t){for(var e={},n=[],i=0;ie[r]?r++:(o.push(t[i]),i++,r++);return o}return e.mixin(n.prototype,{bootstrap:function(t){this.datums=t.datums,this.trie=t.trie},add:function(t){var n=this;t=e.isArray(t)?t:[t],e.each(t,function(t){var o,a;o=n.datums.push(t)-1,a=i(n.datumTokenizer(t)),e.each(a,function(t){var e,i,a;for(e=n.trie,i=t.split("");a=i.shift();)e=e.children[a]||(e.children[a]=r()),e.ids.push(o)})})},get:function(t){var n,r,s=this;return n=i(this.queryTokenizer(t)),e.each(n,function(t){var e,n,i,o;if(r&&0===r.length)return!1;for(e=s.trie,n=t.split("");e&&(i=n.shift());)e=e.children[i];return e&&0===n.length?(o=e.ids.slice(0),void(r=r?a(r,o):o)):(r=[],!1)}),r?e.map(o(r),function(t){return s.datums[t]}):[]},reset:function(){this.datums=[],this.trie=r()},serialize:function(){return{datums:this.datums,trie:this.trie}}}),n}(),l=function(){function i(t){return t.local||null}function r(i){var r,o;return o={url:null,thumbprint:"",ttl:864e5,filter:null,ajax:{}},(r=i.prefetch||null)&&(r=e.isString(r)?{url:r}:r,r=e.mixin(o,r),r.thumbprint=n+r.thumbprint,r.ajax.type=r.ajax.type||"GET",r.ajax.dataType=r.ajax.dataType||"json",!r.url&&t.error("prefetch requires url to be set")),r}function o(n){function i(t){return function(n){return e.debounce(n,t)}}function r(t){return function(n){return e.throttle(n,t)}}var o,a;return a={url:null,wildcard:"%QUERY",replace:null,rateLimitBy:"debounce",rateLimitWait:300,send:null,filter:null,ajax:{}},(o=n.remote||null)&&(o=e.isString(o)?{url:o}:o,o=e.mixin(a,o),o.rateLimiter=/^throttle$/i.test(o.rateLimitBy)?r(o.rateLimitWait):i(o.rateLimitWait),o.ajax.type=o.ajax.type||"GET",o.ajax.dataType=o.ajax.dataType||"json",delete o.rateLimitBy,delete o.rateLimitWait,!o.url&&t.error("remote requires url to be set")),o}return{local:i,prefetch:r,remote:o}}();!function(n){function r(e){e&&(e.local||e.prefetch||e.remote)||t.error("one of local, prefetch, or remote is required"),this.limit=e.limit||5,this.sorter=u(e.sorter),this.dupDetector=e.dupDetector||c,this.local=l.local(e),this.prefetch=l.prefetch(e),this.remote=l.remote(e),this.cacheKey=this.prefetch?this.prefetch.cacheKey||this.prefetch.url:null,this.index=new s({datumTokenizer:e.datumTokenizer,queryTokenizer:e.queryTokenizer}),this.storage=this.cacheKey?new o(this.cacheKey):null}function u(t){function n(e){return e.sort(t)}function i(t){return t}return e.isFunction(t)?n:i}function c(){return!1}var h,d;return h=n.Bloodhound,d={data:"data",protocol:"protocol",thumbprint:"thumbprint"},n.Bloodhound=r,r.noConflict=function(){return n.Bloodhound=h,r},r.tokenizers=i,e.mixin(r.prototype,{_loadPrefetch:function(e){function n(t){o.clear(),o.add(e.filter?e.filter(t):t),o._saveToStorage(o.index.serialize(),e.thumbprint,e.ttl)}var i,r,o=this;return(i=this._readFromStorage(e.thumbprint))?(this.index.bootstrap(i),r=t.Deferred().resolve()):r=t.ajax(e.url,e.ajax).done(n),r},_getFromRemote:function(t,e){function n(t,n){e(t?[]:o.remote.filter?o.remote.filter(n):n)}var i,r,o=this;return t=t||"",r=encodeURIComponent(t),i=this.remote.replace?this.remote.replace(this.remote.url,t):this.remote.url.replace(this.remote.wildcard,r),this.transport.get(i,this.remote.ajax,n)},_saveToStorage:function(t,e,n){this.storage&&(this.storage.set(d.data,t,n),this.storage.set(d.protocol,location.protocol,n),this.storage.set(d.thumbprint,e,n))},_readFromStorage:function(t){var e,n={};return this.storage&&(n.data=this.storage.get(d.data),n.protocol=this.storage.get(d.protocol),n.thumbprint=this.storage.get(d.thumbprint)),e=n.thumbprint!==t||n.protocol!==location.protocol,n.data&&!e?n.data:null},_initialize:function(){function n(){r.add(e.isFunction(o)?o():o)}var i,r=this,o=this.local;return i=this.prefetch?this._loadPrefetch(this.prefetch):t.Deferred().resolve(),o&&i.done(n),this.transport=this.remote?new a(this.remote):null,this.initPromise=i.promise()},initialize:function(t){return!this.initPromise||t?this._initialize():this.initPromise},add:function(t){this.index.add(t)},get:function(t,n){function i(t){var i=o.slice(0);e.each(t,function(t){var n;return n=e.some(i,function(e){return r.dupDetector(t,e)}),!n&&i.push(t),i.length0||!this.transport)&&n&&n(o)},clear:function(){this.index.reset()},clearPrefetchCache:function(){this.storage&&this.storage.clear()},clearRemoteCache:function(){this.transport&&a.resetCache()},ttAdapter:function(){return e.bind(this.get,this)}}),r}(this);var u={wrapper:'',dropdown:'',dataset:'
          ',suggestions:'',suggestion:'
          '},c={wrapper:{position:"relative",display:"inline-block"},hint:{position:"absolute",top:"0",left:"0",borderColor:"transparent",boxShadow:"none"},input:{position:"relative",verticalAlign:"top",backgroundColor:"transparent"},inputWithNoHint:{position:"relative",verticalAlign:"top"},dropdown:{position:"absolute",top:"100%",left:"0",zIndex:"100",display:"none"},suggestions:{display:"block"},suggestion:{whiteSpace:"nowrap",cursor:"pointer"},suggestionChild:{whiteSpace:"normal"},ltr:{left:"0",right:"auto"},rtl:{left:"auto",right:" 0"}};e.isMsie()&&e.mixin(c.input,{backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"}),e.isMsie()&&e.isMsie()<=7&&e.mixin(c.input,{marginTop:"-1px"});var h=function(){function n(e){e&&e.el||t.error("EventBus initialized without el"),this.$el=t(e.el)}var i="typeahead:";return e.mixin(n.prototype,{trigger:function(t){var e=[].slice.call(arguments,1);this.$el.trigger(i+t,e)}}),n}(),d=function(){function t(t,e,n,i){var r;if(!n)return this;for(e=e.split(l),n=i?s(n,i):n,this._callbacks=this._callbacks||{};r=e.shift();)this._callbacks[r]=this._callbacks[r]||{sync:[],async:[]},this._callbacks[r][t].push(n);return this}function e(e,n,i){return t.call(this,"async",e,n,i)}function n(e,n,i){return t.call(this,"sync",e,n,i)}function i(t){var e;if(!this._callbacks)return this;for(t=t.split(l);e=t.shift();)delete this._callbacks[e];return this}function r(t){var e,n,i,r,a;if(!this._callbacks)return this;for(t=t.split(l),i=[].slice.call(arguments,1);(e=t.shift())&&(n=this._callbacks[e]);)r=o(n.sync,this,[e].concat(i)),a=o(n.async,this,[e].concat(i)),r()&&u(a);return this}function o(t,e,n){function i(){for(var i,r=0;!i&&r