Compare commits

..

No commits in common. "main" and "v3.0.0.beta15" have entirely different histories.

5271 changed files with 143504 additions and 202986 deletions

View File

@ -12,6 +12,3 @@ indent_size = 2
[*.md]
trim_trailing_whitespace = false
[*.hbs]
insert_final_newline = false

View File

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

View File

@ -58,16 +58,3 @@ bbe5d8d5cf1220165842985c0e2cd4c454d501cd
# DEV: Template colocation for sidebar files
95c7cdab941a56686ac5831d2a5c5eca38d780c5
# DEV: Apply prettier to hbs files
c8e2e37fa77d3c3c69c7572866017e9bb92befa3
# DEV: Apply syntax_tree to...
5a003715d366e1d871f9fcb0656dc9e23e9c2259
64171730827c58df26a7ad75f0e58f17c2add118
b0fda61a8e75c81e3458c8af9d2afe9d32183457
cb932d6ee1b3b3571e4d4d9118635e2dbf58f0ef
0cf6421716d0908da57ad7743a2decb08588b48a
7c77cc6a580d7cb49f8c19ceee8cfdd08862259d
436b3b392b9c917510d4ff0d73a5167cd3eb936c
055310cea496519a996b9c3bf4dc7e716cfe62ba

View File

@ -22,9 +22,6 @@ jobs:
timeout-minutes: 30
steps:
- name: Set working directory owner
run: chown root:root .
- uses: actions/checkout@v3
with:
fetch-depth: 1
@ -71,12 +68,6 @@ jobs:
if: ${{ !cancelled() }}
run: bundle exec rubocop --parallel .
- name: syntax_tree
if: ${{ !cancelled() }}
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
@ -92,10 +83,8 @@ jobs:
yarn pprettier --list-different \
"app/assets/stylesheets/**/*.scss" \
"app/assets/javascripts/**/*.js" \
"app/assets/javascripts/**/*.hbs" \
"plugins/**/assets/stylesheets/**/*.scss" \
"plugins/**/assets/javascripts/**/*.js" \
"plugins/**/assets/javascripts/**/*.hbs" \
"plugins/**/assets/javascripts/**/*.js"
- name: Ember template lint
if: ${{ !cancelled() }}

View File

@ -18,9 +18,9 @@ permissions:
jobs:
build:
if: "!(github.event_name == 'push' && github.repository == 'discourse/discourse-private-mirror')"
name: ${{ matrix.target }} ${{ matrix.build_type }} ${{ matrix.ruby }}
name: ${{ matrix.target }} ${{ matrix.build_type }}
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' || '' }}${{ (matrix.ruby == '3.1') && '-ruby-3.1.0' || '' }}
container: discourse/discourse_test:slim${{ (matrix.build_type == 'frontend' || matrix.build_type == 'system') && '-browsers' || '' }}
timeout-minutes: 20
env:
@ -29,8 +29,7 @@ jobs:
RAILS_ENV: test
PGUSER: discourse
PGPASSWORD: discourse
USES_PARALLEL_DATABASES: ${{ matrix.build_type == 'backend' || matrix.build_type == 'system' }}
CAPBYARA_DEFAULT_MAX_WAIT_TIME: 4
USES_PARALLEL_DATABASES: ${{ matrix.build_type == 'backend' }}
strategy:
fail-fast: false
@ -38,7 +37,6 @@ jobs:
matrix:
build_type: [backend, frontend, system, annotations]
target: [core, plugins]
ruby: ['3.2']
exclude:
- build_type: annotations
target: plugins
@ -46,9 +44,6 @@ jobs:
target: core # Handled by core_frontend_tests job (below)
steps:
- name: Set working directory owner
run: chown root:root .
- uses: actions/checkout@v3
with:
fetch-depth: 1
@ -72,9 +67,9 @@ jobs:
uses: actions/cache@v3
with:
path: vendor/bundle
key: ${{ runner.os }}-${{ matrix.ruby }}-gem-${{ hashFiles('**/Gemfile.lock') }}
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-${{ matrix.ruby }}-gem-
${{ runner.os }}-gem-
- name: Setup gems
run: |
@ -159,22 +154,6 @@ jobs:
path: tmp/turbo_rspec_runtime.log
key: rspec-runtime-backend-core
- name: Run Zeitwerk check
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: Core RSpec
if: matrix.build_type == 'backend' && matrix.target == 'core'
run: bin/turbo_rspec --verbose
@ -192,17 +171,13 @@ jobs:
if: matrix.build_type == 'system'
run: bin/ember-cli --build
- name: Setup Webdriver
if: matrix.build_type == 'system'
run: bin/rails runner "require 'webdrivers'; Webdrivers::Chromedriver.update"
- name: Core System Tests
if: matrix.build_type == 'system' && matrix.target == 'core'
run: bin/rspec spec/system
run: bin/rspec spec/system --format documentation --profile
- name: Plugin System Tests
if: matrix.build_type == 'system' && matrix.target == 'plugins'
run: LOAD_PLUGINS=1 bin/rspec plugins/*/spec/system
run: LOAD_PLUGINS=1 bin/rspec plugins/*/spec/system --format documentation --profile
- name: Upload failed system test screenshots
uses: actions/upload-artifact@v3

21
.jsdoc
View File

@ -1,21 +0,0 @@
// 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

@ -15,7 +15,6 @@ ignored:
bundler:
- cgi # Ruby (default gem)
- date # Ruby (default gem)
- digest # Ruby (default gem)
- io-wait # Ruby (default gem)
- json # Ruby (default gem)
- net-http # Ruby (default gem)

View File

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

View File

@ -3,7 +3,6 @@ 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,5 +1,5 @@
inherit_gem:
rubocop-discourse: stree-compat.yml
rubocop-discourse: default.yml
# Still work to do in ensuring we don't link old files
Discourse/NoAddReferenceOrAliasesActiveRecordMigration:
@ -7,7 +7,3 @@ Discourse/NoAddReferenceOrAliasesActiveRecordMigration:
Discourse/NoResetColumnInformationInMigrations:
Enabled: true
Lint/Debugger:
Exclude:
- script/**/*

View File

@ -1 +1 @@
3.2.1
3.1.3

View File

@ -1,2 +1,2 @@
--print-width=100
--plugins=plugin/trailing_comma,disable_ternary
--plugins=plugin/trailing_comma

View File

