Merge v3.1.0.beta8 into stable

This commit is contained in:
David Taylor 2023-08-01 10:00:11 +01:00
commit 6a564109d1
No known key found for this signature in database
GPG Key ID: 46904C18B1D3F434
5354 changed files with 200605 additions and 119139 deletions

View File

@ -12,3 +12,4 @@ node_modules/
spec/
dist/
tmp/
documentation/

4
.gitattributes vendored
View File

@ -27,3 +27,7 @@
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
# Ember App
*.gjs linguist-language=js linguist-detectable
*.gts linguist-language=ts linguist-detectable

2
.github/labeler.yml vendored
View File

@ -1,2 +1,2 @@
chat:
- plugins/chat/**/*
- plugins/chat/**/*

View File

@ -1,14 +1,17 @@
name: "Pull Request Labeler"
name: Pull Request Labeler
on:
- pull_request_target
- pull_request_target
permissions:
contents: read
pull-requests: write
jobs:
triage:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
- uses: actions/labeler@v4
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -15,7 +15,7 @@ permissions:
jobs:
build:
if: "!(github.event_name == 'push' && github.repository == 'discourse/discourse-private-mirror')"
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
name: run
runs-on: ubuntu-latest
container: discourse/discourse_test:slim
@ -36,8 +36,7 @@ jobs:
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gem-
restore-keys: ${{ runner.os }}-gem-
- name: Setup gems
run: |
@ -61,8 +60,7 @@ jobs:
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
restore-keys: ${{ runner.os }}-yarn-
- name: Check RubyGems Licenses
if: ${{ !cancelled() }}

View File

@ -15,13 +15,16 @@ permissions:
jobs:
build:
if: "!(github.event_name == 'push' && github.repository == 'discourse/discourse-private-mirror')"
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
name: run
runs-on: ubuntu-latest
container: discourse/discourse_test:slim
timeout-minutes: 30
steps:
- name: Set working directory owner
run: chown root:root .
- uses: actions/checkout@v3
with:
fetch-depth: 1
@ -36,8 +39,7 @@ jobs:
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gem-
restore-keys: ${{ runner.os }}-gem-
- name: Setup gems
run: |
@ -58,11 +60,10 @@ jobs:
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
restore-keys: ${{ runner.os }}-yarn-
- name: Yarn install
run: yarn install
run: yarn install --frozen-lockfile
- name: Rubocop
if: ${{ !cancelled() }}
@ -70,33 +71,27 @@ jobs:
- name: syntax_tree
if: ${{ !cancelled() }}
run: bundle exec stree check Gemfile $(git ls-files '*.rb') $(git ls-files '*.rake')
run: |
set -E
bundle exec stree check Gemfile $(git ls-files '*.rb') $(git ls-files '*.rake')
- name: ESLint (core)
if: ${{ !cancelled() }}
run: yarn eslint app/assets/javascripts
run: yarn lint:js
- name: ESLint (core plugins)
if: ${{ !cancelled() }}
run: yarn eslint plugins
run: yarn lint:js-plugins
- name: Prettier
if: ${{ !cancelled() }}
run: |
yarn prettier -v
yarn pprettier --list-different \
"app/assets/stylesheets/**/*.scss" \
"app/assets/javascripts/**/*.js" \
"plugins/**/assets/stylesheets/**/*.scss" \
"plugins/**/assets/javascripts/**/*.js"
yarn lint:prettier
- name: Ember template lint
if: ${{ !cancelled() }}
run: |
yarn ember-template-lint \
--no-error-on-unmatched-pattern \
"app/assets/javascripts/**/*.hbs" \
"plugins/**/assets/javascripts/**/*.hbs"
run: yarn lint:hbs
- name: English locale lint (core)
if: ${{ !cancelled() }}

View File

@ -17,20 +17,19 @@ permissions:
jobs:
build:
if: "!(github.event_name == 'push' && github.repository == 'discourse/discourse-private-mirror')"
name: ${{ matrix.target }} ${{ matrix.build_type }}
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
name: ${{ matrix.target }} ${{ matrix.build_type }} ${{ matrix.ruby }}
runs-on: ${{ (matrix.build_type == 'annotations') && 'ubuntu-latest' || 'ubuntu-20.04-8core' }}
container: discourse/discourse_test:slim${{ (matrix.build_type == 'frontend' || matrix.build_type == 'system') && '-browsers' || '' }}
container: discourse/discourse_test:slim${{ (matrix.build_type == 'frontend' || matrix.build_type == 'system') && '-browsers' || '' }}${{ (matrix.ruby == '3.1') && '-ruby-3.1.0' || '' }}
timeout-minutes: 20
env:
DISCOURSE_HOSTNAME: www.example.com
RUBY_GLOBAL_METHOD_CACHE_SIZE: 131072
RAILS_ENV: test
PGUSER: discourse
PGPASSWORD: discourse
USES_PARALLEL_DATABASES: ${{ matrix.build_type == 'backend' }}
CAPBYARA_DEFAULT_MAX_WAIT_TIME: 4
USES_PARALLEL_DATABASES: ${{ matrix.build_type == 'backend' || matrix.build_type == 'system' }}
CAPYBARA_DEFAULT_MAX_WAIT_TIME: 10
strategy:
fail-fast: false
@ -38,6 +37,7 @@ jobs:
matrix:
build_type: [backend, frontend, system, annotations]
target: [core, plugins]
ruby: ["3.2"]
exclude:
- build_type: annotations
target: plugins
@ -71,9 +71,8 @@ jobs:
uses: actions/cache@v3
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gem-
key: ${{ runner.os }}-${{ matrix.ruby }}-gem-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: ${{ runner.os }}-${{ matrix.ruby }}-gem-
- name: Setup gems
run: |
@ -94,11 +93,10 @@ jobs:
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
restore-keys: ${{ runner.os }}-yarn-
- name: Yarn install
run: yarn install
run: yarn install --frozen-lockfile
- name: Checkout official plugins
if: matrix.target == 'plugins'
@ -113,10 +111,9 @@ jobs:
id: app-cache
with:
path: tmp/app-cache
key: >- # postgres version, hash of migrations, "parallel?"
key: >-
${{ runner.os }}-
${{ hashFiles('.github/workflows/tests.yml') }}-
${{ matrix.postgres }}-
${{ hashFiles('db/**/*', 'plugins/**/db/**/*') }}-
${{ env.USES_PARALLEL_DATABASES }}
@ -153,18 +150,50 @@ jobs:
- name: Fetch turbo_rspec_runtime.log cache
uses: actions/cache@v3
id: test-runtime-cache
if: matrix.build_type == 'backend' && matrix.target == 'core'
if: matrix.build_type == 'backend' || matrix.build_type == 'system'
with:
path: tmp/turbo_rspec_runtime.log
key: rspec-runtime-backend-core
key: rspec-runtime-${{ matrix.build_type }}-${{ matrix.target }}-${{ github.run_id }}
restore-keys: rspec-runtime-${{ matrix.build_type }}-${{ matrix.target }}-
- name: Check Zeitwerk eager_load
if: matrix.build_type == 'backend'
env:
LOAD_PLUGINS: ${{ (matrix.target == 'plugins') && '1' || '0' }}
run: |
if ! bin/rails zeitwerk:check --trace; then
echo
echo "---------------------------------------------"
echo
echo "::error::'bin/rails zeitwerk:check' failed - the app will fail to boot with 'eager_load=true' (e.g. in production)."
echo "To reproduce locally, run 'bin/rails zeitwerk:check'."
echo "Alternatively, you can run your local server/tests with the 'DISCOURSE_ZEITWERK_EAGER_LOAD=1' environment variable."
echo
exit 1
fi
- name: Check Zeitwerk reloading
if: matrix.build_type == 'backend'
env:
LOAD_PLUGINS: ${{ (matrix.target == 'plugins') && '1' || '0' }}
run: |
if ! bin/rails runner 'Rails.application.reloader.reload!'; then
echo
echo "---------------------------------------------"
echo
echo "::error::Zeitwerk reload failed - the app will not be able to reload properly in development."
echo "To reproduce locally, run \`bin/rails runner 'Rails.application.reloader.reload!'\`."
echo
exit 1
fi
- name: Core RSpec
if: matrix.build_type == 'backend' && matrix.target == 'core'
run: bin/turbo_rspec --verbose
run: bin/turbo_rspec --use-runtime-info --verbose --format documentation
- name: Plugin RSpec
if: matrix.build_type == 'backend' && matrix.target == 'plugins'
run: bin/rake plugin:turbo_spec
run: bin/rake plugin:turbo_spec['*','--verbose --format documentation --use-runtime-info']
- name: Plugin QUnit
if: matrix.build_type == 'frontend' && matrix.target == 'plugins'
@ -181,11 +210,12 @@ jobs:
- name: Core System Tests
if: matrix.build_type == 'system' && matrix.target == 'core'
run: bin/rspec spec/system --format documentation --profile
run: RAILS_ENABLE_TEST_LOG=1 RAILS_TEST_LOG_LEVEL=error PARALLEL_TEST_PROCESSORS=4 bin/turbo_rspec --use-runtime-info --profile=50 --verbose --format documentation spec/system
- name: Plugin System Tests
if: matrix.build_type == 'system' && matrix.target == 'plugins'
run: LOAD_PLUGINS=1 bin/rspec plugins/*/spec/system --format documentation --profile
run: LOAD_PLUGINS=1 RAILS_ENABLE_TEST_LOG=1 RAILS_TEST_LOG_LEVEL=error PARALLEL_TEST_PROCESSORS=4 bin/turbo_rspec --use-runtime-info --profile=50 --verbose --format documentation plugins/*/spec/system
timeout-minutes: 30
- name: Upload failed system test screenshots
uses: actions/upload-artifact@v3
@ -212,7 +242,7 @@ jobs:
timeout-minutes: 30
core_frontend_tests:
if: "!(github.event_name == 'push' && github.repository == 'discourse/discourse-private-mirror')"
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
name: core frontend (${{ matrix.browser }})
runs-on: ubuntu-20.04-8core
container:
@ -250,26 +280,25 @@ jobs:
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
restore-keys: ${{ runner.os }}-yarn-
- name: Yarn install
working-directory: ./app/assets/javascripts/discourse
run: yarn install
run: yarn install --frozen-lockfile
- name: Ember Build
working-directory: ./app/assets/javascripts/discourse
run: |
mkdir /tmp/emberbuild
yarn ember build --environment=test -o /tmp/emberbuild
yarn ember build --environment=test -o /tmp/emberbuild
- name: Core QUnit
working-directory: ./app/assets/javascripts/discourse
run: yarn ember exam --path /tmp/emberbuild --load-balance --parallel=5 --launch "${{ env.TESTEM_BROWSER }}" --write-execution-file --random
run: yarn ember exam --path /tmp/emberbuild --load-balance --parallel=5 --launch "${{ env.TESTEM_BROWSER }}" --write-execution-file --random
timeout-minutes: 15
- uses: actions/upload-artifact@v3
if: ${{ always() }}
with:
name: ember-exam-execution-${{matrix.browser}}
name: ember-exam-execution-${{ matrix.browser }}
path: ./app/assets/javascripts/discourse/test-execution-*.json

10
.gitignore vendored
View File

