diff --git a/.codeclimate.yml b/.codeclimate.yml index e6429c57..d24370dc 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -15,7 +15,7 @@ checks: method-complexity: enabled: true config: - threshold: 20 + threshold: 30 method-count: enabled: true config: @@ -74,6 +74,7 @@ ratings: exclude_paths: - app/assets/javascripts/lcms/engine/vendor/**/* - features/**/* + - public/lcms-engine-assets/**/*.map - spec/**/* - vendor/**/* - node_modules/**/* diff --git a/.codeship/rubygems-version b/.codeship/rubygems-version new file mode 100644 index 00000000..3e42f467 --- /dev/null +++ b/.codeship/rubygems-version @@ -0,0 +1 @@ +3.5.22 diff --git a/.codeship/setup.sh b/.codeship/setup.sh new file mode 100755 index 00000000..136deb49 --- /dev/null +++ b/.codeship/setup.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +gem update --system "$(cat .codeship/rubygems-version)" +gem install bundler:2.5.22 + +bundle _2.5.22_ install + +rbs collection install +rbs collection update + +bin/overcommit --sign +if ! GIT_AUTHOR_EMAIL=ci@test.com GIT_AUTHOR_NAME='ci user' bundle exec overcommit --run; then + exit 1 +fi + +# Setup environment +echo -e 'APPLICATION_DOMAIN=example.org' > .env +RAILS_ENV="test" bin/rails db:create +RAILS_ENV="test" bin/rails db:schema:load +RAILS_ENV="test" bin/rails db:migrate diff --git a/.codeship/tests.sh b/.codeship/tests.sh new file mode 100755 index 00000000..3c3f5e48 --- /dev/null +++ b/.codeship/tests.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +RAILS_ENV="test" bundle exec rspec diff --git a/.dockerignore b/.dockerignore index 9f8a3593..fbf202c5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -19,7 +19,6 @@ pdfs .DS_Store .generators .rakeTasks -public/lcms_engine_packs yarn-debug.log* yarn-integrity yarn-error.log diff --git a/.env.template b/.env.template index 67131d30..dbf3ddc4 100644 --- a/.env.template +++ b/.env.template @@ -13,13 +13,11 @@ POSTGRESQL_DATABASE= POSTGRESQL_USERNAME= POSTGRESQL_PASSWORD= POSTGRESQL_PORT= -WEBPACKER_CHECK_YARN= WKHTMLTOPDF_PATH= # OTHER VARIABLES AIR_BRAKE_PROJECT_ID= AIR_BRAKE_PROJECT_KEY= -BITLY_API_TOKEN= ENABLE_BASE64_CACHING= GOOGLE_APPLICATION_FOLDER_ID= GOOGLE_APPLICATION_PREVIEW_FOLDER_ID= @@ -32,3 +30,4 @@ GOOGLE_API_CLIENT_UPLOAD_TIMEOUT= PUPPETEER_TIMEOUT= RESQUE_NAMESPACE= WORKERS_COUNT= +API_SECRET_KEY= diff --git a/.eslintrc b/.eslintrc index c97b2fc2..9f63a333 100644 --- a/.eslintrc +++ b/.eslintrc @@ -10,9 +10,7 @@ "globals": { "$": false, "ga": false, - "Foundation": false, - "Modernizr": false, - "Routes": false + "Modernizr": false }, "plugins": ["react", "jsx-a11y", "prettier"], "settings": { diff --git a/.gitignore b/.gitignore index b74264b8..8a426c1d 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ spec/dummy/public/assets/ coverage /yarn-error.log +/.gem_rbs_collection/ + +docker-compose.personal.yml diff --git a/.nvmrc b/.nvmrc index 62df50f1..55bffd62 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -14.17.0 +18.15.0 diff --git a/.overcommit.yml b/.overcommit.yml index e62cb445..d9f78786 100644 --- a/.overcommit.yml +++ b/.overcommit.yml @@ -13,8 +13,8 @@ PreCommit: BundleCheck: enabled: true - BundleOutdated: - enabled: true +# BundleOutdated: +# enabled: true LocalPathsInGemfile: enabled: true @@ -22,6 +22,7 @@ PreCommit: ExecutePermissions: enabled: true exclude: + - .codeship/* - bin/* - spec/dummy/bin/* @@ -36,6 +37,16 @@ PreCommit: TrailingWhitespace: enabled: true + TypeCheck: + enabled: true + description: RBS check + command: [ 'steep', 'check' ] + + ShellCheck: + enabled: true + exclude: + - node_modules/**/* + PostCheckout: ALL: quiet: true # Change all post-checkout hooks to only display output on failure diff --git a/.rubocop.yml b/.rubocop.yml index 606b2e14..f25f72e2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -15,8 +15,13 @@ AllCops: - spec/dummy/db/schema.rb - spec/dummy/bin/* - templates/* + - config/initializers/simple_form_bootstrap.rb + - Steepfile NewCops: enable - TargetRubyVersion: 2.7 + TargetRubyVersion: 3.2 + +Gemspec/DevelopmentDependencies: + EnforcedStyle: gemspec Layout/LineLength: Max: 120 @@ -38,7 +43,6 @@ Metrics/BlockLength: - '**/*_spec.rb' - 'config/routes.rb' - 'lcms-engine.gemspec' - - 'lib/tasks/elasticsearch.rake' AllowedMethods: ['guard', 'included', 'class_eval'] Metrics/ClassLength: @@ -53,6 +57,11 @@ Metrics/MethodLength: Metrics/PerceivedComplexity: Max: 10 +Naming/PredicateMethod: + AllowedMethods: + - call + - save + Naming/MethodParameterName: MinNameLength: 2 @@ -80,3 +89,6 @@ Style/PercentLiteralDelimiters: Style/RescueModifier: Enabled: false + +Style/SafeNavigationChainLength: + Max: 3 diff --git a/.ruby-version b/.ruby-version index a4dd9dba..3b47f2e4 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.7.4 +3.3.9 diff --git a/.stylelintignore b/.stylelintignore index 45a7ce45..fc3da325 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -1,7 +1,6 @@ docs coverage app/assets/stylesheets/lcms/engine/vendors/* -app/assets/stylesheets/lcms/engine/utils/_styles_override.scss app/assets/stylesheets/lcms/engine/utils/_mixins.scss app/assets/stylesheets/lcms/engine/base/_typography.scss app/assets/stylesheets/lcms/engine/print/_typography.scss diff --git a/CHANGELOG.md b/CHANGELOG.md index 549442fd..673f1c41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/learningtapestry/lcms-engine/compare/v0.5.4...HEAD) +### Added + +- Parse error propagation: errors in tags and inside metadata tables are now displayed after import - [@paranoicsan](https://github.com/paranoicsan) + +### Changed + +- Bump ruby to 3.3.9 +- Bump ruby to 3.3.8 +- Bump ruby to 3.2.9 +- [BREAKING] Update CKEditor version (see [CKEditor usage](docs/ckeditor-usage.md)) - [@paranoicsan](https://github.com/paranoicsan) +- [BREAKING] Refactor query handling in admin controllers - [@paranoicsan](https://github.com/paranoicsan) +- [BREAKING] Rename `import_status_for` to `import_status_for_nested` inside `Lcms::Engine::NestedReimportable` - [@paranoicsan](https://github.com/paranoicsan) +- Normalize metadata search in Resource model - [@paranoicsan](https://github.com/paranoicsan) + ## [0.5.4](https://github.com/learningtapestry/lcms-engine/compare/v0.5.3...v0.5.4) - 2022.12.30 ### Changed diff --git a/Dockerfile b/Dockerfile index 2758bbfc..6a2ba054 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,27 +1,57 @@ -FROM ruby:2.7.7 +FROM ruby:3.3.9 -ENV APP_PATH /app/ -ENV LANG C.UTF-8 +ENV APP_PATH=/app/ +ENV LANG=C.UTF-8 WORKDIR $APP_PATH -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_14.x | bash - \ +RUN apt-get autoclean \ + && apt-get clean \ && apt-get update -qqy \ - && apt-get install -y --no-install-recommends nodejs yarn \ - && rm -rf /var/lib/apt/lists/* /var/cache/apt/* + && apt-get install -y --no-install-recommends \ + build-essential \ + chromium-driver \ + postgresql-client \ + shellcheck \ + tzdata \ + && rm -r /var/lib/apt/lists/* /var/cache/apt/* + +# install specific wkhtmltopdf binary +RUN apt-get update -qqy \ + && WK_VERSION=0.12.6.1-3 \ + && ARCH=$(dpkg --print-architecture) \ + && CODENAME=$(. /etc/os-release; echo $VERSION_CODENAME) \ + && curl -LSfso wkhtmltopdf.deb https://github.com/wkhtmltopdf/packaging/releases/download/$WK_VERSION/wkhtmltox_$WK_VERSION.${CODENAME}_$ARCH.deb \ + && apt-get install -qy xfonts-75dpi xfonts-base ./wkhtmltopdf.deb \ + && rm wkhtmltopdf.deb \ + && rm -r /var/lib/apt/lists/* /var/cache/apt/* # Add codebase COPY . $APP_PATH # Install gems -RUN gem install bundler:2.4.8 \ +ENV BUNDLER_VERSION=2.5.22 +RUN gem install bundler:$BUNDLER_VERSION \ && bundle install --jobs `expr $(cat /proc/cpuinfo | grep -c "cpu cores") - 1` --retry 3 \ && rm -rf /usr/local/bundle/cache/*.gem \ && find /usr/local/bundle/gems/ -name "*.c" -delete \ && find /usr/local/bundle/gems/ -name "*.o" -delete -# Install yarn packages +# install nvm & yarn & packages +COPY .nvmrc $APP_PATH COPY package.json yarn.lock $APP_PATH -RUN yarn install + +ENV NVM_DIR=/usr/local/nvm +ENV NODE_VERSION=18.15.0 +ENV NODE_PATH=$NVM_DIR/v$NODE_VERSION/lib/node_modules +ENV PATH=$NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH + +SHELL ["/bin/bash", "--login", "-i", "-c"] +RUN mkdir -p /usr/local/nvm \ + && curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash \ + && source $NVM_DIR/nvm.sh \ + && nvm install $NODE_VERSION \ + && nvm alias default $NODE_VERSION \ + && nvm use default \ + && npm install -g yarn \ + && yarn install diff --git a/Gemfile b/Gemfile index 6a86e340..2aa8b553 100644 --- a/Gemfile +++ b/Gemfile @@ -16,6 +16,6 @@ gemspec # To use a debugger # gem 'byebug', group: [:development, :test] -gem 'wicked_pdf', git: 'https://github.com/learningtapestry/wicked_pdf.git', +gem 'wicked_pdf', github: 'learningtapestry/wicked_pdf', branch: 'puppeteer-support', - ref: '964a090' + ref: '50d961e' diff --git a/Gemfile.lock b/Gemfile.lock index c5e6ae7c..875a7f78 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,10 +1,10 @@ GIT remote: https://github.com/learningtapestry/wicked_pdf.git - revision: 964a0906c9284d97927322e4c1e474b1d68373cf - ref: 964a090 + revision: 50d961ed2e0b48846d871a00471247ad7584d6fe + ref: 50d961e branch: puppeteer-support specs: - wicked_pdf (2.0.2) + wicked_pdf (2.0.7) activesupport PATH @@ -13,53 +13,41 @@ PATH lcms-engine (0.5.4) active_model_serializers (~> 0.10.10) activejob-retry (~> 0.6.3) - acts-as-taggable-on (~> 7.0) + acts-as-taggable-on (~> 9.0) acts_as_list (~> 1.0) - addressable (~> 2.7) airbrake (~> 13.0) autoprefixer-rails (~> 9.7) aws-sdk-rails (~> 3.1) aws-sdk-s3 (~> 1) - bitly (~> 1.1, >= 1.1.2) - bootstrap-sass (~> 3.4) - bullet (~> 6.1, >= 6.1.0) carrierwave (~> 2.1) - ckeditor (~> 5.1) + ckeditor (~> 5.1, >= 5.1.3) closure_tree (~> 7.1) combine_pdf (~> 1.0) - daemons (~> 1.3, >= 1.3.1) + concurrent-ruby (= 1.3.4) + cssbundling-rails (~> 1.1) devise (~> 4.7, >= 4.7.1) elasticsearch-dsl (~> 0.1.9) elasticsearch-model (~> 7.0) elasticsearch-persistence (~> 7.0) elasticsearch-rails (~> 7.0) fog-aws (~> 3.5, >= 3.5.2) - font-awesome-sass (~> 5.12) - foundation-rails (~> 6.6, >= 6.6.1) - google-api-client (< 1) + google-apis-drive_v3 (~> 0.66) + google-apis-script_v1 (~> 0.28) hiredis (~> 0.6.3) httparty (~> 0.18) jbuilder (~> 2.10) - jquery-rails (~> 4.3, >= 4.3.5) - js-routes (~> 1.4, >= 1.4.9) - lt-google-api (~> 0.2, >= 0.2.3) - lt-lcms (~> 0.4, >= 0.4.4) - migration_data (~> 0.6) + lt-google-api (~> 0.4) + lt-lcms (~> 0.7) mini_magick (~> 4.10, >= 4.10.1) - nested_form (~> 0.3.2) - nikkou (~> 0.0.5) nokogiri (~> 1.12, >= 1.12.0) oj (~> 3.10, >= 3.10.2) oj_mimic_json (~> 1.0, >= 1.0.1) - pandoc-ruby (~> 2.0, >= 2.0.2) - pdfjs_viewer-rails (~> 0.3.1) pg (~> 1.2, >= 1.2.2) pg_search (~> 2.3, >= 2.3.2) rack-mini-profiler (~> 2.3, >= 2.3.3) - rails (~> 6.1.6, >= 6.1.6.1) + rails (~> 7.0) ransack (~> 2.3, >= 2.3.2) - react-rails (~> 2.6, >= 2.6.1) - redis (~> 4.1, >= 4.1.3) + redis (~> 5.4.1) resque (~> 2.0, >= 2.0.0) resque-scheduler (~> 4.4, >= 4.4.0) rest-client (~> 2.1, >= 2.1.0) @@ -68,171 +56,170 @@ PATH rubyzip (~> 2) sanitize (~> 6.0.0) sass-rails (~> 6) - simple_form (~> 5.0, >= 5.0.2) - staccato (~> 0.5.3) - truncate_html (~> 0.9.3) - turbolinks (~> 5.2, >= 5.2.1) + simple_form (~> 5.2) + sprockets-rails (~> 3.4) validate_url (~> 1.0, >= 1.0.8) virtus (~> 1.0, >= 1.0.5) - will_paginate (~> 3.2, >= 3.2.1) - will_paginate-bootstrap (~> 1.0, >= 1.0.2) + will_paginate (~> 4.0) + will_paginate-bootstrap-style (~> 0.3) with_advisory_lock (~> 4.6) GEM remote: https://rubygems.org/ specs: - actioncable (6.1.7.2) - actionpack (= 6.1.7.2) - activesupport (= 6.1.7.2) + actioncable (7.0.8.7) + actionpack (= 7.0.8.7) + activesupport (= 7.0.8.7) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.7.2) - actionpack (= 6.1.7.2) - activejob (= 6.1.7.2) - activerecord (= 6.1.7.2) - activestorage (= 6.1.7.2) - activesupport (= 6.1.7.2) + actionmailbox (7.0.8.7) + actionpack (= 7.0.8.7) + activejob (= 7.0.8.7) + activerecord (= 7.0.8.7) + activestorage (= 7.0.8.7) + activesupport (= 7.0.8.7) mail (>= 2.7.1) - actionmailer (6.1.7.2) - actionpack (= 6.1.7.2) - actionview (= 6.1.7.2) - activejob (= 6.1.7.2) - activesupport (= 6.1.7.2) + net-imap + net-pop + net-smtp + actionmailer (7.0.8.7) + actionpack (= 7.0.8.7) + actionview (= 7.0.8.7) + activejob (= 7.0.8.7) + activesupport (= 7.0.8.7) mail (~> 2.5, >= 2.5.4) + net-imap + net-pop + net-smtp rails-dom-testing (~> 2.0) - actionpack (6.1.7.2) - actionview (= 6.1.7.2) - activesupport (= 6.1.7.2) - rack (~> 2.0, >= 2.0.9) + actionpack (7.0.8.7) + actionview (= 7.0.8.7) + activesupport (= 7.0.8.7) + rack (~> 2.0, >= 2.2.4) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.7.2) - actionpack (= 6.1.7.2) - activerecord (= 6.1.7.2) - activestorage (= 6.1.7.2) - activesupport (= 6.1.7.2) + actiontext (7.0.8.7) + actionpack (= 7.0.8.7) + activerecord (= 7.0.8.7) + activestorage (= 7.0.8.7) + activesupport (= 7.0.8.7) + globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (6.1.7.2) - activesupport (= 6.1.7.2) + actionview (7.0.8.7) + activesupport (= 7.0.8.7) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - active_model_serializers (0.10.13) - actionpack (>= 4.1, < 7.1) - activemodel (>= 4.1, < 7.1) + active_model_serializers (0.10.15) + actionpack (>= 4.1) + activemodel (>= 4.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (6.1.7.2) - activesupport (= 6.1.7.2) + activejob (7.0.8.7) + activesupport (= 7.0.8.7) globalid (>= 0.3.6) activejob-retry (0.6.3) activejob (>= 4.2) activesupport (>= 4.2) - activemodel (6.1.7.2) - activesupport (= 6.1.7.2) - activerecord (6.1.7.2) - activemodel (= 6.1.7.2) - activesupport (= 6.1.7.2) - activestorage (6.1.7.2) - actionpack (= 6.1.7.2) - activejob (= 6.1.7.2) - activerecord (= 6.1.7.2) - activesupport (= 6.1.7.2) + activemodel (7.0.8.7) + activesupport (= 7.0.8.7) + activerecord (7.0.8.7) + activemodel (= 7.0.8.7) + activesupport (= 7.0.8.7) + activestorage (7.0.8.7) + actionpack (= 7.0.8.7) + activejob (= 7.0.8.7) + activerecord (= 7.0.8.7) + activesupport (= 7.0.8.7) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.7.2) + activesupport (7.0.8.7) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - zeitwerk (~> 2.3) - acts-as-taggable-on (7.0.0) - activerecord (>= 5.0, < 6.2) - acts_as_list (1.0.4) - activerecord (>= 4.2) - addressable (2.8.1) - public_suffix (>= 2.0.2, < 6.0) - airbrake (13.0.3) + acts-as-taggable-on (9.0.1) + activerecord (>= 6.0, < 7.1) + acts_as_list (1.2.4) + activerecord (>= 6.1) + activesupport (>= 6.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + airbrake (13.0.5) airbrake-ruby (~> 6.0) - airbrake-ruby (6.2.0) - rbtree3 (~> 0.5) - ast (2.4.2) + airbrake-ruby (6.2.2) + rbtree3 (~> 0.6) + ast (2.4.3) autoprefixer-rails (9.8.6.5) execjs - aws-eventstream (1.2.0) - aws-partitions (1.701.0) - aws-record (2.10.1) - aws-sdk-dynamodb (~> 1.18) - aws-sdk-core (3.170.0) - aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.5) + aws-eventstream (1.4.0) + aws-partitions (1.1140.0) + aws-record (2.14.0) + aws-sdk-dynamodb (~> 1, >= 1.85.0) + aws-sdk-core (3.228.0) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + bigdecimal jmespath (~> 1, >= 1.6.1) - aws-sdk-dynamodb (1.81.0) - aws-sdk-core (~> 3, >= 3.165.0) - aws-sigv4 (~> 1.1) - aws-sdk-kms (1.62.0) - aws-sdk-core (~> 3, >= 3.165.0) - aws-sigv4 (~> 1.1) - aws-sdk-rails (3.7.0) + logger + aws-sdk-dynamodb (1.148.0) + aws-sdk-core (~> 3, >= 3.228.0) + aws-sigv4 (~> 1.5) + aws-sdk-kms (1.109.0) + aws-sdk-core (~> 3, >= 3.228.0) + aws-sigv4 (~> 1.5) + aws-sdk-rails (3.13.0) aws-record (~> 2) - aws-sdk-ses (~> 1) - aws-sdk-sesv2 (~> 1) - aws-sdk-sqs (~> 1) + aws-sdk-ses (~> 1, >= 1.50.0) + aws-sdk-sesv2 (~> 1, >= 1.34.0) + aws-sdk-sqs (~> 1, >= 1.56.0) aws-sessionstore-dynamodb (~> 2) - concurrent-ruby (~> 1) + concurrent-ruby (>= 1.3.1) railties (>= 5.2.0) - aws-sdk-s3 (1.119.0) - aws-sdk-core (~> 3, >= 3.165.0) + aws-sdk-s3 (1.195.0) + aws-sdk-core (~> 3, >= 3.228.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.4) - aws-sdk-ses (1.49.0) - aws-sdk-core (~> 3, >= 3.165.0) - aws-sigv4 (~> 1.1) - aws-sdk-sesv2 (1.31.0) - aws-sdk-core (~> 3, >= 3.165.0) - aws-sigv4 (~> 1.1) - aws-sdk-sqs (1.53.0) - aws-sdk-core (~> 3, >= 3.165.0) - aws-sigv4 (~> 1.1) - aws-sessionstore-dynamodb (2.0.1) - aws-sdk-dynamodb (~> 1) - rack (~> 2) - aws-sigv4 (1.5.2) + aws-sigv4 (~> 1.5) + aws-sdk-ses (1.87.0) + aws-sdk-core (~> 3, >= 3.228.0) + aws-sigv4 (~> 1.5) + aws-sdk-sesv2 (1.81.0) + aws-sdk-core (~> 3, >= 3.228.0) + aws-sigv4 (~> 1.5) + aws-sdk-sqs (1.99.0) + aws-sdk-core (~> 3, >= 3.228.0) + aws-sigv4 (~> 1.5) + aws-sessionstore-dynamodb (2.2.0) + aws-sdk-dynamodb (~> 1, >= 1.85.0) + rack (>= 2, < 4) + rack-session (>= 1, < 3) + aws-sigv4 (1.12.1) aws-eventstream (~> 1, >= 1.0.2) axiom-types (0.1.1) descendants_tracker (~> 0.0.4) ice_nine (~> 0.11.0) thread_safe (~> 0.3, >= 0.3.1) - babel-source (5.8.35) - babel-transpiler (0.7.0) - babel-source (>= 4.0, < 6) - execjs (~> 2.0) - bcrypt (3.1.18) - bitly (1.1.2) - httparty (>= 0.7.6) - multi_json (~> 1.3) - oauth2 (>= 0.5.0, < 2.0) - bootstrap-sass (3.4.1) - autoprefixer-rails (>= 5.2.1) - sassc (>= 2.0.0) - builder (3.2.4) - bullet (6.1.5) + base64 (0.2.0) + bcrypt (3.1.20) + bigdecimal (3.2.2) + builder (3.3.0) + bullet (7.2.0) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) - byebug (11.1.3) - capybara (3.37.1) + capybara (3.40.0) addressable matrix mini_mime (>= 0.1.3) - nokogiri (~> 1.8) + nokogiri (~> 1.11) rack (>= 1.6.0) rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - carrierwave (2.2.3) + carrierwave (2.2.6) activemodel (>= 5.0.0) activesupport (>= 5.0.0) addressable (~> 2.6) @@ -243,46 +230,47 @@ GEM case_transform (0.2) activesupport childprocess (3.0.0) - ckeditor (5.1.1) + ckeditor (5.1.3) orm_adapter (~> 0.5.0) closure_tree (7.4.0) activerecord (>= 4.2.10) with_advisory_lock (>= 4.0.0) - coderay (1.1.3) coercible (1.0.0) descendants_tracker (~> 0.0.1) - combine_pdf (1.0.22) + combine_pdf (1.0.31) matrix ruby-rc4 (>= 0.1.5) - concurrent-ruby (1.2.2) - connection_pool (2.3.0) + concurrent-ruby (1.3.4) + connection_pool (2.5.3) crass (1.0.6) - daemons (1.4.1) + cssbundling-rails (1.4.3) + railties (>= 6.0.0) + csv (3.3.5) database_cleaner (1.99.0) database_cleaner-active_record (1.99.0) activerecord database_cleaner (~> 1.99.0) + date (3.4.1) declarative (0.0.20) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) - devise (4.8.1) + devise (4.9.4) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) responders warden (~> 1.2.3) - diff-lcs (1.5.0) - docile (1.4.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) + diff-lcs (1.6.2) + docile (1.4.1) + domain_name (0.6.20240107) dotenv (2.8.1) dotenv-rails (2.8.1) dotenv (= 2.8.1) railties (>= 3.2) - elasticsearch (7.17.7) - elasticsearch-api (= 7.17.7) - elasticsearch-transport (= 7.17.7) - elasticsearch-api (7.17.7) + elasticsearch (7.17.11) + elasticsearch-api (= 7.17.11) + elasticsearch-transport (= 7.17.11) + elasticsearch-api (7.17.11) multi_json elasticsearch-dsl (0.1.10) elasticsearch-model (7.2.1) @@ -296,285 +284,278 @@ GEM elasticsearch-model (= 7.2.1) hashie elasticsearch-rails (7.2.1) - elasticsearch-transport (7.17.7) - faraday (~> 1) + elasticsearch-transport (7.17.11) + base64 + faraday (>= 1, < 3) multi_json email_spec (2.2.0) htmlentities (~> 4.3.3) launchy (~> 2.1) mail (~> 2.7) equalizer (0.0.11) - erubi (1.11.0) - et-orbi (1.2.7) + erb (5.0.2) + erubi (1.13.1) + et-orbi (1.2.11) tzinfo - excon (0.98.0) - execjs (2.8.1) + excon (1.2.8) + logger + execjs (2.10.0) factory_bot (5.2.0) activesupport (>= 4.2.0) faker (2.23.0) i18n (>= 1.8.11, < 2) - faraday (1.10.3) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0) - faraday-multipart (~> 1.0) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.0) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - faraday-retry (~> 1.0) - ruby2_keywords (>= 0.0.4) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) - faraday-excon (1.1.0) - faraday-httpclient (1.0.1) - faraday-multipart (1.0.4) - multipart-post (~> 2) - faraday-net_http (1.0.1) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - faraday-retry (1.0.3) - ffi (1.15.5) - fog-aws (3.16.0) - fog-core (~> 2.1) + faraday (2.13.4) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-net_http (3.4.1) + net-http (>= 0.5.0) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + fileutils (1.7.3) + fog-aws (3.32.0) + base64 (~> 0.2.0) + fog-core (~> 2.6) fog-json (~> 1.1) fog-xml (~> 0.1) - fog-core (2.3.0) + fog-core (2.6.0) builder - excon (~> 0.71) + excon (~> 1.0) formatador (>= 0.2, < 2.0) mime-types fog-json (1.2.0) fog-core multi_json (~> 1.10) - fog-xml (0.1.4) + fog-xml (0.1.5) fog-core nokogiri (>= 1.5.11, < 2.0.0) - font-awesome-sass (5.15.1) - sassc (>= 1.11) - formatador (1.1.0) - foundation-rails (6.6.2.0) - railties (>= 3.1.0) - sass (>= 3.3.0) - sprockets-es6 (>= 0.9.0) - fugit (1.8.1) - et-orbi (~> 1, >= 1.2.7) + formatador (1.1.1) + fugit (1.11.1) + et-orbi (~> 1, >= 1.2.11) raabro (~> 1.4) - gems (1.2.0) - globalid (1.0.1) - activesupport (>= 5.0) - google-api-client (0.53.0) - google-apis-core (~> 0.1) - google-apis-generator (~> 0.1) - google-apis-core (0.10.0) + globalid (1.2.1) + activesupport (>= 6.1) + google-apis-core (0.18.0) addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.16.2, < 2.a) - httpclient (>= 2.8.1, < 3.a) + googleauth (~> 1.9) + httpclient (>= 2.8.3, < 3.a) mini_mime (~> 1.0) + mutex_m representable (~> 3.0) retriable (>= 2.0, < 4.a) - rexml - webrick - google-apis-discovery_v1 (0.13.0) - google-apis-core (>= 0.9.1, < 2.a) - google-apis-generator (0.11.1) - activesupport (>= 5.0) - gems (~> 1.2) - google-apis-core (>= 0.9.1, < 2.a) - google-apis-discovery_v1 (~> 0.5) - thor (>= 0.20, < 2.a) - googleauth (0.17.1) - faraday (>= 0.17.3, < 2.0) + google-apis-drive_v3 (0.68.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-script_v1 (0.30.0) + google-apis-core (>= 0.15.0, < 2.a) + google-cloud-env (2.3.1) + base64 (~> 0.2) + faraday (>= 1.0, < 3.a) + google-logging-utils (0.2.0) + googleauth (1.14.0) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.2) + google-logging-utils (~> 0.1) jwt (>= 1.4, < 3.0) - memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) - signet (~> 0.15) + signet (>= 0.16, < 2.a) hashie (5.0.0) hiredis (0.6.3) htmlentities (4.3.4) http-accept (1.7.0) - http-cookie (1.0.5) + http-cookie (1.0.8) domain_name (~> 0.5) - httparty (0.21.0) + httparty (0.23.1) + csv mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - httpclient (2.8.3) - i18n (1.12.0) + httpclient (2.9.0) + mutex_m + i18n (1.14.7) concurrent-ruby (~> 1.0) ice_nine (0.11.2) - image_processing (1.12.2) - mini_magick (>= 4.9.5, < 5) + image_processing (1.14.0) + mini_magick (>= 4.9.5, < 6) ruby-vips (>= 2.0.17, < 3) iniparse (1.5.0) - jbuilder (2.11.5) + jbuilder (2.13.0) actionview (>= 5.0.0) activesupport (>= 5.0.0) jmespath (1.6.2) - jquery-rails (4.5.1) - rails-dom-testing (>= 1, < 3) - railties (>= 4.2.0) - thor (>= 0.14, < 2.0) - js-routes (1.4.14) - railties (>= 4) - json (2.6.2) + jsbundling-rails (1.3.1) + railties (>= 6.0.0) + json (2.13.2) jsonapi-renderer (0.2.2) - jwt (2.6.0) - launchy (2.5.0) - addressable (~> 2.7) - loofah (2.19.1) + jwt (2.10.2) + base64 + language_server-protocol (3.17.0.5) + launchy (2.5.2) + addressable (~> 2.8) + lint_roller (1.1.0) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + loofah (2.24.1) crass (~> 1.0.2) - nokogiri (>= 1.5.9) - lt-google-api (0.2.4) - google-api-client (~> 0.46) - googleauth (~> 0.14) - lt-lcms (0.4.4) - google-api-client (~> 0.38) - httparty (~> 0.18) - lt-google-api (~> 0.2, >= 0.2.4) + nokogiri (>= 1.12.0) + lt-google-api (0.4.0) + google-apis-drive_v3 (~> 0.66) + googleauth (~> 1.14) + lt-lcms (0.7.0) + google-apis-core (~> 0.18) + httparty (~> 0.22) + lt-google-api (~> 0.4.0) nokogiri (~> 1.10, >= 1.10.8) rubyzip (~> 2) - mail (2.7.1) + mail (2.8.1) mini_mime (>= 0.1.1) - marcel (1.0.2) - matrix (0.4.2) - memoist (0.16.2) - method_source (1.0.0) - migration_data (0.6.0) - mime-types (3.4.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2022.0105) - mini_magick (4.12.0) - mini_mime (1.1.2) - minitest (5.18.0) - mono_logger (1.1.1) - multi_json (1.15.0) - multi_xml (0.6.0) - multipart-post (2.3.0) - mustermann (3.0.0) + net-imap + net-pop + net-smtp + marcel (1.0.4) + matrix (0.4.3) + method_source (1.1.0) + mime-types (3.7.0) + logger + mime-types-data (~> 3.2025, >= 3.2025.0507) + mime-types-data (3.2025.0729) + mini_magick (4.13.2) + mini_mime (1.1.5) + minitest (5.25.5) + mock_redis (0.51.0) + redis (~> 5) + mono_logger (1.1.2) + multi_json (1.17.0) + multi_xml (0.7.2) + bigdecimal (~> 3.1) + mustermann (3.0.4) ruby2_keywords (~> 0.0.1) - nested_form (0.3.2) + mutex_m (0.3.0) + net-http (0.6.0) + uri + net-imap (0.5.9) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol netrc (0.11.0) - nikkou (0.0.5) - activesupport - nokogiri - tzinfo - nio4r (2.5.8) - nokogiri (1.13.10-arm64-darwin) + nio4r (2.7.4) + nokogiri (1.18.9-aarch64-linux-gnu) racc (~> 1.4) - nokogiri (1.13.10-x86_64-linux) + nokogiri (1.18.9-arm64-darwin) racc (~> 1.4) - oauth2 (1.4.11) - faraday (>= 0.17.3, < 3.0) - jwt (>= 1.0, < 3.0) - multi_json (~> 1.3) - multi_xml (~> 0.5) - rack (>= 1.2, < 4) - oj (3.14.0) + nokogiri (1.18.9-x86_64-linux-gnu) + racc (~> 1.4) + oj (3.16.11) + bigdecimal (>= 3.0) + ostruct (>= 0.2) oj_mimic_json (1.0.1) orm_adapter (0.5.0) os (1.1.4) - overcommit (0.59.1) - childprocess (>= 0.6.3, < 5) + ostruct (0.6.3) + overcommit (0.68.0) + childprocess (>= 0.6.3, < 6) iniparse (~> 1.4) - rexml (~> 3.2) - pandoc-ruby (2.1.7) - parallel (1.22.1) - parser (3.1.2.1) + rexml (>= 3.3.9) + parallel (1.27.0) + parser (3.3.9.0) ast (~> 2.4.1) - pdfjs_viewer-rails (0.3.2) - json (> 1.8.4) - rails (> 4.2.0) - sassc-rails (>= 2.1) - pg (1.4.5) - pg_search (2.3.6) - activerecord (>= 5.2) - activesupport (>= 5.2) - pry (0.14.1) - coderay (~> 1.1) - method_source (~> 1.0) - pry-byebug (3.10.1) - byebug (~> 11.0) - pry (>= 0.13, < 0.15) - pry-rails (0.3.9) - pry (>= 0.10.4) - psych (4.0.6) + racc + pg (1.6.0-aarch64-linux) + pg (1.6.0-arm64-darwin) + pg (1.6.0-x86_64-linux) + pg_search (2.3.7) + activerecord (>= 6.1) + activesupport (>= 6.1) + prism (1.4.0) + psych (5.2.6) + date stringio - public_suffix (5.0.0) + public_suffix (6.0.2) raabro (1.4.0) - racc (1.6.1) - rack (2.2.6.3) + racc (1.8.1) + rack (2.2.17) rack-mini-profiler (2.3.4) rack (>= 1.2.0) - rack-protection (3.0.5) - rack - rack-proxy (0.7.4) - rack - rack-test (2.0.2) + rack-protection (3.2.0) + base64 (>= 0.1.0) + rack (~> 2.2, >= 2.2.4) + rack-session (1.0.2) + rack (< 3) + rack-test (2.2.0) rack (>= 1.3) - rails (6.1.7.2) - actioncable (= 6.1.7.2) - actionmailbox (= 6.1.7.2) - actionmailer (= 6.1.7.2) - actionpack (= 6.1.7.2) - actiontext (= 6.1.7.2) - actionview (= 6.1.7.2) - activejob (= 6.1.7.2) - activemodel (= 6.1.7.2) - activerecord (= 6.1.7.2) - activestorage (= 6.1.7.2) - activesupport (= 6.1.7.2) + rails (7.0.8.7) + actioncable (= 7.0.8.7) + actionmailbox (= 7.0.8.7) + actionmailer (= 7.0.8.7) + actionpack (= 7.0.8.7) + actiontext (= 7.0.8.7) + actionview (= 7.0.8.7) + activejob (= 7.0.8.7) + activemodel (= 7.0.8.7) + activerecord (= 7.0.8.7) + activestorage (= 7.0.8.7) + activesupport (= 7.0.8.7) bundler (>= 1.15.0) - railties (= 6.1.7.2) - sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + railties (= 7.0.8.7) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.4.4) - loofah (~> 2.19, >= 2.19.1) - railties (6.1.7.2) - actionpack (= 6.1.7.2) - activesupport (= 6.1.7.2) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (7.0.8.7) + actionpack (= 7.0.8.7) + activesupport (= 7.0.8.7) method_source rake (>= 12.2) thor (~> 1.0) + zeitwerk (~> 2.5) rainbow (3.1.1) - rake (13.0.6) + rake (13.3.0) ransack (2.6.0) activerecord (>= 6.0.4) activesupport (>= 6.0.4) i18n rb-fsevent (0.11.2) - rb-inotify (0.10.1) + rb-inotify (0.11.1) ffi (~> 1.0) - rbtree3 (0.7.0) - rdoc (6.4.0) + rbs (3.9.4) + logger + rbs_rails (0.12.1) + parser + rbs (>= 1) + rbtree3 (0.7.1) + rdoc (6.14.2) + erb psych (>= 4.0.0) - react-rails (2.6.2) - babel-transpiler (>= 0.7.0) + redis (5.4.1) + redis-client (>= 0.22.0) + redis-client (0.25.1) connection_pool - execjs - railties (>= 3.2) - tilt - redis (4.8.0) - redis-namespace (1.10.0) + redis-namespace (1.11.0) redis (>= 4) - regexp_parser (2.6.0) + regexp_parser (2.10.0) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) - responders (3.0.1) - actionpack (>= 5.0) - railties (>= 5.0) - resque (2.4.0) - mono_logger (~> 1.0) + responders (3.1.1) + actionpack (>= 5.2) + railties (>= 5.2) + resque (2.7.0) + mono_logger (~> 1) multi_json (~> 1.0) redis-namespace (~> 1.6) sinatra (>= 0.9.2) - resque-scheduler (4.8.0) + resque-scheduler (4.11.0) mono_logger (~> 1.0) redis (>= 3.3) resque (>= 1.27) @@ -585,15 +566,15 @@ GEM mime-types (>= 1.16, < 4.0) netrc (~> 0.8) retriable (3.1.2) - rexml (3.2.5) - rspec-core (3.11.0) - rspec-support (~> 3.11.0) - rspec-expectations (3.11.1) + rexml (3.4.1) + rspec-core (3.13.5) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-mocks (3.11.1) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.5) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) + rspec-support (~> 3.13.0) rspec-rails (4.0.2) actionpack (>= 4.2) activesupport (>= 4.2) @@ -602,37 +583,33 @@ GEM rspec-expectations (~> 3.10) rspec-mocks (~> 3.10) rspec-support (~> 3.10) - rspec-support (3.11.1) - rubocop (1.36.0) + rspec-support (3.13.4) + rubocop (1.79.1) json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) parallel (~> 1.10) - parser (>= 3.1.2.1) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.20.1, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.46.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.21.0) - parser (>= 3.1.1.0) - ruby-progressbar (1.11.0) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-progressbar (1.13.0) ruby-rc4 (0.1.5) - ruby-vips (2.1.4) + ruby-vips (2.2.4) ffi (~> 1.12) + logger ruby2_keywords (0.0.5) - rubyzip (2.3.2) - rufus-scheduler (3.8.2) - fugit (~> 1.1, >= 1.1.6) - safe_attributes (1.0.10) - activerecord (>= 3.0.0) - sanitize (6.0.1) + rubyzip (2.4.1) + rufus-scheduler (3.9.2) + fugit (~> 1.1, >= 1.11.1) + sanitize (6.0.2) crass (~> 1.0.2) nokogiri (>= 1.12.0) - sass (3.7.4) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) sassc (2.4.0) @@ -643,22 +620,22 @@ GEM sprockets (> 3.0) sprockets-rails tilt - sdoc (2.4.0) + sdoc (2.6.1) rdoc (>= 5.0) + securerandom (0.4.1) seedbank (0.5.0) rake (>= 10.0) selenium-webdriver (3.142.7) childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) - semantic_range (3.0.0) shoulda-matchers (4.5.1) activesupport (>= 4.2.0) - signet (0.17.0) + signet (0.20.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - simple_form (5.2.0) + simple_form (5.3.1) actionpack (>= 5.2) activemodel (>= 5.2) simplecov (0.17.1) @@ -666,46 +643,59 @@ GEM json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.2) - sinatra (3.0.5) + sinatra (3.2.0) mustermann (~> 3.0) rack (~> 2.2, >= 2.2.4) - rack-protection (= 3.0.5) + rack-protection (= 3.2.0) tilt (~> 2.0) - spring (2.1.1) + spring (3.1.1) spring-commands-rspec (1.0.4) spring (>= 0.9.1) - sprockets (4.1.1) + sprockets (4.2.2) concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-es6 (0.9.2) - babel-source (>= 5.8.11) - babel-transpiler + logger + rack (>= 2.2.4, < 4) + sprockets-rails (3.5.2) + actionpack (>= 6.1) + activesupport (>= 6.1) sprockets (>= 3.0.0) - sprockets-rails (3.4.2) - actionpack (>= 5.2) - activesupport (>= 5.2) - sprockets (>= 3.0.0) - ssrf_filter (1.1.1) - staccato (0.5.3) - stringio (3.0.2) - thor (1.2.1) + ssrf_filter (1.3.0) + steep (1.10.0) + activesupport (>= 5.1) + concurrent-ruby (>= 1.1.10) + csv (>= 3.0.9) + fileutils (>= 1.1.0) + json (>= 2.1.0) + language_server-protocol (>= 3.17.0.4, < 4.0) + listen (~> 3.0) + logger (>= 1.3.0) + mutex_m (>= 0.3.0) + parser (>= 3.1) + rainbow (>= 2.2.2, < 4.0) + rbs (~> 3.9) + securerandom (>= 0.1) + strscan (>= 1.0.0) + terminal-table (>= 2, < 5) + uri (>= 0.12.0) + stringio (3.1.7) + strscan (3.1.5) + terminal-table (4.0.0) + unicode-display_width (>= 1.1.1, < 4) + thor (1.4.0) thread_safe (0.3.6) - tilt (2.0.11) + tilt (2.6.1) + timeout (0.4.3) traceroute (0.8.1) rails (>= 3.0.0) trailblazer-option (0.1.2) - truncate_html (0.9.3) - turbolinks (5.2.1) - turbolinks-source (~> 5.2) - turbolinks-source (5.2.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) - unicode-display_width (2.3.0) - uniform_notifier (1.16.0) + unicode-display_width (3.1.4) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uniform_notifier (1.17.0) + uri (1.0.3) validate_url (1.0.15) activemodel (>= 3.0.0) public_suffix @@ -720,53 +710,52 @@ GEM nokogiri (~> 1.6) rubyzip (>= 1.3.0) selenium-webdriver (> 3.141, < 5.0) - webpacker (5.4.4) - activesupport (>= 5.2) - rack-proxy (>= 0.6.1) - railties (>= 5.2) - semantic_range (>= 2.3.0) - webrick (1.8.1) - websocket-driver (0.7.5) + websocket-driver (0.8.0) + base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - will_paginate (3.3.1) - will_paginate-bootstrap (1.0.2) - will_paginate (>= 3.0.3) + will_paginate (4.0.1) + will_paginate-bootstrap-style (0.3.0) + will_paginate (~> 4.0, >= 4.0.0) with_advisory_lock (4.6.0) activerecord (>= 4.2) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.7) + zeitwerk (2.7.3) PLATFORMS + aarch64-linux arm64-darwin-21 + arm64-darwin-23 + arm64-darwin-24 x86_64-linux DEPENDENCIES + bullet (~> 7.0) capybara (~> 3.31) database_cleaner-active_record (~> 1.8) dotenv-rails (~> 2.2) email_spec (= 2.2.0) factory_bot (~> 5) faker (~> 2.1) + jsbundling-rails (~> 1.1) lcms-engine! + mock_redis (~> 0.44) overcommit (~> 0.57) - pry-byebug (~> 3.7) - pry-rails (~> 0.3.5) + rbs_rails (~> 0.12) rspec-rails (~> 4.0.2) - rubocop (~> 1.17) - safe_attributes (~> 1.0.10) + rubocop (~> 1.36) sdoc (~> 2) seedbank (~> 0.3) selenium-webdriver (~> 3.142, >= 3.142.7) shoulda-matchers (~> 4.1) simplecov (< 0.18) - spring (~> 2.1) + spring (~> 3.1) spring-commands-rspec (~> 1.0) + steep (~> 1.10.0) traceroute (~> 0.8) webdrivers (~> 4.0) - webpacker (~> 5.0) wicked_pdf! BUNDLED WITH - 2.3.26 + 2.5.22 diff --git a/README.md b/README.md index 54c75da0..012bce06 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,26 @@ Our initial goal is gathering the common code among the current LCMS implementat separately, simplifying the client applications in the process. ## Requirements -- Ruby 2.7.x + +- Ruby 3.2.x - Rails 6.1 or higher - Postgres 9.6 or higher ## Current development -| Branch | Rails version | -|--------|---------------| -| master | Rails 6.1.7 | +| Branch | Rails version | +|-----------|---------------| +| master | Rails 7.0 | +| rails-6.1 | Rails 6.1 | + +## Detailed information + +- [Environment variables](docs/env-variables.md) +- [Google Cloud setup](docs/google-cloud-platform-setup.md) +- [Building and publishing](docs/how-to-build-and-publish.md) +- [PDF generation](docs/pdf-generation.md) +- [Override controllers for Rails 7 application](docs/override-controllers-for-rails-7.md) +- [CKEditor usage](docs/ckeditor-usage.md) ## Guidelines @@ -55,11 +66,11 @@ For regular Ruby classes and modules, we suggest sticking to the recommended pra the official [Rails guide for engines](https://guides.rubyonrails.org/engines.html#improving-engine-functionality) which, for the most part, use the [Decorator pattern](https://en.wikipedia.org/wiki/Decorator_pattern). -* For small changes or refinements you can create a new decorator class inside the `app/decorators` +- For small changes or refinements you can create a new decorator class inside the `app/decorators` folder, and use `class_eval` or `module_eval` to override the methods that you want. You can see a few examples of this technique in the latest changes added to [OpenSciEd](https://github.com/learningtapestry/openscied-lcms/tree/engine-integration/app/decorators) and [Odell](https://github.com/learningtapestry/odell-lcms/tree/engine-integration/app/decorators) -* When changes are bigger or have a much larger impact on the target class or module, it's better to +- When changes are bigger or have a much larger impact on the target class or module, it's better to include a new module that contains your overrides. Again, the Rails guide suggests using `ActiveSupport::Concern`, which simplifies things a bit, although a regular Ruby module would also work. In this case, you move the default code to a new module that extends `ActiveSupport::Concern`, @@ -67,7 +78,7 @@ and include that resulting module in both the engine class and the one in your c After that, you're free to add new methods or override the ones from the module. You can see examples of an [extracted module](https://github.com/learningtapestry/lcms-engine/blob/master/lib/concerns/doc_template/template.rb) and a [class including it](https://github.com/learningtapestry/odell-lcms/blob/engine-integration/lib/doc_template/template.rb). -* Finally, as a last resort, if the customizations you're performing differ a lot from the default +- Finally, as a last resort, if the customizations you're performing differ a lot from the default behaviour, you can consider overriding completely the class by just leaving a file with the same name in the same path. Rails will always give preference to classes inside your project in the loading phase. @@ -76,53 +87,76 @@ Other kinds of assets, like ERB views, images, stylesheets or javascript files, overridden as easily as Ruby classes and modules, but you can always provide your own versions of the same files, overwriting the ones provided by the engine. -You can run the rake task `lcms_engine:webpacker:compile` and set the environment variable `YARN_PATH` to set the yarn binary. +TODO: Update this part about how to build the assets +**OUT DATED** You can run the rake task `lcms_engine:webpacker:compile` and set the environment variable `YARN_PATH` to set the yarn binary. ## Installation + Add this to the Gemfile: + ```ruby -gem 'lcms-engine' # Rails 6.1 +gem 'lcms-engine' ``` And then execute: -```bash -$ bundle -``` -Or install it yourself as: ```bash -$ gem install lcms-engine +bundle ``` Copying all required configuration files + ```bash -$ bundle exec rails g lcms:engine:install +bin/rails g lcms:engine:install ``` You may need to load the schema. Execute from your app's root directory: + ```bash -$ bundle exec rake lcms_engine:load_default_schema +bin/rails lcms_engine:load_default_schema ``` Pre-load required data + ```bash -$ bundle exec rake lcms_engine:seed_data +bin/rails lcms_engine:seed_data ``` Mount the engine in the `routes.rb` + ```ruby mount Lcms::Engine::Engine, at: '/lcms' ``` + Pay attention that adding route alias is not supported. That said you **can't** mount the engine as follow: + ```ruby mount Engine, at: '/engine', as: :engine ```` +Inside environment configuration files explicitly set resque queue adapter and queue prefix if need to separate +queues across environments: + +```ruby +# config/environments/development.rb +# config/environments/staging.rb +# ... +# config/environments/production.rb + +# Use a real queuing backend for Active Job (and separate queues per environment) +# config.active_job.queue_adapter = :resque +# config.active_job.queue_name_prefix = "content_#{Rails.env}" +# Explicitly set queue adapter (set the same as at lcms-engine) +config.active_job.queue_adapter = :resque +config.active_job.queue_name_prefix = '' +``` + If you need to redefine devise routes set up env `DEVISE_ROUTES_REDEFINED` as true and define devise related routes at host app. ### Host app routes When host app has its own routes on upper than engine level: + ```ruby Lcms::Engine::Engine.routes.draw do devise_for :users, class_name: 'Lcms::Engine::User', @@ -149,7 +183,9 @@ devise_scope :user do delete '/register', to: 'lcms/engine/registrations#destroy' end ``` + When host app is ok with `/lcms` devise routes but want to redefine paths after that: + ```ruby Lcms::Engine::Engine.routes.draw do devise_for :users, class_name: 'Lcms::Engine::User', @@ -157,7 +193,7 @@ Lcms::Engine::Engine.routes.draw do registrations: 'lcms/engine/registrations', sessions: 'lcms/engine/sessions' }, - module: :devise, + module: 'devise', path: '', path_names: { sign_in: 'login', @@ -176,9 +212,6 @@ All migrations included in the gem are already available for you to run from ins ### Using with Host app You need to run special rake task if default routes were overridden -```bash -$ bundle exec rake js-routes:generate -``` ## Developing and testing @@ -192,29 +225,57 @@ connection (see `spec/dummy/.env` as a template) For macOS it can be installed with Homebrew: ```bash -$ brew tap homebrew/cask -$ brew cask install chromedriver +brew tap homebrew/cask +brew cask install chromedriver ``` -#### Using Docker +### Using Docker Launch the containers + ```shell -$ docker build -t lcms-engine . -$ docker compose start -$ docker compose exec app sh -c 'bin/rails db:create' -$ docker compose exec db sh -c "psql -U postgres -d template1 -c 'CREATE EXTENSION IF NOT EXISTS hstore;'" +docker compose create +docker compose start db redis +docker compose exec db sh -c "psql -U postgres -d template1 -c 'CREATE EXTENSION IF NOT EXISTS hstore;'" +docker compose exec db sh -c "psql -U postgres -c 'CREATE DATABASE lcms_engine_test;'" +docker compose start app ``` Start the specs + ```shell -$ docker compose exec app sh -c 'bundle exec rspec' +docker compose run --rm app sh -c 'bundle exec rspec' ``` In case you need to rebuild the image, use buildx command to create multi-arch image and push it to the registry + ```shell -docker buildx build --platform linux/arm64/v8,linux/amd64 -t learningtapestry/lcms-engine --push . +docker buildx build --platform linux/arm64/v8,linux/amd64 -t learningtapestry/lcms-engine:ruby-3.3.9 --push . +``` + +### RBS type checking +To run type checks locally: + +```shell +bundle exec steep check +``` + +To generate RBS files for a new model, go to spec/dummy and run the following command: + +```shell +bundle exec rails rbs_rails:generate_rbs_for_models +``` + +You can then copy the relevant files to the correct sig subdirectory from root, and delete the other non-relevant files +under spec/dummy/sig + +To generate a single non-model file signature, follow this example. You may need to manually create the sig subdirectories +if not already there + +```shell +bundle exec rbs prototype rb app/jobs/lcms/engine/integrations/webhook_call_job.rb > sig/app/jobs/lcms/engine/integrations/webhook_call_job.rbs ``` ## License + The gem is available as open source under the terms of the [Apache License](https://github.com/learningtapestry/lcms-engine/blob/master/LICENSE). diff --git a/Steepfile b/Steepfile new file mode 100644 index 00000000..d6a85923 --- /dev/null +++ b/Steepfile @@ -0,0 +1,15 @@ +# D = Steep::Diagnostic + +target :app do + signature "sig", "vendor/rbs" + + check "app/models/**/*.rb" + check "lib" + + ignore "lib/resque_job.rb" + ignore "lib/document_exporter/gdoc/base.rb" + + library "base64" + library "activerecord" + library "activesupport" +end diff --git a/app/assets/builds/lcms/engine/.keep b/app/assets/builds/lcms/engine/.keep new file mode 100644 index 00000000..e69de29b diff --git a/app/assets/config/lcms_engine_manifest.js b/app/assets/config/lcms_engine_manifest.js index 8e9ec21b..be92658e 100644 --- a/app/assets/config/lcms_engine_manifest.js +++ b/app/assets/config/lcms_engine_manifest.js @@ -1,11 +1,2 @@ -// JS and CSS bundles - -//= link lcms/engine/lcms_engine_application.js -//= link lcms/engine/admin/lcms_engine_application.js - -//= link lcms/engine/lcms_engine_application.css -//= link lcms/engine/lcms_engine_admin.css - -// Images and fonts so that views can link to them -// -//= link_tree ../images +//= link_tree ../images/lcms/engine/ +//= link_tree ../builds/lcms/engine/ diff --git a/app/assets/images/lcms/engine/.keep b/app/assets/images/lcms/engine/.keep new file mode 100644 index 00000000..e69de29b diff --git a/app/assets/images/lcms/engine/about.jpg b/app/assets/images/lcms/engine/about.jpg deleted file mode 100644 index 3f1fc6fb..00000000 Binary files a/app/assets/images/lcms/engine/about.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/about_hero_bg.jpg b/app/assets/images/lcms/engine/about_hero_bg.jpg deleted file mode 100644 index 3dfab9f4..00000000 Binary files a/app/assets/images/lcms/engine/about_hero_bg.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/about_partners.jpg b/app/assets/images/lcms/engine/about_partners.jpg deleted file mode 100644 index 86287e66..00000000 Binary files a/app/assets/images/lcms/engine/about_partners.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/about_standards_institute.jpg b/app/assets/images/lcms/engine/about_standards_institute.jpg deleted file mode 100644 index 63a8859d..00000000 Binary files a/app/assets/images/lcms/engine/about_standards_institute.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/about_who_we_are.jpg b/app/assets/images/lcms/engine/about_who_we_are.jpg deleted file mode 100644 index f1fd89eb..00000000 Binary files a/app/assets/images/lcms/engine/about_who_we_are.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/apple-touch-icon.png b/app/assets/images/lcms/engine/apple-touch-icon.png deleted file mode 100644 index 600738f2..00000000 Binary files a/app/assets/images/lcms/engine/apple-touch-icon.png and /dev/null differ diff --git a/app/assets/images/lcms/engine/bg-for-question@3x.png b/app/assets/images/lcms/engine/bg-for-question@3x.png deleted file mode 100644 index 6f64cda6..00000000 Binary files a/app/assets/images/lcms/engine/bg-for-question@3x.png and /dev/null differ diff --git a/app/assets/images/lcms/engine/cc_logo.png b/app/assets/images/lcms/engine/cc_logo.png deleted file mode 100644 index 10fad772..00000000 Binary files a/app/assets/images/lcms/engine/cc_logo.png and /dev/null differ diff --git a/app/assets/images/lcms/engine/cc_logo.svg b/app/assets/images/lcms/engine/cc_logo.svg deleted file mode 100644 index e5f5020c..00000000 --- a/app/assets/images/lcms/engine/cc_logo.svg +++ /dev/null @@ -1,594 +0,0 @@ - - - - - - - - - - - - - -]> - - - - - - - - - - - - - - - - - Adobe PDF library 5.00 - - - - - - - 2004-05-11T00:59:25-07:00 - 2004-05-12T15:44:25Z - Adobe Illustrator 10 - 2004-05-11T00:59:25-07:00 - - - - JPEG - 256 - 64 - /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAQAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FUHqWs6PpcQm1O+ t7GE7CS5lSFa/NyoxVJoPzN/Le4m9G3816NNN/vqPULVm60+yJCepxVkUU0U0aywuskTiqOhDKQe 4I2OKr8VdirsVS+fzBoNvcG2uNStYbgGhhknjV6ntxLVxVHghgCDUHcEdCMVbxV2KuxVLo/MXl+W 4FtFqdo9yWKiFZ4y5YdRxDVriqY4q0SAKnYDqcVS0+Z/LQl9I6tZCUNxMf1iLlyrSlOVa1xVMlZW UMpDKwqrDcEHuMVfO3/OXfn/AM5eUf8ACf8AhvVp9L+u/pD636BA9T0vq3p8qg/Z9RqfPFU6/wCc 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 smEtnp9srC1SZTVZHeQK8nE7qOCivWuKv//Z - - - - - - - uuid:d5a59b43-a42a-11d8-833c-000393d00dfa - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 -pc1D1RYniSbL5pcPVdsBN+8luX2o2m0B+dhdDe96D1XPjbd4V6ejy+XmoeqVanjdPVTtTCrKWlw/ -VO26ZHn2mWr7fEu3xGf5ULVzvuVvH6q2QKDtS98rJo8vRyDe2eXqsky3DDKwemPHqsLONQIXQidO -twQ4uL3LyrM3fMpjU57tIm638KL0Bu/PM4qz3b7O4JRZurw4e5UYrE159joY28yVuXPF2b85FVXL -szeQdeOiPNsFSGRfflee7aI42w5jawatbIqztRelN3m5wmJxts1LRquUZ7vOUfxVebaLiJrtKY/7 -8mwXxdkGV65dnu1CytmuxX15tutXy39Vnm2xifPF2fY2zAojOtYuWmmxFcuzTVRrV5y9ZF/clmev -sC+/KM+2xOLsWcNG9sXR+l3xrk6r8uzfv8DupjzbhbFhvMq0dnm2i+Js82nCbyvd7Yuz17vVfKY8 -2wUq7fKTVyjPdlGcvfKp6GJ5totSY6u8vhXLs10UZzvcC+emPNtFcfZs1s1a5dlrY2zFCkrH4mwn -fnFRduxCS3tcArVi9eUcSHq27frl2YsgWeSN/7o822Vl/S/Ls52Ls8251rjW5nzioOuVWt28o7+d -bXVHt/n1bPe16sveznb1nrjrsnE7wTRXY+UWvBXfznbKtN/M29mGRrZ+PXvt3Zh5O9v1TXq/ejvb -yoJdVkftyqRZWK7t2+if7i5ycSxEJ1XUxN9fI6632tvZlnd1ukagO0PG/b0KnwuVca6vLwFzyBTn -v/y7inGbm1o3XDHuIgq3gYrxxVrRv6gYt8fYJivGN3DDoYuKcbc3HP6uYtycc/V3FeNuXjT4fcW4 -xyHhaDMV44sZRH9RMa5X2FkWG2+qYtyoFBZdMto6FePrvmG3WsX4coxtqmIc85PXr51yWzE+f/b6 -NxXjlpX1G68Yd38XwW8qxmer0v6qYnxZ7ujmKsZXuntw7Ypx29eyNloxvpm6JKeK8RXqkn5RMb6Q -A/8nFeMboDEXFeMe99r3FxXjMzT2ZxXjq7xZv37FuM2b9RuuGCc3gjJuT1fXrRj3+BffPN98xfiG -aqwcKsZNlOy+NG3linEl1mdn8myqYlyxLthNoc2mNG31utd1KsatYxebrhjfHI0tnmUv3j24Ymna -ihXjnpVcl3Urxufv7PqbinH7m1o3WTGuVz/tub5/bY2K8d/cdeO+Ytzj4sHr3xsb2qvlLkpaflEx -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 -sVo= - - - diff --git a/app/assets/images/lcms/engine/cg_icon_complexity.png b/app/assets/images/lcms/engine/cg_icon_complexity.png deleted file mode 100644 index 10238c18..00000000 Binary files a/app/assets/images/lcms/engine/cg_icon_complexity.png and /dev/null differ diff --git a/app/assets/images/lcms/engine/cg_icon_instruction.png b/app/assets/images/lcms/engine/cg_icon_instruction.png deleted file mode 100644 index 71e6991e..00000000 Binary files a/app/assets/images/lcms/engine/cg_icon_instruction.png and /dev/null differ diff --git a/app/assets/images/lcms/engine/cg_icon_volume.png b/app/assets/images/lcms/engine/cg_icon_volume.png deleted file mode 100644 index a2abbab2..00000000 Binary files a/app/assets/images/lcms/engine/cg_icon_volume.png and /dev/null differ diff --git a/app/assets/images/lcms/engine/cg_placeholder.jpg b/app/assets/images/lcms/engine/cg_placeholder.jpg deleted file mode 100644 index d16f8c07..00000000 Binary files a/app/assets/images/lcms/engine/cg_placeholder.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/curriculum.jpg b/app/assets/images/lcms/engine/curriculum.jpg deleted file mode 100644 index 1490649c..00000000 Binary files a/app/assets/images/lcms/engine/curriculum.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/curriculum.svg b/app/assets/images/lcms/engine/curriculum.svg deleted file mode 100644 index d409478c..00000000 --- a/app/assets/images/lcms/engine/curriculum.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - diff --git a/app/assets/images/lcms/engine/favicon.ico b/app/assets/images/lcms/engine/favicon.ico deleted file mode 100644 index be74abd6..00000000 Binary files a/app/assets/images/lcms/engine/favicon.ico and /dev/null differ diff --git a/app/assets/images/lcms/engine/favicon/favicon-128x128.png b/app/assets/images/lcms/engine/favicon/favicon-128x128.png deleted file mode 100644 index e7bf1d74..00000000 Binary files a/app/assets/images/lcms/engine/favicon/favicon-128x128.png and /dev/null differ diff --git a/app/assets/images/lcms/engine/favicon/favicon-16x16.png b/app/assets/images/lcms/engine/favicon/favicon-16x16.png deleted file mode 100644 index 6e4ce5db..00000000 Binary files a/app/assets/images/lcms/engine/favicon/favicon-16x16.png and /dev/null differ diff --git a/app/assets/images/lcms/engine/favicon/favicon-196x196.png b/app/assets/images/lcms/engine/favicon/favicon-196x196.png deleted file mode 100644 index 6e121e50..00000000 Binary files a/app/assets/images/lcms/engine/favicon/favicon-196x196.png and /dev/null differ diff --git a/app/assets/images/lcms/engine/favicon/favicon-32x32.png b/app/assets/images/lcms/engine/favicon/favicon-32x32.png deleted file mode 100644 index 1fb394f1..00000000 Binary files a/app/assets/images/lcms/engine/favicon/favicon-32x32.png and /dev/null differ diff --git a/app/assets/images/lcms/engine/favicon/favicon-96x96.png b/app/assets/images/lcms/engine/favicon/favicon-96x96.png deleted file mode 100644 index f2bd63ac..00000000 Binary files a/app/assets/images/lcms/engine/favicon/favicon-96x96.png and /dev/null differ diff --git a/app/assets/images/lcms/engine/favicon/favicon.ico b/app/assets/images/lcms/engine/favicon/favicon.ico deleted file mode 100644 index 5a246cf8..00000000 Binary files a/app/assets/images/lcms/engine/favicon/favicon.ico and /dev/null differ diff --git a/app/assets/images/lcms/engine/head.svg b/app/assets/images/lcms/engine/head.svg deleted file mode 100644 index c15f1e1e..00000000 --- a/app/assets/images/lcms/engine/head.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - diff --git a/app/assets/images/lcms/engine/home_background.jpeg b/app/assets/images/lcms/engine/home_background.jpeg deleted file mode 100644 index eaa0607b..00000000 Binary files a/app/assets/images/lcms/engine/home_background.jpeg and /dev/null differ diff --git a/app/assets/images/lcms/engine/hp_advance_leadership.jpg b/app/assets/images/lcms/engine/hp_advance_leadership.jpg deleted file mode 100644 index 120a9e6c..00000000 Binary files a/app/assets/images/lcms/engine/hp_advance_leadership.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/hp_ela_bg.jpg b/app/assets/images/lcms/engine/hp_ela_bg.jpg deleted file mode 100644 index 0182be2a..00000000 Binary files a/app/assets/images/lcms/engine/hp_ela_bg.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/hp_enhance_instructions.jpg b/app/assets/images/lcms/engine/hp_enhance_instructions.jpg deleted file mode 100644 index da40f9e4..00000000 Binary files a/app/assets/images/lcms/engine/hp_enhance_instructions.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/hp_find_lessons.jpg b/app/assets/images/lcms/engine/hp_find_lessons.jpg deleted file mode 100644 index ceeb137e..00000000 Binary files a/app/assets/images/lcms/engine/hp_find_lessons.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/hp_hero_bg.jpg b/app/assets/images/lcms/engine/hp_hero_bg.jpg deleted file mode 100644 index 048288a9..00000000 Binary files a/app/assets/images/lcms/engine/hp_hero_bg.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/hp_hero_bg_medium.jpg b/app/assets/images/lcms/engine/hp_hero_bg_medium.jpg deleted file mode 100644 index 957eda0f..00000000 Binary files a/app/assets/images/lcms/engine/hp_hero_bg_medium.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/hp_hero_bg_small.jpg b/app/assets/images/lcms/engine/hp_hero_bg_small.jpg deleted file mode 100644 index 0b5f3d85..00000000 Binary files a/app/assets/images/lcms/engine/hp_hero_bg_small.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/hp_math_bg.jpg b/app/assets/images/lcms/engine/hp_math_bg.jpg deleted file mode 100644 index ec02ef0d..00000000 Binary files a/app/assets/images/lcms/engine/hp_math_bg.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/hp_si_bg.jpg b/app/assets/images/lcms/engine/hp_si_bg.jpg deleted file mode 100644 index b77a6e8c..00000000 Binary files a/app/assets/images/lcms/engine/hp_si_bg.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/ld_p1.svg b/app/assets/images/lcms/engine/ld_p1.svg deleted file mode 100644 index 42034dbe..00000000 --- a/app/assets/images/lcms/engine/ld_p1.svg +++ /dev/null @@ -1 +0,0 @@ -F1/P1 \ No newline at end of file diff --git a/app/assets/images/lcms/engine/ld_p2.svg b/app/assets/images/lcms/engine/ld_p2.svg deleted file mode 100644 index 6a78dd65..00000000 --- a/app/assets/images/lcms/engine/ld_p2.svg +++ /dev/null @@ -1 +0,0 @@ -F1/P1 Copy \ No newline at end of file diff --git a/app/assets/images/lcms/engine/ld_p3.svg b/app/assets/images/lcms/engine/ld_p3.svg deleted file mode 100644 index 981c908b..00000000 --- a/app/assets/images/lcms/engine/ld_p3.svg +++ /dev/null @@ -1 +0,0 @@ -F1/P1 Copy 2 \ No newline at end of file diff --git a/app/assets/images/lcms/engine/logo-beta.svg b/app/assets/images/lcms/engine/logo-beta.svg deleted file mode 100644 index 6ad74713..00000000 --- a/app/assets/images/lcms/engine/logo-beta.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/app/assets/images/lcms/engine/logo_footer.svg b/app/assets/images/lcms/engine/logo_footer.svg deleted file mode 100644 index 4c356072..00000000 --- a/app/assets/images/lcms/engine/logo_footer.svg +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/assets/images/lcms/engine/logo_odell.png b/app/assets/images/lcms/engine/logo_odell.png deleted file mode 100644 index 81c6a42a..00000000 Binary files a/app/assets/images/lcms/engine/logo_odell.png and /dev/null differ diff --git a/app/assets/images/lcms/engine/logo_odell_footer.png b/app/assets/images/lcms/engine/logo_odell_footer.png deleted file mode 100644 index 81c6a42a..00000000 Binary files a/app/assets/images/lcms/engine/logo_odell_footer.png and /dev/null differ diff --git a/app/assets/images/lcms/engine/logo_pdf.svg b/app/assets/images/lcms/engine/logo_pdf.svg deleted file mode 100644 index 09cd066d..00000000 --- a/app/assets/images/lcms/engine/logo_pdf.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - Created with Sketch. - - - diff --git a/app/assets/images/lcms/engine/ls_curriculum.jpg b/app/assets/images/lcms/engine/ls_curriculum.jpg deleted file mode 100644 index 9b3ce2fb..00000000 Binary files a/app/assets/images/lcms/engine/ls_curriculum.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/ls_hero_bg.jpg b/app/assets/images/lcms/engine/ls_hero_bg.jpg deleted file mode 100644 index fbd731fd..00000000 Binary files a/app/assets/images/lcms/engine/ls_hero_bg.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/ls_hero_bg_medium.jpg b/app/assets/images/lcms/engine/ls_hero_bg_medium.jpg deleted file mode 100644 index acc923d8..00000000 Binary files a/app/assets/images/lcms/engine/ls_hero_bg_medium.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/ls_hero_bg_small.jpg b/app/assets/images/lcms/engine/ls_hero_bg_small.jpg deleted file mode 100644 index 64a0e525..00000000 Binary files a/app/assets/images/lcms/engine/ls_hero_bg_small.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/ls_team.jpg b/app/assets/images/lcms/engine/ls_team.jpg deleted file mode 100644 index 396f0a41..00000000 Binary files a/app/assets/images/lcms/engine/ls_team.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/ls_whyilead.jpg b/app/assets/images/lcms/engine/ls_whyilead.jpg deleted file mode 100644 index 09500425..00000000 Binary files a/app/assets/images/lcms/engine/ls_whyilead.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/og_image.png b/app/assets/images/lcms/engine/og_image.png deleted file mode 100644 index da5b0084..00000000 Binary files a/app/assets/images/lcms/engine/og_image.png and /dev/null differ diff --git a/app/assets/images/lcms/engine/pd-cg-placeholder.jpg b/app/assets/images/lcms/engine/pd-cg-placeholder.jpg deleted file mode 100644 index 1435bb2a..00000000 Binary files a/app/assets/images/lcms/engine/pd-cg-placeholder.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/pd-collapse.svg b/app/assets/images/lcms/engine/pd-collapse.svg deleted file mode 100644 index c97381aa..00000000 --- a/app/assets/images/lcms/engine/pd-collapse.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/assets/images/lcms/engine/pd-expand.svg b/app/assets/images/lcms/engine/pd-expand.svg deleted file mode 100644 index 2fd146cf..00000000 --- a/app/assets/images/lcms/engine/pd-expand.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/app/assets/images/lcms/engine/pdf-icon.svg b/app/assets/images/lcms/engine/pdf-icon.svg deleted file mode 100644 index cf4081b9..00000000 --- a/app/assets/images/lcms/engine/pdf-icon.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - Fill 1 - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/assets/images/lcms/engine/placeholder.svg b/app/assets/images/lcms/engine/placeholder.svg deleted file mode 100644 index c939c5cf..00000000 --- a/app/assets/images/lcms/engine/placeholder.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/app/assets/images/lcms/engine/placeholder_white_bg.svg b/app/assets/images/lcms/engine/placeholder_white_bg.svg deleted file mode 100644 index afbf19cb..00000000 --- a/app/assets/images/lcms/engine/placeholder_white_bg.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/app/assets/images/lcms/engine/professional_development.jpg b/app/assets/images/lcms/engine/professional_development.jpg deleted file mode 100644 index 32c4cad5..00000000 Binary files a/app/assets/images/lcms/engine/professional_development.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/resource_background.jpg b/app/assets/images/lcms/engine/resource_background.jpg deleted file mode 100644 index 5e25938d..00000000 Binary files a/app/assets/images/lcms/engine/resource_background.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/resource_placeholder.jpg b/app/assets/images/lcms/engine/resource_placeholder.jpg deleted file mode 100644 index 998be3a4..00000000 Binary files a/app/assets/images/lcms/engine/resource_placeholder.jpg and /dev/null differ diff --git a/app/assets/images/lcms/engine/st_icon_a.svg b/app/assets/images/lcms/engine/st_icon_a.svg deleted file mode 100644 index 87dabc63..00000000 --- a/app/assets/images/lcms/engine/st_icon_a.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/app/assets/images/lcms/engine/st_icon_m.svg b/app/assets/images/lcms/engine/st_icon_m.svg deleted file mode 100644 index 9ac31af3..00000000 --- a/app/assets/images/lcms/engine/st_icon_m.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/assets/images/lcms/engine/st_icon_plus.svg b/app/assets/images/lcms/engine/st_icon_plus.svg deleted file mode 100644 index 2ba3c3a9..00000000 --- a/app/assets/images/lcms/engine/st_icon_plus.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/app/assets/images/lcms/engine/st_icon_s.svg b/app/assets/images/lcms/engine/st_icon_s.svg deleted file mode 100644 index d4d22423..00000000 --- a/app/assets/images/lcms/engine/st_icon_s.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/assets/javascripts/lcms/engine/admin/lcms_engine_application.js b/app/assets/javascripts/lcms/engine/admin/lcms_engine_application.js deleted file mode 100644 index 5faea08a..00000000 --- a/app/assets/javascripts/lcms/engine/admin/lcms_engine_application.js +++ /dev/null @@ -1,21 +0,0 @@ -//= require turbolinks -//= require jquery3 -//= require jquery_nested_form -//= require foundation -//= require html.sortable.min -//= require js-routes -//= require 'selectize.min' -//= require '../initializers/foundation' - -document.addEventListener('turbolinks:load', function () { - $('.selectize').selectize({ - allowEmptyOption: true, - plugins: ['remove_button'], - }); - - if (typeof CKEDITOR === 'undefined' && document.getElementsByClassName('ckeditor').length) { - var script = document.createElement('script'); - script.src = 'https://cdn.ckeditor.com/4.20.0/standard/ckeditor.js'; - document.head.append(script); - } -}); diff --git a/app/assets/javascripts/lcms/engine/application_lti.js b/app/assets/javascripts/lcms/engine/application_lti.js deleted file mode 100644 index 379f693f..00000000 --- a/app/assets/javascripts/lcms/engine/application_lti.js +++ /dev/null @@ -1 +0,0 @@ -// Should require JS needed by LTI rendering diff --git a/app/assets/javascripts/lcms/engine/initializers/foundation.js b/app/assets/javascripts/lcms/engine/initializers/foundation.js deleted file mode 100644 index 73919fd0..00000000 --- a/app/assets/javascripts/lcms/engine/initializers/foundation.js +++ /dev/null @@ -1,5 +0,0 @@ -document.addEventListener('turbolinks:load', function () { - $(function () { - $(document).foundation(); - }); -}); diff --git a/app/assets/javascripts/lcms/engine/lcms_engine_application.js b/app/assets/javascripts/lcms/engine/lcms_engine_application.js deleted file mode 100644 index 9ff4959d..00000000 --- a/app/assets/javascripts/lcms/engine/lcms_engine_application.js +++ /dev/null @@ -1,5 +0,0 @@ -//= require turbolinks -//= require jquery3 -//= require foundation -//= require js-routes -//= require './initializers/foundation' diff --git a/app/assets/stylesheets/lcms/engine/.keep b/app/assets/stylesheets/lcms/engine/.keep new file mode 100644 index 00000000..e69de29b diff --git a/app/assets/stylesheets/lcms/engine/admin/collections.scss b/app/assets/stylesheets/lcms/engine/admin/collections.scss deleted file mode 100644 index 04eb31fd..00000000 --- a/app/assets/stylesheets/lcms/engine/admin/collections.scss +++ /dev/null @@ -1,62 +0,0 @@ -// stylelint-disable max-nesting-depth, selector-max-compound-selectors, scss/selector-no-redundant-nesting-selector -.collection-tree { - label { - background: map-get($ub-ld-colorcodes, material-thumbnail-border); - cursor: pointer; - font-weight: bold; - margin-right: 3px; - text-align: center; - width: 1.5em; - - &::before { - content: '▸'; - } - } - - ul { - display: none; - } -} - -.collection-tree-opener { - display: none; - - &:checked { - & ~ label { - &::before { - content: '▾'; - } - } - - & ~ ul { - display: block; - } - } -} - -.editable-tree { - .resource-title { - cursor: move; - } - - li { - .deleted { - & > .restore-link { - display: inline; - } - - .links { - display: none; - } - - .resource-title { - cursor: default; - text-decoration: line-through; - } - } - } - - .restore-link { - display: none; - } -} diff --git a/app/assets/stylesheets/lcms/engine/admin/components.scss b/app/assets/stylesheets/lcms/engine/admin/components.scss deleted file mode 100644 index 6843a15b..00000000 --- a/app/assets/stylesheets/lcms/engine/admin/components.scss +++ /dev/null @@ -1,149 +0,0 @@ -// stylelint-disable max-nesting-depth, selector-no-qualifying-type, selector-max-compound-selectors, scss/at-extend-no-missing-placeholder, selector-max-id -.o-admin-components-table { - width: 100%; - - .o-admin-components-table--header { - border-bottom: solid 1px #5a5a5a; - font-weight: bold; - - div { - padding: 1em; - text-align: center; - } - } - - .o-admin-components-table--info { - display: inline-block; - vertical-align: top; - } - - .o-admin-components-table--location { - @extend .o-admin-components-table--info; - width: 15%; - } - - .o-admin-components-table--component-type { - @extend .o-admin-components-table--info; - width: 15%; - } - - .o-admin-components-table--origin { - @extend .o-admin-components-table--info; - width: 35%; - } - - .o-admin-components-table--identifier { - @extend .o-admin-components-table--info; - width: 25%; - } - - .o-admin-components-table--details { - @extend .o-admin-components-table--info; - width: 5%; - } - - .o-admin-components-table--entry { - margin-bottom: 1em; - padding: 1em; - - &:nth-child(odd) { - background-color: $ub-whitepure; - } - } - - .o-admin-components-table--details { - .button { - float: right; - margin-bottom: -0.5em; - margin-right: -2em; - } - } - - .o-admin-components-table--name { - margin-top: 2em; - - b { - margin-right: 1em; - } - } -} - -.o-admin-components-search { - border: solid 1px #ddd; - border-radius: 5px; - margin-bottom: 2em; - padding: 1em; - - .row { - // margin-top: 1em; - .o-admin-ctx-filters { - padding-right: 2em; - - label { - display: inline-block; - font-weight: normal; - } - - input { - display: inline-block; - margin-left: 1em; - width: 6em; - } - } - - input[type='submit'] { - margin-left: auto; - } - } - - .o-admin-search-term { - @include placeholder(#5a5a5a); - width: 60%; - } - - .dropdown-pane { - background-color: $ub-whitepure; - box-shadow: 2px 2px 2px #aaa; - } - - #component-type-selector, - #component-grades { - select { - height: 285px; - } - - aside { - font-size: 0.8em; - } - } - - label { - font-weight: bold; - } -} - -.o-admin-component-details { - .o-component-details--attribute { - border-bottom: solid 1px $ub-whitepure; - padding: 1em; - - .o-attr-key { - font-weight: bold; - width: 250px; - } - } - - .o-component-details--content { - background-color: $ub-whitepure; - border-radius: 0.5em; - margin: 2em; - padding: 1em; - } -} - -.o-page { - .c-hidden { - display: none; - } -} -//sass-lint:enable diff --git a/app/assets/stylesheets/lcms/engine/admin/curriculums.scss b/app/assets/stylesheets/lcms/engine/admin/curriculums.scss deleted file mode 100644 index b7df549d..00000000 --- a/app/assets/stylesheets/lcms/engine/admin/curriculums.scss +++ /dev/null @@ -1,67 +0,0 @@ -// stylelint-disable max-nesting-depth, selector-class-pattern - -.c-admcur-results { - .button { - margin-bottom: 0; - } -} - -.o-curriculum-tree-editor { - @include e(container) { - background: $ub-whitepure; - border-radius: 0.5em; - margin: 2em auto; - min-width: 50%; - padding: 2em; - - .button { - &[type='submit'] { - margin: 0 25% 2em; - width: 50%; - } - } - } - @include e(menu-info) { - font-size: 0.9em; - margin-top: -1em; - text-align: center; - } -} - -.o-curriculum-tree-picker { - @include e(container) { - aside { - display: inline-block; - font-size: 0.9em; - margin-left: 1em; - } - - .resource_parent { - // sass-lint:disable-line class-name-format - display: inline-block; - margin-left: 1em; - margin-top: 0.5em; - max-width: 85%; - vertical-align: top; - - strong { - margin-left: 0.25em; - } - } - } -} - -.curriculum-picker-modal { - &.reveal { - background: $ub-whitepure; - border-radius: 0.5em; - margin: 2em auto; - min-width: 50%; - padding: 2em; - - h2 { - margin-bottom: 1em; - text-align: center; - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/admin/lessons.scss b/app/assets/stylesheets/lcms/engine/admin/lessons.scss deleted file mode 100644 index 2b9a9b91..00000000 --- a/app/assets/stylesheets/lcms/engine/admin/lessons.scss +++ /dev/null @@ -1,115 +0,0 @@ -// stylelint-disable max-nesting-depth, selector-max-compound-selectors -.o-admin-lessons-search { - border: solid 1px #ddd; - border-radius: 5px; - margin-bottom: 2em; - padding: 2em 1em 1em; - - .row { - .o-admin-ctx-filters { - padding-right: 2em; - - label { - display: inline-block; - font-weight: normal; - } - - input { - display: inline-block; - margin-left: 1em; - width: 6em; - } - } - } - - .o-admin-search-term { - @include placeholder(#5a5a5a); - width: 60%; - } - - .o-admin-sort-by { - width: 200px; - } - - .o-admin-option { - margin-bottom: 0.75em; - - label { - font-weight: normal; - } - } - - .o-admin-unit, - .o-admin-module { - width: 100px; - } - - .dropdown-pane { - background-color: $ub-whitepure; - box-shadow: 2px 2px 2px #aaa; // sass-lint:disable-line no-color-literals - } - - label { - font-weight: bold; - } -} - -.lessons-table { - table-layout: auto; - width: 100%; - @include e(btns) { - .button { - min-width: 80px; - } - - form { - &:last-child { - .button { - margin-bottom: 0; - } - } - } - } - - @include e(materials) { - list-style: none; - @include m(file) { - margin-right: 0.3em; - } - } - - @include e(status) { - .failed { - color: map-get($ub-colorcodes, error); - font-weight: bold; - } - } - - tr { - th { - &:nth-child(5), - &:nth-child(5) { - max-width: 250px; - min-width: 200px; - } - - &:nth-child(6), - &:nth-child(6) { - min-width: 300px; - } - } - } -} - -.c-reimport-with-materials { - @include e(toggle) { - display: inline-block; - font-weight: bold; - margin-left: 2em; - margin-top: 1.5em; - - input { - margin-right: 0.5em; - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/admin/materials.scss b/app/assets/stylesheets/lcms/engine/admin/materials.scss deleted file mode 100644 index e6f24587..00000000 --- a/app/assets/stylesheets/lcms/engine/admin/materials.scss +++ /dev/null @@ -1,105 +0,0 @@ -.o-adm-materials { - @include e(results) { - list-style: none; - margin-left: 0; - } - - @include e(result) { - border-radius: 0.5rem; - margin-bottom: 0.5rem; - padding: 0.5rem 1rem; - @include m(ok) { - background-color: map-get($ub-green, sixty); - } - - @include m(err) { - background-color: lighten(map-get($ub-colorcodes, error), 40%); - } - - @include m(waiting) { - background-color: lighten($ub-dark-bg, 20%); - } - - @include m(running) { - background-color: map-get($ub-yellow, sixty); - } - - p { - margin-bottom: 0; - } - } - - @include e(link) { - display: block; - font-weight: bold; - } - - @include e(spinner) { - background: none !important; // stylelint-disable-line declaration-no-important - } - - @include e(summary) { - @include m(aside) { - font-size: 0.9em; - text-align: center; - } - - .summary-entry { - display: inline-block; - text-align: center; - width: 33%; - } - } - - @include e(resource) { - border-radius: 0.5em; - } -} - -.o-adm-material-search { - width: 25%; -} - -.materials-table { - @include e(wrapper) { - overflow-x: auto; - width: 100%; - } - - @include e(btns) { - .button { - min-width: 80px; - } - - form { - &:last-child { - .button { - margin-bottom: 0; - } - } - } - } - - @include e(lessons) { - list-style: none; - @include m(file) { - margin-right: 0.3em; - } - } -} - -.c-multi-selected--select-all { - display: inline-block; - font-weight: bold; - margin-left: 0.5em; - margin-top: 1.5em; - - input { - margin-right: 0.5em; - } -} - -.o-adm-list .table .c-selected-ids { - // sass-lint:disable-line force-element-nesting - margin-bottom: 0.2em; -} diff --git a/app/assets/stylesheets/lcms/engine/admin/resources.scss b/app/assets/stylesheets/lcms/engine/admin/resources.scss deleted file mode 100644 index bee390ac..00000000 --- a/app/assets/stylesheets/lcms/engine/admin/resources.scss +++ /dev/null @@ -1,56 +0,0 @@ -.resource-id-label { - cursor: pointer; - font-weight: normal; -} - -.resource-tag-col { - width: 15%; -} - -.resource-search-button { - vertical-align: bottom; -} - -.resource-search-select { - width: 100% !important; // stylelint-disable-line declaration-no-important -} - -.resource-search-url-slug, -.resource-search-title { - flex-grow: 1; - padding: 0 1%; -} - -.resource-search-wrap { - flex-basis: 0; - flex-grow: 1; - padding-right: 1%; - vertical-align: top !important; // stylelint-disable-line declaration-no-important - - &:last-child { - padding: 0; - } -} - -.o-adm-download { - background-color: $ub-white; - margin: 10px 0; - padding: 10px; - @include e(settings) { - @include flex; - - input { - margin-left: 10px; - } - } -} - -.resource-btns-col { - .button { - width: 80px; - } -} - -.c-admin > .o-page { - margin: 40px auto; -} diff --git a/app/assets/stylesheets/lcms/engine/admin/standards.scss b/app/assets/stylesheets/lcms/engine/admin/standards.scss deleted file mode 100644 index efccebdf..00000000 --- a/app/assets/stylesheets/lcms/engine/admin/standards.scss +++ /dev/null @@ -1,36 +0,0 @@ -// stylelint-disable max-nesting-depth -.o-standard-form { - .input { - margin-bottom: 1em; - } - - .o-admin-standard-readonly { - label { - min-width: 5em; - } - } - - label { - font-weight: bold; - margin: 0 1em 1em 0; - } -} - -.o-admin-standards-table { - width: 100%; -} - -.o-admin-standards-search { - .input { - .boolean { - margin-bottom: 1em; - margin-right: 2em; - } - - .string { - &:first-child { - padding-left: 0; - } - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/admin/users.scss b/app/assets/stylesheets/lcms/engine/admin/users.scss deleted file mode 100644 index ac161c42..00000000 --- a/app/assets/stylesheets/lcms/engine/admin/users.scss +++ /dev/null @@ -1,8 +0,0 @@ -.o-admin-users-search { - .o-admin-search-term { - @include placeholder(#3a3a3a); - - margin-right: 0.5em; - width: 45%; - } -} diff --git a/app/assets/stylesheets/lcms/engine/admin/welcome.scss b/app/assets/stylesheets/lcms/engine/admin/welcome.scss deleted file mode 100644 index d3a716b6..00000000 --- a/app/assets/stylesheets/lcms/engine/admin/welcome.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the unbounded::admin::welcome 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/lcms/engine/application.bootstrap.scss b/app/assets/stylesheets/lcms/engine/application.bootstrap.scss new file mode 100644 index 00000000..9ec1cb88 --- /dev/null +++ b/app/assets/stylesheets/lcms/engine/application.bootstrap.scss @@ -0,0 +1,12 @@ +@import 'bootstrap/scss/bootstrap'; +@import 'bootstrap-icons/font/bootstrap-icons'; +@import 'tom-select/dist/css/tom-select.bootstrap5.min'; + +$fa-font-path: '/lcms-engine-assets/fonts'; +@import '@fortawesome/fontawesome-free/scss/fontawesome'; +@import '@fortawesome/fontawesome-free/scss/brands'; +@import '@fortawesome/fontawesome-free/scss/solid'; +@import '@fortawesome/fontawesome-free/scss/regular'; +@import '@fortawesome/fontawesome-free/scss/v4-shims'; + +@import 'ckeditor'; diff --git a/app/assets/stylesheets/lcms/engine/base/_colors.scss b/app/assets/stylesheets/lcms/engine/base/_colors.scss deleted file mode 100644 index 6d3a2d44..00000000 --- a/app/assets/stylesheets/lcms/engine/base/_colors.scss +++ /dev/null @@ -1,189 +0,0 @@ -// Color scheme -$ub-primary: #433973; -$ub-primary1: #433c5d; -$ub-secondary: #ffbf00; -$ub-page-bg: #ededed; -$ub-btn-light: #97c40f; -$ub-btn-base: #ffbf00; -$ub-btn-dark: #7b7b81; -$ub-panel-bg: #dbdbdb; -$ub-hero-bg: #9e9e9e; -$ub-nav-bg: #cec8ea; -$ub-nav-secondary: #423872; -$ub-footer-bg: #cec8ea; -$ub-nav-link: lighten($ub-primary1, 40%); -$ub-light-bg: #dbdbdb; -$ub-dark-bg: #9e9e9e; -$ub-white: #fefefe; -$ub-whitepure: #fff; -$ub-whitesmoke: #f5f5f5; -$ub-heading-txt: #555; -$ub-txt: #666; -$ub-txt-dark: #333; -$ub-label-bg: #777099; -$ub-subscribe-txt: #2f2940; -$ub-hr: #959595; - -// gradient d9d9d9 to c4c3c4 -$ub-gray-gradient: linear-gradient(to bottom, #d9d9d9, #c4c3c4); - -$ub-helpblock: #fee49d; -$ub-dropdown: #ededed; -$ub-tabs: #858585; -$ub-tooltip: #fff8e5; - -$ub-txt-colors: ( - base: $ub-txt, - light: #b3b3b3, - medium-gray: #898989, - h1: $ub-heading-txt, - h2: #333, - h3: $ub-heading-txt, - h4: #898989, -); - -$ub-mignight: ( - clear: #39374a, - filterbar: #413f58, - dark: #282133, - base: #433973, - eighty: #6d6590, - sixty: #928bac, - forty: #b6b2c7, - twenty: #dbd8e3, -); - -$ub-yellow: ( - base: #ffbf00, - eighty: #fc3, - sixty: #ffd966, - forty: #ffe599, - twenty: #fff2cc, -); - -$ub-green: ( - base: #97c40f, - eighty: #a4cc2e, - sixty: #c1dc6f, - forty: #d5e79f, - twenty: #eaf3cf, -); - -$ub-buttons: ( - yellow: #ffbf00, - green: #97c40f, - orange: #f96924, - gray: #959595, - base: $ub-panel-bg, - white: $ub-white, - ela: #f75b28, - math: #00a699, - lead: #a269bf, - print: #423872, - show-more: #9a9a9a, -); - -$ub-leadership: ( - base: #a269bf, - header: #845aa6, - hero: #eee, - bg: #f1e9f6, - subscribe-label: #ca94e0, - subscribe-input: #b57ed1, - arrows1: #edecf1, - arrows2: #e1d9e8, -); - -$ub-colorcodes: ( - // ela color scheme - ela-base: #f75b28, - ela-dark: #9b310e, - ela-pk: #fda43a, - ela-k: #fc9837, - ela-1: #fb8d32, - ela-2: #f7802c, - ela-3: #f97529, - ela-4: #f96924, - ela-5: #f85e20, - ela-6: #f0501a, - ela-7: #e94d1a, - ela-8: #e14b19, - ela-9: #da4818, - ela-10: #d34617, - ela-11: #cc4417, - ela-12: #c54116, - // math color scheme - math-base: #00a699, - math-dark: #005049, - math-foundational: #4bb0db, - math-pk: #69d59d, - math-k: #5bcf9d, - math-1: #4cc89c, - math-2: #3cc19b, - math-3: #2ebb9b, - math-4: #1eb49a, - math-5: #0fad9a, - math-6: #009d90, - math-7: #009488, - math-8: #008b7f, - math-9: #008277, - math-10: #00796e, - math-11: #007066, - math-12: #00675d, - // default color scheme - default-base: #a269bf, - default-pk: #8985ad, - default-k: #8985ad, - default-1: #827da4, - default-2: #78749a, - default-3: #706c90, - default-4: #676385, - default-5: #5e5b7c, - default-6: #555170, - default-7: #4a4764, - default-8: #4a4663, - default-9: #45425f, - default-10: #44415c, - default-11: #423f59, - default-12: #413f58, - //leadership - lead-base: #a269bf, - lead-pk: #cb94e1, - lead-k: #ca94e0, - lead-1: #c58edc, - lead-2: #c088d7, - lead-3: #b47ccd, - lead-4: #a86fc4, - lead-5: #a86fc4, - lead-6: #9961b6, - lead-7: #905aae, - lead-8: #8753a5, - lead-9: #7e4b9d, - lead-10: #754494, - lead-11: #6d3d8c, - lead-12: #633583, - // additional - base: $ub-panel-bg, - base-txt: $ub-heading-txt, - clear: #959595, - dark-gold: #ffbf00, - facet: #6e6e6e, - light: $ub-white, - error: #f00, - violet-light: #ecebf0 -); - -$ub-selection: ( - disabled-parent-bg: rgba(0, 0, 0, 0.2), - ela-disabled-switch-bg: #b75638, - ela-disabled-fg: #f8a58c, - ela-enabled-bg: #f45b33, - ela-enabled-fg: #fff, - ela-enabled-switch-bg: #fbcdbf, - math-disabled-fg: #71c5b8, - math-disabled-switch-bg: #0d8178, - math-enabled-bg: #18a497, - math-enabled-fg: #fff, - math-enabled-switch-bg: #b4e4e0, - modal-title-fg: #4e4b7a, -); diff --git a/app/assets/stylesheets/lcms/engine/base/_ld-colors.scss b/app/assets/stylesheets/lcms/engine/base/_ld-colors.scss deleted file mode 100644 index 0a4c1d0e..00000000 --- a/app/assets/stylesheets/lcms/engine/base/_ld-colors.scss +++ /dev/null @@ -1,26 +0,0 @@ -$ub-ld-colorcodes: ( - activity-optional: #fee59e, - default-image-bg: transparentize(map-get($ub-colorcodes, default-base), 0.85), - ela-expand-bg: transparentize(map-get($ub-colorcodes, ela-base), 0.85), - ela-toggler-bg: transparentize(map-get($ub-colorcodes, ela-base), 0.7), - ela6-section-bg: transparentize(map-get($ub-colorcodes, ela-base), 0.85), - ela-image-bg: transparentize(map-get($ub-colorcodes, ela-base), 0.85), - gls-bg: #fff6cc, - material-overlay: #423872, - material-thumbnail-border: #ccc, - math-activity-bg: transparentize(map-get($ub-colorcodes, math-base), 0.85), - math-activity-foundational: #4bb0db, - math-activity-foundational-bg: #e4f7ff, - math-activity-foundational-disabled-bg: #0088c2, - math-activity-foundational-disabled-fg: #99e0ff, - math-activity-foundational-guidance-bg: #d5effb, - math-activity-foundational-switch-bg: #b0e1f8, - math-activity-guidance-bg: transparentize(map-get($ub-colorcodes, math-base), 0.85), - math-expand-bg: transparentize(map-get($ub-colorcodes, math-base), 0.85), - math-toggler-bg: transparentize(map-get($ub-colorcodes, math-base), 0.7), - math-image-bg: transparentize(map-get($ub-colorcodes, math-base), 0.85), - pd-bg: #e8e7ed, - pd-bg-dark: #8e89a9, - section-optional: #fee59e, - tooltip-bg: #fff8e5, -); diff --git a/app/assets/stylesheets/lcms/engine/base/_typography.scss b/app/assets/stylesheets/lcms/engine/base/_typography.scss deleted file mode 100644 index 0e27fceb..00000000 --- a/app/assets/stylesheets/lcms/engine/base/_typography.scss +++ /dev/null @@ -1,208 +0,0 @@ -.st-logo { - @include e(standards) { - font-family: $ub-serif; - font-size: 125%; - font-weight: 600; - } - @include e(institute) { - font-family: $ub-serif; - font-weight: 200; - text-transform: uppercase; - } -} - -$ub-heading-typography: ( - h1: ('line-height': 36px, 'font-family': $ub-serif, 'font-weight': 500), - h2: ('line-height': 28px, 'font-family': $ub-serif, 'font-weight': 300), - h3: ('line-height': 28px, 'font-family': $ub-sanserif-condensed, 'font-weight': 500), - h4: ('line-height': 26px, 'font-family': $ub-sanserif, 'font-weight': 300, 'font-style': italic), - h5: ('line-height': 24px, 'font-family': $ub-sanserif-condensed, 'font-weight': 500) -); - -@each $element, $typo in $ub-heading-typography { - #{$element} { - @include txt-heading-base($element); - } -} - -$ub-element-typography: ( - base: ('font-size': 18px, 'line-height': 24px, 'font-family': $ub-sanserif), - small: ('font-size': 13px, 'line-height': 18px, 'font-family': $ub-sanserif), - teaser: ('font-size': 22px, 'line-height': 28px, 'font-family': $ub-serif, color: #9a9a9a), - title-type: ( - small: ('font-size': 15px, 'line-height': 21px, 'font-family': $ub-serif, 'font-weight': 600), - ipad: ('font-size': 16px, 'line-height': 21px, 'font-family': $ub-serif, 'font-weight': 600), - ), - title-type-short: ( - small: ('font-size': 15px, 'line-height': 21px, 'font-family': $ub-serif, 'font-weight': 600), - ipad: ('font-size': 14px, 'line-height': 21px, 'font-family': $ub-serif, 'font-weight': 600), - ), - title-duration: ('font-size': 14px, 'line-height': 21px, 'font-family': $ub-sanserif, color: #929299), - breadcrumbs: ('font-size': 14px, 'line-height': 21px, 'font-family': $ub-serif, 'font-weight': 600), - button: ('font-size': 14px, 'line-height': 21px, 'font-family': $ub-serif, 'font-weight': 600), - button-register: ('font-size': 16px, 'line-height': 21px, 'font-family': $ub-serif, 'font-weight': 600), - button-connect: ('font-size': 15px, 'line-height': 21px, 'font-family': $ub-serif, 'font-weight': 600, color: #433c5d), - hero-header: ('font-size': 40px, 'line-height': 42px, 'font-family': $ub-serif, 'font-weight': 500), - hero-text: ('font-size': 21px, 'line-height': 27px, 'font-family': $ub-sanserif), - media-header: ('font-size': 30px, 'line-height': 30px, 'font-family': $ub-serif, 'font-weight': 600, color: #585858), - media-text: ('font-size': 24px, 'line-height': 30px, 'font-family': $ub-serif), - subscribe-header: ('font-size': 27px, 'line-height': 36px, 'font-family': $ub-serif, 'font-weight': 600), - subscribe-text: ('font-size': 21px, 'line-height': 24px, 'font-family': $ub-sanserif), - nav-base: ('font-size': 16px, 'line-height': 20px, 'font-family': $ub-serif), - nav-span: ('font-size': 20px, 'line-height': 20px, 'font-family': $ub-serif, 'font-weight': 600), - footer-links: ('font-size': 13px, 'line-height': 19px, 'font-family': $ub-sanserif), - related-header: ('font-size': 30px, 'line-height': 36px, 'font-family': $ub-serif, 'font-weight': 500, color: #555), - page-header: ('font-size': 32px, 'line-height': 32px, 'font-family': $ub-serif), - search-header: ('font-size': 20px, 'line-height': 20px, 'font-family': $ub-sanserif), - search-select: ('font-size': 16px, 'line-height': 20px, 'font-family': $ub-sanserif-condensed, color: #929299), - connect-header: (color: #ededed, 'font-size': 16px, 'line-height': 30px, 'font-family': $ub-serif, 'font-weight': 600), - connect-text: ('font-size': 18px, 'line-height': 24px, 'font-family': $ub-serif), - resource-map-header: ('font-size': 18px, 'line-height': 24px, 'font-family': $ub-serif, color: #898989), - resource-more: ('font-size': 18px, 'line-height': 24px, 'font-weight': 600, 'font-family': $ub-sanserif, 'font-style': italic), - filter-header: ('font-size': 32px, 'line-height': 32px, 'font-family': $ub-serif, 'font-weight': 500, color: $ub-primary1), - filter-subheader: ('font-size': 20px, 'line-height': 24px, 'font-family': $ub-serif, 'font-style': italic), - filter-btn: ('font-size': 18px, 'line-height': 18px, 'font-family': $ub-serif, 'font-weight': 600), - filter-label: ('font-size': 22px, 'line-height': 22px, 'font-family': $ub-serif, 'font-weight': 600), - filter-input: ('font-size': 20px, 'line-height': 20px, 'font-family': $ub-sanserif), - filter-title: ('font-size': 26px, 'line-height': 32px, 'font-family': $ub-serif, 'font-weight': 500, color: $ub-white), - lesson-hover: ('font-size': 15px, 'line-height': 18px, 'font-family': $ub-sanserif), - lesson-hover-standard: ('font-size': 14px, 'line-height': 14px, 'font-family': $ub-serif, 'font-weight': 600), - lesson-title: ( - small: ('font-size': 26px, 'line-height': 26px, 'font-family': $ub-serif, 'font-weight': 500, color: #595959), - ipad: ('font-size': 24px, 'line-height': 27px, 'font-family': $ub-serif, 'font-weight': 500, color: #595959), - ), - lesson-duration: ('font-size': 14px, 'line-height': 16px, 'font-family': $ub-sanserif, color: #929299), - cg-card-teaser: ('font-size': 16px, 'line-height': 30px, 'font-family': $ub-serif, 'font-weight': 600, text-shadow: 1px 1px 1px rgba(0, 0, 0, .1)), - cg-card-title: ('font-size': 30px, 'line-height': 32px, 'font-family': $ub-serif, 'font-weight': 500, text-shadow: 1px 1px 1px rgba(0, 0, 0, .1)), - media-card-title: ('font-size': 20px, 'line-height': 22px, 'font-family': $ub-serif, 'font-weight': 500, text-shadow: 1px 1px 1px rgba(0, 0, 0, .1)), - media-card-duration: ('font-size': 14px, 'line-height': 16px, 'font-family': $ub-sanserif), - tab-title: ('font-size': 20px, 'line-height': 24px, 'font-family': $ub-serif, 'font-weight': 600), - engageny: ('font-size': 14px, 'line-height': 18px, 'font-family': $ub-sanserif), - pagination: ('font-size': 16px, 'line-height': 30px, 'font-family': $ub-serif, 'font-weight': 600), - search-result-header: ('font-size': 18px, 'line-height': 21px, 'font-family': $ub-serif, 'font-weight': 600), - search-result-subheader: ('font-size': 18px, 'line-height': 21px, 'font-family': $ub-serif), - search-result-title: ('font-size': 30px, 'line-height': 30px, 'font-family': $ub-serif, 'font-weight': 500), - search-result-teaser: ('font-size': 21px, 'line-height': 26px, 'font-family': $ub-sanserif), - arrow-header: ( - small: ('font-size': 16px, 'line-height': 18px, 'font-family': $ub-serif, 'font-weight': 600), - ipad: ('font-size': 20px, 'line-height': 22px, 'font-family': $ub-serif, 'font-weight': 500), - ), - arrow-subheader: ( - small: ('font-size': 16px, 'line-height': 18px, 'font-family': $ub-serif, 'font-weight': 600), - ipad: ('font-size': 14px, 'line-height': 22px, 'font-family': $ub-serif, 'font-weight': 600, color: #31424c), - ), - card-assesment-medium: (color: #4a4a4a, font-family: $ub-serif, font-size: 24px, font-weight: 500, line-height: 27px), - card-assesment-short: (color: #4a4a4a, font-family: $ub-serif, font-size: 20px, font-weight: 500, line-height: 24px), - card-title-medium: ( - small: ('font-size': 26px, 'line-height': 32px, 'font-family': $ub-serif, 'font-weight': 500, color: #595959), - ipad: ('font-size': 32px, 'line-height': 32px, 'font-family': $ub-serif, 'font-weight': 500, color: #595959), - ), - card-title-short: ( - small: ('font-size': 26px, 'line-height': 32px, 'font-family': $ub-serif, 'font-weight': 500, color: #595959, 'margin-top': $curriculum-card-short-top), - ipad: ('font-size': 24px, 'line-height': 27px, 'font-family': $ub-serif, 'font-weight': 500, color: #595959, 'margin-top': $curriculum-card-short-top), - ), - card-dsc: ( - small: ('font-size': 18px, 'line-height': 24px, 'font-family': $ub-sanserif), - ipad: ('font-size': 15px, 'line-height': 19px, 'font-family': $ub-sanserif), - ), - download-header: ('font-size': 32px, 'line-height': 32px, 'font-family': $ub-serif, 'font-weight': 600, color: $ub-primary1), - download-copyright: ('font-size': 11px, 'line-height': 14px, 'font-family': $ub-sanserif, color: #929299), - // content guides - cg-teaser: ('font-size': 16px, 'line-height': 21px, 'font-family': $ub-serif, 'font-weight': 600, 'text-shadow': 1px 1px 1px rgba(0, 0, 0, .1)), - cg-title: ('font-size': 34px, 'line-height': 35px, 'font-family': $ub-serif, 'font-weight': 500, 'text-shadow': 1px 1px 1px rgba(0, 0, 0, .1)), - cg-sidebar-header: ('font-size': 20px, 'line-height': 24px, 'font-family': $ub-serif, 'font-weight': 600), - cg-sidebar-title-type: ('font-size': 14px, 'line-height': 24px, 'font-family': $ub-serif, 'font-weight': 600), - cg-sidebar-title: ('font-size': 24px, 'line-height': 22px, 'font-family': $ub-serif, 'font-weight': 500), - cg-sidebar-l1: ('font-size': 16px, 'line-height': 20px, 'font-family': $ub-sanserif-condensed, 'font-weight': 500), - cg-sidebar-l2: ('font-size': 15px, 'line-height': 20px, 'font-family': $ub-sanserif-condensed, 'font-weight': 500), - cg-sidebar-l3: ('font-size': 15px, 'line-height': 20px, 'font-family': $ub-sanserif-condensed, 'font-weight': 500), - cg-faq-title: ('font-size': 24px, 'line-height': 26px, 'font-family': $ub-serif, 'font-weight': 600, color: #555), - cg-faq-body: ('font-size': 18px, 'line-height': 24px, 'font-family': $ub-sanserif), - cg-faq-q: ('font-size': 44px, 'line-height': 107px, 'font-family': $ub-serif, 'font-weight': 600), - // explore curriculum - ec-opr-title: (color: $ub-primary, font-family: $ub-serif, font-size: 24px, font-weight: 800), - // leaderhsip - ls-subhead: ('font-size': 20px, 'line-height': 23px, 'font-family': $ub-serif, 'font-weight': 600, color: map-get($ub-leadership, base)), - ls-post-title: ('font-size': 18px, 'line-height': 21px, 'font-family': $ub-serif, 'font-weight': 600, color: map-get($ub-leadership, base)), - ls-post-subtitle: ('font-size': 15px, 'line-height': 20px, 'font-family': $ub-serif, 'font-weight': 500, color: #4c4c4c), - ls-post-txt: ('font-size': 15px, 'line-height': 20px, 'font-family': $ub-sanserif, 'font-weight': 500, color: #555), - // nav search - nav-search: ('font-size': 21px, 'line-height': 24px, 'font-family': $ub-sanserif), - // home page - st-label: ('font-size': 18px, 'line-height': 21px, 'font-family': $ub-sanserif), - // resources - resource-preview-title: ('font-size': 17px, 'line-height': 20px, 'font-family': $ub-serif, 'font-weight': 600, color: #4c4c4c), - resource-preview-body: ('font-size': 16px, 'line-height': 20px, 'font-family': $ub-sanserif, color: #4c4c4c), - // generic card - generic-card-title: ('font-size': 14px, 'line-height': 30px, 'font-family': $ub-serif, 'font-weight': 600), - generic-card-teaser: ('font-size': 22px, 'line-height': 24px, 'font-family': $ub-serif, 'font-weight': 500), - // help desk - helpdesk-hover-title: (font-weight: 500), - helpdesk-title: (color: #423872, font-family: $ub-serif, font-size: 24px, font-weight: 600, line-height: 29px), - // about people - staff-name: ('font-size': 22px, 'line-height': 28px, 'font-family': $ub-sanserif-condensed, 'font-weight': 500, color: $ub-primary), - staff-position: ('font-size': 18px, 'line-height': 22px, 'font-family': $ub-sanserif, 'font-weight': 300, 'font-style': italic, color: #666), - // Lessons - ld-activity-h2time: ('font-size': 22px, 'line-height': 1, 'font-family': $ub-sanserif, 'font-weight': bold, 'font-style': italic), - ld-activity-h3time: ('font-size': 16px, 'line-height': 1, 'font-family': $ub-sanserif, 'font-weight': bold, 'font-style': italic), - ld-activity-kicker: ('font-size': 14px, 'line-height': 22px, 'font-family': $ub-serif, 'font-weight': 600, letter-spacing: .3px), - ld-activity-teaser: ('font-size': 18px, 'line-height': 24px, 'font-family': $ub-sanserif, font-style: italic), - ld-base: (font-size: 1rem, font-family: $ub-sanserif), - ld-callout-header: ('font-size': 20px, 'line-height': 28px, 'font-family': $ub-sanserif-condensed, 'font-weight': 500, font-style: normal), - ld-math-foundational-divider: (color: $ub-white, font-family: $ub-serif, font-size: 34px, font-weight: 600, text-transform: uppercase), - ld-h5: ('font-size': 17px, 'line-height': 1, 'font-family': $ub-sanserif, 'font-weight': bold), - ld-header-breadcrumb: (font-size: 17px, line-height: 20px, 'font-family': $ub-serif, font-weight: 600, 'text-shadow': 0 1px 0 rgba(0,0,0,0.15), color: $ub-white), - ld-header-title: (font-size: 40px, line-height: 40px, 'font-family': $ub-serif, font-weight: 500, 'text-shadow': 0 1px 0 rgba(0,0,0,0.15), color: $ub-white), - ld-header-teaser: (font-size: 20px, line-height: 28px, 'font-family': $ub-sanserif, text-shadow: 0 1px 0 rgba(0,0,0,0.15), color: $ub-white), - ld-header-standards: (font-size: 13px, line-height: 16px, 'font-family': $ub-sanserif, color: $ub-white), - ld-optbreak: ('font-size': 20px, 'line-height': 28px, 'font-family': $ub-sanserif-condensed, 'font-weight': 500, font-style: normal), - ld-pd-caption: (color: #8e89a9, font-size: smaller !important, font-weight: 600, text-transform: uppercase), - ld-pd-cg: (font-family: $ub-serif), - ld-pd-cg-header: (font-family: inherit !important, font-weight: 600), - ld-pd-cg-title: (font-family: inherit !important, font-size: 28px !important, font-weight: 500, line-height: 1 !important), - ld-pd-title: (color: #453b74, font-family: $ub-sanserif-condensed !important, font-size: 28px !important, font-weight: 500, line-height: 1 !important), - ld-pd-title-collapsed: (color: #453b74, font-family: $ub-sanserif-condensed !important, font-size: 20px !important, font-weight: 500, line-height: 1 !important), - ld-pd-description-title: (color: $ub-white, font-style: initial !important, font-weight: bolder), - ld-resource-summary: (color: #7f8184, font-size: 24px, line-height: 30px), - ld-sidebar-break: ('font-size': 14px, 'line-height': 18px, 'font-family': $ub-sanserif, font-style: italic, color: #4c4c4c, opacity: .5), - ld-sidebar-break-foundational: (color: map-get($ub-ld-colorcodes, math-activity-foundational), font-size: 14px, line-height: 18px, font-family: $ub-sanserif, font-style: initial, font-weight: 600, opacity: 1, text-transform: uppercase), - ld-sidebar-header: ('font-size': 22px, 'line-height': 26px, 'font-family': $ub-serif, 'font-weight': 600, color: #4c4c4c), - ld-sidebar-icon: ('font-size': 12px, line-height: 22px, vertical-align: baseline), - ld-sidebar-l1: ('font-size': 18px, 'line-height': 22px, 'font-family': $ub-serif, 'font-weight': 500, color: #4c4c4c), - ld-sidebar-l1-mins: ('font-size': 14px, 'line-height': 18px, 'font-family': $ub-sanserif, font-style: italic, color: #4c4c4c), - ld-sidebar-l2: ('font-size': 14px, 'line-height': 17px, 'font-family': $ub-sanserif-condensed, 'font-weight': 500, color: #4c4c4c), - ld-sidebar-l2-mins: ('font-size': 12px, 'line-height': 14px, 'font-family': $ub-sanserif, font-style: italic, color: #4c4c4c), - ld-sidebar-toggle: ('font-size': 13px, 'line-height': 16px, 'font-family': $ub-sanserif, font-style: italic, color: #4c4c4c), - ld-sidebar-total: ('font-size': 18px, 'line-height': 18px, 'font-family': $ub-serif, 'font-weight': 500, color: #4c4c4c), - ld-sidebar-total-mins: ('font-size': 14px, 'line-height': 18px, 'font-family': $ub-sanserif, font-style: italic, 'font-weight': 600, color: #4c4c4c), - ld-smp: ('font-size': 16px, 'line-height': 1, 'font-family': $ub-serif, 'font-weight': 600, color: #fff), - ld-sticky-breadcrumb: ( - large: (font-size: 15px, line-height: 15px, 'font-family': $ub-serif, font-weight: 600, text-shadow: 0 1px 0 rgba(0,0,0,0.25), color: $ub-white), - small: (font-size: 13px, line-height: 16px, 'font-family': $ub-serif, font-weight: 600, color: $ub-white) - ), - ld-sticky-icon: ( - large: (font-size: 28px), - small: (font-size: 18px) - ), - ld-sticky-title: ( - large: (font-size: 31px, line-height: 40px, 'font-family': $ub-serif, font-weight: 500, text-shadow: 0 1px 0 rgba(0,0,0,0.15), color: $ub-white), - small: (font-size: 21px, line-height: 25px, 'font-family': $ub-serif, font-weight: 500, color: $ub-white) - ), - ld-summary: ('font-size': 18px, 'line-height': 24px, 'font-family': $ub-sanserif), - ld-tooltip: ('font-size': 14px !important, line-height: 18px !important, font-family: $ub-sanserif !important, color: #4c4c4c !important), - ld-white-placeholder-title: ('font-size': 1.2rem !important, 'font-weight': 600), - ld-white-placeholder-colored-title: ('font-size': initial !important, 'font-weight': bold, color: initial !important), - m-subtitle: (font-size: 11px, line-height: 13px, font-family: $ub-sanserif, font-weight: bold, color: #939597), - m-tab-title: (font-size: 24px, line-height: 28px, font-family: $ub-sanserif-condensed, font-weight: 500, color: #4c4c4c), - m-title: (font-size: 18px, line-height: 20px, font-family: $ub-sanserif-condensed, font-weight: 500, color: #4c4c4c), -); - -@each $element, $typo in $ub-element-typography { - %txt-#{$element}, .u-txt--#{$element} { - @include txt-element($element); - } -} - -body { - @include txt-element(base); -} diff --git a/app/assets/stylesheets/lcms/engine/ckeditor.scss b/app/assets/stylesheets/lcms/engine/ckeditor.scss new file mode 100644 index 00000000..e502a700 --- /dev/null +++ b/app/assets/stylesheets/lcms/engine/ckeditor.scss @@ -0,0 +1,5 @@ +/* stylelint-disable selector-class-pattern */ +.cke_notifications_area { + display: none; +} +/* stylelint-enable selector-class-pattern */ diff --git a/app/assets/stylesheets/lcms/engine/components/_assoc-picker.scss b/app/assets/stylesheets/lcms/engine/components/_assoc-picker.scss deleted file mode 100644 index 2dfb6d7b..00000000 --- a/app/assets/stylesheets/lcms/engine/components/_assoc-picker.scss +++ /dev/null @@ -1,62 +0,0 @@ -.o-assocpicker { - tr:hover { - cursor: pointer; - } -} - -.o-assocpicker-selections { - margin-bottom: 15px; -} - -.o-assocpicker-selection { - background: #f2f2f2; - border: 0 solid #d0d0d0; - color: #303030; - cursor: pointer; - display: inline-block; - display: inline-block; - margin: 0 3px 3px 0; - padding: 2px 6px; - vertical-align: baseline; - zoom: 1; -} - -.o-assocpicker-close { - border-left: 1px solid #d0d0d0; - border-radius: 0 2px 2px 0; - border-radius: 0 2px 2px 0; - border-radius: 0 2px 2px 0; - bottom: 0; - box-sizing: border-box; - box-sizing: border-box; - box-sizing: border-box; - color: inherit; - display: inline-block; - font-size: 12px; - font-weight: bold; - position: relative; - right: -6px; - text-align: center; - text-decoration: none; - top: 0; - vertical-align: middle; - width: 17px; - z-index: 1; -} - -.o-assocpicker-create { - color: #008000; - font-weight: bold; - margin-left: 5px; -} - -.c-admcur-items .active { - background-color: #a0a0a0; - border-bottom: solid 0.2px #fff; - color: #fff; -} - -.c-assocpicker-submit { - margin: 0 10% 2em; - width: 80%; -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_activity.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_activity.scss deleted file mode 100644 index 096c6436..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_activity.scss +++ /dev/null @@ -1,85 +0,0 @@ -.o-ld-activity { - @include add-column-padding($x: 1); - @include clearfix; - @include flex; - - background-color: map-get($ub-ld-colorcodes, math-activity-bg); - flex-flow: row wrap; - - @include e(guidance) { - @include add-column-padding($x: 1); - @include add-gutter(margin, bottom, 1); - @include add-top-bottom-padding($y: 0.5); - background-color: map-get($ub-ld-colorcodes, math-activity-guidance-bg); - - @include m(title) { - @include add-gutter(margin, bottom, 0.5); - display: block; - - .o-ld-activity-foundational-skills & { - color: map-get($ub-ld-colorcodes, math-activity-foundational); - } - } - - .o-ld-activity-foundational-skills & { - background-color: map-get($ub-ld-colorcodes, math-activity-foundational-guidance-bg); - } - } - - @include e(optional) { - background-color: map-get($ub-ld-colorcodes, activity-optional); - margin: 2rem 0; - padding: 2rem; - - // stylelint-disable declaration-no-important - p { - margin: 0 !important; - } - // stylelint-enable declaration-no-important - } - - @include e(source) { - @include add-gutter(padding, bottom, 1); - display: block; - - &.o-ld-activity-foundational-skills { - background-color: map-get($ub-ld-colorcodes, math-activity-foundational-bg); - } - - // TODO: comment for now, bc it's adding big gaps btw elements - // p { - // &:empty { line-height: 0; } - // - // &::after { - // content: ''; - // display: inline-block; - // width: 0; - // } - // } - } - - @include e(title) { - .o-ld-activity-foundational-skills & { - color: map-get($ub-ld-colorcodes, math-activity-foundational); - } - } - - // stylelint-disable max-nesting-depth - &.c-ld-toc { - @include add-gutter(padding, top, 1); - border-top: 4px solid map-get($ub-colorcodes, math-base); - - .o-ld-activity-foundational-skills & { - border-top: 4px solid map-get($ub-ld-colorcodes, math-activity-foundational); - } - - > div { - width: 100%; - } - } - - .o-ld-activity-foundational-skills & { - background-color: map-get($ub-ld-colorcodes, math-activity-foundational-bg); - } - // stylelint-enable max-nesting-depth -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_callout.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_callout.scss deleted file mode 100644 index 08c34f62..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_callout.scss +++ /dev/null @@ -1,53 +0,0 @@ -.o-ld-callout { - @include clearfix; - border-left: 0.125rem solid; - float: right; - font: italic 1.25rem / 1.3 $ub-serif; - margin: 1.5rem 0 1.5rem 1.5rem; - max-width: 300px; - padding-left: 1.125rem; - width: 50%; - - @include e(content) { - font-size: 17px; - - a { - overflow-wrap: break-word; - word-break: break-all; - } - } - - @include e(header) { - @include txt-element(ld-callout-header); - } - - @include m(math) { - border-color: map-get($ub-colorcodes, math-base); - - &, - *, - ul, - li, - ol { - &, - * { - color: map-get($ub-colorcodes, math-base); - } - } - } - - @include m(ela) { - border-color: map-get($ub-colorcodes, ela-base); - - &, - *, - ul, - li, - ol { - &, - * { - color: map-get($ub-colorcodes, ela-base); - } - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_columns.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_columns.scss deleted file mode 100644 index 5b55f244..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_columns.scss +++ /dev/null @@ -1,16 +0,0 @@ -.o-ld-columns { - margin-left: $list-side-margin * 2; - margin-right: $list-side-margin * 2; -} - -.o-ld-columns-table { - thead, - tbody { - background-color: initial !important; // stylelint-disable-line declaration-no-important - border: zero; - } - - tr { - background-color: initial !important; // stylelint-disable-line declaration-no-important - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_curriculum-map.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_curriculum-map.scss deleted file mode 100644 index 51e9b29e..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_curriculum-map.scss +++ /dev/null @@ -1,54 +0,0 @@ -.o-ld-map-bg { - @include m(base) { - background-color: $ub-white; - opacity: 0.3; - } - - @include m(active) { - background-color: $ub-white; - opacity: 1; - } - @include m(assessment) { - background-color: transparent; - border: 2px solid $ub-white; - } -} - -.o-ld-map-module { - // stylelint-disable declaration-no-important - @include m(ela) { - &, - &:hover, - &:focus { - color: map-get($ub-colorcodes, ela-base) !important; - } - } - @include m(math) { - &, - &:hover, - &:focus { - color: map-get($ub-colorcodes, math-base) !important; - } - } - // stylelint-enable declaration-no-important -} - -.o-ld-map-txt-link { - @include m(base) { - &, - &:hover, - &:focus { - color: $ub-white; - opacity: 0.5; - } - } - - @include m(active) { - &, - &:hover, - &:focus { - color: $ub-white; - opacity: 1; - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_def.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_def.scss deleted file mode 100644 index 14fac24f..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_def.scss +++ /dev/null @@ -1,33 +0,0 @@ -.c-ld-keyword { - cursor: help; - font-weight: bold !important; // stylelint-disable-line declaration-no-important - white-space: nowrap; - - @include m(ela) { - color: map-get($ub-colorcodes, ela-base); - } - - @include m(math) { - color: map-get($ub-colorcodes, math-base); - } -} - -.o-ld-dropdown { - @include txt-element-strict(ld-tooltip); - background: map-get($ub-ld-colorcodes, tooltip-bg); - border: 0; - box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.25); - padding: 1rem; - - * { - word-wrap: break-word; - } - - &::before { - @include css-triangle($tooltip-pip-width, map-get($ub-ld-colorcodes, tooltip-bg), up); - bottom: 100%; - left: $ld-tooltip-offset; - position: absolute; - transform: translateX(-50%); - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_expand.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_expand.scss deleted file mode 100644 index 5251031c..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_expand.scss +++ /dev/null @@ -1,42 +0,0 @@ -.o-ld-expand { - clear: both; - - @include e(content) { - padding: 10px; - - @include m(hidden) { - display: none; - padding: 0 10px; - } - } - - @include e(toggler) { - @include txt-element(button); - cursor: pointer; - display: block; - padding: 0.75rem 0; - text-align: center; - - @include m(hide) { - display: none; - } - } - - @include e(ela-toggler) { - background-color: map-get($ub-ld-colorcodes, ela-toggler-bg); - color: map-get($ub-colorcodes, ela-base); - } - - @include e(math-toggler) { - background-color: map-get($ub-ld-colorcodes, math-toggler-bg); - color: map-get($ub-colorcodes, math-base); - } - - &.o-ld-expand-math { - background-color: map-get($ub-ld-colorcodes, math-expand-bg); - } - - &.o-ld-expand-ela { - background-color: map-get($ub-ld-colorcodes, ela-expand-bg); - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_foundational-divider.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_foundational-divider.scss deleted file mode 100644 index 27f59ff4..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_foundational-divider.scss +++ /dev/null @@ -1,20 +0,0 @@ -.o-ld-foundational-divider { - @include add-gutter(margin, bottom); - @include add-gutter(margin, top); - - @include e(block) { - @include add-gutter(margin, bottom); - @include add-gutter(margin, top); - @include txt-element(ld-math-foundational-divider); - - background-color: map-get($ub-ld-colorcodes, math-activity-foundational); - padding: 1rem; - } - - @include e(delimeter) { - border-bottom: 1px solid $ub-hr; - border-top: 1px solid $ub-hr; - flex: 1 1 auto; - height: 4px; - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_gls.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_gls.scss deleted file mode 100644 index 7c1c8b1a..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_gls.scss +++ /dev/null @@ -1,3 +0,0 @@ -.o-ld-gls { - background-color: map-get($ub-ld-colorcodes, gls-bg); -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_group.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_group.scss deleted file mode 100644 index 384f8065..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_group.scss +++ /dev/null @@ -1,5 +0,0 @@ -.o-ld-group { - li > p { - margin-bottom: 0; - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_hr.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_hr.scss deleted file mode 100644 index 6f1f94f6..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_hr.scss +++ /dev/null @@ -1,11 +0,0 @@ -.o-ld-hr { - @include m(l1) { - @include add-top-bottom-margin(2); - border-top: 3px double $ub-hr; - } - - @include m(l2) { - @include add-top-bottom-margin(1); - border-top: 1px solid $ub-hr; - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_icons.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_icons.scss deleted file mode 100644 index 36065596..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_icons.scss +++ /dev/null @@ -1,37 +0,0 @@ -@mixin icon-dim($height) { - height: $height; - width: 18 / 18 * $height; -} - -.o-ld-icon { - background-size: cover; - display: inline-block; - - @include m(small) { - @include icon-dim(12px); - margin-top: -0.2rem; - vertical-align: middle; - } - - @include m(base) { - @include icon-dim(18px); - vertical-align: baseline; - } - - @include e(wrapper) { - $w-offset: $ld-tooltip-offset - 1.125rem / 2; - display: inline; - margin-left: -$w-offset; - - .o-ld-icon { - margin-left: $w-offset; - } - } -} - -.o-m-icon { - background-size: contain; - display: inline-block; - height: 20px; - width: 20px; -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_image-student-worksheet.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_image-student-worksheet.scss deleted file mode 100644 index 8353b035..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_image-student-worksheet.scss +++ /dev/null @@ -1,13 +0,0 @@ -.o-ld-image-student-worksheet { - margin-left: auto; - margin-right: auto; - overflow: hidden; - position: relative; - - @include e(img) { - margin: auto; - position: absolute; - top: 50%; - transform: translate(0, -50%); - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_image-wrap.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_image-wrap.scss deleted file mode 100644 index b4c4a99b..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_image-wrap.scss +++ /dev/null @@ -1,14 +0,0 @@ -.o-ld-image-wrap { - @include m(math) { - @include add-top-bottom-padding(0.5); - @include clearfix; - background: $ub-white; - display: inline-block; - width: 100%; - - > img { - height: auto !important; // stylelint-disable-line declaration-no-important - max-width: 70%; - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_image.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_image.scss deleted file mode 100644 index bcba47fe..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_image.scss +++ /dev/null @@ -1,38 +0,0 @@ -.o-ld-image { - @include m(clearfix) { - @include clearfix; - } - - @include e(container) { - background-color: map-get($ub-ld-colorcodes, default-image-bg); - - @include m(math) { - background-color: map-get($ub-ld-colorcodes, math-image-bg); - } - - @include m(ela) { - background-color: map-get($ub-ld-colorcodes, ela-image-bg); - } - - figure { - margin: 0; - overflow: hidden; - - img { - margin-top: -5px; - width: 100%; - - @include breakpoint(ipad) { - width: 30%; - } - } - - figcaption { - display: inline-block; - font-weight: bold; - padding: 1.5em; - width: 69%; - } - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_indent.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_indent.scss deleted file mode 100644 index 20b4bfc1..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_indent.scss +++ /dev/null @@ -1,11 +0,0 @@ -.u-ld-indent { - @for $i from 2 through 3 { - @include m('l#{$i}') { - margin-left: $list-side-margin * $i; - } - } -} - -.u-ld-indented:not(li) p { - text-indent: $list-side-margin * 2; -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_inset.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_inset.scss deleted file mode 100644 index 6289343e..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_inset.scss +++ /dev/null @@ -1,11 +0,0 @@ -.o-ld-inset { - margin-left: $list-side-margin * 2; - - .text-bold { - font-weight: 700; - } - - .text-italic { - font-style: italic; - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_materials.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_materials.scss deleted file mode 100644 index 8e7c64d7..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_materials.scss +++ /dev/null @@ -1,22 +0,0 @@ -.o-ld-materials { - @include add-gutter(margin, top, 0.5); - - @include e(content) { - padding: 10px; - - @include m(hidden) { - display: none; - padding: 0 10px; - } - } - - @include e(toggler) { - @include txt-element(base); - color: map-get($ub-colorcodes, math-base); - cursor: pointer; - - @include m(hide) { - display: none; - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_multiple-choice.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_multiple-choice.scss deleted file mode 100644 index 1be35f2e..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_multiple-choice.scss +++ /dev/null @@ -1,11 +0,0 @@ -.c-ld { - @include e(body) { - .o-ld-multiple-choice { - margin-left: $list-side-margin * 2; - - p { - line-height: 2; - } - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_optbreak.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_optbreak.scss deleted file mode 100644 index 3fdc81d6..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_optbreak.scss +++ /dev/null @@ -1,20 +0,0 @@ -.o-ld-optbreak { - @include flex; - @include flex-align($x: spaced, $y: middle); - color: $ub-hr; - margin-bottom: $ub-base-padding; - margin-top: $ub-base-padding; - - @include e(delimeter) { - border-bottom: 1px solid $ub-hr; - border-top: 1px solid $ub-hr; - flex: 1 1 auto; - height: 4px; - } - - @include e(text) { - @include txt-element(ld-optbreak); - padding-left: $ub-base-padding; - padding-right: $ub-base-padding; - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_override.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_override.scss deleted file mode 100644 index e12f3fad..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_override.scss +++ /dev/null @@ -1,80 +0,0 @@ -@mixin ld-custom-styles { - a:not(.o-ub-btn):not(.u-preserve-style) { - border-bottom: 2px solid $ub-secondary; - color: $ub-heading-txt; - font-weight: normal; - } - - ol, - ul { - margin-bottom: 0; - } - - ul li { - text-indent: 0; - } - - ol { - margin-left: 0.25rem; - padding-left: 1em; - - > li::before { - margin-left: -1em; - } - } - - // stylelint-disable selector-no-qualifying-type - ol.c-ld-ol { - margin-left: 1.3rem; - } - // stylelint-enable selector-no-qualifying-type - - thead, - tbody, - tfoot { - border: 0; - } - - .indented { - text-indent: $list-side-margin * 2; - } - - // TODO: Quick fix, cg&lesson styles separated at pr 622 - // After merging with 622 should be checked, maybe we don't need this - sup, - sub { - font-size: 75% !important; // stylelint-disable-line declaration-no-important - } -} - -.c-ld { - @include m(ela-2) { - // stylelint-disable selector-no-qualifying-type - h2.o-ld-title { - margin-top: $ub-large-margin * 2 !important; // stylelint-disable-line declaration-no-important - } - // stylelint-enable selector-no-qualifying-type - } -} - -// stylelint-disable max-nesting-depth -.c-ld-table { - @include e(wrap) { - clear: both; - // TODO: comment for now to avoid scrolling, need to rethink approach - // overflow-x: auto; - } - - tbody tr { - background-color: $ub-white; - } - - td, - th { - border: 1px solid $ub-hr; - - p { - margin-bottom: initial !important; // stylelint-disable-line declaration-no-important - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_pd.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_pd.scss deleted file mode 100644 index fe9cd7aa..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_pd.scss +++ /dev/null @@ -1,188 +0,0 @@ -.o-ld-pd { - @include add-gutter(margin, bottom); - background-color: map-get($ub-ld-colorcodes, pd-bg); - color: $ub-white; - - @include e(audio) { - padding-top: 1rem; - } - - @include e(audio-placeholder) { - display: none; - } - - @include e(container) { - padding: 0 1rem 1rem; - } - - @include e(description) { - color: $ub-txt; - margin-top: 20px; - - @include m(hidden) { - display: none; - } - - h4 { - @include txt-element(ld-pd-description-title); - color: $ub-txt; - padding-bottom: 0; - } - } - - @include e(expander) { - @include flex; - - align-items: baseline; - justify-content: space-between; - } - - @include e(minimizer) { - @include flex; - - align-items: center; - background-color: map-get($ub-ld-colorcodes, pd-bg-dark); - cursor: pointer; - height: 41px; - justify-content: center; - text-align: center; - - p { - margin: 0; - } - - .o-ld-pd--collapsed & { - display: none; - } - } - - @include e(title) { - @include txt-element(ld-pd-title); - - padding-top: 0.5rem; - - .o-ld-pd--collapsed & { - @include txt-element(ld-pd-title-collapsed); - } - } - - @include e(video) { - padding-top: 1rem; - } - - @include e(video-placeholder) { - display: none; - } - - @include m(collapsed) { - @include add-gutter(margin, left); - - float: right; - width: 35%; - } - - @include m(expanded) { - @include add-gutter(margin, top); - } -} - -.o-ld-pd-caption { - @include txt-element(ld-pd-caption); - - margin: 0; - padding-left: 1rem; -} - -.o-ld-pd-toggler { - cursor: pointer; - height: 31px; - width: 31px; - - .o-ld-pd--collapsed & { - background-image: asset-url('pd-expand.svg'); - } - - .o-ld-pd--expanded & { - background-image: asset-url('pd-collapse.svg'); - } -} - -.o-ld-pd-pdf { - height: 600px; - position: relative; - - @include e(object) { - height: 100%; - max-width: 100%; - position: absolute !important; // stylelint-disable-line declaration-no-important - width: 100%; - } - - @include e(overlay) { - height: 100%; - position: absolute; - width: 100%; - - .o-ld-pd--expanded & { - display: none; - } - } - - @include e(overlay-bg) { - background-color: $ub-primary; - opacity: 0.6; - } - - @include e(overlay-icon) { - background: asset-url('pdf-icon.svg') no-repeat 50%; - } - - .o-ld-pd--collapsed & { - height: 166px; - } -} - -.o-ld-cg { - @include add-gutter(margin, bottom, 0.5); - @include add-gutter(margin, top, 0.5); - @include flex; - @include txt-element(ld-pd-cg); - - flex-direction: row; - - @include e(body) { - padding: $ub-base-padding $ub-base-padding 0; - - .o-ld-pd--collapsed & { - display: none; - } - } - - @include e(header) { - @include txt-element(ld-pd-cg-header); - } - - @include e(title) { - @include txt-element(ld-pd-cg-title); - - margin-bottom: 1rem !important; // stylelint-disable-line declaration-no-important - margin-top: 0.5rem !important; // stylelint-disable-line declaration-no-important - } - - @include e(thumbnail) { - background-image: asset-url('pd-cg-placeholder.jpg'); - background-size: cover; - max-width: 100%; - overflow: hidden; - width: 100%; - - .o-ld-pd--collapsed & { - background-position-y: -50px; - height: 150px; - } - - .o-ld-pd--expanded & { - max-width: 25%; - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_position.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_position.scss deleted file mode 100644 index d8abce19..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_position.scss +++ /dev/null @@ -1,15 +0,0 @@ -.o-ld-position { - @include e(center) { - text-align: center; - } - - @include e(left) { - float: left; - margin-right: 20px; - } - - @include e(right) { - float: right; - margin-left: 20px; - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_pv.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_pv.scss deleted file mode 100644 index 12ce6787..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_pv.scss +++ /dev/null @@ -1,16 +0,0 @@ -.o-ld-pv-content { - thead, - tbody { - background-color: initial !important; // stylelint-disable-line declaration-no-important - border: zero; - } - - tr { - background-color: initial !important; // stylelint-disable-line declaration-no-important - } - - td { - @include add-top-bottom-padding(); - border: 1px solid; - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_section.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_section.scss deleted file mode 100644 index 14110d78..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_section.scss +++ /dev/null @@ -1,38 +0,0 @@ -.o-ld-section { - @include clearfix; - @include add-column-padding($x: 1); - @include add-top-bottom-padding($y: 1); - - @include e(optional) { - background-color: map-get($ub-ld-colorcodes, section-optional); - margin: 2rem 0; - padding: 2rem; - } - - @include e(quote) { - @include clearfix; - border-color: map-get($ub-colorcodes, ela-base); - border-left: 0.125rem solid; - float: right; - font: italic 1.25rem / 1.3 $ub-serif; - margin: 1.5rem 0 1rem 3.25rem; - padding-left: 1.125rem; - width: 50%; - - &, - *, - ul, - li, - ol { - &, - * { - color: map-get($ub-colorcodes, ela-base); - } - } - } - - @include m(bg-color) { - background-color: map-get($ub-ld-colorcodes, ela6-section-bg); - border-top: 4px solid map-get($ub-colorcodes, ela-base); - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_selection-control.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_selection-control.scss deleted file mode 100644 index 52026cbe..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_selection-control.scss +++ /dev/null @@ -1,180 +0,0 @@ -// stylelint-disable max-nesting-depth -$shadow-color: rgba(0, 0, 0, 0.3); - -.o-ld-selection { - @include flex; - flex: 1; - padding: 1em 2em; - - @include e(label) { - cursor: pointer; - font-weight: bold; - margin-left: 0.5em; - } - - @include e(switch) { - border-radius: 1em; - cursor: pointer; - height: 1.1em; - position: relative; - top: 0.1em; - width: 2.5em; - - &::before, - &::after { - content: ''; - display: block; - left: 0; - position: absolute; - } - - &::before { - border-radius: 1em; - bottom: 0; - right: 0; - top: 0; - } - - &::after { - background-color: currentColor; - border-radius: 100%; - bottom: -0.15em; - box-shadow: 0 2px 5px $shadow-color; - left: -0.15em; - margin-left: 1.3em; - top: -0.15em; - transition: margin 0.15s; - width: 1.3em; - } - } -} - -.deselected { - .o-ld-selection { - @include e(switch) { - &::after { - margin-left: 0.15em; - } - } - } -} - -// ELA theme -.doc-subject-ela { - .o-ld-selection { - background-color: map-get($ub-selection, ela-enabled-bg); - color: map-get($ub-selection, ela-enabled-fg); - - @include e(switch) { - &::before { - background-color: map-get($ub-selection, ela-enabled-switch-bg); - } - } - } - - .deselected { - .o-ld-section { - background-color: map-get($ub-selection, disabled-parent-bg); - } - - .o-ld-selection { - color: map-get($ub-selection, ela-disabled-fg); - - @include e(switch) { - &::before { - background-color: map-get($ub-selection, ela-disabled-switch-bg); - } - } - } - - // stylelint-disable selector-max-compound-selectors - .o-material-wrapper, - .c-ld-toc > *:not(.u-txt--title-type):not(.o-ld-title) { - display: none; - } - // stylelint-enable selector-max-compound-selectors - } -} - -// MATH theme -.doc-subject-math { - .o-ld-selection { - background-color: map-get($ub-selection, math-enabled-bg); - color: map-get($ub-selection, math-enabled-fg); - - @include e(switch) { - &::before { - background-color: map-get($ub-selection, math-enabled-switch-bg); - } - } - } - - .o-ld-activity-foundational-skills { - .o-ld-selection { - background-color: map-get($ub-ld-colorcodes, math-activity-foundational); - - @include e(switch) { - &::before { - background-color: map-get($ub-ld-colorcodes, math-activity-foundational-switch-bg); - } - } - } - } - - .deselected { - .o-ld-activity { - background-color: map-get($ub-selection, disabled-parent-bg); - } - - .o-ld-selection { - color: map-get($ub-selection, math-disabled-fg); - - @include e(switch) { - &::before { - background-color: map-get($ub-selection, math-disabled-switch-bg); - } - } - } - - &.o-ld-activity-foundational-skills { - .o-ld-selection { - color: map-get($ub-ld-colorcodes, math-activity-foundational-disabled-fg); - - @include e(switch) { - &::before { - background-color: map-get($ub-ld-colorcodes, math-activity-foundational-disabled-bg); - } - } - } - } - - // stylelint-disable selector-max-compound-selectors - .o-ld-activity__source, - .o-material-wrapper, - .o-ld-materials, - .c-ld-toc > div > *:not(.u-txt--title-type):not(.o-ld-title):not(.u-txt--ld-activity-teaser) { - display: none; - } - // stylelint-enable selector-max-compound-selectors - } -} - -.o-ld-selection-modal { - @include add-top-bottom-padding($y: 1); - @include add-column-padding; - - @include e(content) { - max-height: computed-height(map-get($ub-element-typography, base), 4); - overflow: scroll; - - @include m(expanded) { - max-height: initial; - } - } - - // stylelint-disable declaration-no-important - @include e(title) { - @include txt-element(lesson-title); - color: map-get($ub-selection, modal-title-fg) !important; - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_sidebar.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_sidebar.scss deleted file mode 100644 index 17ae8fa5..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_sidebar.scss +++ /dev/null @@ -1,205 +0,0 @@ -@mixin ld-content-dropdown { - // rewrite some css rules to meet foundation accordition - .is-accordion-submenu-parent > a::after { - display: none; - } - - // stylelint-disable selector-no-qualifying-type - li[aria-expanded='true'] > a > .o-ld-sidebar-item__icon { - transform: rotate(90deg); - } - // stylelint-enable selector-no-qualifying-type -} - -.o-ld-sidebar { - @include ld-content-dropdown; - position: relative; - - @include e(wrap) { - position: relative; - width: 100%; - - &.is-stuck { - @include add-top-bottom-padding; - } - - &.is-stuck:not(.stick-wo-margin) { - margin-top: $ub-nav-height !important; // stylelint-disable-line declaration-no-important - } - } - - @include e(item) { - opacity: 0.7; - padding: $ub-base-padding / 2 0; - - @include m(active) { - opacity: 1; - } - } - - @include e(menu) { - @include menu-base; - @include menu-direction(vertical); - - width: auto; - - @include m(nested) { - padding-left: 1.2rem; - } - - // stylelint-disable selector-no-qualifying-type - > li.o-ld-sidebar__item:last-child { - padding-bottom: 0; - } - // stylelint-enable selector-no-qualifying-type - } - - @include e(toc-customizer) { - overflow-x: hidden; - padding-top: 60px; - } -} - -.o-ld-sidebar-header { - @include flex; - align-items: baseline; - flex-flow: row nowrap; - justify-content: space-between; - padding: 0; - - @include e(title) { - @include txt-element(ld-sidebar-header); - flex: 0 1 auto; - } - - @include e(toggle) { - @include txt-element(ld-sidebar-toggle); - cursor: pointer; - flex: 0 0 auto; - - @include m(hide) { - display: none; - } - } - - @include e(sep) { - @include clearfix; - border-bottom: 2px solid map-get($ub-yellow, base); - border-radius: 4px; - border-top: 2px solid map-get($ub-yellow, base); - margin-bottom: $ub-base-margin; - margin-top: $ub-base-margin / 2; - width: 40px; - } -} - -.o-ld-sidebar-item { - // rewrite foundation accordition styles - outline: none !important; // stylelint-disable-line declaration-no-important - padding: 0 !important; // stylelint-disable-line declaration-no-important - - @include e(content) { - @include flex; - align-items: baseline; - flex-flow: row nowrap; - justify-content: space-between; - padding: 0; - - @include m(disabled) { - text-decoration: line-through; - } - } - - @include e(header) { - flex: 0 1 auto; - - @include m(l1) { - @include txt-element(ld-sidebar-l1); - } - - @include m(l2) { - @include txt-element(ld-sidebar-l2); - - .doc-subject-ela .is-submenu-item.o-ld-sidebar__item--active & { - color: map-get($ub-colorcodes, ela-base); - } - - .doc-subject-math .is-submenu-item.o-ld-sidebar__item--active & { - color: map-get($ub-colorcodes, math-base); - } - - // stylelint-disable declaration-no-important - .o-ld-sidebar-foundational-skills .o-ld-sidebar__item--active & { - color: map-get($ub-ld-colorcodes, math-activity-foundational) !important; - } - // stylelint-enable declaration-no-important - } - - @include m(summary) { - @include txt-element(ld-sidebar-total); - } - } - - @include e(hr) { - border-top: 2px solid $ub-hr; - margin-bottom: $ub-base-margin; - margin-top: $ub-base-margin; - } - - @include e(icon) { - cursor: pointer; - float: left; - margin-right: 0.4rem; - - @include m(blank) { - cursor: none; - height: 1px; - width: map-get(map-get($ub-element-typography, ld-sidebar-icon), font-size); - } - - i { - @include txt-element(ld-sidebar-icon); - } - } - - @include e(time) { - flex: 0 0 auto; - - @include m(l1) { - @include txt-element(ld-sidebar-l1-mins); - } - - @include m(l2) { - @include txt-element(ld-sidebar-l2-mins); - } - - @include m(summary) { - @include txt-element(ld-sidebar-total-mins); - } - } -} - -.o-ld-sidebar-break { - @include flex; - @include flex-align($x: spaced, $y: middle); - color: $ub-hr; - - @include e(delimeter) { - border: 1px dotted $ub-hr; - flex: 1 1 auto; - - .doc-subject-math & { - border-color: map-get($ub-ld-colorcodes, math-activity-foundational); - } - } - - @include e(text) { - @include txt-element(ld-sidebar-break); - padding-left: $ub-small-padding; - padding-right: $ub-small-padding; - - .doc-subject-math & { - @include txt-element(ld-sidebar-break-foundational); - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_smp.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_smp.scss deleted file mode 100644 index 6a3f096c..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_smp.scss +++ /dev/null @@ -1,41 +0,0 @@ -.o-ld-smp { - display: flex; - justify-content: space-between; - - @include e(content) { - flex: 1 1 0; - margin-right: 120px; - } - - @include e(lines) { - @include flex; - @include flex-align($x: center, $y: middle); - flex: 0 0 100px; - flex-direction: column; - margin-bottom: 10px; - margin-top: 10px; - position: relative; - } -} - -.o-ld-smp-figure { - background-color: map-get($ub-colorcodes, math-base); - padding: 5px; - z-index: $reveal-zindex; - - @include e(name) { - @include txt-element(ld-smp); - margin: 0; - } -} - -.o-ld-smp-bracket { - border: 3px map-get($ub-colorcodes, math-base) solid; - border-left: 0; - border-radius: 3px; - height: 100%; - left: 0; - position: absolute; - top: 0; - width: 50px; -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_title.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_title.scss deleted file mode 100644 index 6abbb9d2..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_title.scss +++ /dev/null @@ -1,31 +0,0 @@ -.o-ld-title { - @include flex; - @include flex-align($x: justify, $y: bottom); - flex: 0 0 100%; - width: 100%; - - @include e(title) { - @include m(h2) { - @include txt-heading(h1); - } - } - - @include e(time) { - font-style: italic; - - @include m(h2) { - @include txt-element(ld-activity-h2time); - } - @include m(h3) { - @include txt-element(ld-activity-h3time); - } - } - - // stylelint-disable selector-max-id - &#foundational-skills:not(.c-foundational-section) { - border-top: 25px solid #00a699; - margin-top: 40px !important; // stylelint-disable-line declaration-no-important - padding-top: 30px; - } - // stylelint-enable selector-max-id -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_unit-map.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_unit-map.scss deleted file mode 100644 index 0025ce4b..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_unit-map.scss +++ /dev/null @@ -1,31 +0,0 @@ -.o-ld-unit-map { - $ld-lesson: 10px; - $ld-padding: 2px; - align-content: flex-start; - display: flex; - flex-flow: row wrap; - justify-content: flex-start; - margin-right: 1rem; - width: ($ld-lesson + $ld-padding) * 5; - - @include e(lesson) { - background-color: $ub-white; - height: $ld-lesson; - margin: $ld-padding; - opacity: 0.3; - width: $ld-lesson; - - @include m(active) { - opacity: 1; - } - } - - @for $i from 1 through 5 { - @include m('padding#{$i}') { - @include breakpoint(large) { - margin-left: 1rem; - padding-left: ($ld-lesson + $ld-padding) * $i; - } - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/lesson/_white-placeholder.scss b/app/assets/stylesheets/lcms/engine/components/lesson/_white-placeholder.scss deleted file mode 100644 index ab0dc79b..00000000 --- a/app/assets/stylesheets/lcms/engine/components/lesson/_white-placeholder.scss +++ /dev/null @@ -1,27 +0,0 @@ -.o-ld-white-placeholder { - @include add-gutter(margin, left); - @include add-gutter(margin, right); - - background-color: $ub-white; - padding: 15px 30px; - - @include e(title) { - @include txt-element(ld-white-placeholder-title); - - @include m(ela) { - color: map-get($ub-colorcodes, ela-base); - } - - @include m(math) { - color: map-get($ub-colorcodes, math-base); - } - - @include m(colored) { - @include txt-element(ld-white-placeholder-colored-title); - } - } - - @include m(colored) { - border-left: 4px solid map-get($ub-colorcodes, ela-base); - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/material/_list.scss b/app/assets/stylesheets/lcms/engine/components/material/_list.scss deleted file mode 100644 index c1633a6d..00000000 --- a/app/assets/stylesheets/lcms/engine/components/material/_list.scss +++ /dev/null @@ -1,9 +0,0 @@ -.o-materials-list { - @include flex; - - justify-content: space-between; - - div:first-child { - @include flex; - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/material/_material.scss b/app/assets/stylesheets/lcms/engine/components/material/_material.scss deleted file mode 100644 index b76a89c7..00000000 --- a/app/assets/stylesheets/lcms/engine/components/material/_material.scss +++ /dev/null @@ -1,31 +0,0 @@ -.o-material-wrapper { - @include m(ela) { - // keep - } - @include m(math) { - background-color: map-get($ub-ld-colorcodes, math-activity-bg); - } - - @include m(bg-color) { - background-color: map-get($ub-ld-colorcodes, ela6-section-bg); - } - - @include m(within-group) { - @include add-gutter(margin, top, 1.5); - } -} - -.o-ld-activity-foundational-skills { - .o-material-wrapper { - @include m(math) { - background-color: map-get($ub-ld-colorcodes, math-activity-foundational-bg); - } - } -} - -.o-m-title { - @include m(underlined) { - border-bottom: 1px solid $ub-hr; - margin-bottom: $ub-base-padding; - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/material/_overlay.scss b/app/assets/stylesheets/lcms/engine/components/material/_overlay.scss deleted file mode 100644 index 87b14b51..00000000 --- a/app/assets/stylesheets/lcms/engine/components/material/_overlay.scss +++ /dev/null @@ -1,54 +0,0 @@ -.o-m-overlay { - @include flex; - background-color: map-get($ub-ld-colorcodes, material-overlay); - bottom: 0; - color: $ub-white; - display: none; - flex-direction: column; - left: 0; - opacity: 0.9; - position: absolute; - right: 0; - top: 0; - - @include e(actions) { - @include flex; - @include flex-align($x: center, $y: middle); - background-color: darken(map-get($ub-ld-colorcodes, material-overlay), 5%); - height: 40px; - width: 100%; - - > ul { - margin: 0 !important; // stylelint-disable-line declaration-no-important - } - - // stylelint-disable selector-max-compound-selectors - > ul > li > a { - padding: 0 0.5rem; - } - // stylelint-enable selector-max-compound-selectors - } - - @include e(preview) { - @include flex; - @include flex-align($x: center, $y: middle); - flex: 1 1 0; - width: 100%; - } - - @include e(wrap) { - position: relative; - - &:hover, - &:focus { - .o-m-overlay { - @include flex; - z-index: 9999; - } - } - - :not(.o-material-wrapper--bg-color) & { - border: 1px solid map-get($ub-ld-colorcodes, material-thumbnail-border); - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/material/_preview.scss b/app/assets/stylesheets/lcms/engine/components/material/_preview.scss deleted file mode 100644 index b14e8cf5..00000000 --- a/app/assets/stylesheets/lcms/engine/components/material/_preview.scss +++ /dev/null @@ -1,24 +0,0 @@ -$letter-height: 792; -$letter-width: 612; -$pdf-toolbar-height: 50; - -.o-m-preview { - @extend %map-transition; - border: 1px solid map-get($ub-buttons, show-more); - overflow-y: hidden; - position: absolute !important; // stylelint-disable-line declaration-no-important - - @include m(portrait) { - @include aspect-ratio($letter-width, $letter-height + $pdf-toolbar-height); - width: 100%; - } - - @include m(landscape) { - @include aspect-ratio($letter-height, $letter-width + $pdf-toolbar-height); - width: 100%; - } - - @include e(actions) { - @include button-group('a'); - } -} diff --git a/app/assets/stylesheets/lcms/engine/components/material/_thumbnail.scss b/app/assets/stylesheets/lcms/engine/components/material/_thumbnail.scss deleted file mode 100644 index bc7977df..00000000 --- a/app/assets/stylesheets/lcms/engine/components/material/_thumbnail.scss +++ /dev/null @@ -1,4 +0,0 @@ -.o-m-thumbnail { - @include flex-grid-column(2.4); - position: relative; -} diff --git a/app/assets/stylesheets/lcms/engine/components/support/_survey.scss b/app/assets/stylesheets/lcms/engine/components/support/_survey.scss deleted file mode 100644 index c1b7bf29..00000000 --- a/app/assets/stylesheets/lcms/engine/components/support/_survey.scss +++ /dev/null @@ -1,7 +0,0 @@ -.o-survey-form { - @include e(other) { - @include m(hide) { - display: none; - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/layout/_footer.scss b/app/assets/stylesheets/lcms/engine/layout/_footer.scss deleted file mode 100644 index 79af34b9..00000000 --- a/app/assets/stylesheets/lcms/engine/layout/_footer.scss +++ /dev/null @@ -1,170 +0,0 @@ -$logo-width: 143px; -// NOTE: for regular logo it's 8px -$logo-padding: 0; -$footer-padding: 5px; -// NOTE: for full site it's xlarge -$footer-breakpoint: large; -$footer-breakpoint-down: ipad; - -.c-footer { - background-color: $ub-footer-bg; - color: $ub-nav-link; - display: table-row; - padding: $footer-padding; - width: 100%; - - @include e(dropdown) { - @include txt-element-strict(ld-tooltip); - background: map-get($ub-ld-colorcodes, tooltip-bg); - border: 0; - box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.25); - margin-top: $tooltip-pip-width; - padding: 1rem; - - * { - word-wrap: break-word; - } - - a { - position: relative; - text-decoration: none; - - &::after { - background-color: $anchor-color; - bottom: -1px; - content: ''; - height: 1px; - left: 0; - position: absolute; - right: 0; - } - } - - &::before { - @include css-triangle($tooltip-pip-width, map-get($ub-ld-colorcodes, tooltip-bg), down); - position: absolute; - top: 100%; - } - - [data-close] { - float: right; - position: relative; - right: -0.5rem; - top: -0.5rem; - } - } - - @include e(credits) { - line-height: 1.2rem; - - @include m(mobile) { - padding: 5px 5px 0 0; - - a { - color: $ub-nav-link; - text-decoration: underline; - } - } - } - - @include e(credits-link) { - cursor: pointer; - } - - @include e(wrap) { - @include flex-align(left, middle); - padding-bottom: $ub-large-padding; - padding-top: $ub-large-padding; - - @include breakpoint($footer-breakpoint) { - padding-bottom: $ub-large-padding; - padding-top: $ub-xlarge-padding * 1.118; - } - } - - @include e(link) { - &, - a { - color: $ub-nav-link; - } - - &:hover, - &:focus, - a:hover, - a:focus { - color: $ub-white; - text-decoration: none; - } - } - - @include e(logo) { - @include flex-grid-column(shrink, 0); - - @include breakpoint($footer-breakpoint) { - padding-bottom: $logo-padding; - } - - img { - width: $logo-width; - } - } - - @include e(item) { - @include flex-grid-column(12, 0); - color: darken($ub-nav-link, 20%); - margin-top: $ub-small-margin; - text-align: left; - - @include breakpoint($footer-breakpoint) { - @include flex-grid-column(shrink); - margin-top: 0; - - @include m(full) { - @include flex-grid-column(null, 0); - align-self: center; - text-align: right; - } - } - } - - @include e(list) { - @include menu-base; - @include menu-direction(horizontal); - color: $ub-whitesmoke; - - @include breakpoint($footer-breakpoint-down down) { - display: inline-flex; - flex-wrap: wrap; - - li { - flex: 0 0 auto; - - a, - span { - line-height: 1.5; - padding: 0; - } - - // stylelint-disable selector-no-qualifying-type - &.t-bullet { - // stylelint-enable selector-no-qualifying-type - padding: 0 $footer-padding; - } - } - } - - li { - a, - span { - padding: 0 $footer-padding; - } - } - } -} - -.t-bullet { - &::after { - color: $ub-nav-link; - content: '\2022'; - } -} diff --git a/app/assets/stylesheets/lcms/engine/layout/_header.scss b/app/assets/stylesheets/lcms/engine/layout/_header.scss deleted file mode 100644 index 29185f8c..00000000 --- a/app/assets/stylesheets/lcms/engine/layout/_header.scss +++ /dev/null @@ -1,35 +0,0 @@ -@mixin header-link-padding { - @include add-column-padding; - - @include breakpoint(xlarge) { - padding-left: 1.5rem; - padding-right: 1.5rem; - } - - @include breakpoint(large) { - padding-left: 1.2rem; - padding-right: 1.2rem; - } - - @media screen and #{breakpoint(large)} and (max-width: 1110px) { - padding-left: 0.8rem; - padding-right: 0.8rem; - } -} - -.c-header { - background-color: rgba($ub-primary, 0.9); - - @include m(documents) { - background-color: rgba($ub-primary, 0.9); - min-height: $ub-nav-height; - opacity: 0.9; - position: absolute; - width: 100%; - z-index: 1; - } -} - -.c-header-title { - padding: 10px; -} diff --git a/app/assets/stylesheets/lcms/engine/layout/_navigation.scss b/app/assets/stylesheets/lcms/engine/layout/_navigation.scss deleted file mode 100644 index 2c743eef..00000000 --- a/app/assets/stylesheets/lcms/engine/layout/_navigation.scss +++ /dev/null @@ -1,47 +0,0 @@ -.o-top-bar { - @include top-bar-container; - - // Stack on small screens by default - @include top-bar-stacked; - min-height: $ub-nav-height; - width: 100%; - z-index: $reveal-zindex; - - @include e(item) { - flex: 0 0 100%; - max-width: 100%; - } - - @include breakpoint($topbar-unstack-breakpoint) { - @include top-bar-unstack; - - @include e(item) { - flex: 0 0 auto; - } - } - - @include e(wrapper) { - @include breakpoint($topbar-unstack-breakpoint) { - align-items: center; - display: flex !important; // stylelint-disable-line declaration-no-important - flex-wrap: nowrap; - justify-content: space-between; - width: 100%; - } - } -} - -/* To prevent FOUC */ -.no-js { - @include breakpoint($topbar-unstack-breakpoint down) { - .o-top-bar { - display: none; - } - } - - @include breakpoint($topbar-unstack-breakpoint) { - .c-header-title__h-menu { - display: none; - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/layout/_page.scss b/app/assets/stylesheets/lcms/engine/layout/_page.scss deleted file mode 100644 index 4711a2db..00000000 --- a/app/assets/stylesheets/lcms/engine/layout/_page.scss +++ /dev/null @@ -1,77 +0,0 @@ -body { - display: table; - width: 100%; -} - -body, -html { - height: 100%; -} - -.u-page-wrap { - display: table-row; - height: 100%; -} - -.o-page { - @include flex-grid-row; - @include flex-grid-column; - - @include m(margin-to-top) { - @include add-gutter(margin, bottom, 1); - @include add-gutter(margin, top, 2); - } - - @include m(margin-to-bottom) { - @include add-gutter(margin, bottom, 2); - @include add-gutter(margin, top, 1); - } - - @include m(margin-bottom) { - @include add-gutter(margin, bottom, 2); - } - - @include m(centered) { - @include flex-align($x: center); - } - - @include e(wrap) { - @include m(nest) { - @include grid-row-nest; - } - @include m(row) { - @include flex-grid-row; - } - @include m(row-nest) { - @include flex-grid-row(nest); - } - @include m(row-column) { - @include flex-grid-row; - @include flex-grid-column; - } - } - - @include e(content) { - @include flex-grid-column(11); - - @include m(collapse) { - @include flex-grid-column(11, 0); - } - } - - @include e(sticky) { - position: relative; - } - - @include e(module) { - @include flex-grid-column(12, 0); - - @include m(margin-top) { - margin-top: $ub-large-margin * 2; - } - } - - @include e(section) { - @include flex-grid-column(12, $grid-column-2xgutter); - } -} diff --git a/app/assets/stylesheets/lcms/engine/lcms_engine_admin.scss b/app/assets/stylesheets/lcms/engine/lcms_engine_admin.scss deleted file mode 100644 index 76de7f8e..00000000 --- a/app/assets/stylesheets/lcms/engine/lcms_engine_admin.scss +++ /dev/null @@ -1,112 +0,0 @@ -// stylelint-disable max-nesting-depth, selector-no-qualifying-type, selector-max-compound-selectors, selector-class-pattern - -@import 'lcms_engine_application'; -@import 'selectize'; -@import 'admin/resources'; -@import 'admin/curriculums'; -@import 'admin/standards'; -@import 'admin/components'; -@import 'admin/lessons'; -@import 'admin/materials'; -@import 'admin/users'; -@import 'vendors/react-tagsinput'; - -.button { - white-space: nowrap; -} - -.c-admin { - @include breakpoint($topbar-unstack-breakpoint) { - .c-header-title__h-menu { - display: none; - } - } - - .menu { - &.vertical:not(.is-dropdown-submenu) { - display: none !important; // stylelint-disable-line declaration-no-important - } - - .active > a { - background: inherit; - } - } - - .menu > li > a { - line-height: inherit; - white-space: nowrap; - } -} - -.c-admin .o-page { - display: block; -} - -.simple_form { - small.error { - color: #ec5840; - display: block; - font-weight: bold; - margin: -0.5rem 0 1rem; - } - - .input.error { - input { - border-color: #ec5840; - } - - label { - color: #ec5840; - font-weight: bold; - } - } - - .input .selectize-control { - margin-bottom: 1rem; - } -} - -.text-nowrap { - white-space: nowrap; -} - -.is-dropdown-submenu { - min-width: 250px; - z-index: 2; -} - -.simple_form { - small.error { - color: #ec5840; - display: block; - font-weight: bold; - margin: -0.5rem 0 1rem; - } - - .input.error { - input { - border-color: #ec5840; - } - - label { - color: #ec5840; - font-weight: bold; - } - } - - .input .selectize-control { - margin-bottom: 1rem; - } -} - -.flex-pull-right { - margin-left: auto; -} - -.inline-form-btn { - display: inline; -} - -.c-multi-selected-btn { - display: inline-block; -} diff --git a/app/assets/stylesheets/lcms/engine/lcms_engine_application.scss b/app/assets/stylesheets/lcms/engine/lcms_engine_application.scss deleted file mode 100644 index 1cc35029..00000000 --- a/app/assets/stylesheets/lcms/engine/lcms_engine_application.scss +++ /dev/null @@ -1,9 +0,0 @@ -@charset "utf-8"; - -@import 'utils/variables', 'utils/mixins', 'utils/placeholders', 'base/colors', 'base/ld-colors'; -@import 'themes/settings', 'themes/material'; -@import 'font-awesome-sprockets', 'font-awesome', 'vendors/foundation_and_overrides', 'vendors/mathjax'; -@import 'utils/styles_override', 'utils/helpers', 'utils/colorcodes', 'utils/functions'; -@import 'base/typography', 'print/print'; -@import 'components/assoc-picker'; -@import 'layout/footer', 'layout/header', 'layout/navigation', 'layout/page'; diff --git a/app/assets/stylesheets/lcms/engine/pdf.scss b/app/assets/stylesheets/lcms/engine/pdf.scss index fad517d2..e63c05cc 100644 --- a/app/assets/stylesheets/lcms/engine/pdf.scss +++ b/app/assets/stylesheets/lcms/engine/pdf.scss @@ -1,9 +1 @@ @charset "utf-8"; - -@import 'utils/variables', 'utils/mixins'; -@import 'themes/settings'; -@import 'vendors/foundation_and_overrides-pdf', 'vendors/mathjax.css'; -@import 'utils/styles_override', 'utils/helpers', 'utils/colorcodes', 'utils/functions'; -@import 'print/typography'; -@import 'components/material/material'; -@import 'print/base', 'print/colorscheme', 'print/header', 'print/helpers', 'print/lesson-base', 'print/material-base'; diff --git a/app/assets/stylesheets/lcms/engine/pdf_plain.scss b/app/assets/stylesheets/lcms/engine/pdf_plain.scss index 5a1fc95b..e63c05cc 100644 --- a/app/assets/stylesheets/lcms/engine/pdf_plain.scss +++ b/app/assets/stylesheets/lcms/engine/pdf_plain.scss @@ -1,10 +1 @@ @charset "utf-8"; - -@import 'utils/variables', 'utils/mixins'; -@import 'themes/settings'; -@import 'vendors/foundation_and_overrides-pdf'; -@import 'utils/colorcodes', 'utils/helpers'; -@import 'print/typography'; -@import 'components/lesson/override'; -@import 'print/base', 'print/cover', 'print/colorscheme', 'print/footer', 'print/header', 'print/helpers', - 'print/lesson-base'; diff --git a/app/assets/stylesheets/lcms/engine/print/_base.scss b/app/assets/stylesheets/lcms/engine/print/_base.scss deleted file mode 100644 index 9dcc46ab..00000000 --- a/app/assets/stylesheets/lcms/engine/print/_base.scss +++ /dev/null @@ -1,103 +0,0 @@ -// stylelint-disable scss/selector-no-redundant-nesting-selector - -// Unbounded specific -html, -body { - background: none; - height: 100%; -} - -@page { - size: letter; -} - -.o-page { - @include m(pdf) { - padding-left: $pdf-margin; - padding-right: $pdf-margin; - } -} - -header, -footer, -o-sidebar-nav { - display: none; -} - -tr, -img, -table, -pre, -blockquote, -.callout { - page-break-inside: avoid; -} - -// Avoid page breaks after a heading -h1, -h2, -h3, -h4, -h5 { - page-break-after: avoid; -} - -h1, -h2, -h3, -h4 { - font-weight: bold; -} - -h1 { - @include txt-pdf-element(h3); - clear: both; - padding-top: 1.25rem; - - & + h2 { - padding-top: 0 !important; // stylelint-disable-line declaration-no-important - } - - * { - line-height: 1.1; - margin-bottom: 0; - } -} - -h2 { - @include txt-pdf-element(h4); - padding-top: 1.25rem; - - & + h3 { - padding-top: 0 !important; // stylelint-disable-line declaration-no-important - } -} - -h3 { - @include txt-pdf-element(h5); - padding-top: 1.25rem; - - & + h4 { - padding-top: 0 !important; // stylelint-disable-line declaration-no-important - } -} - -h4 { - @include txt-pdf-element(h6); - padding-top: 1.25rem; -} - -ul li { - text-indent: 0 !important; // stylelint-disable-line declaration-no-important -} - -img, -td, -span, -table { - max-width: 100%; -} - -img { - height: auto !important; // stylelint-disable-line declaration-no-important -} diff --git a/app/assets/stylesheets/lcms/engine/print/_colorscheme.scss b/app/assets/stylesheets/lcms/engine/print/_colorscheme.scss deleted file mode 100644 index 45f5ddc9..00000000 --- a/app/assets/stylesheets/lcms/engine/print/_colorscheme.scss +++ /dev/null @@ -1,20 +0,0 @@ -@mixin cg-pdf-color-scheme($map) { - h1 { - color: map-get($map, link); - } -} - -.o-page--cg-ela { - @include cg-color-scheme($ub-cg-ela-colors); - @include cg-pdf-color-scheme($ub-cg-ela-colors); -} - -.o-page--cg-math { - @include cg-color-scheme($ub-cg-math-colors); - @include cg-pdf-color-scheme($ub-cg-math-colors); -} - -.o-page--cg-lead { - @include cg-color-scheme($ub-cg-lead-colors); - @include cg-pdf-color-scheme($ub-cg-lead-colors); -} diff --git a/app/assets/stylesheets/lcms/engine/print/_cover.scss b/app/assets/stylesheets/lcms/engine/print/_cover.scss deleted file mode 100644 index 3af72bed..00000000 --- a/app/assets/stylesheets/lcms/engine/print/_cover.scss +++ /dev/null @@ -1,83 +0,0 @@ -body { - height: 100%; - position: relative; -} - -%pdf-full-height { - bottom: 0; - height: 100%; - position: absolute; - top: 0; - width: 100%; -} - -.c-pdf-title { - @include txt-pdf-element(cover-title); -} - -.c-pdf-timestamp { - @include txt-pdf-element(cover-timestamp); - padding-top: $ub-small-padding; -} - -.c-pdf-logo { - @include m(u) { - @include txt-pdf-element(cover-logo-u); - } - @include m(ed) { - @include txt-pdf-element(cover-logo-ed); - } -} - -.c-pdf-cover { - height: 100%; - @include e(title) { - // keep - } - - @include m(ela) { - @extend %pdf-full-height; - background: map-get($ub-cg-ela-colors, cover-bg); - } - - @include m(math) { - @extend %pdf-full-height; - background: map-get($ub-cg-math-colors, cover-bg); - } - - @include m(lead) { - @extend %pdf-full-height; - background: map-get($ub-cg-lead-colors, cover-bg); - } - - @include e(wrap) { - margin-left: $pdf-margin; - margin-right: $pdf-margin; - } - - @include e(img) { - background-size: cover; - height: 400px; - margin: 0; - min-width: 0; - } - - @include e(content) { - padding: 30px; - } - - @include e(logo) { - bottom: 30px; - position: absolute; - } - - h1 { - @include txt-pdf-element(h1); - padding-top: 0; - } - - h2 { - @include txt-pdf-element(h2); - padding-top: 0; - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/_footer.scss b/app/assets/stylesheets/lcms/engine/print/_footer.scss deleted file mode 100644 index 0e4c4ded..00000000 --- a/app/assets/stylesheets/lcms/engine/print/_footer.scss +++ /dev/null @@ -1,79 +0,0 @@ -.pdf-bullet { - padding-left: $ub-xs-padding / 2; - padding-right: $ub-xs-padding / 2; - - &::after { - content: '\2022'; - } -} - -.c-pdf-footer { - $m-footer-height: 6pt * 2; - - border: 0; - border-collapse: separate; - border-radius: 0; - border-top: 1px solid $ub-hr; - box-shadow: none; - margin: 0; - margin-top: $ub-base-padding; - outline: 0; - padding: 0; - padding-top: $ub-small-padding; - width: 100%; - - @include m(cg) { - @include txt-pdf-element(footer); - } - - @include m(ld) { - &, - a { - @include txt-pdf-element(ld-footer); - } - } - - @include m(material) { - td { - min-height: $m-footer-height; - padding: 0 $ub-xs-padding / 2; - vertical-align: top; - } - } - - @include e(wrap) { - margin-left: $pdf-margin; - margin-right: $pdf-margin; - } - - @include e(cc-logo) { - $cc-logo-ratio: 88 / 31; - height: $m-footer-height; - width: $m-footer-height * $cc-logo-ratio; - } - - @include e(logo) { - $logo-ratio: 200 / 28; - height: $m-footer-height; - width: $m-footer-height * $logo-ratio; - } - - @include e(left) { - padding: 0; - - @include m(draft) { - color: #f00; - padding-right: 5px; - } - } - - @include e(page) { - min-width: 11pt * 5; - text-align: right; - } - - @include e(right) { - padding: 0; - text-align: right; - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/_gdoc-base.scss b/app/assets/stylesheets/lcms/engine/print/_gdoc-base.scss deleted file mode 100644 index a27eaaa5..00000000 --- a/app/assets/stylesheets/lcms/engine/print/_gdoc-base.scss +++ /dev/null @@ -1,19 +0,0 @@ -@import 'gdoc/helpers'; - -@import 'gdoc/typography', 'gdoc/base', 'gdoc/mixins', 'helpers'; - -@import 'gdoc/base-typography', 'gdoc/header'; - -@import 'gdoc/lesson/activity', 'gdoc/lesson/agenda', 'gdoc/lesson/callout', 'gdoc/lesson/def', 'gdoc/lesson/dialogue', - 'gdoc/lesson/expand', 'gdoc/lesson/foundational-divider', 'gdoc/lesson/hr', 'gdoc/lesson/icons', 'gdoc/lesson/image', - 'gdoc/lesson/image-wrap', 'gdoc/lesson/indent', 'gdoc/lesson/inset', 'gdoc/lesson/optbreak', 'gdoc/lesson/section', - 'gdoc/lesson/smp', 'gdoc/lesson/tables', 'gdoc/lesson/title'; - -@import 'gdoc/material/header'; - -@import 'lesson/def', 'lesson/expand', - //'lesson/image', - 'lesson/pd', - 'lesson/pv'; - -@import 'material/header', 'material/list'; diff --git a/app/assets/stylesheets/lcms/engine/print/_header.scss b/app/assets/stylesheets/lcms/engine/print/_header.scss deleted file mode 100644 index 9277b62e..00000000 --- a/app/assets/stylesheets/lcms/engine/print/_header.scss +++ /dev/null @@ -1,11 +0,0 @@ -@import 'material/header'; - -.c-pdf-header { - @include m(ld) { - @include txt-pdf-element(ld-header); - } - - @include e(wrap) { - @include add-top-bottom-padding; - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/_helpers.scss b/app/assets/stylesheets/lcms/engine/print/_helpers.scss deleted file mode 100644 index ab48f1ff..00000000 --- a/app/assets/stylesheets/lcms/engine/print/_helpers.scss +++ /dev/null @@ -1,22 +0,0 @@ -.nobreak { - page-break-before: avoid !important; // stylelint-disable-line declaration-no-important -} - -.u-pdf-alwaysbreak, -%u-pdf-alwaysbreak { - page-break-before: always; -} - -.u-pdf-nobreak, -%u-pdf-nobreak { - page-break-inside: avoid; - - &::before { - clear: both; - } -} - -.u-pdf-nobreak--around { - page-break-after: avoid; - page-break-before: avoid; -} diff --git a/app/assets/stylesheets/lcms/engine/print/_lesson-base.scss b/app/assets/stylesheets/lcms/engine/print/_lesson-base.scss deleted file mode 100644 index aa6f2646..00000000 --- a/app/assets/stylesheets/lcms/engine/print/_lesson-base.scss +++ /dev/null @@ -1,6 +0,0 @@ -.c-ld__body--pdf { - @include ld-custom-styles; - - @import 'lesson/base-typography', 'lesson/callout', 'lesson/def', 'lesson/expand', 'lesson/group', 'lesson/image', - 'lesson/materials', 'lesson/optbreak', 'lesson/pd', 'lesson/pv', 'lesson/section', 'lesson/smp', 'lesson/title'; -} diff --git a/app/assets/stylesheets/lcms/engine/print/_material-base.scss b/app/assets/stylesheets/lcms/engine/print/_material-base.scss deleted file mode 100644 index bd935209..00000000 --- a/app/assets/stylesheets/lcms/engine/print/_material-base.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'material/content', 'material/list'; diff --git a/app/assets/stylesheets/lcms/engine/print/_print.scss b/app/assets/stylesheets/lcms/engine/print/_print.scss deleted file mode 100644 index b59dcffa..00000000 --- a/app/assets/stylesheets/lcms/engine/print/_print.scss +++ /dev/null @@ -1,22 +0,0 @@ -@page { - size: letter; -} - -@media print { - // Unbounded specific - html, - body { - background: none; - } - - header, - footer, - .o-btn, - .o-ub-btn, - .o-filterbar, - .o-sidebar-nav, - .c-cg-content__sidebar, - .c-ld__sidebar { - display: none; - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/_toc.scss b/app/assets/stylesheets/lcms/engine/print/_toc.scss deleted file mode 100644 index 8610109a..00000000 --- a/app/assets/stylesheets/lcms/engine/print/_toc.scss +++ /dev/null @@ -1,35 +0,0 @@ -// stylelint-disable selector-max-compound-selectors -.c-pdf-toc { - @include e(l1) { - @include txt-pdf-element(toc-l1); - } - - @include e(l2) { - @include txt-pdf-element(toc-l2); - margin-bottom: 30px; - } - - h1 { - @include txt-pdf-element(h3); - } - - ul { - padding-left: 0; - } - - ul ul ul { - display: none; - } - - span { - float: right; - } - - li { - list-style: none; - } - - a { - text-decoration: none; - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/_typography.scss b/app/assets/stylesheets/lcms/engine/print/_typography.scss deleted file mode 100644 index 8355c484..00000000 --- a/app/assets/stylesheets/lcms/engine/print/_typography.scss +++ /dev/null @@ -1,58 +0,0 @@ -$ub-pdf-typography: ( - h1: ('font-size': 30pt, 'line-height': 30pt, 'font-family': $ub-serif, 'font-weight': 500, 'font-style': normal, color: $ub-pdf-black), - h2: ('font-size': 15pt, 'line-height': 18pt, 'font-family': $ub-serif, color: $ub-pdf-gray, 'font-style': normal), - h3: ('font-size': 22pt, 'line-height': 24pt, 'font-family': $ub-sanserif-condensed, 'font-weight': 500, 'font-style': normal), - h4: ('font-size': 14pt, 'line-height': 18pt, 'font-family': $ub-sanserif, 'font-style': italic, color: $ub-pdf-gray), - h5: ('font-size': 13pt, 'line-height': 16pt, 'font-family': $ub-sanserif-condensed, 'font-style': normal, 'font-weight': 500, color: $ub-pdf-gray), - h6: ('font-size': 10pt, 'line-height': 13pt, 'font-family': $ub-sanserif-condensed, 'font-style': normal, 'font-weight': 500, color: $ub-pdf-gray), - base: ('font-size': 10pt, 'line-height': 1.5, 'font-family': $ub-sanserif), - toc-l1: ('font-size': 14pt, 'line-height': 18pt, 'font-family': $ub-sanserif-condensed, 'font-weight': 500), - toc-l2: ('font-size': 11pt, 'line-height': 18pt, 'font-family': $ub-sanserif-condensed, 'font-weight': 500, color: $ub-pdf-gray), - task-number: ('font-size': 10pt, 'line-height': 14.5pt, 'font-family': $ub-sanserif-condensed, 'font-weight': 500, color: $ub-pdf-gray), - task-title: ('font-size': 13pt, 'line-height': 16pt, 'font-family': $ub-sanserif-condensed, 'font-weight': 500, color: $ub-pdf-gray), - pullquote: ('font-size': 17pt, 'line-height': 19pt, 'font-family': $ub-serif), - blockquote: ('font-size': 11pt, 'line-height': 14pt, 'font-family': $ub-serif, color: $ub-pdf-gray), - cover-title: ('font-size': 12pt, 'line-height': 23pt, 'font-family': $ub-serif, 'font-weight': 600), - cover-timestamp: ('font-size': 10pt, 'line-height': 14.5pt, 'font-family': $ub-sanserif, color: $ub-pdf-black), - cover-logo-u: ('font-size': 24pt, 'line-height': 24pt, 'font-family': $ub-serif, 'font-weight': 600, color: $ub-pdf-gray), - cover-logo-ed: ('font-size': 24pt, 'line-height': 24pt, 'font-family': $ub-serif, color: $ub-pdf-gray), - footer: ('font-size': 7pt, 'line-height': 8.4pt, 'font-family': $ub-serif, color: $ub-pdf-gray), - header: ('font-size': 7pt, 'line-height': 8.4pt, 'font-family': $ub-serif, color: $ub-pdf-gray), - // Lessons - ld-activity-h2time: ('font-size': 16pt, 'line-height': 1, 'font-family': $ub-sanserif, 'font-weight': bold, 'font-style': italic), - ld-activity-h3time: ('font-size': 12pt, 'line-height': 1, 'font-family': $ub-sanserif, 'font-weight': bold, 'font-style': italic), - ld-activity-teaser: ('font-size': 13.5pt, 'line-height': 1.3, 'font-family': $ub-sanserif, font-style: italic), - ld-base: ('font-size': 16px, 'font-family': $ub-sanserif), - ld-callout-header: ('font-size': 14.5pt, 'line-height': 1.4, 'font-family': $ub-sanserif-condensed, 'font-weight': 500, font-style: normal), - ld-footer: ('font-size': 10pt, 'line-height': 1.2, 'font-family': $ub-sanserif, color: $ub-pdf-gray), - ld-footer-logo: ('font-size': 10pt, 'line-height': 1.2, 'font-family': $ub-serif, color: $ub-pdf-gray), - ld-footer-page: ('font-size': 10pt, 'line-height': 1.2, 'font-family': $ub-sanserif, font-weight: 500, color: $ub-pdf-gray), - ld-h1: (font-size: 30pt, 'line-height': 30pt, 'font-family': $ub-serif, 'font-weight': 500, 'font-style': normal), - ld-h2: (font-size: 26pt, 'line-height': 1.05, 'font-family': $ub-serif, 'font-weight': 500, font-style: normal), - ld-h3: (font-size: 20pt, 'line-height': 1.07, 'font-family': $ub-sanserif-condensed, 'font-weight': 500, font-style: normal), - ld-h4: ('font-size': 15pt, 'line-height': 1.23, 'font-family': $ub-sanserif, 'font-style': italic), - ld-h5: ('font-size': 13pt, 'line-height': 1, 'font-family': $ub-sanserif, 'font-weight': bold), - ld-header: ('font-size': 12pt, 'line-height': 1.2, 'font-family': $ub-serif, color: $ub-white), - ld-optbreak: ('font-size': 14.5pt, 'line-height': 1.4, 'font-family': $ub-sanserif-condensed, 'font-weight': 500, font-style: normal), - ld-smp: ('font-size': 12pt, 'line-height': 1, 'font-family': $ub-serif, 'font-weight': 600, color: #fff), - ld-summary: ('font-size': 13.5pt, 'line-height': 1.3, 'font-family': $ub-sanserif, 'font-style': italic), - // Materials - m-footer-small: ('font-size': 6pt, 'line-height': 8pt, 'font-family': $ub-serif, color: #7f8184), - m-footer-page: ('font-size': 11pt, 'line-height': 12pt, 'font-family': $ub-serif, color: #7f8184, font-style: italic), - m-header-kicker: ('font-size': 11pt, 'line-height': 12pt, 'font-family': $ub-serif, font-weight: 500, color: #7f8184), - m-header-title: ('font-size': 30pt, 'line-height': 1, 'font-family': $ub-serif, font-weight: 500, color: $ub-pdf-black), - m-header-dt-title: (font-size: 15pt, 'line-height': 1, font-family: $ub-serif, color: $ub-pdf-black), - m-header-dt-value: (font-size: 15pt, 'line-height': 1, font-family: $ub-cursive, color: #433a82), -); - -p { - font-feature-settings: "kern" 1; - font-kerning: normal; - text-rendering: optimizeSpeed; //optimize-speed -} - -@each $element, $typo in $ub-pdf-typography { - .u-pdf-txt--#{$element} { - @include txt-pdf-element($element); - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/_base-typography.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/_base-typography.scss deleted file mode 100644 index 715b63b6..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/_base-typography.scss +++ /dev/null @@ -1,33 +0,0 @@ -h1, -h2, -h3, -h4, -h5 { - @extend %u-pdf-nobreak; -} - -h1 { - @include txt-gdoc-element(h1); -} - -h2 { - @include txt-gdoc-element(h2); -} - -h3 { - @include txt-gdoc-element(h3); -} - -h4 { - @include txt-gdoc-element(h4); -} - -h5 { - @include txt-gdoc-element(h5); -} - -body { - @include txt-gdoc-element(base); - margin: 0; - padding: 0; -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/_base.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/_base.scss deleted file mode 100644 index c8141965..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/_base.scss +++ /dev/null @@ -1,34 +0,0 @@ -table { - td, - tr { - border: 0; - } -} - -a { - color: $ub-gdoc-link; - text-decoration: underline; -} - -p { - padding-bottom: 11pt; - padding-top: 0; - - // stylelint-disable selector-no-qualifying-type - &.u-gdoc-empty-p { - padding-bottom: 0; - } - // stylelint-enable selector-no-qualifying-type -} - -span > p { - padding: 0; -} - -ol { - &, - li, - li::before { - font-family: 'Calibri' !important; // stylelint-disable-line declaration-no-important - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/_colors.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/_colors.scss deleted file mode 100644 index 7ff35b30..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/_colors.scss +++ /dev/null @@ -1,26 +0,0 @@ -$ub-gdoc-link: #15c; - -$ub-ld-colorcodes: ( - activity-optional: #fee59e, - default-image-bg: transparentize(map-get($ub-colorcodes, default-base), 0.85), - ela-expand-bg: #fee6df, - ela-toggler-bg: transparentize(map-get($ub-colorcodes, ela-base), 0.7), - ela6-section-bg: #fee6df, - ela-image-bg: darken(#fee6df, 5%), - gls-bg: #fff6cc, - math-activity-bg: #d8f1ef, - math-activity-foundational: #4bb0db, - math-activity-foundational-bg: #e4f7ff, - math-activity-foundational-disabled-bg: #0088c2, - math-activity-foundational-disabled-fg: #99e0ff, - math-activity-foundational-guidance-bg: #d5effb, - math-activity-foundational-switch-bg: #b0e1f8, - math-activity-guidance-bg: darken(#d8f1ef, 5%), - math-expand-bg: #d8f1ef, - math-toggler-bg: transparentize(map-get($ub-colorcodes, math-base), 0.7), - math-image-bg: darken(#d8f1ef, 5%), - pd-bg: #e8e7ed, - pd-bg-dark: #8e89a9, - material-overlay: #423872, - tooltip-bg: #fff8e5, -); diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/_header.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/_header.scss deleted file mode 100644 index 364a4e31..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/_header.scss +++ /dev/null @@ -1,17 +0,0 @@ -.c-gdoc-header { - border: 0; - margin-left: -map-get($gdoc-spacing, large); - margin-right: -map-get($gdoc-spacing, large); - - @include e(wrap) { - padding: 7.2pt map-get($gdoc-spacing, large); - - p { - padding: 0; - } - } - - @include e(hr) { - margin-bottom: map-get($gdoc-spacing, large); - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/_helpers.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/_helpers.scss deleted file mode 100644 index 21e378d6..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/_helpers.scss +++ /dev/null @@ -1,57 +0,0 @@ -// stylelint-disable declaration-no-important -$list-side-margin: 18pt; - -$gdoc-spacing: ( - 'zero': 0, - 'small': 5pt, - 'base': 9pt, - 'large': 18pt, -); -$gdoc-align: (top, bottom, right, left); - -@each $margin-type, $margin-value in $gdoc-spacing { - @each $align in $gdoc-align { - .u-gdoc-margin-#{$align}--#{$margin-type} { - margin-#{$align}: $margin-value !important; - } - .u-gdoc-padding-#{$align}--#{$margin-type} { - padding-#{$align}: $margin-value !important; - } - } - .u-gdoc-margin-vertical--#{$margin-type} { - margin-bottom: $margin-value; - margin-top: $margin-value; - } - %u-gdoc-padding-vertical--#{$margin-type}, - .u-gdoc-padding-vertical--#{$margin-type} { - padding-bottom: $margin-value; - padding-top: $margin-value; - } - .u-gdoc-padding-horizontal--#{$margin-type} { - padding-bottom: $margin-value; - padding-top: $margin-value; - } -} - -.u-border--none { - border-bottom: 0 !important; - border-left: 0 !important; - border-right: 0 !important; - border-top: 0 !important; -} - -.u-table-wrap { - padding: map-get($gdoc-spacing, large); - - @include m(small) { - padding: map-get($gdoc-spacing, base); - } - - @include m(l2) { - padding: 0 map-get($gdoc-spacing, large) map-get($gdoc-spacing, large); - } -} - -.u-table-padding { - padding: 5pt; -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/_mixins.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/_mixins.scss deleted file mode 100644 index ed7ba00e..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/_mixins.scss +++ /dev/null @@ -1,11 +0,0 @@ -@mixin gdoc-clearfix { - &::before, - &::after { - content: ' '; - display: table; - } - - &::after { - clear: both; - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/_typography.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/_typography.scss deleted file mode 100644 index 1e5367f4..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/_typography.scss +++ /dev/null @@ -1,65 +0,0 @@ -$ub-cursive: 'Sriracha', cursive; - -$ub-gdoc-typography: ( - // agenda - agenda-header: (font-size: 9pt, color: #7f8184, font-weight: bold, line-height: 1), - agenda-l1-title: (font-size: 14pt, color: #4c4c4c, font-weight: bold, line-height: 1), - agenda-l1-time: (font-size: 11pt, font-style: italic, line-height: 1), - agenda-l2: (font-size: 9pt, line-height: 1), - agenda-l2-title: (font-size: 11pt, line-height: 1), - agenda-l2-time: (font-style: italic), - // base - title: (color: #4c4c4c, font-weight: bold, font-size: 30pt, line-height: 1, padding-top: 20pt, padding-bottom: 10pt), - title-type: (font-weight: bold), - subtitle: (color: #7f8184, font-size: 14pt, line-height: 1, font-style: italic), - base: (line-height: 1, font-size: 11pt, color: #000, font-family: 'Calibri'), - h1: (color: #000, font-weight: bold, font-size: 22pt, line-height: 1, padding-top: 20pt, padding-bottom: 4pt, margin: 0), - h2: (color: #4c4c4c, font-weight: bold, font-size: 16pt, line-height: 1, text-transform: uppercase, padding-top: 18pt, padding-bottom: 6pt, margin: 0), - h3: (line-height: 1.15, color: #434343, font-weight: bold, font-size: 18pt, padding-top: 16pt, padding-bottom: 4pt, margin: 0 !important), - h4: (color: #4c4c4c, font-weight: bold, font-size: 14pt, line-height: 1.15, padding-top: 15pt, padding-bottom: 5pt, margin: 0), - h5: (color: #000, font-weight: bold, font-size: 11pt, line-height: 1.15, padding: 0, margin: 0), - h6: (color: #666, font-size: 8pt, line-height: 1.15, padding: 0, margin: 0), - header-breadcrumb: (color: #fff, font-size: 10pt, line-height: 1, font-weight: bold, padding: 0), - header-teaser: (color: #000, font-size: 14pt, font-style: italic, padding-bottom: 20pt), - // Lessons - ld-activity-h2: (font-size: 22pt, line-height: 1, padding-top: 0, padding-bottom: 0, margin: 0), - ld-activity-h3: (line-height: 1, padding-top: 0, padding-bottom: 4pt, margin: 0), - ld-activity-h2time: (font-size: 14pt, vertical-align: baseline, line-height: 1, color: #434343, font-style: italic, padding-top: 0, padding-bottom: 4pt, margin: 0), - ld-activity-h3time: (line-height: 1, color: #434343, font-style: italic, padding-top: 0, padding-bottom: 4pt, margin: 0), - ld-activity-teaser: (color: #7f8184, font-style: italic), - ld-callout-header: (font-weight: bold, font-style: italic, padding: 0, margin: 0), - pullquote: (color: #69618d, font-style: italic), - ld-math-foundational-divider: (color: $ub-white, font-size: 14.5pt, font-weight: bold, 'line-height': 1.4), - ld-optbreak: (color: #555, font-size: 14.5pt, line-height: 1, font-weight: bold, font-style: normal), - ld-pd-caption: (color: #8e89a9, font-weight: bold), - ld-pd-cg-header: (color: #69618d, font-style: italic), - ld-pd-cg-title: (color: #69618d, font-style: italic), - ld-pd-title: (color: #69618d, padding: 0, margin: 0, font-weight: bold), - ld-smp: ('font-size': 11pt, 'line-height': 1,'font-weight': bold, text-align: right, padding-right: 5pt), - ld-summary: (color: #7f8184, font-style: italic !important), - ld-white-placeholder-title: ('font-size': 12pt, 'font-weight': bold), - ld-white-placeholder-colored-title: ('font-size': initial !important, 'font-weight': bold, color: initial !important), - // Materials - m-footer-small: ('font-size': 6pt, 'line-height': 8pt, color: #7f8184), - m-footer-page: ('font-size': 11pt, 'line-height': 12pt, color: #7f8184, font-style: italic), - m-header-kicker: ('font-size': 11pt, 'line-height': 12pt, font-weight: bold, color: #7f8184), - m-header-title: ('font-size': 30pt, 'line-height': 1, font-weight: bold, color: $ub-pdf-black, padding-top: 0, margin-top: 0), - m-header-dt-title: (font-size: 15pt, 'line-height': 1, color: $ub-pdf-black), - m-header-dt-value: (font-size: 15pt, 'line-height': 1, font-family: $ub-cursive, color: #433a82), -); - -p { - font-feature-settings: "kern" 1; - font-kerning: normal; - text-rendering: optimizeSpeed; //optimize-speed -} - -@mixin txt-gdoc-element($element) { - @include txt-element($element, $ub-gdoc-typography); -} - -@each $element, $typo in $ub-gdoc-typography { - .u-txt--#{$element} { - @include txt-element($element, $ub-gdoc-typography); - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_activity.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_activity.scss deleted file mode 100644 index cd59953a..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_activity.scss +++ /dev/null @@ -1,21 +0,0 @@ -// stylelint-disable declaration-no-important, max-nesting-depth -.o-ld-activity-wrapper { - .o-simple-table.o-ld-activity__optional { - td { - background-color: map-get($ub-ld-colorcodes, activity-optional); - padding: 20pt; - width: 100%; - } - - p { - padding: 0 !important; - text-align: center !important; - } - } - - .u-txt--ld-activity-teaser { - * { - background-color: initial !important; - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_agenda.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_agenda.scss deleted file mode 100644 index 1fb9cc62..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_agenda.scss +++ /dev/null @@ -1,18 +0,0 @@ -.o-agenda { - @include e(item) { - padding: 5pt; - - p { - padding: 0; - } - } - - tr, - td { - border: 0; - } - - td { - border-bottom: 1pt dotted $ub-hr; - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_callout.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_callout.scss deleted file mode 100644 index 98ebce9b..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_callout.scss +++ /dev/null @@ -1,42 +0,0 @@ -.o-ld-callout { - @extend %u-pdf-nobreak; - @include txt-gdoc-element(pullquote); - - @include e(header) { - @include txt-gdoc-element(ld-callout-header); - } - - @include e(wrap) { - padding-left: 18pt; - } - - @include e(content) { - @include m(ela) { - &, - *, - ul, - li, - ol { - &, - * { - background-color: initial !important; // stylelint-disable-line declaration-no-important - color: map-get($ub-colorcodes, ela-base); - } - } - } - - @include m(math) { - &, - *, - ul, - li, - ol { - &, - * { - background-color: initial !important; // stylelint-disable-line declaration-no-important - color: map-get($ub-colorcodes, math-base); - } - } - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_def.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_def.scss deleted file mode 100644 index 445be568..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_def.scss +++ /dev/null @@ -1,12 +0,0 @@ -.c-ld-keyword { - font-weight: bold !important; // stylelint-disable-line declaration-no-important - white-space: nowrap; - - @include m(ela) { - color: map-get($ub-colorcodes, ela-base); - } - - @include m(math) { - color: map-get($ub-colorcodes, math-base); - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_dialogue.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_dialogue.scss deleted file mode 100644 index 1ca310e9..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_dialogue.scss +++ /dev/null @@ -1,5 +0,0 @@ -.o-ld-dialogue { - p { - margin-bottom: map-get($gdoc-spacing, base); - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_expand.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_expand.scss deleted file mode 100644 index 30675b3c..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_expand.scss +++ /dev/null @@ -1,15 +0,0 @@ -// stylelint-disable declaration-no-important, max-nesting-depth, selector-max-compound-selectors -.o-ld-expand.o-simple-table { - td { - > h3, - > ol, - > p, - > ul { - background-color: initial !important; - - :not(.o-ld-gls) { - background-color: initial !important; - } - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_foundational-divider.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_foundational-divider.scss deleted file mode 100644 index d22d59dc..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_foundational-divider.scss +++ /dev/null @@ -1,15 +0,0 @@ -.o-ld-foundational-divider { - padding: 0; - - @include e(item) { - h2 { - color: #fff; - margin: 0; - padding: 5pt; - - span { - padding: 0; - } - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_hr.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_hr.scss deleted file mode 100644 index c8eba1c4..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_hr.scss +++ /dev/null @@ -1,10 +0,0 @@ -.o-ld-hr { - @include m(l1) { - padding-bottom: map-get($gdoc-spacing, large); - //padding-top: map-get($gdoc-spacing, large); - } - - @include m(l2) { - padding-bottom: map-get($gdoc-spacing, large); - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_icons.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_icons.scss deleted file mode 100644 index ef6e4ade..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_icons.scss +++ /dev/null @@ -1,5 +0,0 @@ -.o-ld-icon { - display: inline-block; - height: 12.5pt; - width: 12.5pt; -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_image-wrap.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_image-wrap.scss deleted file mode 100644 index b8e85d57..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_image-wrap.scss +++ /dev/null @@ -1,15 +0,0 @@ -.o-ld-image-wrap { - @include m(math) { - background: $ub-white; - - img { - display: block; - height: auto !important; // stylelint-disable-line declaration-no-important - max-width: 100% !important; // stylelint-disable-line declaration-no-important - } - } - - @include e(img) { - width: 100%; - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_image.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_image.scss deleted file mode 100644 index 38d8210b..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_image.scss +++ /dev/null @@ -1,20 +0,0 @@ -.o-ld-image { - @include e(img) { - width: 30%; - - img { - height: 110pt; - width: auto; - } - } - @include e(caption) { - padding-left: 9pt; - vertical-align: middle; - width: 70%; - } -} - -.o-google-chart { - height: 50%; - vertical-align: middle !important; // stylelint-disable-line declaration-no-important -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_indent.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_indent.scss deleted file mode 100644 index 7b1226cf..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_indent.scss +++ /dev/null @@ -1,11 +0,0 @@ -.u-ld-indent { - @for $i from 2 through 3 { - @include m('l#{$i}') { - margin-left: $list-side-margin * $i; - } - } -} - -.u-ld-indented { - text-indent: $list-side-margin * 2; -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_inset.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_inset.scss deleted file mode 100644 index 3ce01756..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_inset.scss +++ /dev/null @@ -1,4 +0,0 @@ -.o-ld-inset { - margin-left: $list-side-margin * 2; - padding-bottom: 0; -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_optbreak.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_optbreak.scss deleted file mode 100644 index 94ad8802..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_optbreak.scss +++ /dev/null @@ -1,7 +0,0 @@ -// stylelint-disable declaration-no-important -.o-ld-optbreak { - @include e(divider) { - border-bottom: 1pt dotted $ub-hr !important; - border-top: 1pt dotted $ub-hr !important; - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_section.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_section.scss deleted file mode 100644 index dd688c53..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_section.scss +++ /dev/null @@ -1,40 +0,0 @@ -// stylelint-disable declaration-no-important, max-nesting-depth, selector-max-compound-selectors -.o-ld-section { - @include e(quote) { - @extend %u-pdf-nobreak; - @include txt-gdoc-element(pullquote); - } - - @include m(bg-color) { - background-color: map-get($ub-ld-colorcodes, ela6-section-bg); - } -} - -.o-ld-section-wrapper { - .o-simple-table { - td { - > ol, - > p, - > ul { - background-color: initial !important; - - :not(.o-ld-gls) { - background-color: initial !important; - } - } - } - - &.o-ld-section__optional { - td { - background-color: map-get($ub-ld-colorcodes, activity-optional); - padding: 20pt; - width: 100%; - } - - p { - padding: 0 !important; - text-align: center !important; - } - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_smp.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_smp.scss deleted file mode 100644 index 5424b3eb..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_smp.scss +++ /dev/null @@ -1,10 +0,0 @@ -// stylelint-disable declaration-no-important -.o-ld-smp { - @include e(bracket) { - border-bottom: 2pt solid map-get($ub-colorcodes, math-base) !important; - border-right: 2pt solid map-get($ub-colorcodes, math-base) !important; - border-top: 2pt solid map-get($ub-colorcodes, math-base) !important; - vertical-align: middle; - width: 8%; - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_tables.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_tables.scss deleted file mode 100644 index edd64670..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_tables.scss +++ /dev/null @@ -1,51 +0,0 @@ -// stylelint-disable declaration-no-important, max-nesting-depth -.cs-border-top--ela { - border-top: 4px solid map-get($ub-colorcodes, ela-base) !important; -} - -.cs-border-top--math-foundational { - border-top: 4px solid map-get($ub-ld-colorcodes, math-activity-foundational) !important; -} - -.cs-border-top--math { - border-top: 4px solid map-get($ub-colorcodes, math-base) !important; -} - -.cs-border-left--ela { - border-left: 4px solid map-get($ub-colorcodes, ela-base) !important; -} - -.cs-border-left--math { - border-left: 4px solid map-get($ub-colorcodes, math-base) !important; -} - -.cs-border-bottom--pd { - border-bottom: 4px solid map-get($ub-ld-colorcodes, pd-bg-dark) !important; -} - -.o-simple-table { - @include gdoc-clearfix; - - @include e(content) { - width: 90%; - } - - @include e(v-text) { - width: 10%; - } - - td, - tr { - border: 0; - } -} - -.o-native-table { - td, - th { - p { - padding: 0; - } - } -} -// stylelint-enable declaration-no-important, max-nesting-depth diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_title.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_title.scss deleted file mode 100644 index 5484ebcd..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/lesson/_title.scss +++ /dev/null @@ -1,27 +0,0 @@ -.o-ld-title { - @include e(title) { - width: 80%; - - @include m(h2) { - @include txt-gdoc-element(ld-activity-h2); - } - - @include m(h3) { - @include txt-gdoc-element(ld-activity-h3); - } - } - - @include e(time) { - font-style: italic; - vertical-align: bottom; - width: 20%; - - @include m(h2) { - @include txt-gdoc-element(ld-activity-h2time); - } - - @include m(h3) { - @include txt-gdoc-element(ld-activity-h3time); - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/gdoc/material/_header.scss b/app/assets/stylesheets/lcms/engine/print/gdoc/material/_header.scss deleted file mode 100644 index 0eeffdc7..00000000 --- a/app/assets/stylesheets/lcms/engine/print/gdoc/material/_header.scss +++ /dev/null @@ -1,12 +0,0 @@ -.o-m-header { - @include m(border-bottom) { - border-bottom: 1pt solid $ub-pdf-black !important; // stylelint-disable-line declaration-no-important - } - - @include m(gdoc) { - td, - td p { - padding: 0; - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/lesson/_base-typography.scss b/app/assets/stylesheets/lcms/engine/print/lesson/_base-typography.scss deleted file mode 100644 index 9f2802de..00000000 --- a/app/assets/stylesheets/lcms/engine/print/lesson/_base-typography.scss +++ /dev/null @@ -1,49 +0,0 @@ -p, -ol, -ul { - &, - * { - @include txt-element-strict(ld-base, $ub-pdf-typography); - orphans: 2; - widows: 2; - } - - // stylelint-disable max-nesting-depth - i { - .fa { - font: normal normal normal 14px/1 FontAwesome; - } - } - // stylelint-enable max-nesting-depth -} - -h1, -h2, -h3, -h4, -h5 { - @extend %u-pdf-nobreak; - padding-top: 0; -} - -h2 { - @include txt-pdf-element(ld-h2); -} - -h3 { - @include txt-pdf-element(ld-h3); -} - -h4 { - @include txt-pdf-element(ld-h4); -} - -h5 { - @include txt-pdf-element(ld-h5); -} - -// TODO: Quick fix, cg&lesson styles separated at pr 622 -// After merging with 622 should be removed -sup { - vertical-align: initial !important; // stylelint-disable-line declaration-no-important -} diff --git a/app/assets/stylesheets/lcms/engine/print/lesson/_callout.scss b/app/assets/stylesheets/lcms/engine/print/lesson/_callout.scss deleted file mode 100644 index 6f333c5c..00000000 --- a/app/assets/stylesheets/lcms/engine/print/lesson/_callout.scss +++ /dev/null @@ -1,7 +0,0 @@ -.o-ld-callout { - @include txt-pdf-element(pullquote); - - @include e(header) { - @include txt-pdf-element(ld-callout-header); - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/lesson/_def.scss b/app/assets/stylesheets/lcms/engine/print/lesson/_def.scss deleted file mode 100644 index 53cc59bb..00000000 --- a/app/assets/stylesheets/lcms/engine/print/lesson/_def.scss +++ /dev/null @@ -1,3 +0,0 @@ -.o-ld-dropdown { - display: none; -} diff --git a/app/assets/stylesheets/lcms/engine/print/lesson/_expand.scss b/app/assets/stylesheets/lcms/engine/print/lesson/_expand.scss deleted file mode 100644 index fc4e5dee..00000000 --- a/app/assets/stylesheets/lcms/engine/print/lesson/_expand.scss +++ /dev/null @@ -1,11 +0,0 @@ -.o-ld-expand { - @include e(content) { - @include m(hidden) { - display: block; - } - } - - @include e(toggler) { - display: none; - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/lesson/_group.scss b/app/assets/stylesheets/lcms/engine/print/lesson/_group.scss deleted file mode 100644 index 384f8065..00000000 --- a/app/assets/stylesheets/lcms/engine/print/lesson/_group.scss +++ /dev/null @@ -1,5 +0,0 @@ -.o-ld-group { - li > p { - margin-bottom: 0; - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/lesson/_image.scss b/app/assets/stylesheets/lcms/engine/print/lesson/_image.scss deleted file mode 100644 index 181eb082..00000000 --- a/app/assets/stylesheets/lcms/engine/print/lesson/_image.scss +++ /dev/null @@ -1,7 +0,0 @@ -.o-ld-image { - @include e(container) { - img { - width: 30%; - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/lesson/_materials.scss b/app/assets/stylesheets/lcms/engine/print/lesson/_materials.scss deleted file mode 100644 index 711f1f6f..00000000 --- a/app/assets/stylesheets/lcms/engine/print/lesson/_materials.scss +++ /dev/null @@ -1,13 +0,0 @@ -.o-ld-materials { - @extend %u-pdf-alwaysbreak; - - @include e(content) { - @include m(hidden) { - display: block; - } - } - - @include e(toggler) { - display: none !important; // stylelint-disable-line declaration-no-important - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/lesson/_optbreak.scss b/app/assets/stylesheets/lcms/engine/print/lesson/_optbreak.scss deleted file mode 100644 index 59c9fe34..00000000 --- a/app/assets/stylesheets/lcms/engine/print/lesson/_optbreak.scss +++ /dev/null @@ -1,5 +0,0 @@ -.o-ld-optbreak { - @include e(text) { - @include txt-pdf-element(ld-optbreak); - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/lesson/_pd.scss b/app/assets/stylesheets/lcms/engine/print/lesson/_pd.scss deleted file mode 100644 index dd9c3f04..00000000 --- a/app/assets/stylesheets/lcms/engine/print/lesson/_pd.scss +++ /dev/null @@ -1,44 +0,0 @@ -.o-ld-pd { - @include e(audio) { - background-color: map-get($ub-ld-colorcodes, pd-bg-dark); - } - - @include e(audio-placeholder) { - background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAFH0lEQVR4Ae2bA4wsSRjHd3O2Lzg9u7urn20rOttWeLYVnG3b9oVPG5xt2+v9/9J1vteZqcZ0J11JZWemu6o+s7apGpmPalSjGtWoRvOIESMGGWN2DoLgNGPC6/X5oSAIn2bymd/0+VTe4V3WlBpjIbFaGIaLhdSNQu5LzW47OzQ/0Fym5y8y+Wx/6/jbe1+wlj3YqzSIC+DN4GQQmM8jRMzX+nyTOL+37/vD4pAZOnTo6p43wtMe+2jdzVr/NXuwl/Y8RWs3LSziQm4dENf8SUB3aj4gJOZPnTp11SRSpD0WaM8Hoz3Nj5on6/e1C4W8uDtbgL0vILsE7O0ixsC0zxg+fPhgScId0RnmPZ03q+GIw10hfIEA6hJgr4gQE7OXtOGTdNar0ZnmXGBolJHbQLr6NDqqvxf16tVrzbzOHjdu3Fo691JrH54ElryR31QHtxhjWn0/3L1REuh5Zk8RoVWwrACm3DgfIR/+gu4XwPjOFSy/WiJskLnOW7FvzRp5ByK0GmOeytQmYPDQO983e7isz0EdiDvOywj5EbOxvBi8poIODKP1DrMyCHIMfv6VPK29i3eQlL5GnJBqsBQlKSGUndBU8OF54WSrCielGdv/RITnLppmQ4BKMJ+r87y7JAU/4hrT4n6n53mDykIAGzYjsScnTmltVvcA38tCAIatN3yWKJUmF7f6NL9sBAiC4Qsjlz18URLxv5F8nuCibASw0vuN1t7gXMaikkMxgy/pEMD0cnDBR0IAx3Nv0fzcqbxGXQ6gqeSUkwCsNftyrlN9guKkXTyspATAG/g2Xd+p7sW2etuOLpWVANQYKbRST3SJq6+nUsvnshLA4vGRzr3OBWhq9cvKTgBxf7lU4EGXhU9Tsy+/BJiXwKU0BPC8UX0UxOwgxHe385bsCVAAFbAV5suJ4V0CoZizV6ACRTeCvHNG0kgwxghe6+IGyQI7cCVZE8CGrT+6EyDeDXpecIpzIESvLmsCSM+3jM8FzLMJA6EdnUNhGpX1rOvXr98aAvgYrV0qrr7D1Of34ghAmU36/1sMAa51JPx+Npwf4JoMfUGXtp4MzPfN8zGI9IqJ269cyTqaouMd9f9WagLgkiAdDr8GsRrrBwcBtAMB0Nd1qev/aw21/kMSdJa/xZinURBZUCPBHnQlgB3NnhfOlDQc5/vh4byfoJK9iDMpjCQuiYFYjSL3aBwBBNTmzsA4lsQSd4rIpNBDCo01vHt0DAFezfGWyhBbFD0plU4wNzO4nFCD310XRP8H+fYcLzTAiLt15g+DBo3eJC1xOpnGiER4Uo0Eu0oE+1azjVhcHJmSI/enWPd9Qpot8bVpN8Fd2k/Fbo2Z10X0d1KHExG2enVJUQnwRzIlDzIjK8t6LuJFK7p4yAd7W1d7TqYXJHQIwUorlxKK0xD15mFvNJ/YbrvtVsnjiswKrqUUgQggDyyUvsaMGbN+npekIEJrI9UBsYfzID96tLPLc5eEv8Xul+bpHTgLg8fZiH0izie3CeY8LC83M7ickIefx9VZj3ROZjrv4CL/yPnvImzOJrwN77ZnvJOBq0seLBEx2tJWJ4VIMrEknSXWktWR2Nhi6Q/a98Q81c01dziZTMzq6Dd0aWlUUqKKqzHyjHdsJedWm893R3uZk2xsX4phuTecnPwGWtT/+oeJj7Dc5Al2ruC3v//DhCXg9Q5SVMjRTIuaLm2UXpvrIhUJn2bymbofzyhgUsPL/l9mqlGNalSjGtXoAQ3nPj+4yvM0AAAAAElFTkSuQmCC') - no-repeat 50%; - display: block; - height: 166px; - width: 100%; - } - - @include e(video) { - background-color: map-get($ub-ld-colorcodes, pd-bg-dark); - } - - @include e(video-placeholder) { - background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAQAAAAAYLlVAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAHdElNRQfhCAkRBgwevEQmAAACYUlEQVRo3u2ZMWhTQRjHf685jLQdNKFSgyC1g2Bpl4ehuIrQUenmIpTiICh0E0To5OAkRWxDTSFRUjoIti7qKOIgxqFKB8GICMWWJjq00ieR53CpjaDy7t31zuH9p7xHwv933+X77rs7SORYnu8YQEgMshwijWfJNSRgnTqhBMhwnlGOss/iwL/zkQdUaAiyTDLOfovmUn0Mc5zJVO4SE3RatwcQDPDV85cZdGIv9cbzt0kDsMkKgSXbNCfoBiAQLXuoMc6qlTwIyXGfIYkifr1uUqduLQLNnY+i7bUHVQvuPu1x7rA05r8qAUgAnAMIta//3j2YSFqlCLTse/Hpkc/67Yz6FPRT4BFlztJlAkId4BQjHGaEIlPkSbVFxhJAZ6uQZhijwjX69OKglwX9XKfCBQ7Gh9BNQ8Ewt5nltFzW1SFM1IFuRilzgwE5OWoIpgpRjgnmuUyvahzMVUKPQW5S5pxaepotxWnOcJcp8tErrPm1IMMY81yhK9q/YW8Wo2NcJe8qAlKRu2vF1TCiatzhpSuABg8p8JpmtOXaLEDAM6Z5ylb0n5gDCHlLkQU+y8eozYopgFUWKLJCqGJuCmCTJ0zzfGdrq9ao6QI0ecUMS3yJY64PUKPEPT7ENY8H8E3OMw0WmaHKj/jm8QBe8JiTLFPYTTed9lwd4D0XOcIn1vXNlQGqcn1bY82MeYwImD/AcL43TAASAOcA7WkY6u1zFRT+CUCQJbB0VJvd9XV+WO38uL6DkkoLaVhblFK5dxxgaI/2B//WNnPc8nzHl1by3tDxtR2EbLBhcfyJ/if9BNPEjU+WaHXnAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA4LTA5VDE3OjA2OjEyKzAyOjAw1yQ8gQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wOC0wOVQxNzowNjoxMiswMjowMKZ5hD0AAAAASUVORK5CYII=') - no-repeat 50%; - display: block; - height: 166px; - width: 100%; - } - - .c-cg-media, - .c-cg-video { - display: none; - } -} - -.o-ld-cg { - @include e(thumbnail) { - background: url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/7STUUGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAA8cAVoAAxslRxwCAAACAAAAOEJJTQQlAAAAAAAQzc/6fajHvgkFcHaurwXDTjhCSU0EOgAAAAAA5QAAABAAAAABAAAAAAALcHJpbnRPdXRwdXQAAAAFAAAAAFBzdFNib29sAQAAAABJbnRlZW51bQAAAABJbnRlAAAAAENscm0AAAAPcHJpbnRTaXh0ZWVuQml0Ym9vbAAAAAALcHJpbnRlck5hbWVURVhUAAAAAQAAAAAAD3ByaW50UHJvb2ZTZXR1cE9iamMAAAAMAFAAcgBvAG8AZgAgAFMAZQB0AHUAcAAAAAAACnByb29mU2V0dXAAAAABAAAAAEJsdG5lbnVtAAAADGJ1aWx0aW5Qcm9vZgAAAAlwcm9vZkNNWUsAOEJJTQQ7AAAAAAItAAAAEAAAAAEAAAAAABJwcmludE91dHB1dE9wdGlvbnMAAAAXAAAAAENwdG5ib29sAAAAAABDbGJyYm9vbAAAAAAAUmdzTWJvb2wAAAAAAENybkNib29sAAAAAABDbnRDYm9vbAAAAAAATGJsc2Jvb2wAAAAAAE5ndHZib29sAAAAAABFbWxEYm9vbAAAAAAASW50cmJvb2wAAAAAAEJja2dPYmpjAAAAAQAAAAAAAFJHQkMAAAADAAAAAFJkICBkb3ViQG/gAAAAAAAAAAAAR3JuIGRvdWJAb+AAAAAAAAAAAABCbCAgZG91YkBv4AAAAAAAAAAAAEJyZFRVbnRGI1JsdAAAAAAAAAAAAAAAAEJsZCBVbnRGI1JsdAAAAAAAAAAAAAAAAFJzbHRVbnRGI1B4bEBSAAAAAAAAAAAACnZlY3RvckRhdGFib29sAQAAAABQZ1BzZW51bQAAAABQZ1BzAAAAAFBnUEMAAAAATGVmdFVudEYjUmx0AAAAAAAAAAAAAAAAVG9wIFVudEYjUmx0AAAAAAAAAAAAAAAAU2NsIFVudEYjUHJjQFkAAAAAAAAAAAAQY3JvcFdoZW5QcmludGluZ2Jvb2wAAAAADmNyb3BSZWN0Qm90dG9tbG9uZwAAAAAAAAAMY3JvcFJlY3RMZWZ0bG9uZwAAAAAAAAANY3JvcFJlY3RSaWdodGxvbmcAAAAAAAAAC2Nyb3BSZWN0VG9wbG9uZwAAAAAAOEJJTQPtAAAAAAAQAEgAAAABAAEASAAAAAEAAThCSU0EJgAAAAAADgAAAAAAAAAAAAA/gAAAOEJJTQQNAAAAAAAEAAAAHjhCSU0EGQAAAAAABAAAAB44QklNA/MAAAAAAAkAAAAAAAAAAAEAOEJJTScQAAAAAAAKAAEAAAAAAAAAAThCSU0D9QAAAAAASAAvZmYAAQBsZmYABgAAAAAAAQAvZmYAAQChmZoABgAAAAAAAQAyAAAAAQBaAAAABgAAAAAAAQA1AAAAAQAtAAAABgAAAAAAAThCSU0D+AAAAAAAcAAA/////////////////////////////wPoAAAAAP////////////////////////////8D6AAAAAD/////////////////////////////A+gAAAAA/////////////////////////////wPoAAA4QklNBAgAAAAAABAAAAABAAACQAAAAkAAAAAAOEJJTQQeAAAAAAAEAAAAADhCSU0EGgAAAAADbwAAAAYAAAAAAAAAAAAAB10AAAddAAAAHQBDAEcAKwAtACsARQBMAEEAKwBLAC0AMgArAC0AKwAxAC4AMAA1AHgAMQAtAG0AaQBuACAAKAAxACkAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAB10AAAddAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAEAAAAAAABudWxsAAAAAgAAAAZib3VuZHNPYmpjAAAAAQAAAAAAAFJjdDEAAAAEAAAAAFRvcCBsb25nAAAAAAAAAABMZWZ0bG9uZwAAAAAAAAAAQnRvbWxvbmcAAAddAAAAAFJnaHRsb25nAAAHXQAAAAZzbGljZXNWbExzAAAAAU9iamMAAAABAAAAAAAFc2xpY2UAAAASAAAAB3NsaWNlSURsb25nAAAAAAAAAAdncm91cElEbG9uZwAAAAAAAAAGb3JpZ2luZW51bQAAAAxFU2xpY2VPcmlnaW4AAAANYXV0b0dlbmVyYXRlZAAAAABUeXBlZW51bQAAAApFU2xpY2VUeXBlAAAAAEltZyAAAAAGYm91bmRzT2JqYwAAAAEAAAAAAABSY3QxAAAABAAAAABUb3AgbG9uZwAAAAAAAAAATGVmdGxvbmcAAAAAAAAA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ZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9IjY4NDc0ODY5RTZCM0ExMUEwN0I1ODExN0Y1NzlCNkNFIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjAyODAxMTc0MDcyMDY4MTE4MjJBQUU3NzIyRTUyNTYxIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9IjY4NDc0ODY5RTZCM0ExMUEwN0I1ODExN0Y1NzlCNkNFIiBkYzpmb3JtYXQ9ImltYWdlL2pwZWciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHhtcDpDcmVhdGVEYXRlPSIyMDE3LTA4LTAzVDE1OjU2OjAyKzAzOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxNy0wOC0wM1QxNTo1Nzo0NiswMzowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxNy0wOC0wM1QxNTo1Nzo0NiswMzowMCI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjAyODAxMTc0MDcyMDY4MTE4MjJBQUU3NzIyRTUyNTYxIiBzdEV2dDp3aGVuPSIyMDE3LTA4LTAzVDE1OjU3OjQ2KzAzOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDwvcmRmOlNlcT4gPC94bXBNTTpIaXN0b3J5PiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8P3hwYWNrZXQgZW5kPSJ3Ij8+/9sAQwADAgIDAgIDAwIDAwMDAwQHBQQEBAQJBgcFBwoJCwsKCQoKDA0RDgwMEAwKCg4UDxAREhMTEwsOFBYUEhYREhMS/9sAQwEDAwMEBAQIBQUIEgwKDBISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhIS/8AAEQgB2AHYAwERAAIRAQMRAf/EAB0AAAAGAwEAAAAAAAAAAAAAAAECAwQFBgAHCAn/xABWEAACAQMCAwUFBQQFBwoEBAcBAgMABBEFIQYSMQcTQVFhFCJxgZEIIzKhsRVCwdEWM1JichckQ4KSsuEJNFNjoqPC0vDxJSZkkxhERXNUdHWDhJSz/8QAHAEAAgMBAQEBAAAAAAAAAAAAAQIAAwQFBgcI/8QAQBEAAgIBAwMCAwUHAwQBAgcAAAECEQMEEiEFMUETUQYiYRQVMnGRByNCUoGhsSQzwRZD0eGCNGIXJXKisvDx/9oADAMBAAIRAxEAPwDrTwGPGuKbaMJ2NChvAQnpkbUpKNc8aEnVTj+ztVqFoovaE4fs34kznI0yf/dNW4uXQkzz/wCFBjueh99D+YrazL5PXbR4+fh2zU7A2SA/7AqqSsuf4WN3XvuGbbnBZeVBJt4Cksz8vGqD6UpXUIXZfelsF5j6q3T86ncaEGpcieqafPPrbPBIid/aiMh+mA2SR6iiVZoN5LXYbFHs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') - no-repeat no-repeat; - } -} - -.o-ld-pd-pdf { - @include e(overlay-icon) { - background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iNDlweCIgaGVpZ2h0PSI0OHB4IiB2aWV3Qm94PSIwIDAgNDkgNDgiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDQ2ICg0NDQyMykgLSBodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2ggLS0+CiAgICA8dGl0bGU+RmlsbCAxPC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+CiAgICAgICAgPHBhdGggZD0iTTE2Mi45NDM3ODYsMjU1LjQwNDk1NyBDMTYyLjAxNzUyMiwyNTQuNDg1ODUgMTU5Ljk0Njk4OCwyNTQuMDA1ODEyIDE1Ni44MTQwMDgsMjUzLjk2NDYxNCBDMTU0LjY4OTAwMiwyNTMuOTM3MTg2IDE1Mi4xNDE3NzQsMjU0LjEyOTI5MyAxNDkuNDQ0NjksMjU0LjQ5OTUwNyBDMTQ4LjI0NTgzNCwyNTMuODAwMDQ5IDE0Ny4wMDYyOTUsMjUzLjA0NTUwOCAxNDYuMDI1NTU4LDI1Mi4xMjY1MTYgQzE0My40MTAxODMsMjQ5LjY1NzM1NyAxNDEuMjMwNzAzLDI0Ni4yNDE3NzkgMTM5Ljg2ODU0MywyNDIuNDgzMTg2IEMxMzkuOTUwMjUyLDI0Mi4xMjY1MTUgMTQwLjAzMTk2MSwyNDEuODI0ODEzIDE0MC4xMDAxMDksMjQxLjUwOTM0IEMxNDAuMTAwMTA5LDI0MS41MDkzNCAxNDEuNTcxMjE1LDIzMy4wNzMxNjIgMTQxLjE3NjIzMSwyMzAuMjIwMDE5IEMxNDEuMTIxNzU4LDIyOS44MjIxNDkgMTQxLjA5NDQwOCwyMjkuNzEyNDM5IDE0MC45ODU0NjIsMjI5LjQxMDYyMiBMMTQwLjg2Mjk1NiwyMjkuMDY3NzIyIEMxNDAuNDY3ODU3LDIyOC4xNDAzNTMgMTM5LjY3Nzg4OSwyMjcuMTUyNzM1IDEzOC40MzgyMzUsMjI3LjIwNjIxMyBMMTM3LjcxNjMsMjI3LjE4MjkxNyBMMTM3LjcwMjczOSwyMjcuMTgyOTE3IEMxMzYuMzI2OTA0LDIyNy4xODI5MTcgMTM1LjE5NjMxLDIyNy44OTIxMjkgMTM0LjkxMDI3MSwyMjguOTQ0MjQxIEMxMzQuMDExMjQzLDIzMi4yNzc1MzYgMTM0LjkzNzUwNywyMzcuMjQzMTY3IDEzNi42MTI5NDMsMjQzLjY3NjY4MiBMMTM2LjE3NzA0NywyNDQuNzMyODExIEMxMzQuOTc4NDE5LDI0Ny42NjgzNTEgMTMzLjQ3OTk2MywyNTAuNjMxMzE4IDEzMi4xNTg2LDI1My4yMzc2MTQgTDEzMS45ODE2MjEsMjUzLjU4MDYzIEMxMzAuNTkyMTEsMjU2LjMyNDA2MyAxMjkuMzI1MzM0LDI1OC42NTU5NyAxMjguMTgxMDY1LDI2MC42MzEyMDUgTDEyNi45OTU5OTgsMjYxLjI2MjI2NiBDMTI2LjkxNDI4OSwyNjEuMzE3MTIxIDEyNC44ODQ2NjcsMjYyLjM4NzAyMSAxMjQuNDA3ODU5LDI2Mi42NzUxODEgQzEyMC4zNzU4NTEsMjY1LjEwMzE0MSAxMTcuNjk1MDY0LDI2Ny44NjAyMzEgMTE3LjI1MDk2MywyNzAuMDU1IEMxMTcuMTA5MzExLDI3MC43NDA5MTYgMTE3LjIxNTUyMSwyNzEuNjMyNDggMTE3LjkzMzQ2NywyNzIuMDU3NjYzIEwxMTkuMDc3NzM2LDI3Mi42MzM4NjkgQzExOS41NzIyMDgsMjcyLjg4MDcxNiAxMjAuMTAzMzc0LDI3My4wMDQxOTcgMTIwLjYzNDY1NCwyNzMuMDA0MTk3IEMxMjMuNTA4ODMxLDI3My4wMDQxOTcgMTI2Ljg0NjE0MSwyNjkuNDEwMjgzIDEzMS40MzY2NjUsMjYxLjMzMDg5MiBDMTM2Ljc0OTIzOCwyNTkuNTg4NzMyIDE0Mi43OTcxOTMsMjU4LjEzNDYxOSAxNDguMDk2MDkxLDI1Ny4zMzkxMDggQzE1Mi4xMjgwOTksMjU5LjYyOTgxNiAxNTcuMDg2NDg2LDI2MS4yMjEwNjcgMTYwLjIxOTQ2NiwyNjEuMjIxMDY3IEMxNjAuNzc3ODY4LDI2MS4yMjEwNjcgMTYxLjI1NDY3NSwyNjEuMTY2MjEyIDE2MS42NDk3NzQsMjYxLjA1NjUwMiBDMTYyLjI0OTA4OCwyNjAuOTA1NTk0IDE2Mi43NTMwMTcsMjYwLjU2MjU3OSAxNjMuMDY2NDA3LDI2MC4wODI1NDEgQzE2My42NjU3MjEsMjU5LjE2MzQzNSAxNjMuODAxOTAyLDI1Ny45MDE1NDMgMTYzLjYyNDkyMywyNTYuNTk4MzM3IEMxNjMuNTg0MDEyLDI1Ni4yMTQyMzggMTYzLjI3MDczNiwyNTUuNzM0MDg2IDE2Mi45NDM3ODYsMjU1LjQwNDk1NyBNMTIwLjAwNzk4OSwyNzAuODA5NDI3IEMxMjAuNTM5MjcsMjY5LjM2OTE5OSAxMjIuNjA5ODAzLDI2Ni41MTU5NDIgMTI1LjY3NDYzNSwyNjMuOTc4MjcyIEMxMjUuODY1NDA0LDI2My44MjczNjMgMTI2LjM0MjA5NywyNjMuMzc0NjM4IDEyNi43NzgxMDcsMjYyLjk2MzExMiBDMTIzLjU3Njk3OSwyNjguMTIwOTY0IDEyMS40MjQ3MzYsMjcwLjE2NDcxIDEyMC4wMDc5ODksMjcwLjgwOTQyNyBNMTM4LjE3OTQzMywyMjguNjY5ODUyIEMxMzkuMTA1Njk3LDIyOC42Njk4NTIgMTM5LjYzNjk3NywyMzEuMDEwMDIxIDEzOS42Nzc4ODksMjMzLjIxODU2MiBDMTM5LjcxODY4NiwyMzUuNDEzMzMxIDEzOS4yMTQ2NDIsMjM2Ljk0OTcyNyAxMzguNTc0NTMxLDIzOC4xMDE5MSBDMTM4LjA0MzI1MSwyMzYuNDAwOTQ5IDEzNy43OTgwMSwyMzMuNzM5Nzk4IDEzNy43OTgwMSwyMzEuOTk3NzU0IEMxMzcuNzk4MDEsMjMxLjk5Nzc1NCAxMzcuNzU3MjEyLDIyOC42Njk4NTIgMTM4LjE3OTQzMywyMjguNjY5ODUyIE0xMzIuNzU4MDI4LDI1OC42OTcwNTMgQzEzMy40MTE4MTUsMjU3LjU0NDc1NiAxMzQuMDc5Mjc3LDI1Ni4zMjQwNjMgMTM0Ljc2MDQxNCwyNTUuMDIwODU4IEMxMzYuNDIyMTc0LDI1MS44NTIxMjcgMTM3LjQ4NDczNCwyNDkuMzU1NjU1IDEzOC4yNjExNDIsMjQ3LjMxMTc5NCBDMTM5LjgyNzYzMiwyNTAuMTc4NTkzIDE0MS43NzU1NDUsMjUyLjYwNjU1NCAxNDQuMDUwNDA4LDI1NC41NTQ0NzYgQzE0NC4zMzY1NjEsMjU0LjgwMTMyMyAxNDQuNjM2MTYxLDI1NS4wMzQ1MTQgMTQ0Ljk2MzExMSwyNTUuMjgxNDc2IEMxNDAuMzE4LDI1Ni4yMTQyMzggMTM2LjI5OTY2NywyNTcuMzM5MTA4IDEzMi43NTgwMjgsMjU4LjY5NzA1MyBNMTYyLjA1ODMxOSwyNTguNDM2NDM1IEMxNjEuNzcyMzk0LDI1OC42MTQ3NzEgMTYwLjk1NTA3NSwyNTguNzI0NTk2IDE2MC40MzczNTYsMjU4LjcyNDU5NiBDMTU4Ljc0ODM2LDI1OC43MjQ1OTYgMTU2LjY3NzgyNiwyNTcuOTQyNjI3IDE1My43NDkwNjIsMjU2LjY4MDYyIEMxNTQuODc5NjU2LDI1Ni41OTgzMzcgMTU1LjkwMTMwNSwyNTYuNTU3MTM5IDE1Ni44Mjc1NjksMjU2LjU1NzEzOSBDMTU4LjUxNjY4LDI1Ni41NTcxMzkgMTU5LjAwNzA0OCwyNTYuNTU3MTM5IDE2MC42Njg5MjMsMjU2Ljk4MjQzNiBDMTYyLjMxNzIzNiwyNTcuMzkzOTYzIDE2Mi4zMzA3OTcsMjU4LjI1ODEgMTYyLjA1ODMxOSwyNTguNDM2NDM1IiBpZD0icGF0aC0xIj48L3BhdGg+CiAgICAgICAgPGZpbHRlciB4PSItMi4yJSIgeT0iLTIuMiUiIHdpZHRoPSIxMDguNiUiIGhlaWdodD0iMTA4LjclIiBmaWx0ZXJVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIGlkPSJmaWx0ZXItMiI+CiAgICAgICAgICAgIDxmZU9mZnNldCBkeD0iMSIgZHk9IjEiIGluPSJTb3VyY2VBbHBoYSIgcmVzdWx0PSJzaGFkb3dPZmZzZXRPdXRlcjEiPjwvZmVPZmZzZXQ+CiAgICAgICAgICAgIDxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjAuNSIgaW49InNoYWRvd09mZnNldE91dGVyMSIgcmVzdWx0PSJzaGFkb3dCbHVyT3V0ZXIxIj48L2ZlR2F1c3NpYW5CbHVyPgogICAgICAgICAgICA8ZmVDb2xvck1hdHJpeCB2YWx1ZXM9IjAgMCAwIDAgMCAgIDAgMCAwIDAgMCAgIDAgMCAwIDAgMCAgMCAwIDAgMC4xIDAiIHR5cGU9Im1hdHJpeCIgaW49InNoYWRvd0JsdXJPdXRlcjEiPjwvZmVDb2xvck1hdHJpeD4KICAgICAgICA8L2ZpbHRlcj4KICAgIDwvZGVmcz4KICAgIDxnIGlkPSJGRi1EZXNpZ24tTGlicmFyeSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgb3BhY2l0eT0iMC44ODk5OTk5ODYiPgogICAgICAgIDxnIGlkPSJQRC1SZXNvdXJjZXMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzYuMDAwMDAwLCAtMjAxNS4wMDAwMDApIj4KICAgICAgICAgICAgPGcgaWQ9IlBERi0iIHRyYW5zZm9ybT0idHJhbnNsYXRlKDExOS4wMDAwMDAsIDE3ODguMDAwMDAwKSI+CiAgICAgICAgICAgICAgICA8ZyBpZD0iR3JvdXAtNyI+CiAgICAgICAgICAgICAgICAgICAgPGcgaWQ9IkZpbGwtMSI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx1c2UgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMSIgZmlsdGVyPSJ1cmwoI2ZpbHRlci0yKSIgeGxpbms6aHJlZj0iI3BhdGgtMSI+PC91c2U+CiAgICAgICAgICAgICAgICAgICAgICAgIDx1c2UgZmlsbD0iI0ZGRkZGRiIgZmlsbC1ydWxlPSJldmVub2RkIiB4bGluazpocmVmPSIjcGF0aC0xIj48L3VzZT4KICAgICAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==') - no-repeat 50%; - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/lesson/_pv.scss b/app/assets/stylesheets/lcms/engine/print/lesson/_pv.scss deleted file mode 100644 index dde4afb0..00000000 --- a/app/assets/stylesheets/lcms/engine/print/lesson/_pv.scss +++ /dev/null @@ -1,16 +0,0 @@ -.o-ld-pv { - @extend %u-pdf-alwaysbreak; - page-break-after: always; - - @include e(header) { - display: none; - } - - @include e(row) { - @for $i from 1 through 10 { - @include m('#{$i}') { - height: $pdf-fulltable-height / $i; - } - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/lesson/_section.scss b/app/assets/stylesheets/lcms/engine/print/lesson/_section.scss deleted file mode 100644 index 51c2eecb..00000000 --- a/app/assets/stylesheets/lcms/engine/print/lesson/_section.scss +++ /dev/null @@ -1,6 +0,0 @@ -.o-ld-section { - @include e(quote) { - @extend %u-pdf-nobreak; - @include txt-pdf-element(pullquote); - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/lesson/_smp.scss b/app/assets/stylesheets/lcms/engine/print/lesson/_smp.scss deleted file mode 100644 index 871c47a4..00000000 --- a/app/assets/stylesheets/lcms/engine/print/lesson/_smp.scss +++ /dev/null @@ -1,6 +0,0 @@ -.o-ld-smp-figure { - @include e(name) { - @include txt-pdf-element(ld-smp); - margin: 0; - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/lesson/_title.scss b/app/assets/stylesheets/lcms/engine/print/lesson/_title.scss deleted file mode 100644 index 22dfb0cb..00000000 --- a/app/assets/stylesheets/lcms/engine/print/lesson/_title.scss +++ /dev/null @@ -1,26 +0,0 @@ -.o-ld-title { - @include e(title) { - @include m(h2) { - @include txt-pdf-element(ld-h2); - flex: 1 1 0; - } - @include m(h3) { - @include txt-pdf-element(ld-h3); - flex: 1 1 0; - } - } - - @include e(time) { - font-style: italic; - - @include m(h2) { - @include txt-pdf-element(ld-activity-h2time); - @include flex-grid-column(shrink, 0); - white-space: nowrap; - } - @include m(h3) { - @include txt-pdf-element(ld-activity-h3time); - @include flex-grid-column(shrink, 0); - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/material/_content.scss b/app/assets/stylesheets/lcms/engine/print/material/_content.scss deleted file mode 100644 index ee2ecb7c..00000000 --- a/app/assets/stylesheets/lcms/engine/print/material/_content.scss +++ /dev/null @@ -1,19 +0,0 @@ -.o-m-content { - @include e(base) { - @include m(padded) { - padding-left: 3rem; - } - } - - @include e(vertical-text) { - @extend %u-txt--vertical; - font-size: 80%; - } -} - -.u-txt--vertical, -%u-txt--vertical { - line-height: normal; - transform: rotate(-90deg) translate(-100%, 0); - transform-origin: 0 0; -} diff --git a/app/assets/stylesheets/lcms/engine/print/material/_header.scss b/app/assets/stylesheets/lcms/engine/print/material/_header.scss deleted file mode 100644 index 6f57a2b5..00000000 --- a/app/assets/stylesheets/lcms/engine/print/material/_header.scss +++ /dev/null @@ -1,17 +0,0 @@ -.o-m-header { - border-bottom: 1px solid $ub-pdf-black; - - @include e(title) { - width: 10%; - } - @include e(name-value) { - width: 50%; - } - @include e(name-date) { - width: 30%; - } - - td { - padding: 0 0 5px; - } -} diff --git a/app/assets/stylesheets/lcms/engine/print/material/_list.scss b/app/assets/stylesheets/lcms/engine/print/material/_list.scss deleted file mode 100644 index cebc5197..00000000 --- a/app/assets/stylesheets/lcms/engine/print/material/_list.scss +++ /dev/null @@ -1,9 +0,0 @@ -.o-m-list { - list-style-type: none; - margin-left: 0; - - @include e(wrap) { - @include add-column-padding; - @include add-gutter(padding, bottom, 2); - } -} diff --git a/app/assets/stylesheets/lcms/engine/themes/_material.scss b/app/assets/stylesheets/lcms/engine/themes/_material.scss deleted file mode 100644 index 8132075a..00000000 --- a/app/assets/stylesheets/lcms/engine/themes/_material.scss +++ /dev/null @@ -1,89 +0,0 @@ -$ub-material-radius: 2px; -$ub-material-card-margin: 2px; - -/// https://css-tricks.com/snippets/sass/material-shadows-mixin/ -/// Gives a card depth effect. -/// @param {Number} $depth - depth level (between 1 and 5) -/// @link http://www.google.com/design/spec/layout/layout-principles.html#layout-principles-dimensionality Google Design -/// @requires {function} top-shadow -/// @requires {function} bottom-shadow -@mixin card($depth) { - @if $depth < 1 { - box-shadow: none; - } @else if $depth > 5 { - @warn 'Invalid $depth `#{$depth}` for mixin `card`.'; - } @else { - box-shadow: bottom-shadow($depth), top-shadow($depth); - } -} -/// Computes a top-shadow for a card effect. -/// @param {Number} $depth - depth level -/// @return {List} -@function top-shadow($depth) { - $primary-offset: nth(1.5 3 10 14 19, $depth) * 1px; - $blur: nth(1.5 3 10 14 19, $depth) * 4px; - $color: rgba(#000, nth(0.12 0.16 0.19 0.25 3, $depth)); - - @return 0 $primary-offset $blur $color; -} - -/// Computes a bottom-shadow for a card effect. -/// @param {Number} $depth - depth level -/// @return {List} -@function bottom-shadow($depth) { - $primary-offset: nth(1.5 3 6 10 15, $depth) * 1px; - $blur: nth(1 3 3 5 6, $depth) * 4px; - $color: rgba(#000, nth(0.24 0.23 0.23 0.22 0.22, $depth)); - - @return 0 $primary-offset $blur $color; -} - -%material-card { - //box-shadow: 0 $ub-material-card-margin $ub-material-card-margin rgba(0, 0, 0, .12); - border-radius: $ub-material-radius; - box-shadow: 1px 2px 8px rgba(0, 0, 0, 0.08); - margin-bottom: $ub-material-card-margin; -} - -%map-transition { - transition-duration: 0.4s; - transition-property: width, height, min-height, padding; - transition-timing-function: ease; -} - -%btn-transition { - transition-duration: 0.4s; - transition-property: min-width; - transition-timing-function: ease-out; -} - -%btn-shadow { - //@include card(1); - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.08); -} - -%tooltip-shadow { - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.08); -} - -@mixin base-shadow { - box-shadow: 1px 2px 8px rgba(0, 0, 0, 0.08); -} - -%popup-shadow { - box-shadow: 1px 2px 8px rgba(0, 0, 0, 0.35); -} - -%base-shadow { - @include base-shadow; -} - -// fadeIn transition -.m-fadeIn-enter { - opacity: 0.01; -} - -.m-fadeIn-enter.m-fadeIn-enter-active { - opacity: 1; - transition: opacity 400ms ease-in; -} diff --git a/app/assets/stylesheets/lcms/engine/themes/_settings.scss b/app/assets/stylesheets/lcms/engine/themes/_settings.scss deleted file mode 100644 index d34b6cbf..00000000 --- a/app/assets/stylesheets/lcms/engine/themes/_settings.scss +++ /dev/null @@ -1,878 +0,0 @@ -// Foundation for Sites Settings -// ----------------------------- -// -// Table of Contents: -// -// 1. Global -// 2. Breakpoints -// 3. The Grid -// 4. Base Typography -// 5. Typography Helpers -// 6. Abide -// 7. Accordion -// 8. Accordion Menu -// 9. Badge -// 10. Breadcrumbs -// 11. Button -// 12. Button Group -// 13. Callout -// 14. Card -// 15. Close Button -// 16. Drilldown -// 17. Dropdown -// 18. Dropdown Menu -// 19. Flexbox Utilities -// 20. Forms -// 21. Label -// 22. Media Object -// 23. Menu -// 24. Meter -// 25. Off-canvas -// 26. Orbit -// 27. Pagination -// 28. Progress Bar -// 29. Prototype Arrow -// 30. Prototype Border-Box -// 31. Prototype Border-None -// 32. Prototype Bordered -// 33. Prototype Display -// 34. Prototype Font-Styling -// 35. Prototype List-Style-Type -// 36. Prototype Overflow -// 37. Prototype Position -// 38. Prototype Rounded -// 39. Prototype Separator -// 40. Prototype Shadow -// 41. Prototype Sizing -// 42. Prototype Spacing -// 43. Prototype Text-Decoration -// 44. Prototype Text-Transformation -// 45. Prototype Text-Utilities -// 46. Responsive Embed -// 47. Reveal -// 48. Slider -// 49. Switch -// 50. Table -// 51. Tabs -// 52. Thumbnail -// 53. Title Bar -// 54. Tooltip -// 55. Top Bar -// 56. Xy Grid - -@import 'util/util'; - -// 1. Global -// --------- - -$global-font-size: 100%; -$global-width: rem-calc(1230); -$global-lineheight: 1.4; -$foundation-palette: ( - primary: $ub-primary, - secondary: $ub-secondary, - success: #3adb76, - warning: #ffae00, - alert: #ec5840, -); -$light-gray: #e6e6e6; -$medium-gray: #cacaca; -$dark-gray: #8a8a8a; -$black: #0a0a0a; -$white: $ub-white; -$body-background: $ub-page-bg; -$body-font-color: $ub-txt; -$body-font-family: $ub-sanserif; -$body-antialiased: true; -$global-margin: $ub-base-margin; -$global-padding: $ub-base-padding; -$global-position: 1rem; -$global-weight-normal: normal; -$global-weight-bold: bold; -$global-radius: 2px; -$global-menu-padding: 0.7rem 1rem; -$global-menu-nested-margin: 1rem; -$global-text-direction: ltr; -$global-flexbox: true; -$global-prototype-breakpoints: false; -$global-button-cursor: auto; -$global-color-pick-contrast-tolerance: 0; -$print-transparent-backgrounds: true; - -@include add-foundation-colors; -$print-hrefs: true; - -// 2. Breakpoints -// -------------- - -$breakpoints: ( - small: 0, - medium: 640px, - ipad: 768px, - large: 1024px, - xlarge: 1230px, - xxlarge: 1440px, -); -$print-breakpoint: large; -$breakpoint-classes: (small medium ipad large xlarge); - -// 3. The Grid -// ----------- - -$grid-row-width: $global-width; -$grid-column-count: 12; -$grid-column-gutter: ( - small: 20px, - medium: 30px, -); -$grid-column-2xgutter: ( - small: 40px, - medium: 60px, -); -$grid-column-4xgutter: ( - small: 2 * 40px, - medium: 2 * 60px, -); -$grid-column-align-edge: true; -$grid-column-alias: 'columns'; -$block-grid-max: 8; - -// 4. Base Typography -// ------------------ - -$header-font-family: $ub-serif; -$header-font-weight: $global-weight-normal; -$header-font-style: normal; -$font-family-monospace: Consolas, 'Liberation Mono', Courier, monospace; -$header-color: inherit; -$header-lineheight: 1.4; -$header-margin-bottom: 0.5rem; -$header-styles: ( - small: ( - 'h1': ('font-size': 26), - 'h2': ('font-size': 20), - 'h3': ('font-size': 21), - 'h4': ('font-size': 18), - 'h5': ('font-size': 18), - 'h6': ('font-size': 18), - ), - medium: ( - 'h1': ('font-size': 34), - 'h2': ('font-size': 22), - 'h3': ('font-size': 26), - 'h4': ('font-size': 21), - 'h5': ('font-size': 21), - 'h6': ('font-size': 18), - ), -); -$header-text-rendering: optimizeLegibility; -$small-font-size: 80%; -$header-small-font-color: $medium-gray; -$paragraph-lineheight: 1.6; -$paragraph-margin-bottom: 1rem; -$paragraph-text-rendering: optimizeLegibility; -$code-color: $black; -$code-font-family: $font-family-monospace; -$code-font-weight: $global-weight-normal; -$code-background: $light-gray; -$code-border: 1px solid $medium-gray; -$code-padding: rem-calc(2 5 1); -$anchor-color: $primary-color; -$anchor-color-hover: scale-color($anchor-color, $lightness: -14%); -$anchor-text-decoration: none; -$anchor-text-decoration-hover: none; -$hr-width: $global-width; -$hr-border: 1px solid $medium-gray; -$hr-margin: rem-calc(20) auto; -$list-lineheight: $paragraph-lineheight; -$list-margin-bottom: $paragraph-margin-bottom; -$list-style-type: disc; -$list-style-position: outside; -$list-side-margin: 1.25rem; -$list-nested-side-margin: 1.25rem; -$defnlist-margin-bottom: 1rem; -$defnlist-term-weight: $global-weight-bold; -$defnlist-term-margin-bottom: 0.3rem; -$blockquote-color: $dark-gray; -$blockquote-padding: rem-calc(9 20 0 19); -$blockquote-border: 1px solid $medium-gray; -$cite-font-size: rem-calc(13); -$cite-color: $dark-gray; -$cite-pseudo-content: '\2014 \0020'; -$keystroke-font: $font-family-monospace; -$keystroke-color: $black; -$keystroke-background: $light-gray; -$keystroke-padding: rem-calc(2 4 0); -$keystroke-radius: $global-radius; -$abbr-underline: 1px dotted $black; - -// 5. Typography Helpers -// --------------------- - -$lead-font-size: $global-font-size * 1.25; -$lead-lineheight: 1.6; -$subheader-lineheight: 1.4; -$subheader-color: $dark-gray; -$subheader-font-weight: $global-weight-normal; -$subheader-margin-top: 0.2rem; -$subheader-margin-bottom: 0.5rem; -$stat-font-size: 2.5rem; - -// 6. Abide -// -------- - -$abide-inputs: true; -$abide-labels: true; -$input-background-invalid: get-color(alert); -$form-label-color-invalid: get-color(alert); -$input-error-color: get-color(alert); -$input-error-font-size: rem-calc(12); -$input-error-font-weight: $global-weight-bold; - -// 7. Accordion -// ------------ - -$accordion-background: $white; -$accordion-plusminus: true; -$accordion-title-font-size: rem-calc(12); -$accordion-item-color: $primary-color; -$accordion-item-background-hover: $light-gray; -$accordion-item-padding: 1.25rem 1rem; -$accordion-content-background: $white; -$accordion-content-border: 1px solid $light-gray; -$accordion-content-color: $body-font-color; -$accordion-content-padding: 1rem; - -// 8. Accordion Menu -// ----------------- - -$accordionmenu-padding: $global-menu-padding; -$accordionmenu-nested-margin: $global-menu-nested-margin; -$accordionmenu-submenu-padding: $accordionmenu-padding; -$accordionmenu-arrows: true; -$accordionmenu-arrow-color: $primary-color; -$accordionmenu-item-background: null; -$accordionmenu-border: null; -$accordionmenu-submenu-toggle-background: null; -$accordion-submenu-toggle-border: $accordionmenu-border; -$accordionmenu-submenu-toggle-width: 40px; -$accordionmenu-submenu-toggle-height: $accordionmenu-submenu-toggle-width; -$accordionmenu-arrow-size: 6px; - -// 9. Badge -// -------- - -$badge-background: $primary-color; -$badge-color: $white; -$badge-color-alt: $black; -$badge-palette: $foundation-palette; -$badge-padding: 0.3em; -$badge-minwidth: 2.1em; -$badge-font-size: 0.6rem; - -// 10. Breadcrumbs -// --------------- - -$breadcrumbs-margin: 0 0 $global-margin 0; -$breadcrumbs-item-font-size: rem-calc(11); -$breadcrumbs-item-color: $primary-color; -$breadcrumbs-item-color-current: $black; -$breadcrumbs-item-color-disabled: $medium-gray; -$breadcrumbs-item-margin: 0.75rem; -$breadcrumbs-item-uppercase: true; -$breadcrumbs-item-separator: true; -$breadcrumbs-item-separator-item: '/'; -$breadcrumbs-item-separator-item-rtl: '\\'; -$breadcrumbs-item-separator-color: $medium-gray; - -// 11. Button -// ---------- - -$button-font-family: inherit; -$button-padding: 0.85em 1em; -$button-margin: 0 0 $global-margin 0; -$button-fill: solid; -$button-background: $primary-color; -$button-background-hover: scale-color($button-background, $lightness: -15%); -$button-color: $white; -$button-color-alt: $black; -$button-radius: $global-radius; -$button-hollow-border-width: 1px; -$button-sizes: ( - tiny: 0.6rem, - small: 0.75rem, - default: 0.9rem, - large: 1.25rem, -); -$button-palette: $foundation-palette; -$button-opacity-disabled: 0.25; -$button-background-hover-lightness: -20%; -$button-hollow-hover-lightness: -50%; -$button-transition: background-color 0.25s ease-out, color 0.25s ease-out; -$button-responsive-expanded: false; - -// 12. Button Group -// ---------------- - -$buttongroup-margin: 1rem; -$buttongroup-spacing: 1px; -$buttongroup-child-selector: '.button'; -$buttongroup-expand-max: 6; -$buttongroup-radius-on-each: true; - -// 13. Callout -// ----------- - -$callout-background: $white; -$callout-background-fade: 85%; -$callout-border: 1px solid rgba($black, 0.25); -$callout-margin: 0 0 1rem 0; -$callout-padding: 1rem; -$callout-font-color: $body-font-color; -$callout-font-color-alt: $body-background; -$callout-radius: $global-radius; -$callout-link-tint: 30%; - -// 14. Card -// -------- - -$card-background: $white; -$card-font-color: $body-font-color; -$card-divider-background: $light-gray; -$card-border: 1px solid $light-gray; -$card-shadow: none; -$card-border-radius: $global-radius; -$card-padding: $global-padding; -$card-margin-bottom: $global-margin; - -// 15. Close Button -// ---------------- - -$closebutton-position: right top; -$closebutton-offset-horizontal: ( - small: 0.66rem, - medium: 1rem, -); -$closebutton-offset-vertical: ( - small: 0.33em, - medium: 0.5rem, -); -$closebutton-size: ( - small: 1.5em, - medium: 2em, -); -$closebutton-lineheight: 1; -$closebutton-color: $dark-gray; -$closebutton-color-hover: $black; - -// 16. Drilldown -// ------------- - -$drilldown-transition: transform 0.15s linear; -$drilldown-arrows: true; -$drilldown-padding: $global-menu-padding; -$drilldown-nested-margin: 0; -$drilldown-background: $white; -$drilldown-submenu-padding: $drilldown-padding; -$drilldown-submenu-background: $white; -$drilldown-arrow-color: $primary-color; -$drilldown-arrow-size: 6px; - -// 17. Dropdown -// ------------ - -$dropdown-padding: 1rem; -$dropdown-background: $body-background; -$dropdown-border: 1px solid $medium-gray; -$dropdown-font-size: 1rem; -$dropdown-width: 300px; -$dropdown-radius: $global-radius; -$dropdown-sizes: ( - tiny: 100px, - small: 200px, - large: 400px, -); - -// 18. Dropdown Menu -// ----------------- - -$dropdownmenu-arrows: true; -$dropdownmenu-arrow-color: $anchor-color; -$dropdownmenu-arrow-size: 6px; -$dropdownmenu-arrow-padding: 1.5rem; -$dropdownmenu-min-width: 200px; -$dropdownmenu-background: null; -$dropdownmenu-submenu-background: $white; -$dropdownmenu-padding: $global-menu-padding; -$dropdownmenu-nested-margin: 0; -$dropdownmenu-submenu-padding: $dropdownmenu-padding; -$dropdownmenu-border: 1px solid $medium-gray; -$dropdown-menu-item-color-active: get-color(primary); -$dropdown-menu-item-background-active: transparent; - -// 19. Flexbox Utilities -// --------------------- - -$flex-source-ordering-count: 6; -$flexbox-responsive-breakpoints: true; - -// 20. Forms -// --------- - -$fieldset-border: 1px solid $medium-gray; -$fieldset-padding: rem-calc(20); -$fieldset-margin: rem-calc(18 0); -$legend-padding: rem-calc(0 3); -$form-spacing: rem-calc(16); -$helptext-color: $black; -$helptext-font-size: rem-calc(13); -$helptext-font-style: italic; -$input-prefix-color: $black; -$input-prefix-background: $light-gray; -$input-prefix-border: 1px solid $medium-gray; -$input-prefix-padding: 1rem; -$form-label-color: $black; -$form-label-font-size: rem-calc(14); -$form-label-font-weight: $global-weight-normal; -$form-label-line-height: 1.8; -$select-background: $white; -$select-triangle-color: $dark-gray; -$select-radius: $global-radius; -$input-color: $black; -$input-placeholder-color: $medium-gray; -$input-font-family: inherit; -$input-font-size: rem-calc(16); -$input-font-weight: $global-weight-normal; -$input-line-height: $global-lineheight; -$input-background: $white; -$input-background-focus: $white; -$input-background-disabled: $light-gray; -$input-border: 1px solid $medium-gray; -$input-border-focus: 1px solid $dark-gray; -$input-padding: $form-spacing / 2; -$input-shadow: inset 0 1px 2px rgba($black, 0.1); -$input-shadow-focus: 0 0 5px $medium-gray; -$input-cursor-disabled: not-allowed; -$input-transition: box-shadow 0.5s, border-color 0.25s ease-in-out; -$input-number-spinners: true; -$input-radius: $global-radius; -$form-button-radius: $global-radius; - -// 21. Label -// --------- - -$label-background: $primary-color; -$label-color: $white; -$label-color-alt: $black; -$label-palette: $foundation-palette; -$label-font-size: 0.8rem; -$label-padding: 0.33333rem 0.5rem; -$label-radius: $global-radius; - -// 22. Media Object -// ---------------- - -$mediaobject-margin-bottom: $global-margin; -$mediaobject-section-padding: $global-padding; -$mediaobject-image-width-stacked: 100%; - -// 23. Menu -// -------- - -$menu-margin: 0; -$menu-nested-margin: $global-menu-nested-margin; -$menu-items-padding: $global-menu-padding; -$menu-simple-margin: 1rem; -$menu-item-color-active: $white; -$menu-item-background-active: get-color(primary); -$menu-icon-spacing: 0.25rem; -$menu-state-back-compat: true; -$menu-centered-back-compat: true; -$menu-icons-back-compat: true; - -// 24. Meter -// --------- - -$meter-height: 1rem; -$meter-radius: $global-radius; -$meter-background: $medium-gray; -$meter-fill-good: $success-color; -$meter-fill-medium: $warning-color; -$meter-fill-bad: $alert-color; - -// 25. Off-canvas -// -------------- - -$offcanvas-sizes: ( - small: 250px, -); -$offcanvas-vertical-sizes: ( - small: 250px, -); -$offcanvas-background: $light-gray; -$offcanvas-shadow: 0 0 10px rgba($black, 0.7); -$offcanvas-inner-shadow-size: 20px; -$offcanvas-inner-shadow-color: rgba($black, 0.25); -$offcanvas-overlay-zindex: 11; -$offcanvas-push-zindex: 12; -$offcanvas-overlap-zindex: 13; -$offcanvas-reveal-zindex: 12; -$offcanvas-transition-length: 0.5s; -$offcanvas-transition-timing: ease; -$offcanvas-fixed-reveal: true; -$offcanvas-exit-background: rgba($white, 0.25); -$maincontent-class: 'off-canvas-content'; - -// 26. Orbit -// --------- - -$orbit-bullet-background: $medium-gray; -$orbit-bullet-background-active: $dark-gray; -$orbit-bullet-diameter: 1.2rem; -$orbit-bullet-margin: 0.1rem; -$orbit-bullet-margin-top: 0.8rem; -$orbit-bullet-margin-bottom: 0.8rem; -$orbit-caption-background: rgba($black, 0.5); -$orbit-caption-padding: 1rem; -$orbit-control-background-hover: rgba($black, 0.5); -$orbit-control-padding: 1rem; -$orbit-control-zindex: 10; - -// 27. Pagination -// -------------- - -$pagination-font-size: rem-calc(14); -$pagination-margin-bottom: $global-margin; -$pagination-item-color: $black; -$pagination-item-padding: rem-calc(3 10); -$pagination-item-spacing: rem-calc(1); -$pagination-radius: $global-radius; -$pagination-item-background-hover: $light-gray; -$pagination-item-background-current: $primary-color; -$pagination-item-color-current: $white; -$pagination-item-color-disabled: $medium-gray; -$pagination-ellipsis-color: $black; -$pagination-mobile-items: false; -$pagination-mobile-current-item: false; -$pagination-arrows: true; - -// 28. Progress Bar -// ---------------- - -$progress-height: 1rem; -$progress-background: $medium-gray; -$progress-margin-bottom: $global-margin; -$progress-meter-background: $primary-color; -$progress-radius: $global-radius; - -// 29. Prototype Arrow -// ------------------- - -$prototype-arrow-directions: ( - down, - up, - right, - left -); -$prototype-arrow-size: 0.4375rem; -$prototype-arrow-color: $black; - -// 30. Prototype Border-Box -// ------------------------ - -$prototype-border-box-breakpoints: $global-prototype-breakpoints; - -// 31. Prototype Border-None -// ------------------------- - -$prototype-border-none-breakpoints: $global-prototype-breakpoints; - -// 32. Prototype Bordered -// ---------------------- - -$prototype-bordered-breakpoints: $global-prototype-breakpoints; -$prototype-border-width: rem-calc(1); -$prototype-border-type: solid; -$prototype-border-color: $medium-gray; - -// 33. Prototype Display -// --------------------- - -$prototype-display-breakpoints: $global-prototype-breakpoints; -$prototype-display: ( - inline, - inline-block, - block, - table, - table-cell -); - -// 34. Prototype Font-Styling -// -------------------------- - -$prototype-font-breakpoints: $global-prototype-breakpoints; -$prototype-wide-letter-spacing: rem-calc(4); -$prototype-font-normal: $global-weight-normal; -$prototype-font-bold: $global-weight-bold; - -// 35. Prototype List-Style-Type -// ----------------------------- - -$prototype-list-breakpoints: $global-prototype-breakpoints; -$prototype-style-type-unordered: ( - disc, - circle, - square -); -$prototype-style-type-ordered: ( - decimal, - lower-alpha, - lower-latin, - lower-roman, - upper-alpha, - upper-latin, - upper-roman -); - -// 36. Prototype Overflow -// ---------------------- - -$prototype-overflow-breakpoints: $global-prototype-breakpoints; -$prototype-overflow: ( - visible, - hidden, - scroll -); - -// 37. Prototype Position -// ---------------------- - -$prototype-position-breakpoints: $global-prototype-breakpoints; -$prototype-position: ( - static, - relative, - absolute, - fixed -); -$prototype-position-z-index: 975; - -// 38. Prototype Rounded -// --------------------- - -$prototype-rounded-breakpoints: $global-prototype-breakpoints; -$prototype-border-radius: rem-calc(3); - -// 39. Prototype Separator -// ----------------------- - -$prototype-separator-breakpoints: $global-prototype-breakpoints; -$prototype-separator-align: center; -$prototype-separator-height: rem-calc(2); -$prototype-separator-width: 3rem; -$prototype-separator-background: $primary-color; -$prototype-separator-margin-top: $global-margin; - -// 40. Prototype Shadow -// -------------------- - -$prototype-shadow-breakpoints: $global-prototype-breakpoints; -$prototype-box-shadow: 0 2px 5px 0 rgba(0,0,0,.16), -0 2px 10px 0 rgba(0,0,0,.12); - -// 41. Prototype Sizing -// -------------------- - -$prototype-sizing-breakpoints: $global-prototype-breakpoints; -$prototype-sizing: ( - width, - height -); -$prototype-sizes: ( - 25: 25%, - 50: 50%, - 75: 75%, - 100: 100% -); - -// 42. Prototype Spacing -// --------------------- - -$prototype-spacing-breakpoints: $global-prototype-breakpoints; -$prototype-spacers-count: 3; - -// 43. Prototype Text-Decoration -// ----------------------------- - -$prototype-decoration-breakpoints: $global-prototype-breakpoints; -$prototype-text-decoration: ( - overline, - underline, - line-through, -); - -// 44. Prototype Text-Transformation -// --------------------------------- - -$prototype-transformation-breakpoints: $global-prototype-breakpoints; -$prototype-text-transformation: ( - lowercase, - uppercase, - capitalize -); - -// 45. Prototype Text-Utilities -// ---------------------------- - -$prototype-utilities-breakpoints: $global-prototype-breakpoints; -$prototype-text-overflow: ellipsis; - -// 46. Responsive Embed -// -------------------- - -$responsive-embed-margin-bottom: rem-calc(16); -$responsive-embed-ratios: ( - default: 4 by 3, - widescreen: 16 by 9, -); - -// 47. Reveal -// ---------- - -$reveal-background: $white; -$reveal-width: 600px; -$reveal-max-width: $global-width; -$reveal-padding: $global-padding; -$reveal-border: 1px solid $medium-gray; -$reveal-radius: $global-radius; -$reveal-zindex: 1005; -$reveal-overlay-background: rgba($black, 0.45); - -// 48. Slider -// ---------- - -$slider-width-vertical: 0.5rem; -$slider-transition: all 0.2s ease-in-out; -$slider-height: 0.5rem; -$slider-background: $light-gray; -$slider-fill-background: $medium-gray; -$slider-handle-height: 1.4rem; -$slider-handle-width: 1.4rem; -$slider-handle-background: $primary-color; -$slider-opacity-disabled: 0.25; -$slider-radius: $global-radius; - -// 49. Switch -// ---------- - -$switch-background: $medium-gray; -$switch-background-active: $primary-color; -$switch-height: 2rem; -$switch-height-tiny: 1.5rem; -$switch-height-small: 1.75rem; -$switch-height-large: 2.5rem; -$switch-radius: $global-radius; -$switch-margin: $global-margin; -$switch-paddle-background: $white; -$switch-paddle-offset: 0.25rem; -$switch-paddle-radius: $global-radius; -$switch-paddle-transition: all 0.25s ease-out; - -// 50. Table -// --------- - -$table-background: $white; -$table-color-scale: 5%; -$table-border: 1px solid smart-scale($table-background, $table-color-scale); -$table-padding: rem-calc(8 10 10); -$table-hover-scale: 2%; -$table-row-hover: darken($table-background, $table-hover-scale); -$table-row-stripe-hover: darken($table-background, $table-color-scale + $table-hover-scale); -$table-is-striped: true; -$table-striped-background: smart-scale($table-background, $table-color-scale); -$table-stripe: even; -$table-head-background: smart-scale($table-background, $table-color-scale / 2); -$table-head-row-hover: darken($table-head-background, $table-hover-scale); -$table-foot-background: smart-scale($table-background, $table-color-scale); -$table-foot-row-hover: darken($table-foot-background, $table-hover-scale); -$table-head-font-color: $body-font-color; -$table-foot-font-color: $body-font-color; -$show-header-for-stacked: false; -$table-stack-breakpoint: medium; - -// 51. Tabs -// -------- - -$tab-margin: 0; -$tab-background: $white; -$tab-color: $primary-color; -$tab-background-active: $light-gray; -$tab-active-color: $primary-color; -$tab-item-font-size: rem-calc(12); -$tab-item-background-hover: $white; -$tab-item-padding: 1.25rem 1.5rem; -$tab-content-background: $white; -$tab-content-border: $light-gray; -$tab-content-color: $body-font-color; -$tab-content-padding: 1rem; - -// 52. Thumbnail -// ------------- - -$thumbnail-border: 4px solid $white; -$thumbnail-margin-bottom: $global-margin; -$thumbnail-shadow: 0 0 0 1px rgba($black, 0.2); -$thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5); -$thumbnail-transition: box-shadow 200ms ease-out; -$thumbnail-radius: $global-radius; - -// 53. Title Bar -// ------------- - -$titlebar-background: $black; -$titlebar-color: $white; -$titlebar-padding: 0.5rem; -$titlebar-text-font-weight: bold; -$titlebar-icon-color: $white; -$titlebar-icon-color-hover: $medium-gray; -$titlebar-icon-spacing: 0.25rem; - -// 54. Tooltip -// ----------- - -$has-tip-cursor: help; -$has-tip-font-weight: $global-weight-bold; -$has-tip-border-bottom: dotted 1px $dark-gray; -$tooltip-background-color: $black; -$tooltip-color: $white; -$tooltip-padding: 0.75rem; -$tooltip-max-width: 10rem; -$tooltip-font-size: $small-font-size; -$tooltip-pip-width: 0.75rem; -$tooltip-pip-height: $tooltip-pip-width * 0.866; -$tooltip-radius: $global-radius; - -// 55. Top Bar -// ----------- - -$topbar-padding: 0; -$topbar-background: $ub-nav-bg; -$topbar-submenu-background: $topbar-background; -$topbar-title-spacing: 1rem; -$topbar-input-width: 200px; -$topbar-unstack-breakpoint: large; - -// 56. Xy Grid -// ----------- - -$xy-grid: true; -$grid-container: $global-width; -$grid-columns: 12; -$grid-margin-gutters: ( - small: 20px, - medium: 30px -); -$grid-padding-gutters: $grid-margin-gutters; -$grid-container-padding: $grid-padding-gutters; -$grid-container-max: $global-width; -$xy-block-grid-max: 8; - diff --git a/app/assets/stylesheets/lcms/engine/utils/_colorcodes.scss b/app/assets/stylesheets/lcms/engine/utils/_colorcodes.scss deleted file mode 100644 index b473c150..00000000 --- a/app/assets/stylesheets/lcms/engine/utils/_colorcodes.scss +++ /dev/null @@ -1,71 +0,0 @@ -.cs-bg { - @each $code, $color in $ub-colorcodes { - @include m(#{$code}) { - background-color: $color; - } - } - - @each $code, $color in $ub-ld-colorcodes { - @include m(#{$code}) { - background-color: $color; - } - } -} - -.cs-rgba-bg { - @each $code, $color in $ub-colorcodes { - @include m(#{$code}) { - background-color: rgba($color, 0.8); - } - } -} - -.cs-tint-bg { - @each $code, $color in $ub-colorcodes { - @include m(#{$code}) { - $tint-color: scale-color($color, $lightness: 90%); - background-color: $tint-color; - - &::before { - border-bottom-color: $tint-color; - } - } - } -} - -.cs-txt { - @each $code, $color in $ub-colorcodes { - @include m(#{$code}) { - color: $color; - } - } -} - -.cs-txt-link { - @each $code, $color in $ub-colorcodes { - @include m(#{$code}) { - &, - &:hover, - &:focus { - color: $color; - } - } - } -} - -.cs-link-bg { - @each $code, $color in $ub-colorcodes { - @include m(#{$code}) { - &, - &:hover, - &:focus { - background: none; - color: $ub-white; - - > div { - background-color: $color; - } - } - } - } -} diff --git a/app/assets/stylesheets/lcms/engine/utils/_functions.scss b/app/assets/stylesheets/lcms/engine/utils/_functions.scss deleted file mode 100644 index 659e6d24..00000000 --- a/app/assets/stylesheets/lcms/engine/utils/_functions.scss +++ /dev/null @@ -1,5 +0,0 @@ -@function computed-height($typo, $lines) { - $font-size: map-get($typo, font-size); - $line-height: map-get($typo, line-height); - @return $font-size * ($line-height / $font-size) * $lines; -} diff --git a/app/assets/stylesheets/lcms/engine/utils/_helpers.scss b/app/assets/stylesheets/lcms/engine/utils/_helpers.scss deleted file mode 100644 index fdb58a79..00000000 --- a/app/assets/stylesheets/lcms/engine/utils/_helpers.scss +++ /dev/null @@ -1,228 +0,0 @@ -// stylelint-disable declaration-no-important -$text-sizes: ( - 'base': $global-font-size, - 'large': $lead-font-size, - 'small': $small-font-size, -); - -.u-text { - @each $text-size, $text-size-value in $text-sizes { - @include m(#{$text-size}) { - font-size: $text-size-value; - } - } -} - -.u-color--base { - color: $ub-txt !important; -} - -.u-text--capitalized { - text-transform: capitalize; -} - -.u-text--uppercase { - text-transform: uppercase; -} - -.u-text--centered { - text-align: center; -} - -.u-text--right { - text-align: right; -} - -.u-text--strikethrough { - text-decoration: line-through; -} - -.u-text--middled { - vertical-align: middle; -} - -.u-text--bolded { - font-weight: bold !important; -} - -.u-flex { - display: flex; -} - -.u-show { - display: block !important; -} - -.u-hidden { - display: none !important; -} - -.u-float-none { - float: none !important; -} - -$border-radius-aligns: (top-left, bottom-left, top-right, bottom-right); -@each $border-radius-align in $border-radius-aligns { - .u-br-#{$border-radius-align}--base { - border-#{$border-radius-align}-radius: $global-radius; - } -} -$border-radius-sides: (top, bottom); -@each $border-radius-side in $border-radius-sides { - .u-br-#{$border-radius-side}--base { - border-#{$border-radius-side}-left-radius: $global-radius; - border-#{$border-radius-side}-right-radius: $global-radius; - } -} - -.u-br--2xbase { - border-radius: $global-radius * 2 !important; -} - -$margins: ( - 'zero': 0, - 'xs': $ub-small-margin * 0.7, - 'small': $ub-small-margin, - 'base': $ub-base-margin, - 'large': $ub-large-margin, - 'xlarge': $ub-xlarge-margin, - 'xxlarge': $ub-xxlarge-margin, -); -$margins-align: (top, bottom, right, left); - -@each $margin-type, $margin-value in $margins { - @each $align in $margins-align { - .u-margin-#{$align}--#{$margin-type} { - margin-#{$align}: $margin-value !important; - } - } - - .u-margin-horizontal--#{$margin-type} { - margin-left: $margin-value; - margin-right: $margin-value; - } - - .u-margin-vertical--#{$margin-type} { - margin-bottom: $margin-value; - margin-top: $margin-value; - } -} - -$paddings: ( - 'zero': 0, - 'tiny': 5px, - 'xs': $ub-small-margin * 0.7, - 'small': $ub-small-margin, - 'base': $ub-base-margin, - 'large': $ub-large-margin, - 'xlarge': $ub-xlarge-margin, -); -$paddings-align: (top, bottom, left, right); - -@each $padding-type, $padding-value in $paddings { - @each $align in $paddings-align { - .u-padding-#{$align}--#{$padding-type} { - padding-#{$align}: $padding-value !important; - } - } - - .u-padding-horizontal--#{$padding-type} { - padding-left: $padding-value; - padding-right: $padding-value; - } -} - -$align-directions: (top, bottom, left, right); -$align-types: (padding, margin); - -@each $align-dir in $align-directions { - @each $align-type in $align-types { - .u-#{$align-type}-#{$align-dir}--gutter-half { - @include add-gutter($type: #{$align-type}, $subtype: #{$align-dir}, $x: 0.5); - } - - .u-#{$align-type}-#{$align-dir}--gutter { - @include add-gutter($type: #{$align-type}, $subtype: #{$align-dir}); - } - - .u-#{$align-type}-#{$align-dir}--gutter2x { - @include add-gutter($type: #{$align-type}, $subtype: #{$align-dir}); - } - } -} - -$bg: ( - light: $ub-white, - base: $ub-panel-bg, - dark: $ub-hero-bg, - primary: $ub-primary1, - tooltip: darken($ub-tooltip, 1%), - ls: map-get($ub-leadership, bg), -); - -.u-bg--base-gradient { - background-image: $ub-gray-gradient; -} - -@each $bg-type, $bg-color in $bg { - .u-bg--#{$bg-type} { - background-color: $bg-color; - } -} - -.u-wrap { - @each $bg-type, $bg-color in $bg { - @include m($bg-type) { - @extend .u-bg--#{$bg-type}; // stylelint-disable-line scss/at-extend-no-missing-placeholder - padding: $ub-base-padding; - } - } -} - -.u-pd-content { - @include add-top-bottom-padding; -} - -.u-pd-content--xlarge { - @include add-top-bottom-padding(2); -} - -.u-link--disabled { - &, - a { - color: $medium-gray; - cursor: not-allowed; - } -} - -.u-hr-small { - @include clearfix; - border-bottom: 2px solid map-get($ub-yellow, base); - border-radius: 2px; - border-top: 3px solid map-get($ub-yellow, base); - margin-bottom: $ub-base-margin; - width: 52px; -} - -.u-hr-small--green { - border-color: map-get($ub-green, base); -} - -.u-hr-small--ls { - border-color: map-get($ub-leadership, base); -} - -.u-centered { - margin-left: auto; - margin-right: auto; -} - -.u-container { - @include m(condensed) { - max-width: 30rem; - } -} - -.u-inline { - display: inline; -} diff --git a/app/assets/stylesheets/lcms/engine/utils/_mixins.scss b/app/assets/stylesheets/lcms/engine/utils/_mixins.scss deleted file mode 100644 index 4f3dc482..00000000 --- a/app/assets/stylesheets/lcms/engine/utils/_mixins.scss +++ /dev/null @@ -1,194 +0,0 @@ -// Block Element -// @access public -// @param {String} $element - Element's name -@mixin element($element) { - &__#{$element} { - @content; - } -} -// Block Modifier -// @access public -// @param {String} $modifier - Modifier's name -@mixin modifier($modifier) { - &--#{$modifier} { - @content; - } -} -// @alias element -@mixin e($element) { - @include element($element) { - @content; - } -} -// @alias modifier -@mixin m($modifier) { - @include modifier($modifier) { - @content; - } -} - -@mixin txt-element-base($typo) { - @each $style, $value in $typo { - #{$style}: $value; - } - @if not map-has-key($typo, 'font-weight') { - font-weight: normal; - } -} - -@mixin txt-element-base-strict($typo) { - @each $style, $value in $typo { - #{$style}: $value; - } -} - -@mixin txt-element($element, $ub-map: $ub-element-typography) { - $typo: map-get($ub-map, $element); - @if map-has-key($typo, small) { - @each $breakpoint, $value in $typo { - @include breakpoint($breakpoint) { - @include txt-element-base($value); - } - } - } @else { - @include txt-element-base($typo); - } -} - -@mixin txt-element-strict($element, $ub-map: $ub-element-typography) { - $typo: map-get($ub-map, $element); - @if map-has-key($typo, small) { - @each $breakpoint, $value in $typo { - @include breakpoint($breakpoint) { - @include txt-element-base-strict($value); - } - } - } @else { - @include txt-element-base-strict($typo); - } -} - -@mixin txt-heading-base($element) { - $typo: map-get($ub-heading-typography, $element); - color: map-get($ub-txt-colors, $element); - @each $style, $value in $typo { - #{$style}: $value; - } -} - -@mixin txt-heading($element) { - @include txt-heading-base($element); - @each $size, $headers in $header-styles { - @include breakpoint($size) { - $font-size: map-get(map-get($headers, $element), 'font-size'); - font-size: rem-calc($font-size); - } - } -} - -@mixin txt-pdf-element($element) { - @include txt-element($element, $ub-pdf-typography); -} - -@mixin clamp($lines, $element) { - overflow: hidden; - text-overflow: ellipsis; - display: -webkit-box; - -webkit-line-clamp: $lines; - -webkit-box-orient: vertical; - $typo: map-get($ub-element-typography, $element); - @if map-has-key($typo, small) { - @each $breakpoint, $value in $typo { - @include breakpoint($breakpoint) { - max-height: computed-height($value, $lines); - } - } - } @else { - max-height: computed-height($typo, $lines); - } -} - -@mixin ellipsis { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -@mixin breakpoint-style-from-map($map, $style) { - @each $breakpoint, $value in $map { - @if ($breakpoint == small) { - #{$style}: $value; - } @else { - @include breakpoint($breakpoint) { - #{$style}: $value; - } - } - } -} - -@mixin add-gutter($type: margin, $subtype: bottom, $x: 1, $gutter: $grid-column-gutter) { - @each $breakpoint, $value in $gutter { - $margin: rem-calc($value); - - @include breakpoint($breakpoint) { - #{$type}-#{$subtype}: $margin * $x; - } - } -} - -@mixin add-column-padding($x: 1) { - @include add-gutter(padding, left, $x); - @include add-gutter(padding, right, $x); -} - -@mixin add-column-margin($x: 1) { - @include add-gutter(margin, left, $x); - @include add-gutter(margin, right, $x); -} - -@mixin add-top-bottom-padding($y: 1) { - @include add-gutter(padding, bottom, $y); - @include add-gutter(padding, top, $y); -} - -@mixin add-top-bottom-margin($y: 1) { - @include add-gutter(margin, bottom, $y); - @include add-gutter(margin, top, $y); -} - -@mixin placeholder($color: $ub-txt){ - ::-webkit-input-placeholder { /* Chrome/Opera/Safari */ - color: $color; - } - - ::-moz-placeholder { /* Firefox 19+ */ - color: $color; - } - - :-ms-input-placeholder { /* IE 10+ */ - color: $color; - } - - :-moz-placeholder { /* Firefox 18- */ - color: $color; - } -} - -@mixin aspect-ratio($width, $height) { - position: relative; - - &:before { - content: ''; - display: block; - padding-top: ($height / $width) * 100%; - width: 100%; - } - - > .content { - bottom: 0; - left: 0; - position: absolute; - right: 0; - top: 0; - } -} diff --git a/app/assets/stylesheets/lcms/engine/utils/_placeholders.scss b/app/assets/stylesheets/lcms/engine/utils/_placeholders.scss deleted file mode 100644 index 534f6295..00000000 --- a/app/assets/stylesheets/lcms/engine/utils/_placeholders.scss +++ /dev/null @@ -1,11 +0,0 @@ -%u-ph-img--light { - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE2LjIuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHdpZHRoPSIxNDBweCIgaGVpZ2h0PSIxNDBweCIgdmlld0JveD0iMCAwIDE0MCAxNDAiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPgo8Zz4KCTxsaW5lIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2Y1ZjVmNSIgc3Ryb2tlLXdpZHRoPSIuNSIgeDE9IjAiIHkxPSIxNDAiIHgyPSIxNDAiIHkyPSIwIi8+CiAgPGxpbmUgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZjVmNWY1IiBzdHJva2Utd2lkdGg9Ii41IiB4MT0iMCIgeTE9IjAiIHgyPSIxNDAiIHkyPSIxNDAiLz4KPC9nPgo8L3N2Zz4K'); - background-repeat: no-repeat; - background-size: 100% 100%; -} - -%u-ph-img--dark { - background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE2LjIuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHdpZHRoPSIxNDBweCIgaGVpZ2h0PSIxNDBweCIgdmlld0JveD0iMCAwIDE0MCAxNDAiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPgo8Zz4KCTxsaW5lIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzYxNjE2MSIgc3Ryb2tlLXdpZHRoPSIuNSIgeDE9IjAiIHkxPSIxNDAiIHgyPSIxNDAiIHkyPSIwIi8+CiAgPGxpbmUgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNjE2MTYxIiBzdHJva2Utd2lkdGg9Ii41IiB4MT0iMCIgeTE9IjAiIHgyPSIxNDAiIHkyPSIxNDAiLz4KPC9nPgo8L3N2Zz4K'); - background-repeat: no-repeat; - background-size: 100% 100%; -} diff --git a/app/assets/stylesheets/lcms/engine/utils/_styles_override.scss b/app/assets/stylesheets/lcms/engine/utils/_styles_override.scss deleted file mode 100644 index b58f311c..00000000 --- a/app/assets/stylesheets/lcms/engine/utils/_styles_override.scss +++ /dev/null @@ -1,57 +0,0 @@ -@mixin inline-styles-override($txt-color: $ub-txt, - $bg-color: $ub-white, - $font-size: $global-font-size, - $font-family: $body-font-family, - $font-style: normal, - $line-height: $global-lineheight) -{ - background-color: $bg-color !important; - color: $txt-color !important; - font-family: $font-family !important; - font-size: $font-size !important; - font-style: $font-style !important; - text-decoration: none !important; - - a { - color: $anchor-color !important; - cursor: pointer !important; - line-height: inherit !important; - text-decoration: $anchor-text-decoration !important; - - &:hover, - &:focus { - color: $anchor-color-hover !important; - @if $anchor-text-decoration-hover != $anchor-text-decoration { - text-decoration: $anchor-text-decoration-hover !important; - } - } - - img { - border: 0 !important; - } - } - - p { - font-size: inherit !important; - line-height: $line-height !important; - margin-bottom: $paragraph-margin-bottom !important; - text-rendering: $paragraph-text-rendering !important; - } -} - -@mixin zero-margin-bottom() { - > *:last-child { margin-bottom: 0 !important; } -} - -@mixin html-description() { - $base-typo: map-get($ub-element-typography, base); - @include inline-styles-override($font-size: map-get($base-typo, 'font-size'), - $font-family: map-get($base-typo, 'font-family'), - $line-height: map-get($base-typo, 'line-height')); - @include zero-margin-bottom(); -} - -.u-html-description { - @include inline-styles-override(); - @include zero-margin-bottom(); -} diff --git a/app/assets/stylesheets/lcms/engine/utils/_variables.scss b/app/assets/stylesheets/lcms/engine/utils/_variables.scss deleted file mode 100644 index 0b0807a4..00000000 --- a/app/assets/stylesheets/lcms/engine/utils/_variables.scss +++ /dev/null @@ -1,63 +0,0 @@ -// Font families -$ub-sanserif: 'Helvetica Neue', Helvetica, Arial, sans-serif; -$ub-serif: Georgia, 'Times New Roman', Times, serif; -$ub-sanserif-condensed: $ub-sanserif; -$ub-cursive: 'Times New Roman', cursive; - -$panel-radius: 3px; - -$ub-nav-height: 90px; -$ub-subscribe-height: 90px; - -// Paddings, margins -$ub-base-margin: 1rem; -$ub-base-padding: 1rem; -$ub-large-margin: $ub-base-margin * 1.5; -$ub-xlarge-margin: $ub-base-margin * 2; -$ub-xxlarge-margin: $ub-base-margin * 3; -$ub-small-margin: $ub-base-margin * 0.75; -$ub-tiny-margin: 1rem * 0.06; -$ub-large-padding: $ub-base-padding * 1.5; -$ub-xlarge-padding: $ub-base-padding * 2; -$ub-small-padding: $ub-base-padding * 0.75; -$ub-xs-padding: $ub-small-padding * 0.7; -$ub-tiny-padding: $ub-base-padding * 0.06; - -// Base element sizes -$curriculum-map-width: ( - base: 230px, - medium: 180px, -); -$curriculum-card-short-top: -6px; - -$filterbar-height: 58px; -$filterbar-square: 58px; -$filterbar-xs: (($filterbar-square + 1) * 5 - 1) / 2; -$filterbar-xs-3: (($filterbar-square + 1) * 5 - 1) / 3; -$filterbar-rectangle-2: ( - small: $filterbar-xs, - ipad: 218px, - large: 115px, -); -$filterbar-rectangle-3: ( - small: $filterbar-xs-3 - 1px, - ipad: 108px, - large: 75px, -); -$filterbar-rectangle-big: ( - small: $filterbar-xs - 1px, - ipad: 157px, -); -$ld-sticky-header-height: ( - small: 75px, - large: $ub-nav-height, -); -$ld-sticky-header-padding: ( - small: 1rem, - large: 0.5rem, -); -$ld-tooltip-offset: 1.5rem; - -$pdf-margin: 8px; -$pdf-margin-ld: 29px; // It was 57px -$pdf-fulltable-height: 1150px; diff --git a/app/assets/stylesheets/lcms/engine/vendors/foundation_and_overrides-pdf.scss b/app/assets/stylesheets/lcms/engine/vendors/foundation_and_overrides-pdf.scss deleted file mode 100644 index 587e13df..00000000 --- a/app/assets/stylesheets/lcms/engine/vendors/foundation_and_overrides-pdf.scss +++ /dev/null @@ -1,20 +0,0 @@ -@import 'foundation'; - -// We include everything by default. To slim your CSS, remove components you don't use. -@include foundation-global-styles; -@include foundation-flex-grid; -@include foundation-typography; -@include foundation-accordion; -@include foundation-button-group; -@include foundation-callout; -@include foundation-dropdown; -@include foundation-dropdown-menu; -@include foundation-menu; -@include foundation-menu-icon; -@include foundation-label; -@include foundation-media-object; -@include foundation-table; -@include foundation-thumbnail; -@include foundation-visibility-classes; -@include foundation-float-classes; -@include foundation-flex-classes; diff --git a/app/assets/stylesheets/lcms/engine/vendors/foundation_and_overrides.scss b/app/assets/stylesheets/lcms/engine/vendors/foundation_and_overrides.scss deleted file mode 100644 index 95b18bd0..00000000 --- a/app/assets/stylesheets/lcms/engine/vendors/foundation_and_overrides.scss +++ /dev/null @@ -1,55 +0,0 @@ -@charset "utf-8"; - -@import 'foundation'; - -// If you'd like to include motion-ui the foundation-rails gem comes prepackaged with it, uncomment the 3 @imports, if you are not using the gem you need to install the motion-ui sass package. -// -@import 'motion-ui/motion-ui'; - -// We include everything by default. To slim your CSS, remove components you don't use. - -@include foundation-global-styles; -//@include foundation-xy-grid-classes; -//@include foundation-grid; -@include foundation-flex-grid; -@include foundation-flex-classes; -@include foundation-typography; -@include foundation-forms; -@include foundation-button; -@include foundation-accordion; -@include foundation-accordion-menu; -@include foundation-badge; -@include foundation-breadcrumbs; -@include foundation-button-group; -@include foundation-callout; -@include foundation-card; -@include foundation-close-button; -@include foundation-menu; -@include foundation-menu-icon; -@include foundation-drilldown-menu; -@include foundation-dropdown; -@include foundation-dropdown-menu; -@include foundation-responsive-embed; -@include foundation-label; -@include foundation-media-object; -@include foundation-off-canvas; -@include foundation-orbit; -@include foundation-pagination; -@include foundation-progress-bar; -@include foundation-slider; -@include foundation-sticky; -@include foundation-reveal; -@include foundation-switch; -@include foundation-table; -@include foundation-tabs; -@include foundation-thumbnail; -@include foundation-title-bar; -@include foundation-tooltip; -@include foundation-top-bar; -@include foundation-visibility-classes; -@include foundation-float-classes; - -// If you'd like to include motion-ui the foundation-rails gem comes prepackaged with it, uncomment the 3 @imports, if you are not using the gem you need to install the motion-ui sass package. - -@include motion-ui-transitions; -@include motion-ui-animations; diff --git a/app/assets/stylesheets/lcms/engine/vendors/mathjax.scss b/app/assets/stylesheets/lcms/engine/vendors/mathjax.scss deleted file mode 100644 index bcf261fe..00000000 --- a/app/assets/stylesheets/lcms/engine/vendors/mathjax.scss +++ /dev/null @@ -1,117 +0,0 @@ -.mjx-chtml {display: inline-block; line-height: 0; text-indent: 0; text-align: left; text-transform: none; font-style: normal; font-weight: normal; font-size: 100%; font-size-adjust: none; letter-spacing: normal; word-wrap: normal; word-spacing: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0; min-height: 0; border: 0; margin: 0; padding: 1px 0} -.MJXc-display {display: inline; text-align: center; margin: 1em 0; padding: 0} -.mjx-chtml[tabindex]:focus, body :focus .mjx-chtml[tabindex] {display: inline-table} -.mjx-full-width {text-align: center; display: table-cell!important; width: 10000em} -.mjx-math {display: inline-block; border-collapse: separate; border-spacing: 0} -.mjx-math * {display: inline-block; -webkit-box-sizing: content-box!important; -moz-box-sizing: content-box!important; box-sizing: content-box!important; text-align: left} -.mjx-numerator {display: block; text-align: center} -.mjx-denominator {display: block; text-align: center} -.MJXc-stacked {height: 0; position: relative} -.MJXc-stacked > * {position: absolute} -.MJXc-bevelled > * {display: inline-block} -.mjx-stack {display: inline-block} -.mjx-op {display: block} -.mjx-under {display: table-cell} -.mjx-over {display: block} -.mjx-over > * {padding-left: 0 !important; padding-right: 0 !important} -.mjx-under > * {padding-left: 0 !important; padding-right: 0 !important} -.mjx-stack > .mjx-sup {display: block} -.mjx-stack > .mjx-sub {display: block} -.mjx-prestack > .mjx-presup {display: block} -.mjx-prestack > .mjx-presub {display: block} -.mjx-delim-h > .mjx-char {display: inline-block} -.mjx-surd {vertical-align: top} -.mjx-mphantom * {visibility: hidden} -.mjx-merror {background-color: #FFFF88; color: #CC0000; border: 1px solid #CC0000; padding: 2px 3px; font-style: normal; font-size: 90%} -.mjx-annotation-xml {line-height: normal} -.mjx-menclose > svg {fill: none; stroke: currentColor} -.mjx-mtr {display: table-row} -.mjx-mlabeledtr {display: table-row} -.mjx-mtd {display: table-cell; text-align: center} -.mjx-label {display: table-row} -.mjx-box {display: inline-block} -.mjx-block {display: block} -.mjx-span {display: inline} -.mjx-char {display: block; white-space: pre} -.mjx-itable {display: inline-table; width: auto} -.mjx-row {display: table-row} -.mjx-cell {display: table-cell} -.mjx-table {display: table; width: 100%} -.mjx-line {display: block; height: 0} -.mjx-strut {width: 0; padding-top: 1em} -.mjx-vsize {width: 0} -.MJXc-space1 {margin-left: .167em} -.MJXc-space2 {margin-left: .222em} -.MJXc-space3 {margin-left: .278em} -.mjx-ex-box-test {position: absolute; width: 1px; height: 60ex} -.mjx-line-box-test {display: table!important} -.mjx-line-box-test span {display: table-cell!important; width: 10000em!important; min-width: 0; max-width: none; padding: 0; border: 0; margin: 0} -.MJXc-TeX-unknown-R {font-family: monospace; font-style: normal; font-weight: normal} -.MJXc-TeX-unknown-I {font-family: monospace; font-style: italic; font-weight: normal} -.MJXc-TeX-unknown-B {font-family: monospace; font-style: normal; font-weight: bold} -.MJXc-TeX-unknown-BI {font-family: monospace; font-style: italic; font-weight: bold} -.MJXc-TeX-ams-R {font-family: MJXc-TeX-ams-R,MJXc-TeX-ams-Rw} -.MJXc-TeX-cal-B {font-family: MJXc-TeX-cal-B,MJXc-TeX-cal-Bx,MJXc-TeX-cal-Bw} -.MJXc-TeX-frak-R {font-family: MJXc-TeX-frak-R,MJXc-TeX-frak-Rw} -.MJXc-TeX-frak-B {font-family: MJXc-TeX-frak-B,MJXc-TeX-frak-Bx,MJXc-TeX-frak-Bw} -.MJXc-TeX-math-BI {font-family: MJXc-TeX-math-BI,MJXc-TeX-math-BIx,MJXc-TeX-math-BIw} -.MJXc-TeX-sans-R {font-family: MJXc-TeX-sans-R,MJXc-TeX-sans-Rw} -.MJXc-TeX-sans-B {font-family: MJXc-TeX-sans-B,MJXc-TeX-sans-Bx,MJXc-TeX-sans-Bw} -.MJXc-TeX-sans-I {font-family: MJXc-TeX-sans-I,MJXc-TeX-sans-Ix,MJXc-TeX-sans-Iw} -.MJXc-TeX-script-R {font-family: MJXc-TeX-script-R,MJXc-TeX-script-Rw} -.MJXc-TeX-type-R {font-family: MJXc-TeX-type-R,MJXc-TeX-type-Rw} -.MJXc-TeX-cal-R {font-family: MJXc-TeX-cal-R,MJXc-TeX-cal-Rw} -.MJXc-TeX-main-B {font-family: MJXc-TeX-main-B,MJXc-TeX-main-Bx,MJXc-TeX-main-Bw} -.MJXc-TeX-main-I {font-family: MJXc-TeX-main-I,MJXc-TeX-main-Ix,MJXc-TeX-main-Iw} -.MJXc-TeX-main-R {font-family: MJXc-TeX-main-R,MJXc-TeX-main-Rw} -.MJXc-TeX-math-I {font-family: MJXc-TeX-math-I,MJXc-TeX-math-Ix,MJXc-TeX-math-Iw} -.MJXc-TeX-size1-R {font-family: MJXc-TeX-size1-R,MJXc-TeX-size1-Rw} -.MJXc-TeX-size2-R {font-family: MJXc-TeX-size2-R,MJXc-TeX-size2-Rw} -.MJXc-TeX-size3-R {font-family: MJXc-TeX-size3-R,MJXc-TeX-size3-Rw} -.MJXc-TeX-size4-R {font-family: MJXc-TeX-size4-R,MJXc-TeX-size4-Rw} -@font-face {font-family: MJXc-TeX-ams-R; src: local('MathJax_AMS'), local('MathJax_AMS-Regular')} -@font-face {font-family: MJXc-TeX-ams-Rw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_AMS-Regular.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_AMS-Regular.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_AMS-Regular.otf') format('opentype')} -@font-face {font-family: MJXc-TeX-cal-B; src: local('MathJax_Caligraphic Bold'), local('MathJax_Caligraphic-Bold')} -@font-face {font-family: MJXc-TeX-cal-Bx; src: local('MathJax_Caligraphic'); font-weight: bold} -@font-face {font-family: MJXc-TeX-cal-Bw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_Caligraphic-Bold.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_Caligraphic-Bold.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_Caligraphic-Bold.otf') format('opentype')} -@font-face {font-family: MJXc-TeX-frak-R; src: local('MathJax_Fraktur'), local('MathJax_Fraktur-Regular')} -@font-face {font-family: MJXc-TeX-frak-Rw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_Fraktur-Regular.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_Fraktur-Regular.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_Fraktur-Regular.otf') format('opentype')} -@font-face {font-family: MJXc-TeX-frak-B; src: local('MathJax_Fraktur Bold'), local('MathJax_Fraktur-Bold')} -@font-face {font-family: MJXc-TeX-frak-Bx; src: local('MathJax_Fraktur'); font-weight: bold} -@font-face {font-family: MJXc-TeX-frak-Bw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_Fraktur-Bold.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_Fraktur-Bold.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_Fraktur-Bold.otf') format('opentype')} -@font-face {font-family: MJXc-TeX-math-BI; src: local('MathJax_Math BoldItalic'), local('MathJax_Math-BoldItalic')} -@font-face {font-family: MJXc-TeX-math-BIx; src: local('MathJax_Math'); font-weight: bold; font-style: italic} -@font-face {font-family: MJXc-TeX-math-BIw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_Math-BoldItalic.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_Math-BoldItalic.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_Math-BoldItalic.otf') format('opentype')} -@font-face {font-family: MJXc-TeX-sans-R; src: local('MathJax_SansSerif'), local('MathJax_SansSerif-Regular')} -@font-face {font-family: MJXc-TeX-sans-Rw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_SansSerif-Regular.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_SansSerif-Regular.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_SansSerif-Regular.otf') format('opentype')} -@font-face {font-family: MJXc-TeX-sans-B; src: local('MathJax_SansSerif Bold'), local('MathJax_SansSerif-Bold')} -@font-face {font-family: MJXc-TeX-sans-Bx; src: local('MathJax_SansSerif'); font-weight: bold} -@font-face {font-family: MJXc-TeX-sans-Bw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_SansSerif-Bold.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_SansSerif-Bold.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_SansSerif-Bold.otf') format('opentype')} -@font-face {font-family: MJXc-TeX-sans-I; src: local('MathJax_SansSerif Italic'), local('MathJax_SansSerif-Italic')} -@font-face {font-family: MJXc-TeX-sans-Ix; src: local('MathJax_SansSerif'); font-style: italic} -@font-face {font-family: MJXc-TeX-sans-Iw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_SansSerif-Italic.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_SansSerif-Italic.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_SansSerif-Italic.otf') format('opentype')} -@font-face {font-family: MJXc-TeX-script-R; src: local('MathJax_Script'), local('MathJax_Script-Regular')} -@font-face {font-family: MJXc-TeX-script-Rw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_Script-Regular.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_Script-Regular.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_Script-Regular.otf') format('opentype')} -@font-face {font-family: MJXc-TeX-type-R; src: local('MathJax_Typewriter'), local('MathJax_Typewriter-Regular')} -@font-face {font-family: MJXc-TeX-type-Rw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_Typewriter-Regular.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_Typewriter-Regular.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_Typewriter-Regular.otf') format('opentype')} -@font-face {font-family: MJXc-TeX-cal-R; src: local('MathJax_Caligraphic'), local('MathJax_Caligraphic-Regular')} -@font-face {font-family: MJXc-TeX-cal-Rw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_Caligraphic-Regular.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_Caligraphic-Regular.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_Caligraphic-Regular.otf') format('opentype')} -@font-face {font-family: MJXc-TeX-main-B; src: local('MathJax_Main Bold'), local('MathJax_Main-Bold')} -@font-face {font-family: MJXc-TeX-main-Bx; src: local('MathJax_Main'); font-weight: bold} -@font-face {font-family: MJXc-TeX-main-Bw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_Main-Bold.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_Main-Bold.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_Main-Bold.otf') format('opentype')} -@font-face {font-family: MJXc-TeX-main-I; src: local('MathJax_Main Italic'), local('MathJax_Main-Italic')} -@font-face {font-family: MJXc-TeX-main-Ix; src: local('MathJax_Main'); font-style: italic} -@font-face {font-family: MJXc-TeX-main-Iw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_Main-Italic.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_Main-Italic.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_Main-Italic.otf') format('opentype')} -@font-face {font-family: MJXc-TeX-main-R; src: local('MathJax_Main'), local('MathJax_Main-Regular')} -@font-face {font-family: MJXc-TeX-main-Rw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_Main-Regular.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_Main-Regular.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_Main-Regular.otf') format('opentype')} -@font-face {font-family: MJXc-TeX-math-I; src: local('MathJax_Math Italic'), local('MathJax_Math-Italic')} -@font-face {font-family: MJXc-TeX-math-Ix; src: local('MathJax_Math'); font-style: italic} -@font-face {font-family: MJXc-TeX-math-Iw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_Math-Italic.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_Math-Italic.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_Math-Italic.otf') format('opentype')} -@font-face {font-family: MJXc-TeX-size1-R; src: local('MathJax_Size1'), local('MathJax_Size1-Regular')} -@font-face {font-family: MJXc-TeX-size1-Rw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_Size1-Regular.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_Size1-Regular.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_Size1-Regular.otf') format('opentype')} -@font-face {font-family: MJXc-TeX-size2-R; src: local('MathJax_Size2'), local('MathJax_Size2-Regular')} -@font-face {font-family: MJXc-TeX-size2-Rw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_Size2-Regular.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_Size2-Regular.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_Size2-Regular.otf') format('opentype')} -@font-face {font-family: MJXc-TeX-size3-R; src: local('MathJax_Size3'), local('MathJax_Size3-Regular')} -@font-face {font-family: MJXc-TeX-size3-Rw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_Size3-Regular.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_Size3-Regular.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_Size3-Regular.otf') format('opentype')} -@font-face {font-family: MJXc-TeX-size4-R; src: local('MathJax_Size4'), local('MathJax_Size4-Regular')} -@font-face {font-family: MJXc-TeX-size4-Rw; src /*1*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/eot/MathJax_Size4-Regular.eot'); src /*2*/: url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/woff/MathJax_Size4-Regular.woff') format('woff'), url('https://cdn.mathjax.org/mathjax/latest/fonts/HTML-CSS/TeX/otf/MathJax_Size4-Regular.otf') format('opentype')} diff --git a/app/assets/stylesheets/lcms/engine/vendors/react-tagsinput.css b/app/assets/stylesheets/lcms/engine/vendors/react-tagsinput.css deleted file mode 100644 index 079cd4d5..00000000 --- a/app/assets/stylesheets/lcms/engine/vendors/react-tagsinput.css +++ /dev/null @@ -1,48 +0,0 @@ -.react-tagsinput { - background-color: #fff; - border: 1px solid #ccc; - overflow: hidden; - padding-left: 5px; - padding-top: 5px; -} - -.react-tagsinput--focused { - border-color: #a5d24a; -} - -.react-tagsinput-tag { - background-color: #cde69c; - border-radius: 2px; - border: 1px solid #a5d24a; - color: #638421; - display: inline-block; - font-family: sans-serif; - font-size: 13px; - font-weight: 400; - margin-bottom: 5px; - margin-right: 5px; - padding: 5px; -} - -.react-tagsinput-remove { - cursor: pointer; - font-weight: bold; -} - -.react-tagsinput-tag a::before { - content: " ×"; -} - -.react-tagsinput-input { - background: transparent; - border: 0; - color: #777; - font-family: sans-serif; - font-size: 13px; - font-weight: 400; - margin-bottom: 6px; - margin-top: 1px; - outline: none; - padding: 5px; - width: 80px; -} diff --git a/app/controllers/concerns/lcms/engine/flashable.rb b/app/controllers/concerns/lcms/engine/flashable.rb new file mode 100644 index 00000000..f8d07bd7 --- /dev/null +++ b/app/controllers/concerns/lcms/engine/flashable.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Lcms + module Engine + module Flashable + extend ActiveSupport::Concern + + # Stores a flash message in Redis, if configured, + # or logs an error message and returns an alternative + # message if Redis is not configured. + # + # If the original message length is less than the + # maximum character limit, returns the original message. + # + # @param [String] original_message The original flash message string + # @return [String] Either the original message or the key to retrieve the stored message from Redis + # + def store_flash_message(original_message) + return original_message if original_message.length < Lcms::Engine::FLASH_MESSAGE_MAX_CHAR + + redis = Rails.application.config.redis + if redis.present? + key = "#{Lcms::Engine::FLASH_REDIS_PREFIX}#{SecureRandom.hex}" + redis.set(key, original_message) + redis.expire(key, 1.hour) + key + else + Rails.logger.error 'Preview error: Redis is not configured' + Rails.logger.error "Preview error: #{original_message}" + 'Error is too long to be displayed. Please check the logs.' + end + end + end + end +end diff --git a/app/controllers/concerns/lcms/engine/google_credentials.rb b/app/controllers/concerns/lcms/engine/google_credentials.rb index b1e2a3e3..e2bb7321 100644 --- a/app/controllers/concerns/lcms/engine/google_credentials.rb +++ b/app/controllers/concerns/lcms/engine/google_credentials.rb @@ -9,6 +9,8 @@ module GoogleCredentials def google_credentials @google_credentials ||= Lt::Google::Api::Auth::Cli.new.credentials + rescue StandardError + raise "Can't get file based credentials to access Google Drive!" end end end diff --git a/app/controllers/concerns/lcms/engine/location_storable.rb b/app/controllers/concerns/lcms/engine/location_storable.rb index bab59b89..2307117c 100644 --- a/app/controllers/concerns/lcms/engine/location_storable.rb +++ b/app/controllers/concerns/lcms/engine/location_storable.rb @@ -12,7 +12,7 @@ module LocationStorable private def pdf_request? - request.path.index('pdfjs').present? || request.path.index('pdf-proxy').present? + request.path.index('pdf-proxy').present? end def storable_location? diff --git a/app/controllers/concerns/lcms/engine/nested_reimportable.rb b/app/controllers/concerns/lcms/engine/nested_reimportable.rb index 651eb23a..1c8f2325 100644 --- a/app/controllers/concerns/lcms/engine/nested_reimportable.rb +++ b/app/controllers/concerns/lcms/engine/nested_reimportable.rb @@ -5,23 +5,35 @@ module Engine module NestedReimportable private - def import_status_for(job_class) + # + # @param job_class [Class] The job class. + # @return [Hash] The status of the job. + # + # def import_status_for(job_class) + def import_status_for_nested(job_class) params.fetch(:jids, []).each_with_object({}) do |jid, obj| status = job_class.status_nested(jid) obj[jid] = { - status: status, + status:, result: (status == :done ? flatten_result(job_class, jid) : nil) }.compact end end + # + # @param job_class [Class] The job class. + # @param jid [String] The job ID. + # @return [Hash] The result of the job. + # def flatten_result(job_class, jid) jid_res = job_class.fetch_result(jid) - return jid_res if (failed = job_class.fetch_result_nested(jid).reject { |j| j['ok'] }).blank? + result_nested = job_class.fetch_result_nested(jid) + # Return in case of no errors + return jid_res unless result_nested.any? { _1['ok'] == false } - { ok: false, errors: jid_res&.dig('errors') || [] }.tap do |failed_result| - failed.each do |e| - failed_result[:errors] << "Source: #{e['errors'].join(', ')}" + { ok: false, errors: jid_res&.[]('errors') || [] }.tap do |failed_result| + result_nested.select { _1['ok'] == false }.each do |e| + failed_result[:errors] << "Source: #{(e['errors'] || []).join(', ')}" end end end diff --git a/app/controllers/concerns/lcms/engine/queryable.rb b/app/controllers/concerns/lcms/engine/queryable.rb new file mode 100644 index 00000000..238bcbeb --- /dev/null +++ b/app/controllers/concerns/lcms/engine/queryable.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Lcms + module Engine + module Queryable + extend ActiveSupport::Concern + + included do + before_action :set_query_params + end + + def set_query_params + @query_params = params[:query]&.permit(*self.class::QUERY_ATTRS_KEYS, self.class::QUERY_ATTRS_NESTED) || {} + end + + def query_struct(attrs) + Struct.new(*self.class::QUERY_ATTRS_KEYS, keyword_init: true).new(attrs.to_h) + end + end + end +end diff --git a/app/controllers/concerns/lcms/engine/reimportable.rb b/app/controllers/concerns/lcms/engine/reimportable.rb index 11c155cf..c659e537 100644 --- a/app/controllers/concerns/lcms/engine/reimportable.rb +++ b/app/controllers/concerns/lcms/engine/reimportable.rb @@ -18,7 +18,7 @@ def import_status_for(job_class) params.fetch(:jids, []).each_with_object({}) do |jid, obj| status = job_class.status(jid) obj[jid] = { - status: status, + status:, result: (status == :done ? prepare_result(job_class, jid) : nil) }.compact end @@ -26,11 +26,18 @@ def import_status_for(job_class) def prepare_result(job_class, jid) jid_res = job_class.fetch_result(jid) - return jid_res if jid_res['ok'] + return jid_res if jid_res&.[]('ok') + + error = + if jid_res.present? + "Source: #{jid_res['errors'].join(', ')}" + else + "Error fetching result for #{jid}" + end { ok: false, - errors: Array.wrap("Source: #{jid_res['errors'].join(', ')}") + errors: Array.wrap(error) } end end diff --git a/app/controllers/lcms/engine/admin/admin_controller.rb b/app/controllers/lcms/engine/admin/admin_controller.rb index 4e510ba3..b88d0825 100644 --- a/app/controllers/lcms/engine/admin/admin_controller.rb +++ b/app/controllers/lcms/engine/admin/admin_controller.rb @@ -9,18 +9,17 @@ class AdminController < Lcms::Engine::ApplicationController CONFIG_PATH ||= Rails.root.join('config', 'lcms-admin.yml') DEFAULTS ||= { - layout: 'lcms/engine/admin', - materials_query: Lcms::Engine::AdminMaterialsQuery + layout: 'lcms/engine/admin' }.freeze - RE_GOOGLE_FOLDER = %r{/drive/(.*/)?folders/}.freeze + RE_GOOGLE_FOLDER = %r{/drive/(.*/)?folders/} layout :customized_layout before_action :authenticate_admin! def self.settings - @settings ||= DEFAULTS.merge((YAML.load_file(CONFIG_PATH) || {}).deep_symbolize_keys) + @settings ||= DEFAULTS.merge((YAML.load_file(CONFIG_PATH, aliases: true) || {}).deep_symbolize_keys) end private @@ -43,6 +42,10 @@ def render_customized_view render customized_view if customized_view end + # + # @see lcms-admin.yml + # @return [Array] + # def view_links Array.wrap(AdminController.settings.dig(controller_name.to_sym, :view_links)) end diff --git a/app/controllers/lcms/engine/admin/association_picker_controller.rb b/app/controllers/lcms/engine/admin/association_picker_controller.rb deleted file mode 100644 index 2f76ae86..00000000 --- a/app/controllers/lcms/engine/admin/association_picker_controller.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - module Admin - class AssociationPickerController < AdminController - VALID_ASSOCIATIONS = %w( - content_sources grades topics tags - reading_assignment_authors reading_assignment_texts - standards - ).freeze - - def index - @items = association_items - @items = @items.where('name ilike ?', "%#{params[:q]}%") if index_params[:q].present? - @items = @items.paginate(page: params[:page], per_page: 10).order('name asc') - - respond_to do |format| - format.json { render json: pagination.serialize(@items, AssociationItemSerializer) } - end - end - - private - - def pagination - @pagination ||= Pagination.new params - end - - def index_params - @index_params ||= - begin - expected_params = params - .permit(:association, :page, :pagination, :allowCreate, :allowMultiple, - :onClickDone, :onSelectItem, :q) - .to_h.symbolize_keys - index_p = { q: nil }.merge(expected_params) - - raise StandardError unless VALID_ASSOCIATIONS.include?(index_p[:association]) - - index_p[:association] = index_p[:association].to_sym - index_p - end - end - - def association_items - case index_params[:association] - when :content_sources then tag_scope('content_sources') - when :grades then tag_scope('grades') - when :topics then tag_scope('topics') - when :tags then tag_scope('tags') - when :reading_assignment_authors then ReadingAssignmentAuthor - when :reading_assignment_texts then ReadingAssignmentText - when :standards then Standard - end - end - - def tag_scope(context) - Tag.where_context(context).select(:id, :name).distinct(:name) - end - end - end - end -end diff --git a/app/controllers/lcms/engine/admin/batch_reimports_controller.rb b/app/controllers/lcms/engine/admin/batch_reimports_controller.rb index f28cd48a..bd0d9a67 100644 --- a/app/controllers/lcms/engine/admin/batch_reimports_controller.rb +++ b/app/controllers/lcms/engine/admin/batch_reimports_controller.rb @@ -4,30 +4,69 @@ module Lcms module Engine module Admin class BatchReimportsController < AdminController + include Reimportable + include Lcms::Engine::Queryable + + before_action :set_query_params + + QUERY_ATTRS = %i( + grade + module + subject + type + unit + ).freeze + QUERY_ATTRS_NESTED = {}.freeze + QUERY_ATTRS_KEYS = QUERY_ATTRS + QUERY_ATTRS_NESTED.keys + def new - @query = OpenStruct.new(params[:query]) # rubocop:disable Style/OpenStructUse + @query = query_struct(@query_params) end def create - @query = OpenStruct.new params[:query].except(:type) # rubocop:disable Style/OpenStructUse + @query = query_struct(@query_params.except(:type)) + + # @see lcms.yml + # Possible default values: + # - ::Lcms::Engine::AdminDocumentsQuery + # - ::Lcms::Engine::AdminMaterialsQuery entries = if materials? - DocTemplate.config['queries']['material'].constantize.call(@query) + DocTemplate.config.dig('queries', 'material').constantize.call(@query) else - DocTemplate.config['queries']['document'].constantize.call(@query) + DocTemplate.config.dig('queries', 'document').constantize.call(@query) end - bulk_import entries + + if entries.empty? + redirect_to lcms_engine.new_admin_batch_reimport_path(query: @query.to_h), + notice: 'Nothing found' + return + end + + bulk_import entries.map(&:file_url) render :import end + def import_status + data = import_status_for(job_class) + render json: data, status: :ok + end + private - def bulk_import(docs) - jobs = {} - docs.each do |doc| - job_id = job_class.perform_later(doc).job_id - jobs[job_id] = { link: doc.file_url, status: 'waiting' } + # + # @param [Array] file_urls + # + def bulk_import(file_urls) + jobs = file_urls.each_with_object({}) do |url, jobs_| + job_id = job_class.perform_later(url).job_id + jobs_[job_id] = { link: url, status: 'waiting' } end - @props = { jobs: jobs, type: params.dig(:query, :type), links: view_links } + @props = { + jobs:, + links: view_links, + polling_path: lcms_engine.import_status_admin_batch_reimport_path, + type: params.dig(:query, :type) + }.transform_keys! { _1.to_s.camelize(:lower) } end def job_class @@ -35,7 +74,25 @@ def job_class end def materials? - params.dig(:query, :type) == 'materials' + params.dig(:query, :type) == 'materials' || params[:type].to_s == 'materials' + end + + # + # @return [Array] + # + def view_links + type = materials? ? :materials : :documents + Array.wrap(AdminController.settings.dig(type, :view_links)) + end + + def set_query_params + super + @query_params[:grade] = adjust_grade_value(@query_params[:grade]) + end + + def adjust_grade_value(val) + number_val = val.to_s[/\d+/] + number_val.nil? ? val : number_val end end end diff --git a/app/controllers/lcms/engine/admin/documents_controller.rb b/app/controllers/lcms/engine/admin/documents_controller.rb index 4ca0f797..d01dc34f 100644 --- a/app/controllers/lcms/engine/admin/documents_controller.rb +++ b/app/controllers/lcms/engine/admin/documents_controller.rb @@ -7,18 +7,38 @@ class DocumentsController < AdminController include Lcms::Engine::GoogleCredentials include Lcms::Engine::PathHelper include Reimportable + include Lcms::Engine::Queryable before_action :find_selected, only: %i(destroy_selected reimport_selected) - before_action :set_query_params + before_action :set_query_params # from Lcms::Engine::Queryable + + QUERY_ATTRS = %i( + broken_materials + course + grade + inactive + locale + module + only_failed + reimport_required + search_term + sort_by + subject + unit + ).freeze + QUERY_ATTRS_NESTED = { + grades: [] + }.freeze + QUERY_ATTRS_KEYS = QUERY_ATTRS + QUERY_ATTRS_NESTED.keys def index - @query = OpenStruct.new @query_params # rubocop:disable Style/OpenStructUse + @query = query_struct(@query_params) @documents = DocTemplate.config['queries']['document'].constantize.call(@query, page: params[:page]) render_customized_view end def create - @document = DocumentForm.new(form_params.except(:async, :with_materials)) + @document_form = DocumentForm.new(form_params.except(:async, :with_materials)) return create_multiple if form_params[:link].match?(RE_GOOGLE_FOLDER) @@ -26,14 +46,14 @@ def create end def destroy - @document = Document.find(params[:id]) + @document = Document.find(params[:id].to_i) @document.destroy redirect_to lcms_engine.admin_documents_path(query: @query_params), notice: t('.success') end def destroy_selected count = @documents.destroy_all.count - redirect_to lcms_engine.admin_documents_path(query: @query_params), notice: t('.success', count: count) + redirect_to lcms_engine.admin_documents_path(query: @query_params), notice: t('.success', count:) end def import_status @@ -42,48 +62,61 @@ def import_status end def new - @document = DocumentForm.new + @document_form = DocumentForm.new end def reimport_selected - bulk_import @documents + bulk_import @documents.map(&:file_url) render :import end private def create_async - bulk_import(Array.wrap(form_params[:link])) + bulk_import Array.wrap(form_params[:link]) render :import end def create_sync reimport_lesson_materials if form_params[:with_materials].present? - if @document.save - notice = t('lcms.engine.admin.documents.create.success', - name: @document.document.name, - errors: collect_errors) - redirect_to dynamic_document_path(@document.document), notice: notice + + if @document_form.save + flash_message = + if collect_errors.empty? + { notice: t('lcms.engine.admin.documents.create.success', name: @document_form.document.name) } + else + { alert: t('lcms.engine.admin.documents.create.error', + name: @document_form.document.name, + errors: collect_errors) } + end + redirect_to dynamic_document_path(@document_form.document, query: @query_params), **flash_message else render :new end end - def bulk_import(docs) + # + # @param [Array] file_urls + # + def bulk_import(file_urls) reimport_materials = params[:with_materials].to_i.nonzero? - jobs = docs.each_with_object({}) do |doc, jobs_| - job_id = DocumentGenerator.document_parse_job - .perform_later(doc, reimport_materials: reimport_materials).job_id - link = doc.is_a?(Document) ? doc.file_url : doc - jobs_[job_id] = { link: link, status: 'waiting' } + jobs = file_urls.each_with_object({}) do |url, jobs_| + job_id = DocumentGenerator.document_parse_job.perform_later(url, reimport_materials:).job_id + jobs_[job_id] = { link: url, status: 'waiting' } end - @props = { jobs: jobs, type: :documents, links: view_links } + polling_path = lcms_engine.import_status_admin_documents_path + @props = + { jobs:, links: view_links, polling_path:, type: :documents } + .transform_keys! { _1.to_s.camelize(:lower).to_sym } end def collect_errors - return if @document.service_errors.empty? - - "Errors: #{@document.service_errors.join(' ')}" + @collect_errors ||= + if @document_form.service_errors.empty? + [] + else + @document_form.service_errors.map { "
  • #{_1}
  • " }.join + end end def find_selected @@ -103,7 +136,7 @@ def gdoc_files_from(url) def form_params @form_params ||= begin - data = params.require(:document_form).permit(:async, :link, :link_fs, :reimport, :with_materials).to_h + data = params.require(:document_form).permit(:async, :link, :with_materials).to_h data.delete(:with_materials) if data[:with_materials].to_i.zero? data end @@ -117,21 +150,13 @@ def reimport_lesson def reimport_lesson_materials file_id = ::Lt::Lcms::Lesson::Downloader::Base.file_id_for form_params['link'] - doc = Document.actives.find_by(file_id: file_id) + doc = Document.actives.find_by(file_id:) return unless doc doc.materials.each do |material| - MaterialForm.new({ link: material.file_url, source_type: material.source_type }, google_credentials).save + MaterialForm.new({ link: material.file_url, source_type: material.source_type }).save end end - - def set_query_params - @query_params = params[:query] - &.permit( - :broken_materials, :course, :grade, :inactive, :locale, :module, :only_failed, :reimport_required, - :search_term, :sort_by - ) || {} - end end end end diff --git a/app/controllers/lcms/engine/admin/materials_controller.rb b/app/controllers/lcms/engine/admin/materials_controller.rb index 4e1b95e2..140a0b49 100644 --- a/app/controllers/lcms/engine/admin/materials_controller.rb +++ b/app/controllers/lcms/engine/admin/materials_controller.rb @@ -6,19 +6,40 @@ module Admin class MaterialsController < AdminController include Lcms::Engine::GoogleCredentials include Lcms::Engine::PathHelper - include Reimportable + include Lcms::Engine::Reimportable + include Lcms::Engine::Queryable before_action :find_selected, only: %i(destroy_selected reimport_selected) - before_action :set_query_params + before_action :set_query_params # from Lcms::Engine::Queryable + + QUERY_ATTRS = %i( + header_footer + guidebook + section + activity + lesson + name_date + orientation + search_term + search_file_name + sort_by + title + type + unit + ).freeze + QUERY_ATTRS_NESTED = { + grades: [] + }.freeze + QUERY_ATTRS_KEYS = QUERY_ATTRS + QUERY_ATTRS_NESTED.keys def index - @query = OpenStruct.new @query_params # rubocop:disable Style/OpenStructUse + @query = query_struct(@query_params) @materials = DocTemplate.config['queries']['material'].constantize.call(@query, page: params[:page]) render_customized_view end def create - @material_form = DocumentGenerator.material_form.new(form_params.except(:async)) + @material_form = DocumentGenerator.material_form.new(form_params.except(:async).to_h) return create_multiple if form_params[:link].match?(RE_GOOGLE_FOLDER) @@ -26,18 +47,18 @@ def create end def destroy - material = Material.find(params[:id]) + material = Material.find(params[:id].to_i) material.destroy redirect_to lcms_engine.admin_materials_path(query: @query_params), notice: t('.success') end def destroy_selected count = @materials.destroy_all.count - redirect_to lcms_engine.admin_materials_path(query: @query_params), notice: t('.success', count: count) + redirect_to lcms_engine.admin_materials_path(query: @query_params), notice: t('.success', count:) end def import_status - data = import_status_for DocumentGenerator.material_parse_job + data = import_status_for(DocumentGenerator.material_parse_job) render json: data, status: :ok end @@ -52,28 +73,49 @@ def reimport_selected private + def collect_errors + @collect_errors ||= + if @material_form.service_errors.empty? + [] + else + @material_form.service_errors.map { "
  • #{_1}
  • " }.join + end + end + def create_async - bulk_import(Array.wrap(form_params[:link])) + bulk_import Array.wrap(form_params[:link]) render :import end def create_sync if @material_form.save material = @material_form.material - redirect_to dynamic_material_path(material), - notice: t('lcms.engine.admin.materials.create.success', name: material.name) + flash_message = + if collect_errors.empty? + { notice: t('lcms.engine.admin.materials.create.success', name: material.name) } + else + { alert: t('lcms.engine.admin.materials.create.error', + name: material.name, + errors: collect_errors) } + end + redirect_to dynamic_material_path(material, query: @query_params), **flash_message else render :new end end - def bulk_import(files) - jobs = {} - files.each do |url| - job_id = DocumentGenerator.material_parse_job.perform_later(url).job_id - jobs[job_id] = { link: url, status: 'waiting' } - end - @props = { jobs: jobs, type: :materials, links: view_links } + # + # @param [Array] file_urls + # + def bulk_import(file_urls) + jobs = + file_urls.each_with_object({}) do |url, jobs_| + job_id = DocumentGenerator.material_parse_job.perform_later(url).job_id + jobs_[job_id] = { link: url, status: 'waiting' } + end + polling_path = lcms_engine.import_status_admin_materials_path + @props = { jobs:, links: view_links, polling_path:, type: :materials } + .transform_keys! { _1.to_s.camelize(:lower).to_sym } end def find_selected @@ -84,15 +126,19 @@ def find_selected end def form_params - params.require(:material_form).permit(:async, :link, :source_type) + @form_params ||= params.require(:material_form).permit(:async, :link, :source_type) end + # + # @param [String] url + # @return [Array] + # def gdoc_files_from(url) folder_id = ::Lt::Google::Api::Drive.folder_id_for(url) if form_params[:source_type] == 'pdf' mime_type = Lt::Lcms::Lesson::Downloader::PDF::MIME_TYPE ::Lt::Google::Api::Drive.new(google_credentials) - .list_file_ids_in(folder_id, mime_type: mime_type) + .list_file_ids_in(folder_id, mime_type:) .map { |id| ::Lt::Lcms::Lesson::Downloader::PDF.gdoc_file_url(id) } else ::Lt::Google::Api::Drive.new(google_credentials) @@ -100,14 +146,6 @@ def gdoc_files_from(url) .map { |id| ::Lt::Lcms::Lesson::Downloader::Gdoc.gdoc_file_url(id) } end end - - def set_query_params - @query_params = params[:query] - &.permit( - :course, :lesson, :name_date, :orientation, :search_term, :search_file_name, :sort_by, :title, :type, - :unit - ) || {} - end end end end diff --git a/app/controllers/lcms/engine/admin/resource_bulk_edits_controller.rb b/app/controllers/lcms/engine/admin/resource_bulk_edits_controller.rb deleted file mode 100644 index e84573c6..00000000 --- a/app/controllers/lcms/engine/admin/resource_bulk_edits_controller.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - module Admin - class ResourceBulkEditsController < AdminController - before_action :load_resources - - def new - if @resources.any? - @resource = BulkEditResourcesService.new(@resources).init_sample - else - redirect_to lcms_engine.admin_resources_path, alert: t('.no_resources') - end - end - - def create - BulkEditResourcesService.new(@resources, resource_params).edit! - resources_count_msg = t(:resources_count, count: @resources.count) - notice = t('.success', count: @resources.count, resources_count: resources_count_msg) - redirect_to lcms_engine.admin_resources_path, notice: notice - end - - private - - def load_resources - @resources = Resource.where(id: params[:ids]).includes(:standards) - end - - def resource_params - params.require(:resource) - .permit(standard_ids: [], - grades: [], - resource_type_list: [], - tag_list: []) - end - end - end - end -end diff --git a/app/controllers/lcms/engine/admin/resource_picker_controller.rb b/app/controllers/lcms/engine/admin/resource_picker_controller.rb deleted file mode 100644 index 33f0598f..00000000 --- a/app/controllers/lcms/engine/admin/resource_picker_controller.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - module Admin - class ResourcePickerController < AdminController - def index - @resources = Resource.where(nil) - - @resources = @resources.where_subject(index_params[:subject]) if index_params[:subject].present? - @resources = @resources.where(curriculum_type: index_params[:type]) if index_params[:type].present? - @resources = @resources.where_grade(grade_name) if index_params[:grade].present? - @resources = @resources.where('title ilike ?', "%#{params[:q]}%") if index_params[:q].present? - - @resources = @resources.paginate(pagination.params(strict: true)).order('resources.title asc') - - respond_to do |format| - format.json { render json: pagination.serialize(@resources, ResourcePickerSerializer) } - end - end - - private - - def index_params - @index_params ||= begin - default_params = { type: nil, subject: nil, grade: nil, q: nil } - expected_params = params.permit(:type, :subject, :grade, :q).to_h.symbolize_keys - index_p = default_params.merge(expected_params) - - grade_ok = index_p[:grade].blank? || Filterbar::GRADES.include?(index_p[:grade]) - type_ok = index_p[:type].blank? || Resource.hierarchy.include?(index_p[:type].to_sym) - subject_ok = index_p[:subject].blank? || Resource::SUBJECTS.include?(index_p[:subject]) - - raise StandardError unless grade_ok && type_ok && subject_ok - - index_p - end - end - - def pagination - @pagination ||= Pagination.new params - end - - def grade_name - if index_params[:grade].casecmp('K').zero? - 'kindergarten' - elsif index_params[:grade].casecmp('PK').zero? - 'prekindergarten' - elsif !index_params[:grade].start_with?('grade') - "grade #{index_params[:grade]}" - else - index_params[:grade] - end - end - end - end - end -end diff --git a/app/controllers/lcms/engine/admin/resources_controller.rb b/app/controllers/lcms/engine/admin/resources_controller.rb index 914a7fde..688a2346 100644 --- a/app/controllers/lcms/engine/admin/resources_controller.rb +++ b/app/controllers/lcms/engine/admin/resources_controller.rb @@ -48,7 +48,7 @@ def export_to_lti_cc data = LtiExporter.perform @resource filename = "#{@resource.slug.parameterize}.zip" - send_data data, filename: filename, type: 'application/zip', disposition: 'attachment' + send_data data, filename:, type: 'application/zip', disposition: 'attachment' end def bundle @@ -62,7 +62,7 @@ def bundle def update unless Settings[:editing_enabled] - return redirect_to(lcms_engine.admin_resources_path, alert: t('admin.common.editing_disabled')) + return redirect_to(lcms_engine.admin_resources_path, alert: t('lcms.engine.admin.common.editing_disabled')) end create_tags @@ -86,20 +86,9 @@ def destroy protected def form_params_arrays - download_categories_settings = - DownloadCategory.select(:title).map do |category| - { category.title.parameterize => %i(show_long_description show_short_description) } - end { additional_resource_ids: [], common_core_standard_ids: [], - download_categories_settings: download_categories_settings, - resource_downloads_attributes: [ - :_destroy, - :description, - :id, - :download_category_id, { download_attributes: %i(description file main filename_cache id title) } - ], related_resource_ids: [], standard_ids: [], new_standard_names: [], @@ -115,7 +104,7 @@ def form_params_arrays end # - # The result of this method will be splatted into +form_params_ararys+ + # The result of this method will be splatted into +form_params_arrays+ # and will be injected into the final +form_params+ call. # # Should be used to extend the list of permitted parameters @@ -142,7 +131,6 @@ def form_params_simple time_to_teach ell_appropriate image_file - opr_description ).concat(form_params_simple_override) end @@ -159,6 +147,10 @@ def form_params_simple_override private + # + # @param [Lcms::Engine::Resource] resource + # @return [Boolean] + # def can_bundle?(resource) DocTemplate .config['bundles'].keys @@ -169,7 +161,7 @@ def can_bundle?(resource) helper_method :can_bundle? def find_resource - @resource = Resource.includes(resource_downloads: :download).find(params[:id]) + @resource = Resource.find(params[:id]) end def grade_params @@ -183,12 +175,8 @@ def form_params form_params_simple, form_params_arrays ).to_h - if ps[:download_categories_settings].present? - ps[:download_categories_settings].transform_values! do |settings| - settings.transform_values! { |x| x == '1' } - end - end - ps[:metadata] = metadata ps.delete(:directory)&.split(',') + directory = ps.delete(:directory) + ps[:metadata] = metadata directory.split(',') if directory.present? ps end end diff --git a/app/controllers/lcms/engine/admin/settings_controller.rb b/app/controllers/lcms/engine/admin/settings_controller.rb index 1448f61f..0b283e69 100644 --- a/app/controllers/lcms/engine/admin/settings_controller.rb +++ b/app/controllers/lcms/engine/admin/settings_controller.rb @@ -7,7 +7,7 @@ class SettingsController < AdminController def toggle_editing_enabled Settings[:editing_enabled] = !Settings[:editing_enabled] notice = Settings[:editing_enabled] ? t('.enabled') : t('.disabled') - redirect_to lcms_engine.admin_resources_path, notice: notice + redirect_to lcms_engine.admin_resources_path, notice: end end end diff --git a/app/controllers/lcms/engine/admin/sketch_compilers_controller.rb b/app/controllers/lcms/engine/admin/sketch_compilers_controller.rb deleted file mode 100644 index 0222ccfc..00000000 --- a/app/controllers/lcms/engine/admin/sketch_compilers_controller.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - module Admin - class SketchCompilersController < AdminController - include Lcms::Engine::GoogleCredentials - - before_action :validate_params, only: [:compile] - - def compile - response = SketchCompiler - .new(current_user.id, request.remote_ip, params[:version]) - .compile(params[:url], params[:foundational_url]) - - if response.success? - url = DocumentExporter::Gdoc::Base.url_for JSON.parse(response.body)['id'] - redirect_back fallback_location: new_admin_sketch_compiler_path, notice: t('.success', url: url) - else - redirect_back fallback_location: new_admin_sketch_compiler_path, alert: t('.compile_error') - end - end - - def new - head :bad_request unless google_credentials.present? - @version = params[:version].presence || 'v1' - end - - private - - def validate_params - redirect_to new_admin_sketch_compiler_path, alert: t('.error') unless params[:url].present? - end - end - end - end -end diff --git a/app/controllers/lcms/engine/admin/standards_controller.rb b/app/controllers/lcms/engine/admin/standards_controller.rb index 366b4edc..5304b376 100644 --- a/app/controllers/lcms/engine/admin/standards_controller.rb +++ b/app/controllers/lcms/engine/admin/standards_controller.rb @@ -4,15 +4,33 @@ module Lcms module Engine module Admin class StandardsController < AdminController - before_action :find_standard, except: [:index] - before_action :set_query_params + include Lcms::Engine::Queryable + + before_action :find_standard, except: %i(import index) + before_action :set_query_params # from Lcms::Engine::Queryable + + QUERY_ATTRS = %i( + name + ).freeze + QUERY_ATTRS_NESTED = {}.freeze + QUERY_ATTRS_KEYS = QUERY_ATTRS + QUERY_ATTRS_NESTED.keys def edit; end + def import + @standard_form = Lcms::Engine::StandardForm.new(standard_form_params) + if @standard_form.save + redirect_to lcms_engine.admin_standards_path, notice: t('.success') + else + @standards = Standard.order('id').paginate(page: 1) + render :index + end + end + def index - @query = OpenStruct.new @query_params # rubocop:disable Style/OpenStructUse + @query = query_struct(@query_params) - scope = Standard.order(:id) + scope = Standard.order('id') scope = scope.search_by_name(@query.name) if @query.name.present? @standards = scope.paginate(page: params[:page]) @@ -32,8 +50,8 @@ def find_standard @standard = Standard.find(params[:id]) end - def set_query_params - @query_params = params[:query]&.permit(:name) || {} + def standard_form_params + params.require(:standard_form).permit(:link) end def standard_params diff --git a/app/controllers/lcms/engine/admin/users_controller.rb b/app/controllers/lcms/engine/admin/users_controller.rb index c8bddefd..97679e21 100644 --- a/app/controllers/lcms/engine/admin/users_controller.rb +++ b/app/controllers/lcms/engine/admin/users_controller.rb @@ -4,11 +4,19 @@ module Lcms module Engine module Admin class UsersController < AdminController + include Lcms::Engine::Queryable + before_action :find_user, except: %i(index new create) - before_action :set_query_params + before_action :set_query_params # from Lcms::Engine::Queryable + + QUERY_ATTRS = %i( + access_code + email + ).freeze + QUERY_ATTRS_KEYS = QUERY_ATTRS def index - @query = OpenStruct.new @query_params # rubocop:disable Style/OpenStructUse + @query = query_struct(@query_params) @users = users(@query) end @@ -27,9 +35,7 @@ def create end end - def edit - @url = lcms_engine.admin_user_path(@user) - end + def edit; end def update if @user.update(user_params) @@ -56,10 +62,6 @@ def find_user @user = User.find(params[:id]) end - def set_query_params - @query_params = params[:query]&.permit(:access_code, :email) || {} - end - def user_params params.require(:user).permit(:access_code, :email, :name, :role) end diff --git a/app/controllers/lcms/engine/api/base_controller.rb b/app/controllers/lcms/engine/api/base_controller.rb new file mode 100644 index 00000000..c2d3865e --- /dev/null +++ b/app/controllers/lcms/engine/api/base_controller.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Lcms + module Engine + module Api + class BaseController < ActionController::Base + protect_from_forgery with: :null_session + + before_action :authenticate_request + + API_REQUEST_EXPIRATION_SECONDS = 300 + + private + + def authenticate_request + timestamp = request.headers['X-Api-Timestamp'] + signature = request.headers['X-Api-Signature'] + + unless timestamp && signature + render json: { error: 'Missing authentication headers' }, status: :unauthorized + return + end + + # Verify timestamp is within 5 minutes + if (Time.now.to_i - timestamp.to_i).abs > API_REQUEST_EXPIRATION_SECONDS + render json: { error: 'Request expired' }, status: :unauthorized + return + end + + # Calculate expected signature + expected_signature = AuthHelper.compute_hmac_signature( + timestamp, + request.fullpath, + request.raw_post, + ENV.fetch('API_SECRET_KEY') + ) + + signature_valid = ActiveSupport::SecurityUtils.secure_compare(signature, expected_signature) + + render json: { error: 'Invalid signature' }, status: :unauthorized unless signature_valid + end + end + end + end +end diff --git a/app/controllers/lcms/engine/api/resources_controller.rb b/app/controllers/lcms/engine/api/resources_controller.rb new file mode 100644 index 00000000..e96ff085 --- /dev/null +++ b/app/controllers/lcms/engine/api/resources_controller.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Lcms + module Engine + module Api + class ResourcesController < BaseController + def index + resources = Resource + + # format for the query string is: ?link_updated_after={link_path}:{timestamp} + if params[:link_updated_after] + link_path, timestamp = params[:link_updated_after].split(':') + + resources = resources.where_link_updated_after(link_path, Time.at(timestamp.to_i)) + end + + resources = resources.where(resource_type: params[:resource_type]) if params[:resource_type] + + render json: resources.all.as_json + end + end + end + end +end diff --git a/app/controllers/lcms/engine/application_controller.rb b/app/controllers/lcms/engine/application_controller.rb index 63dd8b1c..ed9368ef 100644 --- a/app/controllers/lcms/engine/application_controller.rb +++ b/app/controllers/lcms/engine/application_controller.rb @@ -3,14 +3,15 @@ module Lcms module Engine class ApplicationController < ActionController::Base + # protect_from_forgery with: :null_session, prepend: true + protect_from_forgery prepend: true + # store location to use at after sign in or other devise callbacks include LocationStorable before_action :authenticate_user!, unless: :pdf_request? - before_action :check_user_has_survey_filled_in, if: :user_signed_in?, unless: :devise_controller? before_action :configure_permitted_parameters, if: :devise_controller? - before_action :handle_x_frame_headers rescue_from ActiveRecord::RecordNotFound do render 'lcms/engine/pages/not_found', status: :not_found @@ -24,25 +25,14 @@ def t(key, options = {}) translate(key, **options) end - def check_user_has_survey_filled_in - return if current_user.ready_to_go? - - store_location_for(:user, request.url) unless devise_controller? - redirect_to survey_path - end - def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:access_code]) end - def handle_x_frame_headers - response.headers.delete('X-Frame-Options') if params[:controller].index('pdfjs_viewer').present? - end - private def after_sign_in_path_for(resource_or_scope) - stored_location_for(resource_or_scope) || main_app.root_path + stored_location_for(resource_or_scope) || lcms_engine.root_path end def after_sign_out_path_for(resource_or_scope) diff --git a/app/controllers/lcms/engine/documents_controller.rb b/app/controllers/lcms/engine/documents_controller.rb index 0573b17d..ff811266 100644 --- a/app/controllers/lcms/engine/documents_controller.rb +++ b/app/controllers/lcms/engine/documents_controller.rb @@ -24,17 +24,13 @@ def export_status render json: data, status: :ok end - def show - @props = CurriculumMap.new(@document.resource).props - end + def show; end def show_lti # To allow access from iFrame element response.headers.delete 'X-Frame-Options' - @props = CurriculumMap.new(@document.resource).props.merge(links_new_tab: true) - - render layout: 'lti' + render layout: 'application_lti' end private @@ -57,7 +53,7 @@ def export_gdoc folder = "#{@document.gdoc_folder}_#{SecureRandom.hex(10)}" options = { bundle: (type == 'full'), - excludes: excludes, + excludes:, gdoc_folder: folder, content_type: type } @@ -73,13 +69,13 @@ def export_pdf filename = "documents-custom/#{SecureRandom.hex(10)}-#{@document.pdf_filename}" url = S3Service.url_for(filename) options = { - excludes: excludes, - filename: filename, + excludes:, + filename:, content_type: type } job_id = DocumentGeneratePdfJob.perform_later(@doc, options).job_id - render json: { id: job_id, url: url }, status: :ok + render json: { id: job_id, url: }, status: :ok end def pdf_key(pdf_type) diff --git a/app/controllers/lcms/engine/resources_controller.rb b/app/controllers/lcms/engine/resources_controller.rb index e1288287..5b8e14f6 100644 --- a/app/controllers/lcms/engine/resources_controller.rb +++ b/app/controllers/lcms/engine/resources_controller.rb @@ -9,20 +9,8 @@ def show # redirect to document if resource has it (#161) return redirect_to dynamic_document_path(@resource.document) if @resource.document? - # redirect grade and module to explore_curriculum (#122) - return redirect_to lcms_engine.explore_curriculum_index_path(p: @resource.slug, e: 1) if grade_or_module? - # redirect to the path with slug if we are using just the id - return redirect_to lcms_engine.show_with_slug_path(@resource.slug), status: 301 if using_id? - - @related_instructions = related_instructions - @props = CurriculumMap.new(@resource).props - end - - def related_instruction - @resource = Resource.find params[:id] - @related_instructions = related_instructions - render json: { instructions: @instructions } + redirect_to lcms_engine.show_with_slug_path(@resource.slug), status: 301 if using_id? end def media @@ -32,23 +20,6 @@ def media @resource = MediaPresenter.new(resource) end - def generic - resource = Resource.find(params[:id]) - return redirect_to lcms_engine.resource_path(resource) unless resource.generic? - - @resource = GenericPresenter.new(resource) - end - - def pdf_proxy - return head(:not_found) if (url = params[:url]).blank? - - uri = URI.parse(Addressable::URI.escape url) - send_data uri.open.read, disposition: :inline, file_name: url.split('/').last - rescue StandardError => e - Rails.logger.warn "PDF-proxy failed! Url: #{url}, Error: #{e.message}" - head :bad_request - end - protected def find_resource @@ -67,11 +38,6 @@ def grade_or_module? def using_id? params[:id].present? && @resource.slug end - - def related_instructions - expanded = params[:expanded] == 'true' - RelatedInstructionsService.new(@resource, expanded) - end end end end diff --git a/app/controllers/lcms/engine/surveys_controller.rb b/app/controllers/lcms/engine/surveys_controller.rb deleted file mode 100644 index 742f9261..00000000 --- a/app/controllers/lcms/engine/surveys_controller.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - class SurveysController < Lcms::Engine::ApplicationController - skip_before_action :check_user_has_survey_filled_in - - def create - @form = SurveyForm.new(permitted_params) - if @form.valid? - current_user.update(survey: @form.attributes) - loc = stored_location_for(:user) - loc = lcms_engine.root_path if loc.is_a?(String) && loc[survey_path] - redirect_to loc || lcms_engine.root_path - else - render :show - end - end - - def show - return redirect_to lcms_engine.root_path if current_user.ready_to_go? - - @form = SurveyForm.new(current_user.survey) - end - - private - - def permitted_params - params.require(:survey_form).permit( - :additional_period, - :additional_period_minutes, - :district_or_system, - :district_or_system_other, - :first_name, - :last_name, - :number_of_minutes, - :prior_experience, - :subject_or_grade, - :subject_or_grade_other - ) - end - end - end -end diff --git a/app/entities/lcms/engine/curriculum_map.rb b/app/entities/lcms/engine/curriculum_map.rb deleted file mode 100644 index e8ec5362..00000000 --- a/app/entities/lcms/engine/curriculum_map.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - class CurriculumMap - attr_reader :resource - - def initialize(resource) - @resource = resource - end - - def props - return {} unless resource.present? - - { active: active_branch, results: curriculum } - end - - private - - def full_depth? - @full_depth ||= resource.lesson? || resource.unit? || resource.module? - end - - def active_branch - @active_branch ||= resource.self_and_ancestor_ids - end - - def target_branch - if full_depth? - mod = resource.parents.detect(&:module?) - mod ? mod.children.ids : [] - else - [] - end - end - - def curriculum - grade = resource.ancestors - .includes(:copyright_attributions) - .eager_load(:standards) - .where(curriculum_type: :grade) - .take - CurriculumResourceSerializer.new( - grade, - depth: full_depth? ? Resource.hierarchy.size : 1, - depth_branch: active_branch + target_branch - ).as_json - end - end - end -end diff --git a/app/entities/lcms/engine/external_page.rb b/app/entities/lcms/engine/external_page.rb index c48c929a..08a9b562 100644 --- a/app/entities/lcms/engine/external_page.rb +++ b/app/entities/lcms/engine/external_page.rb @@ -11,7 +11,7 @@ class ExternalPage attribute :description, String attribute :permalink, String attribute :slug, String - attribute :keywords, Array[String], default: [] + attribute :keywords, Array, default: [] attribute :teaser, String attribute :title, String diff --git a/app/entities/lcms/engine/filterbar.rb b/app/entities/lcms/engine/filterbar.rb deleted file mode 100644 index b74db183..00000000 --- a/app/entities/lcms/engine/filterbar.rb +++ /dev/null @@ -1,95 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - class Filterbar - attr_reader :params - - GRADES = %w(pk k 1 2 3 4 5 6 7 8 9 10 11 12).freeze - FACETS = %w(grade module unit lesson multimedia other).freeze - - def initialize(params) - @params = params - end - - def grades - @grades ||= begin - names = untranslated_grades - - names.map! do |name| - name = name.to_s - if name.casecmp('K').zero? - name = 'kindergarten' - elsif name.casecmp('PK').zero? - name = 'prekindergarten' - elsif !name.start_with?('grade') - name = "grade #{name}" - end - name - end - - names - end - end - - def subjects - @subjects ||= split(params[:subjects]) & Resource::SUBJECTS - end - - def facets - @facets ||= split(params[:facets]) & FACETS - end - - def search_term - @search_term ||= params[:search_term].presence - end - - def search_params - options = pagination.params(strict: true) - - # handle filters - options[:doc_type] = search_facets if facets.present? - options[:subject] = subjects.first if subjects.present? - options[:grade] = grades if grades.present? - - options - end - - def search_facets - search_facets = facets - search_facets.push('video', 'podcast') if search_facets.delete('multimedia') - search_facets.push('text_set', 'quick_reference_guide') if search_facets.delete('other') - search_facets - end - - # props used for building the Filterbar React component - def props - { - filterbar: { - subjects: subjects, - grades: untranslated_grades, - facets: facets, - search_term: search_term - } - } - end - - private - - def untranslated_grades - @untranslated_grades ||= split(params[:grades]) & GRADES - end - - def split(ps) - return [] if ps.blank? - return ps if ps.is_a?(Array) - - ps.gsub(/\s+/, '').split(',') - end - - def pagination - @pagination ||= Pagination.new(params) - end - end - end -end diff --git a/app/entities/lcms/engine/grades.rb b/app/entities/lcms/engine/grades.rb index c837dad3..34d6a343 100644 --- a/app/entities/lcms/engine/grades.rb +++ b/app/entities/lcms/engine/grades.rb @@ -30,8 +30,6 @@ def list @list ||= case model when Resource Array.wrap model.metadata['grade'] - when Search::Document - Array.wrap model.grade.presence else model.grade_list end.sort_by { |g| self.class.grades.index(g) } @@ -47,7 +45,7 @@ def average(abbr: true) def average_number return nil if list.empty? - list.map { |g| self.class.grades.index(g) }.sum / (list.size.nonzero? || 1) + list.map { |g| self.class.grades.index(g) }.compact.sum(0) / (list.size.nonzero? || 1) # rubocop:disable Style/RedundantArgument end def grade_abbr(abbr) diff --git a/app/entities/lcms/engine/roman_numerals.rb b/app/entities/lcms/engine/roman_numerals.rb index 65fef0a5..25388ffa 100644 --- a/app/entities/lcms/engine/roman_numerals.rb +++ b/app/entities/lcms/engine/roman_numerals.rb @@ -3,7 +3,7 @@ module Lcms module Engine class RomanNumerals - ROMAN_NUMERALS_RE = /^(M|CM|D|CD|C|XC|L|XL|X|IX|V|IV|I)/.freeze + ROMAN_NUMERALS_RE = /^(M|CM|D|CD|C|XC|L|XL|X|IX|V|IV|I)/ SYMBOLS = [ [1000, 'M'], [900, 'CM'], diff --git a/app/forms/lcms/engine/curriculum_form.rb b/app/forms/lcms/engine/curriculum_form.rb index 177e31f2..890a7b44 100644 --- a/app/forms/lcms/engine/curriculum_form.rb +++ b/app/forms/lcms/engine/curriculum_form.rb @@ -7,11 +7,11 @@ class CurriculumForm include Virtus.model include ActiveModel::Model - attribute :change_log, Array[Hash] + attribute :change_log, Array def initialize(params = {}) parsed_change_log = parse_change_log params - super params.merge(change_log: parsed_change_log) + super(params.merge(change_log: parsed_change_log)) end def save @@ -35,7 +35,7 @@ def parse_change_log(params) end def find_resource_by(id, curr) - Resource.tree.find_by(id: id) || Resource.tree.find_by_directory(curr) + Resource.tree.find_by(id:) || Resource.tree.find_by_directory(curr) end # Reflect curriculum changes on corresponding resources diff --git a/app/forms/lcms/engine/document_form.rb b/app/forms/lcms/engine/document_form.rb index 44ce8776..506f342f 100644 --- a/app/forms/lcms/engine/document_form.rb +++ b/app/forms/lcms/engine/document_form.rb @@ -2,82 +2,48 @@ module Lcms module Engine - class DocumentForm - include Virtus.model - include ActiveModel::Model - include Lcms::Engine::GoogleCredentials - - attribute :link, String - attribute :link_fs, String - - validates_presence_of :link, if: -> { link_fs.blank? } - validates_presence_of :link_fs, if: -> { link.blank? } - - attr_reader :document, :service_errors - - def initialize(attributes = {}, opts = {}) - @is_reimport = attributes.delete(:reimport).present? || false - super(attributes) - @options = opts + class DocumentForm < ImportForm + attr_reader :document + + # + # Options can be the following: + # - auto_gdoc_generation + # - import_retry + # + # @param [Hash] attributes + # @param [Hash] options + # + def initialize(attributes = {}, options = {}) + super end + # + # @return [Boolean] + # def save - return false unless valid? - - @document = build_document - after_reimport_hook - @document.update(reimported: true) - rescue StandardError => e - @document&.update(reimported: false) - Rails.logger.error "#{e.message}\n #{e.backtrace.join("\n ")}" - errors.add(:link, e.message) - false + super do + @document = build_document + @document.update(reimported: true) + end end private - attr_reader :is_reimport, :options - def after_reimport_hook - DocumentGenerator.generate_for(@document) + DocumentGenerator.generate_for(@document) \ + if ActiveRecord::Type::Boolean.new.cast(options[:auto_gdoc_generation]) + rescue StandardError => e + @document.update(reimported: false) if @document.present? + raise e end def build_document - service = document_build_service - - result = - if is_reimport - doc = service.build_for(link) - doc = service.build_for(link_fs, expand: true) if link_fs.present? - doc - elsif (full_doc = find_full_document) - # if there is a document with the same file_id or foundational_file_id - # we need to make full re-import to correctly handle expand process - service.build_for(full_doc.file_url) - service.build_for(full_doc.file_fs_url, expand: true) - else - service.build_for link - end - - @service_errors = service.errors - + service = DocumentBuildService.new(google_credentials, import_retry: options[:import_retry]) + result = service.build_for(link) + @service_errors.push(*service.errors.uniq) result end - def document_build_service - DocumentBuildService.new(google_credentials, import_retry: options[:import_retry]) - end - - def find_full_document - id = file_id - - doc = Document.actives.find_by(file_id: id) - return doc if doc&.foundational_file_id.present? - - doc = Document.actives.find_by(foundational_file_id: id) - doc if doc&.file_id.present? - end - def file_id ::Lt::Lcms::Lesson::Downloader::Base.file_id_for link end diff --git a/app/forms/lcms/engine/import_form.rb b/app/forms/lcms/engine/import_form.rb new file mode 100644 index 00000000..0e96be92 --- /dev/null +++ b/app/forms/lcms/engine/import_form.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Lcms + module Engine + class ImportForm + include Virtus.model + include ActiveModel::Model + include Lcms::Engine::GoogleCredentials + + attribute :link, String + + validates_presence_of :link + + attr_reader :service_errors + + # + # @param [Hash|ActionController::Parameters] attributes + # @param [Hash] options + # + def initialize(attributes = {}, options = {}) + super(attributes) + @options = options + @service_errors = [] + end + + # + # @return [Boolean] + # + def save + return false unless valid? + + yield + + after_reimport_hook + + true + rescue StandardError => e + Rails.logger.error "#{e.message}\n #{e.backtrace.join("\n ")}" + errors.add(:link, e.message) + false + end + + private + + attr_reader :options + + protected + + def after_reimport_hook; end + end + end +end diff --git a/app/forms/lcms/engine/material_form.rb b/app/forms/lcms/engine/material_form.rb index a409f5b9..0cd7ed9e 100644 --- a/app/forms/lcms/engine/material_form.rb +++ b/app/forms/lcms/engine/material_form.rb @@ -2,47 +2,41 @@ module Lcms module Engine - class MaterialForm - include Virtus.model - include ActiveModel::Model - include Lcms::Engine::GoogleCredentials - - attribute :link, String + class MaterialForm < ImportForm attribute :source_type, String - validates :link, presence: true - - attr_accessor :material, :service_errors - def initialize(attributes = {}, opts = {}) - super(attributes) - @options = opts + attr_reader :material + + # + # Options can be the following: + # - dpi + # - import_retry + # + # @param [Hash] attributes + # @param [Hash] options + # + def initialize(attributes = {}, options = {}) + super end + # + # @return [Boolean] + # def save - return false unless valid? - - params = { - dpi: options[:dpi], - import_retry: options[:import_retry], - source_type: source_type.presence - }.compact - service = MaterialBuildService.new google_credentials, params - @material = service.build link - @service_errors = service.errors - - material.update preview_links: {} - after_reimport_hook - true - rescue StandardError => e - Rails.logger.error "#{e.message}\n #{e.backtrace.join("\n ")}" - errors.add(:link, e.message) - false + super do + params = { + dpi: options[:dpi], + import_retry: options[:import_retry], + source_type: source_type.presence + }.compact + service = MaterialBuildService.new(google_credentials, params) + @material = service.build link + @service_errors.push(*service.errors.uniq) + + material.update preview_links: {} + end end - private - - attr_reader :options - protected def after_reimport_hook; end diff --git a/app/forms/lcms/engine/standard_form.rb b/app/forms/lcms/engine/standard_form.rb new file mode 100644 index 00000000..a219305a --- /dev/null +++ b/app/forms/lcms/engine/standard_form.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Lcms + module Engine + class StandardForm < ImportForm + def save + super { StandardsImportService.call link } + end + end + end +end diff --git a/app/forms/lcms/engine/survey_form.rb b/app/forms/lcms/engine/survey_form.rb deleted file mode 100644 index 5f9ca1d1..00000000 --- a/app/forms/lcms/engine/survey_form.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - class SurveyForm - include Virtus.model - include ActiveModel::Model - - DISTRICT_OR_SCHOOL_SYSTEMS = [ - 'AUSL (IL)', - 'DC International (DC)', - 'DC Prep (DC)', - 'Lawrence (MA)', - 'Other' - ].freeze - - SUBJECT_OR_GRADES = [ - 'Grade 2 ELA', - 'Grade 6 ELA', - 'Grade 4 Math', - 'Grade 7 Math', - 'Other' - ].freeze - - PRIOR_EXPERIENCES = [ - "Yes, I've used them as primary materials", - "Yes, I've used them as supporting materials", - 'No, I have not used them' - ].freeze - - attribute :first_name, String - attribute :last_name, String - attribute :district_or_system, String - attribute :district_or_system_other, String - attribute :subject_or_grade, String - attribute :subject_or_grade_other, String - attribute :number_of_minutes, Integer - attribute :additional_period, String - attribute :additional_period_minutes, String - attribute :prior_experience, String - - validates_presence_of :additional_period, :district_or_system, - :first_name, :last_name, :number_of_minutes, - :prior_experience, :subject_or_grade - - validates_numericality_of :number_of_minutes, greater_than: 0 - end - end -end diff --git a/app/helpers/admin/components_helper.rb b/app/helpers/admin/components_helper.rb deleted file mode 100644 index 9ec87b6a..00000000 --- a/app/helpers/admin/components_helper.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true - -module Admin - module ComponentsHelper - HIDDEN_FIELD_RE = /\b(?<=name=")[^"]+(?=")/.freeze - - def resource_picker_field(form, collection, path:, name:, allow_multiple: true) - path = path.to_s - - base_name = form.hidden_field(name).scan(HIDDEN_FIELD_RE)[0] - computed_name = "#{base_name}[]" - - component = react_component( - 'admin/resource-picker/ResourcePicker', - name: computed_name, - resources: collection.map { |i| { id: i.id, title: i.title } }, - allow_multiple: allow_multiple - ) - content_tag(:div) do - [ - concat(content_tag(:label, path.titleize, for: path)), - concat(content_tag(:div, component, id: path)) - ] - end - end - - # rubocop:disable Metrics/ParameterLists - def association_picker_field(form, collection, path:, name:, allow_create: false, create_name: nil, - allow_multiple: true) - path = path.to_s - collection = collection ? Array.wrap(collection) : [] - - scoped_name = form.hidden_field(name).scan(HIDDEN_FIELD_RE)[0] - - scoped_create_name = scoped_name.gsub(name.to_s, create_name.to_s) if create_name - - component = react_component( - 'admin/association-picker/AssociationPicker', - name: scoped_name, - create_name: scoped_create_name, - association: path, - items: collection.map { |i| { id: i.id, name: i.name } }, - allow_create: allow_create, - allow_multiple: allow_multiple - ) - content_tag(:div) do - [ - concat(content_tag(:label, path.titleize, for: path)), - concat(content_tag(:div, component, id: path)) - ] - end - end - # rubocop:enable Metrics/ParameterLists - - def directory_picker_props(resource) - { - directory: resource.directory, - parent: { - directory: resource.parent.try(:directory) || [], - id: resource.parent_id, - title: resource.parent.try(:title) - }, - path: lcms_engine_url_helpers.children_admin_curriculum_path - } - end - end -end diff --git a/app/helpers/admin/documents_helper.rb b/app/helpers/admin/documents_helper.rb index fa0054ab..dac61ce6 100644 --- a/app/helpers/admin/documents_helper.rb +++ b/app/helpers/admin/documents_helper.rb @@ -4,7 +4,7 @@ module Admin module DocumentsHelper def material_urls(material, doc) lesson = Lcms::Engine::DocumentGenerator.document_presenter.new(doc) - presenter = Lcms::Engine::DocumentGenerator.material_presenter.new(material, lesson: lesson) + presenter = Lcms::Engine::DocumentGenerator.material_presenter.new(material, lesson:) { pdf: presenter.pdf_url, gdoc: presenter.gdoc_url } end end diff --git a/app/helpers/admin/react_components_helper.rb b/app/helpers/admin/react_components_helper.rb new file mode 100644 index 00000000..89503360 --- /dev/null +++ b/app/helpers/admin/react_components_helper.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Admin + module ReactComponentsHelper + HIDDEN_FIELD_RE = /\b(?<=name=")[^"]+(?=")/ + + # + # @param [String] name + # @param [Hash] props + # @return [ActiveSupport::SafeBuffer] HTML content + # @return [String] HTML content + # + def render_component(name, props) + content_tag(:div, nil, id: "#lcms-engine-#{name}", data: { content: props.to_json }) + end + end +end diff --git a/app/helpers/admin/resource_helper.rb b/app/helpers/admin/resource_helper.rb index 23102874..2e273048 100644 --- a/app/helpers/admin/resource_helper.rb +++ b/app/helpers/admin/resource_helper.rb @@ -2,18 +2,6 @@ module Admin module ResourceHelper - def attachment_content_type(download) - t("content_type.#{download.content_type}") rescue download.content_type - end - - def attachment_url(download) - if download.url.present? - download.url.sub('public://', 'http://k12-content.s3-website-us-east-1.amazonaws.com/') - else - download.file.url - end - end - def language_collection_options Language.order(:name).map { |lang| [lang.name, lang.id] } end diff --git a/app/helpers/admin/welcome_helper.rb b/app/helpers/admin/welcome_helper.rb deleted file mode 100644 index f9368ef3..00000000 --- a/app/helpers/admin/welcome_helper.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -module Admin - module WelcomeHelper - end -end diff --git a/app/helpers/lcms/engine/application_helper.rb b/app/helpers/lcms/engine/application_helper.rb index 7d6de9d6..4d033852 100644 --- a/app/helpers/lcms/engine/application_helper.rb +++ b/app/helpers/lcms/engine/application_helper.rb @@ -1,20 +1,9 @@ # frozen_string_literal: true -require 'webpacker/helper' - module Lcms module Engine module ApplicationHelper include ViewHelper - include ::Webpacker::Helper - - # Use this to include lcms-engine based packs - def lcms_engine_javascript_pack_tag(*names, **options) - entries = names - .map { |name| Lcms::Engine.webpacker.manifest.lookup!(name, type: :javascript) } - .flatten - javascript_include_tag(*entries, **options) - end end end end diff --git a/app/helpers/lcms/engine/download_helper.rb b/app/helpers/lcms/engine/download_helper.rb deleted file mode 100644 index 6ff102fb..00000000 --- a/app/helpers/lcms/engine/download_helper.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - module DownloadHelper - def file_icon(type) - %w(excel doc pdf powerpoint zip).include?(type) ? type : 'unknown' - end - end - end -end diff --git a/app/helpers/lcms/engine/path_helper.rb b/app/helpers/lcms/engine/path_helper.rb index 5e950f3d..86315d1e 100644 --- a/app/helpers/lcms/engine/path_helper.rb +++ b/app/helpers/lcms/engine/path_helper.rb @@ -3,28 +3,28 @@ module Lcms module Engine module PathHelper - include Rails.application.routes.url_helpers - - def dynamic_path(path, *args) - host_engine_path(path, *args).presence || main_app.send(path.to_sym, *args) + def dynamic_path(path, *) + host_engine_path(path, *).presence || main_app.send(path.to_sym, *) end - def dynamic_document_path(*args) - host_engine_path(:document_path, *args).presence || main_app.document_path(*args) + def dynamic_document_path(*) + host_engine_path(:document_path, *).presence || main_app.document_path(*) end - def dynamic_material_path(*args) - host_engine_path(:material_path, *args).presence || main_app.material_path(*args) + def dynamic_material_path(*) + host_engine_path(:material_path, *).presence || main_app.material_path(*) end private - def host_engine_path(key, *args) + def host_engine_path(key, *) settings = Lcms::Engine::Admin::AdminController.settings if (host_route = settings.dig(:redirect, :host, key)).present? - main_app.send(host_route.to_sym, *args) + Rails.application.routes.url_helpers.send(host_route.to_sym, *) elsif (engine_route = settings.dig(:redirect, :engine, key)).present? - lcms_engine.send(engine_route.to_sym, *args) + Lcms::Engine::Engine.routes.url_helpers.send(engine_route.to_sym, *) + else + raise "Please tune up config/lcms-admin.yml and set redirect:host:#{key} or redirect:engine:#{key} values" end end end diff --git a/app/helpers/lcms/engine/resource_helper.rb b/app/helpers/lcms/engine/resource_helper.rb index 2c901d93..a932b092 100644 --- a/app/helpers/lcms/engine/resource_helper.rb +++ b/app/helpers/lcms/engine/resource_helper.rb @@ -17,11 +17,6 @@ def type_name(resource) resource.curriculum_type&.capitalize end - def back_to_resource_path(resource) - slug = resource.lesson? && resource.parent ? resource.parent.slug : resource.slug - CGI.unescape(explore_curriculum_index_path(p: slug, e: 1)) - end - def copyrights_text(object) cc_descriptions = [] object.copyrights.each do |copyright| @@ -33,20 +28,14 @@ def copyrights_text(object) cc_descriptions.join(' ') end - def download_per_category_limit - ResourceDownload::DOWNLOAD_PER_CATEGORY_LIMIT - end - def resource_breadcrumbs_with_links(resource) - return GenericPresenter.new(resource).generic_title if resource.generic? - breadcrumbs = Breadcrumbs.new(resource) pieces = breadcrumbs.full_title.split(' / ') short_pieces = breadcrumbs.short_title.split(' / ') [].tap do |result| pieces.each_with_index do |piece, idx| - ((result << piece) && next) if idx.zero? + (result << piece) && next if idx.zero? slug = Slug.build_from(pieces[0..idx]) @@ -61,16 +50,6 @@ def resource_breadcrumbs_with_links(resource) end end.join(' / ').html_safe end - - def prerequisites_standards(resource) - ids = StandardLink - .where(standard_end_id: resource.standards.pluck(:id)) - .where.not(link_type: 'c') - .pluck(:standard_begin_id) - Standard - .where(id: ids).pluck(:alt_names).flatten.uniq - .map { |n| Standard.filter_ccss_standards(n, resource.subject) }.compact.sort - end end end end diff --git a/app/helpers/lcms/engine/view_helper.rb b/app/helpers/lcms/engine/view_helper.rb index 9622703c..ad66ded4 100644 --- a/app/helpers/lcms/engine/view_helper.rb +++ b/app/helpers/lcms/engine/view_helper.rb @@ -2,7 +2,7 @@ module Lcms module Engine - module ViewHelper # rubocop:disable Metrics/ModuleLength + module ViewHelper ENABLE_BASE64_CACHING = ActiveRecord::Type::Boolean.new.cast ENV.fetch('ENABLE_BASE64_CACHING', true) def add_class_for_path(link_path, klass, klass_prefix = nil) @@ -33,56 +33,16 @@ def flash_to_hash result end - def header_mod - controller.controller_name - end - def page_title page_content_for :page_title end - def page_description - page_content_for :description - end - - def page_og_image - if content_for?(:og_image) - page_og_image = content_for(:og_image) - else - controller = controller_path.tr('/', '.') - page_og_image = t("#{controller}.#{action_name}.og_image", default: t('default_og_image')) - end - page_og_image - end - - # Render meta tag - def redirect_meta_tag - content_for(:redirect_meta_tag) if content_for?(:redirect_meta_tag) - end - - # Use in views as redirection directive - def redirect_via_meta_tag(to_url:, delay: 5) - content_for(:redirect_meta_tag) do - content_tag(:meta, nil, { content: "#{delay};url=#{to_url}", 'http-equiv' => 'refresh' }, true) - end - end - def set_page_title(title) # rubocop:disable Naming/AccessorMethodName content_for :page_title do title end end - def set_page_description(dsc) # rubocop:disable Naming/AccessorMethodName - content_for :description do - dsc - end - end - - def set_canonical_url(value) # rubocop:disable Naming/AccessorMethodName - content_for(:canonical_url, value) - end - def base64_encoded_asset(path) AssetHelper.base64_encoded path, cache: ENABLE_BASE64_CACHING end @@ -105,7 +65,7 @@ def color_code(model, base: false) def selected_id?(id) selected_ids = params[:selected_ids] - return unless selected_ids.present? + return false unless selected_ids.present? case selected_ids when Array diff --git a/app/interactors/lcms/engine/enhance_instruction_interactor.rb b/app/interactors/lcms/engine/enhance_instruction_interactor.rb deleted file mode 100644 index 97e2f22e..00000000 --- a/app/interactors/lcms/engine/enhance_instruction_interactor.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - class EnhanceInstructionInteractor < BaseInteractor - TAB_INDEX = %i(instructions videos generic).freeze - - attr_reader :props - - def run - @props = pagination.serialize(data, serializer) - @props.merge!(filterbar.props) - @props.merge!(tab: active_tab) - end - - private - - def filterbar - @filterbar ||= Filterbar.new(params) - end - - def pagination - @pagination ||= Pagination.new(params) - end - - def active_tab - @active_tab ||= (params[:tab] || 0).to_i - end - - def tab(name) - TAB_INDEX.index(name) - end - - def data - case active_tab - when tab(:videos) then resources(:media) - else resources(:generic_resources) - end - end - - def serializer - ResourceInstructionSerializer - end - - def resources(type) - scope = Resource - .send(type) - .where_subject(filterbar.subjects) - .where_grade(filterbar.grades) - - scope = if type == :media - scope.order(created_at: :desc) - else - scope.ordered - end - - scope.paginate(pagination.params(strict: true)) - end - end - end -end diff --git a/app/interactors/lcms/engine/explore_curriculum_interactor.rb b/app/interactors/lcms/engine/explore_curriculum_interactor.rb deleted file mode 100644 index 6c1ca59c..00000000 --- a/app/interactors/lcms/engine/explore_curriculum_interactor.rb +++ /dev/null @@ -1,81 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - class ExploreCurriculumInteractor < BaseInteractor - attr_reader :props - - def run; end - - def index_props - slug_param ? expanded_props : grades_props - end - - def show_props - resource = Resource.tree.find(params[:id]) - CurriculumResourceSerializer.new(resource, depth: 1).as_json - end - - private - - def filterbar - @filterbar ||= Filterbar.new(params) - end - - def grades - @grades ||= Resource.tree.grades - .includes(:copyright_attributions, :resource_downloads) - .eager_load(:standards) - .where_subject(filterbar.subjects) - .where_grade(filterbar.grades) - .ordered - end - - def grades_props - ActiveModelSerializers::SerializableResource.new( - grades, - each_serializer: CurriculumResourceSerializer, - root: :results - ).as_json.merge(filterbar.props) - end - - def expanded_props - target = Resource.tree.find_by(slug: slug_param) - raise "Unknown Resource slug value: '#{slug_param}'" unless target - - grade = target.parents.detect(&:grade?) - depth = Resource.hierarchy.index(target.curriculum_type.to_sym) - - # self and ancestors, except the subject - active_branch = target.self_and_ancestors.reject(&:subject?).map(&:id).reverse - - if expanded? - depth += 1 - first_child = target.children.first - active_branch << first_child.id if first_child.present? - end - - { - active: active_branch, - expanded: expanded? ? true : nil, - results: grades.map do |curr| - CurriculumResourceSerializer.new( - curr, - depth: curr.id == grade.try(:id) ? depth : 0, - depth_branch: active_branch - ).as_json - end - }.compact.merge(filterbar.props) - end - - def slug_param - slug = params[:p] - (slug.start_with?('/') ? slug[1..] : slug) if slug.present? - end - - def expanded? - params[:e].present? - end - end - end -end diff --git a/app/interactors/lcms/engine/find_lessons_interactor.rb b/app/interactors/lcms/engine/find_lessons_interactor.rb deleted file mode 100644 index 64f872bf..00000000 --- a/app/interactors/lcms/engine/find_lessons_interactor.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - class FindLessonsInteractor < BaseInteractor - attr_reader :props - - def run - @props = pagination.serialize(lessons, serializer).merge(filterbar.props) - end - - private - - def filterbar - @filterbar ||= Filterbar.new(params) - end - - def pagination - @pagination ||= Pagination.new(params) - end - - def search? - filterbar.search_term.present? - end - - def serializer - search? ? SearchResourceSerializer : ResourceSerializer - end - - def lessons - if search? - Search::Document - .search(filterbar.search_term, filterbar.search_params.merge(doc_type: :lesson)) - .paginate(pagination.params) - - else - Resource.tree.lessons - .where_subject(filterbar.subjects) - .where_grade(filterbar.grades) - .ordered - .paginate(pagination.params(strict: true)) - end - end - end - end -end diff --git a/app/interactors/lcms/engine/search_interactor.rb b/app/interactors/lcms/engine/search_interactor.rb deleted file mode 100644 index 387c87a7..00000000 --- a/app/interactors/lcms/engine/search_interactor.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - class SearchInteractor < BaseInteractor - attr_reader :props - - def run - @props = pagination.serialize(documents, serializer).merge(filterbar.props) - end - - private - - def filterbar - @filterbar ||= Filterbar.new(params) - end - - def pagination - @pagination ||= Pagination.new(params) - end - - def serializer - SearchDocumentSerializer - end - - def documents - Search::Document - .search(filterbar.search_term, filterbar.search_params) - .paginate(pagination.params) - end - end - end -end diff --git a/app/javascript/admin.js b/app/javascript/admin.js new file mode 100644 index 00000000..6ed75bc3 --- /dev/null +++ b/app/javascript/admin.js @@ -0,0 +1,19 @@ +import '@hotwired/turbo-rails'; +import * as bootstrap from 'bootstrap'; // eslint-disable-line +import TomSelect from 'tom-select'; +import Initializer from './components/admin/Initializer'; + +document.addEventListener('turbo:load', () => { + Initializer.initialize(); + + document.querySelectorAll('.selectize').forEach(el => { + let settings = { + plugins: { + remove_button: { + title: 'Remove', + }, + }, + }; + new TomSelect(el, settings); + }); +}); diff --git a/app/javascript/application.js b/app/javascript/application.js new file mode 100644 index 00000000..d9ea042d --- /dev/null +++ b/app/javascript/application.js @@ -0,0 +1,6 @@ +import '@hotwired/turbo-rails'; +import * as bootstrap from 'bootstrap'; // eslint-disable-line +// import React from 'react'; +// import ReactDOM from 'react-dom'; + +// ReactDOM.render(, document.getElementById('root')); diff --git a/app/javascript/components/admin/ImportStatus.jsx b/app/javascript/components/admin/ImportStatus.jsx index ca076b0b..45878324 100644 --- a/app/javascript/components/admin/ImportStatus.jsx +++ b/app/javascript/components/admin/ImportStatus.jsx @@ -1,42 +1,58 @@ import React from 'react'; import PropTypes from 'prop-types'; import _ from 'lodash'; +import $ from 'jquery'; class ImportStatus extends React.Component { constructor(props) { super(props); - this.state = { jobs: props.jobs }; - this.pollingInterval = 5000; + this.state = { jobs: props.jobs || [] }; + this.pollingInterval = props.pollingInterval || props.polling_interval || 5000; this.chunkSize = 50; this.links = _.isEmpty(props.links) ? [`${props.type}/:id`] : props.links; - if (_.isEmpty(props.path)) { - const k = `lcms_engine_import_status_admin_${this.props.type}_path`; - this.path = Routes[k].call(); - } else { - this.path = props.path; - } - this.withPdf = props.with_pdf || false; + this.withPdf = props.with_pdf || props.withPdf || false; } componentDidMount() { - this.intervalFn = setInterval(this.poll.bind(this), this.pollingInterval); + this.startPolling(); } componentWillUnmount() { - clearInterval(this.intervalFn); + this.stopPolling(); } + hasPendingJobs = () => { + return _.some(this.state.jobs, job => job.status !== 'done'); + }; + poll() { const pendingJobs = _.compact(_.map(this.state.jobs, (job, jid) => (job.status !== 'done' ? jid : null))); if (pendingJobs.length > 0) { - _.each(_.chunk(pendingJobs, this.chunkSize), jids => this.updateChunkStatus(jids)); + const promises = _.map(_.chunk(pendingJobs, this.chunkSize), jids => this.updateChunkStatus(jids)); + Promise.allSettled(promises).then(() => { + this.timeoutFn = setTimeout(() => this.poll(), this.pollingInterval); // Schedule the next poll + }); } else { - clearInterval(this.intervalFn); + this.stopPolling(); } } + startPolling = () => { + this.stopPolling(); // Clear any existing timeout + if (this.hasPendingJobs()) { + this.poll(); + } + }; + + stopPolling = () => { + if (this.timeoutFn) { + clearTimeout(this.timeoutFn); + this.timeoutFn = null; + } + }; + updateChunkStatus(jids) { - $.getJSON(this.path, { + $.getJSON(this.props.pollingPath || this.props.polling_path, { jids: jids, type: this.props.type, _: Date.now(), // prevent cached response @@ -56,13 +72,8 @@ class ImportStatus extends React.Component { resourceButton(job) { if (this.withPdf) { return ( - - + + ); } @@ -76,21 +87,22 @@ class ImportStatus extends React.Component { }; return _.map(this.links, (link, idx) => ( - - - + + + + + )); } spinner() { return ( - + ); @@ -103,57 +115,64 @@ class ImportStatus extends React.Component { const results = _.map(this.state.jobs, (job, key) => { let status; - if (job.status === 'done') { - status = job.ok ? 'ok' : 'err'; - } else { - status = job.status; + switch (job.status) { + case 'done': + if (job.ok) { + status = _.isEmpty(job.warnings) ? 'list-group-item-success' : 'list-group-item-warning'; + } else { + status = 'list-group-item-danger'; + } + break; + case 'running': + status = 'list-group-item-primary'; + break; + default: + status = ''; } return ( -
  • -
    - +
  • +
    + {job.status !== 'done' ? job.text || job.link : 'Done'} - {job.status !== 'done' ? this.spinner() : null} - {job.status === 'done' && job.ok ? {this.resourceButton(job)} : null} + {job.status === 'done' && job.ok ? {this.resourceButton(job)} : null} +
    +
    + {!_.isEmpty(job.errors) ? ( +

    ') }}>

    + ) : null} + {!_.isEmpty(job.warnings) ? ( +

    ') }}>

    + ) : null}
    - {!_.isEmpty(job.errors) ?

    ') }}>

    : null} - {!_.isEmpty(job.warnings) ? ( -

    '), - }} - >

    - ) : null} + {job.status !== 'done' ? this.spinner() : null}
  • ); }); return ( -
    -

    - • {waitingCount} Files(s) Processing - {`✓ ${importedCount} File(s) ${ - this.withPdf ? 'Generated' : 'Imported' - }`} - x {failedCount} File(s) Failed -

    - -
      {results}
    +
    +
    +
    {waitingCount} Files(s) Processing
    +
    {`${importedCount} File(s) ${this.withPdf ? 'Generated' : 'Imported'}`}
    +
    {failedCount} File(s) Failed
    +
    +
      {results}
    ); } } ImportStatus.propTypes = { - jobs: PropTypes.object, + jobs: PropTypes.object.isRequired, links: PropTypes.array, - type: PropTypes.string, - path: PropTypes.string, + type: PropTypes.string.isRequired, + polling_path: PropTypes.string.isRequired, + pollingPath: PropTypes.string.isRequired, + pollingInterval: PropTypes.number, + polling_interval: PropTypes.number, with_pdf: PropTypes.bool, + withPdf: PropTypes.bool, }; export default ImportStatus; diff --git a/app/javascript/components/admin/Initializer.jsx b/app/javascript/components/admin/Initializer.jsx index f048eb52..23206443 100644 --- a/app/javascript/components/admin/Initializer.jsx +++ b/app/javascript/components/admin/Initializer.jsx @@ -1,20 +1,47 @@ -const Initializer = { - initializeResourcesForm: () => { - const form = $('form#resource_form'); - if (!form.length) return; +import $ from 'jquery'; +import CurriculumEditor from './curriculum/CurriculumEditor'; +import ImportStatus from './ImportStatus'; +import MultiSelectedOperation from './MultiSelectedOperation'; +import React from 'react'; +import ReactDOM from 'react-dom'; - const opr_desc = form.find('.resource_opr_description'); - form.find('#resource_curriculum_type').change(ev => { - const el = $(ev.target); - if (el.val() === 'unit') { - opr_desc.slideDown(); - } else { - opr_desc.slideUp(); - } +class Initializer { + static initialize() { + // Mount internal components + Initializer.#initializeCurriculumEditor(); + Initializer.#InitializeImportStatus(); + Initializer.#initializeMultiSelectedOperation(); + + // Initialize simple HTML objects + Initializer.#initializeResourcesList(); + Initializer.#initializeSelectAll(); + } + + static #initializeCurriculumEditor() { + document.querySelectorAll('[id="#lcms-engine-CurriculumEditor"]').forEach(e => { + const props = JSON.parse(e.dataset.content); + e.removeAttribute('data-content'); + ReactDOM.render(, e); + }); + } + + static #InitializeImportStatus() { + document.querySelectorAll('[id="#lcms-engine-ImportStatus"]').forEach(e => { + const props = JSON.parse(e.dataset.content); + e.removeAttribute('data-content'); + ReactDOM.render(, e); + }); + } + + static #initializeMultiSelectedOperation() { + document.querySelectorAll('[id="#lcms-engine-MultiSelectedOperation"]').forEach(e => { + const props = JSON.parse(e.dataset.content); + e.removeAttribute('data-content'); + ReactDOM.render(, e); }); - }, + } - initializeResourcesList: () => { + static #initializeResourcesList() { const page = $('.o-adm-list.o-adm-documents'); if (!page.length) return; @@ -22,9 +49,9 @@ const Initializer = { const value = $(this).prop('checked') ? 1 : 0; page.find('.c-reimport-doc-form .c-reimport-with-materials__field').val(value); }); - }, + } - initializeSelectAll: () => { + static #initializeSelectAll() { const selector = $('.c-multi-selected--select-all'); if (!selector.length) return; @@ -33,7 +60,7 @@ const Initializer = { const checked = el.prop('checked'); $('.table input[type=checkbox][name="selected_ids[]"]').prop('checked', checked); }); - }, -}; + } +} export default Initializer; diff --git a/app/javascript/components/admin/MultiSelectedOperation.jsx b/app/javascript/components/admin/MultiSelectedOperation.jsx index f82a1be7..026fdb06 100644 --- a/app/javascript/components/admin/MultiSelectedOperation.jsx +++ b/app/javascript/components/admin/MultiSelectedOperation.jsx @@ -1,3 +1,4 @@ +import $ from 'jquery'; import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; @@ -13,13 +14,13 @@ class MultiSelectedOperation extends React.Component { componentDidMount() { // eslint-disable-next-line react/no-find-dom-node const $this = $(ReactDOM.findDOMNode(this)); - $this.parent().addClass('c-multi-selected-btn'); + $this.parent().addClass(`c-multi-selected-btn ${this.props.wrapperClass}`); } onSubmit(evt) { if (this.props.operation === 'delete' && !confirm('Are you sure?')) return; // eslint-disable-line no-restricted-globals - const entries = $('.o-page .table input[name="selected_ids[]"]'); + const entries = $('.table input[name="selected_ids[]"]'); const ids = _.filter(entries, e => e.checked).map(e => e.value); if (ids.length === 0) return evt.preventDefault(); @@ -29,7 +30,7 @@ class MultiSelectedOperation extends React.Component { } render() { - const btnClass = `button ${this.props.btn_style}`; + const btnClass = `btn ${this.props.btnStyle}`; const method = this.props.operation === 'delete' ? 'delete' : 'post'; const csrf_token = $('meta[name=csrf-token]').attr('content'); return ( @@ -42,6 +43,7 @@ class MultiSelectedOperation extends React.Component { method="post" className="c-reimport-doc-form" onSubmit={this.onSubmit} + data-turbo={false} > @@ -56,7 +58,8 @@ class MultiSelectedOperation extends React.Component { MultiSelectedOperation.propTypes = { operation: PropTypes.string, - btn_style: PropTypes.string, + btnStyle: PropTypes.string, + wrapperClass: PropTypes.string, path: PropTypes.string, text: PropTypes.string, }; diff --git a/app/javascript/components/admin/association-picker/AssociationPicker.jsx b/app/javascript/components/admin/association-picker/AssociationPicker.jsx deleted file mode 100644 index 8f7a7851..00000000 --- a/app/javascript/components/admin/association-picker/AssociationPicker.jsx +++ /dev/null @@ -1,125 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import PropTypes from 'prop-types'; -import _ from 'lodash'; -// eslint-disable-next-line no-unused-vars -import AssociationPickerItem from './AssociationPickerItem'; -import AssociationPickerWindow from './AssociationPickerWindow'; -// eslint-disable-next-line no-unused-vars -import PickerButton from '../picker/PickerButton'; -import pickerModal from '../picker/pickerModal'; -import pickerWindowWrapper from '../picker/pickerWindowWrapper'; -import { Foundation } from 'foundation-sites'; -import $ from 'jquery'; - -class AssociationPicker extends React.Component { - constructor(props) { - super(props); - - this.state = { - items: this.props.items || [], - }; - } - - get jqmodal() { - return $(this.modal); - } - - componentDidMount() { - Foundation.addToJquery($); - // eslint-disable-next-line no-undef - pickerModal.call(this); - } - - onClickSelect() { - // eslint-disable-next-line no-undef - const pickerComponent = pickerWindowWrapper(AssociationPickerWindow, 'lcms_engine_admin_association_picker_path'); - const picker = React.createElement( - pickerComponent, - { - association: this.props.association, - allowCreate: this.props.allow_create, - allowMultiple: this.props.allow_multiple, - onClickDone: this.closeModal.bind(this), - onSelectItem: this.selectItem.bind(this), - selectedItems: this.state.items, - }, - null - ); - ReactDOM.render(picker, this.modal); - this.jqmodal.foundation('open'); - } - - selectItem(item, operation) { - if (!this.props.allow_multiple) { - this.closeModal(); - } - operation === 'added' ? this.addItem(item) : this.removeItem(item); - } - - closeModal() { - this.jqmodal.foundation('close'); - } - - addItem(item) { - const newItems = this.props.allow_multiple ? [...this.state.items, item] : [item]; - - this.setState({ - ...this.state, - items: newItems, - }); - } - - removeItem(item) { - this.setState({ - ...this.state, - items: _.filter(this.state.items, r => r.id !== item.id), - }); - } - - render() { - const items = this.state.items.map(item => { - return ( - this.removeItem(item)} - /> - ); - }); - - const blankInput = this.props.allow_multiple ? ( - - ) : ( - - ); - - return ( - (this.modal = m)} - /> - ); - } -} - -AssociationPicker.propTypes = { - item: PropTypes.object, - items: PropTypes.array, - association: PropTypes.string, - create_name: PropTypes.string, - allow_create: PropTypes.bool, - allow_multiple: PropTypes.bool, - name: PropTypes.string, -}; - -export default AssociationPicker; diff --git a/app/javascript/components/admin/association-picker/AssociationPickerItem.jsx b/app/javascript/components/admin/association-picker/AssociationPickerItem.jsx deleted file mode 100644 index fe6f7a36..00000000 --- a/app/javascript/components/admin/association-picker/AssociationPickerItem.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -// eslint-disable-next-line no-unused-vars -function AssociationPickerItem(props) { - let input; - - if (props.item._create) { - input = ; - } else if (props.allowMultiple) { - input = ; - } else { - input = ; - } - - return ( -
    - {input} -
    - {props.item.name} - - × - -
    -
    - ); -} - -AssociationPickerItem.propTypes = { - item: PropTypes.object, - createName: PropTypes.string, - allowMultiple: PropTypes.bool, - name: PropTypes.string, - onClickClose: PropTypes.func, -}; - -export default AssociationPickerItem; diff --git a/app/javascript/components/admin/association-picker/AssociationPickerResults.jsx b/app/javascript/components/admin/association-picker/AssociationPickerResults.jsx deleted file mode 100644 index 5023d376..00000000 --- a/app/javascript/components/admin/association-picker/AssociationPickerResults.jsx +++ /dev/null @@ -1,74 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import _ from 'lodash'; - -function AssociationPickerResults(props) { - let items; - - const shouldAllowCreate = - _.isString(props.value) && props.value.length > 0 && props.allowCreate && props.items.length === 0; - - const selectedIds = _.map(props.selectedItems, 'id'); - const isSelected = item => { - return _.includes(selectedIds, item.id); - }; - - if (shouldAllowCreate) { - let newItem = { id: props.value, name: props.value, _create: true }; - items = [ - /* eslint-disable react/jsx-no-bind */ - - props.onSelectItem(newItem)}> - {props.value} - (Create) - - , - /* eslint-enable react/jsx-no-bind */ - ]; - } else { - items = props.items.map(item => { - let newItem = { - id: item.id, - name: item.name, - _create: false, - _selected: isSelected(item), - }; - return ( - /* eslint-disable react/jsx-no-bind */ - - props.onSelectItem(newItem)}>{newItem.name} - - /* eslint-enable react/jsx-no-bind */ - ); - }); - } - - return ( - - - - - - - - {items.length ? ( - items - ) : ( - - - - )} - -
    Name
    Nothing to select
    - ); -} - -AssociationPickerResults.propTypes = { - value: PropTypes.string, - allowCreate: PropTypes.bool, - items: PropTypes.array, - selectedItems: PropTypes.array, - onSelectItem: PropTypes.func, -}; - -export default AssociationPickerResults; diff --git a/app/javascript/components/admin/association-picker/AssociationPickerWindow.jsx b/app/javascript/components/admin/association-picker/AssociationPickerWindow.jsx deleted file mode 100644 index 1452b3e9..00000000 --- a/app/javascript/components/admin/association-picker/AssociationPickerWindow.jsx +++ /dev/null @@ -1,93 +0,0 @@ -import React from 'react'; -import _ from 'lodash'; -import PropTypes from 'prop-types'; -import AssociationPickerResults from './AssociationPickerResults'; - -class AssociationPickerWindow extends React.Component { - constructor(props) { - super(props); - - this.state = { ...props, selectedItems: [] }; - - this.selectItem = this.selectItem.bind(this); - } - - selectItem(item) { - const operation = this.updateSelectedItems(item); - if ('onSelectItem' in this.props) { - this.props.onSelectItem(item, operation); - } - } - - updateSelectedItems(item) { - let operation, newItems; - if (item._selected) { - newItems = _.filter(this.state.selectedItems, r => r.id !== item.id); - operation = 'removed'; - } else { - newItems = [...this.state.selectedItems, item]; - operation = 'added'; - } - this.setState(...this.state, { selectedItems: newItems }); - return operation; - } - - render() { - const { q, results } = this.props; - - return ( -
    -
    -
    -

    Select item

    -
    - -
    -
    -
    - -
    -
    - - - {this.props.pagination()} - - {this.props.allowMultiple ? ( - - ) : null} -
    -
    -
    - ); - } -} - -AssociationPickerWindow.propTypes = { - onSelectItem: PropTypes.func, - q: PropTypes.string, - results: PropTypes.array, - onFilterChange: PropTypes.func, - allowCreate: PropTypes.bool, - pagination: PropTypes.func, - allowMultiple: PropTypes.bool, - onClickDone: PropTypes.func, -}; - -export default AssociationPickerWindow; diff --git a/app/javascript/components/admin/curriculum/CurriculumEditor.jsx b/app/javascript/components/admin/curriculum/CurriculumEditor.jsx index 8cddce1d..8ce7e6c4 100644 --- a/app/javascript/components/admin/curriculum/CurriculumEditor.jsx +++ b/app/javascript/components/admin/curriculum/CurriculumEditor.jsx @@ -126,7 +126,7 @@ class CurriculumEditor extends React.Component { - +

    (Click on a node with the right button to add/edit/remove) diff --git a/app/javascript/components/admin/curriculum/DirectoryPicker.jsx b/app/javascript/components/admin/curriculum/DirectoryPicker.jsx deleted file mode 100644 index 4e5a71fb..00000000 --- a/app/javascript/components/admin/curriculum/DirectoryPicker.jsx +++ /dev/null @@ -1,125 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import PropTypes from 'prop-types'; -import { Foundation } from 'foundation-sites'; -import TagsInput from 'react-tagsinput'; -import $ from 'jquery'; -import '../../../vendor/jstree/jstree.min'; - -class DirectoryPicker extends React.Component { - constructor(props) { - super(props); - this.state = { - directory: props.directory, - parent: props.parent, - }; - - this.onClick = this.onClick.bind(this); - this.handleDirChange = this.handleDirChange.bind(this); - } - - componentDidMount() { - // eslint-disable-next-line react/no-find-dom-node - const $this = $(ReactDOM.findDOMNode(this)); - $this.parent().addClass('o-curriculum-tree-picker__container'); - - const editor = $this.find('#curriculum-tree-picker'); - editor.on('changed.jstree', this.onChanged.bind(this)).jstree({ - core: { - animation: 0, - themes: { dots: true }, - check_callback: true, - data: { - url: this.props.path, - data: node => { - return { id: node.id }; - }, - }, - }, - plugins: ['wholerow', 'changed'], - }); - this.jsTree = editor.data('jstree'); - - Foundation.addToJquery($); - this.jqmodal = $this.find('#curriculum-picker-modal'); - new Foundation.Reveal(this.jqmodal, null); - } - - closeModal() { - this.jqmodal.foundation('close'); - } - - onChanged(_e, data) { - const dir = this.directory(data.node); - const parent = { - id: data.node.id, - title: data.node.li_attr.title, - directory: dir, - }; - this.setState({ directory: dir, parent: parent }); - this.closeModal(); - } - - onClick(e) { - e.preventDefault(); - this.jqmodal.foundation('open'); - } - - directory(node) { - return node.parents - .map(el => this.jsTree.get_node(el, null).text) - .reverse() - .slice(1) - .concat(node.text); - } - - handleDirChange(tags) { - this.setState({ directory: tags }); - } - - render() { - const curr = this.state.parent.directory; - const parent_aside = curr.length > 0 ? `(${curr.join(' | ')}) : ` : ''; - return ( -

    -
    - - - {/* eslint-disable jsx-a11y/anchor-is-valid */} - - Select Parent - - {/* eslint-enable jsx-a11y/anchor-is-valid */} -
    - {this.state.parent.title} -
    -
    -
    - - - -
    -
    -

    Select a Parent Resource

    -
    -
    -
    - ); - } -} - -DirectoryPicker.propTypes = { - tree: PropTypes.array, - directory: PropTypes.array, - parent: PropTypes.object, - path: PropTypes.string, -}; - -export default DirectoryPicker; diff --git a/app/javascript/components/admin/picker/PickerButton.jsx b/app/javascript/components/admin/picker/PickerButton.jsx deleted file mode 100644 index 22ee71ad..00000000 --- a/app/javascript/components/admin/picker/PickerButton.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ResourcePicker from '../resource-picker/ResourcePicker'; // eslint-disable-line no-unused-vars - -function PickerButton(props) { - return ( -
    - -
    - {props.hiddenInputs} - {props.content} -
    -
    -
    - ); -} - -PickerButton.propTypes = { - onClick: PropTypes.func, - hiddenInputs: PropTypes.node, - content: PropTypes.array, - onRef: PropTypes.func, -}; - -export default PickerButton; diff --git a/app/javascript/components/admin/picker/pickerModal.jsx b/app/javascript/components/admin/picker/pickerModal.jsx deleted file mode 100644 index a7d07ebb..00000000 --- a/app/javascript/components/admin/picker/pickerModal.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import ReactDOM from 'react-dom'; - -function pickerModal() { - new Foundation.Reveal(this.jqmodal, null); - this.jqmodal.on('open.zf.reveal', () => this.jqmodal.css({ top: '15px' })); - this.jqmodal.on('closed.zf.reveal', () => { - ReactDOM.unmountComponentAtNode(this.modal); - }); -} - -export default pickerModal; diff --git a/app/javascript/components/admin/picker/pickerWindowWrapper.jsx b/app/javascript/components/admin/picker/pickerWindowWrapper.jsx deleted file mode 100644 index 87bfa033..00000000 --- a/app/javascript/components/admin/picker/pickerWindowWrapper.jsx +++ /dev/null @@ -1,108 +0,0 @@ -import React from 'react'; -import PaginationBoxView from '../../paginate/PaginationBoxView'; - -// eslint-disable-next-line no-unused-vars -function pickerWindowWrapper(WrappedComponent, path) { - // eslint-disable-next-line react/display-name - return class extends React.Component { - constructor(props) { - super(props); - - this.state = { - results: [], - pagination: { - current_page: 1, - total_pages: 0, - }, - q: null, - }; - } - - componentDidMount() { - this.fetch(); - } - - fetch() { - const data = { - page: this.state.pagination.current_page, - q: this.state.q, - }; - const url = Routes[path].call(this, { - ...data, - ...this.state, - ...this.props, - }); - $.getJSON(url).then(x => this.setState({ ...x })); - } - - onFilterChange(field, event) { - this.setState({ [field]: event.target.value }, this.fetch); - } - - pageClick(data) { - const selected = data.selected; - this.setState( - { - ...this.state, - pagination: { - ...this.state.pagination, - current_page: selected + 1, - }, - }, - this.fetch - ); - } - - pagination() { - const breakLabel = ( -
  • - {/* - eslint-disable jsx-a11y/anchor-is-valid, no-script-url - */} - ... - {/* - eslint-enable jsx-a11y/anchor-is-valid, no-script-url - */} -
  • - ); - return ( - '} - breakLabel={breakLabel} - pageNum={this.state.pagination.total_pages} - initialSelected={this.state.pagination.current_page - 1} - forceSelected={this.state.pagination.current_page - 1} - marginPagesDisplayed={2} - pageRangeDisplayed={5} - // eslint-disable-next-line react/jsx-no-bind - clickCallback={this.pageClick.bind(this)} - containerClassName={'o-pagination o-page__wrap--row-nest'} - itemClassName={'o-pagination__item'} - nextClassName={'o-pagination__item--next'} - previousClassName={'o-pagination__item--prev'} - pagesClassName={'o-pagination__item--middle'} - subContainerClassName={'o-pagination__pages'} - activeClassName={'o-pagination__page--active'} - /> - ); - } - - render() { - return ( -
    - -
    - ); - } - }; -} - -export default pickerWindowWrapper; diff --git a/app/javascript/components/admin/resource-picker/ResourcePicker.jsx b/app/javascript/components/admin/resource-picker/ResourcePicker.jsx deleted file mode 100644 index 0a9ac91f..00000000 --- a/app/javascript/components/admin/resource-picker/ResourcePicker.jsx +++ /dev/null @@ -1,107 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import PropTypes from 'prop-types'; -import _ from 'lodash'; -import ResourcePickerWindow from './ResourcePickerWindow'; -import ResourcePickerResource from './ResourcePickerResource'; -import PickerButton from '../picker/PickerButton'; -import pickerWindowWrapper from '../picker/pickerWindowWrapper'; -import pickerModal from '../picker/pickerModal'; -import { Foundation } from 'foundation-sites'; -import $ from 'jquery'; - -class ResourcePicker extends React.Component { - constructor(props) { - super(props); - - const resources = 'resources' in props ? props.resources : []; - - this.state = { - resources: resources, - }; - } - - get jqmodal() { - return $(this.modal); - } - - get allowMultiple() { - if (_.isUndefined(this.props.allow_multiple) || this.props.allow_multiple === null) { - return true; - } - return this.props.allow_multiple; - } - - componentDidMount() { - Foundation.addToJquery($); - // eslint-disable-next-line no-undef - pickerModal.call(this); - } - - onClickSelect() { - // eslint-disable-next-line no-undef - const pickerComponent = pickerWindowWrapper(ResourcePickerWindow, 'lcms_engine_admin_resource_picker_path'); - const picker = React.createElement( - pickerComponent, - { - onSelectResource: this.selectResource.bind(this), - }, - null - ); - ReactDOM.render(picker, this.modal); - this.jqmodal.foundation('open'); - } - - selectResource(resource) { - this.jqmodal.foundation('close'); - - const newResources = this.allowMultiple ? [...this.state.resources, resource] : [resource]; - - this.setState({ - ...this.state, - resources: newResources, - }); - } - - removeResource(resource) { - this.setState({ - ...this.state, - resources: _.filter(this.state.resources, r => r.id !== resource.id), - }); - } - - render() { - const resources = this.state.resources.map(resource => { - return ( - this.removeResource(resource)} - /> - ); - }); - - const input = ; - - return ( - (this.modal = m)} - /> - ); - } -} - -ResourcePicker.propTypes = { - name: PropTypes.string, - resources: PropTypes.array, - allow_multiple: PropTypes.bool, -}; - -export default ResourcePicker; diff --git a/app/javascript/components/admin/resource-picker/ResourcePickerResource.jsx b/app/javascript/components/admin/resource-picker/ResourcePickerResource.jsx deleted file mode 100644 index c5a134c0..00000000 --- a/app/javascript/components/admin/resource-picker/ResourcePickerResource.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -function ResourcePickerResource(props) { - return ( -
    - -
    - {props.resource.title} - - × - -
    -
    - ); -} - -ResourcePickerResource.propTypes = { - name: PropTypes.string, - resource: PropTypes.object, - onClickClose: PropTypes.func, -}; - -export default ResourcePickerResource; diff --git a/app/javascript/components/admin/resource-picker/ResourcePickerWindow.jsx b/app/javascript/components/admin/resource-picker/ResourcePickerWindow.jsx deleted file mode 100644 index a415948f..00000000 --- a/app/javascript/components/admin/resource-picker/ResourcePickerWindow.jsx +++ /dev/null @@ -1,144 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -class ResourcePickerWindow extends React.Component { - constructor(props) { - super(props); - - this.typeOptions = [ - ['subject', 'subject'], - ['grade', 'grade'], - ['module', 'module'], - ['unit', 'unit'], - ]; - - this.subjectOptions = [ - ['ela', 'ELA'], - ['math', 'Math'], - ]; - - this.gradeOptions = [ - ['pk', 'prekindergarten'], - ['k', 'kindergarten'], - ['1', 'grade 1'], - ['2', 'grade 2'], - ['3', 'grade 3'], - ['4', 'grade 4'], - ['5', 'grade 5'], - ['6', 'grade 6'], - ['7', 'grade 7'], - ['8', 'grade 8'], - ['9', 'grade 9'], - ['10', 'grade 10'], - ['11', 'grade 11'], - ['12', 'grade 12'], - ]; - - const initialState = { - pagination: { - current_page: 1, - total_pages: 0, - }, - results: [], - type: null, - subject: null, - grade: null, - q: null, - }; - - this.state = { ...initialState, ...props }; - } - - filterElement(title, value, type, data) { - return ( - - ); - } - - selectResource(resource) { - if ('onSelectResource' in this.props) { - this.props.onSelectResource(resource); - } - } - - render() { - const { grade, q, subject, type } = this.props; - - /* eslint-disable jsx-a11y/anchor-is-valid */ - return ( -
    -
    -
    -

    Select resource

    -
    - {this.filterElement('Curriculum Type', type, 'type', this.typeOptions)} - {this.filterElement('Subject', subject, 'subject', this.subjectOptions)} - {this.filterElement('Grade', grade, 'grade', this.gradeOptions)} - - -
    -
    -
    - -
    -
    - - - - - - - - {this.props.results.map(resource => ( - - {/* - eslint-disable react/jsx-no-bind - */} - - - ))} - -
    Title
    {resource.title}
    - - {this.props.pagination()} -
    -
    -
    - ); - } -} - -ResourcePickerWindow.propTypes = { - onFilterChange: PropTypes.func, - onSelectResource: PropTypes.func, - grade: PropTypes.string, - q: PropTypes.string, - subject: PropTypes.string, - type: PropTypes.string, - results: PropTypes.array, - pagination: PropTypes.func, -}; - -export default ResourcePickerWindow; diff --git a/app/javascript/packs/lcms_engine_admin.js b/app/javascript/packs/lcms_engine_admin.js deleted file mode 100644 index ff64d1fc..00000000 --- a/app/javascript/packs/lcms_engine_admin.js +++ /dev/null @@ -1,15 +0,0 @@ -import Initializer from '../components/admin/Initializer'; - -document.addEventListener('turbolinks:load', () => { - Initializer.initializeResourcesForm(); - Initializer.initializeResourcesList(); - Initializer.initializeSelectAll(); -}); - -// Support component names relative to this directory: -// eslint-disable-next-line no-undef -const componentRequireContext = require.context('components', true); -// eslint-disable-next-line no-undef -const ReactRailsUJS = require('react_ujs'); -// eslint-disable-next-line react-hooks/rules-of-hooks -ReactRailsUJS.useContext(componentRequireContext); diff --git a/app/javascript/packs/lcms_engine_application.js b/app/javascript/packs/lcms_engine_application.js deleted file mode 100644 index 6bbded4e..00000000 --- a/app/javascript/packs/lcms_engine_application.js +++ /dev/null @@ -1,25 +0,0 @@ -/* eslint no-console:0 */ -// This file is automatically compiled by Webpack, along with any other files -// present in this directory. You're encouraged to place your actual application logic in -// a relevant structure within app/javascript and only use these pack files to reference -// that code so it'll be compiled. -// -// To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate -// layout file, like app/views/layouts/application.html.erb - -// Uncomment to copy all static images under ../images to the output folder and reference -// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>) -// or the `imagePath` JavaScript helper below. -// -// const images = require.context('../images', true) -// const imagePath = (name) => images(name, true) - -// Support component names relative to this directory: -// eslint-disable-next-line no-undef -const componentRequireContext = require.context('components', true); -// eslint-disable-next-line no-undef -const ReactRailsUJS = require('react_ujs'); -// eslint-disable-next-line react-hooks/rules-of-hooks -ReactRailsUJS.useContext(componentRequireContext); - -console.debug('Hello World from Webpacker FROM INSIDE `lcms-engine` gem!'); diff --git a/app/javascript/packs/server_rendering.js b/app/javascript/packs/server_rendering.js deleted file mode 100644 index e4a90f8f..00000000 --- a/app/javascript/packs/server_rendering.js +++ /dev/null @@ -1,6 +0,0 @@ -// By default, this pack is loaded for server-side rendering. -// It must expose react_ujs as `ReactRailsUJS` and prepare a require context. -const componentRequireContext = require.context('components', true); -const ReactRailsUJS = require('react_ujs'); -// eslint-disable-next-line react-hooks/rules-of-hooks -ReactRailsUJS.useContext(componentRequireContext); diff --git a/vendor/assets/javascripts/html.sortable.min.js b/app/javascript/vendor/html.sortable.min.js similarity index 100% rename from vendor/assets/javascripts/html.sortable.min.js rename to app/javascript/vendor/html.sortable.min.js diff --git a/app/jobs/concerns/lcms/engine/nested_resque_job.rb b/app/jobs/concerns/lcms/engine/nested_resque_job.rb index dc65c843..537df961 100644 --- a/app/jobs/concerns/lcms/engine/nested_resque_job.rb +++ b/app/jobs/concerns/lcms/engine/nested_resque_job.rb @@ -6,7 +6,7 @@ module NestedResqueJob extend ActiveSupport::Concern class_methods do # rubocop:disable Metrics/BlockLength - def queued_or_running_nested?(job_id, current_job_id = -1) + def queued_or_running_nested?(job_id, current_job_id = '-1') check_child = ->(j) { j['arguments'][1]&.dig('initial_job_id') == job_id && j['job_id'] != current_job_id } job_klasses = self::NESTED_JOBS + [name] job_klasses.each do |job_klass| @@ -25,6 +25,19 @@ def status_nested(jid) :done end + # Fetches the results of nested jobs in Resque. + # + # This method iterates over each job class in the `NESTED_JOBS` constant, + # constructs a Redis key pattern for the nested job results, and retrieves + # the value from Redis. It attempts to parse the value as JSON and adds it + # to the result array. If the parsing fails, it adds the original value + # to the result array instead. + # + # @param jid [String] The job ID of the parent job. + # @return [Array] An array containing the results of all nested jobs of the parent job. + # + # @example + # fetch_result_nested('1234') def fetch_result_nested(jid) [].tap do |result| self::NESTED_JOBS.each do |job_klass| diff --git a/app/jobs/lcms/engine/document_generate_gdoc_job.rb b/app/jobs/lcms/engine/document_generate_gdoc_job.rb index 6321a73d..020d1280 100644 --- a/app/jobs/lcms/engine/document_generate_gdoc_job.rb +++ b/app/jobs/lcms/engine/document_generate_gdoc_job.rb @@ -20,7 +20,7 @@ class DocumentGenerateGdocJob < Lcms::Engine::ApplicationJob def perform(document, options) content_type = options[:content_type] - document = DocumentGenerator.document_presenter.new document.reload, content_type: content_type + document = DocumentGenerator.document_presenter.new(document.reload, content_type:) gdoc = GDOC_EXPORTERS[content_type].new(document, options).export key = options[:excludes].present? ? options[:gdoc_folder] : document.gdoc_key diff --git a/app/jobs/lcms/engine/document_generate_pdf_job.rb b/app/jobs/lcms/engine/document_generate_pdf_job.rb index 1c230d0d..c20fa71d 100644 --- a/app/jobs/lcms/engine/document_generate_pdf_job.rb +++ b/app/jobs/lcms/engine/document_generate_pdf_job.rb @@ -16,7 +16,7 @@ class DocumentGeneratePdfJob < Lcms::Engine::ApplicationJob def perform(doc, options) content_type = options[:content_type] - document = DocumentGenerator.document_presenter.new doc.reload, content_type: content_type + document = DocumentGenerator.document_presenter.new(doc.reload, content_type:) filename = options[:filename].presence || "#{::DocumentExporter::Pdf::Base.s3_folder}/#{document.pdf_filename}" pdf = PDF_EXPORTERS[content_type].new(document, options).export url = S3Service.upload filename, pdf diff --git a/app/jobs/lcms/engine/document_parse_job.rb b/app/jobs/lcms/engine/document_parse_job.rb index 935c77a5..10161366 100644 --- a/app/jobs/lcms/engine/document_parse_job.rb +++ b/app/jobs/lcms/engine/document_parse_job.rb @@ -10,41 +10,63 @@ class DocumentParseJob < Lcms::Engine::ApplicationJob queue_as :default - def perform(entry, options = {}) - if entry.is_a?(Document) - @document = entry - reimport_materials if options[:reimport_materials].present? - reimport_document(@document.file_url) if result.nil? + # + # Options can have: + # - reimport_materials - require materials re-import. + # If such option is passed, then at first try to import + # connected materials. If all is good - import document itself. + # If there were errors with materials - do not import document. + # + # @param [Integer|String] id_or_url + # @param [Hash] options + # + def perform(id_or_url, options = {}) + @options = options - @document.update(reimported: false) unless result[:ok] + if id_or_url.is_a?(String) + reimport_document(id_or_url) else - reimport_document entry + @options.merge!(is_reimport: true) + reimport_by_id(id_or_url) + @document.update(reimported: false) unless result[:ok] end - store_result result, options + store_result(result, options) + rescue StandardError => e + res = { ok: false, link: id_or_url, errors: [e.message] } + store_result(res, options) end private - attr_reader :document, :result + attr_reader :document, :options, :result + + # + # @param [Integer] id + # + def reimport_by_id(id) + @document = Lcms::Engine::Document.find(id) + reimport_materials if options[:reimport_materials].present? + reimport_document(@document.file_url) + end def reimport_document(link) - form = DocumentForm.new({ link: link }, import_retry: true) + form = DocumentForm.new({ link: }, options.merge(import_retry: true)) @result = if form.save - { ok: true, link: link, model: form.document, warnings: form.service_errors } + { ok: true, link:, model: form.document, warnings: form.service_errors } else - { ok: false, link: link, errors: form.errors[:link] } + { ok: false, link:, errors: form.errors[:link] } end end def reimport_materials document.materials.each do |material| link = material.file_url - form = MaterialForm.new({ link: link, source_type: material.source_type }, import_retry: true) + form = MaterialForm.new({ link:, source_type: material.source_type }, import_retry: true) next if form.save error_msg = %(Material error (source): #{form.errors[:link]}) - @result = { ok: false, link: link, errors: [error_msg] } + @result = { ok: false, link:, errors: [error_msg] } break end end diff --git a/app/jobs/lcms/engine/integrations/webhook_call_job.rb b/app/jobs/lcms/engine/integrations/webhook_call_job.rb new file mode 100644 index 00000000..fff434ba --- /dev/null +++ b/app/jobs/lcms/engine/integrations/webhook_call_job.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Lcms + module Engine + module Integrations + class WebhookCallJob < Lcms::Engine::ApplicationJob + extend ResqueJob + include Lcms::Engine::RetryDelayed + + queue_as :low + + def perform(config_id, payload) + webhook_configuration = WebhookConfiguration.find(config_id) + + webhook_configuration.execute_call(payload) + end + end + end + end +end diff --git a/app/jobs/lcms/engine/material_parse_job.rb b/app/jobs/lcms/engine/material_parse_job.rb index e17beb8c..99cc50e1 100644 --- a/app/jobs/lcms/engine/material_parse_job.rb +++ b/app/jobs/lcms/engine/material_parse_job.rb @@ -9,24 +9,27 @@ class MaterialParseJob < Lcms::Engine::ApplicationJob queue_as :default - def perform(entry, options = {}) - attrs = attributes_for entry - form = MaterialForm.new(attrs, import_retry: true) + # + # @param [Integer|String] id_or_url + # @param [Hash] options + # + def perform(id_or_url, options = {}) + url = + if id_or_url.is_a?(String) + id_or_url + else + Lcms::Engine::Material.find(id_or_url).file_url + end + form = MaterialForm.new({ link: url }, import_retry: true) res = if form.save - { ok: true, link: attrs[:link], model: form.material } + { ok: true, link: url, model: form.material } else - { ok: false, link: attrs[:link], errors: form.errors[:link] } + { ok: false, link: url, errors: form.errors[:link] } end - store_result res, options - end - - private - - def attributes_for(entry) - {}.tap do |data| - data[:link] = entry.is_a?(Material) ? entry.file_url : entry - data[:source_type] = entry.source_type if entry.is_a?(Material) - end + store_result(res, options) + rescue StandardError => e + res = { ok: false, link: id_or_url, errors: [e.message] } + store_result(res, options) end end end diff --git a/app/lib/lcms/engine/api/auth_helper.rb b/app/lib/lcms/engine/api/auth_helper.rb new file mode 100644 index 00000000..b876cdd6 --- /dev/null +++ b/app/lib/lcms/engine/api/auth_helper.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Lcms + module Engine + module Api + module AuthHelper + def self.compute_hmac_signature(timestamp, path, body, secret_key) + data = "#{timestamp}#{path}#{body}" + OpenSSL::HMAC.hexdigest( + OpenSSL::Digest.new('sha256'), + secret_key, + data + ) + end + end + end + end +end diff --git a/app/middleware/remove_session.rb b/app/middleware/remove_session.rb index c9d88a64..b7409806 100644 --- a/app/middleware/remove_session.rb +++ b/app/middleware/remove_session.rb @@ -16,11 +16,10 @@ def call(env) # Don't delete the session cookie if: # - We're in the process of logging in (breaks CSRF for sign in form) # - We're logged in (needed for Devise) - skip_delete = ( + skip_delete = path =~ %r{^/users} || user_key.present? || headers[SET_COOKIE].blank? - ) signing_out = path == '/users/sign_out' diff --git a/app/models/concerns/lcms/engine/filterable.rb b/app/models/concerns/lcms/engine/filterable.rb new file mode 100644 index 00000000..64828772 --- /dev/null +++ b/app/models/concerns/lcms/engine/filterable.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Lcms + module Engine + module Filterable + extend ActiveSupport::Concern + + included do + scope :where_grade, ->(grades) { where_metadata_in :grade, grades } + scope :where_module, ->(modules) { where_metadata_in :module, modules } + scope :where_subject, ->(subjects) { where_metadata_in :subject, subjects } + end + + class_methods do + # + # @param [String|Symbol] key + # @param [Array|Array] arr + # @return [ActiveRecord::QueryMethods::WhereChain] + # + def where_metadata_in(key, arr) + arr = Array.wrap(arr).compact.map(&:downcase) + base_table = + case name + when 'Lcms::Engine::Material' + 'materials' + when 'Lcms::Engine::Resource' + 'resources' + when 'Lcms::Engine::Document' + 'documents' + else + raise "Unknown table name: #{name}" + end + clauses = Array.new(arr.count) { "lower(#{base_table}.metadata->>'#{key}') = ?" }.join(' OR ') + where(clauses, *arr) + end + end + end + end +end diff --git a/app/models/concerns/lcms/engine/navigable.rb b/app/models/concerns/lcms/engine/navigable.rb deleted file mode 100644 index d7261e1c..00000000 --- a/app/models/concerns/lcms/engine/navigable.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -require 'active_support/concern' - -module Lcms - module Engine - module Navigable - extend ActiveSupport::Concern - - included do - def parents - ancestors.reverse - end - - def previous - @previous ||= - if level_position.to_i.positive? - siblings.where(level_position: level_position - 1).first - else - # last element of previous node from parent level - parent.try(:previous).try(:children).try(:last) - end - end - - def next - @next ||= - if level_position.nil? - nil - elsif level_position < siblings.size - siblings.where(level_position: level_position + 1).first - else - # first element of next node from parent level - parent.try(:next).try(:children).try(:first) - end - end - end - end - end -end diff --git a/app/models/concerns/lcms/engine/searchable.rb b/app/models/concerns/lcms/engine/searchable.rb deleted file mode 100644 index 3577a0a0..00000000 --- a/app/models/concerns/lcms/engine/searchable.rb +++ /dev/null @@ -1,60 +0,0 @@ -# frozen_string_literal: true - -require 'active_support/concern' - -module Lcms - module Engine - module Searchable - extend ActiveSupport::Concern - - included do - attr_accessor :skip_indexing - - after_commit :index_document, on: %i(create update), if: :should_index? - after_commit :delete_document, on: :destroy, if: :should_index? - - def self.search(term, options = {}) - search_model.search term, options.merge!(model_type: name.underscore) - end - - private - - def self.search_model - @search_model ||= Search::Document - end - - def delete_document - search_repo.delete search_doc - rescue Faraday::ConnectionFailed, Elasticsearch::Transport::Transport::Errors::NotFound => e - Rails.logger.warn("index_document failed: #{e.message}") - end - - def search_doc - self.class.search_model.build_from self - end - - def index_document - doc = search_doc - search_repo.save(doc) if doc.present? - rescue Faraday::ConnectionFailed => e - Rails.logger.warn("index_document failed: #{e.message}") - end - - def search_repo - @search_repo ||= Search::Repository.new - end - - def should_index? - !skip_indexing && search_repo.index_exists? - end - - # - # Explicitly skip indexing as we do not use it now - # - def skip_indexing - true - end - end - end - end -end diff --git a/app/models/lcms/engine/document.rb b/app/models/lcms/engine/document.rb index 91b50a6c..be9d433a 100644 --- a/app/models/lcms/engine/document.rb +++ b/app/models/lcms/engine/document.rb @@ -3,17 +3,20 @@ module Lcms module Engine class Document < ApplicationRecord + include Filterable include Partable + GOOGLE_URL_PREFIX = 'https://docs.google.com/document/d' belongs_to :resource, optional: true has_many :document_parts, as: :renderer, dependent: :delete_all has_and_belongs_to_many :materials + after_destroy :destroy_connected_resource + before_save :clean_curriculum_metadata before_save :set_resource_from_metadata - store_accessor :foundational_metadata serialize :toc, DocTemplate::Objects::TocMetadata scope :actives, -> { where(active: true) } @@ -47,7 +50,7 @@ class Document < ApplicationRecord (documents.metadata ->> 'subject' <> 'ela' AND documents.metadata ->> 'unit' = :mod) OR (documents.metadata ->> 'subject' = 'ela' AND documents.metadata ->> 'module' = :mod) SQL - where(sql, mod: mod) + where(sql, mod:) } scope :with_broken_materials, lambda { @@ -64,10 +67,10 @@ class Document < ApplicationRecord def activate! self.class.transaction do - # deactive all other lessons for this resource - self.class.where(resource_id: resource_id).where.not(id: id).update_all active: false + # de-active all other lessons for this resource + self.class.where(resource_id:).where.not(id:).update_all(active: false) # activate this lesson. PS: use a simple sql update, no callbacks - update_columns active: true + update_columns(active: true) end end @@ -85,25 +88,15 @@ def file_url "#{GOOGLE_URL_PREFIX}/#{file_id}" end - def file_fs_url - return unless foundational_file_id.present? - - "#{GOOGLE_URL_PREFIX}/#{foundational_file_id}" - end - - def foundational? - metadata['type'].to_s.casecmp('fs').zero? - end - def gdoc_material_ids materials.gdoc.pluck(:id) end def materials_anchors - {}.tap do |materials_with_anchors| + {}.tap do |materials_with_anchors| # steep:ignore toc.collect_children.each do |x| x.material_ids.each do |m| - materials_with_anchors[m] ||= { optional: [], anchors: [] } + materials_with_anchors[m] ||= { optional: [], anchors: [] } # steep:ignore materials_with_anchors[m][x.optional ? :optional : :anchors] << x.anchor end end @@ -122,7 +115,7 @@ def tmp_link(key) url = links[key] with_lock do reload.links.delete(key) - update links: links + update links: end url end @@ -135,16 +128,16 @@ def clean_curriculum_metadata # downcase subjects metadata['subject'] = metadata['subject']&.downcase - /(\d+)/.match(metadata['grade']) do |m| - metadata['grade'] = "grade #{m[1].to_i}" - end - # store only the lesson number # or alphanumeric - needed by OPR type, see https://github.com/learningtapestry/unbounded/issues/557 lesson = metadata['lesson'] metadata['lesson'] = lesson.match(/lesson (\w+)/i).try(:[], 1) || lesson if lesson.present? end + def destroy_connected_resource + resource&.destroy if active? + end + def set_resource_from_metadata return unless metadata.present? diff --git a/app/models/lcms/engine/document_bundle.rb b/app/models/lcms/engine/document_bundle.rb index 6f34262c..377b0967 100644 --- a/app/models/lcms/engine/document_bundle.rb +++ b/app/models/lcms/engine/document_bundle.rb @@ -20,18 +20,18 @@ def self.update_bundle(resource, category = 'full') end def self.update_pdf_bundle(resource, category) - zip_path = LessonsPdfBundler.new(resource, category).bundle - return unless File.exist?(zip_path.to_s) + zip_path = LessonsPdfBundler.new(resource, category).bundle.to_s + return unless File.exist?(zip_path) begin - doc_bundle = find_or_create_by(resource: resource, category: category, content_type: 'pdf') + doc_bundle = find_or_create_by(resource:, category:, content_type: 'pdf') File.open(zip_path) do |f| doc_bundle.file = f doc_bundle.save! end ensure - FileUtils.rm(zip_path) + Bundler::FileUtils.rm_f(zip_path) end end private_class_method :update_pdf_bundle @@ -42,7 +42,7 @@ def self.update_gdoc_bundle(resource) bundle_path = LessonsGdocBundler.new(resource).bundle return unless bundle_path - doc_bundle = find_or_create_by(resource: resource, category: 'full', content_type: 'gdoc') + doc_bundle = find_or_create_by(resource:, category: 'full', content_type: 'gdoc') doc_bundle.url = bundle_path doc_bundle.save! end diff --git a/app/models/lcms/engine/download.rb b/app/models/lcms/engine/download.rb deleted file mode 100644 index bf702d59..00000000 --- a/app/models/lcms/engine/download.rb +++ /dev/null @@ -1,60 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - class Download < ApplicationRecord - CONTENT_TYPES = { - zip: 'application/zip', - pdf: 'application/pdf', - excel: %w(application/vnd.ms-excel application/vnd.openxmlformats-officedocument.spreadsheetml.sheet), - powerpoint: %w(application/vnd.ms-powerpoint application/vnd.openxmlformats-officedocument.presentationml.presentation), # rubocop:disable Layout/LineLength - doc: %w(application/msword application/vnd.openxmlformats-officedocument.wordprocessingml.document) - }.freeze - S3_URL = 'http://k12-content.s3-website-us-east-1.amazonaws.com/' - URL_PUBLIC_PREFIX = 'public://' - - mount_uploader :filename, DownloadUploader - alias_attribute :file, :filename - - validates :title, presence: true - validates :file, presence: true, if: -> { url.nil? } - validates :url, presence: true, if: -> { file.nil? } - - before_save :update_metadata - - def attachment_url - if url.present? - url.sub(URL_PUBLIC_PREFIX, S3_URL) - else - file.url - end - end - - def s3_filename - File.basename(attachment_url) - end - - def attachment_content_type - type = content_type - CONTENT_TYPES.each do |key, types| - if Array.wrap(types).include?(content_type) - type = key - break - end - end - type - end - - private - - def update_metadata - if file.present? - self.content_type = file.file.content_type - self.filesize = file.file.size - end - - true - end - end - end -end diff --git a/app/models/lcms/engine/download_category.rb b/app/models/lcms/engine/download_category.rb deleted file mode 100644 index 2445bc95..00000000 --- a/app/models/lcms/engine/download_category.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -require 'acts_as_list' - -module Lcms - module Engine - class DownloadCategory < ApplicationRecord - has_many :resource_downloads - - default_scope { order(:position) } - - acts_as_list - - validates :title, uniqueness: true, presence: true - - before_save :unique_bundle - - def self.bundle - where(bundle: true).first - end - - private - - def unique_bundle - # we need the try here, otherwise this will fail when migrating the first time. - self.class.where.not(id: id).where(bundle: true).update_all(bundle: false) if try(:bundle) && bundle_changed? - end - end - end -end diff --git a/app/models/lcms/engine/integrations.rb b/app/models/lcms/engine/integrations.rb new file mode 100644 index 00000000..ea005c2d --- /dev/null +++ b/app/models/lcms/engine/integrations.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Lcms + module Engine + module Integrations + def self.table_name_prefix + 'lcms_engine_integrations_' + end + end + end +end diff --git a/app/models/lcms/engine/integrations/webhook_configuration.rb b/app/models/lcms/engine/integrations/webhook_configuration.rb new file mode 100644 index 00000000..42a7be77 --- /dev/null +++ b/app/models/lcms/engine/integrations/webhook_configuration.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +module Lcms + module Engine + module Integrations + class WebhookConfiguration < ApplicationRecord + validates :event_name, :endpoint_url, presence: true + validates :endpoint_url, url: true + validates :action, inclusion: { in: %w(post put patch delete) } + validates :auth_type, inclusion: { in: %w(basic bearer hmac) }, allow_blank: true + + scope :active, -> { where(active: true) } + + CALL_TIMEOUT_SECONDS = 30 + + def self.trigger(event_name, payload) + active.where(event_name:).each do |config| + Integrations::WebhookCallJob.perform_later(config.id, payload) + end + end + + class WebhookCallError < StandardError; end + + def execute_call(payload) + response = HTTParty.send( + action.downcase.to_sym, + endpoint_url, + body: payload.to_json, + headers: { + 'Content-Type' => 'application/json' + }.merge(auth_headers(payload)), + timeout: CALL_TIMEOUT_SECONDS + ) + + if response.success? + Rails.logger.info("Webhook call successful: #{event_name} to #{endpoint_url}") + else + error_message = + "Webhook call failed: #{event_name} to #{endpoint_url}. Status: #{response.code}, Body: #{response.body}" + Rails.logger.error(error_message) + + raise WebhookCallError, error_message if should_retry?(response.code) + end + + response + rescue StandardError => e + error_message = "Webhook call error: #{event_name} to #{endpoint_url}. Error: #{e.message}" + Rails.logger.error(error_message) + raise WebhookCallError, error_message + end + + private + + def auth_headers(payload = nil) + case auth_type + when 'basic' + { + 'Authorization' => + "Basic #{Base64.strict_encode64("#{auth_credentials['username']}:#{auth_credentials['password']}")}" + } + when 'bearer' + { 'Authorization' => "Bearer #{auth_credentials['token']}" } + when 'hmac' + timestamp = Time.now.to_i.to_s + { + 'X-HMAC-Signature' => generate_hmac_signature(timestamp, payload), + 'X-Timestamp' => timestamp + } + else + {} + end + end + + def generate_hmac_signature(timestamp, payload) + secret_key = auth_credentials['secret_key'] + path = URI(endpoint_url).path + Lcms::Engine::Api::AuthHelper.compute_hmac_signature(timestamp, path, payload.to_json, secret_key) + end + + def should_retry?(status_code) + # Retry for server errors (5xx) and certain client errors + status_code.to_i >= 500 || [408, 429].include?(status_code.to_i) + end + end + end + end +end diff --git a/app/models/lcms/engine/material.rb b/app/models/lcms/engine/material.rb index 2e001c1a..706f6b04 100644 --- a/app/models/lcms/engine/material.rb +++ b/app/models/lcms/engine/material.rb @@ -5,6 +5,7 @@ module Lcms module Engine class Material < ApplicationRecord + include Filterable include PgSearch::Model include Partable diff --git a/app/models/lcms/engine/material_part.rb b/app/models/lcms/engine/material_part.rb deleted file mode 100644 index c9e27bff..00000000 --- a/app/models/lcms/engine/material_part.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - class MaterialPart < ApplicationRecord - belongs_to :material - enum context_type: { default: 0, gdoc: 1 } - - default_scope { active } - - scope :active, -> { where(active: true) } - end - end -end diff --git a/app/models/lcms/engine/resource.rb b/app/models/lcms/engine/resource.rb index ae4d09fd..abe16562 100644 --- a/app/models/lcms/engine/resource.rb +++ b/app/models/lcms/engine/resource.rb @@ -3,6 +3,8 @@ module Lcms module Engine class Resource < ApplicationRecord + include Filterable + enum resource_type: { resource: 1, podcast: 2, @@ -15,15 +17,12 @@ class Resource < ApplicationRecord MEDIA_TYPES = %i(video podcast).map { |t| resource_types[t] }.freeze GENERIC_TYPES = %i(text_set quick_reference_guide resource_other).map { |t| resource_types[t] }.freeze - SUBJECTS = %w(ela math lead).freeze + SUBJECTS = %w(ela math).freeze HIERARCHY = %i(subject grade module unit lesson).freeze - include Searchable - include Navigable - mount_uploader :image_file, ResourceImageUploader - acts_as_taggable_on :content_sources, :download_types, :resource_types, :tags, :topics + acts_as_taggable_on :resource_types, :tags has_closure_tree order: :level_position, dependent: :destroy, numeric_order: true belongs_to :parent, class_name: 'Lcms::Engine::Resource', foreign_key: 'parent_id', optional: true @@ -38,11 +37,6 @@ class Resource < ApplicationRecord has_many :resource_standards, dependent: :destroy has_many :standards, through: :resource_standards - # Downloads. - has_many :resource_downloads, dependent: :destroy - has_many :downloads, through: :resource_downloads - accepts_nested_attributes_for :resource_downloads, allow_destroy: true - # Reading assignments. has_many :resource_reading_assignments, dependent: :destroy alias_attribute :reading_assignments, :resource_reading_assignments @@ -63,15 +57,24 @@ class Resource < ApplicationRecord has_many :document_bundles, dependent: :destroy validates :title, presence: true - validates :url, presence: true, url: true, if: %i(video? podcast?) + validates :url, presence: true, url: true, if: -> { video? || podcast? } - scope :where_grade, ->(grades) { where_metadata_in :grade, grades } - scope :where_module, ->(modules) { where_metadata_in :module, modules } - scope :where_subject, ->(subjects) { where_metadata_in :subject, subjects } scope :media, -> { where(resource_type: MEDIA_TYPES) } scope :generic_resources, -> { where(resource_type: GENERIC_TYPES) } scope :ordered, -> { order(:hierarchical_position, :slug) } + # @param link_path [String] when nested, use a dot to separate the levels, eg "level1.level2" + # @param datetime [DateTime, Time] + scope :where_link_updated_after, lambda { |link_path, datetime| + path_elements = link_path.split('.') + jsonb_path = path_elements.map(&:to_s).join(',') + + # Convert datetime to Unix timestamp for comparison + timestamp = datetime.to_i + + where("(links #>> '{#{jsonb_path},timestamp}')::bigint >= ?", timestamp) + } + before_save :update_metadata, :update_slug, :update_position after_save :update_descendants_meta, :update_descendants_position, @@ -80,19 +83,13 @@ class Resource < ApplicationRecord before_destroy :destroy_additional_resources class << self - # Define dynamic scopes for hierarchy levels. - # I,e: `grades`, `units`, etc - HIERARCHY.map(&:to_s).each do |level| - define_method(:"#{level.pluralize}") { where(curriculum_type: level) } - end - def metadata_from_dir(dir) pairs = hierarchy[0...dir.size].zip(dir) pairs.to_h.compact.stringify_keys end def find_by_directory(*dir) - dir = dir&.flatten&.select(&:present?) + dir = dir.flatten.select(&:present?) return unless dir.present? type = hierarchy[dir.size - 1] @@ -100,15 +97,6 @@ def find_by_directory(*dir) where('metadata @> ?', meta).where(curriculum_type: type).first end - def find_podcast_by_url(url) - podcast.where(url: url).first - end - - def find_video_by_url(url) - video_id = MediaEmbed.video_id(url) - video.where("url ~ '#{video_id}(&|$)'").first - end - def hierarchy Lcms::Engine::Resource::HIERARCHY end @@ -123,30 +111,34 @@ def ransackable_scopes(_auth_object = nil) def tree(name = nil) if name.present? joins(:curriculum).where('curriculums.name = ? OR curriculums.slug = ?', name, name) - elsif (default = Lcms::Engine::Curriculum.default) - where(curriculum_id: default.id) else - where(nil) + where(curriculum_id: Lcms::Engine::Curriculum.default&.id) end end - - def where_metadata_in(key, arr) - arr = Array.wrap arr - clauses = Array.new(arr.count) { "metadata->>'#{key}' = ?" }.join(' OR ') - where(clauses, *arr) - end end # Define predicate methods for subjects. # I,e: #ela?, #math?, .. SUBJECTS.each do |subject_name| - define_method(:"#{subject_name}?") { subject == subject_name.to_s } + define_method(:"#{subject_name}?") do + # @type self: Lcms::Engine::Resource + subject == subject_name.to_s + end end # Define predicate methods for hierarchy levels. - # I,e: #subject?, #grade?, #lesson?, ... + # Sample: #subject?, #grade?, #lesson?, ... HIERARCHY.each do |level| - define_method(:"#{level}?") { curriculum_type.present? && curriculum_type.casecmp(level.to_s).zero? } + define_method(:"#{level}?") do + # @type self: Lcms::Engine::Resource + curriculum_type.present? && curriculum_type.to_s.casecmp(level.to_s).to_i.zero? + end + + # Define dynamic scopes for hierarchy levels. + # I.e., `grades`, `units`, etc. + define_singleton_method(level.to_s.pluralize) do + where(curriculum_type: level) + end end def tree? @@ -158,20 +150,11 @@ def assessment? end def media? - %w(video podcast).include? resource_type + %w(video podcast).include?(resource_type.to_s) end def generic? - %w(text_set quick_reference_guide resource_other).include?(resource_type) - end - - # `Optional prerequisite` - https://github.com/learningtapestry/unbounded/issues/557 - def opr? - tag_list.include?('opr') - end - - def prerequisite? - tag_list.include?('prereq') + %w(text_set quick_reference_guide resource_other).include?(resource_type.to_s) end def directory @@ -193,7 +176,7 @@ def grades=(gds) end def lesson_number - @lesson_number ||= short_title.match(/(\d+)/)&.[](1).to_i + @lesson_number ||= short_title.to_s.match(/(\d+)/)&.[](1).to_i end def related_resources @@ -203,35 +186,11 @@ def related_resources .map(&:related_resource) end - def download_categories - @download_categories ||= - resource_downloads.includes(:download_category).includes(:download) - .sort_by { |rd| rd.download_category&.position.to_i } - .group_by { |d| d.download_category&.title.to_s } - .transform_values { |v| v.sort_by { |d| [d.download.main ? 0 : 1, d.download.title] } } - end - - def pdf_downloads?(category = nil) - if category.present? - resource_downloads.joins(:download) - .where(download_category: category) - .where(downloads: { content_type: 'application/pdf' }) - .exists? - else - downloads.where(content_type: 'application/pdf').exists? - end - end - - alias do_not_skip_indexing? should_index? - def should_index? - do_not_skip_indexing? && (tree? || media? || generic?) - end - def named_tags { - keywords: (tag_list + topic_list).compact.uniq, - resource_type: resource_type, - ell_appropriate: ell_appropriate, + keywords: tag_list.compact.uniq, + resource_type:, + ell_appropriate:, ccss_standards: tag_standards, ccss_domain: nil, # resource.standards.map { |std| std.domain.try(:name) }.uniq ccss_cluster: nil, # resource.standards.map { |std| std.cluster.try(:name) }.uniq @@ -262,11 +221,37 @@ def document? document.present? end + def next + @next ||= + if level_position.nil? + nil + elsif level_position.to_i < siblings.size + siblings.where(level_position: level_position.to_i + 1).first + else + # first element of next node from parent level + parent&.next&.children&.first + end + end + def next_hierarchy_level - index = Lcms::Engine::Resource.hierarchy.index(curriculum_type.to_sym) + index = Lcms::Engine::Resource.hierarchy.index(curriculum_type.to_s.to_sym) Lcms::Engine::Resource.hierarchy[index + 1] end + def parents + ancestors.reverse + end + + def previous + @previous ||= + if level_position.to_i.positive? + siblings.where(level_position: level_position.to_i - 1).first + else + # last element of previous node from parent level + parent&.previous&.children&.last + end + end + def unit_bundles? unit? && document_bundles.any? end @@ -280,7 +265,7 @@ def self_and_ancestors_not_persisted def update_metadata meta = self_and_ancestors_not_persisted - .each_with_object({}) do |r, obj| + .each_with_object({}) do |r, obj| # steep:ignore obj[r.curriculum_type] = r.short_title end.compact metadata.merge! meta if meta.present? @@ -300,7 +285,7 @@ def update_descendants_author # update only if a grade author has changed return unless grade? && author_id_changed? - descendants.update_all author_id: author_id + descendants.update_all author_id: end def update_descendants_meta diff --git a/app/models/lcms/engine/resource_download.rb b/app/models/lcms/engine/resource_download.rb deleted file mode 100644 index c53c998b..00000000 --- a/app/models/lcms/engine/resource_download.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - class ResourceDownload < ApplicationRecord - DOWNLOAD_PER_CATEGORY_LIMIT = 5 - - belongs_to :resource - belongs_to :download - belongs_to :download_category - - validates :download, presence: true - - accepts_nested_attributes_for :download - end - end -end diff --git a/app/models/lcms/engine/search/document.rb b/app/models/lcms/engine/search/document.rb deleted file mode 100644 index c888b336..00000000 --- a/app/models/lcms/engine/search/document.rb +++ /dev/null @@ -1,155 +0,0 @@ -# frozen_string_literal: true - -require 'virtus' - -module Lcms - module Engine - module Search - class Document < ElasticSearchDocument - include Virtus.model - - METADATA_FIELDS = %w(description teaser title lesson_objective).freeze - - attribute :breadcrumbs, String - attribute :description, String - attribute :doc_type, String - attribute :document_metadata, String - attribute :grade, String - attribute :id, String - attribute :model_id, Integer - attribute :model_type, String - attribute :permalink, String - attribute :position, String - attribute :slug, String - attribute :subject, String - attribute :tag_authors, Array[String] - attribute :tag_keywords, Array[String] - attribute :tag_standards, Array[String] - attribute :tag_texts, Array[String] - attribute :teaser, String - attribute :title, String - - class << self - def build_from(model) - case model - when Lcms::Engine::Resource - new(**attrs_from_resource(model)) - - when Lcms::Engine::ExternalPage - new(**attrs_from_page(model)) - - else - raise "Unsupported Type for Search : #{model.class.name}" - end - end - - def doc_type(model) - model.resource_type == 'resource' ? model.curriculum_type : model.resource_type - end - - def document_metadata(model) - return unless model.document? - - METADATA_FIELDS.map do |k| - value = model.document.metadata[k] - Nokogiri::HTML.fragment(value).text.presence - end.compact.join(' ') - end - - # Position mask: - # - Since lessons uses 4 blocks of 2 numbers for (grade, mod, unit, lesson), - # we use 5 blocks to place them after lessons. - # - the first position is realted to the resource type (always starting - # with 9 to be placed after the lessons). - # - The second most significant is related to the grade - # - The last position is the number of different grades covered, i.e: - # a resource with 3 different grades show after one with 2, (more specific - # at the top, more generic at the bottom) - def grade_position(model) - if model.is_a?(Lcms::Engine::Resource) && model.generic? - rtype = model[:resource_type] || 0 - # for generic resource use the min grade, instead the avg - grade_pos = model.grades.list.map { |g| Lcms::Engine::Grades.grades.index(g) }.compact.min || 0 - last_pos = model.grades.list.size - else - rtype = 0 - grade_pos = model.grades.average_number - last_pos = 0 - end - first_pos = 90 + rtype - - [first_pos, grade_pos, 0, 0, last_pos].map { |n| n.to_s.rjust(2, '0') }.join(' ') - end - - def resource_position(model) - if model.media? || model.generic? - grade_position(model) - else - model.hierarchical_position - end - end - - # Overrides ElasticSearchDocument.search to include standards search - def search(term, options = {}) - return repository.empty_response unless repository.index_exists? - return repository.search(repository.all_query(options)) unless term.present? - - repository.multisearch( - [ - repository.standards_query(term, options), - repository.tags_query(term, [:tag_keywords], options), - repository.tags_query(term, %i(tag_authors tag_texts), options), - repository.fts_query(term, options) - ] - ).max_by(&:total) - end - - private - - def attrs_from_page(model) - { - description: model.description, - doc_type: 'page', - grade: [], - id: "page_#{model.slug}", - model_type: :page, - permalink: model.permalink, - slug: model.slug, - tag_keywords: model.keywords, - teaser: model.teaser, - title: model.title - } - end - - def attrs_from_resource(model) - tags = model.named_tags - - { - breadcrumbs: Lcms::Engine::Breadcrumbs.new(model).title, - description: model.description, - doc_type: doc_type(model), - document_metadata: document_metadata(model), - grade: model.grades.list, - id: "resource_#{model.id}", - model_id: model.id, - model_type: 'resource', - position: resource_position(model), - slug: model.slug, - subject: model.subject, - tag_authors: tags[:authors] || [], - tag_keywords: tags[:keywords] || [], - tag_standards: tags[:ccss_standards] || [], - tag_texts: tags[:texts] || [], - teaser: model.teaser, - title: model.title - } - end - end - - def grades - @grades ||= Lcms::Engine::Grades.new(self) - end - end - end - end -end diff --git a/app/models/lcms/engine/search/elastic_search_document.rb b/app/models/lcms/engine/search/elastic_search_document.rb deleted file mode 100644 index 85bfe88a..00000000 --- a/app/models/lcms/engine/search/elastic_search_document.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - module Search - class ElasticSearchDocument - # return the corresponding repository - def self.repository - @repository ||= Lcms::Engine::Search::Repository.new - end - - def repository - self.class.repository - end - - def index! - repository.save self - end - - def delete! - repository.delete self - end - - # Default search - # - # term: [String || Nil] - # - [Nil] : return a match_all query - # - [String] : perform a `fts_query` on the repository - # - # options: [Hash] - # - per_page : number os results per page - # - page : results page number - # - : any doc specific filters, e.g: 'grade', 'subject', etc - # - def self.search(term, options = {}) - return [] unless repository.index_exists? - - query = if term.present? - repository.fts_query(term, options) - else - repository.all_query(options) - end - repository.search query - end - - # this is necessary for the ActiveModel::Serializer::CollectionSerializer#as_json method to work - # (used on the Pagination#serialize_with_pagination) - # NOTE: https://github.com/rails-api/active_model_serializers/issues/891 - def read_attribute_for_serialization(key) - if key.try(:to_sym) == :id - attributes.fetch(key) { id } - else - attributes[key] - end - end - end - end - end -end diff --git a/app/models/lcms/engine/search/repository.rb b/app/models/lcms/engine/search/repository.rb deleted file mode 100644 index 7af8fc73..00000000 --- a/app/models/lcms/engine/search/repository.rb +++ /dev/null @@ -1,223 +0,0 @@ -# frozen_string_literal: true - -require 'elasticsearch/persistence' - -module Lcms - module Engine - module Search - def self.ngrams_multi_field - { - type: 'string', fields: { - full: { type: 'string', analyzer: 'full_str' }, - partial: { type: 'string', analyzer: 'partial_str' }, - key: { type: 'string', index: 'not_analyzed' } - } - } - end - - def self.index_settings - { - analysis: { - filter: { - str_ngrams: { type: 'nGram', min_gram: 3, max_gram: 10 }, - stop_en: { type: 'stop', stopwords: '_english_' } - }, - analyzer: { - keyword_str: { - filter: ['lowercase'], - type: 'custom', - tokenizer: 'keyword' - }, - full_str: { - filter: %w(standard lowercase stop_en asciifolding), - type: 'custom', - tokenizer: 'standard' - }, - partial_str: { - filter: %w(standard lowercase stop_en asciifolding str_ngrams), - type: 'custom', - tokenizer: 'standard' - } - } - } - } - end - - class Repository - include Elasticsearch::Persistence::Repository - include Elasticsearch::Persistence::Repository::DSL - - client Elasticsearch::Client.new(host: ENV.fetch('ELASTICSEARCH_ADDRESS', nil)) - - index_name :"unbounded_documents_#{Rails.env}" - - document_type :document - - klass Lcms::Engine::Search::Document - - settings index: Lcms::Engine::Search.index_settings do - mappings dynamic: 'false' do - indexes :breadcrumbs, type: 'string', index: 'not_analyzed' - indexes :description, **Lcms::Engine::Search.ngrams_multi_field - indexes :doc_type, type: 'string', index: 'not_analyzed' # module | unit | lesson | video | etc - indexes :document_metadata, type: 'string' - indexes :grade, type: 'string', index: 'not_analyzed' - indexes :model_id, type: 'string', index: 'not_analyzed' - indexes :model_type, type: 'string', index: 'not_analyzed' # ActiveRecord model => resource only for now - indexes :position, type: 'string', index: 'not_analyzed' - indexes :subject, type: 'string', index: 'not_analyzed' - indexes :tag_authors, type: 'string' - indexes :tag_keywords, type: 'string' - indexes :tag_standards, type: 'string', analyzer: 'keyword_str' - indexes :tag_texts, **Lcms::Engine::Search.ngrams_multi_field - indexes :teaser, **Lcms::Engine::Search.ngrams_multi_field - indexes :title, **Lcms::Engine::Search.ngrams_multi_field - end - end - - SYNONYMS = { - 'text sets' => 'text set', - 'expert pack' => 'expert packs' - }.freeze - - def all_query(options) - limit = options.fetch(:per_page, 20) - page = options.fetch(:page, 1) - - query = { - query: { - bool: { - must: { match_all: {} }, - filter: [] - } - }, - sort: [ - { subject: 'asc' }, - { position: 'asc' }, - { 'title.key' => 'asc' } - ], - size: limit, - from: (page - 1) * limit - } - - apply_filters(query, options) - end - - def fts_query(term, options) - return term if term.respond_to?(:to_hash) - - limit = options.fetch(:per_page, 20) - page = options.fetch(:page, 1) - term = replace_synonyms term.downcase - - query = { - min_score: 6, - query: { - bool: { - should: [ - { match: { 'title.full' => { query: term, boost: 3, type: 'phrase' } } }, - { match: { 'title.partial' => { query: term, boost: 0.5 } } }, - - { match: { 'teaser.full' => { query: term, boost: 4, type: 'phrase' } } }, - - { match: { document_metadata: { query: term, boost: 1 } } } - ], - filter: [] - } - }, - size: limit, - from: (page - 1) * limit - } - - apply_filters(query, options) - end - - def standards_query(term, options) - return term if term.respond_to?(:to_hash) - - limit = options.fetch(:per_page, 20) - page = options.fetch(:page, 1) - - query = { - query: { - bool: { - filter: [], - should: [ - { term: { tag_standards: term } }, - { match_phrase_prefix: { tag_standards: { query: term } } } - ], - minimum_should_match: 1 - } - }, - size: limit, - from: (page - 1) * limit - } - - apply_filters query, options - end - - def tags_query(term, tags, options) - return term if term.respond_to?(:to_hash) - - limit = options.fetch(:per_page, 20) - page = options.fetch(:page, 1) - - query = { - query: { - bool: { - filter: [], - should: tags.map { |t| { match: { t => term } } }.concat( - [ - { match_phrase: { title: term } }, - { match_phrase: { teaser: term } } - ] - ), - minimum_should_match: 1 - } - }, - size: limit, - from: (page - 1) * limit - } - - apply_filters query, options - end - - def accepted_filters - %i(model_type subject grade doc_type) - end - - def apply_filters(query, options) - accepted_filters.each do |filter| - next unless options[filter] - - filter_term = if options[filter].is_a? Array - { terms: { filter => options[filter] } } - else - { match: { filter => { query: options[filter] } } } - end - query[:query][:bool][:filter] << filter_term - end - query - end - - def empty_response - Elasticsearch::Persistence::Repository::Response::Results.new( - self, - hits: { total: 0, max_score: nil, hits: [] } - ) - end - - def multisearch(queries) - body = queries.map { |query| { search: query } } - client.msearch(index: index, type: type, body: body)['responses'].map do |r| - Elasticsearch::Persistence::Repository::Response::Results.new(self, r) - end - end - - def replace_synonyms(term) - SYNONYMS[term] || term - end - end - end - end -end diff --git a/app/models/lcms/engine/settings.rb b/app/models/lcms/engine/settings.rb index a6341801..51852ef2 100644 --- a/app/models/lcms/engine/settings.rb +++ b/app/models/lcms/engine/settings.rb @@ -9,7 +9,8 @@ def [](key) end def []=(key, value) - settings.update data: settings.data.merge(key.to_s => value) + new_settings = {}.tap { _1[key.to_s] = value } # steep:ignore + settings.update data: settings.data.merge(new_settings) end private diff --git a/app/models/lcms/engine/standard.rb b/app/models/lcms/engine/standard.rb index a7705562..01b8edb1 100644 --- a/app/models/lcms/engine/standard.rb +++ b/app/models/lcms/engine/standard.rb @@ -92,7 +92,7 @@ def self.filter_ccss_standards(name, subject) # NOTE: #954 - to be removed? def short_name - alt_names.map { |n| self.class.filter_ccss_standards(n, subject) }.compact.try(:first) || name + Array.wrap(alt_names).map { |n| self.class.filter_ccss_standards(n, subject) }.compact.try(:first) || name end end end diff --git a/app/models/lcms/engine/tag.rb b/app/models/lcms/engine/tag.rb index 48248193..510e8c0d 100644 --- a/app/models/lcms/engine/tag.rb +++ b/app/models/lcms/engine/tag.rb @@ -5,7 +5,7 @@ module Lcms module Engine class Tag < ActsAsTaggableOn::Tag - scope :where_context, ->(context) { joins(:taggings).where(taggings: { context: context }) } + scope :where_context, ->(context) { joins(:taggings).where(taggings: { context: }) } end end end diff --git a/app/models/lcms/engine/user.rb b/app/models/lcms/engine/user.rb index 0870a001..f33b9dc3 100644 --- a/app/models/lcms/engine/user.rb +++ b/app/models/lcms/engine/user.rb @@ -16,31 +16,16 @@ class User < ApplicationRecord validates_presence_of :email, :role validate :access_code_valid?, on: :create, unless: :admin? - def full_name - [ - survey&.fetch('first_name', nil), - survey&.fetch('last_name', nil) - ].reject(&:blank?).join(' ') - end - def generate_password pwd = Devise.friendly_token.first(20) self.password = pwd self.password_confirmation = pwd end - def name - super.presence || full_name - end - - def ready_to_go? - admin? || survey.present? - end - private def access_code_valid? - return if AccessCode.by_code(access_code).exists? + return false if AccessCode.by_code(access_code.to_s).exists? errors.add :access_code, 'not found' end diff --git a/app/presenters/lcms/engine/base_presenter.rb b/app/presenters/lcms/engine/base_presenter.rb index f5a2a7c1..2a4c0d8d 100644 --- a/app/presenters/lcms/engine/base_presenter.rb +++ b/app/presenters/lcms/engine/base_presenter.rb @@ -14,9 +14,9 @@ def t(key, options = {}) class_name = self.class.to_s.underscore options[:raise] = true if key.starts_with?('.') - I18n.t("#{class_name}.#{key}", options) + I18n.t("#{class_name}.#{key}", **options) else - I18n.t(key, options) + I18n.t(key, **options) end end end diff --git a/app/presenters/lcms/engine/content_presenter.rb b/app/presenters/lcms/engine/content_presenter.rb index 3aa84915..21647109 100644 --- a/app/presenters/lcms/engine/content_presenter.rb +++ b/app/presenters/lcms/engine/content_presenter.rb @@ -10,11 +10,11 @@ class ContentPresenter < Lcms::Engine::BasePresenter THUMB_EXT = '.jpg' def self.base_config - @base_config ||= YAML.load_file(CONFIG_PATH).deep_symbolize_keys + @base_config ||= YAML.load_file(CONFIG_PATH, aliases: true).deep_symbolize_keys end def self.materials_config - @materials_config ||= YAML.load_file(MATERIALS_CONFIG_PATH).deep_symbolize_keys + @materials_config ||= YAML.load_file(MATERIALS_CONFIG_PATH, aliases: true).deep_symbolize_keys end def base_filename diff --git a/app/presenters/lcms/engine/curriculum_presenter.rb b/app/presenters/lcms/engine/curriculum_presenter.rb index 8d5fbcda..4811145f 100644 --- a/app/presenters/lcms/engine/curriculum_presenter.rb +++ b/app/presenters/lcms/engine/curriculum_presenter.rb @@ -31,7 +31,7 @@ def element_text(resource) case resource.curriculum_type when 'subject' resource.title - when 'unit' + when 'module', 'unit' resource.short_title&.upcase.presence || 'N/A' when 'grade' resource.short_title&.capitalize.presence || 'N/A' diff --git a/app/presenters/lcms/engine/document_presenter.rb b/app/presenters/lcms/engine/document_presenter.rb index fb3ba9be..e9b433aa 100644 --- a/app/presenters/lcms/engine/document_presenter.rb +++ b/app/presenters/lcms/engine/document_presenter.rb @@ -12,11 +12,7 @@ class DocumentPresenter < Lcms::Engine::ContentPresenter TOPIC_SHORT = { 'ela' => 'U', 'math' => 'T' }.freeze def cc_attribution - core_cc = ld_metadata.cc_attribution - return core_cc if (fs_cc = fs_metadata.cc_attribution).blank? - return core_cc if core_cc.casecmp(fs_cc).zero? - - "#{core_cc} #{fs_cc}" + ld_metadata.cc_attribution end def color_code @@ -40,19 +36,7 @@ def description end def doc_type - assessment? ? 'assessment' : 'lesson' - end - - def ela2? - ela? && grade.to_s == '2' - end - - def ela6? - ela? && grade.to_s == '6' - end - - def fs_metadata - @fs_metadata ||= DocTemplate::Objects::BaseMetadata.build_from(foundational_metadata) + 'lesson' end def full_breadcrumb(unit_level: false) @@ -60,13 +44,13 @@ def full_breadcrumb(unit_level: false) end def full_breadcrumb_from_metadata(unit_level) - lesson_level = assessment? ? 'Assessment' : "Lesson #{lesson}" unless unit_level + lesson_level = "Lesson #{lesson}" unless unit_level [ SUBJECT_FULL[subject] || subject, grade.to_i.zero? ? grade : "Grade #{grade}", ll_strand? ? ld_module : "Module #{ld_module.try(:upcase)}", topic.present? ? "#{TOPIC_FULL[subject]} #{topic.try(:upcase)}" : nil, - lesson_level + lesson_level.to_s ].compact.join(' / ') end @@ -133,16 +117,9 @@ def render_content(context_type, options = {}) Lcms::Engine::ReactMaterialsResolver.resolve(content, self) end - # rubocop:disable Metrics/PerceivedComplexity def short_breadcrumb(join_with: ' / ', with_short_lesson: false, with_subject: true, unit_level: false) - unless unit_level - lesson_abbr = - if assessment? - with_short_lesson ? 'A' : 'Assessment' - else - with_short_lesson ? "L#{lesson}" : "Lesson #{lesson}" - end - end + lesson_abbr = with_short_lesson ? "L#{lesson}" : "Lesson #{lesson}" \ + unless unit_level [ with_subject ? SUBJECT_FULL[subject] || subject : nil, grade.to_i.zero? ? grade : "G#{grade}", @@ -151,16 +128,9 @@ def short_breadcrumb(join_with: ' / ', with_short_lesson: false, with_subject: t lesson_abbr ].compact.join(join_with) end - # rubocop:enable Metrics/PerceivedComplexity def short_title - assessment? ? doc_type : "Lesson #{lesson}" - end - - def short_url - @short_url ||= Bitly.client - .shorten(document_url(self)) - .short_url + "Lesson #{lesson}" end def standards @@ -184,8 +154,7 @@ def subject_to_str end def title - title = ld_metadata&.title - resource&.prerequisite? ? "Prerequisite - #{title}" : title + ld_metadata&.title end def teacher_materials diff --git a/app/presenters/lcms/engine/generic_presenter.rb b/app/presenters/lcms/engine/generic_presenter.rb deleted file mode 100644 index e9f68f1d..00000000 --- a/app/presenters/lcms/engine/generic_presenter.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - class GenericPresenter < Lcms::Engine::ResourcePresenter - def generic_title - "#{subject.try(:upcase)} #{grades.to_str}" - end - - def type_name - I18n.t("resource_types.#{resource_type}") - end - - def preview? - downloads.any? { |d| d.main? && d.attachment_content_type == 'pdf' && RestClient.head(d.attachment_url) } - rescue RestClient::ExceptionWithResponse - false - end - - def pdf_preview_download - resource_downloads.find { |d| d.download.main? && d.download.attachment_content_type == 'pdf' } - end - end - end -end diff --git a/app/presenters/lcms/engine/material_presenter.rb b/app/presenters/lcms/engine/material_presenter.rb index e61eb953..646fba44 100644 --- a/app/presenters/lcms/engine/material_presenter.rb +++ b/app/presenters/lcms/engine/material_presenter.rb @@ -5,7 +5,7 @@ module Engine class MaterialPresenter < Lcms::Engine::ContentPresenter attr_accessor :document - delegate :short_url, :subject, to: :document + delegate :subject, to: :document DEFAULT_TITLE = 'Material' @@ -25,8 +25,8 @@ def cc_attribution metadata['cc_attribution'].presence || document&.cc_attribution end - def content_for(context_type) - render_content(context_type) + def content_for(context_type, options = {}) + render_content(context_type, options) end def content_type @@ -107,7 +107,7 @@ def show_title? end def student_material? - ::Material.where(id: id).gdoc.where_metadata_any_of(materials_config_for(:student)).exists? + ::Material.where(id:).gdoc.where_metadata_any_of(materials_config_for(:student)).exists? end def subtitle @@ -115,7 +115,7 @@ def subtitle end def teacher_material? - ::Material.where(id: id).gdoc.where_metadata_any_of(materials_config_for(:teacher)).exists? + ::Material.where(id:).gdoc.where_metadata_any_of(materials_config_for(:teacher)).exists? end def title diff --git a/app/presenters/lcms/engine/resource_presenter.rb b/app/presenters/lcms/engine/resource_presenter.rb index 11c3ff9c..a4013e36 100644 --- a/app/presenters/lcms/engine/resource_presenter.rb +++ b/app/presenters/lcms/engine/resource_presenter.rb @@ -12,32 +12,6 @@ def page_title grade_code = grade_avg.include?('k') ? grade_avg : "G#{grade_avg}" "#{subject.try(:upcase)} #{grade_code.try(:upcase)}: #{title}" end - - def downloads_indent(opts = {}) - pdf_downloads?(opts[:category]) ? 'u-li-indent' : '' - end - - def categorized_downloads_list - @categorized_downloads_list ||= begin - downloads_list = Lcms::Engine::DownloadCategory.all.map do |dc| - downloads = Array.wrap(download_categories[dc.title]) - downloads.concat(document_bundles) if dc.bundle? - settings = download_categories_settings[dc.title.parameterize] || {} - - next unless settings.values.any? || downloads.any? - - data = { category: dc, title: dc.title, downloads: downloads, settings: settings } - Struct.new(*data.keys, keyword_init: true).new(data) - end - - if (uncategorized = download_categories['']).present? - data = { downloads: uncategorized, settings: {} } - downloads_list << Struct.new(*data.keys, keyword_init: true).new(data) - end - - downloads_list.compact - end - end end end end diff --git a/app/queries/lcms/engine/admin_documents_query.rb b/app/queries/lcms/engine/admin_documents_query.rb index c538f789..e337ce02 100644 --- a/app/queries/lcms/engine/admin_documents_query.rb +++ b/app/queries/lcms/engine/admin_documents_query.rb @@ -9,7 +9,7 @@ class AdminDocumentsQuery < BaseQuery # Returns: ActiveRecord relation def call @scope = Document.all # initial scope - apply_filters + @scope = apply_filters if @pagination.present? sorted_scope.paginate(page: @pagination[:page]) @@ -20,16 +20,18 @@ def call private - def apply_filters # rubocop:disable Metrics/AbcSize - @scope = @scope.actives unless q.inactive == '1' - @scope = @scope.failed if q.only_failed == '1' - @scope = @scope.filter_by_term(q.search_term) if q.search_term.present? + def apply_filters # rubocop:todo Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + @scope = q.respond_to?(:inactive) && q.inactive == '1' ? @scope.unscoped : @scope.actives + @scope = @scope.failed if q.respond_to?(:only_failed) && q.only_failed == '1' + @scope = @scope.filter_by_term(q.search_term) if q.respond_to?(:search_term) && q.search_term.present? @scope = @scope.filter_by_subject(q.subject) if q.subject.present? - @scope = @scope.filter_by_grade(q.grade) if q.grade.present? + @scope = @scope.filter_by_grade(q.grade) if q.respond_to?(:grade) && q.grade.present? + @scope = @scope.where_grade(q.grades&.compact) \ + if q.respond_to?(:grades) && Array.wrap(q.grades).reject(&:blank?).present? @scope = @scope.filter_by_module(q.module) if q.module.present? @scope = @scope.filter_by_unit(q.unit) if q.unit.present? - @scope = @scope.with_broken_materials if q.broken_materials == '1' - @scope = @scope.with_updated_materials if q.reimport_required == '1' + @scope = @scope.with_broken_materials if q.respond_to?(:broken_materials) && q.broken_materials == '1' + @scope = @scope.with_updated_materials if q.respond_to?(:reimport_required) && q.reimport_required == '1' @scope end diff --git a/app/queries/lcms/engine/admin_materials_query.rb b/app/queries/lcms/engine/admin_materials_query.rb index 6fc2a760..d3a1218f 100644 --- a/app/queries/lcms/engine/admin_materials_query.rb +++ b/app/queries/lcms/engine/admin_materials_query.rb @@ -5,7 +5,7 @@ module Engine # Usage: # @materials = AdminMaterialsQuery.call(query_params, page: params[:page]) class AdminMaterialsQuery < BaseQuery - STRICT_METADATA = %w(grade subject).freeze + STRICT_METADATA = %w(subject).freeze # Returns: ActiveRecord relation def call @@ -26,10 +26,15 @@ def call def filter_by_metadata metadata_keys.each do |key| + next unless q.respond_to?(key) next unless q[key].present? @scope = - if STRICT_METADATA.include?(key.to_s) + if key.to_s == 'grades' + grades = Array.wrap(q.grades).reject(&:blank?) + values = grades.map { _1[/\d+/].nil? ? _1 : _1[/\d+/] } + values.any? ? @scope = @scope.where_grade(values) : @scope + elsif STRICT_METADATA.include?(key.to_s) @scope.where_metadata(key => q[key].to_s.downcase) else @scope.where_metadata_like(key, q[key]) @@ -38,22 +43,32 @@ def filter_by_metadata end def metadata_keys - DocTemplate.config.dig('metadata', 'service').constantize.materials_metadata.attribute_set.map(&:name) + default_keys = DocTemplate.config.dig('metadata', 'service') + .constantize.materials_metadata.attribute_set.map(&:name) + # From search form comes `grades` field which can contain multiple values + default_keys.delete('grade') + default_keys.push('grades') end def search_by_identifier + return unless q.respond_to?(:search_term) && q.search_term.present? + # we need the `with_pg_search_rank` scope for this to work with DISTINCT # See more on: https://github.com/Casecommons/pg_search/issues/238 - @scope = @scope.search_identifier(q.search_term).with_pg_search_rank if q.search_term.present? + @scope = @scope.search_identifier(q.search_term).with_pg_search_rank end def search_by_file_name - @scope = @scope.search_name(q.search_file_name).with_pg_search_rank if q.search_file_name.present? + return unless q.respond_to?(:search_file_name) && q.search_file_name.present? + + ActiveSupport::Deprecation + .warn('Lcms::Engine::Material.search_name has been removed. Refactor your calls accordingly.') + # @scope = @scope.search_name(q.search_file_name).with_pg_search_rank end def sorted_scope - @scope = @scope.reorder(:identifier) if q.sort_by.blank? || q.sort_by == 'identifier' - @scope = @scope.reorder(updated_at: :desc) if q.sort_by == 'last_update' + @scope = @scope.reorder('identifier') if q.sort_by.blank? || q.sort_by == 'identifier' + @scope = @scope.reorder('updated_at DESC') if q.sort_by == 'last_update' @scope.distinct end end diff --git a/app/queries/lcms/engine/base_query.rb b/app/queries/lcms/engine/base_query.rb index f2127f50..d1d1cb49 100644 --- a/app/queries/lcms/engine/base_query.rb +++ b/app/queries/lcms/engine/base_query.rb @@ -10,7 +10,8 @@ def self.call(query, pagination = nil) # query : query params (Hash or OpenStruct) # pagination : pagination params, if pagination is nil whe return all results def initialize(query, pagination = nil) - @q = OpenStruct.new(query) # rubocop:disable Style/OpenStructUse + # query is a type of `Struct` with pre-defined and pre-populated fields + @q = query @pagination = pagination end diff --git a/app/serializers/lcms/engine/curriculum_resource_serializer.rb b/app/serializers/lcms/engine/curriculum_resource_serializer.rb deleted file mode 100644 index 1eeae783..00000000 --- a/app/serializers/lcms/engine/curriculum_resource_serializer.rb +++ /dev/null @@ -1,72 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - class CurriculumResourceSerializer < ActiveModel::Serializer - attributes :children, :id, :lesson_count, :module_count, :module_sizes, - :resource, :type, :unit_count, :unit_sizes - - def initialize(object, options = {}) - super(object, options) - @depth = options[:depth] || 0 - @depth_branch = options[:depth_branch] - end - - def resource - ResourceDetailsSerializer.new(object).as_json - end - - def type - object.curriculum_type - end - - def children - return [] if !module? && @depth.zero? - return [] if !module? && @depth_branch && !@depth_branch.include?(object.id) - - object.children - .includes(:copyright_attributions) - .eager_load(:standards) - .ordered.map do |res| - CurriculumResourceSerializer.new( - res, - depth: @depth - 1, - depth_branch: @depth_branch - ).as_json - end - end - - def lesson_count - count = descendants.select(&:lesson?).count - object.assessment? ? count + 1 : count - end - - def unit_count - descendants.select(&:unit?).count - end - - def module? - type.casecmp('module').zero? - end - - def module_count - descendants.select(&:module?).count - end - - def module_sizes - descendants.select(&:module?).map { |r| r.self_and_descendants.lessons.count } - end - - def unit_sizes - descendants.select(&:unit?).map do |r| - count = r.self_and_descendants.lessons.count - r.assessment? ? count + 1 : count - end - end - - def descendants - @descendants ||= object.self_and_descendants.ordered - end - end - end -end diff --git a/app/serializers/lcms/engine/resource_details_serializer.rb b/app/serializers/lcms/engine/resource_details_serializer.rb deleted file mode 100644 index f173f00d..00000000 --- a/app/serializers/lcms/engine/resource_details_serializer.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - # This is a superset of ResourceSerializer, meant to be used were we need - # associations and other info. Currently is used on ExploreCurriculums - # (together with the CurriculumResourceSerializer) - class ResourceDetailsSerializer < ResourceSerializer - include ResourceHelper - - attributes :breadcrumb_title, :copyright, :downloads, :grade, :has_related, :id, - :opr_description, :opr_standards, :path, :short_title, :subject, :teaser, :time_to_teach, - :title, :type - - def downloads - serialize_download = lambda do |download| - next unless download.is_a?(ResourceDownload) - - indent = object.pdf_downloads? download.download_category - { - id: download.id, - icon: h.file_icon(download.download.attachment_content_type), - title: download.download.title, - url: download_path(download, slug: object.slug), - preview_url: preview_download_path(id: download, slug: object.slug), - indent: indent - } - end - object.download_categories.map { |k, v| [k, v.map(&serialize_download)] } - end - - def copyright - copyrights_text(object) - end - - def has_related # rubocop:disable Naming/PredicateName - false - end - - def opr_standards - return unless object.opr? && object.document.present? - - DocumentGenerator.document_presenter.new(object.document).standards - end - - private - - def h - ApplicationController.helpers - end - end - end -end diff --git a/app/serializers/lcms/engine/resource_instruction_serializer.rb b/app/serializers/lcms/engine/resource_instruction_serializer.rb deleted file mode 100644 index 44ff3e0f..00000000 --- a/app/serializers/lcms/engine/resource_instruction_serializer.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - class ResourceInstructionSerializer < ActiveModel::Serializer - include ResourceHelper - - attributes :id, :title, :subject, :teaser, :path, :img, :instruction_type, - :grade_avg, :time_to_teach - - def title - return object.title if media? - - type_name = I18n.t("resource_types.#{object.resource_type}") - object.grades.present? ? "#{object.grades.to_str} #{type_name}" : type_name - end - - def subject - object.subject.try(:downcase) || 'default' - end - - def teaser - object.title - end - - def path - media? ? media_path(object.id) : generic_path(object) - end - - def img - object.try(:image_file).try(:url) || placeholder - end - - def instruction_type - media? ? object.resource_type : :generic - end - - def grade_avg - object.grades.average - end - - private - - def media? - object.media? - end - - def placeholder - ActionController::Base.helpers.image_path('resource_placeholder.jpg') - end - end - end -end diff --git a/app/serializers/lcms/engine/resource_picker_serializer.rb b/app/serializers/lcms/engine/resource_picker_serializer.rb deleted file mode 100644 index c11c6724..00000000 --- a/app/serializers/lcms/engine/resource_picker_serializer.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - class ResourcePickerSerializer < ActiveModel::Serializer - attributes :id, :title - end - end -end diff --git a/app/serializers/lcms/engine/resource_serializer.rb b/app/serializers/lcms/engine/resource_serializer.rb index b60116ea..5b4bce47 100644 --- a/app/serializers/lcms/engine/resource_serializer.rb +++ b/app/serializers/lcms/engine/resource_serializer.rb @@ -2,14 +2,10 @@ module Lcms module Engine - # This is a subset of the previous ResourceSerializer, meant to be used on listings - # like find_lessons and search cards. We use this instead the full version (ResourceDetailsSerializer) - # to avoid expensive queries on data we don't need (like downloads, and related) class ResourceSerializer < ActiveModel::Serializer include ResourceHelper - attributes :breadcrumb_title, :grade, :id, :is_assessment, :is_foundational, :is_opr, :is_prerequisite, :path, - :short_title, :subject, :teaser, :time_to_teach, :title, :type + attributes :breadcrumb_title, :grade, :id, :path, :short_title, :subject, :teaser, :time_to_teach, :title, :type def breadcrumb_title Breadcrumbs.new(object).title @@ -19,22 +15,6 @@ def grade object.grades.average end - def is_assessment # rubocop:disable Naming/PredicateName - object&.assessment? || short_title&.index('assessment').present? - end - - def is_foundational # rubocop:disable Naming/PredicateName - object.document&.foundational? - end - - def is_opr # rubocop:disable Naming/PredicateName - object.opr? - end - - def is_prerequisite # rubocop:disable Naming/PredicateName - object.prerequisite? - end - def path return document_path(object.document) if object.document? && !object.assessment? diff --git a/app/services/lcms/engine/bulk_edit_resources_service.rb b/app/services/lcms/engine/bulk_edit_resources_service.rb index 1898000a..c028a552 100644 --- a/app/services/lcms/engine/bulk_edit_resources_service.rb +++ b/app/services/lcms/engine/bulk_edit_resources_service.rb @@ -27,7 +27,7 @@ def edit! .destroy_all (after.standard_ids - before.standard_ids).each do |standard_id| - resource.resource_standards.find_or_create_by!(standard_id: standard_id) + resource.resource_standards.find_or_create_by!(standard_id:) end resource.metadata['grade'] = after.metadata['grade'] diff --git a/app/services/lcms/engine/document_build_service.rb b/app/services/lcms/engine/document_build_service.rb index aa9b5610..11bf0bee 100644 --- a/app/services/lcms/engine/document_build_service.rb +++ b/app/services/lcms/engine/document_build_service.rb @@ -16,24 +16,23 @@ def initialize(credentials, opts = {}) end # - # @return Document ActiveRecord::Model + # @param [String] url + # @return [Lcms::Engine::Document] # - def build_for(url, expand: false) + def build_for(url) @content = download url - @expand_document = expand @template = DocTemplate::Template.parse @content - @errors = @template.metadata_service.errors + @errors = @template.metadata_service.errors + @template.documents.values.flat_map(&:errors) - create_document - clear_preview_link - - content_key = foundational? ? :foundational_content : :original_content - @document.update! content_key => content + @document = create_document + @document.update!(original_content: content) + clear_preview_link build + @document.create_parts_for(template) - ActiveSupport::Notifications.instrument EVENT_BUILT, id: document.id + ActiveSupport::Notifications.instrument(EVENT_BUILT, id: document.id) document.activate! document @@ -41,39 +40,20 @@ def build_for(url, expand: false) private - attr_reader :credentials, :content, :document, :downloader, :expand_document, :options, :template + attr_reader :credentials, :content, :document, :downloader, :options, :template # # Building the document. Handles workflow: # Core-first FS-second and FS-first Core-second. # def build - if expand_document - combine_layout - combine_activity_metadata - document.update! build_params - else - document.document_parts.delete_all - document.update! document_params.merge(toc: template.toc, material_ids: template.toc.collect_material_ids) - end + document.document_parts.delete_all + document.update! document_params.merge(toc: template.toc, material_ids: template.toc.collect_material_ids) end def build_params - params = - if foundational? - { - foundational_metadata: template.foundational_metadata, - fs_name: downloader.file.name - } - else - document_params.merge( - foundational_metadata: document.foundational_metadata, - fs_name: document.name, - name: downloader.file.name - ) - end - - params[:toc] = combine_toc + params = document_params.merge(fs_name: document.name, name: downloader.file.name) + params[:toc] = document.toc params[:material_ids] = params[:toc].collect_material_ids params end @@ -84,66 +64,21 @@ def clear_preview_link document.links = links end - def combine_activity_metadata - old_data = document.activity_metadata - new_data = - if foundational? - old_data.concat template.activity_metadata - old_data - else - template.activity_metadata.concat old_data - template.activity_metadata - end - document.activity_metadata = new_data - end - - def combine_layout - DocumentPart.context_types.each_key do |context_type| - existing_layout = document.layout(context_type) - new_layout = template.remove_part :layout, context_type - new_layout_content = - if foundational? - "#{existing_layout.content}#{new_layout[:content]}" - else - "#{new_layout[:content]}#{existing_layout.content}" - end - existing_layout.update content: new_layout_content - end - end - - def combine_toc - modifier = foundational? ? :append : :prepend - toc = document.toc - toc.send modifier, template.toc - toc - end - + # # Initiate or update existing document: - # - fills in original or fs contents - # - stores specific file_id for each type of a lesson + # - fills in original contents + # - stores specific file_id + # def create_document - # rubocop:disable Lint/AmbiguousOperatorPrecedence - if template.metadata['subject'].presence && - template.metadata['subject'].casecmp('ela').zero? || template.prereq? - @document = Document.actives.find_or_initialize_by(file_id: downloader.file_id) - else - @document = foundational? ? find_core_document : find_fs_document - id_field = foundational? ? :foundational_file_id : :file_id - - @expand_document ||= @document.present? - - @document[id_field] = downloader.file_id if @document.present? - @document ||= Document.actives.find_or_initialize_by(id_field => downloader.file_id) - end - # rubocop:enable Lint/AmbiguousOperatorPrecedence + doc = find_resource + doc[:file_id] = downloader.file_id if doc.present? + doc || Document.actives.find_or_initialize_by(file_id: downloader.file_id) end def document_params { activity_metadata: template.metadata_service.try(:activity_metadata), - agenda_metadata: template.metadata_service.try(:agenda), css_styles: template.css_styles, - foundational_metadata: template.metadata_service.try(:foundational_metadata), name: downloader.file.name, last_modified_at: downloader.file.modified_time, last_author_email: downloader.file.last_modifying_user.try(:email_address), @@ -160,35 +95,11 @@ def download(url) @downloader.content end - # - # If there is existing lesson with Core-type - return it. Nil otherwise. - # - def find_core_document - return unless (core_doc = find_resource) - return if core_doc.foundational? - - core_doc - end - - # - # If there is existing lesson with FS-type - return it. Nil otherwise. - # - def find_fs_document - return unless (fs_doc = find_resource) - return unless fs_doc.foundational? - - fs_doc - end - def find_resource context = DocTemplate.config.dig('metadata', 'context').constantize dir = context.new(template.metadata.with_indifferent_access).directory Resource.find_by_directory(dir)&.document end - - def foundational? - !!template.metadata_service.try(:foundational?) - end end end end diff --git a/app/services/lcms/engine/embed_equations.rb b/app/services/lcms/engine/embed_equations.rb index d1573356..695bfe81 100644 --- a/app/services/lcms/engine/embed_equations.rb +++ b/app/services/lcms/engine/embed_equations.rb @@ -4,7 +4,7 @@ module Lcms module Engine class EmbedEquations REDIS_KEY = 'ub-equation:' - REDIS_KEY_SVG = "#{REDIS_KEY}svg:" + REDIS_KEY_SVG = "#{REDIS_KEY}svg:".freeze class << self def call(content) diff --git a/app/services/lcms/engine/google/drive_service.rb b/app/services/lcms/engine/google/drive_service.rb index bca75a6c..df500bb7 100644 --- a/app/services/lcms/engine/google/drive_service.rb +++ b/app/services/lcms/engine/google/drive_service.rb @@ -14,16 +14,20 @@ def self.build(document, options = {}) new document, options end + def self.escape_double_quotes(file_name) + file_name.to_s.gsub('"', '\"') + end + def copy(file_ids, folder_id = parent) - super file_ids, folder_id + super end def create_folder(folder_name, parent_id = FOLDER_ID) - super folder_name, parent_id + super end def initialize(document, options) - super google_credentials + super(google_credentials) @document = document @options = options end @@ -32,8 +36,12 @@ def file_id @file_id ||= begin folder = @options[:folder_id] || parent file_name = document.base_filename + # Escape double quotes + escaped_name = self.class.escape_double_quotes(file_name) + response = service.list_files( - q: %("#{folder}" in parents and name = "#{file_name}" and mimeType = "#{MIME_FILE}" and trashed = false), + q: %("#{folder}" in parents and name = "#{escaped_name}" and mimeType = "#{MIME_FILE}" \ + and trashed = false), fields: 'files(id)' ) files = Array.wrap(response&.files) diff --git a/app/services/lcms/engine/google/script_service.rb b/app/services/lcms/engine/google/script_service.rb index 10c388b6..4f7c3e2c 100644 --- a/app/services/lcms/engine/google/script_service.rb +++ b/app/services/lcms/engine/google/script_service.rb @@ -48,7 +48,7 @@ def gdoc_template_id end def parameters - [document.cc_attribution, document.full_breadcrumb, document.short_url] + [document.cc_attribution, document.full_breadcrumb, document_url(document)] end def service diff --git a/app/services/lcms/engine/html_sanitizer.rb b/app/services/lcms/engine/html_sanitizer.rb index 62c50bc7..03af5ed9 100644 --- a/app/services/lcms/engine/html_sanitizer.rb +++ b/app/services/lcms/engine/html_sanitizer.rb @@ -5,10 +5,10 @@ module Lcms module Engine class HtmlSanitizer # rubocop:disable Metrics/ClassLength - LIST_STYLE_RE = /\.lst-(\S+)[^{}]+>\s*(?:li:before)\s*{\s*content[^{}]+counter\(lst-ctn-\1,([^)]+)\)/.freeze + LIST_STYLE_RE = /\.lst-(\S+)[^{}]+>\s*(?:li:before)\s*{\s*content[^{}]+counter\(lst-ctn-\1,([^)]+)\)/ CLEAN_ELEMENTS = %w(a div h1 h2 h3 h4 h5 h6 p table).join(',') GDOC_REMOVE_EMPTY_SELECTOR = '.o-ld-activity' - LINK_UNDERLINE_REGEX = /text-decoration\s*:\s*underline/i.freeze + LINK_UNDERLINE_REGEX = /text-decoration\s*:\s*underline/i SKIP_P_CHECK = %w(ul ol table).freeze STRIP_ELEMENTS = %w(a div h1 h2 h3 h4 h5 h6 p span table).freeze @@ -35,8 +35,10 @@ def sanitize_css(css) # Removes all empty nodes before first one filled in # def strip_content(nodes) - nodes.xpath('./*').each do |node| - break if keep_node?(node) + # we need to remove empty nodes unless we meet some node we need to keep + # or text (tag placeholder) + nodes.xpath('./node()').each do |node| + break if node.text? || keep_node?(node) node.remove end @@ -169,7 +171,7 @@ def post_clean_styles(nodes) ].freeze private_constant :FONT_STYLES_RE - BORDER_RE = /border-\w+-width:\s*0\w+;?/.freeze + BORDER_RE = /border-\w+-width:\s*0\w+;?/ private_constant :BORDER_RE BORDER_REPLACE_RE = { @@ -186,7 +188,7 @@ def post_clean_styles(nodes) ].freeze private_constant :SUB_SUP_RE - SUB_SUP_STYLE_RE = /vertical-align:\s*(sub|super);?/i.freeze + SUB_SUP_STYLE_RE = /vertical-align:\s*(sub|super);?/i private_constant :SUB_SUP_STYLE_RE def add_css_class(el, *classes) @@ -234,7 +236,7 @@ def fix_external_target(node) def fix_inline_img(node) # TODO: test if it's working fine with all inline images - node['src'] = node['src'].gsub!(/%(20|0A)/, '') if node['src'].to_s.start_with?('data:') + node['src'] = node['src'].gsub(/%(20|0A)/, '') if node['src'].to_s.start_with?('data:') end def fix_googlechart_img(node) @@ -332,7 +334,7 @@ def post_processing_images_gdoc(nodes) css = ':not(.u-ld-not-image-wrap) > img:not([src*=googleapis]):not(.o-ld-icon):not(.o-ld-latex)' nodes.css(css).each do |img| - img = img.parent.replace(img) if img.parent.name == 'span' || img.parent.name == 'p' + img = img.parent.replace(img) if %w(span p).include?(img.parent.name) img.replace(%( @@ -380,7 +382,7 @@ def remove_spans_wo_attrs(env) def remove_empty_paragraphs(env) node = env[:node] - node.unlink if node.element? && (node.name == 'p' || node.name == 'span') && node.inner_html.squish.blank? + node.unlink if node.element? && %w(p span).include?(node.name) && node.inner_html.squish.blank? end # replace inline borders style with width = 0 as they're not processing correct for pdf diff --git a/app/services/lcms/engine/import_service.rb b/app/services/lcms/engine/import_service.rb new file mode 100644 index 00000000..7bb93168 --- /dev/null +++ b/app/services/lcms/engine/import_service.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Lcms + module Engine + class ImportService + # + # @param [String] url + # @return [String] + # + def self.call(url) + google_credentials = Lt::Google::Api::Auth::Cli.new.credentials + downloader = Lt::Lcms::Lesson::Downloader::Base.new google_credentials, url + downloader.download(mime_type: 'text/csv').content + end + end + end +end diff --git a/app/services/lcms/engine/material_build_service.rb b/app/services/lcms/engine/material_build_service.rb index a48743f9..30ff9bb0 100644 --- a/app/services/lcms/engine/material_build_service.rb +++ b/app/services/lcms/engine/material_build_service.rb @@ -7,7 +7,7 @@ module Lcms module Engine class MaterialBuildService EVENT_BUILT = 'material:built' - PDF_EXT_RE = /\.pdf$/.freeze + PDF_EXT_RE = /\.pdf$/ attr_reader :errors @@ -34,11 +34,11 @@ def build_from_pdf # rubocop:disable Metrics/AbcSize title = @downloader.file.name.sub(PDF_EXT_RE, '') identifier = "#{title.downcase}#{ContentPresenter::PDF_EXT}" - metadata = DocTemplate::Objects::MaterialMetadata.build_from_pdf(identifier: identifier, title: title).as_json + metadata = DocTemplate::Objects::MaterialMetadata.build_from_pdf(identifier:, title:).as_json material.update!( material_params.merge( - identifier: identifier, - metadata: metadata + identifier:, + metadata: ) ) @@ -63,7 +63,7 @@ def build_from_gdoc create_material content = @downloader.download.content template = DocTemplate::Template.parse(content, type: :material) - @errors = template.metadata_service.errors + @errors = template.metadata_service.errors + template.documents.values.flat_map(&:errors) metadata = template.metadata_service.options_for(:default)[:metadata] material.update!( diff --git a/app/services/lcms/engine/material_preview_generator.rb b/app/services/lcms/engine/material_preview_generator.rb index 345fa1c8..f3984e9e 100644 --- a/app/services/lcms/engine/material_preview_generator.rb +++ b/app/services/lcms/engine/material_preview_generator.rb @@ -6,8 +6,8 @@ module Engine # Generates and uploads PDF/GDoc files for material # class MaterialPreviewGenerator - GDOC_RE = %r{docs.google.com/document/d/([^/]*)}i.freeze - GDOC_BROKEN_RE = %r{/open\?id=$}i.freeze + GDOC_RE = %r{docs.google.com/document/d/([^/]*)}i + GDOC_BROKEN_RE = %r{/open\?id=$}i PDF_S3_FOLDER = 'temp-materials-pdf' attr_reader :error, :url @@ -30,7 +30,7 @@ def perform attr_reader :material, :options - def assign_document + def assign_document # rubocop:disable Naming/PredicateMethod document = material.documents.last || Document.last unless document.present? @error = "Can't generate PDF for preview: no documents exist" @@ -45,14 +45,14 @@ def generate_gdoc folder_id = options[:folder_id] file_id = material.preview_links['gdoc'].to_s.match(GDOC_RE)&.[](1) @url = DocumentExporter::Gdoc::Material.new(material) - .export_to(folder_id, file_id: file_id) + .export_to(folder_id, file_id:) .url return true if @url !~ GDOC_BROKEN_RE raise 'GDoc generation failed. Please try again later' end - def generate_pdf + def generate_pdf # rubocop:disable Naming/PredicateMethod pdf_filename = "#{PDF_S3_FOLDER}/#{material.id}/#{material.base_filename}#{ContentPresenter::PDF_EXT}" pdf = DocumentExporter::Pdf::Material.new(material).export @url = S3Service.upload pdf_filename, pdf diff --git a/app/services/lcms/engine/react_materials_resolver.rb b/app/services/lcms/engine/react_materials_resolver.rb index 7724a644..c6b114e5 100644 --- a/app/services/lcms/engine/react_materials_resolver.rb +++ b/app/services/lcms/engine/react_materials_resolver.rb @@ -23,7 +23,7 @@ def replace_react(node, document) # rubocop:disable Metrics/PerceivedComplexity else JSON.parse(data) end - node.remove && return if (raw_props['material_ids']).empty? + node.remove && return if raw_props['material_ids'].empty? props = PreviewsMaterialSerializer.new(raw_props, document) node.remove && return if props.data && props.data.empty? diff --git a/app/services/lcms/engine/related_instructions_service.rb b/app/services/lcms/engine/related_instructions_service.rb deleted file mode 100644 index 4d13c597..00000000 --- a/app/services/lcms/engine/related_instructions_service.rb +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - class RelatedInstructionsService - attr_reader :resource, :expanded, :has_more, :instructions - - def initialize(resource, expanded) - @resource = resource - @expanded = expanded - - find_related_instructions - end - - private - - def find_related_instructions - instructions = expanded ? expanded_instructions : colapsed_instructions - - @has_more = true if videos.size > instructions.size - @instructions = instructions.map do |inst| - ResourceInstructionSerializer.new(inst) - end - end - - def expanded_instructions - videos - end - - def colapsed_instructions - # show 4 videos - videos[0...4] - end - - def videos - @videos ||= find_related_through_standards(limit: 4) do |standard| - standard.resources.media.distinct - end - end - - def find_related_through_standards(limit:, &_block) - related = resource.standards.flat_map do |standard| - qset = yield standard - qset = qset.limit(limit) unless expanded # limit each part - qset - end.uniq - - if expanded - related - else - @has_more = true if related.count > limit - related[0...limit] # limit total - end - end - end - end -end diff --git a/app/services/lcms/engine/s3_service.rb b/app/services/lcms/engine/s3_service.rb index 979a428e..7cfe1165 100644 --- a/app/services/lcms/engine/s3_service.rb +++ b/app/services/lcms/engine/s3_service.rb @@ -12,6 +12,43 @@ def self.create_object(key) .object(key) end + # Reads data from an S3 object specified by the given URI. + # + # @param uri [URI] The URI of the S3 object. The URI should include the bucket name in the host + # and the object key in the path. + # @return [String] The content of the S3 object as a string. + # + # @raise [RuntimeError] If the S3 object is not found or if there is a service error. + # @raise [Aws::S3::Errors::NoSuchKey] If the specified key does not exist in the bucket. + # @raise [Aws::S3::Errors::ServiceError] If there is an error with the S3 service. + # @raise [StandardError] For any other unexpected errors. + # + def self.read_data_from_s3(uri) + # Extract bucket and key from the URL + bucket = uri.host.split('.').first + key = URI.decode_www_form_component(uri.path[1..]) # Decode URL-encoded characters + + # Initialize the S3 client + s3_client = Aws::S3::Client.new( + region: ENV.fetch('AWS_REGION', 'us-east-1'), + access_key_id: ENV.fetch('AWS_ACCESS_KEY_ID', nil), + secret_access_key: ENV.fetch('AWS_SECRET_ACCESS_KEY', nil) + ) + + # Fetch the object from S3 + response = s3_client.get_object(bucket:, key:) + response.body.read + rescue Aws::S3::Errors::NoSuchKey => e + Rails.logger.error "S3 Error: Object not found - #{e.message}" + raise "S3 object not found: #{key}" + rescue Aws::S3::Errors::ServiceError => e + Rails.logger.error "S3 Service Error: #{e.message}" + raise "S3 service error: #{e.message}" + rescue StandardError => e + Rails.logger.error "Unexpected error while fetching S3 object: #{e.message}" + raise "Unexpected error: #{e.message}" + end + # # Upload data to the specified resource by key # @@ -24,7 +61,8 @@ def self.create_object(key) def self.upload(key, data, options = {}) object = create_object key options = options.merge( - body: data + body: data, + cache_control: 'public, max-age=0, must-revalidate' ) object.put(options) object.public_url diff --git a/app/services/lcms/engine/sketch_compiler.rb b/app/services/lcms/engine/sketch_compiler.rb deleted file mode 100644 index a96ca447..00000000 --- a/app/services/lcms/engine/sketch_compiler.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -require 'base64' - -module Lcms - module Engine - class SketchCompiler - def initialize(user_id, user_ip, version) - @user_id = user_id - @user_ip = user_ip - @version = version - end - - # - # Returns HTTParty::Response object - # - def compile(core_url, foundational_url) - api_url = ENV.fetch('UB_COMPONENTS_API_URL') - url = [api_url, @version, 'compile'].join('/') - post_params = { - body: { - uid: Base64.encode64("#{@user_id}@#{@user_ip}"), - url: core_url, - foundational_url: foundational_url - }, - headers: { 'Authorization' => %(Token token="#{ENV.fetch 'UB_COMPONENTS_API_TOKEN'}") }, - timeout: 5 * 60 - } - HTTParty.post url, post_params - end - end - end -end diff --git a/app/services/lcms/engine/standards_import_service.rb b/app/services/lcms/engine/standards_import_service.rb new file mode 100644 index 00000000..acbdf4cc --- /dev/null +++ b/app/services/lcms/engine/standards_import_service.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'csv' + +module Lcms + module Engine + class StandardsImportService < ImportService + class << self + # + # @param [String] url + # + def call(url) + ActiveRecord::Base.transaction do + Lcms::Engine::Standard.destroy_all + CSV.parse(super, headers: true, &method(:create_from_csv_row)) + end + after_reimport_hook + end + + def after_reimport_hook; end + + private + + # + # @param [Array] + # + def create_from_csv_row(_row) + raise NotImplementedError + end + end + end + end +end diff --git a/app/uploaders/download_uploader.rb b/app/uploaders/download_uploader.rb deleted file mode 100644 index 2806fa4a..00000000 --- a/app/uploaders/download_uploader.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -class DownloadUploader < CarrierWave::Uploader::Base - def store_dir - "attachments/#{model.id}" - end - - def fog_attributes - { 'Content-Disposition' => 'attachment' } - end -end diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb index 0dba595d..670d177e 100644 --- a/app/views/devise/confirmations/new.html.erb +++ b/app/views/devise/confirmations/new.html.erb @@ -1,4 +1,4 @@ -
    +

    Resend confirmation instructions

    diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb index c9f20fb0..5f6c08d0 100644 --- a/app/views/devise/passwords/edit.html.erb +++ b/app/views/devise/passwords/edit.html.erb @@ -1,4 +1,4 @@ -
    +

    Change your password

    diff --git a/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb index e466090f..9a2382a1 100644 --- a/app/views/devise/passwords/new.html.erb +++ b/app/views/devise/passwords/new.html.erb @@ -1,4 +1,4 @@ -
    +

    Forgot your password?

    diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index ba2d2f64..96946b19 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -37,7 +37,7 @@

    Cancel my account

    -

    Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>

    +

    Unhappy? <%= button_to 'Cancel my account', registration_path(resource_name), data: { confirm: 'Are you sure?', 'turbo-method': :delete }, method: :delete %>

    <%= link_to "Back", :back %>
    diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb index 1dfc3fe7..7ebe6c45 100644 --- a/app/views/devise/registrations/new.html.erb +++ b/app/views/devise/registrations/new.html.erb @@ -1,4 +1,4 @@ -
    +

    Register

    diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index 5936fbb9..8e6e4d2d 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -1,29 +1,28 @@ -<% content_for :above_flash do %> -
    - Welcome to <%= ENV['APP_NAME'] %>! -
    -<% end %> - -
    -
    -
    -

    Log in

    -
    +
    +
    +

    Login

    - <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> - <%= f.email_field :email, autofocus: true, class: 'o-input-placehoder--medium-gray', placeholder: 'Enter email address' %> - <%= f.password_field :password, autocomplete: 'off', class: 'o-input-placehoder--medium-gray', placeholder: 'Enter password' %> -
    - <%= f.submit 'Log in', class: 'button o-btn--xs-full warning o-btn--xs-full u-margin-right--base ' %> - <%= link_to 'Register', new_registration_path(resource_name), class: 'button o-btn--xs-full o-btn--bordered-yellow u-color--base o-btn--xs-full' %> + <%= form_for(resource, as: resource_name, url: session_path(resource_name), html: { 'data-turbo': 'false' }) do |f| %> +
    + <%= f.email_field :email, autofocus: true, class: 'form-control', placeholder: 'Enter email address', id: 'email-field' %> +
    +
    + <%= f.password_field :password, autocomplete: 'off', class: 'form-control', placeholder: 'Enter password', id: 'password-field' %> +
    +
    +
    + <%= f.submit 'Log in', class: 'btn btn-primary btn-block mb-4' %> +
    +
    + <%= link_to 'Register', new_registration_path(resource_name), class: '' %>
    - <% end %> +
    -
    +
    <%= link_to 'Forgot your password?', new_password_path(resource_name) %>
    <%= link_to "Didn't receive confirmation instructions?", new_user_confirmation_path %>
    -
    + <% end %>
    diff --git a/app/views/layouts/lcms/engine/admin.html.erb b/app/views/layouts/lcms/engine/admin.html.erb index 76537551..54356179 100644 --- a/app/views/layouts/lcms/engine/admin.html.erb +++ b/app/views/layouts/lcms/engine/admin.html.erb @@ -4,24 +4,19 @@ <%= "#{t('ui.unbounded')} - #{page_title}" %> - - <%= render partial: 'lcms/engine/shared/favicon' %> - <%= stylesheet_link_tag 'lcms/engine/lcms_engine_admin', media: 'all', 'data-turbolinks-track' => 'reload' %> <%= csrf_meta_tags %> - <%# NOTE: JS served via Assets pipeline should be added first %> - <%= javascript_include_tag 'lcms/engine/admin/lcms_engine_application', 'data-turbolinks-track' => 'reload' %> - <%= lcms_engine_javascript_pack_tag 'lcms_engine_admin' %> + + + <%= javascript_include_tag Ckeditor.cdn_url, "data-turbo-track": "reload", defer: true %> -
    - <%= render partial: 'lcms/engine/admin/shared/header' %> -
    - <%= render partial: 'lcms/engine/shared/flash' %> - <%= yield %> -
    + <%= render partial: 'lcms/engine/shared/header' %> + <%= render partial: 'lcms/engine/shared/flash' %> +
    + <%= yield %>
    - <%= render partial: 'lcms/engine/shared/footer' %> + <%#= render partial: 'lcms/engine/shared/footer' %> diff --git a/app/views/layouts/lcms/engine/application.html.erb b/app/views/layouts/lcms/engine/application.html.erb index 29144f43..0c845ede 100644 --- a/app/views/layouts/lcms/engine/application.html.erb +++ b/app/views/layouts/lcms/engine/application.html.erb @@ -4,24 +4,18 @@ <%= page_title %> - - <%= redirect_meta_tag %> - <%= render partial: 'lcms/engine/shared/favicon' %> - <%= stylesheet_link_tag 'lcms/engine/lcms_engine_application', media: 'all', 'data-turbolinks-track' => 'reload' %> - <%= javascript_include_tag 'lcms/engine/lcms_engine_application', 'data-turbolinks-track' => 'reload' %> <%= csrf_meta_tags %> - <% if content_for?(:canonical_url) %> - - <% end %> + + -
    - <%= render partial: 'lcms/engine/shared/header' %> - <%= render partial: 'lcms/engine/shared/flash' %> + <%= render partial: 'lcms/engine/shared/header' %> + <%= render partial: 'lcms/engine/shared/flash' %> +
    <%= yield %>
    - <%= render partial: 'lcms/engine/shared/footer' %> + <%#= render partial: 'lcms/engine/shared/footer' %> diff --git a/app/views/layouts/lcms/engine/application_lti.html.erb b/app/views/layouts/lcms/engine/application_lti.html.erb new file mode 100644 index 00000000..e9c8f4b8 --- /dev/null +++ b/app/views/layouts/lcms/engine/application_lti.html.erb @@ -0,0 +1,14 @@ + + + + + + + + <%= javascript_include_tag '//s7.addthis.com/js/300/addthis_widget.js#async=1' %> + <%= csrf_meta_tags %> + + + <%= yield %> + + diff --git a/app/views/layouts/lcms/engine/ld_gdoc.html.erb b/app/views/layouts/lcms/engine/gdoc.html.erb similarity index 92% rename from app/views/layouts/lcms/engine/ld_gdoc.html.erb rename to app/views/layouts/lcms/engine/gdoc.html.erb index f9c25ccb..9284112e 100644 --- a/app/views/layouts/lcms/engine/ld_gdoc.html.erb +++ b/app/views/layouts/lcms/engine/gdoc.html.erb @@ -4,7 +4,7 @@ <%= "#{t('ui.unbounded')} - #{page_title}" %> - diff --git a/app/views/layouts/lcms/engine/lti.html.erb b/app/views/layouts/lcms/engine/lti.html.erb deleted file mode 100644 index 3856fba7..00000000 --- a/app/views/layouts/lcms/engine/lti.html.erb +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - <%= stylesheet_link_tag 'lcms/engine/lcms_engine_application', media: 'all', 'data-turbolinks-track' => 'reload' %> - <%= javascript_include_tag '//s7.addthis.com/js/300/addthis_widget.js#async=1' %> - <%= javascript_include_tag 'lcms/engine/application_lti', 'data-turbolinks-track' => 'reload' %> - <%= csrf_meta_tags %> - <% if content_for?(:canonical_url) %> - - <% end %> - - -
    - <%= yield %> -
    - - diff --git a/app/views/layouts/lcms/engine/cg_pdf.erb b/app/views/layouts/lcms/engine/pdf.erb similarity index 100% rename from app/views/layouts/lcms/engine/cg_pdf.erb rename to app/views/layouts/lcms/engine/pdf.erb diff --git a/app/views/layouts/lcms/engine/cg_plain_pdf.erb b/app/views/layouts/lcms/engine/pdf_plain.erb similarity index 96% rename from app/views/layouts/lcms/engine/cg_plain_pdf.erb rename to app/views/layouts/lcms/engine/pdf_plain.erb index 0eeb47df..7ec0aadc 100644 --- a/app/views/layouts/lcms/engine/cg_plain_pdf.erb +++ b/app/views/layouts/lcms/engine/pdf_plain.erb @@ -5,7 +5,7 @@ <%= params.key?('debug') ? stylesheet_link_tag('lcms/engine/pdf_plain', media: 'all') : stylesheet_link_tag(base64_encoded_asset('lcms/engine/pdf_plain.css')) %> - -
    - <%= render 'header', document: @document %> +
    <%= render 'content', document: @document %>
    ').first + thead.children.reverse.each { |child| tbody.prepend_child child } + thead.remove + end + table end def initialize @@ -39,7 +53,7 @@ def collect_and_render_tags(data, fields, opts = {}) fields.each do |field| (data[field] = []) && next if (row = data[field]).blank? - tags = row + tags = row.to_s .split(opts[:separator].presence || SPLIT_REGEX) .map(&:squish) .reject(&:blank?) @@ -63,7 +77,8 @@ def parse(fragment, *args) # get the table table_key_cell = fragment.at_xpath("table//tr[1]/td[1][contains(., '#{self.class::HEADER_LABEL}')]") - table = table_key_cell&.ancestors('table')&.first + # flatten table to simple structure with tbody only + table = self.class.flatten_table(table_key_cell&.ancestors('table')&.first) @table_exists = table.present? return self unless @table_exists @@ -86,11 +101,11 @@ def parse(fragment, *args) # @return [Hash] nested Hash for each of supported context types # def parse_in_context(content, opts = {}) - # do not generate parts placeholder - inline all the tags + # do not generate part's placeholder - inline all the tags opts[:explicit_render] = true html = Nokogiri::HTML.fragment content - {}.tap do |result| + {}.tap do |result| # steep:ignore ::DocTemplate.context_types.each do |context_type| opts[:context_type] = context_type rendered_content = DocTemplate::Document.parse(html.dup, opts).render @@ -118,8 +133,9 @@ def table_exist? attr_reader :options, :table_exists def fetch(table) - {}.tap do |result| - table.xpath('./tbody/tr[position() > 1]').each do |row| + {}.tap do |result| # steep:ignore + # select direct tr children from tbody, thead, or table itself, excluding nested tables + table.xpath('./tbody/tr[position() > 1] | ./thead/tr[position() > 1] | ./tr[position() > 1]').each do |row| key = row.at_xpath('./td[1]')&.text.to_s.squish.downcase next if key.blank? @@ -129,7 +145,8 @@ def fetch(table) row.at_xpath('./td[2]').text end.squish - result[key] = value + result[key] = + value end end end diff --git a/lib/doc_template/tables/foundational_metadata.rb b/lib/doc_template/tables/foundational_metadata.rb deleted file mode 100644 index e8cd9614..00000000 --- a/lib/doc_template/tables/foundational_metadata.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module DocTemplate - module Tables - class FoundationalMetadata < Base - HEADER_LABEL = 'foundational-metadata' - HTML_VALUE_FIELDS = %w(lesson-objective).freeze - end - end -end diff --git a/lib/doc_template/tables/material_metadata.rb b/lib/doc_template/tables/material_metadata.rb index 2f1dd36b..244e2621 100644 --- a/lib/doc_template/tables/material_metadata.rb +++ b/lib/doc_template/tables/material_metadata.rb @@ -5,9 +5,9 @@ module Tables class MaterialMetadata < Base CONFIG_PATH = Rails.root.join('config', 'materials_rules.yml') HEADER_LABEL = 'material-metadata' - HTML_VALUE_FIELDS = [].freeze + HTML_VALUE_FIELDS = [].freeze # steep:ignore - def parse(fragment, *_args) + def parse(fragment, *args) super if @data['sheet-type'].blank? @data['type'] ||= 'default' @@ -19,7 +19,7 @@ def parse(fragment, *_args) private def config - @config ||= YAML.load_file(CONFIG_PATH)['sheet_types'] + @config ||= YAML.load_file(CONFIG_PATH, aliases: true)['sheet_types'] end end end diff --git a/lib/doc_template/tables/metadata.rb b/lib/doc_template/tables/metadata.rb index b2af6776..741d2788 100644 --- a/lib/doc_template/tables/metadata.rb +++ b/lib/doc_template/tables/metadata.rb @@ -6,7 +6,7 @@ class Metadata < Base HEADER_LABEL = 'document-metadata' HTML_VALUE_FIELDS = %w(description lesson-objective look-fors materials preparation relationship-to-eny1).freeze - def parse(fragment, *_args) + def parse(fragment, *args) super @data['subject'] = @data['subject'].to_s.downcase if @data.present? self diff --git a/lib/doc_template/tables/section.rb b/lib/doc_template/tables/section.rb index 7ab5b362..16db68fe 100644 --- a/lib/doc_template/tables/section.rb +++ b/lib/doc_template/tables/section.rb @@ -11,12 +11,12 @@ class Section < Base def parse(fragment, *args) section_tables = fragment.xpath(xpath_meta_headers, XpathFunctions.new) - # # Allows to handle ELA as Math:: inject fake section + # # Allows handling ELA as Math:: inject a fake section return fake_section(fragment) if section_tables.empty? && args.extract_options![:force_inject] - [].tap do |result| + [].tap do |result| # steep:ignore section_tables.each do |el| - table = el.ancestors('table').first + table = self.class.flatten_table(el.ancestors('table').first) data = fetch table value = data['section-title'].parameterize diff --git a/lib/doc_template/tables/target.rb b/lib/doc_template/tables/target.rb index d01962b9..df34ac9b 100644 --- a/lib/doc_template/tables/target.rb +++ b/lib/doc_template/tables/target.rb @@ -9,7 +9,7 @@ class Target < Base def parse(fragment, *_args) path = ".//table/*/tr[1]/td[1][case_insensitive_contains(.//*/text(), '#{HEADER_LABEL_PIECE}')]" return unless (element = fragment.at_xpath path, XpathFunctions.new) - return unless (table = element.ancestors('table').first) + return unless (table = self.class.flatten_table(element.ancestors('table').first)) @target_data = fetch table @@ -29,7 +29,7 @@ def parse(fragment, *_args) private def fetch(table) - data = {}.tap do |result| + data = {}.tap do |result| # steep:ignore # Need to handle every `p` and `ul` elements result[:long_term_title] = table.xpath('*//tr[1]/td').map(&:content).join(' ') result[:long_term] = table.xpath('*//tr[2]/td/p').map(&:content).join('; ') diff --git a/lib/doc_template/tags.rb b/lib/doc_template/tags.rb index fe95f5ef..2287c459 100644 --- a/lib/doc_template/tags.rb +++ b/lib/doc_template/tags.rb @@ -3,12 +3,37 @@ module DocTemplate module Tags CONFIG_PATH = Rails.root.join('config', 'tags.yml') + RE_BOLD = /(\[b\]([^\[]*)\[b:end\])/i + RE_ITALIC = /(\[i\]([^\[]*)\[i:end\])/i + RE_LINE_BREAK = /\[line-break\]/i + TAG_LINE_BREAK = '

    ' mattr_accessor :config - self.config = YAML.load_file(Tags::CONFIG_PATH) || {} + self.config = YAML.load_file(Tags::CONFIG_PATH, aliases: true) || {} # By default we remove unknown tags (`DefaultTag`) config['default'] ||= { 'remove' => true } + + # + # Substitutes the following custom tags with plain HTML markup: + # + # # [b]Text[b:end] -> Text + # # [i]Text[i:end] -> Text + # # [line-break] ->

    + # + # @param [String] content Content to substitute vars in + # @return [String] + def self.substitute_tags_in(content) + return '' if content.blank? + + # do gsub and mark if any matches + parsed_content = content.to_s + .gsub(RE_LINE_BREAK, TAG_LINE_BREAK) + .gsub(RE_ITALIC, '\2') + .gsub(RE_BOLD, '\2') + # if there are any matches, call recursively + parsed_content == content ? parsed_content.gsub("\n", '
    ') : substitute_tags_in(parsed_content) + end end end diff --git a/lib/doc_template/tags/activity_metadata_section_tag.rb b/lib/doc_template/tags/activity_metadata_section_tag.rb index 467923a9..a13b1f95 100644 --- a/lib/doc_template/tags/activity_metadata_section_tag.rb +++ b/lib/doc_template/tags/activity_metadata_section_tag.rb @@ -16,8 +16,6 @@ def parse(node, opts = {}) @anchor = @section.anchor @materials = @section.material_ids - parse_foundational - before_materials = '' if (with_materials = @section.material_ids.any?) before_materials = content_until_materials node @@ -30,11 +28,10 @@ def parse(node, opts = {}) end content = parse_nested content.to_s, opts params = { - before_materials: before_materials, + before_materials:, # TODO: check maybe it's ok to move it somewhere else, # fixed at #692 bc with new section we always have some garbage before activity content: DocTemplate.sanitizer.strip_html(content), - foundational_skills: opts[:foundational_skills], placeholder: placeholder_id, react_props: { activity: { @@ -44,31 +41,12 @@ def parse(node, opts = {}) material_ids: @section.material_ids }, section: @section, - with_materials: with_materials + with_materials: } @content = parse_template params, template_name(opts) replace_tag node self end - - private - - attr_accessor :opts, :section - - def parse_foundational - return unless opts[:value] == 'foundational-skills' - - # Extend object to store `lesson_objective` (#162) - section.class.attribute :lesson_objective, String - section.lesson_objective = DocTemplate.sanitizer - .strip_html_element(opts[:foundational_metadata].lesson_objective) - # Extend object to store `lesson_standard` (#386) - section.class.attribute :lesson_standard, String - section.lesson_standard = DocTemplate.sanitizer - .strip_html_element(opts[:foundational_metadata].lesson_standard) - opts[:sections].add_break - opts[:foundational_skills] = true - end end end diff --git a/lib/doc_template/tags/activity_metadata_type_tag.rb b/lib/doc_template/tags/activity_metadata_type_tag.rb index ddea8d7c..b0f16282 100644 --- a/lib/doc_template/tags/activity_metadata_type_tag.rb +++ b/lib/doc_template/tags/activity_metadata_type_tag.rb @@ -7,7 +7,7 @@ class ActivityMetadataTypeTag < BaseTag include ERB::Util TAG_NAME = 'activity-metadata-type' - TASK_RE = /(\[task:\s(#)\])/i.freeze + TASK_RE = /(\[task:\s(#)\])/i TEMPLATES = { default: 'activity.html.erb', gdoc: 'gdoc/activity.html.erb' @@ -58,9 +58,7 @@ def extended_params_default end { - foundational: opts[:foundational_skills], priority_description: priority_description(activity), - priority_icon: priority_icon(activity), react_props: { activity: { title: @activity.title, diff --git a/lib/doc_template/tags/answer_space_tag.rb b/lib/doc_template/tags/answer_space_tag.rb index 7ae9cb57..7bc91f99 100644 --- a/lib/doc_template/tags/answer_space_tag.rb +++ b/lib/doc_template/tags/answer_space_tag.rb @@ -11,6 +11,7 @@ class AnswerSpaceTag < BaseTag TAG_NAME = 'answer-space' def parse(node, opts = {}) + @opts = opts num_of_lines = SPACE_SIZE[opts[:value].try(:to_sym)] if num_of_lines space = '
    ' * num_of_lines diff --git a/lib/doc_template/tags/base_tag.rb b/lib/doc_template/tags/base_tag.rb index fdbb18d5..2b0a6dfc 100644 --- a/lib/doc_template/tags/base_tag.rb +++ b/lib/doc_template/tags/base_tag.rb @@ -3,25 +3,64 @@ module DocTemplate module Tags class BaseTag - SOFT_RETURN_RE = /([[:graph:]]+\[|\][[:graph:]]+)/.freeze - UNICODE_SPACES_RE = /(\u0020|\u00A0|\u1680|\u180E|[\u2000-\u200B]|\u202F|\u205F|\u3000|\uFEFF)/.freeze + SOFT_RETURN_RE = /([[:graph:]]+\[|\][[:graph:]]+)/ + UNICODE_SPACES_RE = /(\u0020|\u00A0|\u1680|\u180E|[\u2000-\u200B]|\u202F|\u205F|\u3000|\uFEFF)/ - attr_reader :anchor, :content + attr_reader :anchor, :content, :errors - class << self - def parse(node, opts = {}) - new.parse(node, opts) - end + def self.parse(node, opts = {}) + new.parse(node, opts) + end - def tag_with_html_regexp - raise NotImplementedError unless const_defined?(:TAG_NAME) + def self.tag_with_html_regexp + @tag_with_html_regexp ||= + begin + raise 'TAG_NAME is not specified' unless const_defined?(:TAG_NAME) - @tag_with_html_regexp ||= /\[[^\]]*#{self::TAG_NAME}[[^:,;.]]*:?\s?[^\]]*\]/i - end + /\[[^\]]*#{const_get(:TAG_NAME)}[[^:,;.]]*:?\s?[^\]]*\]/i + end + end - def template_path_for(name) - File.join ::Lcms::Engine::Engine.root.join('lib', 'doc_template', 'templates'), name - end + # + # There can be situations when Google Document is exported + # in a broken way: + # [image: OP.LE.L1.004] + # In this case we need to guess the part which should be substituted + # + # As a result for the tag with name `image` will be the following + # [ + # /\[[^\]]*image[[^:,;.]]*(<\/span>\s*]*>)?:?\s?[^\]]*\]/i, + # /\[[^\]]*imag[[^:,;.]]*(<\/span>\s*]*>)?e:?\s?[^\]]*\]/i, + # /\[[^\]]*ima[[^:,;.]]*(<\/span>\s*]*>)?ge:?\s?[^\]]*\]/i + # ] + # + # @param [Integer] min_char Minimum number of character by which we guess the tag + # @return [Array] + def self.tag_with_html_regexp_array(min_char = 3) + @tag_with_html_regexp_array ||= + begin + raise 'TAG_NAME is not specified' unless const_defined?(:TAG_NAME) + + tag_name = const_get(:TAG_NAME) + (tag_name.length - 1).downto(min_char - 1).map do |idx| + first_part = tag_name[0..idx] + last_part = tag_name[(idx + 1)..] + + %r{\[[^\]]*#{first_part}[[^:,;.]]*(\s*]*>)?#{last_part}:?\s?[^\]]*\]}i + end + end + end + + # + # @param [String] name + # @return [String] path to the template + # + def self.template_path_for(name) + File.join ::Lcms::Engine::Engine.root.join('lib', 'doc_template', 'templates'), name + end + + def initialize + @errors = [] end # @@ -40,7 +79,7 @@ def check_tag_soft_return(node) end def content_until_break(node) - [].tap do |result| + [].tap do |result| # steep:ignore check_tag_soft_return(node) while (sibling = node.next_sibling) break if include_break?(sibling) @@ -52,7 +91,7 @@ def content_until_break(node) end def content_until_materials(node) - [].tap do |result| + [].tap do |result| # steep:ignore check_tag_soft_return(node) while (sibling = node.next_sibling) break if include_break_for?(sibling, 'stop_materials_tags') @@ -63,14 +102,6 @@ def content_until_materials(node) end.join end - def ela2?(metadata) - metadata.resource_subject == 'ela' && metadata.grade == '2' - end - - def ela6?(metadata) - metadata.resource_subject == 'ela' && metadata.grade == '6' - end - def gdoc?(opts) opts[:context_type].to_s.casecmp('gdoc').zero? end @@ -94,7 +125,7 @@ def materials @materials || [] end - def parse(node, _options = {}) + def parse(node, _opts = {}) @result = node remove_tag self @@ -108,6 +139,7 @@ def parse_nested(node, opts = {}) end end parsed = ::DocTemplate::Document.parse(Nokogiri::HTML.fragment(node), opts.merge(level: 1)) + @errors.push(*parsed.errors) if parsed.errors.any? # add the parts to the parent document opts[:parent_document].parts += parsed.parts if opts[:parent_document] parsed.render @@ -124,7 +156,7 @@ def placeholder end def placeholder_id - @placeholder_id ||= "#{self.class.name.demodulize.underscore}_#{SecureRandom.hex(10)}" + @placeholder_id ||= "#{self.class.name.to_s.demodulize.underscore}_#{SecureRandom.hex(10)}" end def render @@ -170,7 +202,7 @@ def remove_tag end_tag_index = @result.children.index(@result.at_xpath(ENDTAG_XPATH)) @result.children[start_tag_index..end_tag_index].each do |node| if node.content.match?(/.+\[[^\]]+\]|\[[^\]]+\].+/) - # a tag followed or preceeded by anything else + # a tag followed or preceded by anything else # removes the tag itself - everything between `[` and `]` node.content = node.content.sub(/\[[^\[\]]+\]/, '') elsif (data = node.content.match(/^([^\[]*)\[|\]([^\[]*)$/)) diff --git a/lib/doc_template/tags/block_tag.rb b/lib/doc_template/tags/block_tag.rb index ebcc6fca..0e8158ca 100644 --- a/lib/doc_template/tags/block_tag.rb +++ b/lib/doc_template/tags/block_tag.rb @@ -19,7 +19,7 @@ def block_nodes(node) tag_node = node # we have to collect all nodes until the we find the end tag - nodes = [].tap do |result| + nodes = [].tap do |result| # steep:ignore check_tag_soft_return(node) while (node = node.next_sibling) if node.content.match?(end_tag_re) diff --git a/lib/doc_template/tags/callout_tag.rb b/lib/doc_template/tags/callout_tag.rb index a4f9403a..56dbc9b3 100644 --- a/lib/doc_template/tags/callout_tag.rb +++ b/lib/doc_template/tags/callout_tag.rb @@ -12,8 +12,8 @@ class CalloutTag < TableTag def parse_table(table) header, content = fetch_content(table) params = { - content: content, - header: header, + content:, + header:, subject: @opts[:metadata].resource_subject } new_content = parse_template params, template_name(@opts) diff --git a/lib/doc_template/tags/columns_tag.rb b/lib/doc_template/tags/columns_tag.rb index cc326fbd..ab7ff2f0 100644 --- a/lib/doc_template/tags/columns_tag.rb +++ b/lib/doc_template/tags/columns_tag.rb @@ -5,7 +5,7 @@ module Tags class ColumnsTag < BlockTag include ERB::Util - ALIGNMENT_RE = /^align-right\s/i.freeze + ALIGNMENT_RE = /^align-right\s/i SPLIT_SYMBOL = ';' TAG_NAME = 'columns' TEMPLATE = 'columns.html.erb' @@ -24,7 +24,7 @@ def parse(node, opts = {}) end end - @content = parse_template({ rows: rows }, TEMPLATE) if rows.any? + @content = parse_template({ rows: }, TEMPLATE) if rows.any? replace_tag node self end @@ -82,9 +82,9 @@ def fetch_images(node) end def handle_alignment_for(td) - {}.tap do |result| + {}.tap do |result| # steep:ignore result[:content] = td.sub ALIGNMENT_RE, '' - result[:css_class] = 'text-right' if td =~ ALIGNMENT_RE + result[:css_class] = 'text-end' if td =~ ALIGNMENT_RE end end diff --git a/lib/doc_template/tags/core_content_objectives_tag.rb b/lib/doc_template/tags/core_content_objectives_tag.rb index 261780e6..450d2852 100644 --- a/lib/doc_template/tags/core_content_objectives_tag.rb +++ b/lib/doc_template/tags/core_content_objectives_tag.rb @@ -7,6 +7,7 @@ class CoreContentObjectivesTag < BlockTag TEMPLATE = 'core_content_objectives.html.erb' def parse(node, opts = {}) + @opts = opts nodes = block_nodes node nodes.each(&:remove) diff --git a/lib/doc_template/tags/def_tag.rb b/lib/doc_template/tags/def_tag.rb index 6fbc090d..779a5f3a 100644 --- a/lib/doc_template/tags/def_tag.rb +++ b/lib/doc_template/tags/def_tag.rb @@ -5,7 +5,7 @@ module Tags class DefTag < BaseTag include ERB::Util - STYLE_RE = /]*)>[^<]*$/i.freeze + STYLE_RE = /]*)>[^<]*$/i TAG_NAME = 'def' TAG_SEPARATOR = '[separator]' TEMPLATES = { @@ -14,6 +14,7 @@ class DefTag < BaseTag }.freeze def parse(node, opts = {}) + @opts = opts # Need to extract the Tag and preserves all the styling inside it node_html = node.inner_html start_pos = node_html.index '[' @@ -28,11 +29,11 @@ def parse(node, opts = {}) params = { append: data[1], - definition: definition, - description: description, + definition:, + description:, prepend: data[0], - preserved_style: preserved_style, - subject: subject + preserved_style:, + subject: } @content = "

    #{parse_template(params, template_name(opts))}

    " diff --git a/lib/doc_template/tags/dialogue_tag.rb b/lib/doc_template/tags/dialogue_tag.rb index 9bc16a08..99df5ab8 100644 --- a/lib/doc_template/tags/dialogue_tag.rb +++ b/lib/doc_template/tags/dialogue_tag.rb @@ -10,6 +10,7 @@ class DialogueTag < BlockTag gdoc: 'gdoc/dialogue.html.erb' }.freeze def parse(node, opts = {}) + @opts = opts @tags = [] nodes = block_nodes node diff --git a/lib/doc_template/tags/expand_tag.rb b/lib/doc_template/tags/expand_tag.rb index b07172c8..9e8377db 100644 --- a/lib/doc_template/tags/expand_tag.rb +++ b/lib/doc_template/tags/expand_tag.rb @@ -21,8 +21,8 @@ def parse_table(table) def fetch_content(node) broken = false - content_visible = [] - content_hidden = [] + content_visible = [] # : Array[Nokogiri::XML::Node] + content_hidden = [] # : Array[Nokogiri::XML::Node] # iterates over all child nodes looking for break tag node.at_xpath('.//tr[2]/td').children.each do |child| diff --git a/lib/doc_template/tags/group_tag.rb b/lib/doc_template/tags/group_tag.rb deleted file mode 100644 index 9d54fcf1..00000000 --- a/lib/doc_template/tags/group_tag.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -module DocTemplate - module Tags - class GroupTag < BaseTag - include DocTemplate::Tags::Helpers - - TAG_NAME = 'group' - TEMPLATES = { - default: 'group-ela.html.erb', - gdoc: 'gdoc/group-ela.html.erb' - }.freeze - - def parse(node, opts = {}) - group = opts[:agenda].level1_by_title(opts[:value].parameterize) - @anchor = group.anchor - @materials = group.material_ids - - before_materials = '' - if (with_materials = group.material_ids.any?) - before_materials = content_until_materials node - before_materials = parse_nested before_materials.to_s, opts - end - - content = content_until_break node - content = parse_nested content.to_s, opts - params = { - before_materials: before_materials, - content: content, - group: group, - placeholder: placeholder_id, - react_props: { - activity: { - title: group.title - }, - group: true, - material_ids: group.material_ids - }, - with_materials: with_materials - } - @content = parse_template params, template_name(opts) - replace_tag node - self - end - end - end - - Template.register_tag(Tags::GroupTag::TAG_NAME, Tags::GroupTag) -end diff --git a/lib/doc_template/tags/heading_tag.rb b/lib/doc_template/tags/heading_tag.rb index d737ea5a..702f469f 100644 --- a/lib/doc_template/tags/heading_tag.rb +++ b/lib/doc_template/tags/heading_tag.rb @@ -6,6 +6,7 @@ class HeadingTag < BaseTag TEMPLATE = 'heading.html.erb' def parse(node, opts = {}) + @opts = opts # we have to collect all the next siblings until next stop-tag params = { content: parse_nested(content_until_break(node), opts), diff --git a/lib/doc_template/tags/helpers.rb b/lib/doc_template/tags/helpers.rb index 29e1ae79..71c72b4a 100644 --- a/lib/doc_template/tags/helpers.rb +++ b/lib/doc_template/tags/helpers.rb @@ -4,12 +4,11 @@ module DocTemplate module Tags module Helpers include ActionView::Helpers::TagHelper - ICON_PATH = 'http://s3.amazonaws.com/ubpilot-uploads/assets' def materials_container(props) return if props.nil? - content_tag :div, nil, data: { react_class: 'MaterialsContainer', react_props: props } + content_tag(:div, nil, data: { react_class: 'MaterialsContainer', react_props: props }) { _1 } end def priority_description(activity) @@ -19,14 +18,6 @@ def priority_description(activity) config = Tags.config[self.class::TAG_NAME.downcase] Array.wrap(config['priority_descriptions'])[priority - 1] end - - def priority_icon(activity) - return unless activity.priority.present? - - # for some odd reason inlined images aren't working at gdoc - # this is why we reference s3 - "#{ICON_PATH}/ld_p#{activity.priority}.png" - end end end end diff --git a/lib/doc_template/tags/image_student_worksheet_tag.rb b/lib/doc_template/tags/image_student_worksheet_tag.rb index 2156119f..1acf6bf8 100644 --- a/lib/doc_template/tags/image_student_worksheet_tag.rb +++ b/lib/doc_template/tags/image_student_worksheet_tag.rb @@ -33,7 +33,7 @@ def find_image(node) end def prepare_content(image) - {}.tap do |result| + {}.tap do |result| # steep:ignore result[:src] = image['src'] image['style'].split(';').each do |style| diff --git a/lib/doc_template/tags/image_tag.rb b/lib/doc_template/tags/image_tag.rb index 54424974..128c96a1 100644 --- a/lib/doc_template/tags/image_tag.rb +++ b/lib/doc_template/tags/image_tag.rb @@ -10,7 +10,7 @@ class ImageTag < TableTag def parse_table(table) params = { caption: table.at_xpath('.//tr[2]/td').text, - image_src: image_src, + image_src:, subject: @opts[:metadata].try(:[], 'subject') } @content = parse_template(params, template_name(@opts)) diff --git a/lib/doc_template/tags/inset_tag.rb b/lib/doc_template/tags/inset_tag.rb index edef4cca..3747b7ee 100644 --- a/lib/doc_template/tags/inset_tag.rb +++ b/lib/doc_template/tags/inset_tag.rb @@ -10,6 +10,7 @@ class InsetTag < BlockTag TAG_NAME = 'inset' def parse(node, opts = {}) + @opts = opts nodes = block_nodes(node) { |n| preserve_styles n, opts } content = parse_nested nodes.map(&:to_html).join, opts nodes.each(&:remove) diff --git a/lib/doc_template/tags/latex_tag.rb b/lib/doc_template/tags/latex_tag.rb index 7d1c197b..e26257cb 100644 --- a/lib/doc_template/tags/latex_tag.rb +++ b/lib/doc_template/tags/latex_tag.rb @@ -5,7 +5,7 @@ module DocTemplate module Tags class LatexTag < BaseTag - SPACE_RE = /[[:space:]]/.freeze + SPACE_RE = /[[:space:]]/ TAG_NAME = 'latex' def self.s3_folder @@ -21,7 +21,7 @@ def parse(node, opts = {}) if opts[:context_type]&.to_sym == :gdoc key = "#{self.class.s3_folder}/#{SecureRandom.hex(20)}.png" generate_image do |png| - url = S3Service.upload key, png + url = Lcms::Engine::S3Service.upload key, png %() end else @@ -58,14 +58,14 @@ def custom_color def generate_image svg_path = Tempfile.open(%w(tex-eq .svg)) do |svg| - svg.write EmbedEquations.tex_to_svg(value, custom_color: custom_color) + svg.write Lcms::Engine::EmbedEquations.tex_to_svg(value, custom_color:) svg.path end png = Tempfile.new %w(tex-eq .png) begin - system 'svgexport', svg_path, png.path - yield File.read(png.path) + system('svgexport', svg_path.to_s, png.path.to_s) + yield File.read(png.path.to_s) ensure png.close true end diff --git a/lib/doc_template/tags/link_tag.rb b/lib/doc_template/tags/link_tag.rb index 522544e5..48e322ee 100644 --- a/lib/doc_template/tags/link_tag.rb +++ b/lib/doc_template/tags/link_tag.rb @@ -6,6 +6,7 @@ class LinkTag < BaseTag FORTHCOMING_PATH = '/forthcoming' def parse(node, opts = {}) + @opts = opts # preserve the node content and replace only the tag by the link content = node.to_s.sub(self.class.tag_with_html_regexp, link(opts)) diff --git a/lib/doc_template/tags/multiple_choice_tag.rb b/lib/doc_template/tags/multiple_choice_tag.rb index 3b3d6aed..7c690032 100644 --- a/lib/doc_template/tags/multiple_choice_tag.rb +++ b/lib/doc_template/tags/multiple_choice_tag.rb @@ -6,6 +6,7 @@ class MultipleChoiceTag < BlockTag TAG_NAME = 'multiple-choice' def parse(node, opts = {}) + @opts = opts nodes = block_nodes node nodes.each(&:remove) diff --git a/lib/doc_template/tags/opt_break_tag.rb b/lib/doc_template/tags/opt_break_tag.rb index 4dc001eb..509657bf 100644 --- a/lib/doc_template/tags/opt_break_tag.rb +++ b/lib/doc_template/tags/opt_break_tag.rb @@ -8,6 +8,7 @@ class OptBreakTag < BaseTag gdoc: 'gdoc/opt_break.html.erb' }.freeze def parse(node, opts = {}) + @opts = opts content = content_until_break node parsed_template = parse_template content, template_name(opts) @content = parse_nested parsed_template, opts diff --git a/lib/doc_template/tags/page_break_tag.rb b/lib/doc_template/tags/page_break_tag.rb index 56d7b321..8095402b 100644 --- a/lib/doc_template/tags/page_break_tag.rb +++ b/lib/doc_template/tags/page_break_tag.rb @@ -4,10 +4,11 @@ module DocTemplate module Tags class PageBreakTag < BaseTag CSS_CLASS = 'u-pdf-alwaysbreak' - TAG_NAME = /page(-|\s*)break/.freeze + TAG_NAME = /page(-|\s*)break/ TAG_SUB = '

    --GDOC-PAGE-BREAK--

    ' def parse(node, opts) + @opts = opts check_tag_soft_return(node) @content = if gdoc?(opts) TAG_SUB diff --git a/lib/doc_template/tags/pd_tag.rb b/lib/doc_template/tags/pd_tag.rb deleted file mode 100644 index 5c8d2a34..00000000 --- a/lib/doc_template/tags/pd_tag.rb +++ /dev/null @@ -1,127 +0,0 @@ -# frozen_string_literal: true - -module DocTemplate - module Tags - class PdTag < BaseTag - include Rails.application.routes.url_helpers - - CG_RE = %r{/content_guides/(\d+)/}i.freeze - PDF_HTTP_RE = %r{^https?://}i.freeze - PDF_HTTP_REPLACE_RE = /^http:/i.freeze - PDF_RE = /\.pdf$/i.freeze - TAG_NAME = 'pd' - TEMPLATES = { - default: 'pd.html.erb', - gdoc: 'gdoc/pd.html.erb' - }.freeze - TYPE_CG = 'cg' - TYPE_PDF = 'pdf' - TYPE_PODCAST = 'podcast' - TYPE_YOUTUBE = 'youtube' - - def parse(node, opts = {}) - @opts = opts - @url, @title, @description, @start, @stop = opts[:value].split(';').map(&:strip) - - unless (embeded = fetch_data) - node.remove - return self - end - - params = { - description: description, - resource: resource, - start: start, - stop: stop, - subject: subject, - title: title - }.merge(embeded) - @content = parse_template params, template_name(opts) - replace_tag node - self - end - - private - - attr_reader :description, :opts, :resource, :start, :stop, :subject, :title, :url - - def embeded_object - if url.index('soundcloud.com') - embeded_object_soundcloud - elsif url.index('youtube.com') || url.index('youtu.be') - embeded_object_youtube - elsif (id = url.scan(CG_RE).flatten.first) - embeded_object_cg(id) - elsif url.match?(PDF_RE) - embeded_object_pdf - end - end - - def embeded_object_cg(id) - return unless (cg = ContentGuide.find_by(permalink: id) || ContentGuide.find(id)) - - @description = @title.presence || cg.description - @title = cg.title - @resource = cg - - grade = cg.grades.list.first.presence - grade = cg.grades.grade_abbr(grade).presence || 'base' - - uri = URI.parse(url) - cg_url = uri.path - cg_url += "##{uri.fragment}" unless uri.fragment.blank? - - { - color: "#{cg.subject}-#{grade}", - content_guide: cg, - resource_url: content_guide_path(cg.permalink || cg.id, slug: cg.slug), - type: TYPE_CG, - url: cg_url - } - end - - def embeded_object_pdf - pdf_url = url.match?(PDF_HTTP_RE) ? url : "https://#{url}" - pdf_url = pdf_url.sub(PDF_HTTP_REPLACE_RE, 'https:') - { - type: TYPE_PDF, - url: pdf_url - } - end - - def embeded_object_soundcloud - { - content: Lcms::Engine::MediaEmbed.soundcloud(url, subject), - resource_url: (media_path(resource.id) if resource.present?), - type: TYPE_PODCAST - } - end - - def embeded_object_youtube - query = {}.tap do |q| - q[:start] = start if start.present? - q[:end] = stop if stop.present? - end.compact - youtube_url = "https://www.youtube.com/embed/#{Lcms::Engine::MediaEmbed.video_id(url)}?#{query.to_query}" - { - resource_url: (media_path(resource.id) if resource.present?), - type: TYPE_YOUTUBE, - url: youtube_url - } - end - - def fetch_data - @resource = Lcms::Engine::Resource.find_by url: url - if (title.blank? || description.blank?) && resource - @title = resource.title if title.blank? - @description = resource.teaser if description.blank? - end - - @subject = opts[:metadata].resource_subject - embeded_object - end - end - end - - Template.register_tag(Tags::PdTag::TAG_NAME, Tags::PdTag) -end diff --git a/lib/doc_template/tags/section_tag.rb b/lib/doc_template/tags/section_tag.rb index 1b2faeb9..0f8f6d14 100644 --- a/lib/doc_template/tags/section_tag.rb +++ b/lib/doc_template/tags/section_tag.rb @@ -5,9 +5,9 @@ module Tags class SectionTag < BaseTag include DocTemplate::Tags::Helpers - STUDENT_RE = /^\s*student\s*resources\s*$/i.freeze + STUDENT_RE = /^\s*student\s*resources\s*$/i TAG_NAME = 'section' - SECTION_REMOVE_RE = /\[#{TAG_NAME}:[^\]]*\]/i.freeze + SECTION_REMOVE_RE = /\[#{TAG_NAME}:[^\]]*\]/i TEMPLATES = { default: 'section.html.erb', gdoc: 'gdoc/section.html.erb' @@ -37,7 +37,6 @@ def general_params @general_params ||= { placeholder: placeholder_id, priority_description: priority_description(section), - priority_icon: priority_icon(section), react_props: { activity: { title: section.title @@ -45,8 +44,7 @@ def general_params material_ids: @section.material_ids, color: @section[:use_color] }, - section: section, - section_icons: section_icons(section) + section: } end @@ -56,12 +54,6 @@ def parse_content(node, template) parsed_template = parse_template(params, template) parse_nested parsed_template, opts end - - def section_icons(section) - return [] if section.icons.nil? - - section.icons.map { |icon| "#{ICON_PATH}/#{icon}.png" } - end end end diff --git a/lib/doc_template/tags/smp_tag.rb b/lib/doc_template/tags/smp_tag.rb index d0f8327b..c7837527 100644 --- a/lib/doc_template/tags/smp_tag.rb +++ b/lib/doc_template/tags/smp_tag.rb @@ -8,6 +8,7 @@ class SmpTag < BlockTag gdoc: 'gdoc/smp.html.erb' }.freeze def parse(node, opts = {}) + @opts = opts nodes = block_nodes node nodes.each(&:remove) diff --git a/lib/doc_template/tags/source_tag.rb b/lib/doc_template/tags/source_tag.rb index a1039069..d4354eb6 100644 --- a/lib/doc_template/tags/source_tag.rb +++ b/lib/doc_template/tags/source_tag.rb @@ -7,6 +7,7 @@ class SourceTag < BaseTag TEMPLATE = 'source.html.erb' def parse(node, opts = {}) + @opts = opts # we have to collect all the next siblings until next activity-metadata content = content_until_break node content = parse_nested content.to_s, opts diff --git a/lib/doc_template/tags/standard_tag.rb b/lib/doc_template/tags/standard_tag.rb index e9bba98f..748fbb78 100644 --- a/lib/doc_template/tags/standard_tag.rb +++ b/lib/doc_template/tags/standard_tag.rb @@ -5,14 +5,15 @@ module Tags class StandardTag < BaseTag include ERB::Util - STANDARD_RE = /[^\[\]]*\[(ela\.)?((rl|ri|rf|w|sl|l)\.[^\]]+)\]/i.freeze # [rl.2.2a.2] - TAG_NAME = /(ela\.)?((rl|ri|rf|w|sl|l)\.[^\]]+)/.freeze # RL.2.4 or ELA.RL.2.4 - TAG_RE = /\[[^\]]*\]/.freeze + STANDARD_RE = /[^\[\]]*\[(ela\.)?((rl|ri|rf|w|sl|l)\.[^\]]+)\]/i # [rl.2.2a.2] + TAG_NAME = /(ela\.)?((rl|ri|rf|w|sl|l)\.[^\]]+)/ # RL.2.4 or ELA.RL.2.4 + TAG_RE = /\[[^\]]*\]/ TAG_SEPARATOR = '[separator]' TEMPLATES = { default: 'standard.html.erb', gdoc: 'gdoc/standard.html.erb' }.freeze def parse(node, opts) + @opts = opts @content = render_template node, opts loop do break unless STANDARD_RE =~ @content @@ -36,7 +37,7 @@ def parse(node, opts) # TODO: Extract to the parent class def fetch_data(source) @preserved_style = %r{]*)>[^<]+$}.match(source).try(:[], 1) - {}.tap do |result| + {}.tap do |result| # steep:ignore data = source.squish .sub(TAG_RE, TAG_SEPARATOR) .split(TAG_SEPARATOR, 2) @@ -51,7 +52,7 @@ def fetch_data(source) def fetch_description(text) return unless (matches = STANDARD_RE.match text) - name = matches[2].downcase.to_sym + name = matches[2].to_s.downcase.to_sym Lcms::Engine::Standard.search_by_name(name).first.try(:description) end diff --git a/lib/doc_template/tags/table_preserve_alignment_tag.rb b/lib/doc_template/tags/table_preserve_alignment_tag.rb index 8b3ad276..5588d64b 100644 --- a/lib/doc_template/tags/table_preserve_alignment_tag.rb +++ b/lib/doc_template/tags/table_preserve_alignment_tag.rb @@ -3,10 +3,11 @@ module DocTemplate module Tags class TablePreserveAlignmentTag < BaseTag - STYLE_RE = /text-align:(left|center|right)/i.freeze + STYLE_RE = /text-align:(left|center|right)/i TAG_NAME = 'table-preserve-alignment' - def parse(node, _options = {}) + def parse(node, opts = {}) + @opts = opts if (table = find_table node) # inside cells for each `p` with `text-align` css param we add specific class table.xpath('.//p').each do |el| diff --git a/lib/doc_template/tags/table_tag.rb b/lib/doc_template/tags/table_tag.rb index 422f7235..4362ef0f 100644 --- a/lib/doc_template/tags/table_tag.rb +++ b/lib/doc_template/tags/table_tag.rb @@ -4,7 +4,7 @@ module DocTemplate module Tags class TableTag < BaseTag def parse(node, opts = {}) - unless (table = node.ancestors('table').first) + unless (table = ::DocTemplate::Tables::Base.flatten_table(node.ancestors('table').first)) raise ::Lcms::Engine::DocumentError, "Tag #{self.class::TAG_NAME.upcase} placed outside table" end diff --git a/lib/doc_template/tags/task_tag.rb b/lib/doc_template/tags/task_tag.rb index b44338b2..ad458364 100644 --- a/lib/doc_template/tags/task_tag.rb +++ b/lib/doc_template/tags/task_tag.rb @@ -6,6 +6,7 @@ class TaskTag < BaseTag TAG_NAME = 'task' def parse(node, opts = {}) + @opts = opts @content = "

    Task #{opts[:value]}

    " replace_tag node self diff --git a/lib/doc_template/tags/vocab_tag.rb b/lib/doc_template/tags/vocab_tag.rb index 4414ff48..da587fe6 100644 --- a/lib/doc_template/tags/vocab_tag.rb +++ b/lib/doc_template/tags/vocab_tag.rb @@ -7,6 +7,7 @@ class VocabTag < BlockTag TEMPLATE = 'vocab.html.erb' def parse(node, opts = {}) + @opts = opts nodes = block_nodes node nodes.each(&:remove) diff --git a/lib/doc_template/tags/vocabulary_tag.rb b/lib/doc_template/tags/vocabulary_tag.rb index 72588cc3..8381271a 100644 --- a/lib/doc_template/tags/vocabulary_tag.rb +++ b/lib/doc_template/tags/vocabulary_tag.rb @@ -16,12 +16,12 @@ def parse_table(table) private def fetch_content(node) - [].tap do |result| + [].tap do |result| # steep:ignore # omit the first row - cur_section = nil + cur_section = {} # : Hash[Symbol, Array[Hash[untyped, untyped]]] node.xpath('.//tr[position() > 1]').each do |tr| if (td_header = tr.at_xpath "./td[@colspan = '2']") - result << cur_section if cur_section.present? + result << cur_section unless cur_section.empty? cur_section = { title: td_header.text } elsif cur_section.present? cur_section[:words] ||= [] diff --git a/lib/doc_template/tags/white_placeholder_tag.rb b/lib/doc_template/tags/white_placeholder_tag.rb index a0452fbe..8e2d660f 100644 --- a/lib/doc_template/tags/white_placeholder_tag.rb +++ b/lib/doc_template/tags/white_placeholder_tag.rb @@ -10,6 +10,7 @@ class WhitePlaceholderTag < BlockTag }.freeze def parse(node, opts = {}) + @opts = opts content = block_nodes(node).map do |n| n.remove n.to_html @@ -19,9 +20,9 @@ def parse(node, opts = {}) params = { content: parse_nested(content, opts), - css_class: css_class, + css_class:, subject: opts[:metadata].subject, - title: title + title: } @content = parse_template params, template_name(opts) diff --git a/lib/doc_template/template.rb b/lib/doc_template/template.rb index e5f69423..f1778114 100644 --- a/lib/doc_template/template.rb +++ b/lib/doc_template/template.rb @@ -4,6 +4,7 @@ module DocTemplate class Template class TagRegistry include Enumerable + delegate :delete, to: :@tags def initialize @@ -31,9 +32,14 @@ def load_class(klass_name) end end - attr_reader :css_styles, :metadata_service, :toc + attr_reader :css_styles, :documents, :metadata_service, :toc class << self + # + # @param [String] source + # @param [Symbol] type + # @return [DocTemplate::Template] + # def parse(source, type: :document) new(type).parse(source) end @@ -93,9 +99,9 @@ def parse(source) @documents[context_type] = ::DocTemplate::Document.parse(@content.dup, options) @documents[context_type].parts << { content: render(options), - context_type: context_type, - data: {}, - materials: [], + context_type:, + data: {}, # : Hash[untyped, untyped] + materials: [], # : Array[untyped] optional: false, part_type: :layout, placeholder: nil @@ -111,10 +117,6 @@ def parts @documents.values.flat_map(&:parts) end - def prereq? - metadata['type'].to_s.casecmp('prereq').zero? - end - def remove_part(type, context_type) result = nil @documents.each_key do |k| diff --git a/lib/doc_template/templates/activity.html.erb b/lib/doc_template/templates/activity.html.erb index 63f3a7b2..efa11b38 100644 --- a/lib/doc_template/templates/activity.html.erb +++ b/lib/doc_template/templates/activity.html.erb @@ -1,4 +1,4 @@ -
    > @@ -54,22 +54,7 @@ <% if @tmpl[:activity].activity_standard.present? %>

    Standards: - <% (standard_infos = @tmpl[:activity].activity_standard_info).each_with_index do |activity, idx| %> - <% id = "cg-k_#{SecureRandom.hex(4)}" %> - <%= activity[:standard] %><%= ',' if idx + 1 < standard_infos.size %> - <% if activity[:description] %> - - <%= activity[:standard] %>
    - <%= h activity[:description] %> -
    - <% end %> - <% end %> + <%= @tmpl[:activity].activity_standard %>

    <% end %>
    diff --git a/lib/doc_template/templates/gdoc/activity.html.erb b/lib/doc_template/templates/gdoc/activity.html.erb index 8b13c0c8..bc4aa914 100644 --- a/lib/doc_template/templates/gdoc/activity.html.erb +++ b/lib/doc_template/templates/gdoc/activity.html.erb @@ -11,13 +11,13 @@
    <% end %> - +
    -
    + <% if @tmpl[:activity].activity_type.present? %> - @@ -27,9 +27,6 @@

    <%= 'Optional: ' if @tmpl[:activity].optional %> <%= @tmpl[:activity].activity_title %> - <% if @tmpl[:activity].activity_priority.present? %> - - <% end %>

    @@ -43,10 +40,10 @@ <% end %> <% if @tmpl[:activity].activity_guidance.present? %> -
    + <%= @tmpl[:activity].activity_type.upcase %>
    <%= @tmpl[:activity].time.zero? ? '—' : "#{@tmpl[:activity].time} mins" %>
    +
    diff --git a/lib/doc_template/templates/group-math.html.erb b/lib/doc_template/templates/group-math.html.erb index 34759859..cf47e633 100644 --- a/lib/doc_template/templates/group-math.html.erb +++ b/lib/doc_template/templates/group-math.html.erb @@ -1,15 +1,7 @@
    - <% if @tmpl[:foundational_skills] %> -
    -
    -
    Foundational Skills Lesson
    -
    -
    - <% else %> -
    - <% end %> +
    -

    +

    <%= @tmpl[:section].title %>
    <%= @tmpl[:section].time.zero? ? '—' : "#{@tmpl[:section].time} mins" %> @@ -21,36 +13,10 @@ <% end %> <% if @tmpl[:section].try(:lesson_objective).present? %> - <% unless @tmpl[:foundational_skills] %> -

    Lesson Summary:

    - <% end %> +

    Lesson Summary:

    <%= @tmpl[:section].lesson_objective %>

    <% end %> - <% if @tmpl[:foundational_skills] %> - <% if (standards = @tmpl[:section].section_standard_info).any? %> -

    - Standards: - <% standards.each_with_index do |data, idx| %> - <% id = "cg-k_#{SecureRandom.hex(4)}" %> - <%= data[:standard] %><%= ',' if idx + 1 < standards.size %> - <% if data[:description] %> - - <%= data[:standard] %>
    - <%= h data[:description] %> -
    - <% end %> - <% end %> -

    - <% end %> - <% end %> - <%= @tmpl[:before_materials] %> <%= materials_container @tmpl[:react_props] if @tmpl[:with_materials] %> diff --git a/lib/doc_template/templates/pd.html.erb b/lib/doc_template/templates/pd.html.erb deleted file mode 100644 index 805008de..00000000 --- a/lib/doc_template/templates/pd.html.erb +++ /dev/null @@ -1,70 +0,0 @@ -
    -
    -
    professional development
    -
    -
    - -
    - <% if @tmpl[:title].present? %> -

    <%= @tmpl[:title] %>

    - <% end %> - - <% if @tmpl[:type] == DocTemplate::Tags::PdTag::TYPE_CG %> -
    -
    -
    -
    - <% %> -

    UnboundEd <%= @tmpl[:content_guide].subject == 'math' ? 'Math' : 'ELA' %> Guide

    -

    <%= @tmpl[:title] %>

    -

    - - View Full Guide - -

    -
    -
    - <% elsif @tmpl[:type] == DocTemplate::Tags::PdTag::TYPE_PDF %> -
    -
    -
    -
    -
    -
    -
    -
    - <% elsif @tmpl[:type] == DocTemplate::Tags::PdTag::TYPE_PODCAST %> -
    -
    -
    - <%= @tmpl[:content] %> -
    -
    -
    -
    - <% elsif @tmpl[:type] == DocTemplate::Tags::PdTag::TYPE_YOUTUBE %> -
    -
    -
    - -
    -
    -
    -
    - <% end %> - - <% if @tmpl[:description].present? %> -
    -

    Description

    -

    <%= @tmpl[:description] %>

    -
    - <% end %> -
    - -
    -

    Minimize

    -
    -
    diff --git a/lib/doc_template/xpath_functions.rb b/lib/doc_template/xpath_functions.rb index 55802b2a..2e72463a 100644 --- a/lib/doc_template/xpath_functions.rb +++ b/lib/doc_template/xpath_functions.rb @@ -3,7 +3,8 @@ module DocTemplate class XpathFunctions def case_insensitive_equals(node_set, str_to_match) - return node_set.to_s.squish.casecmp(str_to_match).zero? if node_set.is_a?(String) + # remove spaces (will be added if there were sub elements) and compare case insensitively + return node_set.to_s.squish.gsub(/\s*/, '').casecmp(str_to_match).zero? if node_set.is_a?(String) node_set.find_all { |node| node.to_s.squish.casecmp(str_to_match).zero? } end @@ -13,7 +14,7 @@ def case_insensitive_contains(node_set, str_to_match) end def case_regular(node_set, re_to_match) - node_set.to_s.match?(Regexp.new(re_to_match, true)) ? node_set : [] + node_set.to_s.match?(Regexp.new(re_to_match)) ? node_set : [] end end end diff --git a/lib/document_exporter/base.rb b/lib/document_exporter/base.rb index f3fee5c5..eee4ccad 100644 --- a/lib/document_exporter/base.rb +++ b/lib/document_exporter/base.rb @@ -19,20 +19,25 @@ def export # Take into consideration that in one component materilas are uniq. So # just the first occurence of exluded material is removed # - def included_materials(context_type: :default) + def included_materials(context_type: :default) # rubocop:todo Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity parts = context_type == :default ? @document.document_parts.default : @document.document_parts.gdoc - @included_materials ||= [].tap do |result| - # Take non optional materials ONLY + @included_materials ||= [].tap do |result| # steep:ignore + # Take non-optional materials ONLY result.concat parts.general.pluck(:materials).flatten.compact @options[:excludes]&.each do |x| next unless (part = parts.find_by anchor: x) - # if it's optional activity - add it - # otherwise - delete it from result + # if it's an optional activity - add it + # otherwise - delete it from a result part.materials.compact.each do |id| - part.optional? ? result.push(id) : result.delete_at(result.index(id)) + result.push(id) && next if part.optional? + + index = result.index(id) + next if index.nil? + + result.delete_at(index) end end end.map(&:to_i) @@ -52,7 +57,7 @@ def render_template(path, layout:) field = path.starts_with?('/') ? :file : :template Lcms::Engine::ApplicationController.render( field => path, - layout: layout, + layout:, assigns: { document: @document, options: @options } ) end diff --git a/lib/document_exporter/docx.rb b/lib/document_exporter/docx.rb deleted file mode 100644 index edf4723d..00000000 --- a/lib/document_exporter/docx.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -require 'pandoc-ruby' - -module DocumentExporter - class Docx - def initialize(document) - @document = document - end - - def export - PandocRuby.convert content, from: :html, to: :docx - end - - private - - def content - @content ||= ApplicationController.render( - layout: 'ld_docx', - locals: { :@document => @document }, - template: 'documents/docx/export' - ) - end - end -end diff --git a/lib/document_exporter/gdoc/base.rb b/lib/document_exporter/gdoc/base.rb index 3d927322..48412e24 100644 --- a/lib/document_exporter/gdoc/base.rb +++ b/lib/document_exporter/gdoc/base.rb @@ -16,7 +16,8 @@ class Base < DocumentExporter::Base send_timeout_sec: GOOGLE_API_CLIENT_UPLOAD_TIMEOUT } }.freeze - VERSION_RE = /_v\d+$/i.freeze + GOOGLE_API_RATE_RETRIABLE_ERRORS = [Google::Apis::ServerError, Google::Apis::RateLimitError].freeze + VERSION_RE = /_v\d+$/i attr_reader :document, :options @@ -32,10 +33,10 @@ def url_for(file_id) def create_gdoc_folders(folder) id = drive_service.create_folder(folder) - folders = [id] + folders = Array.wrap(id) folders << drive_service.create_folder(DocumentExporter::Gdoc::TeacherMaterial::FOLDER_NAME, id) folders << drive_service.create_folder(DocumentExporter::Gdoc::StudentMaterial::FOLDER_NAME, id) - folders.each(&method(:delete_previous_versions_from)) + folders.each { |f| delete_previous_versions_from(f) } end def export @@ -44,19 +45,29 @@ def export file_name = "#{@options[:prefix]}#{document.base_filename}" file_params = { name: file_name, mime_type: 'application/vnd.google-apps.document' } - file_params[:parents] = [parent_folder] if parent_folder.present? - metadata = Google::Apis::DriveV3::File.new(file_params) + file_params[:parents] = Array.wrap(parent_folder) if parent_folder.present? + metadata = Google::Apis::DriveV3::File.new(**file_params) params = { content_type: 'text/html', upload_source: StringIO.new(content) }.merge(GOOGLE_API_UPLOAD_OPTIONS) - @id = if file_id.blank? - drive_service.service.create_file(metadata, params) - else - drive_service.service.update_file(file_id, metadata, params) - end.id + @id = Retriable.retriable(base_interval: ENV.fetch('GOOGLE_API_CLIENT_UPLOAD_RATE_BASE_INTERVAL', 5).to_i, + multiplier: ENV.fetch( + 'GOOGLE_API_CLIENT_UPLOAD_RATE_MULTIPLIER', 2 + ).to_f, + max_interval: ENV.fetch( + 'GOOGLE_API_CLIENT_UPLOAD_RATE_MAX_INTERVAL', 900 + ).to_i, + on: GOOGLE_API_RATE_RETRIABLE_ERRORS, + tries: GOOGLE_API_CLIENT_UPLOAD_RETRIES) do + if file_id.present? + drive_service.service.update_file(file_id, metadata, **params) + else + drive_service.service.create_file(metadata, **params) + end.id + end post_processing @@ -79,11 +90,13 @@ def export_to(folder_id, file_id: nil) upload_source: StringIO.new(content) }.merge(GOOGLE_API_UPLOAD_OPTIONS) - @id = if file_id.present? - drive_service.service.update_file(file_id, metadata, params) - else - drive_service.service.create_file(metadata, params) - end.id + @id = Retriable.retriable(base_interval: 1, tries: GOOGLE_API_CLIENT_UPLOAD_RETRIES) do + if file_id.present? + drive_service.service.update_file(file_id, metadata, **params) + else + drive_service.service.create_file(metadata, **params) + end.id + end post_processing @@ -101,7 +114,7 @@ def base_path(name) end def content - render_template template_path('show'), layout: 'ld_gdoc' + render_template template_path('show'), layout: 'gdoc' end # @@ -128,7 +141,7 @@ def gdoc_folder def gdoc_folder_tmp(material_ids) file_ids = material_ids.map do |id| - document.links['materials']&.dig(id.to_s)&.dig('gdoc')&.gsub(/.*id=/, '') + document.links['materials']&.dig(id.to_s, 'gdoc')&.gsub(/.*id=/, '') end @options[:subfolders] = [self.class::FOLDER_NAME] diff --git a/lib/document_exporter/gdoc/material.rb b/lib/document_exporter/gdoc/material.rb index 0914293c..60272444 100644 --- a/lib/document_exporter/gdoc/material.rb +++ b/lib/document_exporter/gdoc/material.rb @@ -21,7 +21,7 @@ def export def handle_vertical_text data = TextToImage.new(vertical_text, rotate: -90).raw filename = "documents/#{document.base_filename}-vtext.png" - url = S3Service.upload filename, data + url = Lcms::Engine::S3Service.upload filename, data @options[:vertical_text_image_url] = url end diff --git a/lib/document_exporter/pdf/base.rb b/lib/document_exporter/pdf/base.rb index 8dc6350d..fe184053 100644 --- a/lib/document_exporter/pdf/base.rb +++ b/lib/document_exporter/pdf/base.rb @@ -12,7 +12,7 @@ def export end def pdf_content - content = render_template template_path('show'), layout: 'lcms/engine/cg_pdf' + content = render_template template_path('show'), layout: 'lcms/engine/pdf' content.gsub(/(___+)/, '\1') end @@ -60,7 +60,7 @@ def pdf_params disable_smart_shrinking: true, disposition: 'attachment', footer: { - content: render_template(base_path('_footer'), layout: 'lcms/engine/cg_plain_pdf'), + content: render_template(base_path('_footer'), layout: 'lcms/engine/pdf_plain'), line: false, spacing: 2 }, diff --git a/lib/document_exporter/thumbnail.rb b/lib/document_exporter/thumbnail.rb index 01cec294..3545a6d4 100644 --- a/lib/document_exporter/thumbnail.rb +++ b/lib/document_exporter/thumbnail.rb @@ -10,8 +10,8 @@ def initialize(content) def export pdf = ::MiniMagick::Image.read(@content) - @width = pdf.pages[0][:width] / THUMBNAIL_RATIO - @height = pdf.pages[0][:height] / THUMBNAIL_RATIO + @width = pdf.pages[0].width / THUMBNAIL_RATIO + @height = pdf.pages[0].height / THUMBNAIL_RATIO pdf.format('jpg', 0, density: 300, background: '#fff', alpha: 'remove', resize: "#{@width}x#{@height}").to_blob end diff --git a/lib/document_renderer/part.rb b/lib/document_renderer/part.rb index 84182ea5..5b666ec0 100644 --- a/lib/document_renderer/part.rb +++ b/lib/document_renderer/part.rb @@ -2,7 +2,7 @@ module DocumentRenderer class Part - PART_RE = /{{[^}]+}}/.freeze + PART_RE = /{{[^}]+}}/ class << self def call(content, options) diff --git a/lib/elasticsearch/persistence/repository/response/results.rb b/lib/elasticsearch/persistence/repository/response/results.rb deleted file mode 100644 index 2ae44d31..00000000 --- a/lib/elasticsearch/persistence/repository/response/results.rb +++ /dev/null @@ -1,78 +0,0 @@ -# frozen_string_literal: true - -# monkey patch ES results to handle pagination -module Elasticsearch - module Persistence - module Repository - module Response # :nodoc: - class Results - include Enumerable - - attr_reader :repository, :response - - def initialize(repository, response, options = {}) - @repository = repository - @response = Elasticsearch::Model::HashWrapper.new(response) - @options = options - end - - def method_missing(method_name, *arguments, &block) # rubocop:disable Style/MissingRespondToMissing - results.respond_to?(method_name) ? results.__send__(method_name, *arguments, &block) : super - end - - def respond_to?(method_name, include_private = false) # rubocop:disable Style/OptionalBooleanParameter - results.respond_to?(method_name) || super - end - - def total - response['hits']['total'] - end - - def max_score - response['hits']['max_score'] - end - - def each_with_hit(&block) - results.zip(response['hits']['hits']).each(&block) - end - - def map_with_hit(&block) - results.zip(response['hits']['hits']).map(&block) - end - - def results - @results ||= response['hits']['hits'].map do |document| - repository.deserialize(document.to_hash) - end - end - - def paginate(options) - @pagination_attrs = { - total_pages: (total.to_f / options[:per_page]).ceil, - current_page: options[:page], - per_page: options[:per_page], - total_entries: total - } - self - end - - def total_pages - @pagination_attrs[:total_pages] - end - - def current_page - @pagination_attrs[:current_page] - end - - def per_page - @pagination_attrs[:per_page] - end - - def total_entries - @pagination_attrs[:total_entries] - end - end - end - end - end -end diff --git a/lib/generators/lcms/engine/install/install_generator.rb b/lib/generators/lcms/engine/install/install_generator.rb index b35f922f..e0238a44 100644 --- a/lib/generators/lcms/engine/install/install_generator.rb +++ b/lib/generators/lcms/engine/install/install_generator.rb @@ -6,17 +6,17 @@ module Generators class InstallGenerator < Rails::Generators::Base desc 'Copies all required configuration files.' - source_root File.expand_path('templates', __dir__) + source_root File.expand_path('templates', __dir__.to_s) def copy_config_files - directory File.expand_path('templates/config', __dir__), Rails.root.join('config') + directory File.expand_path('templates/config', __dir__.to_s), Rails.root.join('config') end def update_gemfile # Required by lcms-engine because of unpublished gems are not automatically installed with the parent gem gem 'wicked_pdf', git: 'https://github.com/learningtapestry/wicked_pdf.git', branch: 'puppeteer-support', - ref: '964a090' + ref: '50d961e' end end end diff --git a/lib/generators/lcms/engine/install/templates/config/lcms-admin.yml b/lib/generators/lcms/engine/install/templates/config/lcms-admin.yml index 7a9863b6..e4508855 100644 --- a/lib/generators/lcms/engine/install/templates/config/lcms-admin.yml +++ b/lib/generators/lcms/engine/install/templates/config/lcms-admin.yml @@ -18,14 +18,11 @@ # TODO: Check the handle of chained path here # material_path: 'lcms_engine.material_path' -# TODO: Check the default values handling -# Redefine views +# Redefine views and paths #documents: # index: '/lcms/engine/admin/documents/index' # view_links: # - '/documents/:id' - -# TODO: Check the default values handling #materials: # index: '/lcms/engine/admin/materials/index' # view_links: diff --git a/lib/generators/lcms/engine/install/templates/config/lcms.yml b/lib/generators/lcms/engine/install/templates/config/lcms.yml index 1ad32d85..335643f5 100644 --- a/lib/generators/lcms/engine/install/templates/config/lcms.yml +++ b/lib/generators/lcms/engine/install/templates/config/lcms.yml @@ -49,3 +49,4 @@ sanitizer: '::Lcms::Engine::HtmlSanitizer' queries: document: '::Lcms::Engine::AdminDocumentsQuery' + material: '::Lcms::Engine::AdminMaterialsQuery' diff --git a/lib/generators/lcms/engine/install/templates/config/tags.yml b/lib/generators/lcms/engine/install/templates/config/tags.yml index 8306f63c..981ca87b 100644 --- a/lib/generators/lcms/engine/install/templates/config/tags.yml +++ b/lib/generators/lcms/engine/install/templates/config/tags.yml @@ -17,7 +17,6 @@ assess: - AssessTag - EtTag - OptBreakTag - - GroupTag - JsTag - KeyTag - PhotoTag @@ -35,7 +34,6 @@ et: - AssessTag - EtTag - OptBreakTag - - GroupTag - JsTag - KeyTag - PhotoTag @@ -45,16 +43,13 @@ et: - ThTag group: stop_materials_tags: - - GroupTag - SectionTag stop_tags: - - GroupTag js: stop_tags: - AssessTag - EtTag - OptBreakTag - - GroupTag - JsTag - KeyTag - PhotoTag @@ -67,7 +62,6 @@ key: - AssessTag - EtTag - OptBreakTag - - GroupTag - JsTag - KeyTag - PhotoTag @@ -81,7 +75,6 @@ materials: - ActivityMetadataTypeTag optbreak: stop_tags: - - GroupTag - SectionTag pdf_templates_path: photo: @@ -89,7 +82,6 @@ photo: - AssessTag - EtTag - OptBreakTag - - GroupTag - JsTag - KeyTag - PhotoTag @@ -126,7 +118,6 @@ rubric: - AssessTag - EtTag - OptBreakTag - - GroupTag - JsTag - KeyTag - PhotoTag @@ -140,14 +131,12 @@ section: - 'This is a Priority 2 activity that can be moved or adapted if running short on time. It is less tightly connected to the focus objective(s) of the lesson or unit.' stop_tags: - OptBreakTag - - GroupTag - SectionTag sh: stop_tags: - AssessTag - EtTag - OptBreakTag - - GroupTag - JsTag - KeyTag - PhotoTag @@ -166,7 +155,6 @@ th: - AssessTag - EtTag - OptBreakTag - - GroupTag - JsTag - KeyTag - PhotoTag diff --git a/lib/lcms/engine.rb b/lib/lcms/engine.rb index b1de5f13..ba2397de 100644 --- a/lib/lcms/engine.rb +++ b/lib/lcms/engine.rb @@ -2,22 +2,20 @@ module Lcms module Engine - class << self - # TODO: Set the correct prefix after renaming all the tables? - def table_name_prefix - '' - end + # Maximum allowed characters for a flash message. + # If a message exceeds this limit, it should be handled appropriately. + FLASH_MESSAGE_MAX_CHAR = 2048 + + # Prefix used when storing flash messages in Redis. This helps in identifying + # eys related to flash messages in the Redis store. + FLASH_REDIS_PREFIX = 'flash_key:' - def webpacker - @webpacker ||= - begin - root_path = Pathname.new File.expand_path('../..', __dir__) - ::Webpacker::Instance.new( - root_path: root_path, - config_path: root_path.join('config/webpacker.yml') - ) - end - end + # Returns the table name prefix for the current module or class. + # + # @return [String] The table name prefix. + def self.table_name_prefix + # TODO: Set the correct prefix after renaming all the tables? + '' end end end diff --git a/lib/lcms/engine/engine.rb b/lib/lcms/engine/engine.rb index b3cd3fc6..bdfb0717 100644 --- a/lib/lcms/engine/engine.rb +++ b/lib/lcms/engine/engine.rb @@ -7,24 +7,15 @@ require 'carrierwave/orm/activerecord' require 'closure_tree' require 'devise' -require 'jquery-rails' -require 'pdfjs_viewer-rails' require 'ransack' -require 'react-rails' require 'resque/server' -require 'turbolinks' require 'validate_url' require 'virtus' require 'will_paginate' -require 'will_paginate-bootstrap' +require 'will_paginate-bootstrap-style' # UI and asset specific gems have to be required for host app to have access to its assets require 'ckeditor' -require 'font-awesome-sass' -require 'foundation-rails' -require 'js-routes' -require 'nested_form' -require 'webpacker' # LearningTapestry gems require 'lt/google/api' @@ -45,14 +36,13 @@ class Engine < ::Rails::Engine config.i18n.load_path += Dir[config.root.join('config', 'locales', '**', '*.yml')] - config.middleware.insert_after ActionDispatch::Static, Rack::LiveReload if ENV['ENABLE_LIVERELOAD'] - - config.assets.paths << "#{config.root}/public/javascripts" + config.assets.paths << config.root.join('node_modules/bootstrap-icons/font') + config.assets.paths << config.root.join('node_modules/@fortawesome/fontawesome-free/webfonts') config.after_initialize do config.active_job.queue_adapter = :resque - if ::Rails.env.development? || ::Rails.env.test? + if Gem.loaded_specs['bullet'].present? && (::Rails.env.development? || ::Rails.env.test?) require 'bullet' ::Bullet.enable = true @@ -72,10 +62,7 @@ class Engine < ::Rails::Engine #{Rails.root}/app/**/lcms/engine/*_decorator*.rb ] - Dir - .glob(decorators) - .sort - .each(&method(:require)) + Dir.glob(decorators).each { require _1 } rescue ActiveRecord::NoDatabaseError puts 'ActiveRecord::NoDatabaseError thrown!' end @@ -98,27 +85,11 @@ class Engine < ::Rails::Engine app.config.assets.precompile += %w(lcms_engine_manifest.js ckeditor/config.js) end - initializer 'webpacker.proxy' do |app| - insert_middleware = - begin - Lcms::Engine.webpacker.config.dev_server.present? - rescue StandardError - nil - end - next unless insert_middleware - - app.middleware.insert_before( - 0, Webpacker::DevServerProxy, - ssl_verify_none: true, - webpacker: Lcms::Engine.webpacker - ) - end - - # Serves the engine's webpack when requested - initializer 'webpacker.static' do |app| + # Serves the engine's built assets when requested + initializer 'lcms_engine.assets.static' do |app| app.config.middleware.use( Rack::Static, - urls: ['/lcms_engine_packs'], + urls: ['/lcms-engine-assets'], root: File.join(Gem.loaded_specs['lcms-engine'].full_gem_path, 'public') ) end diff --git a/lib/lcms/engine/test/resource_helpers.rb b/lib/lcms/engine/test/resource_helpers.rb deleted file mode 100644 index b695efa6..00000000 --- a/lib/lcms/engine/test/resource_helpers.rb +++ /dev/null @@ -1,83 +0,0 @@ -# frozen_string_literal: true - -module Lcms - module Engine - module Test - module ResourceHelpers - def resources_sample_collection - # ELA G2 => 2 lessons - 2.times do |i| - pos = i + 1 - dir = ['ela', 'grade 2', 'module 1', 'unit 1', "lesson #{pos}"] - create(:resource, - title: "Test Resource ELA G2 L#{pos}", - metadata: ::Lcms::Engine::Resource.metadata_from_dir(dir)) - end - - # ELA G6 => 6 lessons - 6.times do |i| - pos = i + 1 - dir = ['ela', 'grade 6', 'module 1', 'unit 1', "lesson #{pos}"] - create(:resource, - title: "Test Resource ELA G6 L#{pos}", - metadata: ::Lcms::Engine::Resource.metadata_from_dir(dir)) - end - - # Math G4 => 4 lessons - 4.times do |i| - pos = i + 1 - dir = ['math', 'grade 4', 'module 1', 'unit 1', "lesson #{pos}"] - create(:resource, - title: "Test Resource Math G4 L#{pos}", - metadata: ::Lcms::Engine::Resource.metadata_from_dir(dir)) - end - - # Math G7 => 7 lessons - 7.times do |i| - pos = i + 1 - dir = ['math', 'grade 7', 'module 1', 'unit 1', "lesson #{pos}"] - create(:resource, - title: "Test Resource Math G7 L#{pos}", - metadata: ::Lcms::Engine::Resource.metadata_from_dir(dir)) - end - end - - def build_resources_chain(curr) - dir = [] - parent = nil - ::Lcms::Engine::Resource.hierarchy.each_with_index do |type, idx| - next unless curr[idx] - - dir.push curr[idx] - res = create(:resource, - title: "Test Resource #{dir.join('|')}", - short_title: curr[idx], - curriculum_type: type, - parent: parent, - metadata: ::Lcms::Engine::Resource.metadata_from_dir(dir)) - parent = res - end - end - - def build_or_return_resources_chain(curr) - dir = [] - parent = nil - ::Lcms::Engine::Resource.hierarchy.each_with_index do |type, idx| - next unless curr[idx] - - dir.push curr[idx] - res = ::Lcms::Engine::Resource.find_by(short_title: curr[idx]) || - FactoryBot.create(:resource, - title: "Test Resource #{dir.join('|')}", - short_title: curr[idx], - curriculum_type: type, - parent: parent, - metadata: ::Lcms::Engine::Resource.metadata_from_dir(dir)) - parent = res - end - parent - end - end - end - end -end diff --git a/lib/lt/lcms/metadata/base_service.rb b/lib/lt/lcms/metadata/base_service.rb index 24793785..2e978334 100644 --- a/lib/lt/lcms/metadata/base_service.rb +++ b/lib/lt/lcms/metadata/base_service.rb @@ -15,7 +15,8 @@ def materials_metadata def options_for(context) raise 'Metadata is empty' unless metadata.present? - {}.tap do |result| + {}.tap do |result| # steep:ignore + # @type var result: Hash[untyped, untyped] result.merge!(lesson_options) unless material? result[:context_type] = context end diff --git a/lib/lt/lcms/metadata/context.rb b/lib/lt/lcms/metadata/context.rb index d264c7bd..07b7f872 100644 --- a/lib/lt/lcms/metadata/context.rb +++ b/lib/lt/lcms/metadata/context.rb @@ -9,7 +9,7 @@ module Metadata class Context attr_reader :context - NUM_RE = /\d+/.freeze + NUM_RE = /\d+/ class << self # @@ -65,13 +65,14 @@ def metadata 'grade' => grade, 'module' => mod, 'unit' => unit, - 'lesson' => lesson, - 'assessment' => (type if assessment?) + 'lesson' => lesson }.compact.stringify_keys end + # + # @return [Lcms::Engine::Resource] + # def find_or_create_resource - # rubocop:disable Metrics/BlockLength ::Lcms::Engine::Resource.with_advisory_lock('find_or_create_resource') do # if the resource exists, return it resource = ::Lcms::Engine::Resource.tree.find_by_directory(directory) @@ -96,20 +97,11 @@ def find_or_create_resource next end - if mid_assessment? - set_mid_assessment_position(parent, resource) - elsif prerequisite? - set_prerequisite_position(parent, resource) - elsif opr? - set_opr_position(parent, resource) - else - set_lesson_position(parent, resource) - end + set_lesson_position(parent, resource) end update resource end - # rubocop:enable Metrics/BlockLength end private @@ -122,13 +114,13 @@ def build_new_resource(parent, name, index) dir = directory[0..index] curriculum_type = parent.nil? ? ::Lcms::Engine::Resource.hierarchy.first : parent.next_hierarchy_level resource = ::Lcms::Engine::Resource.new( - curriculum_type: curriculum_type, + curriculum_type:, level_position: parent&.children&.size.to_i, metadata: metadata.to_a[0..index].to_h, parent_id: parent&.id, resource_type: :resource, short_title: name, - curriculum_id: ::Lcms::Engine::Curriculum.default.id + curriculum_id: ::Lcms::Engine::Curriculum.default&.id ) if last_item?(index) resource.tag_list = tag_list if resource.lesson? @@ -141,31 +133,16 @@ def build_new_resource(parent, name, index) end def default_title(curr = nil) - if assessment? - mid_assessment? ? 'Mid-Unit Assessment' : 'End-Unit Assessment' - else - # ELA G1 M1 U2 Lesson 1 - curr ||= directory - res = ::Lcms::Engine::Resource.new(metadata: metadata) - ::Lcms::Engine::Breadcrumbs.new(res).title.split(' / ')[0...-1].push(curr.last.to_s.titleize).join(' ') - end + # ELA G1 M1 U2 Lesson 1 + curr ||= directory + res = ::Lcms::Engine::Resource.new(metadata:) + ::Lcms::Engine::Breadcrumbs.new(res).title.split(' / ')[0...-1].push(curr.last.to_s.titleize).join(' ') end def ela? subject.to_s.casecmp('ela').zero? end - # TODO: Extract to specific future UnboundEd gem - def fix_prereq_position(resource) - next_lesson = resource.siblings.detect do |r| - break r unless r.prerequisite? # first non-prereq - - # grab the first prereq lesson with a bigger lesson num - r.lesson_number > context['lesson'].to_i - end - next_lesson&.prepend_sibling(resource) - end - def grade @grade ||= begin value = context[:grade].to_s.downcase @@ -179,22 +156,7 @@ def last_item?(index) end def lesson - @lesson ||= - if assessment? - # assessment is a unit now, so lesson -> nil - nil - else - num = if ela? && prerequisite? - ::Lcms::Engine::RomanNumerals.to_roman(context[:lesson].to_i)&.downcase - else - context[:lesson].presence - end - "lesson #{num}" if num.present? - end - end - - def mid_assessment? - type.to_s.casecmp('assessment-mid').zero? + @lesson ||= "lesson #{context[:lesson]}" end def module @@ -213,15 +175,10 @@ def alnum?(str) str =~ /^\w+$/ end - # `Optional prerequisite` - https://github.com/learningtapestry/unbounded/issues/557 def opr? type.to_s.casecmp('opr').zero? end - def prerequisite? - type.to_s.casecmp('prereq').zero? - end - def subject @subject ||= begin value = context[:subject]&.downcase @@ -230,11 +187,11 @@ def subject end def tag_list - assessment? ? ['assessment', type] : [type.presence || 'core'] # lesson => prereq || core + [type.presence || 'core'] end def teaser - context[:teaser].presence || (assessment? ? title : nil) + context[:teaser] end def title @@ -246,51 +203,22 @@ def type end def unit - @unit ||= if assessment? - type # assessment-mid || assessment-end - else - ela? ? "unit #{context[:unit]}" : "topic #{context[:topic]}" - end + "unit #{context[:unit]}" end - def update(resource) # rubocop:disable Metrics/AbcSize + def update(resource) return if resource.nil? - # if resource changed to prerequisite, fix positioning - prereq = context['type'].to_s.casecmp('prereq').to_i.zero? - fix_prereq_position(resource) if prereq && !resource.prerequisite? - # Update resource with document metadata resource.title = context['title'] if context['title'].present? resource.teaser = context['teaser'] if context['teaser'].present? resource.description = context['description'] if context['description'].present? - resource.tag_list << 'prereq' if prereq resource.tag_list << 'opr' if context['type'].to_s.casecmp('opr').to_i.zero? resource.save resource end - def set_mid_assessment_position(parent, resource) - unit = parent.children.detect { |r| r.short_title =~ /topic #{context['after-topic']}/i } - unit.append_sibling(resource) - end - - def set_prerequisite_position(parent, resource) - next_lesson = parent.children.detect do |r| - break r unless r.prerequisite? # first non-prereq - - # first prereq lesson with a bigger lesson num - r.lesson_number > context[:lesson].to_i - end - next_lesson&.prepend_sibling(resource) - end - - def set_opr_position(parent, resource) - first_non_opr = parent.children.detect { |r| !r.opr? } - first_non_opr&.prepend_sibling(resource) - end - def set_lesson_position(parent, resource) next_lesson = parent.children.detect do |r| # first lesson with a bigger lesson num diff --git a/lib/lt/lcms/metadata/service.rb b/lib/lt/lcms/metadata/service.rb index 7a7364eb..c911da85 100644 --- a/lib/lt/lcms/metadata/service.rb +++ b/lib/lt/lcms/metadata/service.rb @@ -10,10 +10,6 @@ def agenda @agenda.data.presence || [] end - def foundational_metadata - @foundational_metadata.data.presence || {} - end - def materials_metadata DocTemplate::Objects::MaterialMetadata end @@ -27,7 +23,6 @@ def options_for(context) } else { - foundational_metadata: DocTemplate::Objects::BaseMetadata.build_from(@foundational_metadata.data), metadata: DocTemplate::Objects::BaseMetadata.build_from(metadata.data), parts: @target_table.try(:parts) } @@ -35,7 +30,7 @@ def options_for(context) ) end - def parse(content, *args) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + def parse(content, *args) # rubocop:disable Metrics/PerceivedComplexity super if material? @metadata = DocTemplate::Tables::MaterialMetadata.parse content @@ -47,17 +42,9 @@ def parse(content, *args) # rubocop:disable Metrics/CyclomaticComplexity, Metric @errors.concat @metadata.errors raise ::Lcms::Engine::DocumentError, 'No metadata present' unless @metadata&.table_exist? - @agenda = DocTemplate::Tables::Agenda.parse content - @section_metadata = DocTemplate::Tables::Section.parse content, - force_inject_section: force_inject_section? - @activity_metadata = DocTemplate::Tables::Activity.parse content, template_type: template_type + @section_metadata = DocTemplate::Tables::Section.parse content + @activity_metadata = DocTemplate::Tables::Activity.parse(content) @target_table = DocTemplate::Tables::Target.parse(content) if target_table? - - @foundational_metadata = if foundational? - @metadata - else - DocTemplate::Tables::FoundationalMetadata.parse content - end end self @@ -65,22 +52,10 @@ def parse(content, *args) # rubocop:disable Metrics/CyclomaticComplexity, Metric private - # - # Force injecting of Section metadata only if there are no Agenda tables - # - def force_inject_section? - @agenda.data.empty? && metadata.data.any? - end - - def foundational? - metadata.data['type'].to_s.casecmp('fs').zero? - end - def lesson_options { activity: DocTemplate::Objects::ActivityMetadata.build_from(@activity_metadata), - agenda: DocTemplate::Objects::AgendaMetadata.build_from(@agenda.data), - sections: DocTemplate::Objects::SectionsMetadata.build_from(@section_metadata, template_type) + sections: DocTemplate::Objects::SectionsMetadata.build_from(@section_metadata) } end @@ -89,10 +64,6 @@ def target_table? metadata.data['subject']&.downcase == 'ela' && metadata.data['grade'] == '6' end - - def template_type - foundational? ? 'fs' : 'core' - end end end end diff --git a/lib/lti/thin_common_cartridge.rb b/lib/lti/thin_common_cartridge.rb index d6976e60..652e3e35 100644 --- a/lib/lti/thin_common_cartridge.rb +++ b/lib/lti/thin_common_cartridge.rb @@ -7,6 +7,10 @@ module Lti class ThinCommonCartridge attr_reader :links + LTI_LINK_RESOURCE_TYPE = 'imsbasiclti_xmlv1p0' + MANIFEST_FILEPATH = ::Lcms::Engine::Engine.root.join 'lib', 'lti', 'xml', 'imsmanifest.xml' + LTI_LINK_FILEPATH = ::Lcms::Engine::Engine.root.join 'lib', 'lti', 'xml', 'lti_link.xml' + def initialize(items) @items = Array.wrap items @xml = File.open(File.expand_path MANIFEST_FILEPATH) { |f| Nokogiri::XML(f) } @@ -28,10 +32,6 @@ def manifest private - LTI_LINK_RESOURCE_TYPE = 'imsbasiclti_xmlv1p0' - MANIFEST_FILEPATH = ::Lcms::Engine::Engine.root.join 'lib', 'lti', 'xml', 'imsmanifest.xml' - LTI_LINK_FILEPATH = ::Lcms::Engine::Engine.root.join 'lib', 'lti', 'xml', 'lti_link.xml' - attr_reader :items, :link_template, :xml def add_item(item, parent) @@ -64,7 +64,7 @@ def add_resource(item) href = "lti_links/#{item[:identifierref]}.xml" create_link item, href - file_node = create_node 'file', href: href + file_node = create_node('file', href:) node.add_child file_node end diff --git a/lib/resque_job.rb b/lib/resque_job.rb index eb9b1ed3..6b408723 100644 --- a/lib/resque_job.rb +++ b/lib/resque_job.rb @@ -16,14 +16,14 @@ def find_in_queue(job_id) .detect { |job| job['job_id'] == job_id } end - def find_in_queue_by_payload(job_class, &block) + def find_in_queue_by_payload(job_class, &) jobs = Array.wrap Resque.peek(queue_name, 0, 0) result = jobs .select { |j| j['args'].first['job_class'] == job_class.to_s } .flat_map { |j| j['args'] } return result unless block_given? - result.detect(&block) + result.detect(&) end def find_in_working(job_id) @@ -34,7 +34,7 @@ def find_in_working(job_id) end end - def find_in_working_by_payload(job_class, &block) + def find_in_working_by_payload(job_class, &) result = Resque::Worker.working.map(&:job).flat_map do |job| next unless job.is_a?(Hash) && (args = job.dig 'payload', 'args').is_a?(Array) @@ -43,7 +43,7 @@ def find_in_working_by_payload(job_class, &block) end.compact return result unless block_given? - result.detect(&block) + result.detect(&) end def fetch_result(job_id) @@ -75,12 +75,16 @@ def store_initial_result(res, options = {}) Resque.redis.set(key, res.to_json, ex: 1.hour.to_i) end + # + # @param [Hash] res + # @param [Hash] options + # def store_result(res, options = {}) key = if (jid = options[:initial_job_id]).blank? result_key else # store result with parent job id to retrieve the result later knowing only parent job id - [Resque.redis.namespace, 'result', self.class.name.underscore, jid, job_id].join(':') + [Resque.redis.namespace, 'result', self.class.name.to_s.underscore, jid, job_id].join(':') end Resque.redis.set(key, res.to_json, ex: 1.hour.to_i) end diff --git a/lib/standard_importer.rb b/lib/standard_importer.rb index da4b3f81..2acae525 100644 --- a/lib/standard_importer.rb +++ b/lib/standard_importer.rb @@ -20,7 +20,8 @@ def run private - RE_EMPHASIS = /^\(\+\)\s?/.freeze + RE_EMPHASIS = /^\(\+\)\s?/ + private_constant :RE_EMPHASIS attr_reader :source_file, :subject @@ -34,21 +35,21 @@ def find_grade(name) "grade #{name}" end # TODO: Add caching here - Resource.find_by_directory([subject, key]).grades.model + Lcms::Engine::Resource.find_by_directory([subject, key]).grades.model end def find_grades(data) - grades = [] + grades = [] # : Array[String] from_name, to_name = data.squish.downcase.split('-') grades << find_grade(from_name) return grades if to_name.blank? - from_idx = Grades.grades_abbrevs.index from_name - to_idx = Grades.grades_abbrevs.index from_name + from_idx = Lcms::Engine::Grades.grades_abbrevs.index from_name + to_idx = Lcms::Engine::Grades.grades_abbrevs.index from_name - Grades.grades_abbrevs.slice(from_idx..to_idx).each { |name| grades << find_grade(name) } + Lcms::Engine::Grades.grades_abbrevs.slice(from_idx..to_idx).each { |name| grades << find_grade(name) } grades.compact end @@ -69,10 +70,10 @@ def standard_ela(grade_name, data) number = data[2].to_s.squish name = [grade_name, strand, number].join('.') - Standard.create!( + Lcms::Engine::Standard.create!( description: data[4].split(' ', 2).last.to_s.squish, - name: name, - strand: strand, + name:, + strand:, synonyms: data[3].to_s.squish ) end @@ -90,12 +91,12 @@ def standard_math(grade_name, data) prefix = grade_name.to_i < 9 ? grade_name : course name = [prefix, domain, cluster, number].compact.join('.') - Standard.create!( + Lcms::Engine::Standard.create!( description: description&.squish, emphasis: emphasis&.squish, - name: name, - course: course, - domain: domain, + name:, + course:, + domain:, synonyms: data[3].to_s.squish ) end diff --git a/lib/tasks/cleanup.rake b/lib/tasks/cleanup.rake index 99e8d167..48337ff2 100644 --- a/lib/tasks/cleanup.rake +++ b/lib/tasks/cleanup.rake @@ -20,7 +20,7 @@ namespace :cleanup do # rubocop:disable Metrics/BlockLength links = document.links links['pdf']&.delete('preview') - document.update_columns links: links + document.update_columns links: end batch_delete_in args[:s3_folder] @@ -52,7 +52,7 @@ namespace :cleanup do # rubocop:disable Metrics/BlockLength folder_id = ENV.fetch('GOOGLE_APPLICATION_PREVIEW_FOLDER_ID') service = Google::Apis::DriveV3::DriveService.new - service.authorization = ::Lt::Google::Api::Auth::Cli.new.credentials + service.authorization = Lt::Google::Api::Auth::Cli.new.credentials service .list_files(q: "'#{folder_id}' in parents") .files.each { |file| service.delete_file file.id } diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index 50bcd470..a07412e2 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -3,7 +3,7 @@ namespace :db do # rubocop:disable Metrics/BlockLength desc 'Backs up the database.' task backup: [:environment] do - config = ActiveRecord::Base.connection_config + config = ActiveRecord::Base.connection_db_config.configuration_hash backup_cmd = <<-BASH BACKUP_FOLDER=$HOME/database_backups/`date +%Y_%m_%d` @@ -30,9 +30,10 @@ namespace :db do # rubocop:disable Metrics/BlockLength raise unless system(backup_cmd) end - desc 'Dumps the database.' - task dump: :environment do - config = ActiveRecord::Base.connection_config + desc 'Dumps the database. Will create a dump file in db/dump/content.dump or a custom path.' + task :dump, [:dump_path] => [:environment] do |_t, args| + config = ActiveRecord::Base.connection_db_config.configuration_hash + dump_path = args[:dump_path] || "#{Rails.root}/db/dump/content.dump" dump_cmd = <<-BASH PGPASSWORD=#{config[:password]} \ @@ -45,17 +46,18 @@ namespace :db do # rubocop:disable Metrics/BlockLength --no-acl \ --format=c \ -n public \ - #{config[:database]} > #{Rails.root}/db/dump/content.dump + #{config[:database]} > #{dump_path} BASH - puts "Dumping #{Rails.env} database." + puts "Dumping #{Rails.env} database to #{dump_path}." raise unless system(dump_cmd) end - desc 'Runs pg_restore.' - task pg_restore: [:environment] do - config = ActiveRecord::Base.connection_config + desc 'Runs pg_restore. Requires a dump file in db/dump/content.dump or a custom path.' + task :pg_restore, [:dump_path] => [:environment] do |_t, args| + config = ActiveRecord::Base.connection_db_config.configuration_hash + dump_path = args[:dump_path] || "#{Rails.root}/db/dump/content.dump" restore_cmd = <<-BASH PGPASSWORD=#{config[:password]} \ @@ -66,10 +68,10 @@ namespace :db do # rubocop:disable Metrics/BlockLength --no-owner \ --no-acl \ -n public \ - --dbname=#{config[:database]} #{Rails.root}/db/dump/content.dump + --dbname=#{config[:database]} #{dump_path} BASH - puts "Restoring #{Rails.env} database." + puts "Restoring #{Rails.env} database from #{dump_path}." raise unless system(restore_cmd) end diff --git a/lib/tasks/elasticsearch.rake b/lib/tasks/elasticsearch.rake deleted file mode 100644 index 6b47cb98..00000000 --- a/lib/tasks/elasticsearch.rake +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -namespace :es do - desc 'Reset Index' - task reset: :environment do - if repo.index_exists? - puts "Deleting Index: #{repo.index}" - repo.delete_index! - end - - puts "Creating Index: #{repo.index}" - repo.create_index! - end - - desc 'Indexes models' - task index_models: :environment do - repo.create_index! - - index_model 'Resources', Resource.tree.where.not(curriculum_type: 'subject') - index_model 'Media ', Resource.media - index_model 'Generic ', Resource.generic_resources - end - - desc 'Load index' - task load: %i(environment index_models) do - fpath = Rails.root.join('db', 'seeds', 'external_pages.json') - pages = JSON.parse File.read(fpath) - pages.each do |page_attrs| - page = ExternalPage.new(page_attrs.symbolize_keys) - repo.klass.build_from(page).index! - end - end - - def repo - @repo ||= Search::Repository.new - end - - def index_model(name, qset) - pbar = ProgressBar.create title: "Indexing #{name}", total: qset.count - - qset.find_in_batches do |group| - group.each do |item| - repo.klass.build_from(item).index! - pbar.increment - end - end - pbar.finish - end -end diff --git a/lib/tasks/google.rake b/lib/tasks/google.rake index 8cb72295..305be53b 100644 --- a/lib/tasks/google.rake +++ b/lib/tasks/google.rake @@ -7,7 +7,7 @@ require 'lt/google/api/auth/cli' namespace :google do desc 'Set up google credentials. Specify `domain` argument which will be used as a base for redirect URI' task :setup_auth, [:domain] => [:environment] do |_task, args| - service = ::Lt::Google::Api::Auth::Cli.new + service = Lt::Google::Api::Auth::Cli.new # Check if there is existing auth token if service.credentials.present? @@ -32,8 +32,8 @@ namespace :google do code = $stdin.gets.to_s.strip authorizer.get_and_store_credentials_from_code( - user_id: ::Lt::Google::Api::Auth::Cli::USER_ID, - code: code, + user_id: Lt::Google::Api::Auth::Cli::USER_ID, + code:, base_url: args[:domain] ) end diff --git a/lib/tasks/lcms/engine_tasks.rake b/lib/tasks/lcms/engine_tasks.rake index 8dd5f85b..c7e5e110 100644 --- a/lib/tasks/lcms/engine_tasks.rake +++ b/lib/tasks/lcms/engine_tasks.rake @@ -1,6 +1,6 @@ # frozen_string_literal: true -namespace :lcms_engine do # rubocop:disable Metrics/BlockLength +namespace :lcms_engine do desc 'Copy routes file' task :copy_routes do src = File.join Lcms::Engine::Engine.root, 'templates', 'routes.rb' @@ -22,60 +22,4 @@ namespace :lcms_engine do # rubocop:disable Metrics/BlockLength task seed_data: :environment do load File.join Lcms::Engine::Engine.root, 'db', 'seeds.rb' end - - namespace :webpacker do - desc 'Cleans the Webpack output folder (public/lcms_engine_packs)' - task :clean_output_folder do - puts 'Destroying packs output folder ...' - Dir.chdir(File.join(__dir__, '..', '..')) do - # TODO : Load packs output from config/webpacker.yml - system 'rm -rf public/lcms_engine_packs' - end - end - - desc 'Install deps with yarn' - task :yarn_install do - Dir.chdir(File.join(__dir__, '../..')) do - system "#{ENV['YARN_PATH'] || 'yarn'} install --no-progress --production" - end - end - - desc 'Compile JavaScript packs using webpack for production with digests' - task compile: %i(clean_output_folder yarn_install environment) do - Webpacker.with_node_env('production') do - if Lcms::Engine.webpacker.commands.compile - # Successful compilation! - else - # Failed compilation - exit! - end - end - end - end -end - -def yarn_install_available? - rails_major = Rails::VERSION::MAJOR - rails_minor = Rails::VERSION::MINOR - - rails_major > 5 || (rails_major == 5 && rails_minor >= 1) -end - -def enhance_assets_precompile - # yarn:install was added in Rails 5.1 - deps = yarn_install_available? ? [] : ['lcms_engine:webpacker:yarn_install'] - Rake::Task['assets:precompile'].enhance(deps) do - Rake::Task['lcms_engine:webpacker:compile'].invoke - end -end - -# Compile packs after we've compiled all other assets during precompilation -skip_webpacker_precompile = %w(no false n f).include?(ENV.fetch('WEBPACKER_PRECOMPILE', nil)) - -unless skip_webpacker_precompile - if Rake::Task.task_defined?('assets:precompile') - enhance_assets_precompile - else - Rake::Task.define_task('assets:precompile' => 'lcms_engine:webpacker:compile') - end end diff --git a/lib/tasks/pages.rake b/lib/tasks/pages.rake deleted file mode 100644 index ecf6aae9..00000000 --- a/lib/tasks/pages.rake +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -namespace :pages do - desc 'Update pages from seed' - task update: :environment do - pages_to_replace = %w(tos privacy) - Page.where(slug: pages_to_replace).each(&:destroy) - Rake::Task['db:seed:pages'].invoke - end -end diff --git a/package.json b/package.json index 5c1336b1..0202cb69 100644 --- a/package.json +++ b/package.json @@ -1,26 +1,29 @@ { "name": "lcms-engine", - "version": "1.0.0", + "version": "2.0.0", "description": "", "dependencies": { "@babel/core": "^7.6.0", "@babel/preset-react": "^7.0.0", - "@rails/webpacker": "~5.4.3", + "@fortawesome/fontawesome-free": "^6.4.0", + "@hotwired/turbo-rails": "^7.3.0", + "@popperjs/core": "^2.11.7", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", + "bootstrap": "^5.2.3", + "bootstrap-icons": "^1.10.3", "classnames": "^2.2.6", - "foundation-sites": "^6.7.4", - "jquery": "^3.6.0", + "jquery": "^3.6.4", "lodash": "^4.17.21", "prop-types": "^15.7.2", "react": "^16.9.0", "react-dom": "^16.9.0", "react-tagsinput": "^3.19.0", - "react_ujs": "^2.6.0", - "webpack": "^4.46.0", - "webpack-cli": "^3.3.12" + "sass": "^1.60.0", + "tom-select": "^2.2.2" }, "devDependencies": { "babel-eslint": "^10.1.0", + "esbuild": "^0.17.11", "eslint": "^7.23.0", "eslint-config-prettier": "^8.1.0", "eslint-config-react-app": "^6.0.0", @@ -35,10 +38,11 @@ "stylelint-config-prettier": "^8.0.2", "stylelint-config-sass-guidelines": "^8.0.0", "stylelint-prettier": "^1.2.0", - "stylelint-selector-bem-pattern": "^2.1.0", - "webpack-dev-server": "^3" + "stylelint-selector-bem-pattern": "^2.1.0" }, "scripts": { + "build:js": "esbuild app/javascript/*.js --bundle --sourcemap --minify --outdir=public/lcms-engine-assets --loader:.js=jsx", + "build:css": "sass ./app/assets/stylesheets/lcms/engine/application.bootstrap.scss:./public/lcms-engine-assets/application.css --no-source-map --load-path=node_modules", "lint": "eslint --ext .js --ext .jsx app/javascript && stylelint 'app/assets/stylesheets/**/*.{css,scss}' --quiet", "lint:js": "eslint --ext .js --ext .jsx app/javascript", "lint:js:fix": "eslint --ext .js --ext .jsx app/javascript --quiet --fix", @@ -50,7 +54,7 @@ "url": "git+https://github.com/learningtapestry/lcms-engine.git" }, "engines": { - "node": ">=14.17.0", + "node": ">=18.15", "yarn": ">=1.17.1" }, "author": "Alexander Kuznetsov ", diff --git a/public/lcms-engine-assets/admin.js b/public/lcms-engine-assets/admin.js new file mode 100644 index 00000000..d6fd0576 --- /dev/null +++ b/public/lcms-engine-assets/admin.js @@ -0,0 +1,128 @@ +(()=>{var LN=Object.create;var bg=Object.defineProperty;var jN=Object.getOwnPropertyDescriptor;var PN=Object.getOwnPropertyNames;var IN=Object.getPrototypeOf,RN=Object.prototype.hasOwnProperty;var Ki=(e,t)=>()=>(e&&(t=e(e=0)),t);var Pr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),K0=(e,t)=>{for(var n in t)bg(e,n,{get:t[n],enumerable:!0})},DN=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let c of PN(t))!RN.call(e,c)&&c!==n&&bg(e,c,{get:()=>t[c],enumerable:!(i=jN(t,c))||i.enumerable});return e};var Dn=(e,t,n)=>(n=e!=null?LN(IN(e)):{},DN(t||!e||!e.__esModule?bg(n,"default",{value:e,enumerable:!0}):n,e));var MN=(e,t,n)=>{if(!t.has(e))throw TypeError("Cannot "+n)};var il=(e,t,n)=>{if(t.has(e))throw TypeError("Cannot add the same private member more than once");t instanceof WeakSet?t.add(e):t.set(e,n)};var sl=(e,t,n)=>(MN(e,t,"access private method"),n);var to,af=Ki(()=>{to={logger:self.console,WebSocket:self.WebSocket}});var Jt,cl=Ki(()=>{af();Jt={log(...e){this.enabled&&(e.push(Date.now()),to.logger.log("[ActionCable]",...e))}}});var Qu,lf,Xu,uf,em=Ki(()=>{cl();Qu=()=>new Date().getTime(),lf=e=>(Qu()-e)/1e3,Xu=class{constructor(t){this.visibilityDidChange=this.visibilityDidChange.bind(this),this.connection=t,this.reconnectAttempts=0}start(){this.isRunning()||(this.startedAt=Qu(),delete this.stoppedAt,this.startPolling(),addEventListener("visibilitychange",this.visibilityDidChange),Jt.log(`ConnectionMonitor started. stale threshold = ${this.constructor.staleThreshold} s`))}stop(){this.isRunning()&&(this.stoppedAt=Qu(),this.stopPolling(),removeEventListener("visibilitychange",this.visibilityDidChange),Jt.log("ConnectionMonitor stopped"))}isRunning(){return this.startedAt&&!this.stoppedAt}recordPing(){this.pingedAt=Qu()}recordConnect(){this.reconnectAttempts=0,this.recordPing(),delete this.disconnectedAt,Jt.log("ConnectionMonitor recorded connect")}recordDisconnect(){this.disconnectedAt=Qu(),Jt.log("ConnectionMonitor recorded disconnect")}startPolling(){this.stopPolling(),this.poll()}stopPolling(){clearTimeout(this.pollTimeout)}poll(){this.pollTimeout=setTimeout(()=>{this.reconnectIfStale(),this.poll()},this.getPollInterval())}getPollInterval(){let{staleThreshold:t,reconnectionBackoffRate:n}=this.constructor,i=Math.pow(1+n,Math.min(this.reconnectAttempts,10)),g=(this.reconnectAttempts===0?1:n)*Math.random();return t*1e3*i*(1+g)}reconnectIfStale(){this.connectionIsStale()&&(Jt.log(`ConnectionMonitor detected stale connection. reconnectAttempts = ${this.reconnectAttempts}, time stale = ${lf(this.refreshedAt)} s, stale threshold = ${this.constructor.staleThreshold} s`),this.reconnectAttempts++,this.disconnectedRecently()?Jt.log(`ConnectionMonitor skipping reopening recent disconnect. time disconnected = ${lf(this.disconnectedAt)} s`):(Jt.log("ConnectionMonitor reopening"),this.connection.reopen()))}get refreshedAt(){return this.pingedAt?this.pingedAt:this.startedAt}connectionIsStale(){return lf(this.refreshedAt)>this.constructor.staleThreshold}disconnectedRecently(){return this.disconnectedAt&&lf(this.disconnectedAt){(this.connectionIsStale()||!this.connection.isOpen())&&(Jt.log(`ConnectionMonitor reopening stale connection on visibilitychange. visibilityState = ${document.visibilityState}`),this.connection.reopen())},200)}};Xu.staleThreshold=6;Xu.reconnectionBackoffRate=.15;uf=Xu});var Ju,tm=Ki(()=>{Ju={message_types:{welcome:"welcome",disconnect:"disconnect",ping:"ping",confirmation:"confirm_subscription",rejection:"reject_subscription"},disconnect_reasons:{unauthorized:"unauthorized",invalid_request:"invalid_request",server_restart:"server_restart"},default_mount_path:"/cable",protocols:["actioncable-v1-json","actioncable-unsupported"]}});var Zu,cf,WL,aw,ec,df,nm=Ki(()=>{af();em();tm();cl();({message_types:Zu,protocols:cf}=Ju),WL=cf.slice(0,cf.length-1),aw=[].indexOf,ec=class{constructor(t){this.open=this.open.bind(this),this.consumer=t,this.subscriptions=this.consumer.subscriptions,this.monitor=new uf(this),this.disconnected=!0}send(t){return this.isOpen()?(this.webSocket.send(JSON.stringify(t)),!0):!1}open(){return this.isActive()?(Jt.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`),!1):(Jt.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${cf}`),this.webSocket&&this.uninstallEventHandlers(),this.webSocket=new to.WebSocket(this.consumer.url,cf),this.installEventHandlers(),this.monitor.start(),!0)}close({allowReconnect:t}={allowReconnect:!0}){if(t||this.monitor.stop(),this.isOpen())return this.webSocket.close()}reopen(){if(Jt.log(`Reopening WebSocket, current state is ${this.getState()}`),this.isActive())try{return this.close()}catch(t){Jt.log("Failed to reopen WebSocket",t)}finally{Jt.log(`Reopening WebSocket in ${this.constructor.reopenDelay}ms`),setTimeout(this.open,this.constructor.reopenDelay)}else return this.open()}getProtocol(){if(this.webSocket)return this.webSocket.protocol}isOpen(){return this.isState("open")}isActive(){return this.isState("open","connecting")}isProtocolSupported(){return aw.call(WL,this.getProtocol())>=0}isState(...t){return aw.call(t,this.getState())>=0}getState(){if(this.webSocket){for(let t in to.WebSocket)if(to.WebSocket[t]===this.webSocket.readyState)return t.toLowerCase()}return null}installEventHandlers(){for(let t in this.events){let n=this.events[t].bind(this);this.webSocket[`on${t}`]=n}}uninstallEventHandlers(){for(let t in this.events)this.webSocket[`on${t}`]=function(){}}};ec.reopenDelay=500;ec.prototype.events={message(e){if(!this.isProtocolSupported())return;let{identifier:t,message:n,reason:i,reconnect:c,type:g}=JSON.parse(e.data);switch(g){case Zu.welcome:return this.monitor.recordConnect(),this.subscriptions.reload();case Zu.disconnect:return Jt.log(`Disconnecting. Reason: ${i}`),this.close({allowReconnect:c});case Zu.ping:return this.monitor.recordPing();case Zu.confirmation:return this.subscriptions.confirmSubscription(t),this.subscriptions.notify(t,"connected");case Zu.rejection:return this.subscriptions.reject(t);default:return this.subscriptions.notify(t,"received",n)}},open(){if(Jt.log(`WebSocket onopen event, using '${this.getProtocol()}' subprotocol`),this.disconnected=!1,!this.isProtocolSupported())return Jt.log("Protocol is unsupported. Stopping monitor and disconnecting."),this.close({allowReconnect:!1})},close(e){if(Jt.log("WebSocket onclose event"),!this.disconnected)return this.disconnected=!0,this.monitor.recordDisconnect(),this.subscriptions.notifyAll("disconnected",{willAttemptReconnect:this.monitor.isRunning()})},error(){Jt.log("WebSocket onerror event")}};df=ec});var BL,na,rm=Ki(()=>{BL=function(e,t){if(t!=null)for(let n in t){let i=t[n];e[n]=i}return e},na=class{constructor(t,n={},i){this.consumer=t,this.identifier=JSON.stringify(n),BL(this,i)}perform(t,n={}){return n.action=t,this.send(n)}send(t){return this.consumer.send({command:"message",identifier:this.identifier,data:JSON.stringify(t)})}unsubscribe(){return this.consumer.subscriptions.remove(this)}}});var im,ff,sm=Ki(()=>{cl();im=class{constructor(t){this.subscriptions=t,this.pendingSubscriptions=[]}guarantee(t){this.pendingSubscriptions.indexOf(t)==-1?(Jt.log(`SubscriptionGuarantor guaranteeing ${t.identifier}`),this.pendingSubscriptions.push(t)):Jt.log(`SubscriptionGuarantor already guaranteeing ${t.identifier}`),this.startGuaranteeing()}forget(t){Jt.log(`SubscriptionGuarantor forgetting ${t.identifier}`),this.pendingSubscriptions=this.pendingSubscriptions.filter(n=>n!==t)}startGuaranteeing(){this.stopGuaranteeing(),this.retrySubscribing()}stopGuaranteeing(){clearTimeout(this.retryTimeout)}retrySubscribing(){this.retryTimeout=setTimeout(()=>{this.subscriptions&&typeof this.subscriptions.subscribe=="function"&&this.pendingSubscriptions.map(t=>{Jt.log(`SubscriptionGuarantor resubscribing ${t.identifier}`),this.subscriptions.subscribe(t)})},500)}},ff=im});var ra,om=Ki(()=>{rm();sm();cl();ra=class{constructor(t){this.consumer=t,this.guarantor=new ff(this),this.subscriptions=[]}create(t,n){let i=t,c=typeof i=="object"?i:{channel:i},g=new na(this.consumer,c,n);return this.add(g)}add(t){return this.subscriptions.push(t),this.consumer.ensureActiveConnection(),this.notify(t,"initialized"),this.subscribe(t),t}remove(t){return this.forget(t),this.findAll(t.identifier).length||this.sendCommand(t,"unsubscribe"),t}reject(t){return this.findAll(t).map(n=>(this.forget(n),this.notify(n,"rejected"),n))}forget(t){return this.guarantor.forget(t),this.subscriptions=this.subscriptions.filter(n=>n!==t),t}findAll(t){return this.subscriptions.filter(n=>n.identifier===t)}reload(){return this.subscriptions.map(t=>this.subscribe(t))}notifyAll(t,...n){return this.subscriptions.map(i=>this.notify(i,t,...n))}notify(t,n,...i){let c;return typeof t=="string"?c=this.findAll(t):c=[t],c.map(g=>typeof g[n]=="function"?g[n](...i):void 0)}subscribe(t){this.sendCommand(t,"subscribe")&&this.guarantor.guarantee(t)}confirmSubscription(t){Jt.log(`Subscription confirmed ${t}`),this.findAll(t).map(n=>this.guarantor.forget(n))}sendCommand(t,n){let{identifier:i}=t;return this.consumer.send({command:n,identifier:i})}}});function am(e){if(typeof e=="function"&&(e=e()),e&&!/^wss?:/i.test(e)){let t=document.createElement("a");return t.href=e,t.href=t.href,t.protocol=t.protocol.replace("http","ws"),t.href}else return e}var dl,lw=Ki(()=>{nm();om();dl=class{constructor(t){this._url=t,this.subscriptions=new ra(this),this.connection=new df(this)}get url(){return am(this._url)}send(t){return this.connection.send(t)}connect(){return this.connection.open()}disconnect(){return this.connection.close({allowReconnect:!1})}ensureActiveConnection(){if(!this.connection.isActive())return this.connection.open()}}});var cw={};K0(cw,{Connection:()=>df,ConnectionMonitor:()=>uf,Consumer:()=>dl,INTERNAL:()=>Ju,Subscription:()=>na,SubscriptionGuarantor:()=>ff,Subscriptions:()=>ra,adapters:()=>to,createConsumer:()=>qL,createWebSocketURL:()=>am,getConfig:()=>uw,logger:()=>Jt});function qL(e=uw("url")||Ju.default_mount_path){return new dl(e)}function uw(e){let t=document.head.querySelector(`meta[name='action-cable-${e}']`);if(t)return t.getAttribute("content")}var dw=Ki(()=>{nm();em();lw();tm();rm();om();sm();af();cl()});var R1=Pr((Um,zm)=>{(function(e,t){typeof Um=="object"&&typeof zm<"u"?zm.exports=t():typeof define=="function"&&define.amd?define(t):(e=typeof globalThis<"u"?globalThis:e||self,e.TomSelect=t())})(Um,function(){"use strict";function e(L,v){L.split(/\s+/).forEach(T=>{v(T)})}class t{constructor(){this._events=void 0,this._events={}}on(v,T){e(v,A=>{let H=this._events[A]||[];H.push(T),this._events[A]=H})}off(v,T){var A=arguments.length;if(A===0){this._events={};return}e(v,H=>{if(A===1){delete this._events[H];return}let Y=this._events[H];Y!==void 0&&(Y.splice(Y.indexOf(T),1),this._events[H]=Y)})}trigger(v,...T){var A=this;e(v,H=>{let Y=A._events[H];Y!==void 0&&Y.forEach(W=>{W.apply(A,T)})})}}function n(L){return L.plugins={},class extends L{constructor(...v){super(...v),this.plugins={names:[],settings:{},requested:{},loaded:{}}}static define(v,T){L.plugins[v]={name:v,fn:T}}initializePlugins(v){var T,A;let H=this,Y=[];if(Array.isArray(v))v.forEach(W=>{typeof W=="string"?Y.push(W):(H.plugins.settings[W.name]=W.options,Y.push(W.name))});else if(v)for(T in v)v.hasOwnProperty(T)&&(H.plugins.settings[T]=v[T],Y.push(T));for(;A=Y.shift();)H.require(A)}loadPlugin(v){var T=this,A=T.plugins,H=L.plugins[v];if(!L.plugins.hasOwnProperty(v))throw new Error('Unable to find "'+v+'" plugin');A.requested[v]=!0,A.loaded[v]=H.fn.apply(T,[T.plugins.settings[v]||{}]),A.names.push(v)}require(v){var T=this,A=T.plugins;if(!T.plugins.loaded.hasOwnProperty(v)){if(A.requested[v])throw new Error('Plugin has circular dependency ("'+v+'")');T.loadPlugin(v)}return A.loaded[v]}}}let i=L=>(L=L.filter(Boolean),L.length<2?L[0]||"":M(L)==1?"["+L.join("")+"]":"(?:"+L.join("|")+")"),c=L=>{if(!x(L))return L.join("");let v="",T=0,A=()=>{T>1&&(v+="{"+T+"}")};return L.forEach((H,Y)=>{if(H===L[Y-1]){T++;return}A(),v+=H,T=1}),A(),v},g=L=>{let v=te(L);return i(v)},x=L=>new Set(L).size!==L.length,S=L=>(L+"").replace(/([\$\(\)\*\+\.\?\[\]\^\{\|\}\\])/gu,"\\$1"),M=L=>L.reduce((v,T)=>Math.max(v,z(T)),0),z=L=>te(L).length,te=L=>Array.from(L);let de=L=>{if(L.length===1)return[[L]];let v=[],T=L.substring(1);return de(T).forEach(function(H){let Y=H.slice(0);Y[0]=L.charAt(0)+Y[0],v.push(Y),Y=H.slice(0),Y.unshift(L.charAt(0)),v.push(Y)}),v};let Ae=[[0,65535]],pe="[\u0300-\u036F\xB7\u02BE\u02BC]",Te,Le,K=3,s={},p={"/":"\u2044\u2215",0:"\u07C0",a:"\u2C65\u0250\u0251",aa:"\uA733",ae:"\xE6\u01FD\u01E3",ao:"\uA735",au:"\uA737",av:"\uA739\uA73B",ay:"\uA73D",b:"\u0180\u0253\u0183",c:"\uA73F\u0188\u023C\u2184",d:"\u0111\u0257\u0256\u1D05\u018C\uABB7\u0501\u0266",e:"\u025B\u01DD\u1D07\u0247",f:"\uA77C\u0192",g:"\u01E5\u0260\uA7A1\u1D79\uA77F\u0262",h:"\u0127\u2C68\u2C76\u0265",i:"\u0268\u0131",j:"\u0249\u0237",k:"\u0199\u2C6A\uA741\uA743\uA745\uA7A3",l:"\u0142\u019A\u026B\u2C61\uA749\uA747\uA781\u026D",m:"\u0271\u026F\u03FB",n:"\uA7A5\u019E\u0272\uA791\u1D0E\u043B\u0509",o:"\xF8\u01FF\u0254\u0275\uA74B\uA74D\u1D11",oe:"\u0153",oi:"\u01A3",oo:"\uA74F",ou:"\u0223",p:"\u01A5\u1D7D\uA751\uA753\uA755\u03C1",q:"\uA757\uA759\u024B",r:"\u024D\u027D\uA75B\uA7A7\uA783",s:"\xDF\u023F\uA7A9\uA785\u0282",t:"\u0167\u01AD\u0288\u2C66\uA787",th:"\xFE",tz:"\uA729",u:"\u0289",v:"\u028B\uA75F\u028C",vy:"\uA761",w:"\u2C73",y:"\u01B4\u024F\u1EFF",z:"\u01B6\u0225\u0240\u2C6C\uA763",hv:"\u0195"};for(let L in p){let v=p[L]||"";for(let T=0;T{Te===void 0&&(Te=j(L||Ae))},a=(L,v="NFKD")=>L.normalize(v),m=L=>te(L).reduce((v,T)=>v+w(T),""),w=L=>(L=a(L).toLowerCase().replace(l,v=>s[v]||""),a(L,"NFC"));function*N(L){for(let[v,T]of L)for(let A=v;A<=T;A++){let H=String.fromCharCode(A),Y=m(H);Y!=H.toLowerCase()&&(Y.length>K||Y.length!=0&&(yield{folded:Y,composed:H,code_point:A}))}}let C=L=>{let v={},T=(A,H)=>{let Y=v[A]||new Set,W=new RegExp("^"+g(Y)+"$","iu");H.match(W)||(Y.add(S(H)),v[A]=Y)};for(let A of N(L))T(A.folded,A.folded),T(A.folded,A.composed);return v},j=L=>{let v=C(L),T={},A=[];for(let Y in v){let W=v[Y];W&&(T[Y]=g(W)),Y.length>1&&A.push(S(Y))}A.sort((Y,W)=>W.length-Y.length);let H=i(A);return Le=new RegExp("^"+H,"u"),T},R=(L,v=1)=>{let T=0;return L=L.map(A=>(Te[A]&&(T+=A.length),Te[A]||A)),T>=v?c(L):""},B=(L,v=1)=>(v=Math.max(v,L.length-1),i(de(L).map(T=>R(T,v)))),J=(L,v=!0)=>{let T=L.length>1?1:0;return i(L.map(A=>{let H=[],Y=v?A.length():A.length()-1;for(let W=0;W{for(let T of v){if(T.start!=L.start||T.end!=L.end||T.substrs.join("")!==L.substrs.join(""))continue;let A=L.parts,H=W=>{for(let _e of A){if(_e.start===W.start&&_e.substr===W.substr)return!1;if(!(W.length==1||_e.length==1)&&(W.start<_e.start&&W.end>_e.start||_e.startW.start))return!0}return!1};if(!(T.parts.filter(H).length>0))return!0}return!1};class se{constructor(){this.parts=[],this.substrs=[],this.start=0,this.end=0}add(v){v&&(this.parts.push(v),this.substrs.push(v.substr),this.start=Math.min(v.start,this.start),this.end=Math.max(v.end,this.end))}last(){return this.parts[this.parts.length-1]}length(){return this.parts.length}clone(v,T){let A=new se,H=JSON.parse(JSON.stringify(this.parts)),Y=H.pop();for(let Oe of H)A.add(Oe);let W=T.substr.substring(0,v-Y.start),_e=W.length;return A.add({start:Y.start,end:Y.start+_e,length:_e,substr:W}),A}}let le=L=>{h(),L=m(L);let v="",T=[new se];for(let A=0;A0){Oe=Oe.sort(($e,je)=>$e.length()-je.length());for(let $e of Oe)oe($e,T)||T.push($e);continue}if(A>0&&we.size==1&&!we.has("3")){v+=J(T,!1);let $e=new se,je=T[0];je&&$e.add(je.last()),T=[$e]}}return v+=J(T,!0),v};let ie=(L,v)=>{if(L)return L[v]},qe=(L,v)=>{if(L){for(var T,A=v.split(".");(T=A.shift())&&(L=L[T]););return L}},Ke=(L,v,T)=>{var A,H;return!L||(L=L+"",v.regex==null)||(H=L.search(v.regex),H===-1)?0:(A=v.string.length/L.length,H===0&&(A+=.5),A*T)},Fe=(L,v)=>{var T=L[v];if(typeof T=="function")return T;T&&!Array.isArray(T)&&(L[v]=[T])},Ve=(L,v)=>{if(Array.isArray(L))L.forEach(v);else for(var T in L)L.hasOwnProperty(T)&&v(L[T],T)},zt=(L,v)=>typeof L=="number"&&typeof v=="number"?L>v?1:Lv?1:v>L?-1:0);class Tt{constructor(v,T){this.items=void 0,this.settings=void 0,this.items=v,this.settings=T||{diacritics:!0}}tokenize(v,T,A){if(!v||!v.length)return[];let H=[],Y=v.split(/\s+/);var W;return A&&(W=new RegExp("^("+Object.keys(A).map(S).join("|")+"):(.*)$")),Y.forEach(_e=>{let Oe,we=null,$e=null;W&&(Oe=_e.match(W))&&(we=Oe[1],_e=Oe[2]),_e.length>0&&(this.settings.diacritics?$e=le(_e)||null:$e=S(_e),$e&&T&&($e="\\b"+$e)),H.push({string:_e,regex:$e?new RegExp($e,"iu"):null,field:we})}),H}getScoreFunction(v,T){var A=this.prepareSearch(v,T);return this._getScoreFunction(A)}_getScoreFunction(v){let T=v.tokens,A=T.length;if(!A)return function(){return 0};let H=v.options.fields,Y=v.weights,W=H.length,_e=v.getAttrFn;if(!W)return function(){return 1};let Oe=function(){return W===1?function(we,$e){let je=H[0].field;return Ke(_e($e,je),we,Y[je]||1)}:function(we,$e){var je=0;if(we.field){let ze=_e($e,we.field);!we.regex&&ze?je+=1/W:je+=Ke(ze,we,1)}else Ve(Y,(ze,_t)=>{je+=Ke(_e($e,_t),we,ze)});return je/W}}();return A===1?function(we){return Oe(T[0],we)}:v.options.conjunction==="and"?function(we){var $e,je=0;for(let ze of T){if($e=Oe(ze,we),$e<=0)return 0;je+=$e}return je/A}:function(we){var $e=0;return Ve(T,je=>{$e+=Oe(je,we)}),$e/A}}getSortFunction(v,T){var A=this.prepareSearch(v,T);return this._getSortFunction(A)}_getSortFunction(v){var T,A=[];let H=this,Y=v.options,W=!v.query&&Y.sort_empty?Y.sort_empty:Y.sort;if(typeof W=="function")return W.bind(this);let _e=function($e,je){return $e==="$score"?je.score:v.getAttrFn(H.items[je.id],$e)};if(W)for(let we of W)(v.query||we.field!=="$score")&&A.push(we);if(v.query){T=!0;for(let we of A)if(we.field==="$score"){T=!1;break}T&&A.unshift({field:"$score",direction:"desc"})}else A=A.filter(we=>we.field!=="$score");return A.length?function(we,$e){var je,ze;for(let _t of A)if(ze=_t.field,je=(_t.direction==="desc"?-1:1)*zt(_e(ze,we),_e(ze,$e)),je)return je;return 0}:null}prepareSearch(v,T){let A={};var H=Object.assign({},T);if(Fe(H,"sort"),Fe(H,"sort_empty"),H.fields){Fe(H,"fields");let Y=[];H.fields.forEach(W=>{typeof W=="string"&&(W={field:W,weight:1}),Y.push(W),A[W.field]="weight"in W?W.weight:1}),H.fields=Y}return{options:H,query:v.toLowerCase().trim(),tokens:this.tokenize(v,H.respect_word_boundaries,A),total:0,items:[],weights:A,getAttrFn:H.nesting?qe:ie}}search(v,T){var A=this,H,Y;Y=this.prepareSearch(v,T),T=Y.options,v=Y.query;let W=T.score||A._getScoreFunction(Y);v.length?Ve(A.items,(Oe,we)=>{H=W(Oe),(T.filter===!1||H>0)&&Y.items.push({score:H,id:we})}):Ve(A.items,(Oe,we)=>{Y.items.push({score:1,id:we})});let _e=A._getSortFunction(Y);return _e&&Y.items.sort(_e),Y.total=Y.items.length,typeof T.limit=="number"&&(Y.items=Y.items.slice(0,T.limit)),Y}}let Be=(L,v)=>{if(Array.isArray(L))L.forEach(v);else for(var T in L)L.hasOwnProperty(T)&&v(L[T],T)},Je=L=>{if(L.jquery)return L[0];if(L instanceof HTMLElement)return L;if(Pt(L)){var v=document.createElement("template");return v.innerHTML=L.trim(),v.content.firstChild}return document.querySelector(L)},Pt=L=>typeof L=="string"&&L.indexOf("<")>-1,Wt=L=>L.replace(/['"\\]/g,"\\$&"),It=(L,v)=>{var T=document.createEvent("HTMLEvents");T.initEvent(v,!0,!1),L.dispatchEvent(T)},Ee=(L,v)=>{Object.assign(L.style,v)},St=(L,...v)=>{var T=mt(v);L=We(L),L.map(A=>{T.map(H=>{A.classList.add(H)})})},Rt=(L,...v)=>{var T=mt(v);L=We(L),L.map(A=>{T.map(H=>{A.classList.remove(H)})})},mt=L=>{var v=[];return Be(L,T=>{typeof T=="string"&&(T=T.trim().split(/[\11\12\14\15\40]/)),Array.isArray(T)&&(v=v.concat(T))}),v.filter(Boolean)},We=L=>(Array.isArray(L)||(L=[L]),L),hn=(L,v,T)=>{if(!(T&&!T.contains(L)))for(;L&&L.matches;){if(L.matches(v))return L;L=L.parentNode}},De=(L,v=0)=>v>0?L[L.length-1]:L[0],Bt=L=>Object.keys(L).length===0,Pe=(L,v)=>{if(!L)return-1;v=v||L.nodeName;for(var T=0;L=L.previousElementSibling;)L.matches(v)&&T++;return T},Lt=(L,v)=>{Be(v,(T,A)=>{T==null?L.removeAttribute(A):L.setAttribute(A,""+T)})},jn=(L,v)=>{L.parentNode&&L.parentNode.replaceChild(v,L)},$n=(L,v)=>{if(v===null)return;if(typeof v=="string"){if(!v.length)return;v=new RegExp(v,"i")}let T=Y=>{var W=Y.data.match(v);if(W&&Y.data.length>0){var _e=document.createElement("span");_e.className="highlight";var Oe=Y.splitText(W.index);Oe.splitText(W[0].length);var we=Oe.cloneNode(!0);return _e.appendChild(we),jn(Oe,_e),1}return 0},A=Y=>{Y.nodeType===1&&Y.childNodes&&!/(script|style)/i.test(Y.tagName)&&(Y.className!=="highlight"||Y.tagName!=="SPAN")&&Array.from(Y.childNodes).forEach(W=>{H(W)})},H=Y=>Y.nodeType===3?T(Y):(A(Y),0);H(L)},Er=L=>{var v=L.querySelectorAll("span.highlight");Array.prototype.forEach.call(v,function(T){var A=T.parentNode;A.replaceChild(T.firstChild,T),A.normalize()})},Jr=65,On=13,nr=27,Pn=37,$r=38,xr=39,_i=40,is=8,jo=46,Tr=9,vi=(typeof navigator>"u"?!1:/Mac/.test(navigator.userAgent))?"metaKey":"ctrlKey";var ss={options:[],optgroups:[],plugins:[],delimiter:",",splitOn:null,persist:!0,diacritics:!0,create:null,createOnBlur:!1,createFilter:null,highlight:!0,openOnFocus:!0,shouldOpen:null,maxOptions:50,maxItems:null,hideSelected:null,duplicates:!1,addPrecedence:!1,selectOnTab:!1,preload:null,allowEmptyOption:!1,loadThrottle:300,loadingClass:"loading",dataAttr:null,optgroupField:"optgroup",valueField:"value",labelField:"text",disabledField:"disabled",optgroupLabelField:"label",optgroupValueField:"value",lockOptgroupOrder:!1,sortField:"$order",searchField:["text"],searchConjunction:"and",mode:null,wrapperClass:"ts-wrapper",controlClass:"ts-control",dropdownClass:"ts-dropdown",dropdownContentClass:"ts-dropdown-content",itemClass:"item",optionClass:"option",dropdownParent:null,controlInput:'',copyClassesToDropdown:!1,placeholder:null,hidePlaceholder:null,shouldLoad:function(L){return L.length>0},render:{}};let Kt=L=>typeof L>"u"||L===null?null:xn(L),xn=L=>typeof L=="boolean"?L?"1":"0":L+"",$i=L=>(L+"").replace(/&/g,"&").replace(//g,">").replace(/"/g,"""),au=(L,v)=>{var T;return function(A,H){var Y=this;T&&(Y.loading=Math.max(Y.loading-1,0),clearTimeout(T)),T=setTimeout(function(){T=null,Y.loadedSearches[A]=!0,L.call(Y,A,H)},v)}},$s=(L,v,T)=>{var A,H=L.trigger,Y={};L.trigger=function(){var W=arguments[0];if(v.indexOf(W)!==-1)Y[W]=arguments;else return H.apply(L,arguments)},T.apply(L,[]),L.trigger=H;for(A of v)A in Y&&H.apply(L,Y[A])},Po=L=>({start:L.selectionStart||0,length:(L.selectionEnd||0)-(L.selectionStart||0)}),Ct=(L,v=!1)=>{L&&(L.preventDefault(),v&&L.stopPropagation())},Ft=(L,v,T,A)=>{L.addEventListener(v,T,A)},yi=(L,v)=>{if(!v||!v[L])return!1;var T=(v.altKey?1:0)+(v.ctrlKey?1:0)+(v.shiftKey?1:0)+(v.metaKey?1:0);return T===1},Io=(L,v)=>{let T=L.getAttribute("id");return T||(L.setAttribute("id",v),v)},os=L=>L.replace(/[\\"']/g,"\\$&"),Sr=(L,v)=>{v&&L.append(v)};function $a(L,v){var T=Object.assign({},ss,v),A=T.dataAttr,H=T.labelField,Y=T.valueField,W=T.disabledField,_e=T.optgroupField,Oe=T.optgroupLabelField,we=T.optgroupValueField,$e=L.tagName.toLowerCase(),je=L.getAttribute("placeholder")||L.getAttribute("data-placeholder");if(!je&&!T.allowEmptyOption){let en=L.querySelector('option[value=""]');en&&(je=en.textContent)}var ze={placeholder:je,options:[],optgroups:[],items:[],maxItems:null},_t=()=>{var en,pn=ze.options,Yt={},Xe=1,qt=wt=>{var Dt=Object.assign({},wt.dataset),lt=A&&Dt[A];return typeof lt=="string"&<.length&&(Dt=Object.assign(Dt,JSON.parse(lt))),Dt},Yn=(wt,Dt)=>{var lt=Kt(wt.value);if(lt!=null&&!(!lt&&!T.allowEmptyOption)){if(Yt.hasOwnProperty(lt)){if(Dt){var gn=Yt[lt][_e];gn?Array.isArray(gn)?gn.push(Dt):Yt[lt][_e]=[gn,Dt]:Yt[lt][_e]=Dt}}else{var Mt=qt(wt);Mt[H]=Mt[H]||wt.textContent,Mt[Y]=Mt[Y]||lt,Mt[W]=Mt[W]||wt.disabled,Mt[_e]=Mt[_e]||Dt,Mt.$option=wt,Yt[lt]=Mt,pn.push(Mt)}wt.selected&&ze.items.push(lt)}},Zr=wt=>{var Dt,lt;lt=qt(wt),lt[Oe]=lt[Oe]||wt.getAttribute("label")||"",lt[we]=lt[we]||Xe++,lt[W]=lt[W]||wt.disabled,ze.optgroups.push(lt),Dt=lt[we],Be(wt.children,gn=>{Yn(gn,Dt)})};ze.maxItems=L.hasAttribute("multiple")?null:1,Be(L.children,wt=>{en=wt.tagName.toLowerCase(),en==="optgroup"?Zr(wt):en==="option"&&Yn(wt)})},Me=()=>{let en=L.getAttribute(A);if(en)ze.options=JSON.parse(en),Be(ze.options,Yt=>{ze.items.push(Yt[Y])});else{var pn=L.value.trim()||"";if(!T.allowEmptyOption&&!pn.length)return;let Yt=pn.split(T.delimiter);Be(Yt,Xe=>{let qt={};qt[H]=Xe,qt[Y]=Xe,ze.options.push(qt)}),ze.items=Yt}};return $e==="select"?_t():Me(),Object.assign({},ss,ze,v)}var Ha=0;class Tn extends n(t){constructor(v,T){super(),this.control_input=void 0,this.wrapper=void 0,this.dropdown=void 0,this.control=void 0,this.dropdown_content=void 0,this.focus_node=void 0,this.order=0,this.settings=void 0,this.input=void 0,this.tabIndex=void 0,this.is_select_tag=void 0,this.rtl=void 0,this.inputId=void 0,this._destroy=void 0,this.sifter=void 0,this.isOpen=!1,this.isDisabled=!1,this.isRequired=void 0,this.isInvalid=!1,this.isValid=!0,this.isLocked=!1,this.isFocused=!1,this.isInputHidden=!1,this.isSetup=!1,this.ignoreFocus=!1,this.ignoreHover=!1,this.hasOptions=!1,this.currentResults=void 0,this.lastValue="",this.caretPos=0,this.loading=0,this.loadedSearches={},this.activeOption=null,this.activeItems=[],this.optgroups={},this.options={},this.userOptions={},this.items=[],Ha++;var A,H=Je(v);if(H.tomselect)throw new Error("Tom Select already initialized on this element");H.tomselect=this;var Y=window.getComputedStyle&&window.getComputedStyle(H,null);A=Y.getPropertyValue("direction");let W=$a(H,T);this.settings=W,this.input=H,this.tabIndex=H.tabIndex||0,this.is_select_tag=H.tagName.toLowerCase()==="select",this.rtl=/rtl/i.test(A),this.inputId=Io(H,"tomselect-"+Ha),this.isRequired=H.required,this.sifter=new Tt(this.options,{diacritics:W.diacritics}),W.mode=W.mode||(W.maxItems===1?"single":"multi"),typeof W.hideSelected!="boolean"&&(W.hideSelected=W.mode==="multi"),typeof W.hidePlaceholder!="boolean"&&(W.hidePlaceholder=W.mode!=="multi");var _e=W.createFilter;typeof _e!="function"&&(typeof _e=="string"&&(_e=new RegExp(_e)),_e instanceof RegExp?W.createFilter=pn=>_e.test(pn):W.createFilter=pn=>this.settings.duplicates||!this.options[pn]),this.initializePlugins(W.plugins),this.setupCallbacks(),this.setupTemplates();let Oe=Je("
    "),we=Je("
    "),$e=this._render("dropdown"),je=Je('
    '),ze=this.input.getAttribute("class")||"",_t=W.mode;var Me;if(St(Oe,W.wrapperClass,ze,_t),St(we,W.controlClass),Sr(Oe,we),St($e,W.dropdownClass,_t),W.copyClassesToDropdown&&St($e,ze),St(je,W.dropdownContentClass),Sr($e,je),Je(W.dropdownParent||Oe).appendChild($e),Pt(W.controlInput)){Me=Je(W.controlInput);var en=["autocorrect","autocapitalize","autocomplete"];Ve(en,pn=>{H.getAttribute(pn)&&Lt(Me,{[pn]:H.getAttribute(pn)})}),Me.tabIndex=-1,we.appendChild(Me),this.focus_node=Me}else W.controlInput?(Me=Je(W.controlInput),this.focus_node=Me):(Me=Je(""),this.focus_node=we);this.wrapper=Oe,this.dropdown=$e,this.dropdown_content=je,this.control=we,this.control_input=Me,this.setup()}setup(){let v=this,T=v.settings,A=v.control_input,H=v.dropdown,Y=v.dropdown_content,W=v.wrapper,_e=v.control,Oe=v.input,we=v.focus_node,$e={passive:!0},je=v.inputId+"-ts-dropdown";Lt(Y,{id:je}),Lt(we,{role:"combobox","aria-haspopup":"listbox","aria-expanded":"false","aria-controls":je});let ze=Io(we,v.inputId+"-ts-control"),_t="label[for='"+Wt(v.inputId)+"']",Me=document.querySelector(_t),en=v.focus.bind(v);if(Me){Ft(Me,"click",en),Lt(Me,{for:ze});let Xe=Io(Me,v.inputId+"-ts-label");Lt(we,{"aria-labelledby":Xe}),Lt(Y,{"aria-labelledby":Xe})}if(W.style.width=Oe.style.width,v.plugins.names.length){let Xe="plugin-"+v.plugins.names.join(" plugin-");St([W,H],Xe)}(T.maxItems===null||T.maxItems>1)&&v.is_select_tag&&Lt(Oe,{multiple:"multiple"}),T.placeholder&&Lt(A,{placeholder:T.placeholder}),!T.splitOn&&T.delimiter&&(T.splitOn=new RegExp("\\s*"+S(T.delimiter)+"+\\s*")),T.load&&T.loadThrottle&&(T.load=au(T.load,T.loadThrottle)),v.control_input.type=Oe.type,Ft(H,"mousemove",()=>{v.ignoreHover=!1}),Ft(H,"mouseenter",Xe=>{var qt=hn(Xe.target,"[data-selectable]",H);qt&&v.onOptionHover(Xe,qt)},{capture:!0}),Ft(H,"click",Xe=>{let qt=hn(Xe.target,"[data-selectable]");qt&&(v.onOptionSelect(Xe,qt),Ct(Xe,!0))}),Ft(_e,"click",Xe=>{var qt=hn(Xe.target,"[data-ts-item]",_e);if(qt&&v.onItemSelect(Xe,qt)){Ct(Xe,!0);return}A.value==""&&(v.onClick(),Ct(Xe,!0))}),Ft(we,"keydown",Xe=>v.onKeyDown(Xe)),Ft(A,"keypress",Xe=>v.onKeyPress(Xe)),Ft(A,"input",Xe=>v.onInput(Xe)),Ft(we,"blur",Xe=>v.onBlur(Xe)),Ft(we,"focus",Xe=>v.onFocus(Xe)),Ft(A,"paste",Xe=>v.onPaste(Xe));let pn=Xe=>{let qt=Xe.composedPath()[0];if(!W.contains(qt)&&!H.contains(qt)){v.isFocused&&v.blur(),v.inputState();return}qt==A&&v.isOpen?Xe.stopPropagation():Ct(Xe,!0)},Yt=()=>{v.isOpen&&v.positionDropdown()};Ft(document,"mousedown",pn),Ft(window,"scroll",Yt,$e),Ft(window,"resize",Yt,$e),this._destroy=()=>{document.removeEventListener("mousedown",pn),window.removeEventListener("scroll",Yt),window.removeEventListener("resize",Yt),Me&&Me.removeEventListener("click",en)},this.revertSettings={innerHTML:Oe.innerHTML,tabIndex:Oe.tabIndex},Oe.tabIndex=-1,Oe.insertAdjacentElement("afterend",v.wrapper),v.sync(!1),T.items=[],delete T.optgroups,delete T.options,Ft(Oe,"invalid",()=>{v.isValid&&(v.isValid=!1,v.isInvalid=!0,v.refreshState())}),v.updateOriginalInput(),v.refreshItems(),v.close(!1),v.inputState(),v.isSetup=!0,Oe.disabled?v.disable():v.enable(),v.on("change",this.onChange),St(Oe,"tomselected","ts-hidden-accessible"),v.trigger("initialize"),T.preload===!0&&v.preload()}setupOptions(v=[],T=[]){this.addOptions(v),Ve(T,A=>{this.registerOptionGroup(A)})}setupTemplates(){var v=this,T=v.settings.labelField,A=v.settings.optgroupLabelField,H={optgroup:Y=>{let W=document.createElement("div");return W.className="optgroup",W.appendChild(Y.options),W},optgroup_header:(Y,W)=>'
    '+W(Y[A])+"
    ",option:(Y,W)=>"
    "+W(Y[T])+"
    ",item:(Y,W)=>"
    "+W(Y[T])+"
    ",option_create:(Y,W)=>'
    Add '+W(Y.input)+"
    ",no_results:()=>'
    No results found
    ',loading:()=>'
    ',not_loading:()=>{},dropdown:()=>"
    "};v.settings.render=Object.assign({},H,v.settings.render)}setupCallbacks(){var v,T,A={initialize:"onInitialize",change:"onChange",item_add:"onItemAdd",item_remove:"onItemRemove",item_select:"onItemSelect",clear:"onClear",option_add:"onOptionAdd",option_remove:"onOptionRemove",option_clear:"onOptionClear",optgroup_add:"onOptionGroupAdd",optgroup_remove:"onOptionGroupRemove",optgroup_clear:"onOptionGroupClear",dropdown_open:"onDropdownOpen",dropdown_close:"onDropdownClose",type:"onType",load:"onLoad",focus:"onFocus",blur:"onBlur"};for(v in A)T=this.settings[A[v]],T&&this.on(v,T)}sync(v=!0){let T=this,A=v?$a(T.input,{delimiter:T.settings.delimiter}):T.settings;T.setupOptions(A.options,A.optgroups),T.setValue(A.items||[],!0),T.lastQuery=null}onClick(){var v=this;if(v.activeItems.length>0){v.clearActiveItems(),v.focus();return}v.isFocused&&v.isOpen?v.blur():v.focus()}onMouseDown(){}onChange(){It(this.input,"input"),It(this.input,"change")}onPaste(v){var T=this;if(T.isInputHidden||T.isLocked){Ct(v);return}T.settings.splitOn&&setTimeout(()=>{var A=T.inputValue();if(A.match(T.settings.splitOn)){var H=A.trim().split(T.settings.splitOn);Ve(H,Y=>{Kt(Y)&&(this.options[Y]?T.addItem(Y):T.createItem(Y))})}},0)}onKeyPress(v){var T=this;if(T.isLocked){Ct(v);return}var A=String.fromCharCode(v.keyCode||v.which);if(T.settings.create&&T.settings.mode==="multi"&&A===T.settings.delimiter){T.createItem(),Ct(v);return}}onKeyDown(v){var T=this;if(T.ignoreHover=!0,T.isLocked){v.keyCode!==Tr&&Ct(v);return}switch(v.keyCode){case Jr:if(yi(vi,v)&&T.control_input.value==""){Ct(v),T.selectAll();return}break;case nr:T.isOpen&&(Ct(v,!0),T.close()),T.clearActiveItems();return;case _i:if(!T.isOpen&&T.hasOptions)T.open();else if(T.activeOption){let A=T.getAdjacent(T.activeOption,1);A&&T.setActiveOption(A)}Ct(v);return;case $r:if(T.activeOption){let A=T.getAdjacent(T.activeOption,-1);A&&T.setActiveOption(A)}Ct(v);return;case On:T.canSelect(T.activeOption)?(T.onOptionSelect(v,T.activeOption),Ct(v)):(T.settings.create&&T.createItem()||document.activeElement==T.control_input&&T.isOpen)&&Ct(v);return;case Pn:T.advanceSelection(-1,v);return;case xr:T.advanceSelection(1,v);return;case Tr:T.settings.selectOnTab&&(T.canSelect(T.activeOption)&&(T.onOptionSelect(v,T.activeOption),Ct(v)),T.settings.create&&T.createItem()&&Ct(v));return;case is:case jo:T.deleteSelection(v);return}T.isInputHidden&&!yi(vi,v)&&Ct(v)}onInput(v){var T=this;if(!T.isLocked){var A=T.inputValue();T.lastValue!==A&&(T.lastValue=A,T.settings.shouldLoad.call(T,A)&&T.load(A),T.refreshOptions(),T.trigger("type",A))}}onOptionHover(v,T){this.ignoreHover||this.setActiveOption(T,!1)}onFocus(v){var T=this,A=T.isFocused;if(T.isDisabled){T.blur(),Ct(v);return}T.ignoreFocus||(T.isFocused=!0,T.settings.preload==="focus"&&T.preload(),A||T.trigger("focus"),T.activeItems.length||(T.showInput(),T.refreshOptions(!!T.settings.openOnFocus)),T.refreshState())}onBlur(v){if(document.hasFocus()!==!1){var T=this;if(T.isFocused){T.isFocused=!1,T.ignoreFocus=!1;var A=()=>{T.close(),T.setActiveItem(),T.setCaret(T.items.length),T.trigger("blur")};T.settings.create&&T.settings.createOnBlur?T.createItem(null,A):A()}}}onOptionSelect(v,T){var A,H=this;T.parentElement&&T.parentElement.matches("[data-disabled]")||(T.classList.contains("create")?H.createItem(null,()=>{H.settings.closeAfterSelect&&H.close()}):(A=T.dataset.value,typeof A<"u"&&(H.lastQuery=null,H.addItem(A),H.settings.closeAfterSelect&&H.close(),!H.settings.hideSelected&&v.type&&/click/.test(v.type)&&H.setActiveOption(T))))}canSelect(v){return!!(this.isOpen&&v&&this.dropdown_content.contains(v))}onItemSelect(v,T){var A=this;return!A.isLocked&&A.settings.mode==="multi"?(Ct(v),A.setActiveItem(T,v),!0):!1}canLoad(v){return!(!this.settings.load||this.loadedSearches.hasOwnProperty(v))}load(v){let T=this;if(!T.canLoad(v))return;St(T.wrapper,T.settings.loadingClass),T.loading++;let A=T.loadCallback.bind(T);T.settings.load.call(T,v,A)}loadCallback(v,T){let A=this;A.loading=Math.max(A.loading-1,0),A.lastQuery=null,A.clearActiveOption(),A.setupOptions(v,T),A.refreshOptions(A.isFocused&&!A.isInputHidden),A.loading||Rt(A.wrapper,A.settings.loadingClass),A.trigger("load",v,T)}preload(){var v=this.wrapper.classList;v.contains("preloaded")||(v.add("preloaded"),this.load(""))}setTextboxValue(v=""){var T=this.control_input,A=T.value!==v;A&&(T.value=v,It(T,"update"),this.lastValue=v)}getValue(){return this.is_select_tag&&this.input.hasAttribute("multiple")?this.items:this.items.join(this.settings.delimiter)}setValue(v,T){var A=T?[]:["change"];$s(this,A,()=>{this.clear(T),this.addItems(v,T)})}setMaxItems(v){v===0&&(v=null),this.settings.maxItems=v,this.refreshState()}setActiveItem(v,T){var A=this,H,Y,W,_e,Oe,we;if(A.settings.mode!=="single"){if(!v){A.clearActiveItems(),A.isFocused&&A.showInput();return}if(H=T&&T.type.toLowerCase(),H==="click"&&yi("shiftKey",T)&&A.activeItems.length){for(we=A.getLastActive(),W=Array.prototype.indexOf.call(A.control.children,we),_e=Array.prototype.indexOf.call(A.control.children,v),W>_e&&(Oe=W,W=_e,_e=Oe),Y=W;Y<=_e;Y++)v=A.control.children[Y],A.activeItems.indexOf(v)===-1&&A.setActiveItemClass(v);Ct(T)}else H==="click"&&yi(vi,T)||H==="keydown"&&yi("shiftKey",T)?v.classList.contains("active")?A.removeActiveItem(v):A.setActiveItemClass(v):(A.clearActiveItems(),A.setActiveItemClass(v));A.hideInput(),A.isFocused||A.focus()}}setActiveItemClass(v){let T=this,A=T.control.querySelector(".last-active");A&&Rt(A,"last-active"),St(v,"active last-active"),T.trigger("item_select",v),T.activeItems.indexOf(v)==-1&&T.activeItems.push(v)}removeActiveItem(v){var T=this.activeItems.indexOf(v);this.activeItems.splice(T,1),Rt(v,"active")}clearActiveItems(){Rt(this.activeItems,"active"),this.activeItems=[]}setActiveOption(v,T=!0){v!==this.activeOption&&(this.clearActiveOption(),v&&(this.activeOption=v,Lt(this.focus_node,{"aria-activedescendant":v.getAttribute("id")}),Lt(v,{"aria-selected":"true"}),St(v,"active"),T&&this.scrollToOption(v)))}scrollToOption(v,T){if(!v)return;let A=this.dropdown_content,H=A.clientHeight,Y=A.scrollTop||0,W=v.offsetHeight,_e=v.getBoundingClientRect().top-A.getBoundingClientRect().top+Y;_e+W>H+Y?this.scroll(_e-H+W,T):_e{v.setActiveItemClass(A)}))}inputState(){var v=this;v.control.contains(v.control_input)&&(Lt(v.control_input,{placeholder:v.settings.placeholder}),v.activeItems.length>0||!v.isFocused&&v.settings.hidePlaceholder&&v.items.length>0?(v.setTextboxValue(),v.isInputHidden=!0):(v.settings.hidePlaceholder&&v.items.length>0&&Lt(v.control_input,{placeholder:""}),v.isInputHidden=!1),v.wrapper.classList.toggle("input-hidden",v.isInputHidden))}hideInput(){this.inputState()}showInput(){this.inputState()}inputValue(){return this.control_input.value.trim()}focus(){var v=this;v.isDisabled||(v.ignoreFocus=!0,v.control_input.offsetWidth?v.control_input.focus():v.focus_node.focus(),setTimeout(()=>{v.ignoreFocus=!1,v.onFocus()},0))}blur(){this.focus_node.blur(),this.onBlur()}getScoreFunction(v){return this.sifter.getScoreFunction(v,this.getSearchOptions())}getSearchOptions(){var v=this.settings,T=v.sortField;return typeof v.sortField=="string"&&(T=[{field:v.sortField}]),{fields:v.searchField,conjunction:v.searchConjunction,sort:T,nesting:v.nesting}}search(v){var T,A,H=this,Y=this.getSearchOptions();if(H.settings.score&&(A=H.settings.score.call(H,v),typeof A!="function"))throw new Error('Tom Select "score" setting must be a function that returns a function');return v!==H.lastQuery?(H.lastQuery=v,T=H.sifter.search(v,Object.assign(Y,{score:A})),H.currentResults=T):T=Object.assign({},H.currentResults),H.settings.hideSelected&&(T.items=T.items.filter(W=>{let _e=Kt(W.id);return!(_e&&H.items.indexOf(_e)!==-1)})),T}refreshOptions(v=!0){var T,A,H,Y,W,_e,Oe,we,$e,je;let ze={},_t=[];var Me=this,en=Me.inputValue();let pn=en===Me.lastQuery||en==""&&Me.lastQuery==null;var Yt=Me.search(en),Xe=null,qt=Me.settings.shouldOpen||!1,Yn=Me.dropdown_content;for(pn&&(Xe=Me.activeOption,Xe&&($e=Xe.closest("[data-group]"))),Y=Yt.items.length,typeof Me.settings.maxOptions=="number"&&(Y=Math.min(Y,Me.settings.maxOptions)),Y>0&&(qt=!0),T=0;T0&&(Mt=Mt.cloneNode(!0),Lt(Mt,{id:lt.$id+"-clone-"+A,"aria-selected":null}),Mt.classList.add("ts-cloned"),Rt(Mt,"active"),Me.activeOption&&Me.activeOption.dataset.value==Dt&&$e&&$e.dataset.group===W.toString()&&(Xe=Mt)),Cr.appendChild(Mt),ze[W]=Cr}}Me.settings.lockOptgroupOrder&&_t.sort((wt,Dt)=>{let lt=Me.optgroups[wt],gn=Me.optgroups[Dt],Mt=lt&<.$order||0,Cr=gn&&gn.$order||0;return Mt-Cr}),Oe=document.createDocumentFragment(),Ve(_t,wt=>{let Dt=ze[wt];if(!Dt||!Dt.children.length)return;let lt=Me.optgroups[wt];if(lt!==void 0){let gn=document.createDocumentFragment(),Mt=Me.render("optgroup_header",lt);Sr(gn,Mt),Sr(gn,Dt);let Cr=Me.render("optgroup",{group:lt,options:gn});Sr(Oe,Cr)}else Sr(Oe,Dt)}),Yn.innerHTML="",Sr(Yn,Oe),Me.settings.highlight&&(Er(Yn),Yt.query.length&&Yt.tokens.length&&Ve(Yt.tokens,wt=>{$n(Yn,wt.regex)}));var Zr=wt=>{let Dt=Me.render(wt,{input:en});return Dt&&(qt=!0,Yn.insertBefore(Dt,Yn.firstChild)),Dt};if(Me.loading?Zr("loading"):Me.settings.shouldLoad.call(Me,en)?Yt.items.length===0&&Zr("no_results"):Zr("not_loading"),we=Me.canCreate(en),we&&(je=Zr("option_create")),Me.hasOptions=Yt.items.length>0||we,qt){if(Yt.items.length>0){if(!Xe&&Me.settings.mode==="single"&&Me.items[0]!=null&&(Xe=Me.getOption(Me.items[0])),!Yn.contains(Xe)){let wt=0;je&&!Me.settings.addPrecedence&&(wt=1),Xe=Me.selectable()[wt]}}else je&&(Xe=je);v&&!Me.isOpen&&(Me.open(),Me.scrollToOption(Xe,"auto")),Me.setActiveOption(Xe)}else Me.clearActiveOption(),v&&Me.isOpen&&Me.close(!1)}selectable(){return this.dropdown_content.querySelectorAll("[data-selectable]")}addOption(v,T=!1){let A=this;if(Array.isArray(v))return A.addOptions(v,T),!1;let H=Kt(v[A.settings.valueField]);return H===null||A.options.hasOwnProperty(H)?!1:(v.$order=v.$order||++A.order,v.$id=A.inputId+"-opt-"+v.$order,A.options[H]=v,A.lastQuery=null,T&&(A.userOptions[H]=T,A.trigger("option_add",H,v)),H)}addOptions(v,T=!1){Ve(v,A=>{this.addOption(A,T)})}registerOption(v){return this.addOption(v)}registerOptionGroup(v){var T=Kt(v[this.settings.optgroupValueField]);return T===null?!1:(v.$order=v.$order||++this.order,this.optgroups[T]=v,T)}addOptionGroup(v,T){var A;T[this.settings.optgroupValueField]=v,(A=this.registerOptionGroup(T))&&this.trigger("optgroup_add",A,T)}removeOptionGroup(v){this.optgroups.hasOwnProperty(v)&&(delete this.optgroups[v],this.clearCache(),this.trigger("optgroup_remove",v))}clearOptionGroups(){this.optgroups={},this.clearCache(),this.trigger("optgroup_clear")}updateOption(v,T){let A=this;var H,Y;let W=Kt(v),_e=Kt(T[A.settings.valueField]);if(W===null)return;let Oe=A.options[W];if(Oe==null)return;if(typeof _e!="string")throw new Error("Value must be set in option data");let we=A.getOption(W),$e=A.getItem(W);if(T.$order=T.$order||Oe.$order,delete A.options[W],A.uncacheValue(_e),A.options[_e]=T,we){if(A.dropdown_content.contains(we)){let je=A._render("option",T);jn(we,je),A.activeOption===we&&A.setActiveOption(je)}we.remove()}$e&&(Y=A.items.indexOf(W),Y!==-1&&A.items.splice(Y,1,_e),H=A._render("item",T),$e.classList.contains("active")&&St(H,"active"),jn($e,H)),A.lastQuery=null}removeOption(v,T){let A=this;v=xn(v),A.uncacheValue(v),delete A.userOptions[v],delete A.options[v],A.lastQuery=null,A.trigger("option_remove",v),A.removeItem(v,T)}clearOptions(v){let T=(v||this.clearFilter).bind(this);this.loadedSearches={},this.userOptions={},this.clearCache();let A={};Ve(this.options,(H,Y)=>{T(H,Y)&&(A[Y]=H)}),this.options=this.sifter.items=A,this.lastQuery=null,this.trigger("option_clear")}clearFilter(v,T){return this.items.indexOf(T)>=0}getOption(v,T=!1){let A=Kt(v);if(A===null)return null;let H=this.options[A];if(H!=null){if(H.$div)return H.$div;if(T)return this._render("option",H)}return null}getAdjacent(v,T,A="option"){var H=this,Y;if(!v)return null;A=="item"?Y=H.controlChildren():Y=H.dropdown_content.querySelectorAll("[data-selectable]");for(let W=0;W0?Y[W+1]:Y[W-1];return null}getItem(v){if(typeof v=="object")return v;var T=Kt(v);return T!==null?this.control.querySelector(`[data-value="${os(T)}"]`):null}addItems(v,T){var A=this,H=Array.isArray(v)?v:[v];H=H.filter(W=>A.items.indexOf(W)===-1);let Y=H[H.length-1];H.forEach(W=>{A.isPending=W!==Y,A.addItem(W,T)})}addItem(v,T){var A=T?[]:["change","dropdown_close"];$s(this,A,()=>{var H,Y;let W=this,_e=W.settings.mode,Oe=Kt(v);if(!(Oe&&W.items.indexOf(Oe)!==-1&&(_e==="single"&&W.close(),_e==="single"||!W.settings.duplicates))&&!(Oe===null||!W.options.hasOwnProperty(Oe))&&(_e==="single"&&W.clear(T),!(_e==="multi"&&W.isFull()))){if(H=W._render("item",W.options[Oe]),W.control.contains(H)&&(H=H.cloneNode(!0)),Y=W.isFull(),W.items.splice(W.caretPos,0,Oe),W.insertAtCaret(H),W.isSetup){if(!W.isPending&&W.settings.hideSelected){let we=W.getOption(Oe),$e=W.getAdjacent(we,1);$e&&W.setActiveOption($e)}!W.isPending&&!W.settings.closeAfterSelect&&W.refreshOptions(W.isFocused&&_e!=="single"),W.settings.closeAfterSelect!=!1&&W.isFull()?W.close():W.isPending||W.positionDropdown(),W.trigger("item_add",Oe,H),W.isPending||W.updateOriginalInput({silent:T})}(!W.isPending||!Y&&W.isFull())&&(W.inputState(),W.refreshState())}})}removeItem(v=null,T){let A=this;if(v=A.getItem(v),!v)return;var H,Y;let W=v.dataset.value;H=Pe(v),v.remove(),v.classList.contains("active")&&(Y=A.activeItems.indexOf(v),A.activeItems.splice(Y,1),Rt(v,"active")),A.items.splice(H,1),A.lastQuery=null,!A.settings.persist&&A.userOptions.hasOwnProperty(W)&&A.removeOption(W,T),H{}){arguments.length===3&&(T=arguments[2]),typeof T!="function"&&(T=()=>{});var A=this,H=A.caretPos,Y;if(v=v||A.inputValue(),!A.canCreate(v))return T(),!1;A.lock();var W=!1,_e=Oe=>{if(A.unlock(),!Oe||typeof Oe!="object")return T();var we=Kt(Oe[A.settings.valueField]);if(typeof we!="string")return T();A.setTextboxValue(),A.addOption(Oe,!0),A.setCaret(H),A.addItem(we),T(Oe),W=!0};return typeof A.settings.create=="function"?Y=A.settings.create.call(this,v,_e):Y={[A.settings.labelField]:v,[A.settings.valueField]:v},W||_e(Y),!0}refreshItems(){var v=this;v.lastQuery=null,v.isSetup&&v.addItems(v.items),v.updateOriginalInput(),v.refreshState()}refreshState(){let v=this;v.refreshValidityState();let T=v.isFull(),A=v.isLocked;v.wrapper.classList.toggle("rtl",v.rtl);let H=v.wrapper.classList;H.toggle("focus",v.isFocused),H.toggle("disabled",v.isDisabled),H.toggle("required",v.isRequired),H.toggle("invalid",!v.isValid),H.toggle("locked",A),H.toggle("full",T),H.toggle("input-active",v.isFocused&&!v.isInputHidden),H.toggle("dropdown-active",v.isOpen),H.toggle("has-options",Bt(v.options)),H.toggle("has-items",v.items.length>0)}refreshValidityState(){var v=this;v.input.validity&&(v.isValid=v.input.validity.valid,v.isInvalid=!v.isValid)}isFull(){return this.settings.maxItems!==null&&this.items.length>=this.settings.maxItems}updateOriginalInput(v={}){let T=this;var A,H;let Y=T.input.querySelector('option[value=""]');if(T.is_select_tag){let Oe=function(we,$e,je){return we||(we=Je('")),we!=Y&&T.input.append(we),W.push(we),(we!=Y||_e>0)&&(we.selected=!0),we},W=[],_e=T.input.querySelectorAll("option:checked").length;T.input.querySelectorAll("option:checked").forEach(we=>{we.selected=!1}),T.items.length==0&&T.settings.mode=="single"?Oe(Y,"",""):T.items.forEach(we=>{if(A=T.options[we],H=A[T.settings.labelField]||"",W.includes(A.$option)){let $e=T.input.querySelector(`option[value="${os(we)}"]:not(:checked)`);Oe($e,we,H)}else A.$option=Oe(A.$option,we,H)})}else T.input.value=T.getValue();T.isSetup&&(v.silent||T.trigger("change",T.getValue()))}open(){var v=this;v.isLocked||v.isOpen||v.settings.mode==="multi"&&v.isFull()||(v.isOpen=!0,Lt(v.focus_node,{"aria-expanded":"true"}),v.refreshState(),Ee(v.dropdown,{visibility:"hidden",display:"block"}),v.positionDropdown(),Ee(v.dropdown,{visibility:"visible",display:"block"}),v.focus(),v.trigger("dropdown_open",v.dropdown))}close(v=!0){var T=this,A=T.isOpen;v&&(T.setTextboxValue(),T.settings.mode==="single"&&T.items.length&&T.hideInput()),T.isOpen=!1,Lt(T.focus_node,{"aria-expanded":"false"}),Ee(T.dropdown,{display:"none"}),T.settings.hideSelected&&T.clearActiveOption(),T.refreshState(),A&&T.trigger("dropdown_close",T.dropdown)}positionDropdown(){if(this.settings.dropdownParent==="body"){var v=this.control,T=v.getBoundingClientRect(),A=v.offsetHeight+T.top+window.scrollY,H=T.left+window.scrollX;Ee(this.dropdown,{width:T.width+"px",top:A+"px",left:H+"px"})}}clear(v){var T=this;if(T.items.length){var A=T.controlChildren();Ve(A,H=>{T.removeItem(H,!0)}),T.showInput(),v||T.updateOriginalInput(),T.trigger("clear")}}insertAtCaret(v){let T=this,A=T.caretPos,H=T.control;H.insertBefore(v,H.children[A]||null),T.setCaret(A+1)}deleteSelection(v){var T,A,H,Y,W=this;T=v&&v.keyCode===is?-1:1,A=Po(W.control_input);let _e=[];if(W.activeItems.length)Y=De(W.activeItems,T),H=Pe(Y),T>0&&H++,Ve(W.activeItems,Oe=>_e.push(Oe));else if((W.isFocused||W.settings.mode==="single")&&W.items.length){let Oe=W.controlChildren(),we;T<0&&A.start===0&&A.length===0?we=Oe[W.caretPos-1]:T>0&&A.start===W.inputValue().length&&(we=Oe[W.caretPos]),we!==void 0&&_e.push(we)}if(!W.shouldDelete(_e,v))return!1;for(Ct(v,!0),typeof H<"u"&&W.setCaret(H);_e.length;)W.removeItem(_e.pop());return W.showInput(),W.positionDropdown(),W.refreshOptions(!1),!0}shouldDelete(v,T){let A=v.map(H=>H.dataset.value);return!(!A.length||typeof this.settings.onDelete=="function"&&this.settings.onDelete(A,T)===!1)}advanceSelection(v,T){var A,H,Y=this;Y.rtl&&(v*=-1),!Y.inputValue().length&&(yi(vi,T)||yi("shiftKey",T)?(A=Y.getLastActive(v),A?A.classList.contains("active")?H=Y.getAdjacent(A,v,"item"):H=A:v>0?H=Y.control_input.nextElementSibling:H=Y.control_input.previousElementSibling,H&&(H.classList.contains("active")&&Y.removeActiveItem(A),Y.setActiveItemClass(H))):Y.moveCaret(v))}moveCaret(v){}getLastActive(v){let T=this.control.querySelector(".last-active");if(T)return T;var A=this.control.querySelectorAll(".active");if(A)return De(A,v)}setCaret(v){this.caretPos=this.items.length}controlChildren(){return Array.from(this.control.querySelectorAll("[data-ts-item]"))}lock(){this.isLocked=!0,this.refreshState()}unlock(){this.isLocked=!1,this.refreshState()}disable(){var v=this;v.input.disabled=!0,v.control_input.disabled=!0,v.focus_node.tabIndex=-1,v.isDisabled=!0,this.close(),v.lock()}enable(){var v=this;v.input.disabled=!1,v.control_input.disabled=!1,v.focus_node.tabIndex=v.tabIndex,v.isDisabled=!1,v.unlock()}destroy(){var v=this,T=v.revertSettings;v.trigger("destroy"),v.off(),v.wrapper.remove(),v.dropdown.remove(),v.input.innerHTML=T.innerHTML,v.input.tabIndex=T.tabIndex,Rt(v.input,"tomselected","ts-hidden-accessible"),v._destroy(),delete v.input.tomselect}render(v,T){var A,H;let Y=this;if(typeof this.settings.render[v]!="function"||(H=Y.settings.render[v].call(this,T,$i),!H))return null;if(H=Je(H),v==="option"||v==="option_create"?T[Y.settings.disabledField]?Lt(H,{"aria-disabled":"true"}):Lt(H,{"data-selectable":""}):v==="optgroup"&&(A=T.group[Y.settings.optgroupValueField],Lt(H,{"data-group":A}),T.group[Y.settings.disabledField]&&Lt(H,{"data-disabled":""})),v==="option"||v==="item"){let W=xn(T[Y.settings.valueField]);Lt(H,{"data-value":W}),v==="item"?(St(H,Y.settings.itemClass),Lt(H,{"data-ts-item":""})):(St(H,Y.settings.optionClass),Lt(H,{role:"option",id:T.$id}),T.$div=H,Y.options[W]=T)}return H}_render(v,T){let A=this.render(v,T);if(A==null)throw"HTMLElement expected";return A}clearCache(){Ve(this.options,v=>{v.$div&&(v.$div.remove(),delete v.$div)})}uncacheValue(v){let T=this.getOption(v);T&&T.remove()}canCreate(v){return this.settings.create&&v.length>0&&this.settings.createFilter.call(this,v)}hook(v,T,A){var H=this,Y=H[T];H[T]=function(){var W,_e;return v==="after"&&(W=Y.apply(H,arguments)),_e=A.apply(H,arguments),v==="instead"?_e:(v==="before"&&(W=Y.apply(H,arguments)),W)}}}function Wa(){Ft(this.input,"change",()=>{this.sync()})}function Ro(){var L=this,v=L.onOptionSelect;L.settings.hideSelected=!1;var T=function(H){setTimeout(()=>{var Y=H.querySelector("input");Y instanceof HTMLInputElement&&(H.classList.contains("selected")?Y.checked=!0:Y.checked=!1)},1)};L.hook("after","setupTemplates",()=>{var A=L.settings.render.option;L.settings.render.option=(H,Y)=>{var W=Je(A.call(L,H,Y)),_e=document.createElement("input");_e.addEventListener("click",function(we){Ct(we)}),_e.type="checkbox";let Oe=Kt(H[L.settings.valueField]);return Oe&&L.items.indexOf(Oe)>-1&&(_e.checked=!0),W.prepend(_e),W}}),L.on("item_remove",A=>{var H=L.getOption(A);H&&(H.classList.remove("selected"),T(H))}),L.on("item_add",A=>{var H=L.getOption(A);H&&T(H)}),L.hook("instead","onOptionSelect",(A,H)=>{if(H.classList.contains("selected")){H.classList.remove("selected"),L.removeItem(H.dataset.value),L.refreshOptions(),Ct(A,!0);return}v.call(L,A,H),T(H)})}function lu(L){let v=this,T=Object.assign({className:"clear-button",title:"Clear All",html:A=>`
    `},L);v.on("initialize",()=>{var A=Je(T.html(T));A.addEventListener("click",H=>{v.isDisabled||(v.clear(),v.settings.mode==="single"&&v.settings.allowEmptyOption&&v.addItem(""),H.preventDefault(),H.stopPropagation())}),v.control.appendChild(A)})}function Ba(){var L=this;if(!$.fn.sortable)throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".');if(L.settings.mode==="multi"){var v=L.lock,T=L.unlock;L.hook("instead","lock",()=>{var A=$(L.control).data("sortable");return A&&A.disable(),v.call(L)}),L.hook("instead","unlock",()=>{var A=$(L.control).data("sortable");return A&&A.enable(),T.call(L)}),L.on("initialize",()=>{var A=$(L.control).sortable({items:"[data-value]",forcePlaceholderSize:!0,disabled:L.isLocked,start:(H,Y)=>{Y.placeholder.css("width",Y.helper.css("width")),A.css({overflow:"visible"})},stop:()=>{A.css({overflow:"hidden"});var H=[];A.children("[data-value]").each(function(){this.dataset.value&&H.push(this.dataset.value)}),L.setValue(H)}})})}}function uu(L){let v=this,T=Object.assign({title:"Untitled",headerClass:"dropdown-header",titleRowClass:"dropdown-header-title",labelClass:"dropdown-header-label",closeClass:"dropdown-header-close",html:A=>'
    '+A.title+'×
    '},L);v.on("initialize",()=>{var A=Je(T.html(T)),H=A.querySelector("."+T.closeClass);H&&H.addEventListener("click",Y=>{Ct(Y,!0),v.close()}),v.dropdown.insertBefore(A,v.dropdown.firstChild)})}function Hi(){var L=this;L.hook("instead","setCaret",v=>{L.settings.mode==="single"||!L.control.contains(L.control_input)?v=L.items.length:(v=Math.max(0,Math.min(L.items.length,v)),v!=L.caretPos&&!L.isPending&&L.controlChildren().forEach((T,A)=>{A{if(!L.isFocused)return;let T=L.getLastActive(v);if(T){let A=Pe(T);L.setCaret(v>0?A+1:A),L.setActiveItem(),Rt(T,"last-active")}else L.setCaret(L.caretPos+v)})}function qa(){let L=this;L.settings.shouldOpen=!0,L.hook("before","setup",()=>{L.focus_node=L.control,St(L.control_input,"dropdown-input");let v=Je('

    -

    +

    GUIDANCE

    <%= @tmpl[:activity].activity_guidance %> @@ -59,9 +56,7 @@ <% if @tmpl[:activity].activity_standard.present? %>

    Standard: - <% @tmpl[:activity].activity_standard_info.each do |activity| %> - <%= activity[:standard] %> - <% end %> + <%= @tmpl[:activity].activity_standard %>

    <% end %> diff --git a/lib/doc_template/templates/gdoc/group-math.html.erb b/lib/doc_template/templates/gdoc/group-math.html.erb index 5d49d7c1..0a75e085 100644 --- a/lib/doc_template/templates/gdoc/group-math.html.erb +++ b/lib/doc_template/templates/gdoc/group-math.html.erb @@ -1,17 +1,8 @@
    - <% if @tmpl[:section].title.parameterize == 'foundational-skills' %> - - - - -
    -

    FOUNDATIONAL SKILLS LESSON

    -
    - <% else %> -
    -
    -
    - <% end %> + +
    +
    +
    @@ -27,22 +18,9 @@ <% end %> <% if @tmpl[:section].try(:lesson_objective).present? %> - <% unless @tmpl[:foundational_skills] %> -

    Lesson Summary:

    - <% end %> +

    Lesson Summary:

    <%= @tmpl[:section].lesson_objective %>

    <% end %> - <% if @tmpl[:foundational_skills] %> - <% if (standards = @tmpl[:section].section_standard_info).any? %> -

    - Standards: - <% standards.each_with_index do |data, idx| %> - <%= data[:standard] %><%= ',' if idx + 1 < standards.size %> - <% end %> -

    - <% end %> - <% end %> - <%= @tmpl[:content] %> diff --git a/lib/doc_template/templates/gdoc/pd.html.erb b/lib/doc_template/templates/gdoc/pd.html.erb deleted file mode 100644 index ed7de580..00000000 --- a/lib/doc_template/templates/gdoc/pd.html.erb +++ /dev/null @@ -1,21 +0,0 @@ -
    -
    - - - -
    -

    PROFESSIONAL DEVELOPMENT

    - <% if @tmpl[:title].present? %> -

    <%= @tmpl[:resource]&.title || @tmpl[:title] %>

    - <% end %> -
    - <% if @tmpl[:type] == DocTemplate::Tags::PdTag::TYPE_CG %> -

    UnboundEd <%= @tmpl[:content_guide].subject == 'math' ? 'Math' : 'ELA' %> Guide / <%= @tmpl[:title] %>

    - <% end %> - <% if @tmpl[:description].present? %> -
    Description
    -

    <%= @tmpl[:description] %>

    - <% end %> -
    -

    -
    diff --git a/lib/doc_template/templates/gdoc/section.html.erb b/lib/doc_template/templates/gdoc/section.html.erb index 5a613641..26d03063 100644 --- a/lib/doc_template/templates/gdoc/section.html.erb +++ b/lib/doc_template/templates/gdoc/section.html.erb @@ -18,13 +18,7 @@

    - <% @tmpl[:section_icons].each do |icon| %> - - <% end %> <%= @tmpl[:section].title %> - <% if @tmpl[:section].priority.present? %> - - <% end %>

    <%= @tmpl[:section].time.zero? ? '—' : "#{@tmpl[:section].time} mins" %>
    ","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};Kt.tbody=Kt.tfoot=Kt.colgroup=Kt.caption=Kt.thead,Kt.th=Kt.td,pe.option||(Kt.optgroup=Kt.option=[1,""]);function xn(u,f){var _;return typeof u.getElementsByTagName<"u"?_=u.getElementsByTagName(f||"*"):typeof u.querySelectorAll<"u"?_=u.querySelectorAll(f||"*"):_=[],f===void 0||f&&R(u,f)?a.merge([u],_):_}function $i(u,f){for(var _=0,y=u.length;_-1){k&&k.push(b);continue}if(re=Pn(b),P=xn(Se.appendChild(b),"script"),re&&$i(P),_)for(ue=0;b=P[ue++];)ss.test(b.type||"")&&_.push(b)}return Se}var Po=/^([^.]*)(?:\.(.+)|)/;function Ct(){return!0}function Ft(){return!1}function yi(u,f){return u===Io()==(f==="focus")}function Io(){try{return K.activeElement}catch{}}function os(u,f,_,y,k,b){var P,G;if(typeof f=="object"){typeof _!="string"&&(y=y||_,_=void 0);for(G in f)os(u,G,_,y,f[G],b);return u}if(y==null&&k==null?(k=_,y=_=void 0):k==null&&(typeof _=="string"?(k=y,y=void 0):(k=y,y=_,_=void 0)),k===!1)k=Ft;else if(!k)return u;return b===1&&(P=k,k=function(q){return a().off(q),P.apply(this,arguments)},k.guid=P.guid||(P.guid=a.guid++)),u.each(function(){a.event.add(this,f,k,y,_)})}a.event={global:{},add:function(u,f,_,y,k){var b,P,G,q,re,ue,Se,ae,he,Ye,nt,Ue=De.get(u);if(We(u))for(_.handler&&(b=_,_=b.handler,k=b.selector),k&&a.find.matchesSelector(nr,k),_.guid||(_.guid=a.guid++),(q=Ue.events)||(q=Ue.events=Object.create(null)),(P=Ue.handle)||(P=Ue.handle=function(an){return typeof a<"u"&&a.event.triggered!==an.type?a.event.dispatch.apply(u,arguments):void 0}),f=(f||"").match(Fe)||[""],re=f.length;re--;)G=Po.exec(f[re])||[],he=nt=G[1],Ye=(G[2]||"").split(".").sort(),he&&(Se=a.event.special[he]||{},he=(k?Se.delegateType:Se.bindType)||he,Se=a.event.special[he]||{},ue=a.extend({type:he,origType:nt,data:y,handler:_,guid:_.guid,selector:k,needsContext:k&&a.expr.match.needsContext.test(k),namespace:Ye.join(".")},b),(ae=q[he])||(ae=q[he]=[],ae.delegateCount=0,(!Se.setup||Se.setup.call(u,y,Ye,P)===!1)&&u.addEventListener&&u.addEventListener(he,P)),Se.add&&(Se.add.call(u,ue),ue.handler.guid||(ue.handler.guid=_.guid)),k?ae.splice(ae.delegateCount++,0,ue):ae.push(ue),a.event.global[he]=!0)},remove:function(u,f,_,y,k){var b,P,G,q,re,ue,Se,ae,he,Ye,nt,Ue=De.hasData(u)&&De.get(u);if(!(!Ue||!(q=Ue.events))){for(f=(f||"").match(Fe)||[""],re=f.length;re--;){if(G=Po.exec(f[re])||[],he=nt=G[1],Ye=(G[2]||"").split(".").sort(),!he){for(he in q)a.event.remove(u,he+f[re],_,y,!0);continue}for(Se=a.event.special[he]||{},he=(y?Se.delegateType:Se.bindType)||he,ae=q[he]||[],G=G[2]&&new RegExp("(^|\\.)"+Ye.join("\\.(?:.*\\.|)")+"(\\.|$)"),P=b=ae.length;b--;)ue=ae[b],(k||nt===ue.origType)&&(!_||_.guid===ue.guid)&&(!G||G.test(ue.namespace))&&(!y||y===ue.selector||y==="**"&&ue.selector)&&(ae.splice(b,1),ue.selector&&ae.delegateCount--,Se.remove&&Se.remove.call(u,ue));P&&!ae.length&&((!Se.teardown||Se.teardown.call(u,Ye,Ue.handle)===!1)&&a.removeEvent(u,he,Ue.handle),delete q[he])}a.isEmptyObject(q)&&De.remove(u,"handle events")}},dispatch:function(u){var f,_,y,k,b,P,G=new Array(arguments.length),q=a.event.fix(u),re=(De.get(this,"events")||Object.create(null))[q.type]||[],ue=a.event.special[q.type]||{};for(G[0]=q,f=1;f=1)){for(;re!==this;re=re.parentNode||this)if(re.nodeType===1&&!(u.type==="click"&&re.disabled===!0)){for(b=[],P={},_=0;_-1:a.find(k,this,null,[re]).length),P[k]&&b.push(y);b.length&&G.push({elem:re,handlers:b})}}return re=this,q\s*$/g;function Wa(u,f){return R(u,"table")&&R(f.nodeType!==11?f:f.firstChild,"tr")&&a(u).children("tbody")[0]||u}function Ro(u){return u.type=(u.getAttribute("type")!==null)+"/"+u.type,u}function lu(u){return(u.type||"").slice(0,5)==="true/"?u.type=u.type.slice(5):u.removeAttribute("type"),u}function Ba(u,f){var _,y,k,b,P,G,q;if(f.nodeType===1){if(De.hasData(u)&&(b=De.get(u),q=b.events,q)){De.remove(f,"handle events");for(k in q)for(_=0,y=q[k].length;_1&&typeof he=="string"&&!pe.checkClone&&Ha.test(he))return u.each(function(nt){var Ue=u.eq(nt);Ye&&(f[0]=he.call(this,nt,Ue.html())),Hi(Ue,f,_,y)});if(Se&&(k=$s(f,u[0].ownerDocument,!1,u,y),b=k.firstChild,k.childNodes.length===1&&(k=b),b||y)){for(P=a.map(xn(k,"script"),Ro),G=P.length;ue0&&$i(P,!q&&xn(u,"script")),G},cleanData:function(u){for(var f,_,y,k=a.event.special,b=0;(_=u[b])!==void 0;b++)if(We(_)){if(f=_[De.expando]){if(f.events)for(y in f.events)k[y]?a.event.remove(_,y):a.removeEvent(_,y,f.handle);_[De.expando]=void 0}_[Bt.expando]&&(_[Bt.expando]=void 0)}}}),a.fn.extend({detach:function(u){return qa(this,u,!0)},remove:function(u){return qa(this,u)},text:function(u){return It(this,function(f){return f===void 0?a.text(this):this.empty().each(function(){(this.nodeType===1||this.nodeType===11||this.nodeType===9)&&(this.textContent=f)})},null,u,arguments.length)},append:function(){return Hi(this,arguments,function(u){if(this.nodeType===1||this.nodeType===11||this.nodeType===9){var f=Wa(this,u);f.appendChild(u)}})},prepend:function(){return Hi(this,arguments,function(u){if(this.nodeType===1||this.nodeType===11||this.nodeType===9){var f=Wa(this,u);f.insertBefore(u,f.firstChild)}})},before:function(){return Hi(this,arguments,function(u){this.parentNode&&this.parentNode.insertBefore(u,this)})},after:function(){return Hi(this,arguments,function(u){this.parentNode&&this.parentNode.insertBefore(u,this.nextSibling)})},empty:function(){for(var u,f=0;(u=this[f])!=null;f++)u.nodeType===1&&(a.cleanData(xn(u,!1)),u.textContent="");return this},clone:function(u,f){return u=u??!1,f=f??u,this.map(function(){return a.clone(this,u,f)})},html:function(u){return It(this,function(f){var _=this[0]||{},y=0,k=this.length;if(f===void 0&&_.nodeType===1)return _.innerHTML;if(typeof f=="string"&&!$a.test(f)&&!Kt[(vi.exec(f)||["",""])[1].toLowerCase()]){f=a.htmlPrefilter(f);try{for(;y=0&&(q+=Math.max(0,Math.ceil(u["offset"+f[0].toUpperCase()+f.slice(1)]-b-q-G-.5))||0),q}function ze(u,f,_){var y=Hs(u),k=!pe.boxSizingReliable()||_,b=k&&a.css(u,"boxSizing",!1,y)==="border-box",P=b,G=L(u,f,y),q="offset"+f[0].toUpperCase()+f.slice(1);if(Do.test(G)){if(!_)return G;G="auto"}return(!pe.boxSizingReliable()&&b||!pe.reliableTrDimensions()&&R(u,"tr")||G==="auto"||!parseFloat(G)&&a.css(u,"display",!1,y)==="inline")&&u.getClientRects().length&&(b=a.css(u,"boxSizing",!1,y)==="border-box",P=q in u,P&&(G=u[q])),G=parseFloat(G)||0,G+je(u,f,_||(b?"border":"content"),P,y,G)+"px"}a.extend({cssHooks:{opacity:{get:function(u,f){if(f){var _=L(u,"opacity");return _===""?"1":_}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(u,f,_,y){if(!(!u||u.nodeType===3||u.nodeType===8||!u.style)){var k,b,P,G=mt(f),q=Mo.test(f),re=u.style;if(q||(f=W(G)),P=a.cssHooks[f]||a.cssHooks[G],_!==void 0){if(b=typeof _,b==="string"&&(k=Jr.exec(_))&&k[1]&&(_=_i(u,f,k),b="number"),_==null||_!==_)return;b==="number"&&!q&&(_+=k&&k[3]||(a.cssNumber[G]?"":"px")),!pe.clearCloneStyle&&_===""&&f.indexOf("background")===0&&(re[f]="inherit"),(!P||!("set"in P)||(_=P.set(u,_,y))!==void 0)&&(q?re.setProperty(f,_):re[f]=_)}else return P&&"get"in P&&(k=P.get(u,!1,y))!==void 0?k:re[f]}},css:function(u,f,_,y){var k,b,P,G=mt(f),q=Mo.test(f);return q||(f=W(G)),P=a.cssHooks[f]||a.cssHooks[G],P&&"get"in P&&(k=P.get(u,!0,_)),k===void 0&&(k=L(u,f,y)),k==="normal"&&f in we&&(k=we[f]),_===""||_?(b=parseFloat(k),_===!0||isFinite(b)?b||0:k):k}}),a.each(["height","width"],function(u,f){a.cssHooks[f]={get:function(_,y,k){if(y)return _e.test(a.css(_,"display"))&&(!_.getClientRects().length||!_.getBoundingClientRect().width)?Fo(_,Oe,function(){return ze(_,f,k)}):ze(_,f,k)},set:function(_,y,k){var b,P=Hs(_),G=!pe.scrollboxSize()&&P.position==="absolute",q=G||k,re=q&&a.css(_,"boxSizing",!1,P)==="border-box",ue=k?je(_,f,k,re,P):0;return re&&G&&(ue-=Math.ceil(_["offset"+f[0].toUpperCase()+f.slice(1)]-parseFloat(P[f])-je(_,f,"border",!1,P)-.5)),ue&&(b=Jr.exec(y))&&(b[3]||"px")!=="px"&&(_.style[f]=y,y=a.css(_,f)),$e(_,y,ue)}}}),a.cssHooks.marginLeft=v(pe.reliableMarginLeft,function(u,f){if(f)return(parseFloat(L(u,"marginLeft"))||u.getBoundingClientRect().left-Fo(u,{marginLeft:0},function(){return u.getBoundingClientRect().left}))+"px"}),a.each({margin:"",padding:"",border:"Width"},function(u,f){a.cssHooks[u+f]={expand:function(_){for(var y=0,k={},b=typeof _=="string"?_.split(" "):[_];y<4;y++)k[u+On[y]+f]=b[y]||b[y-2]||b[0];return k}},u!=="margin"&&(a.cssHooks[u+f].set=$e)}),a.fn.extend({css:function(u,f){return It(this,function(_,y,k){var b,P,G={},q=0;if(Array.isArray(y)){for(b=Hs(_),P=y.length;q1)}});function _t(u,f,_,y,k){return new _t.prototype.init(u,f,_,y,k)}a.Tween=_t,_t.prototype={constructor:_t,init:function(u,f,_,y,k,b){this.elem=u,this.prop=_,this.easing=k||a.easing._default,this.options=f,this.start=this.now=this.cur(),this.end=y,this.unit=b||(a.cssNumber[_]?"":"px")},cur:function(){var u=_t.propHooks[this.prop];return u&&u.get?u.get(this):_t.propHooks._default.get(this)},run:function(u){var f,_=_t.propHooks[this.prop];return this.options.duration?this.pos=f=a.easing[this.easing](u,this.options.duration*u,0,1,this.options.duration):this.pos=f=u,this.now=(this.end-this.start)*f+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),_&&_.set?_.set(this):_t.propHooks._default.set(this),this}},_t.prototype.init.prototype=_t.prototype,_t.propHooks={_default:{get:function(u){var f;return u.elem.nodeType!==1||u.elem[u.prop]!=null&&u.elem.style[u.prop]==null?u.elem[u.prop]:(f=a.css(u.elem,u.prop,""),!f||f==="auto"?0:f)},set:function(u){a.fx.step[u.prop]?a.fx.step[u.prop](u):u.elem.nodeType===1&&(a.cssHooks[u.prop]||u.elem.style[W(u.prop)]!=null)?a.style(u.elem,u.prop,u.now+u.unit):u.elem[u.prop]=u.now}}},_t.propHooks.scrollTop=_t.propHooks.scrollLeft={set:function(u){u.elem.nodeType&&u.elem.parentNode&&(u.elem[u.prop]=u.now)}},a.easing={linear:function(u){return u},swing:function(u){return .5-Math.cos(u*Math.PI)/2},_default:"swing"},a.fx=_t.prototype.init,a.fx.step={};var Me,en,pn=/^(?:toggle|show|hide)$/,Yt=/queueHooks$/;function Xe(){en&&(K.hidden===!1&&e.requestAnimationFrame?e.requestAnimationFrame(Xe):e.setTimeout(Xe,a.fx.interval),a.fx.tick())}function qt(){return e.setTimeout(function(){Me=void 0}),Me=Date.now()}function Yn(u,f){var _,y=0,k={height:u};for(f=f?1:0;y<4;y+=2-f)_=On[y],k["margin"+_]=k["padding"+_]=u;return f&&(k.opacity=k.width=u),k}function Zr(u,f,_){for(var y,k=(lt.tweeners[f]||[]).concat(lt.tweeners["*"]),b=0,P=k.length;b1)},removeAttr:function(u){return this.each(function(){a.removeAttr(this,u)})}}),a.extend({attr:function(u,f,_){var y,k,b=u.nodeType;if(!(b===3||b===8||b===2)){if(typeof u.getAttribute>"u")return a.prop(u,f,_);if((b!==1||!a.isXMLDoc(u))&&(k=a.attrHooks[f.toLowerCase()]||(a.expr.match.bool.test(f)?gn:void 0)),_!==void 0){if(_===null){a.removeAttr(u,f);return}return k&&"set"in k&&(y=k.set(u,_,f))!==void 0?y:(u.setAttribute(f,_+""),_)}return k&&"get"in k&&(y=k.get(u,f))!==null?y:(y=a.find.attr(u,f),y??void 0)}},attrHooks:{type:{set:function(u,f){if(!pe.radioValue&&f==="radio"&&R(u,"input")){var _=u.value;return u.setAttribute("type",f),_&&(u.value=_),f}}}},removeAttr:function(u,f){var _,y=0,k=f&&f.match(Fe);if(k&&u.nodeType===1)for(;_=k[y++];)u.removeAttribute(_)}}),gn={set:function(u,f,_){return f===!1?a.removeAttr(u,_):u.setAttribute(_,_),_}},a.each(a.expr.match.bool.source.match(/\w+/g),function(u,f){var _=Mt[f]||a.find.attr;Mt[f]=function(y,k,b){var P,G,q=k.toLowerCase();return b||(G=Mt[q],Mt[q]=P,P=_(y,k,b)!=null?q:null,Mt[q]=G),P}});var Cr=/^(?:input|select|textarea|button)$/i,_p=/^(?:a|area)$/i;a.fn.extend({prop:function(u,f){return It(this,a.prop,u,f,arguments.length>1)},removeProp:function(u){return this.each(function(){delete this[a.propFix[u]||u]})}}),a.extend({prop:function(u,f,_){var y,k,b=u.nodeType;if(!(b===3||b===8||b===2))return(b!==1||!a.isXMLDoc(u))&&(f=a.propFix[f]||f,k=a.propHooks[f]),_!==void 0?k&&"set"in k&&(y=k.set(u,_,f))!==void 0?y:u[f]=_:k&&"get"in k&&(y=k.get(u,f))!==null?y:u[f]},propHooks:{tabIndex:{get:function(u){var f=a.find.attr(u,"tabindex");return f?parseInt(f,10):Cr.test(u.nodeName)||_p.test(u.nodeName)&&u.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),pe.optSelected||(a.propHooks.selected={get:function(u){var f=u.parentNode;return f&&f.parentNode&&f.parentNode.selectedIndex,null},set:function(u){var f=u.parentNode;f&&(f.selectedIndex,f.parentNode&&f.parentNode.selectedIndex)}}),a.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){a.propFix[this.toLowerCase()]=this});function Wi(u){var f=u.match(Fe)||[];return f.join(" ")}function wi(u){return u.getAttribute&&u.getAttribute("class")||""}function $o(u){return Array.isArray(u)?u:typeof u=="string"?u.match(Fe)||[]:[]}a.fn.extend({addClass:function(u){var f,_,y,k,b,P;return Te(u)?this.each(function(G){a(this).addClass(u.call(this,G,wi(this)))}):(f=$o(u),f.length?this.each(function(){if(y=wi(this),_=this.nodeType===1&&" "+Wi(y)+" ",_){for(b=0;b-1;)_=_.replace(" "+k+" "," ");P=Wi(_),y!==P&&this.setAttribute("class",P)}}):this):this.attr("class","")},toggleClass:function(u,f){var _,y,k,b,P=typeof u,G=P==="string"||Array.isArray(u);return Te(u)?this.each(function(q){a(this).toggleClass(u.call(this,q,wi(this),f),f)}):typeof f=="boolean"&&G?f?this.addClass(u):this.removeClass(u):(_=$o(u),this.each(function(){if(G)for(b=a(this),k=0;k<_.length;k++)y=_[k],b.hasClass(y)?b.removeClass(y):b.addClass(y);else(u===void 0||P==="boolean")&&(y=wi(this),y&&De.set(this,"__className__",y),this.setAttribute&&this.setAttribute("class",y||u===!1?"":De.get(this,"__className__")||""))}))},hasClass:function(u){var f,_,y=0;for(f=" "+u+" ";_=this[y++];)if(_.nodeType===1&&(" "+Wi(wi(_))+" ").indexOf(f)>-1)return!0;return!1}});var Ws=/\r/g;a.fn.extend({val:function(u){var f,_,y,k=this[0];return arguments.length?(y=Te(u),this.each(function(b){var P;this.nodeType===1&&(y?P=u.call(this,b,a(this).val()):P=u,P==null?P="":typeof P=="number"?P+="":Array.isArray(P)&&(P=a.map(P,function(G){return G==null?"":G+""})),f=a.valHooks[this.type]||a.valHooks[this.nodeName.toLowerCase()],(!f||!("set"in f)||f.set(this,P,"value")===void 0)&&(this.value=P))})):k?(f=a.valHooks[k.type]||a.valHooks[k.nodeName.toLowerCase()],f&&"get"in f&&(_=f.get(k,"value"))!==void 0?_:(_=k.value,typeof _=="string"?_.replace(Ws,""):_??"")):void 0}}),a.extend({valHooks:{option:{get:function(u){var f=a.find.attr(u,"value");return f??Wi(a.text(u))}},select:{get:function(u){var f,_,y,k=u.options,b=u.selectedIndex,P=u.type==="select-one",G=P?null:[],q=P?b+1:k.length;for(b<0?y=q:y=P?b:0;y-1)&&(_=!0);return _||(u.selectedIndex=-1),b}}}}),a.each(["radio","checkbox"],function(){a.valHooks[this]={set:function(u,f){if(Array.isArray(f))return u.checked=a.inArray(a(u).val(),f)>-1}},pe.checkOn||(a.valHooks[this].get=function(u){return u.getAttribute("value")===null?"on":u.value})}),pe.focusin="onfocusin"in e;var fu=/^(?:focusinfocus|focusoutblur)$/,hu=function(u){u.stopPropagation()};a.extend(a.event,{trigger:function(u,f,_,y){var k,b,P,G,q,re,ue,Se,ae=[_||K],he=te.call(u,"type")?u.type:u,Ye=te.call(u,"namespace")?u.namespace.split("."):[];if(b=Se=P=_=_||K,!(_.nodeType===3||_.nodeType===8)&&!fu.test(he+a.event.triggered)&&(he.indexOf(".")>-1&&(Ye=he.split("."),he=Ye.shift(),Ye.sort()),q=he.indexOf(":")<0&&"on"+he,u=u[a.expando]?u:new a.Event(he,typeof u=="object"&&u),u.isTrigger=y?2:3,u.namespace=Ye.join("."),u.rnamespace=u.namespace?new RegExp("(^|\\.)"+Ye.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,u.result=void 0,u.target||(u.target=_),f=f==null?[u]:a.makeArray(f,[u]),ue=a.event.special[he]||{},!(!y&&ue.trigger&&ue.trigger.apply(_,f)===!1))){if(!y&&!ue.noBubble&&!Le(_)){for(G=ue.delegateType||he,fu.test(G+he)||(b=b.parentNode);b;b=b.parentNode)ae.push(b),P=b;P===(_.ownerDocument||K)&&ae.push(P.defaultView||P.parentWindow||e)}for(k=0;(b=ae[k++])&&!u.isPropagationStopped();)Se=b,u.type=k>1?G:ue.bindType||he,re=(De.get(b,"events")||Object.create(null))[u.type]&&De.get(b,"handle"),re&&re.apply(b,f),re=q&&b[q],re&&re.apply&&We(b)&&(u.result=re.apply(b,f),u.result===!1&&u.preventDefault());return u.type=he,!y&&!u.isDefaultPrevented()&&(!ue._default||ue._default.apply(ae.pop(),f)===!1)&&We(_)&&q&&Te(_[he])&&!Le(_)&&(P=_[q],P&&(_[q]=null),a.event.triggered=he,u.isPropagationStopped()&&Se.addEventListener(he,hu),_[he](),u.isPropagationStopped()&&Se.removeEventListener(he,hu),a.event.triggered=void 0,P&&(_[q]=P)),u.result}},simulate:function(u,f,_){var y=a.extend(new a.Event,_,{type:u,isSimulated:!0});a.event.trigger(y,null,f)}}),a.fn.extend({trigger:function(u,f){return this.each(function(){a.event.trigger(u,f,this)})},triggerHandler:function(u,f){var _=this[0];if(_)return a.event.trigger(u,f,_,!0)}}),pe.focusin||a.each({focus:"focusin",blur:"focusout"},function(u,f){var _=function(y){a.event.simulate(f,y.target,a.event.fix(y))};a.event.special[f]={setup:function(){var y=this.ownerDocument||this.document||this,k=De.access(y,f);k||y.addEventListener(u,_,!0),De.access(y,f,(k||0)+1)},teardown:function(){var y=this.ownerDocument||this.document||this,k=De.access(y,f)-1;k?De.access(y,f,k):(y.removeEventListener(u,_,!0),De.remove(y,f))}}});var Ho=e.location,pu={guid:Date.now()},Ua=/\?/;a.parseXML=function(u){var f,_;if(!u||typeof u!="string")return null;try{f=new e.DOMParser().parseFromString(u,"text/xml")}catch{}return _=f&&f.getElementsByTagName("parsererror")[0],(!f||_)&&a.error("Invalid XML: "+(_?a.map(_.childNodes,function(y){return y.textContent}).join(` +`):u)),f};var ld=/\[\]$/,gu=/\r?\n/g,vp=/^(?:submit|button|image|reset|file)$/i,yp=/^(?:input|select|textarea|keygen)/i;function mu(u,f,_,y){var k;if(Array.isArray(f))a.each(f,function(b,P){_||ld.test(u)?y(u,P):mu(u+"["+(typeof P=="object"&&P!=null?b:"")+"]",P,_,y)});else if(!_&&l(f)==="object")for(k in f)mu(u+"["+k+"]",f[k],_,y);else y(u,f)}a.param=function(u,f){var _,y=[],k=function(b,P){var G=Te(P)?P():P;y[y.length]=encodeURIComponent(b)+"="+encodeURIComponent(G??"")};if(u==null)return"";if(Array.isArray(u)||u.jquery&&!a.isPlainObject(u))a.each(u,function(){k(this.name,this.value)});else for(_ in u)mu(_,u[_],f,k);return y.join("&")},a.fn.extend({serialize:function(){return a.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var u=a.prop(this,"elements");return u?a.makeArray(u):this}).filter(function(){var u=this.type;return this.name&&!a(this).is(":disabled")&&yp.test(this.nodeName)&&!vp.test(u)&&(this.checked||!Fi.test(u))}).map(function(u,f){var _=a(this).val();return _==null?null:Array.isArray(_)?a.map(_,function(y){return{name:f.name,value:y.replace(gu,`\r +`)}}):{name:f.name,value:_.replace(gu,`\r +`)}}).get()}});var ud=/%20/g,wp=/#.*$/,Ep=/([?&])_=[^&]*/,xp=/^(.*?):[ \t]*([^\r\n]*)$/mg,Tp=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,_u=/^(?:GET|HEAD)$/,Sp=/^\/\//,cd={},vu={},dd="*/".concat("*"),yu=K.createElement("a");yu.href=Ho.href;function tn(u){return function(f,_){typeof f!="string"&&(_=f,f="*");var y,k=0,b=f.toLowerCase().match(Fe)||[];if(Te(_))for(;y=b[k++];)y[0]==="+"?(y=y.slice(1)||"*",(u[y]=u[y]||[]).unshift(_)):(u[y]=u[y]||[]).push(_)}}function Gt(u,f,_,y){var k={},b=u===vu;function P(G){var q;return k[G]=!0,a.each(u[G]||[],function(re,ue){var Se=ue(f,_,y);if(typeof Se=="string"&&!b&&!k[Se])return f.dataTypes.unshift(Se),P(Se),!1;if(b)return!(q=Se)}),q}return P(f.dataTypes[0])||!k["*"]&&P("*")}function wu(u,f){var _,y,k=a.ajaxSettings.flatOptions||{};for(_ in f)f[_]!==void 0&&((k[_]?u:y||(y={}))[_]=f[_]);return y&&a.extend(!0,u,y),u}function Cp(u,f,_){for(var y,k,b,P,G=u.contents,q=u.dataTypes;q[0]==="*";)q.shift(),y===void 0&&(y=u.mimeType||f.getResponseHeader("Content-Type"));if(y){for(k in G)if(G[k]&&G[k].test(y)){q.unshift(k);break}}if(q[0]in _)b=q[0];else{for(k in _){if(!q[0]||u.converters[k+" "+q[0]]){b=k;break}P||(P=k)}b=b||P}if(b)return b!==q[0]&&q.unshift(b),_[b]}function kp(u,f,_,y){var k,b,P,G,q,re={},ue=u.dataTypes.slice();if(ue[1])for(P in u.converters)re[P.toLowerCase()]=u.converters[P];for(b=ue.shift();b;)if(u.responseFields[b]&&(_[u.responseFields[b]]=f),!q&&y&&u.dataFilter&&(f=u.dataFilter(f,u.dataType)),q=b,b=ue.shift(),b){if(b==="*")b=q;else if(q!=="*"&&q!==b){if(P=re[q+" "+b]||re["* "+b],!P){for(k in re)if(G=k.split(" "),G[1]===b&&(P=re[q+" "+G[0]]||re["* "+G[0]],P)){P===!0?P=re[k]:re[k]!==!0&&(b=G[0],ue.unshift(G[1]));break}}if(P!==!0)if(P&&u.throws)f=P(f);else try{f=P(f)}catch(Se){return{state:"parsererror",error:P?Se:"No conversion from "+q+" to "+b}}}}return{state:"success",data:f}}a.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ho.href,type:"GET",isLocal:Tp.test(Ho.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":dd,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":a.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(u,f){return f?wu(wu(u,a.ajaxSettings),f):wu(a.ajaxSettings,u)},ajaxPrefilter:tn(cd),ajaxTransport:tn(vu),ajax:function(u,f){typeof u=="object"&&(f=u,u=void 0),f=f||{};var _,y,k,b,P,G,q,re,ue,Se,ae=a.ajaxSetup({},f),he=ae.context||ae,Ye=ae.context&&(he.nodeType||he.jquery)?a(he):a.event,nt=a.Deferred(),Ue=a.Callbacks("once memory"),an=ae.statusCode||{},dt={},mn={},$t="canceled",ut={readyState:0,getResponseHeader:function(Ot){var nn;if(q){if(!b)for(b={};nn=xp.exec(k);)b[nn[1].toLowerCase()+" "]=(b[nn[1].toLowerCase()+" "]||[]).concat(nn[2]);nn=b[Ot.toLowerCase()+" "]}return nn==null?null:nn.join(", ")},getAllResponseHeaders:function(){return q?k:null},setRequestHeader:function(Ot,nn){return q==null&&(Ot=mn[Ot.toLowerCase()]=mn[Ot.toLowerCase()]||Ot,dt[Ot]=nn),this},overrideMimeType:function(Ot){return q==null&&(ae.mimeType=Ot),this},statusCode:function(Ot){var nn;if(Ot)if(q)ut.always(Ot[ut.status]);else for(nn in Ot)an[nn]=[an[nn],Ot[nn]];return this},abort:function(Ot){var nn=Ot||$t;return _&&_.abort(nn),Hn(0,nn),this}};if(nt.promise(ut),ae.url=((u||ae.url||Ho.href)+"").replace(Sp,Ho.protocol+"//"),ae.type=f.method||f.type||ae.method||ae.type,ae.dataTypes=(ae.dataType||"*").toLowerCase().match(Fe)||[""],ae.crossDomain==null){G=K.createElement("a");try{G.href=ae.url,G.href=G.href,ae.crossDomain=yu.protocol+"//"+yu.host!=G.protocol+"//"+G.host}catch{ae.crossDomain=!0}}if(ae.data&&ae.processData&&typeof ae.data!="string"&&(ae.data=a.param(ae.data,ae.traditional)),Gt(cd,ae,f,ut),q)return ut;re=a.event&&ae.global,re&&a.active++===0&&a.event.trigger("ajaxStart"),ae.type=ae.type.toUpperCase(),ae.hasContent=!_u.test(ae.type),y=ae.url.replace(wp,""),ae.hasContent?ae.data&&ae.processData&&(ae.contentType||"").indexOf("application/x-www-form-urlencoded")===0&&(ae.data=ae.data.replace(ud,"+")):(Se=ae.url.slice(y.length),ae.data&&(ae.processData||typeof ae.data=="string")&&(y+=(Ua.test(y)?"&":"?")+ae.data,delete ae.data),ae.cache===!1&&(y=y.replace(Ep,"$1"),Se=(Ua.test(y)?"&":"?")+"_="+pu.guid+++Se),ae.url=y+Se),ae.ifModified&&(a.lastModified[y]&&ut.setRequestHeader("If-Modified-Since",a.lastModified[y]),a.etag[y]&&ut.setRequestHeader("If-None-Match",a.etag[y])),(ae.data&&ae.hasContent&&ae.contentType!==!1||f.contentType)&&ut.setRequestHeader("Content-Type",ae.contentType),ut.setRequestHeader("Accept",ae.dataTypes[0]&&ae.accepts[ae.dataTypes[0]]?ae.accepts[ae.dataTypes[0]]+(ae.dataTypes[0]!=="*"?", "+dd+"; q=0.01":""):ae.accepts["*"]);for(ue in ae.headers)ut.setRequestHeader(ue,ae.headers[ue]);if(ae.beforeSend&&(ae.beforeSend.call(he,ut,ae)===!1||q))return ut.abort();if($t="abort",Ue.add(ae.complete),ut.done(ae.success),ut.fail(ae.error),_=Gt(vu,ae,f,ut),!_)Hn(-1,"No Transport");else{if(ut.readyState=1,re&&Ye.trigger("ajaxSend",[ut,ae]),q)return ut;ae.async&&ae.timeout>0&&(P=e.setTimeout(function(){ut.abort("timeout")},ae.timeout));try{q=!1,_.send(dt,Hn)}catch(Ot){if(q)throw Ot;Hn(-1,Ot)}}function Hn(Ot,nn,Bo,qo){var Wn,kr,as,Gn,Ei,rr=nn;q||(q=!0,P&&e.clearTimeout(P),_=void 0,k=qo||"",ut.readyState=Ot>0?4:0,Wn=Ot>=200&&Ot<300||Ot===304,Bo&&(Gn=Cp(ae,ut,Bo)),!Wn&&a.inArray("script",ae.dataTypes)>-1&&a.inArray("json",ae.dataTypes)<0&&(ae.converters["text script"]=function(){}),Gn=kp(ae,Gn,ut,Wn),Wn?(ae.ifModified&&(Ei=ut.getResponseHeader("Last-Modified"),Ei&&(a.lastModified[y]=Ei),Ei=ut.getResponseHeader("etag"),Ei&&(a.etag[y]=Ei)),Ot===204||ae.type==="HEAD"?rr="nocontent":Ot===304?rr="notmodified":(rr=Gn.state,kr=Gn.data,as=Gn.error,Wn=!as)):(as=rr,(Ot||!rr)&&(rr="error",Ot<0&&(Ot=0))),ut.status=Ot,ut.statusText=(nn||rr)+"",Wn?nt.resolveWith(he,[kr,rr,ut]):nt.rejectWith(he,[ut,rr,as]),ut.statusCode(an),an=void 0,re&&Ye.trigger(Wn?"ajaxSuccess":"ajaxError",[ut,ae,Wn?kr:as]),Ue.fireWith(he,[ut,rr]),re&&(Ye.trigger("ajaxComplete",[ut,ae]),--a.active||a.event.trigger("ajaxStop")))}return ut},getJSON:function(u,f,_){return a.get(u,f,_,"json")},getScript:function(u,f){return a.get(u,void 0,f,"script")}}),a.each(["get","post"],function(u,f){a[f]=function(_,y,k,b){return Te(y)&&(b=b||k,k=y,y=void 0),a.ajax(a.extend({url:_,type:f,dataType:b,data:y,success:k},a.isPlainObject(_)&&_))}}),a.ajaxPrefilter(function(u){var f;for(f in u.headers)f.toLowerCase()==="content-type"&&(u.contentType=u.headers[f]||"")}),a._evalUrl=function(u,f,_){return a.ajax({url:u,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(y){a.globalEval(y,f,_)}})},a.fn.extend({wrapAll:function(u){var f;return this[0]&&(Te(u)&&(u=u.call(this[0])),f=a(u,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&f.insertBefore(this[0]),f.map(function(){for(var _=this;_.firstElementChild;)_=_.firstElementChild;return _}).append(this)),this},wrapInner:function(u){return Te(u)?this.each(function(f){a(this).wrapInner(u.call(this,f))}):this.each(function(){var f=a(this),_=f.contents();_.length?_.wrapAll(u):f.append(u)})},wrap:function(u){var f=Te(u);return this.each(function(_){a(this).wrapAll(f?u.call(this,_):u)})},unwrap:function(u){return this.parent(u).not("body").each(function(){a(this).replaceWith(this.childNodes)}),this}}),a.expr.pseudos.hidden=function(u){return!a.expr.pseudos.visible(u)},a.expr.pseudos.visible=function(u){return!!(u.offsetWidth||u.offsetHeight||u.getClientRects().length)},a.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch{}};var Ap={0:200,1223:204},Wo=a.ajaxSettings.xhr();pe.cors=!!Wo&&"withCredentials"in Wo,pe.ajax=Wo=!!Wo,a.ajaxTransport(function(u){var f,_;if(pe.cors||Wo&&!u.crossDomain)return{send:function(y,k){var b,P=u.xhr();if(P.open(u.type,u.url,u.async,u.username,u.password),u.xhrFields)for(b in u.xhrFields)P[b]=u.xhrFields[b];u.mimeType&&P.overrideMimeType&&P.overrideMimeType(u.mimeType),!u.crossDomain&&!y["X-Requested-With"]&&(y["X-Requested-With"]="XMLHttpRequest");for(b in y)P.setRequestHeader(b,y[b]);f=function(G){return function(){f&&(f=_=P.onload=P.onerror=P.onabort=P.ontimeout=P.onreadystatechange=null,G==="abort"?P.abort():G==="error"?typeof P.status!="number"?k(0,"error"):k(P.status,P.statusText):k(Ap[P.status]||P.status,P.statusText,(P.responseType||"text")!=="text"||typeof P.responseText!="string"?{binary:P.response}:{text:P.responseText},P.getAllResponseHeaders()))}},P.onload=f(),_=P.onerror=P.ontimeout=f("error"),P.onabort!==void 0?P.onabort=_:P.onreadystatechange=function(){P.readyState===4&&e.setTimeout(function(){f&&_()})},f=f("abort");try{P.send(u.hasContent&&u.data||null)}catch(G){if(f)throw G}},abort:function(){f&&f()}}}),a.ajaxPrefilter(function(u){u.crossDomain&&(u.contents.script=!1)}),a.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(u){return a.globalEval(u),u}}}),a.ajaxPrefilter("script",function(u){u.cache===void 0&&(u.cache=!1),u.crossDomain&&(u.type="GET")}),a.ajaxTransport("script",function(u){if(u.crossDomain||u.scriptAttrs){var f,_;return{send:function(y,k){f=a("