@ -15,7 +15,7 @@ module.exports = {
"directory-item-value",
"directory-table-header-title",
"loading-spinner",
"directory-item-label",
"mobile-directory-item-label",
],
},
"no-implicit-this": {

297
Gemfile
View File

@ -1,51 +1,51 @@
# frozen_string_literal: true
source "https://rubygems.org"
source 'https://rubygems.org'
# if there is a super emergency and rubygems is playing up, try
#source 'http://production.cf.rubygems.org'
gem "bootsnap", require: false, platform: :mri
gem 'bootsnap', require: false, platform: :mri
def rails_master?
ENV["RAILS_MASTER"] == "1"
ENV["RAILS_MASTER"] == '1'
end
if rails_master?
gem "arel", git: "https://github.com/rails/arel.git"
gem "rails", git: "https://github.com/rails/rails.git"
gem 'arel', git: 'https://github.com/rails/arel.git'
gem 'rails', git: 'https://github.com/rails/rails.git'
else
# NOTE: Until rubygems gives us optional dependencies we are stuck with this needing to be explicit
# this allows us to include the bits of rails we use without pieces we do not.
#
# To issue a rails update bump the version number here
rails_version = "7.0.4.3"
gem "actionmailer", rails_version
gem "actionpack", rails_version
gem "actionview", rails_version
gem "activemodel", rails_version
gem "activerecord", rails_version
gem "activesupport", rails_version
gem "railties", rails_version
gem "sprockets-rails"
rails_version = '7.0.3.1'
gem 'actionmailer', rails_version
gem 'actionpack', rails_version
gem 'actionview', rails_version
gem 'activemodel', rails_version
gem 'activerecord', rails_version
gem 'activesupport', rails_version
gem 'railties', rails_version
gem 'sprockets-rails'
end
gem "json"
gem 'json'
# TODO: At the moment Discourse does not work with Sprockets 4, we would need to correct internals
# We intend to drop sprockets rather than upgrade to 4.x
gem "sprockets", git: "https://github.com/rails/sprockets", branch: "3.x"
# This is a desired upgrade we should get to.
gem 'sprockets', '3.7.2'
# this will eventually be added to rails,
# allows us to precompile all our templates in the unicorn master
gem "actionview_precompiler", require: false
gem 'actionview_precompiler', require: false
gem "discourse-seed-fu"
gem 'discourse-seed-fu'
gem "mail", git: "https://github.com/discourse/mail.git"
gem "mini_mime"
gem "mini_suffix"
gem 'mail', git: 'https://github.com/discourse/mail.git'
gem 'mini_mime'
gem 'mini_suffix'
gem "redis"
gem 'redis'
# This is explicitly used by Sidekiq and is an optional dependency.
# We tell Sidekiq to use the namespace "sidekiq" which triggers this
@ -53,79 +53,79 @@ gem "redis"
# redis namespace support is optional
# We already namespace stuff in DiscourseRedis, so we should consider
# just using a single implementation in core vs having 2 namespace implementations
gem "redis-namespace"
gem 'redis-namespace'
# NOTE: AM serializer gets a lot slower with recent updates
# we used an old branch which is the fastest one out there
# are long term goal here is to fork this gem so we have a
# better maintained living fork
gem "active_model_serializers", "~> 0.8.3"
gem 'active_model_serializers', '~> 0.8.3'
gem "http_accept_language", require: false
gem 'http_accept_language', require: false
gem "discourse-fonts", require: "discourse_fonts"
gem 'discourse-fonts', require: 'discourse_fonts'
gem "message_bus"
gem 'message_bus'
gem "rails_multisite"
gem 'rails_multisite'
gem "fast_xs", platform: :ruby
gem 'fast_xs', platform: :ruby
gem "xorcist"
gem 'xorcist'
gem "fastimage"
gem 'fastimage'
gem "aws-sdk-s3", require: false
gem "aws-sdk-sns", require: false
gem "excon", require: false
gem "unf", require: false
gem 'aws-sdk-s3', require: false
gem 'aws-sdk-sns', require: false
gem 'excon', require: false
gem 'unf', require: false
gem "email_reply_trimmer"
gem 'email_reply_trimmer'
gem "image_optim"
gem "multi_json"
gem "mustache"
gem "nokogiri"
gem "loofah"
gem "css_parser", require: false
gem 'image_optim'
gem 'multi_json'
gem 'mustache'
gem 'nokogiri'
gem 'loofah'
gem 'css_parser', require: false
gem "omniauth"
gem "omniauth-facebook"
gem "omniauth-twitter"
gem "omniauth-github"
gem 'omniauth'
gem 'omniauth-facebook'
gem 'omniauth-twitter'
gem 'omniauth-github'
gem "omniauth-oauth2", require: false
gem 'omniauth-oauth2', require: false
gem "omniauth-google-oauth2"
gem 'omniauth-google-oauth2'
# pending: https://github.com/ohler55/oj/issues/789
gem "oj", "3.13.14"
gem 'oj', '3.13.14'
gem "pg"
gem "mini_sql"
gem "pry-rails", require: false
gem "pry-byebug", require: false
gem "rtlcss", require: false
gem "rake"
gem 'pg'
gem 'mini_sql'
gem 'pry-rails', require: false
gem 'pry-byebug', require: false
gem 'r2', require: false
gem 'rake'
gem "thor", require: false
gem "diffy", require: false
gem "rinku"
gem "sidekiq"
gem "mini_scheduler"
gem 'thor', require: false
gem 'diffy', require: false
gem 'rinku'
gem 'sidekiq'
gem 'mini_scheduler'
gem "execjs", require: false
gem "mini_racer"
gem 'execjs', require: false
gem 'mini_racer'
gem "highline", require: false
gem 'highline', require: false
gem "rack"
gem 'rack'
gem "rack-protection" # security
gem "cbor", require: false
gem "cose", require: false
gem "addressable"
gem "json_schemer"
gem 'rack-protection' # security
gem 'cbor', require: false
gem 'cose', require: false
gem 'addressable'
gem 'json_schemer'
gem "net-smtp", require: false
gem "net-imap", require: false
@ -135,149 +135,146 @@ gem "digest", require: false
# 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
group :assets do
gem "uglifier"
gem 'uglifier'
end
group :test do
gem "capybara", require: false
gem "webmock", require: false
gem "fakeweb", require: false
gem "minitest", require: false
gem "simplecov", require: false
gem "selenium-webdriver", require: false
gem 'capybara', require: false
gem 'webmock', require: false
gem 'fakeweb', require: false
gem 'minitest', require: false
gem 'simplecov', require: false
gem 'selenium-webdriver', require: false
gem "test-prof"
gem "webdrivers", require: false
gem 'webdrivers', require: false
end
group :test, :development do
gem "rspec"
gem "listen", require: false
gem "certified", require: false
gem "fabrication", require: false
gem "mocha", require: false
gem 'rspec'
gem 'listen', require: false
gem 'certified', require: false
gem 'fabrication', require: false
gem 'mocha', require: false
gem "rb-fsevent", require: RUBY_PLATFORM =~ /darwin/i ? "rb-fsevent" : false
gem 'rb-fsevent', require: RUBY_PLATFORM =~ /darwin/i ? 'rb-fsevent' : false
gem "rspec-rails"
gem 'rspec-rails'
gem "shoulda-matchers", require: false
gem "rspec-html-matchers"
gem "byebug", require: ENV["RM_INFO"].nil?, platform: :mri
gem "rubocop-discourse", require: false
gem "parallel_tests"
gem 'shoulda-matchers', require: false
gem 'rspec-html-matchers'
gem 'byebug', require: ENV['RM_INFO'].nil?, platform: :mri
gem 'rubocop-discourse', require: false
gem 'parallel_tests'
gem "rswag-specs"
gem 'rswag-specs'
gem "annotate"
gem "syntax_tree"
gem "syntax_tree-disable_ternary"
gem 'annotate'
end
group :development do
gem "ruby-prof", require: false, platform: :mri
gem "bullet", require: !!ENV["BULLET"]
gem "better_errors", platform: :mri, require: !!ENV["BETTER_ERRORS"]
gem "binding_of_caller"
gem "yaml-lint"
gem "yard"
gem 'ruby-prof', require: false, platform: :mri
gem 'bullet', require: !!ENV['BULLET']
gem 'better_errors', platform: :mri, require: !!ENV['BETTER_ERRORS']
gem 'binding_of_caller'
gem 'yaml-lint'
end
if ENV["ALLOW_DEV_POPULATE"] == "1"
gem "discourse_dev_assets"
gem "faker", "~> 2.16"
gem 'discourse_dev_assets'
gem 'faker', "~> 2.16"
else
group :development, :test do
gem "discourse_dev_assets"
gem "faker", "~> 2.16"
gem 'discourse_dev_assets'
gem 'faker', "~> 2.16"
end
end
# this is an optional gem, it provides a high performance replacement
# to String#blank? a method that is called quite frequently in current
# ActiveRecord, this may change in the future
gem "fast_blank", platform: :ruby
gem 'fast_blank', platform: :ruby
# this provides a very efficient lru cache
gem "lru_redux"
gem 'lru_redux'
gem "htmlentities", require: false
gem 'htmlentities', require: false
# IMPORTANT: mini profiler monkey patches, so it better be required last
# 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 "rack-mini-profiler", require: ["enable_rails_patches"]
gem 'rack-mini-profiler', require: ['enable_rails_patches']
gem "unicorn", require: false, platform: :ruby
gem "puma", require: false
gem "rbtrace", require: false, platform: :mri
gem "gc_tracer", require: false, platform: :mri
gem 'unicorn', require: false, platform: :ruby
gem 'puma', require: false
gem 'rbtrace', require: false, platform: :mri
gem 'gc_tracer', require: false, platform: :mri
# required for feed importing and embedding
gem "ruby-readability", require: false
gem 'ruby-readability', require: false
# rss gem is a bundled gem from Ruby 3 onwards
gem "rss", require: false
gem 'rss', require: false
gem "stackprof", require: false, platform: :mri
gem "memory_profiler", require: false, platform: :mri
gem 'stackprof', require: false, platform: :mri
gem 'memory_profiler', require: false, platform: :mri
gem "cppjieba_rb", require: false
gem 'cppjieba_rb', require: false
gem "lograge", require: false
gem "logstash-event", require: false
gem "logstash-logger", require: false
gem "logster"
gem 'lograge', require: false
gem 'logstash-event', require: false
gem 'logstash-logger', require: false
gem 'logster'
# These are forks of sassc and sassc-rails with dart-sass support
gem "dartsass-ruby"
gem "dartsass-sprockets"
# NOTE: later versions of sassc are causing a segfault, possibly dependent on processer architecture
# and until resolved should be locked at 2.0.1
gem 'sassc', '2.0.1', require: false
gem "sassc-rails"
gem "rotp", require: false
gem 'rotp', require: false
gem "rqrcode"
gem 'rqrcode'
gem "rubyzip", require: false
gem 'rubyzip', require: false
gem "sshkey", require: false
gem 'sshkey', require: false
gem "rchardet", require: false
gem "lz4-ruby", require: false, platform: :ruby
gem 'rchardet', require: false
gem 'lz4-ruby', require: false, platform: :ruby
gem "sanitize"
gem 'sanitize'
if ENV["IMPORT"] == "1"
gem "mysql2"
gem "redcarpet"
gem 'mysql2'
gem 'redcarpet'
# NOTE: in import mode the version of sqlite can matter a lot, so we stick it to a specific one
gem "sqlite3", "~> 1.3", ">= 1.3.13"
gem "ruby-bbcode-to-md", git: "https://github.com/nlalonde/ruby-bbcode-to-md"
gem "reverse_markdown"
gem "tiny_tds"
gem "csv"
gem 'sqlite3', '~> 1.3', '>= 1.3.13'
gem 'ruby-bbcode-to-md', git: 'https://github.com/nlalonde/ruby-bbcode-to-md'
gem 'reverse_markdown'
gem 'tiny_tds'
gem 'csv'
gem "parallel", require: false
gem 'parallel', require: false
end
gem "web-push"
gem "colored2", require: false
gem "maxminddb"
# workaround for openssl 3.0, see
# https://github.com/pushpad/web-push/pull/2
gem 'web-push', require: false, git: 'https://github.com/xfalcox/web-push', branch: 'openssl-3-compat'
gem 'colored2', require: false
gem 'maxminddb'
gem "rails_failover", require: false
gem 'rails_failover', require: false
gem "faraday"
gem "faraday-retry"
gem 'faraday'
gem 'faraday-retry'
# workaround for faraday-net_http, see
# https://github.com/ruby/net-imap/issues/16#issuecomment-803086765
gem "net-http"
gem 'net-http'
# workaround for prometheus-client
gem "webrick", require: false
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

@ -6,36 +6,37 @@ GIT
mini_mime (>= 0.1.1)
GIT
remote: https://github.com/rails/sprockets
revision: f4d3dae71ef29c44b75a49cfbf8032cce07b423a
branch: 3.x
remote: https://github.com/xfalcox/web-push
revision: 369df8f475a4cd4832a7679bec16576665f24d24
branch: openssl-3-compat
specs:
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
web-push (2.1.0)
hkdf (~> 1.0)
jwt (~> 2.0)
openssl (~> 3.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.3.1)
actionpack (= 7.0.3.1)
actionview (= 7.0.3.1)
activejob (= 7.0.3.1)
activesupport (= 7.0.3.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)
actionpack (7.0.3.1)
actionview (= 7.0.3.1)
activesupport (= 7.0.3.1)
rack (~> 2.0, >= 2.2.0)
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.3.1)
activesupport (= 7.0.3.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -44,15 +45,15 @@ 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.3.1)
activesupport (= 7.0.3.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.3.1)
activesupport (= 7.0.3.1)
activerecord (7.0.3.1)
activemodel (= 7.0.3.1)
activesupport (= 7.0.3.1)
activesupport (7.0.3.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@ -88,10 +89,10 @@ GEM
rack (>= 0.9.0)
binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
bootsnap (1.16.0)
bootsnap (1.15.0)
msgpack (~> 1.2)
builder (3.2.4)
bullet (7.0.7)
bullet (7.0.5)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
byebug (11.1.3)
@ -110,7 +111,7 @@ GEM
chunky_png (1.4.0)
coderay (1.1.3)
colored2 (3.1.2)
concurrent-ruby (1.2.2)
concurrent-ruby (1.1.10)
connection_pool (2.3.0)
cose (1.3.0)
cbor (~> 0.5.9)
@ -119,17 +120,8 @@ GEM
crack (0.4.5)
rexml
crass (1.0.6)
css_parser (1.14.0)
css_parser (1.13.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)
@ -145,19 +137,19 @@ GEM
ecma-re-validator (0.4.0)
regexp_parser (~> 2.2)
email_reply_trimmer (0.1.13)
erubi (1.12.0)
excon (0.99.0)
erubi (1.11.0)
excon (0.95.0)
execjs (2.8.1)
exifr (1.3.10)
fabrication (2.30.0)
faker (2.23.0)
i18n (>= 1.8.11, < 2)
fakeweb (1.3.0)
faraday (2.7.4)
faraday (2.7.2)
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday-net_http (3.0.2)
faraday-retry (2.1.0)
faraday-retry (2.0.0)
faraday (~> 2.0)
fast_blank (1.0.1)
fast_xs (0.8.0)
@ -165,13 +157,8 @@ GEM
ffi (1.15.5)
fspath (3.1.2)
gc_tracer (1.5.1)
globalid (1.1.0)
globalid (1.0.0)
activesupport (>= 5.0)
google-protobuf (3.22.2)
google-protobuf (3.22.2-aarch64-linux)
google-protobuf (3.22.2-arm64-darwin)
google-protobuf (3.22.2-x86_64-darwin)
google-protobuf (3.22.2-x86_64-linux)
guess_html_encoding (0.0.11)
hana (1.3.7)
hashdiff (1.0.1)
@ -182,7 +169,7 @@ GEM
http_accept_language (2.1.1)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
image_optim (0.31.3)
image_optim (0.31.2)
exifr (~> 1.2, >= 1.2.2)
fspath (~> 3.0)
image_size (>= 1.5, < 4)
@ -199,7 +186,7 @@ GEM
hana (~> 1.3)
regexp_parser (~> 2.0)
uri_template (~> 0.7)
jwt (2.7.0)
jwt (2.6.0)
kgio (2.11.4)
libv8-node (16.10.0.0)
libv8-node (16.10.0.0-aarch64-linux)
@ -207,7 +194,7 @@ GEM
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)
listen (3.8.0)
listen (3.7.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
literate_randomizer (0.4.0)
@ -219,7 +206,7 @@ GEM
logstash-event (1.2.02)
logstash-logger (0.26.1)
logstash-event (~> 1.2)
logster (2.12.2)
logster (2.11.3)
loofah (2.19.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
@ -228,7 +215,7 @@ GEM
matrix (0.4.2)
maxminddb (0.1.22)
memory_profiler (1.0.1)
message_bus (4.3.2)
message_bus (4.3.0)
rack (>= 1.1.3)
method_source (1.0.0)
mini_mime (1.1.2)
@ -240,17 +227,16 @@ GEM
mini_sql (1.4.0)
mini_suffix (0.3.3)
ffi (~> 1.9)
minitest (5.18.0)
minitest (5.17.0)
mocha (2.0.2)
ruby2_keywords (>= 0.0.5)
msgpack (1.6.1)
msgpack (1.6.0)
multi_json (1.15.0)
multi_xml (0.6.0)
mustache (1.1.1)
net-http (0.3.2)
uri
net-imap (0.3.4)
date
net-imap (0.3.1)
net-protocol
net-pop (0.1.2)
net-protocol
@ -259,16 +245,16 @@ GEM
net-smtp (0.3.3)
net-protocol
nio4r (2.5.8)
nokogiri (1.14.2)
nokogiri (1.13.10)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
nokogiri (1.14.2-aarch64-linux)
nokogiri (1.13.10-aarch64-linux)
racc (~> 1.4)
nokogiri (1.14.2-arm64-darwin)
nokogiri (1.13.10-arm64-darwin)
racc (~> 1.4)
nokogiri (1.14.2-x86_64-darwin)
nokogiri (1.13.10-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.14.2-x86_64-linux)
nokogiri (1.13.10-x86_64-linux)
racc (~> 1.4)
oauth (1.1.0)
oauth-tty (~> 1.0, >= 1.0.1)
@ -305,19 +291,18 @@ GEM
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
rack
openssl (3.1.0)
openssl-signature_algorithm (1.3.0)
openssl (> 2.0)
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.2.0)
parallel_tests (4.0.0)
parallel
parser (3.2.1.1)
parser (3.1.3.0)
ast (~> 2.4.1)
pg (1.4.6)
prettier_print (1.2.1)
pg (1.4.5)
progress (3.6.0)
pry (0.14.2)
pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
pry-byebug (3.10.1)
@ -326,20 +311,21 @@ GEM
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (5.0.1)
puma (6.1.1)
puma (6.0.2)
nio4r (~> 2.0)
racc (1.6.2)
rack (2.2.6.4)
r2 (0.2.7)
racc (1.6.1)
rack (2.2.5)
rack-mini-profiler (3.0.0)
rack (>= 1.2.0)
rack-protection (3.0.5)
rack
rack-test (2.1.0)
rack-test (2.0.2)
rack (>= 1.3)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.5.0)
rails-html-sanitizer (1.4.4)
loofah (~> 2.19, >= 2.19.1)
rails_failover (0.8.1)
activerecord (> 6.0, < 7.1)
@ -348,15 +334,15 @@ GEM
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 (7.0.3.1)
actionpack (= 7.0.3.1)
activesupport (= 7.0.3.1)
method_source
rake (>= 12.2)
thor (~> 1.0)
zeitwerk (~> 2.5)
rainbow (3.1.1)
raindrops (0.20.1)
raindrops (0.20.0)
rake (13.0.6)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
@ -366,10 +352,10 @@ GEM
msgpack (>= 0.4.3)
optimist (>= 3.0.0)
rchardet (1.8.0)
redis (4.8.1)
redis-namespace (1.10.0)
redis (4.8.0)
redis-namespace (1.9.0)
redis (>= 4)
regexp_parser (2.7.0)
regexp_parser (2.6.1)
request_store (1.5.1)
rack (>= 1.4)
rexml (3.2.5)
@ -383,15 +369,15 @@ GEM
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
rspec-mocks (~> 3.12.0)
rspec-core (3.12.1)
rspec-core (3.12.0)
rspec-support (~> 3.12.0)
rspec-expectations (3.12.2)
rspec-expectations (3.12.1)
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.4)
rspec-mocks (3.12.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-rails (6.0.1)
@ -410,50 +396,43 @@ GEM
json-schema (>= 2.2, < 4.0)
railties (>= 3.1, < 7.1)
rspec-core (>= 2.14)
rtlcss (0.2.0)
mini_racer (~> 0.6.3)
rubocop (1.48.1)
rubocop (1.42.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.2.0.0)
parser (>= 3.1.2.1)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.26.0, < 2.0)
rubocop-ast (>= 1.24.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.27.0)
parser (>= 3.2.1.0)
rubocop-capybara (2.17.1)
rubocop (~> 1.41)
rubocop-discourse (3.2.0)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.24.1)
parser (>= 3.1.1.0)
rubocop-discourse (3.0.1)
rubocop (>= 1.1.0)
rubocop-rspec (>= 2.0.0)
rubocop-rspec (2.19.0)
rubocop-rspec (2.16.0)
rubocop (~> 1.33)
rubocop-capybara (~> 2.17)
ruby-prof (1.6.1)
ruby-progressbar (1.13.0)
ruby-prof (1.4.5)
ruby-progressbar (1.11.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.1)
sanitize (6.0.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
sass-embedded (1.59.2)
google-protobuf (~> 3.21)
rake (>= 10.0.0)
sass-embedded (1.59.2-aarch64-linux-gnu)
google-protobuf (~> 3.21)
sass-embedded (1.59.2-arm64-darwin)
google-protobuf (~> 3.21)
sass-embedded (1.59.2-x86_64-darwin)
google-protobuf (~> 3.21)
sass-embedded (1.59.2-x86_64-linux-gnu)
google-protobuf (~> 3.21)
selenium-webdriver (4.8.1)
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)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
@ -463,7 +442,7 @@ GEM
connection_pool (>= 2.2.5, < 3)
rack (~> 2.0)
redis (>= 4.5.0, < 5)
simplecov (0.22.0)
simplecov (0.21.2)
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
@ -472,29 +451,27 @@ GEM
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 (6.0.2)
prettier_print (>= 1.2.0)
syntax_tree-disable_ternary (1.0.0)
test-prof (1.2.0)
test-prof (1.1.0)
thor (1.2.1)
tilt (2.1.0)
timeout (0.3.2)
tzinfo (2.0.6)
tilt (2.0.11)
timeout (0.3.1)
tzinfo (2.0.5)
concurrent-ruby (~> 1.0)
tzinfo-data (1.2022.7)
tzinfo (>= 1.0.0)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (2.4.2)
unicode-display_width (2.3.0)
unicorn (6.1.0)
kgio (~> 2.6)
raindrops (~> 0.7)
@ -502,10 +479,6 @@ GEM
uri (0.12.0)
uri_template (0.7.0)
version_gem (1.1.1)
web-push (3.0.0)
hkdf (~> 1.0)
jwt (~> 2.0)
openssl (~> 3.0)
webdrivers (5.2.0)
nokogiri (~> 1.6)
rubyzip (>= 1.3.0)
@ -519,10 +492,8 @@ GEM
xorcist (1.1.3)
xpath (3.2.0)
nokogiri (~> 1.8)
yaml-lint (0.1.2)
yard (0.9.28)
webrick (~> 1.7.0)
zeitwerk (2.6.7)
yaml-lint (0.0.10)
zeitwerk (2.6.6)
PLATFORMS
aarch64-linux
@ -534,14 +505,14 @@ PLATFORMS
x86_64-linux
DEPENDENCIES
actionmailer (= 7.0.4.3)
actionpack (= 7.0.4.3)
actionview (= 7.0.4.3)
actionmailer (= 7.0.3.1)
actionpack (= 7.0.3.1)
actionview (= 7.0.3.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.3.1)
activerecord (= 7.0.3.1)
activesupport (= 7.0.3.1)
addressable
annotate
aws-sdk-s3
@ -559,8 +530,6 @@ DEPENDENCIES
cose
cppjieba_rb
css_parser
dartsass-ruby
dartsass-sprockets
diffy
digest
discourse-fonts
@ -622,12 +591,13 @@ DEPENDENCIES
pry-byebug
pry-rails
puma
r2
rack
rack-mini-profiler
rack-protection
rails_failover
rails_multisite
railties (= 7.0.4.3)
railties (= 7.0.3.1)
rake
rb-fsevent
rbtrace
@ -642,35 +612,32 @@ 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
sidekiq
simplecov
sprockets!
sprockets (= 3.7.2)
sprockets-rails
sshkey
stackprof
syntax_tree
syntax_tree-disable_ternary
test-prof
thor
tzinfo-data
uglifier
unf
unicorn
web-push
web-push!
webdrivers
webmock
webrick
xorcist
yaml-lint
yard
BUNDLED WITH
2.4.4
2.4.1

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 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!
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!
## 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 15.7+.
Additionally, we aim to support Safari on iOS 12.5+ until January 2023 (Discourse 3.0).
## Built With
@ -91,7 +91,7 @@ The original Discourse code contributors can be found in [**AUTHORS.MD**](docs/A
## Copyright / License
Copyright 2014 - 2023 Civilized Discourse Construction Kit, Inc.
Copyright 2014 - 2022 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

@ -1,13 +1,13 @@
import RestAdapter from "discourse/adapters/rest";
import RESTAdapter from "discourse/adapters/rest";
export default class ApiKey extends RestAdapter {
jsonMode = true;
export default RESTAdapter.extend({
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 class extends RestAdapter {
return RestAdapter.extend({
pathFor(store, type, findArgs) {
return (
"/admin/plugins/" + pluginName + super.pathFor(store, type, findArgs)
"/admin/plugins/" + pluginName + this._super(store, type, findArgs)
);
}
};
},
});
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,9 @@
import RestAdapter from "discourse/adapters/rest";
export default class Theme extends RestAdapter {
jsonMode = true;
export default RestAdapter.extend({
basePath() {
return "/admin/";
}
},
afterFindAll(results) {
let map = {};
@ -21,5 +20,7 @@ export default class Theme extends RestAdapter {
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 class WebHookEvent extends RestAdapter {
export default RESTAdapter.extend({
basePath() {
return "/admin/api/";
}
}
},
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,4 @@
import { tagName } from "@ember-decorators/component";
import Component from "@ember/component";
@tagName("")
export default class AdminNav extends 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";
@classNames("penalty-history")
export default class AdminPenaltyHistory extends Component {
export default Component.extend({
classNames: ["penalty-history"],
@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 class AdminPenaltyHistory extends Component {
return "danger";
}
return "";
}
}
},
});

View File

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

View File

@ -1,46 +1,43 @@
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";
@tagName("")
export default class AdminPenaltyReason extends Component {
selectedReason = CUSTOM_REASON_KEY;
customReason = "";
reasonKeys = [
export default Component.extend({
tagName: "",
selectedReason: CUSTOM_REASON_KEY,
customReason: "",
reasonKeys: [
"not_listening_to_staff",
"consuming_staff_time",
"combative",
"in_wrong_place",
"no_constructive_purpose",
CUSTOM_REASON_KEY,
];
@equal("selectedReason", CUSTOM_REASON_KEY) isCustomReason;
],
isCustomReason: equal("selectedReason", CUSTOM_REASON_KEY),
@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) {
@ -51,5 +48,5 @@ export default class AdminPenaltyReason extends Component {
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";
@tagName("")
export default class AdminPenaltySimilarUsers extends Component {
export default Component.extend({
tagName: "",
@discourseComputed("penaltyType")
penaltyField(penaltyType) {
if (penaltyType === "suspend") {
@ -12,7 +12,7 @@ export default class AdminPenaltySimilarUsers extends Component {
} else if (penaltyType === "silence") {
return "can_be_silenced";
}
}
},
@action
selectUserId(userId, event) {
@ -25,5 +25,5 @@ export default class AdminPenaltySimilarUsers extends Component {
} else {
this.selectedUserIds.removeObject(userId);
}
}
}
},
});

View File

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

View File

@ -1,12 +1,11 @@
import { classNameBindings, tagName } from "@ember-decorators/component";
import { match } from "@ember/object/computed";
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;
}
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"],
});

View File

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

View File

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

View File

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

View File

@ -1,45 +1,43 @@
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";
@classNames("admin-report-storage-stats")
export default class AdminReportStorageStats extends Component {
@setting("backup_location") backupLocation;
export default Component.extend({
classNames: ["admin-report-storage-stats"],
@alias("model.data.backups") backupStats;
@alias("model.data.uploads") uploadStats;
backupLocation: setting("backup_location"),
backupStats: alias("model.data.backups"),
uploadStats: alias("model.data.uploads"),
@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,27 +1,21 @@
import {
attributeBindings,
classNameBindings,
classNames,
tagName,
} from "@ember-decorators/component";
import { alias } from "@ember/object/computed";
import Component from "@ember/component";
import { alias } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators";
@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;
export default Component.extend({
tagName: "td",
classNames: ["admin-report-table-cell"],
classNameBindings: ["type", "property"],
attributeBindings: ["value:title"],
options: null,
@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,24 +1,19 @@
import {
attributeBindings,
classNameBindings,
classNames,
tagName,
} from "@ember-decorators/component";
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
@tagName("th")
@classNames("admin-report-table-header")
@classNameBindings("label.mainProperty", "label.type", "isCurrentSort")
@attributeBindings("label.title:title")
export default class AdminReportTableHeader extends Component {
export default Component.extend({
tagName: "th",
classNames: ["admin-report-table-header"],
classNameBindings: ["label.mainProperty", "label.type", "isCurrentSort"],
attributeBindings: ["label.title:title"],
@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,8 +1,6 @@
import { classNames, tagName } from "@ember-decorators/component";
import Component from "@ember/component";
@tagName("tr")
@classNames("admin-report-table-row")
export default class AdminReportTableRow extends Component {
options = null;
}
export default Component.extend({
tagName: "tr",
classNames: ["admin-report-table-row"],
options: null,
});

View File

@ -1,26 +1,22 @@
import { action } from "@ember/object";
import { classNameBindings, classNames } from "@ember-decorators/component";
import { alias } from "@ember/object/computed";
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";
const PAGES_LIMIT = 8;
@classNameBindings("sortable", "twoColumns")
@classNames("admin-report-table")
export default class AdminReportTable extends Component {
sortable = false;
sortDirection = 1;
@alias("options.perPage") perPage;
page = 0;
export default Component.extend({
classNameBindings: ["sortable", "twoColumns"],
classNames: ["admin-report-table"],
sortable: false,
sortDirection: 1,
perPage: alias("options.perPage"),
page: 0,
@discourseComputed("model.computedLabels.length")
twoColumns(labelsLength) {
return labelsLength === 2;
}
},
@discourseComputed(
"totalsForSample",
@ -35,12 +31,12 @@ export default class AdminReportTable extends Component {
.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}",
@ -54,17 +50,17 @@ export default class AdminReportTable extends Component {
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) {
@ -74,7 +70,7 @@ export default class AdminReportTable extends Component {
computedLabel.property = label.mainProperty;
return computedLabel;
});
}
},
@discourseComputed("model.data", "model.computedLabels")
totalsForSampleRow(rows, labels) {
@ -102,7 +98,7 @@ export default class AdminReportTable extends Component {
});
return totalsRow;
}
},
@discourseComputed("sortLabel", "sortDirection", "model.data.[]")
sortedData(sortLabel, sortDirection, data) {
@ -122,7 +118,7 @@ export default class AdminReportTable extends Component {
}
return data;
}
},
@discourseComputed("sortedData.[]", "perPage", "page")
paginatedData(data, perPage, page) {
@ -132,7 +128,7 @@ export default class AdminReportTable extends Component {
}
return data;
}
},
@discourseComputed("model.data", "perPage", "page")
pages(data, perPage, page) {
@ -160,19 +156,19 @@ export default class AdminReportTable extends Component {
}
return pages;
}
},
@action
changePage(page) {
this.set("page", page);
}
actions: {
changePage(page) {
this.set("page", page);
},
@action
sortByLabel(label) {
if (this.sortLabel === label) {
this.set("sortDirection", this.sortDirection === 1 ? -1 : 1);
} else {
this.set("sortLabel", label);
}
}
}
sortByLabel(label) {
if (this.sortLabel === label) {
this.set("sortDirection", this.sortDirection === 1 ? -1 : 1);
} else {
this.set("sortLabel", label);
}
},
},
});

View File

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

View File

@ -1,7 +1,6 @@
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";
@ -22,58 +21,51 @@ const TABLE_OPTIONS = {
const CHART_OPTIONS = {};
@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;
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,
@alias("model.dates_filtering") showDatesOptions;
init() {
this._super(...arguments);
@or("showDatesOptions", "model.available_filters.length") showRefresh;
this._reports = [];
},
@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() {
isHidden: computed("siteSettings.dashboard_hidden_reports", function () {
return (this.siteSettings.dashboard_hidden_reports || "")
.split("|")
.filter(Boolean)
.includes(this.dataSourceName);
}
}),
didReceiveAttrs() {
super.didReceiveAttrs(...arguments);
this._super(...arguments);
let startDate = moment();
if (this.filters && isPresent(this.filters.startDate)) {
@ -96,35 +88,42 @@ export default class AdminReport extends Component {
} 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) {
@ -140,12 +139,12 @@ export default class AdminReport extends Component {
icon: mode === "table" ? "table" : "signal",
};
});
}
},
@discourseComputed("currentMode")
modeComponent(currentMode) {
return `admin-report-${currentMode.replace(/_/g, "-")}`;
}
},
@discourseComputed(
"dataSourceName",
@ -179,7 +178,7 @@ export default class AdminReport extends Component {
.join(":");
return reportKey;
}
},
@discourseComputed("options.chartGrouping", "model.chartData.length")
chartGroupings(grouping, count) {
@ -193,7 +192,7 @@ export default class AdminReport extends Component {
class: `chart-grouping ${grouping === id ? "active" : "inactive"}`,
};
});
}
},
@action
onChangeDateRange(range) {
@ -201,7 +200,7 @@ export default class AdminReport extends Component {
startDate: range.from,
endDate: range.to,
});
}
},
@action
applyFilter(id, value) {
@ -216,7 +215,7 @@ export default class AdminReport extends Component {
this.send("refreshReport", {
filters: customFilters,
});
}
},
@action
refreshReport(options = {}) {
@ -239,7 +238,7 @@ export default class AdminReport extends Component {
? this.get("filters.customFilters")
: options.filters,
});
}
},
@action
exportCsv() {
@ -255,7 +254,7 @@ export default class AdminReport extends Component {
}
exportEntity("report", args).then(outputExportResult);
}
},
@action
onChangeMode(mode) {
@ -264,7 +263,7 @@ export default class AdminReport extends Component {
this.send("refreshReport", {
chartGrouping: null,
});
}
},
_computeReport() {
if (!this.element || this.isDestroying || this.isDestroyed) {
@ -307,7 +306,7 @@ export default class AdminReport extends Component {
}
this._renderReport(report, this.forcedModes, this.currentMode);
}
},
_renderReport(report, forcedModes, currentMode) {
const modes = forcedModes ? forcedModes.split(",") : report.modes;
@ -318,9 +317,11 @@ export default class AdminReport extends Component {
currentMode,
options: this._buildOptions(currentMode, report),
});
}
},
_fetchReport() {
this._super(...arguments);
this.setProperties({ isLoading: true, rateLimitationString: null });
next(() => {
@ -348,7 +349,7 @@ export default class AdminReport extends Component {
ReportLoader.enqueue(this.dataSourceName, payload.data, callback);
});
}
},
_buildPayload(facets) {
let payload = { data: { facets } };
@ -374,7 +375,7 @@ export default class AdminReport extends Component {
}
return payload;
}
},
_buildOptions(mode, report) {
if (mode === "table") {
@ -392,7 +393,7 @@ export default class AdminReport extends Component {
})
);
}
}
},
_loadReport(jsonReport) {
Report.fillMissingDates(jsonReport, { filledField: "chartData" });
@ -422,5 +423,5 @@ export default class AdminReport extends Component {
}
return Report.create(jsonReport);
}
}
},
});