@ -38,7 +38,7 @@
!/plugins/discourse-local-dates
!/plugins/discourse-narrative-bot
!/plugins/discourse-presence
!/plugins/lazy-yt/
!/plugins/discourse-lazy-videos/
!/plugins/chat/
!/plugins/poll/
!/plugins/styleguide
@ -52,12 +52,20 @@
# We provide a .sample but people can use newer versions if they want to
.ruby-version
.ruby-gemset
# Likewise, there is a .vscode-sample for VSCode config
.vscode
# Front-end
dist
node_modules
yarn-error.log
# Linting artifacts
.eslintcache
/lint-progress/
# Auto-generated plugin JS assets
/app/assets/javascripts/plugins/*

21
.jsdoc Normal file
View File

@ -0,0 +1,21 @@
// jsdoc doesn't accept paths starting with _ (which is the case on github runners)
// so we need to alter the default config
{
"source": {
"excludePattern": ""
},
"templates": {
"default": {
"includeDate": false
}
},
"opts": {
"template": "./node_modules/tidy-jsdoc",
"prism-theme": "prism-custom",
"encoding": "utf8",
"recurse": true
},
"metadata": {
"title": "Discourse"
}
}

View File

@ -51,6 +51,7 @@ reviewed:
- net-imap # Ruby (bundled gem)
- net-pop # Ruby (bundled gem)
- net-smtp # Ruby (bundled gem)
- nio4r # MIT + BSD
- omniauth # MIT
- pg # Ruby
- r2 # Apache-2.0 (Twitter)

View File

@ -11,7 +11,9 @@
"packages": {
"@fortawesome/fontawesome-free": "*",
"ember-template-lint-plugin-discourse": "*",
"squoosh": "2.0.0"
"spawn-command": "0.0.2",
"squoosh": "2.0.0",
"taffydb": "2.6.2"
},
"corrections": true
}
}

View File

@ -3,6 +3,7 @@ plugins/**/assets/stylesheets/vendor/
plugins/**/assets/javascripts/vendor/
plugins/**/config/locales/**/*.yml
plugins/**/config/*.yml
documentation/
package.json
config/locales/**/*.yml
!config/locales/**/*.en*.yml

View File

@ -1 +1,17 @@
{}
{
"plugins": ["prettier-plugin-ember-template-tag"],
"overrides": [
{
"files": "*.gjs",
"options": {
"parser": "ember-template-tag"
}
},
{
"files": "*.gts",
"options": {
"parser": "ember-template-tag"
}
}
]
}

View File

@ -7,3 +7,7 @@ Discourse/NoAddReferenceOrAliasesActiveRecordMigration:
Discourse/NoResetColumnInformationInMigrations:
Enabled: true
Lint/Debugger:
Exclude:
- script/**/*

View File

@ -1 +1 @@
3.1.3
3.2.1

View File

@ -5,6 +5,7 @@ module.exports = {
rules: {
"no-action-modifiers": true,
"no-args-paths": true,
"no-array-prototype-extensions": false,
"no-attrs-in-components": true,
"no-capital-arguments": false, // TODO: we extensively use `args` argument name
"no-curly-component-invocation": {
@ -15,12 +16,15 @@ module.exports = {
"directory-item-value",
"directory-table-header-title",
"loading-spinner",
"mobile-directory-item-label",
"directory-item-label",
],
},
"no-implicit-this": {
allow: ["loading-spinner"],
},
"no-obscure-array-access": false,
"require-mandatory-role-attributes": false,
"require-media-caption": false,
// Begin prettier compatibility
"eol-last": false,
"self-closing-void-elements": false,

View File

@ -0,0 +1,12 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"esbenp.prettier-vscode",
"typed-ember.glint-vscode",
"chiragpat.vscode-glimmer",
"dbaeumer.vscode-eslint"
]
}

View File

@ -0,0 +1,12 @@
{
"[gjs]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[gts]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"eslint.validate": [
"glimmer-js",
"glimmer-ts"
]
}

31
Gemfile
View File

@ -18,7 +18,7 @@ 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
rails_version = "7.0.4.3"
rails_version = "7.0.5.1"
gem "actionmailer", rails_version
gem "actionpack", rails_version
gem "actionview", rails_version
@ -32,8 +32,8 @@ end
gem "json"
# TODO: At the moment Discourse does not work with Sprockets 4, we would need to correct internals
# This is a desired upgrade we should get to.
gem "sprockets", "3.7.2"
# We intend to drop sprockets rather than upgrade to 4.x
gem "sprockets", git: "https://github.com/rails/sprockets", branch: "3.x"
# this will eventually be added to rails,
# allows us to precompile all our templates in the unicorn master
@ -41,7 +41,7 @@ gem "actionview_precompiler", require: false
gem "discourse-seed-fu"
gem "mail", git: "https://github.com/discourse/mail.git"
gem "mail"
gem "mini_mime"
gem "mini_suffix"
@ -71,8 +71,6 @@ gem "rails_multisite"
gem "fast_xs", platform: :ruby
gem "xorcist"
gem "fastimage"
gem "aws-sdk-s3", require: false
@ -98,14 +96,13 @@ gem "omniauth-oauth2", require: false
gem "omniauth-google-oauth2"
# pending: https://github.com/ohler55/oj/issues/789
gem "oj", "3.13.14"
gem "oj"
gem "pg"
gem "mini_sql"
gem "pry-rails", require: false
gem "pry-byebug", require: false
gem "r2", require: false
gem "rtlcss", require: false
gem "rake"
gem "thor", require: false
@ -147,6 +144,7 @@ group :test do
gem "selenium-webdriver", require: false
gem "test-prof"
gem "webdrivers", require: false
gem "rails-dom-testing", require: false
end
group :test, :development do
@ -160,7 +158,7 @@ group :test, :development do
gem "rspec-rails"
gem "shoulda-matchers", require: false
gem "shoulda-matchers", require: false, github: "thoughtbot/shoulda-matchers"
gem "rspec-html-matchers"
gem "byebug", require: ENV["RM_INFO"].nil?, platform: :mri
gem "rubocop-discourse", require: false
@ -180,6 +178,7 @@ group :development do
gem "better_errors", platform: :mri, require: !!ENV["BETTER_ERRORS"]
gem "binding_of_caller"
gem "yaml-lint"
gem "yard"
end
if ENV["ALLOW_DEV_POPULATE"] == "1"
@ -229,10 +228,9 @@ gem "logstash-event", require: false
gem "logstash-logger", require: false
gem "logster"
# NOTE: later versions of sassc are causing a segfault, possibly dependent on processer architecture
# and until resolved should be locked at 2.0.1
gem "sassc", "2.0.1", require: false
gem "sassc-rails"
# These are forks of sassc and sassc-rails with dart-sass support
gem "dartsass-ruby"
gem "dartsass-sprockets"
gem "rotp", require: false
@ -274,8 +272,7 @@ gem "faraday-retry"
# https://github.com/ruby/net-imap/issues/16#issuecomment-803086765
gem "net-http"
# workaround for prometheus-client
gem "webrick", require: false
# Workaround until Ruby ships with cgi version 0.3.6 or higher.
gem "cgi", ">= 0.3.6", require: false
gem "tzinfo-data"

View File

