Compare commits

..

34 Commits

Author SHA1 Message Date
Martin Brennan
f47ee8fd40
WIP: Figuring out messages in thread panel 2023-02-13 12:51:39 +10:00
Martin Brennan
ff90a56f5b
DEV: Test fix 2023-02-13 11:03:20 +10:00
Martin Brennan
dd9059917f
DEV: Review fixes 2023-02-13 10:40:14 +10:00
Martin Brennan
176070cd78
Merge branch 'main' into chat-side-panel-initial-skeleton-threads 2023-02-13 10:30:42 +10:00
Joffrey JAFFEUX
1a9cb0914c re-adds @tracked activeChannel 2023-02-10 12:47:10 +01:00
Joffrey JAFFEUX
074e7ad4ed
Merge branch 'main' into chat-side-panel-initial-skeleton-threads 2023-02-10 12:25:59 +01:00
Martin Brennan
bf2a9063f6 DEV: Fix tracking for activeThread 2023-02-10 16:05:32 +10:00
Martin Brennan
54a4251b8e DEV: Fix lint 2023-02-10 15:41:31 +10:00
Martin Brennan
eaa49e3764 DEV: Use @tracked for activeThread 2023-02-10 15:33:15 +10:00
chapoi
00ddfd761a UX: apply grid for chat-view only 2023-02-10 12:31:29 +07:00
chapoi
3c568fbf1d Merge branch 'chat-side-panel-initial-skeleton-threads' of https://github.com/discourse/discourse into chat-side-panel-initial-skeleton-threads 2023-02-10 12:20:34 +07:00
Martin Brennan
be8968b05b DEV: Add mainOutletModifierClasses for chat main outlet
The main outlet needs different styles based on the route,
and whether the sidebar is expanded.
2023-02-10 15:16:05 +10:00
Martin Brennan
a6af08549b DEV: Add mainOutletModifierClasses for chat main outlet
The main outlet needs different styles based on the route,
and whether the sidebar is expanded.
2023-02-10 15:09:20 +10:00
Martin Brennan
218dbc249a
DEV: Fix routing not nulling channel/thread 2023-02-10 10:16:25 +10:00
Martin Brennan
f6081a9277
DEV: Review fixes 2023-02-10 09:25:02 +10:00
Martin Brennan
c19394c184
Merge branch 'main' into chat-side-panel-initial-skeleton-threads 2023-02-10 08:23:47 +10:00
Martin Brennan
b21664aee1
DEV: Test fix 2023-02-09 16:55:21 +10:00
Martin Brennan
9810c9aba5
DEV: Fix mobile thread full style 2023-02-09 16:47:21 +10:00
Martin Brennan
f341fa9b01 DEV: Register side panel mobile asset 2023-02-09 16:23:00 +10:00
Martin Brennan
77fb4fb2f2 DEV: Fix mobile thread spec 2023-02-09 15:50:22 +10:00
chapoi
81cd3d32f9 UX: side-panel on mobile 2023-02-09 12:25:37 +07:00
Martin Brennan
b33883db2c DEV: Spec for opening thread on mobile 2023-02-09 15:11:44 +10:00
chapoi
d93cdec8bb UX: BEM 2023-02-09 12:09:59 +07:00
chapoi
4a633597f4 UX: moved padding to different level 2023-02-09 12:06:13 +07:00
chapoi
c9f6cfce62 FIX: moved msg out header div 2023-02-09 12:05:55 +07:00
chapoi
769648652e UX: add missing flex prop 2023-02-09 12:02:36 +07:00
chapoi
827980903f UX: remove flex from side-panel 2023-02-09 12:02:01 +07:00
chapoi
fc65cdacc3 UX: add grid-area 2023-02-09 12:00:17 +07:00
chapoi
e7b810e0f9 Merge branch 'chat-side-panel-initial-skeleton-threads' of https://github.com/discourse/discourse into chat-side-panel-initial-skeleton-threads 2023-02-09 11:40:17 +07:00
chapoi
ca7b55d368 UX: use BEM modifier format 2023-02-09 11:22:46 +07:00
Martin Brennan
588e812d33 DEV: Add started by and avatar align and more specs 2023-02-09 14:07:31 +10:00
Martin Brennan
5699facfca
DEV: Start thread system specs 2023-02-09 13:33:38 +10:00
Martin Brennan
b598635753
Merge branch 'main' into chat-side-panel-initial-skeleton-threads 2023-02-09 11:15:41 +10:00
Martin Brennan
9108ef0383 WIP: Chat side panel skeleton 2023-02-08 17:01:12 +10:00
2251 changed files with 43059 additions and 67709 deletions

View File

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

76
.github/workflows/documentation.yml vendored Normal file
View File

@ -0,0 +1,76 @@
name: Documentation
on:
pull_request:
push:
branches:
- main
permissions:
contents: read
jobs:
build:
if: "!(github.event_name == 'push' && github.repository == 'discourse/discourse-private-mirror')"
name: run
runs-on: ubuntu-latest
container: discourse/discourse_test:slim
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 1
- name: Setup Git
run: |
git config --global user.email "ci@ci.invalid"
git config --global user.name "Discourse CI"
- name: Bundler cache
uses: actions/cache@v3
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gem-
- name: Setup gems
run: |
gem install bundler --conservative -v $(awk '/BUNDLED WITH/ { getline; gsub(/ /,""); print $0 }' Gemfile.lock)
bundle config --local path vendor/bundle
bundle config --local deployment true
bundle config --local without development
bundle install --jobs 4
bundle clean
- name: Get yarn cache directory
id: yarn-cache-dir
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- name: Yarn cache
uses: actions/cache@v3
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Yarn install
run: yarn install
- name: Check Chat documentation
run: |
LOAD_PLUGINS=1 bin/rake chat:doc
if [ ! -z "$(git status --porcelain plugins/chat/docs/)" ]; then
echo "Chat documentation is not up to date. To resolve, run:"
echo " LOAD_PLUGINS=1 bin/rake chat:doc"
echo
echo "Or manually apply the diff printed below:"
echo "---------------------------------------------"
git -c color.ui=always diff plugins/chat/docs/
exit 1
fi
timeout-minutes: 30

View File

@ -20,7 +20,7 @@ jobs:
if: "!(github.event_name == 'push' && github.repository == 'discourse/discourse-private-mirror')" 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 }} ${{ matrix.ruby }}
runs-on: ${{ (matrix.build_type == 'annotations') && 'ubuntu-latest' || 'ubuntu-20.04-8core' }} 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' || '' }}${{ (matrix.ruby == '3.2') && '-ruby-3.2.0' || '' }}
timeout-minutes: 20 timeout-minutes: 20
env: env:
@ -38,7 +38,7 @@ jobs:
matrix: matrix:
build_type: [backend, frontend, system, annotations] build_type: [backend, frontend, system, annotations]
target: [core, plugins] target: [core, plugins]
ruby: ['3.2'] ruby: ['3.1']
exclude: exclude:
- build_type: annotations - build_type: annotations
target: plugins target: plugins
@ -159,22 +159,6 @@ jobs:
path: tmp/turbo_rspec_runtime.log path: tmp/turbo_rspec_runtime.log
key: rspec-runtime-backend-core 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 - name: Core RSpec
if: matrix.build_type == 'backend' && matrix.target == 'core' if: matrix.build_type == 'backend' && matrix.target == 'core'
run: bin/turbo_rspec --verbose run: bin/turbo_rspec --verbose
@ -198,11 +182,11 @@ jobs:
- name: Core System Tests - name: Core System Tests
if: matrix.build_type == 'system' && matrix.target == 'core' if: matrix.build_type == 'system' && matrix.target == 'core'
run: bin/rspec spec/system run: PARALLEL_TEST_PROCESSORS=1 bin/turbo_rspec --verbose spec/system
- name: Plugin System Tests - name: Plugin System Tests
if: matrix.build_type == 'system' && matrix.target == 'plugins' if: matrix.build_type == 'system' && matrix.target == 'plugins'
run: LOAD_PLUGINS=1 bin/rspec plugins/*/spec/system run: LOAD_PLUGINS=1 PARALLEL_TEST_PROCESSORS=1 bin/turbo_rspec --verbose plugins/*/spec/system
- name: Upload failed system test screenshots - name: Upload failed system test screenshots
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3

14
.jsdoc
View File

