From ffca09ee25c5d8fb9becb8e565812b8a991a9fee Mon Sep 17 00:00:00 2001 From: Osama Sayegh Date: Mon, 20 Sep 2021 23:52:31 +0300 Subject: [PATCH 01/16] DEV: Plugin cleanup (#1) --- ...tset.json.lock => .discourse-compatibility | 0 .eslintrc | 3 + .github/workflows/plugin-linting.yml | 53 + .github/workflows/plugin-tests.yml | 133 ++ .gitignore | 1 + .prettierrc | 1 + .rubocop.yml | 2 + .template-lintrc.js | 4 + .travis.yml | 10 - Gemfile | 7 + Gemfile.lock | 43 + .../follow/follow_admin_controller.rb | 10 - app/controllers/follow/follow_controller.rb | 84 +- app/models/follow_pages_visibility.rb | 55 + app/models/post_alerter_edits.rb | 85 - app/models/user_destroyer_edits.rb | 30 - app/models/user_follower.rb | 22 + .../discourse/components/admin-follow.js.es6 | 15 - .../discourse/components/follow-button.js | 64 + .../follow-notification-preferences.js | 21 + .../follow-notifications-button.js.es6 | 76 - .../follow-notifications-options.js.es6 | 21 - .../follow-selector-container.js.es6 | 13 - .../discourse/components/follow-statistic.js | 25 + .../components/follow-statistic.js.es6 | 22 - .../admin-menu/follow-nav-button.hbs | 3 - .../follow-button-container.hbs | 1 + .../follow-selector-container.hbs | 3 - .../follow-selector-container.js.es6 | 14 - .../user-main-nav/follow-user-container.hbs | 6 +- ...low-notification-preferences-container.hbs | 1 + ...llow-notification-preferences-container.js | 5 + .../follow-notification-settings.js.es6 | 6 - .../follow-button-container.hbs | 1 + .../follow-toggle-container.hbs | 3 - .../follow-toggle-container.js.es6 | 7 - .../follow-delete-notifications.js.es6 | 16 - .../discourse/controllers/follow-users.js | 18 + .../discourse/controllers/follow-users.js.es6 | 16 - .../controllers/{follow.js.es6 => follow.js} | 5 +- .../discourse/follow-admin-route-map.js.es6 | 7 - .../initializers/follow-initializer.js | 23 + .../initializers/follow-initializer.js.es6 | 61 - .../lib/follow-notification-levels.js.es6 | 19 - .../discourse/routes/admin-follow.js.es6 | 15 - .../routes/{follow.js.es6 => follow.js} | 16 +- .../javascripts/discourse/routes/followers.js | 20 + .../discourse/routes/followers.js.es6 | 25 - .../javascripts/discourse/routes/following.js | 20 + .../discourse/routes/following.js.es6 | 25 - .../discourse/templates/admin/follow.hbs | 3 - .../templates/components/admin-follow.hbs | 10 - .../templates/components/follow-button.hbs | 8 + .../follow-notification-preferences.hbs} | 16 +- .../follow-notifications-button.hbs | 10 - .../components/follow-selector-container.hbs | 11 - .../discourse/templates/follow.hbs | 8 +- .../modal/follow-delete-notifications.hbs | 5 - .../javascripts/discourse/users-route-map.js | 13 + .../discourse/users-route-map.js.es6 | 9 - .../widgets/following-notification-item.js | 14 + .../following-notification-item.js.es6 | 17 - assets/stylesheets/common/follow.scss | 13 +- assets/stylesheets/mobile/follow.scss | 2 +- config/locales/client.en.yml | 43 +- config/locales/server.en.yml | 7 + config/routes.rb | 13 +- config/settings.yml | 32 +- coverage/.last_run.json | 5 - coverage/.resultset.json | 163 -- .../DataTables-1.10.20/images/sort_asc.png | Bin 160 -> 0 bytes .../images/sort_asc_disabled.png | Bin 148 -> 0 bytes .../DataTables-1.10.20/images/sort_both.png | Bin 201 -> 0 bytes .../DataTables-1.10.20/images/sort_desc.png | Bin 158 -> 0 bytes .../images/sort_desc_disabled.png | Bin 146 -> 0 bytes coverage/assets/0.12.3/application.css | 1 - coverage/assets/0.12.3/application.js | 7 - coverage/assets/0.12.3/colorbox/border.png | Bin 163 -> 0 bytes coverage/assets/0.12.3/colorbox/controls.png | Bin 2033 -> 0 bytes coverage/assets/0.12.3/colorbox/loading.gif | Bin 9427 -> 0 bytes .../0.12.3/colorbox/loading_background.png | Bin 166 -> 0 bytes coverage/assets/0.12.3/favicon_green.png | Bin 1009 -> 0 bytes coverage/assets/0.12.3/favicon_red.png | Bin 1009 -> 0 bytes coverage/assets/0.12.3/favicon_yellow.png | Bin 1009 -> 0 bytes .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 180 -> 0 bytes .../images/ui-bg_flat_75_ffffff_40x100.png | Bin 178 -> 0 bytes .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 120 -> 0 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 105 -> 0 bytes .../images/ui-bg_glass_75_dadada_1x400.png | Bin 111 -> 0 bytes .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 110 -> 0 bytes .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin 119 -> 0 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 101 -> 0 bytes .../0.12.3/images/ui-icons_222222_256x240.png | Bin 4369 -> 0 bytes .../0.12.3/images/ui-icons_2e83ff_256x240.png | Bin 4369 -> 0 bytes .../0.12.3/images/ui-icons_454545_256x240.png | Bin 4369 -> 0 bytes .../0.12.3/images/ui-icons_888888_256x240.png | Bin 4369 -> 0 bytes .../0.12.3/images/ui-icons_cd0a0a_256x240.png | Bin 4369 -> 0 bytes coverage/assets/0.12.3/loading.gif | Bin 7247 -> 0 bytes coverage/assets/0.12.3/magnify.png | Bin 1301 -> 0 bytes coverage/index.html | 1799 ----------------- .../20210901115010_create_user_follower.rb | 15 + ...20210907010101_following_data_migration.rb | 26 - ...lower_data_from_custom_fields_to_tables.rb | 58 + ...0045747_rename_follow_plugin_preference.rb | 19 + lib/follow/engine.rb | 4 +- lib/follow/notification.rb | 8 +- lib/follow/updater.rb | 94 +- package.json | 10 + plugin.rb | 272 ++- spec/integration/follow_notifications_spec.rb | 417 ++++ spec/integration/following_topic_list_spec.rb | 107 + spec/integration/user_destroyer_spec.rb | 25 + spec/lib/updater_spec.rb | 225 ++- spec/models/follow_pages_visibility_spec.rb | 135 ++ spec/plugin_helper.rb | 11 - spec/requests/follow_controller_spec.rb | 153 +- spec/services/postalerter_follow_spec.rb | 40 - translator.yml | 7 + yarn.lock | 1615 +++++++++++++++ 119 files changed, 3616 insertions(+), 2970 deletions(-) rename coverage/.resultset.json.lock => .discourse-compatibility (100%) create mode 100644 .eslintrc create mode 100644 .github/workflows/plugin-linting.yml create mode 100644 .github/workflows/plugin-tests.yml create mode 100644 .gitignore create mode 100644 .prettierrc create mode 100644 .rubocop.yml create mode 100644 .template-lintrc.js delete mode 100644 .travis.yml create mode 100644 Gemfile create mode 100644 Gemfile.lock delete mode 100644 app/controllers/follow/follow_admin_controller.rb create mode 100644 app/models/follow_pages_visibility.rb delete mode 100644 app/models/post_alerter_edits.rb delete mode 100644 app/models/user_destroyer_edits.rb create mode 100644 app/models/user_follower.rb delete mode 100644 assets/javascripts/discourse/components/admin-follow.js.es6 create mode 100644 assets/javascripts/discourse/components/follow-button.js create mode 100644 assets/javascripts/discourse/components/follow-notification-preferences.js delete mode 100644 assets/javascripts/discourse/components/follow-notifications-button.js.es6 delete mode 100644 assets/javascripts/discourse/components/follow-notifications-options.js.es6 delete mode 100644 assets/javascripts/discourse/components/follow-selector-container.js.es6 create mode 100644 assets/javascripts/discourse/components/follow-statistic.js delete mode 100644 assets/javascripts/discourse/components/follow-statistic.js.es6 delete mode 100644 assets/javascripts/discourse/connectors/admin-menu/follow-nav-button.hbs create mode 100644 assets/javascripts/discourse/connectors/user-card-additional-buttons/follow-button-container.hbs delete mode 100644 assets/javascripts/discourse/connectors/user-card-additional-controls/follow-selector-container.hbs delete mode 100644 assets/javascripts/discourse/connectors/user-card-additional-controls/follow-selector-container.js.es6 create mode 100644 assets/javascripts/discourse/connectors/user-preferences-notifications/follow-notification-preferences-container.hbs create mode 100644 assets/javascripts/discourse/connectors/user-preferences-notifications/follow-notification-preferences-container.js delete mode 100644 assets/javascripts/discourse/connectors/user-preferences-notifications/follow-notification-settings.js.es6 create mode 100644 assets/javascripts/discourse/connectors/user-profile-controls/follow-button-container.hbs delete mode 100644 assets/javascripts/discourse/connectors/user-profile-controls/follow-toggle-container.hbs delete mode 100644 assets/javascripts/discourse/connectors/user-profile-controls/follow-toggle-container.js.es6 delete mode 100644 assets/javascripts/discourse/controllers/follow-delete-notifications.js.es6 create mode 100644 assets/javascripts/discourse/controllers/follow-users.js delete mode 100644 assets/javascripts/discourse/controllers/follow-users.js.es6 rename assets/javascripts/discourse/controllers/{follow.js.es6 => follow.js} (83%) delete mode 100644 assets/javascripts/discourse/follow-admin-route-map.js.es6 create mode 100644 assets/javascripts/discourse/initializers/follow-initializer.js delete mode 100644 assets/javascripts/discourse/initializers/follow-initializer.js.es6 delete mode 100644 assets/javascripts/discourse/lib/follow-notification-levels.js.es6 delete mode 100644 assets/javascripts/discourse/routes/admin-follow.js.es6 rename assets/javascripts/discourse/routes/{follow.js.es6 => follow.js} (56%) create mode 100644 assets/javascripts/discourse/routes/followers.js delete mode 100644 assets/javascripts/discourse/routes/followers.js.es6 create mode 100644 assets/javascripts/discourse/routes/following.js delete mode 100644 assets/javascripts/discourse/routes/following.js.es6 delete mode 100644 assets/javascripts/discourse/templates/admin/follow.hbs delete mode 100644 assets/javascripts/discourse/templates/components/admin-follow.hbs create mode 100644 assets/javascripts/discourse/templates/components/follow-button.hbs rename assets/javascripts/discourse/{connectors/user-preferences-notifications/follow-notification-settings.hbs => templates/components/follow-notification-preferences.hbs} (61%) delete mode 100644 assets/javascripts/discourse/templates/components/follow-notifications-button.hbs delete mode 100644 assets/javascripts/discourse/templates/components/follow-selector-container.hbs delete mode 100644 assets/javascripts/discourse/templates/modal/follow-delete-notifications.hbs create mode 100644 assets/javascripts/discourse/users-route-map.js delete mode 100644 assets/javascripts/discourse/users-route-map.js.es6 create mode 100644 assets/javascripts/discourse/widgets/following-notification-item.js delete mode 100644 assets/javascripts/discourse/widgets/following-notification-item.js.es6 delete mode 100644 coverage/.last_run.json delete mode 100644 coverage/.resultset.json delete mode 100644 coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc.png delete mode 100644 coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc_disabled.png delete mode 100644 coverage/assets/0.12.3/DataTables-1.10.20/images/sort_both.png delete mode 100644 coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc.png delete mode 100644 coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc_disabled.png delete mode 100644 coverage/assets/0.12.3/application.css delete mode 100644 coverage/assets/0.12.3/application.js delete mode 100644 coverage/assets/0.12.3/colorbox/border.png delete mode 100644 coverage/assets/0.12.3/colorbox/controls.png delete mode 100644 coverage/assets/0.12.3/colorbox/loading.gif delete mode 100644 coverage/assets/0.12.3/colorbox/loading_background.png delete mode 100644 coverage/assets/0.12.3/favicon_green.png delete mode 100644 coverage/assets/0.12.3/favicon_red.png delete mode 100644 coverage/assets/0.12.3/favicon_yellow.png delete mode 100644 coverage/assets/0.12.3/images/ui-bg_flat_0_aaaaaa_40x100.png delete mode 100644 coverage/assets/0.12.3/images/ui-bg_flat_75_ffffff_40x100.png delete mode 100644 coverage/assets/0.12.3/images/ui-bg_glass_55_fbf9ee_1x400.png delete mode 100644 coverage/assets/0.12.3/images/ui-bg_glass_65_ffffff_1x400.png delete mode 100644 coverage/assets/0.12.3/images/ui-bg_glass_75_dadada_1x400.png delete mode 100644 coverage/assets/0.12.3/images/ui-bg_glass_75_e6e6e6_1x400.png delete mode 100644 coverage/assets/0.12.3/images/ui-bg_glass_95_fef1ec_1x400.png delete mode 100644 coverage/assets/0.12.3/images/ui-bg_highlight-soft_75_cccccc_1x100.png delete mode 100644 coverage/assets/0.12.3/images/ui-icons_222222_256x240.png delete mode 100644 coverage/assets/0.12.3/images/ui-icons_2e83ff_256x240.png delete mode 100644 coverage/assets/0.12.3/images/ui-icons_454545_256x240.png delete mode 100644 coverage/assets/0.12.3/images/ui-icons_888888_256x240.png delete mode 100644 coverage/assets/0.12.3/images/ui-icons_cd0a0a_256x240.png delete mode 100644 coverage/assets/0.12.3/loading.gif delete mode 100644 coverage/assets/0.12.3/magnify.png delete mode 100644 coverage/index.html create mode 100644 db/migrate/20210901115010_create_user_follower.rb delete mode 100644 db/migrate/20210907010101_following_data_migration.rb create mode 100644 db/post_migrate/20210901135131_migrate_follower_data_from_custom_fields_to_tables.rb create mode 100644 db/post_migrate/20210920045747_rename_follow_plugin_preference.rb create mode 100644 package.json create mode 100644 spec/integration/follow_notifications_spec.rb create mode 100644 spec/integration/following_topic_list_spec.rb create mode 100644 spec/integration/user_destroyer_spec.rb create mode 100644 spec/models/follow_pages_visibility_spec.rb delete mode 100644 spec/plugin_helper.rb delete mode 100644 spec/services/postalerter_follow_spec.rb create mode 100644 translator.yml create mode 100644 yarn.lock diff --git a/coverage/.resultset.json.lock b/.discourse-compatibility similarity index 100% rename from coverage/.resultset.json.lock rename to .discourse-compatibility diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..7898fbf --- /dev/null +++ b/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "eslint-config-discourse" +} diff --git a/.github/workflows/plugin-linting.yml b/.github/workflows/plugin-linting.yml new file mode 100644 index 0000000..92c6e3e --- /dev/null +++ b/.github/workflows/plugin-linting.yml @@ -0,0 +1,53 @@ +name: Linting + +on: + push: + branches: + - master + - main + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v1 + with: + node-version: 12 + + - name: Set up ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7 + bundler-cache: true + + - name: Yarn install + run: yarn install + + - name: ESLint + if: ${{ always() }} + run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern {test,assets}/javascripts + + - name: Prettier + if: ${{ always() }} + shell: bash + run: | + yarn prettier -v + if [ 0 -lt $(find assets -type f \( -name "*.scss" -or -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then + yarn prettier --list-different "assets/**/*.{scss,js,es6}" + fi + if [ 0 -lt $(find test -type f \( -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then + yarn prettier --list-different "test/**/*.{js,es6}" + fi + + - name: Ember template lint + if: ${{ always() }} + run: yarn ember-template-lint assets/javascripts + + - name: Rubocop + if: ${{ always() }} + run: bundle exec rubocop . diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml new file mode 100644 index 0000000..a5eff7c --- /dev/null +++ b/.github/workflows/plugin-tests.yml @@ -0,0 +1,133 @@ +name: Plugin Tests + +on: + push: + branches: + - master + - main + pull_request: + +jobs: + build: + name: ${{ matrix.build_type }} + runs-on: ubuntu-latest + container: discourse/discourse_test:release + timeout-minutes: 60 + + env: + DISCOURSE_HOSTNAME: www.example.com + RUBY_GLOBAL_METHOD_CACHE_SIZE: 131072 + RAILS_ENV: test + PGHOST: postgres + PGUSER: discourse + PGPASSWORD: discourse + + strategy: + fail-fast: false + + matrix: + build_type: ["backend", "frontend"] + ruby: ["2.7"] + postgres: ["13"] + + services: + postgres: + image: postgres:${{ matrix.postgres }} + ports: + - 5432:5432 + env: + POSTGRES_USER: discourse + POSTGRES_PASSWORD: discourse + options: >- + --mount type=tmpfs,destination=/var/lib/postgresql/data + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v2 + with: + repository: discourse/discourse + fetch-depth: 1 + + - name: Install plugin + uses: actions/checkout@v2 + with: + path: plugins/${{ github.event.repository.name }} + fetch-depth: 1 + + - name: Setup Git + run: | + git config --global user.email "ci@ci.invalid" + git config --global user.name "Discourse CI" + + - name: Start redis + run: | + redis-server /etc/redis/redis.conf & + + - name: Bundler cache + uses: actions/cache@v2 + with: + path: vendor/bundle + key: ${{ runner.os }}-${{ matrix.ruby }}-gem-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.ruby }}-gem- + + - name: Setup gems + run: | + bundle config --local path vendor/bundle + bundle config --local deployment true + bundle config --local without development + bundle install --jobs 4 + bundle clean + + - name: Lint English locale + if: matrix.build_type == 'backend' + run: bundle exec ruby script/i18n_lint.rb "plugins/${{ github.event.repository.name }}/locales/{client,server}.en.yml" + + - name: Get yarn cache directory + id: yarn-cache-dir + run: echo "::set-output name=dir::$(yarn cache dir)" + + - name: Yarn cache + uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir.outputs.dir }} + key: ${{ runner.os }}-${{ matrix.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.os }}-yarn- + + - name: Yarn install + run: yarn install + + - name: Migrate database + run: | + bin/rake db:create + bin/rake db:migrate + + - name: Check spec existence + id: check_spec + shell: bash + run: | + if [ 0 -lt $(find plugins/${{ github.event.repository.name }}/spec -type f -name "*.rb" 2> /dev/null | wc -l) ]; then + echo "::set-output name=files_exist::true" + fi + + - name: Plugin RSpec + if: matrix.build_type == 'backend' && steps.check_spec.outputs.files_exist == 'true' + run: bin/rake plugin:spec[${{ github.event.repository.name }}] + + - name: Check qunit existence + id: check_qunit + shell: bash + run: | + if [ 0 -lt $(find plugins/${{ github.event.repository.name }}/test/javascripts -type f \( -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then + echo "::set-output name=files_exist::true" + fi + + - name: Plugin QUnit + if: matrix.build_type == 'frontend' && steps.check_qunit.outputs.files_exist == 'true' + run: bundle exec rake plugin:qunit['${{ github.event.repository.name }}','1200000'] + timeout-minutes: 30 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..d46296c --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,2 @@ +inherit_gem: + rubocop-discourse: default.yml diff --git a/.template-lintrc.js b/.template-lintrc.js new file mode 100644 index 0000000..a558b8e --- /dev/null +++ b/.template-lintrc.js @@ -0,0 +1,4 @@ +module.exports = { + plugins: ["ember-template-lint-plugin-discourse"], + extends: "discourse:recommended", +}; diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c4e497c..0000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -services: - - docker - -before_install: - - git clone --depth=1 https://github.com/discourse/discourse-plugin-ci - -install: true - -script: - - discourse-plugin-ci/script.sh \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..7da32ec --- /dev/null +++ b/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +group :development do + gem 'rubocop-discourse' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..a24f55a --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,43 @@ +GEM + remote: https://rubygems.org/ + specs: + ast (2.4.2) + parallel (1.20.1) + parser (3.0.0.0) + ast (~> 2.4.1) + rainbow (3.0.0) + regexp_parser (2.1.1) + rexml (3.2.5) + rubocop (1.12.0) + parallel (~> 1.10) + parser (>= 3.0.0.0) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml + rubocop-ast (>= 1.2.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.4.1) + parser (>= 2.7.1.5) + rubocop-discourse (2.4.1) + rubocop (>= 1.1.0) + rubocop-rspec (>= 2.0.0) + rubocop-rspec (2.2.0) + rubocop (~> 1.0) + rubocop-ast (>= 1.1.0) + ruby-progressbar (1.11.0) + unicode-display_width (2.0.0) + +PLATFORMS + arm64-darwin-20 + ruby + x86_64-darwin-18 + x86_64-darwin-19 + x86_64-darwin-20 + x86_64-linux + +DEPENDENCIES + rubocop-discourse + +BUNDLED WITH + 2.2.13 diff --git a/app/controllers/follow/follow_admin_controller.rb b/app/controllers/follow/follow_admin_controller.rb deleted file mode 100644 index 1d2e576..0000000 --- a/app/controllers/follow/follow_admin_controller.rb +++ /dev/null @@ -1,10 +0,0 @@ -class Follow::FollowAdminController < ::Admin::AdminController - before_action :ensure_admin - - def delete_notifications - Notification.where(notification_type: [800,801,802]).find_each do |follow_notification| - follow_notification.destroy - end - render json: success_json - end -end diff --git a/app/controllers/follow/follow_controller.rb b/app/controllers/follow/follow_controller.rb index 024e5df..423de8d 100644 --- a/app/controllers/follow/follow_controller.rb +++ b/app/controllers/follow/follow_controller.rb @@ -1,50 +1,66 @@ -class Follow::FollowController < ApplicationController - def index - end - - def update - params.require(:username) - params.require(:following_notification_level) +# frozen_string_literal: true - raise Discourse::InvalidAccess.new unless current_user - raise Discourse::InvalidParameters.new if current_user.username == params[:username] +class Follow::FollowController < ApplicationController + FOLLOWING ||= :following + FOLLOWERS ||= :followers - if user = User.find_by(username: params[:username]) - updater = Follow::Updater.new(current_user, user) - new_following_notification_level = updater.update(params[:following_notification_level]) + def follow + raise Discourse::InvalidAccess.new if !current_user - render json: success_json.merge(following_notification_level: new_following_notification_level) - else - render json: failed_json + user = User.find_by_username(params.require(:username)) + if user.blank? + return render json: { + errors: [I18n.t("follow.user_not_found", username: params[:username].inspect)] + }, status: 404 end + + Follow::Updater.new(current_user, user).watch_follow + render json: success_json end - def list - params.require(:type) - params.require(:username) + def unfollow + raise Discourse::InvalidAccess.new if !current_user - user = User.where('lower(username) = ?', params[:username].downcase).first - raise Discourse::InvalidParameters.new unless user.present? + user = User.find_by_username(params.require(:username)) + if user.blank? + return render json: { + errors: [I18n.t("follow.user_not_found", username: params[:username].inspect)] + }, status: 404 + end - type = params[:type] + Follow::Updater.new(current_user, user).unfollow + render json: success_json + end - allowed = SiteSetting.try("follow_#{type}_visible") || nil + def list_following + list(FOLLOWING) + end - userInAllowedGroup = false + def list_followers + list(FOLLOWERS) + end - if !['everyone', 'self', 'no-one'].include? allowed - allowedGroup = Group.find_by(name: allowed) - userInAllowedGroup = current_user && allowedGroup && GroupUser.find_by(user_id: current_user.id, group_id: allowedGroup.id) - end + private - if allowed == 'everyone' || allowed != 'no-one' && current_user && user.id == current_user.id || userInAllowedGroup - method = type == 'following' ? 'following_ids' : 'followers' - users = user.send(method).map { |user_id| User.find(user_id) } + def list(type) + user = User.find_by_username(params.require(:username)) + raise Discourse::NotFound.new if user.blank? - serializer = ActiveModel::ArraySerializer.new(users, each_serializer: BasicUserSerializer) - render json: MultiJson.dump(serializer) + if type == FOLLOWERS + if !FollowPagesVisibility.can_see_followers_page?(user: current_user, target_user: user) + raise Discourse::InvalidAccess.new + end + users = user.followers.to_a + elsif type == FOLLOWING + if !FollowPagesVisibility.can_see_following_page?(user: current_user, target_user: user) + raise Discourse::InvalidAccess.new + end + users = user.following.to_a else - render json: [] + raise Discourse::InvalidParameters.new end + + serializer = ActiveModel::ArraySerializer.new(users, each_serializer: BasicUserSerializer) + render json: MultiJson.dump(serializer) end -end \ No newline at end of file +end diff --git a/app/models/follow_pages_visibility.rb b/app/models/follow_pages_visibility.rb new file mode 100644 index 0000000..1bca0f2 --- /dev/null +++ b/app/models/follow_pages_visibility.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +class FollowPagesVisibility < EnumSiteSetting + NO_ONE = "no-one" + SELF = "self" + EVERYONE = "everyone" + + class << self + def valid_value?(val) + values.any? { |v| v[:value] == val } + end + + def values + @values ||= [ + NO_ONE, + SELF, + "trust_level_4", + "trust_level_3", + "trust_level_2", + "trust_level_1", + "trust_level_0", + EVERYONE + ].map do |v| + { name: "follow.follow_pages_visibility.#{v}", value: v } + end + end + + def translate_names? + true + end + + def can_see_followers_page?(user:, target_user:) + can_see_page?(user, target_user, SiteSetting.follow_followers_visible) + end + + def can_see_following_page?(user:, target_user:) + can_see_page?(user, target_user, SiteSetting.follow_following_visible) + end + + private + + def can_see_page?(user, target_user, page_setting_value) + return false if !SiteSetting.discourse_follow_enabled + return false if target_user.blank? + return true if page_setting_value == EVERYONE + return false if page_setting_value == NO_ONE + return false if user.blank? + return true if user.id == target_user.id + return false if page_setting_value == SELF + group = Group.lookup_group(page_setting_value.to_sym) + return false if group.blank? + GroupUser.where(user_id: user.id, group_id: group.id).exists? + end + end +end diff --git a/app/models/post_alerter_edits.rb b/app/models/post_alerter_edits.rb deleted file mode 100644 index 1f5d1f3..0000000 --- a/app/models/post_alerter_edits.rb +++ /dev/null @@ -1,85 +0,0 @@ -## PostAlerter - ## A number of overridden methods need to refer to the core method (i.e. super class) - - module PostAlerterFollowExtension - def after_save_post(post, new_record = false) - super(post, new_record) - - if SiteSetting.discourse_follow_enabled && SiteSetting.follow_notifications_enabled && new_record && !post.topic.private_message? - notified = [*notified_users[post.id]] - followers = post.is_first_post? ? author_posted_followers(post) : author_replied_followers(post) - type = post.is_first_post? ? :following_posted : :following_replied - notify_users((followers || []) - notified, type, post) - end - end - - def author_posted_followers(post) - User.find(post.user_id).followers.map do |user_id| - unless (user = User.find_by(id: user_id)) && user.notify_me_when_followed_posts - user = nil - end - user - end.reject(&:nil?) - end - - def author_replied_followers(post) - User.find(post.user_id).followers.reduce([]) do |users, user_id| - unless (user = User.find_by(id: user_id)) && user.notify_me_when_followed_replies - user = nil - end - following = user ? user.following.find { |data| data[0] == post.user_id.to_s } : nil - if following && following.last == Follow::Notification.levels[:watching] - users.push(user) - else - users - end - end - end - - def notified_users - @notified_users ||= [] - end - - def create_notification(user, type, post, opts = {}) - @current_notification_type = type - super(user, type, post, opts) - @current_notification_type = nil - end - - def unread_posts(user, topic) - if @current_notification_type == Notification.types[:following_replied] - posts = Post.secured(Guardian.new(user)) - .where('post_number > COALESCE(( - SELECT last_read_post_number FROM topic_users tu - WHERE tu.user_id = ? AND tu.topic_id = ? ),0)', - user.id, topic.id) - - posts = posts - .where("exists( - SELECT 1 from user_custom_fields ucf - WHERE ucf.user_id = ? AND - ucf.name = 'following' AND - split_part(ucf.value,',', 1)::integer = posts.user_id AND - split_part(ucf.value, ',', 2)::integer = ? - )", user.id, Follow::Notification.levels[:watching]) - .where(topic_id: topic.id) - else - posts = super(user, topic) - end - - posts - end - - def first_unread_post(user, topic) - unread_posts(user, topic).order('post_number').first - end - - def unread_count(user, topic) - unread_posts(user, topic).count - end - end - - require_dependency 'post_alerter' - class ::PostAlerter - prepend PostAlerterFollowExtension - end diff --git a/app/models/user_destroyer_edits.rb b/app/models/user_destroyer_edits.rb deleted file mode 100644 index db4f054..0000000 --- a/app/models/user_destroyer_edits.rb +++ /dev/null @@ -1,30 +0,0 @@ - ## User Destroyer - ## There is no DiscourseEvent that fires before UserCustomFields are destroyed - - module UserDestroyerFollowerExtension - protected def prepare_for_destroy(user) - if SiteSetting.discourse_follow_enabled - if user.following_ids.present? - user.following_ids.each do |user_id| - if following = User.find_by(id: user_id) - updater = Follow::Updater.new(user, following) - updater.update(false) - end - end - end - if user.followers.present? && - user.followers.each do |user_id| - if follower = User.find_by(id: user_id) - updater = Follow::Updater.new(follower, user) - updater.update(false) - end - end - end - end - super(user) - end - end - - class ::UserDestroyer - prepend UserDestroyerFollowerExtension - end diff --git a/app/models/user_follower.rb b/app/models/user_follower.rb new file mode 100644 index 0000000..02f1eb8 --- /dev/null +++ b/app/models/user_follower.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class UserFollower < ActiveRecord::Base + belongs_to :follower_user, class_name: 'User', foreign_key: :follower_id + belongs_to :followed_user, class_name: 'User', foreign_key: :user_id +end + +# == Schema Information +# +# Table name: user_followers +# +# id :bigint not null, primary key +# user_id :bigint not null +# follower_id :bigint not null +# level :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_user_followers_on_user_id_and_follower_id (user_id,follower_id) UNIQUE +# diff --git a/assets/javascripts/discourse/components/admin-follow.js.es6 b/assets/javascripts/discourse/components/admin-follow.js.es6 deleted file mode 100644 index eb11642..0000000 --- a/assets/javascripts/discourse/components/admin-follow.js.es6 +++ /dev/null @@ -1,15 +0,0 @@ -import showModal from "discourse/lib/show-modal"; - -export default Ember.Component.extend({ - classNames: 'follow-admin', - - init() { - this._super(); - }, - - actions: { - deleteNotifications() { - showModal('follow-delete-notifications'); - } - } -}) diff --git a/assets/javascripts/discourse/components/follow-button.js b/assets/javascripts/discourse/components/follow-button.js new file mode 100644 index 0000000..253a84d --- /dev/null +++ b/assets/javascripts/discourse/components/follow-button.js @@ -0,0 +1,64 @@ +import Component from "@ember/component"; +import discourseComputed from "discourse-common/utils/decorators"; +import { action } from "@ember/object"; +import { alias } from "@ember/object/computed"; +import { ajax } from "discourse/lib/ajax"; +import { popupAjaxError } from "discourse/lib/ajax-error"; + +export default Component.extend({ + loading: false, + isFollowed: alias("user.is_followed"), + + @discourseComputed("user", "currentUser") + showButton(user, currentUser) { + if (!currentUser) { + return false; + } + if (currentUser.id === user.id) { + return false; + } + if (user.suspended) { + return false; + } + if (user.staged) { + return false; + } + if (user.id < 1) { + // bot + return false; + } + return true; + }, + + @discourseComputed("isFollowed") + labelKey(isFollowed) { + if (isFollowed) { + return "follow.unfollow_button_label"; + } else { + return "follow.follow_button_label"; + } + }, + + @discourseComputed("isFollowed") + icon(isFollowed) { + if (isFollowed) { + return "user-times"; + } else { + return "user-plus"; + } + }, + + @action + toggleFollow() { + const type = this.isFollowed ? "DELETE" : "PUT"; + this.set("loading", true); + ajax(`/follow/${this.user.username}.json`, { type }) + .then(() => { + this.set("isFollowed", !this.isFollowed); + }) + .catch(popupAjaxError) + .finally(() => { + this.set("loading", false); + }); + }, +}); diff --git a/assets/javascripts/discourse/components/follow-notification-preferences.js b/assets/javascripts/discourse/components/follow-notification-preferences.js new file mode 100644 index 0000000..73f5e8e --- /dev/null +++ b/assets/javascripts/discourse/components/follow-notification-preferences.js @@ -0,0 +1,21 @@ +import Component from "@ember/component"; +import { observes } from "discourse-common/utils/decorators"; + +const preferences = [ + "notify_me_when_followed", + "notify_followed_user_when_followed", + "notify_me_when_followed_replies", + "notify_me_when_followed_creates_topic", +]; + +export default Component.extend({ + @observes(...preferences.map((p) => `user.${p}`)) + _updatePreferences() { + if (!this.user.custom_fields) { + this.user.set("custom_fields", {}); + } + preferences.forEach((p) => { + this.user.set(`custom_fields.${p}`, this.user[p]); + }); + }, +}); diff --git a/assets/javascripts/discourse/components/follow-notifications-button.js.es6 b/assets/javascripts/discourse/components/follow-notifications-button.js.es6 deleted file mode 100644 index b7c47cf..0000000 --- a/assets/javascripts/discourse/components/follow-notifications-button.js.es6 +++ /dev/null @@ -1,76 +0,0 @@ -import { action, computed } from "@ember/object"; -import { ajax } from 'discourse/lib/ajax'; -import I18n from "I18n"; -import { isEmpty } from "@ember/utils"; -import { NotificationLevels } from "discourse/lib/notification-levels"; -import discourseComputed from "discourse-common/utils/decorators"; -import getURL from "discourse-common/lib/get-url"; -import Component from "@ember/component"; -import layout from "select-kit/templates/components/topic-notifications-button"; - -export default Component.extend({ - layout, - router: Ember.inject.service('-routing'), - classNames: ["follow-notifications-button"], - classNameBindings: ["isLoading"], - appendReason: true, - showFullTitle: true, - notificationLevel: null, - topic: null, - showCaret: true, - isLoading: false, - icon: computed("isLoading", function () { - return this.isLoading ? "spinner" : null; - }), - - init() { - this._super(...arguments); - this.set('notificationLevel', parseInt(this.get('user.following_notification_level'))); - }, - - actions: { - changeFollowNotificationLevel(levelId) { - if (levelId !== this.notificationLevel) { - let user = this.get('user'); - - let following_notification_level = levelId.toString(); - let existingTotal = this.get('user.total_followers'); - - this.set('loading', true); - - ajax(`/follow/${user.username}`, { - type: 'PUT', - data: { - following_notification_level - } - }).then((result) => { - if (["3","4"].includes(result.following_notification_level) && !["3","4"].includes(this.notificationLevel.toString())) { - this.set('user.total_followers', this.user.total_followers + 1); - }; - - if (["","0","1"].includes(result.following_notification_level) && ["3","4"].includes(this.notificationLevel.toString())) { - this.set('user.total_followers', this.user.total_followers - 1); - }; - this.set('notificationLevel',parseInt(result.following_notification_level)); - }).finally(() => { - this.set("isLoading", false); - let newTotal = this.get('user.total_followers'); - const currentRouteName = this.get("router.router.currentRouteName"); - - // refresh if looking at follow - if (currentRouteName.indexOf('follow') > -1) { - const followRoute = getOwner(this).lookup(`route:follow`); - followRoute.refresh(); - } - - // refresh if looking at site discovery && nav item changes - if ((existingTotal == 0 || newTotal == 0) && - currentRouteName.indexOf('discovery') > -1 && - currentRouteName.toLowerCase().indexOf('category') === -1) { - const discoveryRoute = getOwner(this).lookup('route:discovery'); - discoveryRoute.refresh(); - } - }); - } - }}, -}); \ No newline at end of file diff --git a/assets/javascripts/discourse/components/follow-notifications-options.js.es6 b/assets/javascripts/discourse/components/follow-notifications-options.js.es6 deleted file mode 100644 index 5a27c96..0000000 --- a/assets/javascripts/discourse/components/follow-notifications-options.js.es6 +++ /dev/null @@ -1,21 +0,0 @@ -import NotificationsButtonComponent from "select-kit/components/notifications-button"; -import { computed } from "@ember/object"; -import { followLevels } from "../lib/follow-notification-levels"; - -export default NotificationsButtonComponent.extend({ - pluginApiIdentifiers: ["follow-notifications-options"], - classNames: ["follow-notifications-options"], - content: followLevels, - - selectKitOptions: { - i18nPrefix: "user.following_level", - showFullTitle: true, - showCaret: false, - }, - -}); - - // @computed('user', 'currentUser') - // isHidden(user, currentUser) { - // return currentUser && currentUser.username !== user.username; - // }, diff --git a/assets/javascripts/discourse/components/follow-selector-container.js.es6 b/assets/javascripts/discourse/components/follow-selector-container.js.es6 deleted file mode 100644 index 10619cb..0000000 --- a/assets/javascripts/discourse/components/follow-selector-container.js.es6 +++ /dev/null @@ -1,13 +0,0 @@ -import { default as computed, observes, on } from 'discourse-common/utils/decorators'; - -export default Ember.Component.extend({ - router: Ember.inject.service('-routing'), - classNames: 'follow-selector-container', - tagName: 'li', - - @computed('user', 'currentUser') - showToggle(user, currentUser) { - return currentUser && currentUser.username !== user.username; - }, - -}) diff --git a/assets/javascripts/discourse/components/follow-statistic.js b/assets/javascripts/discourse/components/follow-statistic.js new file mode 100644 index 0000000..7ebb859 --- /dev/null +++ b/assets/javascripts/discourse/components/follow-statistic.js @@ -0,0 +1,25 @@ +import Component from "@ember/component"; +import discourseComputed from "discourse-common/utils/decorators"; + +export default Component.extend({ + classNames: "follow-statistic", + + init() { + this._super(); + this.set("tagName", this.get("isCard") ? "h3" : "div"); + }, + + didInsertElement() { + Ember.run.scheduleOnce("afterRender", () => { + let parent = this.get("isCard") + ? ".card-content .metadata" + : ".user-main .secondary dl"; + this.$().prependTo(parent); + }); + }, + + @discourseComputed("context") + isCard(context) { + return context === "card"; + }, +}); diff --git a/assets/javascripts/discourse/components/follow-statistic.js.es6 b/assets/javascripts/discourse/components/follow-statistic.js.es6 deleted file mode 100644 index e4bc752..0000000 --- a/assets/javascripts/discourse/components/follow-statistic.js.es6 +++ /dev/null @@ -1,22 +0,0 @@ -import { default as computed } from 'discourse-common/utils/decorators'; - -export default Ember.Component.extend({ - classNames: 'follow-statistic', - - init() { - this._super(); - this.set('tagName', this.get('isCard') ? 'h3' : 'div'); - }, - - didInsertElement() { - Ember.run.scheduleOnce('afterRender', () => { - let parent = this.get('isCard') ? '.card-content .metadata' : '.user-main .secondary dl'; - this.$().prependTo(parent); - }); - }, - - @computed('context') - isCard(context) { - return context === 'card'; - } -}) diff --git a/assets/javascripts/discourse/connectors/admin-menu/follow-nav-button.hbs b/assets/javascripts/discourse/connectors/admin-menu/follow-nav-button.hbs deleted file mode 100644 index cac8190..0000000 --- a/assets/javascripts/discourse/connectors/admin-menu/follow-nav-button.hbs +++ /dev/null @@ -1,3 +0,0 @@ -{{#if currentUser.admin}} - {{nav-item route='adminFollow' label='admin.follow.label'}} -{{/if}} diff --git a/assets/javascripts/discourse/connectors/user-card-additional-buttons/follow-button-container.hbs b/assets/javascripts/discourse/connectors/user-card-additional-buttons/follow-button-container.hbs new file mode 100644 index 0000000..00eedfe --- /dev/null +++ b/assets/javascripts/discourse/connectors/user-card-additional-buttons/follow-button-container.hbs @@ -0,0 +1 @@ +{{follow-button user=user}} diff --git a/assets/javascripts/discourse/connectors/user-card-additional-controls/follow-selector-container.hbs b/assets/javascripts/discourse/connectors/user-card-additional-controls/follow-selector-container.hbs deleted file mode 100644 index c72d0e5..0000000 --- a/assets/javascripts/discourse/connectors/user-card-additional-controls/follow-selector-container.hbs +++ /dev/null @@ -1,3 +0,0 @@ -{{#if siteSettings.discourse_follow_enabled}} - {{follow-selector-container user=user}} -{{/if}} diff --git a/assets/javascripts/discourse/connectors/user-card-additional-controls/follow-selector-container.js.es6 b/assets/javascripts/discourse/connectors/user-card-additional-controls/follow-selector-container.js.es6 deleted file mode 100644 index a0ed2bc..0000000 --- a/assets/javascripts/discourse/connectors/user-card-additional-controls/follow-selector-container.js.es6 +++ /dev/null @@ -1,14 +0,0 @@ -export default { - setupComponent(attrs, component) { - - Ember.run.scheduleOnce('afterRender', () => { - const $container = $(component.get('element')); - const $usercardControls = $container.siblings('ul.usercard-controls'); - const container = 'li.follow-selector-container'; - - if (!$usercardControls.find(container).length) { - $usercardControls.append($container.find(container)); - } - }); - } -} diff --git a/assets/javascripts/discourse/connectors/user-main-nav/follow-user-container.hbs b/assets/javascripts/discourse/connectors/user-main-nav/follow-user-container.hbs index 5ac7dfe..cd75322 100644 --- a/assets/javascripts/discourse/connectors/user-main-nav/follow-user-container.hbs +++ b/assets/javascripts/discourse/connectors/user-main-nav/follow-user-container.hbs @@ -1,5 +1,3 @@ -{{#if siteSettings.discourse_follow_enabled}} - {{#if model.can_see_follow}} - {{#link-to 'follow'}}{{d-icon "users"}}{{i18n 'user.follow_nav'}}{{/link-to}} - {{/if}} +{{#if model.can_see_network_tab}} + {{#link-to "follow"}}{{d-icon "users"}}{{i18n "user.follow_nav"}}{{/link-to}} {{/if}} diff --git a/assets/javascripts/discourse/connectors/user-preferences-notifications/follow-notification-preferences-container.hbs b/assets/javascripts/discourse/connectors/user-preferences-notifications/follow-notification-preferences-container.hbs new file mode 100644 index 0000000..49cb439 --- /dev/null +++ b/assets/javascripts/discourse/connectors/user-preferences-notifications/follow-notification-preferences-container.hbs @@ -0,0 +1 @@ +{{follow-notification-preferences user=model}} diff --git a/assets/javascripts/discourse/connectors/user-preferences-notifications/follow-notification-preferences-container.js b/assets/javascripts/discourse/connectors/user-preferences-notifications/follow-notification-preferences-container.js new file mode 100644 index 0000000..a18d377 --- /dev/null +++ b/assets/javascripts/discourse/connectors/user-preferences-notifications/follow-notification-preferences-container.js @@ -0,0 +1,5 @@ +export default { + shouldRender(_, component) { + return component.siteSettings.follow_notifications_enabled; + }, +}; diff --git a/assets/javascripts/discourse/connectors/user-preferences-notifications/follow-notification-settings.js.es6 b/assets/javascripts/discourse/connectors/user-preferences-notifications/follow-notification-settings.js.es6 deleted file mode 100644 index 9a130cc..0000000 --- a/assets/javascripts/discourse/connectors/user-preferences-notifications/follow-notification-settings.js.es6 +++ /dev/null @@ -1,6 +0,0 @@ -export default { - shouldRender(_, ctx) { - return ctx.siteSettings.discourse_follow_enabled && - ctx.siteSettings.follow_notifications_enabled; - } -} \ No newline at end of file diff --git a/assets/javascripts/discourse/connectors/user-profile-controls/follow-button-container.hbs b/assets/javascripts/discourse/connectors/user-profile-controls/follow-button-container.hbs new file mode 100644 index 0000000..7c72bdb --- /dev/null +++ b/assets/javascripts/discourse/connectors/user-profile-controls/follow-button-container.hbs @@ -0,0 +1 @@ +{{follow-button user=model}} diff --git a/assets/javascripts/discourse/connectors/user-profile-controls/follow-toggle-container.hbs b/assets/javascripts/discourse/connectors/user-profile-controls/follow-toggle-container.hbs deleted file mode 100644 index e592ffb..0000000 --- a/assets/javascripts/discourse/connectors/user-profile-controls/follow-toggle-container.hbs +++ /dev/null @@ -1,3 +0,0 @@ -{{#if siteSettings.discourse_follow_enabled}} - {{follow-selector-container user=model}} -{{/if}} diff --git a/assets/javascripts/discourse/connectors/user-profile-controls/follow-toggle-container.js.es6 b/assets/javascripts/discourse/connectors/user-profile-controls/follow-toggle-container.js.es6 deleted file mode 100644 index a3c5507..0000000 --- a/assets/javascripts/discourse/connectors/user-profile-controls/follow-toggle-container.js.es6 +++ /dev/null @@ -1,7 +0,0 @@ -export default { - setupComponent() { - Ember.run.scheduleOnce('afterRender', () => { - $('.user-profile-controls-outlet .follow-selector-container').appendTo('.user-main .primary .controls ul'); - }); - } -} diff --git a/assets/javascripts/discourse/controllers/follow-delete-notifications.js.es6 b/assets/javascripts/discourse/controllers/follow-delete-notifications.js.es6 deleted file mode 100644 index 9a16815..0000000 --- a/assets/javascripts/discourse/controllers/follow-delete-notifications.js.es6 +++ /dev/null @@ -1,16 +0,0 @@ -import { ajax } from 'discourse/lib/ajax'; -import { default as computed, observes } from 'discourse-common/utils/decorators'; - -export default Ember.Controller.extend({ - actions: { - deleteNotifications() { - ajax('/admin/follow/delete_notifications', { - type: 'DELETE' - }).then((result) => { - // TODO consider some feedback - }).finally(() => { - this.send("closeModal"); - }) - } - } -}); diff --git a/assets/javascripts/discourse/controllers/follow-users.js b/assets/javascripts/discourse/controllers/follow-users.js new file mode 100644 index 0000000..2e874f4 --- /dev/null +++ b/assets/javascripts/discourse/controllers/follow-users.js @@ -0,0 +1,18 @@ +import Controller from "@ember/controller"; +import discourseComputed from "discourse-common/utils/decorators"; +import { notEmpty } from "@ember/object/computed"; + +export default Controller.extend({ + hasUsers: notEmpty("users"), + + @discourseComputed("viewing") + viewingSelf(viewing) { + return viewing === this.get("currentUser.username"); + }, + + @discourseComputed("type", "viewingSelf") + noneMessage(type, viewingSelf) { + let key = viewingSelf ? "none" : "none_other"; + return `user.${type}.${key}`; + }, +}); diff --git a/assets/javascripts/discourse/controllers/follow-users.js.es6 b/assets/javascripts/discourse/controllers/follow-users.js.es6 deleted file mode 100644 index c47c09b..0000000 --- a/assets/javascripts/discourse/controllers/follow-users.js.es6 +++ /dev/null @@ -1,16 +0,0 @@ -import { default as computed } from 'discourse-common/utils/decorators'; - -export default Ember.Controller.extend({ - hasUsers: Ember.computed.notEmpty('users'), - - @computed('viewing') - viewingSelf(viewing) { - return viewing === this.get('currentUser.username'); - }, - - @computed('type', 'viewingSelf') - noneMessage(type, viewingSelf) { - let key = viewingSelf ? 'none' : 'none_other'; - return `user.${type}.${key}`; - } -}); diff --git a/assets/javascripts/discourse/controllers/follow.js.es6 b/assets/javascripts/discourse/controllers/follow.js similarity index 83% rename from assets/javascripts/discourse/controllers/follow.js.es6 rename to assets/javascripts/discourse/controllers/follow.js index 4c4173f..992b698 100644 --- a/assets/javascripts/discourse/controllers/follow.js.es6 +++ b/assets/javascripts/discourse/controllers/follow.js @@ -1,5 +1,6 @@ import { inject as service } from "@ember/service"; import Controller from "@ember/controller"; + export default Controller.extend({ - router: service() -}); \ No newline at end of file + router: service(), +}); diff --git a/assets/javascripts/discourse/follow-admin-route-map.js.es6 b/assets/javascripts/discourse/follow-admin-route-map.js.es6 deleted file mode 100644 index 25c18bc..0000000 --- a/assets/javascripts/discourse/follow-admin-route-map.js.es6 +++ /dev/null @@ -1,7 +0,0 @@ -export default { - resource: 'admin', - map() { - this.route('adminFollow', { path: '/follow', resetNamespace: true }, function() { - }); - } -}; diff --git a/assets/javascripts/discourse/initializers/follow-initializer.js b/assets/javascripts/discourse/initializers/follow-initializer.js new file mode 100644 index 0000000..1dbd0ce --- /dev/null +++ b/assets/javascripts/discourse/initializers/follow-initializer.js @@ -0,0 +1,23 @@ +import { withPluginApi } from "discourse/lib/plugin-api"; + +export default { + name: "follow-plugin-initializer", + initialize(/*container*/) { + withPluginApi("0.8.10", (api) => { + const currentUser = api.getCurrentUser(); + if (!currentUser) { + return; + } + api.addNavigationBarItem({ + name: "following", + href: "/following", + customFilter(category, args /*, router*/) { + return !category && !args.tagId; + }, + }); + api.replaceIcon("notification.following", "user-friends"); + api.replaceIcon("notification.following_created_topic", "user-friends"); + api.replaceIcon("notification.following_replied", "user-friends"); + }); + }, +}; diff --git a/assets/javascripts/discourse/initializers/follow-initializer.js.es6 b/assets/javascripts/discourse/initializers/follow-initializer.js.es6 deleted file mode 100644 index 8936ddc..0000000 --- a/assets/javascripts/discourse/initializers/follow-initializer.js.es6 +++ /dev/null @@ -1,61 +0,0 @@ -import NavItem from 'discourse/models/nav-item'; -import { withPluginApi } from 'discourse/lib/plugin-api'; -import { replaceIcon } from 'discourse-common/lib/icon-library'; - -export default { - name: 'follow-edits', - initialize(container) { - const currentUser = container.lookup("current-user:main"); - const siteSettings = container.lookup("site-settings:main"); - - if (!siteSettings.discourse_follow_enabled) return; - - NavItem.reopenClass({ - buildList(category, args) { - let items = this._super(category, args); - - items = items.reject((item) => item.name === 'following' ); - - if (!category && currentUser && currentUser.total_following > 0) { - items.push(NavItem.fromText('following', args)); - } - - return items; - } - }); - - withPluginApi('0.8.13', api => { - api.modifyClass('route:discovery', { - actions: { - refresh() { - this.refresh(); - } - } - }); - - api.modifyClass("controller:preferences/notifications", { - actions: { - save() { - const custom_settings = [ - 'notify_me_when_followed', - 'notify_followed_user_when_followed', - 'notify_me_when_followed_replies', - 'notify_me_when_followed_posts' - ]; - - custom_settings.forEach( setting => { - this.set(`model.custom_fields.${setting}`, this.get(`model.${setting}`)) - }); - - this.get("saveAttrNames").push("custom_fields") - this._super(); - } - } - }) - }); - - replaceIcon('notification.following', 'user-friends'); - replaceIcon('notification.following_posted', 'user-friends'); - replaceIcon('notification.following_replied', 'user-friends'); - } -}; diff --git a/assets/javascripts/discourse/lib/follow-notification-levels.js.es6 b/assets/javascripts/discourse/lib/follow-notification-levels.js.es6 deleted file mode 100644 index 9772160..0000000 --- a/assets/javascripts/discourse/lib/follow-notification-levels.js.es6 +++ /dev/null @@ -1,19 +0,0 @@ -import { buttonDetails} from "discourse/lib/notification-levels"; - -const MUTED = 0; -const REGULAR = 1; -const TRACKING = 2; -const WATCHING = 3; -const WATCHING_FIRST_POST = 4; - -export const allLevels = [ - WATCHING, - TRACKING, - WATCHING_FIRST_POST, - REGULAR, - MUTED, -].map(buttonDetails); - -export const followLevels = allLevels.filter( - (l) => (l.id !== MUTED && l.id !== TRACKING) -); \ No newline at end of file diff --git a/assets/javascripts/discourse/routes/admin-follow.js.es6 b/assets/javascripts/discourse/routes/admin-follow.js.es6 deleted file mode 100644 index 3fbf13f..0000000 --- a/assets/javascripts/discourse/routes/admin-follow.js.es6 +++ /dev/null @@ -1,15 +0,0 @@ - -import DiscourseRoute from "discourse/routes/discourse"; - -export default DiscourseRoute.extend({ - actions: { - showSettings() { - const controller = this.controllerFor('adminSiteSettings'); - this.transitionTo('adminSiteSettingsCategory', 'plugins').then(() => { - controller.set('filter', 'follow'); - controller.set('_skipBounce', true); - controller.filterContentNow('plugins'); - }); - } - } -}); diff --git a/assets/javascripts/discourse/routes/follow.js.es6 b/assets/javascripts/discourse/routes/follow.js similarity index 56% rename from assets/javascripts/discourse/routes/follow.js.es6 rename to assets/javascripts/discourse/routes/follow.js index 392bcfd..60a491c 100644 --- a/assets/javascripts/discourse/routes/follow.js.es6 +++ b/assets/javascripts/discourse/routes/follow.js @@ -2,18 +2,12 @@ import DiscourseRoute from "discourse/routes/discourse"; export default DiscourseRoute.extend({ afterModel(model) { - if (!model.can_see_follow) { + if (!model.can_see_network_tab) { this.transitionTo("user"); - } else if (model.can_see_following) { - this.transitionTo("following"); - } else if (model.can_see_followers) { + } else if (!model.can_see_following) { this.transitionTo("followers"); + } else if (!model.can_see_followers) { + this.transitionTo("following"); } }, - - actions:{ - refreshFollow(){ - this.refresh(); - } - } -}); \ No newline at end of file +}); diff --git a/assets/javascripts/discourse/routes/followers.js b/assets/javascripts/discourse/routes/followers.js new file mode 100644 index 0000000..134bc3e --- /dev/null +++ b/assets/javascripts/discourse/routes/followers.js @@ -0,0 +1,20 @@ +import { ajax } from "discourse/lib/ajax"; +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ + model(/*params*/) { + return ajax(`/u/${this.paramsFor("user").username}/follow/followers`); + }, + + setupController(controller, model) { + this.controllerFor("follow-users").setProperties({ + users: model, + type: "followers", + viewing: this.paramsFor("user").username, + }); + }, + + renderTemplate() { + this.render("follow-users"); + }, +}); diff --git a/assets/javascripts/discourse/routes/followers.js.es6 b/assets/javascripts/discourse/routes/followers.js.es6 deleted file mode 100644 index be00333..0000000 --- a/assets/javascripts/discourse/routes/followers.js.es6 +++ /dev/null @@ -1,25 +0,0 @@ -import { ajax } from 'discourse/lib/ajax'; -import DiscourseRoute from "discourse/routes/discourse"; - -export default DiscourseRoute.extend({ - model(params) { - return ajax(`/u/${this.paramsFor('user').username}/follow/following`, { - type: 'GET', - data: { - type: 'followers' - } - }); - }, - - setupController(controller, model) { - this.controllerFor('follow-users').setProperties({ - users: model, - type: 'followers', - viewing: this.paramsFor('user').username - }); - }, - - renderTemplate() { - this.render('follow-users'); - } -}); diff --git a/assets/javascripts/discourse/routes/following.js b/assets/javascripts/discourse/routes/following.js new file mode 100644 index 0000000..7cdd224 --- /dev/null +++ b/assets/javascripts/discourse/routes/following.js @@ -0,0 +1,20 @@ +import { ajax } from "discourse/lib/ajax"; +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ + model(/* params */) { + return ajax(`/u/${this.paramsFor("user").username}/follow/following`); + }, + + setupController(controller, model) { + this.controllerFor("follow-users").setProperties({ + users: model, + type: "following", + viewing: this.paramsFor("user").username, + }); + }, + + renderTemplate() { + this.render("follow-users"); + }, +}); diff --git a/assets/javascripts/discourse/routes/following.js.es6 b/assets/javascripts/discourse/routes/following.js.es6 deleted file mode 100644 index fa5d220..0000000 --- a/assets/javascripts/discourse/routes/following.js.es6 +++ /dev/null @@ -1,25 +0,0 @@ -import { ajax } from 'discourse/lib/ajax'; -import DiscourseRoute from "discourse/routes/discourse"; - -export default DiscourseRoute.extend({ - model(params) { - return ajax(`/u/${this.paramsFor('user').username}/follow/following`, { - type: 'GET', - data: { - type: 'following' - } - }); - }, - - setupController(controller, model) { - this.controllerFor('follow-users').setProperties({ - users: model, - type: 'following', - viewing: this.paramsFor('user').username - }); - }, - - renderTemplate() { - this.render('follow-users'); - } -}); diff --git a/assets/javascripts/discourse/templates/admin/follow.hbs b/assets/javascripts/discourse/templates/admin/follow.hbs deleted file mode 100644 index 5a41e74..0000000 --- a/assets/javascripts/discourse/templates/admin/follow.hbs +++ /dev/null @@ -1,3 +0,0 @@ -{{#admin-nav}} - {{admin-follow}} -{{/admin-nav}} diff --git a/assets/javascripts/discourse/templates/components/admin-follow.hbs b/assets/javascripts/discourse/templates/components/admin-follow.hbs deleted file mode 100644 index 0fb2136..0000000 --- a/assets/javascripts/discourse/templates/components/admin-follow.hbs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - -
diff --git a/assets/javascripts/discourse/templates/components/follow-button.hbs b/assets/javascripts/discourse/templates/components/follow-button.hbs new file mode 100644 index 0000000..6b5737d --- /dev/null +++ b/assets/javascripts/discourse/templates/components/follow-button.hbs @@ -0,0 +1,8 @@ +{{#if showButton}} + {{d-button + label=labelKey + icon=icon + disabled=loading + action=(action "toggleFollow") + }} +{{/if}} diff --git a/assets/javascripts/discourse/connectors/user-preferences-notifications/follow-notification-settings.hbs b/assets/javascripts/discourse/templates/components/follow-notification-preferences.hbs similarity index 61% rename from assets/javascripts/discourse/connectors/user-preferences-notifications/follow-notification-settings.hbs rename to assets/javascripts/discourse/templates/components/follow-notification-preferences.hbs index e928f0b..dcec799 100644 --- a/assets/javascripts/discourse/connectors/user-preferences-notifications/follow-notification-settings.hbs +++ b/assets/javascripts/discourse/templates/components/follow-notification-preferences.hbs @@ -1,21 +1,21 @@
- - + +
{{preference-checkbox labelKey="user.follow_notifications_options.notify_me_when_followed" - checked=model.notify_me_when_followed}} + checked=user.notify_me_when_followed}} {{preference-checkbox labelKey="user.follow_notifications_options.notify_followed_user_when_followed" - checked=model.notify_followed_user_when_followed}} + checked=user.notify_followed_user_when_followed}} {{preference-checkbox labelKey="user.follow_notifications_options.notify_me_when_followed_replies" - checked=model.notify_me_when_followed_replies}} + checked=user.notify_me_when_followed_replies}} {{preference-checkbox - labelKey="user.follow_notifications_options.notify_me_when_followed_posts" - checked=model.notify_me_when_followed_posts}} + labelKey="user.follow_notifications_options.notify_me_when_followed_creates_topic" + checked=user.notify_me_when_followed_creates_topic}}
-
\ No newline at end of file + diff --git a/assets/javascripts/discourse/templates/components/follow-notifications-button.hbs b/assets/javascripts/discourse/templates/components/follow-notifications-button.hbs deleted file mode 100644 index 7ba54f8..0000000 --- a/assets/javascripts/discourse/templates/components/follow-notifications-button.hbs +++ /dev/null @@ -1,10 +0,0 @@ - {{follow-notifications-options - value=notificationLevel - onChange=(action "changeFollowNotificationLevel") - options=(hash - icon=icon - showFullTitle=showFullTitle - preventsClickPropagation=true - showCaret=showCaret - ) - }} \ No newline at end of file diff --git a/assets/javascripts/discourse/templates/components/follow-selector-container.hbs b/assets/javascripts/discourse/templates/components/follow-selector-container.hbs deleted file mode 100644 index b693466..0000000 --- a/assets/javascripts/discourse/templates/components/follow-selector-container.hbs +++ /dev/null @@ -1,11 +0,0 @@ -{{#if showToggle}} - {{follow-notifications-button - user=user - options=(hash - icon=icon - showFullTitle=showFullTitle - preventsClickPropagation=true - showCaret=showCaret - ) - }} -{{/if}} diff --git a/assets/javascripts/discourse/templates/follow.hbs b/assets/javascripts/discourse/templates/follow.hbs index 78f190b..37f509f 100644 --- a/assets/javascripts/discourse/templates/follow.hbs +++ b/assets/javascripts/discourse/templates/follow.hbs @@ -1,18 +1,18 @@ {{#d-section pageClass="user-follow" class="user-secondary-navigation" scrollTop="false"}} - {{#mobile-nav class='activity-nav' desktopClass='action-list follow-list nav-stacked' currentPath=router._router.currentPath}} + {{#mobile-nav class="activity-nav" desktopClass="action-list follow-list nav-stacked" currentPath=router._router.currentPath}} {{#if model.can_see_following}}
  • - {{#link-to 'following'}}{{i18n 'user.following.label'}}{{/link-to}} + {{#link-to "following"}}{{i18n "user.following.label"}}{{/link-to}}
  • {{/if}} {{#if model.can_see_followers}}
  • - {{#link-to 'followers'}}{{i18n 'user.followers.label'}}{{/link-to}} + {{#link-to "followers"}}{{i18n "user.followers.label"}}{{/link-to}}
  • {{/if}} {{/mobile-nav}} {{/d-section}} -
    +
    {{outlet}}
    diff --git a/assets/javascripts/discourse/templates/modal/follow-delete-notifications.hbs b/assets/javascripts/discourse/templates/modal/follow-delete-notifications.hbs deleted file mode 100644 index 6399065..0000000 --- a/assets/javascripts/discourse/templates/modal/follow-delete-notifications.hbs +++ /dev/null @@ -1,5 +0,0 @@ -{{#d-modal-body title="admin.follow.delete_notifications.modal_title" class="follow-admin-modal-title"}} -

    {{i18n 'admin.follow.delete_notifications.modal_question'}}

    - {{d-button action=(action "deleteNotifications") id="follow-admin-delete-notifications-commit-button" class="btn-large btn-danger" icon="trash-alt" label="admin.follow.delete_notifications.modal_commit" disabled=false}} - {{d-modal-cancel class="btn-large btn-default" close=(route-action "closeModal")}} -{{/d-modal-body}} diff --git a/assets/javascripts/discourse/users-route-map.js b/assets/javascripts/discourse/users-route-map.js new file mode 100644 index 0000000..b96ad36 --- /dev/null +++ b/assets/javascripts/discourse/users-route-map.js @@ -0,0 +1,13 @@ +export default { + resource: "user", + map() { + this.route( + "follow", + { path: "/follow", resetNamespace: true }, + function () { + this.route("followers", { path: "/followers", resetNamespace: true }); + this.route("following", { path: "/following", resetNamespace: true }); + } + ); + }, +}; diff --git a/assets/javascripts/discourse/users-route-map.js.es6 b/assets/javascripts/discourse/users-route-map.js.es6 deleted file mode 100644 index 1e9a452..0000000 --- a/assets/javascripts/discourse/users-route-map.js.es6 +++ /dev/null @@ -1,9 +0,0 @@ -export default { - resource: 'user', - map() { - this.route('follow', { path: '/follow', resetNamespace: true }, function() { - this.route('followers', { path: '/followers', resetNamespace: true }); - this.route('following', { path: '/following', resetNamespace: true }); - }); - } -}; diff --git a/assets/javascripts/discourse/widgets/following-notification-item.js b/assets/javascripts/discourse/widgets/following-notification-item.js new file mode 100644 index 0000000..4793c52 --- /dev/null +++ b/assets/javascripts/discourse/widgets/following-notification-item.js @@ -0,0 +1,14 @@ +import { createWidgetFrom } from "discourse/widgets/widget"; +import { DefaultNotificationItem } from "discourse/widgets/default-notification-item"; +import { userPath } from "discourse/lib/url"; +import I18n from "I18n"; + +createWidgetFrom(DefaultNotificationItem, "following-notification-item", { + description() { + return I18n.t("notifications.following_description"); + }, + + url(data) { + return userPath(data.display_username); + }, +}); diff --git a/assets/javascripts/discourse/widgets/following-notification-item.js.es6 b/assets/javascripts/discourse/widgets/following-notification-item.js.es6 deleted file mode 100644 index 9d307e5..0000000 --- a/assets/javascripts/discourse/widgets/following-notification-item.js.es6 +++ /dev/null @@ -1,17 +0,0 @@ -import { createWidgetFrom } from "discourse/widgets/widget"; -import { DefaultNotificationItem } from "discourse/widgets/default-notification-item"; -import { userPath } from "discourse/lib/url"; - -createWidgetFrom( - DefaultNotificationItem, - "following-notification-item", - { - description() { - return I18n.t('notifications.following_description'); - }, - - url(data) { - return userPath(data.display_username); - } - } -); diff --git a/assets/stylesheets/common/follow.scss b/assets/stylesheets/common/follow.scss index df74e45..0229146 100644 --- a/assets/stylesheets/common/follow.scss +++ b/assets/stylesheets/common/follow.scss @@ -28,30 +28,29 @@ } .follow-statistics-user-card { - .value{ - padding-left: 5px + .value { + padding-left: 5px; } } .admin-contents table.follow-admin { - border-collapse: collapse; - tr:hover { + tr:hover { background-color: unset; } td { - text-align: center; + text-align: center; } td:first-of-type { text-align: left; - } + } } .select-kit.dropdown-select-box.user-notifications-dropdown { - width:100%; + width: 100%; .select-kit-header-wrapper { align-items: center; justify-content: center; diff --git a/assets/stylesheets/mobile/follow.scss b/assets/stylesheets/mobile/follow.scss index e4bd79e..27b1802 100644 --- a/assets/stylesheets/mobile/follow.scss +++ b/assets/stylesheets/mobile/follow.scss @@ -1,3 +1,3 @@ #user-card .first-row .usercard-controls li:not(:nth-child(1)) { border-left: 0.5em solid transparent; -} \ No newline at end of file +} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index f847f3f..4c5f18b 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1,17 +1,4 @@ en: - admin_js: - admin: - follow: - label: "Follow" - settings: - label: "Settings" - delete_notifications: - label: "Delete Follow Notifications for All Users" - advice: "(do this before disabling or removing the plugin)" - modal_title: "Delete Follow Notifications" - modal_question: "Are you sure you want to delete all users Follow Notifications?" - modal_cancel: "Cancel" - modal_commit: "Delete" js: filters: following: @@ -39,16 +26,6 @@ en: follow_nav: "Network" follow: label: "Follow" - following_level: - regular: - title: "Regular Only" - description: "You will not be following this User, Notifications will be as normal. They will only appear in the Following Topic List if Posting within a Topic also Posted in by someone you are Following" - watching_first_post: - title: "Following (Topics)" - description: "You will be following this User, and will receive Notifications for all their new Topics. Only Topics they have authored will appear in the Following Topic list" - watching: - title: "Following (All)" - description: "You will be following this User, and will receive Notifications for all their Posts. All Topics containing their Posts will appear in the Following Topic list" following_watching: label: "Following (All)" following_watching_first_post: @@ -62,7 +39,19 @@ en: none: "No-one is following you yet." none_other: "No-one is following {{username}} yet." follow_notifications_options: - notify_followed_user_when_followed: "Notify users when I follow them." - notify_me_when_followed: "Notify me when users follow me." - notify_me_when_followed_replies: "Notify me when someone I follow replies." - notify_me_when_followed_posts: "Notify me when someone I follow posts." + notify_followed_user_when_followed: "Notify users when I follow them" + notify_me_when_followed: "Notify me when users follow me" + notify_me_when_followed_replies: "Notify me when someone I follow replies" + notify_me_when_followed_creates_topic: "Notify me when someone I follow creates a topic" + follow: + follow_pages_visibility: + no-one: "No one" + self: "Self" + trust_level_4: "Trust Level 4" + trust_level_3: "Trust Level 3" + trust_level_2: "Trust Level 2" + trust_level_1: "Trust Level 1" + trust_level_0: "Trust Level 0" + everyone: "Everyone" + follow_button_label: "Follow" + unfollow_button_label: "Unfollow" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 13b3b3c..0f1d614 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -5,3 +5,10 @@ en: follow_notifications_enabled: "Enable all follow notifications." follow_followers_visible: "Followers visibility." follow_following_visible: "Following visibility." + follow: + user_not_found: "Couldn't find user with username %{username}" + user_cannot_follow_themself: "User is not allowed to follow themself" + invalid_notification_level: "Invalid notification level %{level}" + cannot_follow_bot: "Cannot follow a bot user" + cannot_follow_suspended: "Cannot follow a suspend user" + cannot_follow_staged: "Cannot follow a staged user" diff --git a/config/routes.rb b/config/routes.rb index 776998d..5d51bf0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,13 +1,6 @@ -Discourse::Application.routes.append do - mount ::Follow::Engine, at: "follow" - %w{users u}.each_with_index do |root_path, index| - get "#{root_path}/:username/follow" => "follow/follow#index", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/follow/following" => "follow/follow#list", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/follow/followers" => "follow/follow#list", constraints: { username: RouteFormat.username } - end - delete "admin/follow/delete_notifications" => "follow/follow_admin#delete_notifications", constraints: AdminConstraint.new -end +# frozen_string_literal: true Follow::Engine.routes.draw do - put ":username" => "follow#update", constraints: { username: RouteFormat.username, format: /(json|html)/ }, defaults: { format: :json } + put ":username" => "follow#follow", constraints: { username: RouteFormat.username, format: /(json|html)/ }, defaults: { format: :json } + delete ":username" => "follow#unfollow", constraints: { username: RouteFormat.username, format: /(json|html)/ }, defaults: { format: :json } end diff --git a/config/settings.yml b/config/settings.yml index 529b4ed..93e79f2 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -9,28 +9,16 @@ plugins: default: true client: true follow_followers_visible: - type: enum + enum: "FollowPagesVisibility" default: everyone - choices: - - no-one - - self - - staff - - trust_level_4 - - trust_level_3 - - trust_level_2 - - trust_level_1 - - trust_level_0 - - everyone follow_following_visible: - type: enum + enum: "FollowPagesVisibility" default: everyone - choices: - - no-one - - self - - staff - - trust_level_4 - - trust_level_3 - - trust_level_2 - - trust_level_1 - - trust_level_0 - - everyone + default_notify_me_when_followed: + default: true + default_notify_followed_user_when_followed: + default: true + default_notify_me_when_followed_replies: + default: false + default_notify_me_when_followed_creates_topic: + default: false diff --git a/coverage/.last_run.json b/coverage/.last_run.json deleted file mode 100644 index e36719c..0000000 --- a/coverage/.last_run.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "result": { - "covered_percent": 90.66 - } -} diff --git a/coverage/.resultset.json b/coverage/.resultset.json deleted file mode 100644 index db629e5..0000000 --- a/coverage/.resultset.json +++ /dev/null @@ -1,163 +0,0 @@ -{ - "RSpec": { - "coverage": { - "/Users/angusmcleod/discourse/discourse/plugins/discourse-follow/lib/follow/engine.rb": { - "lines": [ - 1, - 1, - 1, - 1, - null, - null - ] - }, - "/Users/angusmcleod/discourse/discourse/plugins/discourse-follow/lib/follow/notification.rb": { - "lines": [ - 1, - 1, - 14, - null, - null, - null, - null, - null - ] - }, - "/Users/angusmcleod/discourse/discourse/plugins/discourse-follow/lib/follow/updater.rb": { - "lines": [ - 1, - 1, - 11, - 11, - null, - null, - 1, - 11, - 11, - null, - 11, - 11, - 11, - 11, - 11, - null, - 11, - 11, - null, - 11, - 0, - 0, - 0, - null, - null, - null, - 11, - null, - null, - 0, - 0, - null, - null, - 11, - 23, - null, - 11, - 11, - null, - 11, - null, - 11, - null, - null, - null, - null, - null, - 11, - null, - null, - null, - 1, - 11, - null, - null, - null, - null, - null, - 1, - 11, - null, - null, - 1, - 11, - null, - null - ] - }, - "/Users/angusmcleod/discourse/discourse/plugins/discourse-follow/app/controllers/follow/follow_controller.rb": { - "lines": [ - 1, - 1, - null, - null, - 1, - 1, - 1, - null, - 1, - 1, - null, - 1, - 1, - 1, - null, - 1, - null, - 1, - null, - 0, - null, - null, - null, - 1, - 2, - 2, - null, - 2, - 2, - null, - 2, - 2, - null, - 2, - 2, - 4, - null, - 2, - 2, - null, - 0, - null, - null, - null - ] - }, - "/Users/angusmcleod/discourse/discourse/plugins/discourse-follow/config/routes.rb": { - "lines": [ - 1, - 1, - 1, - 2, - 2, - 2, - null, - null, - null, - 1, - 1, - null - ] - } - }, - "timestamp": 1606770333 - } -} diff --git a/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc.png b/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc.png deleted file mode 100644 index e1ba61a8055fcb18273f2468d335572204667b1f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S1|*9D%+3I*bWaz@5R22v2@;zYta_*?F5u6Q zWR@in#&u+WgT?Hi<}D3B3}GOXuX|8Oj3tosHiJ3*4TN zC7>_x-r1O=t(?KoTC+`+>7&2GzdqLHBg&F)2Q?&EGZ+}|Rpsc~9`m>jw35No)z4*} HQ$iB}HK{Sd diff --git a/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc_disabled.png b/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc_disabled.png deleted file mode 100644 index fb11dfe24a6c564cb7ddf8bc96703ebb121df1e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S0wixl{&NRX(Vi}jAsXkC6BcOhI9!^3NY?Do zDX;f`c1`y6n0RgO@$!H7chZT&|Jn0dmaqO^XNm-CGtk!Ur<_=Jws3;%W$<+Mb6Mw<&;$T1GdZXL diff --git a/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_both.png b/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_both.png deleted file mode 100644 index af5bc7c5a10b9d6d57cb641aeec752428a07f0ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 201 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S0wixl{&NRX6FglULp08Bycxyy87-Q;~nRxO8@-UU*I^KVWyN+&SiMHu5xDOu|HNvwzODfTdXjhVyNu1 z#7^XbGKZ7LW3XeONb$RKLeE*WhqbYpIXPIqK@r4)v+qN8um%99%MPpS9d#7Ed7SL@Bp00i_>zopr0H-Zb Aj{pDw diff --git a/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc.png b/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc.png deleted file mode 100644 index 0e156deb5f61d18f9e2ec5da4f6a8c94a5b4fb41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S1|*9D%+3I*R8JSj5R22v2@yo z(czD9$NuDl3Ljm9c#_#4$vXUz=f1~&WY3aa=h!;z7fOEN>ySP9QA=6C-^Dmb&tuM= z4Z&=WZU;2WF>e%GI&mWJk^K!jrbro{W;-I>FeCfLGJl3}+Z^2)3Kw?+EoAU?^>bP0 Hl+XkKC^j|Q{b@g3TV7E(Grjn^aLC2o)_ptHrtUEoT$S@q)~)7U@V;W{6)!%@ u>N?4t-1qslpJw9!O?PJ&w0Cby.sorting_1,table.dataTable.order-column tbody tr>.sorting_2,table.dataTable.order-column tbody tr>.sorting_3,table.dataTable.display tbody tr>.sorting_1,table.dataTable.display tbody tr>.sorting_2,table.dataTable.display tbody tr>.sorting_3{background-color:#fafafa}table.dataTable.order-column tbody tr.selected>.sorting_1,table.dataTable.order-column tbody tr.selected>.sorting_2,table.dataTable.order-column tbody tr.selected>.sorting_3,table.dataTable.display tbody tr.selected>.sorting_1,table.dataTable.display tbody tr.selected>.sorting_2,table.dataTable.display tbody tr.selected>.sorting_3{background-color:#acbad5}table.dataTable.display tbody tr.odd>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd>.sorting_1{background-color:#f1f1f1}table.dataTable.display tbody tr.odd>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd>.sorting_2{background-color:#f3f3f3}table.dataTable.display tbody tr.odd>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd>.sorting_3{background-color:whitesmoke}table.dataTable.display tbody tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody tr.even>.sorting_1,table.dataTable.order-column.stripe tbody tr.even>.sorting_1{background-color:#fafafa}table.dataTable.display tbody tr.even>.sorting_2,table.dataTable.order-column.stripe tbody tr.even>.sorting_2{background-color:#fcfcfc}table.dataTable.display tbody tr.even>.sorting_3,table.dataTable.order-column.stripe tbody tr.even>.sorting_3{background-color:#fefefe}table.dataTable.display tbody tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{background-color:#eaeaea}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{background-color:#ececec}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{background-color:#efefef}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{background-color:#a5b2cb}table.dataTable.no-footer{border-bottom:1px solid #111}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px 4px 4px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable,table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both;*zoom:1;zoom:1}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{margin-left:.5em}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;color:#333 !important;border:1px solid transparent;border-radius:2px}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid #979797;background-color:white;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,white),color-stop(100%,#dcdcdc));background:-webkit-linear-gradient(top,white 0,#dcdcdc 100%);background:-moz-linear-gradient(top,white 0,#dcdcdc 100%);background:-ms-linear-gradient(top,white 0,#dcdcdc 100%);background:-o-linear-gradient(top,white 0,#dcdcdc 100%);background:linear-gradient(to bottom,white 0,#dcdcdc 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#585858),color-stop(100%,#111));background:-webkit-linear-gradient(top,#585858 0,#111 100%);background:-moz-linear-gradient(top,#585858 0,#111 100%);background:-ms-linear-gradient(top,#585858 0,#111 100%);background:-o-linear-gradient(top,#585858 0,#111 100%);background:linear-gradient(to bottom,#585858 0,#111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:0;background-color:#2b2b2b;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#2b2b2b),color-stop(100%,#0c0c0c));background:-webkit-linear-gradient(top,#2b2b2b 0,#0c0c0c 100%);background:-moz-linear-gradient(top,#2b2b2b 0,#0c0c0c 100%);background:-ms-linear-gradient(top,#2b2b2b 0,#0c0c0c 100%);background:-o-linear-gradient(top,#2b2b2b 0,#0c0c0c 100%);background:linear-gradient(to bottom,#2b2b2b 0,#0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:40px;margin-left:-50%;margin-top:-25px;padding-top:20px;text-align:center;font-size:1.2em;background-color:white;background:-webkit-gradient(linear,left top,right top,color-stop(0,rgba(255,255,255,0)),color-stop(25%,rgba(255,255,255,0.9)),color-stop(75%,rgba(255,255,255,0.9)),color-stop(100%,rgba(255,255,255,0)));background:-webkit-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,0.9) 25%,rgba(255,255,255,0.9) 75%,rgba(255,255,255,0) 100%);background:-moz-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,0.9) 25%,rgba(255,255,255,0.9) 75%,rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,0.9) 25%,rgba(255,255,255,0.9) 75%,rgba(255,255,255,0) 100%);background:-o-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,0.9) 25%,rgba(255,255,255,0.9) 75%,rgba(255,255,255,0) 100%);background:linear-gradient(to right,rgba(255,255,255,0) 0,rgba(255,255,255,0.9) 25%,rgba(255,255,255,0.9) 75%,rgba(255,255,255,0) 100%)}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{*margin-top:-1px;-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid #111}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:0}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width:767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:.5em}}@media screen and (max-width:640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:.5em}}pre .comment,pre .template_comment,pre .diff .header,pre .javadoc{color:#998;font-style:italic}pre .keyword,pre .css .rule .keyword,pre .winutils,pre .javascript .title,pre .lisp .title{color:#000;font-weight:bold}pre .number,pre .hexcolor{color:#458}pre .string,pre .tag .value,pre .phpdoc,pre .tex .formula{color:#d14}pre .subst{color:#712}pre .constant,pre .title,pre .id{color:#900;font-weight:bold}pre .javascript .title,pre .lisp .title,pre .subst{font-weight:normal}pre .class .title,pre .haskell .label,pre .tex .command{color:#458;font-weight:bold}pre .tag,pre .tag .title,pre .rules .property,pre .django .tag .keyword{color:navy;font-weight:normal}pre .attribute,pre .variable,pre .instancevar,pre .lisp .body{color:teal}pre .regexp{color:#009926}pre .class{color:#458;font-weight:bold}pre .symbol,pre .ruby .symbol .string,pre .ruby .symbol .keyword,pre .ruby .symbol .keymethods,pre .lisp .keyword,pre .tex .special,pre .input_number{color:#990073}pre .builtin,pre .built_in,pre .lisp .title{color:#0086b3}pre .preprocessor,pre .pi,pre .doctype,pre .shebang,pre .cdata{color:#999;font-weight:bold}pre .deletion{background:#fdd}pre .addition{background:#dfd}pre .diff .change{background:#0086b3}pre .chunk{color:#aaa}pre .tex .formula{opacity:.5}.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{position:absolute;left:-99999999px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:after{content:".";display:block;height:0;clear:both;visibility:hidden}.ui-helper-clearfix{display:inline-block}/*\*/* html .ui-helper-clearfix{height:1%}.ui-helper-clearfix{display:block}/**/.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-state-disabled{cursor:default !important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:absolute;top:0;left:0;width:100%;height:100%}.ui-widget{font-family:Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #aaa;background:#fff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x;color:#222}.ui-widget-content a{color:#222}.ui-widget-header{border:1px solid #aaa;background:#ccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x;color:#222;font-weight:bold}.ui-widget-header a{color:#222}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #d3d3d3;background:#e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#555}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#555;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #999;background:#dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-hover a,.ui-state-hover a:hover{color:#212121;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #aaa;background:#fff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#212121;text-decoration:none}.ui-widget :active{outline:0}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fcefa1;background:#fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x;color:#cd0a0a}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#cd0a0a}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#cd0a0a}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-icon{width:16px;height:16px;background-image:url(images/ui-icons_222222_256x240.png)}.ui-widget-content .ui-icon{background-image:url(images/ui-icons_222222_256x240.png)}.ui-widget-header .ui-icon{background-image:url(images/ui-icons_222222_256x240.png)}.ui-state-default .ui-icon{background-image:url(images/ui-icons_888888_256x240.png)}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(images/ui-icons_454545_256x240.png)}.ui-state-active .ui-icon{background-image:url(images/ui-icons_454545_256x240.png)}.ui-state-highlight .ui-icon{background-image:url(images/ui-icons_2e83ff_256x240.png)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(images/ui-icons_cd0a0a_256x240.png)}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-off{background-position:-96px -144px}.ui-icon-radio-on{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-tl{-moz-border-radius-topleft:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px}.ui-corner-tr{-moz-border-radius-topright:4px;-webkit-border-top-right-radius:4px;border-top-right-radius:4px}.ui-corner-bl{-moz-border-radius-bottomleft:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px}.ui-corner-br{-moz-border-radius-bottomright:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px}.ui-corner-top{-moz-border-radius-topleft:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-webkit-border-top-right-radius:4px;border-top-right-radius:4px}.ui-corner-bottom{-moz-border-radius-bottomleft:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px}.ui-corner-right{-moz-border-radius-topright:4px;-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-bottomright:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px}.ui-corner-left{-moz-border-radius-topleft:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px}.ui-corner-all{-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px}.ui-widget-overlay{background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.30;filter:Alpha(Opacity=30)}.ui-widget-shadow{margin:-8px 0 0 -8px;padding:8px;background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.30;filter:Alpha(Opacity=30);-moz-border-radius:8px;-webkit-border-radius:8px;border-radius:8px}#colorbox,#cboxOverlay,#cboxWrapper{position:absolute;top:0;left:0;z-index:9999;overflow:hidden}#cboxOverlay{position:fixed;width:100%;height:100%}#cboxMiddleLeft,#cboxBottomLeft{clear:left}#cboxContent{position:relative}#cboxLoadedContent{overflow:auto}#cboxTitle{margin:0}#cboxLoadingOverlay,#cboxLoadingGraphic{position:absolute;top:0;left:0;width:100%;height:100%}#cboxPrevious,#cboxNext,#cboxClose,#cboxSlideshow{cursor:pointer}.cboxPhoto{float:left;margin:auto;border:0;display:block;max-width:none}.cboxIframe{width:100%;height:100%;display:block;border:0}#colorbox,#cboxContent,#cboxLoadedContent{box-sizing:content-box}#cboxOverlay{background:#000}#cboxTopLeft{width:14px;height:14px;background:url(colorbox/controls.png) no-repeat 0 0}#cboxTopCenter{height:14px;background:url(colorbox/border.png) repeat-x top left}#cboxTopRight{width:14px;height:14px;background:url(colorbox/controls.png) no-repeat -36px 0}#cboxBottomLeft{width:14px;height:43px;background:url(colorbox/controls.png) no-repeat 0 -32px}#cboxBottomCenter{height:43px;background:url(colorbox/border.png) repeat-x bottom left}#cboxBottomRight{width:14px;height:43px;background:url(colorbox/controls.png) no-repeat -36px -32px}#cboxMiddleLeft{width:14px;background:url(colorbox/controls.png) repeat-y -175px 0}#cboxMiddleRight{width:14px;background:url(colorbox/controls.png) repeat-y -211px 0}#cboxContent{background:#fff;overflow:visible}.cboxIframe{background:#fff}#cboxError{padding:50px;border:1px solid #ccc}#cboxLoadedContent{margin-bottom:5px}#cboxLoadingOverlay{background:url(colorbox/loading_background.png) no-repeat center center}#cboxLoadingGraphic{background:url(colorbox/loading.gif) no-repeat center center}#cboxTitle{position:absolute;bottom:-25px;left:0;text-align:center;width:100%;font-weight:bold;color:#7c7c7c}#cboxCurrent{position:absolute;bottom:-25px;left:58px;font-weight:bold;color:#7c7c7c}#cboxPrevious,#cboxNext,#cboxClose,#cboxSlideshow{position:absolute;bottom:-29px;background:url(colorbox/controls.png) no-repeat 0 0;width:23px;height:23px;text-indent:-9999px}#cboxPrevious{left:0;background-position:-51px -25px}#cboxPrevious:hover{background-position:-51px 0}#cboxNext{left:27px;background-position:-75px -25px}#cboxNext:hover{background-position:-75px 0}#cboxClose{right:0;background-position:-100px -25px}#cboxClose:hover{background-position:-100px 0}.cboxSlideshow_on #cboxSlideshow{background-position:-125px 0;right:27px}.cboxSlideshow_on #cboxSlideshow:hover{background-position:-150px 0}.cboxSlideshow_off #cboxSlideshow{background-position:-150px -25px;right:27px}.cboxSlideshow_off #cboxSlideshow:hover{background-position:-125px 0}#loading{position:fixed;left:40%;top:50%}a{color:#333;text-decoration:none}a:hover{color:#000;text-decoration:underline}body{font-family:"Lucida Grande",Helvetica,"Helvetica Neue",Arial,sans-serif;padding:12px;background-color:#333}h1,h2,h3,h4{color:#1c2324;margin:0;padding:0;margin-bottom:12px}table{width:100%}#content{clear:left;background-color:white;border:2px solid #ddd;border-top:8px solid #ddd;padding:18px;-webkit-border-bottom-left-radius:5px;-webkit-border-bottom-right-radius:5px;-webkit-border-top-right-radius:5px;-moz-border-radius-bottomleft:5px;-moz-border-radius-bottomright:5px;-moz-border-radius-topright:5px;border-bottom-left-radius:5px;border-bottom-right-radius:5px;border-top-right-radius:5px}.dataTables_filter,.dataTables_info{padding:2px 6px}abbr.timeago{text-decoration:none;border:0;font-weight:bold}.timestamp{float:right;color:#ddd}.group_tabs{list-style:none;float:left;margin:0;padding:0}.group_tabs li{display:inline;float:left}.group_tabs li a{font-family:Helvetica,Arial,sans-serif;display:block;float:left;text-decoration:none;padding:4px 8px;background-color:#aaa;background:-webkit-gradient(linear,0 0,0 bottom,from(#ddd),to(#aaa));background:-moz-linear-gradient(#ddd,#aaa);background:linear-gradient(#ddd,#aaa);text-shadow:#e5e5e5 1px 1px 0;border-bottom:0;color:#333;font-weight:bold;margin-right:8px;border-top:1px solid #efefef;-webkit-border-top-left-radius:2px;-webkit-border-top-right-radius:2px;-moz-border-radius-topleft:2px;-moz-border-radius-topright:2px;border-top-left-radius:2px;border-top-right-radius:2px}.group_tabs li a:hover{background-color:#ccc;background:-webkit-gradient(linear,0 0,0 bottom,from(#eee),to(#aaa));background:-moz-linear-gradient(#eee,#aaa);background:linear-gradient(#eee,#aaa)}.group_tabs li a:active{padding-top:5px;padding-bottom:3px}.group_tabs li.active a{color:black;text-shadow:#fff 1px 1px 0;background-color:#ddd;background:-webkit-gradient(linear,0 0,0 bottom,from(white),to(#ddd));background:-moz-linear-gradient(white,#ddd);background:linear-gradient(white,#ddd)}.file_list{margin-bottom:18px}.file_list--responsive{overflow-x:auto;overflow-y:hidden}a.src_link{background:url("./magnify.png") no-repeat left 50%;padding-left:18px}tr,td{margin:0;padding:0}th{white-space:nowrap}th.ui-state-default{cursor:pointer}th span.ui-icon{float:left}td{padding:4px 8px}td.strong{font-weight:bold}.cell--number{text-align:right}.source_table h3,.source_table h4{padding:0;margin:0;margin-bottom:4px}.source_table .header{padding:10px}.source_table pre{margin:0;padding:0;white-space:normal;color:#000;font-family:"Monaco","Inconsolata","Consolas",monospace}.source_table code{color:#000;font-family:"Monaco","Inconsolata","Consolas",monospace}.source_table pre{background-color:#333}.source_table pre ol{margin:0;padding:0;margin-left:45px;font-size:12px;color:white}.source_table pre li{margin:0;padding:2px 6px;border-left:5px solid white}.source_table pre li code{white-space:pre;white-space:pre-wrap}.source_table pre .hits{float:right;margin-left:10px;padding:2px 4px;background-color:#444;background:-webkit-gradient(linear,0 0,0 bottom,from(#222),to(#666));background:-moz-linear-gradient(#222,#666);background:linear-gradient(#222,#666);color:white;font-family:Helvetica,"Helvetica Neue",Arial,sans-serif;font-size:10px;font-weight:bold;text-align:center;border-radius:6px}#footer{color:#ddd;font-size:12px;font-weight:bold;margin-top:12px;text-align:right}#footer a{color:#eee;text-decoration:underline}#footer a:hover{color:#fff;text-decoration:none}.green{color:#090}.red{color:#900}.yellow{color:#da0}.blue{color:blue}thead th{background:white}.source_table .covered{border-color:#090}.source_table .missed{border-color:#900}.source_table .never{border-color:black}.source_table .skipped{border-color:#fc0}.source_table .missed-branch{border-color:#bf0000}.source_table .covered:nth-child(odd){background-color:#cdf2cd}.source_table .covered:nth-child(even){background-color:#dbf2db}.source_table .missed:nth-child(odd){background-color:#f7c0c0}.source_table .missed:nth-child(even){background-color:#f7cfcf}.source_table .never:nth-child(odd){background-color:#efefef}.source_table .never:nth-child(even){background-color:#f4f4f4}.source_table .skipped:nth-child(odd){background-color:#fbf0c0}.source_table .skipped:nth-child(even){background-color:#fbffcf}.source_table .missed-branch:nth-child(odd){background-color:#cc8e8e}.source_table .missed-branch:nth-child(even){background-color:#cc6e6e} \ No newline at end of file diff --git a/coverage/assets/0.12.3/application.js b/coverage/assets/0.12.3/application.js deleted file mode 100644 index e1c2ab2..0000000 --- a/coverage/assets/0.12.3/application.js +++ /dev/null @@ -1,7 +0,0 @@ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(T,e){"use strict";function g(e,t,n){var r,a,i=(n=n||le).createElement("script");if(i.text=e,t)for(r in Se)(a=t[r]||t.getAttribute&&t.getAttribute(r))&&i.setAttribute(r,a);n.head.appendChild(i).parentNode.removeChild(i)}function m(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?pe[ge.call(e)]||"object":typeof e}function s(e){var t=!!e&&"length"in e&&e.length,n=m(e);return!we(e)&&!xe(e)&&("array"===n||0===t||"number"==typeof t&&0D.cacheLength&&delete n[r.shift()],n[e+" "]=t}var r=[];return n}function l(e){return e[q]=!0,e}function a(e){var t=E.createElement("fieldset");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function t(e,t){for(var n=e.split("|"),r=n.length;r--;)D.attrHandle[n[r]]=t}function u(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function r(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function i(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function o(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&_e(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function s(o){return l(function(i){return i=+i,l(function(e,t){for(var n,r=o([],e.length,i),a=r.length;a--;)e[n=r[a]]&&(e[n]=!(t[n]=e[n]))})})}function p(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function c(){}function g(e){for(var t=0,n=e.length,r="";t+~]|"+re+")"+re+"*"),fe=new RegExp(re+"|>"),de=new RegExp(oe),he=new RegExp("^"+ae+"$"),pe={ID:new RegExp("^#("+ae+")"),CLASS:new RegExp("^\\.("+ae+")"),TAG:new RegExp("^("+ae+"|[*])"),ATTR:new RegExp("^"+ie),PSEUDO:new RegExp("^"+oe),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+re+"*(even|odd|(([+-]|)(\\d*)n|)"+re+"*(?:([+-]|)"+re+"*(\\d+)|))"+re+"*\\)|)","i"),bool:new RegExp("^(?:"+ne+")$","i"),needsContext:new RegExp("^"+re+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+re+"*((?:-\\d)?\\d*)"+re+"*\\)|)(?=[^-]|$)","i")},ge=/HTML$/i,me=/^(?:input|select|textarea|button)$/i,ve=/^h\d$/i,ye=/^[^{]+\{\s*\[native \w/,be=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,we=/[+~]/,xe=new RegExp("\\\\([\\da-f]{1,6}"+re+"?|("+re+")|.)","ig"),Se=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},De=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,Te=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},Ce=function(){L()},_e=f(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{Q.apply(Y=ee.call(W.childNodes),W.childNodes),Y[W.childNodes.length].nodeType}catch(Ae){Q={apply:Y.length?function(e,t){K.apply(e,ee.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}for(v in S=w.support={},C=w.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!ge.test(t||n&&n.nodeName||"HTML")},L=w.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:W;return r!==E&&9===r.nodeType&&r.documentElement&&(R=(E=r).documentElement,F=!C(E),W!==E&&(n=E.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",Ce,!1):n.attachEvent&&n.attachEvent("onunload",Ce)),S.attributes=a(function(e){return e.className="i",!e.getAttribute("className")}),S.getElementsByTagName=a(function(e){return e.appendChild(E.createComment("")),!e.getElementsByTagName("*").length}),S.getElementsByClassName=ye.test(E.getElementsByClassName),S.getById=a(function(e){return R.appendChild(e).id=q,!E.getElementsByName||!E.getElementsByName(q).length}),S.getById?(D.filter.ID=function(e){var t=e.replace(xe,Se);return function(e){return e.getAttribute("id")===t}},D.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&F){var n=t.getElementById(e);return n?[n]:[]}}):(D.filter.ID=function(e){var n=e.replace(xe,Se);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},D.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&F){var n,r,a,i=t.getElementById(e);if(i){if((n=i.getAttributeNode("id"))&&n.value===e)return[i];for(a=t.getElementsByName(e),r=0;i=a[r++];)if((n=i.getAttributeNode("id"))&&n.value===e)return[i]}return[]}}),D.find.TAG=S.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):S.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],a=0,i=t.getElementsByTagName(e);if("*"!==e)return i;for(;n=i[a++];)1===n.nodeType&&r.push(n);return r},D.find.CLASS=S.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&F)return t.getElementsByClassName(e)},H=[],P=[],(S.qsa=ye.test(E.querySelectorAll))&&(a(function(e){R.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&P.push("[*^$]="+re+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||P.push("\\["+re+"*(?:value|"+ne+")"),e.querySelectorAll("[id~="+q+"-]").length||P.push("~="),e.querySelectorAll(":checked").length||P.push(":checked"),e.querySelectorAll("a#"+q+"+*").length||P.push(".#.+[+~]")}),a(function(e){e.innerHTML="";var t=E.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&P.push("name"+re+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&P.push(":enabled",":disabled"),R.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&P.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),P.push(",.*:")})),(S.matchesSelector=ye.test(M=R.matches||R.webkitMatchesSelector||R.mozMatchesSelector||R.oMatchesSelector||R.msMatchesSelector))&&a(function(e){S.disconnectedMatch=M.call(e,"*"),M.call(e,"[s!='']:x"),H.push("!=",oe)}),P=P.length&&new RegExp(P.join("|")),H=H.length&&new RegExp(H.join("|")),t=ye.test(R.compareDocumentPosition),O=t||ye.test(R.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},G=t?function(e,t){if(e===t)return j=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!S.sortDetached&&t.compareDocumentPosition(e)===n?e===E||e.ownerDocument===W&&O(W,e)?-1:t===E||t.ownerDocument===W&&O(W,t)?1:I?te(I,e)-te(I,t):0:4&n?-1:1)}:function(e,t){if(e===t)return j=!0,0;var n,r=0,a=e.parentNode,i=t.parentNode,o=[e],s=[t];if(!a||!i)return e===E?-1:t===E?1:a?-1:i?1:I?te(I,e)-te(I,t):0;if(a===i)return u(e,t);for(n=e;n=n.parentNode;)o.unshift(n);for(n=t;n=n.parentNode;)s.unshift(n);for(;o[r]===s[r];)r++;return r?u(o[r],s[r]):o[r]===W?-1:s[r]===W?1:0}),E},w.matches=function(e,t){return w(e,null,null,t)},w.matchesSelector=function(e,t){if((e.ownerDocument||e)!==E&&L(e),S.matchesSelector&&F&&!V[t+" "]&&(!H||!H.test(t))&&(!P||!P.test(t)))try{var n=M.call(e,t);if(n||S.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(Ae){V(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(xe,Se),e[3]=(e[3]||e[4]||e[5]||"").replace(xe,Se),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||w.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&w.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return pe.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&de.test(n)&&(t=_(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(xe,Se).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=U[e+" "];return t||(t=new RegExp("(^|"+re+")"+e+"("+re+"|$)"))&&U(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,a){return function(e){var t=w.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===a:"!="===r?t!==a:"^="===r?a&&0===t.indexOf(a):"*="===r?a&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;Te.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?Te.find.matchesSelector(r,e)?[r]:[]:Te.find.matches(e,Te.grep(t,function(e){return 1===e.nodeType}))},Te.fn.extend({find:function(e){var t,n,r=this.length,a=this;if("string"!=typeof e)return this.pushStack(Te(e).filter(function(){for(t=0;t)[^>]*|#([\w-]+))$/;(Te.fn.init=function(e,t,n){var r,a;if(!e)return this;if(n=n||je,"string"!=typeof e)return e.nodeType?(this[0]=e,this.length=1,this):we(e)?n.ready!==undefined?n.ready(e):e(Te):Te.makeArray(e,this);if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:Le.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof Te?t[0]:t,Te.merge(this,Te.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:le,!0)),Ie.test(r[1])&&Te.isPlainObject(t))for(r in t)we(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(a=le.getElementById(r[2]))&&(this[0]=a,this.length=1),this}).prototype=Te.fn,je=Te(le);var Ee=/^(?:parents|prev(?:Until|All))/,Re={children:!0,contents:!0,next:!0,prev:!0};Te.fn.extend({has:function(e){var t=Te(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,rt=/^$|^module$|\/(?:java|ecma)script/i,at={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};at.optgroup=at.option,at.tbody=at.tfoot=at.colgroup=at.caption=at.thead,at.th=at.td;var it,ot,st=/<|&#?\w+;/;it=le.createDocumentFragment().appendChild(le.createElement("div")),(ot=le.createElement("input")).setAttribute("type","radio"),ot.setAttribute("checked","checked"),ot.setAttribute("name","t"),it.appendChild(ot),be.checkClone=it.cloneNode(!0).cloneNode(!0).lastChild.checked,it.innerHTML="",be.noCloneChecked=!!it.cloneNode(!0).lastChild.defaultValue;var lt=/^key/,ut=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ct=/^([^.]*)(?:\.(.+)|)/;Te.event={global:{},add:function(t,e,n,r,a){var i,o,s,l,u,c,f,d,h,p,g,m=Be.get(t);if(m)for(n.handler&&(n=(i=n).handler,a=i.selector),a&&Te.find.matchesSelector(Je,a),n.guid||(n.guid=Te.guid++),(l=m.events)||(l=m.events={}),(o=m.handle)||(o=m.handle=function(e){return void 0!==Te&&Te.event.triggered!==e.type?Te.event.dispatch.apply(t,arguments):undefined}),u=(e=(e||"").match(Fe)||[""]).length;u--;)h=g=(s=ct.exec(e[u])||[])[1],p=(s[2]||"").split(".").sort(),h&&(f=Te.event.special[h]||{},h=(a?f.delegateType:f.bindType)||h,f=Te.event.special[h]||{},c=Te.extend({type:h,origType:g,data:r,handler:n,guid:n.guid,selector:a,needsContext:a&&Te.expr.match.needsContext.test(a),namespace:p.join(".")},i),(d=l[h])||((d=l[h]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,p,o)||t.addEventListener&&t.addEventListener(h,o)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),a?d.splice(d.delegateCount++,0,c):d.push(c),Te.event.global[h]=!0)},remove:function(e,t,n,r,a){var i,o,s,l,u,c,f,d,h,p,g,m=Be.hasData(e)&&Be.get(e);if(m&&(l=m.events)){for(u=(t=(t||"").match(Fe)||[""]).length;u--;)if(h=g=(s=ct.exec(t[u])||[])[1],p=(s[2]||"").split(".").sort(),h){for(f=Te.event.special[h]||{},d=l[h=(r?f.delegateType:f.bindType)||h]||[],s=s[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),o=i=d.length;i--;)c=d[i],!a&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(d.splice(i,1),c.selector&&d.delegateCount--,f.remove&&f.remove.call(e,c));o&&!d.length&&(f.teardown&&!1!==f.teardown.call(e,p,m.handle)||Te.removeEvent(e,h,m.handle),delete l[h])}else for(h in l)Te.event.remove(e,h+t[u],n,r,!0);Te.isEmptyObject(l)&&Be.remove(e,"handle events")}},dispatch:function(e){var t,n,r,a,i,o,s=Te.event.fix(e),l=new Array(arguments.length),u=(Be.get(this,"events")||{})[s.type]||[],c=Te.event.special[s.type]||{};for(l[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,dt=/\s*$/g;Te.extend({htmlPrefilter:function(e){return e.replace(ft,"<$1>")},clone:function(e,t,n){var r,a,i,o,s=e.cloneNode(!0),l=Ye(e);if(!(be.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||Te.isXMLDoc(e)))for(o=w(s),r=0,a=(i=w(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",a=function(e){r.remove(),a=null,e&&t("error"===e.type?404:200,e.type)}),le.head.appendChild(r[0])},abort:function(){a&&a()}}});var an,on=[],sn=/(=)\?(?=&|$)|\?\?/;Te.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=on.pop()||Te.expando+"_"+Ot++;return this[e]=!0,e}}),Te.ajaxPrefilter("json jsonp",function(e,t,n){var r,a,i,o=!1!==e.jsonp&&(sn.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&sn.test(e.data)&&"data");if(o||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=we(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,o?e[o]=e[o].replace(sn,"$1"+r):!1!==e.jsonp&&(e.url+=(qt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return i||Te.error(r+" was not called"),i[0]},e.dataTypes[0]="json",a=T[r],T[r]=function(){i=arguments},n.always(function(){a===undefined?Te(T).removeProp(r):T[r]=a,e[r]&&(e.jsonpCallback=t.jsonpCallback,on.push(r)),i&&we(a)&&a(i[0]),i=a=undefined}),"script"}),be.createHTMLDocument=((an=le.implementation.createHTMLDocument("").body).innerHTML="
    ",2===an.childNodes.length),Te.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(be.createHTMLDocument?((r=(t=le.implementation.createHTMLDocument("")).createElement("base")).href=le.location.href,t.head.appendChild(r)):t=le),i=!n&&[],(a=Ie.exec(e))?[t.createElement(a[1])]:(a=S([e],t,i),i&&i.length&&Te(i).remove(),Te.merge([],a.childNodes)));var r,a,i},Te.fn.load=function(e,t,n){var r,a,i,o=this,s=e.indexOf(" ");return-1").append(Te.parseHTML(e)).find(r):e)}).always(n&&function(e,t){o.each(function(){n.apply(this,i||[e.responseText,t,e])})}),this},Te.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){Te.fn[t]=function(e){return this.on(t,e)}}),Te.expr.pseudos.animated=function(t){return Te.grep(Te.timers,function(e){return t===e.elem}).length},Te.offset={setOffset:function(e,t,n){var r,a,i,o,s,l,u=Te.css(e,"position"),c=Te(e),f={};"static"===u&&(e.style.position="relative"),s=c.offset(),i=Te.css(e,"top"),l=Te.css(e,"left"),("absolute"===u||"fixed"===u)&&-1<(i+l).indexOf("auto")?(o=(r=c.position()).top,a=r.left):(o=parseFloat(i)||0,a=parseFloat(l)||0),we(t)&&(t=t.call(e,n,Te.extend({},s))),null!=t.top&&(f.top=t.top-s.top+o),null!=t.left&&(f.left=t.left-s.left+a),"using"in t?t.using.call(e,f):c.css(f)}},Te.fn.extend({offset:function(t){if(arguments.length)return t===undefined?this:this.each(function(e){Te.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],a={top:0,left:0};if("fixed"===Te.css(r,"position"))t=r.getBoundingClientRect();else{for(t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;e&&(e===n.body||e===n.documentElement)&&"static"===Te.css(e,"position");)e=e.parentNode;e&&e!==r&&1===e.nodeType&&((a=Te(e).offset()).top+=Te.css(e,"borderTopWidth",!0),a.left+=Te.css(e,"borderLeftWidth",!0))}return{top:t.top-a.top-Te.css(r,"marginTop",!0),left:t.left-a.left-Te.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var e=this.offsetParent;e&&"static"===Te.css(e,"position");)e=e.offsetParent;return e||Je})}}),Te.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,a){var i="pageYOffset"===a;Te.fn[t]=function(e){return Me(this,function(e,t,n){var r;if(xe(e)?r=e:9===e.nodeType&&(r=e.defaultView),n===undefined)return r?r[a]:e[t];r?r.scrollTo(i?r.pageXOffset:n,i?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),Te.each(["top","left"],function(e,n){Te.cssHooks[n]=M(be.pixelPosition,function(e,t){if(t)return t=H(e,n),gt.test(t)?Te(e).position()[n]+"px":t})}),Te.each({Height:"height",Width:"width"},function(o,s){Te.each({padding:"inner"+o,content:s,"":"outer"+o},function(r,i){Te.fn[i]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),a=r||(!0===e||!0===t?"margin":"border");return Me(this,function(e,t,n){var r;return xe(e)?0===i.indexOf("outer")?e["inner"+o]:e.document.documentElement["client"+o]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+o],r["scroll"+o],e.body["offset"+o],r["offset"+o],r["client"+o])):n===undefined?Te.css(e,t,a):Te.style(e,t,n,a)},s,n?e:undefined,n)}})}),Te.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){Te.fn[n]=function(e,t){return 0"}for(var i=0,o="",s=[];e.length||t.length;){var l=r().splice(0,1)[0];if(o+=x(n.substr(i,l.offset-i)),i=l.offset,"start"==l.event)o+=a(l.node),s.push(l.node);else if("stop"==l.event){var u=s.length;do{var c=s[--u];o+=""}while(c!=l.node);for(s.splice(u,1);u'+x(a[0])+""):n+=x(a[0]),r=t.lR.lastIndex,a=t.lR.exec(e)}return n+=x(e.substr(r,e.length-r))}function f(e,t){if(t.sL&&T[t.sL]){var n=D(t.sL,e);return g+=n.keyword_count,n.value}return r(e,t)}function d(e,t){var n=e.cN?'':"";e.rB?(m+=n,e.buffer=""):e.eB?(m+=x(t)+n,e.buffer=""):(m+=n,e.buffer=t),h.push(e),p+=e.r}function i(e,t,n){var r=h[h.length-1];if(n)return m+=f(r.buffer+e,r),!1;var a=l(t,r);if(a)return m+=f(r.buffer+e,r),d(a,t),a.rB;var i=u(h.length-1,t);if(i){var o=r.cN?"":"";for(r.rE?m+=f(r.buffer+e,r)+o:r.eE?m+=f(r.buffer+e,r)+o+x(t):m+=f(r.buffer+e+t,r)+o;1":"",m+=o,i--,h.length--;var s=h[h.length-1];return h.length--,h[h.length-1].buffer="",s.starts&&d(s.starts,""),r.rE}if(c(t,r))throw"Illegal"}var s=T[e],h=[s.dM],p=0,g=0,m="";try{var v=0;s.dM.buffer="";do{var y=n(t,v),b=i(y[0],y[1],y[2]);v+=y[0].length,b||(v+=y[1].length)}while(!y[2]);if(1o.keyword_count+o.r&&(o=l),l.keyword_count+l.r>i.keyword_count+i.r&&(o=i,i=l)}}var u=e.className;u.match(i.language)||(u=u?u+" "+i.language:i.language);var c=g(e);if(c.length)(f=document.createElement("pre")).innerHTML=i.value,i.value=m(c,g(f),r);if(n&&(i.value=i.value.replace(/^((<[^>]+>|\t)+)/gm,function(e,t){return t.replace(/\t/g,n)})),t&&(i.value=i.value.replace(/\n/g,"
    ")),/MSIE [678]/.test(navigator.userAgent)&&"CODE"==e.tagName&&"PRE"==e.parentNode.tagName){var f=e.parentNode,d=document.createElement("div");d.innerHTML="
    "+i.value+"
    ",e=d.firstChild.firstChild,d.firstChild.cN=f.cN,f.parentNode.replaceChild(d.firstChild,f)}else e.innerHTML=i.value;e.className=u,e.dataset={},e.dataset.result={language:i.language,kw:i.keyword_count,re:i.r},o&&o.language&&(e.dataset.second_best={language:o.language,kw:o.keyword_count,re:o.r})}}function i(){if(!i.called){i.called=!0,v();for(var e=document.getElementsByTagName("pre"),t=0;t|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",this.BE={b:"\\\\.",r:0},this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE],r:0},this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE],r:0},this.CLCM={cN:"comment",b:"//",e:"$"},this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"},this.HCM={cN:"comment",b:"#",e:"$"},this.NM={cN:"number",b:this.NR,r:0},this.CNM={cN:"number",b:this.CNR,r:0},this.inherit=function(e,t){var n={};for(var r in e)n[r]=e[r];if(t)for(var r in t)n[r]=t[r];return n}};hljs.LANGUAGES.ruby=function(){var e="[a-zA-Z_][a-zA-Z0-9_]*(\\!|\\?)?",t="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",n={keyword:{and:1,"false":1,then:1,defined:1,module:1,"in":1,"return":1,redo:1,"if":1,BEGIN:1,retry:1,end:1,"for":1,"true":1,self:1,when:1,next:1,until:1,"do":1,begin:1,unless:1,END:1,rescue:1,nil:1,"else":1,"break":1,undef:1,not:1,"super":1,"class":1,"case":1,require:1,"yield":1,alias:1,"while":1,ensure:1,elsif:1,or:1,def:1},keymethods:{__id__:1,__send__:1,abort:1,abs:1,"all?":1,allocate:1,ancestors:1,"any?":1,arity:1,assoc:1,at:1,at_exit:1,autoload:1,"autoload?":1,"between?":1,binding:1,binmode:1,"block_given?":1,call:1,callcc:1,caller:1,capitalize:1,"capitalize!":1,casecmp:1,"catch":1,ceil:1,center:1,chomp:1,"chomp!":1,chop:1,"chop!":1,chr:1,"class":1,class_eval:1,"class_variable_defined?":1,class_variables:1,clear:1,clone:1,close:1,close_read:1,close_write:1,"closed?":1,coerce:1,collect:1,"collect!":1,compact:1,"compact!":1,concat:1,"const_defined?":1,const_get:1,const_missing:1,const_set:1,constants:1,count:1,crypt:1,"default":1,default_proc:1,"delete":1,"delete!":1,delete_at:1,delete_if:1,detect:1,display:1,div:1,divmod:1,downcase:1,"downcase!":1,downto:1,dump:1,dup:1,each:1,each_byte:1,each_index:1,each_key:1,each_line:1,each_pair:1,each_value:1,each_with_index:1,"empty?":1,entries:1,eof:1,"eof?":1,"eql?":1,"equal?":1,eval:1,exec:1,exit:1,"exit!":1,extend:1,fail:1,fcntl:1,fetch:1,fileno:1,fill:1,find:1,find_all:1,first:1,flatten:1,"flatten!":1,floor:1,flush:1,for_fd:1,foreach:1,fork:1,format:1,freeze:1,"frozen?":1,fsync:1,getc:1,gets:1,global_variables:1,grep:1,gsub:1,"gsub!":1,"has_key?":1,"has_value?":1,hash:1,hex:1,id:1,include:1,"include?":1,included_modules:1,index:1,indexes:1,indices:1,induced_from:1,inject:1,insert:1,inspect:1,instance_eval:1,instance_method:1,instance_methods:1,"instance_of?":1,"instance_variable_defined?":1,instance_variable_get:1,instance_variable_set:1,instance_variables:1,"integer?":1,intern:1,invert:1,ioctl:1,"is_a?":1,isatty:1,"iterator?":1,join:1,"key?":1,keys:1,"kind_of?":1,lambda:1,last:1,length:1,lineno:1,ljust:1,load:1,local_variables:1,loop:1,lstrip:1,"lstrip!":1,map:1,"map!":1,match:1,max:1,"member?":1,merge:1,"merge!":1,method:1,"method_defined?":1,method_missing:1,methods:1,min:1,module_eval:1,modulo:1,name:1,nesting:1,"new":1,next:1,"next!":1,"nil?":1,nitems:1,"nonzero?":1,object_id:1,oct:1,open:1,pack:1,partition:1,pid:1,pipe:1,pop:1,popen:1,pos:1,prec:1,prec_f:1,prec_i:1,print:1,printf:1,private_class_method:1,private_instance_methods:1,"private_method_defined?":1,private_methods:1,proc:1,protected_instance_methods:1, -"protected_method_defined?":1,protected_methods:1,public_class_method:1,public_instance_methods:1,"public_method_defined?":1,public_methods:1,push:1,putc:1,puts:1,quo:1,raise:1,rand:1,rassoc:1,read:1,read_nonblock:1,readchar:1,readline:1,readlines:1,readpartial:1,rehash:1,reject:1,"reject!":1,remainder:1,reopen:1,replace:1,require:1,"respond_to?":1,reverse:1,"reverse!":1,reverse_each:1,rewind:1,rindex:1,rjust:1,round:1,rstrip:1,"rstrip!":1,scan:1,seek:1,select:1,send:1,set_trace_func:1,shift:1,singleton_method_added:1,singleton_methods:1,size:1,sleep:1,slice:1,"slice!":1,sort:1,"sort!":1,sort_by:1,split:1,sprintf:1,squeeze:1,"squeeze!":1,srand:1,stat:1,step:1,store:1,strip:1,"strip!":1,sub:1,"sub!":1,succ:1,"succ!":1,sum:1,superclass:1,swapcase:1,"swapcase!":1,sync:1,syscall:1,sysopen:1,sysread:1,sysseek:1,system:1,syswrite:1,taint:1,"tainted?":1,tell:1,test:1,"throw":1,times:1,to_a:1,to_ary:1,to_f:1,to_hash:1,to_i:1,to_int:1,to_io:1,to_proc:1,to_s:1,to_str:1,to_sym:1,tr:1,"tr!":1,tr_s:1,"tr_s!":1,trace_var:1,transpose:1,trap:1,truncate:1,"tty?":1,type:1,ungetc:1,uniq:1,"uniq!":1,unpack:1,unshift:1,untaint:1,untrace_var:1,upcase:1,"upcase!":1,update:1,upto:1,"value?":1,values:1,values_at:1,warn:1,write:1,write_nonblock:1,"zero?":1,zip:1}},r={cN:"yardoctag",b:"@[A-Za-z]+"},a={cN:"comment",b:"#",e:"$",c:[r]},i={cN:"comment",b:"^\\=begin",e:"^\\=end",c:[r],r:10},o={cN:"comment",b:"^__END__",e:"\\n$"},s={cN:"subst",b:"#\\{",e:"}",l:e,k:n},l=[hljs.BE,s],u={cN:"string",b:"'",e:"'",c:l,r:0},c={cN:"string",b:'"',e:'"',c:l,r:0},f={cN:"string",b:"%[qw]?\\(",e:"\\)",c:l,r:10},d={cN:"string",b:"%[qw]?\\[",e:"\\]",c:l,r:10},h={cN:"string",b:"%[qw]?{",e:"}",c:l,r:10},p={cN:"string",b:"%[qw]?<",e:">",c:l,r:10},g={cN:"string",b:"%[qw]?/",e:"/",c:l,r:10},m={cN:"string",b:"%[qw]?%",e:"%",c:l,r:10},v={cN:"string",b:"%[qw]?-",e:"-",c:l,r:10},y={cN:"string",b:"%[qw]?\\|",e:"\\|",c:l,r:10},b={cN:"function",b:"\\bdef\\s+",e:" |$|;",l:e,k:n,c:[{cN:"title",b:t,l:e,k:n},{cN:"params",b:"\\(",e:"\\)",l:e,k:n},a,i,o]},w={cN:"identifier",b:e,l:e,k:n,r:0},x=[a,i,o,u,c,f,d,h,p,g,m,v,y,{cN:"class",b:"\\b(class|module)\\b",e:"$|;",k:{"class":1,module:1},c:[{cN:"title",b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?",r:0},{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+hljs.IR+"::)?"+hljs.IR}]},a,i,o]},b,{cN:"constant",b:"(::)?([A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:":",c:[u,c,f,d,h,p,g,m,v,y,w],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"number",b:"\\?\\w"},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},w,{b:"("+hljs.RSR+")\\s*",c:[a,i,o,{cN:"regexp",b:"/",e:"/[a-z]*",i:"\\n",c:[hljs.BE]}],r:0}];return s.c=x,{dM:{l:e,k:n,c:b.c[1].c=x}}}(),function(c,s,o){function l(e,t,n){var r=s.createElement(e);return t&&(r.id=te+t),n&&(r.style.cssText=n),c(r)}function f(){return o.innerHeight?o.innerHeight:c(o).height()}function u(e,n){n!==Object(n)&&(n={}),this.cache={},this.el=e,this.value=function(e){var t;return this.cache[e]===undefined&&((t=c(this.el).attr("data-cbox-"+e))!==undefined?this.cache[e]=t:n[e]!==undefined?this.cache[e]=n[e]:Q[e]!==undefined&&(this.cache[e]=Q[e])),this.cache[e]},this.get=function(e){var t=this.value(e);return c.isFunction(t)?t.call(this.el,this):t}}function i(e){var t=k.length,n=(X+e)%t;return n<0?t+n:n}function d(e,t){return Math.round((/%/.test(e)?("x"===t?I.width():f())/100:1)*parseInt(e,10))}function h(e,t){return e.get("photo")||e.get("photoRegex").test(t)}function p(e,t){return e.get("retinaUrl")&&1"),w()}}function a(){S||(t=!1,I=c(o),S=l(ce).attr({id:ee,"class":!1===c.support.opacity?te+"IE":"",role:"dialog",tabindex:"-1"}).hide(),x=l(ce,"Overlay").hide(),E=c([l(ce,"LoadingOverlay")[0],l(ce,"LoadingGraphic")[0]]),D=l(ce,"Wrapper"),T=l(ce,"Content").append(R=l(ce,"Title"),F=l(ce,"Current"),M=c('