@ -1,32 +1,41 @@
GIT
remote: https://github.com/discourse/mail.git
revision: 5b700fc95ee66378e0cf2559abc73c8bc3062a4b
remote: https://github.com/rails/sprockets
revision: f4d3dae71ef29c44b75a49cfbf8032cce07b423a
branch: 3.x
specs:
mail (2.8.0.edge)
mini_mime (>= 0.1.1)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
GIT
remote: https://github.com/thoughtbot/shoulda-matchers.git
revision: 783a90554053002017510285bc736099b2749c22
specs:
shoulda-matchers (5.3.0)
activesupport (>= 5.2.0)
GEM
remote: https://rubygems.org/
specs:
actionmailer (7.0.4.3)
actionpack (= 7.0.4.3)
actionview (= 7.0.4.3)
activejob (= 7.0.4.3)
activesupport (= 7.0.4.3)
actionmailer (7.0.5.1)
actionpack (= 7.0.5.1)
actionview (= 7.0.5.1)
activejob (= 7.0.5.1)
activesupport (= 7.0.5.1)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.4.3)
actionview (= 7.0.4.3)
activesupport (= 7.0.4.3)
rack (~> 2.0, >= 2.2.0)
actionpack (7.0.5.1)
actionview (= 7.0.5.1)
activesupport (= 7.0.5.1)
rack (~> 2.0, >= 2.2.4)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actionview (7.0.4.3)
activesupport (= 7.0.4.3)
actionview (7.0.5.1)
activesupport (= 7.0.5.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -35,20 +44,20 @@ GEM
actionview (>= 6.0.a)
active_model_serializers (0.8.4)
activemodel (>= 3.0)
activejob (7.0.4.3)
activesupport (= 7.0.4.3)
activejob (7.0.5.1)
activesupport (= 7.0.5.1)
globalid (>= 0.3.6)
activemodel (7.0.4.3)
activesupport (= 7.0.4.3)
activerecord (7.0.4.3)
activemodel (= 7.0.4.3)
activesupport (= 7.0.4.3)
activesupport (7.0.4.3)
activemodel (7.0.5.1)
activesupport (= 7.0.5.1)
activerecord (7.0.5.1)
activemodel (= 7.0.5.1)
activesupport (= 7.0.5.1)
activesupport (7.0.5.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
addressable (2.8.1)
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
annotate (3.2.0)
activerecord (>= 3.2, < 8.0)
@ -73,20 +82,20 @@ GEM
aws-sigv4 (~> 1.1)
aws-sigv4 (1.5.0)
aws-eventstream (~> 1, >= 1.0.2)
better_errors (2.9.1)
coderay (>= 1.0.0)
better_errors (2.10.1)
erubi (>= 1.0.0)
rack (>= 0.9.0)
rouge (>= 1.0.0)
binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
bootsnap (1.15.0)
bootsnap (1.16.0)
msgpack (~> 1.2)
builder (3.2.4)
bullet (7.0.7)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
byebug (11.1.3)
capybara (3.38.0)
capybara (3.39.2)
addressable
matrix
mini_mime (>= 0.1.3)
@ -101,8 +110,8 @@ GEM
chunky_png (1.4.0)
coderay (1.1.3)
colored2 (3.1.2)
concurrent-ruby (1.1.10)
connection_pool (2.3.0)
concurrent-ruby (1.2.2)
connection_pool (2.4.1)
cose (1.3.0)
cbor (~> 0.5.9)
openssl-signature_algorithm (~> 1.0)
@ -110,8 +119,17 @@ GEM
crack (0.4.5)
rexml
crass (1.0.6)
css_parser (1.13.0)
css_parser (1.14.0)
addressable
dartsass-ruby (3.0.1)
sass-embedded (~> 1.54)
dartsass-sprockets (3.0.0)
dartsass-ruby (~> 3.0)
railties (>= 4.0.0)
sprockets (> 3.0)
sprockets-rails
tilt
date (3.3.3)
debug_inspector (1.1.0)
diff-lcs (1.5.0)
diffy (3.4.2)
@ -124,31 +142,34 @@ GEM
faker (~> 2.16)
literate_randomizer
docile (1.4.0)
ecma-re-validator (0.4.0)
regexp_parser (~> 2.2)
email_reply_trimmer (0.1.13)
erubi (1.11.0)
excon (0.96.0)
erubi (1.12.0)
excon (0.100.0)
execjs (2.8.1)
exifr (1.3.10)
exifr (1.4.0)
fabrication (2.30.0)
faker (2.23.0)
i18n (>= 1.8.11, < 2)
fakeweb (1.3.0)
faraday (2.7.2)
faraday (2.7.10)
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday-net_http (3.0.2)
faraday-retry (2.0.0)
faraday-retry (2.2.0)
faraday (~> 2.0)
fast_blank (1.0.1)
fast_xs (0.8.0)
fastimage (2.2.6)
fastimage (2.2.7)
ffi (1.15.5)
fspath (3.1.2)
gc_tracer (1.5.1)
globalid (1.0.1)
globalid (1.1.0)
activesupport (>= 5.0)
google-protobuf (3.23.4)
google-protobuf (3.23.4-aarch64-linux)
google-protobuf (3.23.4-arm64-darwin)
google-protobuf (3.23.4-x86_64-darwin)
google-protobuf (3.23.4-x86_64-linux)
guess_html_encoding (0.0.11)
hana (1.3.7)
hashdiff (1.0.1)
@ -157,38 +178,37 @@ GEM
hkdf (1.0.0)
htmlentities (4.3.4)
http_accept_language (2.1.1)
i18n (1.12.0)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
image_optim (0.31.2)
image_optim (0.31.3)
exifr (~> 1.2, >= 1.2.2)
fspath (~> 3.0)
image_size (>= 1.5, < 4)
in_threads (~> 1.3)
progress (~> 3.0, >= 3.0.1)
image_size (3.2.0)
image_size (3.3.0)
in_threads (1.6.0)
jmespath (1.6.2)
json (2.6.3)
json-schema (3.0.0)
addressable (>= 2.8)
json_schemer (0.2.23)
ecma-re-validator (~> 0.3)
json_schemer (1.0.3)
hana (~> 1.3)
regexp_parser (~> 2.0)
uri_template (~> 0.7)
jwt (2.6.0)
simpleidn (~> 0.2)
jwt (2.7.1)
kgio (2.11.4)
libv8-node (16.10.0.0)
libv8-node (16.10.0.0-aarch64-linux)
libv8-node (16.10.0.0-arm64-darwin)
libv8-node (16.10.0.0-x86_64-darwin)
libv8-node (16.10.0.0-x86_64-darwin-19)
libv8-node (16.10.0.0-x86_64-linux)
language_server-protocol (3.17.0.3)
libv8-node (18.16.0.0)
libv8-node (18.16.0.0-aarch64-linux)
libv8-node (18.16.0.0-arm64-darwin)
libv8-node (18.16.0.0-x86_64-darwin)
libv8-node (18.16.0.0-x86_64-linux)
listen (3.8.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
literate_randomizer (0.4.0)
lograge (0.12.0)
lograge (0.13.0)
actionpack (>= 4)
activesupport (>= 4)
railties (>= 4)
@ -196,37 +216,43 @@ GEM
logstash-event (1.2.02)
logstash-logger (0.26.1)
logstash-event (~> 1.2)
logster (2.11.3)
loofah (2.19.1)
logster (2.12.2)
loofah (2.21.3)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
nokogiri (>= 1.12.0)
lru_redux (1.1.0)
lz4-ruby (0.3.3)
mail (2.8.1)
mini_mime (>= 0.1.1)
net-imap
net-pop
net-smtp
matrix (0.4.2)
maxminddb (0.1.22)
memory_profiler (1.0.1)
message_bus (4.3.1)
message_bus (4.3.7)
rack (>= 1.1.3)
method_source (1.0.0)
mini_mime (1.1.2)
mini_portile2 (2.8.1)
mini_racer (0.6.3)
libv8-node (~> 16.10.0.0)
mini_scheduler (0.15.0)
mini_portile2 (2.8.4)
mini_racer (0.8.0)
libv8-node (~> 18.16.0.0)
mini_scheduler (0.16.0)
sidekiq (>= 4.2.3, < 7.0)
mini_sql (1.4.0)
mini_suffix (0.3.3)
ffi (~> 1.9)
minitest (5.17.0)
mocha (2.0.2)
minitest (5.19.0)
mocha (2.1.0)
ruby2_keywords (>= 0.0.5)
msgpack (1.6.0)
msgpack (1.7.2)
multi_json (1.15.0)
multi_xml (0.6.0)
mustache (1.1.1)
net-http (0.3.2)
uri
net-imap (0.3.1)
net-imap (0.3.7)
date
net-protocol
net-pop (0.1.2)
net-protocol
@ -234,17 +260,17 @@ GEM
timeout
net-smtp (0.3.3)
net-protocol
nio4r (2.5.8)
nokogiri (1.14.2)
mini_portile2 (~> 2.8.0)
nio4r (2.5.9)
nokogiri (1.15.3)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.14.2-aarch64-linux)
nokogiri (1.15.3-aarch64-linux)
racc (~> 1.4)
nokogiri (1.14.2-arm64-darwin)
nokogiri (1.15.3-arm64-darwin)
racc (~> 1.4)
nokogiri (1.14.2-x86_64-darwin)
nokogiri (1.15.3-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.14.2-x86_64-linux)
nokogiri (1.15.3-x86_64-linux)
racc (~> 1.4)
oauth (1.1.0)
oauth-tty (~> 1.0, >= 1.0.1)
@ -258,7 +284,7 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 4)
oj (3.13.14)
oj (3.15.1)
omniauth (1.9.2)
hashie (>= 3.4.6)
rack (>= 1.6.2, < 3)
@ -281,17 +307,18 @@ GEM
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
rack
openssl (3.0.2)
openssl-signature_algorithm (1.2.1)
openssl (> 2.0, < 3.1)
optimist (3.0.1)
parallel (1.22.1)
parallel_tests (4.0.0)
openssl (3.1.0)
openssl-signature_algorithm (1.3.0)
openssl (> 2.0)
optimist (3.1.0)
parallel (1.23.0)
parallel_tests (4.2.1)
parallel
parser (3.2.0.0)
parser (3.2.2.3)
ast (~> 2.4.1)
pg (1.4.5)
prettier_print (1.2.0)
racc
pg (1.4.6)
prettier_print (1.2.1)
progress (3.6.0)
pry (0.14.2)
coderay (~> 1.1)
@ -301,39 +328,40 @@ GEM
pry (>= 0.13, < 0.15)
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (5.0.1)
puma (6.0.2)
public_suffix (5.0.3)
puma (6.3.0)
nio4r (~> 2.0)
r2 (0.2.7)
racc (1.6.2)
rack (2.2.5)
rack-mini-profiler (3.0.0)
racc (1.7.1)
rack (2.2.8)
rack-mini-profiler (3.1.0)
rack (>= 1.2.0)
rack-protection (3.0.5)
rack-protection (3.0.6)
rack
rack-test (2.0.2)
rack-test (2.1.0)
rack (>= 1.3)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
rails-dom-testing (2.1.1)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.5.0)
loofah (~> 2.19, >= 2.19.1)
rails_failover (0.8.1)
activerecord (> 6.0, < 7.1)
rails-html-sanitizer (1.6.0)
loofah (~> 2.21)
nokogiri (~> 1.14)
rails_failover (2.0.1)
activerecord (>= 6.1, <= 7.1)
concurrent-ruby
railties (> 6.0, < 7.1)
rails_multisite (4.0.1)
activerecord (> 5.0, < 7.1)
railties (> 5.0, < 7.1)
railties (7.0.4.3)
actionpack (= 7.0.4.3)
activesupport (= 7.0.4.3)
railties (>= 6.1, <= 7.1)
rails_multisite (5.0.0)
activerecord (>= 6.0)
railties (>= 6.0)
railties (7.0.5.1)
actionpack (= 7.0.5.1)
activesupport (= 7.0.5.1)
method_source
rake (>= 12.2)
thor (~> 1.0)
zeitwerk (~> 2.5)
rainbow (3.1.1)
raindrops (0.20.0)
raindrops (0.20.1)
rake (13.0.6)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
@ -343,16 +371,17 @@ GEM
msgpack (>= 0.4.3)
optimist (>= 3.0.0)
rchardet (1.8.0)
redis (4.8.0)
redis-namespace (1.9.0)
redis (4.8.1)
redis-namespace (1.11.0)
redis (>= 4)
regexp_parser (2.6.1)
regexp_parser (2.8.1)
request_store (1.5.1)
rack (>= 1.4)
rexml (3.2.5)
rexml (3.2.6)
rinku (2.0.6)
rotp (6.2.2)
rqrcode (2.1.2)
rouge (4.1.3)
rqrcode (2.2.0)
chunky_png (~> 1.0)
rqrcode_core (~> 1.0)
rqrcode_core (1.2.0)
@ -360,76 +389,85 @@ GEM
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
rspec-mocks (~> 3.12.0)
rspec-core (3.12.0)
rspec-core (3.12.2)
rspec-support (~> 3.12.0)
rspec-expectations (3.12.2)
rspec-expectations (3.12.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-html-matchers (0.10.0)
nokogiri (~> 1)
rspec (>= 3.0.0.a)
rspec-mocks (3.12.2)
rspec-mocks (3.12.6)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-rails (6.0.1)
rspec-rails (6.0.3)
actionpack (>= 6.1)
activesupport (>= 6.1)
railties (>= 6.1)
rspec-core (~> 3.11)
rspec-expectations (~> 3.11)
rspec-mocks (~> 3.11)
rspec-support (~> 3.11)
rspec-support (3.12.0)
rspec-core (~> 3.12)
rspec-expectations (~> 3.12)
rspec-mocks (~> 3.12)
rspec-support (~> 3.12)
rspec-support (3.12.1)
rss (0.2.9)
rexml
rswag-specs (2.8.0)
rswag-specs (2.10.1)
activesupport (>= 3.1, < 7.1)
json-schema (>= 2.2, < 4.0)
railties (>= 3.1, < 7.1)
rspec-core (>= 2.14)
rubocop (1.43.0)
rtlcss (0.2.1)
mini_racer (>= 0.6.3)
rubocop (1.55.1)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.2.0.0)
parser (>= 3.2.2.3)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.24.1, < 2.0)
rubocop-ast (>= 1.28.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.24.1)
parser (>= 3.1.1.0)
rubocop-discourse (3.0.3)
rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
rubocop-capybara (2.18.0)
rubocop (~> 1.41)
rubocop-discourse (3.3.0)
rubocop (>= 1.1.0)
rubocop-rspec (>= 2.0.0)
rubocop-rspec (2.16.0)
rubocop-factory_bot (2.23.1)
rubocop (~> 1.33)
ruby-prof (1.4.5)
ruby-progressbar (1.11.0)
rubocop-rspec (2.23.0)
rubocop (~> 1.33)
rubocop-capybara (~> 2.17)
rubocop-factory_bot (~> 2.22)
ruby-prof (1.6.3)
ruby-progressbar (1.13.0)
ruby-readability (0.7.0)
guess_html_encoding (>= 0.0.4)
nokogiri (>= 1.6.0)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
sanitize (6.0.0)
sanitize (6.0.2)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
sassc (2.0.1)
ffi (~> 1.9)
rake
sassc-rails (2.1.2)
railties (>= 4.0.0)
sassc (>= 2.0)
sprockets (> 3.0)
sprockets-rails
tilt
selenium-webdriver (4.7.1)
sass-embedded (1.64.1)
google-protobuf (~> 3.23)
rake (>= 13.0.0)
sass-embedded (1.64.1-aarch64-linux-gnu)
google-protobuf (~> 3.23)
sass-embedded (1.64.1-arm64-darwin)
google-protobuf (~> 3.23)
sass-embedded (1.64.1-x86_64-darwin)
google-protobuf (~> 3.23)
sass-embedded (1.64.1-x86_64-linux-gnu)
google-protobuf (~> 3.23)
selenium-webdriver (4.10.0)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
shoulda-matchers (5.3.0)
activesupport (>= 5.2.0)
sidekiq (6.5.8)
sidekiq (6.5.9)
connection_pool (>= 2.2.5, < 3)
rack (~> 2.0)
redis (>= 4.5.0, < 5)
@ -439,27 +477,28 @@ GEM
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4)
simpleidn (0.2.1)
unf (~> 0.1.4)
snaky_hash (2.0.1)
hashie
version_gem (~> 1.1, >= 1.1.1)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.4.2)
actionpack (>= 5.2)
activesupport (>= 5.2)
sprockets (>= 3.0.0)
sshkey (2.0.0)
stackprof (0.2.23)
syntax_tree (5.2.0)
stackprof (0.2.25)
syntax_tree (6.1.1)
prettier_print (>= 1.2.0)
syntax_tree-disable_ternary (1.0.0)
test-prof (1.1.0)
thor (1.2.1)
tilt (2.0.11)
timeout (0.3.1)
tzinfo (2.0.5)
test-prof (1.2.2)
thor (1.2.2)
tilt (2.2.0)
timeout (0.4.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
tzinfo-data (1.2023.3)
tzinfo (>= 1.0.0)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
unf (0.1.4)
@ -471,27 +510,25 @@ GEM
raindrops (~> 0.7)
uniform_notifier (1.16.0)
uri (0.12.2)
uri_template (0.7.0)
version_gem (1.1.1)
version_gem (1.1.3)
web-push (3.0.0)
hkdf (~> 1.0)
jwt (~> 2.0)
openssl (~> 3.0)
webdrivers (5.2.0)
webdrivers (5.3.1)
nokogiri (~> 1.6)
rubyzip (>= 1.3.0)
selenium-webdriver (~> 4.0)
selenium-webdriver (~> 4.0, < 4.11)
webmock (3.18.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.7.0)
websocket (1.2.9)
xorcist (1.1.3)
xpath (3.2.0)
nokogiri (~> 1.8)
yaml-lint (0.0.10)
zeitwerk (2.6.6)
yaml-lint (0.1.2)
yard (0.9.34)
zeitwerk (2.6.10)
PLATFORMS
aarch64-linux
@ -503,14 +540,14 @@ PLATFORMS
x86_64-linux
DEPENDENCIES
actionmailer (= 7.0.4.3)
actionpack (= 7.0.4.3)
actionview (= 7.0.4.3)
actionmailer (= 7.0.5.1)
actionpack (= 7.0.5.1)
actionview (= 7.0.5.1)
actionview_precompiler
active_model_serializers (~> 0.8.3)
activemodel (= 7.0.4.3)
activerecord (= 7.0.4.3)
activesupport (= 7.0.4.3)
activemodel (= 7.0.5.1)
activerecord (= 7.0.5.1)
activesupport (= 7.0.5.1)
addressable
annotate
aws-sdk-s3
@ -528,6 +565,8 @@ DEPENDENCIES
cose
cppjieba_rb
css_parser
dartsass-ruby
dartsass-sprockets
diffy
digest
discourse-fonts
@ -559,7 +598,7 @@ DEPENDENCIES
loofah
lru_redux
lz4-ruby
mail!
mail
maxminddb
memory_profiler
message_bus
@ -577,7 +616,7 @@ DEPENDENCIES
net-pop
net-smtp
nokogiri
oj (= 3.13.14)
oj
omniauth
omniauth-facebook
omniauth-github
@ -589,13 +628,13 @@ DEPENDENCIES
pry-byebug
pry-rails
puma
r2
rack
rack-mini-profiler
rack-protection
rails-dom-testing
rails_failover
rails_multisite
railties (= 7.0.4.3)
railties (= 7.0.5.1)
rake
rb-fsevent
rbtrace
@ -610,18 +649,17 @@ DEPENDENCIES
rspec-rails
rss
rswag-specs
rtlcss
rubocop-discourse
ruby-prof
ruby-readability
rubyzip
sanitize
sassc (= 2.0.1)
sassc-rails
selenium-webdriver
shoulda-matchers
shoulda-matchers!
sidekiq
simplecov
sprockets (= 3.7.2)
sprockets!
sprockets-rails
sshkey
stackprof
@ -629,15 +667,15 @@ DEPENDENCIES
syntax_tree-disable_ternary
test-prof
thor
tzinfo-data
uglifier
unf
unicorn
web-push
webdrivers
webmock
webrick
xorcist
yaml-lint
yard
BUNDLED WITH
2.4.1
2.4.13

View File

@ -30,7 +30,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.7+](https://www.ruby-lang.org/en/downloads/), [PostgreSQL 13+](https://www.postgresql.org/download/), [Redis 6.2+](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 3.2+](https://www.ruby-lang.org/en/downloads/), [PostgreSQL 13](https://www.postgresql.org/download/), [Redis 7](https://redis.io/download). If you're having trouble, please see our [**TROUBLESHOOTING GUIDE**](docs/TROUBLESHOOTING.md) first!
## Setting up Discourse
@ -51,7 +51,7 @@ Discourse supports the **latest, stable releases** of all major browsers and pla
| Microsoft Edge | | |
| Mozilla Firefox | | |
Additionally, we aim to support Safari on iOS 12.5+ until January 2023 (Discourse 3.0).
Additionally, we aim to support Safari on iOS 15.7+.
## Built With

View File

@ -10,15 +10,16 @@
]
},
"packages": {
"cli-table": "0.3.11",
"component-bind": "1.0.0",
"component-inherit": "0.0.3",
"duplex": "1.0.0",
"glob": "3.1.21",
"indexof": "0.0.1",
"inherits": "1.0.2",
"jsonify": "0.0.0",
"messageformat": "0.1.5",
"jsonify": "0.0.1",
"line-stream": "0.0.0",
"messageformat": "0.1.5",
"regenerator-transform": "0.10.1",
"source-map": "0.1.43",
"sourcemap-validator": "1.1.1"

View File

@ -1,13 +1,13 @@
import RESTAdapter from "discourse/adapters/rest";
import RestAdapter from "discourse/adapters/rest";
export default RESTAdapter.extend({
jsonMode: true,
export default class ApiKey extends RestAdapter {
jsonMode = true;
basePath() {
return "/admin/api/";
},
}
apiNameFor() {
return "key";
},
});
}
}

