diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..aa854cde75 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,13 @@ +{ + "name": "Discourse", + "image": "discourse/discourse_dev:release", + "workspaceMount": "source=${localWorkspaceFolder}/../..,target=/var/www/discourse,type=bind", + "workspaceFolder": "/var/www/discourse", + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + }, + "postCreateCommand": "sudo /sbin/boot", + "extensions": ["rebornix.Ruby"], + "forwardPorts": [9292], + "remoteUser": "discourse" +} \ No newline at end of file diff --git a/.eslintignore b/.eslintignore index c7910a26c0..68b8560b4a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,5 @@ +app/assets/javascripts/browser-update.js +app/assets/javascripts/discourse-loader.js app/assets/javascripts/env.js app/assets/javascripts/main_include_admin.js app/assets/javascripts/vendor.js diff --git a/.eslintrc b/.eslintrc index 58952b4eb3..8706c9f23d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,7 +2,7 @@ "extends": "eslint-config-discourse", "rules": { "discourse-ember/global-ember": 2, - "no-duplicate-imports": 2 + "eol-last": 2 }, "globals": { "moduleFor": "off", @@ -13,6 +13,6 @@ "currentURL": "off", "invisible": "off", "visible": "off", - "count": "off", + "count": "off" } } diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 7e97daba6d..ab85796b3c 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -45,10 +45,13 @@ bf88410126f73aab47b7e694e3c5b46453cec1b6 ce3fe2f4c4ddf166949ee3cec3d9ecbf9108ab52 # REFACTOR: Move qunit tests to a different directory structure -bc97c79a35d8acd283d4d8b79aa079bce9d127c6 +445d6ba45fe954fb7de11ce7b1392232160e2b63 # REFACTOR: Move javascript tests inside discourse app 23f24bfb510edb25b18b6a0d5485270c88df9b24 # DEV: Tidy up imports. (#11364) 1c2358ba162eb9f9ba9095c9afe30cf51dd85e04 + +# DEV: Sort imports alphabetically (#11382) +bbe5d8d5cf1220165842985c0e2cd4c454d501cd diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7b1bb84427..aefab9fadb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,5 +1,9 @@ version: 2 updates: +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" - package-ecosystem: bundler directory: "/" schedule: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 8e4c2b6ac1..0000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,222 +0,0 @@ -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: ["12"] - 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 update - sudo apt-get -yqq install postgresql-client libpq-dev jpegoptim optipng jhead - wget -qO- https://raw.githubusercontent.com/discourse/discourse_docker/master/image/base/install-pngquant | sudo sh - - - name: Update imagemagick - if: env.BUILD_TYPE == 'BACKEND' - run: | - wget https://raw.githubusercontent.com/discourse/discourse_docker/master/image/base/install-imagemagick - chmod +x install-imagemagick - sudo ./install-imagemagick - - - 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 }} - - - name: Setup bundler - run: | - gem install bundler -v 2.1.4 --no-doc - bundle config deployment 'true' - bundle config without 'development' - bundle config path vendor/bundle - - - name: Bundler cache - uses: actions/cache@v2 - 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 --jobs 4 - - - name: Get yarn cache directory - id: yarn-cache-dir - run: echo "::set-output name=dir::$(yarn cache dir)" - - - name: Yarn cache - uses: actions/cache@v2 - id: yarn-cache - with: - path: ${{ steps.yarn-cache-dir.outputs.dir }} - key: ${{ runner.os }}-${{ matrix.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-${{ matrix.os }}-yarn- - - - name: Yarn install - run: yarn install --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 (core and core plugins) - if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'CORE' - run: bundle exec rubocop . - - - name: Rubocop (all plugins) - if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'PLUGINS' - run: bundle exec rubocop plugins - - - name: ESLint (core) - if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'CORE' - run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern app/assets/javascripts - - - name: ESLint (core plugins) - if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'CORE' - run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern plugins/**/{test,assets}/javascripts - - - name: ESLint (all plugins) - if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'PLUGINS' - run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern plugins/**/{test,assets}/javascripts - - - name: Prettier (core and core plugins) - if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'CORE' - run: | - yarn prettier -v - yarn prettier --list-different \ - "app/assets/stylesheets/**/*.scss" \ - "app/assets/javascripts/**/*.{js,es6}" \ - "plugins/**/assets/stylesheets/**/*.scss" \ - "plugins/**/assets/javascripts/**/*.{js,es6}" - - - name: Prettier (all plugins) - if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'PLUGINS' - run: | - yarn prettier -v - yarn prettier --list-different \ - "plugins/**/assets/stylesheets/**/*.scss" \ - "plugins/**/assets/javascripts/**/*.{js,es6}" - - - name: Ember template lint (core and core plugins) - if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'CORE' - run: | - yarn ember-template-lint \ - app/assets/javascripts \ - plugins/**/assets/javascripts - - - name: Ember template lint (all plugins) - if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'PLUGINS' - run: | - yarn ember-template-lint \ - plugins/**/assets/javascripts - - - name: Core English locale - if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'CORE' - run: bundle exec ruby script/i18n_lint.rb "config/**/locales/{client,server}.en.yml" - - - name: Plugin English locale - if: env.BUILD_TYPE == 'LINT' && env.TARGET == 'PLUGINS' - run: bundle exec ruby script/i18n_lint.rb "plugins/**/locales/{client,server}.en.yml" - - - 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['*','1200000'] - timeout-minutes: 30 diff --git a/.github/workflows/ember.yml b/.github/workflows/ember.yml new file mode 100644 index 0000000000..d06fc9885a --- /dev/null +++ b/.github/workflows/ember.yml @@ -0,0 +1,47 @@ +name: Ember CLI tests + +on: + pull_request: + push: + branches: + - master + - main + +jobs: + build: + name: run + runs-on: ubuntu-latest + container: discourse/discourse_test:release + timeout-minutes: 40 + + 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: Get yarn cache directory + id: yarn-cache-dir + run: echo "::set-output name=dir::$(yarn cache dir)" + + - name: Yarn cache + uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Yarn install + working-directory: ./app/assets/javascripts/discourse + run: yarn install + + - name: Core QUnit + working-directory: ./app/assets/javascripts/discourse + run: yarn ember test + timeout-minutes: 30 diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml new file mode 100644 index 0000000000..ff63a3b85b --- /dev/null +++ b/.github/workflows/linting.yml @@ -0,0 +1,94 @@ +name: Linting + +on: + pull_request: + push: + branches: + - master + - main + +jobs: + build: + name: run + runs-on: ubuntu-latest + container: discourse/discourse_test:release + timeout-minutes: 30 + + 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: Bundler cache + uses: actions/cache@v2 + with: + path: vendor/bundle + key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gem- + + - name: Setup gems + run: | + bundle config --local path vendor/bundle + bundle config --local deployment true + bundle config --local without development + bundle install --jobs 4 + bundle clean + + - name: Get yarn cache directory + id: yarn-cache-dir + run: echo "::set-output name=dir::$(yarn cache dir)" + + - name: Yarn cache + uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Yarn install + run: yarn install + + - name: Rubocop + if: ${{ always() }} + run: bundle exec rubocop . + + - name: ESLint (core) + if: ${{ always() }} + run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern app/assets/javascripts + + - name: ESLint (core plugins) + if: ${{ always() }} + run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern plugins/**/{test,assets}/javascripts + + - name: Prettier + if: ${{ always() }} + run: | + yarn prettier -v + yarn prettier --list-different \ + "app/assets/stylesheets/**/*.scss" \ + "app/assets/javascripts/**/*.{js,es6}" \ + "plugins/**/assets/stylesheets/**/*.scss" \ + "plugins/**/assets/javascripts/**/*.{js,es6}" + + - name: Ember template lint + if: ${{ always() }} + run: | + yarn ember-template-lint \ + app/assets/javascripts \ + plugins/**/assets/javascripts + + - name: English locale lint (core) + if: ${{ always() }} + run: bundle exec ruby script/i18n_lint.rb "config/**/locales/{client,server}.en.yml" + + - name: English locale lint (core plugins) + if: ${{ always() }} + run: bundle exec ruby script/i18n_lint.rb "plugins/**/locales/{client,server}.en.yml" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000000..8a94b40452 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,133 @@ +name: Tests + +on: + pull_request: + push: + branches: + - master + - main + +jobs: + build: + name: ${{ matrix.target }} ${{ matrix.build_type }} + runs-on: ubuntu-latest + container: discourse/discourse_test:release + timeout-minutes: 60 + + env: + DISCOURSE_HOSTNAME: www.example.com + RUBY_GLOBAL_METHOD_CACHE_SIZE: 131072 + RAILS_ENV: test + PGHOST: postgres + PGUSER: discourse + PGPASSWORD: discourse + + strategy: + fail-fast: false + + matrix: + build_type: [backend, frontend] + target: [core, plugins] + postgres: ["13"] + redis: ["6.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 redis + uses: shogo82148/actions-setup-redis@v1 + with: + redis-version: ${{ matrix.redis }} + + - name: Bundler cache + uses: actions/cache@v2 + with: + path: vendor/bundle + key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gem- + + - name: Setup gems + run: | + bundle config --local path vendor/bundle + bundle config --local deployment true + bundle config --local without development + bundle install --jobs 4 + bundle clean + + - name: Get yarn cache directory + id: yarn-cache-dir + run: echo "::set-output name=dir::$(yarn cache dir)" + + - name: Yarn cache + uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Yarn install + run: yarn install + + - name: Checkout official plugins + if: matrix.target == 'plugins' + run: bin/rake plugin:install_all_official + + - name: Create database + run: | + bin/rake db:create + bin/rake db:migrate + + - name: Create parallel databases + if: matrix.build_type == 'backend' && matrix.target == 'core' + run: | + bin/rake parallel:create + bin/rake parallel:migrate + + - name: Core RSpec + if: matrix.build_type == 'backend' && matrix.target == 'core' + run: bin/turbo_rspec + + - name: Plugin RSpec + if: matrix.build_type == 'backend' && matrix.target == 'plugins' + run: bin/rake plugin:spec + + - name: Core QUnit + if: matrix.build_type == 'frontend' && matrix.target == 'core' + run: bin/rake qunit:test['1200000'] + timeout-minutes: 30 + + - name: Wizard QUnit + if: matrix.build_type == 'frontend' && matrix.target == 'core' + run: bin/rake qunit:test['600000','/wizard/qunit'] + timeout-minutes: 10 + + - name: Plugin QUnit + if: matrix.build_type == 'frontend' && matrix.target == 'plugins' + run: bin/rake plugin:qunit['*','1200000'] + timeout-minutes: 30 diff --git a/.gitignore b/.gitignore index d0359a12ee..8aea19bec0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,33 +1,26 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. -# -# If you find yourself ignoring temporary files generated by your text editor -# or operating system, you probably want to add a global ignore instead: -# git config --global core.excludesfile ~/.gitignore_global +/copyright +/coverage +/data +/log +/tmp -.DS_Store -._.DS_Store -dump.rdb +/.env +/.procfile +/dump.rdb -bin/* +/config/discourse.conf +/config/discourse.pill +/config/multisite.yml +# `discourse_dev` gem +/config/dev.yml -data/ - -.sass-cache/* -public/csv/* -public/plugins/* -public/tombstone/* - -# Ignore bundler config -/.bundle -/cache -/coverage/* - -/public/assets/* -/public/tombstone/* -/bundle/* - -config/discourse.pill -config/discourse.conf +/public/assets +/public/backups +/public/csv +/public/fonts +/public/plugins +/public/tombstone +/public/uploads # Ignore the default SQLite database and db dumps *.sql @@ -36,117 +29,31 @@ config/discourse.conf /db/*.sqlite3 /db/structure.sql /db/schema.rb +/db/schema_cache.yml -# Ignore all logfiles and tempfiles. -/log/*.log -/tmp -/logfile -log/ -bootsnap-load-path-cache -bootsnap-compile-cache/ - -# Ignore plugins except for the bundled ones. +# Plugins except for the bundled ones /plugins/* -!/plugins/lazy-yt/ -!/plugins/poll/ !/plugins/discourse-details/ -!/plugins/discourse-nginx-performance-report +!/plugins/discourse-local-dates !/plugins/discourse-narrative-bot !/plugins/discourse-presence +!/plugins/lazy-yt/ +!/plugins/poll/ !/plugins/styleguide -!/plugins/discourse-local-dates /plugins/*/auto_generated/ /spec/fixtures/plugins/my_plugin/auto_generated -# Ignore Eclipse .project file -/.project - -# Ignore Eclipse .buildpath file -/.buildpath - -# Ignore byebug history -/.byebug_history - -# Ignore RubyMine settings -/.idea - -# Ignore gem that is copied in -MiniProfiler/Ruby/rack-mini-profiler-2.0.1a.gem - -discourse.sublime-workspace - -# Vim temp files -*~ -*.swp -*.swo -*.swm - -# don't check in multisite config -config/multisite.yml -# don't check in my renamed multisite config as well :) -config/multisite1.yml -config/fog_credentials.yml - -/public/fonts -/public/uploads -/public/backups -/public/stylesheet-cache/* - -# Scripts used for downloading/refreshing db -script/download_db -script/refresh_db - -# .procfile -.procfile - -# .env, local environment variables for use with foreman -.env - -# exclude our git version file for now -config/version.rb - -# ignore the Ruby Version manager (rvm) -.rvmrc -.ruby-version -.ruby-gemset -.rbenv -bundler_stubs/* - -vendor/bundle/* -*.db - -# ignore jetbrains ide file -*.iml - -# vim swap -*.swn - -# ignore nodejs files -node_modules -/package-lock.json - +/vendor/bundle/* /vendor/data/GeoLite2-City.mmdb -# Vagrant -.vagrant +# Front-end +dist +node_modules +yarn-error.log -# ignore auto-generated plugin js assets +# Auto-generated plugin JS assets /app/assets/javascripts/plugins/* -# ignore generated api documentation files +# Generated API documentation files openapi/* - -# ignore VSCode config files -.vscode - -# ignore direnv -.envrc - -# ember-cli generated -dist - -# Copyright Deposits -copyright - -yarn-error.log diff --git a/.prettierignore b/.prettierignore index ca81e935a8..3884169675 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,6 +6,8 @@ config/locales/**/*.yml !config/locales/**/*.en*.yml script/import_scripts/**/*.yml +app/assets/javascripts/browser-update.js +app/assets/javascripts/discourse-loader.js app/assets/javascripts/env.js app/assets/javascripts/main_include_admin.js app/assets/javascripts/vendor.js diff --git a/.template-lintrc.js b/.template-lintrc.js index 9148dda21f..66890f87ba 100644 --- a/.template-lintrc.js +++ b/.template-lintrc.js @@ -2,12 +2,10 @@ module.exports = { extends: "recommended", ignore: ["**/*.raw"], - // Pending: - // "eol-last": "always", - rules: { "block-indentation": true, "deprecated-render-helper": true, + "eol-last": "always", "linebreak-style": true, "link-rel-noopener": "strict", "no-abstract-roles": true, diff --git a/.vscode-sample/launch.json b/.vscode-sample/launch.json new file mode 100644 index 0000000000..e1231a270f --- /dev/null +++ b/.vscode-sample/launch.json @@ -0,0 +1,19 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Discourse", + "type": "Ruby", + "request": "launch", + "cwd": "/home/discourse/workspace/discourse", + // run bundle install before rails server + "preLaunchTask": "Prepare discourse", + "env": { "DISCOURSE_DEV_HOSTS": "${env:CLOUDENV_ENVIRONMENT_ID}-9292.apps.codespaces.githubusercontent.com", "UNICORN_BIND_ALL": "1", "UNICORN_WORKERS": "4", "DISCOURSE_DEV_ALLOW_ANON_TO_IMPERSONATE": "1" }, + "program": "bin/unicorn", + "args": ["-x"], + } + ] +} diff --git a/.vscode-sample/tasks.json b/.vscode-sample/tasks.json new file mode 100644 index 0000000000..ad8af2fea2 --- /dev/null +++ b/.vscode-sample/tasks.json @@ -0,0 +1,12 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Prepare discourse", + "type": "shell", + "command": "cd /home/discourse/workspace/discourse && bundle install && yarn && bin/rake db:migrate" + }, + ], +} diff --git a/Dangerfile b/Dangerfile deleted file mode 100644 index 33cf8fc565..0000000000 --- a/Dangerfile +++ /dev/null @@ -1,73 +0,0 @@ -# frozen_string_literal: true - -if github.pr_json && (github.pr_json["additions"] || 0) > 250 || (github.pr_json["deletions"] || 0) > 250 - warn("This pull request is big! We prefer smaller PRs whenever possible, as they are easier to review. Can this be split into a few smaller PRs?") -end - -prettier_offenses = `yarn --silent prettier --list-different "app/assets/stylesheets/**/*.scss" "app/assets/javascripts/**/*.js" "app/assets/javascripts/**/*.es6" "test/javascripts/**/*.es6"`.split("\n") - -unless prettier_offenses.empty? - fail(%{ -This PR doesn't match our required code formatting standards, as enforced by prettier.io. Here's how to set up prettier in your code editor.\n -#{prettier_offenses.map { |o| github.html_link(o) }.join("\n")} - }) -end - -locales_changes = git.modified_files.grep(%r{config/locales}) -has_non_en_locales_changes = locales_changes.grep_v(%r{config/locales/(?:client|server)\.(?:en|en_US)\.yml}).any? - -if locales_changes.any? && has_non_en_locales_changes - fail("Please submit your non-English translation updates via [Crowdin](https://translate.discourse.org/). You can read more on how to contribute translations [here](https://meta.discourse.org/t/contribute-a-translation-to-discourse/14882).") -end - -files = (git.added_files + git.modified_files) - .select { |path| !path.start_with?("plugins/") } - .select { |path| path.end_with?("es6") || path.end_with?("js") || path.end_with?("rb") } - -js_files = files.select { |path| path.end_with?(".js.es6") || path.end_with?(".js") } -js_test_files = js_files.select { |path| path.end_with?("-test.js.es6") } - -super_offenses = [] -self_offenses = [] -js_files.each do |path| - diff = git.diff_for_file(path) - - next if !diff - - diff.patch.lines.grep(/^\+\s\s/).each do |added_line| - super_offenses << path if added_line['this._super()'] - self_offenses << path if added_line[/(?:(^|\W)self\.?)/] - end -end - -jquery_find_offenses = [] -js_test_files.each do |path| - diff = git.diff_for_file(path) - - next if !diff - - diff.patch.lines.grep(/^\+\s\s/).each do |added_line| - jquery_find_offenses << path if added_line['this.$('] - end -end - -if !self_offenses.empty? - warn(%{ -Use fat arrow instead of self pattern.\n -#{self_offenses.uniq.map { |o| github.html_link(o) }.join("\n")} - }) -end - -if !super_offenses.empty? - warn(%{ -When possible use `this._super(...arguments)` instead of `this._super()`\n -#{super_offenses.uniq.map { |o| github.html_link(o) }.join("\n")} - }) -end - -if !jquery_find_offenses.empty? - warn(%{ -Use `find()` instead of `this.$` in js tests`\n -#{jquery_find_offenses.uniq.map { |o| github.html_link(o) }.join("\n")} - }) -end diff --git a/Gemfile b/Gemfile index ed8fd0f8c9..71ee3f910c 100644 --- a/Gemfile +++ b/Gemfile @@ -18,13 +18,13 @@ else # 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.3.7' - gem 'actionpack', '6.0.3.7' - gem 'actionview', '6.0.3.7' - gem 'activemodel', '6.0.3.7' - gem 'activerecord', '6.0.3.7' - gem 'activesupport', '6.0.3.7' - gem 'railties', '6.0.3.7' + gem 'actionmailer', '6.1.3.2' + gem 'actionpack', '6.1.3.2' + gem 'actionview', '6.1.3.2' + gem 'activemodel', '6.1.3.2' + gem 'activerecord', '6.1.3.2' + gem 'activesupport', '6.1.3.2' + gem 'railties', '6.1.3.2' gem 'sprockets-rails' end @@ -40,7 +40,7 @@ gem 'actionview_precompiler', require: false gem 'seed-fu' -gem 'mail', require: false +gem 'mail', git: 'https://github.com/discourse/mail.git', require: false gem 'mini_mime' gem 'mini_suffix' @@ -96,6 +96,7 @@ gem 'discourse_image_optim', require: 'image_optim' gem 'multi_json' gem 'mustache' gem 'nokogiri' +gem 'loofah' gem 'css_parser', require: false gem 'omniauth' @@ -132,6 +133,7 @@ gem 'rack-protection' # security gem 'cbor', require: false gem 'cose', require: false gem 'addressable' +gem 'json_schemer' # 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 @@ -176,6 +178,7 @@ group :development do gem 'binding_of_caller' gem 'yaml-lint' gem 'annotate' + gem 'discourse_dev' end # this is an optional gem, it provides a high performance replacement @@ -192,7 +195,6 @@ gem 'htmlentities', require: false # If you want to amend mini profiler to do the monkey patches in the railties # we are open to it. by deferring require to the initializer we can configure discourse installs without it -gem 'flamegraph', require: false gem 'rack-mini-profiler', require: ['enable_rails_patches'] gem 'unicorn', require: false, platform: :ruby diff --git a/Gemfile.lock b/Gemfile.lock index 2dd6c3013f..2bbc42e7d2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,21 +1,29 @@ +GIT + remote: https://github.com/discourse/mail.git + revision: 5b700fc95ee66378e0cf2559abc73c8bc3062a4b + specs: + mail (2.8.0.edge) + mini_mime (>= 0.1.1) + GEM remote: https://rubygems.org/ specs: - actionmailer (6.0.3.7) - actionpack (= 6.0.3.7) - actionview (= 6.0.3.7) - activejob (= 6.0.3.7) + actionmailer (6.1.3.2) + actionpack (= 6.1.3.2) + actionview (= 6.1.3.2) + activejob (= 6.1.3.2) + activesupport (= 6.1.3.2) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.0.3.7) - actionview (= 6.0.3.7) - activesupport (= 6.0.3.7) - rack (~> 2.0, >= 2.0.8) + actionpack (6.1.3.2) + actionview (= 6.1.3.2) + activesupport (= 6.1.3.2) + rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actionview (6.0.3.7) - activesupport (= 6.0.3.7) + actionview (6.1.3.2) + activesupport (= 6.1.3.2) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -24,44 +32,44 @@ GEM actionview (>= 6.0.a) active_model_serializers (0.8.4) activemodel (>= 3.0) - activejob (6.0.3.7) - activesupport (= 6.0.3.7) + activejob (6.1.3.2) + activesupport (= 6.1.3.2) globalid (>= 0.3.6) - activemodel (6.0.3.7) - activesupport (= 6.0.3.7) - activerecord (6.0.3.7) - activemodel (= 6.0.3.7) - activesupport (= 6.0.3.7) - activesupport (6.0.3.7) + activemodel (6.1.3.2) + activesupport (= 6.1.3.2) + activerecord (6.1.3.2) + activemodel (= 6.1.3.2) + activesupport (= 6.1.3.2) + activesupport (6.1.3.2) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) annotate (3.1.1) activerecord (>= 3.2, < 7.0) rake (>= 10.4, < 14.0) - ast (2.4.1) - aws-eventstream (1.1.0) - aws-partitions (1.390.0) - aws-sdk-core (3.109.2) + ast (2.4.2) + aws-eventstream (1.1.1) + aws-partitions (1.432.0) + aws-sdk-core (3.112.1) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.39.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kms (1.42.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.83.2) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-s3 (1.90.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) - aws-sdk-sns (1.35.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-sns (1.38.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sigv4 (1.2.2) + aws-sigv4 (1.2.3) aws-eventstream (~> 1, >= 1.0.2) barber (0.12.2) ember-source (>= 1.0, < 3.1) @@ -70,31 +78,32 @@ GEM coderay (>= 1.0.0) erubi (>= 1.0.0) rack (>= 0.9.0) - binding_of_caller (0.8.0) + binding_of_caller (1.0.0) debug_inspector (>= 0.0.1) - bootsnap (1.5.1) + bootsnap (1.7.5) msgpack (~> 1.0) builder (3.2.4) - bullet (6.1.0) + bullet (6.1.4) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) byebug (11.1.3) cbor (0.5.9.6) certified (1.0.0) - chunky_png (1.3.14) + chunky_png (1.4.0) coderay (1.1.3) colored2 (3.1.2) concurrent-ruby (1.1.8) - connection_pool (2.2.3) + connection_pool (2.2.5) cose (1.2.0) cbor (~> 0.5.9) openssl-signature_algorithm (~> 1.0) cppjieba_rb (0.3.3) - crack (0.4.4) + crack (0.4.5) + rexml crass (1.0.6) - css_parser (1.7.1) + css_parser (1.9.0) addressable - debug_inspector (0.0.3) + debug_inspector (1.1.0) diff-lcs (1.4.4) diffy (3.4.0) discourse-ember-rails (0.18.6) @@ -104,15 +113,19 @@ GEM ember-source (>= 1.1.0) jquery-rails (>= 1.0.17) railties (>= 3.1) - discourse-ember-source (3.12.2.2) - discourse-fonts (0.0.5) + discourse-ember-source (3.12.2.3) + discourse-fonts (0.0.8) + discourse_dev (0.2.1) + faker (~> 2.16) 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.2) + docile (1.4.0) + ecma-re-validator (0.3.0) + regexp_parser (~> 2.0) email_reply_trimmer (0.1.13) ember-data-source (3.0.2) ember-source (>= 2, < 3.0) @@ -121,24 +134,32 @@ GEM sprockets (>= 3.3, < 4.1) ember-source (2.18.2) erubi (1.10.0) - excon (0.78.0) - execjs (2.7.0) + excon (0.81.0) + execjs (2.8.1) exifr (1.3.9) - fabrication (2.21.1) + fabrication (2.22.0) + faker (2.17.0) + i18n (>= 1.6, < 2) fakeweb (1.3.0) - faraday (1.1.0) + faraday (1.4.1) + faraday-excon (~> 1.1) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.1) multipart-post (>= 1.2, < 3) - ruby2_keywords + ruby2_keywords (>= 0.0.4) + faraday-excon (1.1.0) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.1.0) fast_blank (1.0.0) fast_xs (0.8.0) - fastimage (2.2.0) - ffi (1.13.1) - flamegraph (0.9.5) + fastimage (2.2.3) + ffi (1.15.0) fspath (3.1.2) gc_tracer (1.5.1) globalid (0.4.2) activesupport (>= 4.2.0) guess_html_encoding (0.0.11) + hana (1.3.7) hashdiff (1.0.1) hashie (4.1.0) highline (2.0.3) @@ -154,18 +175,23 @@ GEM rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.3.1) + json (2.5.1) json-schema (2.8.1) addressable (>= 2.4) - jwt (2.2.2) + json_schemer (0.2.18) + ecma-re-validator (~> 0.3) + hana (~> 1.3) + regexp_parser (~> 2.0) + uri_template (~> 0.7) + jwt (2.2.3) kgio (2.11.3) - libv8 (8.4.255.0) - libv8 (8.4.255.0-universal-darwin-20) - libv8 (8.4.255.0-x86_64-darwin-18) - libv8 (8.4.255.0-x86_64-darwin-19) - libv8 (8.4.255.0-x86_64-darwin-20) - libv8 (8.4.255.0-x86_64-linux) - listen (3.3.1) + libv8-node (15.14.0.1) + libv8-node (15.14.0.1-arm64-darwin-20) + libv8-node (15.14.0.1-x86_64-darwin-18) + libv8-node (15.14.0.1-x86_64-darwin-19) + libv8-node (15.14.0.1-x86_64-darwin-20) + libv8-node (15.14.0.1-x86_64-linux) + listen (3.5.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) lograge (0.11.2) @@ -176,37 +202,36 @@ GEM logstash-event (1.2.02) logstash-logger (0.26.1) logstash-event (~> 1.2) - logster (2.9.4) + logster (2.9.6) loofah (2.9.1) 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.14) - message_bus (3.3.4) + memory_profiler (1.0.0) + message_bus (3.3.5) rack (>= 1.1.3) method_source (1.0.0) mini_mime (1.1.0) mini_portile2 (2.5.1) - mini_racer (0.3.1) - libv8 (~> 8.4.255) - mini_scheduler (0.12.3) - sidekiq - mini_sql (0.3) - mini_suffix (0.3.0) + mini_racer (0.4.0) + libv8-node (~> 15.14.0.0) + mini_scheduler (0.13.0) + sidekiq (>= 4.2.3) + mini_sql (1.1.3) + mini_suffix (0.3.2) ffi (~> 1.9) minitest (5.14.4) - mocha (1.11.2) - mock_redis (0.26.0) - msgpack (1.3.3) + mocha (1.12.0) + mock_redis (0.28.0) + ruby2_keywords + msgpack (1.4.2) multi_json (1.15.0) multi_xml (0.6.0) multipart-post (2.1.1) mustache (1.1.1) - nio4r (2.5.4) + nio4r (2.5.7) nokogiri (1.11.3) mini_portile2 (~> 2.5.0) racc (~> 1.4) @@ -216,16 +241,16 @@ GEM racc (~> 1.4) nokogiri (1.11.3-x86_64-linux) racc (~> 1.4) - nokogumbo (2.0.2) + nokogumbo (2.0.5) nokogiri (~> 1.8, >= 1.8.4) - oauth (0.5.4) - oauth2 (1.4.4) + oauth (0.5.6) + oauth2 (1.4.7) faraday (>= 0.8, < 2.0) jwt (>= 1.0, < 3.0) multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) - oj (3.10.16) + oj (3.11.5) omniauth (1.9.1) hashie (>= 3.4.6) rack (>= 1.6.2, < 3) @@ -234,35 +259,38 @@ GEM omniauth-github (1.4.0) omniauth (~> 1.5) omniauth-oauth2 (>= 1.4.0, < 2.0) - omniauth-google-oauth2 (0.8.0) + omniauth-google-oauth2 (0.8.2) jwt (>= 2.0) - omniauth (>= 1.1.1) + oauth2 (~> 1.1) + omniauth (~> 1.1) omniauth-oauth2 (>= 1.6) - omniauth-oauth (1.1.0) + omniauth-oauth (1.2.0) oauth - omniauth (~> 1.0) - omniauth-oauth2 (1.7.0) + omniauth (>= 1.0, < 3) + omniauth-oauth2 (1.7.1) oauth2 (~> 1.4) - omniauth (~> 1.9) + omniauth (>= 1.9, < 3) omniauth-twitter (1.4.0) omniauth-oauth (~> 1.1) rack - onebox (2.2.1) + onebox (2.2.15) addressable (~> 2.7.0) htmlentities (~> 4.3) multi_json (~> 1.11) mustache nokogiri (~> 1.7) sanitize - openssl-signature_algorithm (1.0.0) + openssl (2.2.0) + openssl-signature_algorithm (1.1.1) + openssl (~> 2.0) optimist (3.0.1) parallel (1.20.1) - parallel_tests (3.4.0) + parallel_tests (3.7.0) parallel - parser (2.7.2.0) + parser (3.0.1.1) ast (~> 2.4.1) pg (1.2.3) - progress (3.5.2) + progress (3.6.0) pry (0.13.1) coderay (~> 1.1) method_source (~> 1.0) @@ -272,12 +300,12 @@ GEM pry-rails (0.3.9) pry (>= 0.10.4) public_suffix (4.0.6) - puma (5.0.4) + puma (5.3.1) nio4r (~> 2.0) r2 (0.2.7) racc (1.5.2) rack (2.2.3) - rack-mini-profiler (2.2.0) + rack-mini-profiler (2.3.2) rack (>= 1.2.0) rack-protection (2.1.0) rack @@ -288,23 +316,23 @@ GEM nokogiri (>= 1.6) rails-html-sanitizer (1.3.0) loofah (~> 2.3) - rails_failover (0.6.2) + rails_failover (0.7.3) activerecord (~> 6.0) concurrent-ruby railties (~> 6.0) - rails_multisite (2.5.0) + rails_multisite (3.0.0) activerecord (> 5.0, < 7) railties (> 5.0, < 7) - railties (6.0.3.7) - actionpack (= 6.0.3.7) - activesupport (= 6.0.3.7) + railties (6.1.3.2) + actionpack (= 6.1.3.2) + activesupport (= 6.1.3.2) method_source rake (>= 0.8.7) - thor (>= 0.20.3, < 2.0) + thor (~> 1.0) rainbow (3.0.0) raindrops (0.19.1) rake (13.0.3) - rb-fsevent (0.10.4) + rb-fsevent (0.11.0) rb-inotify (0.10.1) ffi (~> 1.0) rbtrace (0.4.14) @@ -313,72 +341,72 @@ GEM optimist (>= 3.0.0) rchardet (1.8.0) redis (4.2.5) - redis-namespace (1.8.0) + redis-namespace (1.8.1) redis (>= 3.0.4) - regexp_parser (2.0.0) + regexp_parser (2.1.1) request_store (1.5.0) rack (>= 1.4) - rexml (3.2.4) + rexml (3.2.5) rinku (2.0.6) rotp (6.2.0) - rqrcode (1.1.2) + rqrcode (2.0.0) chunky_png (~> 1.0) - rqrcode_core (~> 0.1) - rqrcode_core (0.1.2) + rqrcode_core (~> 1.0) + rqrcode_core (1.0.0) rspec (3.10.0) rspec-core (~> 3.10.0) rspec-expectations (~> 3.10.0) rspec-mocks (~> 3.10.0) - rspec-core (3.10.0) + rspec-core (3.10.1) rspec-support (~> 3.10.0) - rspec-expectations (3.10.0) + rspec-expectations (3.10.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) rspec-html-matchers (0.9.4) nokogiri (~> 1) rspec (>= 3.0.0.a, < 4) - rspec-mocks (3.10.0) + rspec-mocks (3.10.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) - rspec-rails (4.0.1) - actionpack (>= 4.2) - activesupport (>= 4.2) - railties (>= 4.2) - rspec-core (~> 3.9) - rspec-expectations (~> 3.9) - rspec-mocks (~> 3.9) - rspec-support (~> 3.9) - rspec-support (3.10.0) - rswag-specs (2.3.1) + rspec-rails (5.0.1) + actionpack (>= 5.2) + activesupport (>= 5.2) + railties (>= 5.2) + rspec-core (~> 3.10) + rspec-expectations (~> 3.10) + rspec-mocks (~> 3.10) + rspec-support (~> 3.10) + rspec-support (3.10.2) + rswag-specs (2.4.0) activesupport (>= 3.1, < 7.0) json-schema (~> 2.2) railties (>= 3.1, < 7.0) rtlit (0.0.5) - rubocop (1.4.2) + rubocop (1.14.0) parallel (~> 1.10) - parser (>= 2.7.1.5) + parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8) + regexp_parser (>= 1.8, < 3.0) rexml - rubocop-ast (>= 1.1.1) + rubocop-ast (>= 1.5.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 2.0) - rubocop-ast (1.2.0) - parser (>= 2.7.1.5) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.5.0) + parser (>= 3.0.1.1) rubocop-discourse (2.4.1) rubocop (>= 1.1.0) rubocop-rspec (>= 2.0.0) - rubocop-rspec (2.0.0) + rubocop-rspec (2.3.0) rubocop (~> 1.0) rubocop-ast (>= 1.1.0) - ruby-prof (1.4.2) - ruby-progressbar (1.10.1) + ruby-prof (1.4.3) + ruby-progressbar (1.11.0) ruby-readability (0.7.0) guess_html_encoding (>= 0.0.4) nokogiri (>= 1.6.0) - ruby2_keywords (0.0.2) + ruby2_keywords (0.0.4) rubyzip (2.3.0) - sanitize (5.2.1) + sanitize (5.2.3) crass (~> 1.0.2) nokogiri (>= 1.8.0) nokogumbo (~> 2.0) @@ -394,18 +422,18 @@ GEM seed-fu (2.3.9) activerecord (>= 3.1) activesupport (>= 3.1) - shoulda-matchers (4.4.1) + shoulda-matchers (4.5.1) activesupport (>= 4.2.0) - sidekiq (6.1.2) + sidekiq (6.2.1) connection_pool (>= 2.2.2) rack (~> 2.0) redis (>= 4.2.0) - simplecov (0.20.0) + simplecov (0.21.2) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) - simplecov_json_formatter (0.1.2) + simplecov_json_formatter (0.1.3) sprockets (3.7.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) @@ -414,24 +442,24 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) sshkey (2.0.0) - stackprof (0.2.16) - test-prof (0.12.2) + stackprof (0.2.17) + test-prof (1.0.5) thor (1.1.0) - thread_safe (0.3.6) tilt (2.0.10) - tzinfo (1.2.9) - thread_safe (~> 0.1) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) uglifier (4.2.0) execjs (>= 0.3.0, < 3) unf (0.1.4) unf_ext unf_ext (0.0.7.7) - unicode-display_width (1.7.0) - unicorn (5.7.0) + unicode-display_width (2.0.0) + unicorn (6.0.0) kgio (~> 2.6) raindrops (~> 0.7) - uniform_notifier (1.13.0) - webmock (3.10.0) + uniform_notifier (1.14.2) + uri_template (0.7.0) + webmock (3.12.2) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -451,14 +479,14 @@ PLATFORMS x86_64-linux DEPENDENCIES - actionmailer (= 6.0.3.7) - actionpack (= 6.0.3.7) - actionview (= 6.0.3.7) + actionmailer (= 6.1.3.2) + actionpack (= 6.1.3.2) + actionview (= 6.1.3.2) actionview_precompiler active_model_serializers (~> 0.8.3) - activemodel (= 6.0.3.7) - activerecord (= 6.0.3.7) - activesupport (= 6.0.3.7) + activemodel (= 6.1.3.2) + activerecord (= 6.1.3.2) + activesupport (= 6.1.3.2) addressable annotate aws-sdk-s3 @@ -479,6 +507,7 @@ DEPENDENCIES discourse-ember-rails (= 0.18.6) discourse-ember-source (~> 3.12.2) discourse-fonts + discourse_dev discourse_image_optim email_reply_trimmer ember-handlebars-template (= 0.8.0) @@ -489,20 +518,21 @@ DEPENDENCIES fast_blank fast_xs fastimage - flamegraph gc_tracer highline htmlentities http_accept_language json + json_schemer listen lograge logstash-event logstash-logger logster + loofah lru_redux lz4-ruby - mail + mail! maxminddb memory_profiler message_bus @@ -536,7 +566,7 @@ DEPENDENCIES rack-protection rails_failover rails_multisite - railties (= 6.0.3.7) + railties (= 6.1.3.2) rake rb-fsevent rbtrace @@ -576,4 +606,4 @@ DEPENDENCIES yaml-lint BUNDLED WITH - 2.2.15 + 2.2.16 diff --git a/README.md b/README.md index 91a9304300..1bb39f2f5c 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ To get your environment setup, follow the community setup guide for your operati If you're familiar with how Rails works and are comfortable setting up your own environment, you can also try out the [**Discourse Advanced Developer Guide**](docs/DEVELOPER-ADVANCED.md), which is aimed primarily at Ubuntu and macOS environments. -Before you get started, ensure you have the following minimum versions: [Ruby 2.6+](https://www.ruby-lang.org/en/downloads/), [PostgreSQL 10+](https://www.postgresql.org/download/), [Redis 4.0+](https://redis.io/download). If you're having trouble, please see our [**TROUBLESHOOTING GUIDE**](docs/TROUBLESHOOTING.md) first! +Before you get started, ensure you have the following minimum versions: [Ruby 2.7+](https://www.ruby-lang.org/en/downloads/), [PostgreSQL 13+](https://www.postgresql.org/download/), [Redis 6.0+](https://redis.io/download). If you're having trouble, please see our [**TROUBLESHOOTING GUIDE**](docs/TROUBLESHOOTING.md) first! ## Setting up Discourse @@ -43,6 +43,8 @@ If you want to set up a Discourse forum for production use, see our [**Discourse If you're looking for business class hosting, see [discourse.org/buy](https://www.discourse.org/buy/). +If you're looking for our remote work solution, see [teams.discourse.com](https://teams.discourse.com/). + ## Requirements Discourse is built for the *next* 10 years of the Internet, so our requirements are high. @@ -94,7 +96,7 @@ The original Discourse code contributors can be found in [**AUTHORS.MD**](docs/A ## Copyright / License -Copyright 2014 - 2020 Civilized Discourse Construction Kit, Inc. +Copyright 2014 - 2021 Civilized Discourse Construction Kit, Inc. Licensed under the GNU General Public License Version 2.0 (or later); you may not use this work except in compliance with the License. diff --git a/Rakefile b/Rakefile index dc7fdf2ecf..d0e674f5ab 100755 --- a/Rakefile +++ b/Rakefile @@ -7,7 +7,3 @@ require File.expand_path('../config/application', __FILE__) Discourse::Application.load_tasks - -# this prevents crashes when migrating a database in production in certain -# PostgreSQL configuations when trying to create structure.sql -Rake::Task["db:structure:dump"].clear if Rails.env.production? diff --git a/app/assets/images/favicons/amazon.png b/app/assets/images/favicons/amazon.png deleted file mode 100644 index b2ef6045ee..0000000000 Binary files a/app/assets/images/favicons/amazon.png and /dev/null differ diff --git a/app/assets/images/favicons/apple.png b/app/assets/images/favicons/apple.png deleted file mode 100644 index da6b59f17f..0000000000 Binary files a/app/assets/images/favicons/apple.png and /dev/null differ diff --git a/app/assets/images/favicons/github.png b/app/assets/images/favicons/github.png deleted file mode 100644 index d19beac3d0..0000000000 Binary files a/app/assets/images/favicons/github.png and /dev/null differ diff --git a/app/assets/images/favicons/rottentomatoes.png b/app/assets/images/favicons/rottentomatoes.png deleted file mode 100644 index acd019cad3..0000000000 Binary files a/app/assets/images/favicons/rottentomatoes.png and /dev/null differ diff --git a/app/assets/images/favicons/twitter.png b/app/assets/images/favicons/twitter.png deleted file mode 100644 index d46388dc36..0000000000 Binary files a/app/assets/images/favicons/twitter.png and /dev/null differ diff --git a/app/assets/images/favicons/wikipedia.png b/app/assets/images/favicons/wikipedia.png deleted file mode 100644 index 050c5df90c..0000000000 Binary files a/app/assets/images/favicons/wikipedia.png and /dev/null differ diff --git a/app/assets/images/select2-spinner.gif b/app/assets/images/select2-spinner.gif deleted file mode 100644 index 703391003b..0000000000 Binary files a/app/assets/images/select2-spinner.gif and /dev/null differ diff --git a/app/assets/images/select2.png b/app/assets/images/select2.png deleted file mode 100644 index 32c2fc3a22..0000000000 Binary files a/app/assets/images/select2.png and /dev/null differ diff --git a/app/assets/images/select2x2.png b/app/assets/images/select2x2.png deleted file mode 100644 index e0a62658da..0000000000 Binary files a/app/assets/images/select2x2.png and /dev/null differ diff --git a/app/assets/images/spinner_96.gif b/app/assets/images/spinner_96.gif deleted file mode 100644 index 0fe3321439..0000000000 Binary files a/app/assets/images/spinner_96.gif and /dev/null differ diff --git a/app/assets/javascripts/admin.js.erb b/app/assets/javascripts/admin.js.erb index 41f0315deb..dda5756177 100644 --- a/app/assets/javascripts/admin.js.erb +++ b/app/assets/javascripts/admin.js.erb @@ -3,10 +3,10 @@ require_asset("main_include_admin.js") DiscoursePluginRegistry.admin_javascripts.each { |js| require_asset(js) } -DiscoursePluginRegistry.each_globbed_asset(admin: true) do |f, ext| +DiscoursePluginRegistry.each_globbed_asset(admin: true) do |f| if File.directory?(f) depend_on(f) - elsif f.to_s.end_with?(".#{ext}") + else require_asset(f) end end diff --git a/app/assets/javascripts/admin/addon/components/ace-editor.js b/app/assets/javascripts/admin/addon/components/ace-editor.js index 41c22eff56..06afc360e6 100644 --- a/app/assets/javascripts/admin/addon/components/ace-editor.js +++ b/app/assets/javascripts/admin/addon/components/ace-editor.js @@ -1,15 +1,19 @@ import Component from "@ember/component"; -import loadScript from "discourse/lib/load-script"; import getURL from "discourse-common/lib/get-url"; +import loadScript from "discourse/lib/load-script"; +import I18n from "I18n"; import { observes } from "discourse-common/utils/decorators"; import { on } from "@ember/object/evented"; +const COLOR_VARS_REGEX = /\$(primary|secondary|tertiary|quaternary|header_background|header_primary|highlight|danger|success|love)(\s|;|-(low|medium|high))/g; + export default Component.extend({ mode: "css", classNames: ["ace-wrapper"], _editor: null, _skipContentChangeEvent: null, disabled: false, + htmlPlaceholder: false, @observes("editorId") editorIdChanged() { @@ -18,6 +22,10 @@ export default Component.extend({ } }, + didRender() { + this._skipContentChangeEvent = false; + }, + @observes("content") contentChanged() { const content = this.content || ""; @@ -86,6 +94,10 @@ export default Component.extend({ loadedAce.config.set("loadWorkerFromBlob", false); loadedAce.config.set("workerPath", getURL("/javascripts/ace")); // Do not use CDN for workers + if (this.htmlPlaceholder) { + this._overridePlaceholder(loadedAce); + } + if (!this.element || this.isDestroying || this.isDestroyed) { return; } @@ -98,14 +110,28 @@ export default Component.extend({ editor.on("change", () => { this._skipContentChangeEvent = true; this.set("content", editor.getSession().getValue()); - this._skipContentChangeEvent = false; }); + if (this.attrs.save) { + editor.commands.addCommand({ + name: "save", + exec: () => { + this.attrs.save(); + }, + bindKey: { mac: "cmd-s", win: "ctrl-s" }, + }); + } + + editor.on("blur", () => { + this.warnSCSSDeprecations(); + }); + editor.$blockScrolling = Infinity; editor.renderer.setScrollMargin(10, 10); this.element.setAttribute("data-editor", editor); this._editor = editor; this.changeDisabledState(); + this.warnSCSSDeprecations(); $(window) .off("ace:resize") @@ -123,6 +149,38 @@ export default Component.extend({ }); }, + warnSCSSDeprecations() { + if ( + this.mode !== "scss" || + this.editorId.startsWith("color_definitions") || + !this._editor + ) { + return; + } + + let warnings = this.content + .split("\n") + .map((line, row) => { + if (line.match(COLOR_VARS_REGEX)) { + return { + row, + column: 0, + text: I18n.t("admin.customize.theme.scss_warning_inline"), + type: "warning", + }; + } + }) + .filter(Boolean); + + this._editor.getSession().setAnnotations(warnings); + + this.setWarning( + warnings.length + ? I18n.t("admin.customize.theme.scss_color_variables_warning") + : false + ); + }, + actions: { focus() { if (this._editor) { @@ -131,4 +189,32 @@ export default Component.extend({ } }, }, + + _overridePlaceholder(loadedAce) { + const originalPlaceholderSetter = + loadedAce.config.$defaultOptions.editor.placeholder.set; + + loadedAce.config.$defaultOptions.editor.placeholder.set = function () { + if (!this.$updatePlaceholder) { + const originalRendererOn = this.renderer.on; + this.renderer.on = function () {}; + originalPlaceholderSetter.call(this, ...arguments); + this.renderer.on = originalRendererOn; + + const originalUpdatePlaceholder = this.$updatePlaceholder; + + this.$updatePlaceholder = function () { + originalUpdatePlaceholder.call(this, ...arguments); + + if (this.renderer.placeholderNode) { + this.renderer.placeholderNode.innerHTML = this.$placeholder || ""; + } + }.bind(this); + + this.on("input", this.$updatePlaceholder); + } + + this.$updatePlaceholder(); + }; + }, }); diff --git a/app/assets/javascripts/admin/addon/components/admin-backups-logs.js b/app/assets/javascripts/admin/addon/components/admin-backups-logs.js index 1bb36931a0..f691f3ffc6 100644 --- a/app/assets/javascripts/admin/addon/components/admin-backups-logs.js +++ b/app/assets/javascripts/admin/addon/components/admin-backups-logs.js @@ -1,8 +1,8 @@ -import I18n from "I18n"; -import { scheduleOnce } from "@ember/runloop"; -import Component from "@ember/component"; -import discourseDebounce from "discourse/lib/debounce"; import { observes, on } from "discourse-common/utils/decorators"; +import Component from "@ember/component"; +import I18n from "I18n"; +import discourseDebounce from "discourse-common/lib/debounce"; +import { scheduleOnce } from "@ember/runloop"; export default Component.extend({ classNames: ["admin-backups-logs"], @@ -33,9 +33,7 @@ export default Component.extend({ } }, - @on("init") - @observes("logs.[]") - _updateFormattedLogs: discourseDebounce(function () { + _updateFormattedLogsFunc: function () { const logs = this.logs; if (logs.length === 0) { return; @@ -57,7 +55,13 @@ export default Component.extend({ this.renderLogs(); scheduleOnce("afterRender", this, this._scrollDown); - }, 150), + }, + + @on("init") + @observes("logs.[]") + _updateFormattedLogs() { + discourseDebounce(this, this._updateFormattedLogsFunc, 150); + }, renderLogs() { const formattedLogs = this.formattedLogs; diff --git a/app/assets/javascripts/admin/addon/components/admin-graph.js b/app/assets/javascripts/admin/addon/components/admin-graph.js index bee93aa1c5..b6bf870bc1 100644 --- a/app/assets/javascripts/admin/addon/components/admin-graph.js +++ b/app/assets/javascripts/admin/addon/components/admin-graph.js @@ -10,7 +10,7 @@ export default Component.extend({ const model = this.model; const rawData = this.get("model.data"); - var data = { + let data = { labels: rawData.map((r) => r.x), datasets: [ { diff --git a/app/assets/javascripts/admin/addon/components/admin-report-chart.js b/app/assets/javascripts/admin/addon/components/admin-report-chart.js index 20a190d9d2..8a894b9562 100644 --- a/app/assets/javascripts/admin/addon/components/admin-report-chart.js +++ b/app/assets/javascripts/admin/addon/components/admin-report-chart.js @@ -1,8 +1,9 @@ -import { makeArray } from "discourse-common/lib/helpers"; -import { debounce, schedule } from "@ember/runloop"; import Component from "@ember/component"; -import { number } from "discourse/lib/formatter"; +import discourseDebounce from "discourse-common/lib/debounce"; import loadScript from "discourse/lib/load-script"; +import { makeArray } from "discourse-common/lib/helpers"; +import { number } from "discourse/lib/formatter"; +import { schedule } from "@ember/runloop"; export default Component.extend({ classNames: ["admin-report-chart"], @@ -14,7 +15,7 @@ export default Component.extend({ this._super(...arguments); this.resizeHandler = () => - debounce(this, this._scheduleChartRendering, 500); + discourseDebounce(this, this._scheduleChartRendering, 500); }, didInsertElement() { @@ -34,7 +35,7 @@ export default Component.extend({ didReceiveAttrs() { this._super(...arguments); - debounce(this, this._scheduleChartRendering, 100); + discourseDebounce(this, this._scheduleChartRendering, 100); }, _scheduleChartRendering() { diff --git a/app/assets/javascripts/admin/addon/components/admin-report-counts.js b/app/assets/javascripts/admin/addon/components/admin-report-counts.js index 6d3c0b398b..03c690dbbd 100644 --- a/app/assets/javascripts/admin/addon/components/admin-report-counts.js +++ b/app/assets/javascripts/admin/addon/components/admin-report-counts.js @@ -1,5 +1,5 @@ -import { match } from "@ember/object/computed"; import Component from "@ember/component"; +import { match } from "@ember/object/computed"; export default Component.extend({ allTime: true, tagName: "tr", diff --git a/app/assets/javascripts/admin/addon/components/admin-report-stacked-chart.js b/app/assets/javascripts/admin/addon/components/admin-report-stacked-chart.js index 3519e6a163..4cac3b15db 100644 --- a/app/assets/javascripts/admin/addon/components/admin-report-stacked-chart.js +++ b/app/assets/javascripts/admin/addon/components/admin-report-stacked-chart.js @@ -1,8 +1,9 @@ -import { makeArray } from "discourse-common/lib/helpers"; -import { debounce, schedule } from "@ember/runloop"; import Component from "@ember/component"; -import { number } from "discourse/lib/formatter"; +import discourseDebounce from "discourse-common/lib/debounce"; import loadScript from "discourse/lib/load-script"; +import { makeArray } from "discourse-common/lib/helpers"; +import { number } from "discourse/lib/formatter"; +import { schedule } from "@ember/runloop"; export default Component.extend({ classNames: ["admin-report-chart", "admin-report-stacked-chart"], @@ -11,7 +12,7 @@ export default Component.extend({ this._super(...arguments); this.resizeHandler = () => - debounce(this, this._scheduleChartRendering, 500); + discourseDebounce(this, this._scheduleChartRendering, 500); }, didInsertElement() { @@ -31,7 +32,7 @@ export default Component.extend({ didReceiveAttrs() { this._super(...arguments); - debounce(this, this._scheduleChartRendering, 100); + discourseDebounce(this, this._scheduleChartRendering, 100); }, _scheduleChartRendering() { diff --git a/app/assets/javascripts/admin/addon/components/admin-report-storage-stats.js b/app/assets/javascripts/admin/addon/components/admin-report-storage-stats.js index b41304ec34..e12bc6432f 100644 --- a/app/assets/javascripts/admin/addon/components/admin-report-storage-stats.js +++ b/app/assets/javascripts/admin/addon/components/admin-report-storage-stats.js @@ -1,7 +1,7 @@ -import I18n from "I18n"; -import discourseComputed from "discourse-common/utils/decorators"; -import { alias } from "@ember/object/computed"; import Component from "@ember/component"; +import I18n from "I18n"; +import { alias } from "@ember/object/computed"; +import discourseComputed from "discourse-common/utils/decorators"; import { setting } from "discourse/lib/computed"; export default Component.extend({ diff --git a/app/assets/javascripts/admin/addon/components/admin-report-table-cell.js b/app/assets/javascripts/admin/addon/components/admin-report-table-cell.js index 967370ab8e..aadf6e0f73 100644 --- a/app/assets/javascripts/admin/addon/components/admin-report-table-cell.js +++ b/app/assets/javascripts/admin/addon/components/admin-report-table-cell.js @@ -1,6 +1,6 @@ -import discourseComputed from "discourse-common/utils/decorators"; -import { alias } from "@ember/object/computed"; import Component from "@ember/component"; +import { alias } from "@ember/object/computed"; +import discourseComputed from "discourse-common/utils/decorators"; export default Component.extend({ tagName: "td", diff --git a/app/assets/javascripts/admin/addon/components/admin-report-table-header.js b/app/assets/javascripts/admin/addon/components/admin-report-table-header.js index f7c91dcab6..5c7cdf1e4c 100644 --- a/app/assets/javascripts/admin/addon/components/admin-report-table-header.js +++ b/app/assets/javascripts/admin/addon/components/admin-report-table-header.js @@ -1,5 +1,5 @@ -import discourseComputed from "discourse-common/utils/decorators"; import Component from "@ember/component"; +import discourseComputed from "discourse-common/utils/decorators"; export default Component.extend({ tagName: "th", diff --git a/app/assets/javascripts/admin/addon/components/admin-report-table.js b/app/assets/javascripts/admin/addon/components/admin-report-table.js index b44ea784dd..a237e93ee4 100644 --- a/app/assets/javascripts/admin/addon/components/admin-report-table.js +++ b/app/assets/javascripts/admin/addon/components/admin-report-table.js @@ -1,7 +1,7 @@ +import Component from "@ember/component"; +import { alias } from "@ember/object/computed"; 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; diff --git a/app/assets/javascripts/admin/addon/components/admin-report.js b/app/assets/javascripts/admin/addon/components/admin-report.js index eb9b06f703..3ecf00451d 100644 --- a/app/assets/javascripts/admin/addon/components/admin-report.js +++ b/app/assets/javascripts/admin/addon/components/admin-report.js @@ -1,16 +1,16 @@ -import I18n from "I18n"; -import discourseComputed from "discourse-common/utils/decorators"; -import { makeArray } from "discourse-common/lib/helpers"; -import { alias, or, and, equal, notEmpty } from "@ember/object/computed"; -import EmberObject, { computed, action } 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 EmberObject, { action, computed } from "@ember/object"; import Report, { SCHEMA_VERSION } from "admin/models/report"; +import { alias, and, equal, notEmpty, or } from "@ember/object/computed"; +import Component from "@ember/component"; +import I18n from "I18n"; +import ReportLoader from "discourse/lib/reports-loader"; +import discourseComputed from "discourse-common/utils/decorators"; +import { exportEntity } from "discourse/lib/export-csv"; import { isPresent } from "@ember/utils"; import { isTesting } from "discourse-common/config/environment"; +import { makeArray } from "discourse-common/lib/helpers"; +import { next } from "@ember/runloop"; +import { outputExportResult } from "discourse/lib/export-result"; const TABLE_OPTIONS = { perPage: 8, @@ -68,6 +68,8 @@ export default Component.extend({ showDatesOptions: alias("model.dates_filtering"), showRefresh: or("showDatesOptions", "model.available_filters.length"), shouldDisplayTrend: and("showTrend", "model.prev_period"), + endDate: null, + startDate: null, init() { this._super(...arguments); @@ -82,25 +84,21 @@ export default Component.extend({ .includes(this.dataSourceName); }), - startDate: computed("filters.startDate", function () { - if (this.filters && isPresent(this.filters.startDate)) { - return moment(this.filters.startDate, "YYYY-MM-DD"); - } else { - return moment(); - } - }), - - endDate: computed("filters.endDate", function () { - if (this.filters && isPresent(this.filters.endDate)) { - return moment(this.filters.endDate, "YYYY-MM-DD"); - } else { - return moment(); - } - }), - didReceiveAttrs() { this._super(...arguments); + let startDate = moment(); + if (this.filters && isPresent(this.filters.startDate)) { + startDate = moment(this.filters.startDate, "YYYY-MM-DD"); + } + this.set("startDate", startDate); + + let endDate = moment(); + if (this.filters && isPresent(this.filters.endDate)) { + endDate = moment(this.filters.endDate, "YYYY-MM-DD"); + } + this.set("endDate", endDate); + if (this.report) { this._renderReport(this.report, this.forcedModes, this.currentMode); } else if (this.dataSourceName) { @@ -213,7 +211,7 @@ export default Component.extend({ @action onChangeDateRange(range) { - this.send("refreshReport", { + this.setProperties({ startDate: range.from, endDate: range.to, }); diff --git a/app/assets/javascripts/admin/addon/components/admin-theme-editor.js b/app/assets/javascripts/admin/addon/components/admin-theme-editor.js index c453c877d6..36d861779a 100644 --- a/app/assets/javascripts/admin/addon/components/admin-theme-editor.js +++ b/app/assets/javascripts/admin/addon/components/admin-theme-editor.js @@ -1,10 +1,13 @@ -import I18n from "I18n"; -import { next } from "@ember/runloop"; import Component from "@ember/component"; +import I18n from "I18n"; import discourseComputed from "discourse-common/utils/decorators"; import { fmt } from "discourse/lib/computed"; +import { isDocumentRTL } from "discourse/lib/text-direction"; +import { next } from "@ember/runloop"; export default Component.extend({ + warning: null, + @discourseComputed("theme.targets", "onlyOverridden", "showAdvanced") visibleTargets(targets, onlyOverridden, showAdvanced) { return targets.filter((target) => { @@ -43,9 +46,17 @@ export default Component.extend({ @discourseComputed("currentTargetName", "fieldName") placeholder(targetName, fieldName) { - return fieldName && fieldName === "color_definitions" - ? I18n.t("admin.customize.theme.color_definitions.placeholder") - : ""; + if (fieldName && fieldName === "color_definitions") { + const example = + ":root {\n" + + " --mytheme-tertiary-or-quaternary: #{dark-light-choose($tertiary, $quaternary)};\n" + + "}"; + + return I18n.t("admin.customize.theme.color_definitions.placeholder", { + example: isDocumentRTL() ? `
${example}
` : example, + }); + } + return ""; }, @discourseComputed("fieldName", "currentTargetName", "theme") @@ -111,5 +122,13 @@ export default Component.extend({ onlyOverriddenChanged(value) { this.onlyOverriddenChanged(value); }, + + save() { + this.attrs.save(); + }, + + setWarning(message) { + this.set("warning", message); + }, }, }); diff --git a/app/assets/javascripts/admin/addon/components/admin-user-field-item.js b/app/assets/javascripts/admin/addon/components/admin-user-field-item.js index 17634d5c49..be2674ec6c 100644 --- a/app/assets/javascripts/admin/addon/components/admin-user-field-item.js +++ b/app/assets/javascripts/admin/addon/components/admin-user-field-item.js @@ -1,16 +1,16 @@ -import I18n from "I18n"; -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, i18n } from "discourse/lib/computed"; import discourseComputed, { observes, on, } from "discourse-common/utils/decorators"; +import { i18n, propertyEqual } from "discourse/lib/computed"; +import Component from "@ember/component"; +import I18n from "I18n"; +import UserField from "admin/models/user-field"; +import { bufferedProperty } from "discourse/mixins/buffered-content"; +import { empty } from "@ember/object/computed"; +import { isEmpty } from "@ember/utils"; +import { popupAjaxError } from "discourse/lib/ajax-error"; +import { scheduleOnce } from "@ember/runloop"; export default Component.extend(bufferedProperty("userField"), { editing: empty("userField.id"), @@ -44,25 +44,25 @@ export default Component.extend(bufferedProperty("userField"), { }, @discourseComputed( - "userField.editable", - "userField.required", - "userField.show_on_profile", - "userField.show_on_user_card" + "userField.{editable,required,show_on_profile,show_on_user_card,searchable}" ) - flags(editable, required, showOnProfile, showOnUserCard) { + flags(userField) { const ret = []; - if (editable) { + if (userField.editable) { ret.push(I18n.t("admin.user_fields.editable.enabled")); } - if (required) { + if (userField.required) { ret.push(I18n.t("admin.user_fields.required.enabled")); } - if (showOnProfile) { + if (userField.showOnProfile) { ret.push(I18n.t("admin.user_fields.show_on_profile.enabled")); } - if (showOnUserCard) { + if (userField.showOnUserCard) { ret.push(I18n.t("admin.user_fields.show_on_user_card.enabled")); } + if (userField.searchable) { + ret.push(I18n.t("admin.user_fields.searchable.enabled")); + } return ret.join(", "); }, @@ -78,6 +78,7 @@ export default Component.extend(bufferedProperty("userField"), { "required", "show_on_profile", "show_on_user_card", + "searchable", "options" ); diff --git a/app/assets/javascripts/admin/addon/components/admin-watched-word.js b/app/assets/javascripts/admin/addon/components/admin-watched-word.js index 061c83d56a..fb24a6f388 100644 --- a/app/assets/javascripts/admin/addon/components/admin-watched-word.js +++ b/app/assets/javascripts/admin/addon/components/admin-watched-word.js @@ -1,17 +1,9 @@ -import I18n from "I18n"; import Component from "@ember/component"; -import { iconHTML } from "discourse-common/lib/icon-library"; +import I18n from "I18n"; import bootbox from "bootbox"; export default Component.extend({ classNames: ["watched-word"], - watchedWord: null, - xIcon: iconHTML("times").htmlSafe(), - - init() { - this._super(...arguments); - this.set("watchedWord", this.get("word.word")); - }, click() { this.word diff --git a/app/assets/javascripts/admin/addon/components/admin-web-hook-event-chooser.js b/app/assets/javascripts/admin/addon/components/admin-web-hook-event-chooser.js index 98e39518bf..1c8e59fd27 100644 --- a/app/assets/javascripts/admin/addon/components/admin-web-hook-event-chooser.js +++ b/app/assets/javascripts/admin/addon/components/admin-web-hook-event-chooser.js @@ -1,7 +1,7 @@ -import I18n from "I18n"; -import discourseComputed from "discourse-common/utils/decorators"; -import { alias } from "@ember/object/computed"; import Component from "@ember/component"; +import I18n from "I18n"; +import { alias } from "@ember/object/computed"; +import discourseComputed from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["hook-event"], diff --git a/app/assets/javascripts/admin/addon/components/admin-web-hook-event.js b/app/assets/javascripts/admin/addon/components/admin-web-hook-event.js index 75f1326a48..fbcf1594e0 100644 --- a/app/assets/javascripts/admin/addon/components/admin-web-hook-event.js +++ b/app/assets/javascripts/admin/addon/components/admin-web-hook-event.js @@ -1,10 +1,10 @@ -import I18n from "I18n"; -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"; +import Component from "@ember/component"; +import I18n from "I18n"; +import { ajax } from "discourse/lib/ajax"; import bootbox from "bootbox"; +import discourseComputed from "discourse-common/utils/decorators"; +import { popupAjaxError } from "discourse/lib/ajax-error"; export default Component.extend({ tagName: "li", diff --git a/app/assets/javascripts/admin/addon/components/admin-web-hook-status.js b/app/assets/javascripts/admin/addon/components/admin-web-hook-status.js index 818970f125..a80a769e94 100644 --- a/app/assets/javascripts/admin/addon/components/admin-web-hook-status.js +++ b/app/assets/javascripts/admin/addon/components/admin-web-hook-status.js @@ -1,6 +1,6 @@ +import Component from "@ember/component"; import I18n from "I18n"; import discourseComputed from "discourse-common/utils/decorators"; -import Component from "@ember/component"; import { iconHTML } from "discourse-common/lib/icon-library"; export default Component.extend({ diff --git a/app/assets/javascripts/admin/addon/components/admin-wrapper.js b/app/assets/javascripts/admin/addon/components/admin-wrapper.js index 5b907c9a04..d6a3564d42 100644 --- a/app/assets/javascripts/admin/addon/components/admin-wrapper.js +++ b/app/assets/javascripts/admin/addon/components/admin-wrapper.js @@ -2,11 +2,13 @@ import Component from "@ember/component"; export default Component.extend({ didInsertElement() { this._super(...arguments); - $("body").addClass("admin-interface"); + document.querySelector("html").classList.add("admin-area"); + document.querySelector("body").classList.add("admin-interface"); }, willDestroyElement() { this._super(...arguments); - $("body").removeClass("admin-interface"); + document.querySelector("html").classList.remove("admin-area"); + document.querySelector("body").classList.remove("admin-interface"); }, }); diff --git a/app/assets/javascripts/admin/addon/components/color-input.js b/app/assets/javascripts/admin/addon/components/color-input.js index 3d7dad5e00..c563fedc94 100644 --- a/app/assets/javascripts/admin/addon/components/color-input.js +++ b/app/assets/javascripts/admin/addon/components/color-input.js @@ -1,8 +1,8 @@ -import { schedule } from "@ember/runloop"; -import Component from "@ember/component"; -import { computed, action } from "@ember/object"; +import { action, computed } from "@ember/object"; import loadScript, { loadCSS } from "discourse/lib/load-script"; +import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; +import { schedule } from "@ember/runloop"; /** An input field for a color. diff --git a/app/assets/javascripts/admin/addon/components/dashboard-new-features.js b/app/assets/javascripts/admin/addon/components/dashboard-new-features.js new file mode 100644 index 0000000000..059e138240 --- /dev/null +++ b/app/assets/javascripts/admin/addon/components/dashboard-new-features.js @@ -0,0 +1,33 @@ +import Component from "@ember/component"; +import { action, computed } from "@ember/object"; +import { ajax } from "discourse/lib/ajax"; + +export default Component.extend({ + newFeatures: null, + classNames: ["section", "dashboard-new-features"], + classNameBindings: ["hasUnseenFeatures:ordered-first"], + releaseNotesLink: null, + + init() { + this._super(...arguments); + + ajax("/admin/dashboard/new-features.json").then((json) => { + this.setProperties({ + newFeatures: json.new_features, + hasUnseenFeatures: json.has_unseen_features, + releaseNotesLink: json.release_notes_link, + }); + }); + }, + + columnCountClass: computed("newFeatures", function () { + return this.newFeatures.length > 2 ? "three-or-more-items" : ""; + }), + + @action + dismissNewFeatures() { + ajax("/admin/dashboard/mark-new-features-as-seen.json", { + type: "PUT", + }).then(() => this.set("hasUnseenFeatures", false)); + }, +}); diff --git a/app/assets/javascripts/admin/addon/components/email-styles-editor.js b/app/assets/javascripts/admin/addon/components/email-styles-editor.js index c40f8a543e..e3e980e632 100644 --- a/app/assets/javascripts/admin/addon/components/email-styles-editor.js +++ b/app/assets/javascripts/admin/addon/components/email-styles-editor.js @@ -1,8 +1,8 @@ +import Component from "@ember/component"; import I18n from "I18n"; +import bootbox from "bootbox"; import discourseComputed from "discourse-common/utils/decorators"; import { reads } from "@ember/object/computed"; -import Component from "@ember/component"; -import bootbox from "bootbox"; export default Component.extend({ editorId: reads("fieldName"), @@ -50,5 +50,8 @@ export default Component.extend({ } ); }, + save() { + this.attrs.save(); + }, }, }); diff --git a/app/assets/javascripts/admin/addon/components/embeddable-host.js b/app/assets/javascripts/admin/addon/components/embeddable-host.js index 9846c9fc7b..a0f546227b 100644 --- a/app/assets/javascripts/admin/addon/components/embeddable-host.js +++ b/app/assets/javascripts/admin/addon/components/embeddable-host.js @@ -1,16 +1,12 @@ +import Category from "discourse/models/category"; +import Component from "@ember/component"; import I18n from "I18n"; -import discourseComputed, { - on, - observes, -} from "discourse-common/utils/decorators"; +import bootbox from "bootbox"; +import { bufferedProperty } from "discourse/mixins/buffered-content"; +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 { popupAjaxError } from "discourse/lib/ajax-error"; -import Category from "discourse/models/category"; -import bootbox from "bootbox"; export default Component.extend(bufferedProperty("host"), { editToggled: false, @@ -19,14 +15,6 @@ export default Component.extend(bufferedProperty("host"), { editing: or("host.isNew", "editToggled"), - @on("didInsertElement") - @observes("editing") - _focusOnInput() { - schedule("afterRender", () => { - this.element.querySelector(".host-name").focus(); - }); - }, - @discourseComputed("buffered.host", "host.isSaving") cantSave(host, isSaving) { return isSaving || isEmpty(host); diff --git a/app/assets/javascripts/admin/addon/components/embedding-setting.js b/app/assets/javascripts/admin/addon/components/embedding-setting.js index 8c1a187ac6..7b8417aa06 100644 --- a/app/assets/javascripts/admin/addon/components/embedding-setting.js +++ b/app/assets/javascripts/admin/addon/components/embedding-setting.js @@ -1,5 +1,5 @@ -import discourseComputed from "discourse-common/utils/decorators"; import Component from "@ember/component"; +import discourseComputed from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["embed-setting"], diff --git a/app/assets/javascripts/admin/addon/components/emoji-value-list.js b/app/assets/javascripts/admin/addon/components/emoji-value-list.js new file mode 100644 index 0000000000..e7096a71da --- /dev/null +++ b/app/assets/javascripts/admin/addon/components/emoji-value-list.js @@ -0,0 +1,168 @@ +import Component from "@ember/component"; +import I18n from "I18n"; +import discourseComputed from "discourse-common/utils/decorators"; +import { emojiUrlFor } from "discourse/lib/text"; +import { action, set, setProperties } from "@ember/object"; +import { later, schedule } from "@ember/runloop"; + +export default Component.extend({ + classNameBindings: [":value-list", ":emoji-list"], + values: null, + validationMessage: null, + emojiPickerIsActive: false, + isEditorFocused: false, + + @discourseComputed("values") + collection(values) { + values = values || ""; + + return values + .split("|") + .filter(Boolean) + .map((value) => { + return { + isEditable: true, + isEditing: false, + value, + emojiUrl: emojiUrlFor(value), + }; + }); + }, + + @action + closeEmojiPicker() { + this.collection.setEach("isEditing", false); + this.set("emojiPickerIsActive", false); + this.set("isEditorFocused", false); + }, + + @action + emojiSelected(code) { + if (!this._validateInput(code)) { + return; + } + + const item = this.collection.findBy("isEditing"); + if (item) { + setProperties(item, { + value: code, + emojiUrl: emojiUrlFor(code), + isEditing: false, + }); + + this._saveValues(); + } else { + const newCollectionValue = { + value: code, + emojiUrl: emojiUrlFor(code), + isEditable: true, + isEditing: false, + }; + this.collection.addObject(newCollectionValue); + this._saveValues(); + } + + this.set("emojiPickerIsActive", false); + this.set("isEditorFocused", false); + }, + + @discourseComputed("collection") + showUpDownButtons(collection) { + return collection.length - 1 ? true : false; + }, + + _splitValues(values) { + if (values && values.length) { + const emojiList = []; + const emojis = values.split("|").filter(Boolean); + emojis.forEach((emojiName) => { + const emoji = { + isEditable: true, + isEditing: false, + }; + emoji.value = emojiName; + emoji.emojiUrl = emojiUrlFor(emojiName); + + emojiList.push(emoji); + }); + + return emojiList; + } else { + return []; + } + }, + + @action + editValue(index) { + this.closeEmojiPicker(); + schedule("afterRender", () => { + if (parseInt(index, 10) >= 0) { + const item = this.collection[index]; + if (item.isEditable) { + set(item, "isEditing", true); + } + } + + this.set("isEditorFocused", true); + later(() => { + if (this.element && !this.isDestroying && !this.isDestroyed) { + this.set("emojiPickerIsActive", true); + } + }, 100); + }); + }, + + @action + removeValue(value) { + this._removeValue(value); + }, + + @action + shift(operation, index) { + let futureIndex = index + operation; + + if (futureIndex > this.collection.length - 1) { + futureIndex = 0; + } else if (futureIndex < 0) { + futureIndex = this.collection.length - 1; + } + + const shiftedEmoji = this.collection[index]; + this.collection.removeAt(index); + this.collection.insertAt(futureIndex, shiftedEmoji); + + this._saveValues(); + }, + + _validateInput(input) { + this.set("validationMessage", null); + + if (!emojiUrlFor(input)) { + this.set( + "validationMessage", + I18n.t("admin.site_settings.emoji_list.invalid_input") + ); + return false; + } + + return true; + }, + + _removeValue(value) { + this.collection.removeObject(value); + this._saveValues(); + }, + + _replaceValue(index, newValue) { + const item = this.collection[index]; + if (item.value === newValue) { + return; + } + set(item, "value", newValue); + this._saveValues(); + }, + + _saveValues() { + this.set("values", this.collection.mapBy("value").join("|")); + }, +}); diff --git a/app/assets/javascripts/admin/addon/components/highlighted-code.js b/app/assets/javascripts/admin/addon/components/highlighted-code.js index f5486db8d9..21cfaf6b15 100644 --- a/app/assets/javascripts/admin/addon/components/highlighted-code.js +++ b/app/assets/javascripts/admin/addon/components/highlighted-code.js @@ -1,5 +1,5 @@ +import { observes, on } from "discourse-common/utils/decorators"; import Component from "@ember/component"; -import { on, observes } from "discourse-common/utils/decorators"; import highlightSyntax from "discourse/lib/highlight-syntax"; export default Component.extend({ diff --git a/app/assets/javascripts/admin/addon/components/ip-lookup.js b/app/assets/javascripts/admin/addon/components/ip-lookup.js index db19cb25c3..99929048a4 100644 --- a/app/assets/javascripts/admin/addon/components/ip-lookup.js +++ b/app/assets/javascripts/admin/addon/components/ip-lookup.js @@ -1,12 +1,12 @@ -import I18n from "I18n"; -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"; +import Component from "@ember/component"; +import EmberObject from "@ember/object"; +import I18n from "I18n"; +import { ajax } from "discourse/lib/ajax"; import bootbox from "bootbox"; +import copyText from "discourse/lib/copy-text"; +import discourseComputed from "discourse-common/utils/decorators"; +import { later } from "@ember/runloop"; export default Component.extend({ classNames: ["ip-lookup"], diff --git a/app/assets/javascripts/admin/addon/components/penalty-post-action.js b/app/assets/javascripts/admin/addon/components/penalty-post-action.js index 4a46bbbea7..e010272447 100644 --- a/app/assets/javascripts/admin/addon/components/penalty-post-action.js +++ b/app/assets/javascripts/admin/addon/components/penalty-post-action.js @@ -1,9 +1,9 @@ -import I18n from "I18n"; import discourseComputed, { afterRender, } from "discourse-common/utils/decorators"; -import { equal } from "@ember/object/computed"; import Component from "@ember/component"; +import I18n from "I18n"; +import { equal } from "@ember/object/computed"; const ACTIONS = ["delete", "delete_replies", "edit", "none"]; diff --git a/app/assets/javascripts/admin/addon/components/permalink-form.js b/app/assets/javascripts/admin/addon/components/permalink-form.js index b7238f5700..da8f947170 100644 --- a/app/assets/javascripts/admin/addon/components/permalink-form.js +++ b/app/assets/javascripts/admin/addon/components/permalink-form.js @@ -1,10 +1,10 @@ -import I18n from "I18n"; -import { schedule } from "@ember/runloop"; import Component from "@ember/component"; -import discourseComputed from "discourse-common/utils/decorators"; -import { fmt } from "discourse/lib/computed"; +import I18n from "I18n"; import Permalink from "admin/models/permalink"; import bootbox from "bootbox"; +import discourseComputed from "discourse-common/utils/decorators"; +import { fmt } from "discourse/lib/computed"; +import { schedule } from "@ember/runloop"; export default Component.extend({ classNames: ["permalink-form"], @@ -69,9 +69,13 @@ export default Component.extend({ this.set("formSubmitted", false); let error; - if (e.responseJSON && e.responseJSON.errors) { + if ( + e.jqXHR && + e.jqXHR.responseJSON && + e.jqXHR.responseJSON.errors + ) { error = I18n.t("generic_error_with_reason", { - error: e.responseJSON.errors.join(". "), + error: e.jqXHR.responseJSON.errors.join(". "), }); } else { error = I18n.t("generic_error"); diff --git a/app/assets/javascripts/admin/addon/components/report-filters/bool.js b/app/assets/javascripts/admin/addon/components/report-filters/bool.js index 0a752c54e2..544709791c 100644 --- a/app/assets/javascripts/admin/addon/components/report-filters/bool.js +++ b/app/assets/javascripts/admin/addon/components/report-filters/bool.js @@ -1,5 +1,5 @@ -import { action } from "@ember/object"; import FilterComponent from "admin/components/report-filters/filter"; +import { action } from "@ember/object"; export default FilterComponent.extend({ checked: false, diff --git a/app/assets/javascripts/admin/addon/components/report-filters/category.js b/app/assets/javascripts/admin/addon/components/report-filters/category.js index 4cbe965193..877c3aee7a 100644 --- a/app/assets/javascripts/admin/addon/components/report-filters/category.js +++ b/app/assets/javascripts/admin/addon/components/report-filters/category.js @@ -1,6 +1,6 @@ +import FilterComponent from "admin/components/report-filters/filter"; import { action } from "@ember/object"; import { readOnly } from "@ember/object/computed"; -import FilterComponent from "admin/components/report-filters/filter"; export default FilterComponent.extend({ category: readOnly("filter.default"), diff --git a/app/assets/javascripts/admin/addon/components/report-filters/group.js b/app/assets/javascripts/admin/addon/components/report-filters/group.js index 5cf379332b..30dd4c7b1b 100644 --- a/app/assets/javascripts/admin/addon/components/report-filters/group.js +++ b/app/assets/javascripts/admin/addon/components/report-filters/group.js @@ -1,5 +1,5 @@ -import { computed } from "@ember/object"; import FilterComponent from "admin/components/report-filters/filter"; +import { computed } from "@ember/object"; export default FilterComponent.extend({ classNames: ["group-filter"], diff --git a/app/assets/javascripts/admin/addon/components/resumable-upload.js b/app/assets/javascripts/admin/addon/components/resumable-upload.js index 0afa189a1d..8e3ce18e8c 100644 --- a/app/assets/javascripts/admin/addon/components/resumable-upload.js +++ b/app/assets/javascripts/admin/addon/components/resumable-upload.js @@ -1,9 +1,9 @@ -import getURL from "discourse-common/lib/get-url"; -import I18n from "I18n"; +import discourseComputed, { on } from "discourse-common/utils/decorators"; import { later, schedule } from "@ember/runloop"; import Component from "@ember/component"; +import I18n from "I18n"; +import getURL from "discourse-common/lib/get-url"; import { iconHTML } from "discourse-common/lib/icon-library"; -import discourseComputed, { on } from "discourse-common/utils/decorators"; /*global Resumable:true */ diff --git a/app/assets/javascripts/admin/addon/components/screened-ip-address-form.js b/app/assets/javascripts/admin/addon/components/screened-ip-address-form.js index e738c81b85..40dd71ad9a 100644 --- a/app/assets/javascripts/admin/addon/components/screened-ip-address-form.js +++ b/app/assets/javascripts/admin/addon/components/screened-ip-address-form.js @@ -1,9 +1,9 @@ -import I18n from "I18n"; import discourseComputed, { on } from "discourse-common/utils/decorators"; -import { schedule } from "@ember/runloop"; import Component from "@ember/component"; -import bootbox from "bootbox"; +import I18n from "I18n"; import ScreenedIpAddress from "admin/models/screened-ip-address"; +import bootbox from "bootbox"; +import { schedule } from "@ember/runloop"; /** A form to create an IP address that will be blocked or allowed. diff --git a/app/assets/javascripts/admin/addon/components/secret-value-list.js b/app/assets/javascripts/admin/addon/components/secret-value-list.js index 9b59c26d3a..cd63e3b0ad 100644 --- a/app/assets/javascripts/admin/addon/components/secret-value-list.js +++ b/app/assets/javascripts/admin/addon/components/secret-value-list.js @@ -1,6 +1,6 @@ +import Component from "@ember/component"; import I18n from "I18n"; import { isEmpty } from "@ember/utils"; -import Component from "@ember/component"; import { on } from "discourse-common/utils/decorators"; import { set } from "@ember/object"; @@ -94,9 +94,9 @@ export default Component.extend({ _splitValues(values, delimiter) { if (values && values.length) { const keys = ["key", "secret"]; - var res = []; + let res = []; values.split(delimiter).forEach(function (str) { - var object = {}; + let object = {}; str.split("|").forEach(function (a, i) { object[keys[i]] = a; }); diff --git a/app/assets/javascripts/admin/addon/components/simple-list.js b/app/assets/javascripts/admin/addon/components/simple-list.js index f3db4c5973..96136c98e2 100644 --- a/app/assets/javascripts/admin/addon/components/simple-list.js +++ b/app/assets/javascripts/admin/addon/components/simple-list.js @@ -1,6 +1,6 @@ -import { empty } from "@ember/object/computed"; import Component from "@ember/component"; import { action } from "@ember/object"; +import { empty } from "@ember/object/computed"; import { on } from "discourse-common/utils/decorators"; export default Component.extend({ diff --git a/app/assets/javascripts/admin/addon/components/site-setting.js b/app/assets/javascripts/admin/addon/components/site-setting.js index 695ddac781..888daa3117 100644 --- a/app/assets/javascripts/admin/addon/components/site-setting.js +++ b/app/assets/javascripts/admin/addon/components/site-setting.js @@ -1,7 +1,7 @@ -import Component from "@ember/component"; import BufferedContent from "discourse/mixins/buffered-content"; -import SiteSetting from "admin/models/site-setting"; +import Component from "@ember/component"; import SettingComponent from "admin/mixins/setting-component"; +import SiteSetting from "admin/models/site-setting"; import { readOnly } from "@ember/object/computed"; export default Component.extend(BufferedContent, SettingComponent, { diff --git a/app/assets/javascripts/admin/addon/components/site-settings/bool.js b/app/assets/javascripts/admin/addon/components/site-settings/bool.js index a565648c6d..70d7c59ae0 100644 --- a/app/assets/javascripts/admin/addon/components/site-settings/bool.js +++ b/app/assets/javascripts/admin/addon/components/site-settings/bool.js @@ -1,6 +1,6 @@ +import Component from "@ember/component"; import discourseComputed from "discourse-common/utils/decorators"; import { isEmpty } from "@ember/utils"; -import Component from "@ember/component"; export default Component.extend({ @discourseComputed("value") diff --git a/app/assets/javascripts/admin/addon/components/site-settings/category-list.js b/app/assets/javascripts/admin/addon/components/site-settings/category-list.js index 0369756c08..9dc792e6de 100644 --- a/app/assets/javascripts/admin/addon/components/site-settings/category-list.js +++ b/app/assets/javascripts/admin/addon/components/site-settings/category-list.js @@ -1,5 +1,5 @@ -import Component from "@ember/component"; import Category from "discourse/models/category"; +import Component from "@ember/component"; import { computed } from "@ember/object"; export default Component.extend({ diff --git a/app/assets/javascripts/admin/addon/components/site-settings/color.js b/app/assets/javascripts/admin/addon/components/site-settings/color.js index 7e5e71effc..8d098cb7d8 100644 --- a/app/assets/javascripts/admin/addon/components/site-settings/color.js +++ b/app/assets/javascripts/admin/addon/components/site-settings/color.js @@ -1,5 +1,5 @@ +import { action, computed } from "@ember/object"; import Component from "@ember/component"; -import { computed, action } from "@ember/object"; function RGBToHex(rgb) { // Choose correct separator diff --git a/app/assets/javascripts/admin/addon/components/site-settings/group-list.js b/app/assets/javascripts/admin/addon/components/site-settings/group-list.js index f7ea894503..ad9dbbfd1c 100644 --- a/app/assets/javascripts/admin/addon/components/site-settings/group-list.js +++ b/app/assets/javascripts/admin/addon/components/site-settings/group-list.js @@ -1,5 +1,5 @@ -import { computed } from "@ember/object"; import Component from "@ember/component"; +import { computed } from "@ember/object"; export default Component.extend({ tokenSeparator: "|", diff --git a/app/assets/javascripts/admin/addon/components/site-settings/string.js b/app/assets/javascripts/admin/addon/components/site-settings/string.js new file mode 100644 index 0000000000..2224c8745f --- /dev/null +++ b/app/assets/javascripts/admin/addon/components/site-settings/string.js @@ -0,0 +1,20 @@ +import { action } from "@ember/object"; +import Component from "@ember/component"; +import showModal from "discourse/lib/show-modal"; + +export default Component.extend({ + @action + launchJsonEditorModal() { + const schemaModal = showModal("json-schema-editor", { + model: { + value: this.value, + settingName: this.setting.setting, + jsonSchema: this.setting.json_schema, + }, + }); + + schemaModal.set("onClose", () => { + this.set("value", schemaModal.model.value); + }); + }, +}); diff --git a/app/assets/javascripts/admin/addon/components/site-settings/tag-list.js b/app/assets/javascripts/admin/addon/components/site-settings/tag-list.js index e5723668d7..d679063df1 100644 --- a/app/assets/javascripts/admin/addon/components/site-settings/tag-list.js +++ b/app/assets/javascripts/admin/addon/components/site-settings/tag-list.js @@ -1,6 +1,6 @@ -import discourseComputed from "discourse-common/utils/decorators"; import Component from "@ember/component"; import { action } from "@ember/object"; +import discourseComputed from "discourse-common/utils/decorators"; export default Component.extend({ @discourseComputed("value") diff --git a/app/assets/javascripts/admin/addon/components/site-text-summary.js b/app/assets/javascripts/admin/addon/components/site-text-summary.js index abe4f5fa63..071091f12b 100644 --- a/app/assets/javascripts/admin/addon/components/site-text-summary.js +++ b/app/assets/javascripts/admin/addon/components/site-text-summary.js @@ -1,6 +1,6 @@ import Component from "@ember/component"; -import { on } from "discourse-common/utils/decorators"; import highlightHTML from "discourse/lib/highlight-html"; +import { on } from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["site-text"], diff --git a/app/assets/javascripts/admin/addon/components/suspension-details.js b/app/assets/javascripts/admin/addon/components/suspension-details.js index f9edc0e225..e99a7e1c5b 100644 --- a/app/assets/javascripts/admin/addon/components/suspension-details.js +++ b/app/assets/javascripts/admin/addon/components/suspension-details.js @@ -1,8 +1,8 @@ import Component from "@ember/component"; -import discourseComputed from "discourse-common/utils/decorators"; import I18n from "I18n"; -import { equal } from "@ember/object/computed"; import { action } from "@ember/object"; +import discourseComputed from "discourse-common/utils/decorators"; +import { equal } from "@ember/object/computed"; const CUSTOM_REASON_KEY = "custom"; diff --git a/app/assets/javascripts/admin/addon/components/tags-uploader.js b/app/assets/javascripts/admin/addon/components/tags-uploader.js index 3c547adf8d..1dc3bfc467 100644 --- a/app/assets/javascripts/admin/addon/components/tags-uploader.js +++ b/app/assets/javascripts/admin/addon/components/tags-uploader.js @@ -1,7 +1,7 @@ -import I18n from "I18n"; -import { alias } from "@ember/object/computed"; import Component from "@ember/component"; +import I18n from "I18n"; import UploadMixin from "discourse/mixins/upload"; +import { alias } from "@ember/object/computed"; import bootbox from "bootbox"; export default Component.extend(UploadMixin, { diff --git a/app/assets/javascripts/admin/addon/components/theme-setting-editor.js b/app/assets/javascripts/admin/addon/components/theme-setting-editor.js index cf87683ff7..8ceaf32bbb 100644 --- a/app/assets/javascripts/admin/addon/components/theme-setting-editor.js +++ b/app/assets/javascripts/admin/addon/components/theme-setting-editor.js @@ -1,5 +1,5 @@ -import Component from "@ember/component"; import BufferedContent from "discourse/mixins/buffered-content"; +import Component from "@ember/component"; import SettingComponent from "admin/mixins/setting-component"; import { ajax } from "discourse/lib/ajax"; import { url } from "discourse/lib/computed"; diff --git a/app/assets/javascripts/admin/addon/components/theme-setting-relatives-selector.js b/app/assets/javascripts/admin/addon/components/theme-setting-relatives-selector.js index b23760f665..4f02a14768 100644 --- a/app/assets/javascripts/admin/addon/components/theme-setting-relatives-selector.js +++ b/app/assets/javascripts/admin/addon/components/theme-setting-relatives-selector.js @@ -1,5 +1,5 @@ -import Component from "@ember/component"; import BufferedContent from "discourse/mixins/buffered-content"; +import Component from "@ember/component"; import SettingComponent from "admin/mixins/setting-component"; export default Component.extend(BufferedContent, SettingComponent, { diff --git a/app/assets/javascripts/admin/addon/components/theme-translation.js b/app/assets/javascripts/admin/addon/components/theme-translation.js index 6174aeb4eb..7a598db1c8 100644 --- a/app/assets/javascripts/admin/addon/components/theme-translation.js +++ b/app/assets/javascripts/admin/addon/components/theme-translation.js @@ -1,7 +1,7 @@ -import { alias } from "@ember/object/computed"; -import Component from "@ember/component"; import BufferedContent from "discourse/mixins/buffered-content"; +import Component from "@ember/component"; import SettingComponent from "admin/mixins/setting-component"; +import { alias } from "@ember/object/computed"; export default Component.extend(BufferedContent, SettingComponent, { layoutName: "admin/templates/components/site-setting", diff --git a/app/assets/javascripts/admin/addon/components/themes-list-item.js b/app/assets/javascripts/admin/addon/components/themes-list-item.js index d8af9eabc7..edc1783535 100644 --- a/app/assets/javascripts/admin/addon/components/themes-list-item.js +++ b/app/assets/javascripts/admin/addon/components/themes-list-item.js @@ -1,10 +1,10 @@ -import { gt, and } from "@ember/object/computed"; -import { schedule } from "@ember/runloop"; -import Component from "@ember/component"; +import { and, gt } from "@ember/object/computed"; import discourseComputed, { observes } from "discourse-common/utils/decorators"; -import { iconHTML } from "discourse-common/lib/icon-library"; +import Component from "@ember/component"; import { escape } from "pretty-text/sanitizer"; +import { iconHTML } from "discourse-common/lib/icon-library"; import { isTesting } from "discourse-common/config/environment"; +import { schedule } from "@ember/runloop"; const MAX_COMPONENTS = 4; diff --git a/app/assets/javascripts/admin/addon/components/themes-list.js b/app/assets/javascripts/admin/addon/components/themes-list.js index cdad5e454a..715ca9ccee 100644 --- a/app/assets/javascripts/admin/addon/components/themes-list.js +++ b/app/assets/javascripts/admin/addon/components/themes-list.js @@ -1,6 +1,6 @@ -import { gt, equal } from "@ember/object/computed"; +import { COMPONENTS, THEMES } from "admin/models/theme"; +import { equal, gt } from "@ember/object/computed"; import Component from "@ember/component"; -import { THEMES, COMPONENTS } from "admin/models/theme"; import discourseComputed from "discourse-common/utils/decorators"; import { inject as service } from "@ember/service"; diff --git a/app/assets/javascripts/admin/addon/components/value-list.js b/app/assets/javascripts/admin/addon/components/value-list.js index 9951eee3f4..aa89de7f38 100644 --- a/app/assets/javascripts/admin/addon/components/value-list.js +++ b/app/assets/javascripts/admin/addon/components/value-list.js @@ -1,7 +1,7 @@ import discourseComputed, { on } 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 { makeArray } from "discourse-common/lib/helpers"; export default Component.extend({ classNameBindings: [":value-list"], diff --git a/app/assets/javascripts/admin/addon/components/watched-word-form.js b/app/assets/javascripts/admin/addon/components/watched-word-form.js index 5dc9d7b8e6..cf957148e2 100644 --- a/app/assets/javascripts/admin/addon/components/watched-word-form.js +++ b/app/assets/javascripts/admin/addon/components/watched-word-form.js @@ -1,13 +1,14 @@ -import I18n from "I18n"; -import { isEmpty } from "@ember/utils"; -import { schedule } from "@ember/runloop"; +import discourseComputed, { + observes, + on, +} from "discourse-common/utils/decorators"; import Component from "@ember/component"; +import I18n from "I18n"; import WatchedWord from "admin/models/watched-word"; import bootbox from "bootbox"; -import discourseComputed, { - on, - observes, -} from "discourse-common/utils/decorators"; +import { equal } from "@ember/object/computed"; +import { isEmpty } from "@ember/utils"; +import { schedule } from "@ember/runloop"; export default Component.extend({ classNames: ["watched-word-form"], @@ -15,6 +16,9 @@ export default Component.extend({ actionKey: null, showMessage: false, + canReplace: equal("actionKey", "replace"), + canTag: equal("actionKey", "tag"), + @discourseComputed("regularExpressions") placeholderKey(regularExpressions) { return ( @@ -56,6 +60,7 @@ export default Component.extend({ const watchedWord = WatchedWord.create({ word: this.word, + replacement: this.canReplace || this.canTag ? this.replacement : null, action: this.actionKey, }); @@ -64,6 +69,7 @@ export default Component.extend({ .then((result) => { this.setProperties({ word: "", + replacement: "", formSubmitted: false, showMessage: true, message: I18n.t("admin.watched_words.form.success"), diff --git a/app/assets/javascripts/admin/addon/components/watched-word-uploader.js b/app/assets/javascripts/admin/addon/components/watched-word-uploader.js index 800c0d4988..040154d30b 100644 --- a/app/assets/javascripts/admin/addon/components/watched-word-uploader.js +++ b/app/assets/javascripts/admin/addon/components/watched-word-uploader.js @@ -1,14 +1,14 @@ -import I18n from "I18n"; -import discourseComputed from "discourse-common/utils/decorators"; -import { alias } from "@ember/object/computed"; import Component from "@ember/component"; +import I18n from "I18n"; import UploadMixin from "discourse/mixins/upload"; +import { alias } from "@ember/object/computed"; import bootbox from "bootbox"; +import discourseComputed from "discourse-common/utils/decorators"; export default Component.extend(UploadMixin, { type: "txt", classNames: "watched-words-uploader", - uploadUrl: "/admin/logs/watched_words/upload", + uploadUrl: "/admin/customize/watched_words/upload", addDisabled: alias("uploading"), validateUploadedFilesOptions() { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-api-keys-index.js b/app/assets/javascripts/admin/addon/controllers/admin-api-keys-index.js index 880a2c6435..5b4ee4ee0a 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-api-keys-index.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-api-keys-index.js @@ -1,5 +1,5 @@ -import { popupAjaxError } from "discourse/lib/ajax-error"; import Controller from "@ember/controller"; +import { popupAjaxError } from "discourse/lib/ajax-error"; export default Controller.extend({ actions: { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-api-keys-new.js b/app/assets/javascripts/admin/addon/controllers/admin-api-keys-new.js index 06e495109c..c90316369a 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-api-keys-new.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-api-keys-new.js @@ -1,8 +1,9 @@ -import I18n from "I18n"; -import { isBlank } from "@ember/utils"; import Controller from "@ember/controller"; +import I18n from "I18n"; import discourseComputed from "discourse-common/utils/decorators"; +import { isBlank } from "@ember/utils"; import { popupAjaxError } from "discourse/lib/ajax-error"; +import { get } from "@ember/object"; import showModal from "discourse/lib/show-modal"; export default Controller.extend({ @@ -30,6 +31,10 @@ export default Controller.extend({ }, actions: { + updateUsername(selected) { + this.set("model.username", get(selected, "firstObject")); + }, + changeUserMode(value) { if (value === "all") { this.model.set("username", null); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-api-keys-show.js b/app/assets/javascripts/admin/addon/controllers/admin-api-keys-show.js index 11251a45e6..28a883d985 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-api-keys-show.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-api-keys-show.js @@ -1,8 +1,8 @@ -import { bufferedProperty } from "discourse/mixins/buffered-content"; import Controller from "@ember/controller"; +import { bufferedProperty } from "discourse/mixins/buffered-content"; +import { empty } from "@ember/object/computed"; import { isEmpty } from "@ember/utils"; import { popupAjaxError } from "discourse/lib/ajax-error"; -import { empty } from "@ember/object/computed"; import showModal from "discourse/lib/show-modal"; export default Controller.extend(bufferedProperty("model"), { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-backups-index.js b/app/assets/javascripts/admin/addon/controllers/admin-backups-index.js index c32e70f633..c53adeecf9 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-backups-index.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-backups-index.js @@ -1,10 +1,10 @@ -import I18n from "I18n"; -import { alias, equal } from "@ember/object/computed"; import Controller, { inject as controller } from "@ember/controller"; +import { alias, equal } from "@ember/object/computed"; +import { i18n, setting } from "discourse/lib/computed"; +import I18n from "I18n"; import { ajax } from "discourse/lib/ajax"; -import discourseComputed from "discourse-common/utils/decorators"; -import { setting, i18n } from "discourse/lib/computed"; import bootbox from "bootbox"; +import discourseComputed from "discourse-common/utils/decorators"; export default Controller.extend({ adminBackups: controller(), diff --git a/app/assets/javascripts/admin/addon/controllers/admin-backups-logs.js b/app/assets/javascripts/admin/addon/controllers/admin-backups-logs.js index 8c70f05439..e8a1694e2c 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-backups-logs.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-backups-logs.js @@ -1,5 +1,5 @@ -import { alias } from "@ember/object/computed"; import Controller, { inject as controller } from "@ember/controller"; +import { alias } from "@ember/object/computed"; export default Controller.extend({ adminBackups: controller(), diff --git a/app/assets/javascripts/admin/addon/controllers/admin-backups.js b/app/assets/javascripts/admin/addon/controllers/admin-backups.js index 83a93d8898..77dfbc3132 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-backups.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-backups.js @@ -1,4 +1,4 @@ -import { not, and } from "@ember/object/computed"; +import { and, not } from "@ember/object/computed"; import Controller from "@ember/controller"; export default Controller.extend({ noOperationIsRunning: not("model.isOperationRunning"), diff --git a/app/assets/javascripts/admin/addon/controllers/admin-badges-award.js b/app/assets/javascripts/admin/addon/controllers/admin-badges-award.js index f3ef51d701..218b3cb0e0 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-badges-award.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-badges-award.js @@ -1,8 +1,8 @@ -import I18n from "I18n"; import Controller from "@ember/controller"; +import I18n from "I18n"; import { ajax } from "discourse/lib/ajax"; -import { popupAjaxError } from "discourse/lib/ajax-error"; import bootbox from "bootbox"; +import { popupAjaxError } from "discourse/lib/ajax-error"; export default Controller.extend({ saving: false, diff --git a/app/assets/javascripts/admin/addon/controllers/admin-badges-show.js b/app/assets/javascripts/admin/addon/controllers/admin-badges-show.js index 3fec918ba9..5e1ed5bb21 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-badges-show.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-badges-show.js @@ -1,23 +1,31 @@ -import I18n from "I18n"; -import discourseComputed, { observes } from "discourse-common/utils/decorators"; -import { reads } from "@ember/object/computed"; import Controller, { inject as controller } from "@ember/controller"; -import { popupAjaxError } from "discourse/lib/ajax-error"; -import { bufferedProperty } from "discourse/mixins/buffered-content"; -import { propertyNotEqual } from "discourse/lib/computed"; -import { run } from "@ember/runloop"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; +import I18n from "I18n"; import bootbox from "bootbox"; +import { bufferedProperty } from "discourse/mixins/buffered-content"; +import { popupAjaxError } from "discourse/lib/ajax-error"; +import { propertyNotEqual } from "discourse/lib/computed"; +import { equal, reads } from "@ember/object/computed"; +import { run } from "@ember/runloop"; +import { action } from "@ember/object"; +import getURL from "discourse-common/lib/get-url"; + +const IMAGE = "image"; +const ICON = "icon"; export default Controller.extend(bufferedProperty("model"), { adminBadges: controller(), saving: false, savingStatus: "", + selectedGraphicType: null, badgeTypes: reads("adminBadges.badgeTypes"), badgeGroupings: reads("adminBadges.badgeGroupings"), badgeTriggers: reads("adminBadges.badgeTriggers"), protectedSystemFields: reads("adminBadges.protectedSystemFields"), readOnly: reads("buffered.system"), showDisplayName: propertyNotEqual("name", "displayName"), + iconSelectorSelected: equal("selectedGraphicType", ICON), + imageUploaderSelected: equal("selectedGraphicType", IMAGE), init() { this._super(...arguments); @@ -56,12 +64,52 @@ export default Controller.extend(bufferedProperty("model"), { return modelQuery && modelQuery.trim().length > 0; }, + @discourseComputed("model.i18n_name") + textCustomizationPrefix(i18n_name) { + return `badges.${i18n_name}.`; + }, + @observes("model.id") _resetSaving: function () { this.set("saving", false); this.set("savingStatus", ""); }, + showIconSelector() { + this.set("selectedGraphicType", ICON); + }, + + showImageUploader() { + this.set("selectedGraphicType", IMAGE); + }, + + @action + changeGraphicType(newType) { + if (newType === IMAGE) { + this.showImageUploader(); + } else if (newType === ICON) { + this.showIconSelector(); + } else { + throw new Error(`Unknown badge graphic type "${newType}"`); + } + }, + + @action + setImage(upload) { + this.buffered.setProperties({ + image_upload_id: upload.id, + image_url: getURL(upload.url), + }); + }, + + @action + removeImage() { + this.buffered.setProperties({ + image_upload_id: null, + image_url: null, + }); + }, + actions: { save() { if (!this.saving) { @@ -77,7 +125,7 @@ export default Controller.extend(bufferedProperty("model"), { "description", "long_description", "icon", - "image", + "image_upload_id", "query", "badge_grouping_id", "trigger", @@ -105,7 +153,7 @@ export default Controller.extend(bufferedProperty("model"), { const data = {}; const buffered = this.buffered; fields.forEach(function (field) { - var d = buffered.get(field); + let d = buffered.get(field); if (boolFields.includes(field)) { d = !!d; } diff --git a/app/assets/javascripts/admin/addon/controllers/admin-badges.js b/app/assets/javascripts/admin/addon/controllers/admin-badges.js index 433c49bac5..11d78fc4cc 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-badges.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-badges.js @@ -1,6 +1,6 @@ import Controller from "@ember/controller"; -import { inject as service } from "@ember/service"; import discourseComputed from "discourse-common/utils/decorators"; +import { inject as service } from "@ember/service"; export default Controller.extend({ routing: service("-routing"), diff --git a/app/assets/javascripts/admin/addon/controllers/admin-customize-colors-show.js b/app/assets/javascripts/admin/addon/controllers/admin-customize-colors-show.js index 5fa6218839..37394fd17f 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-customize-colors-show.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-customize-colors-show.js @@ -1,8 +1,8 @@ +import Controller from "@ember/controller"; import I18n from "I18n"; +import bootbox from "bootbox"; import discourseComputed from "discourse-common/utils/decorators"; import { later } from "@ember/runloop"; -import Controller from "@ember/controller"; -import bootbox from "bootbox"; export default Controller.extend({ @discourseComputed("model.colors", "onlyOverridden") diff --git a/app/assets/javascripts/admin/addon/controllers/admin-customize-colors.js b/app/assets/javascripts/admin/addon/controllers/admin-customize-colors.js index 01898b161a..7f06b1f3f0 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-customize-colors.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-customize-colors.js @@ -1,8 +1,8 @@ -import I18n from "I18n"; -import EmberObject from "@ember/object"; import Controller from "@ember/controller"; -import showModal from "discourse/lib/show-modal"; +import EmberObject from "@ember/object"; +import I18n from "I18n"; import discourseComputed from "discourse-common/utils/decorators"; +import showModal from "discourse/lib/show-modal"; export default Controller.extend({ @discourseComputed("model.@each.id") diff --git a/app/assets/javascripts/admin/addon/controllers/admin-customize-email-style-edit.js b/app/assets/javascripts/admin/addon/controllers/admin-customize-email-style-edit.js index f1190600e8..79813399b2 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-customize-email-style-edit.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-customize-email-style-edit.js @@ -1,7 +1,7 @@ -import I18n from "I18n"; -import discourseComputed from "discourse-common/utils/decorators"; import Controller from "@ember/controller"; +import I18n from "I18n"; import bootbox from "bootbox"; +import discourseComputed from "discourse-common/utils/decorators"; export default Controller.extend({ @discourseComputed("model.isSaving") diff --git a/app/assets/javascripts/admin/addon/controllers/admin-customize-email-templates-edit.js b/app/assets/javascripts/admin/addon/controllers/admin-customize-email-templates-edit.js index f5f7a563ef..d20f812ca3 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-customize-email-templates-edit.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-customize-email-templates-edit.js @@ -1,10 +1,10 @@ -import I18n from "I18n"; -import discourseComputed from "discourse-common/utils/decorators"; import Controller, { inject as controller } from "@ember/controller"; -import { popupAjaxError } from "discourse/lib/ajax-error"; -import { bufferedProperty } from "discourse/mixins/buffered-content"; +import I18n from "I18n"; import { action } from "@ember/object"; import bootbox from "bootbox"; +import { bufferedProperty } from "discourse/mixins/buffered-content"; +import discourseComputed from "discourse-common/utils/decorators"; +import { popupAjaxError } from "discourse/lib/ajax-error"; export default Controller.extend(bufferedProperty("emailTemplate"), { adminCustomizeEmailTemplates: controller(), diff --git a/app/assets/javascripts/admin/addon/controllers/admin-customize-email-templates.js b/app/assets/javascripts/admin/addon/controllers/admin-customize-email-templates.js index 415cf1d157..a3d7f360e0 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-customize-email-templates.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-customize-email-templates.js @@ -1,6 +1,6 @@ -import { sort } from "@ember/object/computed"; -import { action } from "@ember/object"; import Controller from "@ember/controller"; +import { action } from "@ember/object"; +import { sort } from "@ember/object/computed"; export default Controller.extend({ sortedTemplates: sort("emailTemplates", "titleSorting"), diff --git a/app/assets/javascripts/admin/addon/controllers/admin-customize-robots-txt.js b/app/assets/javascripts/admin/addon/controllers/admin-customize-robots-txt.js index 7da0720744..a178f2c999 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-customize-robots-txt.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-customize-robots-txt.js @@ -1,7 +1,7 @@ -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 { not } from "@ember/object/computed"; import { propertyEqual } from "discourse/lib/computed"; export default Controller.extend(bufferedProperty("model"), { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-edit.js b/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-edit.js index 889d90a921..2e1536d181 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-edit.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-edit.js @@ -1,7 +1,7 @@ -import I18n from "I18n"; import Controller from "@ember/controller"; -import { url } from "discourse/lib/computed"; +import I18n from "I18n"; import discourseComputed from "discourse-common/utils/decorators"; +import { url } from "discourse/lib/computed"; export default Controller.extend({ section: null, @@ -64,5 +64,9 @@ export default Controller.extend({ } } }, + + goBack() { + this.replaceRoute(this.showRouteName, this.model.id); + }, }, }); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js b/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js index 9e9afda71d..000e1d3fc8 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js @@ -1,21 +1,21 @@ -import I18n from "I18n"; -import { makeArray } from "discourse-common/lib/helpers"; +import { COMPONENTS, THEMES } from "admin/models/theme"; import { empty, filterBy, - match, mapBy, + match, notEmpty, } from "@ember/object/computed"; import Controller from "@ember/controller"; +import EmberObject from "@ember/object"; +import I18n from "I18n"; +import ThemeSettings from "admin/models/theme-settings"; +import bootbox from "bootbox"; import discourseComputed from "discourse-common/utils/decorators"; -import { url } from "discourse/lib/computed"; +import { makeArray } from "discourse-common/lib/helpers"; 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"; -import bootbox from "bootbox"; +import { url } from "discourse/lib/computed"; const THEME_UPLOAD_VAR = 2; @@ -231,6 +231,11 @@ export default Controller.extend({ : remoteThemeUrl; }, + @discourseComputed("model.user.id", "model.default") + showConvert(userId, defaultTheme) { + return userId > 0 && !defaultTheme; + }, + actions: { updateToLatest() { this.set("updatingRemote", true); @@ -369,25 +374,29 @@ export default Controller.extend({ switchType() { const relatives = this.get("model.component") - ? this.parentThemes + ? this.get("model.parentThemes") : this.get("model.childThemes"); + + let message = I18n.t(`${this.convertKey}_alert_generic`); + if (relatives && relatives.length > 0) { - const names = relatives.map((relative) => relative.get("name")); - bootbox.confirm( - I18n.t(`${this.convertKey}_alert`, { - relatives: names.join(", "), - }), - I18n.t("no_value"), - I18n.t("yes_value"), - (result) => { - if (result) { - this.commitSwitchType(); - } - } - ); - } else { - this.commitSwitchType(); + message = I18n.t(`${this.convertKey}_alert`, { + relatives: relatives + .map((relative) => relative.get("name")) + .join(", "), + }); } + + bootbox.confirm( + message, + I18n.t("no_value"), + I18n.t("yes_value"), + (result) => { + if (result) { + this.commitSwitchType(); + } + } + ); }, enableComponent() { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-customize-themes.js b/app/assets/javascripts/admin/addon/controllers/admin-customize-themes.js index 3ffe9301f1..a598ade6f5 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-customize-themes.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-customize-themes.js @@ -1,6 +1,6 @@ import Controller from "@ember/controller"; -import discourseComputed from "discourse-common/utils/decorators"; import { THEMES } from "admin/models/theme"; +import discourseComputed from "discourse-common/utils/decorators"; export default Controller.extend({ currentTab: THEMES, @@ -15,8 +15,8 @@ export default Controller.extend({ return themes.filter((t) => t.get("component")); }, - @discourseComputed("model", "model.@each.component") - installedThemes(themes) { - return themes.map((t) => t.name); + @discourseComputed("model.content") + installedThemes(content) { + return content || []; }, }); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-dashboard-general.js b/app/assets/javascripts/admin/addon/controllers/admin-dashboard-general.js index fe97fb0855..a831b686bc 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-dashboard-general.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-dashboard-general.js @@ -1,13 +1,13 @@ -import I18n from "I18n"; -import discourseComputed from "discourse-common/utils/decorators"; -import { makeArray } from "discourse-common/lib/helpers"; import Controller, { inject } from "@ember/controller"; -import { setting } from "discourse/lib/computed"; import AdminDashboard from "admin/models/admin-dashboard"; -import Report from "admin/models/report"; +import I18n from "I18n"; import PeriodComputationMixin from "admin/mixins/period-computation"; +import Report from "admin/models/report"; import { computed } from "@ember/object"; +import discourseComputed from "discourse-common/utils/decorators"; import getURL from "discourse-common/lib/get-url"; +import { makeArray } from "discourse-common/lib/helpers"; +import { setting } from "discourse/lib/computed"; function staticReport(reportType) { return computed("reports.[]", function () { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-dashboard-moderation.js b/app/assets/javascripts/admin/addon/controllers/admin-dashboard-moderation.js index 6781c50bde..16e632d6e2 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-dashboard-moderation.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-dashboard-moderation.js @@ -1,8 +1,8 @@ -import getURL from "discourse-common/lib/get-url"; -import discourseComputed from "discourse-common/utils/decorators"; import Controller from "@ember/controller"; import PeriodComputationMixin from "admin/mixins/period-computation"; import { computed } from "@ember/object"; +import discourseComputed from "discourse-common/utils/decorators"; +import getURL from "discourse-common/lib/get-url"; export default Controller.extend(PeriodComputationMixin, { @discourseComputed diff --git a/app/assets/javascripts/admin/addon/controllers/admin-dashboard-reports.js b/app/assets/javascripts/admin/addon/controllers/admin-dashboard-reports.js index 966304a0e5..ab10591c80 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-dashboard-reports.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-dashboard-reports.js @@ -1,7 +1,7 @@ -import discourseComputed from "discourse-common/utils/decorators"; -import { debounce } from "@ember/runloop"; import Controller from "@ember/controller"; import { INPUT_DELAY } from "discourse-common/config/environment"; +import discourseComputed from "discourse-common/utils/decorators"; +import discourseDebounce from "discourse-common/lib/debounce"; const { get } = Ember; @@ -34,7 +34,7 @@ export default Controller.extend({ actions: { filterReports(filter) { - debounce(this, this._performFiltering, filter, INPUT_DELAY); + discourseDebounce(this, this._performFiltering, filter, INPUT_DELAY); }, }, diff --git a/app/assets/javascripts/admin/addon/controllers/admin-dashboard.js b/app/assets/javascripts/admin/addon/controllers/admin-dashboard.js index 29e1ad0c5a..89955953fb 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-dashboard.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-dashboard.js @@ -1,9 +1,9 @@ -import discourseComputed from "discourse-common/utils/decorators"; import Controller, { inject } from "@ember/controller"; -import { setting } from "discourse/lib/computed"; -import { computed } from "@ember/object"; import AdminDashboard from "admin/models/admin-dashboard"; import VersionCheck from "admin/models/version-check"; +import { computed } from "@ember/object"; +import discourseComputed from "discourse-common/utils/decorators"; +import { setting } from "discourse/lib/computed"; const PROBLEMS_CHECK_MINUTES = 1; diff --git a/app/assets/javascripts/admin/addon/controllers/admin-email-bounced.js b/app/assets/javascripts/admin/addon/controllers/admin-email-bounced.js index 07721cdff0..1500549564 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-email-bounced.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-email-bounced.js @@ -1,11 +1,11 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; -import discourseDebounce from "discourse/lib/debounce"; -import { observes } from "discourse-common/utils/decorators"; import { INPUT_DELAY } from "discourse-common/config/environment"; +import discourseDebounce from "discourse-common/lib/debounce"; +import { observes } from "discourse-common/utils/decorators"; export default AdminEmailLogsController.extend({ @observes("filter.{status,user,address,type}") - filterEmailLogs: discourseDebounce(function () { - this.loadLogs(); - }, INPUT_DELAY), + filterEmailLogs() { + discourseDebounce(this, this.loadLogs, INPUT_DELAY); + }, }); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-email-index.js b/app/assets/javascripts/admin/addon/controllers/admin-email-index.js index f356731e6d..18bfd4a8c4 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-email-index.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-email-index.js @@ -1,9 +1,9 @@ -import I18n from "I18n"; -import { empty } from "@ember/object/computed"; import Controller from "@ember/controller"; +import I18n from "I18n"; import { ajax } from "discourse/lib/ajax"; -import { observes } from "discourse-common/utils/decorators"; import bootbox from "bootbox"; +import { empty } from "@ember/object/computed"; +import { observes } from "discourse-common/utils/decorators"; export default Controller.extend({ /** diff --git a/app/assets/javascripts/admin/addon/controllers/admin-email-preview-digest.js b/app/assets/javascripts/admin/addon/controllers/admin-email-preview-digest.js index 4b6ba80ce6..1a6fbf2184 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-email-preview-digest.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-email-preview-digest.js @@ -1,8 +1,9 @@ -import { empty, or, notEmpty } from "@ember/object/computed"; +import { empty, notEmpty, or } from "@ember/object/computed"; import Controller from "@ember/controller"; import EmailPreview from "admin/models/email-preview"; -import { popupAjaxError } from "discourse/lib/ajax-error"; import bootbox from "bootbox"; +import { get } from "@ember/object"; +import { popupAjaxError } from "discourse/lib/ajax-error"; export default Controller.extend({ username: null, @@ -14,6 +15,10 @@ export default Controller.extend({ htmlEmpty: empty("model.html_content"), actions: { + updateUsername(selected) { + this.set("username", get(selected, "firstObject")); + }, + refresh() { const model = this.model; diff --git a/app/assets/javascripts/admin/addon/controllers/admin-email-received.js b/app/assets/javascripts/admin/addon/controllers/admin-email-received.js index ab02337866..0a2933fa23 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-email-received.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-email-received.js @@ -1,14 +1,14 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; -import discourseDebounce from "discourse/lib/debounce"; -import IncomingEmail from "admin/models/incoming-email"; -import { observes } from "discourse-common/utils/decorators"; import { INPUT_DELAY } from "discourse-common/config/environment"; +import IncomingEmail from "admin/models/incoming-email"; +import discourseDebounce from "discourse-common/lib/debounce"; +import { observes } from "discourse-common/utils/decorators"; export default AdminEmailLogsController.extend({ @observes("filter.{status,from,to,subject}") - filterIncomingEmails: discourseDebounce(function () { - this.loadLogs(IncomingEmail); - }, INPUT_DELAY), + filterIncomingEmails() { + discourseDebounce(this, this.loadLogs, IncomingEmail, INPUT_DELAY); + }, actions: { loadMore() { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-email-rejected.js b/app/assets/javascripts/admin/addon/controllers/admin-email-rejected.js index 2c160648c6..89c67f3cf9 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-email-rejected.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-email-rejected.js @@ -1,14 +1,14 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; -import discourseDebounce from "discourse/lib/debounce"; -import IncomingEmail from "admin/models/incoming-email"; -import { observes } from "discourse-common/utils/decorators"; import { INPUT_DELAY } from "discourse-common/config/environment"; +import IncomingEmail from "admin/models/incoming-email"; +import discourseDebounce from "discourse-common/lib/debounce"; +import { observes } from "discourse-common/utils/decorators"; export default AdminEmailLogsController.extend({ @observes("filter.{status,from,to,subject,error}") - filterIncomingEmails: discourseDebounce(function () { - this.loadLogs(IncomingEmail); - }, INPUT_DELAY), + filterIncomingEmails() { + discourseDebounce(this, this.loadLogs, IncomingEmail, INPUT_DELAY); + }, actions: { loadMore() { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-email-sent.js b/app/assets/javascripts/admin/addon/controllers/admin-email-sent.js index 2c7890cde8..5cc83109f8 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-email-sent.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-email-sent.js @@ -1,11 +1,11 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; -import discourseDebounce from "discourse/lib/debounce"; -import { observes } from "discourse-common/utils/decorators"; import { INPUT_DELAY } from "discourse-common/config/environment"; +import discourseDebounce from "discourse-common/lib/debounce"; +import { observes } from "discourse-common/utils/decorators"; export default AdminEmailLogsController.extend({ @observes("filter.{status,user,address,type,reply_key}") - filterEmailLogs: discourseDebounce(function () { - this.loadLogs(); - }, INPUT_DELAY), + filterEmailLogs() { + discourseDebounce(this, this.loadLogs, INPUT_DELAY); + }, }); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-email-skipped.js b/app/assets/javascripts/admin/addon/controllers/admin-email-skipped.js index 07721cdff0..1500549564 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-email-skipped.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-email-skipped.js @@ -1,11 +1,11 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; -import discourseDebounce from "discourse/lib/debounce"; -import { observes } from "discourse-common/utils/decorators"; import { INPUT_DELAY } from "discourse-common/config/environment"; +import discourseDebounce from "discourse-common/lib/debounce"; +import { observes } from "discourse-common/utils/decorators"; export default AdminEmailLogsController.extend({ @observes("filter.{status,user,address,type}") - filterEmailLogs: discourseDebounce(function () { - this.loadLogs(); - }, INPUT_DELAY), + filterEmailLogs() { + discourseDebounce(this, this.loadLogs, INPUT_DELAY); + }, }); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-embedding.js b/app/assets/javascripts/admin/addon/controllers/admin-embedding.js index df7f856ed5..147d4dd35a 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-embedding.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-embedding.js @@ -1,5 +1,5 @@ -import discourseComputed from "discourse-common/utils/decorators"; import Controller from "@ember/controller"; +import discourseComputed from "discourse-common/utils/decorators"; import { popupAjaxError } from "discourse/lib/ajax-error"; export default Controller.extend({ diff --git a/app/assets/javascripts/admin/addon/controllers/admin-emojis.js b/app/assets/javascripts/admin/addon/controllers/admin-emojis.js index 4019cb188e..94e284c0c9 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-emojis.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-emojis.js @@ -1,9 +1,9 @@ -import I18n from "I18n"; -import { sort } from "@ember/object/computed"; import EmberObject, { action, computed } from "@ember/object"; import Controller from "@ember/controller"; +import I18n from "I18n"; import { ajax } from "discourse/lib/ajax"; import bootbox from "bootbox"; +import { sort } from "@ember/object/computed"; const ALL_FILTER = "all"; diff --git a/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-emails.js b/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-emails.js index 134968eb10..ed3cd6489d 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-emails.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-emails.js @@ -1,7 +1,7 @@ import Controller from "@ember/controller"; +import ScreenedEmail from "admin/models/screened-email"; import { exportEntity } from "discourse/lib/export-csv"; import { outputExportResult } from "discourse/lib/export-result"; -import ScreenedEmail from "admin/models/screened-email"; export default Controller.extend({ loading: false, diff --git a/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-ip-addresses.js b/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-ip-addresses.js index 68ac649425..fb923131ed 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-ip-addresses.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-ip-addresses.js @@ -1,25 +1,29 @@ -import I18n from "I18n"; 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"; +import I18n from "I18n"; import { INPUT_DELAY } from "discourse-common/config/environment"; +import ScreenedIpAddress from "admin/models/screened-ip-address"; import bootbox from "bootbox"; +import discourseDebounce from "discourse-common/lib/debounce"; +import { exportEntity } from "discourse/lib/export-csv"; +import { observes } from "discourse-common/utils/decorators"; +import { outputExportResult } from "discourse/lib/export-result"; export default Controller.extend({ loading: false, filter: null, savedIpAddress: null, - @observes("filter") - show: discourseDebounce(function () { + _debouncedShow() { this.set("loading", true); ScreenedIpAddress.findAll(this.filter).then((result) => { this.setProperties({ model: result, loading: false }); }); - }, INPUT_DELAY), + }, + + @observes("filter") + show() { + discourseDebounce(this, this._debouncedShow, INPUT_DELAY); + }, actions: { allow(record) { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-urls.js b/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-urls.js index 90d008bb86..86621f5968 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-urls.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-urls.js @@ -1,7 +1,7 @@ import Controller from "@ember/controller"; +import ScreenedUrl from "admin/models/screened-url"; import { exportEntity } from "discourse/lib/export-csv"; import { outputExportResult } from "discourse/lib/export-result"; -import ScreenedUrl from "admin/models/screened-url"; export default Controller.extend({ loading: false, diff --git a/app/assets/javascripts/admin/addon/controllers/admin-logs-staff-action-logs.js b/app/assets/javascripts/admin/addon/controllers/admin-logs-staff-action-logs.js index 70496049d1..2758a10aa1 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-logs-staff-action-logs.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-logs-staff-action-logs.js @@ -1,10 +1,10 @@ import Controller from "@ember/controller"; import EmberObject from "@ember/object"; -import { scheduleOnce } from "@ember/runloop"; +import I18n from "I18n"; import discourseComputed from "discourse-common/utils/decorators"; import { exportEntity } from "discourse/lib/export-csv"; import { outputExportResult } from "discourse/lib/export-result"; -import I18n from "I18n"; +import { scheduleOnce } from "@ember/runloop"; export default Controller.extend({ queryParams: ["filters"], diff --git a/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js b/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js index 729cdab0c8..b11b397311 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js @@ -1,22 +1,26 @@ -import I18n from "I18n"; import Controller from "@ember/controller"; -import discourseDebounce from "discourse/lib/debounce"; -import Permalink from "admin/models/permalink"; -import { observes } from "discourse-common/utils/decorators"; +import I18n from "I18n"; import { INPUT_DELAY } from "discourse-common/config/environment"; +import Permalink from "admin/models/permalink"; import bootbox from "bootbox"; +import discourseDebounce from "discourse-common/lib/debounce"; +import { observes } from "discourse-common/utils/decorators"; export default Controller.extend({ loading: false, filter: null, - @observes("filter") - show: discourseDebounce(function () { + _debouncedShow() { Permalink.findAll(this.filter).then((result) => { this.set("model", result); this.set("loading", false); }); - }, INPUT_DELAY), + }, + + @observes("filter") + show() { + discourseDebounce(this, this._debouncedShow, INPUT_DELAY); + }, actions: { recordAdded(arg) { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-plugins.js b/app/assets/javascripts/admin/addon/controllers/admin-plugins.js index 955aaba5c3..d19c5584aa 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-plugins.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-plugins.js @@ -1,5 +1,5 @@ -import discourseComputed from "discourse-common/utils/decorators"; import Controller from "@ember/controller"; +import discourseComputed from "discourse-common/utils/decorators"; export default Controller.extend({ @discourseComputed diff --git a/app/assets/javascripts/admin/addon/controllers/admin-reports-show.js b/app/assets/javascripts/admin/addon/controllers/admin-reports-show.js index c20dfc1c2e..64a2c5de93 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-reports-show.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-reports-show.js @@ -1,5 +1,5 @@ -import discourseComputed from "discourse-common/utils/decorators"; import Controller from "@ember/controller"; +import discourseComputed from "discourse-common/utils/decorators"; export default Controller.extend({ queryParams: ["start_date", "end_date", "filters", "chart_grouping"], diff --git a/app/assets/javascripts/admin/addon/controllers/admin-search-logs-index.js b/app/assets/javascripts/admin/addon/controllers/admin-search-logs-index.js index 2e2bcb4f73..bf110796f2 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-search-logs-index.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-search-logs-index.js @@ -1,5 +1,5 @@ -import I18n from "I18n"; import Controller from "@ember/controller"; +import I18n from "I18n"; export const DEFAULT_PERIOD = "yearly"; export default Controller.extend({ diff --git a/app/assets/javascripts/admin/addon/controllers/admin-search-logs-term.js b/app/assets/javascripts/admin/addon/controllers/admin-search-logs-term.js index 8ee5dd5df1..2d4f22211b 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-search-logs-term.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-search-logs-term.js @@ -1,6 +1,6 @@ -import I18n from "I18n"; import Controller from "@ember/controller"; import { DEFAULT_PERIOD } from "admin/controllers/admin-search-logs-index"; +import I18n from "I18n"; export default Controller.extend({ loading: false, diff --git a/app/assets/javascripts/admin/addon/controllers/admin-site-settings-category.js b/app/assets/javascripts/admin/addon/controllers/admin-site-settings-category.js index 5a12f1d717..2727c56010 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-site-settings-category.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-site-settings-category.js @@ -1,5 +1,5 @@ -import discourseComputed from "discourse-common/utils/decorators"; import Controller, { inject as controller } from "@ember/controller"; +import discourseComputed from "discourse-common/utils/decorators"; export default Controller.extend({ adminSiteSettings: controller(), diff --git a/app/assets/javascripts/admin/addon/controllers/admin-site-settings.js b/app/assets/javascripts/admin/addon/controllers/admin-site-settings.js index 02e7ab8f07..58859ae933 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-site-settings.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-site-settings.js @@ -1,10 +1,10 @@ -import I18n from "I18n"; -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"; +import I18n from "I18n"; import { INPUT_DELAY } from "discourse-common/config/environment"; +import { alias } from "@ember/object/computed"; +import discourseDebounce from "discourse-common/lib/debounce"; +import { isEmpty } from "@ember/utils"; +import { observes } from "discourse-common/utils/decorators"; export default Controller.extend({ filter: null, @@ -112,13 +112,19 @@ export default Controller.extend({ }, @observes("filter", "onlyOverridden", "model") - filterContent: discourseDebounce(function () { - if (this._skipBounce) { - this.set("_skipBounce", false); - } else { - this.filterContentNow(this.categoryNameKey); - } - }, INPUT_DELAY), + filterContent() { + discourseDebounce( + this, + () => { + if (this._skipBounce) { + this.set("_skipBounce", false); + } else { + this.filterContentNow(this.categoryNameKey); + } + }, + INPUT_DELAY + ); + }, actions: { clearFilter() { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-site-text-edit.js b/app/assets/javascripts/admin/addon/controllers/admin-site-text-edit.js index b33bbe3968..cc7bac3267 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-site-text-edit.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-site-text-edit.js @@ -1,12 +1,13 @@ -import I18n from "I18n"; -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 I18n from "I18n"; import bootbox from "bootbox"; +import { bufferedProperty } from "discourse/mixins/buffered-content"; +import discourseComputed from "discourse-common/utils/decorators"; +import { popupAjaxError } from "discourse/lib/ajax-error"; export default Controller.extend(bufferedProperty("siteText"), { saved: false, + queryParams: ["locale"], @discourseComputed("buffered.value") saveDisabled(value) { @@ -15,9 +16,11 @@ export default Controller.extend(bufferedProperty("siteText"), { actions: { saveChanges() { - const buffered = this.buffered; + const attrs = this.buffered.getProperties("value"); + attrs.locale = this.locale; + this.siteText - .save(buffered.getProperties("value")) + .save(attrs) .then(() => { this.commitBuffer(); this.set("saved", true); @@ -27,10 +30,11 @@ export default Controller.extend(bufferedProperty("siteText"), { revertChanges() { this.set("saved", false); + bootbox.confirm(I18n.t("admin.site_text.revert_confirm"), (result) => { if (result) { this.siteText - .revert() + .revert(this.locale) .then((props) => { const buffered = this.buffered; buffered.setProperties(props); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-site-text-index.js b/app/assets/javascripts/admin/addon/controllers/admin-site-text-index.js index e46096c751..2f2b708d17 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-site-text-index.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-site-text-index.js @@ -1,43 +1,88 @@ -import { debounce } from "@ember/runloop"; import Controller from "@ember/controller"; +import discourseComputed from "discourse-common/utils/decorators"; +import discourseDebounce from "discourse-common/lib/debounce"; let lastSearch; export default Controller.extend({ searching: false, siteTexts: null, preferred: false, - queryParams: ["q", "overridden"], + queryParams: ["q", "overridden", "locale"], + locale: null, q: null, overridden: false, + init() { + this._super(...arguments); + + this.set("locale", this.siteSettings.default_locale); + }, + _performSearch() { this.store - .find("site-text", this.getProperties("q", "overridden")) + .find("site-text", this.getProperties("q", "overridden", "locale")) .then((results) => { this.set("siteTexts", results); }) .finally(() => this.set("searching", false)); }, + @discourseComputed() + availableLocales() { + return JSON.parse(this.siteSettings.available_locales); + }, + + @discourseComputed("locale") + fallbackLocaleFullName() { + if (this.siteTexts.extras.fallback_locale) { + return this.availableLocales.find((l) => { + return l.value === this.siteTexts.extras.fallback_locale; + }).name; + } + }, + + @discourseComputed("locale") + showFallbackLocaleWarning() { + return ( + this.siteSettings.allow_user_locale && + this.siteSettings.set_locale_from_accept_language_header && + this.fallbackLocaleFullName + ); + }, + actions: { edit(siteText) { - this.transitionToRoute("adminSiteText.edit", siteText.get("id")); + this.transitionToRoute("adminSiteText.edit", siteText.get("id"), { + queryParams: { + locale: this.locale, + localeFullName: this.availableLocales[this.locale], + }, + }); }, toggleOverridden() { this.toggleProperty("overridden"); this.set("searching", true); - debounce(this, this._performSearch, 400); + discourseDebounce(this, this._performSearch, 400); }, search() { const q = this.q; if (q !== lastSearch) { this.set("searching", true); - debounce(this, this._performSearch, 400); + discourseDebounce(this, this._performSearch, 400); lastSearch = q; } }, + + updateLocale(value) { + this.setProperties({ + searching: true, + locale: value, + }); + + discourseDebounce(this, this._performSearch, 400); + }, }, }); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-user-badges.js b/app/assets/javascripts/admin/addon/controllers/admin-user-badges.js index 6951a7b562..2c5212e4ba 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-user-badges.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-user-badges.js @@ -1,11 +1,11 @@ -import I18n from "I18n"; -import discourseComputed from "discourse-common/utils/decorators"; -import { alias, sort } from "@ember/object/computed"; -import { next } from "@ember/runloop"; import Controller, { inject as controller } from "@ember/controller"; +import { alias, sort } from "@ember/object/computed"; import GrantBadgeController from "discourse/mixins/grant-badge-controller"; -import { popupAjaxError } from "discourse/lib/ajax-error"; +import I18n from "I18n"; import bootbox from "bootbox"; +import discourseComputed from "discourse-common/utils/decorators"; +import { next } from "@ember/runloop"; +import { popupAjaxError } from "discourse/lib/ajax-error"; export default Controller.extend(GrantBadgeController, { adminUser: controller(), diff --git a/app/assets/javascripts/admin/addon/controllers/admin-user-fields.js b/app/assets/javascripts/admin/addon/controllers/admin-user-fields.js index 913ed729a7..bafcc02937 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-user-fields.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-user-fields.js @@ -1,8 +1,8 @@ -import I18n from "I18n"; import { gte, sort } from "@ember/object/computed"; import Controller from "@ember/controller"; -import { popupAjaxError } from "discourse/lib/ajax-error"; +import I18n from "I18n"; import bootbox from "bootbox"; +import { popupAjaxError } from "discourse/lib/ajax-error"; const MAX_FIELDS = 30; @@ -53,17 +53,18 @@ export default Controller.extend({ // Only confirm if we already been saved if (f.get("id")) { - bootbox.confirm(I18n.t("admin.user_fields.delete_confirm"), function ( - result - ) { - if (result) { - f.destroyRecord() - .then(function () { - model.removeObject(f); - }) - .catch(popupAjaxError); + bootbox.confirm( + I18n.t("admin.user_fields.delete_confirm"), + function (result) { + if (result) { + f.destroyRecord() + .then(function () { + model.removeObject(f); + }) + .catch(popupAjaxError); + } } - }); + ); } else { model.removeObject(f); } diff --git a/app/assets/javascripts/admin/addon/controllers/admin-user-index.js b/app/assets/javascripts/admin/addon/controllers/admin-user-index.js index 2bed595b3f..517f0c0386 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-user-index.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-user-index.js @@ -1,16 +1,19 @@ -import I18n from "I18n"; -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 DiscourseURL, { userPath } from "discourse/lib/url"; +import { and, notEmpty } from "@ember/object/computed"; +import { fmt, propertyNotEqual, setting } from "discourse/lib/computed"; +import AdminUser from "admin/models/admin-user"; import CanCheckEmails from "discourse/mixins/can-check-emails"; -import { propertyNotEqual, setting, fmt } from "discourse/lib/computed"; -import { userPath } from "discourse/lib/url"; -import { popupAjaxError } from "discourse/lib/ajax-error"; -import discourseComputed from "discourse-common/utils/decorators"; -import { htmlSafe } from "@ember/template"; -import showModal from "discourse/lib/show-modal"; +import Controller from "@ember/controller"; +import I18n from "I18n"; +import { ajax } from "discourse/lib/ajax"; import bootbox from "bootbox"; +import discourseComputed from "discourse-common/utils/decorators"; +import getURL from "discourse-common/lib/get-url"; +import { htmlSafe } from "@ember/template"; +import { iconHTML } from "discourse-common/lib/icon-library"; +import { popupAjaxError } from "discourse/lib/ajax-error"; +import { inject as service } from "@ember/service"; +import showModal from "discourse/lib/show-modal"; export default Controller.extend(CanCheckEmails, { adminTools: service(), @@ -19,6 +22,7 @@ export default Controller.extend(CanCheckEmails, { availableGroups: null, userTitleValue: null, ssoExternalEmail: null, + ssoLastPayload: null, showBadges: setting("enable_badges"), hasLockedTrustLevel: notEmpty("model.manual_locked_trust_level"), @@ -134,17 +138,28 @@ export default Controller.extend(CanCheckEmails, { .catch(() => bootbox.alert(I18n.t("generic_error"))); }, - @discourseComputed("model.single_sign_on_record.last_payload") + @discourseComputed("ssoLastPayload") ssoPayload(lastPayload) { return lastPayload.split("&"); }, actions: { impersonate() { - return this.model.impersonate(); + return this.model + .impersonate() + .then(() => DiscourseURL.redirectTo("/")) + .catch((e) => { + if (e.status === 404) { + bootbox.alert(I18n.t("admin.impersonate.not_found")); + } else { + bootbox.alert(I18n.t("admin.impersonate.invalid")); + } + }); }, logOut() { - return this.model.logOut(); + return this.model + .logOut() + .then(() => bootbox.alert(I18n.t("admin.user.logged_out"))); }, resetBounceScore() { return this.model.resetBounceScore(); @@ -152,20 +167,56 @@ export default Controller.extend(CanCheckEmails, { approve() { return this.model.approve(this.currentUser); }, + + _formatError(event) { + return `http: ${event.status} - ${event.body}`; + }, + deactivate() { - return this.model.deactivate(); + return this.model + .deactivate() + .then(() => + this.model.setProperties({ active: false, can_activate: true }) + ) + .catch((e) => { + const error = I18n.t("admin.user.deactivate_failed", { + error: this._formatError(e), + }); + bootbox.alert(error); + }); }, sendActivationEmail() { - return this.model.sendActivationEmail(); + return this.model + .sendActivationEmail() + .then(() => bootbox.alert(I18n.t("admin.user.activation_email_sent"))) + .catch(popupAjaxError); }, activate() { - return this.model.activate(); + return this.model + .activate() + .then(() => + this.model.setProperties({ + active: true, + can_deactivate: !this.model.staff, + }) + ) + .catch((e) => { + const error = I18n.t("admin.user.activate_failed", { + error: this._formatError(e), + }); + bootbox.alert(error); + }); }, revokeAdmin() { return this.model.revokeAdmin(); }, grantAdmin() { - return this.model.grantAdmin(); + return this.model + .grantAdmin() + .then(() => { + bootbox.alert(I18n.t("admin.user.grant_admin_confirm")); + }) + .catch(popupAjaxError); }, revokeModeration() { return this.model.revokeModeration(); @@ -174,13 +225,41 @@ export default Controller.extend(CanCheckEmails, { return this.model.grantModeration(); }, saveTrustLevel() { - return this.model.saveTrustLevel(); + return this.model + .saveTrustLevel() + .then(() => window.location.reload()) + .catch((e) => { + let error; + if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) { + error = e.jqXHR.responseJSON.errors[0]; + } + error = + error || + I18n.t("admin.user.trust_level_change_failed", { + error: this._formatError(e), + }); + bootbox.alert(error); + }); }, restoreTrustLevel() { return this.model.restoreTrustLevel(); }, lockTrustLevel(locked) { - return this.model.lockTrustLevel(locked); + return this.model + .lockTrustLevel(locked) + .then(() => window.location.reload()) + .catch((e) => { + let error; + if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) { + error = e.jqXHR.responseJSON.errors[0]; + } + error = + error || + I18n.t("admin.user.trust_level_change_failed", { + error: this._formatError(e), + }); + bootbox.alert(error); + }); }, unsilence() { return this.model.unsilence(); @@ -189,11 +268,117 @@ export default Controller.extend(CanCheckEmails, { return this.model.silence(); }, deleteAllPosts() { - return this.model.deleteAllPosts(); + let deletedPosts = 0; + let deletedPercentage = 0; + const user = this.model; + const message = I18n.messageFormat( + "admin.user.delete_all_posts_confirm_MF", + { + POSTS: user.get("post_count"), + TOPICS: user.get("topic_count"), + } + ); + + const performDelete = (progressModal) => { + this.model + .deleteAllPosts() + .then(({ posts_deleted }) => { + if (posts_deleted === 0) { + user.set("post_count", 0); + progressModal.send("closeModal"); + } else { + deletedPosts += posts_deleted; + deletedPercentage = Math.floor( + (deletedPosts * 100) / user.get("post_count") + ); + progressModal.setProperties({ + deletedPercentage: deletedPercentage, + }); + performDelete(progressModal); + } + }) + .catch((e) => { + progressModal.send("closeModal"); + let error; + AdminUser.find(user.get("id")).then((u) => user.setProperties(u)); + if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) { + error = e.jqXHR.responseJSON.errors[0]; + } + error = error || I18n.t("admin.user.delete_posts_failed"); + bootbox.alert(error); + }); + }; + + const buttons = [ + { + label: I18n.t("composer.cancel"), + class: "d-modal-cancel", + link: true, + }, + { + icon: iconHTML("exclamation-triangle"), + label: I18n.t("admin.user.delete_all_posts"), + class: "btn btn-danger", + callback: () => { + const progressModal = openProgressModal(); + performDelete(progressModal); + }, + }, + ]; + + const openProgressModal = () => { + return showModal("admin-delete-user-posts-progress", { + admin: true, + }); + }; + + bootbox.dialog(message, buttons, { classes: "delete-all-posts" }); }, + anonymize() { - return this.model.anonymize(); + const user = this.model; + const message = I18n.t("admin.user.anonymize_confirm"); + + const performAnonymize = () => { + this.model + .anonymize() + .then((data) => { + if (data.success) { + if (data.username) { + document.location = getURL( + `/admin/users/${user.get("id")}/${data.username}` + ); + } else { + document.location = getURL("/admin/users/list/active"); + } + } else { + bootbox.alert(I18n.t("admin.user.anonymize_failed")); + if (data.user) { + user.setProperties(data.user); + } + } + }) + .catch(() => bootbox.alert(I18n.t("admin.user.anonymize_failed"))); + }; + const buttons = [ + { + label: I18n.t("composer.cancel"), + class: "cancel", + link: true, + }, + { + label: I18n.t("admin.user.anonymize_yes"), + class: "btn btn-danger", + icon: iconHTML("exclamation-triangle"), + callback: () => { + performAnonymize(); + }, + }, + ]; + + bootbox.dialog(message, buttons, { classes: "delete-user-modal" }); }, + disableSecondFactor() { return this.model.disableSecondFactor(); }, @@ -210,11 +395,62 @@ export default Controller.extend(CanCheckEmails, { destroy() { const postCount = this.get("model.post_count"); const maxPostCount = this.siteSettings.delete_all_posts_max; - if (postCount <= maxPostCount) { - return this.model.destroy({ deletePosts: true }); - } else { - return this.model.destroy(); - } + const message = I18n.t("admin.user.delete_confirm"); + const location = document.location.pathname; + + const performDestroy = (block) => { + bootbox.dialog(I18n.t("admin.user.deleting_user")); + let formData = { context: location }; + if (block) { + formData["block_email"] = true; + formData["block_urls"] = true; + formData["block_ip"] = true; + } + if (postCount <= maxPostCount) { + formData["delete_posts"] = true; + } + this.model + .destroy(formData) + .then((data) => { + if (data.deleted) { + if (/^\/admin\/users\/list\//.test(location)) { + document.location = location; + } else { + document.location = getURL("/admin/users/list/active"); + } + } else { + bootbox.alert(I18n.t("admin.user.delete_failed")); + } + }) + .catch(() => { + bootbox.alert(I18n.t("admin.user.delete_failed")); + }); + }; + + const buttons = [ + { + label: I18n.t("composer.cancel"), + class: "btn", + link: true, + }, + { + icon: iconHTML("exclamation-triangle"), + label: I18n.t("admin.user.delete_and_block"), + class: "btn btn-danger", + callback: () => { + performDestroy(true); + }, + }, + { + label: I18n.t("admin.user.delete_dont_block"), + class: "btn btn-primary", + callback: () => { + performDestroy(false); + }, + }, + ]; + + bootbox.dialog(message, buttons, { classes: "delete-user-modal" }); }, promptTargetUser() { @@ -235,7 +471,31 @@ export default Controller.extend(CanCheckEmails, { }, merge(targetUsername) { - return this.model.merge({ targetUsername }); + const user = this.model; + const location = document.location.pathname; + + let formData = { context: location }; + + if (targetUsername) { + formData["target_username"] = targetUsername; + } + + this.model + .merge(formData) + .then((response) => { + if (response.success) { + showModal("admin-merge-users-progress", { + admin: true, + model: this.model, + }); + } else { + bootbox.alert(I18n.t("admin.user.merge_failed")); + } + }) + .catch(() => { + AdminUser.find(user.id).then((u) => user.setProperties(u)); + bootbox.alert(I18n.t("admin.user.merge_failed")); + }); }, viewActionLogs() { @@ -331,11 +591,13 @@ export default Controller.extend(CanCheckEmails, { deleteSSORecord() { return bootbox.confirm( - I18n.t("admin.user.sso.confirm_delete"), + I18n.t("admin.user.discourse_connect.confirm_delete"), I18n.t("no_value"), I18n.t("yes_value"), - () => { - return this.model.deleteSSORecord(); + (confirmed) => { + if (confirmed) { + return this.model.deleteSSORecord(); + } } ); }, @@ -349,5 +611,15 @@ export default Controller.extend(CanCheckEmails, { } }); }, + + checkSsoPayload() { + return ajax(userPath(`${this.model.username_lower}/sso-payload.json`), { + data: { context: window.location.pathname }, + }).then((result) => { + if (result) { + this.set("ssoLastPayload", result.payload); + } + }); + }, }, }); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-users-list-show.js b/app/assets/javascripts/admin/addon/controllers/admin-users-list-show.js index fd20b6c308..b0b25706e5 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-users-list-show.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-users-list-show.js @@ -1,11 +1,11 @@ -import I18n from "I18n"; 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 Controller from "@ember/controller"; +import I18n from "I18n"; import { INPUT_DELAY } from "discourse-common/config/environment"; +import discourseDebounce from "discourse-common/lib/debounce"; +import { i18n } from "discourse/lib/computed"; export default Controller.extend(CanCheckEmails, { model: null, @@ -32,9 +32,9 @@ export default Controller.extend(CanCheckEmails, { }, @observes("listFilter") - _filterUsers: discourseDebounce(function () { - this.resetFilters(); - }, INPUT_DELAY), + _filterUsers() { + discourseDebounce(this, this.resetFilters, INPUT_DELAY); + }, resetFilters() { this._page = 1; diff --git a/app/assets/javascripts/admin/addon/controllers/admin-watched-words-action.js b/app/assets/javascripts/admin/addon/controllers/admin-watched-words-action.js index 6a9b78bf29..1265e3d028 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-watched-words-action.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-watched-words-action.js @@ -1,13 +1,13 @@ -import I18n from "I18n"; -import discourseComputed from "discourse-common/utils/decorators"; -import { or } from "@ember/object/computed"; -import { schedule } from "@ember/runloop"; import Controller, { inject as controller } from "@ember/controller"; +import I18n from "I18n"; 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"; import bootbox from "bootbox"; +import discourseComputed from "discourse-common/utils/decorators"; +import { fmt } from "discourse/lib/computed"; +import { or } from "@ember/object/computed"; +import { schedule } from "@ember/runloop"; +import showModal from "discourse/lib/show-modal"; export default Controller.extend({ adminWatchedWords: controller(), @@ -18,7 +18,7 @@ export default Controller.extend({ ), downloadLink: fmt( "actionNameKey", - "/admin/logs/watched_words/action/%@/download" + "/admin/customize/watched_words/action/%@/download" ), findAction(actionName) { @@ -90,12 +90,14 @@ export default Controller.extend({ clearAll() { const actionKey = this.actionNameKey; bootbox.confirm( - I18n.t(`admin.watched_words.clear_all_confirm_${actionKey}`), + I18n.t("admin.watched_words.clear_all_confirm", { + action: I18n.t("admin.watched_words.actions." + actionKey), + }), I18n.t("no_value"), I18n.t("yes_value"), (result) => { if (result) { - ajax(`/admin/logs/watched_words/action/${actionKey}.json`, { + ajax(`/admin/customize/watched_words/action/${actionKey}.json`, { type: "DELETE", }).then(() => { const action = this.findAction(actionKey); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-watched-words.js b/app/assets/javascripts/admin/addon/controllers/admin-watched-words.js index 151fc82c33..cfd86c6c22 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-watched-words.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-watched-words.js @@ -1,10 +1,10 @@ -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"; +import EmberObject from "@ember/object"; import { INPUT_DELAY } from "discourse-common/config/environment"; +import { alias } from "@ember/object/computed"; +import discourseDebounce from "discourse-common/lib/debounce"; +import { isEmpty } from "@ember/utils"; +import { observes } from "discourse-common/utils/decorators"; export default Controller.extend({ filter: null, @@ -48,10 +48,16 @@ export default Controller.extend({ }, @observes("filter") - filterContent: discourseDebounce(function () { - this.filterContentNow(); - this.set("filtered", !isEmpty(this.filter)); - }, INPUT_DELAY), + filterContent() { + discourseDebounce( + this, + function () { + this.filterContentNow(); + this.set("filtered", !isEmpty(this.filter)); + }, + INPUT_DELAY + ); + }, actions: { clearFilter() { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-web-hooks-show-events.js b/app/assets/javascripts/admin/addon/controllers/admin-web-hooks-show-events.js index 49c87926d2..cdb4c9b615 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-web-hooks-show-events.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-web-hooks-show-events.js @@ -1,7 +1,7 @@ -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 { alias } from "@ember/object/computed"; +import discourseComputed from "discourse-common/utils/decorators"; import { popupAjaxError } from "discourse/lib/ajax-error"; export default Controller.extend({ diff --git a/app/assets/javascripts/admin/addon/controllers/admin-web-hooks-show.js b/app/assets/javascripts/admin/addon/controllers/admin-web-hooks-show.js index f2e9f2178d..26b5fb20d0 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-web-hooks-show.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-web-hooks-show.js @@ -1,13 +1,13 @@ -import I18n from "I18n"; -import discourseComputed from "discourse-common/utils/decorators"; -import { isEmpty } from "@ember/utils"; -import { alias } from "@ember/object/computed"; import Controller, { inject as controller } from "@ember/controller"; -import { popupAjaxError } from "discourse/lib/ajax-error"; -import { extractDomainFromUrl } from "discourse/lib/utilities"; import EmberObject from "@ember/object"; -import { isAbsoluteURL } from "discourse-common/lib/get-url"; +import I18n from "I18n"; +import { alias } from "@ember/object/computed"; import bootbox from "bootbox"; +import discourseComputed from "discourse-common/utils/decorators"; +import { extractDomainFromUrl } from "discourse/lib/utilities"; +import { isAbsoluteURL } from "discourse-common/lib/get-url"; +import { isEmpty } from "@ember/utils"; +import { popupAjaxError } from "discourse/lib/ajax-error"; export default Controller.extend({ adminWebHooks: controller(), diff --git a/app/assets/javascripts/admin/addon/controllers/admin-web-hooks.js b/app/assets/javascripts/admin/addon/controllers/admin-web-hooks.js index cc13ea9e9d..6e1acf97dc 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-web-hooks.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-web-hooks.js @@ -1,7 +1,7 @@ -import I18n from "I18n"; import Controller from "@ember/controller"; -import { popupAjaxError } from "discourse/lib/ajax-error"; +import I18n from "I18n"; import bootbox from "bootbox"; +import { popupAjaxError } from "discourse/lib/ajax-error"; export default Controller.extend({ actions: { diff --git a/app/assets/javascripts/admin/addon/controllers/admin.js b/app/assets/javascripts/admin/addon/controllers/admin.js index 4c51eb38b7..03838b789c 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin.js +++ b/app/assets/javascripts/admin/addon/controllers/admin.js @@ -1,7 +1,7 @@ -import discourseComputed from "discourse-common/utils/decorators"; -import { inject as service } from "@ember/service"; import Controller from "@ember/controller"; import { dasherize } from "@ember/string"; +import discourseComputed from "discourse-common/utils/decorators"; +import { inject as service } from "@ember/service"; export default Controller.extend({ router: service(), diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-add-upload.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-add-upload.js index 12b2be4f4a..876ac3ae9a 100644 --- a/app/assets/javascripts/admin/addon/controllers/modals/admin-add-upload.js +++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-add-upload.js @@ -1,10 +1,10 @@ -import I18n from "I18n"; -import { isEmpty } from "@ember/utils"; -import { and, not } from "@ember/object/computed"; import Controller, { inject as controller } from "@ember/controller"; +import { and, not } from "@ember/object/computed"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; +import I18n from "I18n"; import ModalFunctionality from "discourse/mixins/modal-functionality"; import { ajax } from "discourse/lib/ajax"; -import discourseComputed, { observes } from "discourse-common/utils/decorators"; +import { isEmpty } from "@ember/utils"; import { popupAjaxError } from "discourse/lib/ajax-error"; const THEME_FIELD_VARIABLE_TYPE_IDS = [2, 3, 4]; diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-badge-preview.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-badge-preview.js index 90af87cb73..322436303f 100644 --- a/app/assets/javascripts/admin/addon/controllers/modals/admin-badge-preview.js +++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-badge-preview.js @@ -1,6 +1,6 @@ -import I18n from "I18n"; import { alias, map } from "@ember/object/computed"; import Controller from "@ember/controller"; +import I18n from "I18n"; import discourseComputed from "discourse-common/utils/decorators"; import { escapeExpression } from "discourse/lib/utilities"; diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-delete-user-posts-progress.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-delete-user-posts-progress.js new file mode 100644 index 0000000000..61b9bae999 --- /dev/null +++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-delete-user-posts-progress.js @@ -0,0 +1,6 @@ +import Controller from "@ember/controller"; +import ModalFunctionality from "discourse/mixins/modal-functionality"; + +export default Controller.extend(ModalFunctionality, { + deletedPercentage: 0, +}); diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-edit-badge-groupings.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-edit-badge-groupings.js index cedbf43632..aa7d5665fa 100644 --- a/app/assets/javascripts/admin/addon/controllers/modals/admin-edit-badge-groupings.js +++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-edit-badge-groupings.js @@ -1,10 +1,10 @@ -import I18n from "I18n"; -import Controller from "@ember/controller"; import { A } from "@ember/array"; -import { ajax } from "discourse/lib/ajax"; +import Controller from "@ember/controller"; +import I18n from "I18n"; import ModalFunctionality from "discourse/mixins/modal-functionality"; -import { observes } from "discourse-common/utils/decorators"; +import { ajax } from "discourse/lib/ajax"; import bootbox from "bootbox"; +import { observes } from "discourse-common/utils/decorators"; export default Controller.extend(ModalFunctionality, { @observes("model") diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-incoming-email.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-incoming-email.js index 45df8e4628..6055baa1fe 100644 --- a/app/assets/javascripts/admin/addon/controllers/modals/admin-incoming-email.js +++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-incoming-email.js @@ -1,7 +1,7 @@ -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 ModalFunctionality from "discourse/mixins/modal-functionality"; +import discourseComputed from "discourse-common/utils/decorators"; import { longDate } from "discourse/lib/formatter"; import { popupAjaxError } from "discourse/lib/ajax-error"; diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-install-theme.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-install-theme.js index 4bdb6ca85c..df91acd094 100644 --- a/app/assets/javascripts/admin/addon/controllers/modals/admin-install-theme.js +++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-install-theme.js @@ -1,12 +1,12 @@ -import I18n from "I18n"; -import { equal, match, alias } from "@ember/object/computed"; +import { COMPONENTS, THEMES } from "admin/models/theme"; import Controller, { inject as controller } from "@ember/controller"; +import { alias, equal, match } from "@ember/object/computed"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; +import I18n from "I18n"; import ModalFunctionality from "discourse/mixins/modal-functionality"; +import { POPULAR_THEMES } from "discourse-common/helpers/popular-themes"; import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; -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; @@ -18,15 +18,16 @@ export default Controller.extend(ModalFunctionality, { local: equal("selection", "local"), remote: equal("selection", "remote"), create: equal("selection", "create"), + directRepoInstall: equal("selection", "directRepoInstall"), selection: "popular", loading: false, keyGenUrl: "/admin/themes/generate_key_pair", importUrl: "/admin/themes/import", recordType: "theme", - checkPrivate: match("uploadUrl", /^git/), + checkPrivate: match("uploadUrl", /^ssh\:\/\/.*\@.*\.git$|.*\@.*\:.*\.git$/), localFile: null, uploadUrl: null, - urlPlaceholder: "https://github.com/discourse/sample_theme", + uploadName: null, advancedVisible: false, selectedType: alias("themesController.currentTab"), component: equal("selectedType", COMPONENTS), @@ -43,7 +44,9 @@ export default Controller.extend(ModalFunctionality, { @discourseComputed("themesController.installedThemes") themes(installedThemes) { return POPULAR_THEMES.map((t) => { - if (installedThemes.includes(t.name)) { + if ( + installedThemes.some((theme) => this.themeHasSameUrl(theme, t.value)) + ) { set(t, "installed", true); } return t; @@ -76,12 +79,15 @@ export default Controller.extend(ModalFunctionality, { ); }, + @discourseComputed("privateChecked") + urlPlaceholder(privateChecked) { + return privateChecked + ? "git@github.com:discourse/sample_theme.git" + : "https://github.com/discourse/sample_theme"; + }, + @observes("privateChecked") privateWasChecked() { - this.privateChecked - ? this.set("urlPlaceholder", "git@github.com:discourse/sample_theme.git") - : this.set("urlPlaceholder", "https://github.com/discourse/sample_theme"); - const checked = this.privateChecked; if (checked && !this._keyLoading) { this._keyLoading = true; @@ -125,6 +131,28 @@ export default Controller.extend(ModalFunctionality, { return privateChecked && checkPrivate && publicKey; }, + onClose() { + this.setProperties({ + duplicateRemoteThemeWarning: null, + privateChecked: false, + privateKey: null, + localFile: null, + uploadUrl: null, + publicKey: null, + branch: null, + selection: "popular", + }); + }, + + themeHasSameUrl(theme, url) { + const themeUrl = theme.remote_theme && theme.remote_theme.remote_url; + return ( + themeUrl && + url && + url.replace(/\.git$/, "") === themeUrl.replace(/\.git$/, "") + ); + }, + actions: { uploadLocaleFile() { this.set("localFile", $("#file-input")[0].files[0]); @@ -166,7 +194,18 @@ export default Controller.extend(ModalFunctionality, { options.data.append("theme", this.localFile); } - if (this.remote || this.popular) { + if (this.remote || this.popular || this.directRepoInstall) { + const duplicate = this.themesController.model.content.find((theme) => + this.themeHasSameUrl(theme, this.uploadUrl) + ); + if (duplicate && !this.duplicateRemoteThemeWarning) { + const warning = I18n.t( + "admin.customize.theme.duplicate_remote_theme", + { name: duplicate.name } + ); + this.set("duplicateRemoteThemeWarning", warning); + return; + } options.data = { remote: this.uploadUrl, branch: this.branch, diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-merge-users-confirmation.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-merge-users-confirmation.js index f2233c07a3..7c37ddd017 100644 --- a/app/assets/javascripts/admin/addon/controllers/modals/admin-merge-users-confirmation.js +++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-merge-users-confirmation.js @@ -1,9 +1,9 @@ -import I18n from "I18n"; import Controller, { inject as controller } from "@ember/controller"; +import I18n from "I18n"; import ModalFunctionality from "discourse/mixins/modal-functionality"; -import discourseComputed from "discourse-common/utils/decorators"; -import { alias } from "@ember/object/computed"; import { action } from "@ember/object"; +import { alias } from "@ember/object/computed"; +import discourseComputed from "discourse-common/utils/decorators"; export default Controller.extend(ModalFunctionality, { adminUserIndex: controller(), @@ -22,6 +22,13 @@ export default Controller.extend(ModalFunctionality, { }); }, + @discourseComputed("username") + mergeButtonText(username) { + return I18n.t(`admin.user.merge.confirmation.transfer_and_delete`, { + username, + }); + }, + @discourseComputed("value", "text") mergeDisabled(value, text) { return !value || text !== value; diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-merge-users-progress.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-merge-users-progress.js new file mode 100644 index 0000000000..3d9248d36d --- /dev/null +++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-merge-users-progress.js @@ -0,0 +1,31 @@ +import Controller from "@ember/controller"; +import DiscourseURL from "discourse/lib/url"; +import I18n from "I18n"; +import ModalFunctionality from "discourse/mixins/modal-functionality"; +import messageBus from "message-bus-client"; + +export default Controller.extend(ModalFunctionality, { + message: I18n.t("admin.user.merging_user"), + + onShow() { + messageBus.subscribe("/merge_user", (data) => { + if (data.merged) { + if (/^\/admin\/users\/list\//.test(location)) { + DiscourseURL.redirectTo(location); + } else { + DiscourseURL.redirectTo( + `/admin/users/${data.user.id}/${data.user.username}` + ); + } + } else if (data.message) { + this.set("message", data.message); + } else if (data.failed) { + this.set("message", I18n.t("admin.user.merge_failed")); + } + }); + }, + + onClose() { + this.messageBus.unsubscribe("/merge_user"); + }, +}); diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-merge-users-prompt.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-merge-users-prompt.js index baaf7cef7a..46e23305f2 100644 --- a/app/assets/javascripts/admin/addon/controllers/modals/admin-merge-users-prompt.js +++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-merge-users-prompt.js @@ -1,8 +1,9 @@ import Controller, { inject as controller } from "@ember/controller"; +import I18n from "I18n"; import ModalFunctionality from "discourse/mixins/modal-functionality"; -import discourseComputed from "discourse-common/utils/decorators"; +import { action, get } from "@ember/object"; import { alias } from "@ember/object/computed"; -import { action } from "@ember/object"; +import discourseComputed from "discourse-common/utils/decorators"; export default Controller.extend(ModalFunctionality, { adminUserIndex: controller(), @@ -17,6 +18,13 @@ export default Controller.extend(ModalFunctionality, { return !targetUsername || username === targetUsername; }, + @discourseComputed("username") + mergeButtonText(username) { + return I18n.t(`admin.user.merge.confirmation.transfer_and_delete`, { + username, + }); + }, + @action showConfirmation() { this.send("closeModal"); @@ -27,4 +35,9 @@ export default Controller.extend(ModalFunctionality, { close() { this.send("closeModal"); }, + + @action + updateUsername(selected) { + this.set("targetUsername", get(selected, "firstObject")); + }, }); diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-reseed.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-reseed.js index b29a800aa0..672f6d8db3 100644 --- a/app/assets/javascripts/admin/addon/controllers/modals/admin-reseed.js +++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-reseed.js @@ -1,5 +1,5 @@ -import I18n from "I18n"; import Controller from "@ember/controller"; +import I18n from "I18n"; import ModalFunctionality from "discourse/mixins/modal-functionality"; import { ajax } from "discourse/lib/ajax"; import bootbox from "bootbox"; diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-silence-user.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-silence-user.js index e379b903c2..b76565f815 100644 --- a/app/assets/javascripts/admin/addon/controllers/modals/admin-silence-user.js +++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-silence-user.js @@ -1,7 +1,7 @@ -import discourseComputed from "discourse-common/utils/decorators"; -import { isEmpty } from "@ember/utils"; import Controller from "@ember/controller"; import PenaltyController from "admin/mixins/penalty-controller"; +import discourseComputed from "discourse-common/utils/decorators"; +import { isEmpty } from "@ember/utils"; export default Controller.extend(PenaltyController, { silenceUntil: null, diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-suspend-user.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-suspend-user.js index 4997f62947..2712b57cf2 100644 --- a/app/assets/javascripts/admin/addon/controllers/modals/admin-suspend-user.js +++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-suspend-user.js @@ -1,7 +1,7 @@ -import discourseComputed from "discourse-common/utils/decorators"; -import { isEmpty } from "@ember/utils"; import Controller from "@ember/controller"; import PenaltyController from "admin/mixins/penalty-controller"; +import discourseComputed from "discourse-common/utils/decorators"; +import { isEmpty } from "@ember/utils"; export default Controller.extend(PenaltyController, { suspendUntil: null, diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-uploaded-image-list.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-uploaded-image-list.js index 0aeffaad59..7ab4cb6984 100644 --- a/app/assets/javascripts/admin/addon/controllers/modals/admin-uploaded-image-list.js +++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-uploaded-image-list.js @@ -1,5 +1,5 @@ +import { observes, on } from "discourse-common/utils/decorators"; import Controller from "@ember/controller"; -import { on, observes } from "discourse-common/utils/decorators"; import ModalFunctionality from "discourse/mixins/modal-functionality"; export default Controller.extend(ModalFunctionality, { diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-watched-word-test.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-watched-word-test.js index b088cccb75..215cc08a6d 100644 --- a/app/assets/javascripts/admin/addon/controllers/modals/admin-watched-word-test.js +++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-watched-word-test.js @@ -1,6 +1,6 @@ import Controller from "@ember/controller"; -import discourseComputed from "discourse-common/utils/decorators"; import ModalFunctionality from "discourse/mixins/modal-functionality"; +import discourseComputed from "discourse-common/utils/decorators"; export default Controller.extend(ModalFunctionality, { @discourseComputed("value", "model.compiledRegularExpression") diff --git a/app/assets/javascripts/admin/addon/helpers/check-icon.js b/app/assets/javascripts/admin/addon/helpers/check-icon.js index 51fb238550..14ae3e649c 100644 --- a/app/assets/javascripts/admin/addon/helpers/check-icon.js +++ b/app/assets/javascripts/admin/addon/helpers/check-icon.js @@ -1,6 +1,6 @@ +import { htmlSafe } from "@ember/template"; import { registerUnbound } from "discourse-common/lib/helpers"; import { renderIcon } from "discourse-common/lib/icon-library"; -import { htmlSafe } from "@ember/template"; registerUnbound("check-icon", function (value) { let icon = value ? "check" : "times"; diff --git a/app/assets/javascripts/admin/addon/helpers/disposition-icon.js b/app/assets/javascripts/admin/addon/helpers/disposition-icon.js index 03960ff289..5dbd069cf3 100644 --- a/app/assets/javascripts/admin/addon/helpers/disposition-icon.js +++ b/app/assets/javascripts/admin/addon/helpers/disposition-icon.js @@ -1,5 +1,5 @@ -import { iconHTML } from "discourse-common/lib/icon-library"; import Helper from "@ember/component/helper"; +import { iconHTML } from "discourse-common/lib/icon-library"; export default Helper.extend({ compute([disposition]) { diff --git a/app/assets/javascripts/admin/addon/helpers/post-action-title.js b/app/assets/javascripts/admin/addon/helpers/post-action-title.js index 2d8e3c3fe8..9a1d213d93 100644 --- a/app/assets/javascripts/admin/addon/helpers/post-action-title.js +++ b/app/assets/javascripts/admin/addon/helpers/post-action-title.js @@ -1,5 +1,5 @@ -import I18n from "I18n"; import Helper from "@ember/component/helper"; +import I18n from "I18n"; function postActionTitle([id, nameKey]) { let title = I18n.t(`admin.flags.short_names.${nameKey}`, { diff --git a/app/assets/javascripts/admin/addon/helpers/preserve-newlines.js b/app/assets/javascripts/admin/addon/helpers/preserve-newlines.js index 16639f30cc..3d7f2e019e 100644 --- a/app/assets/javascripts/admin/addon/helpers/preserve-newlines.js +++ b/app/assets/javascripts/admin/addon/helpers/preserve-newlines.js @@ -1,5 +1,5 @@ -import { htmlHelper } from "discourse-common/lib/helpers"; import { escapeExpression } from "discourse/lib/utilities"; +import { htmlHelper } from "discourse-common/lib/helpers"; export default htmlHelper((str) => escapeExpression(str).replace(/\n/g, "
") diff --git a/app/assets/javascripts/admin/addon/helpers/value-at-tl.js b/app/assets/javascripts/admin/addon/helpers/value-at-tl.js index 9e6b8c7514..158b02b546 100644 --- a/app/assets/javascripts/admin/addon/helpers/value-at-tl.js +++ b/app/assets/javascripts/admin/addon/helpers/value-at-tl.js @@ -1,9 +1,9 @@ import { registerUnbound } from "discourse-common/lib/helpers"; registerUnbound("value-at-tl", function (data, params) { - var tl = parseInt(params.level, 10); + let tl = parseInt(params.level, 10); if (data) { - var item = data.find(function (d) { + let item = data.find(function (d) { return parseInt(d.x, 10) === tl; }); if (item) { diff --git a/app/assets/javascripts/admin/addon/mixins/penalty-controller.js b/app/assets/javascripts/admin/addon/mixins/penalty-controller.js index 250d60664f..55514dfe48 100644 --- a/app/assets/javascripts/admin/addon/mixins/penalty-controller.js +++ b/app/assets/javascripts/admin/addon/mixins/penalty-controller.js @@ -1,10 +1,10 @@ import I18n from "I18n"; -import ModalFunctionality from "discourse/mixins/modal-functionality"; -import { extractError } from "discourse/lib/ajax-error"; import Mixin from "@ember/object/mixin"; -import { next } from "@ember/runloop"; +import ModalFunctionality from "discourse/mixins/modal-functionality"; import { Promise } from "rsvp"; import bootbox from "bootbox"; +import { extractError } from "discourse/lib/ajax-error"; +import { next } from "@ember/runloop"; export default Mixin.create(ModalFunctionality, { errorMessage: null, diff --git a/app/assets/javascripts/admin/addon/mixins/period-computation.js b/app/assets/javascripts/admin/addon/mixins/period-computation.js index 7b8afe796a..b46503e73c 100644 --- a/app/assets/javascripts/admin/addon/mixins/period-computation.js +++ b/app/assets/javascripts/admin/addon/mixins/period-computation.js @@ -1,6 +1,6 @@ -import discourseComputed from "discourse-common/utils/decorators"; import DiscourseURL from "discourse/lib/url"; import Mixin from "@ember/object/mixin"; +import discourseComputed from "discourse-common/utils/decorators"; export default Mixin.create({ queryParams: ["period"], diff --git a/app/assets/javascripts/admin/addon/mixins/setting-component.js b/app/assets/javascripts/admin/addon/mixins/setting-component.js index c7d42df43a..fc6c6c24d5 100644 --- a/app/assets/javascripts/admin/addon/mixins/setting-component.js +++ b/app/assets/javascripts/admin/addon/mixins/setting-component.js @@ -1,14 +1,14 @@ -import I18n from "I18n"; -import { warn } from "@ember/debug"; -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 I18n from "I18n"; import Mixin from "@ember/object/mixin"; -import showModal from "discourse/lib/show-modal"; import { Promise } from "rsvp"; import { ajax } from "discourse/lib/ajax"; +import { categoryLinkHTML } from "discourse/helpers/category-link"; +import discourseComputed from "discourse-common/utils/decorators"; import { htmlSafe } from "@ember/template"; +import { on } from "@ember/object/evented"; +import showModal from "discourse/lib/show-modal"; +import { warn } from "@ember/debug"; const CUSTOM_TYPES = [ "bool", @@ -27,6 +27,7 @@ const CUSTOM_TYPES = [ "tag_list", "color", "simple_list", + "emoji_list", ]; const AUTO_REFRESH_ON_SAVE = ["logo", "logo_small", "large_icon"]; diff --git a/app/assets/javascripts/admin/addon/mixins/setting-object.js b/app/assets/javascripts/admin/addon/mixins/setting-object.js index 5a40360b08..29dbb0d967 100644 --- a/app/assets/javascripts/admin/addon/mixins/setting-object.js +++ b/app/assets/javascripts/admin/addon/mixins/setting-object.js @@ -1,7 +1,7 @@ import I18n from "I18n"; -import discourseComputed from "discourse-common/utils/decorators"; -import { computed } from "@ember/object"; import Mixin from "@ember/object/mixin"; +import { computed } from "@ember/object"; +import discourseComputed from "discourse-common/utils/decorators"; import { isPresent } from "@ember/utils"; export default Mixin.create({ diff --git a/app/assets/javascripts/admin/addon/models/admin-dashboard.js b/app/assets/javascripts/admin/addon/models/admin-dashboard.js index dec30faf2d..c0c9f86702 100644 --- a/app/assets/javascripts/admin/addon/models/admin-dashboard.js +++ b/app/assets/javascripts/admin/addon/models/admin-dashboard.js @@ -1,5 +1,5 @@ -import { ajax } from "discourse/lib/ajax"; import EmberObject from "@ember/object"; +import { ajax } from "discourse/lib/ajax"; const GENERAL_ATTRIBUTES = [ "updated_at", @@ -14,6 +14,7 @@ AdminDashboard.reopenClass({ return ajax("/admin/dashboard.json").then((json) => { const model = AdminDashboard.create(); model.set("version_check", json.version_check); + return model; }); }, diff --git a/app/assets/javascripts/admin/addon/models/admin-user.js b/app/assets/javascripts/admin/addon/models/admin-user.js index 0bc1132623..cbcdcc2e24 100644 --- a/app/assets/javascripts/admin/addon/models/admin-user.js +++ b/app/assets/javascripts/admin/addon/models/admin-user.js @@ -1,16 +1,14 @@ -import getURL from "discourse-common/lib/get-url"; -import I18n from "I18n"; -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 { propertyNotEqual } from "discourse/lib/computed"; -import { popupAjaxError } from "discourse/lib/ajax-error"; +import { filter, gt, lt, not, or } from "@ember/object/computed"; import Group from "discourse/models/group"; -import DiscourseURL, { userPath } from "discourse/lib/url"; +import I18n from "I18n"; import { Promise } from "rsvp"; import User from "discourse/models/user"; -import bootbox from "bootbox"; +import { ajax } from "discourse/lib/ajax"; +import discourseComputed from "discourse-common/utils/decorators"; +import getURL from "discourse-common/lib/get-url"; +import { popupAjaxError } from "discourse/lib/ajax-error"; +import { propertyNotEqual } from "discourse/lib/computed"; +import { userPath } from "discourse/lib/url"; const wrapAdmin = (user) => (user ? AdminUser.create(user) : null); @@ -80,74 +78,9 @@ const AdminUser = User.extend({ }, deleteAllPosts() { - let deletedPosts = 0; - const user = this; - const message = I18n.messageFormat( - "admin.user.delete_all_posts_confirm_MF", - { - POSTS: user.get("post_count"), - TOPICS: user.get("topic_count"), - } - ); - const buttons = [ - { - label: I18n.t("composer.cancel"), - class: "d-modal-cancel", - link: true, - }, - { - label: - `${iconHTML("exclamation-triangle")} ` + - I18n.t("admin.user.delete_all_posts"), - class: "btn btn-danger", - callback: () => { - openProgressModal(); - performDelete(); - }, - }, - ]; - const openProgressModal = () => { - bootbox.dialog( - `