View File

@ -1,130 +0,0 @@
<div class="edit-main-nav admin-controls">
<nav>
<ul class="nav nav-pills target">
{{#each this.visibleTargets as |target|}}
<li>
<LinkTo
@route={{this.editRouteName}}
@models={{array this.theme.id target.name this.fieldName}}
@replace={{true}}
title={{this.field.title}}
class={{if target.edited "edited" "blank"}}
>
{{#if target.error}}{{d-icon "exclamation-triangle"}}{{/if}}
{{#if target.icon}}{{d-icon target.icon}}{{/if}}
{{i18n (concat "admin.customize.theme." target.name)}}
</LinkTo>
</li>
{{/each}}
{{#if this.allowAdvanced}}
<li>
<a
{{on "click" this.toggleShowAdvanced}}
href
title={{i18n
(concat
"admin.customize.theme."
(if this.showAdvanced "hide_advanced" "show_advanced")
)
}}
class="no-text"
>
{{d-icon
(if this.showAdvanced "angle-double-left" "angle-double-right")
}}
</a>
</li>
{{/if}}
<li class="spacer"></li>
<li>
<label>
<Input
@type="checkbox"
@checked={{this.onlyOverridden}}
{{on
"click"
(action this.onlyOverriddenChanged value="target.checked")
}}
/>
{{i18n "admin.customize.theme.hide_unused_fields"}}
</label>
</li>
</ul>
</nav>
</div>
<div class="admin-controls">
<nav>
<ul class="nav nav-pills fields">
{{#each this.visibleFields as |field|}}
<li>
<LinkTo
@route={{this.editRouteName}}
@models={{array this.theme.id this.currentTargetName field.name}}
@replace={{true}}
title={{field.title}}
class={{if field.edited "edited" "blank"}}
>
{{#if field.error}}{{d-icon "exclamation-triangle"}}{{/if}}
{{#if field.icon}}{{d-icon field.icon}}{{/if}}
{{field.translatedName}}
</LinkTo>
</li>
{{/each}}
{{#if this.showAddField}}
<li>
{{#if this.addingField}}
<Input
@type={{this.text}}
@value={{this.newFieldName}}
@enter={{action "addField"}}
@escape-press={{action "cancelAddField"}}
/>
<DButton
@class="ok"
@action={{action "addField" this.newFieldName}}
@icon="check"
/>
<DButton
@class="cancel"
@action={{action "cancelAddField"}}
@icon="times"
/>
{{else}}
<a href {{on "click" this.toggleAddField}} class="no-text">
{{d-icon "plus"}}
</a>
{{/if}}
</li>
{{/if}}
<li class="spacer"></li>
<li>
<a href {{on "click" this.toggleMaximize}} class="no-text">
{{d-icon this.maximizeIcon}}
</a>
</li>
</ul>
</nav>
</div>
{{#if this.error}}
<pre class="field-error">{{this.error}}</pre>
{{/if}}
{{#if this.warning}}
<pre class="field-warning">{{html-safe this.warning}}</pre>
{{/if}}
<AceEditor
@content={{this.activeSection}}
@editorId={{this.editorId}}
@mode={{this.activeSectionMode}}
@autofocus="true"
@placeholder={{this.placeholder}}
@htmlPlaceholder={{true}}
@save={{this.save}}
@setWarning={{action "setWarning"}}
/>

View File

@ -3,13 +3,11 @@ 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, computed } from "@ember/object";
import { action } from "@ember/object";
import { next } from "@ember/runloop";
export default class AdminThemeEditor extends Component {
warning = null;
@fmt("fieldName", "currentTargetName", "%@|%@") editorId;
export default Component.extend({
warning: null,
@discourseComputed("theme.targets", "onlyOverridden", "showAdvanced")
visibleTargets(targets, onlyOverridden, showAdvanced) {
@ -22,7 +20,7 @@ export default class AdminThemeEditor extends Component {
}
return target.edited;
});
}
},
@discourseComputed("currentTargetName", "onlyOverridden", "theme.fields")
visibleFields(targetName, onlyOverridden, fields) {
@ -31,7 +29,7 @@ export default class AdminThemeEditor extends Component {
fields = fields.filter((field) => field.edited);
}
return fields;
}
},
@discourseComputed("currentTargetName", "fieldName")
activeSectionMode(targetName, fieldName) {
@ -45,7 +43,7 @@ export default class AdminThemeEditor extends Component {
return "scss";
}
return fieldName && fieldName.includes("scss") ? "scss" : "html";
}
},
@discourseComputed("currentTargetName", "fieldName")
placeholder(targetName, fieldName) {
@ -60,27 +58,30 @@ export default class AdminThemeEditor extends Component {
});
}
return "";
}
},
@computed("fieldName", "currentTargetName", "theme")
get activeSection() {
return this.theme.getField(this.currentTargetName, this.fieldName);
}
@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;
},
},
set activeSection(value) {
this.theme.setField(this.currentTargetName, this.fieldName, value);
return value;
}
editorId: fmt("fieldName", "currentTargetName", "%@|%@"),
@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",
@ -89,45 +90,52 @@ export default class AdminThemeEditor extends Component {
)
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"));
}
},
@action
cancelAddField() {
this.set("addingField", false);
}
actions: {
cancelAddField() {
this.set("addingField", false);
},
@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);
}
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
setWarning(message) {
this.set("warning", message);
}
}
onlyOverriddenChanged(value) {
this.onlyOverriddenChanged(value);
},
save() {
this.attrs.save();
},
setWarning(message) {
this.set("warning", message);
},
},
});

View File

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

View File

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

View File

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

View File

@ -1,7 +1,6 @@
import { classNames } from "@ember-decorators/component";
import { action, computed } from "@ember/object";
import Component from "@ember/component";
import { observes } from "@ember-decorators/object";
import { observes } from "discourse-common/utils/decorators";
/**
An input field for a color.
@ -10,20 +9,20 @@ import { observes } from "@ember-decorators/object";
@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.
**/
@classNames("color-picker")
export default class ColorInput extends Component {
onlyHex = true;
styleSelection = true;
export default Component.extend({
classNames: ["color-picker"],
@computed("onlyHex")
get maxlength() {
onlyHex: true,
styleSelection: true,
maxlength: computed("onlyHex", function () {
return this.onlyHex ? 6 : null;
}
}),
@computed("hexValue")
get normalizedHexValue() {
normalizedHexValue: computed("hexValue", function () {
return this.normalize(this.hexValue);
}
}),
normalize(color) {
if (this._valid(color)) {
@ -41,19 +40,19 @@ export default class ColorInput extends Component {
}
}
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() {
@ -66,9 +65,9 @@ export default class ColorInput extends Component {
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 class DashboardNewFeatureItem extends Component {}
export default Component.extend({});

View File

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

View File

@ -1,44 +0,0 @@
<div class="row">
<div class="admin-controls">
<nav>
<ul class="nav nav-pills">
<li>
<LinkTo
@route="adminCustomizeEmailStyle.edit"
@model="html"
@replace={{true}}
>
{{i18n "admin.customize.email_style.html"}}
</LinkTo>
</li>
<li>
<LinkTo
@route="adminCustomizeEmailStyle.edit"
@model="css"
@replace={{true}}
>
{{i18n "admin.customize.email_style.css"}}
</LinkTo>
</li>
</ul>
</nav>
</div>
</div>
<AceEditor
@content={{this.editorContents}}
@mode={{this.currentEditorMode}}
@editorId={{this.editorId}}
@save={{@save}}
/>
<div class="admin-footer">
<div class="buttons">
<DButton
@action={{action "reset"}}
@disabled={{this.resetDisabled}}
@class="btn-default"
@label="admin.customize.email_style.reset"
/>
</div>
</div>

View File

@ -1,19 +1,17 @@
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 class EmailStylesEditor extends Component {
@service dialog;
@reads("fieldName") editorId;
export default Component.extend({
dialog: service(),
editorId: reads("fieldName"),
@discourseComputed("fieldName")
currentEditorMode(fieldName) {
return fieldName === "css" ? "scss" : fieldName;
}
},
@discourseComputed("fieldName", "styles.html", "styles.css")
resetDisabled(fieldName) {
@ -21,31 +19,36 @@ export default class EmailStylesEditor extends Component {
this.get(`styles.${fieldName}`) ===
this.get(`styles.default_${fieldName}`)
);
}
},
@computed("styles", "fieldName")
get editorContents() {
return this.styles[this.fieldName];
}
@discourseComputed("styles", "fieldName")
editorContents: {
get(styles, fieldName) {
return styles[fieldName];
},
set(value, styles, fieldName) {
styles.setField(fieldName, value);
return value;
},
},
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");
},
});
}
}
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();
},
},
});

View File

@ -1,66 +0,0 @@
{{#if this.editing}}
<td class="editing-input">
<div class="label">{{i18n "admin.embedding.host"}}</div>
<Input
@value={{this.buffered.host}}
placeholder="example.com"
@enter={{action "save"}}
class="host-name"
autofocus={{true}}
/>
</td>
<td class="editing-input">
<div class="label">{{i18n "admin.embedding.allowed_paths"}}</div>
<Input
@value={{this.buffered.allowed_paths}}
placeholder="/blog/.*"
@enter={{action "save"}}
class="path-allowlist"
/>
</td>
<td class="editing-input">
<div class="label">{{i18n "admin.embedding.category"}}</div>
<CategoryChooser
@value={{this.categoryId}}
@class="small"
@onChange={{action (mut this.categoryId)}}
/>
</td>
<td class="editing-controls">
<DButton
@icon="check"
@action={{action "save"}}
@class="btn-primary"
@disabled={{this.cantSave}}
/>
<DButton
@icon="times"
@action={{action "cancel"}}
@class="btn-danger"
@disabled={{this.host.isSaving}}
/>
</td>
{{else}}
<td>
<div class="label">{{i18n "admin.embedding.host"}}</div>
{{this.host.host}}
</td>
<td>
<div class="label">
{{i18n "admin.embedding.allowed_paths"}}
</div>
{{this.host.allowed_paths}}
</td>
<td>
<div class="label">{{i18n "admin.embedding.category"}}</div>
{{category-badge this.host.category allowUncategorized=true}}
</td>
<td class="controls">
<DButton @icon="pencil-alt" @action={{action "edit"}} />
<DButton
@icon="far-trash-alt"
@action={{action "delete"}}
@class="btn-danger"
/>
</td>
{{/if}}

View File

@ -1,91 +1,85 @@
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";
@tagName("tr")
export default class EmbeddableHost extends Component.extend(
bufferedProperty("host")
) {
@service dialog;
editToggled = false;
categoryId = null;
category = null;
export default Component.extend(bufferedProperty("host"), {
editToggled: false,
tagName: "tr",
categoryId: null,
category: null,
dialog: service(),
@or("host.isNew", "editToggled") editing;
editing: or("host.isNew", "editToggled"),
init() {
super.init(...arguments);
this._super(...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);
}
},
@action
edit() {
this.set("categoryId", this.get("host.category.id"));
this.set("editToggled", true);
}
actions: {
edit() {
this.set("categoryId", this.get("host.category.id"));
this.set("editToggled", true);
},
@action
save() {
if (this.cantSave) {
return;
}
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));
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();
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";
@classNames("embed-setting")
export default class EmbeddingSetting extends Component {
export default Component.extend({
classNames: ["embed-setting"],
@discourseComputed("field")
inputId(field) {
return dasherize(field);
}
},
@discourseComputed("field")
translationKey(field) {
return `admin.embedding.${field}`;
}
},
@discourseComputed("type")
isCheckbox(type) {
return type === "checkbox";
}
},
@computed("value")
get checked() {
return !!this.value;
}
set(value) {
this.set("value", value);
return value;
}
}
@discourseComputed("value")
checked: {
get(value) {
return !!value;
},
set(value) {
this.set("value", value);
return value;
},
},
});

View File

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

View File

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

View File

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

View File

@ -1,71 +0,0 @@
<div class="form-templates__form">
<div class="control-group">
<label for="template-name">
{{i18n "admin.form_templates.new_template_form.name.label"}}
</label>
<TextField
@value={{this.templateName}}
@name="template-name"
@class="form-templates__form-name-input"
@placeholderKey="admin.form_templates.new_template_form.name.placeholder"
/>
</div>
<div class="control-group form-templates__editor">
<div class="form-templates__quick-insert-field-buttons">
<span>
{{I18n "admin.form_templates.quick_insert_fields.add_new_field"}}
</span>
{{#each this.quickInsertFields as |field|}}
<DButton
@class="btn-flat btn-icon-text quick-insert-{{field.type}}"
@icon={{field.icon}}
@label="admin.form_templates.quick_insert_fields.{{field.type}}"
@action={{this.onInsertField}}
@actionParam={{field.type}}
/>
{{/each}}
<DButton
class="btn-flat btn-icon-text form-templates__validations-modal-button"
@label="admin.form_templates.validations_modal.button_title"
@icon="check-circle"
@action={{this.showValidationOptionsModal}}
/>
</div>
<DButton
@class="form-templates__preview-button"
@icon="eye"
@label="admin.form_templates.new_template_form.preview"
@action={{this.showPreview}}
@disabled={{this.disablePreviewButton}}
/>
</div>
<div class="control-group">
<AceEditor @content={{this.templateContent}} @mode="yaml" />
</div>
<div class="footer-buttons">
<DButton
@class="btn-primary"
@label="admin.form_templates.new_template_form.submit"
@icon="check"
@action={{this.onSubmit}}
@disabled={{this.disableSubmitButton}}
/>
<DButton
@label="admin.form_templates.new_template_form.cancel"
@icon="times"
@action={{this.onCancel}}
/>
{{#if this.isEditing}}
<DButton
@class="btn-danger"
@label="admin.form_templates.view_template.delete"
@icon="trash-alt"
@action={{this.onDelete}}
/>
{{/if}}
</div>
</div>

View File

@ -1,140 +0,0 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { tracked } from "@glimmer/tracking";
import I18n from "I18n";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { templateFormFields } from "admin/lib/template-form-fields";
import FormTemplate from "admin/models/form-template";
import showModal from "discourse/lib/show-modal";
export default class FormTemplateForm extends Component {
@service router;
@service dialog;
@tracked formSubmitted = false;
@tracked templateContent = this.args.model?.template || "";
@tracked templateName = this.args.model?.name || "";
isEditing = this.args.model?.id ? true : false;
quickInsertFields = [
{
type: "checkbox",
icon: "check-square",
},
{
type: "input",
icon: "grip-lines",
},
{
type: "textarea",
icon: "align-left",
},
{
type: "dropdown",
icon: "chevron-circle-down",
},
{
type: "upload",
icon: "cloud-upload-alt",
},
{
type: "multiselect",
icon: "bullseye",
},
];
get disablePreviewButton() {
return Boolean(!this.templateName.length || !this.templateContent.length);
}
get disableSubmitButton() {
return (
Boolean(!this.templateName.length || !this.templateContent.length) ||
this.formSubmitted
);
}
@action
onSubmit() {
if (!this.formSubmitted) {
this.formSubmitted = true;
}
const postData = {
name: this.templateName,
template: this.templateContent,
};
if (this.isEditing) {
postData["id"] = this.args.model.id;
}
FormTemplate.createOrUpdateTemplate(postData)
.then(() => {
this.formSubmitted = false;
this.router.transitionTo("adminCustomizeFormTemplates.index");
})
.catch((e) => {
popupAjaxError(e);
this.formSubmitted = false;
});
}
@action
onCancel() {
this.router.transitionTo("adminCustomizeFormTemplates.index");
}
@action
onDelete() {
return this.dialog.yesNoConfirm({
message: I18n.t("admin.form_templates.delete_confirm"),
didConfirm: () => {
FormTemplate.deleteTemplate(this.args.model.id)
.then(() => {
this.router.transitionTo("adminCustomizeFormTemplates.index");
})
.catch(popupAjaxError);
},
});
}
@action
onInsertField(type) {
const structure = templateFormFields.findBy("type", type).structure;
if (this.templateContent.length === 0) {
this.templateContent += structure;
} else {
this.templateContent += `\n${structure}`;
}
}
@action
showValidationOptionsModal() {
return showModal("admin-form-template-validation-options", {
admin: true,
});
}
@action
showPreview() {
const data = {
name: this.templateName,
template: this.templateContent,
};
if (this.isEditing) {
data["id"] = this.args.model.id;
}
FormTemplate.validateTemplate(data)
.then(() => {
return showModal("form-template-form-preview", {
model: {
content: this.templateContent,
},
});
})
.catch(popupAjaxError);
}
}

View File

@ -1,4 +0,0 @@
<div class="form-templates--info">
<h2>{{i18n "admin.form_templates.title"}}</h2>
<p class="desc">{{i18n "admin.form_templates.help"}}</p>
</div>

View File

@ -1,28 +0,0 @@
<tr class="admin-list-item">
<td class="col first">{{@template.name}}</td>
<td class="col categories">
{{#each this.activeCategories as |category|}}
{{category-link category}}
{{/each}}
</td>
<td class="col action">
<DButton
@title="admin.form_templates.list_table.actions.view"
@icon="far-eye"
@class="btn-view-template"
@action={{this.viewTemplate}}
/>
<DButton
@title="admin.form_templates.list_table.actions.edit"
@icon="pencil-alt"
@class="btn-edit-template"
@action={{this.editTemplate}}
/>
<DButton
@title="admin.form_templates.list_table.actions.delete"
@icon="far-trash-alt"
@class="btn-danger btn-delete-template"
@action={{this.deleteTemplate}}
/>
</td>
</tr>

View File

@ -1,53 +0,0 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import showModal from "discourse/lib/show-modal";
import { inject as service } from "@ember/service";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import I18n from "I18n";
export default class FormTemplateRowItem extends Component {
@service router;
@service dialog;
@service site;
get activeCategories() {
return this.site?.categories?.filter((c) =>
c["form_template_ids"].includes(this.args.template.id)
);
}
@action
viewTemplate() {
showModal("customize-form-template-view", {
model: this.args.template,
refreshModel: this.args.refreshModel,
});
}
@action
editTemplate() {
this.router.transitionTo(
"adminCustomizeFormTemplates.edit",
this.args.template
);
}
@action
deleteTemplate() {
return this.dialog.yesNoConfirm({
message: I18n.t("admin.form_templates.delete_confirm", {
template_name: this.args.template.name,
}),
didConfirm: () => {
ajax(`/admin/customize/form-templates/${this.args.template.id}.json`, {
type: "DELETE",
})
.then(() => {
this.args.refreshModel();
})
.catch(popupAjaxError);
},
});
}
}

View File

@ -1,11 +1,11 @@
import { observes, on } from "@ember-decorators/object";
import { observes, on } from "discourse-common/utils/decorators";
import Component from "@ember/component";
import highlightSyntax from "discourse/lib/highlight-syntax";
export default class HighlightedCode extends Component {
export default Component.extend({
@on("didInsertElement")
@observes("code")
_refresh() {
highlightSyntax(this.element, this.siteSettings, this.session);
}
}
},
});

View File

@ -1,15 +1,15 @@
import { classNames } from "@ember-decorators/component";
import Component from "@ember/component";
import { action } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators";
@classNames("inline-edit")
export default class InlineEditCheckbox extends Component {
buffer = null;
bufferModelId = null;
export default Component.extend({
classNames: ["inline-edit"],
buffer: null,
bufferModelId: null,
didReceiveAttrs() {
super.didReceiveAttrs(...arguments);
this._super(...arguments);
if (this.modelId !== this.bufferModelId) {
// HACK: The condition above ensures this method is called only when its
@ -24,21 +24,21 @@ export default class InlineEditCheckbox extends Component {
bufferModelId: this.modelId,
});
}
}
},
@discourseComputed("checked", "buffer")
changed(checked, buffer) {
return !!checked !== !!buffer;
}
},
@action
apply() {
this.set("checked", this.buffer);
this.action();
}
},
@action
cancel() {
this.set("buffer", this.checked);
}
}
},
});

View File

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

View File

@ -1,5 +1,3 @@
import { classNames } from "@ember-decorators/component";
import { inject as service } from "@ember/service";
import AdminUser from "admin/models/admin-user";
import Component from "@ember/component";
import EmberObject, { action } from "@ember/object";
@ -8,11 +6,12 @@ import { ajax } from "discourse/lib/ajax";
import copyText from "discourse/lib/copy-text";
import discourseComputed from "discourse-common/utils/decorators";
import discourseLater from "discourse-common/lib/later";
import { inject as service } from "@ember/service";
import { popupAjaxError } from "discourse/lib/ajax-error";
@classNames("ip-lookup")
export default class IpLookup extends Component {
@service dialog;
export default Component.extend({
classNames: ["ip-lookup"],
dialog: service(),
@discourseComputed("other_accounts.length", "totalOthersWithSameIP")
otherAccountsToDelete(otherAccountsLength, totalOthersWithSameIP) {
@ -20,100 +19,101 @@ export default class IpLookup extends Component {
const total = Math.min(50, totalOthersWithSameIP || 0);
const visible = Math.min(50, otherAccountsLength || 0);
return Math.max(visible, total);
}
},
@action
hide(event) {
event?.preventDefault();
this.set("show", false);
}
},
@action
lookup() {
this.set("show", true);
actions: {
lookup() {
this.set("show", true);
if (!this.location) {
ajax("/admin/users/ip-info", {
data: { ip: this.ip },
}).then((location) => this.set("location", EmberObject.create(location)));
}
if (!this.location) {
ajax("/admin/users/ip-info", {
data: { ip: this.ip },
}).then((location) =>
this.set("location", EmberObject.create(location))
);
}
if (!this.other_accounts) {
this.set("otherAccountsLoading", true);
if (!this.other_accounts) {
this.set("otherAccountsLoading", true);
const data = {
ip: this.ip,
exclude: this.userId,
order: "trust_level DESC",
};
const data = {
ip: this.ip,
exclude: this.userId,
order: "trust_level DESC",
};
ajax("/admin/users/total-others-with-same-ip", {
data,
}).then((result) => this.set("totalOthersWithSameIP", result.total));
ajax("/admin/users/total-others-with-same-ip", {
data,
}).then((result) => this.set("totalOthersWithSameIP", result.total));
AdminUser.findAll("active", data).then((users) => {
this.setProperties({
other_accounts: users,
otherAccountsLoading: false,
AdminUser.findAll("active", data).then((users) => {
this.setProperties({
other_accounts: users,
otherAccountsLoading: false,
});
});
}
},
copy() {
let text = `IP: ${this.ip}\n`;
const location = this.location;
if (location) {
if (location.hostname) {
text += `${I18n.t("ip_lookup.hostname")}: ${location.hostname}\n`;
}
text += I18n.t("ip_lookup.location");
if (location.location) {
text += `: ${location.location}\n`;
} else {
text += `: ${I18n.t("ip_lookup.location_not_found")}\n`;
}
if (location.organization) {
text += I18n.t("ip_lookup.organisation");
text += `: ${location.organization}\n`;
}
}
const $copyRange = $('<p id="copy-range"></p>');
$copyRange.html(text.trim().replace(/\n/g, "<br>"));
$(document.body).append($copyRange);
if (copyText(text, $copyRange[0])) {
this.set("copied", true);
discourseLater(() => this.set("copied", false), 2000);
}
$copyRange.remove();
},
deleteOtherAccounts() {
this.dialog.yesNoConfirm({
message: I18n.t("ip_lookup.confirm_delete_other_accounts"),
didConfirm: () => {
this.setProperties({
other_accounts: null,
otherAccountsLoading: true,
totalOthersWithSameIP: null,
});
ajax("/admin/users/delete-others-with-same-ip.json", {
type: "DELETE",
data: {
ip: this.ip,
exclude: this.userId,
order: "trust_level DESC",
},
})
.catch(popupAjaxError)
.finally(this.send("lookup"));
},
});
}
}
@action
copy() {
let text = `IP: ${this.ip}\n`;
const location = this.location;
if (location) {
if (location.hostname) {
text += `${I18n.t("ip_lookup.hostname")}: ${location.hostname}\n`;
}
text += I18n.t("ip_lookup.location");
if (location.location) {
text += `: ${location.location}\n`;
} else {
text += `: ${I18n.t("ip_lookup.location_not_found")}\n`;
}
if (location.organization) {
text += I18n.t("ip_lookup.organisation");
text += `: ${location.organization}\n`;
}
}
const $copyRange = $('<p id="copy-range"></p>');
$copyRange.html(text.trim().replace(/\n/g, "<br>"));
$(document.body).append($copyRange);
if (copyText(text, $copyRange[0])) {
this.set("copied", true);
discourseLater(() => this.set("copied", false), 2000);
}
$copyRange.remove();
}
@action
deleteOtherAccounts() {
this.dialog.yesNoConfirm({
message: I18n.t("ip_lookup.confirm_delete_other_accounts"),
didConfirm: () => {
this.setProperties({
other_accounts: null,
otherAccountsLoading: true,
totalOthersWithSameIP: null,
});
ajax("/admin/users/delete-others-with-same-ip.json", {
type: "DELETE",
data: {
ip: this.ip,
exclude: this.userId,
order: "trust_level DESC",
},
})
.catch(popupAjaxError)
.finally(this.send("lookup"));
},
});
}
}
},
},
});

View File

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

View File

@ -1,5 +1,3 @@
import { tagName } from "@ember-decorators/component";
import { inject as service } from "@ember/service";
import Component from "@ember/component";
import I18n from "I18n";
import Permalink from "admin/models/permalink";
@ -7,18 +5,16 @@ import discourseComputed, { bind } from "discourse-common/utils/decorators";
import { fmt } from "discourse/lib/computed";
import { schedule } from "@ember/runloop";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
@tagName("")
export default class PermalinkForm extends Component {
@service dialog;
formSubmitted = false;
permalinkType = "topic_id";
@fmt("permalinkType", "admin.permalink.%@") permalinkTypePlaceholder;
action = null;
permalinkTypeValue = null;
export default Component.extend({
tagName: "",
dialog: service(),
formSubmitted: false,
permalinkType: "topic_id",
permalinkTypePlaceholder: fmt("permalinkType", "admin.permalink.%@"),
action: null,
permalinkTypeValue: null,
@discourseComputed
permalinkTypes() {
@ -29,21 +25,21 @@ export default class PermalinkForm extends Component {
{ id: "tag_name", name: I18n.t("admin.permalink.tag_name") },
{ id: "external_url", name: I18n.t("admin.permalink.external_url") },
];
}
},
@bind
focusPermalink() {
schedule("afterRender", () =>
document.querySelector(".permalink-url")?.focus()
);
}
},
@action
submitFormOnEnter(event) {
if (event.key === "Enter") {
this.onSubmit();
}
}
},
@action
onSubmit() {
@ -88,5 +84,5 @@ export default class PermalinkForm extends Component {
}
);
}
}
}
},
});

View File

@ -1,16 +1,16 @@
import FilterComponent from "admin/components/report-filters/filter";
import { action } from "@ember/object";
export default class Bool extends FilterComponent {
checked = false;
export default FilterComponent.extend({
checked: false,
didReceiveAttrs() {
super.didReceiveAttrs(...arguments);
this._super(...arguments);
this.set("checked", !!this.filter.default);
}
},
@action
onChange() {
this.applyFilter(this.filter.id, !this.checked || undefined);
}
}
},
});

View File

@ -1,12 +1,12 @@
import { readOnly } from "@ember/object/computed";
import FilterComponent from "admin/components/report-filters/filter";
import { action } from "@ember/object";
import { readOnly } from "@ember/object/computed";
export default class Category extends FilterComponent {
@readOnly("filter.default") category;
export default FilterComponent.extend({
category: readOnly("filter.default"),
@action
onChange(categoryId) {
this.applyFilter(this.filter.id, categoryId || undefined);
}
}
},
});