View File

@ -1,11 +1,11 @@
import RestAdapter from "discourse/adapters/rest";
export default function buildPluginAdapter(pluginName) {
return RestAdapter.extend({
return class extends RestAdapter {
pathFor(store, type, findArgs) {
return (
"/admin/plugins/" + pluginName + this._super(store, type, findArgs)
"/admin/plugins/" + pluginName + super.pathFor(store, type, findArgs)
);
},
});
}
};
}

View File

@ -1,7 +1,7 @@
import RestAdapter from "discourse/adapters/rest";
export default RestAdapter.extend({
export default class CustomizationBase extends RestAdapter {
basePath() {
return "/admin/customize/";
},
});
}
}

View File

@ -1,7 +1,7 @@
import RestAdapter from "discourse/adapters/rest";
export default RestAdapter.extend({
export default class EmailStyle extends RestAdapter {
pathFor() {
return "/admin/customize/email_style";
},
});
}
}

View File

@ -1,7 +1,7 @@
import RestAdapter from "discourse/adapters/rest";
export default RestAdapter.extend({
export default class Embedding extends RestAdapter {
pathFor() {
return "/admin/customize/embedding";
},
});
}
}

View File

@ -1,7 +1,7 @@
import RestAdapter from "discourse/adapters/rest";
export default RestAdapter.extend({
export default class StaffActionLog extends RestAdapter {
basePath() {
return "/admin/logs/";
},
});
}
}

View File

@ -1,5 +1,5 @@
import RestAdapter from "discourse/adapters/rest";
export default RestAdapter.extend({
jsonMode: true,
});
export default class TagGroup extends RestAdapter {
jsonMode = true;
}

View File

@ -1,9 +1,10 @@
import RestAdapter from "discourse/adapters/rest";
export default RestAdapter.extend({
export default class Theme extends RestAdapter {
jsonMode = true;
basePath() {
return "/admin/";
},
}
afterFindAll(results) {
let map = {};
@ -20,7 +21,5 @@ export default RestAdapter.extend({
theme.set("parentThemes", mappedParents);
});
return results;
},
jsonMode: true,
});
}
}

View File

@ -1,7 +1,7 @@
import RESTAdapter from "discourse/adapters/rest";
import RestAdapter from "discourse/adapters/rest";
export default RESTAdapter.extend({
export default class WebHookEvent extends RestAdapter {
basePath() {
return "/admin/api/";
},
});
}
}

View File

@ -1,7 +1,7 @@
import RESTAdapter from "discourse/adapters/rest";
import RestAdapter from "discourse/adapters/rest";
export default RESTAdapter.extend({
export default class WebHook extends RestAdapter {
basePath() {
return "/admin/api/";
},
});
}
}

View File

@ -1,31 +1,33 @@
import { action } from "@ember/object";
import { classNames } from "@ember-decorators/component";
import { observes, on } from "@ember-decorators/object";
import Component from "@ember/component";
import getURL from "discourse-common/lib/get-url";
import loadScript from "discourse/lib/load-script";
import I18n from "I18n";
import { bind, observes } from "discourse-common/utils/decorators";
import { on } from "@ember/object/evented";
import { bind } from "discourse-common/utils/decorators";
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,
@classNames("ace-wrapper")
export default class AceEditor extends Component {
mode = "css";
disabled = false;
htmlPlaceholder = false;
_editor = null;
_skipContentChangeEvent = null;
@observes("editorId")
editorIdChanged() {
if (this.autofocus) {
this.send("focus");
}
},
}
didRender() {
this._skipContentChangeEvent = false;
},
}
@observes("content")
contentChanged() {
@ -33,14 +35,14 @@ export default Component.extend({
if (this._editor && !this._skipContentChangeEvent) {
this._editor.getSession().setValue(content);
}
},
}
@observes("mode")
modeChanged() {
if (this._editor && !this._skipContentChangeEvent) {
this._editor.getSession().setMode("ace/mode/" + this.mode);
}
},
}
@observes("placeholder")
placeholderChanged() {
@ -49,12 +51,12 @@ export default Component.extend({
placeholder: this.placeholder,
});
}
},
}
@observes("disabled")
disabledStateChanged() {
this.changeDisabledState();
},
}
changeDisabledState() {
const editor = this._editor;
@ -67,9 +69,10 @@ export default Component.extend({
});
editor.container.parentNode.setAttribute("data-disabled", disabled);
}
},
}
_destroyEditor: on("willDestroyElement", function () {
@on("willDestroyElement")
_destroyEditor() {
if (this._editor) {
this._editor.destroy();
this._editor = null;
@ -80,16 +83,16 @@ export default Component.extend({
}
$(window).off("ace:resize");
}),
}
resize() {
if (this._editor) {
this._editor.resize();
}
},
}
didInsertElement() {
this._super(...arguments);
super.didInsertElement(...arguments);
loadScript("/javascripts/ace/ace.js").then(() => {
window.ace.require(["ace/ace"], (loadedAce) => {
loadedAce.config.set("loadWorkerFromBlob", false);
@ -153,13 +156,13 @@ export default Component.extend({
this._darkModeListener.addListener(this.setAceTheme);
});
});
},
}
willDestroyElement() {
if (this._darkModeListener) {
this._darkModeListener.removeListener(this.setAceTheme);
}
},
}
@bind
setAceTheme() {
@ -170,7 +173,7 @@ export default Component.extend({
this._editor.setTheme(
`ace/theme/${schemeType === "dark" ? "chaos" : "chrome"}`
);
},
}
warnSCSSDeprecations() {
if (
@ -197,21 +200,20 @@ export default Component.extend({
this._editor.getSession().setAnnotations(warnings);
this.setWarning(
this.setWarning?.(
warnings.length
? I18n.t("admin.customize.theme.scss_color_variables_warning")
: false
);
},
}
actions: {
focus() {
if (this._editor) {
this._editor.focus();
this._editor.navigateFileEnd();
}
},
},
@action
focus() {
if (this._editor) {
this._editor.focus();
this._editor.navigateFileEnd();
}
}
_overridePlaceholder(loadedAce) {
const originalPlaceholderSetter =
@ -239,5 +241,5 @@ export default Component.extend({
this.$updatePlaceholder();
};
},
});
}
}

View File

@ -1,28 +1,26 @@
import { observes, on } from "discourse-common/utils/decorators";
import { classNames } from "@ember-decorators/component";
import { observes, on } from "@ember-decorators/object";
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"],
showLoadingSpinner: false,
hasFormattedLogs: false,
noLogsMessage: I18n.t("admin.backups.logs.none"),
init() {
this._super(...arguments);
this._reset();
},
@classNames("admin-backups-logs")
export default class AdminBackupsLogs extends Component {
showLoadingSpinner = false;
hasFormattedLogs = false;
noLogsMessage = I18n.t("admin.backups.logs.none");
formattedLogs = "";
index = 0;
_reset() {
this.setProperties({ formattedLogs: "", index: 0 });
},
}
_scrollDown() {
const div = this.element;
div.scrollTop = div.scrollHeight;
},
}
@on("init")
@observes("logs.[]")
@ -31,7 +29,7 @@ export default Component.extend({
this._reset(); // reset the cached logs whenever the model is reset
this.renderLogs();
}
},
}
_updateFormattedLogsFunc() {
const logs = this.logs;
@ -55,13 +53,13 @@ export default Component.extend({
this.renderLogs();
scheduleOnce("afterRender", this, this._scrollDown);
},
}
@on("init")
@observes("logs.[]")
_updateFormattedLogs() {
discourseDebounce(this, this._updateFormattedLogsFunc, 150);
},
}
renderLogs() {
const formattedLogs = this.formattedLogs;
@ -76,5 +74,5 @@ export default Component.extend({
} else {
this.set("showLoadingSpinner", false);
}
},
});
}
}