${I18n.t( - "admin.user.delete_posts_progress" - )}

`, - [], - { classes: "delete-posts-progress" } - ); - }; - const performDelete = () => { - let deletedPercentage = 0; - return ajax(`/admin/users/${user.get("id")}/delete_posts_batch`, { - type: "PUT", - }) - .then(({ posts_deleted }) => { - if (posts_deleted === 0) { - user.set("post_count", 0); - bootbox.hideAll(); - } else { - deletedPosts += posts_deleted; - deletedPercentage = Math.floor( - (deletedPosts * 100) / user.get("post_count") - ); - $(".delete-posts-progress .progress-bar > span").css({ - width: `${deletedPercentage}%`, - }); - performDelete(); - } - }) - .catch((e) => { - bootbox.hideAll(); - let error; - AdminUser.find(user.get("id")).then((u) => user.setProperties(u)); - if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) { - error = e.jqXHR.responseJSON.errors[0]; - } - error = error || I18n.t("admin.user.delete_posts_failed"); - bootbox.alert(error); - }); - }; - - bootbox.dialog(message, buttons, { classes: "delete-all-posts" }); + return ajax(`/admin/users/${this.get("id")}/delete_posts_batch`, { + type: "PUT", + }); }, revokeAdmin() { @@ -165,11 +98,7 @@ const AdminUser = User.extend({ grantAdmin() { return ajax(`/admin/users/${this.id}/grant_admin`, { type: "PUT", - }) - .then(() => { - bootbox.alert(I18n.t("admin.user.grant_admin_confirm")); - }) - .catch(popupAjaxError); + }); }, revokeModeration() { @@ -232,20 +161,7 @@ const AdminUser = User.extend({ return ajax(`/admin/users/${this.id}/trust_level`, { type: "PUT", data: { level: this.trust_level }, - }) - .then(() => window.location.reload()) - .catch((e) => { - let error; - if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) { - error = e.jqXHR.responseJSON.errors[0]; - } - error = - error || - I18n.t("admin.user.trust_level_change_failed", { - error: this._formatError(e), - }); - bootbox.alert(error); - }); + }); }, restoreTrustLevel() { @@ -256,20 +172,7 @@ const AdminUser = User.extend({ return ajax(`/admin/users/${this.id}/trust_level_lock`, { type: "PUT", data: { locked: !!locked }, - }) - .then(() => window.location.reload()) - .catch((e) => { - let error; - if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) { - error = e.jqXHR.responseJSON.errors[0]; - } - error = - error || - I18n.t("admin.user.trust_level_change_failed", { - error: this._formatError(e), - }); - bootbox.alert(error); - }); + }); }, canLockTrustLevel: lt("trust_level", 4), @@ -300,49 +203,27 @@ const AdminUser = User.extend({ return ajax("/admin/users/" + this.id + "/log_out", { type: "POST", data: { username_or_email: this.username }, - }).then(() => bootbox.alert(I18n.t("admin.user.logged_out"))); + }); }, impersonate() { return ajax("/admin/impersonate", { type: "POST", data: { username_or_email: this.username }, - }) - .then(() => (document.location = getURL("/"))) - .catch((e) => { - if (e.status === 404) { - bootbox.alert(I18n.t("admin.impersonate.not_found")); - } else { - bootbox.alert(I18n.t("admin.impersonate.invalid")); - } - }); + }); }, activate() { return ajax(`/admin/users/${this.id}/activate`, { type: "PUT", - }) - .then(() => window.location.reload()) - .catch((e) => { - const error = I18n.t("admin.user.activate_failed", { - error: this._formatError(e), - }); - bootbox.alert(error); - }); + }); }, deactivate() { return ajax(`/admin/users/${this.id}/deactivate`, { type: "PUT", data: { context: document.location.pathname }, - }) - .then(() => window.location.reload()) - .catch((e) => { - const error = I18n.t("admin.user.deactivate_failed", { - error: this._formatError(e), - }); - bootbox.alert(error); - }); + }); }, unsilence() { @@ -370,162 +251,39 @@ const AdminUser = User.extend({ return ajax(userPath("action/send_activation_email"), { type: "POST", data: { username: this.username }, - }) - .then(() => bootbox.alert(I18n.t("admin.user.activation_email_sent"))) - .catch(popupAjaxError); + }); }, anonymize() { - const user = this; - const message = I18n.t("admin.user.anonymize_confirm"); - - const performAnonymize = function () { - return ajax(`/admin/users/${user.get("id")}/anonymize.json`, { - type: "PUT", - }) - .then(function (data) { - if (data.success) { - if (data.username) { - document.location = getURL( - `/admin/users/${user.get("id")}/${data.username}` - ); - } else { - document.location = getURL("/admin/users/list/active"); - } - } else { - bootbox.alert(I18n.t("admin.user.anonymize_failed")); - if (data.user) { - user.setProperties(data.user); - } - } - }) - .catch(() => bootbox.alert(I18n.t("admin.user.anonymize_failed"))); - }; - - const buttons = [ - { - label: I18n.t("composer.cancel"), - class: "cancel", - link: true, - }, - { - label: - `${iconHTML("exclamation-triangle")} ` + - I18n.t("admin.user.anonymize_yes"), - class: "btn btn-danger", - callback: function () { - performAnonymize(); - }, - }, - ]; - - bootbox.dialog(message, buttons, { classes: "delete-user-modal" }); + return ajax(`/admin/users/${this.id}/anonymize.json`, { + type: "PUT", + }); }, - destroy(opts) { - const user = this; - const message = I18n.t("admin.user.delete_confirm"); - const location = document.location.pathname; - - const performDestroy = function (block) { - bootbox.dialog(I18n.t("admin.user.deleting_user")); - let formData = { context: location }; - if (block) { - formData["block_email"] = true; - formData["block_urls"] = true; - formData["block_ip"] = true; - } - if (opts && opts.deletePosts) { - formData["delete_posts"] = true; - } - return ajax(`/admin/users/${user.get("id")}.json`, { - type: "DELETE", - data: formData, - }) - .then(function (data) { - if (data.deleted) { - if (/^\/admin\/users\/list\//.test(location)) { - document.location = location; - } else { - document.location = getURL("/admin/users/list/active"); - } - } else { - bootbox.alert(I18n.t("admin.user.delete_failed")); - if (data.user) { - user.setProperties(data.user); - } - } - }) - .catch(function () { - AdminUser.find(user.get("id")).then((u) => user.setProperties(u)); - bootbox.alert(I18n.t("admin.user.delete_failed")); - }); - }; - - const buttons = [ - { - label: I18n.t("composer.cancel"), - class: "btn", - link: true, - }, - { - label: - `${iconHTML("exclamation-triangle")} ` + - I18n.t("admin.user.delete_and_block"), - class: "btn btn-danger", - callback: function () { - performDestroy(true); - }, - }, - { - label: I18n.t("admin.user.delete_dont_block"), - class: "btn btn-primary", - callback: function () { - performDestroy(false); - }, - }, - ]; - - bootbox.dialog(message, buttons, { classes: "delete-user-modal" }); - }, - - merge(opts) { - const user = this; - const location = document.location.pathname; - - bootbox.dialog(I18n.t("admin.user.merging_user")); - let formData = { context: location }; - - if (opts && opts.targetUsername) { - formData["target_username"] = opts.targetUsername; - } - - return ajax(`/admin/users/${user.id}/merge.json`, { - type: "POST", + destroy(formData) { + return ajax(`/admin/users/${this.id}.json`, { + type: "DELETE", data: formData, }) .then((data) => { - if (data.merged) { - if (/^\/admin\/users\/list\//.test(location)) { - DiscourseURL.redirectTo(location); - } else { - DiscourseURL.redirectTo( - `/admin/users/${data.user.id}/${data.user.username}` - ); - } - } else { - bootbox.alert(I18n.t("admin.user.merge_failed")); - if (data.user) { - user.setProperties(data.user); - } + if (!data.deleted && data.user) { + this.setProperties(data.user); } + + return data; }) .catch(() => { - AdminUser.find(user.id).then((u) => user.setProperties(u)); - bootbox.alert(I18n.t("admin.user.merge_failed")); + this.find(this.id).then((u) => this.setProperties(u)); }); }, + merge(formData) { + return ajax(`/admin/users/${this.id}/merge.json`, { + type: "POST", + data: formData, + }); + }, + loadDetails() { if (this.loadedDetails) { return Promise.resolve(this); @@ -553,10 +311,6 @@ const AdminUser = User.extend({ @discourseComputed("approved_by") approvedBy: wrapAdmin, - _formatError(event) { - return `http: ${event.status} - ${event.body}`; - }, - deleteSSORecord() { return ajax(`/admin/users/${this.id}/sso_record.json`, { type: "DELETE", diff --git a/app/assets/javascripts/admin/addon/models/api-key.js b/app/assets/javascripts/admin/addon/models/api-key.js index adf186d454..be5475a4ab 100644 --- a/app/assets/javascripts/admin/addon/models/api-key.js +++ b/app/assets/javascripts/admin/addon/models/api-key.js @@ -1,8 +1,8 @@ -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 discourseComputed from "discourse-common/utils/decorators"; import { fmt } from "discourse/lib/computed"; const ApiKey = RestModel.extend({ diff --git a/app/assets/javascripts/admin/addon/models/backup-status.js b/app/assets/javascripts/admin/addon/models/backup-status.js index 474c3c1688..dffad0d060 100644 --- a/app/assets/javascripts/admin/addon/models/backup-status.js +++ b/app/assets/javascripts/admin/addon/models/backup-status.js @@ -1,6 +1,6 @@ +import EmberObject from "@ember/object"; import discourseComputed from "discourse-common/utils/decorators"; import { not } from "@ember/object/computed"; -import EmberObject from "@ember/object"; export default EmberObject.extend({ restoreDisabled: not("restoreEnabled"), diff --git a/app/assets/javascripts/admin/addon/models/backup.js b/app/assets/javascripts/admin/addon/models/backup.js index 4112b06f2b..717192e8ac 100644 --- a/app/assets/javascripts/admin/addon/models/backup.js +++ b/app/assets/javascripts/admin/addon/models/backup.js @@ -1,10 +1,6 @@ -import getURL from "discourse-common/lib/get-url"; -import I18n from "I18n"; -import { ajax } from "discourse/lib/ajax"; -import { extractError } from "discourse/lib/ajax-error"; import EmberObject from "@ember/object"; import MessageBus from "message-bus-client"; -import bootbox from "bootbox"; +import { ajax } from "discourse/lib/ajax"; const Backup = EmberObject.extend({ destroy() { @@ -21,16 +17,7 @@ const Backup = EmberObject.extend({ Backup.reopenClass({ find() { - return ajax("/admin/backups.json") - .then((backups) => backups.map((backup) => Backup.create(backup))) - .catch((error) => { - bootbox.alert( - I18n.t("admin.backups.backup_storage_error", { - error_message: extractError(error), - }) - ); - return []; - }); + return ajax("/admin/backups.json"); }, start(withUploads) { @@ -43,33 +30,18 @@ Backup.reopenClass({ with_uploads: withUploads, client_id: MessageBus.clientId, }, - }).then((result) => { - if (!result.success) { - bootbox.alert(result.message); - } }); }, cancel() { return ajax("/admin/backups/cancel.json", { type: "DELETE", - }).then((result) => { - if (!result.success) { - bootbox.alert(result.message); - } }); }, rollback() { return ajax("/admin/backups/rollback.json", { type: "POST", - }).then((result) => { - if (!result.success) { - bootbox.alert(result.message); - } else { - // redirect to homepage (session might be lost) - window.location = getURL("/"); - } }); }, }); diff --git a/app/assets/javascripts/admin/addon/models/color-scheme-color.js b/app/assets/javascripts/admin/addon/models/color-scheme-color.js index f5427d542c..49cb552a92 100644 --- a/app/assets/javascripts/admin/addon/models/color-scheme-color.js +++ b/app/assets/javascripts/admin/addon/models/color-scheme-color.js @@ -1,10 +1,10 @@ -import I18n from "I18n"; import discourseComputed, { observes, on, } from "discourse-common/utils/decorators"; -import { propertyNotEqual } from "discourse/lib/computed"; import EmberObject from "@ember/object"; +import I18n from "I18n"; +import { propertyNotEqual } from "discourse/lib/computed"; const ColorSchemeColor = EmberObject.extend({ @on("init") diff --git a/app/assets/javascripts/admin/addon/models/color-scheme.js b/app/assets/javascripts/admin/addon/models/color-scheme.js index 4bce289ba7..e1b73ad14e 100644 --- a/app/assets/javascripts/admin/addon/models/color-scheme.js +++ b/app/assets/javascripts/admin/addon/models/color-scheme.js @@ -1,11 +1,11 @@ -import I18n from "I18n"; import { A } from "@ember/array"; import ArrayProxy from "@ember/array/proxy"; -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 EmberObject from "@ember/object"; +import I18n from "I18n"; +import { ajax } from "discourse/lib/ajax"; +import discourseComputed from "discourse-common/utils/decorators"; +import { not } from "@ember/object/computed"; const ColorScheme = EmberObject.extend({ init() { diff --git a/app/assets/javascripts/admin/addon/models/email-log.js b/app/assets/javascripts/admin/addon/models/email-log.js index 18406ceb7a..6565611974 100644 --- a/app/assets/javascripts/admin/addon/models/email-log.js +++ b/app/assets/javascripts/admin/addon/models/email-log.js @@ -1,7 +1,7 @@ -import getURL from "discourse-common/lib/get-url"; -import { ajax } from "discourse/lib/ajax"; import AdminUser from "admin/models/admin-user"; import EmberObject from "@ember/object"; +import { ajax } from "discourse/lib/ajax"; +import getURL from "discourse-common/lib/get-url"; const EmailLog = EmberObject.extend({}); diff --git a/app/assets/javascripts/admin/addon/models/email-preview.js b/app/assets/javascripts/admin/addon/models/email-preview.js index 893fb5b4fb..96ffe2f949 100644 --- a/app/assets/javascripts/admin/addon/models/email-preview.js +++ b/app/assets/javascripts/admin/addon/models/email-preview.js @@ -1,5 +1,5 @@ -import { ajax } from "discourse/lib/ajax"; import EmberObject from "@ember/object"; +import { ajax } from "discourse/lib/ajax"; const EmailPreview = EmberObject.extend({}); diff --git a/app/assets/javascripts/admin/addon/models/email-settings.js b/app/assets/javascripts/admin/addon/models/email-settings.js index 52ac14acec..f959df408f 100644 --- a/app/assets/javascripts/admin/addon/models/email-settings.js +++ b/app/assets/javascripts/admin/addon/models/email-settings.js @@ -1,5 +1,5 @@ -import { ajax } from "discourse/lib/ajax"; import EmberObject from "@ember/object"; +import { ajax } from "discourse/lib/ajax"; const EmailSettings = EmberObject.extend({}); diff --git a/app/assets/javascripts/admin/addon/models/email-template.js b/app/assets/javascripts/admin/addon/models/email-template.js index b58da0457f..4be7d9fff9 100644 --- a/app/assets/javascripts/admin/addon/models/email-template.js +++ b/app/assets/javascripts/admin/addon/models/email-template.js @@ -1,5 +1,5 @@ -import { ajax } from "discourse/lib/ajax"; import RestModel from "discourse/models/rest"; +import { ajax } from "discourse/lib/ajax"; const { getProperties } = Ember; export default RestModel.extend({ diff --git a/app/assets/javascripts/admin/addon/models/flag-type.js b/app/assets/javascripts/admin/addon/models/flag-type.js index 607cb755d8..5859584ce7 100644 --- a/app/assets/javascripts/admin/addon/models/flag-type.js +++ b/app/assets/javascripts/admin/addon/models/flag-type.js @@ -1,6 +1,6 @@ import I18n from "I18n"; -import discourseComputed from "discourse-common/utils/decorators"; import RestModel from "discourse/models/rest"; +import discourseComputed from "discourse-common/utils/decorators"; export default RestModel.extend({ @discourseComputed("id") diff --git a/app/assets/javascripts/admin/addon/models/incoming-email.js b/app/assets/javascripts/admin/addon/models/incoming-email.js index 42eb995f85..8489ae8dd3 100644 --- a/app/assets/javascripts/admin/addon/models/incoming-email.js +++ b/app/assets/javascripts/admin/addon/models/incoming-email.js @@ -1,6 +1,6 @@ -import { ajax } from "discourse/lib/ajax"; import AdminUser from "admin/models/admin-user"; import EmberObject from "@ember/object"; +import { ajax } from "discourse/lib/ajax"; const IncomingEmail = EmberObject.extend({}); diff --git a/app/assets/javascripts/admin/addon/models/permalink.js b/app/assets/javascripts/admin/addon/models/permalink.js index de61b27fd4..a2e8445b08 100644 --- a/app/assets/javascripts/admin/addon/models/permalink.js +++ b/app/assets/javascripts/admin/addon/models/permalink.js @@ -1,8 +1,8 @@ +import Category from "discourse/models/category"; +import DiscourseURL from "discourse/lib/url"; +import EmberObject from "@ember/object"; import { ajax } from "discourse/lib/ajax"; import discourseComputed from "discourse-common/utils/decorators"; -import DiscourseURL from "discourse/lib/url"; -import Category from "discourse/models/category"; -import EmberObject from "@ember/object"; const Permalink = EmberObject.extend({ save: function () { diff --git a/app/assets/javascripts/admin/addon/models/report.js b/app/assets/javascripts/admin/addon/models/report.js index a7b3d5786d..84e6304c75 100644 --- a/app/assets/javascripts/admin/addon/models/report.js +++ b/app/assets/javascripts/admin/addon/models/report.js @@ -1,19 +1,19 @@ -import getURL from "discourse-common/lib/get-url"; -import I18n from "I18n"; -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 { ajax } from "discourse/lib/ajax"; -import round from "discourse/lib/round"; +import { durationTiny, number } from "discourse/lib/formatter"; import { + escapeExpression, fillMissingDates, formatUsername, toNumber, - escapeExpression, } from "discourse/lib/utilities"; -import { number, durationTiny } from "discourse/lib/formatter"; +import EmberObject from "@ember/object"; +import I18n from "I18n"; +import { ajax } from "discourse/lib/ajax"; +import discourseComputed from "discourse-common/utils/decorators"; +import getURL from "discourse-common/lib/get-url"; +import { isEmpty } from "@ember/utils"; +import { makeArray } from "discourse-common/lib/helpers"; import { renderAvatar } from "discourse/helpers/user-avatar"; +import round from "discourse/lib/round"; // Change this line each time report format change // and you want to ensure cache is reset @@ -278,6 +278,7 @@ const Report = EmberObject.extend({ return { title: label.title, + htmlTitle: label.html_title, sortProperty: label.sort_property || mainProperty, mainProperty, type, diff --git a/app/assets/javascripts/admin/addon/models/screened-email.js b/app/assets/javascripts/admin/addon/models/screened-email.js index 4e902488d1..857cca0d63 100644 --- a/app/assets/javascripts/admin/addon/models/screened-email.js +++ b/app/assets/javascripts/admin/addon/models/screened-email.js @@ -1,7 +1,7 @@ -import I18n from "I18n"; -import discourseComputed from "discourse-common/utils/decorators"; -import { ajax } from "discourse/lib/ajax"; import EmberObject from "@ember/object"; +import I18n from "I18n"; +import { ajax } from "discourse/lib/ajax"; +import discourseComputed from "discourse-common/utils/decorators"; const ScreenedEmail = EmberObject.extend({ @discourseComputed("action") diff --git a/app/assets/javascripts/admin/addon/models/screened-ip-address.js b/app/assets/javascripts/admin/addon/models/screened-ip-address.js index 33e2b44317..a7d29aab31 100644 --- a/app/assets/javascripts/admin/addon/models/screened-ip-address.js +++ b/app/assets/javascripts/admin/addon/models/screened-ip-address.js @@ -1,8 +1,8 @@ +import EmberObject from "@ember/object"; import I18n from "I18n"; +import { ajax } from "discourse/lib/ajax"; import discourseComputed from "discourse-common/utils/decorators"; import { equal } from "@ember/object/computed"; -import { ajax } from "discourse/lib/ajax"; -import EmberObject from "@ember/object"; const ScreenedIpAddress = EmberObject.extend({ @discourseComputed("action_name") diff --git a/app/assets/javascripts/admin/addon/models/screened-url.js b/app/assets/javascripts/admin/addon/models/screened-url.js index a7bc7cfa6f..f3769c7d2f 100644 --- a/app/assets/javascripts/admin/addon/models/screened-url.js +++ b/app/assets/javascripts/admin/addon/models/screened-url.js @@ -1,7 +1,7 @@ -import I18n from "I18n"; -import discourseComputed from "discourse-common/utils/decorators"; -import { ajax } from "discourse/lib/ajax"; import EmberObject from "@ember/object"; +import I18n from "I18n"; +import { ajax } from "discourse/lib/ajax"; +import discourseComputed from "discourse-common/utils/decorators"; const ScreenedUrl = EmberObject.extend({ @discourseComputed("action") diff --git a/app/assets/javascripts/admin/addon/models/site-setting.js b/app/assets/javascripts/admin/addon/models/site-setting.js index 350281e616..49ed58366e 100644 --- a/app/assets/javascripts/admin/addon/models/site-setting.js +++ b/app/assets/javascripts/admin/addon/models/site-setting.js @@ -1,7 +1,7 @@ -import I18n from "I18n"; -import { ajax } from "discourse/lib/ajax"; -import Setting from "admin/mixins/setting-object"; import EmberObject from "@ember/object"; +import I18n from "I18n"; +import Setting from "admin/mixins/setting-object"; +import { ajax } from "discourse/lib/ajax"; import discourseComputed from "discourse-common/utils/decorators"; const SiteSetting = EmberObject.extend(Setting, { diff --git a/app/assets/javascripts/admin/addon/models/site-text.js b/app/assets/javascripts/admin/addon/models/site-text.js index 7dcd614402..c25a5b30a0 100644 --- a/app/assets/javascripts/admin/addon/models/site-text.js +++ b/app/assets/javascripts/admin/addon/models/site-text.js @@ -1,10 +1,10 @@ -import { ajax } from "discourse/lib/ajax"; import RestModel from "discourse/models/rest"; -const { getProperties } = Ember; +import { ajax } from "discourse/lib/ajax"; +import { getProperties } from "@ember/object"; export default RestModel.extend({ - revert() { - return ajax(`/admin/customize/site_texts/${this.id}`, { + revert(locale) { + return ajax(`/admin/customize/site_texts/${this.id}?locale=${locale}`, { type: "DELETE", }).then((result) => getProperties(result.site_text, "value", "can_revert")); }, diff --git a/app/assets/javascripts/admin/addon/models/staff-action-log.js b/app/assets/javascripts/admin/addon/models/staff-action-log.js index 09d84b6791..70d86c41e7 100644 --- a/app/assets/javascripts/admin/addon/models/staff-action-log.js +++ b/app/assets/javascripts/admin/addon/models/staff-action-log.js @@ -1,9 +1,9 @@ -import I18n from "I18n"; -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 I18n from "I18n"; import RestModel from "discourse/models/rest"; +import { ajax } from "discourse/lib/ajax"; +import discourseComputed from "discourse-common/utils/decorators"; +import { escapeExpression } from "discourse/lib/utilities"; function format(label, value, escape = true) { return value diff --git a/app/assets/javascripts/admin/addon/models/theme-settings.js b/app/assets/javascripts/admin/addon/models/theme-settings.js index a823592ad2..d91c3e583e 100644 --- a/app/assets/javascripts/admin/addon/models/theme-settings.js +++ b/app/assets/javascripts/admin/addon/models/theme-settings.js @@ -1,4 +1,4 @@ -import Setting from "admin/mixins/setting-object"; import EmberObject from "@ember/object"; +import Setting from "admin/mixins/setting-object"; export default EmberObject.extend(Setting, {}); diff --git a/app/assets/javascripts/admin/addon/models/theme.js b/app/assets/javascripts/admin/addon/models/theme.js index 10c78c5b82..d41ecb7f5f 100644 --- a/app/assets/javascripts/admin/addon/models/theme.js +++ b/app/assets/javascripts/admin/addon/models/theme.js @@ -1,9 +1,9 @@ -import I18n from "I18n"; -import { get } from "@ember/object"; +import { gt, or } from "@ember/object/computed"; import { isBlank, isEmpty } from "@ember/utils"; -import { or, gt } from "@ember/object/computed"; +import I18n from "I18n"; import RestModel from "discourse/models/rest"; import discourseComputed from "discourse-common/utils/decorators"; +import { get } from "@ember/object"; import { popupAjaxError } from "discourse/lib/ajax-error"; const THEME_UPLOAD_VAR = 2; @@ -253,6 +253,11 @@ const Theme = RestModel.extend({ } }, + @discourseComputed("recentlyInstalled", "component", "hasParents") + warnUnassignedComponent(recent, component, hasParents) { + return recent && component && !hasParents; + }, + removeChildTheme(theme) { const childThemes = this.childThemes; childThemes.removeObject(theme); diff --git a/app/assets/javascripts/admin/addon/models/tl3-requirements.js b/app/assets/javascripts/admin/addon/models/tl3-requirements.js index a6b31363b9..920d6e5f66 100644 --- a/app/assets/javascripts/admin/addon/models/tl3-requirements.js +++ b/app/assets/javascripts/admin/addon/models/tl3-requirements.js @@ -1,5 +1,5 @@ -import discourseComputed from "discourse-common/utils/decorators"; import EmberObject from "@ember/object"; +import discourseComputed from "discourse-common/utils/decorators"; export default EmberObject.extend({ @discourseComputed("days_visited", "time_period") diff --git a/app/assets/javascripts/admin/addon/models/version-check.js b/app/assets/javascripts/admin/addon/models/version-check.js index 53882d6eca..4f939064af 100644 --- a/app/assets/javascripts/admin/addon/models/version-check.js +++ b/app/assets/javascripts/admin/addon/models/version-check.js @@ -1,6 +1,6 @@ -import discourseComputed from "discourse-common/utils/decorators"; -import { ajax } from "discourse/lib/ajax"; import EmberObject from "@ember/object"; +import { ajax } from "discourse/lib/ajax"; +import discourseComputed from "discourse-common/utils/decorators"; const VersionCheck = EmberObject.extend({ @discourseComputed("updated_at") diff --git a/app/assets/javascripts/admin/addon/models/watched-word.js b/app/assets/javascripts/admin/addon/models/watched-word.js index 938b25f7bc..9aebef18eb 100644 --- a/app/assets/javascripts/admin/addon/models/watched-word.js +++ b/app/assets/javascripts/admin/addon/models/watched-word.js @@ -1,21 +1,27 @@ +import EmberObject from "@ember/object"; import I18n from "I18n"; import { ajax } from "discourse/lib/ajax"; -import EmberObject from "@ember/object"; const WatchedWord = EmberObject.extend({ save() { return ajax( - "/admin/logs/watched_words" + (this.id ? "/" + this.id : "") + ".json", + "/admin/customize/watched_words" + + (this.id ? "/" + this.id : "") + + ".json", { type: this.id ? "PUT" : "POST", - data: { word: this.word, action_key: this.action }, + data: { + word: this.word, + replacement: this.replacement, + action_key: this.action, + }, dataType: "json", } ); }, destroy() { - return ajax("/admin/logs/watched_words/" + this.id + ".json", { + return ajax("/admin/customize/watched_words/" + this.id + ".json", { type: "DELETE", }); }, @@ -23,7 +29,7 @@ const WatchedWord = EmberObject.extend({ WatchedWord.reopenClass({ findAll() { - return ajax("/admin/logs/watched_words.json").then((list) => { + return ajax("/admin/customize/watched_words.json").then((list) => { const actions = {}; list.words.forEach((s) => { if (!actions[s.action]) { diff --git a/app/assets/javascripts/admin/addon/models/web-hook.js b/app/assets/javascripts/admin/addon/models/web-hook.js index 677d76aa88..8dd568a9fb 100644 --- a/app/assets/javascripts/admin/addon/models/web-hook.js +++ b/app/assets/javascripts/admin/addon/models/web-hook.js @@ -1,9 +1,9 @@ -import { isEmpty } from "@ember/utils"; -import RestModel from "discourse/models/rest"; +import discourseComputed, { observes } from "discourse-common/utils/decorators"; import Category from "discourse/models/category"; import Group from "discourse/models/group"; -import discourseComputed, { observes } from "discourse-common/utils/decorators"; +import RestModel from "discourse/models/rest"; import Site from "discourse/models/site"; +import { isEmpty } from "@ember/utils"; export default RestModel.extend({ content_type: 1, // json diff --git a/app/assets/javascripts/admin/addon/routes/admin-backups-index.js b/app/assets/javascripts/admin/addon/routes/admin-backups-index.js index aee755b705..cb05c26580 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-backups-index.js +++ b/app/assets/javascripts/admin/addon/routes/admin-backups-index.js @@ -1,5 +1,5 @@ -import Route from "@ember/routing/route"; import Backup from "admin/models/backup"; +import Route from "@ember/routing/route"; export default Route.extend({ activate() { @@ -12,7 +12,9 @@ export default Route.extend({ }, model() { - return Backup.find(); + return Backup.find().then((backups) => + backups.map((backup) => Backup.create(backup)) + ); }, deactivate() { diff --git a/app/assets/javascripts/admin/addon/routes/admin-backups-logs.js b/app/assets/javascripts/admin/addon/routes/admin-backups-logs.js index e2e3a933c8..8de1ef5240 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-backups-logs.js +++ b/app/assets/javascripts/admin/addon/routes/admin-backups-logs.js @@ -1,6 +1,6 @@ import EmberObject from "@ember/object"; -import Route from "@ember/routing/route"; import PreloadStore from "discourse/lib/preload-store"; +import Route from "@ember/routing/route"; export default Route.extend({ // since the logs are pushed via the message bus diff --git a/app/assets/javascripts/admin/addon/routes/admin-backups.js b/app/assets/javascripts/admin/addon/routes/admin-backups.js index 41c6cb0f8b..063eeec9d7 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-backups.js +++ b/app/assets/javascripts/admin/addon/routes/admin-backups.js @@ -1,14 +1,15 @@ -import getURL from "discourse-common/lib/get-url"; -import I18n from "I18n"; -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 BackupStatus from "admin/models/backup-status"; +import DiscourseRoute from "discourse/routes/discourse"; +import EmberObject from "@ember/object"; +import I18n from "I18n"; import PreloadStore from "discourse/lib/preload-store"; import User from "discourse/models/user"; +import { ajax } from "discourse/lib/ajax"; import bootbox from "bootbox"; +import { extractError } from "discourse/lib/ajax-error"; +import getURL from "discourse-common/lib/get-url"; +import showModal from "discourse/lib/show-modal"; const LOG_CHANNEL = "/admin/backups/logs"; @@ -74,7 +75,11 @@ export default DiscourseRoute.extend({ startBackup(withUploads) { this.transitionTo("admin.backups.logs"); - Backup.start(withUploads); + Backup.start(withUploads).then((result) => { + if (!result.success) { + bootbox.alert(result.message); + } + }); }, destroyBackup(backup) { @@ -135,7 +140,14 @@ export default DiscourseRoute.extend({ I18n.t("yes_value"), (confirmed) => { if (confirmed) { - Backup.rollback(); + Backup.rollback().then((result) => { + if (!result.success) { + bootbox.alert(result.message); + } else { + // redirect to homepage (session might be lost) + window.location = getURL("/"); + } + }); } } ); @@ -152,12 +164,22 @@ export default DiscourseRoute.extend({ }, remoteUploadSuccess() { - Backup.find().then((backups) => { - this.controllerFor("adminBackupsIndex").set( - "model", - backups.map((backup) => Backup.create(backup)) - ); - }); + Backup.find() + .then((backups) => backups.map((backup) => Backup.create(backup))) + .then((backups) => { + this.controllerFor("adminBackupsIndex").set( + "model", + backups.map((backup) => Backup.create(backup)) + ); + }) + .catch((error) => { + bootbox.alert( + I18n.t("admin.backups.backup_storage_error", { + error_message: extractError(error), + }) + ); + return []; + }); }, }, }); diff --git a/app/assets/javascripts/admin/addon/routes/admin-badges-show.js b/app/assets/javascripts/admin/addon/routes/admin-badges-show.js index c95cae8afa..7053be216f 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-badges-show.js +++ b/app/assets/javascripts/admin/addon/routes/admin-badges-show.js @@ -1,10 +1,10 @@ +import Badge from "discourse/models/badge"; import I18n from "I18n"; -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"; import bootbox from "bootbox"; +import { get } from "@ember/object"; +import showModal from "discourse/lib/show-modal"; export default Route.extend({ serialize(m) { @@ -23,6 +23,15 @@ export default Route.extend({ ); }, + setupController(controller, model) { + this._super(...arguments); + if (model.image_url) { + controller.showImageUploader(); + } else if (model.icon) { + controller.showIconSelector(); + } + }, + actions: { saveError(e) { let msg = I18n.t("generic_error"); diff --git a/app/assets/javascripts/admin/addon/routes/admin-badges.js b/app/assets/javascripts/admin/addon/routes/admin-badges.js index 59bc0944cb..ac4b9e9b53 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-badges.js +++ b/app/assets/javascripts/admin/addon/routes/admin-badges.js @@ -1,8 +1,8 @@ -import I18n from "I18n"; -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"; +import DiscourseRoute from "discourse/routes/discourse"; +import I18n from "I18n"; +import { ajax } from "discourse/lib/ajax"; export default DiscourseRoute.extend({ _json: null, diff --git a/app/assets/javascripts/admin/addon/routes/admin-customize-colors.js b/app/assets/javascripts/admin/addon/routes/admin-customize-colors.js index b2d4f2c16b..cf2dc3d18d 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-customize-colors.js +++ b/app/assets/javascripts/admin/addon/routes/admin-customize-colors.js @@ -1,5 +1,5 @@ -import Route from "@ember/routing/route"; import ColorScheme from "admin/models/color-scheme"; +import Route from "@ember/routing/route"; export default Route.extend({ model() { diff --git a/app/assets/javascripts/admin/addon/routes/admin-customize-index.js b/app/assets/javascripts/admin/addon/routes/admin-customize-index.js index 931867c40c..615926c1c3 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-customize-index.js +++ b/app/assets/javascripts/admin/addon/routes/admin-customize-index.js @@ -1,6 +1,10 @@ import Route from "@ember/routing/route"; export default Route.extend({ beforeModel() { - this.transitionTo("adminCustomizeThemes"); + if (this.currentUser.admin) { + this.transitionTo("adminCustomizeThemes"); + } else { + this.transitionTo("adminWatchedWords"); + } }, }); diff --git a/app/assets/javascripts/admin/addon/routes/admin-customize-themes-edit.js b/app/assets/javascripts/admin/addon/routes/admin-customize-themes-edit.js index 030645e445..0d55baa6ce 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-customize-themes-edit.js +++ b/app/assets/javascripts/admin/addon/routes/admin-customize-themes-edit.js @@ -28,7 +28,7 @@ export default Route.extend({ const fields = wrapper.model .get("fields") [wrapper.target].map((f) => f.name); - if (wrapper.model.remote_theme) { + if (wrapper.model.remote_theme && wrapper.model.remote_theme.is_git) { this.transitionTo("adminCustomizeThemes.index"); return; } diff --git a/app/assets/javascripts/admin/addon/routes/admin-customize-themes-show.js b/app/assets/javascripts/admin/addon/routes/admin-customize-themes-show.js index feaf0ef7ae..5bcf95d3d2 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-customize-themes-show.js +++ b/app/assets/javascripts/admin/addon/routes/admin-customize-themes-show.js @@ -1,7 +1,21 @@ +import { COMPONENTS, THEMES } from "admin/models/theme"; import I18n from "I18n"; import Route from "@ember/routing/route"; import { scrollTop } from "discourse/mixins/scroll-top"; -import { THEMES, COMPONENTS } from "admin/models/theme"; + +export function showUnassignedComponentWarning(theme, callback) { + 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) { + theme.set("recentlyInstalled", false); + } + callback(result); + } + ); +} export default Route.extend({ serialize(model) { @@ -11,7 +25,7 @@ export default Route.extend({ model(params) { const all = this.modelFor("adminCustomizeThemes"); const model = all.findBy("id", parseInt(params.theme_id, 10)); - return model ? model : this.replaceWith("adminCustomizeTheme.index"); + return model ? model : this.replaceWith("adminCustomizeThemes.index"); }, setupController(controller, model) { @@ -55,19 +69,13 @@ export default Route.extend({ }, willTransition(transition) { const model = this.controller.model; - if (model.recentlyInstalled && !model.hasParents && model.component) { + if (model.warnUnassignedComponent) { 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(); - } + showUnassignedComponentWarning(model, (result) => { + if (!result) { + transition.retry(); } - ); + }); } }, }, diff --git a/app/assets/javascripts/admin/addon/routes/admin-customize-themes.js b/app/assets/javascripts/admin/addon/routes/admin-customize-themes.js index 2da0266f42..5eeae3a551 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-customize-themes.js +++ b/app/assets/javascripts/admin/addon/routes/admin-customize-themes.js @@ -1,7 +1,14 @@ import Route from "@ember/routing/route"; import showModal from "discourse/lib/show-modal"; +import { next } from "@ember/runloop"; +import { showUnassignedComponentWarning } from "admin/routes/admin-customize-themes-show"; export default Route.extend({ + queryParams: { + repoUrl: null, + repoName: null, + }, + model() { return this.store.findAll("theme"); }, @@ -9,17 +16,44 @@ export default Route.extend({ setupController(controller, model) { this._super(controller, model); controller.set("editingTheme", false); + + if (controller.repoUrl) { + next(() => { + showModal("admin-install-theme", { + admin: true, + }).setProperties({ + uploadUrl: controller.repoUrl, + uploadName: controller.repoName, + selection: "directRepoInstall", + }); + }); + } }, actions: { installModal() { - showModal("admin-install-theme", { admin: true }); + const currentTheme = this.controllerFor("adminCustomizeThemes.show") + .model; + if (currentTheme && currentTheme.warnUnassignedComponent) { + showUnassignedComponentWarning(currentTheme, (result) => { + if (!result) { + showModal("admin-install-theme", { admin: true }); + } + }); + } else { + showModal("admin-install-theme", { admin: true }); + } }, addTheme(theme) { this.refresh(); theme.setProperties({ recentlyInstalled: true }); - this.transitionTo("adminCustomizeThemes.show", theme.get("id")); + this.transitionTo("adminCustomizeThemes.show", theme.get("id"), { + queryParams: { + repoName: null, + repoUrl: null, + }, + }); }, }, }); diff --git a/app/assets/javascripts/admin/addon/routes/admin-email-bounced.js b/app/assets/javascripts/admin/addon/routes/admin-email-bounced.js index 041b25fc05..d89010c9ef 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-email-bounced.js +++ b/app/assets/javascripts/admin/addon/routes/admin-email-bounced.js @@ -1,5 +1,5 @@ -import showModal from "discourse/lib/show-modal"; import AdminEmailLogs from "admin/routes/admin-email-logs"; +import showModal from "discourse/lib/show-modal"; export default AdminEmailLogs.extend({ status: "bounced", diff --git a/app/assets/javascripts/admin/addon/routes/admin-email-preview-digest.js b/app/assets/javascripts/admin/addon/routes/admin-email-preview-digest.js index 03d72f0f82..730bcd0e7e 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-email-preview-digest.js +++ b/app/assets/javascripts/admin/addon/routes/admin-email-preview-digest.js @@ -1,5 +1,5 @@ -import DiscourseRoute from "discourse/routes/discourse"; import EmailPreview, { oneWeekAgo } from "admin/models/email-preview"; +import DiscourseRoute from "discourse/routes/discourse"; export default DiscourseRoute.extend({ model() { diff --git a/app/assets/javascripts/admin/addon/routes/admin-email-rejected.js b/app/assets/javascripts/admin/addon/routes/admin-email-rejected.js index e5d4501549..ce45fa8962 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-email-rejected.js +++ b/app/assets/javascripts/admin/addon/routes/admin-email-rejected.js @@ -1,5 +1,5 @@ -import showModal from "discourse/lib/show-modal"; import AdminEmailIncomings from "admin/routes/admin-email-incomings"; +import showModal from "discourse/lib/show-modal"; export default AdminEmailIncomings.extend({ status: "rejected", diff --git a/app/assets/javascripts/admin/addon/routes/admin-emojis.js b/app/assets/javascripts/admin/addon/routes/admin-emojis.js index 5db8760a51..5047bd6f82 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-emojis.js +++ b/app/assets/javascripts/admin/addon/routes/admin-emojis.js @@ -1,5 +1,5 @@ -import EmberObject from "@ember/object"; import DiscourseRoute from "discourse/routes/discourse"; +import EmberObject from "@ember/object"; import { ajax } from "discourse/lib/ajax"; export default DiscourseRoute.extend({ diff --git a/app/assets/javascripts/admin/addon/routes/admin-logs-staff-action-logs.js b/app/assets/javascripts/admin/addon/routes/admin-logs-staff-action-logs.js index a3c3f196e0..f643d41e18 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-logs-staff-action-logs.js +++ b/app/assets/javascripts/admin/addon/routes/admin-logs-staff-action-logs.js @@ -1,12 +1,20 @@ import DiscourseRoute from "discourse/routes/discourse"; -import showModal from "discourse/lib/show-modal"; import EmberObject from "@ember/object"; +import showModal from "discourse/lib/show-modal"; export default DiscourseRoute.extend({ queryParams: { filters: { refreshModel: true }, }, + beforeModel(transition) { + const params = transition.to.queryParams; + const controller = this.controllerFor("admin-logs-staff-action-logs"); + if (controller.filters === null || params.force_refresh) { + controller.resetFilters(); + } + }, + deserializeQueryParam(value, urlKey, defaultValueType) { if (urlKey === "filters") { return EmberObject.create(JSON.parse(decodeURIComponent(value))); @@ -27,13 +35,6 @@ export default DiscourseRoute.extend({ return this._super(value, urlKey, defaultValueType); }, - activate() { - const controller = this.controllerFor("admin-logs-staff-action-logs"); - if (controller.filters === null) { - controller.resetFilters(); - } - }, - // TODO: make this automatic using an `{{outlet}}` renderTemplate() { this.render("admin/templates/logs/staff-action-logs", { diff --git a/app/assets/javascripts/admin/addon/routes/admin-route-map.js b/app/assets/javascripts/admin/addon/routes/admin-route-map.js index bdc27d133f..9b888f9ce5 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-route-map.js +++ b/app/assets/javascripts/admin/addon/routes/admin-route-map.js @@ -97,6 +97,14 @@ export default function () { this.route("edit", { path: "/:field_name" }); } ); + this.route( + "adminWatchedWords", + { path: "/watched_words", resetNamespace: true }, + function () { + this.route("index", { path: "/" }); + this.route("action", { path: "/action/:action_id" }); + } + ); } ); @@ -152,14 +160,6 @@ export default function () { this.route("term", { path: "/term" }); } ); - this.route( - "adminWatchedWords", - { path: "/watched_words", resetNamespace: true }, - function () { - this.route("index", { path: "/" }); - this.route("action", { path: "/action/:action_id" }); - } - ); } ); diff --git a/app/assets/javascripts/admin/addon/routes/admin-search-logs-index.js b/app/assets/javascripts/admin/addon/routes/admin-search-logs-index.js index c688b8ebba..c04f3b22b8 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-search-logs-index.js +++ b/app/assets/javascripts/admin/addon/routes/admin-search-logs-index.js @@ -1,5 +1,5 @@ -import EmberObject from "@ember/object"; import DiscourseRoute from "discourse/routes/discourse"; +import EmberObject from "@ember/object"; import { ajax } from "discourse/lib/ajax"; export default DiscourseRoute.extend({ diff --git a/app/assets/javascripts/admin/addon/routes/admin-search-logs-term.js b/app/assets/javascripts/admin/addon/routes/admin-search-logs-term.js index 989d258384..b9613fe04f 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-search-logs-term.js +++ b/app/assets/javascripts/admin/addon/routes/admin-search-logs-term.js @@ -1,5 +1,5 @@ -import EmberObject from "@ember/object"; import DiscourseRoute from "discourse/routes/discourse"; +import EmberObject from "@ember/object"; import { ajax } from "discourse/lib/ajax"; import { fillMissingDates } from "discourse/lib/utilities"; import { translateResults } from "discourse/lib/search"; diff --git a/app/assets/javascripts/admin/addon/routes/admin-site-settings-category.js b/app/assets/javascripts/admin/addon/routes/admin-site-settings-category.js index be2a2180d2..aab0b337db 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-site-settings-category.js +++ b/app/assets/javascripts/admin/addon/routes/admin-site-settings-category.js @@ -1,6 +1,6 @@ -import I18n from "I18n"; -import EmberObject from "@ember/object"; import DiscourseRoute from "discourse/routes/discourse"; +import EmberObject from "@ember/object"; +import I18n from "I18n"; export default DiscourseRoute.extend({ model(params) { diff --git a/app/assets/javascripts/admin/addon/routes/admin-site-text-edit.js b/app/assets/javascripts/admin/addon/routes/admin-site-text-edit.js index fe720907c7..40c74fced3 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-site-text-edit.js +++ b/app/assets/javascripts/admin/addon/routes/admin-site-text-edit.js @@ -1,10 +1,30 @@ import Route from "@ember/routing/route"; +import { ajax } from "discourse/lib/ajax"; + export default Route.extend({ + queryParams: { + locale: { replace: true }, + }, + model(params) { - return this.store.find("site-text", params.id); + return ajax( + `/admin/customize/site_texts/${params.id}?locale=${params.locale}` + ).then((result) => { + return this.store.createRecord("site-text", result.site_text); + }); }, setupController(controller, siteText) { - controller.setProperties({ siteText, saved: false }); + const locales = JSON.parse(this.siteSettings.available_locales); + + const localeFullName = locales.find((locale) => { + return locale.value === controller.locale; + }).name; + + controller.setProperties({ + siteText, + saved: false, + localeFullName: localeFullName, + }); }, }); diff --git a/app/assets/javascripts/admin/addon/routes/admin-site-text-index.js b/app/assets/javascripts/admin/addon/routes/admin-site-text-index.js index 036438235d..255315a846 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-site-text-index.js +++ b/app/assets/javascripts/admin/addon/routes/admin-site-text-index.js @@ -1,17 +1,18 @@ import Route from "@ember/routing/route"; -import showModal from "discourse/lib/show-modal"; import { getProperties } from "@ember/object"; +import showModal from "discourse/lib/show-modal"; export default Route.extend({ queryParams: { q: { replace: true }, overridden: { replace: true }, + locale: { replace: true }, }, model(params) { return this.store.find( "site-text", - getProperties(params, "q", "overridden") + getProperties(params, "q", "overridden", "locale") ); }, diff --git a/app/assets/javascripts/admin/addon/routes/admin-user-badges.js b/app/assets/javascripts/admin/addon/routes/admin-user-badges.js index f15649f92d..0c1621926f 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-user-badges.js +++ b/app/assets/javascripts/admin/addon/routes/admin-user-badges.js @@ -1,6 +1,6 @@ +import Badge from "discourse/models/badge"; import DiscourseRoute from "discourse/routes/discourse"; import UserBadge from "discourse/models/user-badge"; -import Badge from "discourse/models/badge"; export default DiscourseRoute.extend({ model() { @@ -14,7 +14,7 @@ export default DiscourseRoute.extend({ Badge.findAll().then(function (badges) { controller.set("badges", badges); if (badges.length > 0) { - var grantableBadges = controller.get("grantableBadges"); + let grantableBadges = controller.get("grantableBadges"); if (grantableBadges.length > 0) { controller.set("selectedBadgeId", grantableBadges[0].get("id")); } diff --git a/app/assets/javascripts/admin/addon/routes/admin-user.js b/app/assets/javascripts/admin/addon/routes/admin-user.js index 90436dabd4..d9627ba571 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-user.js +++ b/app/assets/javascripts/admin/addon/routes/admin-user.js @@ -1,6 +1,6 @@ -import { get } from "@ember/object"; -import DiscourseRoute from "discourse/routes/discourse"; import AdminUser from "admin/models/admin-user"; +import DiscourseRoute from "discourse/routes/discourse"; +import { get } from "@ember/object"; export default DiscourseRoute.extend({ serialize(model) { diff --git a/app/assets/javascripts/admin/addon/routes/admin-users-list.js b/app/assets/javascripts/admin/addon/routes/admin-users-list.js index db8106a8ed..4630c8efc8 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-users-list.js +++ b/app/assets/javascripts/admin/addon/routes/admin-users-list.js @@ -1,7 +1,7 @@ +import AdminUser from "admin/models/admin-user"; 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 DiscourseRoute.extend({ actions: { diff --git a/app/assets/javascripts/admin/addon/routes/admin-watched-words-action.js b/app/assets/javascripts/admin/addon/routes/admin-watched-words-action.js index 3c770f7e09..fe1ce75ab7 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-watched-words-action.js +++ b/app/assets/javascripts/admin/addon/routes/admin-watched-words-action.js @@ -1,6 +1,6 @@ -import I18n from "I18n"; -import EmberObject from "@ember/object"; import DiscourseRoute from "discourse/routes/discourse"; +import EmberObject from "@ember/object"; +import I18n from "I18n"; export default DiscourseRoute.extend({ model(params) { diff --git a/app/assets/javascripts/admin/addon/routes/admin-web-hooks-show-events.js b/app/assets/javascripts/admin/addon/routes/admin-web-hooks-show-events.js index b07b390d40..1b7923224b 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-web-hooks-show-events.js +++ b/app/assets/javascripts/admin/addon/routes/admin-web-hooks-show-events.js @@ -1,5 +1,5 @@ -import { get } from "@ember/object"; import DiscourseRoute from "discourse/routes/discourse"; +import { get } from "@ember/object"; export default DiscourseRoute.extend({ model(params) { diff --git a/app/assets/javascripts/admin/addon/routes/admin-web-hooks-show.js b/app/assets/javascripts/admin/addon/routes/admin-web-hooks-show.js index a5eb3dd873..6d7c64f955 100644 --- a/app/assets/javascripts/admin/addon/routes/admin-web-hooks-show.js +++ b/app/assets/javascripts/admin/addon/routes/admin-web-hooks-show.js @@ -1,5 +1,5 @@ -import { get } from "@ember/object"; import DiscourseRoute from "discourse/routes/discourse"; +import { get } from "@ember/object"; export default DiscourseRoute.extend({ serialize(model) { diff --git a/app/assets/javascripts/admin/addon/routes/admin.js b/app/assets/javascripts/admin/addon/routes/admin.js index c84051d171..e0949ba414 100644 --- a/app/assets/javascripts/admin/addon/routes/admin.js +++ b/app/assets/javascripts/admin/addon/routes/admin.js @@ -1,5 +1,5 @@ -import I18n from "I18n"; import DiscourseRoute from "discourse/routes/discourse"; +import I18n from "I18n"; export default DiscourseRoute.extend({ titleToken() { diff --git a/app/assets/javascripts/admin/addon/services/admin-tools.js b/app/assets/javascripts/admin/addon/services/admin-tools.js index fc02b998fe..8928af14e5 100644 --- a/app/assets/javascripts/admin/addon/services/admin-tools.js +++ b/app/assets/javascripts/admin/addon/services/admin-tools.js @@ -1,16 +1,16 @@ -import I18n from "I18n"; +import AdminUser from "admin/models/admin-user"; // A service that can act as a bridge between the front end Discourse application // 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 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 I18n from "I18n"; import { Promise } from "rsvp"; +import Service from "@ember/service"; +import { ajax } from "discourse/lib/ajax"; import bootbox from "bootbox"; +import { getOwner } from "discourse-common/lib/get-owner"; +import { iconHTML } from "discourse-common/lib/icon-library"; +import showModal from "discourse/lib/show-modal"; export default Service.extend({ init() { @@ -33,8 +33,8 @@ export default Service.extend({ return AdminUser.find(userId).then((au) => this.spammerDetails(au)); }, - deleteUser(id) { - AdminUser.find(id).then((user) => user.destroy({ deletePosts: true })); + deleteUser(id, formData) { + return AdminUser.find(id).then((user) => user.destroy(formData)); }, spammerDetails(adminUser) { diff --git a/app/assets/javascripts/admin/addon/templates/admin.hbs b/app/assets/javascripts/admin/addon/templates/admin.hbs index bcbe6940d4..cc27366dd9 100644 --- a/app/assets/javascripts/admin/addon/templates/admin.hbs +++ b/app/assets/javascripts/admin/addon/templates/admin.hbs @@ -18,8 +18,8 @@ {{nav-item route="adminEmail" label="admin.email.title"}} {{/if}} {{nav-item route="adminLogs" label="admin.logs.title"}} + {{nav-item route="adminCustomize" label="admin.customize.title"}} {{#if currentUser.admin}} - {{nav-item route="adminCustomize" label="admin.customize.title"}} {{nav-item route="adminApi" label="admin.api.title"}} {{#if siteSettings.enable_backups}} {{nav-item route="admin.backups" label="admin.backups.title"}} diff --git a/app/assets/javascripts/admin/addon/templates/api-keys-new.hbs b/app/assets/javascripts/admin/addon/templates/api-keys-new.hbs index 7d6219ffac..a8957262df 100644 --- a/app/assets/javascripts/admin/addon/templates/api-keys-new.hbs +++ b/app/assets/javascripts/admin/addon/templates/api-keys-new.hbs @@ -25,10 +25,14 @@ {{#if showUserSelector}} {{#admin-form-row label="admin.api.user"}} - {{user-selector single="true" - usernames=model.username - placeholderKey="admin.api.user_placeholder" - }} + {{email-group-user-chooser + value=model.username + onChange=(action "updateUsername") + options=(hash + maximum=1 + filterPlaceholder="admin.api.user_placeholder" + ) + }} {{/admin-form-row}} {{/if}} diff --git a/app/assets/javascripts/admin/addon/templates/badges-show.hbs b/app/assets/javascripts/admin/addon/templates/badges-show.hbs index 49741c8bc6..e71ed63bb3 100644 --- a/app/assets/javascripts/admin/addon/templates/badges-show.hbs +++ b/app/assets/javascripts/admin/addon/templates/badges-show.hbs @@ -4,29 +4,59 @@ {{#if readOnly}} {{input type="text" name="name" value=buffered.name disabled=true}} +

+ {{#link-to "adminSiteText" (query-params q=(concat textCustomizationPrefix "name"))}} + {{i18n "admin.badges.read_only_setting_help"}} + {{/link-to}} +

{{else}} {{input type="text" name="name" value=buffered.name}} {{/if}}
- - {{icon-picker - name="icon" - value=buffered.icon - options=(hash - maximum=1 - ) - onChange=(action (mut buffered.icon)) - }} + +
+ -

{{i18n "admin.badges.icon_help"}}

-
- -
- - {{input type="text" name="image" value=buffered.image}} -

{{i18n "admin.badges.image_help"}}

+ +
+ {{#if imageUploaderSelected}} + {{image-uploader + imageUrl=buffered.image_url + onUploadDone=(action "setImage") + onUploadDeleted=(action "removeImage") + type="badge_image" + class="no-repeat contain-image"}} +
+

{{i18n "admin.badges.image_help"}}

+
+ {{else if iconSelectorSelected}} + {{icon-picker + name="icon" + value=buffered.icon + options=(hash maximum=1) + onChange=(action (mut buffered.icon)) + }} + {{/if}}
@@ -64,6 +94,11 @@ {{#if buffered.system}} {{textarea name="description" value=buffered.description disabled=true}} +

+ {{#link-to "adminSiteText" (query-params q=(concat textCustomizationPrefix "description"))}} + {{i18n "admin.badges.read_only_setting_help"}} + {{/link-to}} +

{{else}} {{textarea name="description" value=buffered.description}} {{/if}} @@ -73,6 +108,11 @@ {{#if buffered.system}} {{textarea name="long_description" value=buffered.long_description disabled=true}} +

+ {{#link-to "adminSiteText" (query-params q=(concat textCustomizationPrefix "long_description"))}} + {{i18n "admin.badges.read_only_setting_help"}} + {{/link-to}} +

{{else}} {{textarea name="long_description" value=buffered.long_description}} {{/if}} diff --git a/app/assets/javascripts/admin/addon/templates/badges.hbs b/app/assets/javascripts/admin/addon/templates/badges.hbs index 82d77418d3..2f3d3358d9 100644 --- a/app/assets/javascripts/admin/addon/templates/badges.hbs +++ b/app/assets/javascripts/admin/addon/templates/badges.hbs @@ -7,7 +7,7 @@ {{i18n "admin.badges.new"}} {{/link-to}} - {{#link-to "adminBadges.award" "new" class="btn"}} + {{#link-to "adminBadges.award" "new" class="btn btn-default"}} {{d-icon "upload"}} {{i18n "admin.badges.mass_award.title"}} {{/link-to}} diff --git a/app/assets/javascripts/admin/addon/templates/components/admin-report-table-header.hbs b/app/assets/javascripts/admin/addon/templates/components/admin-report-table-header.hbs index 15f85276f5..f03bb92f2c 100644 --- a/app/assets/javascripts/admin/addon/templates/components/admin-report-table-header.hbs +++ b/app/assets/javascripts/admin/addon/templates/components/admin-report-table-header.hbs @@ -2,4 +2,8 @@ {{d-button action=sortByLabel icon=sortIcon class="sort-btn"}} {{/if}} -{{label.title}} +{{#if label.htmlTitle}} + {{html-safe label.htmlTitle}} +{{else}} + {{label.title}} +{{/if}} diff --git a/app/assets/javascripts/admin/addon/templates/components/admin-theme-editor.hbs b/app/assets/javascripts/admin/addon/templates/components/admin-theme-editor.hbs index ce928108a2..a309f9aba5 100644 --- a/app/assets/javascripts/admin/addon/templates/components/admin-theme-editor.hbs +++ b/app/assets/javascripts/admin/addon/templates/components/admin-theme-editor.hbs @@ -87,4 +87,17 @@
{{error}}
{{/if}} -{{ace-editor content=activeSection editorId=editorId mode=activeSectionMode autofocus="true" placeholder=placeholder}} +{{#if warning}} +
{{html-safe warning}}
+{{/if}} + +{{ace-editor + content=activeSection + editorId=editorId + mode=activeSectionMode + autofocus="true" + placeholder=placeholder + htmlPlaceholder=true + save=(action "save") + setWarning=(action "setWarning") +}} diff --git a/app/assets/javascripts/admin/addon/templates/components/admin-user-field-item.hbs b/app/assets/javascripts/admin/addon/templates/components/admin-user-field-item.hbs index 211cc00b51..06ab7a286e 100644 --- a/app/assets/javascripts/admin/addon/templates/components/admin-user-field-item.hbs +++ b/app/assets/javascripts/admin/addon/templates/components/admin-user-field-item.hbs @@ -22,19 +22,23 @@ {{/if}} {{#admin-form-row wrapLabel="true"}} - {{input type="checkbox" checked=buffered.editable}} {{i18n "admin.user_fields.editable.title"}} + {{input type="checkbox" checked=buffered.editable}} {{i18n "admin.user_fields.editable.title"}} {{/admin-form-row}} {{#admin-form-row wrapLabel="true"}} - {{input type="checkbox" checked=buffered.required}} {{i18n "admin.user_fields.required.title"}} + {{input type="checkbox" checked=buffered.required}} {{i18n "admin.user_fields.required.title"}} {{/admin-form-row}} {{#admin-form-row wrapLabel="true"}} - {{input type="checkbox" checked=buffered.show_on_profile}} {{i18n "admin.user_fields.show_on_profile.title"}} + {{input type="checkbox" checked=buffered.show_on_profile}} {{i18n "admin.user_fields.show_on_profile.title"}} {{/admin-form-row}} {{#admin-form-row wrapLabel="true"}} - {{input type="checkbox" checked=buffered.show_on_user_card}} {{i18n "admin.user_fields.show_on_user_card.title"}} + {{input type="checkbox" checked=buffered.show_on_user_card}} {{i18n "admin.user_fields.show_on_user_card.title"}} + {{/admin-form-row}} + + {{#admin-form-row wrapLabel="true"}} + {{input type="checkbox" checked=buffered.searchable}} {{i18n "admin.user_fields.searchable.title"}} {{/admin-form-row}} {{#admin-form-row}} diff --git a/app/assets/javascripts/admin/addon/templates/components/admin-watched-word.hbs b/app/assets/javascripts/admin/addon/templates/components/admin-watched-word.hbs index c9e4fd9b04..cc03496510 100644 --- a/app/assets/javascripts/admin/addon/templates/components/admin-watched-word.hbs +++ b/app/assets/javascripts/admin/addon/templates/components/admin-watched-word.hbs @@ -1 +1 @@ -{{xIcon}}{{watchedWord}} +{{d-icon "times"}} {{word.word}} {{#if word.replacement}}→ {{word.replacement}}{{/if}} diff --git a/app/assets/javascripts/admin/addon/templates/components/dashboard-new-feature-item.hbs b/app/assets/javascripts/admin/addon/templates/components/dashboard-new-feature-item.hbs new file mode 100644 index 0000000000..2aff298640 --- /dev/null +++ b/app/assets/javascripts/admin/addon/templates/components/dashboard-new-feature-item.hbs @@ -0,0 +1,13 @@ +
+
{{item.emoji}}
+
+
+ {{#if item.link}} + {{item.title}} + {{else}} + {{item.title}} + {{/if}} +
+
{{item.description}}
+
+
diff --git a/app/assets/javascripts/admin/addon/templates/components/dashboard-new-features.hbs b/app/assets/javascripts/admin/addon/templates/components/dashboard-new-features.hbs new file mode 100644 index 0000000000..74f1a90d9c --- /dev/null +++ b/app/assets/javascripts/admin/addon/templates/components/dashboard-new-features.hbs @@ -0,0 +1,19 @@ +{{#if newFeatures}} +
+

{{replace-emoji (i18n "admin.dashboard.new_features.title") }}

+
+ +
+ {{#each newFeatures as |feature|}} + {{dashboard-new-feature-item item=feature tagName=""}} + {{/each}} +
+ +{{/if}} diff --git a/app/assets/javascripts/admin/addon/templates/components/email-styles-editor.hbs b/app/assets/javascripts/admin/addon/templates/components/email-styles-editor.hbs index 742c71a5b3..cc510651d0 100644 --- a/app/assets/javascripts/admin/addon/templates/components/email-styles-editor.hbs +++ b/app/assets/javascripts/admin/addon/templates/components/email-styles-editor.hbs @@ -9,12 +9,10 @@
-{{ace-editor content=editorContents mode=currentEditorMode editorId=editorId}} +{{ace-editor content=editorContents mode=currentEditorMode editorId=editorId save=(action "save")}} diff --git a/app/assets/javascripts/admin/addon/templates/components/embeddable-host.hbs b/app/assets/javascripts/admin/addon/templates/components/embeddable-host.hbs index 7d73d93a2b..db8d108308 100644 --- a/app/assets/javascripts/admin/addon/templates/components/embeddable-host.hbs +++ b/app/assets/javascripts/admin/addon/templates/components/embeddable-host.hbs @@ -1,7 +1,7 @@ {{#if editing}}
{{i18n "admin.embedding.host"}}
- {{input value=buffered.host placeholder="example.com" enter=(action "save") class="host-name"}} + {{input value=buffered.host placeholder="example.com" enter=(action "save") class="host-name" autofocus=true}}
{{i18n "admin.embedding.class_name"}}
diff --git a/app/assets/javascripts/admin/addon/templates/components/emoji-value-list.hbs b/app/assets/javascripts/admin/addon/templates/components/emoji-value-list.hbs new file mode 100644 index 0000000000..d1843a2a26 --- /dev/null +++ b/app/assets/javascripts/admin/addon/templates/components/emoji-value-list.hbs @@ -0,0 +1,52 @@ +{{#if collection}} + +{{/if}} + +
+ {{d-button + action=(action "editValue") + actionParam=data + icon="emoji-icon" + class="add-emoji-button d-editor-textarea-wrapper" + label="admin.site_settings.emoji_list.add_emoji_button.label" + }} +
+ +{{emoji-picker + isActive=emojiPickerIsActive + isEditorFocused=isEditorFocused + emojiSelected=(action "emojiSelected") + onEmojiPickerClose=(action "closeEmojiPicker") +}} + +{{setting-validation-message message=validationMessage}} diff --git a/app/assets/javascripts/admin/addon/templates/components/report-filters/bool.hbs b/app/assets/javascripts/admin/addon/templates/components/report-filters/bool.hbs index 4d96755ff9..de106adfd8 100644 --- a/app/assets/javascripts/admin/addon/templates/components/report-filters/bool.hbs +++ b/app/assets/javascripts/admin/addon/templates/components/report-filters/bool.hbs @@ -2,4 +2,4 @@ type="checkbox" checked=checked click=(action "onChange") -}} \ No newline at end of file +}} diff --git a/app/assets/javascripts/admin/addon/templates/components/site-setting.hbs b/app/assets/javascripts/admin/addon/templates/components/site-setting.hbs index b7029ebe02..01e47fee83 100644 --- a/app/assets/javascripts/admin/addon/templates/components/site-setting.hbs +++ b/app/assets/javascripts/admin/addon/templates/components/site-setting.hbs @@ -2,7 +2,7 @@

{{#if staffLogFilter}} {{settingName}} - {{#link-to "adminLogs.staffActionLogs" (query-params filters=staffLogFilter) title=(i18n "admin.settings.history")}} + {{#link-to "adminLogs.staffActionLogs" (query-params filters=staffLogFilter force_refresh=true) title=(i18n "admin.settings.history")}} {{d-icon "history"}} diff --git a/app/assets/javascripts/admin/addon/templates/components/site-settings/emoji-list.hbs b/app/assets/javascripts/admin/addon/templates/components/site-settings/emoji-list.hbs new file mode 100644 index 0000000000..50d1d18898 --- /dev/null +++ b/app/assets/javascripts/admin/addon/templates/components/site-settings/emoji-list.hbs @@ -0,0 +1,3 @@ +{{emoji-value-list setting=setting values=value}} +
{{html-safe setting.description}}
+{{setting-validation-message message=validationMessage}} diff --git a/app/assets/javascripts/admin/addon/templates/components/site-settings/html.hbs b/app/assets/javascripts/admin/addon/templates/components/site-settings/html.hbs new file mode 100644 index 0000000000..0da61a84e1 --- /dev/null +++ b/app/assets/javascripts/admin/addon/templates/components/site-settings/html.hbs @@ -0,0 +1,2 @@ +{{text-field value=(html-safe value) classNames="input-setting-string"}} +
{{html-safe setting.description}}
diff --git a/app/assets/javascripts/admin/addon/templates/components/site-settings/string.hbs b/app/assets/javascripts/admin/addon/templates/components/site-settings/string.hbs index 91106e9bdd..09eb918646 100644 --- a/app/assets/javascripts/admin/addon/templates/components/site-settings/string.hbs +++ b/app/assets/javascripts/admin/addon/templates/components/site-settings/string.hbs @@ -1,5 +1,11 @@ {{#if setting.textarea}} {{textarea value=value classNames="input-setting-textarea"}} +{{else if setting.json_schema}} + {{d-button + action=(action "launchJsonEditorModal") + icon="pencil-alt" + label="admin.site_settings.json_schema.edit" + }} {{else if isSecret}} {{input type="password" value=value classNames="input-setting-string"}} {{else}} diff --git a/app/assets/javascripts/admin/addon/templates/components/site-settings/tag-list.hbs b/app/assets/javascripts/admin/addon/templates/components/site-settings/tag-list.hbs index 7efd7421ce..b03ec4f36e 100644 --- a/app/assets/javascripts/admin/addon/templates/components/site-settings/tag-list.hbs +++ b/app/assets/javascripts/admin/addon/templates/components/site-settings/tag-list.hbs @@ -1,6 +1,7 @@ {{tag-chooser tags=selectedTags onChange=(action "changeSelectedTags") + everyTag=true options=(hash allowAny=false ) diff --git a/app/assets/javascripts/admin/addon/templates/components/tags-uploader.hbs b/app/assets/javascripts/admin/addon/templates/components/tags-uploader.hbs index 1531feb677..4ab018c5e9 100644 --- a/app/assets/javascripts/admin/addon/templates/components/tags-uploader.hbs +++ b/app/assets/javascripts/admin/addon/templates/components/tags-uploader.hbs @@ -1,4 +1,4 @@ -