View File

@ -1,9 +1,9 @@
import Component from "@ember/component";
import { action } from "@ember/object";
export default class Filter extends Component {
export default Component.extend({
@action
onChange(value) {
this.applyFilter(this.filter.id, value);
}
}
},
});

View File

@ -1,18 +1,18 @@
import { classNames } from "@ember-decorators/component";
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"],
@classNames("group-filter")
export default class Group extends FilterComponent {
@computed
get groupOptions() {
return (this.site.groups || []).map((group) => {
return { name: group["name"], value: group["id"] };
});
}
},
@computed("filter.default")
get groupId() {
return this.filter.default ? parseInt(this.filter.default, 10) : null;
}
}
},
});

View File

@ -1,3 +1,3 @@
import FilterComponent from "admin/components/report-filters/filter";
export default class List extends FilterComponent {}
export default FilterComponent.extend();

View File

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

View File

@ -1,23 +0,0 @@
<label>{{i18n "admin.logs.screened_ips.form.label"}}</label>
<TextField
@value={{this.ip_address}}
@disabled={{this.formSubmitted}}
@class="ip-address-input"
@placeholderKey="admin.logs.screened_ips.form.ip_address"
@autocorrect="off"
@autocapitalize="off"
/>
<ComboBox
@content={{this.actionNames}}
@value={{this.actionName}}
@onChange={{action (mut this.actionName)}}
/>
<DButton
@type="submit"
@class="btn-default"
@action={{action "submitForm"}}
@disabled={{this.formSubmitted}}
@label="admin.logs.screened_ips.form.add"
/>