View File

@ -1,28 +1,22 @@
import { tagName } from "@ember-decorators/component";
import Component from "@ember/component";
import { action } from "@ember/object";
export default Component.extend({
tagName: "",
buffer: "",
editing: false,
init() {
this._super(...arguments);
this.set("editing", false);
},
@tagName("")
export default class AdminEditableField extends Component {
buffer = "";
editing = false;
@action
edit(event) {
event?.preventDefault();
this.set("buffer", this.value);
this.toggleProperty("editing");
},
}
actions: {
save() {
// Action has to toggle 'editing' property.
this.action(this.buffer);
},
},
});
@action
save() {
// Action has to toggle 'editing' property.
this.action(this.buffer);
}
}

View File

@ -1,4 +1,5 @@
import { classNames } from "@ember-decorators/component";
import Component from "@ember/component";
export default Component.extend({
classNames: ["row"],
});
@classNames("row")
export default class AdminFormRow extends Component {}

View File

@ -1,9 +1,10 @@
import { tagName } from "@ember-decorators/component";
import Component from "@ember/component";
import loadScript from "discourse/lib/load-script";
export default Component.extend({
tagName: "canvas",
type: "line",
@tagName("canvas")
export default class AdminGraph extends Component {
type = "line";
refreshChart() {
const ctx = this.element.getContext("2d");
@ -49,11 +50,11 @@ export default Component.extend({
};
this._chart = new window.Chart(ctx, config);
},
}
didInsertElement() {
loadScript("/javascripts/Chart.min.js").then(() =>
this.refreshChart.apply(this)
);
},
});
}
}

View File

@ -0,0 +1,15 @@
import { tagName } from "@ember-decorators/component";
import Component from "@ember/component";
@tagName("")
export default class AdminNav extends Component {
<template>
<div class="admin-controls">
<nav>
<ul class="nav nav-pills">
{{yield}}
</ul>
</nav>
</div>
</template>
}

View File

@ -1,4 +0,0 @@
import Component from "@ember/component";
export default Component.extend({
tagName: "",
});

View File

@ -1,16 +1,16 @@
import { classNames } from "@ember-decorators/component";
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
classNames: ["penalty-history"],
@classNames("penalty-history")
export default class AdminPenaltyHistory extends Component {
@discourseComputed("user.penalty_counts.suspended")
suspendedCountClass(count) {
if (count > 0) {
return "danger";
}
return "";
},
}
@discourseComputed("user.penalty_counts.silenced")
silencedCountClass(count) {
@ -18,5 +18,5 @@ export default Component.extend({
return "danger";
}
return "";
},
});
}
}

View File

@ -1,5 +1,6 @@
import Component from "@ember/component";
import { action } from "@ember/object";
import { equal } from "@ember/object/computed";
import Component from "@ember/component";
import discourseComputed, {
afterRender,
} from "discourse-common/utils/decorators";
@ -7,30 +8,28 @@ import I18n from "I18n";
const ACTIONS = ["delete", "delete_replies", "edit", "none"];
export default Component.extend({
postId: null,
postAction: null,
postEdit: null,
export default class AdminPenaltyPostAction extends Component {
postId = null;
postAction = null;
postEdit = null;
@equal("postAction", "edit") editing;
@discourseComputed
penaltyActions() {
return ACTIONS.map((id) => {
return { id, name: I18n.t(`admin.user.penalty_post_${id}`) };
});
},
}
editing: equal("postAction", "edit"),
@action
penaltyChanged(postAction) {
this.set("postAction", postAction);
actions: {
penaltyChanged(postAction) {
this.set("postAction", postAction);
// If we switch to edit mode, jump to the edit textarea
if (postAction === "edit") {
this._focusEditTextarea();
}
},
},
// If we switch to edit mode, jump to the edit textarea
if (postAction === "edit") {
this._focusEditTextarea();
}
}
@afterRender
_focusEditTextarea() {
@ -38,5 +37,5 @@ export default Component.extend({
const body = elem.closest(".modal-body");
body.scrollTo(0, body.clientHeight);
elem.querySelector(".post-editor").focus();
},
});
}
}

View File

@ -1,43 +1,46 @@
import { tagName } from "@ember-decorators/component";
import { equal } from "@ember/object/computed";
import Component from "@ember/component";
import { action } from "@ember/object";
import { equal } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n";
const CUSTOM_REASON_KEY = "custom";
export default Component.extend({
tagName: "",
selectedReason: CUSTOM_REASON_KEY,
customReason: "",
reasonKeys: [
@tagName("")
export default class AdminPenaltyReason extends Component {
selectedReason = CUSTOM_REASON_KEY;
customReason = "";
reasonKeys = [
"not_listening_to_staff",
"consuming_staff_time",
"combative",
"in_wrong_place",
"no_constructive_purpose",
CUSTOM_REASON_KEY,
],
isCustomReason: equal("selectedReason", CUSTOM_REASON_KEY),
];
@equal("selectedReason", CUSTOM_REASON_KEY) isCustomReason;
@discourseComputed("reasonKeys")
reasons(keys) {
return keys.map((key) => {
return { id: key, name: I18n.t(`admin.user.suspend_reasons.${key}`) };
});
},
}
@action
setSelectedReason(value) {
this.set("selectedReason", value);
this.setReason();
},
}
@action
setCustomReason(value) {
this.set("customReason", value);
this.setReason();
},
}
setReason() {
if (this.isCustomReason) {
@ -48,5 +51,5 @@ export default Component.extend({
I18n.t(`admin.user.suspend_reasons.${this.selectedReason}`)
);
}
},
});
}
}

View File

@ -1,10 +1,10 @@
import { tagName } from "@ember-decorators/component";
import Component from "@ember/component";
import { action } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
tagName: "",
@tagName("")
export default class AdminPenaltySimilarUsers extends Component {
@discourseComputed("penaltyType")
penaltyField(penaltyType) {
if (penaltyType === "suspend") {
@ -12,7 +12,7 @@ export default Component.extend({
} else if (penaltyType === "silence") {
return "can_be_silenced";
}
},
}
@action
selectUserId(userId, event) {
@ -25,5 +25,5 @@ export default Component.extend({
} else {
this.selectedUserIds.removeObject(userId);
}
},
});
}
}

View File

@ -1,3 +1,4 @@
import { classNames } from "@ember-decorators/component";
import Report from "admin/models/report";
import Component from "@ember/component";
import discourseDebounce from "discourse-common/lib/debounce";
@ -7,31 +8,31 @@ import { number } from "discourse/lib/formatter";
import { schedule } from "@ember/runloop";
import { bind } from "discourse-common/utils/decorators";
export default Component.extend({
classNames: ["admin-report-chart"],
limit: 8,
total: 0,
options: null,
@classNames("admin-report-chart")
export default class AdminReportChart extends Component {
limit = 8;
total = 0;
options = null;
didInsertElement() {
this._super(...arguments);
super.didInsertElement(...arguments);
window.addEventListener("resize", this._resizeHandler);
},
}
willDestroyElement() {
this._super(...arguments);
super.willDestroyElement(...arguments);
window.removeEventListener("resize", this._resizeHandler);
this._resetChart();
},
}
didReceiveAttrs() {
this._super(...arguments);
super.didReceiveAttrs(...arguments);
discourseDebounce(this, this._scheduleChartRendering, 100);
},
}
_scheduleChartRendering() {
schedule("afterRender", () => {
@ -40,7 +41,7 @@ export default Component.extend({
this.element && this.element.querySelector(".chart-canvas")
);
});
},
}
_renderChart(model, chartCanvas) {
if (!chartCanvas) {
@ -99,7 +100,7 @@ export default Component.extend({
this._buildChartConfig(data, this.options)
);
});
},
}
_buildChartConfig(data, options) {
return {
@ -161,21 +162,21 @@ export default Component.extend({
},
},
};
},
}
_resetChart() {
if (this._chart) {
this._chart.destroy();
this._chart = null;
}
},
}
_applyChartGrouping(model, data, options) {
return Report.collapse(model, data, options.chartGrouping);
},
}
@bind
_resizeHandler() {
discourseDebounce(this, this._scheduleChartRendering, 500);
},
});
}
}

View File

@ -1,6 +1,6 @@
import { attributeBindings, classNames } from "@ember-decorators/component";
import Component from "@ember/component";
export default Component.extend({
classNames: ["admin-report-counters"],
attributeBindings: ["model.description:title"],
});
@classNames("admin-report-counters")
@attributeBindings("model.description:title")
export default class AdminReportCounters extends Component {}

View File

@ -1,11 +1,12 @@
import Component from "@ember/component";
import { classNameBindings, tagName } from "@ember-decorators/component";
import { match } from "@ember/object/computed";
export default Component.extend({
allTime: true,
tagName: "tr",
reverseColors: match(
"report.type",
/^(time_to_first_response|topics_with_no_response)$/
),
classNameBindings: ["reverseColors"],
});
import Component from "@ember/component";
@tagName("tr")
@classNameBindings("reverseColors")
export default class AdminReportCounts extends Component {
allTime = true;
@match("report.type", /^(time_to_first_response|topics_with_no_response)$/)
reverseColors;
}

