diff --git a/.codeclimate.yml b/.codeclimate.yml deleted file mode 100644 index 009a4859a0..0000000000 --- a/.codeclimate.yml +++ /dev/null @@ -1,21 +0,0 @@ -languages: - Ruby: true - JavaScript: true - Python: false - PHP: false - -exclude_paths: - - "app/assets/javascripts/defer/*" - - "app/assets/javascripts/ember-addons/*" - - "lib/autospec/*" - - "lib/es6_module_transpiler/*" - - "lib/highlight_js/*" - - "lib/import/*" - - "lib/javascripts/*" - - "lib/tasks/*" - - "lib/*.js" - - "public/*" - - "script/*" - - "spec/*" - - "test/*" - - "vendor/*" diff --git a/.env.sample b/.env.sample deleted file mode 100644 index f96d901f09..0000000000 --- a/.env.sample +++ /dev/null @@ -1 +0,0 @@ -RAILS_ENV=development \ No newline at end of file diff --git a/.eslintrc b/.eslintrc index a656e80d38..d7350cda7f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,96 +1,7 @@ { - "env": { - "browser": true, - "builtin": true, - "es6": true, - "jasmine": true, - "mocha": true, - "node": true - }, - "parserOptions": { - "ecmaVersion": 7, - "sourceType": "module" - }, - "globals": { - "$": true, - "_": true, - "andThen": true, - "asyncRender": true, - "Blob": true, - "bootbox": true, - "click": true, - "waitUntil": true, - "getSettledState": true, - "count": true, - "currentPath": true, - "currentRouteName": true, - "currentURL": true, - "define": true, - "Discourse": true, - "Ember": true, - "exists": true, - "File": true, - "fillIn": true, - "find": true, - "Handlebars": true, - "hasModule": true, - "I18n": true, - "invisible": true, - "jQuery": true, - "keyboardHelper": true, - "keyEvent": true, - "moduleFor": true, - "moduleForComponent": true, - "moment": true, - "Pretender": true, - "QUnit": true, - "require": true, - "requirejs": true, - "RSVP": true, - "sandbox": true, - "sinon": true, - "test": true, - "triggerEvent": true, - "visible": true, - "visit": true, - "pauseTest": true - }, + "extends": "eslint-config-discourse", + "plugins": ["discourse-ember"], "rules": { - "block-scoped-var": 2, - "dot-notation": 0, - "eqeqeq": [2, "allow-null"], - "guard-for-in": 2, - "no-alert": 2, - "no-bitwise": 2, - "no-caller": 2, - "no-cond-assign": 0, - "no-console": 2, - "no-debugger": 2, - "no-empty": 0, - "no-eval": 2, - "no-extend-native": 2, - "no-extra-parens": 0, - "no-inner-declarations": 2, - "no-irregular-whitespace": 2, - "no-iterator": 2, - "no-loop-func": 2, - "no-mixed-spaces-and-tabs": 2, - "no-multi-str": 2, - "no-new": 2, - "no-plusplus": 0, - "no-proto": 2, - "no-script-url": 2, - "no-sequences": 2, - "no-shadow": 2, - "no-this-before-super": 2, - "no-trailing-spaces": 2, - "no-undef": 2, - "no-unused-vars": 2, - "no-with": 2, - "semi": 2, - "strict": 0, - "valid-typeof": 2, - "wrap-iife": [2, "inside"] - }, - "parser": "babel-eslint" + "discourse-ember/global-ember": 2 + } } diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..f1b56462f2 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,9 @@ +# Only add no-op commits to this file +# To prevent these commits to show in git blame +# git config blame.ignoreRevsFile .git-blame-ignore-revs + +# DEV: introduces prettier for es6 files +03a7d532cf8f09b12573b21ef013c21100d52728 + +# DEV: enforces no self-closing-void-elements +dafd3c3b47f116c6c1dc56cb18df614c11747733 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..c02b8d1947 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,158 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + branches-ignore: + - 'tests-passed' + +jobs: + build: + name: "${{ matrix.target }}-${{ matrix.build_types }}" + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + + env: + DISCOURSE_HOSTNAME: www.example.com + RUBY_GLOBAL_METHOD_CACHE_SIZE: 131072 + BUILD_TYPE: ${{ matrix.build_types }} + TARGET: ${{ matrix.target }} + RAILS_ENV: test + PGHOST: localhost + PGUSER: discourse + PGPASSWORD: discourse + + strategy: + fail-fast: false + + matrix: + build_types: [ 'BACKEND', 'FRONTEND', 'LINT' ] + target: [ 'PLUGINS', 'CORE' ] + os: [ ubuntu-latest ] + ruby: [ '2.6' ] + postgres: [ '10' ] + redis: [ '4.x' ] + + services: + postgres: + image: postgres:${{ matrix.postgres }} + ports: + - 5432:5432 + env: + POSTGRES_USER: discourse + POSTGRES_PASSWORD: discourse + POSTGRES_DB: discourse_test + 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@master + with: + fetch-depth: 1 + + - name: Setup Git + run: git config --global user.email "ci@ci.invalid" && git config --global user.name "Discourse CI" + + - name: Setup packages + if: env.BUILD_TYPE != 'LINT' + run: | + sudo apt-get -yqq install postgresql-client libpq-dev gifsicle jpegoptim optipng jhead && \ + wget -qO- https://raw.githubusercontent.com/discourse/discourse_docker/master/image/base/install-pngquant | sudo sh + + - name: Setup redis + uses: shogo82148/actions-setup-redis@v1 + if: env.BUILD_TYPE != 'LINT' + with: + redis-version: ${{ matrix.redis }} + + - name: Setup ruby + uses: actions/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + architecture: 'x64' + + - name: Setup bundler + run: gem install bundler -v 2.1.1 --no-doc + + - name: Bundler cache + uses: actions/cache@v1 + id: bundler-cache + with: + path: vendor/bundle + key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gem- + + - name: Setup gems + run: bundle install --without development --deployment --jobs 4 --retry 3 + + - name: Get yarn cache directory + id: yarn-cache-dir + run: echo "::set-output name=dir::$(yarn cache dir)" + + - name: Yarn cache + uses: actions/cache@v1 + 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 --dev + + - name: "Checkout official plugins" + if: env.TARGET == 'PLUGINS' + run: bin/rake plugin:install_all_official + + - name: Create database + if: env.BUILD_TYPE != 'LINT' + run: bin/rake db:create && bin/rake db:migrate + + - name: Create parallel databases + if: env.BUILD_TYPE == 'BACKEND' && env.TARGET == 'CORE' + run: bin/rake parallel:create && bin/rake parallel:migrate + + - name: Rubocop + if: env.BUILD_TYPE == 'LINT' + run: bundle exec rubocop . + + - name: ESLint + if: env.BUILD_TYPE == 'LINT' + run: yarn eslint app/assets/javascripts test/javascripts && yarn eslint --ext .es6 app/assets/javascripts test/javascripts plugins + + - name: Prettier + if: env.BUILD_TYPE == 'LINT' + run: | + yarn prettier -v + yarn prettier --list-different "app/assets/stylesheets/**/*.scss" "app/assets/javascripts/**/*.es6" "test/javascripts/**/*.es6" "plugins/**/*.scss" "plugins/**/*.es6" + + - name: Core RSpec + if: env.BUILD_TYPE == 'BACKEND' && env.TARGET == 'CORE' + run: bin/turbo_rspec && bin/rake plugin:spec + + - name: Plugin RSpec + if: env.BUILD_TYPE == 'BACKEND' && env.TARGET == 'PLUGINS' + run: bin/rake plugin:spec + + - name: Core QUnit + if: env.BUILD_TYPE == 'FRONTEND' && env.TARGET == 'CORE' + run: bundle exec rake qunit:test['1200000'] + timeout-minutes: 30 + + - name: Wizard QUnit + if: env.BUILD_TYPE == 'FRONTEND' && env.TARGET == 'CORE' + run: bundle exec rake qunit:test['1200000','/wizard/qunit'] + timeout-minutes: 30 + + - name: Plugin QUnit # Tests core plugins in TARGET=CORE, and all plugins in TARGET=PLUGINS + if: env.BUILD_TYPE == 'FRONTEND' + run: bundle exec rake plugin:qunit + timeout-minutes: 30 diff --git a/.gitignore b/.gitignore index 3db0e4399a..59ad3c0b6f 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ config/discourse.conf # Ignore the default SQLite database and db dumps *.sql *.sql.gz +!/spec/fixtures/**/*.sql /db/*.sqlite3 /db/structure.sql /db/schema.rb @@ -46,14 +47,14 @@ bootsnap-compile-cache/ # Ignore plugins except for the bundled ones. /plugins/* -!/plugins/lazyYT/ +!/plugins/lazy-yt/ !/plugins/poll/ !/plugins/discourse-details/ !/plugins/discourse-nginx-performance-report !/plugins/discourse-narrative-bot !/plugins/discourse-presence !/plugins/discourse-local-dates -!/public/plugins/discourse-internet-explorer +!/plugins/discourse-internet-explorer /plugins/*/auto_generated/ /spec/fixtures/plugins/my_plugin/auto_generated @@ -128,3 +129,6 @@ vendor/bundle/* # Vagrant .vagrant + +# ignore auto-generated plugin js assets +/app/assets/javascripts/plugins/* diff --git a/.overcommit.yml b/.overcommit.yml deleted file mode 100644 index 8a48ff500e..0000000000 --- a/.overcommit.yml +++ /dev/null @@ -1,45 +0,0 @@ -# Use this file to configure the Overcommit hooks you wish to use. This will -# extend the default configuration defined in: -# https://github.com/brigade/overcommit/blob/master/config/default.yml -# -# At the topmost level of this YAML file is a key representing type of hook -# being run (e.g. pre-commit, commit-msg, etc.). Within each type you can -# customize each hook, such as whether to only run it on certain files (via -# `include`), whether to only display output if it fails (via `quiet`), etc. -# -# For a complete list of hooks, see: -# https://github.com/brigade/overcommit/tree/master/lib/overcommit/hook -# -# For a complete list of options that you can use to customize hooks, see: -# https://github.com/brigade/overcommit#configuration - -PreCommit: - RuboCop: - enabled: true - command: ['bundle', 'exec', 'rubocop'] - EsLint: - enabled: true - required_executable: './node_modules/.bin/eslint' - install_command: 'yarn install' - command: ['yarn', 'eslint', '--ext', '.es6', '-f', 'compact'] - include: '**/*.es6' - YamlSyntax: - enabled: true - -PostCheckout: - BundleInstall: - enabled: true - YarnInstall: - enabled: true - -PostMerge: - BundleInstall: - enabled: true - YarnInstall: - enabled: true - -PostRewrite: - BundleInstall: - enabled: true - YarnInstall: - enabled: true diff --git a/.pkgr.yml b/.pkgr.yml deleted file mode 100644 index 39a5f66dde..0000000000 --- a/.pkgr.yml +++ /dev/null @@ -1 +0,0 @@ -before_precompile: ./packaging/debian/setup.sh diff --git a/.rubocop.yml b/.rubocop.yml index 79ab0f14d1..1e682c1045 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,13 +1,16 @@ +require: + - rubocop-discourse + AllCops: TargetRubyVersion: 2.4 DisabledByDefault: true Exclude: - - 'db/schema.rb' - - 'bundle/**/*' - - 'vendor/**/*' - - 'node_modules/**/*' - - 'public/**/*' - - 'plugins/**/*' + - "db/schema.rb" + - "bundle/**/*" + - "vendor/**/*" + - "node_modules/**/*" + - "public/**/*" + - "plugins/**/gems/**/*" # Prefer &&/|| over and/or. Style/AndOr: @@ -16,11 +19,6 @@ Style/AndOr: Style/FrozenStringLiteralComment: Enabled: true -# Do not use braces for hash literals when they are the last argument of a -# method call. -Style/BracesAroundHashParameters: - Enabled: true - # Align `when` with `case`. Layout/CaseIndentation: Enabled: true @@ -57,7 +55,7 @@ Layout/SpaceAroundOperators: Enabled: true Layout/SpaceBeforeFirstArg: - Enabled: true + Enabled: true # Defining a method with parameters needs parentheses. Style/MethodDefParentheses: @@ -83,7 +81,7 @@ Layout/Tab: Enabled: true # Blank lines should not have any spaces. -Layout/TrailingBlankLines: +Layout/TrailingEmptyLines: Enabled: true # No trailing whitespace. @@ -113,7 +111,7 @@ Layout/MultilineMethodCallIndentation: Enabled: true EnforcedStyle: indented -Layout/AlignHash: +Layout/HashAlignment: Enabled: true Bundler/OrderedGems: @@ -126,6 +124,18 @@ Style/Semicolon: Enabled: true AllowAsExpressionSeparator: true +Style/RedundantReturn: + Enabled: true + +DiscourseCops/NoChdir: + Enabled: true + Exclude: + - 'spec/**/*' # Specs are run sequentially, so chdir can be used + - 'plugins/*/spec/**/*' + +DiscourseCops/NoURIEscapeEncode: + Enabled: true + Style/GlobalVars: Enabled: true Severity: warning diff --git a/.ruby-version.sample b/.ruby-version.sample index 6a6a3d8e35..57cf282ebb 100644 --- a/.ruby-version.sample +++ b/.ruby-version.sample @@ -1 +1 @@ -2.6.1 +2.6.5 diff --git a/.template-lintrc.js b/.template-lintrc.js new file mode 100644 index 0000000000..5731778fc0 --- /dev/null +++ b/.template-lintrc.js @@ -0,0 +1,11 @@ +module.exports = { + // extends: "recommended", + ignore: ["**/*.raw"], + + rules: { + "self-closing-void-elements": true, + "table-groups": true, + "style-concatenation": true, + "no-invalid-interactive": true + } +}; diff --git a/.travis.yml b/.travis.yml index f8bc50b501..7119b6cd2e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,7 @@ env: addons: chrome: stable - postgresql: 9.6 + postgresql: "9.6" apt: update: true packages: @@ -40,10 +40,9 @@ services: - redis-server sudo: required -dist: trusty +dist: xenial cache: - apt: true yarn: true directories: - vendor/bundle @@ -60,7 +59,7 @@ before_install: - git clone --depth=1 https://github.com/discourse/discourse-chat-integration.git plugins/discourse-chat-integration - git clone --depth=1 https://github.com/discourse/discourse-assign.git plugins/discourse-assign - git clone --depth=1 https://github.com/discourse/discourse-patreon.git plugins/discourse-patreon - - git clone --depth=1 https://github.com/discourse/discourse-staff-notes.git plugins/discourse-staff-notes + - git clone --depth=1 https://github.com/discourse/discourse-user-notes.git plugins/discourse-user-notes - git clone --depth=1 https://github.com/discourse/discourse-group-tracker - export PATH=$HOME/.yarn/bin:$PATH @@ -74,13 +73,7 @@ script: - | bash -c " if [ '$RUN_LINT' == '1' ]; then - bundle exec rubocop --parallel && \ - yarn prettier --list-different "app/assets/stylesheets/**/*.scss" "app/assets/javascripts/**/*.es6" "test/javascripts/**/*.es6" - yarn eslint --ext .es6 app/assets/javascripts && \ - yarn eslint --ext .es6 test/javascripts && \ - yarn eslint --ext .es6 plugins/**/assets/javascripts && \ - yarn eslint --ext .es6 plugins/**/test/javascripts && \ - yarn eslint app/assets/javascripts test/javascripts + npx lefthook run lints else if [ '$QUNIT_RUN' == '1' ]; then bundle exec rake qunit:test['1200000'] && \ diff --git a/Gemfile b/Gemfile index 160f77f562..ced7f0376e 100644 --- a/Gemfile +++ b/Gemfile @@ -14,45 +14,60 @@ if rails_master? gem 'arel', git: 'https://github.com/rails/arel.git' gem 'rails', git: 'https://github.com/rails/rails.git' else - # until rubygems gives us optional dependencies we are stuck with this - # bundle update actionmailer actionpack actionview activemodel activerecord activesupport railties - gem 'actionmailer', '5.2.3' - gem 'actionpack', '5.2.3' - gem 'actionview', '5.2.3' - gem 'activemodel', '5.2.3' - gem 'activerecord', '5.2.3' - gem 'activesupport', '5.2.3' - gem 'railties', '5.2.3' + # NOTE: Until rubygems gives us optional dependencies we are stuck with this needing to be explicit + # this allows us to include the bits of rails we use without pieces we do not. + # + # To issue a rails update bump the version number here + gem 'actionmailer', '6.0.1' + gem 'actionpack', '6.0.1' + gem 'actionview', '6.0.1' + gem 'activemodel', '6.0.1' + gem 'activerecord', '6.0.1' + gem 'activesupport', '6.0.1' + gem 'railties', '6.0.1' gem 'sprockets-rails' end +# TODO: At the moment Discourse does not work with Sprockets 4, we would need to correct internals +# This is a desired upgrade we should get to. +gem 'sprockets', '3.7.2' + +# this will eventually be added to rails, +# allows us to precompile all our templates in the unicorn master +gem 'actionview_precompiler', require: false + gem 'seed-fu' gem 'mail', require: false gem 'mini_mime' gem 'mini_suffix' -gem 'hiredis' +gem 'redis' -# holding off redis upgrade temporarily as it is having issues with our current -# freedom patch, we will follow this up. -# -# FrozenError: can't modify frozen Hash -# /var/www/discourse/vendor/bundle/ruby/2.5.0/gems/redis-4.1.0/lib/redis/client.rb:93:in `delete' -# /var/www/discourse/vendor/bundle/ruby/2.5.0/gems/redis-4.1.0/lib/redis/client.rb:93:in `initialize' -# /var/www/discourse/lib/freedom_patches/redis.rb:7:in `initialize' -gem 'redis', '4.0.1', require: ["redis", "redis/connection/hiredis"] +# This is explicitly used by Sidekiq and is an optional dependency. +# We tell Sidekiq to use the namespace "sidekiq" which triggers this +# gem to be used. There is no explicit dependency in sidekiq cause +# redis namespace support is optional +# We already namespace stuff in DiscourseRedis, so we should consider +# just using a single implementation in core vs having 2 namespace implementations gem 'redis-namespace' +# NOTE: AM serializer gets a lot slower with recent updates +# we used an old branch which is the fastest one out there +# are long term goal here is to fork this gem so we have a +# better maintained living fork gem 'active_model_serializers', '~> 0.8.3' -gem 'onebox', '1.8.92' +gem 'onebox' -gem 'http_accept_language', '~>2.0.5', require: false +gem 'http_accept_language', require: false +# Ember related gems need to be pinned cause they control client side +# behavior, we will push these versions up when upgrading ember gem 'ember-rails', '0.18.5' -gem 'discourse-ember-source', '~> 3.8.0' +gem 'discourse-ember-source', '~> 3.12.2' gem 'ember-handlebars-template', '0.8.0' + gem 'barber' gem 'message_bus' @@ -71,17 +86,17 @@ gem 'aws-sdk-sns', require: false gem 'excon', require: false gem 'unf', require: false -gem 'email_reply_trimmer', '~> 0.1' +gem 'email_reply_trimmer' # Forked until https://github.com/toy/image_optim/pull/162 is merged +# https://github.com/discourse/image_optim gem 'discourse_image_optim', require: 'image_optim' gem 'multi_json' gem 'mustache' gem 'nokogiri' +gem 'css_parser', require: false gem 'omniauth' -gem 'omniauth-openid' -gem 'openid-redis-store' gem 'omniauth-facebook' gem 'omniauth-twitter' gem 'omniauth-instagram' @@ -95,7 +110,7 @@ gem 'oj' gem 'pg' gem 'mini_sql' gem 'pry-rails', require: false -gem 'r2', '~> 0.2.5', require: false +gem 'r2', require: false gem 'rake' gem 'thor', require: false @@ -110,8 +125,20 @@ gem 'tilt', require: false gem 'execjs', require: false gem 'mini_racer' + +# TODO: determine why highline is being held back and upgrade to latest gem 'highline', '~> 1.7.0', require: false + +# TODO: Upgrading breaks Sidekiq Web +# This is a bit of a hornets nest cause in an ideal world we much prefer +# if Sidekiq reused session and CSRF mitigation with Discourse on the +# _forum_session cookie instead of a rack.session cookie +gem 'rack', '2.0.8' + gem 'rack-protection' # security +gem 'cbor', require: false +gem 'cose', require: false +gem 'addressable' # Gems used only for assets and not required in production environments by default. # Allow everywhere for now cause we are allowing asset debugging in production @@ -122,7 +149,7 @@ end group :test do gem 'webmock', require: false - gem 'fakeweb', '~> 1.3.0', require: false + gem 'fakeweb', require: false gem 'minitest', require: false gem 'simplecov', require: false gem "test-prof" @@ -133,32 +160,37 @@ group :test, :development do gem 'mock_redis' gem 'listen', require: false gem 'certified', require: false - # later appears to break Fabricate(:topic, category: category) gem 'fabrication', require: false - gem 'mocha', require: false + + # TODO: upgrading to 1.10.1 cause it breaks our test suite. + # We want our test suite fixed though to support this upgrade. + gem 'mocha', '1.8.0', require: false + gem 'rb-fsevent', require: RUBY_PLATFORM =~ /darwin/i ? 'rb-fsevent' : false + + # TODO determine if we can update this to 0.10, API changes happened + # we would like to upgrade it if possible gem 'rb-inotify', '~> 0.9', require: RUBY_PLATFORM =~ /linux/i ? 'rb-inotify' : false - gem 'rspec-rails', require: false - gem 'shoulda-matchers', '~> 3.1', '>= 3.1.3', require: false + + # TODO once 4.0.0 is released upgrade to it, at time of writing 3.9.0 is latest + gem 'rspec-rails', '4.0.0.beta2', require: false + + gem 'shoulda-matchers', require: false gem 'rspec-html-matchers' gem 'pry-nav' - gem 'byebug', require: ENV['RM_INFO'].nil? + gem 'byebug', require: ENV['RM_INFO'].nil?, platform: :mri gem 'rubocop', require: false + gem "rubocop-discourse", require: false gem 'parallel_tests' end group :development do - gem 'ruby-prof', require: false + gem 'ruby-prof', require: false, platform: :mri gem 'bullet', require: !!ENV['BULLET'] - gem 'better_errors' + gem 'better_errors', platform: :mri gem 'binding_of_caller' - - # waiting on 2.7.5 per: https://github.com/ctran/annotate_models/pull/595 - if rails_master? - gem 'annotate', git: 'https://github.com/ctran/annotate_models.git' - else - gem 'annotate' - end + gem 'yaml-lint' + gem 'annotate' end # this is an optional gem, it provides a high performance replacement @@ -196,24 +228,31 @@ gem 'logstash-event', require: false gem 'logstash-logger', require: false gem 'logster' -gem 'sassc', require: false +# NOTE: later versions of sassc are causing a segfault, possibly dependent on processer architecture +# and until resolved should be locked at 2.0.1 +gem 'sassc', '2.0.1', require: false gem "sassc-rails" -gem 'rotp' +gem 'rotp', require: false gem 'rqrcode' +gem 'rubyzip', require: false + gem 'sshkey', require: false gem 'rchardet', require: false +gem 'lz4-ruby', require: false, platform: :mri if ENV["IMPORT"] == "1" gem 'mysql2' gem 'redcarpet' + + # NOTE: in import mode the version of sqlite can matter a lot, so we stick it to a specific one gem 'sqlite3', '~> 1.3', '>= 1.3.13' gem 'ruby-bbcode-to-md', git: 'https://github.com/nlalonde/ruby-bbcode-to-md' gem 'reverse_markdown' gem 'tiny_tds' - gem 'csv', '~> 3.0' + gem 'csv' end gem 'webpush', require: false diff --git a/Gemfile.lock b/Gemfile.lock index ec2407907e..013a17c384 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,64 +1,65 @@ GEM remote: https://rubygems.org/ specs: - actionmailer (5.2.3) - actionpack (= 5.2.3) - actionview (= 5.2.3) - activejob (= 5.2.3) + actionmailer (6.0.1) + actionpack (= 6.0.1) + actionview (= 6.0.1) + activejob (= 6.0.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.3) - actionview (= 5.2.3) - activesupport (= 5.2.3) + actionpack (6.0.1) + actionview (= 6.0.1) + activesupport (= 6.0.1) rack (~> 2.0) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.3) - activesupport (= 5.2.3) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actionview (6.0.1) + activesupport (= 6.0.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.3) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + actionview_precompiler (0.2.2) + actionview (>= 6.0.a) active_model_serializers (0.8.4) activemodel (>= 3.0) - activejob (5.2.3) - activesupport (= 5.2.3) + activejob (6.0.1) + activesupport (= 6.0.1) globalid (>= 0.3.6) - activemodel (5.2.3) - activesupport (= 5.2.3) - activerecord (5.2.3) - activemodel (= 5.2.3) - activesupport (= 5.2.3) - arel (>= 9.0) - activesupport (5.2.3) + activemodel (6.0.1) + activesupport (= 6.0.1) + activerecord (6.0.1) + activemodel (= 6.0.1) + activesupport (= 6.0.1) + activesupport (6.0.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) - addressable (2.5.2) - public_suffix (>= 2.0.2, < 4.0) - annotate (2.7.5) + zeitwerk (~> 2.2) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + annotate (3.1.0) activerecord (>= 3.2, < 7.0) - rake (>= 10.4, < 13.0) - arel (9.0.0) + rake (>= 10.4, < 14.0) ast (2.4.0) aws-eventstream (1.0.3) - aws-partitions (1.154.0) - aws-sdk-core (3.48.6) + aws-partitions (1.272.0) + aws-sdk-core (3.89.1) aws-eventstream (~> 1.0, >= 1.0.2) - aws-partitions (~> 1.0) + aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.17.0) - aws-sdk-core (~> 3, >= 3.48.2) + aws-sdk-kms (1.29.0) + aws-sdk-core (~> 3, >= 3.71.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.36.1) - aws-sdk-core (~> 3, >= 3.48.2) + aws-sdk-s3 (1.60.2) + aws-sdk-core (~> 3, >= 3.83.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.0) - aws-sdk-sns (1.13.0) - aws-sdk-core (~> 3, >= 3.48.2) + aws-sigv4 (~> 1.1) + aws-sdk-sns (1.21.0) + aws-sdk-core (~> 3, >= 3.71.0) aws-sigv4 (~> 1.1) aws-sigv4 (1.1.0) aws-eventstream (~> 1.0, >= 1.0.2) @@ -71,34 +72,40 @@ GEM rack (>= 0.9.0) binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) - bootsnap (1.4.4) + bootsnap (1.4.6) msgpack (~> 1.0) - builder (3.2.3) - bullet (6.0.0) + builder (3.2.4) + bullet (6.1.0) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) - byebug (11.0.1) + byebug (11.1.1) + cbor (0.5.9.6) certified (1.0.0) chunky_png (1.3.11) coderay (1.1.2) colored2 (3.1.2) - concurrent-ruby (1.1.5) + concurrent-ruby (1.1.6) connection_pool (2.2.2) + cose (0.11.0) + cbor (~> 0.5.9) + openssl-signature_algorithm (~> 0.3.0) cppjieba_rb (0.3.3) crack (0.4.3) safe_yaml (~> 1.0.0) - crass (1.0.4) + crass (1.0.6) + css_parser (1.7.1) + addressable debug_inspector (0.0.3) diff-lcs (1.3) diffy (3.3.0) - discourse-ember-source (3.8.0.1) + discourse-ember-source (3.12.2.0) discourse_image_optim (0.26.2) exifr (~> 1.2, >= 1.2.2) fspath (~> 3.0) image_size (~> 1.5) in_threads (~> 1.3) progress (~> 3.0, >= 3.0.1) - docile (1.3.1) + docile (1.3.2) email_reply_trimmer (0.1.12) ember-data-source (3.0.2) ember-source (>= 2, < 3.0) @@ -113,53 +120,50 @@ GEM jquery-rails (>= 1.0.17) railties (>= 3.1) ember-source (2.18.2) - erubi (1.8.0) - excon (0.64.0) + erubi (1.9.0) + excon (0.72.0) execjs (2.7.0) exifr (1.3.6) - fabrication (2.20.1) + fabrication (2.21.0) fakeweb (1.3.0) - faraday (0.15.4) + faraday (0.17.3) multipart-post (>= 1.2, < 3) fast_blank (1.0.0) fast_xor (1.1.3) rake rake-compiler fast_xs (0.8.0) - fastimage (2.1.5) - ffi (1.10.0) + fastimage (2.1.7) + ffi (1.12.2) flamegraph (0.9.5) - fspath (3.1.0) + fspath (3.1.2) gc_tracer (1.5.1) globalid (0.4.2) activesupport (>= 4.2.0) guess_html_encoding (0.0.11) - hashdiff (0.3.9) + hashdiff (1.0.0) hashie (3.6.0) highline (1.7.10) - hiredis (0.6.3) hkdf (0.3.0) htmlentities (4.3.4) - http_accept_language (2.0.5) - i18n (1.6.0) + http_accept_language (2.1.1) + i18n (1.8.2) concurrent-ruby (~> 1.0) image_size (1.5.0) - in_threads (1.5.1) - jaro_winkler (1.5.2) + in_threads (1.5.4) + jaro_winkler (1.5.4) jmespath (1.4.0) - jquery-rails (4.3.3) + jquery-rails (4.3.5) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.2.0) jwt (2.2.1) - kgio (2.11.2) + kgio (2.11.3) libv8 (7.3.492.27.1) - listen (3.1.5) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) - lograge (0.11.0) + listen (3.2.1) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + lograge (0.11.2) actionpack (>= 4) activesupport (>= 4) railties (>= 4) @@ -167,62 +171,63 @@ GEM logstash-event (1.2.02) logstash-logger (0.26.1) logstash-event (~> 1.2) - logster (2.3.2) - loofah (2.2.3) + logster (2.6.3) + loofah (2.4.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) lru_redux (1.1.0) + lz4-ruby (0.3.3) mail (2.7.1) mini_mime (>= 0.1.1) maxminddb (0.1.22) - memory_profiler (0.9.13) - message_bus (2.2.0) + memory_profiler (0.9.14) + message_bus (2.2.3) rack (>= 1.1.3) metaclass (0.0.4) method_source (0.9.2) - mini_mime (1.0.1) + mini_mime (1.0.2) mini_portile2 (2.4.0) - mini_racer (0.2.6) + mini_racer (0.2.9) libv8 (>= 6.9.411) - mini_scheduler (0.11.0) + mini_scheduler (0.12.2) sidekiq - mini_sql (0.2.2) + mini_sql (0.2.4) mini_suffix (0.3.0) ffi (~> 1.9) - minitest (5.11.3) + minitest (5.14.0) mocha (1.8.0) metaclass (~> 0.0.1) - mock_redis (0.19.0) - moneta (1.1.1) - msgpack (1.2.10) - multi_json (1.13.1) + mock_redis (0.22.0) + msgpack (1.3.3) + multi_json (1.14.1) multi_xml (0.6.0) multipart-post (2.1.1) - mustache (1.1.0) - nokogiri (1.10.3) + mustache (1.1.1) + nio4r (2.5.2) + nokogiri (1.10.8) mini_portile2 (~> 2.4.0) - nokogumbo (2.0.1) + nokogumbo (2.0.2) nokogiri (~> 1.8, >= 1.8.4) oauth (0.5.4) - oauth2 (1.4.1) - faraday (>= 0.8, < 0.16.0) + oauth2 (1.4.2) + faraday (>= 0.8, < 2.0) jwt (>= 1.0, < 3.0) multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) - oj (3.7.12) + oj (3.10.2) omniauth (1.9.0) hashie (>= 3.4.6, < 3.7.0) rack (>= 1.6.2, < 3) - omniauth-facebook (5.0.0) + omniauth-facebook (6.0.0) omniauth-oauth2 (~> 1.2) omniauth-github (1.4.0) omniauth (~> 1.5) omniauth-oauth2 (>= 1.4.0, < 2.0) - omniauth-google-oauth2 (0.7.0) + omniauth-google-oauth2 (0.8.0) jwt (>= 2.0) omniauth (>= 1.1.1) - omniauth-oauth2 (>= 1.5) + omniauth-oauth2 (>= 1.6) omniauth-instagram (1.3.0) omniauth (~> 1) omniauth-oauth2 (~> 1) @@ -232,30 +237,25 @@ GEM omniauth-oauth2 (1.6.0) oauth2 (~> 1.1) omniauth (~> 1.9) - omniauth-openid (1.0.1) - omniauth (~> 1.0) - rack-openid (~> 1.3.1) omniauth-twitter (1.4.0) omniauth-oauth (~> 1.1) rack - onebox (1.8.92) + onebox (1.9.26) + addressable (~> 2.7.0) htmlentities (~> 4.3) - moneta (~> 1.0) multi_json (~> 1.11) mustache nokogiri (~> 1.7) sanitize - openid-redis-store (0.0.2) - redis - ruby-openid + openssl-signature_algorithm (0.3.0) optimist (3.0.0) - parallel (1.17.0) - parallel_tests (2.28.0) + parallel (1.19.1) + parallel_tests (2.31.0) parallel - parser (2.6.3.0) + parser (2.7.0.2) ast (~> 2.4.0) - pg (1.1.4) - progress (3.5.0) + pg (1.2.2) + progress (3.5.2) pry (0.12.2) coderay (~> 1.1.0) method_source (~> 0.9.0) @@ -263,103 +263,107 @@ GEM pry (>= 0.9.10, < 0.13.0) pry-rails (0.3.9) pry (>= 0.10.4) - public_suffix (3.0.3) - puma (3.12.1) + public_suffix (4.0.3) + puma (4.3.1) + nio4r (~> 2.0) r2 (0.2.7) - rack (2.0.7) - rack-mini-profiler (1.1.4) + rack (2.0.8) + rack-mini-profiler (1.1.6) rack (>= 1.2.0) - rack-openid (1.3.1) - rack (>= 1.1.0) - ruby-openid (>= 2.1.8) - rack-protection (2.0.5) + rack-protection (2.0.8.1) rack rack-test (1.1.0) rack (>= 1.0, < 3) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.0.4) - loofah (~> 2.2, >= 2.2.2) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) rails_multisite (2.0.7) activerecord (> 4.2, < 7) railties (> 4.2, < 7) - railties (5.2.3) - actionpack (= 5.2.3) - activesupport (= 5.2.3) + railties (6.0.1) + actionpack (= 6.0.1) + activesupport (= 6.0.1) method_source rake (>= 0.8.7) - thor (>= 0.19.0, < 2.0) + thor (>= 0.20.3, < 2.0) rainbow (3.0.0) - raindrops (0.19.0) - rake (12.3.2) - rake-compiler (1.0.7) + raindrops (0.19.1) + rake (13.0.1) + rake-compiler (1.1.0) rake rb-fsevent (0.10.3) - rb-inotify (0.10.0) + rb-inotify (0.10.1) ffi (~> 1.0) rbtrace (0.4.11) ffi (>= 1.0.6) msgpack (>= 0.4.3) optimist (>= 3.0.0) rchardet (1.8.0) - redis (4.0.1) - redis-namespace (1.6.0) + redis (4.1.3) + redis-namespace (1.7.0) redis (>= 3.0.4) - request_store (1.4.1) + request_store (1.5.0) rack (>= 1.4) + rexml (3.2.4) rinku (2.0.6) - rotp (3.3.1) - rqrcode (0.10.1) + rotp (5.1.0) + addressable (~> 2.5) + rqrcode (1.1.2) chunky_png (~> 1.0) - rspec (3.8.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) - rspec-core (3.8.0) - rspec-support (~> 3.8.0) - rspec-expectations (3.8.3) + rqrcode_core (~> 0.1) + rqrcode_core (0.1.1) + rspec (3.9.0) + rspec-core (~> 3.9.0) + rspec-expectations (~> 3.9.0) + rspec-mocks (~> 3.9.0) + rspec-core (3.9.1) + rspec-support (~> 3.9.1) + rspec-expectations (3.9.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-html-matchers (0.9.1) + rspec-support (~> 3.9.0) + rspec-html-matchers (0.9.2) nokogiri (~> 1) rspec (>= 3.0.0.a, < 4) - rspec-mocks (3.8.0) + rspec-mocks (3.9.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-rails (3.8.2) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) - rspec-support (~> 3.8.0) - rspec-support (3.8.0) + rspec-support (~> 3.9.0) + rspec-rails (4.0.0.beta2) + actionpack (>= 4.2) + activesupport (>= 4.2) + railties (>= 4.2) + rspec-core (~> 3.8) + rspec-expectations (~> 3.8) + rspec-mocks (~> 3.8) + rspec-support (~> 3.8) + rspec-support (3.9.2) rtlit (0.0.5) - rubocop (0.69.0) + rubocop (0.80.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) - parser (>= 2.6) + parser (>= 2.7.0.1) rainbow (>= 2.2.2, < 4.0) + rexml ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 1.7) - ruby-openid (2.7.0) - ruby-prof (0.17.0) - ruby-progressbar (1.10.0) + rubocop-discourse (1.0.2) + rubocop (>= 0.69.0) + ruby-prof (1.3.0) + ruby-progressbar (1.10.1) ruby-readability (0.7.0) guess_html_encoding (>= 0.0.4) nokogiri (>= 1.6.0) - ruby_dep (1.5.0) + rubyzip (2.2.0) safe_yaml (1.0.5) - sanitize (5.0.0) + sanitize (5.1.0) crass (~> 1.0.2) nokogiri (>= 1.8.0) nokogumbo (~> 2.0) sassc (2.0.1) ffi (~> 1.9) rake - sassc-rails (2.1.1) + sassc-rails (2.1.2) railties (>= 4.0.0) sassc (>= 2.0) sprockets (> 3.0) @@ -368,18 +372,17 @@ GEM seed-fu (2.3.9) activerecord (>= 3.1) activesupport (>= 3.1) - shoulda-matchers (3.1.3) - activesupport (>= 4.0.0) - sidekiq (5.2.7) - connection_pool (~> 2.2, >= 2.2.2) - rack (>= 1.5.0) - rack-protection (>= 1.5.0) - redis (>= 3.3.5, < 5) - simplecov (0.16.1) + shoulda-matchers (4.3.0) + activesupport (>= 4.2.0) + sidekiq (6.0.5) + connection_pool (>= 2.2.2) + rack (~> 2.0) + rack-protection (>= 2.0.0) + redis (>= 4.1.0) + simplecov (0.18.3) docile (~> 1.1) - json (>= 1.8, < 3) - simplecov-html (~> 0.10.0) - simplecov-html (0.10.2) + simplecov-html (~> 0.11) + simplecov-html (0.12.1) sprockets (3.7.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) @@ -388,42 +391,46 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) sshkey (2.0.0) - stackprof (0.2.12) - test-prof (0.9.0) - thor (0.20.3) + stackprof (0.2.15) + test-prof (0.11.3) + thor (1.0.1) thread_safe (0.3.6) - tilt (2.0.9) - tzinfo (1.2.5) + tilt (2.0.10) + tzinfo (1.2.6) thread_safe (~> 0.1) - uglifier (4.1.20) + uglifier (4.2.0) execjs (>= 0.3.0, < 3) unf (0.1.4) unf_ext unf_ext (0.0.7.6) - unicode-display_width (1.6.0) - unicorn (5.5.1) + unicode-display_width (1.6.1) + unicorn (5.5.3) kgio (~> 2.6) raindrops (~> 0.7) - uniform_notifier (1.12.1) - webmock (3.5.1) + uniform_notifier (1.13.0) + webmock (3.8.2) addressable (>= 2.3.6) crack (>= 0.3.2) - hashdiff - webpush (0.3.8) + hashdiff (>= 0.4.0, < 2.0.0) + webpush (1.0.0) hkdf (~> 0.2) jwt (~> 2.0) + yaml-lint (0.0.10) + zeitwerk (2.2.2) PLATFORMS ruby DEPENDENCIES - actionmailer (= 5.2.3) - actionpack (= 5.2.3) - actionview (= 5.2.3) + actionmailer (= 6.0.1) + actionpack (= 6.0.1) + actionview (= 6.0.1) + actionview_precompiler active_model_serializers (~> 0.8.3) - activemodel (= 5.2.3) - activerecord (= 5.2.3) - activesupport (= 5.2.3) + activemodel (= 6.0.1) + activerecord (= 6.0.1) + activesupport (= 6.0.1) + addressable annotate aws-sdk-s3 aws-sdk-sns @@ -433,19 +440,22 @@ DEPENDENCIES bootsnap bullet byebug + cbor certified colored2 + cose cppjieba_rb + css_parser diffy - discourse-ember-source (~> 3.8.0) + discourse-ember-source (~> 3.12.2) discourse_image_optim - email_reply_trimmer (~> 0.1) + email_reply_trimmer ember-handlebars-template (= 0.8.0) ember-rails (= 0.18.5) excon execjs fabrication - fakeweb (~> 1.3.0) + fakeweb fast_blank fast_xor fast_xs @@ -453,15 +463,15 @@ DEPENDENCIES flamegraph gc_tracer highline (~> 1.7.0) - hiredis htmlentities - http_accept_language (~> 2.0.5) + http_accept_language listen lograge logstash-event logstash-logger logster lru_redux + lz4-ruby mail maxminddb memory_profiler @@ -472,7 +482,7 @@ DEPENDENCIES mini_sql mini_suffix minitest - mocha + mocha (= 1.8.0) mock_redis multi_json mustache @@ -484,44 +494,46 @@ DEPENDENCIES omniauth-google-oauth2 omniauth-instagram omniauth-oauth2 - omniauth-openid omniauth-twitter - onebox (= 1.8.92) - openid-redis-store + onebox parallel_tests pg pry-nav pry-rails puma - r2 (~> 0.2.5) + r2 + rack (= 2.0.8) rack-mini-profiler rack-protection rails_multisite - railties (= 5.2.3) + railties (= 6.0.1) rake rb-fsevent rb-inotify (~> 0.9) rbtrace rchardet - redis (= 4.0.1) + redis redis-namespace rinku rotp rqrcode rspec rspec-html-matchers - rspec-rails + rspec-rails (= 4.0.0.beta2) rtlit rubocop + rubocop-discourse ruby-prof ruby-readability + rubyzip sanitize - sassc + sassc (= 2.0.1) sassc-rails seed-fu - shoulda-matchers (~> 3.1, >= 3.1.3) + shoulda-matchers sidekiq simplecov + sprockets (= 3.7.2) sprockets-rails sshkey stackprof @@ -533,6 +545,7 @@ DEPENDENCIES unicorn webmock webpush + yaml-lint BUNDLED WITH 2.1.1 diff --git a/README.md b/README.md index 0714caf896..323a21e73b 100644 --- a/README.md +++ b/README.md @@ -44,14 +44,16 @@ If you're looking for business class hosting, see [discourse.org/buy](https://ww ## Requirements -Discourse is built for the *next* 10 years of the Internet, so our requirements are high: +Discourse is built for the *next* 10 years of the Internet, so our requirements are high. + +Discourse supports the **latest, stable releases** of all major browsers and platforms: | Browsers | Tablets | Phones | | --------------------- | ------------ | ------------ | -| Safari 10+ | iPad 4+ | iOS 10+ | -| Google Chrome 57+ | Android 4.4+ | Android 4.4+ | -| Internet Explorer 11+ | | | -| Firefox 52+ | | | +| Apple Safari | iPadOS | iOS | +| Google Chrome | Android | Android | +| Microsoft Edge | | | +| Mozilla Firefox | | | ## Built With diff --git a/app/assets/javascripts/admin/adapters/api-key.js.es6 b/app/assets/javascripts/admin/adapters/api-key.js.es6 new file mode 100644 index 0000000000..a473f66e08 --- /dev/null +++ b/app/assets/javascripts/admin/adapters/api-key.js.es6 @@ -0,0 +1,11 @@ +import RESTAdapter from "discourse/adapters/rest"; + +export default RESTAdapter.extend({ + basePath() { + return "/admin/api/"; + }, + + apiNameFor() { + return "key"; + } +}); diff --git a/app/assets/javascripts/admin/adapters/email-style.js.es6 b/app/assets/javascripts/admin/adapters/email-style.js.es6 new file mode 100644 index 0000000000..c9f3865d4c --- /dev/null +++ b/app/assets/javascripts/admin/adapters/email-style.js.es6 @@ -0,0 +1,7 @@ +import RestAdapter from "discourse/adapters/rest"; + +export default RestAdapter.extend({ + pathFor() { + return "/admin/customize/email_style"; + } +}); diff --git a/app/assets/javascripts/admin/adapters/staff-action-log.js.es6 b/app/assets/javascripts/admin/adapters/staff-action-log.js.es6 new file mode 100644 index 0000000000..8e0f087c2b --- /dev/null +++ b/app/assets/javascripts/admin/adapters/staff-action-log.js.es6 @@ -0,0 +1,7 @@ +import RestAdapter from "discourse/adapters/rest"; + +export default RestAdapter.extend({ + basePath() { + return "/admin/logs/"; + } +}); diff --git a/app/assets/javascripts/admin/adapters/tag-group.js.es6 b/app/assets/javascripts/admin/adapters/tag-group.js.es6 new file mode 100644 index 0000000000..2e950aa2c1 --- /dev/null +++ b/app/assets/javascripts/admin/adapters/tag-group.js.es6 @@ -0,0 +1,5 @@ +import RestAdapter from "discourse/adapters/rest"; + +export default RestAdapter.extend({ + jsonMode: true +}); diff --git a/app/assets/javascripts/admin/components/ace-editor.js.es6 b/app/assets/javascripts/admin/components/ace-editor.js.es6 index d289d7e47f..4fc881dae4 100644 --- a/app/assets/javascripts/admin/components/ace-editor.js.es6 +++ b/app/assets/javascripts/admin/components/ace-editor.js.es6 @@ -1,7 +1,9 @@ +import Component from "@ember/component"; import loadScript from "discourse/lib/load-script"; -import { observes } from "ember-addons/ember-computed-decorators"; +import { observes } from "discourse-common/utils/decorators"; +import { on } from "@ember/object/evented"; -export default Ember.Component.extend({ +export default Component.extend({ mode: "css", classNames: ["ace-wrapper"], _editor: null, @@ -17,8 +19,9 @@ export default Ember.Component.extend({ @observes("content") contentChanged() { - if (this._editor && !this._skipContentChangeEvent && this.content) { - this._editor.getSession().setValue(this.content); + const content = this.content || ""; + if (this._editor && !this._skipContentChangeEvent) { + this._editor.getSession().setValue(content); } }, @@ -47,7 +50,7 @@ export default Ember.Component.extend({ } }, - _destroyEditor: function() { + _destroyEditor: on("willDestroyElement", function() { if (this._editor) { this._editor.destroy(); this._editor = null; @@ -58,7 +61,7 @@ export default Ember.Component.extend({ } $(window).off("ace:resize"); - }.on("willDestroyElement"), + }), resize() { if (this._editor) { @@ -74,7 +77,7 @@ export default Ember.Component.extend({ if (!this.element || this.isDestroying || this.isDestroyed) { return; } - const editor = loadedAce.edit(this.$(".ace")[0]); + const editor = loadedAce.edit(this.element.querySelector(".ace")); editor.setTheme("ace/theme/chrome"); editor.setShowPrintMargin(false); @@ -88,7 +91,7 @@ export default Ember.Component.extend({ editor.$blockScrolling = Infinity; editor.renderer.setScrollMargin(10, 10); - this.$().data("editor", editor); + this.element.setAttribute("data-editor", editor); this._editor = editor; this.changeDisabledState(); diff --git a/app/assets/javascripts/admin/components/admin-backups-logs.js.es6 b/app/assets/javascripts/admin/components/admin-backups-logs.js.es6 index 56f90325f3..1837409c58 100644 --- a/app/assets/javascripts/admin/components/admin-backups-logs.js.es6 +++ b/app/assets/javascripts/admin/components/admin-backups-logs.js.es6 @@ -1,73 +1,73 @@ -import debounce from "discourse/lib/debounce"; -import { renderSpinner } from "discourse/helpers/loading-spinner"; -import { escapeExpression } from "discourse/lib/utilities"; -import { bufferedRender } from "discourse-common/lib/buffered-render"; -import { observes, on } from "ember-addons/ember-computed-decorators"; +import { scheduleOnce } from "@ember/runloop"; +import Component from "@ember/component"; +import discourseDebounce from "discourse/lib/debounce"; +import { observes, on } from "discourse-common/utils/decorators"; -export default Ember.Component.extend( - bufferedRender({ - classNames: ["admin-backups-logs"], +export default Component.extend({ + classNames: ["admin-backups-logs"], + showLoadingSpinner: false, + hasFormattedLogs: false, + noLogsMessage: I18n.t("admin.backups.logs.none"), - init() { - this._super(...arguments); - this._reset(); - }, + init() { + this._super(...arguments); + this._reset(); + }, - _reset() { - this.setProperties({ formattedLogs: "", index: 0 }); - }, + _reset() { + this.setProperties({ formattedLogs: "", index: 0 }); + }, - _scrollDown() { - const $div = this.$()[0]; - $div.scrollTop = $div.scrollHeight; - }, + _scrollDown() { + const div = this.element; + div.scrollTop = div.scrollHeight; + }, - @on("init") - @observes("logs.[]") - _resetFormattedLogs() { - if (this.logs.length === 0) { - this._reset(); // reset the cached logs whenever the model is reset - this.rerenderBuffer(); - } - }, - - @on("init") - @observes("logs.[]") - _updateFormattedLogs: debounce(function() { - const logs = this.logs; - if (logs.length === 0) return; - - // do the log formatting only once for HELLish performance - let formattedLogs = this.formattedLogs; - for (let i = this.index, length = logs.length; i < length; i++) { - const date = logs[i].get("timestamp"), - message = escapeExpression(logs[i].get("message")); - formattedLogs += "[" + date + "] " + message + "\n"; - } - // update the formatted logs & cache index - this.setProperties({ - formattedLogs: formattedLogs, - index: logs.length - }); - // force rerender - this.rerenderBuffer(); - - Ember.run.scheduleOnce("afterRender", this, this._scrollDown); - }, 150), - - buildBuffer(buffer) { - const formattedLogs = this.formattedLogs; - if (formattedLogs && formattedLogs.length > 0) { - buffer.push("
");
-        buffer.push(formattedLogs);
-        buffer.push("
"); - } else { - buffer.push("

" + I18n.t("admin.backups.logs.none") + "

"); - } - // add a loading indicator - if (this.get("status.isOperationRunning")) { - buffer.push(renderSpinner("small")); - } + @on("init") + @observes("logs.[]") + _resetFormattedLogs() { + if (this.logs.length === 0) { + this._reset(); // reset the cached logs whenever the model is reset + this.renderLogs(); } - }) -); + }, + + @on("init") + @observes("logs.[]") + _updateFormattedLogs: discourseDebounce(function() { + const logs = this.logs; + if (logs.length === 0) return; + + // do the log formatting only once for HELLish performance + let formattedLogs = this.formattedLogs; + for (let i = this.index, length = logs.length; i < length; i++) { + const date = logs[i].get("timestamp"), + message = logs[i].get("message"); + formattedLogs += "[" + date + "] " + message + "\n"; + } + // update the formatted logs & cache index + this.setProperties({ + formattedLogs: formattedLogs, + index: logs.length + }); + // force rerender + this.renderLogs(); + + scheduleOnce("afterRender", this, this._scrollDown); + }, 150), + + renderLogs() { + const formattedLogs = this.formattedLogs; + if (formattedLogs && formattedLogs.length > 0) { + this.set("hasFormattedLogs", true); + } else { + this.set("hasFormattedLogs", false); + } + // add a loading indicator + if (this.get("status.isOperationRunning")) { + this.set("showLoadingSpinner", true); + } else { + this.set("showLoadingSpinner", false); + } + } +}); diff --git a/app/assets/javascripts/admin/components/admin-directory-toggle.js.es6 b/app/assets/javascripts/admin/components/admin-directory-toggle.js.es6 index f2480802b8..093f29826b 100644 --- a/app/assets/javascripts/admin/components/admin-directory-toggle.js.es6 +++ b/app/assets/javascripts/admin/components/admin-directory-toggle.js.es6 @@ -1,35 +1,30 @@ +import Component from "@ember/component"; import { iconHTML } from "discourse-common/lib/icon-library"; -import { bufferedRender } from "discourse-common/lib/buffered-render"; -export default Ember.Component.extend( - bufferedRender({ - tagName: "th", - classNames: ["sortable"], - rerenderTriggers: ["order", "ascending"], - - buildBuffer(buffer) { - const icon = this.icon; - - if (icon) { - buffer.push(iconHTML(icon)); - } - - buffer.push(I18n.t(this.i18nKey)); - - if (this.field === this.order) { - buffer.push(iconHTML(this.ascending ? "chevron-up" : "chevron-down")); - } - }, - - click() { - const currentOrder = this.order; - const field = this.field; - - if (currentOrder === field) { - this.set("ascending", this.ascending ? null : true); - } else { - this.setProperties({ order: field, ascending: null }); - } +export default Component.extend({ + tagName: "th", + classNames: ["sortable"], + chevronIcon: null, + toggleProperties() { + if (this.order === this.field) { + this.set("ascending", this.ascending ? null : true); + } else { + this.setProperties({ order: this.field, ascending: null }); } - }) -); + }, + toggleChevron() { + if (this.order === this.field) { + let chevron = iconHTML(this.ascending ? "chevron-up" : "chevron-down"); + this.set("chevronIcon", `${chevron}`.htmlSafe()); + } else { + this.set("chevronIcon", null); + } + }, + click() { + this.toggleProperties(); + }, + didReceiveAttrs() { + this._super(...arguments); + this.toggleChevron(); + } +}); diff --git a/app/assets/javascripts/admin/components/admin-editable-field.js.es6 b/app/assets/javascripts/admin/components/admin-editable-field.js.es6 index 9d91e2a27f..f2785cffd4 100644 --- a/app/assets/javascripts/admin/components/admin-editable-field.js.es6 +++ b/app/assets/javascripts/admin/components/admin-editable-field.js.es6 @@ -1,4 +1,5 @@ -export default Ember.Component.extend({ +import Component from "@ember/component"; +export default Component.extend({ tagName: "", buffer: "", diff --git a/app/assets/javascripts/admin/components/admin-form-row.js.es6 b/app/assets/javascripts/admin/components/admin-form-row.js.es6 index 5159168c30..b5f78c2a21 100644 --- a/app/assets/javascripts/admin/components/admin-form-row.js.es6 +++ b/app/assets/javascripts/admin/components/admin-form-row.js.es6 @@ -1,3 +1,4 @@ -export default Ember.Component.extend({ +import Component from "@ember/component"; +export default Component.extend({ classNames: ["row"] }); diff --git a/app/assets/javascripts/admin/components/admin-graph.js.es6 b/app/assets/javascripts/admin/components/admin-graph.js.es6 index 5ef384022e..be2e0f4e1a 100644 --- a/app/assets/javascripts/admin/components/admin-graph.js.es6 +++ b/app/assets/javascripts/admin/components/admin-graph.js.es6 @@ -1,11 +1,12 @@ +import Component from "@ember/component"; import loadScript from "discourse/lib/load-script"; -export default Ember.Component.extend({ +export default Component.extend({ tagName: "canvas", type: "line", refreshChart() { - const ctx = this.$()[0].getContext("2d"); + const ctx = this.element.getContext("2d"); const model = this.model; const rawData = this.get("model.data"); diff --git a/app/assets/javascripts/admin/components/admin-nav.js.es6 b/app/assets/javascripts/admin/components/admin-nav.js.es6 index 91ad923ffc..89720fbbe8 100644 --- a/app/assets/javascripts/admin/components/admin-nav.js.es6 +++ b/app/assets/javascripts/admin/components/admin-nav.js.es6 @@ -1,3 +1,4 @@ -export default Ember.Component.extend({ +import Component from "@ember/component"; +export default Component.extend({ tagName: "" }); diff --git a/app/assets/javascripts/admin/components/admin-report-chart.js.es6 b/app/assets/javascripts/admin/components/admin-report-chart.js.es6 index b4ced4e1cb..5351a11952 100644 --- a/app/assets/javascripts/admin/components/admin-report-chart.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report-chart.js.es6 @@ -1,7 +1,11 @@ +import { makeArray } from "discourse-common/lib/helpers"; +import { debounce } from "@ember/runloop"; +import { schedule } from "@ember/runloop"; +import Component from "@ember/component"; import { number } from "discourse/lib/formatter"; import loadScript from "discourse/lib/load-script"; -export default Ember.Component.extend({ +export default Component.extend({ classNames: ["admin-report-chart"], limit: 8, total: 0, @@ -10,7 +14,7 @@ export default Ember.Component.extend({ this._super(...arguments); this.resizeHandler = () => - Ember.run.debounce(this, this._scheduleChartRendering, 500); + debounce(this, this._scheduleChartRendering, 500); }, didInsertElement() { @@ -30,23 +34,24 @@ export default Ember.Component.extend({ didReceiveAttrs() { this._super(...arguments); - Ember.run.debounce(this, this._scheduleChartRendering, 100); + debounce(this, this._scheduleChartRendering, 100); }, _scheduleChartRendering() { - Ember.run.schedule("afterRender", () => { - this._renderChart(this.model, this.$(".chart-canvas")); + schedule("afterRender", () => { + this._renderChart( + this.model, + this.element && this.element.querySelector(".chart-canvas") + ); }); }, - _renderChart(model, $chartCanvas) { - if (!$chartCanvas || !$chartCanvas.length) return; + _renderChart(model, chartCanvas) { + if (!chartCanvas) return; - const context = $chartCanvas[0].getContext("2d"); - const chartData = Ember.makeArray( - model.get("chartData") || model.get("data") - ); - const prevChartData = Ember.makeArray( + const context = chartCanvas.getContext("2d"); + const chartData = makeArray(model.get("chartData") || model.get("data")); + const prevChartData = makeArray( model.get("prevChartData") || model.get("prev_data") ); @@ -82,6 +87,11 @@ export default Ember.Component.extend({ loadScript("/javascripts/Chart.min.js").then(() => { this._resetChart(); + + if (!this.element) { + return; + } + this._chart = new window.Chart(context, this._buildChartConfig(data)); }); }, @@ -102,6 +112,10 @@ export default Ember.Component.extend({ }, responsive: true, maintainAspectRatio: false, + responsiveAnimationDuration: 0, + animation: { + duration: 0 + }, layout: { padding: { left: 0, @@ -118,7 +132,10 @@ export default Ember.Component.extend({ userCallback: label => { if (Math.floor(label) === label) return label; }, - callback: label => number(label) + callback: label => number(label), + sampleSize: 5, + maxRotation: 25, + minRotation: 25 } } ], @@ -129,6 +146,11 @@ export default Ember.Component.extend({ type: "time", time: { parser: "YYYY-MM-DD" + }, + ticks: { + sampleSize: 5, + maxRotation: 50, + minRotation: 50 } } ] diff --git a/app/assets/javascripts/admin/components/admin-report-counters.js.es6 b/app/assets/javascripts/admin/components/admin-report-counters.js.es6 index d64bbe20fc..a66a3804c2 100644 --- a/app/assets/javascripts/admin/components/admin-report-counters.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report-counters.js.es6 @@ -1,4 +1,5 @@ -export default Ember.Component.extend({ +import Component from "@ember/component"; +export default Component.extend({ classNames: ["admin-report-counters"], attributeBindings: ["model.description:title"] diff --git a/app/assets/javascripts/admin/components/admin-report-counts.js.es6 b/app/assets/javascripts/admin/components/admin-report-counts.js.es6 index 849d81460a..625dd669ee 100644 --- a/app/assets/javascripts/admin/components/admin-report-counts.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report-counts.js.es6 @@ -1,7 +1,9 @@ -export default Ember.Component.extend({ +import { match } from "@ember/object/computed"; +import Component from "@ember/component"; +export default Component.extend({ allTime: true, tagName: "tr", - reverseColors: Ember.computed.match( + reverseColors: match( "report.type", /^(time_to_first_response|topics_with_no_response)$/ ), diff --git a/app/assets/javascripts/admin/components/admin-report-inline-table.js.es6 b/app/assets/javascripts/admin/components/admin-report-inline-table.js.es6 index 7e4933381c..38b3d4595e 100644 --- a/app/assets/javascripts/admin/components/admin-report-inline-table.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report-inline-table.js.es6 @@ -1,3 +1,4 @@ -export default Ember.Component.extend({ +import Component from "@ember/component"; +export default Component.extend({ classNames: ["admin-report-inline-table"] }); diff --git a/app/assets/javascripts/admin/components/admin-report-per-day-counts.js.es6 b/app/assets/javascripts/admin/components/admin-report-per-day-counts.js.es6 index b7620b66cd..b644dbab9f 100644 --- a/app/assets/javascripts/admin/components/admin-report-per-day-counts.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report-per-day-counts.js.es6 @@ -1,3 +1,4 @@ -export default Ember.Component.extend({ +import Component from "@ember/component"; +export default Component.extend({ tagName: "tr" }); diff --git a/app/assets/javascripts/admin/components/admin-report-stacked-chart.js.es6 b/app/assets/javascripts/admin/components/admin-report-stacked-chart.js.es6 index a70f698010..911d858ba9 100644 --- a/app/assets/javascripts/admin/components/admin-report-stacked-chart.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report-stacked-chart.js.es6 @@ -1,14 +1,18 @@ +import { makeArray } from "discourse-common/lib/helpers"; +import { debounce } from "@ember/runloop"; +import { schedule } from "@ember/runloop"; +import Component from "@ember/component"; import { number } from "discourse/lib/formatter"; import loadScript from "discourse/lib/load-script"; -export default Ember.Component.extend({ +export default Component.extend({ classNames: ["admin-report-chart", "admin-report-stacked-chart"], init() { this._super(...arguments); this.resizeHandler = () => - Ember.run.debounce(this, this._scheduleChartRendering, 500); + debounce(this, this._scheduleChartRendering, 500); }, didInsertElement() { @@ -28,26 +32,31 @@ export default Ember.Component.extend({ didReceiveAttrs() { this._super(...arguments); - Ember.run.debounce(this, this._scheduleChartRendering, 100); + debounce(this, this._scheduleChartRendering, 100); }, _scheduleChartRendering() { - Ember.run.schedule("afterRender", () => { - this._renderChart(this.model, this.$(".chart-canvas")); + schedule("afterRender", () => { + if (!this.element) { + return; + } + + this._renderChart( + this.model, + this.element.querySelector(".chart-canvas") + ); }); }, - _renderChart(model, $chartCanvas) { - if (!$chartCanvas || !$chartCanvas.length) return; + _renderChart(model, chartCanvas) { + if (!chartCanvas) return; - const context = $chartCanvas[0].getContext("2d"); + const context = chartCanvas.getContext("2d"); - const chartData = Ember.makeArray( - model.get("chartData") || model.get("data") - ); + const chartData = makeArray(model.get("chartData") || model.get("data")); const data = { - labels: chartData[0].data.map(cd => cd.x), + labels: chartData[0].data.mapBy("x"), datasets: chartData.map(cd => { return { label: cd.label, @@ -60,6 +69,7 @@ export default Ember.Component.extend({ loadScript("/javascripts/Chart.min.js").then(() => { this._resetChart(); + this._chart = new window.Chart(context, this._buildChartConfig(data)); }); }, @@ -71,7 +81,11 @@ export default Ember.Component.extend({ options: { responsive: true, maintainAspectRatio: false, + responsiveAnimationDuration: 0, hover: { mode: "index" }, + animation: { + duration: 0 + }, tooltips: { mode: "index", intersect: false, @@ -87,7 +101,6 @@ export default Ember.Component.extend({ moment(tooltipItem[0].xLabel, "YYYY-MM-DD").format("LL") } }, - legend: { display: false }, layout: { padding: { left: 0, @@ -105,7 +118,10 @@ export default Ember.Component.extend({ userCallback: label => { if (Math.floor(label) === label) return label; }, - callback: label => number(label) + callback: label => number(label), + sampleSize: 5, + maxRotation: 25, + minRotation: 25 } } ], @@ -118,6 +134,11 @@ export default Ember.Component.extend({ time: { parser: "YYYY-MM-DD", minUnit: "day" + }, + ticks: { + sampleSize: 5, + maxRotation: 50, + minRotation: 50 } } ] diff --git a/app/assets/javascripts/admin/components/admin-report-storage-stats.js.es6 b/app/assets/javascripts/admin/components/admin-report-storage-stats.js.es6 index c4bf831f56..61629c626e 100644 --- a/app/assets/javascripts/admin/components/admin-report-storage-stats.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report-storage-stats.js.es6 @@ -1,39 +1,41 @@ +import discourseComputed from "discourse-common/utils/decorators"; +import { alias } from "@ember/object/computed"; +import Component from "@ember/component"; import { setting } from "discourse/lib/computed"; -import computed from "ember-addons/ember-computed-decorators"; -export default Ember.Component.extend({ +export default Component.extend({ classNames: ["admin-report-storage-stats"], backupLocation: setting("backup_location"), - backupStats: Ember.computed.alias("model.data.backups"), - uploadStats: Ember.computed.alias("model.data.uploads"), + backupStats: alias("model.data.backups"), + uploadStats: alias("model.data.uploads"), - @computed("backupStats") + @discourseComputed("backupStats") showBackupStats(stats) { return stats && this.currentUser.admin; }, - @computed("backupLocation") + @discourseComputed("backupLocation") backupLocationName(backupLocation) { return I18n.t(`admin.backups.location.${backupLocation}`); }, - @computed("backupStats.used_bytes") + @discourseComputed("backupStats.used_bytes") usedBackupSpace(bytes) { return I18n.toHumanSize(bytes); }, - @computed("backupStats.free_bytes") + @discourseComputed("backupStats.free_bytes") freeBackupSpace(bytes) { return I18n.toHumanSize(bytes); }, - @computed("uploadStats.used_bytes") + @discourseComputed("uploadStats.used_bytes") usedUploadSpace(bytes) { return I18n.toHumanSize(bytes); }, - @computed("uploadStats.free_bytes") + @discourseComputed("uploadStats.free_bytes") freeUploadSpace(bytes) { return I18n.toHumanSize(bytes); } diff --git a/app/assets/javascripts/admin/components/admin-report-table-cell.js.es6 b/app/assets/javascripts/admin/components/admin-report-table-cell.js.es6 index 7140b69668..e7bf688f2f 100644 --- a/app/assets/javascripts/admin/components/admin-report-table-cell.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report-table-cell.js.es6 @@ -1,18 +1,20 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import { alias } from "@ember/object/computed"; +import Component from "@ember/component"; -export default Ember.Component.extend({ +export default Component.extend({ tagName: "td", classNames: ["admin-report-table-cell"], classNameBindings: ["type", "property"], options: null, - @computed("label", "data", "options") + @discourseComputed("label", "data", "options") computedLabel(label, data, options) { return label.compute(data, options || {}); }, - type: Ember.computed.alias("label.type"), - property: Ember.computed.alias("label.mainProperty"), - formatedValue: Ember.computed.alias("computedLabel.formatedValue"), - value: Ember.computed.alias("computedLabel.value") + type: alias("label.type"), + property: alias("label.mainProperty"), + formatedValue: alias("computedLabel.formatedValue"), + value: alias("computedLabel.value") }); diff --git a/app/assets/javascripts/admin/components/admin-report-table-header.js.es6 b/app/assets/javascripts/admin/components/admin-report-table-header.js.es6 index ab986f2946..bc5633b21d 100644 --- a/app/assets/javascripts/admin/components/admin-report-table-header.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report-table-header.js.es6 @@ -1,17 +1,18 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import Component from "@ember/component"; -export default Ember.Component.extend({ +export default Component.extend({ tagName: "th", classNames: ["admin-report-table-header"], classNameBindings: ["label.mainProperty", "label.type", "isCurrentSort"], attributeBindings: ["label.title:title"], - @computed("currentSortLabel.sortProperty", "label.sortProperty") + @discourseComputed("currentSortLabel.sortProperty", "label.sortProperty") isCurrentSort(currentSortField, labelSortField) { return currentSortField === labelSortField; }, - @computed("currentSortDirection") + @discourseComputed("currentSortDirection") sortIcon(currentSortDirection) { return currentSortDirection === 1 ? "caret-up" : "caret-down"; } diff --git a/app/assets/javascripts/admin/components/admin-report-table-row.js.es6 b/app/assets/javascripts/admin/components/admin-report-table-row.js.es6 index 3be140c308..c86f586a08 100644 --- a/app/assets/javascripts/admin/components/admin-report-table-row.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report-table-row.js.es6 @@ -1,4 +1,5 @@ -export default Ember.Component.extend({ +import Component from "@ember/component"; +export default Component.extend({ tagName: "tr", classNames: ["admin-report-table-row"], options: null diff --git a/app/assets/javascripts/admin/components/admin-report-table.js.es6 b/app/assets/javascripts/admin/components/admin-report-table.js.es6 index 6e1e22c172..aa636224d4 100644 --- a/app/assets/javascripts/admin/components/admin-report-table.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report-table.js.es6 @@ -1,21 +1,28 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import { makeArray } from "discourse-common/lib/helpers"; +import { alias } from "@ember/object/computed"; +import Component from "@ember/component"; const PAGES_LIMIT = 8; -export default Ember.Component.extend({ +export default Component.extend({ classNameBindings: ["sortable", "twoColumns"], classNames: ["admin-report-table"], sortable: false, sortDirection: 1, - perPage: Ember.computed.alias("options.perPage"), + perPage: alias("options.perPage"), page: 0, - @computed("model.computedLabels.length") + @discourseComputed("model.computedLabels.length") twoColumns(labelsLength) { return labelsLength === 2; }, - @computed("totalsForSample", "options.total", "model.dates_filtering") + @discourseComputed( + "totalsForSample", + "options.total", + "model.dates_filtering" + ) showTotalForSample(totalsForSample, total, datesFiltering) { // check if we have at least one cell which contains a value const sum = totalsForSample @@ -26,12 +33,16 @@ export default Ember.Component.extend({ return sum >= 1 && total && datesFiltering; }, - @computed("model.total", "options.total", "twoColumns") + @discourseComputed("model.total", "options.total", "twoColumns") showTotal(reportTotal, total, twoColumns) { return reportTotal && total && twoColumns; }, - @computed("model.{average,data}", "totalsForSample.1.value", "twoColumns") + @discourseComputed( + "model.{average,data}", + "totalsForSample.1.value", + "twoColumns" + ) showAverage(model, sampleTotalValue, hasTwoColumns) { return ( model.average && @@ -41,17 +52,17 @@ export default Ember.Component.extend({ ); }, - @computed("totalsForSample.1.value", "model.data.length") + @discourseComputed("totalsForSample.1.value", "model.data.length") averageForSample(totals, count) { return (totals / count).toFixed(0); }, - @computed("model.data.length") + @discourseComputed("model.data.length") showSortingUI(dataLength) { return dataLength >= 5; }, - @computed("totalsForSampleRow", "model.computedLabels") + @discourseComputed("totalsForSampleRow", "model.computedLabels") totalsForSample(row, labels) { return labels.map(label => { const computedLabel = label.compute(row); @@ -61,7 +72,7 @@ export default Ember.Component.extend({ }); }, - @computed("model.data", "model.computedLabels") + @discourseComputed("model.data", "model.computedLabels") totalsForSampleRow(rows, labels) { if (!rows || !rows.length) return {}; @@ -87,9 +98,9 @@ export default Ember.Component.extend({ return totalsRow; }, - @computed("sortLabel", "sortDirection", "model.data.[]") + @discourseComputed("sortLabel", "sortDirection", "model.data.[]") sortedData(sortLabel, sortDirection, data) { - data = Ember.makeArray(data); + data = makeArray(data); if (sortLabel) { const compare = (label, direction) => { @@ -107,7 +118,7 @@ export default Ember.Component.extend({ return data; }, - @computed("sortedData.[]", "perPage", "page") + @discourseComputed("sortedData.[]", "perPage", "page") paginatedData(data, perPage, page) { if (perPage < data.length) { const start = perPage * page; @@ -117,7 +128,7 @@ export default Ember.Component.extend({ return data; }, - @computed("model.data", "perPage", "page") + @discourseComputed("model.data", "perPage", "page") pages(data, perPage, page) { if (!data || data.length <= perPage) return []; diff --git a/app/assets/javascripts/admin/components/admin-report-trust-level-counts.js.es6 b/app/assets/javascripts/admin/components/admin-report-trust-level-counts.js.es6 index b7620b66cd..b644dbab9f 100644 --- a/app/assets/javascripts/admin/components/admin-report-trust-level-counts.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report-trust-level-counts.js.es6 @@ -1,3 +1,4 @@ -export default Ember.Component.extend({ +import Component from "@ember/component"; +export default Component.extend({ tagName: "tr" }); diff --git a/app/assets/javascripts/admin/components/admin-report.js.es6 b/app/assets/javascripts/admin/components/admin-report.js.es6 index d539776804..a385b71730 100644 --- a/app/assets/javascripts/admin/components/admin-report.js.es6 +++ b/app/assets/javascripts/admin/components/admin-report.js.es6 @@ -1,9 +1,15 @@ +import discourseComputed from "discourse-common/utils/decorators"; +import { makeArray } from "discourse-common/lib/helpers"; +import { alias, or, and, reads, equal, notEmpty } from "@ember/object/computed"; +import EmberObject from "@ember/object"; +import { next } from "@ember/runloop"; +import Component from "@ember/component"; import ReportLoader from "discourse/lib/reports-loader"; import { exportEntity } from "discourse/lib/export-csv"; import { outputExportResult } from "discourse/lib/export-result"; import { isNumeric } from "discourse/lib/utilities"; -import { SCHEMA_VERSION, default as Report } from "admin/models/report"; -import computed from "ember-addons/ember-computed-decorators"; +import Report, { SCHEMA_VERSION } from "admin/models/report"; +import ENV from "discourse-common/config/environment"; const TABLE_OPTIONS = { perPage: 8, @@ -34,7 +40,7 @@ function collapseWeekly(data, average) { return aggregate; } -export default Ember.Component.extend({ +export default Component.extend({ classNameBindings: ["isEnabled", "isLoading", "dasherizedDataSourceName"], classNames: ["admin-report"], isEnabled: true, @@ -54,13 +60,9 @@ export default Ember.Component.extend({ showHeader: true, showTitle: true, showFilteringUI: false, - showDatesOptions: Ember.computed.alias("model.dates_filtering"), - showExport: Ember.computed.not("model.onlyTable"), - showRefresh: Ember.computed.or( - "showDatesOptions", - "model.available_filters.length" - ), - shouldDisplayTrend: Ember.computed.and("showTrend", "model.prev_period"), + showDatesOptions: alias("model.dates_filtering"), + showRefresh: or("showDatesOptions", "model.available_filters.length"), + shouldDisplayTrend: and("showTrend", "model.prev_period"), init() { this._super(...arguments); @@ -68,8 +70,8 @@ export default Ember.Component.extend({ this._reports = []; }, - startDate: Ember.computed.reads("filters.startDate"), - endDate: Ember.computed.reads("filters.endDate"), + startDate: reads("filters.startDate"), + endDate: reads("filters.endDate"), didReceiveAttrs() { this._super(...arguments); @@ -81,38 +83,34 @@ export default Ember.Component.extend({ } }, - showError: Ember.computed.or( - "showTimeoutError", - "showExceptionError", - "showNotFoundError" - ), - showNotFoundError: Ember.computed.equal("model.error", "not_found"), - showTimeoutError: Ember.computed.equal("model.error", "timeout"), - showExceptionError: Ember.computed.equal("model.error", "exception"), + showError: or("showTimeoutError", "showExceptionError", "showNotFoundError"), + showNotFoundError: equal("model.error", "not_found"), + showTimeoutError: equal("model.error", "timeout"), + showExceptionError: equal("model.error", "exception"), - hasData: Ember.computed.notEmpty("model.data"), + hasData: notEmpty("model.data"), - @computed("dataSourceName", "model.type") + @discourseComputed("dataSourceName", "model.type") dasherizedDataSourceName(dataSourceName, type) { return (dataSourceName || type || "undefined").replace(/_/g, "-"); }, - @computed("dataSourceName", "model.type") + @discourseComputed("dataSourceName", "model.type") dataSource(dataSourceName, type) { dataSourceName = dataSourceName || type; return `/admin/reports/${dataSourceName}`; }, - @computed("displayedModes.length") + @discourseComputed("displayedModes.length") showModes(displayedModesLength) { return displayedModesLength > 1; }, - @computed("currentMode", "model.modes", "forcedModes") + @discourseComputed("currentMode", "model.modes", "forcedModes") displayedModes(currentMode, reportModes, forcedModes) { const modes = forcedModes ? forcedModes.split(",") : reportModes; - return Ember.makeArray(modes).map(mode => { + return makeArray(modes).map(mode => { const base = `btn-default mode-btn ${mode}`; const cssClass = currentMode === mode ? `${base} is-current` : base; @@ -124,12 +122,12 @@ export default Ember.Component.extend({ }); }, - @computed("currentMode") + @discourseComputed("currentMode") modeComponent(currentMode) { - return `admin-report-${currentMode}`; + return `admin-report-${currentMode.replace(/_/g, "-")}`; }, - @computed("startDate") + @discourseComputed("startDate") normalizedStartDate(startDate) { return startDate && typeof startDate.isValid === "function" ? moment @@ -141,7 +139,7 @@ export default Ember.Component.extend({ .format("YYYYMMDD"); }, - @computed("endDate") + @discourseComputed("endDate") normalizedEndDate(endDate) { return endDate && typeof endDate.isValid === "function" ? moment @@ -153,7 +151,7 @@ export default Ember.Component.extend({ .format("YYYYMMDD"); }, - @computed( + @discourseComputed( "dataSourceName", "normalizedStartDate", "normalizedEndDate", @@ -165,8 +163,8 @@ export default Ember.Component.extend({ let reportKey = "reports:"; reportKey += [ dataSourceName, - startDate.replace(/-/g, ""), - endDate.replace(/-/g, ""), + ENV.environment === "test" ? "start" : startDate.replace(/-/g, ""), + ENV.environment === "test" ? "end" : endDate.replace(/-/g, ""), "[:prev_period]", this.get("reportOptions.table.limit"), customFilters @@ -184,6 +182,32 @@ export default Ember.Component.extend({ }, actions: { + onChangeEndDate(date) { + const startDate = moment(this.startDate); + const newEndDate = moment(date).endOf("day"); + + if (newEndDate.isSameOrAfter(startDate)) { + this.set("endDate", newEndDate.format("YYYY-MM-DD")); + } else { + this.set("endDate", startDate.endOf("day").format("YYYY-MM-DD")); + } + + this.send("refreshReport"); + }, + + onChangeStartDate(date) { + const endDate = moment(this.endDate); + const newStartDate = moment(date).startOf("day"); + + if (newStartDate.isSameOrBefore(endDate)) { + this.set("startDate", newStartDate.format("YYYY-MM-DD")); + } else { + this.set("startDate", endDate.startOf("day").format("YYYY-MM-DD")); + } + + this.send("refreshReport"); + }, + applyFilter(id, value) { let customFilters = this.get("filters.customFilters") || {}; @@ -286,7 +310,7 @@ export default Ember.Component.extend({ this.setProperties({ isLoading: true, rateLimitationString: null }); - Ember.run.next(() => { + next(() => { let payload = this._buildPayload(["prev_period"]); const callback = response => { @@ -342,12 +366,12 @@ export default Ember.Component.extend({ _buildOptions(mode) { if (mode === "table") { const tableOptions = JSON.parse(JSON.stringify(TABLE_OPTIONS)); - return Ember.Object.create( + return EmberObject.create( Object.assign(tableOptions, this.get("reportOptions.table") || {}) ); } else { const chartOptions = JSON.parse(JSON.stringify(CHART_OPTIONS)); - return Ember.Object.create( + return EmberObject.create( Object.assign(chartOptions, this.get("reportOptions.chart") || {}) ); } diff --git a/app/assets/javascripts/admin/components/admin-theme-editor.js.es6 b/app/assets/javascripts/admin/components/admin-theme-editor.js.es6 index e54cb32c60..0299c04715 100644 --- a/app/assets/javascripts/admin/components/admin-theme-editor.js.es6 +++ b/app/assets/javascripts/admin/components/admin-theme-editor.js.es6 @@ -1,8 +1,10 @@ -import { default as computed } from "ember-addons/ember-computed-decorators"; +import { next } from "@ember/runloop"; +import Component from "@ember/component"; +import discourseComputed from "discourse-common/utils/decorators"; import { fmt } from "discourse/lib/computed"; -export default Ember.Component.extend({ - @computed("theme.targets", "onlyOverridden", "showAdvanced") +export default Component.extend({ + @discourseComputed("theme.targets", "onlyOverridden", "showAdvanced") visibleTargets(targets, onlyOverridden, showAdvanced) { return targets.filter(target => { if (target.advanced && !showAdvanced) { @@ -15,7 +17,7 @@ export default Ember.Component.extend({ }); }, - @computed("currentTargetName", "onlyOverridden", "theme.fields") + @discourseComputed("currentTargetName", "onlyOverridden", "theme.fields") visibleFields(targetName, onlyOverridden, fields) { fields = fields[targetName]; if (onlyOverridden) { @@ -24,14 +26,14 @@ export default Ember.Component.extend({ return fields; }, - @computed("currentTargetName", "fieldName") + @discourseComputed("currentTargetName", "fieldName") activeSectionMode(targetName, fieldName) { if (["settings", "translations"].includes(targetName)) return "yaml"; if (["extra_scss"].includes(targetName)) return "scss"; return fieldName && fieldName.indexOf("scss") > -1 ? "scss" : "html"; }, - @computed("fieldName", "currentTargetName", "theme") + @discourseComputed("fieldName", "currentTargetName", "theme") activeSection: { get(fieldName, target, model) { return model.getField(target, fieldName); @@ -44,17 +46,21 @@ export default Ember.Component.extend({ editorId: fmt("fieldName", "currentTargetName", "%@|%@"), - @computed("maximized") + @discourseComputed("maximized") maximizeIcon(maximized) { return maximized ? "discourse-compress" : "discourse-expand"; }, - @computed("currentTargetName", "theme.targets") + @discourseComputed("currentTargetName", "theme.targets") showAddField(currentTargetName, targets) { return targets.find(t => t.name === currentTargetName).customNames; }, - @computed("currentTargetName", "fieldName", "theme.theme_fields.@each.error") + @discourseComputed( + "currentTargetName", + "fieldName", + "theme.theme_fields.@each.error" + ) error(target, fieldName) { return this.theme.getError(target, fieldName); }, @@ -82,7 +88,7 @@ export default Ember.Component.extend({ toggleMaximize: function() { this.toggleProperty("maximized"); - Ember.run.next(() => this.appEvents.trigger("ace:resize")); + next(() => this.appEvents.trigger("ace:resize")); }, onlyOverriddenChanged(value) { diff --git a/app/assets/javascripts/admin/components/admin-user-field-item.js.es6 b/app/assets/javascripts/admin/components/admin-user-field-item.js.es6 index 1bf6128f1c..a02404de98 100644 --- a/app/assets/javascripts/admin/components/admin-user-field-item.js.es6 +++ b/app/assets/javascripts/admin/components/admin-user-field-item.js.es6 @@ -1,16 +1,19 @@ +import { isEmpty } from "@ember/utils"; +import { empty } from "@ember/object/computed"; +import { scheduleOnce } from "@ember/runloop"; +import Component from "@ember/component"; import UserField from "admin/models/user-field"; import { bufferedProperty } from "discourse/mixins/buffered-content"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { propertyEqual } from "discourse/lib/computed"; import { i18n } from "discourse/lib/computed"; -import { - default as computed, +import discourseComputed, { observes, on -} from "ember-addons/ember-computed-decorators"; +} from "discourse-common/utils/decorators"; -export default Ember.Component.extend(bufferedProperty("userField"), { - editing: Ember.computed.empty("userField.id"), +export default Component.extend(bufferedProperty("userField"), { + editing: empty("userField.id"), classNameBindings: [":user-field"], cantMoveUp: propertyEqual("userField", "firstField"), @@ -18,7 +21,7 @@ export default Ember.Component.extend(bufferedProperty("userField"), { userFieldsDescription: i18n("admin.user_fields.description"), - @computed("buffered.field_type") + @discourseComputed("buffered.field_type") bufferedFieldType(fieldType) { return UserField.fieldTypeById(fieldType); }, @@ -27,7 +30,7 @@ export default Ember.Component.extend(bufferedProperty("userField"), { @observes("editing") _focusOnEdit() { if (this.editing) { - Ember.run.scheduleOnce("afterRender", this, "_focusName"); + scheduleOnce("afterRender", this, "_focusName"); } }, @@ -35,12 +38,12 @@ export default Ember.Component.extend(bufferedProperty("userField"), { $(".user-field-name").select(); }, - @computed("userField.field_type") + @discourseComputed("userField.field_type") fieldName(fieldType) { return UserField.fieldTypeById(fieldType).get("name"); }, - @computed( + @discourseComputed( "userField.editable", "userField.required", "userField.show_on_profile", @@ -93,7 +96,7 @@ export default Ember.Component.extend(bufferedProperty("userField"), { cancel() { const id = this.get("userField.id"); - if (Ember.isEmpty(id)) { + if (isEmpty(id)) { this.destroyAction(this.userField); } else { this.rollbackBuffer(); diff --git a/app/assets/javascripts/admin/components/admin-watched-word.js.es6 b/app/assets/javascripts/admin/components/admin-watched-word.js.es6 index 4da1828021..d4f5108c0f 100644 --- a/app/assets/javascripts/admin/components/admin-watched-word.js.es6 +++ b/app/assets/javascripts/admin/components/admin-watched-word.js.es6 @@ -1,29 +1,28 @@ +import Component from "@ember/component"; import { iconHTML } from "discourse-common/lib/icon-library"; -import { bufferedRender } from "discourse-common/lib/buffered-render"; -import { escapeExpression } from "discourse/lib/utilities"; -export default Ember.Component.extend( - bufferedRender({ - classNames: ["watched-word"], +export default Component.extend({ + classNames: ["watched-word"], + watchedWord: null, + xIcon: iconHTML("times").htmlSafe(), - buildBuffer(buffer) { - buffer.push(iconHTML("times")); - buffer.push(` ${escapeExpression(this.get("word.word"))}`); - }, + init() { + this._super(...arguments); + this.set("watchedWord", this.get("word.word")); + }, - click() { - this.word - .destroy() - .then(() => { - this.action(this.word); - }) - .catch(e => { - bootbox.alert( - I18n.t("generic_error_with_reason", { - error: `http: ${e.status} - ${e.body}` - }) - ); - }); - } - }) -); + click() { + this.word + .destroy() + .then(() => { + this.action(this.word); + }) + .catch(e => { + bootbox.alert( + I18n.t("generic_error_with_reason", { + error: `http: ${e.status} - ${e.body}` + }) + ); + }); + } +}); diff --git a/app/assets/javascripts/admin/components/admin-web-hook-event-chooser.js.es6 b/app/assets/javascripts/admin/components/admin-web-hook-event-chooser.js.es6 index 7a6b5c13f1..a38695c735 100644 --- a/app/assets/javascripts/admin/components/admin-web-hook-event-chooser.js.es6 +++ b/app/assets/javascripts/admin/components/admin-web-hook-event-chooser.js.es6 @@ -1,25 +1,27 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import { alias } from "@ember/object/computed"; +import Component from "@ember/component"; -export default Ember.Component.extend({ +export default Component.extend({ classNames: ["hook-event"], - typeName: Ember.computed.alias("type.name"), + typeName: alias("type.name"), - @computed("typeName") + @discourseComputed("typeName") name(typeName) { return I18n.t(`admin.web_hooks.${typeName}_event.name`); }, - @computed("typeName") + @discourseComputed("typeName") details(typeName) { return I18n.t(`admin.web_hooks.${typeName}_event.details`); }, - @computed("model.[]", "typeName") + @discourseComputed("model.[]", "typeName") eventTypeExists(eventTypes, typeName) { return eventTypes.any(event => event.name === typeName); }, - @computed("eventTypeExists") + @discourseComputed("eventTypeExists") enabled: { get(eventTypeExists) { return eventTypeExists; diff --git a/app/assets/javascripts/admin/components/admin-web-hook-event.js.es6 b/app/assets/javascripts/admin/components/admin-web-hook-event.js.es6 index b558baa0b4..365e22aa67 100644 --- a/app/assets/javascripts/admin/components/admin-web-hook-event.js.es6 +++ b/app/assets/javascripts/admin/components/admin-web-hook-event.js.es6 @@ -1,15 +1,16 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import Component from "@ember/component"; import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { ensureJSON, plainJSON, prettyJSON } from "discourse/lib/formatter"; -export default Ember.Component.extend({ +export default Component.extend({ tagName: "li", expandDetails: null, expandDetailsRequestKey: "request", expandDetailsResponseKey: "response", - @computed("model.status") + @discourseComputed("model.status") statusColorClasses(status) { if (!status) return ""; @@ -20,25 +21,25 @@ export default Ember.Component.extend({ } }, - @computed("model.created_at") + @discourseComputed("model.created_at") createdAt(createdAt) { return moment(createdAt).format("YYYY-MM-DD HH:mm:ss"); }, - @computed("model.duration") + @discourseComputed("model.duration") completion(duration) { const seconds = Math.floor(duration / 10.0) / 100.0; return I18n.t("admin.web_hooks.events.completed_in", { count: seconds }); }, - @computed("expandDetails") + @discourseComputed("expandDetails") expandRequestIcon(expandDetails) { return expandDetails === this.expandDetailsRequestKey ? "ellipsis-h" : "ellipsis-v"; }, - @computed("expandDetails") + @discourseComputed("expandDetails") expandResponseIcon(expandDetails) { return expandDetails === this.expandDetailsResponseKey ? "ellipsis-h" diff --git a/app/assets/javascripts/admin/components/admin-web-hook-status.js.es6 b/app/assets/javascripts/admin/components/admin-web-hook-status.js.es6 index 1dc7a27c35..0c24edc9d6 100644 --- a/app/assets/javascripts/admin/components/admin-web-hook-status.js.es6 +++ b/app/assets/javascripts/admin/components/admin-web-hook-status.js.es6 @@ -1,32 +1,37 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import Component from "@ember/component"; import { iconHTML } from "discourse-common/lib/icon-library"; -import { bufferedRender } from "discourse-common/lib/buffered-render"; -export default Ember.Component.extend( - bufferedRender({ - classes: ["text-muted", "text-danger", "text-successful", "text-muted"], - icons: ["circle-o", "times-circle", "circle", "circle"], +export default Component.extend({ + classes: ["text-muted", "text-danger", "text-successful", "text-muted"], + icons: ["far-circle", "times-circle", "circle", "circle"], + circleIcon: null, + deliveryStatus: null, - @computed("deliveryStatuses", "model.last_delivery_status") - status(deliveryStatuses, lastDeliveryStatus) { - return deliveryStatuses.find(s => s.id === lastDeliveryStatus); - }, + @discourseComputed("deliveryStatuses", "model.last_delivery_status") + status(deliveryStatuses, lastDeliveryStatus) { + return deliveryStatuses.find(s => s.id === lastDeliveryStatus); + }, - @computed("status.id", "icons") - icon(statusId, icons) { - return icons[statusId - 1]; - }, + @discourseComputed("status.id", "icons") + icon(statusId, icons) { + return icons[statusId - 1]; + }, - @computed("status.id", "classes") - class(statusId, classes) { - return classes[statusId - 1]; - }, + @discourseComputed("status.id", "classes") + class(statusId, classes) { + return classes[statusId - 1]; + }, - buildBuffer(buffer) { - buffer.push(iconHTML(this.icon, { class: this.class })); - buffer.push( - I18n.t(`admin.web_hooks.delivery_status.${this.get("status.name")}`) - ); - } - }) -); + didReceiveAttrs() { + this._super(...arguments); + this.set( + "circleIcon", + iconHTML(this.icon, { class: this.class }).htmlSafe() + ); + this.set( + "deliveryStatus", + I18n.t(`admin.web_hooks.delivery_status.${this.get("status.name")}`) + ); + } +}); diff --git a/app/assets/javascripts/admin/components/admin-wrapper.js.es6 b/app/assets/javascripts/admin/components/admin-wrapper.js.es6 index 45daf32977..b60ac12855 100644 --- a/app/assets/javascripts/admin/components/admin-wrapper.js.es6 +++ b/app/assets/javascripts/admin/components/admin-wrapper.js.es6 @@ -1,4 +1,5 @@ -export default Ember.Component.extend({ +import Component from "@ember/component"; +export default Component.extend({ didInsertElement() { this._super(...arguments); $("body").addClass("admin-interface"); diff --git a/app/assets/javascripts/admin/components/cancel-link.js.es6 b/app/assets/javascripts/admin/components/cancel-link.js.es6 index 91ad923ffc..89720fbbe8 100644 --- a/app/assets/javascripts/admin/components/cancel-link.js.es6 +++ b/app/assets/javascripts/admin/components/cancel-link.js.es6 @@ -1,3 +1,4 @@ -export default Ember.Component.extend({ +import Component from "@ember/component"; +export default Component.extend({ tagName: "" }); diff --git a/app/assets/javascripts/admin/components/color-input.js.es6 b/app/assets/javascripts/admin/components/color-input.js.es6 index e57a8efde8..8648506bf9 100644 --- a/app/assets/javascripts/admin/components/color-input.js.es6 +++ b/app/assets/javascripts/admin/components/color-input.js.es6 @@ -1,4 +1,7 @@ -import { default as loadScript, loadCSS } from "discourse/lib/load-script"; +import { schedule } from "@ember/runloop"; +import Component from "@ember/component"; +import loadScript, { loadCSS } from "discourse/lib/load-script"; +import { observes } from "discourse-common/utils/decorators"; /** An input field for a color. @@ -7,14 +10,16 @@ import { default as loadScript, loadCSS } from "discourse/lib/load-script"; @param brightnessValue is a number from 0 to 255 representing the brightness of the color. See ColorSchemeColor. @params valid is a boolean indicating if the input field is a valid color. **/ -export default Ember.Component.extend({ +export default Component.extend({ classNames: ["color-picker"], + + @observes("hexValue", "brightnessValue", "valid") hexValueChanged: function() { var hex = this.hexValue; - let $text = this.$("input.hex-input"); + let text = this.element.querySelector("input.hex-input"); if (this.valid) { - $text.attr( + text.setAttribute( "style", "color: " + (this.brightnessValue > 125 ? "black" : "white") + @@ -24,18 +29,20 @@ export default Ember.Component.extend({ ); if (this.pickerLoaded) { - this.$(".picker").spectrum({ color: "#" + this.hexValue }); + $(this.element.querySelector(".picker")).spectrum({ + color: "#" + this.hexValue + }); } } else { - $text.attr("style", ""); + text.setAttribute("style", ""); } - }.observes("hexValue", "brightnessValue", "valid"), + }, didInsertElement() { loadScript("/javascripts/spectrum.js").then(() => { loadCSS("/javascripts/spectrum.css").then(() => { - Ember.run.schedule("afterRender", () => { - this.$(".picker") + schedule("afterRender", () => { + $(this.element.querySelector(".picker")) .spectrum({ color: "#" + this.hexValue }) .on("change.spectrum", (me, color) => { this.set("hexValue", color.toHexString().replace("#", "")); @@ -44,7 +51,7 @@ export default Ember.Component.extend({ }); }); }); - Ember.run.schedule("afterRender", () => { + schedule("afterRender", () => { this.hexValueChanged(); }); } diff --git a/app/assets/javascripts/admin/components/email-styles-editor.js.es6 b/app/assets/javascripts/admin/components/email-styles-editor.js.es6 new file mode 100644 index 0000000000..ef5cdb077e --- /dev/null +++ b/app/assets/javascripts/admin/components/email-styles-editor.js.es6 @@ -0,0 +1,52 @@ +import discourseComputed from "discourse-common/utils/decorators"; +import { reads } from "@ember/object/computed"; +import Component from "@ember/component"; + +export default Component.extend({ + editorId: reads("fieldName"), + + @discourseComputed("fieldName") + currentEditorMode(fieldName) { + return fieldName === "css" ? "scss" : fieldName; + }, + + @discourseComputed("fieldName", "styles.html", "styles.css") + resetDisabled(fieldName) { + return ( + this.get(`styles.${fieldName}`) === + this.get(`styles.default_${fieldName}`) + ); + }, + + @discourseComputed("styles", "fieldName") + editorContents: { + get(styles, fieldName) { + return styles[fieldName]; + }, + set(value, styles, fieldName) { + styles.setField(fieldName, value); + return value; + } + }, + + actions: { + reset() { + bootbox.confirm( + I18n.t("admin.customize.email_style.reset_confirm", { + fieldName: I18n.t(`admin.customize.email_style.${this.fieldName}`) + }), + I18n.t("no_value"), + I18n.t("yes_value"), + result => { + if (result) { + this.styles.setField( + this.fieldName, + this.styles.get(`default_${this.fieldName}`) + ); + this.notifyPropertyChange("editorContents"); + } + } + ); + } + } +}); diff --git a/app/assets/javascripts/admin/components/embeddable-host.js.es6 b/app/assets/javascripts/admin/components/embeddable-host.js.es6 index 34d46ac181..1d853b8986 100644 --- a/app/assets/javascripts/admin/components/embeddable-host.js.es6 +++ b/app/assets/javascripts/admin/components/embeddable-host.js.es6 @@ -1,26 +1,31 @@ +import discourseComputed from "discourse-common/utils/decorators"; +import { isEmpty } from "@ember/utils"; +import { or } from "@ember/object/computed"; +import { schedule } from "@ember/runloop"; +import Component from "@ember/component"; import { bufferedProperty } from "discourse/mixins/buffered-content"; -import computed from "ember-addons/ember-computed-decorators"; -import { on, observes } from "ember-addons/ember-computed-decorators"; +import { on, observes } from "discourse-common/utils/decorators"; import { popupAjaxError } from "discourse/lib/ajax-error"; +import Category from "discourse/models/category"; -export default Ember.Component.extend(bufferedProperty("host"), { +export default Component.extend(bufferedProperty("host"), { editToggled: false, tagName: "tr", categoryId: null, - editing: Ember.computed.or("host.isNew", "editToggled"), + editing: or("host.isNew", "editToggled"), @on("didInsertElement") @observes("editing") _focusOnInput() { - Ember.run.schedule("afterRender", () => { - this.$(".host-name").focus(); + schedule("afterRender", () => { + this.element.querySelector(".host-name").focus(); }); }, - @computed("buffered.host", "host.isSaving") + @discourseComputed("buffered.host", "host.isSaving") cantSave(host, isSaving) { - return isSaving || Ember.isEmpty(host); + return isSaving || isEmpty(host); }, actions: { @@ -46,7 +51,7 @@ export default Ember.Component.extend(bufferedProperty("host"), { host .save(props) .then(() => { - host.set("category", Discourse.Category.findById(this.categoryId)); + host.set("category", Category.findById(this.categoryId)); this.set("editToggled", false); }) .catch(popupAjaxError); diff --git a/app/assets/javascripts/admin/components/embedding-setting.js.es6 b/app/assets/javascripts/admin/components/embedding-setting.js.es6 index 4791e84e35..517e37f4f9 100644 --- a/app/assets/javascripts/admin/components/embedding-setting.js.es6 +++ b/app/assets/javascripts/admin/components/embedding-setting.js.es6 @@ -1,24 +1,25 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import Component from "@ember/component"; -export default Ember.Component.extend({ +export default Component.extend({ classNames: ["embed-setting"], - @computed("field") + @discourseComputed("field") inputId(field) { return field.dasherize(); }, - @computed("field") + @discourseComputed("field") translationKey(field) { return `admin.embedding.${field}`; }, - @computed("type") + @discourseComputed("type") isCheckbox(type) { return type === "checkbox"; }, - @computed("value") + @discourseComputed("value") checked: { get(value) { return !!value; diff --git a/app/assets/javascripts/admin/components/flag-user-lists.js.es6 b/app/assets/javascripts/admin/components/flag-user-lists.js.es6 index ae6094c6a7..a6156a93ad 100644 --- a/app/assets/javascripts/admin/components/flag-user-lists.js.es6 +++ b/app/assets/javascripts/admin/components/flag-user-lists.js.es6 @@ -1,3 +1,4 @@ -export default Ember.Component.extend({ +import Component from "@ember/component"; +export default Component.extend({ classNames: ["flag-user-lists"] }); diff --git a/app/assets/javascripts/admin/components/highlighted-code.js.es6 b/app/assets/javascripts/admin/components/highlighted-code.js.es6 index 62cf58f21b..9159bb574a 100644 --- a/app/assets/javascripts/admin/components/highlighted-code.js.es6 +++ b/app/assets/javascripts/admin/components/highlighted-code.js.es6 @@ -1,10 +1,11 @@ -import { on, observes } from "ember-addons/ember-computed-decorators"; +import Component from "@ember/component"; +import { on, observes } from "discourse-common/utils/decorators"; import highlightSyntax from "discourse/lib/highlight-syntax"; -export default Ember.Component.extend({ +export default Component.extend({ @on("didInsertElement") @observes("code") _refresh: function() { - highlightSyntax(this.$()); + highlightSyntax($(this.element)); } }); diff --git a/app/assets/javascripts/admin/components/inline-edit-checkbox.js.es6 b/app/assets/javascripts/admin/components/inline-edit-checkbox.js.es6 index a31b436167..2eb2c3ee5a 100644 --- a/app/assets/javascripts/admin/components/inline-edit-checkbox.js.es6 +++ b/app/assets/javascripts/admin/components/inline-edit-checkbox.js.es6 @@ -1,9 +1,7 @@ -import { - default as computed, - observes -} from "ember-addons/ember-computed-decorators"; +import Component from "@ember/component"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; -export default Ember.Component.extend({ +export default Component.extend({ classNames: ["inline-edit"], checked: null, @@ -20,12 +18,12 @@ export default Ember.Component.extend({ this.set("checkedInternal", this.checked); }, - @computed("labelKey") + @discourseComputed("labelKey") label(key) { return I18n.t(key); }, - @computed("checked", "checkedInternal") + @discourseComputed("checked", "checkedInternal") changed(checked, checkedInternal) { return !!checked !== !!checkedInternal; }, diff --git a/app/assets/javascripts/admin/components/install-theme-item.js.es6 b/app/assets/javascripts/admin/components/install-theme-item.js.es6 index c1f3f57a8c..040760db01 100644 --- a/app/assets/javascripts/admin/components/install-theme-item.js.es6 +++ b/app/assets/javascripts/admin/components/install-theme-item.js.es6 @@ -1,3 +1,4 @@ -export default Ember.Component.extend({ +import Component from "@ember/component"; +export default Component.extend({ classNames: ["install-theme-item"] }); diff --git a/app/assets/javascripts/admin/components/ip-lookup.js.es6 b/app/assets/javascripts/admin/components/ip-lookup.js.es6 index d4bf91ec6a..b27191d617 100644 --- a/app/assets/javascripts/admin/components/ip-lookup.js.es6 +++ b/app/assets/javascripts/admin/components/ip-lookup.js.es6 @@ -1,12 +1,15 @@ -import { default as computed } from "ember-addons/ember-computed-decorators"; +import EmberObject from "@ember/object"; +import { later } from "@ember/runloop"; +import Component from "@ember/component"; +import discourseComputed from "discourse-common/utils/decorators"; import { ajax } from "discourse/lib/ajax"; import AdminUser from "admin/models/admin-user"; import copyText from "discourse/lib/copy-text"; -export default Ember.Component.extend({ +export default Component.extend({ classNames: ["ip-lookup"], - @computed("other_accounts.length", "totalOthersWithSameIP") + @discourseComputed("other_accounts.length", "totalOthersWithSameIP") otherAccountsToDelete(otherAccountsLength, totalOthersWithSameIP) { // can only delete up to 50 accounts at a time const total = Math.min(50, totalOthersWithSameIP || 0); @@ -20,7 +23,7 @@ export default Ember.Component.extend({ if (!this.location) { ajax("/admin/users/ip-info", { data: { ip: this.ip } }).then(location => - this.set("location", Ember.Object.create(location)) + this.set("location", EmberObject.create(location)) ); } @@ -76,7 +79,7 @@ export default Ember.Component.extend({ $(document.body).append($copyRange); if (copyText(text, $copyRange[0])) { this.set("copied", true); - Ember.run.later(() => this.set("copied", false), 2000); + later(() => this.set("copied", false), 2000); } $copyRange.remove(); }, diff --git a/app/assets/javascripts/admin/components/moderation-history-item.js.es6 b/app/assets/javascripts/admin/components/moderation-history-item.js.es6 index b7620b66cd..b644dbab9f 100644 --- a/app/assets/javascripts/admin/components/moderation-history-item.js.es6 +++ b/app/assets/javascripts/admin/components/moderation-history-item.js.es6 @@ -1,3 +1,4 @@ -export default Ember.Component.extend({ +import Component from "@ember/component"; +export default Component.extend({ tagName: "tr" }); diff --git a/app/assets/javascripts/admin/components/penalty-post-action.js.es6 b/app/assets/javascripts/admin/components/penalty-post-action.js.es6 index 543a8baf3c..09f267390c 100644 --- a/app/assets/javascripts/admin/components/penalty-post-action.js.es6 +++ b/app/assets/javascripts/admin/components/penalty-post-action.js.es6 @@ -1,32 +1,33 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import { equal } from "@ember/object/computed"; +import { scheduleOnce } from "@ember/runloop"; +import Component from "@ember/component"; const ACTIONS = ["delete", "delete_replies", "edit", "none"]; -export default Ember.Component.extend({ +export default Component.extend({ postId: null, postAction: null, postEdit: null, - @computed + @discourseComputed penaltyActions() { return ACTIONS.map(id => { return { id, name: I18n.t(`admin.user.penalty_post_${id}`) }; }); }, - editing: Ember.computed.equal("postAction", "edit"), + editing: equal("postAction", "edit"), actions: { penaltyChanged() { - let postAction = this.postAction; - // If we switch to edit mode, jump to the edit textarea - if (postAction === "edit") { - Ember.run.scheduleOnce("afterRender", () => { - let $elem = this.$(); - let body = $elem.closest(".modal-body"); + if (this.postAction === "edit") { + scheduleOnce("afterRender", () => { + const elem = this.element; + const body = elem.closest(".modal-body"); body.scrollTop(body.height()); - $elem.find(".post-editor").focus(); + elem.querySelector(".post-editor").focus(); }); } } diff --git a/app/assets/javascripts/admin/components/permalink-form.js.es6 b/app/assets/javascripts/admin/components/permalink-form.js.es6 index bce150f8c9..e1b62a11df 100644 --- a/app/assets/javascripts/admin/components/permalink-form.js.es6 +++ b/app/assets/javascripts/admin/components/permalink-form.js.es6 @@ -1,14 +1,16 @@ -import { default as computed } from "ember-addons/ember-computed-decorators"; +import { schedule } from "@ember/runloop"; +import Component from "@ember/component"; +import discourseComputed from "discourse-common/utils/decorators"; import { fmt } from "discourse/lib/computed"; import Permalink from "admin/models/permalink"; -export default Ember.Component.extend({ +export default Component.extend({ classNames: ["permalink-form"], formSubmitted: false, permalinkType: "topic_id", permalinkTypePlaceholder: fmt("permalinkType", "admin.permalink.%@"), - @computed + @discourseComputed permalinkTypes() { return [ { id: "topic_id", name: I18n.t("admin.permalink.topic_id") }, @@ -18,8 +20,23 @@ export default Ember.Component.extend({ ]; }, + didInsertElement() { + this._super(...arguments); + + schedule("afterRender", () => { + $(this.element.querySelector(".external-url")).keydown(e => { + // enter key + if (e.keyCode === 13) { + this.send("submit"); + } + }); + }); + }, + focusPermalink() { - Ember.run.schedule("afterRender", () => this.$(".permalink-url").focus()); + schedule("afterRender", () => + this.element.querySelector(".permalink-url").focus() + ); }, actions: { @@ -60,19 +77,10 @@ export default Ember.Component.extend({ } ); } + }, + + onChangePermalinkType(type) { + this.set("permalinkType", type); } - }, - - didInsertElement() { - this._super(...arguments); - - Ember.run.schedule("afterRender", () => { - this.$(".external-url").keydown(e => { - // enter key - if (e.keyCode === 13) { - this.send("submit"); - } - }); - }); } }); diff --git a/app/assets/javascripts/admin/components/report-filters/category.js.es6 b/app/assets/javascripts/admin/components/report-filters/category.js.es6 index 7efdd4a465..fab7753da4 100644 --- a/app/assets/javascripts/admin/components/report-filters/category.js.es6 +++ b/app/assets/javascripts/admin/components/report-filters/category.js.es6 @@ -1,5 +1,4 @@ -import Category from "discourse/models/category"; -import { default as computed } from "ember-addons/ember-computed-decorators"; +import { readOnly } from "@ember/object/computed"; import FilterComponent from "admin/components/report-filters/filter"; export default FilterComponent.extend({ @@ -7,14 +6,11 @@ export default FilterComponent.extend({ layoutName: "admin/templates/components/report-filters/category", - @computed("filter.default") - category(categoryId) { - return Category.findById(categoryId); - }, + category: readOnly("filter.default"), actions: { - onDeselect() { - this.applyFilter(this.get("filter.id"), undefined); + onChange(categoryId) { + this.applyFilter(this.get("filter.id"), categoryId || undefined); } } }); diff --git a/app/assets/javascripts/admin/components/report-filters/filter.js.es6 b/app/assets/javascripts/admin/components/report-filters/filter.js.es6 index 25f2464f95..f61b2d496a 100644 --- a/app/assets/javascripts/admin/components/report-filters/filter.js.es6 +++ b/app/assets/javascripts/admin/components/report-filters/filter.js.es6 @@ -1,4 +1,5 @@ -export default Ember.Component.extend({ +import Component from "@ember/component"; +export default Component.extend({ actions: { onChange(value) { this.applyFilter(this.get("filter.id"), value); diff --git a/app/assets/javascripts/admin/components/report-filters/group.js.es6 b/app/assets/javascripts/admin/components/report-filters/group.js.es6 index 54523f9446..a6511e75f6 100644 --- a/app/assets/javascripts/admin/components/report-filters/group.js.es6 +++ b/app/assets/javascripts/admin/components/report-filters/group.js.es6 @@ -1,19 +1,19 @@ import FilterComponent from "admin/components/report-filters/filter"; -import { default as computed } from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; export default FilterComponent.extend({ classNames: ["group-filter"], layoutName: "admin/templates/components/report-filters/group", - @computed() + @discourseComputed() groupOptions() { return (this.site.groups || []).map(group => { return { name: group["name"], value: group["id"] }; }); }, - @computed("filter.default") + @discourseComputed("filter.default") groupId(filterDefault) { return filterDefault ? parseInt(filterDefault, 10) : null; } diff --git a/app/assets/javascripts/admin/components/resumable-upload.js.es6 b/app/assets/javascripts/admin/components/resumable-upload.js.es6 index b9cab23040..830e560252 100644 --- a/app/assets/javascripts/admin/components/resumable-upload.js.es6 +++ b/app/assets/javascripts/admin/components/resumable-upload.js.es6 @@ -1,9 +1,8 @@ +import { schedule } from "@ember/runloop"; +import { later } from "@ember/runloop"; +import Component from "@ember/component"; import { iconHTML } from "discourse-common/lib/icon-library"; -import { bufferedRender } from "discourse-common/lib/buffered-render"; -import { - default as computed, - on -} from "ember-addons/ember-computed-decorators"; +import discourseComputed, { on } from "discourse-common/utils/decorators"; /*global Resumable:true */ @@ -17,114 +16,124 @@ import { uploadText="UPLOAD" }} **/ -export default Ember.Component.extend( - bufferedRender({ - tagName: "button", - classNames: ["btn", "ru"], - classNameBindings: ["isUploading"], - attributeBindings: ["translatedTitle:title"], - resumable: null, - isUploading: false, - progress: 0, - rerenderTriggers: ["isUploading", "progress"], +export default Component.extend({ + tagName: "button", + classNames: ["btn", "ru"], + classNameBindings: ["isUploading"], + attributeBindings: ["translatedTitle:title"], + resumable: null, + isUploading: false, + progress: 0, + rerenderTriggers: ["isUploading", "progress"], + uploadingIcon: null, + progressBar: null, - @on("init") - _initialize() { - this.resumable = new Resumable({ - target: Discourse.getURL(this.target), - maxFiles: 1, // only 1 file at a time - headers: { - "X-CSRF-Token": document.querySelector("meta[name='csrf-token']") - .content - } - }); - - this.resumable.on("fileAdded", () => { - // automatically upload the selected file - this.resumable.upload(); - - // mark as uploading - Ember.run.later(() => this.set("isUploading", true)); - }); - - this.resumable.on("fileProgress", file => { - // update progress - Ember.run.later(() => - this.set("progress", parseInt(file.progress() * 100, 10)) - ); - }); - - this.resumable.on("fileSuccess", file => { - Ember.run.later(() => { - // mark as not uploading anymore - this._reset(); - - // fire an event to allow the parent route to reload its model - this.success(file.fileName); - }); - }); - - this.resumable.on("fileError", (file, message) => { - Ember.run.later(() => { - // mark as not uploading anymore - this._reset(); - - // fire an event to allow the parent route to display the error message - this.error(file.fileName, message); - }); - }); - }, - - @on("didInsertElement") - _assignBrowse() { - Ember.run.schedule("afterRender", () => - this.resumable.assignBrowse($(this.element)) - ); - }, - - @on("willDestroyElement") - _teardown() { - if (this.resumable) { - this.resumable.cancel(); - this.resumable = null; + @on("init") + _initialize() { + this.resumable = new Resumable({ + target: Discourse.getURL(this.target), + maxFiles: 1, // only 1 file at a time + headers: { + "X-CSRF-Token": document.querySelector("meta[name='csrf-token']") + .content } - }, + }); - @computed("title", "text") - translatedTitle(title, text) { - return title ? I18n.t(title) : text; - }, + this.resumable.on("fileAdded", () => { + // automatically upload the selected file + this.resumable.upload(); - @computed("isUploading", "progress") - text(isUploading, progress) { - if (isUploading) { - return progress + " %"; - } else { - return this.uploadText; - } - }, + // mark as uploading + later(() => { + this.set("isUploading", true); + this._updateIcon(); + }); + }); - buildBuffer(buffer) { - const icon = this.isUploading ? "times" : "upload"; - buffer.push(iconHTML(icon)); - buffer.push("" + this.text + ""); - buffer.push( - "" - ); - }, + this.resumable.on("fileProgress", file => { + // update progress + later(() => { + this.set("progress", parseInt(file.progress() * 100, 10)); + this._updateProgressBar(); + }); + }); - click() { - if (this.isUploading) { - this.resumable.cancel(); - Ember.run.later(() => this._reset()); - return false; - } else { - return true; - } - }, + this.resumable.on("fileSuccess", file => { + later(() => { + // mark as not uploading anymore + this._reset(); - _reset() { - this.setProperties({ isUploading: false, progress: 0 }); + // fire an event to allow the parent route to reload its model + this.success(file.fileName); + }); + }); + + this.resumable.on("fileError", (file, message) => { + later(() => { + // mark as not uploading anymore + this._reset(); + + // fire an event to allow the parent route to display the error message + this.error(file.fileName, message); + }); + }); + }, + + @on("didInsertElement") + _assignBrowse() { + schedule("afterRender", () => this.resumable.assignBrowse($(this.element))); + }, + + @on("willDestroyElement") + _teardown() { + if (this.resumable) { + this.resumable.cancel(); + this.resumable = null; } - }) -); + }, + + @discourseComputed("title", "text") + translatedTitle(title, text) { + return title ? I18n.t(title) : text; + }, + + @discourseComputed("isUploading", "progress") + text(isUploading, progress) { + if (isUploading) { + return progress + " %"; + } else { + return this.uploadText; + } + }, + + didReceiveAttrs() { + this._super(...arguments); + this._updateIcon(); + }, + + click() { + if (this.isUploading) { + this.resumable.cancel(); + later(() => this._reset()); + return false; + } else { + return true; + } + }, + + _updateIcon() { + const icon = this.isUploading ? "times" : "upload"; + this.set("uploadingIcon", `${iconHTML(icon)}`.htmlSafe()); + }, + + _updateProgressBar() { + const pb = `${"width:" + this.progress + "%"}`.htmlSafe(); + this.set("progressBar", pb); + }, + + _reset() { + this.setProperties({ isUploading: false, progress: 0 }); + this._updateIcon(); + this._updateProgressBar(); + } +}); diff --git a/app/assets/javascripts/admin/components/save-controls.js.es6 b/app/assets/javascripts/admin/components/save-controls.js.es6 index cade010e5b..9da4e49fe2 100644 --- a/app/assets/javascripts/admin/components/save-controls.js.es6 +++ b/app/assets/javascripts/admin/components/save-controls.js.es6 @@ -1,11 +1,13 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import { or } from "@ember/object/computed"; +import Component from "@ember/component"; -export default Ember.Component.extend({ +export default Component.extend({ classNames: ["controls"], - buttonDisabled: Ember.computed.or("model.isSaving", "saveDisabled"), + buttonDisabled: or("model.isSaving", "saveDisabled"), - @computed("model.isSaving") + @discourseComputed("model.isSaving") savingText(saving) { return saving ? "saving" : "save"; } diff --git a/app/assets/javascripts/admin/components/screened-ip-address-form.js.es6 b/app/assets/javascripts/admin/components/screened-ip-address-form.js.es6 index 5573374a24..48b92641b4 100644 --- a/app/assets/javascripts/admin/components/screened-ip-address-form.js.es6 +++ b/app/assets/javascripts/admin/components/screened-ip-address-form.js.es6 @@ -1,3 +1,6 @@ +import discourseComputed from "discourse-common/utils/decorators"; +import { schedule } from "@ember/runloop"; +import Component from "@ember/component"; /** A form to create an IP address that will be blocked or whitelisted. Example usage: @@ -10,20 +13,19 @@ **/ import ScreenedIpAddress from "admin/models/screened-ip-address"; -import computed from "ember-addons/ember-computed-decorators"; -import { on } from "ember-addons/ember-computed-decorators"; +import { on } from "discourse-common/utils/decorators"; -export default Ember.Component.extend({ +export default Component.extend({ classNames: ["screened-ip-address-form"], formSubmitted: false, actionName: "block", - @computed + @discourseComputed adminWhitelistEnabled() { return Discourse.SiteSettings.use_admin_ip_whitelist; }, - @computed("adminWhitelistEnabled") + @discourseComputed("adminWhitelistEnabled") actionNames(adminWhitelistEnabled) { if (adminWhitelistEnabled) { return [ @@ -61,8 +63,8 @@ export default Ember.Component.extend({ .then(result => { this.setProperties({ ip_address: "", formSubmitted: false }); this.action(ScreenedIpAddress.create(result.screened_ip_address)); - Ember.run.schedule("afterRender", () => - this.$(".ip-address-input").focus() + schedule("afterRender", () => + this.element.querySelector(".ip-address-input").focus() ); }) .catch(e => { @@ -73,7 +75,9 @@ export default Ember.Component.extend({ error: e.jqXHR.responseJSON.errors.join(". ") }) : I18n.t("generic_error"); - bootbox.alert(msg, () => this.$(".ip-address-input").focus()); + bootbox.alert(msg, () => + this.element.querySelector(".ip-address-input").focus() + ); }); } } @@ -81,8 +85,8 @@ export default Ember.Component.extend({ @on("didInsertElement") _init() { - Ember.run.schedule("afterRender", () => { - this.$(".ip-address-input").keydown(e => { + schedule("afterRender", () => { + $(this.element.querySelector(".ip-address-input")).keydown(e => { if (e.keyCode === 13) { this.send("submit"); } diff --git a/app/assets/javascripts/admin/components/secret-value-list.js.es6 b/app/assets/javascripts/admin/components/secret-value-list.js.es6 index 58539cb916..ea4ecf792c 100644 --- a/app/assets/javascripts/admin/components/secret-value-list.js.es6 +++ b/app/assets/javascripts/admin/components/secret-value-list.js.es6 @@ -1,6 +1,9 @@ -import { on } from "ember-addons/ember-computed-decorators"; +import { isEmpty } from "@ember/utils"; +import Component from "@ember/component"; +import { on } from "discourse-common/utils/decorators"; +import { set } from "@ember/object"; -export default Ember.Component.extend({ +export default Component.extend({ classNameBindings: [":value-list", ":secret-value-list"], inputDelimiter: null, collection: null, @@ -42,7 +45,7 @@ export default Ember.Component.extend({ _checkInvalidInput(inputs) { this.set("validationMessage", null); for (let input of inputs) { - if (Ember.isEmpty(input) || input.includes("|")) { + if (isEmpty(input) || input.includes("|")) { this.set( "validationMessage", I18n.t("admin.site_settings.secret_list.invalid_input") @@ -65,7 +68,7 @@ export default Ember.Component.extend({ _replaceValue(index, newValue, keyName) { let item = this.collection[index]; - Ember.set(item, keyName, newValue); + set(item, keyName, newValue); this._saveValues(); }, diff --git a/app/assets/javascripts/admin/components/silence-details.js.es6 b/app/assets/javascripts/admin/components/silence-details.js.es6 index 91ad923ffc..89720fbbe8 100644 --- a/app/assets/javascripts/admin/components/silence-details.js.es6 +++ b/app/assets/javascripts/admin/components/silence-details.js.es6 @@ -1,3 +1,4 @@ -export default Ember.Component.extend({ +import Component from "@ember/component"; +export default Component.extend({ tagName: "" }); diff --git a/app/assets/javascripts/admin/components/site-setting.js.es6 b/app/assets/javascripts/admin/components/site-setting.js.es6 index 78696c6e06..cd768407ff 100644 --- a/app/assets/javascripts/admin/components/site-setting.js.es6 +++ b/app/assets/javascripts/admin/components/site-setting.js.es6 @@ -1,10 +1,15 @@ +import Component from "@ember/component"; import BufferedContent from "discourse/mixins/buffered-content"; import SiteSetting from "admin/models/site-setting"; import SettingComponent from "admin/mixins/setting-component"; -export default Ember.Component.extend(BufferedContent, SettingComponent, { +export default Component.extend(BufferedContent, SettingComponent, { + updateExistingUsers: null, + _save() { const setting = this.buffered; - return SiteSetting.update(setting.get("setting"), setting.get("value")); + return SiteSetting.update(setting.get("setting"), setting.get("value"), { + updateExistingUsers: this.updateExistingUsers + }); } }); diff --git a/app/assets/javascripts/admin/components/site-settings/bool.js.es6 b/app/assets/javascripts/admin/components/site-settings/bool.js.es6 index f46e965832..88f4387601 100644 --- a/app/assets/javascripts/admin/components/site-settings/bool.js.es6 +++ b/app/assets/javascripts/admin/components/site-settings/bool.js.es6 @@ -1,10 +1,12 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import { isEmpty } from "@ember/utils"; +import Component from "@ember/component"; -export default Ember.Component.extend({ - @computed("value") +export default Component.extend({ + @discourseComputed("value") enabled: { get(value) { - if (Ember.isEmpty(value)) { + if (isEmpty(value)) { return false; } return value.toString() === "true"; diff --git a/app/assets/javascripts/admin/components/site-settings/category-list.js.es6 b/app/assets/javascripts/admin/components/site-settings/category-list.js.es6 index 36c712fa8d..24e9bb23b9 100644 --- a/app/assets/javascripts/admin/components/site-settings/category-list.js.es6 +++ b/app/assets/javascripts/admin/components/site-settings/category-list.js.es6 @@ -1,14 +1,15 @@ -import computed from "ember-addons/ember-computed-decorators"; +import Component from "@ember/component"; +import Category from "discourse/models/category"; +import { computed } from "@ember/object"; -export default Ember.Component.extend({ - @computed("value") - selectedCategories: { - get(value) { - return Discourse.Category.findByIds(value.split("|")); - }, - set(value) { - this.set("value", value.mapBy("id").join("|")); - return value; +export default Component.extend({ + selectedCategories: computed("value", function() { + return Category.findByIds(this.value.split("|").filter(Boolean)); + }), + + actions: { + onChangeSelectedCategories(value) { + this.set("value", (value || []).mapBy("id").join("|")); } } }); diff --git a/app/assets/javascripts/admin/components/site-settings/compact-list.js.es6 b/app/assets/javascripts/admin/components/site-settings/compact-list.js.es6 new file mode 100644 index 0000000000..f68e78b572 --- /dev/null +++ b/app/assets/javascripts/admin/components/site-settings/compact-list.js.es6 @@ -0,0 +1,43 @@ +import Component from "@ember/component"; +import { computed } from "@ember/object"; +import { makeArray } from "discourse-common/lib/helpers"; + +export default Component.extend({ + tokenSeparator: "|", + + createdChoices: null, + + settingValue: computed("value", function() { + return this.value + .toString() + .split(this.tokenSeparator) + .filter(Boolean); + }), + + settingChoices: computed( + "settingValue", + "setting.choices.[]", + "createdChoices.[]", + function() { + return [ + ...new Set([ + ...makeArray(this.settingValue), + ...makeArray(this.setting.choices), + ...makeArray(this.createdChoices) + ]) + ]; + } + ), + + actions: { + onChangeListSetting(value) { + this.set("value", value.join(this.tokenSeparator)); + }, + + onChangeChoices(choices) { + this.set("createdChoices", [ + ...new Set([...makeArray(this.createdChoices), ...makeArray(choices)]) + ]); + } + } +}); diff --git a/app/assets/javascripts/admin/components/site-settings/group-list.js.es6 b/app/assets/javascripts/admin/components/site-settings/group-list.js.es6 index 4e60f1da08..41dcdbcb4d 100644 --- a/app/assets/javascripts/admin/components/site-settings/group-list.js.es6 +++ b/app/assets/javascripts/admin/components/site-settings/group-list.js.es6 @@ -1,8 +1,25 @@ -import computed from "ember-addons/ember-computed-decorators"; +import { computed } from "@ember/object"; +import Component from "@ember/component"; -export default Ember.Component.extend({ - @computed() - groupChoices() { - return this.site.get("groups").map(g => g.name); +export default Component.extend({ + tokenSeparator: "|", + + nameProperty: "name", + valueProperty: "id", + + groupChoices: computed("site.groups", function() { + return (this.site.groups || []).map(g => { + return { name: g.name, id: g.id.toString() }; + }); + }), + + settingValue: computed("value", function() { + return (this.value || "").split(this.tokenSeparator).filter(Boolean); + }), + + actions: { + onChangeGroupListSetting(value) { + this.set("value", value.join(this.tokenSeparator)); + } } }); diff --git a/app/assets/javascripts/admin/components/site-settings/tag-list.js.es6 b/app/assets/javascripts/admin/components/site-settings/tag-list.js.es6 new file mode 100644 index 0000000000..c8a8e0a06f --- /dev/null +++ b/app/assets/javascripts/admin/components/site-settings/tag-list.js.es6 @@ -0,0 +1,15 @@ +import discourseComputed from "discourse-common/utils/decorators"; +import Component from "@ember/component"; + +export default Component.extend({ + @discourseComputed("value") + selectedTags: { + get(value) { + return value.split("|"); + }, + set(value) { + this.set("value", value.join("|")); + return value; + } + } +}); diff --git a/app/assets/javascripts/admin/components/site-settings/uploaded-image-list.js.es6 b/app/assets/javascripts/admin/components/site-settings/uploaded-image-list.js.es6 index 57bd7fa49b..7e705321d0 100644 --- a/app/assets/javascripts/admin/components/site-settings/uploaded-image-list.js.es6 +++ b/app/assets/javascripts/admin/components/site-settings/uploaded-image-list.js.es6 @@ -1,6 +1,7 @@ +import Component from "@ember/component"; import showModal from "discourse/lib/show-modal"; -export default Ember.Component.extend({ +export default Component.extend({ actions: { showUploadModal({ value, setting }) { showModal("admin-uploaded-image-list", { diff --git a/app/assets/javascripts/admin/components/site-text-summary.js.es6 b/app/assets/javascripts/admin/components/site-text-summary.js.es6 index 003ff2ffef..11c6bc45eb 100644 --- a/app/assets/javascripts/admin/components/site-text-summary.js.es6 +++ b/app/assets/javascripts/admin/components/site-text-summary.js.es6 @@ -1,6 +1,7 @@ -import { on } from "ember-addons/ember-computed-decorators"; +import Component from "@ember/component"; +import { on } from "discourse-common/utils/decorators"; -export default Ember.Component.extend({ +export default Component.extend({ classNames: ["site-text"], classNameBindings: ["siteText.overridden"], @@ -9,11 +10,13 @@ export default Ember.Component.extend({ const term = this._searchTerm(); if (term) { - this.$(".site-text-id, .site-text-value").highlight(term, { + $( + this.element.querySelector(".site-text-id, .site-text-value") + ).highlight(term, { className: "text-highlight" }); } - this.$(".site-text-value").ellipsis(); + $(this.element.querySelector(".site-text-value")).ellipsis(); }, click() { diff --git a/app/assets/javascripts/admin/components/staff-actions.js.es6 b/app/assets/javascripts/admin/components/staff-actions.js.es6 index 3a979b0290..1c295799dd 100644 --- a/app/assets/javascripts/admin/components/staff-actions.js.es6 +++ b/app/assets/javascripts/admin/components/staff-actions.js.es6 @@ -1,22 +1,27 @@ +import Component from "@ember/component"; import DiscourseURL from "discourse/lib/url"; -export default Ember.Component.extend({ +export default Component.extend({ classNames: ["table", "staff-actions"], willDestroyElement() { - this.$().off("click.discourse-staff-logs"); + $(this.element).off("click.discourse-staff-logs"); }, didInsertElement() { this._super(...arguments); - this.$().on("click.discourse-staff-logs", "[data-link-post-id]", e => { - let postId = $(e.target).attr("data-link-post-id"); + $(this.element).on( + "click.discourse-staff-logs", + "[data-link-post-id]", + e => { + let postId = $(e.target).attr("data-link-post-id"); - this.store.find("post", postId).then(p => { - DiscourseURL.routeTo(p.get("url")); - }); - return false; - }); + this.store.find("post", postId).then(p => { + DiscourseURL.routeTo(p.get("url")); + }); + return false; + } + ); } }); diff --git a/app/assets/javascripts/admin/components/suspension-details.js.es6 b/app/assets/javascripts/admin/components/suspension-details.js.es6 index 91ad923ffc..89720fbbe8 100644 --- a/app/assets/javascripts/admin/components/suspension-details.js.es6 +++ b/app/assets/javascripts/admin/components/suspension-details.js.es6 @@ -1,3 +1,4 @@ -export default Ember.Component.extend({ +import Component from "@ember/component"; +export default Component.extend({ tagName: "" }); diff --git a/app/assets/javascripts/admin/components/tags-uploader.js.es6 b/app/assets/javascripts/admin/components/tags-uploader.js.es6 index 1373792f24..88f4afc8d9 100644 --- a/app/assets/javascripts/admin/components/tags-uploader.js.es6 +++ b/app/assets/javascripts/admin/components/tags-uploader.js.es6 @@ -1,9 +1,11 @@ +import { alias } from "@ember/object/computed"; +import Component from "@ember/component"; import UploadMixin from "discourse/mixins/upload"; -export default Ember.Component.extend(UploadMixin, { +export default Component.extend(UploadMixin, { type: "csv", uploadUrl: "/tags/upload", - addDisabled: Ember.computed.alias("uploading"), + addDisabled: alias("uploading"), elementId: "tag-uploader", validateUploadedFilesOptions() { diff --git a/app/assets/javascripts/admin/components/theme-setting-editor.js.es6 b/app/assets/javascripts/admin/components/theme-setting-editor.js.es6 index df9398aecc..ec87c4d84f 100644 --- a/app/assets/javascripts/admin/components/theme-setting-editor.js.es6 +++ b/app/assets/javascripts/admin/components/theme-setting-editor.js.es6 @@ -1,12 +1,20 @@ +import Component from "@ember/component"; import BufferedContent from "discourse/mixins/buffered-content"; import SettingComponent from "admin/mixins/setting-component"; +import { ajax } from "discourse/lib/ajax"; +import { url } from "discourse/lib/computed"; -export default Ember.Component.extend(BufferedContent, SettingComponent, { +export default Component.extend(BufferedContent, SettingComponent, { layoutName: "admin/templates/components/site-setting", + updateUrl: url("model.id", "/admin/themes/%@/setting"), + _save() { - return this.model.saveSettings( - this.get("setting.setting"), - this.get("buffered.value") - ); + return ajax(this.updateUrl, { + type: "PUT", + data: { + name: this.setting.setting, + value: this.get("buffered.value") + } + }); } }); diff --git a/app/assets/javascripts/admin/components/theme-setting-relatives-selector.js.es6 b/app/assets/javascripts/admin/components/theme-setting-relatives-selector.js.es6 new file mode 100644 index 0000000000..8ba638076c --- /dev/null +++ b/app/assets/javascripts/admin/components/theme-setting-relatives-selector.js.es6 @@ -0,0 +1,26 @@ +import Component from "@ember/component"; +import BufferedContent from "discourse/mixins/buffered-content"; +import SettingComponent from "admin/mixins/setting-component"; + +export default Component.extend(BufferedContent, SettingComponent, { + layoutName: "admin/templates/components/site-setting", + + _save() { + return this.model + .save({ [this.setting.setting]: this.convertNamesToIds() }) + .then(() => this.store.findAll("theme")); + }, + + convertNamesToIds() { + return this.get("buffered.value") + .split("|") + .filter(Boolean) + .map(themeName => { + if (themeName !== "") { + return this.setting.allThemes.find(theme => theme.name === themeName) + .id; + } + return themeName; + }); + } +}); diff --git a/app/assets/javascripts/admin/components/theme-translation.js.es6 b/app/assets/javascripts/admin/components/theme-translation.js.es6 index ab29ac2312..361df489af 100644 --- a/app/assets/javascripts/admin/components/theme-translation.js.es6 +++ b/app/assets/javascripts/admin/components/theme-translation.js.es6 @@ -1,11 +1,13 @@ +import { alias } from "@ember/object/computed"; +import Component from "@ember/component"; import BufferedContent from "discourse/mixins/buffered-content"; import SettingComponent from "admin/mixins/setting-component"; -export default Ember.Component.extend(BufferedContent, SettingComponent, { +export default Component.extend(BufferedContent, SettingComponent, { layoutName: "admin/templates/components/site-setting", - setting: Ember.computed.alias("translation"), + setting: alias("translation"), type: "string", - settingName: Ember.computed.alias("translation.key"), + settingName: alias("translation.key"), _save() { return this.model.saveTranslation( diff --git a/app/assets/javascripts/admin/components/themes-list-item.js.es6 b/app/assets/javascripts/admin/components/themes-list-item.js.es6 index 5043f6f743..c292b80f99 100644 --- a/app/assets/javascripts/admin/components/themes-list-item.js.es6 +++ b/app/assets/javascripts/admin/components/themes-list-item.js.es6 @@ -1,17 +1,20 @@ -import { - default as computed, - observes -} from "ember-addons/ember-computed-decorators"; +import { gt, and } from "@ember/object/computed"; +import { schedule } from "@ember/runloop"; +import Component from "@ember/component"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; +import { iconHTML } from "discourse-common/lib/icon-library"; +import { escape } from "pretty-text/sanitizer"; +import ENV from "discourse-common/config/environment"; const MAX_COMPONENTS = 4; -export default Ember.Component.extend({ +export default Component.extend({ childrenExpanded: false, classNames: ["themes-list-item"], classNameBindings: ["theme.selected:selected"], - hasComponents: Ember.computed.gt("children.length", 0), - displayComponents: Ember.computed.and("hasComponents", "theme.isActive"), - displayHasMore: Ember.computed.gt("theme.childThemes.length", MAX_COMPONENTS), + hasComponents: gt("children.length", 0), + displayComponents: and("hasComponents", "theme.isActive"), + displayHasMore: gt("theme.childThemes.length", MAX_COMPONENTS), click(e) { if (!$(e.target).hasClass("others-count")) { @@ -30,15 +33,15 @@ export default Ember.Component.extend({ }, scheduleAnimation() { - Ember.run.schedule("afterRender", () => { + schedule("afterRender", () => { this.animate(true); }); }, animate(isInitial) { - const $container = this.$(); - const $list = this.$(".components-list"); - if ($list.length === 0 || Ember.testing) { + const $container = $(this.element); + const $list = $(this.element.querySelector(".components-list")); + if ($list.length === 0 || ENV.environment === "test") { return; } const duration = 300; @@ -49,7 +52,7 @@ export default Ember.Component.extend({ } }, - @computed( + @discourseComputed( "theme.component", "theme.childThemes.@each.name", "theme.childThemes.length", @@ -64,15 +67,18 @@ export default Ember.Component.extend({ children = this.childrenExpanded ? children : children.slice(0, MAX_COMPONENTS); - return children.map(t => t.get("name")); + return children.map(t => { + const name = escape(t.name); + return t.enabled ? name : `${iconHTML("ban")} ${name}`; + }); }, - @computed("children") + @discourseComputed("children") childrenString(children) { return children.join(", "); }, - @computed( + @discourseComputed( "theme.childThemes.length", "theme.component", "childrenExpanded", diff --git a/app/assets/javascripts/admin/components/themes-list.js.es6 b/app/assets/javascripts/admin/components/themes-list.js.es6 index d306bab11c..ae883a115d 100644 --- a/app/assets/javascripts/admin/components/themes-list.js.es6 +++ b/app/assets/javascripts/admin/components/themes-list.js.es6 @@ -1,20 +1,23 @@ +import { gt, equal } from "@ember/object/computed"; +import Component from "@ember/component"; import { THEMES, COMPONENTS } from "admin/models/theme"; -import { default as computed } from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import { getOwner } from "@ember/application"; -export default Ember.Component.extend({ +export default Component.extend({ THEMES: THEMES, COMPONENTS: COMPONENTS, classNames: ["themes-list"], - hasThemes: Ember.computed.gt("themesList.length", 0), - hasActiveThemes: Ember.computed.gt("activeThemes.length", 0), - hasInactiveThemes: Ember.computed.gt("inactiveThemes.length", 0), + hasThemes: gt("themesList.length", 0), + hasActiveThemes: gt("activeThemes.length", 0), + hasInactiveThemes: gt("inactiveThemes.length", 0), - themesTabActive: Ember.computed.equal("currentTab", THEMES), - componentsTabActive: Ember.computed.equal("currentTab", COMPONENTS), + themesTabActive: equal("currentTab", THEMES), + componentsTabActive: equal("currentTab", COMPONENTS), - @computed("themes", "components", "currentTab") + @discourseComputed("themes", "components", "currentTab") themesList(themes, components) { if (this.themesTabActive) { return themes; @@ -23,7 +26,7 @@ export default Ember.Component.extend({ } }, - @computed( + @discourseComputed( "themesList", "currentTab", "themesList.@each.user_selectable", @@ -38,7 +41,7 @@ export default Ember.Component.extend({ ); }, - @computed( + @discourseComputed( "themesList", "currentTab", "themesList.@each.user_selectable", @@ -68,7 +71,7 @@ export default Ember.Component.extend({ } }, navigateToTheme(theme) { - Ember.getOwner(this) + getOwner(this) .lookup("router:main") .transitionTo("adminCustomizeThemes.show", theme); } diff --git a/app/assets/javascripts/admin/components/value-list.js.es6 b/app/assets/javascripts/admin/components/value-list.js.es6 index ff93375260..4ab79389d2 100644 --- a/app/assets/javascripts/admin/components/value-list.js.es6 +++ b/app/assets/javascripts/admin/components/value-list.js.es6 @@ -1,17 +1,18 @@ -import { on } from "ember-addons/ember-computed-decorators"; -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import { makeArray } from "discourse-common/lib/helpers"; +import { empty, reads } from "@ember/object/computed"; +import Component from "@ember/component"; +import { on } from "discourse-common/utils/decorators"; -export default Ember.Component.extend({ +export default Component.extend({ classNameBindings: [":value-list"], - - inputInvalid: Ember.computed.empty("newValue"), - + inputInvalid: empty("newValue"), inputDelimiter: null, inputType: null, newValue: "", collection: null, values: null, - noneKey: Ember.computed.alias("addKey"), + noneKey: reads("addKey"), @on("didReceiveAttrs") _setupCollection() { @@ -27,9 +28,9 @@ export default Ember.Component.extend({ ); }, - @computed("choices.[]", "collection.[]") + @discourseComputed("choices.[]", "collection.[]") filteredChoices(choices, collection) { - return Ember.makeArray(choices).filter(i => collection.indexOf(i) < 0); + return makeArray(choices).filter(i => collection.indexOf(i) < 0); }, keyDown(event) { @@ -44,7 +45,7 @@ export default Ember.Component.extend({ addValue(newValue) { if (this.inputInvalid) return; - this.set("newValue", ""); + this.set("newValue", null); this._addValue(newValue); }, @@ -59,12 +60,25 @@ export default Ember.Component.extend({ _addValue(value) { this.collection.addObject(value); + + if (this.choices) { + this.set("choices", this.choices.rejectBy("id", value)); + } else { + this.set("choices", []); + } + this._saveValues(); }, _removeValue(value) { - const collection = this.collection; - collection.removeObject(value); + this.collection.removeObject(value); + + if (this.choices) { + this.set("choices", this.choices.concat([value]).uniq()); + } else { + this.set("choices", makeArray(value)); + } + this._saveValues(); }, diff --git a/app/assets/javascripts/admin/components/watched-word-form.js.es6 b/app/assets/javascripts/admin/components/watched-word-form.js.es6 index 837ce517b0..fff5b86e26 100644 --- a/app/assets/javascripts/admin/components/watched-word-form.js.es6 +++ b/app/assets/javascripts/admin/components/watched-word-form.js.es6 @@ -1,17 +1,19 @@ +import { isEmpty } from "@ember/utils"; +import { schedule } from "@ember/runloop"; +import Component from "@ember/component"; import WatchedWord from "admin/models/watched-word"; -import { - default as computed, +import discourseComputed, { on, observes -} from "ember-addons/ember-computed-decorators"; +} from "discourse-common/utils/decorators"; -export default Ember.Component.extend({ +export default Component.extend({ classNames: ["watched-word-form"], formSubmitted: false, actionKey: null, showMessage: false, - @computed("regularExpressions") + @discourseComputed("regularExpressions") placeholderKey(regularExpressions) { return ( "admin.watched_words.form.placeholder" + @@ -21,12 +23,12 @@ export default Ember.Component.extend({ @observes("word") removeMessage() { - if (this.showMessage && !Ember.isEmpty(this.word)) { + if (this.showMessage && !isEmpty(this.word)) { this.set("showMessage", false); } }, - @computed("word") + @discourseComputed("word") isUniqueWord(word) { const words = this.filteredContent || []; const filtered = words.filter(content => content.action === this.actionKey); @@ -63,8 +65,8 @@ export default Ember.Component.extend({ message: I18n.t("admin.watched_words.form.success") }); this.action(WatchedWord.create(result)); - Ember.run.schedule("afterRender", () => - this.$(".watched-word-input").focus() + schedule("afterRender", () => + this.element.querySelector(".watched-word-input").focus() ); }) .catch(e => { @@ -75,7 +77,9 @@ export default Ember.Component.extend({ error: e.jqXHR.responseJSON.errors.join(". ") }) : I18n.t("generic_error"); - bootbox.alert(msg, () => this.$(".watched-word-input").focus()); + bootbox.alert(msg, () => + this.element.querySelector(".watched-word-input").focus() + ); }); } } @@ -83,8 +87,8 @@ export default Ember.Component.extend({ @on("didInsertElement") _init() { - Ember.run.schedule("afterRender", () => { - this.$(".watched-word-input").keydown(e => { + schedule("afterRender", () => { + $(this.element.querySelector(".watched-word-input")).keydown(e => { if (e.keyCode === 13) { this.send("submit"); } diff --git a/app/assets/javascripts/admin/components/watched-word-uploader.js.es6 b/app/assets/javascripts/admin/components/watched-word-uploader.js.es6 index 5f047a2bc6..05dc41c207 100644 --- a/app/assets/javascripts/admin/components/watched-word-uploader.js.es6 +++ b/app/assets/javascripts/admin/components/watched-word-uploader.js.es6 @@ -1,17 +1,19 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import { alias } from "@ember/object/computed"; +import Component from "@ember/component"; import UploadMixin from "discourse/mixins/upload"; -export default Ember.Component.extend(UploadMixin, { - type: "csv", +export default Component.extend(UploadMixin, { + type: "txt", classNames: "watched-words-uploader", uploadUrl: "/admin/logs/watched_words/upload", - addDisabled: Ember.computed.alias("uploading"), + addDisabled: alias("uploading"), validateUploadedFilesOptions() { - return { csvOnly: true }; + return { skipValidation: true }; }, - @computed("actionKey") + @discourseComputed("actionKey") data(actionKey) { return { action_key: actionKey }; }, diff --git a/app/assets/javascripts/admin/controllers/admin-api-keys-index.js.es6 b/app/assets/javascripts/admin/controllers/admin-api-keys-index.js.es6 new file mode 100644 index 0000000000..99176791bf --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-api-keys-index.js.es6 @@ -0,0 +1,14 @@ +import { popupAjaxError } from "discourse/lib/ajax-error"; +import Controller from "@ember/controller"; + +export default Controller.extend({ + actions: { + revokeKey(key) { + key.revoke().catch(popupAjaxError); + }, + + undoRevokeKey(key) { + key.undoRevoke().catch(popupAjaxError); + } + } +}); diff --git a/app/assets/javascripts/admin/controllers/admin-api-keys-new.js.es6 b/app/assets/javascripts/admin/controllers/admin-api-keys-new.js.es6 new file mode 100644 index 0000000000..42391beaf8 --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-api-keys-new.js.es6 @@ -0,0 +1,39 @@ +import discourseComputed from "discourse-common/utils/decorators"; +import Controller from "@ember/controller"; +import { popupAjaxError } from "discourse/lib/ajax-error"; + +export default Controller.extend({ + userModes: [ + { id: "all", name: I18n.t("admin.api.all_users") }, + { id: "single", name: I18n.t("admin.api.single_user") } + ], + + @discourseComputed("userMode") + showUserSelector(mode) { + return mode === "single"; + }, + + @discourseComputed("model.description", "model.username", "userMode") + saveDisabled(description, username, userMode) { + if (Ember.isBlank(description)) return true; + if (userMode === "single" && Ember.isBlank(username)) return true; + return false; + }, + + actions: { + changeUserMode(value) { + if (value === "all") { + this.model.set("username", null); + } + this.set("userMode", value); + }, + + save() { + this.model.save().catch(popupAjaxError); + }, + + continue() { + this.transitionToRoute("adminApiKeys.show", this.model.id); + } + } +}); diff --git a/app/assets/javascripts/admin/controllers/admin-api-keys-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-api-keys-show.js.es6 new file mode 100644 index 0000000000..6927de87e7 --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-api-keys-show.js.es6 @@ -0,0 +1,56 @@ +import { bufferedProperty } from "discourse/mixins/buffered-content"; +import Controller from "@ember/controller"; +import { isEmpty } from "@ember/utils"; +import { popupAjaxError } from "discourse/lib/ajax-error"; +import { empty } from "@ember/object/computed"; + +export default Controller.extend(bufferedProperty("model"), { + isNew: empty("model.id"), + + actions: { + saveDescription() { + const buffered = this.buffered; + const attrs = buffered.getProperties("description"); + + this.model + .save(attrs) + .then(() => { + this.set("editingDescription", false); + this.rollbackBuffer(); + }) + .catch(popupAjaxError); + }, + + cancel() { + const id = this.get("userField.id"); + if (isEmpty(id)) { + this.destroyAction(this.userField); + } else { + this.rollbackBuffer(); + this.set("editing", false); + } + }, + + editDescription() { + this.toggleProperty("editingDescription"); + if (!this.editingDescription) { + this.rollbackBuffer(); + } + }, + + revokeKey(key) { + key.revoke().catch(popupAjaxError); + }, + + deleteKey(key) { + key + .destroyRecord() + .then(() => this.transitionToRoute("adminApiKeys.index")) + .catch(popupAjaxError); + }, + + undoRevokeKey(key) { + key.undoRevoke().catch(popupAjaxError); + } + } +}); diff --git a/app/assets/javascripts/admin/controllers/admin-api-keys.js.es6 b/app/assets/javascripts/admin/controllers/admin-api-keys.js.es6 index 87b26f5ef1..e69de29bb2 100644 --- a/app/assets/javascripts/admin/controllers/admin-api-keys.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-api-keys.js.es6 @@ -1,41 +0,0 @@ -import ApiKey from "admin/models/api-key"; -import { default as computed } from "ember-addons/ember-computed-decorators"; - -export default Ember.Controller.extend({ - @computed("model.[]") - hasMasterKey(model) { - return !!model.findBy("user", null); - }, - - actions: { - generateMasterKey() { - ApiKey.generateMasterKey().then(key => this.model.pushObject(key)); - }, - - regenerateKey(key) { - bootbox.confirm( - I18n.t("admin.api.confirm_regen"), - I18n.t("no_value"), - I18n.t("yes_value"), - result => { - if (result) { - key.regenerate(); - } - } - ); - }, - - revokeKey(key) { - bootbox.confirm( - I18n.t("admin.api.confirm_revoke"), - I18n.t("no_value"), - I18n.t("yes_value"), - result => { - if (result) { - key.revoke().then(() => this.model.removeObject(key)); - } - } - ); - } - } -}); diff --git a/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 b/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 index 07cd082fcc..01103105e8 100644 --- a/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-backups-index.js.es6 @@ -1,15 +1,18 @@ +import { alias, equal } from "@ember/object/computed"; +import { inject } from "@ember/controller"; +import Controller from "@ember/controller"; import { ajax } from "discourse/lib/ajax"; -import { default as computed } from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { setting, i18n } from "discourse/lib/computed"; -export default Ember.Controller.extend({ - adminBackups: Ember.inject.controller(), - status: Ember.computed.alias("adminBackups.model"), +export default Controller.extend({ + adminBackups: inject(), + status: alias("adminBackups.model"), uploadLabel: i18n("admin.backups.upload.label"), backupLocation: setting("backup_location"), - localBackupStorage: Ember.computed.equal("backupLocation", "local"), + localBackupStorage: equal("backupLocation", "local"), - @computed("status.allowRestore", "status.isOperationRunning") + @discourseComputed("status.allowRestore", "status.isOperationRunning") restoreTitle(allowRestore, isOperationRunning) { if (!allowRestore) { return "admin.backups.operations.restore.is_disabled"; @@ -29,7 +32,7 @@ export default Ember.Controller.extend({ I18n.t("yes_value"), confirmed => { if (confirmed) { - Discourse.User.currentProp("hideReadOnlyAlert", true); + this.set("currentUser.hideReadOnlyAlert", true); this._toggleReadOnlyMode(true); } } diff --git a/app/assets/javascripts/admin/controllers/admin-backups-logs.js.es6 b/app/assets/javascripts/admin/controllers/admin-backups-logs.js.es6 index 40bad8ae00..a32e216255 100644 --- a/app/assets/javascripts/admin/controllers/admin-backups-logs.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-backups-logs.js.es6 @@ -1,6 +1,9 @@ -export default Ember.Controller.extend({ - adminBackups: Ember.inject.controller(), - status: Ember.computed.alias("adminBackups.model"), +import { alias } from "@ember/object/computed"; +import { inject } from "@ember/controller"; +import Controller from "@ember/controller"; +export default Controller.extend({ + adminBackups: inject(), + status: alias("adminBackups.model"), init() { this._super(...arguments); diff --git a/app/assets/javascripts/admin/controllers/admin-backups.js.es6 b/app/assets/javascripts/admin/controllers/admin-backups.js.es6 index 5cfa57271e..7e942d9e9c 100644 --- a/app/assets/javascripts/admin/controllers/admin-backups.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-backups.js.es6 @@ -1,9 +1,11 @@ -export default Ember.Controller.extend({ - noOperationIsRunning: Ember.computed.not("model.isOperationRunning"), - rollbackEnabled: Ember.computed.and( +import { not, and } from "@ember/object/computed"; +import Controller from "@ember/controller"; +export default Controller.extend({ + noOperationIsRunning: not("model.isOperationRunning"), + rollbackEnabled: and( "model.canRollback", "model.restoreEnabled", "noOperationIsRunning" ), - rollbackDisabled: Ember.computed.not("rollbackEnabled") + rollbackDisabled: not("rollbackEnabled") }); diff --git a/app/assets/javascripts/admin/controllers/admin-badges-award.js.es6 b/app/assets/javascripts/admin/controllers/admin-badges-award.js.es6 new file mode 100644 index 0000000000..ef141ccac1 --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-badges-award.js.es6 @@ -0,0 +1,37 @@ +import Controller from "@ember/controller"; +import { ajax } from "discourse/lib/ajax"; +import { popupAjaxError } from "discourse/lib/ajax-error"; + +export default Controller.extend({ + saving: false, + replaceBadgeOwners: false, + + actions: { + massAward() { + const file = document.querySelector("#massAwardCSVUpload").files[0]; + + if (this.model && file) { + const options = { + type: "POST", + processData: false, + contentType: false, + data: new FormData() + }; + + options.data.append("file", file); + options.data.append("replace_badge_owners", this.replaceBadgeOwners); + + this.set("saving", true); + + ajax(`/admin/badges/award/${this.model.id}`, options) + .then(() => { + bootbox.alert(I18n.t("admin.badges.mass_award.success")); + }) + .catch(popupAjaxError) + .finally(() => this.set("saving", false)); + } else { + bootbox.alert(I18n.t("admin.badges.mass_award.aborted")); + } + } + } +}); diff --git a/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 index 1f7382f54b..0586a33505 100644 --- a/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-badges-show.js.es6 @@ -1,24 +1,52 @@ +import discourseComputed, { observes } from "discourse-common/utils/decorators"; +import { reads } from "@ember/object/computed"; +import Controller, { inject } from "@ember/controller"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { bufferedProperty } from "discourse/mixins/buffered-content"; import { propertyNotEqual } from "discourse/lib/computed"; -import computed from "ember-addons/ember-computed-decorators"; +import { run } from "@ember/runloop"; -export default Ember.Controller.extend(bufferedProperty("model"), { - adminBadges: Ember.inject.controller(), +export default Controller.extend(bufferedProperty("model"), { + adminBadges: inject(), saving: false, savingStatus: "", - - badgeTypes: Ember.computed.alias("adminBadges.badgeTypes"), - badgeGroupings: Ember.computed.alias("adminBadges.badgeGroupings"), - badgeTriggers: Ember.computed.alias("adminBadges.badgeTriggers"), - protectedSystemFields: Ember.computed.alias( - "adminBadges.protectedSystemFields" - ), - - readOnly: Ember.computed.alias("buffered.system"), + badgeTypes: reads("adminBadges.badgeTypes"), + badgeGroupings: reads("adminBadges.badgeGroupings"), + badgeTriggers: reads("adminBadges.badgeTriggers"), + protectedSystemFields: reads("adminBadges.protectedSystemFields"), + readOnly: reads("buffered.system"), showDisplayName: propertyNotEqual("name", "displayName"), - @computed("model.query", "buffered.query") + init() { + this._super(...arguments); + + // this is needed because the model doesnt have default values + // and as we are using a bufferedProperty it's not accessible + // in any other way + run.next(() => { + if (this.model) { + if (!this.model.badge_type_id) { + this.model.set( + "badge_type_id", + this.get("badgeTypes.firstObject.id") + ); + } + + if (!this.model.badge_grouping_id) { + this.model.set( + "badge_grouping_id", + this.get("badgeGroupings.firstObject.id") + ); + } + + if (!this.model.trigger) { + this.model.set("trigger", this.get("badgeTriggers.firstObject.id")); + } + } + }); + }, + + @discourseComputed("model.query", "buffered.query") hasQuery(modelQuery, bufferedQuery) { if (bufferedQuery) { return bufferedQuery.trim().length > 0; @@ -26,10 +54,11 @@ export default Ember.Controller.extend(bufferedProperty("model"), { return modelQuery && modelQuery.trim().length > 0; }, + @observes("model.id") _resetSaving: function() { this.set("saving", false); this.set("savingStatus", ""); - }.observes("model.id"), + }, actions: { save() { diff --git a/app/assets/javascripts/admin/controllers/admin-badges.js.es6 b/app/assets/javascripts/admin/controllers/admin-badges.js.es6 index 77c79b724a..4797fb2695 100644 --- a/app/assets/javascripts/admin/controllers/admin-badges.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-badges.js.es6 @@ -1 +1,18 @@ -export default Ember.Controller.extend(); +import Controller from "@ember/controller"; +import { inject as service } from "@ember/service"; +import discourseComputed from "discourse-common/utils/decorators"; + +export default Controller.extend({ + routing: service("-routing"), + + @discourseComputed("routing.currentRouteName") + selectedRoute() { + const currentRoute = this.routing.currentRouteName; + const indexRoute = "adminBadges.index"; + if (currentRoute === indexRoute) { + return "adminBadges.show"; + } else { + return this.routing.currentRouteName; + } + } +}); diff --git a/app/assets/javascripts/admin/controllers/admin-customize-colors-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-colors-show.js.es6 index 3fbdb989cf..9cdca3f098 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-colors-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-colors-show.js.es6 @@ -1,7 +1,9 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import { later } from "@ember/runloop"; +import Controller from "@ember/controller"; -export default Ember.Controller.extend({ - @computed("model.colors", "onlyOverridden") +export default Controller.extend({ + @discourseComputed("model.colors", "onlyOverridden") colors(allColors, onlyOverridden) { if (onlyOverridden) { return allColors.filter(color => color.get("overridden")); @@ -40,7 +42,7 @@ export default Ember.Controller.extend({ ); } - Ember.run.later(() => { + later(() => { this.set("model.savingStatus", null); }, 2000); diff --git a/app/assets/javascripts/admin/controllers/admin-customize-colors.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-colors.js.es6 index 7a9be8cc5d..869918bebb 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-colors.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-colors.js.es6 @@ -1,20 +1,22 @@ +import EmberObject from "@ember/object"; +import Controller from "@ember/controller"; import showModal from "discourse/lib/show-modal"; -import { default as computed } from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; -export default Ember.Controller.extend({ - @computed("model.@each.id") +export default Controller.extend({ + @discourseComputed("model.@each.id") baseColorScheme() { return this.model.findBy("is_base", true); }, - @computed("model.@each.id") + @discourseComputed("model.@each.id") baseColorSchemes() { return this.model.filterBy("is_base", true); }, - @computed("baseColorScheme") + @discourseComputed("baseColorScheme") baseColors(baseColorScheme) { - const baseColorsHash = Ember.Object.create({}); + const baseColorsHash = EmberObject.create({}); baseColorScheme.get("colors").forEach(color => { baseColorsHash.set(color.get("name"), color); }); diff --git a/app/assets/javascripts/admin/controllers/admin-customize-email-style-edit.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-email-style-edit.js.es6 new file mode 100644 index 0000000000..d534792b00 --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-customize-email-style-edit.js.es6 @@ -0,0 +1,34 @@ +import discourseComputed from "discourse-common/utils/decorators"; +import Controller from "@ember/controller"; + +export default Controller.extend({ + @discourseComputed("model.isSaving") + saveButtonText(isSaving) { + return isSaving ? I18n.t("saving") : I18n.t("admin.customize.save"); + }, + + @discourseComputed("model.changed", "model.isSaving") + saveDisabled(changed, isSaving) { + return !changed || isSaving; + }, + + actions: { + save() { + if (!this.model.saving) { + this.set("saving", true); + this.model + .update(this.model.getProperties("html", "css")) + .catch(e => { + const msg = + e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors + ? I18n.t("admin.customize.email_style.save_error_with_reason", { + error: e.jqXHR.responseJSON.errors.join(". ") + }) + : I18n.t("generic_error"); + bootbox.alert(msg); + }) + .finally(() => this.set("model.changed", false)); + } + } + } +}); diff --git a/app/assets/javascripts/admin/controllers/admin-customize-email-templates-edit.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-email-templates-edit.js.es6 index 926fd565e1..3617edca75 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-email-templates-edit.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-email-templates-edit.js.es6 @@ -1,11 +1,19 @@ +import discourseComputed from "discourse-common/utils/decorators"; +import Controller from "@ember/controller"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { bufferedProperty } from "discourse/mixins/buffered-content"; -import computed from "ember-addons/ember-computed-decorators"; -export default Ember.Controller.extend(bufferedProperty("emailTemplate"), { +export default Controller.extend(bufferedProperty("emailTemplate"), { saved: false, - @computed("buffered") + @discourseComputed("buffered.body", "buffered.subject") + saveDisabled(body, subject) { + return ( + this.emailTemplate.body === body && this.emailTemplate.subject === subject + ); + }, + + @discourseComputed("buffered") hasMultipleSubjects(buffered) { if (buffered.getProperties("subject")["subject"]) { return false; diff --git a/app/assets/javascripts/admin/controllers/admin-customize-email-templates.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-email-templates.js.es6 index 7bf7659f65..2f85e8418d 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-email-templates.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-email-templates.js.es6 @@ -1,10 +1,18 @@ -export default Ember.Controller.extend({ +import { sort } from "@ember/object/computed"; +import Controller from "@ember/controller"; +export default Controller.extend({ emailTemplates: null, - sortedTemplates: Ember.computed.sort("emailTemplates", "titleSorting"), + sortedTemplates: sort("emailTemplates", "titleSorting"), init() { this._super(...arguments); this.titleSorting = ["title"]; + }, + + actions: { + selectTemplate(template) { + this.transitionToRoute("adminCustomizeEmailTemplates.edit", template); + } } }); diff --git a/app/assets/javascripts/admin/controllers/admin-customize-robots-txt.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-robots-txt.js.es6 new file mode 100644 index 0000000000..5c6d2499f4 --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-customize-robots-txt.js.es6 @@ -0,0 +1,47 @@ +import { not } from "@ember/object/computed"; +import Controller from "@ember/controller"; +import { ajax } from "discourse/lib/ajax"; +import { bufferedProperty } from "discourse/mixins/buffered-content"; +import { propertyEqual } from "discourse/lib/computed"; + +export default Controller.extend(bufferedProperty("model"), { + saved: false, + isSaving: false, + saveDisabled: propertyEqual("model.robots_txt", "buffered.robots_txt"), + resetDisbaled: not("model.overridden"), + + actions: { + save() { + this.setProperties({ + isSaving: true, + saved: false + }); + + ajax("robots.json", { + method: "PUT", + data: { robots_txt: this.buffered.get("robots_txt") } + }) + .then(data => { + this.commitBuffer(); + this.set("saved", true); + this.set("model.overridden", data.overridden); + }) + .finally(() => this.set("isSaving", false)); + }, + + reset() { + this.setProperties({ + isSaving: true, + saved: false + }); + ajax("robots.json", { method: "DELETE" }) + .then(data => { + this.buffered.set("robots_txt", data.robots_txt); + this.commitBuffer(); + this.set("saved", true); + this.set("model.overridden", false); + }) + .finally(() => this.set("isSaving", false)); + } + } +}); diff --git a/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6 index ba54e3c91f..c3c0f4fc99 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-themes-edit.js.es6 @@ -1,7 +1,8 @@ +import Controller from "@ember/controller"; import { url } from "discourse/lib/computed"; -import { default as computed } from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; -export default Ember.Controller.extend({ +export default Controller.extend({ section: null, currentTarget: 0, maximized: false, @@ -15,7 +16,7 @@ export default Ember.Controller.extend({ this.set("currentTarget", target && target.id); }, - @computed("currentTarget") + @discourseComputed("currentTarget") currentTargetName(id) { const target = this.get("model.targets").find( t => t.id === parseInt(id, 10) @@ -23,12 +24,12 @@ export default Ember.Controller.extend({ return target && target.name; }, - @computed("model.isSaving") + @discourseComputed("model.isSaving") saveButtonText(isSaving) { return isSaving ? I18n.t("saving") : I18n.t("admin.customize.save"); }, - @computed("model.changed", "model.isSaving") + @discourseComputed("model.changed", "model.isSaving") saveDisabled(changed, isSaving) { return !changed || isSaving; }, diff --git a/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 index 552a1d3aab..a8e25fdec9 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 @@ -1,19 +1,38 @@ -import { default as computed } from "ember-addons/ember-computed-decorators"; +import { makeArray } from "discourse-common/lib/helpers"; +import { + empty, + filterBy, + match, + mapBy, + notEmpty +} from "@ember/object/computed"; +import Controller from "@ember/controller"; +import discourseComputed from "discourse-common/utils/decorators"; import { url } from "discourse/lib/computed"; import { popupAjaxError } from "discourse/lib/ajax-error"; import showModal from "discourse/lib/show-modal"; import ThemeSettings from "admin/models/theme-settings"; import { THEMES, COMPONENTS } from "admin/models/theme"; +import EmberObject from "@ember/object"; const THEME_UPLOAD_VAR = 2; -export default Ember.Controller.extend({ +export default Controller.extend({ downloadUrl: url("model.id", "/admin/customize/themes/%@/export"), previewUrl: url("model.id", "/admin/themes/%@/preview"), - addButtonDisabled: Ember.computed.empty("selectedChildThemeId"), + addButtonDisabled: empty("selectedChildThemeId"), editRouteName: "adminCustomizeThemes.edit", + parentThemesNames: mapBy("model.parentThemes", "name"), + availableParentThemes: filterBy("allThemes", "component", false), + availableActiveParentThemes: filterBy("availableParentThemes", "isActive"), + availableThemesNames: mapBy("availableParentThemes", "name"), + availableActiveThemesNames: mapBy("availableActiveParentThemes", "name"), + availableActiveChildThemes: filterBy("availableChildThemes", "hasParents"), + availableComponentsNames: mapBy("availableChildThemes", "name"), + availableActiveComponentsNames: mapBy("availableActiveChildThemes", "name"), + childThemesNames: mapBy("model.childThemes", "name"), - @computed("model.editedFields") + @discourseComputed("model.editedFields") editedFieldsFormatted() { const descriptions = []; ["common", "desktop", "mobile"].forEach(target => { @@ -31,13 +50,13 @@ export default Ember.Controller.extend({ return descriptions; }, - @computed("colorSchemeId", "model.color_scheme_id") + @discourseComputed("colorSchemeId", "model.color_scheme_id") colorSchemeChanged(colorSchemeId, existingId) { - colorSchemeId = colorSchemeId === null ? null : parseInt(colorSchemeId); + colorSchemeId = colorSchemeId === null ? null : parseInt(colorSchemeId, 10); return colorSchemeId !== existingId; }, - @computed("availableChildThemes", "model.childThemes.[]", "model") + @discourseComputed("availableChildThemes", "model.childThemes.[]", "model") selectableChildThemes(available, childThemes) { if (available) { const themes = !childThemes @@ -47,7 +66,43 @@ export default Ember.Controller.extend({ } }, - @computed("allThemes", "model.component", "model") + @discourseComputed("model.parentThemes.[]") + relativesSelectorSettingsForComponent() { + return EmberObject.create({ + list_type: "compact", + type: "list", + preview: null, + anyValue: false, + setting: "parent_theme_ids", + label: I18n.t("admin.customize.theme.component_on_themes"), + choices: this.availableThemesNames, + default: this.parentThemesNames.join("|"), + value: this.parentThemesNames.join("|"), + defaultValues: this.availableActiveThemesNames.join("|"), + allThemes: this.allThemes, + setDefaultValuesLabel: I18n.t("admin.customize.theme.add_all_themes") + }); + }, + + @discourseComputed("model.parentThemes.[]") + relativesSelectorSettingsForTheme() { + return EmberObject.create({ + list_type: "compact", + type: "list", + preview: null, + anyValue: false, + setting: "child_theme_ids", + label: I18n.t("admin.customize.theme.included_components"), + choices: this.availableComponentsNames, + default: this.childThemesNames.join("|"), + value: this.childThemesNames.join("|"), + defaultValues: this.availableActiveComponentsNames.join("|"), + allThemes: this.allThemes, + setDefaultValuesLabel: I18n.t("admin.customize.theme.add_all") + }); + }, + + @discourseComputed("allThemes", "model.component", "model") availableChildThemes(allThemes) { if (!this.get("model.component")) { const themeId = this.get("model.id"); @@ -57,38 +112,38 @@ export default Ember.Controller.extend({ } }, - @computed("model.component") + @discourseComputed("model.component") convertKey(component) { const type = component ? "component" : "theme"; return `admin.customize.theme.convert_${type}`; }, - @computed("model.component") + @discourseComputed("model.component") convertIcon(component) { return component ? "cube" : ""; }, - @computed("model.component") + @discourseComputed("model.component") convertTooltip(component) { const type = component ? "component" : "theme"; return `admin.customize.theme.convert_${type}_tooltip`; }, - @computed("model.settings") + @discourseComputed("model.settings") settings(settings) { return settings.map(setting => ThemeSettings.create(setting)); }, - hasSettings: Ember.computed.notEmpty("settings"), + hasSettings: notEmpty("settings"), - @computed("model.translations") + @discourseComputed("model.translations") translations(translations) { return translations.map(setting => ThemeSettings.create(setting)); }, - hasTranslations: Ember.computed.notEmpty("translations"), + hasTranslations: notEmpty("translations"), - @computed("model.remoteError", "updatingRemote") + @discourseComputed("model.remoteError", "updatingRemote") showRemoteError(errorMessage, updating) { return errorMessage && !updating; }, @@ -124,8 +179,8 @@ export default Ember.Controller.extend({ }); this.get("parentController.model.content").forEach(theme => { - const children = Ember.makeArray(theme.get("childThemes")); - const rawChildren = Ember.makeArray(theme.get("child_themes")); + const children = makeArray(theme.get("childThemes")); + const rawChildren = makeArray(theme.get("child_themes")); const index = children ? children.indexOf(model) : -1; if (index > -1) { children.splice(index, 1); @@ -147,10 +202,7 @@ export default Ember.Controller.extend({ "scss" ); }, - sourceIsHttp: Ember.computed.match( - "model.remote_theme.remote_url", - /^http(s)?:\/\// - ), + sourceIsHttp: match("model.remote_theme.remote_url", /^http(s)?:\/\//), actions: { updateToLatest() { this.set("updatingRemote", true); @@ -189,7 +241,7 @@ export default Ember.Controller.extend({ let schemeId = this.colorSchemeId; this.set( "model.color_scheme_id", - schemeId === null ? null : parseInt(schemeId) + schemeId === null ? null : parseInt(schemeId, 10) ); this.model.saveChanges("color_scheme_id"); }, @@ -239,9 +291,9 @@ export default Ember.Controller.extend({ }, addChildTheme() { - let themeId = parseInt(this.selectedChildThemeId); + let themeId = parseInt(this.selectedChildThemeId, 10); let theme = this.allThemes.findBy("id", themeId); - this.model.addChildTheme(theme); + this.model.addChildTheme(theme).then(() => this.store.findAll("theme")); }, removeUpload(upload) { @@ -258,7 +310,9 @@ export default Ember.Controller.extend({ }, removeChildTheme(theme) { - this.model.removeChildTheme(theme); + this.model + .removeChildTheme(theme) + .then(() => this.store.findAll("theme")); }, destroy() { @@ -271,6 +325,7 @@ export default Ember.Controller.extend({ result => { if (result) { const model = this.model; + model.setProperties({ recentlyInstalled: false }); model.destroyRecord().then(() => { this.allThemes.removeObject(model); this.transitionToRoute("adminCustomizeThemes"); @@ -301,6 +356,20 @@ export default Ember.Controller.extend({ } else { this.commitSwitchType(); } + }, + + enableComponent() { + this.model.set("enabled", true); + this.model + .saveChanges("enabled") + .catch(() => this.model.set("enabled", false)); + }, + + disableComponent() { + this.model.set("enabled", false); + this.model + .saveChanges("enabled") + .catch(() => this.model.set("enabled", true)); } } }); diff --git a/app/assets/javascripts/admin/controllers/admin-customize-themes.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-themes.js.es6 index f38ae3ab74..e99532bf03 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-themes.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-themes.js.es6 @@ -1,20 +1,21 @@ -import { default as computed } from "ember-addons/ember-computed-decorators"; +import Controller from "@ember/controller"; +import discourseComputed from "discourse-common/utils/decorators"; import { THEMES } from "admin/models/theme"; -export default Ember.Controller.extend({ +export default Controller.extend({ currentTab: THEMES, - @computed("model", "model.@each.component") + @discourseComputed("model", "model.@each.component") fullThemes(themes) { return themes.filter(t => !t.get("component")); }, - @computed("model", "model.@each.component") + @discourseComputed("model", "model.@each.component") childThemes(themes) { return themes.filter(t => t.get("component")); }, - @computed("model", "model.@each.component") + @discourseComputed("model", "model.@each.component") installedThemes(themes) { return themes.map(t => t.name); } diff --git a/app/assets/javascripts/admin/controllers/admin-dashboard-general.js.es6 b/app/assets/javascripts/admin/controllers/admin-dashboard-general.js.es6 index 205c5fe853..b77e3e0288 100644 --- a/app/assets/javascripts/admin/controllers/admin-dashboard-general.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-dashboard-general.js.es6 @@ -1,30 +1,32 @@ +import discourseComputed from "discourse-common/utils/decorators"; +import { makeArray } from "discourse-common/lib/helpers"; +import { inject } from "@ember/controller"; +import Controller from "@ember/controller"; import { setting } from "discourse/lib/computed"; -import computed from "ember-addons/ember-computed-decorators"; import AdminDashboard from "admin/models/admin-dashboard"; import Report from "admin/models/report"; import PeriodComputationMixin from "admin/mixins/period-computation"; +import { computed } from "@ember/object"; function staticReport(reportType) { - return function() { - return Ember.makeArray(this.reports).find( - report => report.type === reportType - ); - }.property("reports.[]"); + return computed("reports.[]", function() { + return makeArray(this.reports).find(report => report.type === reportType); + }); } -export default Ember.Controller.extend(PeriodComputationMixin, { +export default Controller.extend(PeriodComputationMixin, { isLoading: false, dashboardFetchedAt: null, - exceptionController: Ember.inject.controller("exception"), + exceptionController: inject("exception"), logSearchQueriesEnabled: setting("log_search_queries"), basePath: Discourse.BaseUri, - @computed("siteSettings.dashboard_general_tab_activity_metrics") + @discourseComputed("siteSettings.dashboard_general_tab_activity_metrics") activityMetrics(metrics) { return (metrics || "").split("|").filter(m => m); }, - @computed + @discourseComputed activityMetricsFilters() { return { startDate: this.lastMonth, @@ -32,14 +34,14 @@ export default Ember.Controller.extend(PeriodComputationMixin, { }; }, - @computed + @discourseComputed topReferredTopicsOptions() { return { table: { total: false, limit: 8 } }; }, - @computed + @discourseComputed topReferredTopicsFilters() { return { startDate: moment() @@ -49,7 +51,7 @@ export default Ember.Controller.extend(PeriodComputationMixin, { }; }, - @computed + @discourseComputed trendingSearchFilters() { return { startDate: moment() @@ -59,14 +61,14 @@ export default Ember.Controller.extend(PeriodComputationMixin, { }; }, - @computed + @discourseComputed trendingSearchOptions() { return { table: { total: false, limit: 8 } }; }, - @computed + @discourseComputed trendingSearchDisabledLabel() { return I18n.t("admin.dashboard.reports.trending_search.disabled", { basePath: Discourse.BaseUri @@ -93,7 +95,7 @@ export default Ember.Controller.extend(PeriodComputationMixin, { this.setProperties({ dashboardFetchedAt: new Date(), model: adminDashboardModel, - reports: Ember.makeArray(adminDashboardModel.reports).map(x => + reports: makeArray(adminDashboardModel.reports).map(x => Report.create(x) ) }); @@ -106,7 +108,7 @@ export default Ember.Controller.extend(PeriodComputationMixin, { } }, - @computed("startDate", "endDate") + @discourseComputed("startDate", "endDate") filters(startDate, endDate) { return { startDate, endDate }; }, diff --git a/app/assets/javascripts/admin/controllers/admin-dashboard-moderation.js.es6 b/app/assets/javascripts/admin/controllers/admin-dashboard-moderation.js.es6 index df06e682f4..8925825fba 100644 --- a/app/assets/javascripts/admin/controllers/admin-dashboard-moderation.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-dashboard-moderation.js.es6 @@ -1,8 +1,9 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import Controller from "@ember/controller"; import PeriodComputationMixin from "admin/mixins/period-computation"; -export default Ember.Controller.extend(PeriodComputationMixin, { - @computed +export default Controller.extend(PeriodComputationMixin, { + @discourseComputed flagsStatusOptions() { return { table: { @@ -12,7 +13,7 @@ export default Ember.Controller.extend(PeriodComputationMixin, { }; }, - @computed + @discourseComputed userFlaggingRatioOptions() { return { table: { @@ -22,12 +23,12 @@ export default Ember.Controller.extend(PeriodComputationMixin, { }; }, - @computed("startDate", "endDate") + @discourseComputed("startDate", "endDate") filters(startDate, endDate) { return { startDate, endDate }; }, - @computed("lastWeek", "endDate") + @discourseComputed("lastWeek", "endDate") lastWeekfilters(startDate, endDate) { return { startDate, endDate }; }, diff --git a/app/assets/javascripts/admin/controllers/admin-dashboard-reports.js.es6 b/app/assets/javascripts/admin/controllers/admin-dashboard-reports.js.es6 index 9c2eccdab1..9a57b9cf32 100644 --- a/app/assets/javascripts/admin/controllers/admin-dashboard-reports.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-dashboard-reports.js.es6 @@ -1,10 +1,13 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import { debounce } from "@ember/runloop"; +import Controller from "@ember/controller"; + const { get } = Ember; -export default Ember.Controller.extend({ +export default Controller.extend({ filter: null, - @computed("model.[]", "filter") + @discourseComputed("model.[]", "filter") filterReports(reports, filter) { if (filter) { filter = filter.toLowerCase(); @@ -20,7 +23,7 @@ export default Ember.Controller.extend({ actions: { filterReports(filter) { - Ember.run.debounce(this, this._performFiltering, filter, 250); + debounce(this, this._performFiltering, filter, 250); } }, diff --git a/app/assets/javascripts/admin/controllers/admin-dashboard.js.es6 b/app/assets/javascripts/admin/controllers/admin-dashboard.js.es6 index 0ff4b540d1..bd8561abc1 100644 --- a/app/assets/javascripts/admin/controllers/admin-dashboard.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-dashboard.js.es6 @@ -1,17 +1,19 @@ +import discourseComputed from "discourse-common/utils/decorators"; +import { inject } from "@ember/controller"; +import Controller from "@ember/controller"; import { setting } from "discourse/lib/computed"; -import computed from "ember-addons/ember-computed-decorators"; import AdminDashboard from "admin/models/admin-dashboard"; import VersionCheck from "admin/models/version-check"; const PROBLEMS_CHECK_MINUTES = 1; -export default Ember.Controller.extend({ +export default Controller.extend({ isLoading: false, dashboardFetchedAt: null, - exceptionController: Ember.inject.controller("exception"), + exceptionController: inject("exception"), showVersionChecks: setting("version_checks"), - @computed("problems.length") + @discourseComputed("problems.length") foundProblems(problemsLength) { return this.currentUser.get("admin") && (problemsLength || 0) > 0; }, @@ -75,7 +77,7 @@ export default Ember.Controller.extend({ .finally(() => this.set("loadingProblems", false)); }, - @computed("problemsFetchedAt") + @discourseComputed("problemsFetchedAt") problemsTimestamp(problemsFetchedAt) { return moment(problemsFetchedAt) .locale("en") diff --git a/app/assets/javascripts/admin/controllers/admin-email-advanced-test.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-advanced-test.js.es6 index c98fb7cfb9..8443344349 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-advanced-test.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-advanced-test.js.es6 @@ -1,7 +1,8 @@ +import Controller from "@ember/controller"; import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; -export default Ember.Controller.extend({ +export default Controller.extend({ email: null, text: null, elided: null, diff --git a/app/assets/javascripts/admin/controllers/admin-email-bounced.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-bounced.js.es6 index 535fa4bca1..508bc559c5 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-bounced.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-bounced.js.es6 @@ -1,8 +1,10 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; -import debounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse/lib/debounce"; +import { observes } from "discourse-common/utils/decorators"; export default AdminEmailLogsController.extend({ - filterEmailLogs: debounce(function() { + @observes("filter.{status,user,address,type}") + filterEmailLogs: discourseDebounce(function() { this.loadLogs(); - }, 250).observes("filter.{status,user,address,type}") + }, 250) }); diff --git a/app/assets/javascripts/admin/controllers/admin-email-index.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-index.js.es6 index 02860948c0..3433f6f55f 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-index.js.es6 @@ -1,20 +1,25 @@ +import { empty } from "@ember/object/computed"; +import Controller from "@ember/controller"; import { ajax } from "discourse/lib/ajax"; -export default Ember.Controller.extend({ +import { observes } from "discourse-common/utils/decorators"; + +export default Controller.extend({ /** Is the "send test email" button disabled? @property sendTestEmailDisabled **/ - sendTestEmailDisabled: Ember.computed.empty("testEmailAddress"), + sendTestEmailDisabled: empty("testEmailAddress"), /** Clears the 'sentTestEmail' property on successful send. @method testEmailAddressChanged **/ + @observes("testEmailAddress") testEmailAddressChanged: function() { this.set("sentTestEmail", false); - }.observes("testEmailAddress"), + }, actions: { /** diff --git a/app/assets/javascripts/admin/controllers/admin-email-logs.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-logs.js.es6 index c7c2df8c95..49a84ea6d1 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-logs.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-logs.js.es6 @@ -1,6 +1,7 @@ +import Controller from "@ember/controller"; import EmailLog from "admin/models/email-log"; -export default Ember.Controller.extend({ +export default Controller.extend({ loading: false, loadLogs(sourceModel, loadMore) { diff --git a/app/assets/javascripts/admin/controllers/admin-email-preview-digest.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-preview-digest.js.es6 index 407478d681..66b393196a 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-preview-digest.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-preview-digest.js.es6 @@ -1,14 +1,16 @@ +import { empty, or, notEmpty } from "@ember/object/computed"; +import Controller from "@ember/controller"; import EmailPreview from "admin/models/email-preview"; import { popupAjaxError } from "discourse/lib/ajax-error"; -export default Ember.Controller.extend({ +export default Controller.extend({ username: null, lastSeen: null, - emailEmpty: Ember.computed.empty("email"), - sendEmailDisabled: Ember.computed.or("emailEmpty", "sendingEmail"), - showSendEmailForm: Ember.computed.notEmpty("model.html_content"), - htmlEmpty: Ember.computed.empty("model.html_content"), + emailEmpty: empty("email"), + sendEmailDisabled: or("emailEmpty", "sendingEmail"), + showSendEmailForm: notEmpty("model.html_content"), + htmlEmpty: empty("model.html_content"), actions: { refresh() { diff --git a/app/assets/javascripts/admin/controllers/admin-email-received.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-received.js.es6 index 7659e61edd..4a3cc363df 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-received.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-received.js.es6 @@ -1,11 +1,13 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; -import debounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse/lib/debounce"; import IncomingEmail from "admin/models/incoming-email"; +import { observes } from "discourse-common/utils/decorators"; export default AdminEmailLogsController.extend({ - filterIncomingEmails: debounce(function() { + @observes("filter.{status,from,to,subject}") + filterIncomingEmails: discourseDebounce(function() { this.loadLogs(IncomingEmail); - }, 250).observes("filter.{status,from,to,subject}"), + }, 250), actions: { loadMore() { diff --git a/app/assets/javascripts/admin/controllers/admin-email-rejected.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-rejected.js.es6 index 602bb052ce..8c6f6767f4 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-rejected.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-rejected.js.es6 @@ -1,11 +1,13 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; -import debounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse/lib/debounce"; import IncomingEmail from "admin/models/incoming-email"; +import { observes } from "discourse-common/utils/decorators"; export default AdminEmailLogsController.extend({ - filterIncomingEmails: debounce(function() { + @observes("filter.{status,from,to,subject,error}") + filterIncomingEmails: discourseDebounce(function() { this.loadLogs(IncomingEmail); - }, 250).observes("filter.{status,from,to,subject,error}"), + }, 250), actions: { loadMore() { diff --git a/app/assets/javascripts/admin/controllers/admin-email-sent.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-sent.js.es6 index 83f52d3510..f727c9d6f4 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-sent.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-sent.js.es6 @@ -1,8 +1,10 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; -import debounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse/lib/debounce"; +import { observes } from "discourse-common/utils/decorators"; export default AdminEmailLogsController.extend({ - filterEmailLogs: debounce(function() { + @observes("filter.{status,user,address,type,reply_key}") + filterEmailLogs: discourseDebounce(function() { this.loadLogs(); - }, 250).observes("filter.{status,user,address,type,reply_key}") + }, 250) }); diff --git a/app/assets/javascripts/admin/controllers/admin-email-skipped.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-skipped.js.es6 index 535fa4bca1..508bc559c5 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-skipped.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-skipped.js.es6 @@ -1,8 +1,10 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; -import debounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse/lib/debounce"; +import { observes } from "discourse-common/utils/decorators"; export default AdminEmailLogsController.extend({ - filterEmailLogs: debounce(function() { + @observes("filter.{status,user,address,type}") + filterEmailLogs: discourseDebounce(function() { this.loadLogs(); - }, 250).observes("filter.{status,user,address,type}") + }, 250) }); diff --git a/app/assets/javascripts/admin/controllers/admin-embedding.js.es6 b/app/assets/javascripts/admin/controllers/admin-embedding.js.es6 index a8b522130c..b71c173e36 100644 --- a/app/assets/javascripts/admin/controllers/admin-embedding.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-embedding.js.es6 @@ -1,18 +1,19 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import Controller from "@ember/controller"; import { popupAjaxError } from "discourse/lib/ajax-error"; -export default Ember.Controller.extend({ +export default Controller.extend({ saved: false, embedding: null, // show settings if we have at least one created host - @computed("embedding.embeddable_hosts.@each.isCreated") + @discourseComputed("embedding.embeddable_hosts.@each.isCreated") showSecondary() { const hosts = this.get("embedding.embeddable_hosts"); return hosts.length && hosts.findBy("isCreated"); }, - @computed("embedding.base_url") + @discourseComputed("embedding.base_url") embeddingCode(baseUrl) { const html = `
diff --git a/app/assets/javascripts/admin/controllers/admin-emojis.js.es6 b/app/assets/javascripts/admin/controllers/admin-emojis.js.es6 index d110904338..6a1d295759 100644 --- a/app/assets/javascripts/admin/controllers/admin-emojis.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-emojis.js.es6 @@ -1,6 +1,9 @@ +import { sort } from "@ember/object/computed"; +import EmberObject from "@ember/object"; +import Controller from "@ember/controller"; import { ajax } from "discourse/lib/ajax"; -export default Ember.Controller.extend({ - sortedEmojis: Ember.computed.sort("model", "emojiSorting"), +export default Controller.extend({ + sortedEmojis: sort("model", "emojiSorting"), init() { this._super(...arguments); @@ -11,7 +14,7 @@ export default Ember.Controller.extend({ actions: { emojiUploaded(emoji) { emoji.url += "?t=" + new Date().getTime(); - this.model.pushObject(Ember.Object.create(emoji)); + this.model.pushObject(EmberObject.create(emoji)); }, destroy(emoji) { diff --git a/app/assets/javascripts/admin/controllers/admin-logs-screened-emails.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-screened-emails.js.es6 index 2a410f7705..5b0908c363 100644 --- a/app/assets/javascripts/admin/controllers/admin-logs-screened-emails.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-logs-screened-emails.js.es6 @@ -1,8 +1,9 @@ +import Controller from "@ember/controller"; import { exportEntity } from "discourse/lib/export-csv"; import { outputExportResult } from "discourse/lib/export-result"; import ScreenedEmail from "admin/models/screened-email"; -export default Ember.Controller.extend({ +export default Controller.extend({ loading: false, actions: { diff --git a/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6 index 66ffe3c08c..baee177c02 100644 --- a/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-logs-screened-ip-addresses.js.es6 @@ -1,19 +1,22 @@ -import debounce from "discourse/lib/debounce"; +import Controller from "@ember/controller"; +import discourseDebounce from "discourse/lib/debounce"; import { outputExportResult } from "discourse/lib/export-result"; import { exportEntity } from "discourse/lib/export-csv"; import ScreenedIpAddress from "admin/models/screened-ip-address"; +import { observes } from "discourse-common/utils/decorators"; -export default Ember.Controller.extend({ +export default Controller.extend({ loading: false, filter: null, savedIpAddress: null, - show: debounce(function() { + @observes("filter") + show: discourseDebounce(function() { this.set("loading", true); ScreenedIpAddress.findAll(this.filter).then(result => { this.setProperties({ model: result, loading: false }); }); - }, 250).observes("filter"), + }, 250), actions: { allow(record) { diff --git a/app/assets/javascripts/admin/controllers/admin-logs-screened-urls.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-screened-urls.js.es6 index ef01562302..3f33783184 100644 --- a/app/assets/javascripts/admin/controllers/admin-logs-screened-urls.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-logs-screened-urls.js.es6 @@ -1,8 +1,9 @@ +import Controller from "@ember/controller"; import { exportEntity } from "discourse/lib/export-csv"; import { outputExportResult } from "discourse/lib/export-result"; import ScreenedUrl from "admin/models/screened-url"; -export default Ember.Controller.extend({ +export default Controller.extend({ loading: false, show() { diff --git a/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 index c1cd77b63b..849861c921 100644 --- a/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 @@ -1,23 +1,18 @@ +import { gt } from "@ember/object/computed"; +import EmberObject from "@ember/object"; +import { scheduleOnce } from "@ember/runloop"; +import Controller from "@ember/controller"; import { exportEntity } from "discourse/lib/export-csv"; import { outputExportResult } from "discourse/lib/export-result"; -import StaffActionLog from "admin/models/staff-action-log"; -import { - default as computed, - on -} from "ember-addons/ember-computed-decorators"; +import discourseComputed, { on } from "discourse-common/utils/decorators"; -export default Ember.Controller.extend({ - loading: false, - filters: null, - userHistoryActions: [], +export default Controller.extend({ model: null, - nextPage: 0, - lastPage: null, + filters: null, + filtersExists: gt("filterCount", 0), + userHistoryActions: null, - filtersExists: Ember.computed.gt("filterCount", 0), - showTable: Ember.computed.gt("model.length", 0), - - @computed("filters.action_name") + @discourseComputed("filters.action_name") actionFilter(name) { return name ? I18n.t("admin.logs.staff_actions.actions." + name) : null; }, @@ -25,34 +20,21 @@ export default Ember.Controller.extend({ @on("init") resetFilters() { this.setProperties({ - filters: Ember.Object.create(), - model: [], - nextPage: 0, - lastPage: null + model: EmberObject.create({ loadingMore: true }), + filters: EmberObject.create() }); this.scheduleRefresh(); }, _changeFilters(props) { + this.set("model", EmberObject.create({ loadingMore: true })); this.filters.setProperties(props); - this.setProperties({ - model: [], - nextPage: 0, - lastPage: null - }); this.scheduleRefresh(); }, _refresh() { - if (this.lastPage && this.nextPage >= this.lastPage) { - return; - } - - this.set("loading", true); - - const page = this.nextPage; let filters = this.filters; - let params = { page }; + let params = {}; let count = 0; // Don't send null values @@ -65,36 +47,27 @@ export default Ember.Controller.extend({ }); this.set("filterCount", count); - StaffActionLog.findAll(params) - .then(result => { - this.setProperties({ - model: this.model.concat(result.staff_action_logs), - nextPage: page + 1 - }); + this.store.findAll("staff-action-log", params).then(result => { + this.set("model", result); - if (result.staff_action_logs.length === 0) { - this.set("lastPage", page); - } - - if (this.userHistoryActions.length === 0) { - this.set( - "userHistoryActions", - result.user_history_actions - .map(action => ({ - id: action.id, - action_id: action.action_id, - name: I18n.t("admin.logs.staff_actions.actions." + action.id), - name_raw: action.id - })) - .sort((a, b) => (a.name > b.name ? 1 : -1)) - ); - } - }) - .finally(() => this.set("loading", false)); + if (!this.userHistoryActions) { + this.set( + "userHistoryActions", + result.extras.user_history_actions + .map(action => ({ + id: action.id, + action_id: action.action_id, + name: I18n.t("admin.logs.staff_actions.actions." + action.id), + name_raw: action.id + })) + .sort((a, b) => a.name.localeCompare(b.name)) + ); + } + }); }, scheduleRefresh() { - Ember.run.scheduleOnce("afterRender", this, this._refresh); + scheduleOnce("afterRender", this, this._refresh); }, actions: { @@ -153,7 +126,7 @@ export default Ember.Controller.extend({ }, loadMore() { - this._refresh(); + this.model.loadMore(); } } }); diff --git a/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 b/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 index 45e88514a0..a45f8cc89a 100644 --- a/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-permalinks.js.es6 @@ -1,16 +1,19 @@ -import debounce from "discourse/lib/debounce"; +import Controller from "@ember/controller"; +import discourseDebounce from "discourse/lib/debounce"; import Permalink from "admin/models/permalink"; +import { observes } from "discourse-common/utils/decorators"; -export default Ember.Controller.extend({ +export default Controller.extend({ loading: false, filter: null, - show: debounce(function() { + @observes("filter") + show: discourseDebounce(function() { Permalink.findAll(this.filter).then(result => { this.set("model", result); this.set("loading", false); }); - }, 250).observes("filter"), + }, 250), actions: { recordAdded(arg) { diff --git a/app/assets/javascripts/admin/controllers/admin-plugins.js.es6 b/app/assets/javascripts/admin/controllers/admin-plugins.js.es6 index 36b1f7ca11..f9b34e70a4 100644 --- a/app/assets/javascripts/admin/controllers/admin-plugins.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-plugins.js.es6 @@ -1,7 +1,8 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import Controller from "@ember/controller"; -export default Ember.Controller.extend({ - @computed +export default Controller.extend({ + @discourseComputed adminRoutes: function() { return this.model .map(p => { diff --git a/app/assets/javascripts/admin/controllers/admin-reports-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-reports-show.js.es6 index 2a78ece7f4..6d302204ce 100644 --- a/app/assets/javascripts/admin/controllers/admin-reports-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-reports-show.js.es6 @@ -1,12 +1,13 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import Controller from "@ember/controller"; -export default Ember.Controller.extend({ +export default Controller.extend({ queryParams: ["start_date", "end_date", "filters"], start_date: null, end_date: null, filters: null, - @computed("model.type") + @discourseComputed("model.type") reportOptions(type) { let options = { table: { perPage: 50, limit: 50, formatNumbers: false } }; diff --git a/app/assets/javascripts/admin/controllers/admin-search-logs-index.js.es6 b/app/assets/javascripts/admin/controllers/admin-search-logs-index.js.es6 index 5c26cb0e9f..397b4c9b9e 100644 --- a/app/assets/javascripts/admin/controllers/admin-search-logs-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-search-logs-index.js.es6 @@ -1,6 +1,7 @@ +import Controller from "@ember/controller"; export const DEFAULT_PERIOD = "yearly"; -export default Ember.Controller.extend({ +export default Controller.extend({ loading: false, period: DEFAULT_PERIOD, searchType: "all", diff --git a/app/assets/javascripts/admin/controllers/admin-search-logs-term.js.es6 b/app/assets/javascripts/admin/controllers/admin-search-logs-term.js.es6 index 229aa67db8..66def62c97 100644 --- a/app/assets/javascripts/admin/controllers/admin-search-logs-term.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-search-logs-term.js.es6 @@ -1,6 +1,7 @@ +import Controller from "@ember/controller"; import { DEFAULT_PERIOD } from "admin/controllers/admin-search-logs-index"; -export default Ember.Controller.extend({ +export default Controller.extend({ loading: false, term: null, period: DEFAULT_PERIOD, diff --git a/app/assets/javascripts/admin/controllers/admin-site-settings-category.js.es6 b/app/assets/javascripts/admin/controllers/admin-site-settings-category.js.es6 index a5831299ec..bfd727e6ea 100644 --- a/app/assets/javascripts/admin/controllers/admin-site-settings-category.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-site-settings-category.js.es6 @@ -1,15 +1,17 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import { inject } from "@ember/controller"; +import Controller from "@ember/controller"; -export default Ember.Controller.extend({ +export default Controller.extend({ categoryNameKey: null, - adminSiteSettings: Ember.inject.controller(), + adminSiteSettings: inject(), - @computed("adminSiteSettings.visibleSiteSettings", "categoryNameKey") + @discourseComputed("adminSiteSettings.visibleSiteSettings", "categoryNameKey") category(categories, nameKey) { return (categories || []).findBy("nameKey", nameKey); }, - @computed("category") + @discourseComputed("category") filteredContent(category) { return category ? category.siteSettings : []; } diff --git a/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6 b/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6 index 09a335946d..28721de995 100644 --- a/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-site-settings.js.es6 @@ -1,14 +1,18 @@ -import debounce from "discourse/lib/debounce"; +import { isEmpty } from "@ember/utils"; +import { alias } from "@ember/object/computed"; +import Controller from "@ember/controller"; +import discourseDebounce from "discourse/lib/debounce"; +import { observes } from "discourse-common/utils/decorators"; -export default Ember.Controller.extend({ +export default Controller.extend({ filter: null, - allSiteSettings: Ember.computed.alias("model"), + allSiteSettings: alias("model"), visibleSiteSettings: null, onlyOverridden: false, filterContentNow(category) { // If we have no content, don't bother filtering anything - if (!!Ember.isEmpty(this.allSiteSettings)) return; + if (!!isEmpty(this.allSiteSettings)) return; let filter; if (this.filter) { @@ -73,13 +77,14 @@ export default Ember.Controller.extend({ ); }, - filterContent: debounce(function() { + @observes("filter", "onlyOverridden", "model") + filterContent: discourseDebounce(function() { if (this._skipBounce) { this.set("_skipBounce", false); } else { this.filterContentNow(); } - }, 250).observes("filter", "onlyOverridden", "model"), + }, 250), actions: { clearFilter() { diff --git a/app/assets/javascripts/admin/controllers/admin-site-text-edit.js.es6 b/app/assets/javascripts/admin/controllers/admin-site-text-edit.js.es6 index cb361ed818..d24a172910 100644 --- a/app/assets/javascripts/admin/controllers/admin-site-text-edit.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-site-text-edit.js.es6 @@ -1,9 +1,16 @@ +import discourseComputed from "discourse-common/utils/decorators"; +import Controller from "@ember/controller"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { bufferedProperty } from "discourse/mixins/buffered-content"; -export default Ember.Controller.extend(bufferedProperty("siteText"), { +export default Controller.extend(bufferedProperty("siteText"), { saved: false, + @discourseComputed("buffered.value") + saveDisabled(value) { + return this.siteText.value === value; + }, + actions: { saveChanges() { const buffered = this.buffered; diff --git a/app/assets/javascripts/admin/controllers/admin-site-text-index.js.es6 b/app/assets/javascripts/admin/controllers/admin-site-text-index.js.es6 index 1c484e4287..1a1b266ae1 100644 --- a/app/assets/javascripts/admin/controllers/admin-site-text-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-site-text-index.js.es6 @@ -1,6 +1,8 @@ +import { debounce } from "@ember/runloop"; +import Controller from "@ember/controller"; let lastSearch; -export default Ember.Controller.extend({ +export default Controller.extend({ searching: false, siteTexts: null, preferred: false, @@ -26,14 +28,14 @@ export default Ember.Controller.extend({ toggleOverridden() { this.toggleProperty("overridden"); this.set("searching", true); - Ember.run.debounce(this, this._performSearch, 400); + debounce(this, this._performSearch, 400); }, search() { const q = this.q; if (q !== lastSearch) { this.set("searching", true); - Ember.run.debounce(this, this._performSearch, 400); + debounce(this, this._performSearch, 400); lastSearch = q; } } diff --git a/app/assets/javascripts/admin/controllers/admin-user-badges.js.es6 b/app/assets/javascripts/admin/controllers/admin-user-badges.js.es6 index d419cf9e34..3bedafb45c 100644 --- a/app/assets/javascripts/admin/controllers/admin-user-badges.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-user-badges.js.es6 @@ -1,13 +1,17 @@ +import discourseComputed from "discourse-common/utils/decorators"; +import { alias, sort } from "@ember/object/computed"; +import { next } from "@ember/runloop"; +import { inject } from "@ember/controller"; +import Controller from "@ember/controller"; import GrantBadgeController from "discourse/mixins/grant-badge-controller"; import { popupAjaxError } from "discourse/lib/ajax-error"; -import computed from "ember-addons/ember-computed-decorators"; -export default Ember.Controller.extend(GrantBadgeController, { - adminUser: Ember.inject.controller(), - user: Ember.computed.alias("adminUser.model"), - userBadges: Ember.computed.alias("model"), - allBadges: Ember.computed.alias("badges"), - sortedBadges: Ember.computed.sort("model", "badgeSortOrder"), +export default Controller.extend(GrantBadgeController, { + adminUser: inject(), + user: alias("adminUser.model"), + userBadges: alias("model"), + allBadges: alias("badges"), + sortedBadges: sort("model", "badgeSortOrder"), init() { this._super(...arguments); @@ -15,7 +19,7 @@ export default Ember.Controller.extend(GrantBadgeController, { this.badgeSortOrder = ["granted_at:desc"]; }, - @computed("model", "model.[]", "model.expandedBadges.[]") + @discourseComputed("model", "model.[]", "model.expandedBadges.[]") groupedBadges() { const allBadges = this.model; @@ -69,7 +73,7 @@ export default Ember.Controller.extend(GrantBadgeController, { ).then( () => { this.set("badgeReason", ""); - Ember.run.next(() => { + next(() => { // Update the selected badge ID after the combobox has re-rendered. const newSelectedBadge = this.grantableBadges[0]; if (newSelectedBadge) { diff --git a/app/assets/javascripts/admin/controllers/admin-user-fields.js.es6 b/app/assets/javascripts/admin/controllers/admin-user-fields.js.es6 index 7d2a6b3d56..b81b08f559 100644 --- a/app/assets/javascripts/admin/controllers/admin-user-fields.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-user-fields.js.es6 @@ -1,11 +1,13 @@ +import { gte, sort } from "@ember/object/computed"; +import Controller from "@ember/controller"; import { popupAjaxError } from "discourse/lib/ajax-error"; const MAX_FIELDS = 20; -export default Ember.Controller.extend({ +export default Controller.extend({ fieldTypes: null, - createDisabled: Ember.computed.gte("model.length", MAX_FIELDS), - sortedFields: Ember.computed.sort("model", "fieldSortOrder"), + createDisabled: gte("model.length", MAX_FIELDS), + sortedFields: sort("model", "fieldSortOrder"), init() { this._super(...arguments); diff --git a/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 b/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 index 79eceed9f1..0460c314c7 100644 --- a/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 @@ -1,39 +1,41 @@ +import { notEmpty, and } from "@ember/object/computed"; +import { inject as service } from "@ember/service"; +import Controller from "@ember/controller"; import { ajax } from "discourse/lib/ajax"; import CanCheckEmails from "discourse/mixins/can-check-emails"; import { propertyNotEqual, setting } from "discourse/lib/computed"; import { userPath } from "discourse/lib/url"; import { popupAjaxError } from "discourse/lib/ajax-error"; -import { default as computed } from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { fmt } from "discourse/lib/computed"; +import { htmlSafe } from "@ember/template"; -export default Ember.Controller.extend(CanCheckEmails, { - adminTools: Ember.inject.service(), +export default Controller.extend(CanCheckEmails, { + adminTools: service(), originalPrimaryGroupId: null, customGroupIdsBuffer: null, availableGroups: null, userTitleValue: null, showBadges: setting("enable_badges"), - hasLockedTrustLevel: Ember.computed.notEmpty( - "model.manual_locked_trust_level" - ), + hasLockedTrustLevel: notEmpty("model.manual_locked_trust_level"), primaryGroupDirty: propertyNotEqual( "originalPrimaryGroupId", "model.primary_group_id" ), - canDisableSecondFactor: Ember.computed.and( + canDisableSecondFactor: and( "model.second_factor_enabled", "model.can_disable_second_factor" ), - @computed("model.customGroups") + @discourseComputed("model.customGroups") customGroupIds(customGroups) { return customGroups.mapBy("id"); }, - @computed("customGroupIdsBuffer", "customGroupIds") + @discourseComputed("customGroupIdsBuffer", "customGroupIds") customGroupsDirty(buffer, original) { if (buffer === null) return false; @@ -42,36 +44,40 @@ export default Ember.Controller.extend(CanCheckEmails, { : true; }, - @computed("model.automaticGroups") + @discourseComputed("model.automaticGroups") automaticGroups(automaticGroups) { return automaticGroups .map(group => { - const name = Ember.String.htmlSafe(group.name); + const name = htmlSafe(group.name); return `${name}`; }) .join(", "); }, - @computed("model.associated_accounts") + @discourseComputed("model.associated_accounts") associatedAccountsLoaded(associatedAccounts) { return typeof associatedAccounts !== "undefined"; }, - @computed("model.associated_accounts") + @discourseComputed("model.associated_accounts") associatedAccounts(associatedAccounts) { return associatedAccounts .map(provider => `${provider.name} (${provider.description})`) .join(", "); }, - @computed("model.user_fields.[]") + @discourseComputed("model.user_fields.[]") userFields(userFields) { return this.site.collectUserFields(userFields); }, preferencesPath: fmt("model.username_lower", userPath("%@/preferences")), - @computed("model.can_delete_all_posts", "model.staff", "model.post_count") + @discourseComputed( + "model.can_delete_all_posts", + "model.staff", + "model.post_count" + ) deleteAllPostsExplanation(canDeleteAllPosts, staff, postCount) { if (canDeleteAllPosts) { return null; @@ -91,7 +97,7 @@ export default Ember.Controller.extend(CanCheckEmails, { } }, - @computed("model.canBeDeleted", "model.staff") + @discourseComputed("model.canBeDeleted", "model.staff") deleteExplanation(canBeDeleted, staff) { if (canBeDeleted) { return null; @@ -134,7 +140,7 @@ export default Ember.Controller.extend(CanCheckEmails, { return this.model.resetBounceScore(); }, approve() { - return this.model.approve(); + return this.model.approve(this.currentUser); }, deactivate() { return this.model.deactivate(); @@ -257,10 +263,6 @@ export default Ember.Controller.extend(CanCheckEmails, { .finally(() => this.toggleProperty("editingTitle")); }, - generateApiKey() { - this.model.generateApiKey(); - }, - saveCustomGroups() { const currentIds = this.customGroupIds; const bufferedIds = this.customGroupIdsBuffer; @@ -276,7 +278,7 @@ export default Ember.Controller.extend(CanCheckEmails, { }, resetCustomGroups() { - this.set("customGroupIdsBuffer", null); + this.set("customGroupIdsBuffer", this.model.customGroups.mapBy("id")); }, savePrimaryGroup() { @@ -293,32 +295,6 @@ export default Ember.Controller.extend(CanCheckEmails, { resetPrimaryGroup() { this.set("model.primary_group_id", this.originalPrimaryGroupId); - }, - - regenerateApiKey() { - bootbox.confirm( - I18n.t("admin.api.confirm_regen"), - I18n.t("no_value"), - I18n.t("yes_value"), - result => { - if (result) { - this.model.generateApiKey(); - } - } - ); - }, - - revokeApiKey() { - bootbox.confirm( - I18n.t("admin.api.confirm_revoke"), - I18n.t("no_value"), - I18n.t("yes_value"), - result => { - if (result) { - this.model.revokeApiKey(); - } - } - ); } } }); diff --git a/app/assets/javascripts/admin/controllers/admin-user.js.es6 b/app/assets/javascripts/admin/controllers/admin-user.js.es6 index 77c79b724a..cf6c4e3aa2 100644 --- a/app/assets/javascripts/admin/controllers/admin-user.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-user.js.es6 @@ -1 +1,2 @@ -export default Ember.Controller.extend(); +import Controller from "@ember/controller"; +export default Controller.extend(); diff --git a/app/assets/javascripts/admin/controllers/admin-users-list-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-users-list-show.js.es6 index 2b5a727730..8776fee466 100644 --- a/app/assets/javascripts/admin/controllers/admin-users-list-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-users-list-show.js.es6 @@ -1,10 +1,11 @@ -import debounce from "discourse/lib/debounce"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; +import Controller from "@ember/controller"; +import discourseDebounce from "discourse/lib/debounce"; import { i18n } from "discourse/lib/computed"; import AdminUser from "admin/models/admin-user"; import CanCheckEmails from "discourse/mixins/can-check-emails"; -import computed from "ember-addons/ember-computed-decorators"; -export default Ember.Controller.extend(CanCheckEmails, { +export default Controller.extend(CanCheckEmails, { model: null, query: null, order: null, @@ -15,32 +16,65 @@ export default Ember.Controller.extend(CanCheckEmails, { selectAll: false, searchHint: i18n("search_hint"), - @computed("query") + init() { + this._super(...arguments); + + this._page = 1; + this._results = []; + this._canLoadMore = true; + }, + + @discourseComputed("query") title(query) { return I18n.t("admin.users.titles." + query); }, - _filterUsers: debounce(function() { + @observes("listFilter") + _filterUsers: discourseDebounce(function() { + this.resetFilters(); + }, 250), + + resetFilters() { + this._page = 1; + this._results = []; + this._canLoadMore = true; this._refreshUsers(); - }, 250).observes("listFilter"), + }, _refreshUsers() { + if (!this._canLoadMore) { + return; + } + this.set("refreshing", true); AdminUser.findAll(this.query, { filter: this.listFilter, show_emails: this.showEmails, order: this.order, - ascending: this.ascending + ascending: this.ascending, + page: this._page }) - .then(result => this.set("model", result)) + .then(result => { + if (!result || result.length === 0) { + this._canLoadMore = false; + } + + this._results = this._results.concat(result); + this.set("model", this._results); + }) .finally(() => this.set("refreshing", false)); }, actions: { + loadMore() { + this._page += 1; + this._refreshUsers(); + }, + toggleEmailVisibility() { this.toggleProperty("showEmails"); - this._refreshUsers(); + this.resetFilters(); } } }); diff --git a/app/assets/javascripts/admin/controllers/admin-watched-words-action.js.es6 b/app/assets/javascripts/admin/controllers/admin-watched-words-action.js.es6 index 2e38279b54..d6f52c13fd 100644 --- a/app/assets/javascripts/admin/controllers/admin-watched-words-action.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-watched-words-action.js.es6 @@ -1,13 +1,24 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import { or } from "@ember/object/computed"; +import { schedule } from "@ember/runloop"; +import { inject } from "@ember/controller"; +import Controller from "@ember/controller"; import WatchedWord from "admin/models/watched-word"; +import { ajax } from "discourse/lib/ajax"; +import { fmt } from "discourse/lib/computed"; +import showModal from "discourse/lib/show-modal"; -export default Ember.Controller.extend({ +export default Controller.extend({ actionNameKey: null, - adminWatchedWords: Ember.inject.controller(), - showWordsList: Ember.computed.or( + adminWatchedWords: inject(), + showWordsList: or( "adminWatchedWords.filtered", "adminWatchedWords.showWords" ), + downloadLink: fmt( + "actionNameKey", + "/admin/logs/watched_words/action/%@/download" + ), findAction(actionName) { return (this.get("adminWatchedWords.model") || []).findBy( @@ -16,25 +27,24 @@ export default Ember.Controller.extend({ ); }, - @computed("actionNameKey", "adminWatchedWords.model") - filteredContent(actionNameKey) { - if (!actionNameKey) { - return []; - } - - const a = this.findAction(actionNameKey); - return a ? a.words : []; + @discourseComputed("actionNameKey", "adminWatchedWords.model") + currentAction(actionName) { + return this.findAction(actionName); }, - @computed("actionNameKey") + @discourseComputed("currentAction.words.[]", "adminWatchedWords.model") + filteredContent(words) { + return words || []; + }, + + @discourseComputed("actionNameKey") actionDescription(actionNameKey) { return I18n.t("admin.watched_words.action_descriptions." + actionNameKey); }, - @computed("actionNameKey", "adminWatchedWords.model") - wordCount(actionNameKey) { - const a = this.findAction(actionNameKey); - return a ? a.words.length : 0; + @discourseComputed("currentAction.count") + wordCount(count) { + return count || 0; }, actions: { @@ -43,7 +53,7 @@ export default Ember.Controller.extend({ if (a) { a.words.unshiftObject(arg); a.incrementProperty("count"); - Ember.run.schedule("afterRender", () => { + schedule("afterRender", () => { // remove from other actions lists let match = null; this.get("adminWatchedWords.model").forEach(action => { @@ -62,10 +72,9 @@ export default Ember.Controller.extend({ }, recordRemoved(arg) { - const a = this.findAction(this.actionNameKey); - if (a) { - a.words.removeObject(arg); - a.decrementProperty("count"); + if (this.currentAction) { + this.currentAction.words.removeObject(arg); + this.currentAction.decrementProperty("count"); } }, @@ -73,6 +82,40 @@ export default Ember.Controller.extend({ WatchedWord.findAll().then(data => { this.set("adminWatchedWords.model", data); }); + }, + + clearAll() { + const actionKey = this.actionNameKey; + bootbox.confirm( + I18n.t(`admin.watched_words.clear_all_confirm_${actionKey}`), + I18n.t("no_value"), + I18n.t("yes_value"), + result => { + if (result) { + ajax(`/admin/logs/watched_words/action/${actionKey}.json`, { + method: "DELETE" + }).then(() => { + const action = this.findAction(actionKey); + if (action) { + action.setProperties({ + words: [], + count: 0 + }); + } + }); + } + } + ); + }, + + test() { + WatchedWord.findAll().then(data => { + this.set("adminWatchedWords.model", data); + showModal("admin-watched-word-test", { + admin: true, + model: this.currentAction + }); + }); } } }); diff --git a/app/assets/javascripts/admin/controllers/admin-watched-words.js.es6 b/app/assets/javascripts/admin/controllers/admin-watched-words.js.es6 index 20ed611781..dbc2afe1dc 100644 --- a/app/assets/javascripts/admin/controllers/admin-watched-words.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-watched-words.js.es6 @@ -1,14 +1,19 @@ -import debounce from "discourse/lib/debounce"; +import { isEmpty } from "@ember/utils"; +import { alias } from "@ember/object/computed"; +import EmberObject from "@ember/object"; +import Controller from "@ember/controller"; +import discourseDebounce from "discourse/lib/debounce"; +import { observes } from "discourse-common/utils/decorators"; -export default Ember.Controller.extend({ +export default Controller.extend({ filter: null, filtered: false, showWords: false, - disableShowWords: Ember.computed.alias("filtered"), + disableShowWords: alias("filtered"), regularExpressions: null, filterContentNow() { - if (!!Ember.isEmpty(this.allWatchedWords)) return; + if (!!isEmpty(this.allWatchedWords)) return; let filter; if (this.filter) { @@ -27,7 +32,7 @@ export default Ember.Controller.extend({ return wordRecord.word.indexOf(filter) > -1; }); matchesByAction.pushObject( - Ember.Object.create({ + EmberObject.create({ nameKey: wordsForAction.nameKey, name: wordsForAction.name, words: wordRecords, @@ -39,10 +44,11 @@ export default Ember.Controller.extend({ this.set("model", matchesByAction); }, - filterContent: debounce(function() { + @observes("filter") + filterContent: discourseDebounce(function() { this.filterContentNow(); - this.set("filtered", !Ember.isEmpty(this.filter)); - }, 250).observes("filter"), + this.set("filtered", !isEmpty(this.filter)); + }, 250), actions: { clearFilter() { diff --git a/app/assets/javascripts/admin/controllers/admin-web-hooks-show-events.js.es6 b/app/assets/javascripts/admin/controllers/admin-web-hooks-show-events.js.es6 index 388fab304f..6cd94efb68 100644 --- a/app/assets/javascripts/admin/controllers/admin-web-hooks-show-events.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-web-hooks-show-events.js.es6 @@ -1,10 +1,12 @@ +import discourseComputed from "discourse-common/utils/decorators"; +import { alias } from "@ember/object/computed"; +import Controller from "@ember/controller"; import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; -import computed from "ember-addons/ember-computed-decorators"; -export default Ember.Controller.extend({ +export default Controller.extend({ pingDisabled: false, - incomingCount: Ember.computed.alias("incomingEventIds.length"), + incomingCount: alias("incomingEventIds.length"), init() { this._super(...arguments); @@ -12,7 +14,7 @@ export default Ember.Controller.extend({ this.incomingEventIds = []; }, - @computed("incomingCount") + @discourseComputed("incomingCount") hasIncoming(incomingCount) { return incomingCount > 0; }, diff --git a/app/assets/javascripts/admin/controllers/admin-web-hooks-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-web-hooks-show.js.es6 index 2a637d7278..4ba34034f3 100644 --- a/app/assets/javascripts/admin/controllers/admin-web-hooks-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-web-hooks-show.js.es6 @@ -1,20 +1,24 @@ +import discourseComputed from "discourse-common/utils/decorators"; +import { isEmpty } from "@ember/utils"; +import { alias } from "@ember/object/computed"; +import { inject } from "@ember/controller"; +import Controller from "@ember/controller"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { extractDomainFromUrl } from "discourse/lib/utilities"; -import computed from "ember-addons/ember-computed-decorators"; -import InputValidation from "discourse/models/input-validation"; +import EmberObject from "@ember/object"; -export default Ember.Controller.extend({ - adminWebHooks: Ember.inject.controller(), - eventTypes: Ember.computed.alias("adminWebHooks.eventTypes"), - defaultEventTypes: Ember.computed.alias("adminWebHooks.defaultEventTypes"), - contentTypes: Ember.computed.alias("adminWebHooks.contentTypes"), +export default Controller.extend({ + adminWebHooks: inject(), + eventTypes: alias("adminWebHooks.eventTypes"), + defaultEventTypes: alias("adminWebHooks.defaultEventTypes"), + contentTypes: alias("adminWebHooks.contentTypes"), - @computed + @discourseComputed showTagsFilter() { return this.siteSettings.tagging_enabled; }, - @computed("model.isSaving", "saved", "saveButtonDisabled") + @discourseComputed("model.isSaving", "saved", "saveButtonDisabled") savingStatus(isSaving, saved, saveButtonDisabled) { if (isSaving) { return I18n.t("saving"); @@ -26,25 +30,25 @@ export default Ember.Controller.extend({ return ""; }, - @computed("model.isNew") + @discourseComputed("model.isNew") saveButtonText(isNew) { return isNew ? I18n.t("admin.web_hooks.create") : I18n.t("admin.web_hooks.save"); }, - @computed("model.secret") + @discourseComputed("model.secret") secretValidation(secret) { - if (!Ember.isEmpty(secret)) { + if (!isEmpty(secret)) { if (secret.indexOf(" ") !== -1) { - return InputValidation.create({ + return EmberObject.create({ failed: true, reason: I18n.t("admin.web_hooks.secret_invalid") }); } if (secret.length < 12) { - return InputValidation.create({ + return EmberObject.create({ failed: true, reason: I18n.t("admin.web_hooks.secret_too_short") }); @@ -52,17 +56,17 @@ export default Ember.Controller.extend({ } }, - @computed("model.wildcard_web_hook", "model.web_hook_event_types.[]") + @discourseComputed("model.wildcard_web_hook", "model.web_hook_event_types.[]") eventTypeValidation(isWildcard, eventTypes) { - if (!isWildcard && Ember.isEmpty(eventTypes)) { - return InputValidation.create({ + if (!isWildcard && isEmpty(eventTypes)) { + return EmberObject.create({ failed: true, reason: I18n.t("admin.web_hooks.event_type_missing") }); } }, - @computed( + @discourseComputed( "model.isSaving", "secretValidation", "eventTypeValidation", @@ -76,7 +80,7 @@ export default Ember.Controller.extend({ ) { return isSaving ? false - : secretValidation || eventTypeValidation || Ember.isEmpty(payloadUrl); + : secretValidation || eventTypeValidation || isEmpty(payloadUrl); }, actions: { diff --git a/app/assets/javascripts/admin/controllers/admin-web-hooks.js.es6 b/app/assets/javascripts/admin/controllers/admin-web-hooks.js.es6 index 696541c1cf..f9f401e330 100644 --- a/app/assets/javascripts/admin/controllers/admin-web-hooks.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-web-hooks.js.es6 @@ -1,6 +1,7 @@ +import Controller from "@ember/controller"; import { popupAjaxError } from "discourse/lib/ajax-error"; -export default Ember.Controller.extend({ +export default Controller.extend({ actions: { destroy(webhook) { return bootbox.confirm( diff --git a/app/assets/javascripts/admin/controllers/admin.js.es6 b/app/assets/javascripts/admin/controllers/admin.js.es6 index 9823f0fa79..641643d573 100644 --- a/app/assets/javascripts/admin/controllers/admin.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin.js.es6 @@ -1,19 +1,22 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import { inject as service } from "@ember/service"; +import Controller from "@ember/controller"; +import { dasherize } from "@ember/string"; -export default Ember.Controller.extend({ - application: Ember.inject.controller(), +export default Controller.extend({ + router: service(), - @computed("siteSettings.enable_group_directory") + @discourseComputed("siteSettings.enable_group_directory") showGroups(enableGroupDirectory) { return !enableGroupDirectory; }, - @computed("siteSettings.enable_badges") + @discourseComputed("siteSettings.enable_badges") showBadges(enableBadges) { return this.currentUser.get("admin") && enableBadges; }, - @computed("application.currentPath") + @discourseComputed("router._router.currentPath") adminContentsClassName(currentPath) { let cssClasses = currentPath .split(".") @@ -25,7 +28,7 @@ export default Ember.Controller.extend({ segment !== "admin" ); }) - .map(Ember.String.dasherize) + .map(dasherize) .join(" "); // this is done to avoid breaking css customizations diff --git a/app/assets/javascripts/admin/controllers/modals/admin-add-upload.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-add-upload.js.es6 index 73f1f1e11f..c7d9447d8d 100644 --- a/app/assets/javascripts/admin/controllers/modals/admin-add-upload.js.es6 +++ b/app/assets/javascripts/admin/controllers/modals/admin-add-upload.js.es6 @@ -1,9 +1,10 @@ +import { isEmpty } from "@ember/utils"; +import { and, not } from "@ember/object/computed"; +import { inject } from "@ember/controller"; +import Controller from "@ember/controller"; import ModalFunctionality from "discourse/mixins/modal-functionality"; import { ajax } from "discourse/lib/ajax"; -import { - default as computed, - observes -} from "ember-addons/ember-computed-decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { popupAjaxError } from "discourse/lib/ajax-error"; const THEME_FIELD_VARIABLE_TYPE_IDS = [2, 3, 4]; @@ -52,8 +53,8 @@ const SCSS_VARIABLE_NAMES = [ "love-low" ]; -export default Ember.Controller.extend(ModalFunctionality, { - adminCustomizeThemesShow: Ember.inject.controller(), +export default Controller.extend(ModalFunctionality, { + adminCustomizeThemesShow: inject(), uploadUrl: "/admin/themes/upload_asset", @@ -62,10 +63,10 @@ export default Ember.Controller.extend(ModalFunctionality, { this.set("fileSelected", false); }, - enabled: Ember.computed.and("nameValid", "fileSelected"), - disabled: Ember.computed.not("enabled"), + enabled: and("nameValid", "fileSelected"), + disabled: not("enabled"), - @computed("name", "adminCustomizeThemesShow.model.theme_fields") + @discourseComputed("name", "adminCustomizeThemesShow.model.theme_fields") errorMessage(name, themeFields) { if (name) { if (!name.match(/^[a-z_][a-z0-9_-]*$/i)) { @@ -90,7 +91,7 @@ export default Ember.Controller.extend(ModalFunctionality, { return null; }, - @computed("errorMessage") + @discourseComputed("errorMessage") nameValid(errorMessage) { return null === errorMessage; }, @@ -104,7 +105,7 @@ export default Ember.Controller.extend(ModalFunctionality, { actions: { updateName() { let name = this.name; - if (Ember.isEmpty(name)) { + if (isEmpty(name)) { name = $("#file-input")[0].files[0].name; this.set("name", name.split(".")[0]); } diff --git a/app/assets/javascripts/admin/controllers/modals/admin-badge-preview.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-badge-preview.js.es6 index 045a96c2aa..84325f90d1 100644 --- a/app/assets/javascripts/admin/controllers/modals/admin-badge-preview.js.es6 +++ b/app/assets/javascripts/admin/controllers/modals/admin-badge-preview.js.es6 @@ -1,12 +1,14 @@ -import { default as computed } from "ember-addons/ember-computed-decorators"; +import { alias, map } from "@ember/object/computed"; +import Controller from "@ember/controller"; +import discourseComputed from "discourse-common/utils/decorators"; import { escapeExpression } from "discourse/lib/utilities"; -export default Ember.Controller.extend({ - sample: Ember.computed.alias("model.sample"), - errors: Ember.computed.alias("model.errors"), - count: Ember.computed.alias("model.grant_count"), +export default Controller.extend({ + sample: alias("model.sample"), + errors: alias("model.errors"), + count: alias("model.grant_count"), - @computed("count", "sample.length") + @discourseComputed("count", "sample.length") countWarning(count, sampleLength) { if (count <= 10) { return sampleLength !== count; @@ -15,12 +17,12 @@ export default Ember.Controller.extend({ } }, - @computed("model.query_plan") + @discourseComputed("model.query_plan") hasQueryPlan(queryPlan) { return !!queryPlan; }, - @computed("model.query_plan") + @discourseComputed("model.query_plan") queryPlanHtml(queryPlan) { let output = `
`;
 
@@ -33,7 +35,7 @@ export default Ember.Controller.extend({
     return output;
   },
 
-  processedSample: Ember.computed.map("model.sample", grant => {
+  processedSample: map("model.sample", grant => {
     let i18nKey = "admin.badges.preview.grant.with";
     const i18nParams = { username: escapeExpression(grant.username) };
 
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-color-scheme-select-base.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-color-scheme-select-base.js.es6
index 374b6392f4..e0b7bfe7e6 100644
--- a/app/assets/javascripts/admin/controllers/modals/admin-color-scheme-select-base.js.es6
+++ b/app/assets/javascripts/admin/controllers/modals/admin-color-scheme-select-base.js.es6
@@ -1,7 +1,20 @@
+import { inject } from "@ember/controller";
+import Controller from "@ember/controller";
 import ModalFunctionality from "discourse/mixins/modal-functionality";
 
-export default Ember.Controller.extend(ModalFunctionality, {
-  adminCustomizeColors: Ember.inject.controller(),
+export default Controller.extend(ModalFunctionality, {
+  adminCustomizeColors: inject(),
+
+  selectedBaseThemeId: null,
+
+  init() {
+    this._super(...arguments);
+
+    const defaultScheme = this.get(
+      "adminCustomizeColors.baseColorSchemes.0.base_scheme_id"
+    );
+    this.set("selectedBaseThemeId", defaultScheme);
+  },
 
   actions: {
     selectBase() {
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-edit-badge-groupings.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-edit-badge-groupings.js.es6
index 76dc4c07d7..1629aab70b 100644
--- a/app/assets/javascripts/admin/controllers/modals/admin-edit-badge-groupings.js.es6
+++ b/app/assets/javascripts/admin/controllers/modals/admin-edit-badge-groupings.js.es6
@@ -1,8 +1,9 @@
+import Controller from "@ember/controller";
 import { ajax } from "discourse/lib/ajax";
 import ModalFunctionality from "discourse/mixins/modal-functionality";
-import { observes } from "ember-addons/ember-computed-decorators";
+import { observes } from "discourse-common/utils/decorators";
 
-export default Ember.Controller.extend(ModalFunctionality, {
+export default Controller.extend(ModalFunctionality, {
   @observes("model")
   modelChanged() {
     const model = this.model;
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-incoming-email.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-incoming-email.js.es6
index e19afcd359..cca2cc54bf 100644
--- a/app/assets/javascripts/admin/controllers/modals/admin-incoming-email.js.es6
+++ b/app/assets/javascripts/admin/controllers/modals/admin-incoming-email.js.es6
@@ -1,11 +1,12 @@
+import discourseComputed from "discourse-common/utils/decorators";
+import Controller from "@ember/controller";
 import ModalFunctionality from "discourse/mixins/modal-functionality";
 import IncomingEmail from "admin/models/incoming-email";
-import computed from "ember-addons/ember-computed-decorators";
 import { longDate } from "discourse/lib/formatter";
 import { popupAjaxError } from "discourse/lib/ajax-error";
 
-export default Ember.Controller.extend(ModalFunctionality, {
-  @computed("model.date")
+export default Controller.extend(ModalFunctionality, {
+  @discourseComputed("model.date")
   date(d) {
     return longDate(d);
   },
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-install-theme.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-install-theme.js.es6
index a81e8caef7..cfb61ccc8f 100644
--- a/app/assets/javascripts/admin/controllers/modals/admin-install-theme.js.es6
+++ b/app/assets/javascripts/admin/controllers/modals/admin-install-theme.js.es6
@@ -1,34 +1,35 @@
+import { equal, match, alias } from "@ember/object/computed";
+import { inject } from "@ember/controller";
+import Controller from "@ember/controller";
 import ModalFunctionality from "discourse/mixins/modal-functionality";
 import { ajax } from "discourse/lib/ajax";
 import { popupAjaxError } from "discourse/lib/ajax-error";
-import {
-  default as computed,
-  observes
-} from "ember-addons/ember-computed-decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
 import { THEMES, COMPONENTS } from "admin/models/theme";
 import { POPULAR_THEMES } from "discourse-common/helpers/popular-themes";
+import { set } from "@ember/object";
 
 const MIN_NAME_LENGTH = 4;
 
-export default Ember.Controller.extend(ModalFunctionality, {
-  popular: Ember.computed.equal("selection", "popular"),
-  local: Ember.computed.equal("selection", "local"),
-  remote: Ember.computed.equal("selection", "remote"),
-  create: Ember.computed.equal("selection", "create"),
+export default Controller.extend(ModalFunctionality, {
+  popular: equal("selection", "popular"),
+  local: equal("selection", "local"),
+  remote: equal("selection", "remote"),
+  create: equal("selection", "create"),
   selection: "popular",
-  adminCustomizeThemes: Ember.inject.controller(),
+  adminCustomizeThemes: inject(),
   loading: false,
   keyGenUrl: "/admin/themes/generate_key_pair",
   importUrl: "/admin/themes/import",
   recordType: "theme",
-  checkPrivate: Ember.computed.match("uploadUrl", /^git/),
+  checkPrivate: match("uploadUrl", /^git/),
   localFile: null,
   uploadUrl: null,
   urlPlaceholder: "https://github.com/discourse/sample_theme",
   advancedVisible: false,
-  themesController: Ember.inject.controller("adminCustomizeThemes"),
-  selectedType: Ember.computed.alias("themesController.currentTab"),
-  component: Ember.computed.equal("selectedType", COMPONENTS),
+  themesController: inject("adminCustomizeThemes"),
+  selectedType: alias("themesController.currentTab"),
+  component: equal("selectedType", COMPONENTS),
 
   init() {
     this._super(...arguments);
@@ -39,17 +40,17 @@ export default Ember.Controller.extend(ModalFunctionality, {
     ];
   },
 
-  @computed("themesController.installedThemes")
+  @discourseComputed("themesController.installedThemes")
   themes(installedThemes) {
     return POPULAR_THEMES.map(t => {
       if (installedThemes.includes(t.name)) {
-        Ember.set(t, "installed", true);
+        set(t, "installed", true);
       }
       return t;
     });
   },
 
-  @computed(
+  @discourseComputed(
     "loading",
     "remote",
     "uploadUrl",
@@ -98,12 +99,12 @@ export default Ember.Controller.extend(ModalFunctionality, {
     }
   },
 
-  @computed("name")
+  @discourseComputed("name")
   nameTooShort(name) {
     return !name || name.length < MIN_NAME_LENGTH;
   },
 
-  @computed("component")
+  @discourseComputed("component")
   placeholder(component) {
     if (component) {
       return I18n.t("admin.customize.theme.component_name");
@@ -112,14 +113,14 @@ export default Ember.Controller.extend(ModalFunctionality, {
     }
   },
 
-  @computed("selection")
+  @discourseComputed("selection")
   submitLabel(selection) {
     return `admin.customize.theme.${
       selection === "create" ? "create" : "install"
     }`;
   },
 
-  @computed("privateChecked", "checkPrivate", "publicKey")
+  @discourseComputed("privateChecked", "checkPrivate", "publicKey")
   showPublicKey(privateChecked, checkPrivate, publicKey) {
     return privateChecked && checkPrivate && publicKey;
   },
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-reseed.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-reseed.js.es6
index f71c7eaf2e..176c46be36 100644
--- a/app/assets/javascripts/admin/controllers/modals/admin-reseed.js.es6
+++ b/app/assets/javascripts/admin/controllers/modals/admin-reseed.js.es6
@@ -1,7 +1,8 @@
+import Controller from "@ember/controller";
 import ModalFunctionality from "discourse/mixins/modal-functionality";
 import { ajax } from "discourse/lib/ajax";
 
-export default Ember.Controller.extend(ModalFunctionality, {
+export default Controller.extend(ModalFunctionality, {
   loading: true,
   reseeding: false,
   categories: null,
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-silence-user.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-silence-user.js.es6
index 79ec3e6945..d15264f46b 100644
--- a/app/assets/javascripts/admin/controllers/modals/admin-silence-user.js.es6
+++ b/app/assets/javascripts/admin/controllers/modals/admin-silence-user.js.es6
@@ -1,7 +1,9 @@
-import computed from "ember-addons/ember-computed-decorators";
+import discourseComputed from "discourse-common/utils/decorators";
+import { isEmpty } from "@ember/utils";
+import Controller from "@ember/controller";
 import PenaltyController from "admin/mixins/penalty-controller";
 
-export default Ember.Controller.extend(PenaltyController, {
+export default Controller.extend(PenaltyController, {
   silenceUntil: null,
   silencing: false,
 
@@ -10,11 +12,9 @@ export default Ember.Controller.extend(PenaltyController, {
     this.setProperties({ silenceUntil: null, silencing: false });
   },
 
-  @computed("silenceUntil", "reason", "silencing")
+  @discourseComputed("silenceUntil", "reason", "silencing")
   submitDisabled(silenceUntil, reason, silencing) {
-    return (
-      silencing || Ember.isEmpty(silenceUntil) || !reason || reason.length < 1
-    );
+    return silencing || isEmpty(silenceUntil) || !reason || reason.length < 1;
   },
 
   actions: {
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-staff-action-log-details.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-staff-action-log-details.js.es6
index 23420631f0..06110e0113 100644
--- a/app/assets/javascripts/admin/controllers/modals/admin-staff-action-log-details.js.es6
+++ b/app/assets/javascripts/admin/controllers/modals/admin-staff-action-log-details.js.es6
@@ -1,3 +1,4 @@
+import Controller from "@ember/controller";
 import ModalFunctionality from "discourse/mixins/modal-functionality";
 
-export default Ember.Controller.extend(ModalFunctionality);
+export default Controller.extend(ModalFunctionality);
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-start-backup.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-start-backup.js.es6
index 7bd96b326a..b4cc2f188d 100644
--- a/app/assets/javascripts/admin/controllers/modals/admin-start-backup.js.es6
+++ b/app/assets/javascripts/admin/controllers/modals/admin-start-backup.js.es6
@@ -1,7 +1,9 @@
+import { inject } from "@ember/controller";
+import Controller from "@ember/controller";
 import ModalFunctionality from "discourse/mixins/modal-functionality";
 
-export default Ember.Controller.extend(ModalFunctionality, {
-  adminBackupsLogs: Ember.inject.controller(),
+export default Controller.extend(ModalFunctionality, {
+  adminBackupsLogs: inject(),
 
   actions: {
     startBackupWithUploads() {
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-suspend-user.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-suspend-user.js.es6
index c5911322c5..03fa9fbc83 100644
--- a/app/assets/javascripts/admin/controllers/modals/admin-suspend-user.js.es6
+++ b/app/assets/javascripts/admin/controllers/modals/admin-suspend-user.js.es6
@@ -1,7 +1,9 @@
-import computed from "ember-addons/ember-computed-decorators";
+import discourseComputed from "discourse-common/utils/decorators";
+import { isEmpty } from "@ember/utils";
+import Controller from "@ember/controller";
 import PenaltyController from "admin/mixins/penalty-controller";
 
-export default Ember.Controller.extend(PenaltyController, {
+export default Controller.extend(PenaltyController, {
   suspendUntil: null,
   suspending: false,
 
@@ -10,11 +12,9 @@ export default Ember.Controller.extend(PenaltyController, {
     this.setProperties({ suspendUntil: null, suspending: false });
   },
 
-  @computed("suspendUntil", "reason", "suspending")
+  @discourseComputed("suspendUntil", "reason", "suspending")
   submitDisabled(suspendUntil, reason, suspending) {
-    return (
-      suspending || Ember.isEmpty(suspendUntil) || !reason || reason.length < 1
-    );
+    return suspending || isEmpty(suspendUntil) || !reason || reason.length < 1;
   },
 
   actions: {
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-theme-change.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-theme-change.js.es6
index 834376a2d6..e33284d233 100644
--- a/app/assets/javascripts/admin/controllers/modals/admin-theme-change.js.es6
+++ b/app/assets/javascripts/admin/controllers/modals/admin-theme-change.js.es6
@@ -1,7 +1,8 @@
+import Controller from "@ember/controller";
 import ModalFunctionality from "discourse/mixins/modal-functionality";
 import { ajax } from "discourse/lib/ajax";
 
-export default Ember.Controller.extend(ModalFunctionality, {
+export default Controller.extend(ModalFunctionality, {
   loadDiff() {
     this.set("loading", true);
     ajax(
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-uploaded-image-list.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-uploaded-image-list.js.es6
index 22aa327651..08e1e178e4 100644
--- a/app/assets/javascripts/admin/controllers/modals/admin-uploaded-image-list.js.es6
+++ b/app/assets/javascripts/admin/controllers/modals/admin-uploaded-image-list.js.es6
@@ -1,7 +1,8 @@
-import { on, observes } from "ember-addons/ember-computed-decorators";
+import Controller from "@ember/controller";
+import { on, observes } from "discourse-common/utils/decorators";
 import ModalFunctionality from "discourse/mixins/modal-functionality";
 
-export default Ember.Controller.extend(ModalFunctionality, {
+export default Controller.extend(ModalFunctionality, {
   @on("init")
   @observes("model.value")
   _setup() {
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-watched-word-test.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-watched-word-test.js.es6
new file mode 100644
index 0000000000..a3de68d4cd
--- /dev/null
+++ b/app/assets/javascripts/admin/controllers/modals/admin-watched-word-test.js.es6
@@ -0,0 +1,12 @@
+import Controller from "@ember/controller";
+import discourseComputed from "discourse-common/utils/decorators";
+import ModalFunctionality from "discourse/mixins/modal-functionality";
+
+export default Controller.extend(ModalFunctionality, {
+  @discourseComputed("value", "model.compiledRegularExpression")
+  matches(value, regexpString) {
+    if (!value || !regexpString) return;
+    let censorRegexp = new RegExp(regexpString, "ig");
+    return value.match(censorRegexp);
+  }
+});
diff --git a/app/assets/javascripts/admin/controllers/modals/site-setting-default-categories.js.es6 b/app/assets/javascripts/admin/controllers/modals/site-setting-default-categories.js.es6
new file mode 100644
index 0000000000..62e77ed8e6
--- /dev/null
+++ b/app/assets/javascripts/admin/controllers/modals/site-setting-default-categories.js.es6
@@ -0,0 +1,20 @@
+import Controller from "@ember/controller";
+import ModalFunctionality from "discourse/mixins/modal-functionality";
+
+export default Controller.extend(ModalFunctionality, {
+  onShow() {
+    this.set("updateExistingUsers", null);
+  },
+
+  actions: {
+    updateExistingUsers() {
+      this.set("updateExistingUsers", true);
+      this.send("closeModal");
+    },
+
+    cancel() {
+      this.set("updateExistingUsers", false);
+      this.send("closeModal");
+    }
+  }
+});
diff --git a/app/assets/javascripts/admin/helpers/disposition-icon.js.es6 b/app/assets/javascripts/admin/helpers/disposition-icon.js.es6
index 2a2440a294..0e1ea29a27 100644
--- a/app/assets/javascripts/admin/helpers/disposition-icon.js.es6
+++ b/app/assets/javascripts/admin/helpers/disposition-icon.js.es6
@@ -1,6 +1,7 @@
 import { iconHTML } from "discourse-common/lib/icon-library";
+import Helper from "@ember/component/helper";
 
-export default Ember.Helper.extend({
+export default Helper.extend({
   compute([disposition]) {
     if (!disposition) {
       return null;
diff --git a/app/assets/javascripts/admin/helpers/post-action-title.js.es6 b/app/assets/javascripts/admin/helpers/post-action-title.js.es6
index 5ce437bf4a..657aee2e9b 100644
--- a/app/assets/javascripts/admin/helpers/post-action-title.js.es6
+++ b/app/assets/javascripts/admin/helpers/post-action-title.js.es6
@@ -1,3 +1,5 @@
+import Helper from "@ember/component/helper";
+
 function postActionTitle([id, nameKey]) {
   let title = I18n.t(`admin.flags.short_names.${nameKey}`, {
     defaultValue: null
@@ -11,4 +13,4 @@ function postActionTitle([id, nameKey]) {
   return title;
 }
 
-export default Ember.Helper.helper(postActionTitle);
+export default Helper.helper(postActionTitle);
diff --git a/app/assets/javascripts/admin/mixins/penalty-controller.js.es6 b/app/assets/javascripts/admin/mixins/penalty-controller.js.es6
index b38e249da3..cb07e9d2ca 100644
--- a/app/assets/javascripts/admin/mixins/penalty-controller.js.es6
+++ b/app/assets/javascripts/admin/mixins/penalty-controller.js.es6
@@ -1,7 +1,9 @@
 import ModalFunctionality from "discourse/mixins/modal-functionality";
 import { popupAjaxError } from "discourse/lib/ajax-error";
+import Mixin from "@ember/object/mixin";
+import { Promise } from "rsvp";
 
-export default Ember.Mixin.create(ModalFunctionality, {
+export default Mixin.create(ModalFunctionality, {
   reason: null,
   message: null,
   postEdit: null,
@@ -25,7 +27,7 @@ export default Ember.Mixin.create(ModalFunctionality, {
 
   penalize(cb) {
     let before = this.before;
-    let promise = before ? before() : Ember.RSVP.resolve();
+    let promise = before ? before() : Promise.resolve();
 
     return promise
       .then(() => cb())
diff --git a/app/assets/javascripts/admin/mixins/period-computation.js.es6 b/app/assets/javascripts/admin/mixins/period-computation.js.es6
index 4323532e84..c7af0e4cb3 100644
--- a/app/assets/javascripts/admin/mixins/period-computation.js.es6
+++ b/app/assets/javascripts/admin/mixins/period-computation.js.es6
@@ -1,7 +1,8 @@
+import discourseComputed from "discourse-common/utils/decorators";
 import DiscourseURL from "discourse/lib/url";
-import computed from "ember-addons/ember-computed-decorators";
+import Mixin from "@ember/object/mixin";
 
-export default Ember.Mixin.create({
+export default Mixin.create({
   queryParams: ["period"],
   period: "monthly",
 
@@ -11,7 +12,7 @@ export default Ember.Mixin.create({
     this.availablePeriods = ["yearly", "quarterly", "monthly", "weekly"];
   },
 
-  @computed("period")
+  @discourseComputed("period")
   startDate(period) {
     let fullDay = moment()
       .locale("en")
@@ -36,7 +37,7 @@ export default Ember.Mixin.create({
     }
   },
 
-  @computed()
+  @discourseComputed()
   lastWeek() {
     return moment()
       .locale("en")
@@ -45,7 +46,7 @@ export default Ember.Mixin.create({
       .subtract(1, "week");
   },
 
-  @computed()
+  @discourseComputed()
   lastMonth() {
     return moment()
       .locale("en")
@@ -54,7 +55,7 @@ export default Ember.Mixin.create({
       .subtract(1, "month");
   },
 
-  @computed()
+  @discourseComputed()
   endDate() {
     return moment()
       .locale("en")
@@ -63,7 +64,7 @@ export default Ember.Mixin.create({
       .endOf("day");
   },
 
-  @computed()
+  @discourseComputed()
   today() {
     return moment()
       .locale("en")
diff --git a/app/assets/javascripts/admin/mixins/setting-component.js.es6 b/app/assets/javascripts/admin/mixins/setting-component.js.es6
index dbed60a799..c182abadd6 100644
--- a/app/assets/javascripts/admin/mixins/setting-component.js.es6
+++ b/app/assets/javascripts/admin/mixins/setting-component.js.es6
@@ -1,5 +1,11 @@
-import computed from "ember-addons/ember-computed-decorators";
+import discourseComputed from "discourse-common/utils/decorators";
+import { alias, oneWay } from "@ember/object/computed";
 import { categoryLinkHTML } from "discourse/helpers/category-link";
+import { on } from "@ember/object/evented";
+import Mixin from "@ember/object/mixin";
+import showModal from "discourse/lib/show-modal";
+import { Promise } from "rsvp";
+import { ajax } from "discourse/lib/ajax";
 
 const CUSTOM_TYPES = [
   "bool",
@@ -14,18 +20,27 @@ const CUSTOM_TYPES = [
   "compact_list",
   "secret_list",
   "upload",
-  "group_list"
+  "group_list",
+  "tag_list"
 ];
 
 const AUTO_REFRESH_ON_SAVE = ["logo", "logo_small", "large_icon"];
 
-export default Ember.Mixin.create({
-  classNameBindings: [":row", ":setting", "overridden", "typeClass"],
-  content: Ember.computed.alias("setting"),
-  validationMessage: null,
-  isSecret: Ember.computed.oneWay("setting.secret"),
+function splitPipes(str) {
+  if (typeof str === "string") {
+    return str.split("|").filter(Boolean);
+  } else {
+    return [];
+  }
+}
 
-  @computed("buffered.value", "setting.value")
+export default Mixin.create({
+  classNameBindings: [":row", ":setting", "overridden", "typeClass"],
+  content: alias("setting"),
+  validationMessage: null,
+  isSecret: oneWay("setting.secret"),
+
+  @discourseComputed("buffered.value", "setting.value")
   dirty(bufferVal, settingVal) {
     if (bufferVal === null || bufferVal === undefined) bufferVal = "";
     if (settingVal === null || settingVal === undefined) settingVal = "";
@@ -33,7 +48,7 @@ export default Ember.Mixin.create({
     return bufferVal.toString() !== settingVal.toString();
   },
 
-  @computed("setting", "buffered.value")
+  @discourseComputed("setting", "buffered.value")
   preview(setting, value) {
     // A bit hacky, but allows us to use helpers
     if (setting.get("setting") === "category_style") {
@@ -44,7 +59,6 @@ export default Ember.Mixin.create({
         });
       }
     }
-
     let preview = setting.get("preview");
     if (preview) {
       return new Handlebars.SafeString(
@@ -55,22 +69,22 @@ export default Ember.Mixin.create({
     }
   },
 
-  @computed("componentType")
+  @discourseComputed("componentType")
   typeClass(componentType) {
     return componentType.replace(/\_/g, "-");
   },
 
-  @computed("setting.setting")
-  settingName(setting) {
-    return setting.replace(/\_/g, " ");
+  @discourseComputed("setting.setting", "setting.label")
+  settingName(setting, label) {
+    return label || setting.replace(/\_/g, " ");
   },
 
-  @computed("type")
+  @discourseComputed("type")
   componentType(type) {
     return CUSTOM_TYPES.indexOf(type) !== -1 ? type : "string";
   },
 
-  @computed("setting")
+  @discourseComputed("setting")
   type(setting) {
     if (setting.type === "list" && setting.list_type) {
       return `${setting.list_type}_list`;
@@ -79,43 +93,127 @@ export default Ember.Mixin.create({
     return setting.type;
   },
 
-  @computed("typeClass")
+  @discourseComputed("typeClass")
   componentName(typeClass) {
     return "site-settings/" + typeClass;
   },
 
-  @computed("setting.default", "buffered.value")
+  @discourseComputed("setting.anyValue")
+  allowAny(anyValue) {
+    return anyValue !== false;
+  },
+
+  @discourseComputed("setting.default", "buffered.value")
   overridden(settingDefault, bufferedValue) {
     return settingDefault !== bufferedValue;
   },
 
-  _watchEnterKey: function() {
-    this.$().on("keydown.setting-enter", ".input-setting-string", e => {
+  @discourseComputed("buffered.value")
+  bufferedValues: splitPipes,
+
+  @discourseComputed("setting.defaultValues")
+  defaultValues: splitPipes,
+
+  @discourseComputed("defaultValues", "bufferedValues")
+  defaultIsAvailable(defaultValues, bufferedValues) {
+    return (
+      defaultValues &&
+      defaultValues.length > 0 &&
+      !defaultValues.every(value => bufferedValues.includes(value))
+    );
+  },
+
+  _watchEnterKey: on("didInsertElement", function() {
+    $(this.element).on("keydown.setting-enter", ".input-setting-string", e => {
       if (e.keyCode === 13) {
         // enter key
         this.send("save");
       }
     });
-  }.on("didInsertElement"),
+  }),
 
-  _removeBindings: function() {
-    this.$().off("keydown.setting-enter");
-  }.on("willDestroyElement"),
+  _removeBindings: on("willDestroyElement", function() {
+    $(this.element).off("keydown.setting-enter");
+  }),
 
   _save() {
     Ember.warn("You should define a `_save` method", {
       id: "discourse.setting-component.missing-save"
     });
-    return Ember.RSVP.resolve();
+    return Promise.resolve();
   },
 
   actions: {
+    update() {
+      const defaultUserPreferences = [
+        "default_email_digest_frequency",
+        "default_include_tl0_in_digests",
+        "default_email_level",
+        "default_email_messages_level",
+        "default_email_mailing_list_mode",
+        "default_email_mailing_list_mode_frequency",
+        "default_email_previous_replies",
+        "default_email_in_reply_to",
+        "default_other_new_topic_duration_minutes",
+        "default_other_auto_track_topics_after_msecs",
+        "default_other_notification_level_when_replying",
+        "default_other_external_links_in_new_tab",
+        "default_other_enable_quoting",
+        "default_other_enable_defer",
+        "default_other_dynamic_favicon",
+        "default_other_like_notification_frequency",
+        "default_topics_automatic_unpin",
+        "default_categories_watching",
+        "default_categories_tracking",
+        "default_categories_muted",
+        "default_categories_watching_first_post",
+        "default_tags_watching",
+        "default_tags_tracking",
+        "default_tags_muted",
+        "default_tags_watching_first_post",
+        "default_text_size",
+        "default_title_count_mode"
+      ];
+      const key = this.buffered.get("setting");
+
+      if (defaultUserPreferences.includes(key)) {
+        const data = {};
+        data[key] = this.buffered.get("value");
+
+        ajax(`/admin/site_settings/${key}/user_count.json`, {
+          type: "PUT",
+          data
+        }).then(result => {
+          const count = result.user_count;
+
+          if (count > 0) {
+            const controller = showModal("site-setting-default-categories", {
+              model: {
+                count: result.user_count,
+                key: key.replace(/_/g, " ")
+              },
+              admin: true
+            });
+
+            controller.set("onClose", () => {
+              this.updateExistingUsers = controller.updateExistingUsers;
+              this.send("save");
+            });
+          } else {
+            this.send("save");
+          }
+        });
+      } else {
+        this.send("save");
+      }
+    },
+
     save() {
       this._save()
         .then(() => {
           this.set("validationMessage", null);
           this.commitBuffer();
-          if (AUTO_REFRESH_ON_SAVE.includes(this.get("setting.setting"))) {
+          if (AUTO_REFRESH_ON_SAVE.includes(this.setting.setting)) {
             this.afterSave();
           }
         })
@@ -138,6 +236,17 @@ export default Ember.Mixin.create({
 
     toggleSecret() {
       this.toggleProperty("isSecret");
+    },
+
+    setDefaultValues() {
+      this.set(
+        "buffered.value",
+        this.bufferedValues
+          .concat(this.defaultValues)
+          .uniq()
+          .join("|")
+      );
+      return false;
     }
   }
 });
diff --git a/app/assets/javascripts/admin/mixins/setting-object.js.es6 b/app/assets/javascripts/admin/mixins/setting-object.js.es6
index ef047af732..0d97e79873 100644
--- a/app/assets/javascripts/admin/mixins/setting-object.js.es6
+++ b/app/assets/javascripts/admin/mixins/setting-object.js.es6
@@ -1,7 +1,10 @@
-import computed from "ember-addons/ember-computed-decorators";
+import discourseComputed from "discourse-common/utils/decorators";
+import { computed } from "@ember/object";
+import Mixin from "@ember/object/mixin";
+import { isPresent } from "@ember/utils";
 
-export default Ember.Mixin.create({
-  @computed("value", "default")
+export default Mixin.create({
+  @discourseComputed("value", "default")
   overridden(val, defaultVal) {
     if (val === null) val = "";
     if (defaultVal === null) defaultVal = "";
@@ -9,7 +12,35 @@ export default Ember.Mixin.create({
     return val.toString() !== defaultVal.toString();
   },
 
-  @computed("valid_values")
+  computedValueProperty: computed(
+    "valueProperty",
+    "validValues.[]",
+    function() {
+      if (isPresent(this.valueProperty)) {
+        return this.valueProperty;
+      }
+
+      if (isPresent(this.validValues.get("firstObject.value"))) {
+        return "value";
+      } else {
+        return null;
+      }
+    }
+  ),
+
+  computedNameProperty: computed("nameProperty", "validValues.[]", function() {
+    if (isPresent(this.nameProperty)) {
+      return this.nameProperty;
+    }
+
+    if (isPresent(this.validValues.get("firstObject.name"))) {
+      return "name";
+    } else {
+      return null;
+    }
+  }),
+
+  @discourseComputed("valid_values")
   validValues(validValues) {
     const vals = [],
       translateNames = this.translate_names;
@@ -24,7 +55,7 @@ export default Ember.Mixin.create({
     return vals;
   },
 
-  @computed("valid_values")
+  @discourseComputed("valid_values")
   allowsNone(validValues) {
     if (validValues && validValues.indexOf("") >= 0) {
       return "admin.settings.none";
diff --git a/app/assets/javascripts/admin/models/admin-dashboard.js.es6 b/app/assets/javascripts/admin/models/admin-dashboard.js.es6
index 20cdbe2772..756de2cc09 100644
--- a/app/assets/javascripts/admin/models/admin-dashboard.js.es6
+++ b/app/assets/javascripts/admin/models/admin-dashboard.js.es6
@@ -1,4 +1,5 @@
 import { ajax } from "discourse/lib/ajax";
+import EmberObject from "@ember/object";
 
 const GENERAL_ATTRIBUTES = [
   "updated_at",
@@ -6,7 +7,7 @@ const GENERAL_ATTRIBUTES = [
   "release_notes_link"
 ];
 
-const AdminDashboard = Discourse.Model.extend({});
+const AdminDashboard = EmberObject.extend({});
 
 AdminDashboard.reopenClass({
   fetch() {
diff --git a/app/assets/javascripts/admin/models/admin-user.js.es6 b/app/assets/javascripts/admin/models/admin-user.js.es6
index f5ff4c2761..25a822f388 100644
--- a/app/assets/javascripts/admin/models/admin-user.js.es6
+++ b/app/assets/javascripts/admin/models/admin-user.js.es6
@@ -1,28 +1,24 @@
+import discourseComputed from "discourse-common/utils/decorators";
+import { filter, or, gt, lt, not } from "@ember/object/computed";
 import { iconHTML } from "discourse-common/lib/icon-library";
 import { ajax } from "discourse/lib/ajax";
-import computed from "ember-addons/ember-computed-decorators";
 import { propertyNotEqual } from "discourse/lib/computed";
 import { popupAjaxError } from "discourse/lib/ajax-error";
-import ApiKey from "admin/models/api-key";
 import Group from "discourse/models/group";
 import { userPath } from "discourse/lib/url";
+import { Promise } from "rsvp";
+import User from "discourse/models/user";
 
 const wrapAdmin = user => (user ? AdminUser.create(user) : null);
 
-const AdminUser = Discourse.User.extend({
+const AdminUser = User.extend({
   adminUserView: true,
-  customGroups: Ember.computed.filter(
-    "groups",
-    g => !g.automatic && Group.create(g)
-  ),
-  automaticGroups: Ember.computed.filter(
-    "groups",
-    g => g.automatic && Group.create(g)
-  ),
+  customGroups: filter("groups", g => !g.automatic && Group.create(g)),
+  automaticGroups: filter("groups", g => g.automatic && Group.create(g)),
 
-  canViewProfile: Ember.computed.or("active", "staged"),
+  canViewProfile: or("active", "staged"),
 
-  @computed("bounce_score", "reset_bounce_score_after")
+  @discourseComputed("bounce_score", "reset_bounce_score_after")
   bounceScore(bounce_score, reset_bounce_score_after) {
     if (bounce_score > 0) {
       return `${bounce_score} - ${moment(reset_bounce_score_after).format(
@@ -33,7 +29,7 @@ const AdminUser = Discourse.User.extend({
     }
   },
 
-  @computed("bounce_score")
+  @discourseComputed("bounce_score")
   bounceScoreExplanation(bounce_score) {
     if (bounce_score === 0) {
       return I18n.t("admin.user.bounce_score_explanation.none");
@@ -44,12 +40,12 @@ const AdminUser = Discourse.User.extend({
     }
   },
 
-  @computed
+  @discourseComputed
   bounceLink() {
     return Discourse.getURL("/admin/email/bounced");
   },
 
-  canResetBounceScore: Ember.computed.gt("bounce_score", 0),
+  canResetBounceScore: gt("bounce_score", 0),
 
   resetBounceScore() {
     return ajax(`/admin/users/${this.id}/reset_bounce_score`, {
@@ -62,16 +58,6 @@ const AdminUser = Discourse.User.extend({
     );
   },
 
-  generateApiKey() {
-    return ajax(`/admin/users/${this.id}/generate_api_key`, {
-      type: "POST"
-    }).then(result => {
-      const apiKey = ApiKey.create(result.api_key);
-      this.set("api_key", apiKey);
-      return apiKey;
-    });
-  },
-
   groupAdded(added) {
     return ajax(`/admin/users/${this.id}/groups`, {
       type: "POST",
@@ -227,14 +213,14 @@ const AdminUser = Discourse.User.extend({
       .catch(popupAjaxError);
   },
 
-  approve() {
+  approve(approvedBy) {
     return ajax(`/admin/users/${this.id}/approve`, {
       type: "PUT"
     }).then(() => {
       this.setProperties({
         can_approve: false,
         approved: true,
-        approved_by: Discourse.User.current()
+        approved_by: approvedBy
       });
     });
   },
@@ -243,12 +229,12 @@ const AdminUser = Discourse.User.extend({
     this.set("originalTrustLevel", this.trust_level);
   },
 
-  dirty: propertyNotEqual("originalTrustLevel", "trustLevel.id"),
+  dirty: propertyNotEqual("originalTrustLevel", "trust_level"),
 
   saveTrustLevel() {
     return ajax(`/admin/users/${this.id}/trust_level`, {
       type: "PUT",
-      data: { level: this.get("trustLevel.id") }
+      data: { level: this.trust_level }
     })
       .then(() => window.location.reload())
       .catch(e => {
@@ -266,7 +252,7 @@ const AdminUser = Discourse.User.extend({
   },
 
   restoreTrustLevel() {
-    this.set("trustLevel.id", this.originalTrustLevel);
+    this.set("trust_level", this.originalTrustLevel);
   },
 
   lockTrustLevel(locked) {
@@ -289,11 +275,11 @@ const AdminUser = Discourse.User.extend({
       });
   },
 
-  canLockTrustLevel: Ember.computed.lt("trust_level", 4),
+  canLockTrustLevel: lt("trust_level", 4),
 
-  canSuspend: Ember.computed.not("staff"),
+  canSuspend: not("staff"),
 
-  @computed("suspended_till", "suspended_at")
+  @discourseComputed("suspended_till", "suspended_at")
   suspendDuration(suspendedTill, suspendedAt) {
     suspendedAt = moment(suspendedAt);
     suspendedTill = moment(suspendedTill);
@@ -519,7 +505,7 @@ const AdminUser = Discourse.User.extend({
 
   loadDetails() {
     if (this.loadedDetails) {
-      return Ember.RSVP.resolve(this);
+      return Promise.resolve(this);
     }
 
     return AdminUser.find(this.id).then(result => {
@@ -528,20 +514,20 @@ const AdminUser = Discourse.User.extend({
     });
   },
 
-  @computed("tl3_requirements")
+  @discourseComputed("tl3_requirements")
   tl3Requirements(requirements) {
     if (requirements) {
       return this.store.createRecord("tl3Requirements", requirements);
     }
   },
 
-  @computed("suspended_by")
+  @discourseComputed("suspended_by")
   suspendedBy: wrapAdmin,
 
-  @computed("silenced_by")
+  @discourseComputed("silenced_by")
   silencedBy: wrapAdmin,
 
-  @computed("approved_by")
+  @discourseComputed("approved_by")
   approvedBy: wrapAdmin,
 
   _formatError(event) {
@@ -557,9 +543,9 @@ AdminUser.reopenClass({
     });
   },
 
-  findAll(query, filter) {
+  findAll(query, userFilter) {
     return ajax(`/admin/users/list/${query}.json`, {
-      data: filter
+      data: userFilter
     }).then(users => users.map(u => AdminUser.create(u)));
   }
 });
diff --git a/app/assets/javascripts/admin/models/api-key.js.es6 b/app/assets/javascripts/admin/models/api-key.js.es6
index 62a4e003bf..06d861fa4a 100644
--- a/app/assets/javascripts/admin/models/api-key.js.es6
+++ b/app/assets/javascripts/admin/models/api-key.js.es6
@@ -1,47 +1,54 @@
+import discourseComputed from "discourse-common/utils/decorators";
 import AdminUser from "admin/models/admin-user";
+import RestModel from "discourse/models/rest";
 import { ajax } from "discourse/lib/ajax";
+import { computed } from "@ember/object";
+import { fmt } from "discourse/lib/computed";
 
-const KEY_ENDPOINT = "/admin/api/key";
-const KEYS_ENDPOINT = "/admin/api/keys";
+const ApiKey = RestModel.extend({
+  user: computed("_user", {
+    get() {
+      return this._user;
+    },
+    set(key, value) {
+      if (value && !(value instanceof AdminUser)) {
+        this.set("_user", AdminUser.create(value));
+      } else {
+        this.set("_user", value);
+      }
+      return this._user;
+    }
+  }),
 
-const ApiKey = Discourse.Model.extend({
-  regenerate() {
-    return ajax(KEY_ENDPOINT, {
-      type: "PUT",
-      data: { id: this.id }
-    }).then(result => {
-      this.set("key", result.api_key.key);
-      return this;
-    });
+  @discourseComputed("description")
+  shortDescription(description) {
+    if (!description || description.length < 40) return description;
+    return `${description.substring(0, 40)}...`;
   },
 
+  truncatedKey: fmt("truncated_key", "%@..."),
+
   revoke() {
-    return ajax(KEY_ENDPOINT, {
-      type: "DELETE",
-      data: { id: this.id }
-    });
-  }
-});
-
-ApiKey.reopenClass({
-  create() {
-    const result = this._super.apply(this, arguments);
-    if (result.user) {
-      result.user = AdminUser.create(result.user);
-    }
-    return result;
+    return ajax(`${this.basePath}/revoke`, {
+      type: "POST"
+    }).then(result => this.setProperties(result.api_key));
   },
 
-  find() {
-    return ajax(KEYS_ENDPOINT).then(keys =>
-      keys.map(key => ApiKey.create(key))
-    );
+  undoRevoke() {
+    return ajax(`${this.basePath}/undo-revoke`, {
+      type: "POST"
+    }).then(result => this.setProperties(result.api_key));
   },
 
-  generateMasterKey() {
-    return ajax(KEY_ENDPOINT, { type: "POST" }).then(result =>
-      ApiKey.create(result.api_key)
-    );
+  createProperties() {
+    return this.getProperties("description", "username");
+  },
+
+  @discourseComputed()
+  basePath() {
+    return this.store
+      .adapterFor("api-key")
+      .pathFor(this.store, "api-key", this.id);
   }
 });
 
diff --git a/app/assets/javascripts/admin/models/backup-status.js.es6 b/app/assets/javascripts/admin/models/backup-status.js.es6
index 2eb856c3be..62c360b532 100644
--- a/app/assets/javascripts/admin/models/backup-status.js.es6
+++ b/app/assets/javascripts/admin/models/backup-status.js.es6
@@ -1,9 +1,11 @@
-import computed from "ember-addons/ember-computed-decorators";
+import discourseComputed from "discourse-common/utils/decorators";
+import { not } from "@ember/object/computed";
+import EmberObject from "@ember/object";
 
-export default Discourse.Model.extend({
-  restoreDisabled: Ember.computed.not("restoreEnabled"),
+export default EmberObject.extend({
+  restoreDisabled: not("restoreEnabled"),
 
-  @computed("allowRestore", "isOperationRunning")
+  @discourseComputed("allowRestore", "isOperationRunning")
   restoreEnabled(allowRestore, isOperationRunning) {
     return allowRestore && !isOperationRunning;
   }
diff --git a/app/assets/javascripts/admin/models/backup.js.es6 b/app/assets/javascripts/admin/models/backup.js.es6
index 7dc945d1ae..882173300a 100644
--- a/app/assets/javascripts/admin/models/backup.js.es6
+++ b/app/assets/javascripts/admin/models/backup.js.es6
@@ -1,7 +1,8 @@
 import { ajax } from "discourse/lib/ajax";
 import { extractError } from "discourse/lib/ajax-error";
+import EmberObject from "@ember/object";
 
-const Backup = Discourse.Model.extend({
+const Backup = EmberObject.extend({
   destroy() {
     return ajax("/admin/backups/" + this.filename, { type: "DELETE" });
   },
@@ -63,7 +64,7 @@ Backup.reopenClass({
         bootbox.alert(result.message);
       } else {
         // redirect to homepage (session might be lost)
-        window.location.pathname = Discourse.getURL("/");
+        window.location = Discourse.getURL("/");
       }
     });
   }
diff --git a/app/assets/javascripts/admin/models/color-scheme-color.js.es6 b/app/assets/javascripts/admin/models/color-scheme-color.js.es6
index a2f1a7b12d..f1aa235a4b 100644
--- a/app/assets/javascripts/admin/models/color-scheme-color.js.es6
+++ b/app/assets/javascripts/admin/models/color-scheme-color.js.es6
@@ -1,11 +1,11 @@
-import {
-  default as computed,
+import discourseComputed, {
   observes,
   on
-} from "ember-addons/ember-computed-decorators";
-import { propertyNotEqual, i18n } from "discourse/lib/computed";
+} from "discourse-common/utils/decorators";
+import { propertyNotEqual } from "discourse/lib/computed";
+import EmberObject from "@ember/object";
 
-const ColorSchemeColor = Discourse.Model.extend({
+const ColorSchemeColor = EmberObject.extend({
   @on("init")
   startTrackingChanges() {
     this.set("originals", { hex: this.hex || "FFFFFF" });
@@ -15,7 +15,7 @@ const ColorSchemeColor = Discourse.Model.extend({
   },
 
   // Whether value has changed since it was last saved.
-  @computed("hex")
+  @discourseComputed("hex")
   changed(hex) {
     if (!this.originals) return false;
     if (hex !== this.originals.hex) return true;
@@ -27,7 +27,7 @@ const ColorSchemeColor = Discourse.Model.extend({
   overridden: propertyNotEqual("hex", "default_hex"),
 
   // Whether the saved value is different than Discourse's default color scheme.
-  @computed("default_hex", "hex")
+  @discourseComputed("default_hex", "hex")
   savedIsOverriden(defaultHex) {
     return this.originals.hex !== defaultHex;
   },
@@ -42,9 +42,23 @@ const ColorSchemeColor = Discourse.Model.extend({
     }
   },
 
-  translatedName: i18n("name", "admin.customize.colors.%@.name"),
+  @discourseComputed("name")
+  translatedName(name) {
+    if (!this.is_advanced) {
+      return I18n.t(`admin.customize.colors.${name}.name`);
+    } else {
+      return name;
+    }
+  },
 
-  description: i18n("name", "admin.customize.colors.%@.description"),
+  @discourseComputed("name")
+  description(name) {
+    if (!this.is_advanced) {
+      return I18n.t(`admin.customize.colors.${name}.description`);
+    } else {
+      return "";
+    }
+  },
 
   /**
     brightness returns a number between 0 (darkest) to 255 (brightest).
@@ -52,7 +66,7 @@ const ColorSchemeColor = Discourse.Model.extend({
 
     @property brightness
   **/
-  @computed("hex")
+  @discourseComputed("hex")
   brightness(hex) {
     if (hex.length === 6 || hex.length === 3) {
       if (hex.length === 3) {
@@ -65,9 +79,9 @@ const ColorSchemeColor = Discourse.Model.extend({
           hex.substr(2, 1);
       }
       return Math.round(
-        (parseInt("0x" + hex.substr(0, 2)) * 299 +
-          parseInt("0x" + hex.substr(2, 2)) * 587 +
-          parseInt("0x" + hex.substr(4, 2)) * 114) /
+        (parseInt(hex.substr(0, 2), 16) * 299 +
+          parseInt(hex.substr(2, 2), 16) * 587 +
+          parseInt(hex.substr(4, 2), 16) * 114) /
           1000
       );
     }
@@ -80,7 +94,7 @@ const ColorSchemeColor = Discourse.Model.extend({
     }
   },
 
-  @computed("hex")
+  @discourseComputed("hex")
   valid(hex) {
     return hex.match(/^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/) !== null;
   }
diff --git a/app/assets/javascripts/admin/models/color-scheme.js.es6 b/app/assets/javascripts/admin/models/color-scheme.js.es6
index 5276ef849d..8486002386 100644
--- a/app/assets/javascripts/admin/models/color-scheme.js.es6
+++ b/app/assets/javascripts/admin/models/color-scheme.js.es6
@@ -1,15 +1,17 @@
+import discourseComputed from "discourse-common/utils/decorators";
+import { not } from "@ember/object/computed";
 import { ajax } from "discourse/lib/ajax";
 import ColorSchemeColor from "admin/models/color-scheme-color";
-import computed from "ember-addons/ember-computed-decorators";
+import EmberObject from "@ember/object";
 
-const ColorScheme = Discourse.Model.extend(Ember.Copyable, {
+const ColorScheme = EmberObject.extend(Ember.Copyable, {
   init() {
     this._super(...arguments);
 
     this.startTrackingChanges();
   },
 
-  @computed
+  @discourseComputed
   description() {
     return "" + this.name;
   },
@@ -41,7 +43,7 @@ const ColorScheme = Discourse.Model.extend(Ember.Copyable, {
     return newScheme;
   },
 
-  @computed("name", "colors.@each.changed", "saving")
+  @discourseComputed("name", "colors.@each.changed", "saving")
   changed(name) {
     if (!this.originals) return false;
     if (this.originals.name !== name) return true;
@@ -50,7 +52,7 @@ const ColorScheme = Discourse.Model.extend(Ember.Copyable, {
     return false;
   },
 
-  @computed("changed")
+  @discourseComputed("changed")
   disableSave(changed) {
     if (this.theme_id) {
       return false;
@@ -59,7 +61,7 @@ const ColorScheme = Discourse.Model.extend(Ember.Copyable, {
     return !changed || this.saving || this.colors.any(c => !c.get("valid"));
   },
 
-  newRecord: Ember.computed.not("id"),
+  newRecord: not("id"),
 
   save(opts) {
     if (this.is_base || this.disableSave) return;
@@ -128,7 +130,8 @@ ColorScheme.reopenClass({
               return ColorSchemeColor.create({
                 name: c.name,
                 hex: c.hex,
-                default_hex: c.default_hex
+                default_hex: c.default_hex,
+                is_advanced: c.is_advanced
               });
             })
           })
diff --git a/app/assets/javascripts/admin/models/email-log.js.es6 b/app/assets/javascripts/admin/models/email-log.js.es6
index f1ac52ac8d..c2eaaa26e6 100644
--- a/app/assets/javascripts/admin/models/email-log.js.es6
+++ b/app/assets/javascripts/admin/models/email-log.js.es6
@@ -1,7 +1,8 @@
 import { ajax } from "discourse/lib/ajax";
 import AdminUser from "admin/models/admin-user";
+import EmberObject from "@ember/object";
 
-const EmailLog = Discourse.Model.extend({});
+const EmailLog = EmberObject.extend({});
 
 EmailLog.reopenClass({
   create(attrs) {
diff --git a/app/assets/javascripts/admin/models/email-preview.js.es6 b/app/assets/javascripts/admin/models/email-preview.js.es6
index b8585d9080..42b7ab5878 100644
--- a/app/assets/javascripts/admin/models/email-preview.js.es6
+++ b/app/assets/javascripts/admin/models/email-preview.js.es6
@@ -1,5 +1,7 @@
 import { ajax } from "discourse/lib/ajax";
-const EmailPreview = Discourse.Model.extend({});
+import EmberObject from "@ember/object";
+
+const EmailPreview = EmberObject.extend({});
 
 export function oneWeekAgo() {
   return moment()
diff --git a/app/assets/javascripts/admin/models/email-settings.js.es6 b/app/assets/javascripts/admin/models/email-settings.js.es6
index e1d838463e..1730aae7c9 100644
--- a/app/assets/javascripts/admin/models/email-settings.js.es6
+++ b/app/assets/javascripts/admin/models/email-settings.js.es6
@@ -1,5 +1,7 @@
 import { ajax } from "discourse/lib/ajax";
-const EmailSettings = Discourse.Model.extend({});
+import EmberObject from "@ember/object";
+
+const EmailSettings = EmberObject.extend({});
 
 EmailSettings.reopenClass({
   find: function() {
diff --git a/app/assets/javascripts/admin/models/email-style.js.es6 b/app/assets/javascripts/admin/models/email-style.js.es6
new file mode 100644
index 0000000000..29d7568aba
--- /dev/null
+++ b/app/assets/javascripts/admin/models/email-style.js.es6
@@ -0,0 +1,10 @@
+import RestModel from "discourse/models/rest";
+
+export default RestModel.extend({
+  changed: false,
+
+  setField(fieldName, value) {
+    this.set(`${fieldName}`, value);
+    this.set("changed", true);
+  }
+});
diff --git a/app/assets/javascripts/admin/models/flag-type.js.es6 b/app/assets/javascripts/admin/models/flag-type.js.es6
index b1bf1ca828..93fb2eacc9 100644
--- a/app/assets/javascripts/admin/models/flag-type.js.es6
+++ b/app/assets/javascripts/admin/models/flag-type.js.es6
@@ -1,8 +1,8 @@
+import discourseComputed from "discourse-common/utils/decorators";
 import RestModel from "discourse/models/rest";
-import computed from "ember-addons/ember-computed-decorators";
 
 export default RestModel.extend({
-  @computed("id")
+  @discourseComputed("id")
   name(id) {
     return I18n.t(`admin.flags.summary.action_type_${id}`, { count: 1 });
   }
diff --git a/app/assets/javascripts/admin/models/incoming-email.js.es6 b/app/assets/javascripts/admin/models/incoming-email.js.es6
index fd9d68730b..8d46429a1d 100644
--- a/app/assets/javascripts/admin/models/incoming-email.js.es6
+++ b/app/assets/javascripts/admin/models/incoming-email.js.es6
@@ -1,7 +1,8 @@
 import { ajax } from "discourse/lib/ajax";
 import AdminUser from "admin/models/admin-user";
+import EmberObject from "@ember/object";
 
-const IncomingEmail = Discourse.Model.extend({});
+const IncomingEmail = EmberObject.extend({});
 
 IncomingEmail.reopenClass({
   create(attrs) {
diff --git a/app/assets/javascripts/admin/models/permalink.js.es6 b/app/assets/javascripts/admin/models/permalink.js.es6
index 9019bdbc30..b86e931692 100644
--- a/app/assets/javascripts/admin/models/permalink.js.es6
+++ b/app/assets/javascripts/admin/models/permalink.js.es6
@@ -1,5 +1,7 @@
 import { ajax } from "discourse/lib/ajax";
-const Permalink = Discourse.Model.extend({
+import EmberObject from "@ember/object";
+
+const Permalink = EmberObject.extend({
   save: function() {
     return ajax("/admin/permalinks.json", {
       type: "POST",
diff --git a/app/assets/javascripts/admin/models/report.js.es6 b/app/assets/javascripts/admin/models/report.js.es6
index cbe397df98..bd7074ced2 100644
--- a/app/assets/javascripts/admin/models/report.js.es6
+++ b/app/assets/javascripts/admin/models/report.js.es6
@@ -1,8 +1,15 @@
+import discourseComputed from "discourse-common/utils/decorators";
+import { makeArray } from "discourse-common/lib/helpers";
+import { isEmpty } from "@ember/utils";
+import EmberObject from "@ember/object";
 import { escapeExpression } from "discourse/lib/utilities";
 import { ajax } from "discourse/lib/ajax";
 import round from "discourse/lib/round";
-import { fillMissingDates, formatUsername } from "discourse/lib/utilities";
-import computed from "ember-addons/ember-computed-decorators";
+import {
+  fillMissingDates,
+  formatUsername,
+  toNumber
+} from "discourse/lib/utilities";
 import { number, durationTiny } from "discourse/lib/formatter";
 import { renderAvatar } from "discourse/helpers/user-avatar";
 
@@ -10,17 +17,12 @@ import { renderAvatar } from "discourse/helpers/user-avatar";
 // and you want to ensure cache is reset
 export const SCHEMA_VERSION = 4;
 
-const Report = Discourse.Model.extend({
+const Report = EmberObject.extend({
   average: false,
   percent: false,
   higher_is_better: true,
 
-  @computed("modes")
-  onlyTable(modes) {
-    return modes.length === 1 && modes[0] === "table";
-  },
-
-  @computed("type", "start_date", "end_date")
+  @discourseComputed("type", "start_date", "end_date")
   reportUrl(type, start_date, end_date) {
     start_date = moment
       .utc(start_date)
@@ -76,32 +78,32 @@ const Report = Discourse.Model.extend({
     }
   },
 
-  @computed("data", "average")
+  @discourseComputed("data", "average")
   todayCount() {
     return this.valueAt(0);
   },
 
-  @computed("data", "average")
+  @discourseComputed("data", "average")
   yesterdayCount() {
     return this.valueAt(1);
   },
 
-  @computed("data", "average")
+  @discourseComputed("data", "average")
   sevenDaysAgoCount() {
     return this.valueAt(7);
   },
 
-  @computed("data", "average")
+  @discourseComputed("data", "average")
   thirtyDaysAgoCount() {
     return this.valueAt(30);
   },
 
-  @computed("data", "average")
+  @discourseComputed("data", "average")
   lastSevenDaysCount() {
     return this.averageCount(7, this.valueFor(1, 7));
   },
 
-  @computed("data", "average")
+  @discourseComputed("data", "average")
   lastThirtyDaysCount() {
     return this.averageCount(30, this.valueFor(1, 30));
   },
@@ -110,12 +112,12 @@ const Report = Discourse.Model.extend({
     return this.average ? value / count : value;
   },
 
-  @computed("yesterdayCount", "higher_is_better")
+  @discourseComputed("yesterdayCount", "higher_is_better")
   yesterdayTrend(yesterdayCount, higherIsBetter) {
     return this._computeTrend(this.valueAt(2), yesterdayCount, higherIsBetter);
   },
 
-  @computed("lastSevenDaysCount", "higher_is_better")
+  @discourseComputed("lastSevenDaysCount", "higher_is_better")
   sevenDaysTrend(lastSevenDaysCount, higherIsBetter) {
     return this._computeTrend(
       this.valueFor(8, 14),
@@ -124,50 +126,55 @@ const Report = Discourse.Model.extend({
     );
   },
 
-  @computed("data")
+  @discourseComputed("data")
   currentTotal(data) {
     return data.reduce((cur, pair) => cur + pair.y, 0);
   },
 
-  @computed("data", "currentTotal")
+  @discourseComputed("data", "currentTotal")
   currentAverage(data, total) {
-    return Ember.makeArray(data).length === 0
+    return makeArray(data).length === 0
       ? 0
       : parseFloat((total / parseFloat(data.length)).toFixed(1));
   },
 
-  @computed("trend", "higher_is_better")
+  @discourseComputed("trend", "higher_is_better")
   trendIcon(trend, higherIsBetter) {
     return this._iconForTrend(trend, higherIsBetter);
   },
 
-  @computed("sevenDaysTrend", "higher_is_better")
+  @discourseComputed("sevenDaysTrend", "higher_is_better")
   sevenDaysTrendIcon(sevenDaysTrend, higherIsBetter) {
     return this._iconForTrend(sevenDaysTrend, higherIsBetter);
   },
 
-  @computed("thirtyDaysTrend", "higher_is_better")
+  @discourseComputed("thirtyDaysTrend", "higher_is_better")
   thirtyDaysTrendIcon(thirtyDaysTrend, higherIsBetter) {
     return this._iconForTrend(thirtyDaysTrend, higherIsBetter);
   },
 
-  @computed("yesterdayTrend", "higher_is_better")
+  @discourseComputed("yesterdayTrend", "higher_is_better")
   yesterdayTrendIcon(yesterdayTrend, higherIsBetter) {
     return this._iconForTrend(yesterdayTrend, higherIsBetter);
   },
 
-  @computed("prev_period", "currentTotal", "currentAverage", "higher_is_better")
+  @discourseComputed(
+    "prev_period",
+    "currentTotal",
+    "currentAverage",
+    "higher_is_better"
+  )
   trend(prev, currentTotal, currentAverage, higherIsBetter) {
     const total = this.average ? currentAverage : currentTotal;
     return this._computeTrend(prev, total, higherIsBetter);
   },
 
-  @computed("prev30Days", "lastThirtyDaysCount", "higher_is_better")
+  @discourseComputed("prev30Days", "lastThirtyDaysCount", "higher_is_better")
   thirtyDaysTrend(prev30Days, lastThirtyDaysCount, higherIsBetter) {
     return this._computeTrend(prev30Days, lastThirtyDaysCount, higherIsBetter);
   },
 
-  @computed("type")
+  @discourseComputed("type")
   method(type) {
     if (type === "time_to_first_response") {
       return "average";
@@ -188,7 +195,7 @@ const Report = Discourse.Model.extend({
     }
   },
 
-  @computed("prev_period", "currentTotal", "currentAverage")
+  @discourseComputed("prev_period", "currentTotal", "currentAverage")
   trendTitle(prev, currentTotal, currentAverage) {
     let current = this.average ? currentAverage : currentTotal;
     let percent = this.percentChangeString(prev, current);
@@ -221,12 +228,12 @@ const Report = Discourse.Model.extend({
     return title;
   },
 
-  @computed("yesterdayCount")
+  @discourseComputed("yesterdayCount")
   yesterdayCountTitle(yesterdayCount) {
     return this.changeTitle(this.valueAt(2), yesterdayCount, "two days ago");
   },
 
-  @computed("lastSevenDaysCount")
+  @discourseComputed("lastSevenDaysCount")
   sevenDaysCountTitle(lastSevenDaysCount) {
     return this.changeTitle(
       this.valueFor(8, 14),
@@ -235,7 +242,7 @@ const Report = Discourse.Model.extend({
     );
   },
 
-  @computed("prev30Days", "lastThirtyDaysCount")
+  @discourseComputed("prev30Days", "lastThirtyDaysCount")
   thirtyDaysCountTitle(prev30Days, lastThirtyDaysCount) {
     return this.changeTitle(
       prev30Days,
@@ -244,18 +251,18 @@ const Report = Discourse.Model.extend({
     );
   },
 
-  @computed("data")
+  @discourseComputed("data")
   sortedData(data) {
     return this.xAxisIsDate ? data.toArray().reverse() : data.toArray();
   },
 
-  @computed("data")
+  @discourseComputed("data")
   xAxisIsDate() {
     if (!this.data[0]) return false;
     return this.data && this.data[0].x.match(/\d{4}-\d{1,2}-\d{1,2}/);
   },
 
-  @computed("labels")
+  @discourseComputed("labels")
   computedLabels(labels) {
     return labels.map(label => {
       const type = label.type || "string";
@@ -319,7 +326,7 @@ const Report = Discourse.Model.extend({
     const formatedValue = () => {
       const userId = row[properties.id];
 
-      const user = Ember.Object.create({
+      const user = EmberObject.create({
         username,
         name: formatUsername(username),
         avatar_template: row[properties.avatar]
@@ -332,9 +339,7 @@ const Report = Discourse.Model.extend({
         ignoreTitle: true
       });
 
-      return `${avatarImg}${
-        user.name
-      }`;
+      return `${avatarImg}${user.name}`;
     };
 
     return {
@@ -376,34 +381,34 @@ const Report = Discourse.Model.extend({
 
   _secondsLabel(value) {
     return {
-      value,
+      value: toNumber(value),
       formatedValue: durationTiny(value)
     };
   },
 
   _percentLabel(value) {
     return {
-      value,
+      value: toNumber(value),
       formatedValue: value ? `${value}%` : "—"
     };
   },
 
   _numberLabel(value, options = {}) {
-    const formatNumbers = Ember.isEmpty(options.formatNumbers)
+    const formatNumbers = isEmpty(options.formatNumbers)
       ? true
       : options.formatNumbers;
 
     const formatedValue = () => (formatNumbers ? number(value) : value);
 
     return {
-      value,
+      value: toNumber(value),
       formatedValue: value ? formatedValue() : "—"
     };
   },
 
   _bytesLabel(value) {
     return {
-      value,
+      value: toNumber(value),
       formatedValue: I18n.toHumanSize(value)
     };
   },
diff --git a/app/assets/javascripts/admin/models/screened-email.js.es6 b/app/assets/javascripts/admin/models/screened-email.js.es6
index 6eb014c484..ea72510551 100644
--- a/app/assets/javascripts/admin/models/screened-email.js.es6
+++ b/app/assets/javascripts/admin/models/screened-email.js.es6
@@ -1,8 +1,9 @@
+import discourseComputed from "discourse-common/utils/decorators";
 import { ajax } from "discourse/lib/ajax";
-import computed from "ember-addons/ember-computed-decorators";
+import EmberObject from "@ember/object";
 
-const ScreenedEmail = Discourse.Model.extend({
-  @computed("action")
+const ScreenedEmail = EmberObject.extend({
+  @discourseComputed("action")
   actionName(action) {
     return I18n.t("admin.logs.screened_actions." + action);
   },
diff --git a/app/assets/javascripts/admin/models/screened-ip-address.js.es6 b/app/assets/javascripts/admin/models/screened-ip-address.js.es6
index c7f3863738..bfac17d86c 100644
--- a/app/assets/javascripts/admin/models/screened-ip-address.js.es6
+++ b/app/assets/javascripts/admin/models/screened-ip-address.js.es6
@@ -1,15 +1,17 @@
+import discourseComputed from "discourse-common/utils/decorators";
+import { equal } from "@ember/object/computed";
 import { ajax } from "discourse/lib/ajax";
-import computed from "ember-addons/ember-computed-decorators";
+import EmberObject from "@ember/object";
 
-const ScreenedIpAddress = Discourse.Model.extend({
-  @computed("action_name")
+const ScreenedIpAddress = EmberObject.extend({
+  @discourseComputed("action_name")
   actionName(actionName) {
     return I18n.t(`admin.logs.screened_ips.actions.${actionName}`);
   },
 
-  isBlocked: Ember.computed.equal("action_name", "block"),
+  isBlocked: equal("action_name", "block"),
 
-  @computed("ip_address")
+  @discourseComputed("ip_address")
   isRange(ipAddress) {
     return ipAddress.indexOf("/") > 0;
   },
diff --git a/app/assets/javascripts/admin/models/screened-url.js.es6 b/app/assets/javascripts/admin/models/screened-url.js.es6
index b899c61962..31ea850778 100644
--- a/app/assets/javascripts/admin/models/screened-url.js.es6
+++ b/app/assets/javascripts/admin/models/screened-url.js.es6
@@ -1,8 +1,9 @@
+import discourseComputed from "discourse-common/utils/decorators";
 import { ajax } from "discourse/lib/ajax";
-import computed from "ember-addons/ember-computed-decorators";
+import EmberObject from "@ember/object";
 
-const ScreenedUrl = Discourse.Model.extend({
-  @computed("action")
+const ScreenedUrl = EmberObject.extend({
+  @discourseComputed("action")
   actionName(action) {
     return I18n.t("admin.logs.screened_actions." + action);
   }
diff --git a/app/assets/javascripts/admin/models/site-setting.js.es6 b/app/assets/javascripts/admin/models/site-setting.js.es6
index ca1c175864..4edc89a1b9 100644
--- a/app/assets/javascripts/admin/models/site-setting.js.es6
+++ b/app/assets/javascripts/admin/models/site-setting.js.es6
@@ -1,7 +1,8 @@
 import { ajax } from "discourse/lib/ajax";
 import Setting from "admin/mixins/setting-object";
+import EmberObject from "@ember/object";
 
-const SiteSetting = Discourse.Model.extend(Setting, {});
+const SiteSetting = EmberObject.extend(Setting, {});
 
 SiteSetting.reopenClass({
   findAll() {
@@ -25,9 +26,14 @@ SiteSetting.reopenClass({
     });
   },
 
-  update(key, value) {
+  update(key, value, opts = {}) {
     const data = {};
     data[key] = value;
+
+    if (opts["updateExistingUsers"] === true) {
+      data["updateExistingUsers"] = true;
+    }
+
     return ajax(`/admin/site_settings/${key}`, { type: "PUT", data });
   }
 });
diff --git a/app/assets/javascripts/admin/models/staff-action-log.js.es6 b/app/assets/javascripts/admin/models/staff-action-log.js.es6
index 3451cffa22..45330b13fc 100644
--- a/app/assets/javascripts/admin/models/staff-action-log.js.es6
+++ b/app/assets/javascripts/admin/models/staff-action-log.js.es6
@@ -1,7 +1,8 @@
-import computed from "ember-addons/ember-computed-decorators";
+import discourseComputed from "discourse-common/utils/decorators";
 import { ajax } from "discourse/lib/ajax";
 import AdminUser from "admin/models/admin-user";
 import { escapeExpression } from "discourse/lib/utilities";
+import RestModel from "discourse/models/rest";
 
 function format(label, value, escape = true) {
   return value
@@ -9,15 +10,15 @@ function format(label, value, escape = true) {
     : "";
 }
 
-const StaffActionLog = Discourse.Model.extend({
+const StaffActionLog = RestModel.extend({
   showFullDetails: false,
 
-  @computed("action_name")
+  @discourseComputed("action_name")
   actionName(actionName) {
     return I18n.t(`admin.logs.staff_actions.actions.${actionName}`);
   },
 
-  @computed(
+  @discourseComputed(
     "email",
     "ip_address",
     "topic_id",
@@ -68,28 +69,26 @@ const StaffActionLog = Discourse.Model.extend({
     return formatted.length > 0 ? formatted + "
" : ""; }, - @computed("details") + @discourseComputed("details") useModalForDetails(details) { return details && details.length > 100; }, - @computed("action_name") + @discourseComputed("action_name") useCustomModalForDetails(actionName) { return ["change_theme", "delete_theme"].includes(actionName); } }); StaffActionLog.reopenClass({ - create(attrs) { - attrs = attrs || {}; - - if (attrs.acting_user) { - attrs.acting_user = AdminUser.create(attrs.acting_user); + munge(json) { + if (json.acting_user) { + json.acting_user = AdminUser.create(json.acting_user); } - if (attrs.target_user) { - attrs.target_user = AdminUser.create(attrs.target_user); + if (json.target_user) { + json.target_user = AdminUser.create(json.target_user); } - return this._super(attrs); + return json; }, findAll(data) { diff --git a/app/assets/javascripts/admin/models/theme-settings.js.es6 b/app/assets/javascripts/admin/models/theme-settings.js.es6 index ab9e5bf9ce..a823592ad2 100644 --- a/app/assets/javascripts/admin/models/theme-settings.js.es6 +++ b/app/assets/javascripts/admin/models/theme-settings.js.es6 @@ -1,3 +1,4 @@ import Setting from "admin/mixins/setting-object"; +import EmberObject from "@ember/object"; -export default Discourse.Model.extend(Setting, {}); +export default EmberObject.extend(Setting, {}); diff --git a/app/assets/javascripts/admin/models/theme.js.es6 b/app/assets/javascripts/admin/models/theme.js.es6 index 7e49ff9b9e..543858c677 100644 --- a/app/assets/javascripts/admin/models/theme.js.es6 +++ b/app/assets/javascripts/admin/models/theme.js.es6 @@ -1,9 +1,13 @@ +import { get } from "@ember/object"; +import { isEmpty } from "@ember/utils"; +import { or, gt } from "@ember/object/computed"; import RestModel from "discourse/models/rest"; -import { default as computed } from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { ajax } from "discourse/lib/ajax"; import { escapeExpression } from "discourse/lib/utilities"; import highlightSyntax from "discourse/lib/highlight-syntax"; +import { url } from "discourse/lib/computed"; const THEME_UPLOAD_VAR = 2; const FIELDS_IDS = [0, 1, 5]; @@ -13,11 +17,13 @@ export const COMPONENTS = "components"; const SETTINGS_TYPE_ID = 5; const Theme = RestModel.extend({ - isActive: Ember.computed.or("default", "user_selectable"), - isPendingUpdates: Ember.computed.gt("remote_theme.commits_behind", 0), - hasEditedFields: Ember.computed.gt("editedFields.length", 0), + isActive: or("default", "user_selectable"), + isPendingUpdates: gt("remote_theme.commits_behind", 0), + hasEditedFields: gt("editedFields.length", 0), + hasParents: gt("parent_themes.length", 0), + diffLocalChangesUrl: url("id", "/admin/themes/%@/diff_local_changes"), - @computed("theme_fields.[]") + @discourseComputed("theme_fields.[]") targets() { return [ { id: 0, name: "common" }, @@ -45,7 +51,7 @@ const Theme = RestModel.extend({ }); }, - @computed("theme_fields.[]") + @discourseComputed("theme_fields.[]") fieldNames() { const common = [ "scss", @@ -79,7 +85,11 @@ const Theme = RestModel.extend({ }; }, - @computed("fieldNames", "theme_fields.[]", "theme_fields.@each.error") + @discourseComputed( + "fieldNames", + "theme_fields.[]", + "theme_fields.@each.error" + ) fields(fieldNames) { const hash = {}; Object.keys(fieldNames).forEach(target => { @@ -109,7 +119,7 @@ const Theme = RestModel.extend({ return hash; }, - @computed("theme_fields") + @discourseComputed("theme_fields") themeFields(fields) { if (!fields) { this.set("theme_fields", []); @@ -125,7 +135,7 @@ const Theme = RestModel.extend({ return hash; }, - @computed("theme_fields", "theme_fields.[]") + @discourseComputed("theme_fields", "theme_fields.[]") uploads(fields) { if (!fields) { return []; @@ -135,19 +145,19 @@ const Theme = RestModel.extend({ ); }, - @computed("theme_fields", "theme_fields.@each.error") + @discourseComputed("theme_fields", "theme_fields.@each.error") isBroken(fields) { return fields && fields.any(field => field.error && field.error.length > 0); }, - @computed("theme_fields.[]") + @discourseComputed("theme_fields.[]") editedFields(fields) { return fields.filter( field => !Ember.isBlank(field.value) && field.type_id !== SETTINGS_TYPE_ID ); }, - @computed("remote_theme.last_error_text") + @discourseComputed("remote_theme.last_error_text") remoteError(errorText) { if (errorText && errorText.length > 0) { return errorText; @@ -160,11 +170,11 @@ const Theme = RestModel.extend({ hasEdited(target, name) { if (name) { - return !Ember.isEmpty(this.getField(target, name)); + return !isEmpty(this.getField(target, name)); } else { let fields = this.theme_fields || []; return fields.any( - field => field.target === target && !Ember.isEmpty(field.value) + field => field.target === target && !isEmpty(field.value) ); } }, @@ -226,8 +236,8 @@ const Theme = RestModel.extend({ themeFields[key] = field; } else { const changed = - (Ember.isEmpty(existingField.value) && !Ember.isEmpty(value)) || - (Ember.isEmpty(value) && !Ember.isEmpty(existingField.value)); + (isEmpty(existingField.value) && !isEmpty(value)) || + (isEmpty(value) && !isEmpty(existingField.value)); existingField.value = value; if (changed) { @@ -238,10 +248,10 @@ const Theme = RestModel.extend({ } }, - @computed("childThemes.[]") + @discourseComputed("childThemes.[]") child_theme_ids(childThemes) { if (childThemes) { - return childThemes.map(theme => Ember.get(theme, "id")); + return childThemes.map(theme => get(theme, "id")); } }, @@ -262,7 +272,16 @@ const Theme = RestModel.extend({ return this.saveChanges("child_theme_ids"); }, - @computed("name", "default") + addParentTheme(theme) { + let parentThemes = this.parentThemes; + if (!parentThemes) { + parentThemes = []; + this.set("parentThemes", parentThemes); + } + parentThemes.addObject(theme); + }, + + @discourseComputed("name", "default") description: function(name, isDefault) { if (isDefault) { return I18n.t("admin.customize.theme.default_name", { name: name }); @@ -278,7 +297,7 @@ const Theme = RestModel.extend({ }, updateToLatest() { - return ajax(`/admin/themes/${this.id}/diff_local_changes`).then(json => { + return ajax(this.diffLocalChangesUrl).then(json => { if (json && json.error) { bootbox.alert( I18n.t("generic_error_with_reason", { diff --git a/app/assets/javascripts/admin/models/tl3-requirements.js.es6 b/app/assets/javascripts/admin/models/tl3-requirements.js.es6 index 222c8077d7..424aea4f58 100644 --- a/app/assets/javascripts/admin/models/tl3-requirements.js.es6 +++ b/app/assets/javascripts/admin/models/tl3-requirements.js.es6 @@ -1,17 +1,18 @@ -import computed from "ember-addons/ember-computed-decorators"; +import discourseComputed from "discourse-common/utils/decorators"; +import EmberObject from "@ember/object"; -export default Discourse.Model.extend({ - @computed("days_visited", "time_period") +export default EmberObject.extend({ + @discourseComputed("days_visited", "time_period") days_visited_percent(daysVisited, timePeriod) { return Math.round((daysVisited * 100) / timePeriod); }, - @computed("min_days_visited", "time_period") + @discourseComputed("min_days_visited", "time_period") min_days_visited_percent(minDaysVisited, timePeriod) { return Math.round((minDaysVisited * 100) / timePeriod); }, - @computed( + @discourseComputed( "days_visited", "min_days_visited", "num_topics_replied_to", diff --git a/app/assets/javascripts/admin/models/user-field.js.es6 b/app/assets/javascripts/admin/models/user-field.js.es6 index a57e452d86..78c004d418 100644 --- a/app/assets/javascripts/admin/models/user-field.js.es6 +++ b/app/assets/javascripts/admin/models/user-field.js.es6 @@ -1,9 +1,10 @@ +import EmberObject from "@ember/object"; import RestModel from "discourse/models/rest"; import { i18n } from "discourse/lib/computed"; const UserField = RestModel.extend(); -const UserFieldType = Ember.Object.extend({ +const UserFieldType = EmberObject.extend({ name: i18n("id", "admin.user_fields.field_types.%@") }); diff --git a/app/assets/javascripts/admin/models/version-check.js.es6 b/app/assets/javascripts/admin/models/version-check.js.es6 index ba0bdf018d..cc888b2588 100644 --- a/app/assets/javascripts/admin/models/version-check.js.es6 +++ b/app/assets/javascripts/admin/models/version-check.js.es6 @@ -1,34 +1,35 @@ +import discourseComputed from "discourse-common/utils/decorators"; import { ajax } from "discourse/lib/ajax"; -import computed from "ember-addons/ember-computed-decorators"; +import EmberObject from "@ember/object"; -const VersionCheck = Discourse.Model.extend({ - @computed("updated_at") +const VersionCheck = EmberObject.extend({ + @discourseComputed("updated_at") noCheckPerformed(updatedAt) { return updatedAt === null; }, - @computed("missing_versions_count") + @discourseComputed("missing_versions_count") upToDate(missingVersionsCount) { return missingVersionsCount === 0 || missingVersionsCount === null; }, - @computed("missing_versions_count") + @discourseComputed("missing_versions_count") behindByOneVersion(missingVersionsCount) { return missingVersionsCount === 1; }, - @computed("git_branch", "installed_sha") - gitLink(gitBranch, installedSHA) { - if (gitBranch) { - return `https://github.com/discourse/discourse/compare/${installedSHA}...${gitBranch}`; - } else { - return `https://github.com/discourse/discourse/tree/${installedSHA}`; + @discourseComputed("installed_sha") + gitLink(installedSHA) { + if (installedSHA) { + return `https://github.com/discourse/discourse/commits/${installedSHA}`; } }, - @computed("installed_sha") + @discourseComputed("installed_sha") shortSha(installedSHA) { - return installedSHA.substr(0, 10); + if (installedSHA) { + return installedSHA.substr(0, 10); + } } }); diff --git a/app/assets/javascripts/admin/models/watched-word.js.es6 b/app/assets/javascripts/admin/models/watched-word.js.es6 index 03404e7de0..dac78affe1 100644 --- a/app/assets/javascripts/admin/models/watched-word.js.es6 +++ b/app/assets/javascripts/admin/models/watched-word.js.es6 @@ -1,6 +1,7 @@ import { ajax } from "discourse/lib/ajax"; +import EmberObject from "@ember/object"; -const WatchedWord = Discourse.Model.extend({ +const WatchedWord = EmberObject.extend({ save() { return ajax( "/admin/logs/watched_words" + (this.id ? "/" + this.id : "") + ".json", @@ -37,12 +38,13 @@ WatchedWord.reopenClass({ }); return Object.keys(actions).map(n => { - return Ember.Object.create({ + return EmberObject.create({ nameKey: n, name: I18n.t("admin.watched_words.actions." + n), words: actions[n], count: actions[n].length, - regularExpressions: list.regular_expressions + regularExpressions: list.regular_expressions, + compiledRegularExpression: list.compiled_regular_expressions[n] }); }); }); diff --git a/app/assets/javascripts/admin/models/web-hook.js.es6 b/app/assets/javascripts/admin/models/web-hook.js.es6 index cc35f47437..b727ea59d1 100644 --- a/app/assets/javascripts/admin/models/web-hook.js.es6 +++ b/app/assets/javascripts/admin/models/web-hook.js.es6 @@ -1,10 +1,9 @@ +import { isEmpty } from "@ember/utils"; import RestModel from "discourse/models/rest"; import Category from "discourse/models/category"; import Group from "discourse/models/group"; -import { - default as computed, - observes -} from "ember-addons/ember-computed-decorators"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; +import Site from "discourse/models/site"; export default RestModel.extend({ content_type: 1, // json @@ -15,7 +14,7 @@ export default RestModel.extend({ web_hook_event_types: null, groupsFilterInName: null, - @computed("wildcard_web_hook") + @discourseComputed("wildcard_web_hook") webHookType: { get(wildcard) { return wildcard ? "wildcard" : "individual"; @@ -25,7 +24,7 @@ export default RestModel.extend({ } }, - @computed("category_ids") + @discourseComputed("category_ids") categories(categoryIds) { return Category.findByIds(categoryIds); }, @@ -35,7 +34,7 @@ export default RestModel.extend({ const groupIds = this.group_ids; this.set( "groupsFilterInName", - Discourse.Site.currentProp("groups").reduce((groupNames, g) => { + Site.currentProp("groups").reduce((groupNames, g) => { if (groupIds.includes(g.id)) { groupNames.push(g.name); } @@ -48,7 +47,7 @@ export default RestModel.extend({ return Group.findAll({ term: term, ignore_automatic: false }); }, - @computed("wildcard_web_hook", "web_hook_event_types.[]") + @discourseComputed("wildcard_web_hook", "web_hook_event_types.[]") description(isWildcardWebHook, types) { let desc = ""; @@ -78,15 +77,15 @@ export default RestModel.extend({ wildcard_web_hook: this.wildcard_web_hook, verify_certificate: this.verify_certificate, active: this.active, - web_hook_event_type_ids: Ember.isEmpty(types) + web_hook_event_type_ids: isEmpty(types) ? [null] : types.map(type => type.id), - category_ids: Ember.isEmpty(categoryIds) ? [null] : categoryIds, - tag_names: Ember.isEmpty(tagNames) ? [null] : tagNames, + category_ids: isEmpty(categoryIds) ? [null] : categoryIds, + tag_names: isEmpty(tagNames) ? [null] : tagNames, group_ids: - Ember.isEmpty(groupNames) || Ember.isEmpty(groupNames[0]) + isEmpty(groupNames) || isEmpty(groupNames[0]) ? [null] - : Discourse.Site.currentProp("groups").reduce((groupIds, g) => { + : Site.currentProp("groups").reduce((groupIds, g) => { if (groupNames.includes(g.name)) { groupIds.push(g.id); } diff --git a/app/assets/javascripts/admin/routes/admin-api-index.js.es6 b/app/assets/javascripts/admin/routes/admin-api-index.js.es6 index c66efab129..f770f90c91 100644 --- a/app/assets/javascripts/admin/routes/admin-api-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-api-index.js.es6 @@ -1,4 +1,6 @@ -export default Ember.Route.extend({ +import Route from "@ember/routing/route"; + +export default Route.extend({ beforeModel() { this.transitionTo("adminApiKeys"); } diff --git a/app/assets/javascripts/admin/routes/admin-api-keys-index.js.es6 b/app/assets/javascripts/admin/routes/admin-api-keys-index.js.es6 new file mode 100644 index 0000000000..25f6df3e4a --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-api-keys-index.js.es6 @@ -0,0 +1,7 @@ +import Route from "@ember/routing/route"; + +export default Route.extend({ + model() { + return this.store.findAll("api-key"); + } +}); diff --git a/app/assets/javascripts/admin/routes/admin-api-keys-new.es6 b/app/assets/javascripts/admin/routes/admin-api-keys-new.es6 new file mode 100644 index 0000000000..3969615c9f --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-api-keys-new.es6 @@ -0,0 +1,7 @@ +import Route from "@ember/routing/route"; + +export default Route.extend({ + model() { + return this.store.createRecord("api-key"); + } +}); diff --git a/app/assets/javascripts/admin/routes/admin-api-keys-show.js.es6 b/app/assets/javascripts/admin/routes/admin-api-keys-show.js.es6 new file mode 100644 index 0000000000..0355241c03 --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-api-keys-show.js.es6 @@ -0,0 +1,7 @@ +import Route from "@ember/routing/route"; + +export default Route.extend({ + model(params) { + return this.store.find("api-key", params.api_key_id); + } +}); diff --git a/app/assets/javascripts/admin/routes/admin-api-keys.js.es6 b/app/assets/javascripts/admin/routes/admin-api-keys.js.es6 index 4332cb0adc..1b3f3067fc 100644 --- a/app/assets/javascripts/admin/routes/admin-api-keys.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-api-keys.js.es6 @@ -1,7 +1,13 @@ -import ApiKey from "admin/models/api-key"; +import Route from "@ember/routing/route"; -export default Ember.Route.extend({ - model() { - return ApiKey.find(); +export default Route.extend({ + actions: { + show(apiKey) { + this.transitionTo("adminApiKeys.show", apiKey.id); + }, + + new() { + this.transitionTo("adminApiKeys.new"); + } } }); diff --git a/app/assets/javascripts/admin/routes/admin-backups-index.js.es6 b/app/assets/javascripts/admin/routes/admin-backups-index.js.es6 index 39ed982a78..d463819a2a 100644 --- a/app/assets/javascripts/admin/routes/admin-backups-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-backups-index.js.es6 @@ -1,9 +1,13 @@ +import Route from "@ember/routing/route"; import Backup from "admin/models/backup"; -export default Ember.Route.extend({ +export default Route.extend({ activate() { this.messageBus.subscribe("/admin/backups", backups => - this.controller.set("model", backups.map(backup => Backup.create(backup))) + this.controller.set( + "model", + backups.map(backup => Backup.create(backup)) + ) ); }, diff --git a/app/assets/javascripts/admin/routes/admin-backups-logs.js.es6 b/app/assets/javascripts/admin/routes/admin-backups-logs.js.es6 index a0febc3d1d..3d24756a4a 100644 --- a/app/assets/javascripts/admin/routes/admin-backups-logs.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-backups-logs.js.es6 @@ -1,6 +1,8 @@ +import EmberObject from "@ember/object"; +import Route from "@ember/routing/route"; import PreloadStore from "preload-store"; -export default Ember.Route.extend({ +export default Route.extend({ // since the logs are pushed via the message bus // we only want to preload them (hence the beforeModel hook) beforeModel() { @@ -15,7 +17,7 @@ export default Ember.Route.extend({ return log.message.length === 0 || log.message[0] === "["; }) .map(function(log) { - return Ember.Object.create(log); + return EmberObject.create(log); }) .value(); logs.pushObjects(newLogs); diff --git a/app/assets/javascripts/admin/routes/admin-backups.js.es6 b/app/assets/javascripts/admin/routes/admin-backups.js.es6 index b06b3e8468..6bc42d725f 100644 --- a/app/assets/javascripts/admin/routes/admin-backups.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-backups.js.es6 @@ -1,16 +1,19 @@ +import EmberObject from "@ember/object"; +import DiscourseRoute from "discourse/routes/discourse"; import { ajax } from "discourse/lib/ajax"; import showModal from "discourse/lib/show-modal"; import BackupStatus from "admin/models/backup-status"; import Backup from "admin/models/backup"; import PreloadStore from "preload-store"; +import User from "discourse/models/user"; const LOG_CHANNEL = "/admin/backups/logs"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ activate() { this.messageBus.subscribe(LOG_CHANNEL, log => { if (log.message === "[STARTED]") { - Discourse.User.currentProp("hideReadOnlyAlert", true); + User.currentProp("hideReadOnlyAlert", true); this.controllerFor("adminBackups").set( "model.isOperationRunning", true @@ -29,19 +32,19 @@ export default Discourse.Route.extend({ }) ); } else if (log.message === "[SUCCESS]") { - Discourse.User.currentProp("hideReadOnlyAlert", false); + User.currentProp("hideReadOnlyAlert", false); this.controllerFor("adminBackups").set( "model.isOperationRunning", false ); if (log.operation === "restore") { // redirect to homepage when the restore is done (session might be lost) - window.location.pathname = Discourse.getURL("/"); + window.location = Discourse.getURL("/"); } } else { this.controllerFor("adminBackupsLogs") .get("logs") - .pushObject(Ember.Object.create(log)); + .pushObject(EmberObject.create(log)); } }); }, diff --git a/app/assets/javascripts/admin/routes/admin-badges-award.js.es6 b/app/assets/javascripts/admin/routes/admin-badges-award.js.es6 new file mode 100644 index 0000000000..90a4cba17f --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-badges-award.js.es6 @@ -0,0 +1,12 @@ +import Route from "discourse/routes/discourse"; + +export default Route.extend({ + model(params) { + if (params.badge_id !== "new") { + return this.modelFor("adminBadges").findBy( + "id", + parseInt(params.badge_id, 10) + ); + } + } +}); diff --git a/app/assets/javascripts/admin/routes/admin-badges-index.js.es6 b/app/assets/javascripts/admin/routes/admin-badges-index.js.es6 index 84a5bee571..36a4414877 100644 --- a/app/assets/javascripts/admin/routes/admin-badges-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-badges-index.js.es6 @@ -1,3 +1,4 @@ +import Route from "@ember/routing/route"; import { emojiUrlFor } from "discourse/lib/text"; const badgeIntroLinks = [ @@ -13,7 +14,7 @@ const badgeIntroLinks = [ } ]; -export default Ember.Route.extend({ +export default Route.extend({ setupController(controller) { controller.setProperties({ badgeIntroLinks, diff --git a/app/assets/javascripts/admin/routes/admin-badges-show.js.es6 b/app/assets/javascripts/admin/routes/admin-badges-show.js.es6 index 7837d1b30d..67acdcba58 100644 --- a/app/assets/javascripts/admin/routes/admin-badges-show.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-badges-show.js.es6 @@ -1,10 +1,12 @@ +import { get } from "@ember/object"; +import Route from "@ember/routing/route"; import { ajax } from "discourse/lib/ajax"; import Badge from "discourse/models/badge"; import showModal from "discourse/lib/show-modal"; -export default Ember.Route.extend({ +export default Route.extend({ serialize(m) { - return { badge_id: Ember.get(m, "id") || "new" }; + return { badge_id: get(m, "id") || "new" }; }, model(params) { @@ -13,7 +15,10 @@ export default Ember.Route.extend({ name: I18n.t("admin.badges.new_badge") }); } - return this.modelFor("adminBadges").findBy("id", parseInt(params.badge_id)); + return this.modelFor("adminBadges").findBy( + "id", + parseInt(params.badge_id, 10) + ); }, actions: { @@ -49,7 +54,8 @@ export default Ember.Route.extend({ }) .catch(function(error) { badge.set("preview_loading", false); - Ember.Logger.error(error); + // eslint-disable-next-line no-console + console.error(error); bootbox.alert("Network error"); }); } diff --git a/app/assets/javascripts/admin/routes/admin-badges.js.es6 b/app/assets/javascripts/admin/routes/admin-badges.js.es6 index 9fee47fb4e..0dac4bbaa2 100644 --- a/app/assets/javascripts/admin/routes/admin-badges.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-badges.js.es6 @@ -1,8 +1,9 @@ +import DiscourseRoute from "discourse/routes/discourse"; import { ajax } from "discourse/lib/ajax"; import Badge from "discourse/models/badge"; import BadgeGrouping from "discourse/models/badge-grouping"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ _json: null, model() { diff --git a/app/assets/javascripts/admin/routes/admin-customize-colors-show.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-colors-show.js.es6 index 545a09ad9b..8807df2c56 100644 --- a/app/assets/javascripts/admin/routes/admin-customize-colors-show.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-customize-colors-show.js.es6 @@ -1,7 +1,8 @@ -export default Ember.Route.extend({ +import Route from "@ember/routing/route"; +export default Route.extend({ model(params) { const all = this.modelFor("adminCustomize.colors"); - const model = all.findBy("id", parseInt(params.scheme_id)); + const model = all.findBy("id", parseInt(params.scheme_id, 10)); return model ? model : this.replaceWith("adminCustomize.colors.index"); }, diff --git a/app/assets/javascripts/admin/routes/admin-customize-colors.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-colors.js.es6 index f4853fda28..835814df07 100644 --- a/app/assets/javascripts/admin/routes/admin-customize-colors.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-customize-colors.js.es6 @@ -1,6 +1,7 @@ +import Route from "@ember/routing/route"; import ColorScheme from "admin/models/color-scheme"; -export default Ember.Route.extend({ +export default Route.extend({ model() { return ColorScheme.findAll(); }, diff --git a/app/assets/javascripts/admin/routes/admin-customize-email-style-edit.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-email-style-edit.js.es6 new file mode 100644 index 0000000000..8dcc18f0d9 --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-customize-email-style-edit.js.es6 @@ -0,0 +1,40 @@ +import Route from "@ember/routing/route"; +export default Route.extend({ + model(params) { + return { + model: this.modelFor("adminCustomizeEmailStyle"), + fieldName: params.field_name + }; + }, + + setupController(controller, model) { + controller.setProperties({ + fieldName: model.fieldName, + model: model.model + }); + this._shouldAlertUnsavedChanges = true; + }, + + actions: { + willTransition(transition) { + if ( + this.get("controller.model.changed") && + this._shouldAlertUnsavedChanges && + transition.intent.name !== this.routeName + ) { + transition.abort(); + bootbox.confirm( + I18n.t("admin.customize.theme.unsaved_changes_alert"), + I18n.t("admin.customize.theme.discard"), + I18n.t("admin.customize.theme.stay"), + result => { + if (!result) { + this._shouldAlertUnsavedChanges = false; + transition.retry(); + } + } + ); + } + } + } +}); diff --git a/app/assets/javascripts/admin/routes/admin-customize-email-style-index.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-email-style-index.js.es6 new file mode 100644 index 0000000000..a6ba062829 --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-customize-email-style-index.js.es6 @@ -0,0 +1,7 @@ +import Route from "@ember/routing/route"; + +export default Route.extend({ + beforeModel() { + this.replaceWith("adminCustomizeEmailStyle.edit", "html"); + } +}); diff --git a/app/assets/javascripts/admin/routes/admin-customize-email-style.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-email-style.js.es6 new file mode 100644 index 0000000000..3ec7b6e1bb --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-customize-email-style.js.es6 @@ -0,0 +1,7 @@ +import Route from "@ember/routing/route"; + +export default Route.extend({ + model() { + return this.store.find("email-style"); + } +}); diff --git a/app/assets/javascripts/admin/routes/admin-customize-email-templates-edit.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-email-templates-edit.js.es6 index a6f170b09e..0bf85baf4a 100644 --- a/app/assets/javascripts/admin/routes/admin-customize-email-templates-edit.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-customize-email-templates-edit.js.es6 @@ -1,6 +1,7 @@ +import Route from "@ember/routing/route"; import { scrollTop } from "discourse/mixins/scroll-top"; -export default Ember.Route.extend({ +export default Route.extend({ model(params) { const all = this.modelFor("adminCustomizeEmailTemplates"); return all.findBy("id", params.id); diff --git a/app/assets/javascripts/admin/routes/admin-customize-email-templates.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-email-templates.js.es6 index fa6d3041a6..39b04b835f 100644 --- a/app/assets/javascripts/admin/routes/admin-customize-email-templates.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-customize-email-templates.js.es6 @@ -1,4 +1,5 @@ -export default Ember.Route.extend({ +import Route from "@ember/routing/route"; +export default Route.extend({ model() { return this.store.findAll("email-template"); }, diff --git a/app/assets/javascripts/admin/routes/admin-customize-index.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-index.js.es6 index 10d55df015..9c593b7e25 100644 --- a/app/assets/javascripts/admin/routes/admin-customize-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-customize-index.js.es6 @@ -1,4 +1,5 @@ -export default Ember.Route.extend({ +import Route from "@ember/routing/route"; +export default Route.extend({ beforeModel() { this.transitionTo("adminCustomizeThemes"); } diff --git a/app/assets/javascripts/admin/routes/admin-customize-robots-txt.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-robots-txt.js.es6 new file mode 100644 index 0000000000..866d7cf8ec --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-customize-robots-txt.js.es6 @@ -0,0 +1,8 @@ +import Route from "@ember/routing/route"; +import { ajax } from "discourse/lib/ajax"; + +export default Route.extend({ + model() { + return ajax("/admin/customize/robots"); + } +}); diff --git a/app/assets/javascripts/admin/routes/admin-customize-themes-edit.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-themes-edit.js.es6 index 2e37bdc1ce..62a70f0d7c 100644 --- a/app/assets/javascripts/admin/routes/admin-customize-themes-edit.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-customize-themes-edit.js.es6 @@ -1,7 +1,8 @@ -export default Ember.Route.extend({ +import Route from "@ember/routing/route"; +export default Route.extend({ model(params) { const all = this.modelFor("adminCustomizeThemes"); - const model = all.findBy("id", parseInt(params.theme_id)); + const model = all.findBy("id", parseInt(params.theme_id, 10)); return model ? { model, diff --git a/app/assets/javascripts/admin/routes/admin-customize-themes-index.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-themes-index.js.es6 index ea09122d62..425a04617a 100644 --- a/app/assets/javascripts/admin/routes/admin-customize-themes-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-customize-themes-index.js.es6 @@ -1,3 +1,4 @@ +import Route from "@ember/routing/route"; import { emojiUrlFor } from "discourse/lib/text"; const externalResources = [ @@ -18,7 +19,7 @@ const externalResources = [ } ]; -export default Ember.Route.extend({ +export default Route.extend({ setupController(controller) { this._super(...arguments); this.controllerFor("adminCustomizeThemes").set("editingTheme", false); diff --git a/app/assets/javascripts/admin/routes/admin-customize-themes-show.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-themes-show.js.es6 index a0ca4feef8..5b9ce25169 100644 --- a/app/assets/javascripts/admin/routes/admin-customize-themes-show.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-customize-themes-show.js.es6 @@ -1,14 +1,15 @@ +import Route from "@ember/routing/route"; import { scrollTop } from "discourse/mixins/scroll-top"; import { THEMES, COMPONENTS } from "admin/models/theme"; -export default Ember.Route.extend({ +export default Route.extend({ serialize(model) { return { theme_id: model.get("id") }; }, model(params) { const all = this.modelFor("adminCustomizeThemes"); - const model = all.findBy("id", parseInt(params.theme_id)); + const model = all.findBy("id", parseInt(params.theme_id, 10)); return model ? model : this.replaceWith("adminCustomizeTheme.index"); }, @@ -50,6 +51,23 @@ export default Ember.Route.extend({ actions: { didTransition() { scrollTop(); + }, + willTransition(transition) { + const model = this.controller.model; + if (model.recentlyInstalled && !model.hasParents && model.component) { + transition.abort(); + bootbox.confirm( + I18n.t("admin.customize.theme.unsaved_parent_themes"), + I18n.t("admin.customize.theme.discard"), + I18n.t("admin.customize.theme.stay"), + result => { + if (!result) { + this.controller.model.setProperties({ recentlyInstalled: false }); + transition.retry(); + } + } + ); + } } } }); diff --git a/app/assets/javascripts/admin/routes/admin-customize-themes.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-themes.js.es6 index 91c7ec5b76..7413057253 100644 --- a/app/assets/javascripts/admin/routes/admin-customize-themes.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-customize-themes.js.es6 @@ -1,6 +1,7 @@ +import Route from "@ember/routing/route"; import showModal from "discourse/lib/show-modal"; -export default Ember.Route.extend({ +export default Route.extend({ model() { return this.store.findAll("theme"); }, @@ -17,6 +18,7 @@ export default Ember.Route.extend({ addTheme(theme) { this.refresh(); + theme.setProperties({ recentlyInstalled: true }); this.transitionTo("adminCustomizeThemes.show", theme.get("id")); } } diff --git a/app/assets/javascripts/admin/routes/admin-dashboard-general.js.es6 b/app/assets/javascripts/admin/routes/admin-dashboard-general.js.es6 index ffd544b3db..1ee1b22121 100644 --- a/app/assets/javascripts/admin/routes/admin-dashboard-general.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-dashboard-general.js.es6 @@ -1,4 +1,6 @@ -export default Discourse.Route.extend({ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ activate() { this.controllerFor("admin-dashboard-general").fetchDashboard(); } diff --git a/app/assets/javascripts/admin/routes/admin-dashboard-reports.js.es6 b/app/assets/javascripts/admin/routes/admin-dashboard-reports.js.es6 index 7aea901096..0de5bfe505 100644 --- a/app/assets/javascripts/admin/routes/admin-dashboard-reports.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-dashboard-reports.js.es6 @@ -1,6 +1,7 @@ +import DiscourseRoute from "discourse/routes/discourse"; import { ajax } from "discourse/lib/ajax"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ model() { return ajax("/admin/reports").then(json => json); }, diff --git a/app/assets/javascripts/admin/routes/admin-dashboard.js.es6 b/app/assets/javascripts/admin/routes/admin-dashboard.js.es6 index a6ff3c215e..905a148bc7 100644 --- a/app/assets/javascripts/admin/routes/admin-dashboard.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-dashboard.js.es6 @@ -1,6 +1,7 @@ +import DiscourseRoute from "discourse/routes/discourse"; import { scrollTop } from "discourse/mixins/scroll-top"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ activate() { this.controllerFor("admin-dashboard").fetchProblems(); this.controllerFor("admin-dashboard").fetchDashboard(); diff --git a/app/assets/javascripts/admin/routes/admin-email-incomings.js.es6 b/app/assets/javascripts/admin/routes/admin-email-incomings.js.es6 index 79331282bb..aa86f7f31d 100644 --- a/app/assets/javascripts/admin/routes/admin-email-incomings.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-email-incomings.js.es6 @@ -1,6 +1,7 @@ +import DiscourseRoute from "discourse/routes/discourse"; import IncomingEmail from "admin/models/incoming-email"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ model() { return IncomingEmail.findAll({ status: this.status }); }, diff --git a/app/assets/javascripts/admin/routes/admin-email-index.js.es6 b/app/assets/javascripts/admin/routes/admin-email-index.js.es6 index ca912cc009..878885f983 100644 --- a/app/assets/javascripts/admin/routes/admin-email-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-email-index.js.es6 @@ -1,6 +1,7 @@ +import DiscourseRoute from "discourse/routes/discourse"; import EmailSettings from "admin/models/email-settings"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ model() { return EmailSettings.find(); } diff --git a/app/assets/javascripts/admin/routes/admin-email-logs.js.es6 b/app/assets/javascripts/admin/routes/admin-email-logs.js.es6 index 7813fb93fd..8ebfebd199 100644 --- a/app/assets/javascripts/admin/routes/admin-email-logs.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-email-logs.js.es6 @@ -1,4 +1,6 @@ -export default Discourse.Route.extend({ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ setupController(controller) { controller.setProperties({ loading: true, diff --git a/app/assets/javascripts/admin/routes/admin-email-preview-digest.js.es6 b/app/assets/javascripts/admin/routes/admin-email-preview-digest.js.es6 index 972f3c9b6d..56464eaf8b 100644 --- a/app/assets/javascripts/admin/routes/admin-email-preview-digest.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-email-preview-digest.js.es6 @@ -1,9 +1,7 @@ -import { - default as EmailPreview, - oneWeekAgo -} from "admin/models/email-preview"; +import DiscourseRoute from "discourse/routes/discourse"; +import EmailPreview, { oneWeekAgo } from "admin/models/email-preview"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ model() { return EmailPreview.findDigest(this.currentUser.get("username")); }, diff --git a/app/assets/javascripts/admin/routes/admin-embedding.js.es6 b/app/assets/javascripts/admin/routes/admin-embedding.js.es6 index d698c52450..080dce2083 100644 --- a/app/assets/javascripts/admin/routes/admin-embedding.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-embedding.js.es6 @@ -1,4 +1,5 @@ -export default Ember.Route.extend({ +import Route from "@ember/routing/route"; +export default Route.extend({ model() { return this.store.find("embedding"); }, diff --git a/app/assets/javascripts/admin/routes/admin-emojis.js.es6 b/app/assets/javascripts/admin/routes/admin-emojis.js.es6 index f412a90898..dd7e94d40a 100644 --- a/app/assets/javascripts/admin/routes/admin-emojis.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-emojis.js.es6 @@ -1,9 +1,12 @@ +import EmberObject from "@ember/object"; +import DiscourseRoute from "discourse/routes/discourse"; import { ajax } from "discourse/lib/ajax"; -export default Discourse.Route.extend({ + +export default DiscourseRoute.extend({ model: function() { return ajax("/admin/customize/emojis.json").then(function(emojis) { return emojis.map(function(emoji) { - return Ember.Object.create(emoji); + return EmberObject.create(emoji); }); }); } diff --git a/app/assets/javascripts/admin/routes/admin-flags-posts-active.js.es6 b/app/assets/javascripts/admin/routes/admin-flags-posts-active.js.es6 index d971939503..1a1d01a091 100644 --- a/app/assets/javascripts/admin/routes/admin-flags-posts-active.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-flags-posts-active.js.es6 @@ -1,4 +1,6 @@ -export default Discourse.Route.extend({ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ model() { return this.store.findAll("flagged-post", { filter: "active" }); } diff --git a/app/assets/javascripts/admin/routes/admin-flags-posts-old.js.es6 b/app/assets/javascripts/admin/routes/admin-flags-posts-old.js.es6 index c7d22ccdad..a4157d5714 100644 --- a/app/assets/javascripts/admin/routes/admin-flags-posts-old.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-flags-posts-old.js.es6 @@ -1,4 +1,6 @@ -export default Discourse.Route.extend({ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ model() { return this.store.findAll("flagged-post", { filter: "old" }); } diff --git a/app/assets/javascripts/admin/routes/admin-flags-topics-index.js.es6 b/app/assets/javascripts/admin/routes/admin-flags-topics-index.js.es6 index d635328c05..5fb295d648 100644 --- a/app/assets/javascripts/admin/routes/admin-flags-topics-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-flags-topics-index.js.es6 @@ -1,4 +1,6 @@ -export default Discourse.Route.extend({ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ model() { return this.store.findAll("flagged-topic"); }, diff --git a/app/assets/javascripts/admin/routes/admin-logs-index.js.es6 b/app/assets/javascripts/admin/routes/admin-logs-index.js.es6 index ff4c94aada..d003b429b6 100644 --- a/app/assets/javascripts/admin/routes/admin-logs-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-logs-index.js.es6 @@ -1,4 +1,6 @@ -export default Discourse.Route.extend({ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ redirect: function() { this.transitionTo("adminLogs.staffActionLogs"); } diff --git a/app/assets/javascripts/admin/routes/admin-logs-screened-emails.js.es6 b/app/assets/javascripts/admin/routes/admin-logs-screened-emails.js.es6 index f4f3a4f673..2016a96188 100644 --- a/app/assets/javascripts/admin/routes/admin-logs-screened-emails.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-logs-screened-emails.js.es6 @@ -1,4 +1,6 @@ -export default Discourse.Route.extend({ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ renderTemplate: function() { this.render("admin/templates/logs/screened-emails", { into: "adminLogs" }); }, diff --git a/app/assets/javascripts/admin/routes/admin-logs-screened-ip-addresses.js.es6 b/app/assets/javascripts/admin/routes/admin-logs-screened-ip-addresses.js.es6 index 24000436b7..4343785594 100644 --- a/app/assets/javascripts/admin/routes/admin-logs-screened-ip-addresses.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-logs-screened-ip-addresses.js.es6 @@ -1,4 +1,6 @@ -export default Discourse.Route.extend({ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ renderTemplate() { this.render("admin/templates/logs/screened-ip-addresses", { into: "adminLogs" diff --git a/app/assets/javascripts/admin/routes/admin-logs-screened-urls.js.es6 b/app/assets/javascripts/admin/routes/admin-logs-screened-urls.js.es6 index 2369a886cd..ba0b76c9c5 100644 --- a/app/assets/javascripts/admin/routes/admin-logs-screened-urls.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-logs-screened-urls.js.es6 @@ -1,4 +1,6 @@ -export default Discourse.Route.extend({ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ renderTemplate: function() { this.render("admin/templates/logs/screened-urls", { into: "adminLogs" }); }, diff --git a/app/assets/javascripts/admin/routes/admin-logs-staff-action-logs.js.es6 b/app/assets/javascripts/admin/routes/admin-logs-staff-action-logs.js.es6 index fd1fea9615..0ef8e6e232 100644 --- a/app/assets/javascripts/admin/routes/admin-logs-staff-action-logs.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-logs-staff-action-logs.js.es6 @@ -1,6 +1,7 @@ +import DiscourseRoute from "discourse/routes/discourse"; import showModal from "discourse/lib/show-modal"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ // TODO: make this automatic using an `{{outlet}}` renderTemplate: function() { this.render("admin/templates/logs/staff-action-logs", { diff --git a/app/assets/javascripts/admin/routes/admin-permalinks.js.es6 b/app/assets/javascripts/admin/routes/admin-permalinks.js.es6 index d8c3c43865..996b90aaa8 100644 --- a/app/assets/javascripts/admin/routes/admin-permalinks.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-permalinks.js.es6 @@ -1,6 +1,7 @@ +import DiscourseRoute from "discourse/routes/discourse"; import Permalink from "admin/models/permalink"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ model() { return Permalink.findAll(); }, diff --git a/app/assets/javascripts/admin/routes/admin-plugins.js.es6 b/app/assets/javascripts/admin/routes/admin-plugins.js.es6 index 06ea7eb259..c4af286faf 100644 --- a/app/assets/javascripts/admin/routes/admin-plugins.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-plugins.js.es6 @@ -1,4 +1,5 @@ -export default Ember.Route.extend({ +import Route from "@ember/routing/route"; +export default Route.extend({ model() { return this.store.findAll("plugin"); }, diff --git a/app/assets/javascripts/admin/routes/admin-reports-index.js.es6 b/app/assets/javascripts/admin/routes/admin-reports-index.js.es6 index 2d21467ae6..864f1a3fb2 100644 --- a/app/assets/javascripts/admin/routes/admin-reports-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-reports-index.js.es6 @@ -1,4 +1,6 @@ -export default Discourse.Route.extend({ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ beforeModel() { this.transitionTo("admin.dashboardReports"); } diff --git a/app/assets/javascripts/admin/routes/admin-reports-show.js.es6 b/app/assets/javascripts/admin/routes/admin-reports-show.js.es6 index 9bf9ff8eba..1fa8bc739d 100644 --- a/app/assets/javascripts/admin/routes/admin-reports-show.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-reports-show.js.es6 @@ -1,4 +1,6 @@ -export default Discourse.Route.extend({ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ queryParams: { start_date: { refreshModel: true }, end_date: { refreshModel: true }, diff --git a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 index 9ae0063ffe..8297380e05 100644 --- a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 @@ -86,11 +86,29 @@ export default function() { this.route("edit", { path: "/:id" }); } ); + this.route("adminCustomizeRobotsTxt", { + path: "/robots", + resetNamespace: true + }); + this.route( + "adminCustomizeEmailStyle", + { path: "/email_style", resetNamespace: true }, + function() { + this.route("edit", { path: "/:field_name" }); + } + ); } ); this.route("adminApi", { path: "/api", resetNamespace: true }, function() { - this.route("adminApiKeys", { path: "/keys", resetNamespace: true }); + this.route( + "adminApiKeys", + { path: "/keys", resetNamespace: true }, + function() { + this.route("show", { path: "/:api_key_id" }); + this.route("new", { path: "/new" }); + } + ); this.route( "adminWebHooks", @@ -172,6 +190,7 @@ export default function() { "adminBadges", { path: "/badges", resetNamespace: true }, function() { + this.route("award", { path: "/award/:badge_id" }); this.route("show", { path: "/:badge_id" }); } ); diff --git a/app/assets/javascripts/admin/routes/admin-search-logs-index.js.es6 b/app/assets/javascripts/admin/routes/admin-search-logs-index.js.es6 index 966032045e..8244c0f2af 100644 --- a/app/assets/javascripts/admin/routes/admin-search-logs-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-search-logs-index.js.es6 @@ -1,6 +1,8 @@ +import EmberObject from "@ember/object"; +import DiscourseRoute from "discourse/routes/discourse"; import { ajax } from "discourse/lib/ajax"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ queryParams: { period: { refreshModel: true }, searchType: { refreshModel: true } @@ -11,7 +13,7 @@ export default Discourse.Route.extend({ return ajax("/admin/logs/search_logs.json", { data: { period: params.period, search_type: params.searchType } }).then(search_logs => { - return search_logs.map(sl => Ember.Object.create(sl)); + return search_logs.map(sl => EmberObject.create(sl)); }); }, diff --git a/app/assets/javascripts/admin/routes/admin-search-logs-term.js.es6 b/app/assets/javascripts/admin/routes/admin-search-logs-term.js.es6 index 40d3e25be6..7c9c8f274c 100644 --- a/app/assets/javascripts/admin/routes/admin-search-logs-term.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-search-logs-term.js.es6 @@ -1,8 +1,10 @@ +import EmberObject from "@ember/object"; +import DiscourseRoute from "discourse/routes/discourse"; import { ajax } from "discourse/lib/ajax"; import { fillMissingDates } from "discourse/lib/utilities"; import { translateResults } from "discourse/lib/search"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ queryParams: { term: { refreshModel: true }, period: { refreshModel: true }, @@ -32,7 +34,7 @@ export default Discourse.Route.extend({ json.term.search_result = translateResults(json.term.search_result); } - const model = Ember.Object.create({ type: "search_log_term" }); + const model = EmberObject.create({ type: "search_log_term" }); model.setProperties(json.term); return model; }); diff --git a/app/assets/javascripts/admin/routes/admin-site-settings-category.js.es6 b/app/assets/javascripts/admin/routes/admin-site-settings-category.js.es6 index a774415083..b5a2f810b2 100644 --- a/app/assets/javascripts/admin/routes/admin-site-settings-category.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-site-settings-category.js.es6 @@ -1,4 +1,7 @@ -export default Discourse.Route.extend({ +import EmberObject from "@ember/object"; +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ model(params) { // The model depends on user input, so let the controller do the work: this.controllerFor("adminSiteSettingsCategory").set( @@ -9,7 +12,7 @@ export default Discourse.Route.extend({ "categoryNameKey", params.category_id ); - return Ember.Object.create({ + return EmberObject.create({ nameKey: params.category_id, name: I18n.t("admin.site_settings.categories." + params.category_id), siteSettings: this.controllerFor("adminSiteSettingsCategory").get( diff --git a/app/assets/javascripts/admin/routes/admin-site-settings-index.js.es6 b/app/assets/javascripts/admin/routes/admin-site-settings-index.js.es6 index 59e851e509..88a091c4b9 100644 --- a/app/assets/javascripts/admin/routes/admin-site-settings-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-site-settings-index.js.es6 @@ -2,7 +2,9 @@ Handles when you click the Site Settings tab in admin, but haven't chosen a category. It will redirect to the first category. **/ -export default Discourse.Route.extend({ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ beforeModel() { this.replaceWith( "adminSiteSettingsCategory", diff --git a/app/assets/javascripts/admin/routes/admin-site-settings.js.es6 b/app/assets/javascripts/admin/routes/admin-site-settings.js.es6 index b6c3e857ea..ab687a4585 100644 --- a/app/assets/javascripts/admin/routes/admin-site-settings.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-site-settings.js.es6 @@ -1,6 +1,7 @@ +import DiscourseRoute from "discourse/routes/discourse"; import SiteSetting from "admin/models/site-setting"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ queryParams: { filter: { replace: true } }, diff --git a/app/assets/javascripts/admin/routes/admin-site-text-edit.js.es6 b/app/assets/javascripts/admin/routes/admin-site-text-edit.js.es6 index 00ccca0256..c423eae45a 100644 --- a/app/assets/javascripts/admin/routes/admin-site-text-edit.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-site-text-edit.js.es6 @@ -1,4 +1,5 @@ -export default Ember.Route.extend({ +import Route from "@ember/routing/route"; +export default Route.extend({ model(params) { return this.store.find("site-text", params.id); }, diff --git a/app/assets/javascripts/admin/routes/admin-site-text-index.js.es6 b/app/assets/javascripts/admin/routes/admin-site-text-index.js.es6 index dfec2f64d3..815dd1c084 100644 --- a/app/assets/javascripts/admin/routes/admin-site-text-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-site-text-index.js.es6 @@ -1,6 +1,8 @@ +import Route from "@ember/routing/route"; import showModal from "discourse/lib/show-modal"; +import { getProperties } from "@ember/object"; -export default Ember.Route.extend({ +export default Route.extend({ queryParams: { q: { replace: true }, overridden: { replace: true } @@ -9,7 +11,7 @@ export default Ember.Route.extend({ model(params) { return this.store.find( "site-text", - Ember.getProperties(params, "q", "overridden") + getProperties(params, "q", "overridden") ); }, diff --git a/app/assets/javascripts/admin/routes/admin-user-badges.js.es6 b/app/assets/javascripts/admin/routes/admin-user-badges.js.es6 index b585117f19..cce32753d1 100644 --- a/app/assets/javascripts/admin/routes/admin-user-badges.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-user-badges.js.es6 @@ -1,7 +1,8 @@ +import DiscourseRoute from "discourse/routes/discourse"; import UserBadge from "discourse/models/user-badge"; import Badge from "discourse/models/badge"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ model() { const username = this.modelFor("adminUser").get("username"); return UserBadge.findByUsername(username); diff --git a/app/assets/javascripts/admin/routes/admin-user-fields.js.es6 b/app/assets/javascripts/admin/routes/admin-user-fields.js.es6 index c4278dc627..ea4e0adef0 100644 --- a/app/assets/javascripts/admin/routes/admin-user-fields.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-user-fields.js.es6 @@ -1,6 +1,7 @@ +import DiscourseRoute from "discourse/routes/discourse"; import UserField from "admin/models/user-field"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ model: function() { return this.store.findAll("user-field"); }, diff --git a/app/assets/javascripts/admin/routes/admin-user-index.js.es6 b/app/assets/javascripts/admin/routes/admin-user-index.js.es6 index 2b623dbcf8..a0aeadd590 100644 --- a/app/assets/javascripts/admin/routes/admin-user-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-user-index.js.es6 @@ -1,6 +1,7 @@ +import DiscourseRoute from "discourse/routes/discourse"; import Group from "discourse/models/group"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ model() { return this.modelFor("adminUser"); }, @@ -18,7 +19,7 @@ export default Discourse.Route.extend({ controller.setProperties({ originalPrimaryGroupId: model.primary_group_id, availableGroups: this._availableGroups, - customGroupIdsBuffer: null, + customGroupIdsBuffer: model.customGroups.mapBy("id"), model }); } diff --git a/app/assets/javascripts/admin/routes/admin-user-tl3-requirements.js.es6 b/app/assets/javascripts/admin/routes/admin-user-tl3-requirements.js.es6 index 6364e89f7c..17cd54dc8a 100644 --- a/app/assets/javascripts/admin/routes/admin-user-tl3-requirements.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-user-tl3-requirements.js.es6 @@ -1,4 +1,6 @@ -export default Discourse.Route.extend({ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ model() { return this.modelFor("adminUser"); } diff --git a/app/assets/javascripts/admin/routes/admin-user.js.es6 b/app/assets/javascripts/admin/routes/admin-user.js.es6 index c25f750c25..563c63d0a5 100644 --- a/app/assets/javascripts/admin/routes/admin-user.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-user.js.es6 @@ -1,6 +1,8 @@ +import { get } from "@ember/object"; +import DiscourseRoute from "discourse/routes/discourse"; import AdminUser from "admin/models/admin-user"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ serialize(model) { return { user_id: model.get("id"), @@ -9,7 +11,7 @@ export default Discourse.Route.extend({ }, model(params) { - return AdminUser.find(Ember.get(params, "user_id")); + return AdminUser.find(get(params, "user_id")); }, renderTemplate() { diff --git a/app/assets/javascripts/admin/routes/admin-users-index.js.es6 b/app/assets/javascripts/admin/routes/admin-users-index.js.es6 index 8b28ea79fc..7549cb230c 100644 --- a/app/assets/javascripts/admin/routes/admin-users-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-users-index.js.es6 @@ -1,4 +1,6 @@ -export default Discourse.Route.extend({ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ redirect: function() { this.transitionTo("adminUsersList"); } diff --git a/app/assets/javascripts/admin/routes/admin-users-list-index.js.es6 b/app/assets/javascripts/admin/routes/admin-users-list-index.js.es6 index 696345ad42..9f15d72cff 100644 --- a/app/assets/javascripts/admin/routes/admin-users-list-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-users-list-index.js.es6 @@ -1,4 +1,6 @@ -export default Discourse.Route.extend({ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ beforeModel: function() { this.transitionTo("adminUsersList.show", "active"); } diff --git a/app/assets/javascripts/admin/routes/admin-users-list-show.js.es6 b/app/assets/javascripts/admin/routes/admin-users-list-show.js.es6 index 0b72b80e62..764fe4ad34 100644 --- a/app/assets/javascripts/admin/routes/admin-users-list-show.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-users-list-show.js.es6 @@ -1,4 +1,6 @@ -export default Discourse.Route.extend({ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ queryParams: { order: { refreshModel: true }, ascending: { refreshModel: true } @@ -21,7 +23,7 @@ export default Discourse.Route.extend({ refreshing: false }); - controller._refreshUsers(); + controller.resetFilters(); } } } diff --git a/app/assets/javascripts/admin/routes/admin-users-list.js.es6 b/app/assets/javascripts/admin/routes/admin-users-list.js.es6 index f2edad8aa5..9e520d1735 100644 --- a/app/assets/javascripts/admin/routes/admin-users-list.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-users-list.js.es6 @@ -1,8 +1,9 @@ +import DiscourseRoute from "discourse/routes/discourse"; import { exportEntity } from "discourse/lib/export-csv"; import { outputExportResult } from "discourse/lib/export-result"; import AdminUser from "admin/models/admin-user"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ actions: { exportUsers() { exportEntity("user_list", { @@ -11,7 +12,7 @@ export default Discourse.Route.extend({ }, sendInvites() { - this.transitionTo("userInvited", Discourse.User.current()); + this.transitionTo("userInvited", this.currentUser); }, deleteUser(user) { diff --git a/app/assets/javascripts/admin/routes/admin-watched-words-action.js.es6 b/app/assets/javascripts/admin/routes/admin-watched-words-action.js.es6 index 99450e09ce..358cfebe67 100644 --- a/app/assets/javascripts/admin/routes/admin-watched-words-action.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-watched-words-action.js.es6 @@ -1,4 +1,7 @@ -export default Discourse.Route.extend({ +import EmberObject from "@ember/object"; +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ model(params) { this.controllerFor("adminWatchedWordsAction").set( "actionNameKey", @@ -7,7 +10,7 @@ export default Discourse.Route.extend({ let filteredContent = this.controllerFor("adminWatchedWordsAction").get( "filteredContent" ); - return Ember.Object.create({ + return EmberObject.create({ nameKey: params.action_id, name: I18n.t("admin.watched_words.actions." + params.action_id), words: filteredContent diff --git a/app/assets/javascripts/admin/routes/admin-watched-words-index.js.es6 b/app/assets/javascripts/admin/routes/admin-watched-words-index.js.es6 index 4ff2351e1b..a957c11478 100644 --- a/app/assets/javascripts/admin/routes/admin-watched-words-index.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-watched-words-index.js.es6 @@ -1,4 +1,6 @@ -export default Discourse.Route.extend({ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ beforeModel() { this.replaceWith( "adminWatchedWords.action", diff --git a/app/assets/javascripts/admin/routes/admin-watched-words.js.es6 b/app/assets/javascripts/admin/routes/admin-watched-words.js.es6 index 8970e6a614..924ab49779 100644 --- a/app/assets/javascripts/admin/routes/admin-watched-words.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-watched-words.js.es6 @@ -1,6 +1,7 @@ +import DiscourseRoute from "discourse/routes/discourse"; import WatchedWord from "admin/models/watched-word"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ queryParams: { filter: { replace: true } }, diff --git a/app/assets/javascripts/admin/routes/admin-web-hooks-show-events.js.es6 b/app/assets/javascripts/admin/routes/admin-web-hooks-show-events.js.es6 index 1e0d3220cf..64f28d3652 100644 --- a/app/assets/javascripts/admin/routes/admin-web-hooks-show-events.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-web-hooks-show-events.js.es6 @@ -1,9 +1,9 @@ -export default Discourse.Route.extend({ +import { get } from "@ember/object"; +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ model(params) { - return this.store.findAll( - "web-hook-event", - Ember.get(params, "web_hook_id") - ); + return this.store.findAll("web-hook-event", get(params, "web_hook_id")); }, setupController(controller, model) { diff --git a/app/assets/javascripts/admin/routes/admin-web-hooks-show.js.es6 b/app/assets/javascripts/admin/routes/admin-web-hooks-show.js.es6 index 611b015dcb..34aed775cc 100644 --- a/app/assets/javascripts/admin/routes/admin-web-hooks-show.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-web-hooks-show.js.es6 @@ -1,4 +1,8 @@ -export default Discourse.Route.extend({ +import { get } from "@ember/object"; +import { isEmpty } from "@ember/utils"; +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ serialize(model) { return { web_hook_id: model.get("id") || "new" }; }, @@ -7,14 +11,11 @@ export default Discourse.Route.extend({ if (params.web_hook_id === "new") { return this.store.createRecord("web-hook"); } - return this.store.find("web-hook", Ember.get(params, "web_hook_id")); + return this.store.find("web-hook", get(params, "web_hook_id")); }, setupController(controller, model) { - if ( - model.get("isNew") || - Ember.isEmpty(model.get("web_hook_event_types")) - ) { + if (model.get("isNew") || isEmpty(model.get("web_hook_event_types"))) { model.set("web_hook_event_types", controller.get("defaultEventTypes")); } diff --git a/app/assets/javascripts/admin/routes/admin-web-hooks.js.es6 b/app/assets/javascripts/admin/routes/admin-web-hooks.js.es6 index eed884a8ab..30daf6b6cb 100644 --- a/app/assets/javascripts/admin/routes/admin-web-hooks.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-web-hooks.js.es6 @@ -1,4 +1,5 @@ -export default Ember.Route.extend({ +import Route from "@ember/routing/route"; +export default Route.extend({ model() { return this.store.findAll("web-hook"); }, diff --git a/app/assets/javascripts/admin/routes/admin.js.es6 b/app/assets/javascripts/admin/routes/admin.js.es6 index fa373dd937..bc4e8173ef 100644 --- a/app/assets/javascripts/admin/routes/admin.js.es6 +++ b/app/assets/javascripts/admin/routes/admin.js.es6 @@ -1,4 +1,6 @@ -export default Discourse.Route.extend({ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ titleToken() { return I18n.t("admin_title"); }, diff --git a/app/assets/javascripts/admin/services/admin-tools.js.es6 b/app/assets/javascripts/admin/services/admin-tools.js.es6 index 7f3b4d88a8..bb5e1ac06a 100644 --- a/app/assets/javascripts/admin/services/admin-tools.js.es6 +++ b/app/assets/javascripts/admin/services/admin-tools.js.es6 @@ -2,13 +2,16 @@ // and the admin application. Use this if you need front end code to access admin // modules. Inject it optionally, and if it exists go to town! +import EmberObject from "@ember/object"; import AdminUser from "admin/models/admin-user"; import { iconHTML } from "discourse-common/lib/icon-library"; import { ajax } from "discourse/lib/ajax"; import showModal from "discourse/lib/show-modal"; import { getOwner } from "discourse-common/lib/get-owner"; +import Service from "@ember/service"; +import { Promise } from "rsvp"; -export default Ember.Service.extend({ +export default Service.extend({ init() { this._super(...arguments); @@ -21,7 +24,7 @@ export default Ember.Service.extend({ "controller:adminLogs.staffActionLogs" ); target.transitionToRoute("adminLogs.staffActionLogs").then(() => { - controller.set("filters", Ember.Object.create()); + controller.set("filters", EmberObject.create()); controller._changeFilters(filters); }); }, @@ -52,7 +55,7 @@ export default Ember.Service.extend({ controller.setProperties({ postId: opts.postId, postEdit: opts.postEdit }); return (user.adminUserView - ? Ember.RSVP.resolve(user) + ? Promise.resolve(user) : AdminUser.find(user.get("id")) ).then(loadedUser => { controller.setProperties({ @@ -76,7 +79,7 @@ export default Ember.Service.extend({ // Try loading the email if the site supports it let tryEmail = this.siteSettings.moderators_view_emails ? adminUser.checkEmail() - : Ember.RSVP.resolve(); + : Promise.resolve(); return tryEmail.then(() => { let message = I18n.messageFormat("flagging.delete_confirm_MF", { @@ -90,7 +93,7 @@ export default Ember.Service.extend({ let userId = adminUser.get("id"); - return new Ember.RSVP.Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const buttons = [ { label: I18n.t("composer.cancel"), diff --git a/app/assets/javascripts/admin/templates/api-keys-index.hbs b/app/assets/javascripts/admin/templates/api-keys-index.hbs new file mode 100644 index 0000000000..7943a5b9e2 --- /dev/null +++ b/app/assets/javascripts/admin/templates/api-keys-index.hbs @@ -0,0 +1,72 @@ +{{d-button + class="btn-primary" + action=(route-action "new") + icon="plus" + label="admin.api.new_key"}} + +{{#if model}} + + + + + + + + + + + {{#each model as |k|}} + + + + + + + + + {{/each}} + +
{{i18n "admin.api.key"}}{{i18n "admin.api.description"}}{{i18n "admin.api.user"}}{{i18n "admin.api.created"}}{{i18n "admin.api.last_used"}} 
+ {{#if k.revoked_at}}{{d-icon 'times-circle'}}{{/if}} + {{k.truncatedKey}} + + {{k.shortDescription}} + +
{{i18n 'admin.api.user'}}
+ {{#if k.user}} + {{#link-to "adminUser" k.user}} + {{avatar k.user imageSize="small"}} + {{/link-to}} + {{else}} + {{i18n "admin.api.all_users"}} + {{/if}} +
+
{{i18n 'admin.api.created'}}
+ {{format-date k.created_at}} +
+
{{i18n 'admin.api.last_used'}}
+ {{#if k.last_used_at}} + {{format-date k.last_used_at}} + {{else}} + {{i18n "admin.api.never_used"}} + {{/if}} +
+ {{d-button action=(route-action "show" k) icon="far-eye" title="admin.api.show_details"}} + {{#if k.revoked_at}} + {{d-button + action=(action "undoRevokeKey") + actionParam=k icon="undo" + title="admin.api.undo_revoke"}} + {{else}} + {{d-button + class="btn-danger" + action=(action "revokeKey") + actionParam=k + icon="times" + title="admin.api.revoke"}} + {{/if}} + +
+{{else}} +

{{i18n "admin.api.none"}}

+{{/if}} \ No newline at end of file diff --git a/app/assets/javascripts/admin/templates/api-keys-new.hbs b/app/assets/javascripts/admin/templates/api-keys-new.hbs new file mode 100644 index 0000000000..3dac84dae2 --- /dev/null +++ b/app/assets/javascripts/admin/templates/api-keys-new.hbs @@ -0,0 +1,38 @@ +{{#link-to 'adminApiKeys.index' class="go-back"}} + {{d-icon 'arrow-left'}} + {{i18n 'admin.api.all_api_keys'}} +{{/link-to}} + +
+ {{#if model.id}} + {{#admin-form-row label="admin.api.key"}} +
{{model.key}}
+ {{/admin-form-row}} + {{#admin-form-row}} + {{i18n "admin.api.not_shown_again"}} + {{/admin-form-row}} + {{#admin-form-row}} + {{d-button icon="angle-right" label="admin.api.continue" action=(action "continue") class="btn-primary"}} + {{/admin-form-row}} + {{else}} + {{#admin-form-row label="admin.api.description"}} + {{input value=model.description maxlength="255" placeholder=(i18n "admin.api.description_placeholder")}} + {{/admin-form-row}} + + {{#admin-form-row label="admin.api.user_mode"}} + {{combo-box content=userModes value=userMode onChange=(action "changeUserMode")}} + {{/admin-form-row}} + + {{#if showUserSelector}} + {{#admin-form-row label="admin.api.user"}} + {{user-selector single="true" + usernames=model.username + placeholderKey="admin.api.user_placeholder" + }} + {{/admin-form-row}} + {{/if}} + {{#admin-form-row}} + {{d-button icon="check" label="admin.api.save" action=(action "save") class="btn-primary" disabled=saveDisabled}} + {{/admin-form-row}} + {{/if}} +
diff --git a/app/assets/javascripts/admin/templates/api-keys-show.hbs b/app/assets/javascripts/admin/templates/api-keys-show.hbs new file mode 100644 index 0000000000..b96bc109b3 --- /dev/null +++ b/app/assets/javascripts/admin/templates/api-keys-show.hbs @@ -0,0 +1,82 @@ +{{#link-to 'adminApiKeys.index' class="go-back"}} + {{d-icon 'arrow-left'}} + {{i18n 'admin.api.all_api_keys'}} +{{/link-to}} + +
+ {{#admin-form-row label="admin.api.key"}} + {{#if model.revoked_at}}{{d-icon 'times-circle'}}{{/if}} + {{model.truncatedKey}} + {{/admin-form-row}} + + {{#admin-form-row label="admin.api.description"}} + {{#if editingDescription}} + {{input value=buffered.description maxlength="255" placeholder=(i18n "admin.api.description_placeholder")}} + {{else}} + + {{if model.description model.description (i18n "admin.api.no_description")}} + + {{/if}} + +
+ {{#if editingDescription}} + {{d-button class="ok" action=(action "saveDescription") icon="check"}} + {{d-button class="cancel" action=(action "editDescription") icon="times"}} + {{else}} + {{d-button class="btn-default" action=(action "editDescription") icon="pencil-alt"}} + {{/if}} +
+ {{/admin-form-row}} + + {{#admin-form-row label="admin.api.user"}} + {{#if model.user}} + {{#link-to "adminUser" model.user}} + {{avatar model.user imageSize="small"}} {{model.user.username}} + {{/link-to}} + {{else}} + {{i18n "admin.api.all_users"}} + {{/if}} + {{/admin-form-row}} + + {{#admin-form-row label="admin.api.created"}} + {{format-date model.created_at leaveAgo="true"}} + {{/admin-form-row}} + + {{#admin-form-row label="admin.api.updated"}} + {{format-date model.updated_at leaveAgo="true"}} + {{/admin-form-row}} + + {{#admin-form-row label="admin.api.last_used"}} + {{#if model.last_used_at}} + {{format-date model.last_used_at leaveAgo="true"}} + {{else}} + {{i18n "admin.api.never_used"}} + {{/if}} + {{/admin-form-row}} + + {{#admin-form-row label="admin.api.revoked"}} + {{#if model.revoked_at}} + {{format-date model.revoked_at leaveAgo="true"}} + {{/if}} +
+ {{#if model.revoked_at}} + {{d-button + action=(action "undoRevokeKey") + actionParam=model icon="undo" + label="admin.api.undo_revoke"}} + {{d-button + action=(action "deleteKey") + actionParam=model icon="trash-alt" + label="admin.api.delete" + class="btn-danger"}} + {{else}} + {{d-button + class="btn-danger" + action=(action "revokeKey") + actionParam=model + icon="times" + label="admin.api.revoke"}} + {{/if}} +
+ {{/admin-form-row}} +
diff --git a/app/assets/javascripts/admin/templates/api-keys.hbs b/app/assets/javascripts/admin/templates/api-keys.hbs deleted file mode 100644 index d631e4188d..0000000000 --- a/app/assets/javascripts/admin/templates/api-keys.hbs +++ /dev/null @@ -1,47 +0,0 @@ -{{#if model}} - - - - - - - - {{#each model as |k|}} - - - - - - {{/each}} - -
{{i18n "admin.api.key"}}{{i18n "admin.api.user"}} 
{{k.key}} - {{#if k.user}} - {{#link-to "adminUser" k.user}} - {{avatar k.user imageSize="small"}} - {{/link-to}} - {{else}} - {{i18n "admin.api.all_users"}} - {{/if}} - - {{d-button - class="btn-default" - action=(action "regenerateKey") - actionParam=k icon="undo" - label="admin.api.regenerate"}} - {{d-button - class="btn-default" - action=(action "revokeKey") - actionParam=k - icon="times" - label="admin.api.revoke"}} -
-{{else}} -

{{i18n "admin.api.none"}}

-{{/if}} - -{{#unless hasMasterKey}} - {{d-button - class="btn-primary" - action=(action "generateMasterKey") - icon="key"}} -{{/unless}} diff --git a/app/assets/javascripts/admin/templates/backups-index.hbs b/app/assets/javascripts/admin/templates/backups-index.hbs index ee7abc5f3a..7f0675e5a5 100644 --- a/app/assets/javascripts/admin/templates/backups-index.hbs +++ b/app/assets/javascripts/admin/templates/backups-index.hbs @@ -28,6 +28,11 @@ title="admin.backups.read_only.enable.title" label="admin.backups.read_only.enable.label"}} {{/if}} +
+ {{#if status.restoreDisabled}} + {{d-icon "info-circle"}} {{i18n "admin.backups.operations.restore.is_disabled"}} + {{/if}} +
diff --git a/app/assets/javascripts/admin/templates/backups.hbs b/app/assets/javascripts/admin/templates/backups.hbs index 38810c41ec..c8d72599e8 100644 --- a/app/assets/javascripts/admin/templates/backups.hbs +++ b/app/assets/javascripts/admin/templates/backups.hbs @@ -32,6 +32,8 @@ + {{plugin-outlet name="before-backup-list" tagName=""}} +
{{outlet}}
diff --git a/app/assets/javascripts/admin/templates/badges-award.hbs b/app/assets/javascripts/admin/templates/badges-award.hbs new file mode 100644 index 0000000000..b13462bf33 --- /dev/null +++ b/app/assets/javascripts/admin/templates/badges-award.hbs @@ -0,0 +1,39 @@ +{{#d-section class="award-badge"}} +

{{i18n 'admin.badges.mass_award.title'}}

+

{{i18n 'admin.badges.mass_award.description'}}

+ + {{#if model}} + +
+ {{#if model}} + {{icon-or-image model}} + {{model.name}} + {{else}} + {{I18n 'admin.badges.mass_award.no_badge_selected'}} + {{/if}} +
+
+

{{I18n 'admin.badges.mass_award.upload_csv'}}

+ +
+
+ +
+ {{d-button + class="btn-primary" + action=(action 'massAward') + type="submit" + disabled=saving + label="admin.badges.mass_award.perform"}} + {{#link-to 'adminBadges.index' class="btn btn-danger"}} + {{d-icon "times"}} + {{i18n 'cancel'}} + {{/link-to}} + + {{else}} + {{I18n 'admin.badges.mass_award.no_badge_selected'}} + {{/if}} +{{/d-section}} diff --git a/app/assets/javascripts/admin/templates/badges-show.hbs b/app/assets/javascripts/admin/templates/badges-show.hbs index eaaa9fcde1..f3935c4f2f 100644 --- a/app/assets/javascripts/admin/templates/badges-show.hbs +++ b/app/assets/javascripts/admin/templates/badges-show.hbs @@ -11,7 +11,15 @@
- {{input type="text" name="icon" value=buffered.icon}} + {{icon-picker + name="icon" + value=buffered.icon + options=(hash + maximum=1 + ) + onChange=(action (mut buffered.icon)) + }} +

{{i18n 'admin.badges.icon_help'}}

@@ -23,27 +31,35 @@
- {{combo-box name="badge_type_id" - value=buffered.badge_type_id - content=badgeTypes - allowInitialValueMutation=true - isDisabled=readOnly}} + {{combo-box + name="badge_type_id" + value=buffered.badge_type_id + content=badgeTypes + onChange=(action (mut buffered.badge_type_id)) + isDisabled=readOnly + }}
- {{combo-box name="badge_grouping_id" + {{combo-box + name="badge_grouping_id" value=buffered.badge_grouping_id content=badgeGroupings class="badge-selector" - nameProperty="name"}} - + nameProperty="name" + onChange=(action (mut buffered.badge_grouping_id)) + }} + {{d-button + class="btn-default" + action=(route-action "editGroupings") + icon="pencil-alt" + }}
-
{{#if buffered.system}} @@ -92,12 +108,13 @@
- {{combo-box name="trigger" - value=buffered.trigger - content=badgeTriggers - optionValuePath="content.id" - optionLabelPath="content.name" - disabled=readOnly}} + {{combo-box + name="trigger" + value=buffered.trigger + content=badgeTriggers + onChange=(action (mut buffered.trigger)) + disabled=readOnly + }}
{{/if}} {{/if}} @@ -138,10 +155,19 @@
- + {{d-button + class="btn-primary" + action=(action "save") + type="submit" + disabled=saving + label="admin.badges.save"}} {{savingStatus}} {{#unless readOnly}} - {{i18n 'admin.badges.delete'}} + {{d-button + action=(action "destroy") + class="btn-danger" + label="admin.badges.delete" + }} {{/unless}}
diff --git a/app/assets/javascripts/admin/templates/badges.hbs b/app/assets/javascripts/admin/templates/badges.hbs index 398a5c08fc..f5ff6c7a0d 100644 --- a/app/assets/javascripts/admin/templates/badges.hbs +++ b/app/assets/javascripts/admin/templates/badges.hbs @@ -6,13 +6,18 @@ {{d-icon "plus"}} {{i18n 'admin.badges.new'}} {{/link-to}} + + {{#link-to 'adminBadges.award' 'new' class="btn"}} + {{d-icon "upload"}} + {{i18n 'admin.badges.mass_award.title'}} + {{/link-to}}
{{outlet}} - \ No newline at end of file + diff --git a/app/assets/javascripts/admin/templates/components/admin-backups-logs.hbs b/app/assets/javascripts/admin/templates/components/admin-backups-logs.hbs new file mode 100644 index 0000000000..532185386b --- /dev/null +++ b/app/assets/javascripts/admin/templates/components/admin-backups-logs.hbs @@ -0,0 +1,8 @@ +{{#if hasFormattedLogs}} +
{{formattedLogs}}
+{{else}} +

{{noLogsMessage}}

+{{/if}} +{{#if showLoadingSpinner}} +
+{{/if}} diff --git a/app/assets/javascripts/admin/templates/components/admin-directory-toggle.hbs b/app/assets/javascripts/admin/templates/components/admin-directory-toggle.hbs new file mode 100644 index 0000000000..7ea697404f --- /dev/null +++ b/app/assets/javascripts/admin/templates/components/admin-directory-toggle.hbs @@ -0,0 +1 @@ +{{i18n this.i18nKey}}{{chevronIcon}} diff --git a/app/assets/javascripts/admin/templates/components/admin-editable-field.hbs b/app/assets/javascripts/admin/templates/components/admin-editable-field.hbs index 354707edb5..05250286a8 100644 --- a/app/assets/javascripts/admin/templates/components/admin-editable-field.hbs +++ b/app/assets/javascripts/admin/templates/components/admin-editable-field.hbs @@ -1,9 +1,11 @@
{{i18n name}}
{{#if editing}} - {{text-field value=buffer autofocus="autofocus"}} + {{text-field value=buffer autofocus="autofocus" autocomplete="discourse"}} {{else}} - {{value}} + + {{value}} + {{/if}}
diff --git a/app/assets/javascripts/admin/templates/components/admin-report.hbs b/app/assets/javascripts/admin/templates/components/admin-report.hbs index 7181ac3dd1..f8a36fa7d3 100644 --- a/app/assets/javascripts/admin/templates/components/admin-report.hbs +++ b/app/assets/javascripts/admin/templates/components/admin-report.hbs @@ -130,9 +130,10 @@
- {{date-picker-past - value=startDate - defaultDate=startDate}} + {{date-input + date=startDate + onChange=(action "onChangeStartDate") + }}
@@ -142,9 +143,10 @@
- {{date-picker-past - value=endDate - defaultDate=endDate}} + {{date-input + date=endDate + onChange=(action "onChangeEndDate") + }}
{{/if}} @@ -164,17 +166,15 @@ {{/each}} - {{#if showExport}} -
-
- {{d-button - class="btn-default export-csv-btn" - action=(action "exportCsv") - label="admin.export_csv.button_text" - icon="download"}} -
+
+
+ {{d-button + class="btn-default export-csv-btn" + action=(action "exportCsv") + label="admin.export_csv.button_text" + icon="download"}}
- {{/if}} +
{{#if showRefresh}}
diff --git a/app/assets/javascripts/admin/templates/components/admin-theme-editor.hbs b/app/assets/javascripts/admin/templates/components/admin-theme-editor.hbs index 049563bd55..bf9d2fd3cb 100644 --- a/app/assets/javascripts/admin/templates/components/admin-theme-editor.hbs +++ b/app/assets/javascripts/admin/templates/components/admin-theme-editor.hbs @@ -4,11 +4,11 @@ {{#each visibleTargets as |target|}}
  • {{#link-to editRouteName - theme.id - target.name - fieldName - replace=true - title=field.title + theme.id + target.name + fieldName + replace=true + title=field.title class=(if target.edited 'edited' 'blank') }} {{#if target.error}}{{d-icon 'exclamation-triangle'}}{{/if}} @@ -19,14 +19,15 @@ {{/link-to}}
  • {{/each}} - + {{#if allowAdvanced}}
  • - - {{d-icon (if showAdvanced "angle-double-left" "angle-double-right")}} + {{d-icon (if showAdvanced "angle-double-left" "angle-double-right")}}
  • {{/if}} @@ -48,17 +49,17 @@ {{#each visibleFields as |field|}}
  • {{#link-to editRouteName - theme.id - currentTargetName - field.name - replace=true - title=field.title + theme.id + currentTargetName + field.name + replace=true + title=field.title class=(if field.edited 'edited' 'blank') }} {{#if field.error}}{{d-icon 'exclamation-triangle'}}{{/if}} {{#if field.icon}}{{d-icon field.icon}}{{/if}} {{field.translatedName}} - + {{/link-to}}
  • {{/each}} @@ -66,11 +67,11 @@ {{#if showAddField}}
  • {{#if addingField}} - {{input type=text value=newFieldName enter=(action 'addField') escape-press=(action "cancelAddField")}} - {{d-button class="ok" action=(action "addField" newFieldName) icon="check"}} + {{input type=text value=newFieldName enter=(action 'addField') escape-press=(action "cancelAddField")}} + {{d-button class="ok" action=(action "addField" newFieldName) icon="check"}} {{d-button class="cancel" action=(action "cancelAddField") icon="times"}} {{else}} - + {{d-icon "plus"}} {{/if}} @@ -78,8 +79,8 @@ {{/if}}
  • - - {{d-icon maximizeIcon}} + + {{d-icon maximizeIcon}}
  • diff --git a/app/assets/javascripts/admin/templates/components/admin-user-field-item.hbs b/app/assets/javascripts/admin/templates/components/admin-user-field-item.hbs index a23d21f6e9..269d6babc2 100644 --- a/app/assets/javascripts/admin/templates/components/admin-user-field-item.hbs +++ b/app/assets/javascripts/admin/templates/components/admin-user-field-item.hbs @@ -1,6 +1,10 @@ {{#if editing}} {{#admin-form-row label="admin.user_fields.type"}} - {{combo-box content=fieldTypes value=buffered.field_type}} + {{combo-box + content=fieldTypes + value=buffered.field_type + onChange=(action (mut buffered.field_type)) + }} {{/admin-form-row}} {{#admin-form-row label="admin.user_fields.name"}} @@ -41,7 +45,7 @@
    {{userField.name}} -
    +
    {{{userField.description}}}
    {{fieldName}}
    diff --git a/app/assets/javascripts/admin/templates/components/admin-watched-word.hbs b/app/assets/javascripts/admin/templates/components/admin-watched-word.hbs new file mode 100644 index 0000000000..c9e4fd9b04 --- /dev/null +++ b/app/assets/javascripts/admin/templates/components/admin-watched-word.hbs @@ -0,0 +1 @@ +{{xIcon}}{{watchedWord}} diff --git a/app/assets/javascripts/admin/templates/components/admin-web-hook-status.hbs b/app/assets/javascripts/admin/templates/components/admin-web-hook-status.hbs new file mode 100644 index 0000000000..7aa2d1455f --- /dev/null +++ b/app/assets/javascripts/admin/templates/components/admin-web-hook-status.hbs @@ -0,0 +1 @@ +{{circleIcon}} {{deliveryStatus}} diff --git a/app/assets/javascripts/admin/templates/components/email-styles-editor.hbs b/app/assets/javascripts/admin/templates/components/email-styles-editor.hbs new file mode 100644 index 0000000000..e03feb7cb7 --- /dev/null +++ b/app/assets/javascripts/admin/templates/components/email-styles-editor.hbs @@ -0,0 +1,20 @@ +
    +
    + +
    +
    + +{{ace-editor content=editorContents mode=currentEditorMode editorId=editorId}} + + diff --git a/app/assets/javascripts/admin/templates/components/embeddable-host.hbs b/app/assets/javascripts/admin/templates/components/embeddable-host.hbs index 559544abce..b7833d621d 100644 --- a/app/assets/javascripts/admin/templates/components/embeddable-host.hbs +++ b/app/assets/javascripts/admin/templates/components/embeddable-host.hbs @@ -13,7 +13,11 @@
    diff --git a/app/assets/javascripts/admin/templates/customize-email-style-edit.hbs b/app/assets/javascripts/admin/templates/customize-email-style-edit.hbs new file mode 100644 index 0000000000..1e68fd0c1a --- /dev/null +++ b/app/assets/javascripts/admin/templates/customize-email-style-edit.hbs @@ -0,0 +1,9 @@ +{{email-styles-editor styles=model fieldName=fieldName}} + + diff --git a/app/assets/javascripts/admin/templates/customize-email-style.hbs b/app/assets/javascripts/admin/templates/customize-email-style.hbs new file mode 100644 index 0000000000..d46e732603 --- /dev/null +++ b/app/assets/javascripts/admin/templates/customize-email-style.hbs @@ -0,0 +1,7 @@ +
    +

    {{i18n 'admin.customize.email_style.heading'}}

    + +

    {{i18n 'admin.customize.email_style.instructions'}}

    +
    + +{{outlet}} diff --git a/app/assets/javascripts/admin/templates/customize-email-templates-edit.hbs b/app/assets/javascripts/admin/templates/customize-email-templates-edit.hbs index a1e3910e11..4b80de8718 100644 --- a/app/assets/javascripts/admin/templates/customize-email-templates-edit.hbs +++ b/app/assets/javascripts/admin/templates/customize-email-templates-edit.hbs @@ -10,7 +10,7 @@ {{d-editor value=buffered.body}} - {{#save-controls model=emailTemplate action=(action "saveChanges") saved=saved}} + {{#save-controls model=emailTemplate action=(action "saveChanges") saved=saved saveDisabled=saveDisabled}} {{#if emailTemplate.can_revert}} {{d-button action=(action "revertChanges") label="admin.customize.email_templates.revert"}} {{/if}} diff --git a/app/assets/javascripts/admin/templates/customize-email-templates.hbs b/app/assets/javascripts/admin/templates/customize-email-templates.hbs index 7dd95b092f..55434c9e46 100644 --- a/app/assets/javascripts/admin/templates/customize-email-templates.hbs +++ b/app/assets/javascripts/admin/templates/customize-email-templates.hbs @@ -1,15 +1,8 @@ -
    -
    -
      - {{#each sortedTemplates as |et|}} -
    • - {{#link-to 'adminCustomizeEmailTemplates.edit' et}}{{et.title}}{{/link-to}} -
    • - {{/each}} -
    -
    +{{combo-box + content=sortedTemplates + valueProperty="id" + nameProperty="title" + onChange=(action "selectTemplate") +}} -
    - {{outlet}} -
    -
    +{{outlet}} diff --git a/app/assets/javascripts/admin/templates/customize-robots-txt.hbs b/app/assets/javascripts/admin/templates/customize-robots-txt.hbs new file mode 100644 index 0000000000..b556f0c537 --- /dev/null +++ b/app/assets/javascripts/admin/templates/customize-robots-txt.hbs @@ -0,0 +1,20 @@ +
    +

    {{i18n "admin.customize.robots.title"}}

    +

    {{i18n "admin.customize.robots.warning"}}

    + {{#if model.overridden}} +
    + {{i18n "admin.customize.robots.overridden"}} +
    + {{/if}} + {{textarea + value=buffered.robots_txt + class="robots-txt-input"}} + {{#save-controls model=this action=(action "save") saved=saved saveDisabled=saveDisabled}} + {{d-button + class="btn-default" + disabled=resetDisbaled + icon="undo" + action=(action "reset") + label="admin.settings.reset"}} + {{/save-controls}} +
    diff --git a/app/assets/javascripts/admin/templates/customize-themes-show.hbs b/app/assets/javascripts/admin/templates/customize-themes-show.hbs index e4796c8cdc..de6b46da74 100644 --- a/app/assets/javascripts/admin/templates/customize-themes-show.hbs +++ b/app/assets/javascripts/admin/templates/customize-themes-show.hbs @@ -3,9 +3,13 @@ {{#if editingName}} {{text-field value=model.name autofocus="true"}} {{d-button action=(action "finishedEditingName") class="btn-primary submit-edit" icon="check"}} - {{d-button action=(action "cancelEditingName") class="btn-default cancel-edit" icon="times"}} + {{d-button action=(action "cancelEditingName") class="cancel-edit" icon="times"}} {{else}} - {{model.name}} {{d-icon "pencil-alt"}} + {{model.name}} + {{d-button + action=(action "startEditingName") + icon="pencil-alt" + }} {{/if}} @@ -16,7 +20,7 @@ {{/each}} - {{#unless model.enabled}} + {{#unless model.supported}}
    {{i18n "admin.customize.theme.required_version.error"}} {{#if model.remote_theme.minimum_discourse_version}} @@ -28,6 +32,26 @@
    {{/unless}} + {{#unless model.enabled}} +
    + {{#if model.disabled_by}} + {{i18n "admin.customize.theme.disabled_by"}} + {{#user-link user=model.disabled_by}} + {{avatar model.disabled_by imageSize="tiny"}} + {{model.disabled_by.username}} + {{/user-link}} + {{format-date model.disabled_at leaveAgo="true"}} + {{else}} + {{i18n "admin.customize.theme.disabled"}} + {{/if}} + {{d-button + class='btn-default' + action=(action "enableComponent") + icon="check" + label="admin.customize.theme.enable"}} +
    + {{/unless}} + {{#unless model.component}}
    {{inline-edit-checkbox action=(action "applyDefault") labelKey="admin.customize.theme.is_default" checked=model.default}} @@ -39,16 +63,16 @@ {{#if model.remote_theme.remote_url}} {{#if sourceIsHttp}} - {{i18n "admin.customize.theme.source_url"}} {{d-icon "link"}} + {{i18n "admin.customize.theme.source_url"}}{{d-icon "link"}} {{else}}
    {{model.remote_theme.remote_url}}
    {{/if}} {{/if}} {{#if model.remote_theme.about_url}} - {{i18n "admin.customize.theme.about_theme"}} {{d-icon "link"}} + {{i18n "admin.customize.theme.about_theme"}}{{d-icon "link"}} {{/if}} {{#if model.remote_theme.license_url}} - {{i18n "admin.customize.theme.license"}} {{d-icon "link"}} + {{i18n "admin.customize.theme.license"}}{{d-icon "link"}} {{/if}} {{#if model.description}} @@ -79,23 +103,23 @@ {{/if}} - {{#if updatingRemote}} - {{i18n 'admin.customize.theme.updating'}} - {{else}} - {{#if model.remote_theme.commits_behind}} - {{i18n 'admin.customize.theme.commits_behind' count=model.remote_theme.commits_behind}} - {{#if model.remote_theme.github_diff_link}} - - {{i18n 'admin.customize.theme.compare_commits'}} - - {{/if}} - {{else}} - {{#unless showRemoteError}} - {{i18n 'admin.customize.theme.up_to_date'}} {{format-date model.remote_theme.updated_at leaveAgo="true"}} - {{/unless}} + {{#if updatingRemote}} + {{i18n 'admin.customize.theme.updating'}} + {{else}} + {{#if model.remote_theme.commits_behind}} + {{i18n 'admin.customize.theme.commits_behind' count=model.remote_theme.commits_behind}} + {{#if model.remote_theme.github_diff_link}} + + {{i18n 'admin.customize.theme.compare_commits'}} + {{/if}} + {{else}} + {{#unless showRemoteError}} + {{i18n 'admin.customize.theme.up_to_date'}} {{format-date model.remote_theme.updated_at leaveAgo="true"}} + {{/unless}} {{/if}} - + {{/if}} + {{else}} {{d-icon "info-circle"}} {{i18n "admin.customize.theme.imported_from_archive"}} @@ -105,24 +129,29 @@ {{/if}} {{#unless model.component}} -
    -
    {{i18n "admin.customize.theme.color_scheme"}}
    -
    {{i18n "admin.customize.theme.color_scheme_select"}}
    -
    - {{color-palettes - content=colorSchemes - filterable=true - forceEscape=true - value=colorSchemeId - icon="paint-brush"}} + {{#d-section class="form-horizontal theme settings"}} +
    +
    + {{i18n "admin.customize.theme.color_scheme"}} +
    +
    + {{color-palettes + content=colorSchemes + filterable=true + forceEscape=true + value=colorSchemeId + icon="paint-brush"}} - {{#if colorSchemeChanged}} - {{d-button action=(action "changeScheme") class="btn-primary submit-edit" icon="check"}} - {{d-button action=(action "cancelChangeScheme") class="btn-default cancel-edit" icon="times"}} - {{/if}} +
    {{i18n "admin.customize.theme.color_scheme_select"}}
    +
    +
    + {{#if colorSchemeChanged}} + {{d-button action=(action "changeScheme") class="ok submit-edit" icon="check"}} + {{d-button action=(action "cancelChangeScheme") class="cancel cancel-edit" icon="times"}} + {{/if}} +
    - {{#link-to 'adminCustomize.colors' class="btn btn-default edit"}}{{i18n 'admin.customize.colors.edit'}}{{/link-to}} -
    + {{/d-section}} {{/unless}} {{#if parentThemes}} @@ -136,6 +165,20 @@
    {{/if}} + {{#if model.component}} + {{#d-section class="form-horizontal theme settings"}} +
    + {{theme-setting-relatives-selector setting=relativesSelectorSettingsForComponent model=model class="theme-setting"}} +
    + {{/d-section}} + {{else}} + {{#d-section class="form-horizontal theme settings"}} +
    + {{theme-setting-relatives-selector setting=relativesSelectorSettingsForTheme model=model class="theme-setting"}} +
    + {{/d-section}} + {{/if}} +
    {{i18n "admin.customize.theme.css_html"}}
    {{#if model.hasEditedFields}} @@ -151,7 +194,10 @@
    {{/if}} - {{#d-button action=(action "editTheme") class="btn btn-default edit"}}{{i18n 'admin.customize.theme.edit_css_html'}}{{/d-button}} + {{d-button + class="btn-default edit" + action=(action "editTheme") + label="admin.customize.theme.edit_css_html"}}
    @@ -159,12 +205,12 @@ {{#if model.uploads}} {{else}} @@ -195,31 +241,26 @@
    {{/if}} - {{#if availableChildThemes}} -
    -
    - {{d-icon "puzzle-piece"}} - {{i18n "admin.customize.theme.theme_components"}} -
    - {{#if model.childThemes.length}} - - {{/if}} - {{#if selectableChildThemes}} -
    - {{combo-box forceEscape=true filterable=true content=selectableChildThemes value=selectedChildThemeId none="admin.customize.theme.select_component"}} - {{#d-button action=(action "addChildTheme") icon="plus" disabled=addButtonDisabled class="btn-default add-component-button"}}{{i18n "admin.customize.theme.add"}}{{/d-button}} -
    - {{/if}} -
    - {{/if}} - {{d-icon 'desktop'}}{{i18n 'admin.customize.theme.preview'}}{{d-icon "download"}} {{i18n 'admin.export_json.button_text'}} {{d-button action=(action "switchType") label="admin.customize.theme.convert" icon=convertIcon class="btn-default btn-normal" title=convertTooltip}} + + {{#if model.component}} + {{#if model.enabled}} + {{d-button + class='btn-default' + action=(action "disableComponent") + icon="ban" + label="admin.customize.theme.disable"}} + {{else}} + {{d-button + class='btn-default' + action=(action "enableComponent") + icon="check" + label="admin.customize.theme.enable"}} + {{/if}} + {{/if}} + {{d-button action=(action "destroy") label="admin.customize.delete" icon="trash-alt" class="btn-danger"}} diff --git a/app/assets/javascripts/admin/templates/customize.hbs b/app/assets/javascripts/admin/templates/customize.hbs index d5d3816310..768eaf29a2 100644 --- a/app/assets/javascripts/admin/templates/customize.hbs +++ b/app/assets/javascripts/admin/templates/customize.hbs @@ -3,6 +3,7 @@ {{nav-item route='adminCustomize.colors' label='admin.customize.colors.title'}} {{nav-item route='adminSiteText' label='admin.site_text.title'}} {{nav-item route='adminCustomizeEmailTemplates' label='admin.customize.email_templates.title'}} + {{nav-item route='adminCustomizeEmailStyle' label='admin.customize.email_style.title'}} {{nav-item route='adminUserFields' label='admin.user_fields.title'}} {{nav-item route='adminEmojis' label='admin.emoji.title'}} {{nav-item route='adminPermalinks' label='admin.permalink.title'}} diff --git a/app/assets/javascripts/admin/templates/dashboard_general.hbs b/app/assets/javascripts/admin/templates/dashboard_general.hbs index 2feaefe97d..af0bb36ae9 100644 --- a/app/assets/javascripts/admin/templates/dashboard_general.hbs +++ b/app/assets/javascripts/admin/templates/dashboard_general.hbs @@ -117,13 +117,15 @@

    {{i18n "admin.dashboard.last_updated"}}

    {{format-date model.attributes.updated_at leaveAgo="true"}}

    -
    -

    {{i18n "admin.dashboard.discourse_last_updated"}}

    -

    {{format-date model.attributes.discourse_updated_at leaveAgo="true"}}

    - - {{i18n "admin.dashboard.whats_new_in_discourse"}} - -
    + {{#if model.attributes.discourse_updated_at}} +
    +

    {{i18n "admin.dashboard.discourse_last_updated"}}

    +

    {{format-date model.attributes.discourse_updated_at leaveAgo="true"}}

    + + {{i18n "admin.dashboard.whats_new_in_discourse"}} + +
    + {{/if}} diff --git a/app/assets/javascripts/admin/templates/email-advanced-test.hbs b/app/assets/javascripts/admin/templates/email-advanced-test.hbs index c0b8ee7a27..97a479e2c5 100644 --- a/app/assets/javascripts/admin/templates/email-advanced-test.hbs +++ b/app/assets/javascripts/admin/templates/email-advanced-test.hbs @@ -3,19 +3,21 @@
    {{textarea name="email" value=email class="email-body"}} - + {{d-button + action=(action "run") + label="admin.email.advanced_test.run"}}
    {{#conditional-loading-spinner condition=loading}} {{#if format}} -
    +

    {{i18n 'admin.email.advanced_test.text'}}

    {{{text}}}
    -
    +

    {{i18n 'admin.email.advanced_test.elided'}}

    {{{elided}}}
    diff --git a/app/assets/javascripts/admin/templates/email-bounced.hbs b/app/assets/javascripts/admin/templates/email-bounced.hbs index e8c7f9007f..0387050d8f 100644 --- a/app/assets/javascripts/admin/templates/email-bounced.hbs +++ b/app/assets/javascripts/admin/templates/email-bounced.hbs @@ -8,38 +8,38 @@
    - - - - - - - - - {{#each model as |l|}} - - - - - {{#if l.has_bounce_key}} - - {{else}} - - {{/if}} + + + + + + - {{else}} - {{#unless loading}} - - {{/unless}} - {{/each}} + {{#each model as |l|}} + + + + + {{#if l.has_bounce_key}} + + {{else}} + + {{/if}} + + {{else}} + {{#unless loading}} + + {{/unless}} + {{/each}} +
    {{i18n "admin.embedding.category"}}
    - {{category-chooser value=categoryId class="small"}} + {{category-chooser + value=categoryId + class="small" + onChange=(action (mut categoryId)) + }}
    {{d-button icon="check" action=(action "save") class="btn-primary" disabled=cantSave}} diff --git a/app/assets/javascripts/admin/templates/components/penalty-post-action.hbs b/app/assets/javascripts/admin/templates/components/penalty-post-action.hbs index 1c8ffe56d8..1a672329cd 100644 --- a/app/assets/javascripts/admin/templates/components/penalty-post-action.hbs +++ b/app/assets/javascripts/admin/templates/components/penalty-post-action.hbs @@ -4,7 +4,11 @@ {{{i18n 'admin.user.penalty_post_actions'}}} - {{combo-box value=postAction content=penaltyActions onSelect=(action "penaltyChanged")}} + {{combo-box + value=postAction + content=penaltyActions + onChange=(action "penaltyChanged") + }} {{#if editing}} diff --git a/app/assets/javascripts/admin/templates/components/permalink-form.hbs b/app/assets/javascripts/admin/templates/components/permalink-form.hbs index 8efdbbf581..1f5d1c06f2 100644 --- a/app/assets/javascripts/admin/templates/components/permalink-form.hbs +++ b/app/assets/javascripts/admin/templates/components/permalink-form.hbs @@ -8,7 +8,11 @@ autocorrect="off" autocapitalize="off"}} -{{combo-box content=permalinkTypes value=permalinkType}} +{{combo-box + content=permalinkTypes + value=permalinkType + onChange=(action (mut permalinkType)) +}} {{text-field value=permalink_type_value diff --git a/app/assets/javascripts/admin/templates/components/report-filters/category.hbs b/app/assets/javascripts/admin/templates/components/report-filters/category.hbs index 94aa60f7d0..31110bcbbd 100644 --- a/app/assets/javascripts/admin/templates/components/report-filters/category.hbs +++ b/app/assets/javascripts/admin/templates/components/report-filters/category.hbs @@ -1,7 +1,7 @@ {{search-advanced-category-chooser - filterable=true value=category - castInteger=true - onSelectNone=(action "onChange") - onDeselect=(action "onDeselect") - onSelect=(action "onChange")}} + onChange=(action "onChange") + options=(hash + filterable=true + ) +}} diff --git a/app/assets/javascripts/admin/templates/components/report-filters/file-extension.hbs b/app/assets/javascripts/admin/templates/components/report-filters/file-extension.hbs index 871179d63d..88d71d0747 100644 --- a/app/assets/javascripts/admin/templates/components/report-filters/file-extension.hbs +++ b/app/assets/javascripts/admin/templates/components/report-filters/file-extension.hbs @@ -4,5 +4,5 @@ allowAny=filter.allow_any value=filter.default none="admin.dashboard.report_filter_any" - onSelectNone=(action "onChange") - onSelect=(action "onChange")}} + onChange=(action "onChange") +}} diff --git a/app/assets/javascripts/admin/templates/components/report-filters/group.hbs b/app/assets/javascripts/admin/templates/components/report-filters/group.hbs index 0209c1cb6e..9e17bda2ac 100644 --- a/app/assets/javascripts/admin/templates/components/report-filters/group.hbs +++ b/app/assets/javascripts/admin/templates/components/report-filters/group.hbs @@ -1,10 +1,9 @@ {{combo-box - castInteger=true filterable=true - valueAttribute="value" + valueProperty="value" content=groupOptions value=groupId allowAny=filter.allow_any none="admin.dashboard.reports.groups" - onSelectNone=(action "onChange") - onSelect=(action "onChange")}} + onChange=(action "onChange") +}} diff --git a/app/assets/javascripts/admin/templates/components/resumable-upload.hbs b/app/assets/javascripts/admin/templates/components/resumable-upload.hbs new file mode 100644 index 0000000000..dfa89864b7 --- /dev/null +++ b/app/assets/javascripts/admin/templates/components/resumable-upload.hbs @@ -0,0 +1,3 @@ +{{uploadingIcon}} +{{text}} + diff --git a/app/assets/javascripts/admin/templates/components/screened-ip-address-form.hbs b/app/assets/javascripts/admin/templates/components/screened-ip-address-form.hbs index 6bc99fdbd1..2be732079e 100644 --- a/app/assets/javascripts/admin/templates/components/screened-ip-address-form.hbs +++ b/app/assets/javascripts/admin/templates/components/screened-ip-address-form.hbs @@ -1,4 +1,10 @@ {{i18n 'admin.logs.screened_ips.form.label'}} {{text-field value=ip_address disabled=formSubmitted class="ip-address-input" placeholderKey="admin.logs.screened_ips.form.ip_address" autocorrect="off" autocapitalize="off"}} -{{combo-box content=actionNames value=actionName}} + +{{combo-box + content=actionNames + value=actionName + onChange=(action (mut actionName)) +}} + {{d-button class="btn-default" action=(action "submit") disabled=formSubmitted label="admin.logs.screened_ips.form.add"}} diff --git a/app/assets/javascripts/admin/templates/components/site-customization-change-field.hbs b/app/assets/javascripts/admin/templates/components/site-customization-change-field.hbs index 43aea18163..65fb3b3b66 100644 --- a/app/assets/javascripts/admin/templates/components/site-customization-change-field.hbs +++ b/app/assets/javascripts/admin/templates/components/site-customization-change-field.hbs @@ -1,7 +1,7 @@ {{#if field}}
    {{i18n name}}: ({{i18n 'character_count' count=field.length}}) -
    +
    {{textarea value=field class="plain"}}
    {{/if}} diff --git a/app/assets/javascripts/admin/templates/components/site-setting.hbs b/app/assets/javascripts/admin/templates/components/site-setting.hbs index 50fb7d413e..649df86a4f 100644 --- a/app/assets/javascripts/admin/templates/components/site-setting.hbs +++ b/app/assets/javascripts/admin/templates/components/site-setting.hbs @@ -1,12 +1,15 @@

    {{unbound settingName}}

    + {{#if defaultIsAvailable}} + {{setting.setDefaultValuesLabel}} + {{/if}}
    - {{component componentName setting=setting value=buffered.value validationMessage=validationMessage preview=preview isSecret=isSecret}} + {{component componentName setting=setting value=buffered.value validationMessage=validationMessage preview=preview isSecret=isSecret allowAny=allowAny}}
    {{#if dirty}}
    - {{d-button class="ok" action=(action "save") icon="check"}} + {{d-button class="ok" action=(action "update") icon="check"}} {{d-button class="cancel" action=(action "cancel") icon="times"}}
    {{else if setting.overridden}} diff --git a/app/assets/javascripts/admin/templates/components/site-settings/category-list.hbs b/app/assets/javascripts/admin/templates/components/site-settings/category-list.hbs index 0aadec18d8..14d7210604 100644 --- a/app/assets/javascripts/admin/templates/components/site-settings/category-list.hbs +++ b/app/assets/javascripts/admin/templates/components/site-settings/category-list.hbs @@ -1,3 +1,7 @@ -{{category-selector categories=selectedCategories}} +{{category-selector + categories=selectedCategories + onChange=(action "onChangeSelectedCategories") +}} +
    {{{unbound setting.description}}}
    {{setting-validation-message message=validationMessage}} diff --git a/app/assets/javascripts/admin/templates/components/site-settings/category.hbs b/app/assets/javascripts/admin/templates/components/site-settings/category.hbs index e3ab41137c..d6b6bee565 100644 --- a/app/assets/javascripts/admin/templates/components/site-settings/category.hbs +++ b/app/assets/javascripts/admin/templates/components/site-settings/category.hbs @@ -1,3 +1,7 @@ -{{category-chooser value=value allowUncategorized="true"}} +{{category-chooser + value=value + allowUncategorized=true + onChange=(action (mut value)) +}} {{setting-validation-message message=validationMessage}}
    {{{unbound setting.description}}}
    diff --git a/app/assets/javascripts/admin/templates/components/site-settings/compact-list.hbs b/app/assets/javascripts/admin/templates/components/site-settings/compact-list.hbs index e741bea5ed..86fce97f15 100644 --- a/app/assets/javascripts/admin/templates/components/site-settings/compact-list.hbs +++ b/app/assets/javascripts/admin/templates/components/site-settings/compact-list.hbs @@ -1,3 +1,11 @@ -{{list-setting settingValue=value choices=setting.choices settingName=setting.setting}} +{{list-setting + value=settingValue + settingName=setting.setting + allowAny=allowAny + choices=settingChoices + onChange=(action "onChangeListSetting") + onChangeChoices=(action "onChangeChoices") +}} + {{setting-validation-message message=validationMessage}}
    {{{unbound setting.description}}}
    diff --git a/app/assets/javascripts/admin/templates/components/site-settings/enum.hbs b/app/assets/javascripts/admin/templates/components/site-settings/enum.hbs index 9b3da3178e..86faf57d75 100644 --- a/app/assets/javascripts/admin/templates/components/site-settings/enum.hbs +++ b/app/assets/javascripts/admin/templates/components/site-settings/enum.hbs @@ -1,4 +1,19 @@ -{{combo-box castInteger=true valueAttribute="value" content=setting.validValues value=value none=setting.allowsNone}} +{{combo-box + content=setting.validValues + value=value + onChange=(action (mut value)) + valueProperty=setting.computedValueProperty + nameProperty=setting.computedNameProperty + options=(hash + castInteger=true + allowAny=setting.allowsNone + ) +}} + {{preview}} + {{setting-validation-message message=validationMessage}} -
    {{{unbound setting.description}}}
    + +
    + {{{unbound setting.description}}} +
    diff --git a/app/assets/javascripts/admin/templates/components/site-settings/group-list.hbs b/app/assets/javascripts/admin/templates/components/site-settings/group-list.hbs index adb6ec5098..768311c3f9 100644 --- a/app/assets/javascripts/admin/templates/components/site-settings/group-list.hbs +++ b/app/assets/javascripts/admin/templates/components/site-settings/group-list.hbs @@ -1,3 +1,10 @@ -{{list-setting settingValue=value choices=groupChoices settingName=setting.setting}} +{{list-setting + value=settingValue + choices=groupChoices + settingName="name" + nameProperty=nameProperty + valueProperty=valueProperty + onChange=(action "onChangeGroupListSetting") +}} {{setting-validation-message message=validationMessage}}
    {{{unbound setting.description}}}
    diff --git a/app/assets/javascripts/admin/templates/components/site-settings/tag-list.hbs b/app/assets/javascripts/admin/templates/components/site-settings/tag-list.hbs new file mode 100644 index 0000000000..84c6623cfb --- /dev/null +++ b/app/assets/javascripts/admin/templates/components/site-settings/tag-list.hbs @@ -0,0 +1,3 @@ +{{tag-chooser tags=selectedTags}} +
    {{{unbound setting.description}}}
    +{{setting-validation-message message=validationMessage}} diff --git a/app/assets/javascripts/admin/templates/components/tags-uploader.hbs b/app/assets/javascripts/admin/templates/components/tags-uploader.hbs index db3c9aa301..62e353810f 100644 --- a/app/assets/javascripts/admin/templates/components/tags-uploader.hbs +++ b/app/assets/javascripts/admin/templates/components/tags-uploader.hbs @@ -1,6 +1,6 @@ {{i18n 'tagging.upload_instructions'}} diff --git a/app/assets/javascripts/admin/templates/components/tap-tile-grid.hbs b/app/assets/javascripts/admin/templates/components/tap-tile-grid.hbs new file mode 100644 index 0000000000..7807cc0567 --- /dev/null +++ b/app/assets/javascripts/admin/templates/components/tap-tile-grid.hbs @@ -0,0 +1 @@ +{{ yield (hash activeTile=this.activeTile) }} diff --git a/app/assets/javascripts/admin/templates/components/tap-tile.hbs b/app/assets/javascripts/admin/templates/components/tap-tile.hbs new file mode 100644 index 0000000000..4e04200945 --- /dev/null +++ b/app/assets/javascripts/admin/templates/components/tap-tile.hbs @@ -0,0 +1,2 @@ +{{d-icon icon}} +{{text}} diff --git a/app/assets/javascripts/admin/templates/components/themes-list-item.hbs b/app/assets/javascripts/admin/templates/components/themes-list-item.hbs index 663488607f..363ca394ec 100644 --- a/app/assets/javascripts/admin/templates/components/themes-list-item.hbs +++ b/app/assets/javascripts/admin/templates/components/themes-list-item.hbs @@ -17,6 +17,9 @@ {{#if theme.isBroken}} {{d-icon "exclamation-circle" class="broken-indicator" title="admin.customize.theme.broken_theme_tooltip"}} {{/if}} + {{#unless theme.enabled}} + {{d-icon "ban" class="light-grey-icon" title="admin.customize.theme.disabled_component_tooltip"}} + {{/unless}} {{else}} {{d-icon "caret-right"}} {{/unless}} @@ -25,16 +28,16 @@ {{#if displayComponents}} {{/if}} diff --git a/app/assets/javascripts/admin/templates/components/themes-list.hbs b/app/assets/javascripts/admin/templates/components/themes-list.hbs index 1f0c7fc2d5..29935b95b8 100644 --- a/app/assets/javascripts/admin/templates/components/themes-list.hbs +++ b/app/assets/javascripts/admin/templates/components/themes-list.hbs @@ -1,10 +1,17 @@
    -
    - {{I18n "admin.customize.theme.title"}} -
    - {{d-icon "puzzle-piece"}} - {{I18n "admin.customize.theme.components"}} -
    + {{d-button + action=(action "changeView") + actionParam=THEMES + class=(concat "themes-tab " "tab " (if themesTabActive 'btn-danger active' '')) + label="admin.customize.theme.title" + }} + {{d-button + action=(action "changeView") + actionParam=COMPONENTS + class=(concat "components-tab " "tab " (if componentsTabActive 'btn-danger active' '')) + label="admin.customize.theme.components" + icon="puzzle-piece" + }}
    diff --git a/app/assets/javascripts/admin/templates/components/value-list.hbs b/app/assets/javascripts/admin/templates/components/value-list.hbs index 372de2ac1d..a5ade53ee0 100644 --- a/app/assets/javascripts/admin/templates/components/value-list.hbs +++ b/app/assets/javascripts/admin/templates/components/value-list.hbs @@ -2,12 +2,19 @@
    {{#each collection as |value index|}}
    - {{d-button action=(action "removeValue") - actionParam=value - icon="times" - class="btn-default remove-value-btn btn-small"}} + {{d-button + action=(action "removeValue") + actionParam=value + icon="times" + class="remove-value-btn btn-small" + }} - {{input title=value value=value class="value-input" focus-out=(action "changeValue" index)}} + {{input + title=value + value=value + class="value-input" + focus-out=(action "changeValue" index) + }}
    {{/each}}
    @@ -15,7 +22,10 @@ {{combo-box allowAny=true - allowContentReplacement=true none=noneKey + valueProperty=null + nameProperty=null + value=newValue content=filteredChoices - onSelect=(action "selectChoice")}} + onChange=(action "selectChoice") +}} diff --git a/app/assets/javascripts/admin/templates/components/watched-word-uploader.hbs b/app/assets/javascripts/admin/templates/components/watched-word-uploader.hbs index d7c7f2a572..3b9518186a 100644 --- a/app/assets/javascripts/admin/templates/components/watched-word-uploader.hbs +++ b/app/assets/javascripts/admin/templates/components/watched-word-uploader.hbs @@ -1,7 +1,6 @@ -
    -One word per line +{{i18n 'admin.watched_words.one_word_per_line'}} diff --git a/app/assets/javascripts/admin/templates/customize-colors-show.hbs b/app/assets/javascripts/admin/templates/customize-colors-show.hbs index fd44f997f6..ca912cfc33 100644 --- a/app/assets/javascripts/admin/templates/customize-colors-show.hbs +++ b/app/assets/javascripts/admin/templates/customize-colors-show.hbs @@ -3,20 +3,36 @@

    {{#if model.theme_id}}{{model.name}}{{else}}{{text-field class="style-name" value=model.name}}{{/if}}

    {{#unless model.theme_id}} - + {{d-button + class="btn-primary" + action=(action "save") + disabled=model.disableSave + label="admin.customize.save"}} {{/unless}} - - + {{d-button + class="btn-default" + action=(action "copy" model) + icon="copy" + label="admin.customize.copy"}} + {{d-button + class="btn-default" + action=(action "copyToClipboard" model) + icon="far-clipboard" + label="admin.customize.copy_to_clipboard"}} {{#if model.theme_id}} {{i18n "admin.customize.theme_owner"}} {{#link-to "adminCustomizeThemes.show" model.theme_id}}{{model.theme_name}}{{/link-to}} {{else}} - + {{d-button + action=(action "destroy") + class="btn-danger" + icon="far-trash-alt" + label="admin.customize.delete"}} {{/if}} {{model.savingStatus}}
    -
    +
    - {{c.translatedName}} -
    - {{c.description}} +

    {{c.translatedName}}

    +

    {{c.description}}

    {{color-input hexValue=c.hex brightnessValue=c.brightness valid=c.valid}} {{#unless model.theme_id}} - - + {{d-button + class=(concat "btn-default revert " (unless c.savedIsOverriden "invisible")) + action=(action "revert" c) + title="admin.customize.colors.revert_title" + label="revert"}} + {{d-button + class=(concat "btn-default undo " (unless c.changed "invisible")) + action=(action "undo" c) + title="admin.customize.colors.undo_title" + label="undo"}} {{/unless}}
    {{i18n 'admin.email.email_type'}}
    {{i18n 'admin.email.logs.filters.title'}}{{text-field value=filter.user placeholderKey="admin.email.logs.filters.user_placeholder"}}{{text-field value=filter.address placeholderKey="admin.email.logs.filters.address_placeholder"}}{{text-field value=filter.type placeholderKey="admin.email.logs.filters.type_placeholder"}}
    {{format-date l.created_at}} - {{#if l.user}} - {{#link-to 'adminUser' l.user}}{{avatar l.user imageSize="tiny"}}{{/link-to}} - {{#link-to 'adminUser' l.user}}{{l.user.username}}{{/link-to}} - {{else}} - — - {{/if}} - {{l.to_address}}{{l.email_type}}{{l.email_type}}
    {{i18n 'admin.email.logs.filters.title'}}{{text-field value=filter.user placeholderKey="admin.email.logs.filters.user_placeholder"}}{{text-field value=filter.address placeholderKey="admin.email.logs.filters.address_placeholder"}}{{text-field value=filter.type placeholderKey="admin.email.logs.filters.type_placeholder"}}
    {{i18n 'admin.email.logs.none'}}
    {{format-date l.created_at}} + {{#if l.user}} + {{#link-to 'adminUser' l.user}}{{avatar l.user imageSize="tiny"}}{{/link-to}} + {{#link-to 'adminUser' l.user}}{{l.user.username}}{{/link-to}} + {{else}} + — + {{/if}} + {{l.to_address}}{{l.email_type}}{{l.email_type}}
    {{i18n 'admin.email.logs.none'}}
    {{/load-more}} diff --git a/app/assets/javascripts/admin/templates/email-index.hbs b/app/assets/javascripts/admin/templates/email-index.hbs index c21e3a5ca6..200e8aad27 100644 --- a/app/assets/javascripts/admin/templates/email-index.hbs +++ b/app/assets/javascripts/admin/templates/email-index.hbs @@ -1,27 +1,36 @@ - - - - - - {{#each model.settings as |s|}} + - - + + - {{/each}} + + {{#each model.settings as |s|}} + + + + + {{/each}} +
    {{i18n 'admin.email.delivery_method'}}{{delivery_method}}
    {{s.name}}{{s.value}}{{i18n 'admin.email.delivery_method'}}{{delivery_method}}
    {{s.name}}{{s.value}}
    -
    - {{#if sendingEmail}} -
    {{i18n 'admin.email.sending_test'}}
    - {{else}} -
    - {{text-field value=testEmailAddress placeholderKey="admin.email.test_email_address"}} +
    +
    + {{#if sendingEmail}} +
    {{i18n 'admin.email.sending_test'}}
    + {{else}} +
    + {{text-field value=testEmailAddress placeholderKey="admin.email.test_email_address"}} +
    +
    + {{d-button + class="btn-primary" + action=(action "sendTestEmail") + disabled=sendTestEmailDisabled + label="admin.email.send_test" + type="submit"}} + {{#if sentTestEmailMessage}}{{sentTestEmailMessage}}{{/if}} +
    + {{/if}}
    -
    - - {{#if sentTestEmailMessage}}{{sentTestEmailMessage}}{{/if}} -
    - {{/if}} -
    + diff --git a/app/assets/javascripts/admin/templates/email-preview-digest.hbs b/app/assets/javascripts/admin/templates/email-preview-digest.hbs index 70fe22afe6..078b642dd4 100644 --- a/app/assets/javascripts/admin/templates/email-preview-digest.hbs +++ b/app/assets/javascripts/admin/templates/email-preview-digest.hbs @@ -5,14 +5,19 @@ {{date-picker-past value=lastSeen id="last-seen"}} - {{user-selector single="true" usernames=username canReceiveUpdates="true"}} - + {{user-selector single="true" usernames=username canReceiveUpdates=true}} + {{d-button + class="btn-primary digest-refresh-button" + action=(action "refresh") + label="admin.email.refresh"}}
    {{#if showHtml}} - {{i18n 'admin.email.html'}} | {{i18n 'admin.email.text'}} + {{i18n 'admin.email.html'}} | {{i18n 'admin.email.text'}} {{else}} - {{i18n 'admin.email.html'}} | {{i18n 'admin.email.text'}} + {{i18n 'admin.email.html'}} | + {{i18n 'admin.email.text'}} {{/if}}
    @@ -20,35 +25,37 @@ {{#conditional-loading-spinner condition=loading}} -
    - {{#if showSendEmailForm}} -
    -
    - {{#if sendingEmail}} - {{i18n 'admin.email.sending_test'}} - {{else}} - - {{text-field value=email placeholderKey="admin.email.test_email_address"}} - - {{#if sentEmail}} - {{i18n 'admin.email.sent_test'}} +