View File

@ -1,11 +1,9 @@
import { action } from "@ember/object";
import { classNames, tagName } from "@ember-decorators/component";
import { inject as service } from "@ember/service";
import discourseComputed from "discourse-common/utils/decorators";
import Component from "@ember/component";
import I18n from "I18n";
import ScreenedIpAddress from "admin/models/screened-ip-address";
import { schedule } from "@ember/runloop";
import { inject as service } from "@ember/service";
/**
A form to create an IP address that will be blocked or allowed.
@ -18,13 +16,12 @@ import { schedule } from "@ember/runloop";
as an argument.
**/
@tagName("form")
@classNames("screened-ip-address-form", "inline-form")
export default class ScreenedIpAddressForm extends Component {
@service dialog;
formSubmitted = false;
actionName = "block";
export default Component.extend({
tagName: "form",
dialog: service(),
classNames: ["screened-ip-address-form", "inline-form"],
formSubmitted: false,
actionName: "block",
@discourseComputed("siteSettings.use_admin_ip_allowlist")
actionNames(adminAllowlistEnabled) {
@ -49,42 +46,43 @@ export default class ScreenedIpAddressForm extends Component {
},
];
}
}
},
focusInput() {
schedule("afterRender", () => {
this.element.querySelector("input").focus();
});
}
},
@action
submitForm() {
if (!this.formSubmitted) {
this.set("formSubmitted", true);
const screenedIpAddress = ScreenedIpAddress.create({
ip_address: this.ip_address,
action_name: this.actionName,
});
screenedIpAddress
.save()
.then((result) => {
this.setProperties({ ip_address: "", formSubmitted: false });
this.action(ScreenedIpAddress.create(result.screened_ip_address));
this.focusInput();
})
.catch((e) => {
this.set("formSubmitted", false);
const message = e.jqXHR.responseJSON?.errors
? I18n.t("generic_error_with_reason", {
error: e.jqXHR.responseJSON.errors.join(". "),
})
: I18n.t("generic_error");
this.dialog.alert({
message,
didConfirm: () => this.focusInput(),
didCancel: () => this.focusInput(),
});
actions: {
submit() {
if (!this.formSubmitted) {
this.set("formSubmitted", true);
const screenedIpAddress = ScreenedIpAddress.create({
ip_address: this.ip_address,
action_name: this.actionName,
});
}
}
}
screenedIpAddress
.save()
.then((result) => {
this.setProperties({ ip_address: "", formSubmitted: false });
this.action(ScreenedIpAddress.create(result.screened_ip_address));
this.focusInput();
})
.catch((e) => {
this.set("formSubmitted", false);
const message = e.jqXHR.responseJSON?.errors
? I18n.t("generic_error_with_reason", {
error: e.jqXHR.responseJSON.errors.join(". "),
})
: I18n.t("generic_error");
this.dialog.alert({
message,
didConfirm: () => this.focusInput(),
didCancel: () => this.focusInput(),
});
});
}
},
},
});