View File

@ -1,4 +1,5 @@
import { classNames } from "@ember-decorators/component";
import Component from "@ember/component";
export default Component.extend({
classNames: ["admin-report-inline-table"],
});
@classNames("admin-report-inline-table")
export default class AdminReportInlineTable extends Component {}

View File

@ -1,4 +1,5 @@
import { tagName } from "@ember-decorators/component";
import Component from "@ember/component";
export default Component.extend({
tagName: "tr",
});
@tagName("tr")
export default class AdminReportPerDayCounts extends Component {}

View File

@ -1,3 +1,4 @@
import { classNames } from "@ember-decorators/component";
import Report from "admin/models/report";
import Component from "@ember/component";
import discourseDebounce from "discourse-common/lib/debounce";
@ -7,32 +8,31 @@ import { number } from "discourse/lib/formatter";
import { schedule } from "@ember/runloop";
import { bind } from "discourse-common/utils/decorators";
export default Component.extend({
classNames: ["admin-report-chart", "admin-report-stacked-chart"],
@classNames("admin-report-chart", "admin-report-stacked-chart")
export default class AdminReportStackedChart extends Component {
didInsertElement() {
this._super(...arguments);
super.didInsertElement(...arguments);
window.addEventListener("resize", this._resizeHandler);
},
}
willDestroyElement() {
this._super(...arguments);
super.willDestroyElement(...arguments);
window.removeEventListener("resize", this._resizeHandler);
this._resetChart();
},
}
didReceiveAttrs() {
this._super(...arguments);
super.didReceiveAttrs(...arguments);
discourseDebounce(this, this._scheduleChartRendering, 100);
},
}
@bind
_resizeHandler() {
discourseDebounce(this, this._scheduleChartRendering, 500);
},
}
_scheduleChartRendering() {
schedule("afterRender", () => {
@ -45,7 +45,7 @@ export default Component.extend({
this.element.querySelector(".chart-canvas")
);
});
},
}
_renderChart(model, chartCanvas) {
if (!chartCanvas) {
@ -79,7 +79,7 @@ export default Component.extend({
this._chart = new window.Chart(context, this._buildChartConfig(data));
});
},
}
_buildChartConfig(data) {
return {
@ -150,10 +150,10 @@ export default Component.extend({
},
},
};
},
}
_resetChart() {
this._chart?.destroy();
this._chart = null;
},
});
}
}

View File

@ -1,43 +1,45 @@
import { classNames } from "@ember-decorators/component";
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({
classNames: ["admin-report-storage-stats"],
@classNames("admin-report-storage-stats")
export default class AdminReportStorageStats extends Component {
@setting("backup_location") backupLocation;
backupLocation: setting("backup_location"),
backupStats: alias("model.data.backups"),
uploadStats: alias("model.data.uploads"),
@alias("model.data.backups") backupStats;
@alias("model.data.uploads") uploadStats;
@discourseComputed("backupStats")
showBackupStats(stats) {
return stats && this.currentUser.admin;
},
}
@discourseComputed("backupLocation")
backupLocationName(backupLocation) {
return I18n.t(`admin.backups.location.${backupLocation}`);
},
}
@discourseComputed("backupStats.used_bytes")
usedBackupSpace(bytes) {
return I18n.toHumanSize(bytes);
},
}
@discourseComputed("backupStats.free_bytes")
freeBackupSpace(bytes) {
return I18n.toHumanSize(bytes);
},
}
@discourseComputed("uploadStats.used_bytes")
usedUploadSpace(bytes) {
return I18n.toHumanSize(bytes);
},
}
@discourseComputed("uploadStats.free_bytes")
freeUploadSpace(bytes) {
return I18n.toHumanSize(bytes);
},
});
}
}

View File

@ -1,21 +1,27 @@
import Component from "@ember/component";
import {
attributeBindings,
classNameBindings,
classNames,
tagName,
} from "@ember-decorators/component";
import { alias } from "@ember/object/computed";
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
tagName: "td",
classNames: ["admin-report-table-cell"],
classNameBindings: ["type", "property"],
attributeBindings: ["value:title"],
options: null,
@tagName("td")
@classNames("admin-report-table-cell")
@classNameBindings("type", "property")
@attributeBindings("value:title")
export default class AdminReportTableCell extends Component {
options = null;
@alias("label.type") type;
@alias("label.mainProperty") property;
@alias("computedLabel.formattedValue") formattedValue;
@alias("computedLabel.value") value;
@discourseComputed("label", "data", "options")
computedLabel(label, data, options) {
return label.compute(data, options || {});
},
type: alias("label.type"),
property: alias("label.mainProperty"),
formattedValue: alias("computedLabel.formattedValue"),
value: alias("computedLabel.value"),
});
}
}

View File

@ -1,19 +1,24 @@
import {
attributeBindings,
classNameBindings,
classNames,
tagName,
} from "@ember-decorators/component";
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
tagName: "th",
classNames: ["admin-report-table-header"],
classNameBindings: ["label.mainProperty", "label.type", "isCurrentSort"],
attributeBindings: ["label.title:title"],
@tagName("th")
@classNames("admin-report-table-header")
@classNameBindings("label.mainProperty", "label.type", "isCurrentSort")
@attributeBindings("label.title:title")
export default class AdminReportTableHeader extends Component {
@discourseComputed("currentSortLabel.sortProperty", "label.sortProperty")
isCurrentSort(currentSortField, labelSortField) {
return currentSortField === labelSortField;
},
}
@discourseComputed("currentSortDirection")
sortIcon(currentSortDirection) {
return currentSortDirection === 1 ? "caret-up" : "caret-down";
},
});
}
}

View File

@ -1,6 +1,8 @@
import { classNames, tagName } from "@ember-decorators/component";
import Component from "@ember/component";
export default Component.extend({
tagName: "tr",
classNames: ["admin-report-table-row"],
options: null,
});
@tagName("tr")
@classNames("admin-report-table-row")
export default class AdminReportTableRow extends Component {
options = null;
}

View File

@ -1,22 +1,26 @@
import Component from "@ember/component";
import { action } from "@ember/object";
import { classNameBindings, classNames } from "@ember-decorators/component";
import { alias } from "@ember/object/computed";
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
import { makeArray } from "discourse-common/lib/helpers";
const PAGES_LIMIT = 8;
export default Component.extend({
classNameBindings: ["sortable", "twoColumns"],
classNames: ["admin-report-table"],
sortable: false,
sortDirection: 1,
perPage: alias("options.perPage"),
page: 0,
@classNameBindings("sortable", "twoColumns")
@classNames("admin-report-table")
export default class AdminReportTable extends Component {
sortable = false;
sortDirection = 1;
@alias("options.perPage") perPage;
page = 0;
@discourseComputed("model.computedLabels.length")
twoColumns(labelsLength) {
return labelsLength === 2;
},
}
@discourseComputed(
"totalsForSample",
@ -31,12 +35,12 @@ export default Component.extend({
.reduce((s, v) => s + v, 0);
return sum >= 1 && total && datesFiltering;
},
}
@discourseComputed("model.total", "options.total", "twoColumns")
showTotal(reportTotal, total, twoColumns) {
return reportTotal && total && twoColumns;
},
}
@discourseComputed(
"model.{average,data}",
@ -50,17 +54,17 @@ export default Component.extend({
sampleTotalValue &&
hasTwoColumns
);
},
}
@discourseComputed("totalsForSample.1.value", "model.data.length")
averageForSample(totals, count) {
return (totals / count).toFixed(0);
},
}
@discourseComputed("model.data.length")
showSortingUI(dataLength) {
return dataLength >= 5;
},
}
@discourseComputed("totalsForSampleRow", "model.computedLabels")
totalsForSample(row, labels) {
@ -70,7 +74,7 @@ export default Component.extend({
computedLabel.property = label.mainProperty;
return computedLabel;
});
},
}
@discourseComputed("model.data", "model.computedLabels")
totalsForSampleRow(rows, labels) {
@ -98,7 +102,7 @@ export default Component.extend({
});
return totalsRow;
},
}
@discourseComputed("sortLabel", "sortDirection", "model.data.[]")
sortedData(sortLabel, sortDirection, data) {
@ -118,7 +122,7 @@ export default Component.extend({
}
return data;
},
}
@discourseComputed("sortedData.[]", "perPage", "page")
paginatedData(data, perPage, page) {
@ -128,7 +132,7 @@ export default Component.extend({
}
return data;
},
}
@discourseComputed("model.data", "perPage", "page")
pages(data, perPage, page) {
@ -156,19 +160,19 @@ export default Component.extend({
}
return pages;
},
}
actions: {
changePage(page) {
this.set("page", page);
},
@action
changePage(page) {
this.set("page", page);
}
sortByLabel(label) {
if (this.sortLabel === label) {
this.set("sortDirection", this.sortDirection === 1 ? -1 : 1);
} else {
this.set("sortLabel", label);
}
},
},
});
@action
sortByLabel(label) {
if (this.sortLabel === label) {
this.set("sortDirection", this.sortDirection === 1 ? -1 : 1);
} else {
this.set("sortLabel", label);
}
}
}

View File

@ -1,4 +1,5 @@
import { tagName } from "@ember-decorators/component";
import Component from "@ember/component";
export default Component.extend({
tagName: "tr",
});
@tagName("tr")
export default class AdminReportTrustLevelCounts extends Component {}

View File

