Merge master

This commit is contained in:
Neil Lalonde 2021-05-18 14:09:54 -04:00
commit 2c399a84fe
No known key found for this signature in database
GPG Key ID: FF871CA9037D0A91
3296 changed files with 143829 additions and 68301 deletions

View File

@ -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"
}

View File

@ -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

View File

@ -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"
}
}

View File

@ -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

View File

@ -1,5 +1,9 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: bundler
directory: "/"
schedule:

View File

@ -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

47
.github/workflows/ember.yml vendored Normal file
View File

@ -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

94
.github/workflows/linting.yml vendored Normal file
View File

@ -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"

133
.github/workflows/tests.yml vendored Normal file
View File

@ -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

157
.gitignore vendored
View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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"],
}
]
}

12
.vscode-sample/tasks.json Normal file
View File

@ -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"
},
],
}

View File

@ -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. <a href='https://meta.discourse.org/t/prettier-code-formatting-tool/93212'>Here's how to set up prettier in your code editor.</a>\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

20
Gemfile
View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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?

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 759 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@ -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

View File

@ -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();
};
},
});

View File

@ -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;

View File

@ -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: [
{

View File

@ -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() {

View File

@ -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",

View File

@ -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() {

View File

@ -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({

View File

@ -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",

View File

@ -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",

View File

@ -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;

View File

@ -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,
});

View File

@ -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() ? `<div dir="ltr">${example}</div>` : 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);
},
},
});

View File

@ -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"
);

View File

@ -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

View File

@ -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"],

View File

@ -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",

View File

@ -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({

View File

@ -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");
},
});

View File

@ -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.

View File

@ -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));
},
});

View File

@ -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();
},
},
});

View File

@ -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);

View File

@ -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"],

View File

@ -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("|"));
},
});

View File

@ -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({

View File

@ -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"],

View File

@ -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"];

View File

@ -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");

View File

@ -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,

View File

@ -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"),

View File

@ -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"],

View File

@ -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 */

View File

@ -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.

View File

@ -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;
});

View File

@ -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({

View File

@ -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, {

View File

@ -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")

View File

@ -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({

View File

@ -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

View File

@ -1,5 +1,5 @@
import { computed } from "@ember/object";
import Component from "@ember/component";
import { computed } from "@ember/object";
export default Component.extend({
tokenSeparator: "|",

View File

@ -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);
});
},
});

View File

@ -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")

View File

@ -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"],

View File

@ -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";

View File

@ -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, {

View File

@ -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";

View File

@ -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, {

View File

@ -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",

View File

@ -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;

View File

@ -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";

View File

@ -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"],

View File

@ -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"),

View File

@ -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() {

View File

@ -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: {

View File

@ -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);

View File

@ -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"), {

View File

@ -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(),

View File

@ -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(),

View File

@ -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"),

View File

@ -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,

View File

@ -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;
}

View File

@ -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"),

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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(),

View File

@ -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"),

View File

@ -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"), {

View File

@ -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);
},
},
});

View File

@ -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() {

Some files were not shown because too many files have changed in this diff Show More