View File

@ -1,16 +1,15 @@
import { classNameBindings } from "@ember-decorators/component";
import Component from "@ember/component";
import I18n from "I18n";
import { isEmpty } from "@ember/utils";
import { on } from "@ember-decorators/object";
import { action, set } from "@ember/object";
import { on } from "discourse-common/utils/decorators";
import { set } from "@ember/object";
@classNameBindings(":value-list", ":secret-value-list")
export default class SecretValueList extends Component {
inputDelimiter = null;
collection = null;
values = null;
validationMessage = null;
export default Component.extend({
classNameBindings: [":value-list", ":secret-value-list"],
inputDelimiter: null,
collection: null,
values: null,
validationMessage: null,
@on("didReceiveAttrs")
_setupCollection() {
@ -20,43 +19,41 @@ export default class SecretValueList extends Component {
"collection",
this._splitValues(values, this.inputDelimiter || "\n")
);
}
},
@action
changeKey(index, event) {
const newValue = event.target.value;
actions: {
changeKey(index, event) {
const newValue = event.target.value;
if (this._checkInvalidInput(newValue)) {
return;
}
if (this._checkInvalidInput(newValue)) {
return;
}
this._replaceValue(index, newValue, "key");
}
this._replaceValue(index, newValue, "key");
},
@action
changeSecret(index, event) {
const newValue = event.target.value;
changeSecret(index, event) {
const newValue = event.target.value;
if (this._checkInvalidInput(newValue)) {
return;
}
if (this._checkInvalidInput(newValue)) {
return;
}
this._replaceValue(index, newValue, "secret");
}
this._replaceValue(index, newValue, "secret");
},
@action
addValue() {
if (this._checkInvalidInput([this.newKey, this.newSecret])) {
return;
}
this._addValue(this.newKey, this.newSecret);
this.setProperties({ newKey: "", newSecret: "" });
}
addValue() {
if (this._checkInvalidInput([this.newKey, this.newSecret])) {
return;
}
this._addValue(this.newKey, this.newSecret);
this.setProperties({ newKey: "", newSecret: "" });
},
@action
removeValue(value) {
this._removeValue(value);
}
removeValue(value) {
this._removeValue(value);
},
},
_checkInvalidInput(inputs) {
this.set("validationMessage", null);
@ -69,25 +66,25 @@ export default class SecretValueList extends Component {
return true;
}
}
}
},
_addValue(value, secret) {
this.collection.addObject({ key: value, secret });
this._saveValues();
}
},
_removeValue(value) {
const collection = this.collection;
collection.removeObject(value);
this._saveValues();
}
},
_replaceValue(index, newValue, keyName) {
let item = this.collection[index];
set(item, keyName, newValue);
this._saveValues();
}
},
_saveValues() {
this.set(
@ -98,7 +95,7 @@ export default class SecretValueList extends Component {
})
.join("\n")
);
}
},
_splitValues(values, delimiter) {
if (values && values.length) {
@ -116,5 +113,5 @@ export default class SecretValueList extends Component {
} else {
return [];
}
}
}
},
});