@ -1,6 +1,7 @@
import { classNameBindings, classNames } from "@ember-decorators/component";
import { alias, and, equal, notEmpty, or } from "@ember/object/computed";
import EmberObject, { action, computed } from "@ember/object";
import Report, { DAILY_LIMIT_DAYS, 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";
@ -21,51 +22,58 @@ const TABLE_OPTIONS = {
const CHART_OPTIONS = {};
export default Component.extend({
classNameBindings: [
"isHidden:hidden",
"isHidden::is-visible",
"isEnabled",
"isLoading",
"dasherizedDataSourceName",
],
classNames: ["admin-report"],
isEnabled: true,
disabledLabel: I18n.t("admin.dashboard.disabled"),
isLoading: false,
rateLimitationString: null,
dataSourceName: null,
report: null,
model: null,
reportOptions: null,
forcedModes: null,
showAllReportsLink: false,
filters: null,
showTrend: false,
showHeader: true,
showTitle: true,
showFilteringUI: false,
showDatesOptions: alias("model.dates_filtering"),
showRefresh: or("showDatesOptions", "model.available_filters.length"),
shouldDisplayTrend: and("showTrend", "model.prev_period"),
endDate: null,
startDate: null,
@classNameBindings(
"isHidden:hidden",
"isHidden::is-visible",
"isEnabled",
"isLoading",
"dasherizedDataSourceName"
)
@classNames("admin-report")
export default class AdminReport extends Component {
isEnabled = true;
disabledLabel = I18n.t("admin.dashboard.disabled");
isLoading = false;
rateLimitationString = null;
dataSourceName = null;
report = null;
model = null;
reportOptions = null;
forcedModes = null;
showAllReportsLink = false;
filters = null;
showTrend = false;
showHeader = true;
showTitle = true;
showFilteringUI = false;
init() {
this._super(...arguments);
@alias("model.dates_filtering") showDatesOptions;
this._reports = [];
},
@or("showDatesOptions", "model.available_filters.length") showRefresh;
isHidden: computed("siteSettings.dashboard_hidden_reports", function () {
@and("showTrend", "model.prev_period") shouldDisplayTrend;
endDate = null;
startDate = null;
@or("showTimeoutError", "showExceptionError", "showNotFoundError") showError;
@equal("model.error", "not_found") showNotFoundError;
@equal("model.error", "timeout") showTimeoutError;
@equal("model.error", "exception") showExceptionError;
@notEmpty("model.data") hasData;
_reports = [];
@computed("siteSettings.dashboard_hidden_reports")
get isHidden() {
return (this.siteSettings.dashboard_hidden_reports || "")
.split("|")
.filter(Boolean)
.includes(this.dataSourceName);
}),
}
didReceiveAttrs() {
this._super(...arguments);
super.didReceiveAttrs(...arguments);
let startDate = moment();
if (this.filters && isPresent(this.filters.startDate)) {
@ -88,42 +96,35 @@ export default Component.extend({
} else if (this.dataSourceName) {
this._fetchReport();
}
},
showError: or("showTimeoutError", "showExceptionError", "showNotFoundError"),
showNotFoundError: equal("model.error", "not_found"),
showTimeoutError: equal("model.error", "timeout"),
showExceptionError: equal("model.error", "exception"),
hasData: notEmpty("model.data"),
}
@discourseComputed("dataSourceName", "model.type")
dasherizedDataSourceName(dataSourceName, type) {
return (dataSourceName || type || "undefined").replace(/_/g, "-");
},
}
@discourseComputed("dataSourceName", "model.type")
dataSource(dataSourceName, type) {
dataSourceName = dataSourceName || type;
return `/admin/reports/${dataSourceName}`;
},
}
@discourseComputed("displayedModes.length")
showModes(displayedModesLength) {
return displayedModesLength > 1;
},
}
@discourseComputed("currentMode")
isChartMode(currentMode) {
return currentMode === "chart";
},
}
@action
changeGrouping(grouping) {
this.send("refreshReport", {
chartGrouping: grouping,
});
},
}
@discourseComputed("currentMode", "model.modes", "forcedModes")
displayedModes(currentMode, reportModes, forcedModes) {
@ -139,12 +140,12 @@ export default Component.extend({
icon: mode === "table" ? "table" : "signal",
};
});
},
}
@discourseComputed("currentMode")
modeComponent(currentMode) {
return `admin-report-${currentMode.replace(/_/g, "-")}`;
},
}
@discourseComputed(
"dataSourceName",
@ -178,7 +179,7 @@ export default Component.extend({
.join(":");
return reportKey;
},
}
@discourseComputed("options.chartGrouping", "model.chartData.length")
chartGroupings(grouping, count) {
@ -192,7 +193,7 @@ export default Component.extend({
class: `chart-grouping ${grouping === id ? "active" : "inactive"}`,
};
});
},
}
@action
onChangeDateRange(range) {
@ -200,7 +201,7 @@ export default Component.extend({
startDate: range.from,
endDate: range.to,
});
},
}
@action
applyFilter(id, value) {
@ -215,7 +216,7 @@ export default Component.extend({
this.send("refreshReport", {
filters: customFilters,
});
},
}
@action
refreshReport(options = {}) {
@ -238,7 +239,7 @@ export default Component.extend({
? this.get("filters.customFilters")
: options.filters,
});
},
}
@action
exportCsv() {
@ -254,7 +255,7 @@ export default Component.extend({
}
exportEntity("report", args).then(outputExportResult);
},
}
@action
onChangeMode(mode) {
@ -263,7 +264,7 @@ export default Component.extend({
this.send("refreshReport", {
chartGrouping: null,
});
},
}
_computeReport() {
if (!this.element || this.isDestroying || this.isDestroyed) {
@ -306,7 +307,7 @@ export default Component.extend({
}
this._renderReport(report, this.forcedModes, this.currentMode);
},
}
_renderReport(report, forcedModes, currentMode) {
const modes = forcedModes ? forcedModes.split(",") : report.modes;
@ -317,11 +318,9 @@ export default Component.extend({
currentMode,
options: this._buildOptions(currentMode, report),
});
},
}
_fetchReport() {
this._super(...arguments);
this.setProperties({ isLoading: true, rateLimitationString: null });
next(() => {
@ -349,7 +348,7 @@ export default Component.extend({
ReportLoader.enqueue(this.dataSourceName, payload.data, callback);
});
},
}
_buildPayload(facets) {
let payload = { data: { facets } };
@ -375,7 +374,7 @@ export default Component.extend({
}
return payload;
},
}
_buildOptions(mode, report) {
if (mode === "table") {
@ -393,7 +392,7 @@ export default Component.extend({
})
);
}
},
}
_loadReport(jsonReport) {
Report.fillMissingDates(jsonReport, { filledField: "chartData" });
@ -423,5 +422,5 @@ export default Component.extend({
}
return Report.create(jsonReport);
},
});
}
}

View File

@ -44,7 +44,7 @@
@checked={{this.onlyOverridden}}
{{on
"click"
(action "onlyOverriddenChanged" value="target.checked")
(action this.onlyOverriddenChanged value="target.checked")
}}
/>
{{i18n "admin.customize.theme.hide_unused_fields"}}
@ -125,6 +125,6 @@
@autofocus="true"
@placeholder={{this.placeholder}}
@htmlPlaceholder={{true}}
@save={{action "save"}}
@save={{this.save}}
@setWarning={{action "setWarning"}}
/>

View File

@ -3,11 +3,13 @@ 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 { action } from "@ember/object";
import { action, computed } from "@ember/object";
import { next } from "@ember/runloop";
export default Component.extend({
warning: null,
export default class AdminThemeEditor extends Component {
warning = null;
@fmt("fieldName", "currentTargetName", "%@|%@") editorId;
@discourseComputed("theme.targets", "onlyOverridden", "showAdvanced")
visibleTargets(targets, onlyOverridden, showAdvanced) {
@ -20,7 +22,7 @@ export default Component.extend({
}
return target.edited;
});
},
}
@discourseComputed("currentTargetName", "onlyOverridden", "theme.fields")
visibleFields(targetName, onlyOverridden, fields) {
@ -29,7 +31,7 @@ export default Component.extend({
fields = fields.filter((field) => field.edited);
}
return fields;
},
}
@discourseComputed("currentTargetName", "fieldName")
activeSectionMode(targetName, fieldName) {
@ -43,7 +45,7 @@ export default Component.extend({
return "scss";
}
return fieldName && fieldName.includes("scss") ? "scss" : "html";
},
}
@discourseComputed("currentTargetName", "fieldName")
placeholder(targetName, fieldName) {
@ -58,30 +60,27 @@ export default Component.extend({
});
}
return "";
},
}
@discourseComputed("fieldName", "currentTargetName", "theme")
activeSection: {
get(fieldName, target, model) {
return model.getField(target, fieldName);
},
set(value, fieldName, target, model) {
model.setField(target, fieldName, value);
return value;
},
},
@computed("fieldName", "currentTargetName", "theme")
get activeSection() {
return this.theme.getField(this.currentTargetName, this.fieldName);
}
editorId: fmt("fieldName", "currentTargetName", "%@|%@"),
set activeSection(value) {
this.theme.setField(this.currentTargetName, this.fieldName, value);
return value;
}
@discourseComputed("maximized")
maximizeIcon(maximized) {
return maximized ? "discourse-compress" : "discourse-expand";
},
}
@discourseComputed("currentTargetName", "theme.targets")
showAddField(currentTargetName, targets) {
return targets.find((t) => t.name === currentTargetName).customNames;
},
}
@discourseComputed(
"currentTargetName",
@ -90,52 +89,45 @@ export default Component.extend({
)
error(target, fieldName) {
return this.theme.getError(target, fieldName);
},
}
@action
toggleShowAdvanced(event) {
event?.preventDefault();
this.toggleProperty("showAdvanced");
},
}
@action
toggleAddField(event) {
event?.preventDefault();
this.toggleProperty("addingField");
},
}
@action
toggleMaximize(event) {
event?.preventDefault();
this.toggleProperty("maximized");
next(() => this.appEvents.trigger("ace:resize"));
},
}
actions: {
cancelAddField() {
this.set("addingField", false);
},
@action
cancelAddField() {
this.set("addingField", false);
}
addField(name) {
if (!name) {
return;
}
name = name.replace(/[^a-zA-Z0-9-_/]/g, "");
this.theme.setField(this.currentTargetName, name, "");
this.setProperties({ newFieldName: "", addingField: false });
this.fieldAdded(this.currentTargetName, name);
},
@action
addField(name) {
if (!name) {
return;
}
name = name.replace(/[^a-zA-Z0-9-_/]/g, "");
this.theme.setField(this.currentTargetName, name, "");
this.setProperties({ newFieldName: "", addingField: false });
this.fieldAdded(this.currentTargetName, name);
}
onlyOverriddenChanged(value) {
this.onlyOverriddenChanged(value);
},
save() {
this.attrs.save();
},
setWarning(message) {
this.set("warning", message);
},
},
});
@action
setWarning(message) {
this.set("warning", message);
}
}

View File

@ -1,23 +1,27 @@
import Component from "@ember/component";
import { classNames } from "@ember-decorators/component";
import { inject as service } from "@ember/service";
import { alias, equal } from "@ember/object/computed";
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
import { action } from "@ember/object";
import I18n from "I18n";
import { inject as service } from "@ember/service";
export default Component.extend({
classNames: ["watched-word"],
dialog: service(),
@classNames("watched-word")
export default class AdminWatchedWord extends Component {
@service dialog;
isReplace: equal("actionKey", "replace"),
isTag: equal("actionKey", "tag"),
isLink: equal("actionKey", "link"),
isCaseSensitive: alias("word.case_sensitive"),
@equal("actionKey", "replace") isReplace;
@equal("actionKey", "tag") isTag;
@equal("actionKey", "link") isLink;
@alias("word.case_sensitive") isCaseSensitive;
@discourseComputed("word.replacement")
tags(replacement) {
return replacement.split(",");
},
}
@action
deleteWord() {
@ -33,5 +37,5 @@ export default Component.extend({
})
);
});
},
});
}
}

View File

@ -1,14 +1,15 @@
import Component from "@ember/component";
export default Component.extend({
export default class AdminWrapper extends Component {
didInsertElement() {
this._super(...arguments);
super.didInsertElement(...arguments);
document.querySelector("html").classList.add("admin-area");
document.querySelector("body").classList.add("admin-interface");
},
}
willDestroyElement() {
this._super(...arguments);
super.willDestroyElement(...arguments);
document.querySelector("html").classList.remove("admin-area");
document.querySelector("body").classList.remove("admin-interface");
},
});
}
}

View File

@ -1,4 +1,5 @@
import { tagName } from "@ember-decorators/component";
import Component from "@ember/component";
export default Component.extend({
tagName: "",
});
@tagName("")
export default class CancelLink extends Component {}

View File