@ -3,19 +3,5 @@
{ {
"source": { "source": {
"excludePattern": "" "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: bundler:
- cgi # Ruby (default gem) - cgi # Ruby (default gem)
- date # Ruby (default gem) - date # Ruby (default gem)
- digest # Ruby (default gem)
- io-wait # Ruby (default gem) - io-wait # Ruby (default gem)
- json # Ruby (default gem) - json # Ruby (default gem)
- net-http # Ruby (default gem) - net-http # Ruby (default gem)

View File

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

View File

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

View File

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

View File

@ -1 +1 @@
3.2.1 3.1.3

View File

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

View File

@ -18,7 +18,7 @@ else
# this allows us to include the bits of rails we use without pieces we do not. # 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 # To issue a rails update bump the version number here
rails_version = "7.0.4.3" rails_version = "7.0.4.1"
gem "actionmailer", rails_version gem "actionmailer", rails_version
gem "actionpack", rails_version gem "actionpack", rails_version
gem "actionview", rails_version gem "actionview", rails_version
@ -105,7 +105,7 @@ gem "pg"
gem "mini_sql" gem "mini_sql"
gem "pry-rails", require: false gem "pry-rails", require: false
gem "pry-byebug", require: false gem "pry-byebug", require: false
gem "rtlcss", require: false gem "rtlcss_wrapper", require: false
gem "rake" gem "rake"
gem "thor", require: false gem "thor", require: false
@ -180,7 +180,6 @@ group :development do
gem "better_errors", platform: :mri, require: !!ENV["BETTER_ERRORS"] gem "better_errors", platform: :mri, require: !!ENV["BETTER_ERRORS"]
gem "binding_of_caller" gem "binding_of_caller"
gem "yaml-lint" gem "yaml-lint"
gem "yard"
end end
if ENV["ALLOW_DEV_POPULATE"] == "1" if ENV["ALLOW_DEV_POPULATE"] == "1"
@ -279,5 +278,3 @@ gem "webrick", require: false
# Workaround until Ruby ships with cgi version 0.3.6 or higher. # Workaround until Ruby ships with cgi version 0.3.6 or higher.
gem "cgi", ">= 0.3.6", require: false gem "cgi", ">= 0.3.6", require: false
gem "tzinfo-data"

View File

@ -17,25 +17,25 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actionmailer (7.0.4.3) actionmailer (7.0.4.1)
actionpack (= 7.0.4.3) actionpack (= 7.0.4.1)
actionview (= 7.0.4.3) actionview (= 7.0.4.1)
activejob (= 7.0.4.3) activejob (= 7.0.4.1)
activesupport (= 7.0.4.3) activesupport (= 7.0.4.1)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (7.0.4.3) actionpack (7.0.4.1)
actionview (= 7.0.4.3) actionview (= 7.0.4.1)
activesupport (= 7.0.4.3) activesupport (= 7.0.4.1)
rack (~> 2.0, >= 2.2.0) rack (~> 2.0, >= 2.2.0)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actionview (7.0.4.3) actionview (7.0.4.1)
activesupport (= 7.0.4.3) activesupport (= 7.0.4.1)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
@ -44,15 +44,15 @@ GEM
actionview (>= 6.0.a) actionview (>= 6.0.a)
active_model_serializers (0.8.4) active_model_serializers (0.8.4)
activemodel (>= 3.0) activemodel (>= 3.0)
activejob (7.0.4.3) activejob (7.0.4.1)
activesupport (= 7.0.4.3) activesupport (= 7.0.4.1)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.0.4.3) activemodel (7.0.4.1)
activesupport (= 7.0.4.3) activesupport (= 7.0.4.1)
activerecord (7.0.4.3) activerecord (7.0.4.1)
activemodel (= 7.0.4.3) activemodel (= 7.0.4.1)
activesupport (= 7.0.4.3) activesupport (= 7.0.4.1)
activesupport (7.0.4.3) activesupport (7.0.4.1)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
minitest (>= 5.1) minitest (>= 5.1)
@ -110,7 +110,7 @@ GEM
chunky_png (1.4.0) chunky_png (1.4.0)
coderay (1.1.3) coderay (1.1.3)
colored2 (3.1.2) colored2 (3.1.2)
concurrent-ruby (1.2.2) concurrent-ruby (1.2.0)
connection_pool (2.3.0) connection_pool (2.3.0)
cose (1.3.0) cose (1.3.0)
cbor (~> 0.5.9) cbor (~> 0.5.9)
@ -157,7 +157,7 @@ GEM
faraday-net_http (>= 2.0, < 3.1) faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4) ruby2_keywords (>= 0.0.4)
faraday-net_http (3.0.2) faraday-net_http (3.0.2)
faraday-retry (2.1.0) faraday-retry (2.0.0)
faraday (~> 2.0) faraday (~> 2.0)
fast_blank (1.0.1) fast_blank (1.0.1)
fast_xs (0.8.0) fast_xs (0.8.0)
@ -167,11 +167,9 @@ GEM
gc_tracer (1.5.1) gc_tracer (1.5.1)
globalid (1.1.0) globalid (1.1.0)
activesupport (>= 5.0) activesupport (>= 5.0)
google-protobuf (3.22.2) google-protobuf (3.21.12)
google-protobuf (3.22.2-aarch64-linux) google-protobuf (3.21.12-x86_64-darwin)
google-protobuf (3.22.2-arm64-darwin) google-protobuf (3.21.12-x86_64-linux)
google-protobuf (3.22.2-x86_64-darwin)
google-protobuf (3.22.2-x86_64-linux)
guess_html_encoding (0.0.11) guess_html_encoding (0.0.11)
hana (1.3.7) hana (1.3.7)
hashdiff (1.0.1) hashdiff (1.0.1)
@ -182,7 +180,7 @@ GEM
http_accept_language (2.1.1) http_accept_language (2.1.1)
i18n (1.12.0) i18n (1.12.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
image_optim (0.31.3) image_optim (0.31.2)
exifr (~> 1.2, >= 1.2.2) exifr (~> 1.2, >= 1.2.2)
fspath (~> 3.0) fspath (~> 3.0)
image_size (>= 1.5, < 4) image_size (>= 1.5, < 4)
@ -219,7 +217,7 @@ GEM
logstash-event (1.2.02) logstash-event (1.2.02)
logstash-logger (0.26.1) logstash-logger (0.26.1)
logstash-event (~> 1.2) logstash-event (~> 1.2)
logster (2.12.2) logster (2.11.4)
loofah (2.19.1) loofah (2.19.1)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
@ -240,10 +238,10 @@ GEM
mini_sql (1.4.0) mini_sql (1.4.0)
mini_suffix (0.3.3) mini_suffix (0.3.3)
ffi (~> 1.9) ffi (~> 1.9)
minitest (5.18.0) minitest (5.17.0)
mocha (2.0.2) mocha (2.0.2)
ruby2_keywords (>= 0.0.5) ruby2_keywords (>= 0.0.5)
msgpack (1.6.1) msgpack (1.6.0)
multi_json (1.15.0) multi_json (1.15.0)
multi_xml (0.6.0) multi_xml (0.6.0)
mustache (1.1.1) mustache (1.1.1)
@ -259,16 +257,16 @@ GEM
net-smtp (0.3.3) net-smtp (0.3.3)
net-protocol net-protocol
nio4r (2.5.8) nio4r (2.5.8)
nokogiri (1.14.2) nokogiri (1.14.1)
mini_portile2 (~> 2.8.0) mini_portile2 (~> 2.8.0)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.14.2-aarch64-linux) nokogiri (1.14.1-aarch64-linux)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.14.2-arm64-darwin) nokogiri (1.14.1-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.14.2-x86_64-darwin) nokogiri (1.14.1-x86_64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.14.2-x86_64-linux) nokogiri (1.14.1-x86_64-linux)
racc (~> 1.4) racc (~> 1.4)
oauth (1.1.0) oauth (1.1.0)
oauth-tty (~> 1.0, >= 1.0.1) oauth-tty (~> 1.0, >= 1.0.1)
@ -305,17 +303,17 @@ GEM
omniauth-twitter (1.4.0) omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1) omniauth-oauth (~> 1.1)
rack rack
openssl (3.1.0) openssl (3.0.2)
openssl-signature_algorithm (1.3.0) openssl-signature_algorithm (1.2.1)
openssl (> 2.0) openssl (> 2.0, < 3.1)
optimist (3.0.1) optimist (3.0.1)
parallel (1.22.1) parallel (1.22.1)
parallel_tests (4.2.0) parallel_tests (4.2.0)
parallel parallel
parser (3.2.1.1) parser (3.2.1.0)
ast (~> 2.4.1) ast (~> 2.4.1)
pg (1.4.6) pg (1.4.5)
prettier_print (1.2.1) prettier_print (1.2.0)
progress (3.6.0) progress (3.6.0)
pry (0.14.2) pry (0.14.2)
coderay (~> 1.1) coderay (~> 1.1)
@ -326,15 +324,15 @@ GEM
pry-rails (0.3.9) pry-rails (0.3.9)
pry (>= 0.10.4) pry (>= 0.10.4)
public_suffix (5.0.1) public_suffix (5.0.1)
puma (6.1.1) puma (6.0.2)
nio4r (~> 2.0) nio4r (~> 2.0)
racc (1.6.2) racc (1.6.2)
rack (2.2.6.4) rack (2.2.6.2)
rack-mini-profiler (3.0.0) rack-mini-profiler (3.0.0)
rack (>= 1.2.0) rack (>= 1.2.0)
rack-protection (3.0.5) rack-protection (3.0.5)
rack rack
rack-test (2.1.0) rack-test (2.0.2)
rack (>= 1.3) rack (>= 1.3)
rails-dom-testing (2.0.3) rails-dom-testing (2.0.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
@ -348,15 +346,15 @@ GEM
rails_multisite (4.0.1) rails_multisite (4.0.1)
activerecord (> 5.0, < 7.1) activerecord (> 5.0, < 7.1)
railties (> 5.0, < 7.1) railties (> 5.0, < 7.1)
railties (7.0.4.3) railties (7.0.4.1)
actionpack (= 7.0.4.3) actionpack (= 7.0.4.1)
activesupport (= 7.0.4.3) activesupport (= 7.0.4.1)
method_source method_source
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0) thor (~> 1.0)
zeitwerk (~> 2.5) zeitwerk (~> 2.5)
rainbow (3.1.1) rainbow (3.1.1)
raindrops (0.20.1) raindrops (0.20.0)
rake (13.0.6) rake (13.0.6)
rb-fsevent (0.11.2) rb-fsevent (0.11.2)
rb-inotify (0.10.1) rb-inotify (0.10.1)
@ -391,7 +389,7 @@ GEM
rspec-html-matchers (0.10.0) rspec-html-matchers (0.10.0)
nokogiri (~> 1) nokogiri (~> 1)
rspec (>= 3.0.0.a) rspec (>= 3.0.0.a)
rspec-mocks (3.12.4) rspec-mocks (3.12.3)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0) rspec-support (~> 3.12.0)
rspec-rails (6.0.1) rspec-rails (6.0.1)
@ -410,30 +408,30 @@ GEM
json-schema (>= 2.2, < 4.0) json-schema (>= 2.2, < 4.0)
railties (>= 3.1, < 7.1) railties (>= 3.1, < 7.1)
rspec-core (>= 2.14) rspec-core (>= 2.14)
rtlcss (0.2.0) rtlcss_wrapper (0.1.0)
mini_racer (~> 0.6.3) mini_racer (~> 0.6.3)
rubocop (1.48.1) rubocop (1.44.1)
json (~> 2.3) json (~> 2.3)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.2.0.0) parser (>= 3.2.0.0)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0) regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.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) ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0) unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.27.0) rubocop-ast (1.24.1)
parser (>= 3.2.1.0) parser (>= 3.1.1.0)
rubocop-capybara (2.17.1) rubocop-capybara (2.17.0)
rubocop (~> 1.41) rubocop (~> 1.41)
rubocop-discourse (3.2.0) rubocop-discourse (3.0.3)
rubocop (>= 1.1.0) rubocop (>= 1.1.0)
rubocop-rspec (>= 2.0.0) rubocop-rspec (>= 2.0.0)
rubocop-rspec (2.19.0) rubocop-rspec (2.18.1)
rubocop (~> 1.33) rubocop (~> 1.33)
rubocop-capybara (~> 2.17) rubocop-capybara (~> 2.17)
ruby-prof (1.6.1) ruby-prof (1.5.0)
ruby-progressbar (1.13.0) ruby-progressbar (1.11.0)
ruby-readability (0.7.0) ruby-readability (0.7.0)
guess_html_encoding (>= 0.0.4) guess_html_encoding (>= 0.0.4)
nokogiri (>= 1.6.0) nokogiri (>= 1.6.0)
@ -442,18 +440,18 @@ GEM
sanitize (6.0.1) sanitize (6.0.1)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
sass-embedded (1.59.2) sass-embedded (1.58.0)
google-protobuf (~> 3.21) google-protobuf (~> 3.21)
rake (>= 10.0.0) rake (>= 10.0.0)
sass-embedded (1.59.2-aarch64-linux-gnu) sass-embedded (1.58.0-aarch64-linux-gnu)
google-protobuf (~> 3.21) google-protobuf (~> 3.21)
sass-embedded (1.59.2-arm64-darwin) sass-embedded (1.58.0-arm64-darwin)
google-protobuf (~> 3.21) google-protobuf (~> 3.21)
sass-embedded (1.59.2-x86_64-darwin) sass-embedded (1.58.0-x86_64-darwin)
google-protobuf (~> 3.21) google-protobuf (~> 3.21)
sass-embedded (1.59.2-x86_64-linux-gnu) sass-embedded (1.58.0-x86_64-linux-gnu)
google-protobuf (~> 3.21) google-protobuf (~> 3.21)
selenium-webdriver (4.8.1) selenium-webdriver (4.8.0)
rexml (~> 3.2, >= 3.2.5) rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0) rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0) websocket (~> 1.0)
@ -478,17 +476,15 @@ GEM
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
sshkey (2.0.0) sshkey (2.0.0)
stackprof (0.2.23) stackprof (0.2.23)
syntax_tree (6.0.2) syntax_tree (6.0.0)
prettier_print (>= 1.2.0) prettier_print (>= 1.2.0)
syntax_tree-disable_ternary (1.0.0) syntax_tree-disable_ternary (1.0.0)
test-prof (1.2.0) test-prof (1.2.0)
thor (1.2.1) thor (1.2.1)
tilt (2.1.0) tilt (2.0.11)
timeout (0.3.2) timeout (0.3.1)
tzinfo (2.0.6) tzinfo (2.0.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
tzinfo-data (1.2022.7)
tzinfo (>= 1.0.0)
uglifier (4.2.0) uglifier (4.2.0)
execjs (>= 0.3.0, < 3) execjs (>= 0.3.0, < 3)
unf (0.1.4) unf (0.1.4)
@ -520,8 +516,6 @@ GEM
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
yaml-lint (0.1.2) yaml-lint (0.1.2)
yard (0.9.28)
webrick (~> 1.7.0)
zeitwerk (2.6.7) zeitwerk (2.6.7)
PLATFORMS PLATFORMS
@ -534,14 +528,14 @@ PLATFORMS
x86_64-linux x86_64-linux
DEPENDENCIES DEPENDENCIES
actionmailer (= 7.0.4.3) actionmailer (= 7.0.4.1)
actionpack (= 7.0.4.3) actionpack (= 7.0.4.1)
actionview (= 7.0.4.3) actionview (= 7.0.4.1)
actionview_precompiler actionview_precompiler
active_model_serializers (~> 0.8.3) active_model_serializers (~> 0.8.3)
activemodel (= 7.0.4.3) activemodel (= 7.0.4.1)
activerecord (= 7.0.4.3) activerecord (= 7.0.4.1)
activesupport (= 7.0.4.3) activesupport (= 7.0.4.1)
addressable addressable
annotate annotate
aws-sdk-s3 aws-sdk-s3
@ -627,7 +621,7 @@ DEPENDENCIES
rack-protection rack-protection
rails_failover rails_failover
rails_multisite rails_multisite
railties (= 7.0.4.3) railties (= 7.0.4.1)
rake rake
rb-fsevent rb-fsevent
rbtrace rbtrace
@ -642,7 +636,7 @@ DEPENDENCIES
rspec-rails rspec-rails
rss rss
rswag-specs rswag-specs
rtlcss rtlcss_wrapper
rubocop-discourse rubocop-discourse
ruby-prof ruby-prof
ruby-readability ruby-readability
@ -660,7 +654,6 @@ DEPENDENCIES
syntax_tree-disable_ternary syntax_tree-disable_ternary
test-prof test-prof
thor thor
tzinfo-data
uglifier uglifier
unf unf
unicorn unicorn
@ -670,7 +663,6 @@ DEPENDENCIES
webrick webrick
xorcist xorcist
yaml-lint yaml-lint
yard
BUNDLED WITH BUNDLED WITH
2.4.4 2.4.4

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. 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 3.1+](https://www.ruby-lang.org/en/downloads/), [PostgreSQL 13](https://www.postgresql.org/download/), [Redis 7](https://redis.io/download). If you're having trouble, please see our [**TROUBLESHOOTING GUIDE**](docs/TROUBLESHOOTING.md) first!
## Setting up Discourse ## Setting up Discourse

View File

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

View File

@ -1,11 +1,11 @@
import RestAdapter from "discourse/adapters/rest"; import RestAdapter from "discourse/adapters/rest";
export default function buildPluginAdapter(pluginName) { export default function buildPluginAdapter(pluginName) {
return class extends RestAdapter { return RestAdapter.extend({
pathFor(store, type, findArgs) { pathFor(store, type, findArgs) {
return ( 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"; import RestAdapter from "discourse/adapters/rest";
export default class CustomizationBase extends RestAdapter { export default RestAdapter.extend({
basePath() { basePath() {
return "/admin/customize/"; return "/admin/customize/";
} },
} });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
import { action } from "@ember/object";
import { equal } from "@ember/object/computed";
import Component from "@ember/component"; import Component from "@ember/component";
import { equal } from "@ember/object/computed";
import discourseComputed, { import discourseComputed, {
afterRender, afterRender,
} from "discourse-common/utils/decorators"; } from "discourse-common/utils/decorators";
@ -8,28 +7,30 @@ import I18n from "I18n";
const ACTIONS = ["delete", "delete_replies", "edit", "none"]; const ACTIONS = ["delete", "delete_replies", "edit", "none"];
export default class AdminPenaltyPostAction extends Component { export default Component.extend({
postId = null; postId: null,
postAction = null; postAction: null,
postEdit = null; postEdit: null,
@equal("postAction", "edit") editing;
@discourseComputed @discourseComputed
penaltyActions() { penaltyActions() {
return ACTIONS.map((id) => { return ACTIONS.map((id) => {
return { id, name: I18n.t(`admin.user.penalty_post_${id}`) }; return { id, name: I18n.t(`admin.user.penalty_post_${id}`) };
}); });
} },
@action editing: equal("postAction", "edit"),
penaltyChanged(postAction) {
this.set("postAction", postAction);
// If we switch to edit mode, jump to the edit textarea actions: {
if (postAction === "edit") { penaltyChanged(postAction) {
this._focusEditTextarea(); this.set("postAction", postAction);
}
} // If we switch to edit mode, jump to the edit textarea
if (postAction === "edit") {
this._focusEditTextarea();
}
},
},
@afterRender @afterRender
_focusEditTextarea() { _focusEditTextarea() {
@ -37,5 +38,5 @@ export default class AdminPenaltyPostAction extends Component {
const body = elem.closest(".modal-body"); const body = elem.closest(".modal-body");
body.scrollTo(0, body.clientHeight); body.scrollTo(0, body.clientHeight);
elem.querySelector(".post-editor").focus(); 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 Component from "@ember/component";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { equal } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n"; import I18n from "I18n";
const CUSTOM_REASON_KEY = "custom"; const CUSTOM_REASON_KEY = "custom";
@tagName("") export default Component.extend({
export default class AdminPenaltyReason extends Component { tagName: "",
selectedReason = CUSTOM_REASON_KEY; selectedReason: CUSTOM_REASON_KEY,
customReason = ""; customReason: "",
reasonKeys: [
reasonKeys = [
"not_listening_to_staff", "not_listening_to_staff",
"consuming_staff_time", "consuming_staff_time",
"combative", "combative",
"in_wrong_place", "in_wrong_place",
"no_constructive_purpose", "no_constructive_purpose",
CUSTOM_REASON_KEY, CUSTOM_REASON_KEY,
]; ],
isCustomReason: equal("selectedReason", CUSTOM_REASON_KEY),
@equal("selectedReason", CUSTOM_REASON_KEY) isCustomReason;
@discourseComputed("reasonKeys") @discourseComputed("reasonKeys")
reasons(keys) { reasons(keys) {
return keys.map((key) => { return keys.map((key) => {
return { id: key, name: I18n.t(`admin.user.suspend_reasons.${key}`) }; return { id: key, name: I18n.t(`admin.user.suspend_reasons.${key}`) };
}); });
} },
@action @action
setSelectedReason(value) { setSelectedReason(value) {
this.set("selectedReason", value); this.set("selectedReason", value);
this.setReason(); this.setReason();
} },
@action @action
setCustomReason(value) { setCustomReason(value) {
this.set("customReason", value); this.set("customReason", value);
this.setReason(); this.setReason();
} },
setReason() { setReason() {
if (this.isCustomReason) { if (this.isCustomReason) {
@ -51,5 +48,5 @@ export default class AdminPenaltyReason extends Component {
I18n.t(`admin.user.suspend_reasons.${this.selectedReason}`) 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 Component from "@ember/component";
import { action } from "@ember/object"; import { action } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
@tagName("") export default Component.extend({
export default class AdminPenaltySimilarUsers extends Component { tagName: "",
@discourseComputed("penaltyType") @discourseComputed("penaltyType")
penaltyField(penaltyType) { penaltyField(penaltyType) {
if (penaltyType === "suspend") { if (penaltyType === "suspend") {
@ -12,7 +12,7 @@ export default class AdminPenaltySimilarUsers extends Component {
} else if (penaltyType === "silence") { } else if (penaltyType === "silence") {
return "can_be_silenced"; return "can_be_silenced";
} }
} },
@action @action
selectUserId(userId, event) { selectUserId(userId, event) {
@ -25,5 +25,5 @@ export default class AdminPenaltySimilarUsers extends Component {
} else { } else {
this.selectedUserIds.removeObject(userId); this.selectedUserIds.removeObject(userId);
} }
} },
} });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,3 @@
import { classNames } from "@ember-decorators/component";
import Report from "admin/models/report"; import Report from "admin/models/report";
import Component from "@ember/component"; import Component from "@ember/component";
import discourseDebounce from "discourse-common/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
@ -8,31 +7,32 @@ import { number } from "discourse/lib/formatter";
import { schedule } from "@ember/runloop"; import { schedule } from "@ember/runloop";
import { bind } from "discourse-common/utils/decorators"; import { bind } from "discourse-common/utils/decorators";
@classNames("admin-report-chart", "admin-report-stacked-chart") export default Component.extend({
export default class AdminReportStackedChart extends Component { classNames: ["admin-report-chart", "admin-report-stacked-chart"],
didInsertElement() { didInsertElement() {
super.didInsertElement(...arguments); this._super(...arguments);
window.addEventListener("resize", this._resizeHandler); window.addEventListener("resize", this._resizeHandler);
} },
willDestroyElement() { willDestroyElement() {
super.willDestroyElement(...arguments); this._super(...arguments);
window.removeEventListener("resize", this._resizeHandler); window.removeEventListener("resize", this._resizeHandler);
this._resetChart(); this._resetChart();
} },
didReceiveAttrs() { didReceiveAttrs() {
super.didReceiveAttrs(...arguments); this._super(...arguments);
discourseDebounce(this, this._scheduleChartRendering, 100); discourseDebounce(this, this._scheduleChartRendering, 100);
} },
@bind @bind
_resizeHandler() { _resizeHandler() {
discourseDebounce(this, this._scheduleChartRendering, 500); discourseDebounce(this, this._scheduleChartRendering, 500);
} },
_scheduleChartRendering() { _scheduleChartRendering() {
schedule("afterRender", () => { schedule("afterRender", () => {
@ -45,7 +45,7 @@ export default class AdminReportStackedChart extends Component {
this.element.querySelector(".chart-canvas") this.element.querySelector(".chart-canvas")
); );
}); });
} },
_renderChart(model, chartCanvas) { _renderChart(model, chartCanvas) {
if (!chartCanvas) { if (!chartCanvas) {
@ -79,7 +79,7 @@ export default class AdminReportStackedChart extends Component {
this._chart = new window.Chart(context, this._buildChartConfig(data)); this._chart = new window.Chart(context, this._buildChartConfig(data));
}); });
} },
_buildChartConfig(data) { _buildChartConfig(data) {
return { return {
@ -150,10 +150,10 @@ export default class AdminReportStackedChart extends Component {
}, },
}, },
}; };
} },
_resetChart() { _resetChart() {
this._chart?.destroy(); this._chart?.destroy();
this._chart = null; 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 Component from "@ember/component";
import I18n from "I18n"; import I18n from "I18n";
import { alias } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { setting } from "discourse/lib/computed"; import { setting } from "discourse/lib/computed";
@classNames("admin-report-storage-stats") export default Component.extend({
export default class AdminReportStorageStats extends Component { classNames: ["admin-report-storage-stats"],
@setting("backup_location") backupLocation;
@alias("model.data.backups") backupStats; backupLocation: setting("backup_location"),
backupStats: alias("model.data.backups"),
@alias("model.data.uploads") uploadStats; uploadStats: alias("model.data.uploads"),
@discourseComputed("backupStats") @discourseComputed("backupStats")
showBackupStats(stats) { showBackupStats(stats) {
return stats && this.currentUser.admin; return stats && this.currentUser.admin;
} },
@discourseComputed("backupLocation") @discourseComputed("backupLocation")
backupLocationName(backupLocation) { backupLocationName(backupLocation) {
return I18n.t(`admin.backups.location.${backupLocation}`); return I18n.t(`admin.backups.location.${backupLocation}`);
} },
@discourseComputed("backupStats.used_bytes") @discourseComputed("backupStats.used_bytes")
usedBackupSpace(bytes) { usedBackupSpace(bytes) {
return I18n.toHumanSize(bytes); return I18n.toHumanSize(bytes);
} },
@discourseComputed("backupStats.free_bytes") @discourseComputed("backupStats.free_bytes")
freeBackupSpace(bytes) { freeBackupSpace(bytes) {
return I18n.toHumanSize(bytes); return I18n.toHumanSize(bytes);
} },
@discourseComputed("uploadStats.used_bytes") @discourseComputed("uploadStats.used_bytes")
usedUploadSpace(bytes) { usedUploadSpace(bytes) {
return I18n.toHumanSize(bytes); return I18n.toHumanSize(bytes);
} },
@discourseComputed("uploadStats.free_bytes") @discourseComputed("uploadStats.free_bytes")
freeUploadSpace(bytes) { freeUploadSpace(bytes) {
return I18n.toHumanSize(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 Component from "@ember/component";
import { alias } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
@tagName("td") export default Component.extend({
@classNames("admin-report-table-cell") tagName: "td",
@classNameBindings("type", "property") classNames: ["admin-report-table-cell"],
@attributeBindings("value:title") classNameBindings: ["type", "property"],
export default class AdminReportTableCell extends Component { attributeBindings: ["value:title"],
options = null; options: null,
@alias("label.type") type;
@alias("label.mainProperty") property;
@alias("computedLabel.formattedValue") formattedValue;
@alias("computedLabel.value") value;
@discourseComputed("label", "data", "options") @discourseComputed("label", "data", "options")
computedLabel(label, data, options) { computedLabel(label, data, options) {
return label.compute(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 Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
@tagName("th") export default Component.extend({
@classNames("admin-report-table-header") tagName: "th",
@classNameBindings("label.mainProperty", "label.type", "isCurrentSort") classNames: ["admin-report-table-header"],
@attributeBindings("label.title:title") classNameBindings: ["label.mainProperty", "label.type", "isCurrentSort"],
export default class AdminReportTableHeader extends Component { attributeBindings: ["label.title:title"],
@discourseComputed("currentSortLabel.sortProperty", "label.sortProperty") @discourseComputed("currentSortLabel.sortProperty", "label.sortProperty")
isCurrentSort(currentSortField, labelSortField) { isCurrentSort(currentSortField, labelSortField) {
return currentSortField === labelSortField; return currentSortField === labelSortField;
} },
@discourseComputed("currentSortDirection") @discourseComputed("currentSortDirection")
sortIcon(currentSortDirection) { sortIcon(currentSortDirection) {
return currentSortDirection === 1 ? "caret-up" : "caret-down"; 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"; import Component from "@ember/component";
export default Component.extend({
@tagName("tr") tagName: "tr",
@classNames("admin-report-table-row") classNames: ["admin-report-table-row"],
export default class AdminReportTableRow extends Component { options: null,
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 Component from "@ember/component";
import { alias } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { makeArray } from "discourse-common/lib/helpers"; import { makeArray } from "discourse-common/lib/helpers";
const PAGES_LIMIT = 8; const PAGES_LIMIT = 8;
@classNameBindings("sortable", "twoColumns") export default Component.extend({
@classNames("admin-report-table") classNameBindings: ["sortable", "twoColumns"],
export default class AdminReportTable extends Component { classNames: ["admin-report-table"],
sortable = false; sortable: false,
sortDirection = 1; sortDirection: 1,
perPage: alias("options.perPage"),
@alias("options.perPage") perPage; page: 0,
page = 0;
@discourseComputed("model.computedLabels.length") @discourseComputed("model.computedLabels.length")
twoColumns(labelsLength) { twoColumns(labelsLength) {
return labelsLength === 2; return labelsLength === 2;
} },
@discourseComputed( @discourseComputed(
"totalsForSample", "totalsForSample",
@ -35,12 +31,12 @@ export default class AdminReportTable extends Component {
.reduce((s, v) => s + v, 0); .reduce((s, v) => s + v, 0);
return sum >= 1 && total && datesFiltering; return sum >= 1 && total && datesFiltering;
} },
@discourseComputed("model.total", "options.total", "twoColumns") @discourseComputed("model.total", "options.total", "twoColumns")
showTotal(reportTotal, total, twoColumns) { showTotal(reportTotal, total, twoColumns) {
return reportTotal && total && twoColumns; return reportTotal && total && twoColumns;
} },
@discourseComputed( @discourseComputed(
"model.{average,data}", "model.{average,data}",
@ -54,17 +50,17 @@ export default class AdminReportTable extends Component {
sampleTotalValue && sampleTotalValue &&
hasTwoColumns hasTwoColumns
); );
} },
@discourseComputed("totalsForSample.1.value", "model.data.length") @discourseComputed("totalsForSample.1.value", "model.data.length")
averageForSample(totals, count) { averageForSample(totals, count) {
return (totals / count).toFixed(0); return (totals / count).toFixed(0);
} },
@discourseComputed("model.data.length") @discourseComputed("model.data.length")
showSortingUI(dataLength) { showSortingUI(dataLength) {
return dataLength >= 5; return dataLength >= 5;
} },
@discourseComputed("totalsForSampleRow", "model.computedLabels") @discourseComputed("totalsForSampleRow", "model.computedLabels")
totalsForSample(row, labels) { totalsForSample(row, labels) {
@ -74,7 +70,7 @@ export default class AdminReportTable extends Component {
computedLabel.property = label.mainProperty; computedLabel.property = label.mainProperty;
return computedLabel; return computedLabel;
}); });
} },
@discourseComputed("model.data", "model.computedLabels") @discourseComputed("model.data", "model.computedLabels")
totalsForSampleRow(rows, labels) { totalsForSampleRow(rows, labels) {
@ -102,7 +98,7 @@ export default class AdminReportTable extends Component {
}); });
return totalsRow; return totalsRow;
} },
@discourseComputed("sortLabel", "sortDirection", "model.data.[]") @discourseComputed("sortLabel", "sortDirection", "model.data.[]")
sortedData(sortLabel, sortDirection, data) { sortedData(sortLabel, sortDirection, data) {
@ -122,7 +118,7 @@ export default class AdminReportTable extends Component {
} }
return data; return data;
} },
@discourseComputed("sortedData.[]", "perPage", "page") @discourseComputed("sortedData.[]", "perPage", "page")
paginatedData(data, perPage, page) { paginatedData(data, perPage, page) {
@ -132,7 +128,7 @@ export default class AdminReportTable extends Component {
} }
return data; return data;
} },
@discourseComputed("model.data", "perPage", "page") @discourseComputed("model.data", "perPage", "page")
pages(data, perPage, page) { pages(data, perPage, page) {
@ -160,19 +156,19 @@ export default class AdminReportTable extends Component {
} }
return pages; return pages;
} },
@action actions: {
changePage(page) { changePage(page) {
this.set("page", page); this.set("page", page);
} },
@action sortByLabel(label) {
sortByLabel(label) { if (this.sortLabel === label) {
if (this.sortLabel === label) { this.set("sortDirection", this.sortDirection === 1 ? -1 : 1);
this.set("sortDirection", this.sortDirection === 1 ? -1 : 1); } else {
} else { this.set("sortLabel", label);
this.set("sortLabel", label); }
} },
} },
} });

View File

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

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 EmberObject, { action, computed } from "@ember/object";
import Report, { DAILY_LIMIT_DAYS, SCHEMA_VERSION } from "admin/models/report"; 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 Component from "@ember/component";
import I18n from "I18n"; import I18n from "I18n";
import ReportLoader from "discourse/lib/reports-loader"; import ReportLoader from "discourse/lib/reports-loader";
@ -22,58 +21,51 @@ const TABLE_OPTIONS = {
const CHART_OPTIONS = {}; const CHART_OPTIONS = {};
@classNameBindings( export default Component.extend({
"isHidden:hidden", classNameBindings: [
"isHidden::is-visible", "isHidden:hidden",
"isEnabled", "isHidden::is-visible",
"isLoading", "isEnabled",
"dasherizedDataSourceName" "isLoading",
) "dasherizedDataSourceName",
@classNames("admin-report") ],
export default class AdminReport extends Component { classNames: ["admin-report"],
isEnabled = true; isEnabled: true,
disabledLabel = I18n.t("admin.dashboard.disabled"); disabledLabel: I18n.t("admin.dashboard.disabled"),
isLoading = false; isLoading: false,
rateLimitationString = null; rateLimitationString: null,
dataSourceName = null; dataSourceName: null,
report = null; report: null,
model = null; model: null,
reportOptions = null; reportOptions: null,
forcedModes = null; forcedModes: null,
showAllReportsLink = false; showAllReportsLink: false,
filters = null; filters: null,
showTrend = false; showTrend: false,
showHeader = true; showHeader: true,
showTitle = true; showTitle: true,
showFilteringUI = false; 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; isHidden: computed("siteSettings.dashboard_hidden_reports", function () {
endDate = null;
startDate = null;
@or("showTimeoutError", "showExceptionError", "showNotFoundError") showError;
@equal("model.error", "not_found") showNotFoundError;
@equal("model.error", "timeout") showTimeoutError;
@equal("model.error", "exception") showExceptionError;
@notEmpty("model.data") hasData;
_reports = [];
@computed("siteSettings.dashboard_hidden_reports")
get isHidden() {
return (this.siteSettings.dashboard_hidden_reports || "") return (this.siteSettings.dashboard_hidden_reports || "")
.split("|") .split("|")
.filter(Boolean) .filter(Boolean)
.includes(this.dataSourceName); .includes(this.dataSourceName);
} }),
didReceiveAttrs() { didReceiveAttrs() {
super.didReceiveAttrs(...arguments); this._super(...arguments);
let startDate = moment(); let startDate = moment();
if (this.filters && isPresent(this.filters.startDate)) { if (this.filters && isPresent(this.filters.startDate)) {
@ -96,35 +88,42 @@ export default class AdminReport extends Component {
} else if (this.dataSourceName) { } else if (this.dataSourceName) {
this._fetchReport(); 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") @discourseComputed("dataSourceName", "model.type")
dasherizedDataSourceName(dataSourceName, type) { dasherizedDataSourceName(dataSourceName, type) {
return (dataSourceName || type || "undefined").replace(/_/g, "-"); return (dataSourceName || type || "undefined").replace(/_/g, "-");
} },
@discourseComputed("dataSourceName", "model.type") @discourseComputed("dataSourceName", "model.type")
dataSource(dataSourceName, type) { dataSource(dataSourceName, type) {
dataSourceName = dataSourceName || type; dataSourceName = dataSourceName || type;
return `/admin/reports/${dataSourceName}`; return `/admin/reports/${dataSourceName}`;
} },
@discourseComputed("displayedModes.length") @discourseComputed("displayedModes.length")
showModes(displayedModesLength) { showModes(displayedModesLength) {
return displayedModesLength > 1; return displayedModesLength > 1;
} },
@discourseComputed("currentMode") @discourseComputed("currentMode")
isChartMode(currentMode) { isChartMode(currentMode) {
return currentMode === "chart"; return currentMode === "chart";
} },
@action @action
changeGrouping(grouping) { changeGrouping(grouping) {
this.send("refreshReport", { this.send("refreshReport", {
chartGrouping: grouping, chartGrouping: grouping,
}); });
} },
@discourseComputed("currentMode", "model.modes", "forcedModes") @discourseComputed("currentMode", "model.modes", "forcedModes")
displayedModes(currentMode, reportModes, forcedModes) { displayedModes(currentMode, reportModes, forcedModes) {
@ -140,12 +139,12 @@ export default class AdminReport extends Component {
icon: mode === "table" ? "table" : "signal", icon: mode === "table" ? "table" : "signal",
}; };
}); });
} },
@discourseComputed("currentMode") @discourseComputed("currentMode")
modeComponent(currentMode) { modeComponent(currentMode) {
return `admin-report-${currentMode.replace(/_/g, "-")}`; return `admin-report-${currentMode.replace(/_/g, "-")}`;
} },
@discourseComputed( @discourseComputed(
"dataSourceName", "dataSourceName",
@ -179,7 +178,7 @@ export default class AdminReport extends Component {
.join(":"); .join(":");
return reportKey; return reportKey;
} },
@discourseComputed("options.chartGrouping", "model.chartData.length") @discourseComputed("options.chartGrouping", "model.chartData.length")
chartGroupings(grouping, count) { chartGroupings(grouping, count) {
@ -193,7 +192,7 @@ export default class AdminReport extends Component {
class: `chart-grouping ${grouping === id ? "active" : "inactive"}`, class: `chart-grouping ${grouping === id ? "active" : "inactive"}`,
}; };
}); });
} },
@action @action
onChangeDateRange(range) { onChangeDateRange(range) {
@ -201,7 +200,7 @@ export default class AdminReport extends Component {
startDate: range.from, startDate: range.from,
endDate: range.to, endDate: range.to,
}); });
} },
@action @action
applyFilter(id, value) { applyFilter(id, value) {
@ -216,7 +215,7 @@ export default class AdminReport extends Component {
this.send("refreshReport", { this.send("refreshReport", {
filters: customFilters, filters: customFilters,
}); });
} },
@action @action
refreshReport(options = {}) { refreshReport(options = {}) {
@ -239,7 +238,7 @@ export default class AdminReport extends Component {
? this.get("filters.customFilters") ? this.get("filters.customFilters")
: options.filters, : options.filters,
}); });
} },
@action @action
exportCsv() { exportCsv() {
@ -255,7 +254,7 @@ export default class AdminReport extends Component {
} }
exportEntity("report", args).then(outputExportResult); exportEntity("report", args).then(outputExportResult);
} },
@action @action
onChangeMode(mode) { onChangeMode(mode) {
@ -264,7 +263,7 @@ export default class AdminReport extends Component {
this.send("refreshReport", { this.send("refreshReport", {
chartGrouping: null, chartGrouping: null,
}); });
} },
_computeReport() { _computeReport() {
if (!this.element || this.isDestroying || this.isDestroyed) { if (!this.element || this.isDestroying || this.isDestroyed) {
@ -307,7 +306,7 @@ export default class AdminReport extends Component {
} }
this._renderReport(report, this.forcedModes, this.currentMode); this._renderReport(report, this.forcedModes, this.currentMode);
} },
_renderReport(report, forcedModes, currentMode) { _renderReport(report, forcedModes, currentMode) {
const modes = forcedModes ? forcedModes.split(",") : report.modes; const modes = forcedModes ? forcedModes.split(",") : report.modes;
@ -318,9 +317,11 @@ export default class AdminReport extends Component {
currentMode, currentMode,
options: this._buildOptions(currentMode, report), options: this._buildOptions(currentMode, report),
}); });
} },
_fetchReport() { _fetchReport() {
this._super(...arguments);
this.setProperties({ isLoading: true, rateLimitationString: null }); this.setProperties({ isLoading: true, rateLimitationString: null });
next(() => { next(() => {
@ -348,7 +349,7 @@ export default class AdminReport extends Component {
ReportLoader.enqueue(this.dataSourceName, payload.data, callback); ReportLoader.enqueue(this.dataSourceName, payload.data, callback);
}); });
} },
_buildPayload(facets) { _buildPayload(facets) {
let payload = { data: { facets } }; let payload = { data: { facets } };
@ -374,7 +375,7 @@ export default class AdminReport extends Component {
} }
return payload; return payload;
} },
_buildOptions(mode, report) { _buildOptions(mode, report) {
if (mode === "table") { if (mode === "table") {
@ -392,7 +393,7 @@ export default class AdminReport extends Component {
}) })
); );
} }
} },
_loadReport(jsonReport) { _loadReport(jsonReport) {
Report.fillMissingDates(jsonReport, { filledField: "chartData" }); Report.fillMissingDates(jsonReport, { filledField: "chartData" });
@ -422,5 +423,5 @@ export default class AdminReport extends Component {
} }
return Report.create(jsonReport); 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 discourseComputed from "discourse-common/utils/decorators";
import { fmt } from "discourse/lib/computed"; import { fmt } from "discourse/lib/computed";
import { isDocumentRTL } from "discourse/lib/text-direction"; import { isDocumentRTL } from "discourse/lib/text-direction";
import { action, computed } from "@ember/object"; import { action } from "@ember/object";
import { next } from "@ember/runloop"; import { next } from "@ember/runloop";
export default class AdminThemeEditor extends Component { export default Component.extend({
warning = null; warning: null,
@fmt("fieldName", "currentTargetName", "%@|%@") editorId;
@discourseComputed("theme.targets", "onlyOverridden", "showAdvanced") @discourseComputed("theme.targets", "onlyOverridden", "showAdvanced")
visibleTargets(targets, onlyOverridden, showAdvanced) { visibleTargets(targets, onlyOverridden, showAdvanced) {
@ -22,7 +20,7 @@ export default class AdminThemeEditor extends Component {
} }
return target.edited; return target.edited;
}); });
} },
@discourseComputed("currentTargetName", "onlyOverridden", "theme.fields") @discourseComputed("currentTargetName", "onlyOverridden", "theme.fields")
visibleFields(targetName, onlyOverridden, fields) { visibleFields(targetName, onlyOverridden, fields) {
@ -31,7 +29,7 @@ export default class AdminThemeEditor extends Component {
fields = fields.filter((field) => field.edited); fields = fields.filter((field) => field.edited);
} }
return fields; return fields;
} },
@discourseComputed("currentTargetName", "fieldName") @discourseComputed("currentTargetName", "fieldName")
activeSectionMode(targetName, fieldName) { activeSectionMode(targetName, fieldName) {
@ -45,7 +43,7 @@ export default class AdminThemeEditor extends Component {
return "scss"; return "scss";
} }
return fieldName && fieldName.includes("scss") ? "scss" : "html"; return fieldName && fieldName.includes("scss") ? "scss" : "html";
} },
@discourseComputed("currentTargetName", "fieldName") @discourseComputed("currentTargetName", "fieldName")
placeholder(targetName, fieldName) { placeholder(targetName, fieldName) {
@ -60,27 +58,30 @@ export default class AdminThemeEditor extends Component {
}); });
} }
return ""; return "";
} },
@computed("fieldName", "currentTargetName", "theme") @discourseComputed("fieldName", "currentTargetName", "theme")
get activeSection() { activeSection: {
return this.theme.getField(this.currentTargetName, this.fieldName); get(fieldName, target, model) {
} return model.getField(target, fieldName);
},
set(value, fieldName, target, model) {
model.setField(target, fieldName, value);
return value;
},
},
set activeSection(value) { editorId: fmt("fieldName", "currentTargetName", "%@|%@"),
this.theme.setField(this.currentTargetName, this.fieldName, value);
return value;
}
@discourseComputed("maximized") @discourseComputed("maximized")
maximizeIcon(maximized) { maximizeIcon(maximized) {
return maximized ? "discourse-compress" : "discourse-expand"; return maximized ? "discourse-compress" : "discourse-expand";
} },
@discourseComputed("currentTargetName", "theme.targets") @discourseComputed("currentTargetName", "theme.targets")
showAddField(currentTargetName, targets) { showAddField(currentTargetName, targets) {
return targets.find((t) => t.name === currentTargetName).customNames; return targets.find((t) => t.name === currentTargetName).customNames;
} },
@discourseComputed( @discourseComputed(
"currentTargetName", "currentTargetName",
@ -89,45 +90,52 @@ export default class AdminThemeEditor extends Component {
) )
error(target, fieldName) { error(target, fieldName) {
return this.theme.getError(target, fieldName); return this.theme.getError(target, fieldName);
} },
@action @action
toggleShowAdvanced(event) { toggleShowAdvanced(event) {
event?.preventDefault(); event?.preventDefault();
this.toggleProperty("showAdvanced"); this.toggleProperty("showAdvanced");
} },
@action @action
toggleAddField(event) { toggleAddField(event) {
event?.preventDefault(); event?.preventDefault();
this.toggleProperty("addingField"); this.toggleProperty("addingField");
} },
@action @action
toggleMaximize(event) { toggleMaximize(event) {
event?.preventDefault(); event?.preventDefault();
this.toggleProperty("maximized"); this.toggleProperty("maximized");
next(() => this.appEvents.trigger("ace:resize")); next(() => this.appEvents.trigger("ace:resize"));
} },
@action actions: {
cancelAddField() { cancelAddField() {
this.set("addingField", false); this.set("addingField", false);
} },
@action addField(name) {
addField(name) { if (!name) {
if (!name) { return;
return; }
} name = name.replace(/[^a-zA-Z0-9-_/]/g, "");
name = name.replace(/[^a-zA-Z0-9-_/]/g, ""); this.theme.setField(this.currentTargetName, name, "");
this.theme.setField(this.currentTargetName, name, ""); this.setProperties({ newFieldName: "", addingField: false });
this.setProperties({ newFieldName: "", addingField: false }); this.fieldAdded(this.currentTargetName, name);
this.fieldAdded(this.currentTargetName, name); },
}
@action onlyOverriddenChanged(value) {
setWarning(message) { this.onlyOverriddenChanged(value);
this.set("warning", message); },
}
} 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 Component from "@ember/component";
import { alias, equal } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { action } from "@ember/object"; import { action } from "@ember/object";
import I18n from "I18n"; import I18n from "I18n";
import { inject as service } from "@ember/service";
@classNames("watched-word") export default Component.extend({
export default class AdminWatchedWord extends Component { classNames: ["watched-word"],
@service dialog; dialog: service(),
@equal("actionKey", "replace") isReplace; isReplace: equal("actionKey", "replace"),
isTag: equal("actionKey", "tag"),
@equal("actionKey", "tag") isTag; isLink: equal("actionKey", "link"),
isCaseSensitive: alias("word.case_sensitive"),
@equal("actionKey", "link") isLink;
@alias("word.case_sensitive") isCaseSensitive;
@discourseComputed("word.replacement") @discourseComputed("word.replacement")
tags(replacement) { tags(replacement) {
return replacement.split(","); return replacement.split(",");
} },
@action @action
deleteWord() { deleteWord() {
@ -37,5 +33,5 @@ export default class AdminWatchedWord extends Component {
}) })
); );
}); });
} },
} });

View File

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

View File

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

View File

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

View File

@ -1,3 +1,3 @@
import Component from "@ember/component"; 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 Component from "@ember/component";
import { action, computed } from "@ember/object"; import { action, computed } from "@ember/object";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
@classNames("section", "dashboard-new-features") export default Component.extend({
@classNameBindings("hasUnseenFeatures:ordered-first") newFeatures: null,
export default class DashboardNewFeatures extends Component { classNames: ["section", "dashboard-new-features"],
newFeatures = null; classNameBindings: ["hasUnseenFeatures:ordered-first"],
releaseNotesLink = null; releaseNotesLink: null,
constructor() { init() {
super(...arguments); this._super(...arguments);
ajax("/admin/dashboard/new-features.json").then((json) => { ajax("/admin/dashboard/new-features.json").then((json) => {
if (this.isDestroying || this.isDestroyed) { if (!this.element || this.isDestroying || this.isDestroyed) {
return; return;
} }
@ -23,17 +22,16 @@ export default class DashboardNewFeatures extends Component {
releaseNotesLink: json.release_notes_link, releaseNotesLink: json.release_notes_link,
}); });
}); });
} },
@computed("newFeatures") columnCountClass: computed("newFeatures", function () {
get columnCountClass() {
return this.newFeatures.length > 2 ? "three-or-more-items" : ""; return this.newFeatures.length > 2 ? "three-or-more-items" : "";
} }),
@action @action
dismissNewFeatures() { dismissNewFeatures() {
ajax("/admin/dashboard/mark-new-features-as-seen.json", { ajax("/admin/dashboard/mark-new-features-as-seen.json", {
type: "PUT", type: "PUT",
}).then(() => this.set("hasUnseenFeatures", false)); }).then(() => this.set("hasUnseenFeatures", false));
} },
} });

View File

@ -1,3 +1,3 @@
import Component from "@ember/component"; 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 Component from "@ember/component";
import I18n from "I18n"; import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators"; 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 { export default Component.extend({
@service dialog; dialog: service(),
editorId: reads("fieldName"),
@reads("fieldName") editorId;
@discourseComputed("fieldName") @discourseComputed("fieldName")
currentEditorMode(fieldName) { currentEditorMode(fieldName) {
return fieldName === "css" ? "scss" : fieldName; return fieldName === "css" ? "scss" : fieldName;
} },
@discourseComputed("fieldName", "styles.html", "styles.css") @discourseComputed("fieldName", "styles.html", "styles.css")
resetDisabled(fieldName) { resetDisabled(fieldName) {
@ -21,31 +19,36 @@ export default class EmailStylesEditor extends Component {
this.get(`styles.${fieldName}`) === this.get(`styles.${fieldName}`) ===
this.get(`styles.default_${fieldName}`) this.get(`styles.default_${fieldName}`)
); );
} },
@computed("styles", "fieldName") @discourseComputed("styles", "fieldName")
get editorContents() { editorContents: {
return this.styles[this.fieldName]; get(styles, fieldName) {
} return styles[fieldName];
},
set(value, styles, fieldName) {
styles.setField(fieldName, value);
return value;
},
},
set editorContents(value) { actions: {
this.styles.setField(this.fieldName, value); reset() {
return value; this.dialog.yesNoConfirm({
} message: I18n.t("admin.customize.email_style.reset_confirm", {
fieldName: I18n.t(`admin.customize.email_style.${this.fieldName}`),
@action }),
reset() { didConfirm: () => {
this.dialog.yesNoConfirm({ this.styles.setField(
message: I18n.t("admin.customize.email_style.reset_confirm", { this.fieldName,
fieldName: I18n.t(`admin.customize.email_style.${this.fieldName}`), this.styles.get(`default_${this.fieldName}`)
}), );
didConfirm: () => { this.notifyPropertyChange("editorContents");
this.styles.setField( },
this.fieldName, });
this.styles.get(`default_${this.fieldName}`) },
); save() {
this.notifyPropertyChange("editorContents"); 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 Category from "discourse/models/category";
import Component from "@ember/component"; import Component from "@ember/component";
import I18n from "I18n"; import I18n from "I18n";
import { bufferedProperty } from "discourse/mixins/buffered-content"; import { bufferedProperty } from "discourse/mixins/buffered-content";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { inject as service } from "@ember/service";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { or } from "@ember/object/computed";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
@tagName("tr") export default Component.extend(bufferedProperty("host"), {
export default class EmbeddableHost extends Component.extend( editToggled: false,
bufferedProperty("host") tagName: "tr",
) { categoryId: null,
@service dialog; category: null,
editToggled = false; dialog: service(),
categoryId = null;
category = null;
@or("host.isNew", "editToggled") editing; editing: or("host.isNew", "editToggled"),
init() { init() {
super.init(...arguments); this._super(...arguments);
const host = this.host; const host = this.host;
const categoryId = host.category_id || this.site.uncategorized_category_id; const categoryId = host.category_id || this.site.uncategorized_category_id;
const category = Category.findById(categoryId); const category = Category.findById(categoryId);
host.set("category", category); host.set("category", category);
} },
@discourseComputed("buffered.host", "host.isSaving") @discourseComputed("buffered.host", "host.isSaving")
cantSave(host, isSaving) { cantSave(host, isSaving) {
return isSaving || isEmpty(host); return isSaving || isEmpty(host);
} },
@action actions: {
edit() { edit() {
this.set("categoryId", this.get("host.category.id")); this.set("categoryId", this.get("host.category.id"));
this.set("editToggled", true); this.set("editToggled", true);
} },
@action save() {
save() { if (this.cantSave) {
if (this.cantSave) { return;
return; }
}
const props = this.buffered.getProperties( const props = this.buffered.getProperties(
"host", "host",
"allowed_paths", "allowed_paths",
"class_name" "class_name"
); );
props.category_id = this.categoryId; props.category_id = this.categoryId;
const host = this.host; const host = this.host;
host host
.save(props) .save(props)
.then(() => { .then(() => {
host.set("category", Category.findById(this.categoryId)); 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); 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 Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { dasherize } from "@ember/string"; import { dasherize } from "@ember/string";
@classNames("embed-setting") export default Component.extend({
export default class EmbeddingSetting extends Component { classNames: ["embed-setting"],
@discourseComputed("field") @discourseComputed("field")
inputId(field) { inputId(field) {
return dasherize(field); return dasherize(field);
} },
@discourseComputed("field") @discourseComputed("field")
translationKey(field) { translationKey(field) {
return `admin.embedding.${field}`; return `admin.embedding.${field}`;
} },
@discourseComputed("type") @discourseComputed("type")
isCheckbox(type) { isCheckbox(type) {
return type === "checkbox"; return type === "checkbox";
} },
@computed("value") @discourseComputed("value")
get checked() { checked: {
return !!this.value; get(value) {
} return !!value;
},
set(value) { set(value) {
this.set("value", value); this.set("value", value);
return value; return value;
} },
} },
});

View File

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

View File

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

View File

@ -1,4 +1,4 @@
<div class="form-templates__form"> <div class="form-templates--form">
<div class="control-group"> <div class="control-group">
<label for="template-name"> <label for="template-name">
{{i18n "admin.form_templates.new_template_form.name.label"}} {{i18n "admin.form_templates.new_template_form.name.label"}}
@ -6,38 +6,24 @@
<TextField <TextField
@value={{this.templateName}} @value={{this.templateName}}
@name="template-name" @name="template-name"
@class="form-templates__form-name-input" @class="form-templates--form-name-input"
@placeholderKey="admin.form_templates.new_template_form.name.placeholder" @placeholderKey="admin.form_templates.new_template_form.name.placeholder"
/> />
</div> </div>
<div class="control-group form-templates__editor">
<div class="form-templates__quick-insert-field-buttons"> <div class="control-group form-templates--quick-insert-field-buttons">
<span> <span>
{{I18n "admin.form_templates.quick_insert_fields.add_new_field"}} {{I18n "admin.form_templates.quick_insert_fields.add_new_field"}}
</span> </span>
{{#each this.quickInsertFields as |field|}} {{#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 <DButton
class="btn-flat btn-icon-text form-templates__validations-modal-button" @class="btn-flat btn-icon-text quick-insert-{{field.type}}"
@label="admin.form_templates.validations_modal.button_title" @icon={{field.icon}}
@icon="check-circle" @label="admin.form_templates.quick_insert_fields.{{field.type}}"
@action={{this.showValidationOptionsModal}} @action={{this.onInsertField}}
@actionParam={{field.type}}
/> />
</div> {{/each}}
<DButton
@class="form-templates__preview-button"
@icon="eye"
@label="admin.form_templates.new_template_form.preview"
@action={{this.showPreview}}
@disabled={{this.disablePreviewButton}}
/>
</div> </div>
<div class="control-group"> <div class="control-group">
@ -50,7 +36,7 @@
@label="admin.form_templates.new_template_form.submit" @label="admin.form_templates.new_template_form.submit"
@icon="check" @icon="check"
@action={{this.onSubmit}} @action={{this.onSubmit}}
@disabled={{this.disableSubmitButton}} @disabled={{this.formSubmitted}}
/> />
<DButton <DButton

View File

@ -6,15 +6,14 @@ import I18n from "I18n";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import { templateFormFields } from "admin/lib/template-form-fields"; import { templateFormFields } from "admin/lib/template-form-fields";
import FormTemplate from "admin/models/form-template"; import FormTemplate from "admin/models/form-template";
import showModal from "discourse/lib/show-modal";
export default class FormTemplateForm extends Component { export default class FormTemplateForm extends Component {
@service router; @service router;
@service dialog; @service dialog;
@tracked formSubmitted = false; @tracked formSubmitted = false;
@tracked templateContent = this.args.model?.template || ""; @tracked templateContent = this.args.model?.template || "";
@tracked templateName = this.args.model?.name || "";
isEditing = this.args.model?.id ? true : false; isEditing = this.args.model?.id ? true : false;
templateName = this.args.model?.name;
quickInsertFields = [ quickInsertFields = [
{ {
type: "checkbox", type: "checkbox",
@ -42,17 +41,6 @@ export default class FormTemplateForm extends Component {
}, },
]; ];
get disablePreviewButton() {
return Boolean(!this.templateName.length || !this.templateContent.length);
}
get disableSubmitButton() {
return (
Boolean(!this.templateName.length || !this.templateContent.length) ||
this.formSubmitted
);
}
@action @action
onSubmit() { onSubmit() {
if (!this.formSubmitted) { if (!this.formSubmitted) {
@ -66,17 +54,27 @@ export default class FormTemplateForm extends Component {
if (this.isEditing) { if (this.isEditing) {
postData["id"] = this.args.model.id; postData["id"] = this.args.model.id;
}
FormTemplate.createOrUpdateTemplate(postData) FormTemplate.updateTemplate(this.args.model.id, postData)
.then(() => { .then(() => {
this.formSubmitted = false; this.formSubmitted = false;
this.router.transitionTo("adminCustomizeFormTemplates.index"); this.router.transitionTo("adminCustomizeFormTemplates.index");
}) })
.catch((e) => { .catch((e) => {
popupAjaxError(e); popupAjaxError(e);
this.formSubmitted = false; this.formSubmitted = false;
}); });
} else {
FormTemplate.createTemplate(postData)
.then(() => {
this.formSubmitted = false;
this.router.transitionTo("adminCustomizeFormTemplates.index");
})
.catch((e) => {
popupAjaxError(e);
this.formSubmitted = false;
});
}
} }
@action @action
@ -108,33 +106,4 @@ export default class FormTemplateForm extends Component {
this.templateContent += `\n${structure}`; 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,10 +1,5 @@
<tr class="admin-list-item"> <tr class="admin-list-item">
<td class="col first">{{@template.name}}</td> <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"> <td class="col action">
<DButton <DButton
@title="admin.form_templates.list_table.actions.view" @title="admin.form_templates.list_table.actions.view"

View File

@ -9,17 +9,11 @@ import I18n from "I18n";
export default class FormTemplateRowItem extends Component { export default class FormTemplateRowItem extends Component {
@service router; @service router;
@service dialog; @service dialog;
@service site;
get activeCategories() {
return this.site?.categories?.filter((c) =>
c["form_template_ids"].includes(this.args.template.id)
);
}
@action @action
viewTemplate() { viewTemplate() {
showModal("customize-form-template-view", { showModal("admin-customize-form-template-view", {
admin: true,
model: this.args.template, model: this.args.template,
refreshModel: this.args.refreshModel, refreshModel: this.args.refreshModel,
}); });

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 Component from "@ember/component";
import highlightSyntax from "discourse/lib/highlight-syntax"; import highlightSyntax from "discourse/lib/highlight-syntax";
export default class HighlightedCode extends Component { export default Component.extend({
@on("didInsertElement") @on("didInsertElement")
@observes("code") @observes("code")
_refresh() { _refresh() {
highlightSyntax(this.element, this.siteSettings, this.session); 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 Component from "@ember/component";
import { action } from "@ember/object"; import { action } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
@classNames("inline-edit") export default Component.extend({
export default class InlineEditCheckbox extends Component { classNames: ["inline-edit"],
buffer = null;
bufferModelId = null; buffer: null,
bufferModelId: null,
didReceiveAttrs() { didReceiveAttrs() {
super.didReceiveAttrs(...arguments); this._super(...arguments);
if (this.modelId !== this.bufferModelId) { if (this.modelId !== this.bufferModelId) {
// HACK: The condition above ensures this method is called only when its // 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, bufferModelId: this.modelId,
}); });
} }
} },
@discourseComputed("checked", "buffer") @discourseComputed("checked", "buffer")
changed(checked, buffer) { changed(checked, buffer) {
return !!checked !== !!buffer; return !!checked !== !!buffer;
} },
@action @action
apply() { apply() {
this.set("checked", this.buffer); this.set("checked", this.buffer);
this.action(); this.action();
} },
@action @action
cancel() { cancel() {
this.set("buffer", this.checked); this.set("buffer", this.checked);
} },
} });

View File

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

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 AdminUser from "admin/models/admin-user";
import Component from "@ember/component"; import Component from "@ember/component";
import EmberObject, { action } from "@ember/object"; import EmberObject, { action } from "@ember/object";
@ -8,11 +6,12 @@ import { ajax } from "discourse/lib/ajax";
import copyText from "discourse/lib/copy-text"; import copyText from "discourse/lib/copy-text";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import discourseLater from "discourse-common/lib/later"; import discourseLater from "discourse-common/lib/later";
import { inject as service } from "@ember/service";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
@classNames("ip-lookup") export default Component.extend({
export default class IpLookup extends Component { classNames: ["ip-lookup"],
@service dialog; dialog: service(),
@discourseComputed("other_accounts.length", "totalOthersWithSameIP") @discourseComputed("other_accounts.length", "totalOthersWithSameIP")
otherAccountsToDelete(otherAccountsLength, totalOthersWithSameIP) { otherAccountsToDelete(otherAccountsLength, totalOthersWithSameIP) {
@ -20,100 +19,101 @@ export default class IpLookup extends Component {
const total = Math.min(50, totalOthersWithSameIP || 0); const total = Math.min(50, totalOthersWithSameIP || 0);
const visible = Math.min(50, otherAccountsLength || 0); const visible = Math.min(50, otherAccountsLength || 0);
return Math.max(visible, total); return Math.max(visible, total);
} },
@action @action
hide(event) { hide(event) {
event?.preventDefault(); event?.preventDefault();
this.set("show", false); this.set("show", false);
} },
@action actions: {
lookup() { lookup() {
this.set("show", true); this.set("show", true);
if (!this.location) { if (!this.location) {
ajax("/admin/users/ip-info", { ajax("/admin/users/ip-info", {
data: { ip: this.ip }, data: { ip: this.ip },
}).then((location) => this.set("location", EmberObject.create(location))); }).then((location) =>
} this.set("location", EmberObject.create(location))
);
}
if (!this.other_accounts) { if (!this.other_accounts) {
this.set("otherAccountsLoading", true); this.set("otherAccountsLoading", true);
const data = { const data = {
ip: this.ip, ip: this.ip,
exclude: this.userId, exclude: this.userId,
order: "trust_level DESC", order: "trust_level DESC",
}; };
ajax("/admin/users/total-others-with-same-ip", { ajax("/admin/users/total-others-with-same-ip", {
data, data,
}).then((result) => this.set("totalOthersWithSameIP", result.total)); }).then((result) => this.set("totalOthersWithSameIP", result.total));
AdminUser.findAll("active", data).then((users) => { AdminUser.findAll("active", data).then((users) => {
this.setProperties({ this.setProperties({
other_accounts: users, other_accounts: users,
otherAccountsLoading: false, 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"; import Component from "@ember/component";
export default Component.extend({
@tagName("tr") tagName: "tr",
export default class ModerationHistoryItem extends Component {} });

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 Component from "@ember/component";
import I18n from "I18n"; import I18n from "I18n";
import Permalink from "admin/models/permalink"; 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 { fmt } from "discourse/lib/computed";
import { schedule } from "@ember/runloop"; import { schedule } from "@ember/runloop";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { inject as service } from "@ember/service";
@tagName("") export default Component.extend({
export default class PermalinkForm extends Component { tagName: "",
@service dialog; dialog: service(),
formSubmitted: false,
formSubmitted = false; permalinkType: "topic_id",
permalinkType = "topic_id"; permalinkTypePlaceholder: fmt("permalinkType", "admin.permalink.%@"),
action: null,
@fmt("permalinkType", "admin.permalink.%@") permalinkTypePlaceholder; permalinkTypeValue: null,
action = null;
permalinkTypeValue = null;
@discourseComputed @discourseComputed
permalinkTypes() { permalinkTypes() {
@ -29,21 +25,21 @@ export default class PermalinkForm extends Component {
{ id: "tag_name", name: I18n.t("admin.permalink.tag_name") }, { id: "tag_name", name: I18n.t("admin.permalink.tag_name") },
{ id: "external_url", name: I18n.t("admin.permalink.external_url") }, { id: "external_url", name: I18n.t("admin.permalink.external_url") },
]; ];
} },
@bind @bind
focusPermalink() { focusPermalink() {
schedule("afterRender", () => schedule("afterRender", () =>
document.querySelector(".permalink-url")?.focus() document.querySelector(".permalink-url")?.focus()
); );
} },
@action @action
submitFormOnEnter(event) { submitFormOnEnter(event) {
if (event.key === "Enter") { if (event.key === "Enter") {
this.onSubmit(); this.onSubmit();
} }
} },
@action @action
onSubmit() { 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 FilterComponent from "admin/components/report-filters/filter";
import { action } from "@ember/object"; import { action } from "@ember/object";
export default class Bool extends FilterComponent { export default FilterComponent.extend({
checked = false; checked: false,
didReceiveAttrs() { didReceiveAttrs() {
super.didReceiveAttrs(...arguments); this._super(...arguments);
this.set("checked", !!this.filter.default); this.set("checked", !!this.filter.default);
} },
@action @action
onChange() { onChange() {
this.applyFilter(this.filter.id, !this.checked || undefined); 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 FilterComponent from "admin/components/report-filters/filter";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { readOnly } from "@ember/object/computed";
export default class Category extends FilterComponent { export default FilterComponent.extend({
@readOnly("filter.default") category; category: readOnly("filter.default"),
@action @action
onChange(categoryId) { onChange(categoryId) {
this.applyFilter(this.filter.id, categoryId || undefined); this.applyFilter(this.filter.id, categoryId || undefined);
} },
} });

View File

@ -1,9 +1,9 @@
import Component from "@ember/component"; import Component from "@ember/component";
import { action } from "@ember/object"; import { action } from "@ember/object";
export default class Filter extends Component { export default Component.extend({
@action @action
onChange(value) { onChange(value) {
this.applyFilter(this.filter.id, 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 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 @computed
get groupOptions() { get groupOptions() {
return (this.site.groups || []).map((group) => { return (this.site.groups || []).map((group) => {
return { name: group["name"], value: group["id"] }; return { name: group["name"], value: group["id"] };
}); });
} },
@computed("filter.default") @computed("filter.default")
get groupId() { get groupId() {
return this.filter.default ? parseInt(this.filter.default, 10) : null; return this.filter.default ? parseInt(this.filter.default, 10) : null;
} },
} });

View File

@ -1,3 +1,3 @@
import FilterComponent from "admin/components/report-filters/filter"; 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"; 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 discourseComputed from "discourse-common/utils/decorators";
import Component from "@ember/component"; import Component from "@ember/component";
import I18n from "I18n"; import I18n from "I18n";
import ScreenedIpAddress from "admin/models/screened-ip-address"; import ScreenedIpAddress from "admin/models/screened-ip-address";
import { schedule } from "@ember/runloop"; 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. 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. as an argument.
**/ **/
@tagName("form") export default Component.extend({
@classNames("screened-ip-address-form", "inline-form") tagName: "form",
export default class ScreenedIpAddressForm extends Component { dialog: service(),
@service dialog; classNames: ["screened-ip-address-form", "inline-form"],
formSubmitted: false,
formSubmitted = false; actionName: "block",
actionName = "block";
@discourseComputed("siteSettings.use_admin_ip_allowlist") @discourseComputed("siteSettings.use_admin_ip_allowlist")
actionNames(adminAllowlistEnabled) { actionNames(adminAllowlistEnabled) {
@ -49,42 +46,43 @@ export default class ScreenedIpAddressForm extends Component {
}, },
]; ];
} }
} },
focusInput() { focusInput() {
schedule("afterRender", () => { schedule("afterRender", () => {
this.element.querySelector("input").focus(); this.element.querySelector("input").focus();
}); });
} },
@action actions: {
submitForm() { submit() {
if (!this.formSubmitted) { if (!this.formSubmitted) {
this.set("formSubmitted", true); this.set("formSubmitted", true);
const screenedIpAddress = ScreenedIpAddress.create({ const screenedIpAddress = ScreenedIpAddress.create({
ip_address: this.ip_address, ip_address: this.ip_address,
action_name: this.actionName, 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(),
});
}); });
} 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 Component from "@ember/component";
import I18n from "I18n"; import I18n from "I18n";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { on } from "@ember-decorators/object"; import { on } from "discourse-common/utils/decorators";
import { action, set } from "@ember/object"; import { set } from "@ember/object";
@classNameBindings(":value-list", ":secret-value-list") export default Component.extend({
export default class SecretValueList extends Component { classNameBindings: [":value-list", ":secret-value-list"],
inputDelimiter = null; inputDelimiter: null,
collection = null; collection: null,
values = null; values: null,
validationMessage = null; validationMessage: null,
@on("didReceiveAttrs") @on("didReceiveAttrs")
_setupCollection() { _setupCollection() {
@ -20,43 +19,41 @@ export default class SecretValueList extends Component {
"collection", "collection",
this._splitValues(values, this.inputDelimiter || "\n") this._splitValues(values, this.inputDelimiter || "\n")
); );
} },
@action actions: {
changeKey(index, event) { changeKey(index, event) {
const newValue = event.target.value; const newValue = event.target.value;
if (this._checkInvalidInput(newValue)) { if (this._checkInvalidInput(newValue)) {
return; return;
} }
this._replaceValue(index, newValue, "key"); this._replaceValue(index, newValue, "key");
} },
@action changeSecret(index, event) {
changeSecret(index, event) { const newValue = event.target.value;
const newValue = event.target.value;
if (this._checkInvalidInput(newValue)) { if (this._checkInvalidInput(newValue)) {
return; return;
} }
this._replaceValue(index, newValue, "secret"); this._replaceValue(index, newValue, "secret");
} },
@action addValue() {
addValue() { if (this._checkInvalidInput([this.newKey, this.newSecret])) {
if (this._checkInvalidInput([this.newKey, this.newSecret])) { return;
return; }
} this._addValue(this.newKey, this.newSecret);
this._addValue(this.newKey, this.newSecret); this.setProperties({ newKey: "", newSecret: "" });
this.setProperties({ newKey: "", newSecret: "" }); },
}
@action removeValue(value) {
removeValue(value) { this._removeValue(value);
this._removeValue(value); },
} },
_checkInvalidInput(inputs) { _checkInvalidInput(inputs) {
this.set("validationMessage", null); this.set("validationMessage", null);
@ -69,25 +66,25 @@ export default class SecretValueList extends Component {
return true; return true;
} }
} }
} },
_addValue(value, secret) { _addValue(value, secret) {
this.collection.addObject({ key: value, secret }); this.collection.addObject({ key: value, secret });
this._saveValues(); this._saveValues();
} },
_removeValue(value) { _removeValue(value) {
const collection = this.collection; const collection = this.collection;
collection.removeObject(value); collection.removeObject(value);
this._saveValues(); this._saveValues();
} },
_replaceValue(index, newValue, keyName) { _replaceValue(index, newValue, keyName) {
let item = this.collection[index]; let item = this.collection[index];
set(item, keyName, newValue); set(item, keyName, newValue);
this._saveValues(); this._saveValues();
} },
_saveValues() { _saveValues() {
this.set( this.set(
@ -98,7 +95,7 @@ export default class SecretValueList extends Component {
}) })
.join("\n") .join("\n")
); );
} },
_splitValues(values, delimiter) { _splitValues(values, delimiter) {
if (values && values.length) { if (values && values.length) {
@ -116,5 +113,5 @@ export default class SecretValueList extends Component {
} else { } else {
return []; return [];
} }
} },
} });

View File

@ -1,3 +1,3 @@
import Component from "@ember/component"; 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"; import Component from "@ember/component";
export default Component.extend({
@tagName("") tagName: "",
export default class SilenceDetails extends Component {} });

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,3 @@
import Component from "@ember/component"; 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; return "#" + r + g + b;
} }
export default class Color extends Component { export default Component.extend({
@computed("value") valid: computed("value", function () {
get valid() {
let value = this.value.toLowerCase(); let value = this.value.toLowerCase();
let testColor = new Option().style; let testColor = new Option().style;
@ -44,10 +43,10 @@ export default class Color extends Component {
} }
return testColor.color && hexifiedColor === value; return testColor.color && hexifiedColor === value;
} }),
@action @action
onChangeColor(color) { onChangeColor(color) {
this.set("value", color); this.set("value", color);
} },
} });

View File

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

View File

@ -1,3 +1,3 @@
import Component from "@ember/component"; 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"; import Component from "@ember/component";
export default class Enum extends Component {} export default Component.extend({});

View File

@ -1,25 +1,25 @@
import { action, computed } from "@ember/object";
import Component from "@ember/component"; import Component from "@ember/component";
import { computed } from "@ember/object";
export default class GroupList extends Component { export default Component.extend({
tokenSeparator = "|"; tokenSeparator: "|",
nameProperty = "name";
valueProperty = "id";
@computed("site.groups") nameProperty: "name",
get groupChoices() { valueProperty: "id",
groupChoices: computed("site.groups", function () {
return (this.site.groups || []).map((g) => { return (this.site.groups || []).map((g) => {
return { name: g.name, id: g.id.toString() }; return { name: g.name, id: g.id.toString() };
}); });
} }),
@computed("value") settingValue: computed("value", function () {
get settingValue() {
return (this.value || "").split(this.tokenSeparator).filter(Boolean); return (this.value || "").split(this.tokenSeparator).filter(Boolean);
} }),
@action actions: {
onChangeGroupListSetting(value) { onChangeGroupListSetting(value) {
this.set("value", value.join(this.tokenSeparator)); this.set("value", value.join(this.tokenSeparator));
} },
} },
});

View File

@ -1,14 +1,14 @@
import Component from "@ember/component"; import Component from "@ember/component";
import { action, computed } from "@ember/object"; import { action, computed } from "@ember/object";
export default class HostList extends Component { export default Component.extend({
tokenSeparator = "|"; tokenSeparator: "|",
choices = null; choices: null,
@computed("value") @computed("value")
get settingValue() { get settingValue() {
return this.value.toString().split(this.tokenSeparator).filter(Boolean); return this.value.toString().split(this.tokenSeparator).filter(Boolean);
} },
@action @action
onChange(value) { onChange(value) {
@ -17,5 +17,5 @@ export default class HostList extends Component {
} }
this.set("value", value.join(this.tokenSeparator)); this.set("value", value.join(this.tokenSeparator));
} },
} });

View File

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

View File

@ -1,13 +1,13 @@
import Component from "@ember/component"; import Component from "@ember/component";
import { action, computed } from "@ember/object"; import { action, computed } from "@ember/object";
export default class NamedList extends Component { export default Component.extend({
tokenSeparator = "|"; tokenSeparator: "|",
@computed("value") @computed("value")
get settingValue() { get settingValue() {
return this.value.toString().split(this.tokenSeparator).filter(Boolean); return this.value.toString().split(this.tokenSeparator).filter(Boolean);
} },
@computed("setting.choices.[]", "settingValue") @computed("setting.choices.[]", "settingValue")
get settingChoices() { get settingChoices() {
@ -24,10 +24,10 @@ export default class NamedList extends Component {
} }
return choices; return choices;
} },
@action @action
onChangeListSetting(value) { onChangeListSetting(value) {
this.set("value", value.join(this.tokenSeparator)); this.set("value", value.join(this.tokenSeparator));
} },
} });

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