View File

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

View File

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

View File

@ -1,37 +1,34 @@
import { classNameBindings } from "@ember-decorators/component";
import { empty } from "@ember/object/computed";
import Component from "@ember/component";
import { action } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators";
import { on } from "@ember-decorators/object";
import { empty } from "@ember/object/computed";
import discourseComputed, { on } from "discourse-common/utils/decorators";
@classNameBindings(":simple-list", ":value-list")
export default class SimpleList extends Component {
@empty("newValue") inputEmpty;
inputDelimiter = null;
newValue = "";
collection = null;
values = null;
export default Component.extend({
classNameBindings: [":simple-list", ":value-list"],
inputEmpty: empty("newValue"),
inputDelimiter: null,
newValue: "",
collection: null,
values: null,
@on("didReceiveAttrs")
_setupCollection() {
this.set("collection", this._splitValues(this.values, this.inputDelimiter));
}
},
keyDown(event) {
if (event.which === 13) {
this.addValue(this.newValue);
return;
}
}
},
@action
changeValue(index, event) {
this.collection.replace(index, 1, [event.target.value]);
this.collection.arrayContentDidChange(index);
this._onChange();
}
},
@action
addValue(newValue) {
@ -42,13 +39,13 @@ export default class SimpleList extends Component {
this.set("newValue", null);
this.collection.addObject(newValue);
this._onChange();
}
},
@action
removeValue(value) {
this.collection.removeObject(value);
this._onChange();
}
},
@action
shift(operation, index) {
@ -65,20 +62,20 @@ export default class SimpleList extends Component {
this.collection.insertAt(futureIndex, shiftedValue);
this._onChange();
}
},
_onChange() {
this.onChange?.(this.collection);
}
},
@discourseComputed("collection")
showUpDownButtons(collection) {
return collection.length - 1 ? true : false;
}
},
_splitValues(values, delimiter) {
return values && values.length
? values.split(delimiter || "\n").filter(Boolean)
: [];
}
}
},
});