@ -1,6 +1,7 @@
import { classNames } from "@ember-decorators/component";
import { action, computed } from "@ember/object";
import Component from "@ember/component";
import { observes } from "discourse-common/utils/decorators";
import { observes } from "@ember-decorators/object";
/**
An input field for a color.
@ -9,20 +10,20 @@ import { observes } from "discourse-common/utils/decorators";
@param brightnessValue is a number from 0 to 255 representing the brightness of the color. See ColorSchemeColor.
@params valid is a boolean indicating if the input field is a valid color.
**/
export default Component.extend({
classNames: ["color-picker"],
@classNames("color-picker")
export default class ColorInput extends Component {
onlyHex = true;
styleSelection = true;
onlyHex: true,
styleSelection: true,
maxlength: computed("onlyHex", function () {
@computed("onlyHex")
get maxlength() {
return this.onlyHex ? 6 : null;
}),
}
normalizedHexValue: computed("hexValue", function () {
@computed("hexValue")
get normalizedHexValue() {
return this.normalize(this.hexValue);
}),
}
normalize(color) {
if (this._valid(color)) {
@ -40,19 +41,19 @@ export default Component.extend({
}
}
return color;
},
}
@action
onHexInput(color) {
if (this.attrs.onChangeColor) {
this.attrs.onChangeColor(this.normalize(color || ""));
}
},
}
@action
onPickerInput(event) {
this.set("hexValue", event.target.value.replace("#", ""));
},
}
@observes("hexValue", "brightnessValue", "valid")
hexValueChanged() {
@ -65,9 +66,9 @@ export default Component.extend({
if (this._valid()) {
this.element.querySelector(".picker").value = this.normalize(hex);
}
},
}
_valid(color = this.hexValue) {
return /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(color);
},
});
}
}

View File

@ -1,3 +1,3 @@
import Component from "@ember/component";
export default Component.extend({});
export default class DashboardNewFeatureItem extends Component {}

View File

@ -1,18 +1,19 @@
import { classNameBindings, classNames } from "@ember-decorators/component";
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,
@classNames("section", "dashboard-new-features")
@classNameBindings("hasUnseenFeatures:ordered-first")
export default class DashboardNewFeatures extends Component {
newFeatures = null;
releaseNotesLink = null;
init() {
this._super(...arguments);
constructor() {
super(...arguments);
ajax("/admin/dashboard/new-features.json").then((json) => {
if (!this.element || this.isDestroying || this.isDestroyed) {
if (this.isDestroying || this.isDestroyed) {
return;
}
@ -22,16 +23,17 @@ export default Component.extend({
releaseNotesLink: json.release_notes_link,
});
});
},
}
columnCountClass: computed("newFeatures", function () {
@computed("newFeatures")
get columnCountClass() {
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,3 +1,3 @@
import Component from "@ember/component";
export default Component.extend({});
export default class DashboardProblems extends Component {}

View File

@ -29,7 +29,7 @@
@content={{this.editorContents}}
@mode={{this.currentEditorMode}}
@editorId={{this.editorId}}
@save={{action "save"}}
@save={{@save}}
/>
<div class="admin-footer">

View File

@ -1,17 +1,19 @@
import { action, computed } from "@ember/object";
import { inject as service } from "@ember/service";
import { reads } from "@ember/object/computed";
import Component from "@ember/component";
import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators";
import { reads } from "@ember/object/computed";
import { inject as service } from "@ember/service";
export default Component.extend({
dialog: service(),
editorId: reads("fieldName"),
export default class EmailStylesEditor extends Component {
@service dialog;
@reads("fieldName") editorId;
@discourseComputed("fieldName")
currentEditorMode(fieldName) {
return fieldName === "css" ? "scss" : fieldName;
},
}
@discourseComputed("fieldName", "styles.html", "styles.css")
resetDisabled(fieldName) {
@ -19,36 +21,31 @@ export default Component.extend({
this.get(`styles.${fieldName}`) ===
this.get(`styles.default_${fieldName}`)
);
},
}
@discourseComputed("styles", "fieldName")
editorContents: {
get(styles, fieldName) {
return styles[fieldName];
},
set(value, styles, fieldName) {
styles.setField(fieldName, value);
return value;
},
},
@computed("styles", "fieldName")
get editorContents() {
return this.styles[this.fieldName];
}
actions: {
reset() {
this.dialog.yesNoConfirm({
message: I18n.t("admin.customize.email_style.reset_confirm", {
fieldName: I18n.t(`admin.customize.email_style.${this.fieldName}`),
}),
didConfirm: () => {
this.styles.setField(
this.fieldName,
this.styles.get(`default_${this.fieldName}`)
);
this.notifyPropertyChange("editorContents");
},
});
},
save() {
this.attrs.save();
},
},
});
set editorContents(value) {
this.styles.setField(this.fieldName, value);
return value;
}
@action
reset() {
this.dialog.yesNoConfirm({
message: I18n.t("admin.customize.email_style.reset_confirm", {
fieldName: I18n.t(`admin.customize.email_style.${this.fieldName}`),
}),
didConfirm: () => {
this.styles.setField(
this.fieldName,
this.styles.get(`default_${this.fieldName}`)
);
this.notifyPropertyChange("editorContents");
},
});
}
}

View File

@ -9,15 +9,6 @@
autofocus={{true}}
/>
</td>
<td class="editing-input">
<div class="label">{{i18n "admin.embedding.class_name"}}</div>
<Input
@value={{this.buffered.class_name}}
placeholder="class"
@enter={{action "save"}}
class="class-name"
/>
</td>
<td class="editing-input">
<div class="label">{{i18n "admin.embedding.allowed_paths"}}</div>
<Input
@ -54,12 +45,6 @@
<div class="label">{{i18n "admin.embedding.host"}}</div>
{{this.host.host}}
</td>
<td>
<div class="label">
{{i18n "admin.embedding.class_name"}}
</div>
{{this.host.class_name}}
</td>
<td>
<div class="label">
{{i18n "admin.embedding.allowed_paths"}}

View File

@ -1,85 +1,91 @@
import { action } from "@ember/object";
import { tagName } from "@ember-decorators/component";
import { inject as service } from "@ember/service";
import { or } from "@ember/object/computed";
import Category from "discourse/models/category";
import Component from "@ember/component";
import I18n from "I18n";
import { bufferedProperty } from "discourse/mixins/buffered-content";
import discourseComputed from "discourse-common/utils/decorators";
import { inject as service } from "@ember/service";
import { isEmpty } from "@ember/utils";
import { or } from "@ember/object/computed";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default Component.extend(bufferedProperty("host"), {
editToggled: false,
tagName: "tr",
categoryId: null,
category: null,
dialog: service(),
@tagName("tr")
export default class EmbeddableHost extends Component.extend(
bufferedProperty("host")
) {
@service dialog;
editToggled = false;
categoryId = null;
category = null;
editing: or("host.isNew", "editToggled"),
@or("host.isNew", "editToggled") editing;
init() {
this._super(...arguments);
super.init(...arguments);
const host = this.host;
const categoryId = host.category_id || this.site.uncategorized_category_id;
const category = Category.findById(categoryId);
host.set("category", category);
},
}
@discourseComputed("buffered.host", "host.isSaving")
cantSave(host, isSaving) {
return isSaving || isEmpty(host);
},
}
actions: {
edit() {
this.set("categoryId", this.get("host.category.id"));
this.set("editToggled", true);
},
@action
edit() {
this.set("categoryId", this.get("host.category.id"));
this.set("editToggled", true);
}
save() {
if (this.cantSave) {
return;
}
@action
save() {
if (this.cantSave) {
return;
}
const props = this.buffered.getProperties(
"host",
"allowed_paths",
"class_name"
);
props.category_id = this.categoryId;
const props = this.buffered.getProperties(
"host",
"allowed_paths",
"class_name"
);
props.category_id = this.categoryId;
const host = this.host;
const host = this.host;
host
.save(props)
.then(() => {
host.set("category", Category.findById(this.categoryId));
this.set("editToggled", false);
})
.catch(popupAjaxError);
},
delete() {
return this.dialog.confirm({
message: I18n.t("admin.embedding.confirm_delete"),
didConfirm: () => {
return this.host.destroyRecord().then(() => {
this.deleteHost(this.host);
});
},
});
},
cancel() {
const host = this.host;
if (host.get("isNew")) {
this.deleteHost(host);
} else {
this.rollbackBuffer();
host
.save(props)
.then(() => {
host.set("category", Category.findById(this.categoryId));
this.set("editToggled", false);
}
},
},
});
})
.catch(popupAjaxError);
}
@action
delete() {
return this.dialog.confirm({
message: I18n.t("admin.embedding.confirm_delete"),
didConfirm: () => {
return this.host.destroyRecord().then(() => {
this.deleteHost(this.host);
});
},
});
}
@action
cancel() {
const host = this.host;
if (host.get("isNew")) {
this.deleteHost(host);
} else {
this.rollbackBuffer();
this.set("editToggled", false);
}
}
}

View File

@ -1,33 +1,33 @@
import { classNames } from "@ember-decorators/component";
import { computed } from "@ember/object";
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
import { dasherize } from "@ember/string";
export default Component.extend({
classNames: ["embed-setting"],
@classNames("embed-setting")
export default class EmbeddingSetting extends Component {
@discourseComputed("field")
inputId(field) {
return dasherize(field);
},
}
@discourseComputed("field")
translationKey(field) {
return `admin.embedding.${field}`;
},
}
@discourseComputed("type")
isCheckbox(type) {
return type === "checkbox";
},
}
@discourseComputed("value")
checked: {
get(value) {
return !!value;
},
set(value) {
this.set("value", value);
return value;
},
},
});
@computed("value")
get checked() {
return !!this.value;
}
set checked(value) {
this.set("value", value);
return value;
}
}

View File

@ -1,3 +1,4 @@
import { classNameBindings } from "@ember-decorators/component";
import Component from "@ember/component";
import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators";
@ -6,12 +7,12 @@ import { action, set, setProperties } from "@ember/object";
import { schedule } from "@ember/runloop";
import discourseLater from "discourse-common/lib/later";
export default Component.extend({
classNameBindings: [":value-list", ":emoji-list"],
values: null,
validationMessage: null,
emojiPickerIsActive: false,
isEditorFocused: false,
@classNameBindings(":value-list", ":emoji-list")
export default class EmojiValueList extends Component {
values = null;
validationMessage = null;
emojiPickerIsActive = false;
isEditorFocused = false;
@discourseComputed("values")
collection(values) {
@ -28,14 +29,14 @@ export default Component.extend({
emojiUrl: emojiUrlFor(value),
};
});
},
}
@action
closeEmojiPicker() {
this.collection.setEach("isEditing", false);
this.set("emojiPickerIsActive", false);
this.set("isEditorFocused", false);
},
}
@action
emojiSelected(code) {
@ -65,12 +66,12 @@ export default Component.extend({
this.set("emojiPickerIsActive", false);
this.set("isEditorFocused", false);
},
}
@discourseComputed("collection")
showUpDownButtons(collection) {
return collection.length - 1 ? true : false;
},
}
_splitValues(values) {
if (values && values.length) {
@ -91,7 +92,7 @@ export default Component.extend({
} else {
return [];
}
},
}
@action
editValue(index) {
@ -111,12 +112,12 @@ export default Component.extend({
}
}, 100);
});
},
}
@action
removeValue(value) {
this._removeValue(value);
},
}
@action
shift(operation, index) {
@ -133,7 +134,7 @@ export default Component.extend({
this.collection.insertAt(futureIndex, shiftedEmoji);
this._saveValues();
},
}
_validateInput(input) {
this.set("validationMessage", null);
@ -147,12 +148,12 @@ export default Component.extend({
}
return true;
},
}
_removeValue(value) {
this.collection.removeObject(value);
this._saveValues();
},
}
_replaceValue(index, newValue) {
const item = this.collection[index];
@ -161,9 +162,9 @@ export default Component.extend({
}
set(item, "value", newValue);
this._saveValues();
},
}
_saveValues() {
this.set("values", this.collection.mapBy("value").join("|"));
},
});
}
}

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