View File

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

View File

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

View File

@ -1,55 +0,0 @@
<div class="setting-label">
<h3>
{{this.settingName}}
{{#if this.staffLogFilter}}
<LinkTo
@route="adminLogs.staffActionLogs"
@query={{hash filters=this.staffLogFilter force_refresh=true}}
title={{i18n "admin.settings.history"}}
>
<span class="history-icon">
{{d-icon "history"}}
</span>
</LinkTo>
{{/if}}
</h3>
{{#if this.defaultIsAvailable}}
<DButton
class="btn-link"
@action={{this.setDefaultValues}}
@translatedLabel={{this.setting.setDefaultValuesLabel}}
/>
{{/if}}
</div>
<div class="setting-value">
{{component
this.componentName
setting=this.setting
value=this.buffered.value
validationMessage=this.validationMessage
preview=this.preview
isSecret=this.isSecret
allowAny=this.allowAny
}}
</div>
{{#if this.dirty}}
<div class="setting-controls">
<DButton class="ok" @action={{this.update}} @icon="check" />
<DButton class="cancel" @action={{this.cancel}} @icon="times" />
</div>
{{else if this.setting.overridden}}
{{#if this.setting.secret}}
<DButton @action={{this.toggleSecret}} @icon="far-eye-slash" />
{{/if}}
<DButton
class="btn-default undo"
@action={{this.resetDefault}}
@icon="undo"
@label="admin.settings.reset"
/>
{{/if}}

View File

@ -1,21 +1,18 @@
import { readOnly } from "@ember/object/computed";
import BufferedContent from "discourse/mixins/buffered-content";
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 class SiteSettingComponent extends Component.extend(
BufferedContent,
SettingComponent
) {
updateExistingUsers = null;
@readOnly("setting.staffLogFilter") staffLogFilter;
export default Component.extend(BufferedContent, SettingComponent, {
updateExistingUsers: null,
_save() {
const setting = this.buffered;
return SiteSetting.update(setting.get("setting"), setting.get("value"), {
updateExistingUsers: this.updateExistingUsers,
});
}
}
},
staffLogFilter: readOnly("setting.staffLogFilter"),
});

View File

@ -1,18 +1,19 @@
import { computed } from "@ember/object";
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
import { isEmpty } from "@ember/utils";
export default class Bool extends Component {
@computed("value")
get enabled() {
if (isEmpty(this.value)) {
return false;
}
return this.value.toString() === "true";
}
set enabled(value) {
this.set("value", value ? "true" : "false");
return value;
}
}
export default Component.extend({
@discourseComputed("value")
enabled: {
get(value) {
if (isEmpty(value)) {
return false;
}
return value.toString() === "true";
},
set(value) {
this.set("value", value ? "true" : "false");
return value;
},
},
});

View File

@ -1,15 +1,15 @@
import { action, computed } from "@ember/object";
import Category from "discourse/models/category";
import Component from "@ember/component";
import { computed } from "@ember/object";
export default class CategoryList extends Component {
@computed("value")
get selectedCategories() {
export default Component.extend({
selectedCategories: computed("value", function () {
return Category.findByIds(this.value.split("|").filter(Boolean));
}
}),
@action
onChangeSelectedCategories(value) {
this.set("value", (value || []).mapBy("id").join("|"));
}
}
actions: {
onChangeSelectedCategories(value) {
this.set("value", (value || []).mapBy("id").join("|"));
},
},
});

View File

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

View File

@ -24,9 +24,8 @@ function RGBToHex(rgb) {
return "#" + r + g + b;
}
export default class Color extends Component {
@computed("value")
get valid() {
export default Component.extend({
valid: computed("value", function () {
let value = this.value.toLowerCase();
let testColor = new Option().style;
@ -44,10 +43,10 @@ export default class Color extends Component {
}
return testColor.color && hexifiedColor === value;
}
}),
@action
onChangeColor(color) {
this.set("value", color);
}
}
},
});

View File

@ -1,36 +1,40 @@
import { action, computed } from "@ember/object";
import Component from "@ember/component";
import { computed } from "@ember/object";
import { makeArray } from "discourse-common/lib/helpers";
export default class CompactList extends Component {
tokenSeparator = "|";
createdChoices = null;
export default Component.extend({
tokenSeparator: "|",
@computed("value")
get settingValue() {
createdChoices: null,
settingValue: computed("value", function () {
return this.value.toString().split(this.tokenSeparator).filter(Boolean);
}
}),
@computed("settingValue", "setting.choices.[]", "createdChoices.[]")
get settingChoices() {
return [
...new Set([
...makeArray(this.settingValue),
...makeArray(this.setting.choices),
...makeArray(this.createdChoices),
]),
];
}
settingChoices: computed(
"settingValue",
"setting.choices.[]",
"createdChoices.[]",
function () {
return [
...new Set([
...makeArray(this.settingValue),
...makeArray(this.setting.choices),
...makeArray(this.createdChoices),
]),
];
}
),
@action
onChangeListSetting(value) {
this.set("value", value.join(this.tokenSeparator));
}
actions: {
onChangeListSetting(value) {
this.set("value", value.join(this.tokenSeparator));
},
@action
onChangeChoices(choices) {
this.set("createdChoices", [
...new Set([...makeArray(this.createdChoices), ...makeArray(choices)]),
]);
}
}
onChangeChoices(choices) {
this.set("createdChoices", [
...new Set([...makeArray(this.createdChoices), ...makeArray(choices)]),
]);
},
},
});

View File

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

View File

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

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