Version bump
This commit is contained in:
commit
4dd8f6e486
@ -12,3 +12,4 @@ node_modules/
|
||||
spec/
|
||||
dist/
|
||||
tmp/
|
||||
documentation/
|
||||
|
||||
76
.github/workflows/documentation.yml
vendored
76
.github/workflows/documentation.yml
vendored
@ -1,76 +0,0 @@
|
||||
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
|
||||
11
.github/workflows/linting.yml
vendored
11
.github/workflows/linting.yml
vendored
@ -22,6 +22,9 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Set working directory owner
|
||||
run: chown root:root .
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 1
|
||||
@ -70,7 +73,9 @@ jobs:
|
||||
|
||||
- name: syntax_tree
|
||||
if: ${{ !cancelled() }}
|
||||
run: bundle exec stree check Gemfile $(git ls-files '*.rb') $(git ls-files '*.rake')
|
||||
run: |
|
||||
set -E
|
||||
bundle exec stree check Gemfile $(git ls-files '*.rb') $(git ls-files '*.rake')
|
||||
|
||||
- name: ESLint (core)
|
||||
if: ${{ !cancelled() }}
|
||||
@ -87,8 +92,10 @@ jobs:
|
||||
yarn pprettier --list-different \
|
||||
"app/assets/stylesheets/**/*.scss" \
|
||||
"app/assets/javascripts/**/*.js" \
|
||||
"app/assets/javascripts/**/*.hbs" \
|
||||
"plugins/**/assets/stylesheets/**/*.scss" \
|
||||
"plugins/**/assets/javascripts/**/*.js"
|
||||
"plugins/**/assets/javascripts/**/*.js" \
|
||||
"plugins/**/assets/javascripts/**/*.hbs" \
|
||||
|
||||
- name: Ember template lint
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
33
.github/workflows/tests.yml
vendored
33
.github/workflows/tests.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
||||
if: "!(github.event_name == 'push' && github.repository == 'discourse/discourse-private-mirror')"
|
||||
name: ${{ matrix.target }} ${{ matrix.build_type }} ${{ matrix.ruby }}
|
||||
runs-on: ${{ (matrix.build_type == 'annotations') && 'ubuntu-latest' || 'ubuntu-20.04-8core' }}
|
||||
container: discourse/discourse_test:slim${{ (matrix.build_type == 'frontend' || matrix.build_type == 'system') && '-browsers' || '' }}${{ (matrix.ruby == '3.2') && '-ruby-3.2.0' || '' }}
|
||||
container: discourse/discourse_test:slim${{ (matrix.build_type == 'frontend' || matrix.build_type == 'system') && '-browsers' || '' }}${{ (matrix.ruby == '3.1') && '-ruby-3.1.0' || '' }}
|
||||
timeout-minutes: 20
|
||||
|
||||
env:
|
||||
@ -29,7 +29,7 @@ jobs:
|
||||
RAILS_ENV: test
|
||||
PGUSER: discourse
|
||||
PGPASSWORD: discourse
|
||||
USES_PARALLEL_DATABASES: ${{ matrix.build_type == 'backend' }}
|
||||
USES_PARALLEL_DATABASES: ${{ matrix.build_type == 'backend' || matrix.build_type == 'system' }}
|
||||
CAPBYARA_DEFAULT_MAX_WAIT_TIME: 4
|
||||
|
||||
strategy:
|
||||
@ -38,7 +38,7 @@ jobs:
|
||||
matrix:
|
||||
build_type: [backend, frontend, system, annotations]
|
||||
target: [core, plugins]
|
||||
ruby: ['3.1']
|
||||
ruby: ['3.2']
|
||||
exclude:
|
||||
- build_type: annotations
|
||||
target: plugins
|
||||
@ -46,6 +46,9 @@ jobs:
|
||||
target: core # Handled by core_frontend_tests job (below)
|
||||
|
||||
steps:
|
||||
- name: Set working directory owner
|
||||
run: chown root:root .
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 1
|
||||
@ -156,6 +159,22 @@ jobs:
|
||||
path: tmp/turbo_rspec_runtime.log
|
||||
key: rspec-runtime-backend-core
|
||||
|
||||
- name: Run Zeitwerk check
|
||||
if: matrix.build_type == 'backend'
|
||||
env:
|
||||
LOAD_PLUGINS: ${{ (matrix.target == 'plugins') && '1' || '0' }}
|
||||
run: |
|
||||
if ! bin/rails zeitwerk:check --trace; then
|
||||
echo
|
||||
echo "---------------------------------------------"
|
||||
echo
|
||||
echo "::error::'bin/rails zeitwerk:check' failed - the app will fail to boot with 'eager_load=true' (e.g. in production)."
|
||||
echo "To reproduce locally, run 'bin/rails zeitwerk:check'."
|
||||
echo "Alternatively, you can run your local server/tests with the 'DISCOURSE_ZEITWERK_EAGER_LOAD=1' environment variable."
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Core RSpec
|
||||
if: matrix.build_type == 'backend' && matrix.target == 'core'
|
||||
run: bin/turbo_rspec --verbose
|
||||
@ -173,13 +192,17 @@ jobs:
|
||||
if: matrix.build_type == 'system'
|
||||
run: bin/ember-cli --build
|
||||
|
||||
- name: Setup Webdriver
|
||||
if: matrix.build_type == 'system'
|
||||
run: bin/rails runner "require 'webdrivers'; Webdrivers::Chromedriver.update"
|
||||
|
||||
- name: Core System Tests
|
||||
if: matrix.build_type == 'system' && matrix.target == 'core'
|
||||
run: bin/rspec spec/system --format documentation --profile
|
||||
run: bin/rspec spec/system
|
||||
|
||||
- name: Plugin System Tests
|
||||
if: matrix.build_type == 'system' && matrix.target == 'plugins'
|
||||
run: LOAD_PLUGINS=1 bin/rspec plugins/*/spec/system --format documentation --profile
|
||||
run: LOAD_PLUGINS=1 bin/rspec plugins/*/spec/system
|
||||
|
||||
- name: Upload failed system test screenshots
|
||||
uses: actions/upload-artifact@v3
|
||||
|
||||
14
.jsdoc
14
.jsdoc
@ -3,5 +3,19 @@
|
||||
{
|
||||
"source": {
|
||||
"excludePattern": ""
|
||||
},
|
||||
"templates": {
|
||||
"default": {
|
||||
"includeDate": false
|
||||
}
|
||||
},
|
||||
"opts": {
|
||||
"template": "./node_modules/tidy-jsdoc",
|
||||
"prism-theme": "prism-custom",
|
||||
"encoding": "utf8",
|
||||
"recurse": true
|
||||
},
|
||||
"metadata": {
|
||||
"title": "Discourse"
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ ignored:
|
||||
bundler:
|
||||
- cgi # Ruby (default gem)
|
||||
- date # Ruby (default gem)
|
||||
- digest # Ruby (default gem)
|
||||
- io-wait # Ruby (default gem)
|
||||
- json # Ruby (default gem)
|
||||
- net-http # Ruby (default gem)
|
||||
|
||||
@ -11,7 +11,8 @@
|
||||
"packages": {
|
||||
"@fortawesome/fontawesome-free": "*",
|
||||
"ember-template-lint-plugin-discourse": "*",
|
||||
"squoosh": "2.0.0"
|
||||
"squoosh": "2.0.0",
|
||||
"taffydb": "2.6.2"
|
||||
},
|
||||
"corrections": true
|
||||
}
|
||||
@ -3,6 +3,7 @@ plugins/**/assets/stylesheets/vendor/
|
||||
plugins/**/assets/javascripts/vendor/
|
||||
plugins/**/config/locales/**/*.yml
|
||||
plugins/**/config/*.yml
|
||||
documentation/
|
||||
package.json
|
||||
config/locales/**/*.yml
|
||||
!config/locales/**/*.en*.yml
|
||||
|
||||
@ -7,3 +7,7 @@ Discourse/NoAddReferenceOrAliasesActiveRecordMigration:
|
||||
|
||||
Discourse/NoResetColumnInformationInMigrations:
|
||||
Enabled: true
|
||||
|
||||
Lint/Debugger:
|
||||
Exclude:
|
||||
- script/**/*
|
||||
|
||||
@ -1 +1 @@
|
||||
3.1.3
|
||||
3.2.1
|
||||
|
||||
@ -15,7 +15,7 @@ module.exports = {
|
||||
"directory-item-value",
|
||||
"directory-table-header-title",
|
||||
"loading-spinner",
|
||||
"mobile-directory-item-label",
|
||||
"directory-item-label",
|
||||
],
|
||||
},
|
||||
"no-implicit-this": {
|
||||
|
||||
14
Gemfile
14
Gemfile
@ -18,7 +18,7 @@ else
|
||||
# this allows us to include the bits of rails we use without pieces we do not.
|
||||
#
|
||||
# To issue a rails update bump the version number here
|
||||
rails_version = "7.0.4.1"
|
||||
rails_version = "7.0.4.3"
|
||||
gem "actionmailer", rails_version
|
||||
gem "actionpack", rails_version
|
||||
gem "actionview", rails_version
|
||||
@ -105,7 +105,7 @@ gem "pg"
|
||||
gem "mini_sql"
|
||||
gem "pry-rails", require: false
|
||||
gem "pry-byebug", require: false
|
||||
gem "r2", require: false
|
||||
gem "rtlcss", require: false
|
||||
gem "rake"
|
||||
|
||||
gem "thor", require: false
|
||||
@ -180,6 +180,7 @@ group :development do
|
||||
gem "better_errors", platform: :mri, require: !!ENV["BETTER_ERRORS"]
|
||||
gem "binding_of_caller"
|
||||
gem "yaml-lint"
|
||||
gem "yard"
|
||||
end
|
||||
|
||||
if ENV["ALLOW_DEV_POPULATE"] == "1"
|
||||
@ -229,10 +230,9 @@ gem "logstash-event", require: false
|
||||
gem "logstash-logger", require: false
|
||||
gem "logster"
|
||||
|
||||
# NOTE: later versions of sassc are causing a segfault, possibly dependent on processer architecture
|
||||
# and until resolved should be locked at 2.0.1
|
||||
gem "sassc", "2.0.1", require: false
|
||||
gem "sassc-rails"
|
||||
# These are forks of sassc and sassc-rails with dart-sass support
|
||||
gem "dartsass-ruby"
|
||||
gem "dartsass-sprockets"
|
||||
|
||||
gem "rotp", require: false
|
||||
|
||||
@ -279,3 +279,5 @@ gem "webrick", require: false
|
||||
|
||||
# Workaround until Ruby ships with cgi version 0.3.6 or higher.
|
||||
gem "cgi", ">= 0.3.6", require: false
|
||||
|
||||
gem "tzinfo-data"
|
||||
|
||||
200
Gemfile.lock
200
Gemfile.lock
@ -17,25 +17,25 @@ GIT
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actionmailer (7.0.4.1)
|
||||
actionpack (= 7.0.4.1)
|
||||
actionview (= 7.0.4.1)
|
||||
activejob (= 7.0.4.1)
|
||||
activesupport (= 7.0.4.1)
|
||||
actionmailer (7.0.4.3)
|
||||
actionpack (= 7.0.4.3)
|
||||
actionview (= 7.0.4.3)
|
||||
activejob (= 7.0.4.3)
|
||||
activesupport (= 7.0.4.3)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (7.0.4.1)
|
||||
actionview (= 7.0.4.1)
|
||||
activesupport (= 7.0.4.1)
|
||||
actionpack (7.0.4.3)
|
||||
actionview (= 7.0.4.3)
|
||||
activesupport (= 7.0.4.3)
|
||||
rack (~> 2.0, >= 2.2.0)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actionview (7.0.4.1)
|
||||
activesupport (= 7.0.4.1)
|
||||
actionview (7.0.4.3)
|
||||
activesupport (= 7.0.4.3)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
@ -44,15 +44,15 @@ GEM
|
||||
actionview (>= 6.0.a)
|
||||
active_model_serializers (0.8.4)
|
||||
activemodel (>= 3.0)
|
||||
activejob (7.0.4.1)
|
||||
activesupport (= 7.0.4.1)
|
||||
activejob (7.0.4.3)
|
||||
activesupport (= 7.0.4.3)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.0.4.1)
|
||||
activesupport (= 7.0.4.1)
|
||||
activerecord (7.0.4.1)
|
||||
activemodel (= 7.0.4.1)
|
||||
activesupport (= 7.0.4.1)
|
||||
activesupport (7.0.4.1)
|
||||
activemodel (7.0.4.3)
|
||||
activesupport (= 7.0.4.3)
|
||||
activerecord (7.0.4.3)
|
||||
activemodel (= 7.0.4.3)
|
||||
activesupport (= 7.0.4.3)
|
||||
activesupport (7.0.4.3)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
@ -88,7 +88,7 @@ GEM
|
||||
rack (>= 0.9.0)
|
||||
binding_of_caller (1.0.0)
|
||||
debug_inspector (>= 0.0.1)
|
||||
bootsnap (1.15.0)
|
||||
bootsnap (1.16.0)
|
||||
msgpack (~> 1.2)
|
||||
builder (3.2.4)
|
||||
bullet (7.0.7)
|
||||
@ -110,7 +110,7 @@ GEM
|
||||
chunky_png (1.4.0)
|
||||
coderay (1.1.3)
|
||||
colored2 (3.1.2)
|
||||
concurrent-ruby (1.2.0)
|
||||
concurrent-ruby (1.2.2)
|
||||
connection_pool (2.3.0)
|
||||
cose (1.3.0)
|
||||
cbor (~> 0.5.9)
|
||||
@ -121,6 +121,14 @@ GEM
|
||||
crass (1.0.6)
|
||||
css_parser (1.14.0)
|
||||
addressable
|
||||
dartsass-ruby (3.0.1)
|
||||
sass-embedded (~> 1.54)
|
||||
dartsass-sprockets (3.0.0)
|
||||
dartsass-ruby (~> 3.0)
|
||||
railties (>= 4.0.0)
|
||||
sprockets (> 3.0)
|
||||
sprockets-rails
|
||||
tilt
|
||||
date (3.3.3)
|
||||
debug_inspector (1.1.0)
|
||||
diff-lcs (1.5.0)
|
||||
@ -138,7 +146,7 @@ GEM
|
||||
regexp_parser (~> 2.2)
|
||||
email_reply_trimmer (0.1.13)
|
||||
erubi (1.12.0)
|
||||
excon (0.97.2)
|
||||
excon (0.99.0)
|
||||
execjs (2.8.1)
|
||||
exifr (1.3.10)
|
||||
fabrication (2.30.0)
|
||||
@ -149,7 +157,7 @@ GEM
|
||||
faraday-net_http (>= 2.0, < 3.1)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-net_http (3.0.2)
|
||||
faraday-retry (2.0.0)
|
||||
faraday-retry (2.1.0)
|
||||
faraday (~> 2.0)
|
||||
fast_blank (1.0.1)
|
||||
fast_xs (0.8.0)
|
||||
@ -157,8 +165,13 @@ GEM
|
||||
ffi (1.15.5)
|
||||
fspath (3.1.2)
|
||||
gc_tracer (1.5.1)
|
||||
globalid (1.0.1)
|
||||
globalid (1.1.0)
|
||||
activesupport (>= 5.0)
|
||||
google-protobuf (3.22.2)
|
||||
google-protobuf (3.22.2-aarch64-linux)
|
||||
google-protobuf (3.22.2-arm64-darwin)
|
||||
google-protobuf (3.22.2-x86_64-darwin)
|
||||
google-protobuf (3.22.2-x86_64-linux)
|
||||
guess_html_encoding (0.0.11)
|
||||
hana (1.3.7)
|
||||
hashdiff (1.0.1)
|
||||
@ -169,7 +182,7 @@ GEM
|
||||
http_accept_language (2.1.1)
|
||||
i18n (1.12.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
image_optim (0.31.2)
|
||||
image_optim (0.31.3)
|
||||
exifr (~> 1.2, >= 1.2.2)
|
||||
fspath (~> 3.0)
|
||||
image_size (>= 1.5, < 4)
|
||||
@ -186,7 +199,7 @@ GEM
|
||||
hana (~> 1.3)
|
||||
regexp_parser (~> 2.0)
|
||||
uri_template (~> 0.7)
|
||||
jwt (2.6.0)
|
||||
jwt (2.7.0)
|
||||
kgio (2.11.4)
|
||||
libv8-node (16.10.0.0)
|
||||
libv8-node (16.10.0.0-aarch64-linux)
|
||||
@ -206,7 +219,7 @@ GEM
|
||||
logstash-event (1.2.02)
|
||||
logstash-logger (0.26.1)
|
||||
logstash-event (~> 1.2)
|
||||
logster (2.11.3)
|
||||
logster (2.12.2)
|
||||
loofah (2.19.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
@ -227,10 +240,10 @@ GEM
|
||||
mini_sql (1.4.0)
|
||||
mini_suffix (0.3.3)
|
||||
ffi (~> 1.9)
|
||||
minitest (5.17.0)
|
||||
minitest (5.18.0)
|
||||
mocha (2.0.2)
|
||||
ruby2_keywords (>= 0.0.5)
|
||||
msgpack (1.6.0)
|
||||
msgpack (1.6.1)
|
||||
multi_json (1.15.0)
|
||||
multi_xml (0.6.0)
|
||||
mustache (1.1.1)
|
||||
@ -246,16 +259,16 @@ GEM
|
||||
net-smtp (0.3.3)
|
||||
net-protocol
|
||||
nio4r (2.5.8)
|
||||
nokogiri (1.14.0)
|
||||
nokogiri (1.14.2)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.14.0-aarch64-linux)
|
||||
nokogiri (1.14.2-aarch64-linux)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.14.0-arm64-darwin)
|
||||
nokogiri (1.14.2-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.14.0-x86_64-darwin)
|
||||
nokogiri (1.14.2-x86_64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.14.0-x86_64-linux)
|
||||
nokogiri (1.14.2-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
oauth (1.1.0)
|
||||
oauth-tty (~> 1.0, >= 1.0.1)
|
||||
@ -292,17 +305,17 @@ GEM
|
||||
omniauth-twitter (1.4.0)
|
||||
omniauth-oauth (~> 1.1)
|
||||
rack
|
||||
openssl (3.0.2)
|
||||
openssl-signature_algorithm (1.2.1)
|
||||
openssl (> 2.0, < 3.1)
|
||||
openssl (3.1.0)
|
||||
openssl-signature_algorithm (1.3.0)
|
||||
openssl (> 2.0)
|
||||
optimist (3.0.1)
|
||||
parallel (1.22.1)
|
||||
parallel_tests (4.1.0)
|
||||
parallel_tests (4.2.0)
|
||||
parallel
|
||||
parser (3.2.0.0)
|
||||
parser (3.2.1.1)
|
||||
ast (~> 2.4.1)
|
||||
pg (1.4.5)
|
||||
prettier_print (1.2.0)
|
||||
pg (1.4.6)
|
||||
prettier_print (1.2.1)
|
||||
progress (3.6.0)
|
||||
pry (0.14.2)
|
||||
coderay (~> 1.1)
|
||||
@ -313,16 +326,15 @@ GEM
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (5.0.1)
|
||||
puma (6.0.2)
|
||||
puma (6.1.1)
|
||||
nio4r (~> 2.0)
|
||||
r2 (0.2.7)
|
||||
racc (1.6.2)
|
||||
rack (2.2.6.2)
|
||||
rack (2.2.6.4)
|
||||
rack-mini-profiler (3.0.0)
|
||||
rack (>= 1.2.0)
|
||||
rack-protection (3.0.5)
|
||||
rack
|
||||
rack-test (2.0.2)
|
||||
rack-test (2.1.0)
|
||||
rack (>= 1.3)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
@ -336,15 +348,15 @@ GEM
|
||||
rails_multisite (4.0.1)
|
||||
activerecord (> 5.0, < 7.1)
|
||||
railties (> 5.0, < 7.1)
|
||||
railties (7.0.4.1)
|
||||
actionpack (= 7.0.4.1)
|
||||
activesupport (= 7.0.4.1)
|
||||
railties (7.0.4.3)
|
||||
actionpack (= 7.0.4.3)
|
||||
activesupport (= 7.0.4.3)
|
||||
method_source
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
zeitwerk (~> 2.5)
|
||||
rainbow (3.1.1)
|
||||
raindrops (0.20.0)
|
||||
raindrops (0.20.1)
|
||||
rake (13.0.6)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.10.1)
|
||||
@ -354,10 +366,10 @@ GEM
|
||||
msgpack (>= 0.4.3)
|
||||
optimist (>= 3.0.0)
|
||||
rchardet (1.8.0)
|
||||
redis (4.8.0)
|
||||
redis (4.8.1)
|
||||
redis-namespace (1.10.0)
|
||||
redis (>= 4)
|
||||
regexp_parser (2.6.2)
|
||||
regexp_parser (2.7.0)
|
||||
request_store (1.5.1)
|
||||
rack (>= 1.4)
|
||||
rexml (3.2.5)
|
||||
@ -371,7 +383,7 @@ GEM
|
||||
rspec-core (~> 3.12.0)
|
||||
rspec-expectations (~> 3.12.0)
|
||||
rspec-mocks (~> 3.12.0)
|
||||
rspec-core (3.12.0)
|
||||
rspec-core (3.12.1)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-expectations (3.12.2)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
@ -379,7 +391,7 @@ GEM
|
||||
rspec-html-matchers (0.10.0)
|
||||
nokogiri (~> 1)
|
||||
rspec (>= 3.0.0.a)
|
||||
rspec-mocks (3.12.3)
|
||||
rspec-mocks (3.12.4)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-rails (6.0.1)
|
||||
@ -398,46 +410,50 @@ GEM
|
||||
json-schema (>= 2.2, < 4.0)
|
||||
railties (>= 3.1, < 7.1)
|
||||
rspec-core (>= 2.14)
|
||||
rubocop (1.44.0)
|
||||
rtlcss (0.2.0)
|
||||
mini_racer (~> 0.6.3)
|
||||
rubocop (1.48.1)
|
||||
json (~> 2.3)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.2.0.0)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.24.1, < 2.0)
|
||||
rubocop-ast (>= 1.26.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.24.1)
|
||||
parser (>= 3.1.1.0)
|
||||
rubocop-capybara (2.17.0)
|
||||
rubocop-ast (1.27.0)
|
||||
parser (>= 3.2.1.0)
|
||||
rubocop-capybara (2.17.1)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-discourse (3.0.3)
|
||||
rubocop-discourse (3.2.0)
|
||||
rubocop (>= 1.1.0)
|
||||
rubocop-rspec (>= 2.0.0)
|
||||
rubocop-rspec (2.18.1)
|
||||
rubocop-rspec (2.19.0)
|
||||
rubocop (~> 1.33)
|
||||
rubocop-capybara (~> 2.17)
|
||||
ruby-prof (1.4.5)
|
||||
ruby-progressbar (1.11.0)
|
||||
ruby-prof (1.6.1)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-readability (0.7.0)
|
||||
guess_html_encoding (>= 0.0.4)
|
||||
nokogiri (>= 1.6.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
sanitize (6.0.0)
|
||||
sanitize (6.0.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
sassc (2.0.1)
|
||||
ffi (~> 1.9)
|
||||
rake
|
||||
sassc-rails (2.1.2)
|
||||
railties (>= 4.0.0)
|
||||
sassc (>= 2.0)
|
||||
sprockets (> 3.0)
|
||||
sprockets-rails
|
||||
tilt
|
||||
selenium-webdriver (4.8.0)
|
||||
sass-embedded (1.59.2)
|
||||
google-protobuf (~> 3.21)
|
||||
rake (>= 10.0.0)
|
||||
sass-embedded (1.59.2-aarch64-linux-gnu)
|
||||
google-protobuf (~> 3.21)
|
||||
sass-embedded (1.59.2-arm64-darwin)
|
||||
google-protobuf (~> 3.21)
|
||||
sass-embedded (1.59.2-x86_64-darwin)
|
||||
google-protobuf (~> 3.21)
|
||||
sass-embedded (1.59.2-x86_64-linux-gnu)
|
||||
google-protobuf (~> 3.21)
|
||||
selenium-webdriver (4.8.1)
|
||||
rexml (~> 3.2, >= 3.2.5)
|
||||
rubyzip (>= 1.2.2, < 3.0)
|
||||
websocket (~> 1.0)
|
||||
@ -462,15 +478,17 @@ GEM
|
||||
sprockets (>= 3.0.0)
|
||||
sshkey (2.0.0)
|
||||
stackprof (0.2.23)
|
||||
syntax_tree (5.2.0)
|
||||
syntax_tree (6.0.2)
|
||||
prettier_print (>= 1.2.0)
|
||||
syntax_tree-disable_ternary (1.0.0)
|
||||
test-prof (1.1.0)
|
||||
test-prof (1.2.0)
|
||||
thor (1.2.1)
|
||||
tilt (2.0.11)
|
||||
timeout (0.3.1)
|
||||
tzinfo (2.0.5)
|
||||
tilt (2.1.0)
|
||||
timeout (0.3.2)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
tzinfo-data (1.2022.7)
|
||||
tzinfo (>= 1.0.0)
|
||||
uglifier (4.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unf (0.1.4)
|
||||
@ -502,7 +520,9 @@ GEM
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
yaml-lint (0.1.2)
|
||||
zeitwerk (2.6.6)
|
||||
yard (0.9.28)
|
||||
webrick (~> 1.7.0)
|
||||
zeitwerk (2.6.7)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux
|
||||
@ -514,14 +534,14 @@ PLATFORMS
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
actionmailer (= 7.0.4.1)
|
||||
actionpack (= 7.0.4.1)
|
||||
actionview (= 7.0.4.1)
|
||||
actionmailer (= 7.0.4.3)
|
||||
actionpack (= 7.0.4.3)
|
||||
actionview (= 7.0.4.3)
|
||||
actionview_precompiler
|
||||
active_model_serializers (~> 0.8.3)
|
||||
activemodel (= 7.0.4.1)
|
||||
activerecord (= 7.0.4.1)
|
||||
activesupport (= 7.0.4.1)
|
||||
activemodel (= 7.0.4.3)
|
||||
activerecord (= 7.0.4.3)
|
||||
activesupport (= 7.0.4.3)
|
||||
addressable
|
||||
annotate
|
||||
aws-sdk-s3
|
||||
@ -539,6 +559,8 @@ DEPENDENCIES
|
||||
cose
|
||||
cppjieba_rb
|
||||
css_parser
|
||||
dartsass-ruby
|
||||
dartsass-sprockets
|
||||
diffy
|
||||
digest
|
||||
discourse-fonts
|
||||
@ -600,13 +622,12 @@ DEPENDENCIES
|
||||
pry-byebug
|
||||
pry-rails
|
||||
puma
|
||||
r2
|
||||
rack
|
||||
rack-mini-profiler
|
||||
rack-protection
|
||||
rails_failover
|
||||
rails_multisite
|
||||
railties (= 7.0.4.1)
|
||||
railties (= 7.0.4.3)
|
||||
rake
|
||||
rb-fsevent
|
||||
rbtrace
|
||||
@ -621,13 +642,12 @@ DEPENDENCIES
|
||||
rspec-rails
|
||||
rss
|
||||
rswag-specs
|
||||
rtlcss
|
||||
rubocop-discourse
|
||||
ruby-prof
|
||||
ruby-readability
|
||||
rubyzip
|
||||
sanitize
|
||||
sassc (= 2.0.1)
|
||||
sassc-rails
|
||||
selenium-webdriver
|
||||
shoulda-matchers
|
||||
sidekiq
|
||||
@ -640,6 +660,7 @@ DEPENDENCIES
|
||||
syntax_tree-disable_ternary
|
||||
test-prof
|
||||
thor
|
||||
tzinfo-data
|
||||
uglifier
|
||||
unf
|
||||
unicorn
|
||||
@ -649,6 +670,7 @@ DEPENDENCIES
|
||||
webrick
|
||||
xorcist
|
||||
yaml-lint
|
||||
yard
|
||||
|
||||
BUNDLED WITH
|
||||
2.4.1
|
||||
2.4.4
|
||||
|
||||
@ -30,7 +30,7 @@ To get your environment setup, follow the community setup guide for your operati
|
||||
|
||||
If you're familiar with how Rails works and are comfortable setting up your own environment, you can also try out the [**Discourse Advanced Developer Guide**](docs/DEVELOPER-ADVANCED.md), which is aimed primarily at Ubuntu and macOS environments.
|
||||
|
||||
Before you get started, ensure you have the following minimum versions: [Ruby 3.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!
|
||||
Before you get started, ensure you have the following minimum versions: [Ruby 3.2+](https://www.ruby-lang.org/en/downloads/), [PostgreSQL 13](https://www.postgresql.org/download/), [Redis 7](https://redis.io/download). If you're having trouble, please see our [**TROUBLESHOOTING GUIDE**](docs/TROUBLESHOOTING.md) first!
|
||||
|
||||
## Setting up Discourse
|
||||
|
||||
|
||||
@ -1,31 +1,33 @@
|
||||
import { action } from "@ember/object";
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import { observes, on } from "@ember-decorators/object";
|
||||
import Component from "@ember/component";
|
||||
import getURL from "discourse-common/lib/get-url";
|
||||
import loadScript from "discourse/lib/load-script";
|
||||
import I18n from "I18n";
|
||||
import { bind, observes } from "discourse-common/utils/decorators";
|
||||
import { on } from "@ember/object/evented";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
const COLOR_VARS_REGEX =
|
||||
/\$(primary|secondary|tertiary|quaternary|header_background|header_primary|highlight|danger|success|love)(\s|;|-(low|medium|high))/g;
|
||||
|
||||
export default Component.extend({
|
||||
mode: "css",
|
||||
classNames: ["ace-wrapper"],
|
||||
_editor: null,
|
||||
_skipContentChangeEvent: null,
|
||||
disabled: false,
|
||||
htmlPlaceholder: false,
|
||||
@classNames("ace-wrapper")
|
||||
export default class AceEditor extends Component {
|
||||
mode = "css";
|
||||
disabled = false;
|
||||
htmlPlaceholder = false;
|
||||
_editor = null;
|
||||
_skipContentChangeEvent = null;
|
||||
|
||||
@observes("editorId")
|
||||
editorIdChanged() {
|
||||
if (this.autofocus) {
|
||||
this.send("focus");
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
didRender() {
|
||||
this._skipContentChangeEvent = false;
|
||||
},
|
||||
}
|
||||
|
||||
@observes("content")
|
||||
contentChanged() {
|
||||
@ -33,14 +35,14 @@ export default Component.extend({
|
||||
if (this._editor && !this._skipContentChangeEvent) {
|
||||
this._editor.getSession().setValue(content);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@observes("mode")
|
||||
modeChanged() {
|
||||
if (this._editor && !this._skipContentChangeEvent) {
|
||||
this._editor.getSession().setMode("ace/mode/" + this.mode);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@observes("placeholder")
|
||||
placeholderChanged() {
|
||||
@ -49,12 +51,12 @@ export default Component.extend({
|
||||
placeholder: this.placeholder,
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@observes("disabled")
|
||||
disabledStateChanged() {
|
||||
this.changeDisabledState();
|
||||
},
|
||||
}
|
||||
|
||||
changeDisabledState() {
|
||||
const editor = this._editor;
|
||||
@ -67,9 +69,10 @@ export default Component.extend({
|
||||
});
|
||||
editor.container.parentNode.setAttribute("data-disabled", disabled);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_destroyEditor: on("willDestroyElement", function () {
|
||||
@on("willDestroyElement")
|
||||
_destroyEditor() {
|
||||
if (this._editor) {
|
||||
this._editor.destroy();
|
||||
this._editor = null;
|
||||
@ -80,16 +83,16 @@ export default Component.extend({
|
||||
}
|
||||
|
||||
$(window).off("ace:resize");
|
||||
}),
|
||||
}
|
||||
|
||||
resize() {
|
||||
if (this._editor) {
|
||||
this._editor.resize();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
super.didInsertElement(...arguments);
|
||||
loadScript("/javascripts/ace/ace.js").then(() => {
|
||||
window.ace.require(["ace/ace"], (loadedAce) => {
|
||||
loadedAce.config.set("loadWorkerFromBlob", false);
|
||||
@ -153,13 +156,13 @@ export default Component.extend({
|
||||
this._darkModeListener.addListener(this.setAceTheme);
|
||||
});
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
willDestroyElement() {
|
||||
if (this._darkModeListener) {
|
||||
this._darkModeListener.removeListener(this.setAceTheme);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@bind
|
||||
setAceTheme() {
|
||||
@ -170,7 +173,7 @@ export default Component.extend({
|
||||
this._editor.setTheme(
|
||||
`ace/theme/${schemeType === "dark" ? "chaos" : "chrome"}`
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
warnSCSSDeprecations() {
|
||||
if (
|
||||
@ -197,21 +200,20 @@ export default Component.extend({
|
||||
|
||||
this._editor.getSession().setAnnotations(warnings);
|
||||
|
||||
this.setWarning(
|
||||
this.setWarning?.(
|
||||
warnings.length
|
||||
? I18n.t("admin.customize.theme.scss_color_variables_warning")
|
||||
: false
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
actions: {
|
||||
focus() {
|
||||
if (this._editor) {
|
||||
this._editor.focus();
|
||||
this._editor.navigateFileEnd();
|
||||
}
|
||||
},
|
||||
},
|
||||
@action
|
||||
focus() {
|
||||
if (this._editor) {
|
||||
this._editor.focus();
|
||||
this._editor.navigateFileEnd();
|
||||
}
|
||||
}
|
||||
|
||||
_overridePlaceholder(loadedAce) {
|
||||
const originalPlaceholderSetter =
|
||||
@ -239,5 +241,5 @@ export default Component.extend({
|
||||
|
||||
this.$updatePlaceholder();
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,28 +1,26 @@
|
||||
import { observes, on } from "discourse-common/utils/decorators";
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import { observes, on } from "@ember-decorators/object";
|
||||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import { scheduleOnce } from "@ember/runloop";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ["admin-backups-logs"],
|
||||
showLoadingSpinner: false,
|
||||
hasFormattedLogs: false,
|
||||
noLogsMessage: I18n.t("admin.backups.logs.none"),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this._reset();
|
||||
},
|
||||
@classNames("admin-backups-logs")
|
||||
export default class AdminBackupsLogs extends Component {
|
||||
showLoadingSpinner = false;
|
||||
hasFormattedLogs = false;
|
||||
noLogsMessage = I18n.t("admin.backups.logs.none");
|
||||
formattedLogs = "";
|
||||
index = 0;
|
||||
|
||||
_reset() {
|
||||
this.setProperties({ formattedLogs: "", index: 0 });
|
||||
},
|
||||
}
|
||||
|
||||
_scrollDown() {
|
||||
const div = this.element;
|
||||
div.scrollTop = div.scrollHeight;
|
||||
},
|
||||
}
|
||||
|
||||
@on("init")
|
||||
@observes("logs.[]")
|
||||
@ -31,7 +29,7 @@ export default Component.extend({
|
||||
this._reset(); // reset the cached logs whenever the model is reset
|
||||
this.renderLogs();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_updateFormattedLogsFunc() {
|
||||
const logs = this.logs;
|
||||
@ -55,13 +53,13 @@ export default Component.extend({
|
||||
this.renderLogs();
|
||||
|
||||
scheduleOnce("afterRender", this, this._scrollDown);
|
||||
},
|
||||
}
|
||||
|
||||
@on("init")
|
||||
@observes("logs.[]")
|
||||
_updateFormattedLogs() {
|
||||
discourseDebounce(this, this._updateFormattedLogsFunc, 150);
|
||||
},
|
||||
}
|
||||
|
||||
renderLogs() {
|
||||
const formattedLogs = this.formattedLogs;
|
||||
@ -76,5 +74,5 @@ export default Component.extend({
|
||||
} else {
|
||||
this.set("showLoadingSpinner", false);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,28 +1,22 @@
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default Component.extend({
|
||||
tagName: "",
|
||||
|
||||
buffer: "",
|
||||
editing: false,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.set("editing", false);
|
||||
},
|
||||
@tagName("")
|
||||
export default class AdminEditableField extends Component {
|
||||
buffer = "";
|
||||
editing = false;
|
||||
|
||||
@action
|
||||
edit(event) {
|
||||
event?.preventDefault();
|
||||
this.set("buffer", this.value);
|
||||
this.toggleProperty("editing");
|
||||
},
|
||||
}
|
||||
|
||||
actions: {
|
||||
save() {
|
||||
// Action has to toggle 'editing' property.
|
||||
this.action(this.buffer);
|
||||
},
|
||||
},
|
||||
});
|
||||
@action
|
||||
save() {
|
||||
// Action has to toggle 'editing' property.
|
||||
this.action(this.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
classNames: ["row"],
|
||||
});
|
||||
|
||||
@classNames("row")
|
||||
export default class AdminFormRow extends Component {}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
import loadScript from "discourse/lib/load-script";
|
||||
|
||||
export default Component.extend({
|
||||
tagName: "canvas",
|
||||
type: "line",
|
||||
@tagName("canvas")
|
||||
export default class AdminGraph extends Component {
|
||||
type = "line";
|
||||
|
||||
refreshChart() {
|
||||
const ctx = this.element.getContext("2d");
|
||||
@ -49,11 +50,11 @@ export default Component.extend({
|
||||
};
|
||||
|
||||
this._chart = new window.Chart(ctx, config);
|
||||
},
|
||||
}
|
||||
|
||||
didInsertElement() {
|
||||
loadScript("/javascripts/Chart.min.js").then(() =>
|
||||
this.refreshChart.apply(this)
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
tagName: "",
|
||||
});
|
||||
|
||||
@tagName("")
|
||||
export default class AdminNav extends Component {}
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ["penalty-history"],
|
||||
|
||||
@classNames("penalty-history")
|
||||
export default class AdminPenaltyHistory extends Component {
|
||||
@discourseComputed("user.penalty_counts.suspended")
|
||||
suspendedCountClass(count) {
|
||||
if (count > 0) {
|
||||
return "danger";
|
||||
}
|
||||
return "";
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("user.penalty_counts.silenced")
|
||||
silencedCountClass(count) {
|
||||
@ -18,5 +18,5 @@ export default Component.extend({
|
||||
return "danger";
|
||||
}
|
||||
return "";
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import Component from "@ember/component";
|
||||
import { action } from "@ember/object";
|
||||
import { equal } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import discourseComputed, {
|
||||
afterRender,
|
||||
} from "discourse-common/utils/decorators";
|
||||
@ -7,30 +8,28 @@ import I18n from "I18n";
|
||||
|
||||
const ACTIONS = ["delete", "delete_replies", "edit", "none"];
|
||||
|
||||
export default Component.extend({
|
||||
postId: null,
|
||||
postAction: null,
|
||||
postEdit: null,
|
||||
export default class AdminPenaltyPostAction extends Component {
|
||||
postId = null;
|
||||
postAction = null;
|
||||
postEdit = null;
|
||||
|
||||
@equal("postAction", "edit") editing;
|
||||
@discourseComputed
|
||||
penaltyActions() {
|
||||
return ACTIONS.map((id) => {
|
||||
return { id, name: I18n.t(`admin.user.penalty_post_${id}`) };
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
editing: equal("postAction", "edit"),
|
||||
@action
|
||||
penaltyChanged(postAction) {
|
||||
this.set("postAction", postAction);
|
||||
|
||||
actions: {
|
||||
penaltyChanged(postAction) {
|
||||
this.set("postAction", postAction);
|
||||
|
||||
// If we switch to edit mode, jump to the edit textarea
|
||||
if (postAction === "edit") {
|
||||
this._focusEditTextarea();
|
||||
}
|
||||
},
|
||||
},
|
||||
// If we switch to edit mode, jump to the edit textarea
|
||||
if (postAction === "edit") {
|
||||
this._focusEditTextarea();
|
||||
}
|
||||
}
|
||||
|
||||
@afterRender
|
||||
_focusEditTextarea() {
|
||||
@ -38,5 +37,5 @@ export default Component.extend({
|
||||
const body = elem.closest(".modal-body");
|
||||
body.scrollTo(0, body.clientHeight);
|
||||
elem.querySelector(".post-editor").focus();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,43 +1,46 @@
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import { equal } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import { action } from "@ember/object";
|
||||
import { equal } from "@ember/object/computed";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import I18n from "I18n";
|
||||
|
||||
const CUSTOM_REASON_KEY = "custom";
|
||||
|
||||
export default Component.extend({
|
||||
tagName: "",
|
||||
selectedReason: CUSTOM_REASON_KEY,
|
||||
customReason: "",
|
||||
reasonKeys: [
|
||||
@tagName("")
|
||||
export default class AdminPenaltyReason extends Component {
|
||||
selectedReason = CUSTOM_REASON_KEY;
|
||||
customReason = "";
|
||||
|
||||
reasonKeys = [
|
||||
"not_listening_to_staff",
|
||||
"consuming_staff_time",
|
||||
"combative",
|
||||
"in_wrong_place",
|
||||
"no_constructive_purpose",
|
||||
CUSTOM_REASON_KEY,
|
||||
],
|
||||
isCustomReason: equal("selectedReason", CUSTOM_REASON_KEY),
|
||||
];
|
||||
|
||||
@equal("selectedReason", CUSTOM_REASON_KEY) isCustomReason;
|
||||
|
||||
@discourseComputed("reasonKeys")
|
||||
reasons(keys) {
|
||||
return keys.map((key) => {
|
||||
return { id: key, name: I18n.t(`admin.user.suspend_reasons.${key}`) };
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
setSelectedReason(value) {
|
||||
this.set("selectedReason", value);
|
||||
this.setReason();
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
setCustomReason(value) {
|
||||
this.set("customReason", value);
|
||||
this.setReason();
|
||||
},
|
||||
}
|
||||
|
||||
setReason() {
|
||||
if (this.isCustomReason) {
|
||||
@ -48,5 +51,5 @@ export default Component.extend({
|
||||
I18n.t(`admin.user.suspend_reasons.${this.selectedReason}`)
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
import { action } from "@ember/object";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
export default Component.extend({
|
||||
tagName: "",
|
||||
|
||||
@tagName("")
|
||||
export default class AdminPenaltySimilarUsers extends Component {
|
||||
@discourseComputed("penaltyType")
|
||||
penaltyField(penaltyType) {
|
||||
if (penaltyType === "suspend") {
|
||||
@ -12,7 +12,7 @@ export default Component.extend({
|
||||
} else if (penaltyType === "silence") {
|
||||
return "can_be_silenced";
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
selectUserId(userId, event) {
|
||||
@ -25,5 +25,5 @@ export default Component.extend({
|
||||
} else {
|
||||
this.selectedUserIds.removeObject(userId);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import Report from "admin/models/report";
|
||||
import Component from "@ember/component";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
@ -7,31 +8,31 @@ import { number } from "discourse/lib/formatter";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ["admin-report-chart"],
|
||||
limit: 8,
|
||||
total: 0,
|
||||
options: null,
|
||||
@classNames("admin-report-chart")
|
||||
export default class AdminReportChart extends Component {
|
||||
limit = 8;
|
||||
total = 0;
|
||||
options = null;
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
super.didInsertElement(...arguments);
|
||||
|
||||
window.addEventListener("resize", this._resizeHandler);
|
||||
},
|
||||
}
|
||||
|
||||
willDestroyElement() {
|
||||
this._super(...arguments);
|
||||
super.willDestroyElement(...arguments);
|
||||
|
||||
window.removeEventListener("resize", this._resizeHandler);
|
||||
|
||||
this._resetChart();
|
||||
},
|
||||
}
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
super.didReceiveAttrs(...arguments);
|
||||
|
||||
discourseDebounce(this, this._scheduleChartRendering, 100);
|
||||
},
|
||||
}
|
||||
|
||||
_scheduleChartRendering() {
|
||||
schedule("afterRender", () => {
|
||||
@ -40,7 +41,7 @@ export default Component.extend({
|
||||
this.element && this.element.querySelector(".chart-canvas")
|
||||
);
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
_renderChart(model, chartCanvas) {
|
||||
if (!chartCanvas) {
|
||||
@ -99,7 +100,7 @@ export default Component.extend({
|
||||
this._buildChartConfig(data, this.options)
|
||||
);
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
_buildChartConfig(data, options) {
|
||||
return {
|
||||
@ -161,21 +162,21 @@ export default Component.extend({
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
_resetChart() {
|
||||
if (this._chart) {
|
||||
this._chart.destroy();
|
||||
this._chart = null;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_applyChartGrouping(model, data, options) {
|
||||
return Report.collapse(model, data, options.chartGrouping);
|
||||
},
|
||||
}
|
||||
|
||||
@bind
|
||||
_resizeHandler() {
|
||||
discourseDebounce(this, this._scheduleChartRendering, 500);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { attributeBindings, classNames } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
classNames: ["admin-report-counters"],
|
||||
|
||||
attributeBindings: ["model.description:title"],
|
||||
});
|
||||
@classNames("admin-report-counters")
|
||||
@attributeBindings("model.description:title")
|
||||
export default class AdminReportCounters extends Component {}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import Component from "@ember/component";
|
||||
import { classNameBindings, tagName } from "@ember-decorators/component";
|
||||
import { match } from "@ember/object/computed";
|
||||
export default Component.extend({
|
||||
allTime: true,
|
||||
tagName: "tr",
|
||||
reverseColors: match(
|
||||
"report.type",
|
||||
/^(time_to_first_response|topics_with_no_response)$/
|
||||
),
|
||||
classNameBindings: ["reverseColors"],
|
||||
});
|
||||
import Component from "@ember/component";
|
||||
|
||||
@tagName("tr")
|
||||
@classNameBindings("reverseColors")
|
||||
export default class AdminReportCounts extends Component {
|
||||
allTime = true;
|
||||
|
||||
@match("report.type", /^(time_to_first_response|topics_with_no_response)$/)
|
||||
reverseColors;
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
classNames: ["admin-report-inline-table"],
|
||||
});
|
||||
|
||||
@classNames("admin-report-inline-table")
|
||||
export default class AdminReportInlineTable extends Component {}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
tagName: "tr",
|
||||
});
|
||||
|
||||
@tagName("tr")
|
||||
export default class AdminReportPerDayCounts extends Component {}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import Report from "admin/models/report";
|
||||
import Component from "@ember/component";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
@ -7,32 +8,31 @@ import { number } from "discourse/lib/formatter";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ["admin-report-chart", "admin-report-stacked-chart"],
|
||||
|
||||
@classNames("admin-report-chart", "admin-report-stacked-chart")
|
||||
export default class AdminReportStackedChart extends Component {
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
super.didInsertElement(...arguments);
|
||||
|
||||
window.addEventListener("resize", this._resizeHandler);
|
||||
},
|
||||
}
|
||||
|
||||
willDestroyElement() {
|
||||
this._super(...arguments);
|
||||
super.willDestroyElement(...arguments);
|
||||
|
||||
window.removeEventListener("resize", this._resizeHandler);
|
||||
this._resetChart();
|
||||
},
|
||||
}
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
super.didReceiveAttrs(...arguments);
|
||||
|
||||
discourseDebounce(this, this._scheduleChartRendering, 100);
|
||||
},
|
||||
}
|
||||
|
||||
@bind
|
||||
_resizeHandler() {
|
||||
discourseDebounce(this, this._scheduleChartRendering, 500);
|
||||
},
|
||||
}
|
||||
|
||||
_scheduleChartRendering() {
|
||||
schedule("afterRender", () => {
|
||||
@ -45,7 +45,7 @@ export default Component.extend({
|
||||
this.element.querySelector(".chart-canvas")
|
||||
);
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
_renderChart(model, chartCanvas) {
|
||||
if (!chartCanvas) {
|
||||
@ -79,7 +79,7 @@ export default Component.extend({
|
||||
|
||||
this._chart = new window.Chart(context, this._buildChartConfig(data));
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
_buildChartConfig(data) {
|
||||
return {
|
||||
@ -150,10 +150,10 @@ export default Component.extend({
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
_resetChart() {
|
||||
this._chart?.destroy();
|
||||
this._chart = null;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,43 +1,45 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { setting } from "discourse/lib/computed";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ["admin-report-storage-stats"],
|
||||
@classNames("admin-report-storage-stats")
|
||||
export default class AdminReportStorageStats extends Component {
|
||||
@setting("backup_location") backupLocation;
|
||||
|
||||
backupLocation: setting("backup_location"),
|
||||
backupStats: alias("model.data.backups"),
|
||||
uploadStats: alias("model.data.uploads"),
|
||||
@alias("model.data.backups") backupStats;
|
||||
|
||||
@alias("model.data.uploads") uploadStats;
|
||||
|
||||
@discourseComputed("backupStats")
|
||||
showBackupStats(stats) {
|
||||
return stats && this.currentUser.admin;
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("backupLocation")
|
||||
backupLocationName(backupLocation) {
|
||||
return I18n.t(`admin.backups.location.${backupLocation}`);
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("backupStats.used_bytes")
|
||||
usedBackupSpace(bytes) {
|
||||
return I18n.toHumanSize(bytes);
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("backupStats.free_bytes")
|
||||
freeBackupSpace(bytes) {
|
||||
return I18n.toHumanSize(bytes);
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("uploadStats.used_bytes")
|
||||
usedUploadSpace(bytes) {
|
||||
return I18n.toHumanSize(bytes);
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("uploadStats.free_bytes")
|
||||
freeUploadSpace(bytes) {
|
||||
return I18n.toHumanSize(bytes);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,21 +1,27 @@
|
||||
import Component from "@ember/component";
|
||||
import {
|
||||
attributeBindings,
|
||||
classNameBindings,
|
||||
classNames,
|
||||
tagName,
|
||||
} from "@ember-decorators/component";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
export default Component.extend({
|
||||
tagName: "td",
|
||||
classNames: ["admin-report-table-cell"],
|
||||
classNameBindings: ["type", "property"],
|
||||
attributeBindings: ["value:title"],
|
||||
options: null,
|
||||
@tagName("td")
|
||||
@classNames("admin-report-table-cell")
|
||||
@classNameBindings("type", "property")
|
||||
@attributeBindings("value:title")
|
||||
export default class AdminReportTableCell extends Component {
|
||||
options = null;
|
||||
|
||||
@alias("label.type") type;
|
||||
@alias("label.mainProperty") property;
|
||||
@alias("computedLabel.formattedValue") formattedValue;
|
||||
@alias("computedLabel.value") value;
|
||||
|
||||
@discourseComputed("label", "data", "options")
|
||||
computedLabel(label, data, options) {
|
||||
return label.compute(data, options || {});
|
||||
},
|
||||
|
||||
type: alias("label.type"),
|
||||
property: alias("label.mainProperty"),
|
||||
formattedValue: alias("computedLabel.formattedValue"),
|
||||
value: alias("computedLabel.value"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,19 +1,24 @@
|
||||
import {
|
||||
attributeBindings,
|
||||
classNameBindings,
|
||||
classNames,
|
||||
tagName,
|
||||
} from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
export default Component.extend({
|
||||
tagName: "th",
|
||||
classNames: ["admin-report-table-header"],
|
||||
classNameBindings: ["label.mainProperty", "label.type", "isCurrentSort"],
|
||||
attributeBindings: ["label.title:title"],
|
||||
|
||||
@tagName("th")
|
||||
@classNames("admin-report-table-header")
|
||||
@classNameBindings("label.mainProperty", "label.type", "isCurrentSort")
|
||||
@attributeBindings("label.title:title")
|
||||
export default class AdminReportTableHeader extends Component {
|
||||
@discourseComputed("currentSortLabel.sortProperty", "label.sortProperty")
|
||||
isCurrentSort(currentSortField, labelSortField) {
|
||||
return currentSortField === labelSortField;
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("currentSortDirection")
|
||||
sortIcon(currentSortDirection) {
|
||||
return currentSortDirection === 1 ? "caret-up" : "caret-down";
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { classNames, tagName } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
tagName: "tr",
|
||||
classNames: ["admin-report-table-row"],
|
||||
options: null,
|
||||
});
|
||||
|
||||
@tagName("tr")
|
||||
@classNames("admin-report-table-row")
|
||||
export default class AdminReportTableRow extends Component {
|
||||
options = null;
|
||||
}
|
||||
|
||||
@ -1,22 +1,26 @@
|
||||
import Component from "@ember/component";
|
||||
import { action } from "@ember/object";
|
||||
import { classNameBindings, classNames } from "@ember-decorators/component";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { makeArray } from "discourse-common/lib/helpers";
|
||||
|
||||
const PAGES_LIMIT = 8;
|
||||
|
||||
export default Component.extend({
|
||||
classNameBindings: ["sortable", "twoColumns"],
|
||||
classNames: ["admin-report-table"],
|
||||
sortable: false,
|
||||
sortDirection: 1,
|
||||
perPage: alias("options.perPage"),
|
||||
page: 0,
|
||||
@classNameBindings("sortable", "twoColumns")
|
||||
@classNames("admin-report-table")
|
||||
export default class AdminReportTable extends Component {
|
||||
sortable = false;
|
||||
sortDirection = 1;
|
||||
|
||||
@alias("options.perPage") perPage;
|
||||
|
||||
page = 0;
|
||||
|
||||
@discourseComputed("model.computedLabels.length")
|
||||
twoColumns(labelsLength) {
|
||||
return labelsLength === 2;
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed(
|
||||
"totalsForSample",
|
||||
@ -31,12 +35,12 @@ export default Component.extend({
|
||||
.reduce((s, v) => s + v, 0);
|
||||
|
||||
return sum >= 1 && total && datesFiltering;
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("model.total", "options.total", "twoColumns")
|
||||
showTotal(reportTotal, total, twoColumns) {
|
||||
return reportTotal && total && twoColumns;
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed(
|
||||
"model.{average,data}",
|
||||
@ -50,17 +54,17 @@ export default Component.extend({
|
||||
sampleTotalValue &&
|
||||
hasTwoColumns
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("totalsForSample.1.value", "model.data.length")
|
||||
averageForSample(totals, count) {
|
||||
return (totals / count).toFixed(0);
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("model.data.length")
|
||||
showSortingUI(dataLength) {
|
||||
return dataLength >= 5;
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("totalsForSampleRow", "model.computedLabels")
|
||||
totalsForSample(row, labels) {
|
||||
@ -70,7 +74,7 @@ export default Component.extend({
|
||||
computedLabel.property = label.mainProperty;
|
||||
return computedLabel;
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("model.data", "model.computedLabels")
|
||||
totalsForSampleRow(rows, labels) {
|
||||
@ -98,7 +102,7 @@ export default Component.extend({
|
||||
});
|
||||
|
||||
return totalsRow;
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("sortLabel", "sortDirection", "model.data.[]")
|
||||
sortedData(sortLabel, sortDirection, data) {
|
||||
@ -118,7 +122,7 @@ export default Component.extend({
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("sortedData.[]", "perPage", "page")
|
||||
paginatedData(data, perPage, page) {
|
||||
@ -128,7 +132,7 @@ export default Component.extend({
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("model.data", "perPage", "page")
|
||||
pages(data, perPage, page) {
|
||||
@ -156,19 +160,19 @@ export default Component.extend({
|
||||
}
|
||||
|
||||
return pages;
|
||||
},
|
||||
}
|
||||
|
||||
actions: {
|
||||
changePage(page) {
|
||||
this.set("page", page);
|
||||
},
|
||||
@action
|
||||
changePage(page) {
|
||||
this.set("page", page);
|
||||
}
|
||||
|
||||
sortByLabel(label) {
|
||||
if (this.sortLabel === label) {
|
||||
this.set("sortDirection", this.sortDirection === 1 ? -1 : 1);
|
||||
} else {
|
||||
this.set("sortLabel", label);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
@action
|
||||
sortByLabel(label) {
|
||||
if (this.sortLabel === label) {
|
||||
this.set("sortDirection", this.sortDirection === 1 ? -1 : 1);
|
||||
} else {
|
||||
this.set("sortLabel", label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
tagName: "tr",
|
||||
});
|
||||
|
||||
@tagName("tr")
|
||||
export default class AdminReportTrustLevelCounts extends Component {}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { classNameBindings, classNames } from "@ember-decorators/component";
|
||||
import { alias, and, equal, notEmpty, or } from "@ember/object/computed";
|
||||
import EmberObject, { action, computed } from "@ember/object";
|
||||
import Report, { DAILY_LIMIT_DAYS, SCHEMA_VERSION } from "admin/models/report";
|
||||
import { alias, and, equal, notEmpty, or } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import ReportLoader from "discourse/lib/reports-loader";
|
||||
@ -21,51 +22,58 @@ const TABLE_OPTIONS = {
|
||||
|
||||
const CHART_OPTIONS = {};
|
||||
|
||||
export default Component.extend({
|
||||
classNameBindings: [
|
||||
"isHidden:hidden",
|
||||
"isHidden::is-visible",
|
||||
"isEnabled",
|
||||
"isLoading",
|
||||
"dasherizedDataSourceName",
|
||||
],
|
||||
classNames: ["admin-report"],
|
||||
isEnabled: true,
|
||||
disabledLabel: I18n.t("admin.dashboard.disabled"),
|
||||
isLoading: false,
|
||||
rateLimitationString: null,
|
||||
dataSourceName: null,
|
||||
report: null,
|
||||
model: null,
|
||||
reportOptions: null,
|
||||
forcedModes: null,
|
||||
showAllReportsLink: false,
|
||||
filters: null,
|
||||
showTrend: false,
|
||||
showHeader: true,
|
||||
showTitle: true,
|
||||
showFilteringUI: false,
|
||||
showDatesOptions: alias("model.dates_filtering"),
|
||||
showRefresh: or("showDatesOptions", "model.available_filters.length"),
|
||||
shouldDisplayTrend: and("showTrend", "model.prev_period"),
|
||||
endDate: null,
|
||||
startDate: null,
|
||||
@classNameBindings(
|
||||
"isHidden:hidden",
|
||||
"isHidden::is-visible",
|
||||
"isEnabled",
|
||||
"isLoading",
|
||||
"dasherizedDataSourceName"
|
||||
)
|
||||
@classNames("admin-report")
|
||||
export default class AdminReport extends Component {
|
||||
isEnabled = true;
|
||||
disabledLabel = I18n.t("admin.dashboard.disabled");
|
||||
isLoading = false;
|
||||
rateLimitationString = null;
|
||||
dataSourceName = null;
|
||||
report = null;
|
||||
model = null;
|
||||
reportOptions = null;
|
||||
forcedModes = null;
|
||||
showAllReportsLink = false;
|
||||
filters = null;
|
||||
showTrend = false;
|
||||
showHeader = true;
|
||||
showTitle = true;
|
||||
showFilteringUI = false;
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
@alias("model.dates_filtering") showDatesOptions;
|
||||
|
||||
this._reports = [];
|
||||
},
|
||||
@or("showDatesOptions", "model.available_filters.length") showRefresh;
|
||||
|
||||
isHidden: computed("siteSettings.dashboard_hidden_reports", function () {
|
||||
@and("showTrend", "model.prev_period") shouldDisplayTrend;
|
||||
|
||||
endDate = null;
|
||||
startDate = null;
|
||||
|
||||
@or("showTimeoutError", "showExceptionError", "showNotFoundError") showError;
|
||||
@equal("model.error", "not_found") showNotFoundError;
|
||||
@equal("model.error", "timeout") showTimeoutError;
|
||||
@equal("model.error", "exception") showExceptionError;
|
||||
@notEmpty("model.data") hasData;
|
||||
|
||||
_reports = [];
|
||||
|
||||
@computed("siteSettings.dashboard_hidden_reports")
|
||||
get isHidden() {
|
||||
return (this.siteSettings.dashboard_hidden_reports || "")
|
||||
.split("|")
|
||||
.filter(Boolean)
|
||||
.includes(this.dataSourceName);
|
||||
}),
|
||||
}
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
super.didReceiveAttrs(...arguments);
|
||||
|
||||
let startDate = moment();
|
||||
if (this.filters && isPresent(this.filters.startDate)) {
|
||||
@ -88,42 +96,35 @@ export default Component.extend({
|
||||
} else if (this.dataSourceName) {
|
||||
this._fetchReport();
|
||||
}
|
||||
},
|
||||
|
||||
showError: or("showTimeoutError", "showExceptionError", "showNotFoundError"),
|
||||
showNotFoundError: equal("model.error", "not_found"),
|
||||
showTimeoutError: equal("model.error", "timeout"),
|
||||
showExceptionError: equal("model.error", "exception"),
|
||||
|
||||
hasData: notEmpty("model.data"),
|
||||
}
|
||||
|
||||
@discourseComputed("dataSourceName", "model.type")
|
||||
dasherizedDataSourceName(dataSourceName, type) {
|
||||
return (dataSourceName || type || "undefined").replace(/_/g, "-");
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("dataSourceName", "model.type")
|
||||
dataSource(dataSourceName, type) {
|
||||
dataSourceName = dataSourceName || type;
|
||||
return `/admin/reports/${dataSourceName}`;
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("displayedModes.length")
|
||||
showModes(displayedModesLength) {
|
||||
return displayedModesLength > 1;
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("currentMode")
|
||||
isChartMode(currentMode) {
|
||||
return currentMode === "chart";
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
changeGrouping(grouping) {
|
||||
this.send("refreshReport", {
|
||||
chartGrouping: grouping,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("currentMode", "model.modes", "forcedModes")
|
||||
displayedModes(currentMode, reportModes, forcedModes) {
|
||||
@ -139,12 +140,12 @@ export default Component.extend({
|
||||
icon: mode === "table" ? "table" : "signal",
|
||||
};
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("currentMode")
|
||||
modeComponent(currentMode) {
|
||||
return `admin-report-${currentMode.replace(/_/g, "-")}`;
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed(
|
||||
"dataSourceName",
|
||||
@ -178,7 +179,7 @@ export default Component.extend({
|
||||
.join(":");
|
||||
|
||||
return reportKey;
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("options.chartGrouping", "model.chartData.length")
|
||||
chartGroupings(grouping, count) {
|
||||
@ -192,7 +193,7 @@ export default Component.extend({
|
||||
class: `chart-grouping ${grouping === id ? "active" : "inactive"}`,
|
||||
};
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
onChangeDateRange(range) {
|
||||
@ -200,7 +201,7 @@ export default Component.extend({
|
||||
startDate: range.from,
|
||||
endDate: range.to,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
applyFilter(id, value) {
|
||||
@ -215,7 +216,7 @@ export default Component.extend({
|
||||
this.send("refreshReport", {
|
||||
filters: customFilters,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
refreshReport(options = {}) {
|
||||
@ -238,7 +239,7 @@ export default Component.extend({
|
||||
? this.get("filters.customFilters")
|
||||
: options.filters,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
exportCsv() {
|
||||
@ -254,7 +255,7 @@ export default Component.extend({
|
||||
}
|
||||
|
||||
exportEntity("report", args).then(outputExportResult);
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
onChangeMode(mode) {
|
||||
@ -263,7 +264,7 @@ export default Component.extend({
|
||||
this.send("refreshReport", {
|
||||
chartGrouping: null,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
_computeReport() {
|
||||
if (!this.element || this.isDestroying || this.isDestroyed) {
|
||||
@ -306,7 +307,7 @@ export default Component.extend({
|
||||
}
|
||||
|
||||
this._renderReport(report, this.forcedModes, this.currentMode);
|
||||
},
|
||||
}
|
||||
|
||||
_renderReport(report, forcedModes, currentMode) {
|
||||
const modes = forcedModes ? forcedModes.split(",") : report.modes;
|
||||
@ -317,11 +318,9 @@ export default Component.extend({
|
||||
currentMode,
|
||||
options: this._buildOptions(currentMode, report),
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
_fetchReport() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.setProperties({ isLoading: true, rateLimitationString: null });
|
||||
|
||||
next(() => {
|
||||
@ -349,7 +348,7 @@ export default Component.extend({
|
||||
|
||||
ReportLoader.enqueue(this.dataSourceName, payload.data, callback);
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
_buildPayload(facets) {
|
||||
let payload = { data: { facets } };
|
||||
@ -375,7 +374,7 @@ export default Component.extend({
|
||||
}
|
||||
|
||||
return payload;
|
||||
},
|
||||
}
|
||||
|
||||
_buildOptions(mode, report) {
|
||||
if (mode === "table") {
|
||||
@ -393,7 +392,7 @@ export default Component.extend({
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_loadReport(jsonReport) {
|
||||
Report.fillMissingDates(jsonReport, { filledField: "chartData" });
|
||||
@ -423,5 +422,5 @@ export default Component.extend({
|
||||
}
|
||||
|
||||
return Report.create(jsonReport);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@
|
||||
@checked={{this.onlyOverridden}}
|
||||
{{on
|
||||
"click"
|
||||
(action "onlyOverriddenChanged" value="target.checked")
|
||||
(action this.onlyOverriddenChanged value="target.checked")
|
||||
}}
|
||||
/>
|
||||
{{i18n "admin.customize.theme.hide_unused_fields"}}
|
||||
@ -125,6 +125,6 @@
|
||||
@autofocus="true"
|
||||
@placeholder={{this.placeholder}}
|
||||
@htmlPlaceholder={{true}}
|
||||
@save={{action "save"}}
|
||||
@save={{this.save}}
|
||||
@setWarning={{action "setWarning"}}
|
||||
/>
|
||||
@ -3,11 +3,13 @@ import I18n from "I18n";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { fmt } from "discourse/lib/computed";
|
||||
import { isDocumentRTL } from "discourse/lib/text-direction";
|
||||
import { action } from "@ember/object";
|
||||
import { action, computed } from "@ember/object";
|
||||
import { next } from "@ember/runloop";
|
||||
|
||||
export default Component.extend({
|
||||
warning: null,
|
||||
export default class AdminThemeEditor extends Component {
|
||||
warning = null;
|
||||
|
||||
@fmt("fieldName", "currentTargetName", "%@|%@") editorId;
|
||||
|
||||
@discourseComputed("theme.targets", "onlyOverridden", "showAdvanced")
|
||||
visibleTargets(targets, onlyOverridden, showAdvanced) {
|
||||
@ -20,7 +22,7 @@ export default Component.extend({
|
||||
}
|
||||
return target.edited;
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("currentTargetName", "onlyOverridden", "theme.fields")
|
||||
visibleFields(targetName, onlyOverridden, fields) {
|
||||
@ -29,7 +31,7 @@ export default Component.extend({
|
||||
fields = fields.filter((field) => field.edited);
|
||||
}
|
||||
return fields;
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("currentTargetName", "fieldName")
|
||||
activeSectionMode(targetName, fieldName) {
|
||||
@ -43,7 +45,7 @@ export default Component.extend({
|
||||
return "scss";
|
||||
}
|
||||
return fieldName && fieldName.includes("scss") ? "scss" : "html";
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("currentTargetName", "fieldName")
|
||||
placeholder(targetName, fieldName) {
|
||||
@ -58,30 +60,27 @@ export default Component.extend({
|
||||
});
|
||||
}
|
||||
return "";
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("fieldName", "currentTargetName", "theme")
|
||||
activeSection: {
|
||||
get(fieldName, target, model) {
|
||||
return model.getField(target, fieldName);
|
||||
},
|
||||
set(value, fieldName, target, model) {
|
||||
model.setField(target, fieldName, value);
|
||||
return value;
|
||||
},
|
||||
},
|
||||
@computed("fieldName", "currentTargetName", "theme")
|
||||
get activeSection() {
|
||||
return this.theme.getField(this.currentTargetName, this.fieldName);
|
||||
}
|
||||
|
||||
editorId: fmt("fieldName", "currentTargetName", "%@|%@"),
|
||||
set activeSection(value) {
|
||||
this.theme.setField(this.currentTargetName, this.fieldName, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@discourseComputed("maximized")
|
||||
maximizeIcon(maximized) {
|
||||
return maximized ? "discourse-compress" : "discourse-expand";
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("currentTargetName", "theme.targets")
|
||||
showAddField(currentTargetName, targets) {
|
||||
return targets.find((t) => t.name === currentTargetName).customNames;
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed(
|
||||
"currentTargetName",
|
||||
@ -90,52 +89,45 @@ export default Component.extend({
|
||||
)
|
||||
error(target, fieldName) {
|
||||
return this.theme.getError(target, fieldName);
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
toggleShowAdvanced(event) {
|
||||
event?.preventDefault();
|
||||
this.toggleProperty("showAdvanced");
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
toggleAddField(event) {
|
||||
event?.preventDefault();
|
||||
this.toggleProperty("addingField");
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
toggleMaximize(event) {
|
||||
event?.preventDefault();
|
||||
this.toggleProperty("maximized");
|
||||
next(() => this.appEvents.trigger("ace:resize"));
|
||||
},
|
||||
}
|
||||
|
||||
actions: {
|
||||
cancelAddField() {
|
||||
this.set("addingField", false);
|
||||
},
|
||||
@action
|
||||
cancelAddField() {
|
||||
this.set("addingField", false);
|
||||
}
|
||||
|
||||
addField(name) {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
name = name.replace(/[^a-zA-Z0-9-_/]/g, "");
|
||||
this.theme.setField(this.currentTargetName, name, "");
|
||||
this.setProperties({ newFieldName: "", addingField: false });
|
||||
this.fieldAdded(this.currentTargetName, name);
|
||||
},
|
||||
@action
|
||||
addField(name) {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
name = name.replace(/[^a-zA-Z0-9-_/]/g, "");
|
||||
this.theme.setField(this.currentTargetName, name, "");
|
||||
this.setProperties({ newFieldName: "", addingField: false });
|
||||
this.fieldAdded(this.currentTargetName, name);
|
||||
}
|
||||
|
||||
onlyOverriddenChanged(value) {
|
||||
this.onlyOverriddenChanged(value);
|
||||
},
|
||||
|
||||
save() {
|
||||
this.attrs.save();
|
||||
},
|
||||
|
||||
setWarning(message) {
|
||||
this.set("warning", message);
|
||||
},
|
||||
},
|
||||
});
|
||||
@action
|
||||
setWarning(message) {
|
||||
this.set("warning", message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,23 +1,27 @@
|
||||
import Component from "@ember/component";
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { alias, equal } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { action } from "@ember/object";
|
||||
import I18n from "I18n";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ["watched-word"],
|
||||
dialog: service(),
|
||||
@classNames("watched-word")
|
||||
export default class AdminWatchedWord extends Component {
|
||||
@service dialog;
|
||||
|
||||
isReplace: equal("actionKey", "replace"),
|
||||
isTag: equal("actionKey", "tag"),
|
||||
isLink: equal("actionKey", "link"),
|
||||
isCaseSensitive: alias("word.case_sensitive"),
|
||||
@equal("actionKey", "replace") isReplace;
|
||||
|
||||
@equal("actionKey", "tag") isTag;
|
||||
|
||||
@equal("actionKey", "link") isLink;
|
||||
|
||||
@alias("word.case_sensitive") isCaseSensitive;
|
||||
|
||||
@discourseComputed("word.replacement")
|
||||
tags(replacement) {
|
||||
return replacement.split(",");
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
deleteWord() {
|
||||
@ -33,5 +37,5 @@ export default Component.extend({
|
||||
})
|
||||
);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
|
||||
export default class AdminWrapper extends Component {
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
super.didInsertElement(...arguments);
|
||||
document.querySelector("html").classList.add("admin-area");
|
||||
document.querySelector("body").classList.add("admin-interface");
|
||||
},
|
||||
}
|
||||
|
||||
willDestroyElement() {
|
||||
this._super(...arguments);
|
||||
super.willDestroyElement(...arguments);
|
||||
document.querySelector("html").classList.remove("admin-area");
|
||||
document.querySelector("body").classList.remove("admin-interface");
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
tagName: "",
|
||||
});
|
||||
|
||||
@tagName("")
|
||||
export default class CancelLink extends Component {}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import { action, computed } from "@ember/object";
|
||||
import Component from "@ember/component";
|
||||
import { observes } from "discourse-common/utils/decorators";
|
||||
import { observes } from "@ember-decorators/object";
|
||||
|
||||
/**
|
||||
An input field for a color.
|
||||
@ -9,20 +10,20 @@ import { observes } from "discourse-common/utils/decorators";
|
||||
@param brightnessValue is a number from 0 to 255 representing the brightness of the color. See ColorSchemeColor.
|
||||
@params valid is a boolean indicating if the input field is a valid color.
|
||||
**/
|
||||
export default Component.extend({
|
||||
classNames: ["color-picker"],
|
||||
@classNames("color-picker")
|
||||
export default class ColorInput extends Component {
|
||||
onlyHex = true;
|
||||
styleSelection = true;
|
||||
|
||||
onlyHex: true,
|
||||
|
||||
styleSelection: true,
|
||||
|
||||
maxlength: computed("onlyHex", function () {
|
||||
@computed("onlyHex")
|
||||
get maxlength() {
|
||||
return this.onlyHex ? 6 : null;
|
||||
}),
|
||||
}
|
||||
|
||||
normalizedHexValue: computed("hexValue", function () {
|
||||
@computed("hexValue")
|
||||
get normalizedHexValue() {
|
||||
return this.normalize(this.hexValue);
|
||||
}),
|
||||
}
|
||||
|
||||
normalize(color) {
|
||||
if (this._valid(color)) {
|
||||
@ -40,19 +41,19 @@ export default Component.extend({
|
||||
}
|
||||
}
|
||||
return color;
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
onHexInput(color) {
|
||||
if (this.attrs.onChangeColor) {
|
||||
this.attrs.onChangeColor(this.normalize(color || ""));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
onPickerInput(event) {
|
||||
this.set("hexValue", event.target.value.replace("#", ""));
|
||||
},
|
||||
}
|
||||
|
||||
@observes("hexValue", "brightnessValue", "valid")
|
||||
hexValueChanged() {
|
||||
@ -65,9 +66,9 @@ export default Component.extend({
|
||||
if (this._valid()) {
|
||||
this.element.querySelector(".picker").value = this.normalize(hex);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_valid(color = this.hexValue) {
|
||||
return /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(color);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default Component.extend({});
|
||||
export default class DashboardNewFeatureItem extends Component {}
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
import { classNameBindings, classNames } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
import { action, computed } from "@ember/object";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
|
||||
export default Component.extend({
|
||||
newFeatures: null,
|
||||
classNames: ["section", "dashboard-new-features"],
|
||||
classNameBindings: ["hasUnseenFeatures:ordered-first"],
|
||||
releaseNotesLink: null,
|
||||
@classNames("section", "dashboard-new-features")
|
||||
@classNameBindings("hasUnseenFeatures:ordered-first")
|
||||
export default class DashboardNewFeatures extends Component {
|
||||
newFeatures = null;
|
||||
releaseNotesLink = null;
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
ajax("/admin/dashboard/new-features.json").then((json) => {
|
||||
if (!this.element || this.isDestroying || this.isDestroyed) {
|
||||
if (this.isDestroying || this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -22,16 +23,17 @@ export default Component.extend({
|
||||
releaseNotesLink: json.release_notes_link,
|
||||
});
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
columnCountClass: computed("newFeatures", function () {
|
||||
@computed("newFeatures")
|
||||
get columnCountClass() {
|
||||
return this.newFeatures.length > 2 ? "three-or-more-items" : "";
|
||||
}),
|
||||
}
|
||||
|
||||
@action
|
||||
dismissNewFeatures() {
|
||||
ajax("/admin/dashboard/mark-new-features-as-seen.json", {
|
||||
type: "PUT",
|
||||
}).then(() => this.set("hasUnseenFeatures", false));
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default Component.extend({});
|
||||
export default class DashboardProblems extends Component {}
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
@content={{this.editorContents}}
|
||||
@mode={{this.currentEditorMode}}
|
||||
@editorId={{this.editorId}}
|
||||
@save={{action "save"}}
|
||||
@save={{@save}}
|
||||
/>
|
||||
|
||||
<div class="admin-footer">
|
||||
@ -1,17 +1,19 @@
|
||||
import { action, computed } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { reads } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { reads } from "@ember/object/computed";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default Component.extend({
|
||||
dialog: service(),
|
||||
editorId: reads("fieldName"),
|
||||
export default class EmailStylesEditor extends Component {
|
||||
@service dialog;
|
||||
|
||||
@reads("fieldName") editorId;
|
||||
|
||||
@discourseComputed("fieldName")
|
||||
currentEditorMode(fieldName) {
|
||||
return fieldName === "css" ? "scss" : fieldName;
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("fieldName", "styles.html", "styles.css")
|
||||
resetDisabled(fieldName) {
|
||||
@ -19,36 +21,31 @@ export default Component.extend({
|
||||
this.get(`styles.${fieldName}`) ===
|
||||
this.get(`styles.default_${fieldName}`)
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("styles", "fieldName")
|
||||
editorContents: {
|
||||
get(styles, fieldName) {
|
||||
return styles[fieldName];
|
||||
},
|
||||
set(value, styles, fieldName) {
|
||||
styles.setField(fieldName, value);
|
||||
return value;
|
||||
},
|
||||
},
|
||||
@computed("styles", "fieldName")
|
||||
get editorContents() {
|
||||
return this.styles[this.fieldName];
|
||||
}
|
||||
|
||||
actions: {
|
||||
reset() {
|
||||
this.dialog.yesNoConfirm({
|
||||
message: I18n.t("admin.customize.email_style.reset_confirm", {
|
||||
fieldName: I18n.t(`admin.customize.email_style.${this.fieldName}`),
|
||||
}),
|
||||
didConfirm: () => {
|
||||
this.styles.setField(
|
||||
this.fieldName,
|
||||
this.styles.get(`default_${this.fieldName}`)
|
||||
);
|
||||
this.notifyPropertyChange("editorContents");
|
||||
},
|
||||
});
|
||||
},
|
||||
save() {
|
||||
this.attrs.save();
|
||||
},
|
||||
},
|
||||
});
|
||||
set editorContents(value) {
|
||||
this.styles.setField(this.fieldName, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@action
|
||||
reset() {
|
||||
this.dialog.yesNoConfirm({
|
||||
message: I18n.t("admin.customize.email_style.reset_confirm", {
|
||||
fieldName: I18n.t(`admin.customize.email_style.${this.fieldName}`),
|
||||
}),
|
||||
didConfirm: () => {
|
||||
this.styles.setField(
|
||||
this.fieldName,
|
||||
this.styles.get(`default_${this.fieldName}`)
|
||||
);
|
||||
this.notifyPropertyChange("editorContents");
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,15 +9,6 @@
|
||||
autofocus={{true}}
|
||||
/>
|
||||
</td>
|
||||
<td class="editing-input">
|
||||
<div class="label">{{i18n "admin.embedding.class_name"}}</div>
|
||||
<Input
|
||||
@value={{this.buffered.class_name}}
|
||||
placeholder="class"
|
||||
@enter={{action "save"}}
|
||||
class="class-name"
|
||||
/>
|
||||
</td>
|
||||
<td class="editing-input">
|
||||
<div class="label">{{i18n "admin.embedding.allowed_paths"}}</div>
|
||||
<Input
|
||||
@ -54,12 +45,6 @@
|
||||
<div class="label">{{i18n "admin.embedding.host"}}</div>
|
||||
{{this.host.host}}
|
||||
</td>
|
||||
<td>
|
||||
<div class="label">
|
||||
{{i18n "admin.embedding.class_name"}}
|
||||
</div>
|
||||
{{this.host.class_name}}
|
||||
</td>
|
||||
<td>
|
||||
<div class="label">
|
||||
{{i18n "admin.embedding.allowed_paths"}}
|
||||
@ -1,85 +1,91 @@
|
||||
import { action } from "@ember/object";
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { or } from "@ember/object/computed";
|
||||
import Category from "discourse/models/category";
|
||||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import { bufferedProperty } from "discourse/mixins/buffered-content";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import { or } from "@ember/object/computed";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
|
||||
export default Component.extend(bufferedProperty("host"), {
|
||||
editToggled: false,
|
||||
tagName: "tr",
|
||||
categoryId: null,
|
||||
category: null,
|
||||
dialog: service(),
|
||||
@tagName("tr")
|
||||
export default class EmbeddableHost extends Component.extend(
|
||||
bufferedProperty("host")
|
||||
) {
|
||||
@service dialog;
|
||||
editToggled = false;
|
||||
categoryId = null;
|
||||
category = null;
|
||||
|
||||
editing: or("host.isNew", "editToggled"),
|
||||
@or("host.isNew", "editToggled") editing;
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
super.init(...arguments);
|
||||
|
||||
const host = this.host;
|
||||
const categoryId = host.category_id || this.site.uncategorized_category_id;
|
||||
const category = Category.findById(categoryId);
|
||||
|
||||
host.set("category", category);
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("buffered.host", "host.isSaving")
|
||||
cantSave(host, isSaving) {
|
||||
return isSaving || isEmpty(host);
|
||||
},
|
||||
}
|
||||
|
||||
actions: {
|
||||
edit() {
|
||||
this.set("categoryId", this.get("host.category.id"));
|
||||
this.set("editToggled", true);
|
||||
},
|
||||
@action
|
||||
edit() {
|
||||
this.set("categoryId", this.get("host.category.id"));
|
||||
this.set("editToggled", true);
|
||||
}
|
||||
|
||||
save() {
|
||||
if (this.cantSave) {
|
||||
return;
|
||||
}
|
||||
@action
|
||||
save() {
|
||||
if (this.cantSave) {
|
||||
return;
|
||||
}
|
||||
|
||||
const props = this.buffered.getProperties(
|
||||
"host",
|
||||
"allowed_paths",
|
||||
"class_name"
|
||||
);
|
||||
props.category_id = this.categoryId;
|
||||
const props = this.buffered.getProperties(
|
||||
"host",
|
||||
"allowed_paths",
|
||||
"class_name"
|
||||
);
|
||||
props.category_id = this.categoryId;
|
||||
|
||||
const host = this.host;
|
||||
const host = this.host;
|
||||
|
||||
host
|
||||
.save(props)
|
||||
.then(() => {
|
||||
host.set("category", Category.findById(this.categoryId));
|
||||
this.set("editToggled", false);
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
|
||||
delete() {
|
||||
return this.dialog.confirm({
|
||||
message: I18n.t("admin.embedding.confirm_delete"),
|
||||
didConfirm: () => {
|
||||
return this.host.destroyRecord().then(() => {
|
||||
this.deleteHost(this.host);
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
cancel() {
|
||||
const host = this.host;
|
||||
if (host.get("isNew")) {
|
||||
this.deleteHost(host);
|
||||
} else {
|
||||
this.rollbackBuffer();
|
||||
host
|
||||
.save(props)
|
||||
.then(() => {
|
||||
host.set("category", Category.findById(this.categoryId));
|
||||
this.set("editToggled", false);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
}
|
||||
|
||||
@action
|
||||
delete() {
|
||||
return this.dialog.confirm({
|
||||
message: I18n.t("admin.embedding.confirm_delete"),
|
||||
didConfirm: () => {
|
||||
return this.host.destroyRecord().then(() => {
|
||||
this.deleteHost(this.host);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
cancel() {
|
||||
const host = this.host;
|
||||
if (host.get("isNew")) {
|
||||
this.deleteHost(host);
|
||||
} else {
|
||||
this.rollbackBuffer();
|
||||
this.set("editToggled", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,33 +1,33 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import { computed } from "@ember/object";
|
||||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { dasherize } from "@ember/string";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ["embed-setting"],
|
||||
|
||||
@classNames("embed-setting")
|
||||
export default class EmbeddingSetting extends Component {
|
||||
@discourseComputed("field")
|
||||
inputId(field) {
|
||||
return dasherize(field);
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("field")
|
||||
translationKey(field) {
|
||||
return `admin.embedding.${field}`;
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("type")
|
||||
isCheckbox(type) {
|
||||
return type === "checkbox";
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("value")
|
||||
checked: {
|
||||
get(value) {
|
||||
return !!value;
|
||||
},
|
||||
set(value) {
|
||||
this.set("value", value);
|
||||
return value;
|
||||
},
|
||||
},
|
||||
});
|
||||
@computed("value")
|
||||
get checked() {
|
||||
return !!this.value;
|
||||
}
|
||||
|
||||
set(value) {
|
||||
this.set("value", value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { classNameBindings } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
@ -6,12 +7,12 @@ import { action, set, setProperties } from "@ember/object";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import discourseLater from "discourse-common/lib/later";
|
||||
|
||||
export default Component.extend({
|
||||
classNameBindings: [":value-list", ":emoji-list"],
|
||||
values: null,
|
||||
validationMessage: null,
|
||||
emojiPickerIsActive: false,
|
||||
isEditorFocused: false,
|
||||
@classNameBindings(":value-list", ":emoji-list")
|
||||
export default class EmojiValueList extends Component {
|
||||
values = null;
|
||||
validationMessage = null;
|
||||
emojiPickerIsActive = false;
|
||||
isEditorFocused = false;
|
||||
|
||||
@discourseComputed("values")
|
||||
collection(values) {
|
||||
@ -28,14 +29,14 @@ export default Component.extend({
|
||||
emojiUrl: emojiUrlFor(value),
|
||||
};
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
closeEmojiPicker() {
|
||||
this.collection.setEach("isEditing", false);
|
||||
this.set("emojiPickerIsActive", false);
|
||||
this.set("isEditorFocused", false);
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
emojiSelected(code) {
|
||||
@ -65,12 +66,12 @@ export default Component.extend({
|
||||
|
||||
this.set("emojiPickerIsActive", false);
|
||||
this.set("isEditorFocused", false);
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("collection")
|
||||
showUpDownButtons(collection) {
|
||||
return collection.length - 1 ? true : false;
|
||||
},
|
||||
}
|
||||
|
||||
_splitValues(values) {
|
||||
if (values && values.length) {
|
||||
@ -91,7 +92,7 @@ export default Component.extend({
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
editValue(index) {
|
||||
@ -111,12 +112,12 @@ export default Component.extend({
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
removeValue(value) {
|
||||
this._removeValue(value);
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
shift(operation, index) {
|
||||
@ -133,7 +134,7 @@ export default Component.extend({
|
||||
this.collection.insertAt(futureIndex, shiftedEmoji);
|
||||
|
||||
this._saveValues();
|
||||
},
|
||||
}
|
||||
|
||||
_validateInput(input) {
|
||||
this.set("validationMessage", null);
|
||||
@ -147,12 +148,12 @@ export default Component.extend({
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
}
|
||||
|
||||
_removeValue(value) {
|
||||
this.collection.removeObject(value);
|
||||
this._saveValues();
|
||||
},
|
||||
}
|
||||
|
||||
_replaceValue(index, newValue) {
|
||||
const item = this.collection[index];
|
||||
@ -161,9 +162,9 @@ export default Component.extend({
|
||||
}
|
||||
set(item, "value", newValue);
|
||||
this._saveValues();
|
||||
},
|
||||
}
|
||||
|
||||
_saveValues() {
|
||||
this.set("values", this.collection.mapBy("value").join("|"));
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
classNames: ["flag-user-lists"],
|
||||
});
|
||||
|
||||
@classNames("flag-user-lists")
|
||||
export default class FlagUserLists extends Component {}
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default Component.extend({});
|
||||
export default class FlagUser extends Component {}
|
||||
|
||||
@ -0,0 +1,71 @@
|
||||
<div class="form-templates__form">
|
||||
<div class="control-group">
|
||||
<label for="template-name">
|
||||
{{i18n "admin.form_templates.new_template_form.name.label"}}
|
||||
</label>
|
||||
<TextField
|
||||
@value={{this.templateName}}
|
||||
@name="template-name"
|
||||
@class="form-templates__form-name-input"
|
||||
@placeholderKey="admin.form_templates.new_template_form.name.placeholder"
|
||||
/>
|
||||
</div>
|
||||
<div class="control-group form-templates__editor">
|
||||
<div class="form-templates__quick-insert-field-buttons">
|
||||
<span>
|
||||
{{I18n "admin.form_templates.quick_insert_fields.add_new_field"}}
|
||||
</span>
|
||||
{{#each this.quickInsertFields as |field|}}
|
||||
<DButton
|
||||
@class="btn-flat btn-icon-text quick-insert-{{field.type}}"
|
||||
@icon={{field.icon}}
|
||||
@label="admin.form_templates.quick_insert_fields.{{field.type}}"
|
||||
@action={{this.onInsertField}}
|
||||
@actionParam={{field.type}}
|
||||
/>
|
||||
{{/each}}
|
||||
<DButton
|
||||
class="btn-flat btn-icon-text form-templates__validations-modal-button"
|
||||
@label="admin.form_templates.validations_modal.button_title"
|
||||
@icon="check-circle"
|
||||
@action={{this.showValidationOptionsModal}}
|
||||
/>
|
||||
</div>
|
||||
<DButton
|
||||
@class="form-templates__preview-button"
|
||||
@icon="eye"
|
||||
@label="admin.form_templates.new_template_form.preview"
|
||||
@action={{this.showPreview}}
|
||||
@disabled={{this.disablePreviewButton}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<AceEditor @content={{this.templateContent}} @mode="yaml" />
|
||||
</div>
|
||||
|
||||
<div class="footer-buttons">
|
||||
<DButton
|
||||
@class="btn-primary"
|
||||
@label="admin.form_templates.new_template_form.submit"
|
||||
@icon="check"
|
||||
@action={{this.onSubmit}}
|
||||
@disabled={{this.disableSubmitButton}}
|
||||
/>
|
||||
|
||||
<DButton
|
||||
@label="admin.form_templates.new_template_form.cancel"
|
||||
@icon="times"
|
||||
@action={{this.onCancel}}
|
||||
/>
|
||||
|
||||
{{#if this.isEditing}}
|
||||
<DButton
|
||||
@class="btn-danger"
|
||||
@label="admin.form_templates.view_template.delete"
|
||||
@icon="trash-alt"
|
||||
@action={{this.onDelete}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,140 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import I18n from "I18n";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { templateFormFields } from "admin/lib/template-form-fields";
|
||||
import FormTemplate from "admin/models/form-template";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
|
||||
export default class FormTemplateForm extends Component {
|
||||
@service router;
|
||||
@service dialog;
|
||||
@tracked formSubmitted = false;
|
||||
@tracked templateContent = this.args.model?.template || "";
|
||||
@tracked templateName = this.args.model?.name || "";
|
||||
isEditing = this.args.model?.id ? true : false;
|
||||
quickInsertFields = [
|
||||
{
|
||||
type: "checkbox",
|
||||
icon: "check-square",
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
icon: "grip-lines",
|
||||
},
|
||||
{
|
||||
type: "textarea",
|
||||
icon: "align-left",
|
||||
},
|
||||
{
|
||||
type: "dropdown",
|
||||
icon: "chevron-circle-down",
|
||||
},
|
||||
{
|
||||
type: "upload",
|
||||
icon: "cloud-upload-alt",
|
||||
},
|
||||
{
|
||||
type: "multiselect",
|
||||
icon: "bullseye",
|
||||
},
|
||||
];
|
||||
|
||||
get disablePreviewButton() {
|
||||
return Boolean(!this.templateName.length || !this.templateContent.length);
|
||||
}
|
||||
|
||||
get disableSubmitButton() {
|
||||
return (
|
||||
Boolean(!this.templateName.length || !this.templateContent.length) ||
|
||||
this.formSubmitted
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
onSubmit() {
|
||||
if (!this.formSubmitted) {
|
||||
this.formSubmitted = true;
|
||||
}
|
||||
|
||||
const postData = {
|
||||
name: this.templateName,
|
||||
template: this.templateContent,
|
||||
};
|
||||
|
||||
if (this.isEditing) {
|
||||
postData["id"] = this.args.model.id;
|
||||
}
|
||||
|
||||
FormTemplate.createOrUpdateTemplate(postData)
|
||||
.then(() => {
|
||||
this.formSubmitted = false;
|
||||
this.router.transitionTo("adminCustomizeFormTemplates.index");
|
||||
})
|
||||
.catch((e) => {
|
||||
popupAjaxError(e);
|
||||
this.formSubmitted = false;
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
onCancel() {
|
||||
this.router.transitionTo("adminCustomizeFormTemplates.index");
|
||||
}
|
||||
|
||||
@action
|
||||
onDelete() {
|
||||
return this.dialog.yesNoConfirm({
|
||||
message: I18n.t("admin.form_templates.delete_confirm"),
|
||||
didConfirm: () => {
|
||||
FormTemplate.deleteTemplate(this.args.model.id)
|
||||
.then(() => {
|
||||
this.router.transitionTo("adminCustomizeFormTemplates.index");
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
onInsertField(type) {
|
||||
const structure = templateFormFields.findBy("type", type).structure;
|
||||
|
||||
if (this.templateContent.length === 0) {
|
||||
this.templateContent += structure;
|
||||
} else {
|
||||
this.templateContent += `\n${structure}`;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
showValidationOptionsModal() {
|
||||
return showModal("admin-form-template-validation-options", {
|
||||
admin: true,
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
showPreview() {
|
||||
const data = {
|
||||
name: this.templateName,
|
||||
template: this.templateContent,
|
||||
};
|
||||
|
||||
if (this.isEditing) {
|
||||
data["id"] = this.args.model.id;
|
||||
}
|
||||
|
||||
FormTemplate.validateTemplate(data)
|
||||
.then(() => {
|
||||
return showModal("form-template-form-preview", {
|
||||
model: {
|
||||
content: this.templateContent,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
<div class="form-templates--info">
|
||||
<h2>{{i18n "admin.form_templates.title"}}</h2>
|
||||
<p class="desc">{{i18n "admin.form_templates.help"}}</p>
|
||||
</div>
|
||||
@ -0,0 +1,28 @@
|
||||
<tr class="admin-list-item">
|
||||
<td class="col first">{{@template.name}}</td>
|
||||
<td class="col categories">
|
||||
{{#each this.activeCategories as |category|}}
|
||||
{{category-link category}}
|
||||
{{/each}}
|
||||
</td>
|
||||
<td class="col action">
|
||||
<DButton
|
||||
@title="admin.form_templates.list_table.actions.view"
|
||||
@icon="far-eye"
|
||||
@class="btn-view-template"
|
||||
@action={{this.viewTemplate}}
|
||||
/>
|
||||
<DButton
|
||||
@title="admin.form_templates.list_table.actions.edit"
|
||||
@icon="pencil-alt"
|
||||
@class="btn-edit-template"
|
||||
@action={{this.editTemplate}}
|
||||
/>
|
||||
<DButton
|
||||
@title="admin.form_templates.list_table.actions.delete"
|
||||
@icon="far-trash-alt"
|
||||
@class="btn-danger btn-delete-template"
|
||||
@action={{this.deleteTemplate}}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@ -0,0 +1,53 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import I18n from "I18n";
|
||||
|
||||
export default class FormTemplateRowItem extends Component {
|
||||
@service router;
|
||||
@service dialog;
|
||||
@service site;
|
||||
|
||||
get activeCategories() {
|
||||
return this.site?.categories?.filter((c) =>
|
||||
c["form_template_ids"].includes(this.args.template.id)
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
viewTemplate() {
|
||||
showModal("customize-form-template-view", {
|
||||
model: this.args.template,
|
||||
refreshModel: this.args.refreshModel,
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
editTemplate() {
|
||||
this.router.transitionTo(
|
||||
"adminCustomizeFormTemplates.edit",
|
||||
this.args.template
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
deleteTemplate() {
|
||||
return this.dialog.yesNoConfirm({
|
||||
message: I18n.t("admin.form_templates.delete_confirm", {
|
||||
template_name: this.args.template.name,
|
||||
}),
|
||||
didConfirm: () => {
|
||||
ajax(`/admin/customize/form-templates/${this.args.template.id}.json`, {
|
||||
type: "DELETE",
|
||||
})
|
||||
.then(() => {
|
||||
this.args.refreshModel();
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
import { observes, on } from "discourse-common/utils/decorators";
|
||||
import { observes, on } from "@ember-decorators/object";
|
||||
import Component from "@ember/component";
|
||||
import highlightSyntax from "discourse/lib/highlight-syntax";
|
||||
|
||||
export default Component.extend({
|
||||
export default class HighlightedCode extends Component {
|
||||
@on("didInsertElement")
|
||||
@observes("code")
|
||||
_refresh() {
|
||||
highlightSyntax(this.element, this.siteSettings, this.session);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
import { action } from "@ember/object";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ["inline-edit"],
|
||||
|
||||
buffer: null,
|
||||
bufferModelId: null,
|
||||
@classNames("inline-edit")
|
||||
export default class InlineEditCheckbox extends Component {
|
||||
buffer = null;
|
||||
bufferModelId = null;
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
super.didReceiveAttrs(...arguments);
|
||||
|
||||
if (this.modelId !== this.bufferModelId) {
|
||||
// HACK: The condition above ensures this method is called only when its
|
||||
@ -24,21 +24,21 @@ export default Component.extend({
|
||||
bufferModelId: this.modelId,
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@discourseComputed("checked", "buffer")
|
||||
changed(checked, buffer) {
|
||||
return !!checked !== !!buffer;
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
apply() {
|
||||
this.set("checked", this.buffer);
|
||||
this.action();
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
cancel() {
|
||||
this.set("buffer", this.checked);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
classNames: ["install-theme-item"],
|
||||
});
|
||||
|
||||
@classNames("install-theme-item")
|
||||
export default class InstallThemeItem extends Component {}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import { inject as service } from "@ember/service";
|
||||
import AdminUser from "admin/models/admin-user";
|
||||
import Component from "@ember/component";
|
||||
import EmberObject, { action } from "@ember/object";
|
||||
@ -6,12 +8,11 @@ import { ajax } from "discourse/lib/ajax";
|
||||
import copyText from "discourse/lib/copy-text";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import discourseLater from "discourse-common/lib/later";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ["ip-lookup"],
|
||||
dialog: service(),
|
||||
@classNames("ip-lookup")
|
||||
export default class IpLookup extends Component {
|
||||
@service dialog;
|
||||
|
||||
@discourseComputed("other_accounts.length", "totalOthersWithSameIP")
|
||||
otherAccountsToDelete(otherAccountsLength, totalOthersWithSameIP) {
|
||||
@ -19,101 +20,100 @@ export default Component.extend({
|
||||
const total = Math.min(50, totalOthersWithSameIP || 0);
|
||||
const visible = Math.min(50, otherAccountsLength || 0);
|
||||
return Math.max(visible, total);
|
||||
},
|
||||
}
|
||||
|
||||
@action
|
||||
hide(event) {
|
||||
event?.preventDefault();
|
||||
this.set("show", false);
|
||||
},
|
||||
}
|
||||
|
||||
actions: {
|
||||
lookup() {
|
||||
this.set("show", true);
|
||||
@action
|
||||
lookup() {
|
||||
this.set("show", true);
|
||||
|
||||
if (!this.location) {
|
||||
ajax("/admin/users/ip-info", {
|
||||
data: { ip: this.ip },
|
||||
}).then((location) =>
|
||||
this.set("location", EmberObject.create(location))
|
||||
);
|
||||
}
|
||||
if (!this.location) {
|
||||
ajax("/admin/users/ip-info", {
|
||||
data: { ip: this.ip },
|
||||
}).then((location) => this.set("location", EmberObject.create(location)));
|
||||
}
|
||||
|
||||
if (!this.other_accounts) {
|
||||
this.set("otherAccountsLoading", true);
|
||||
if (!this.other_accounts) {
|
||||
this.set("otherAccountsLoading", true);
|
||||
|
||||
const data = {
|
||||
ip: this.ip,
|
||||
exclude: this.userId,
|
||||
order: "trust_level DESC",
|
||||
};
|
||||
const data = {
|
||||
ip: this.ip,
|
||||
exclude: this.userId,
|
||||
order: "trust_level DESC",
|
||||
};
|
||||
|
||||
ajax("/admin/users/total-others-with-same-ip", {
|
||||
data,
|
||||
}).then((result) => this.set("totalOthersWithSameIP", result.total));
|
||||
ajax("/admin/users/total-others-with-same-ip", {
|
||||
data,
|
||||
}).then((result) => this.set("totalOthersWithSameIP", result.total));
|
||||
|
||||
AdminUser.findAll("active", data).then((users) => {
|
||||
this.setProperties({
|
||||
other_accounts: users,
|
||||
otherAccountsLoading: false,
|
||||
});
|
||||
AdminUser.findAll("active", data).then((users) => {
|
||||
this.setProperties({
|
||||
other_accounts: users,
|
||||
otherAccountsLoading: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
copy() {
|
||||
let text = `IP: ${this.ip}\n`;
|
||||
const location = this.location;
|
||||
if (location) {
|
||||
if (location.hostname) {
|
||||
text += `${I18n.t("ip_lookup.hostname")}: ${location.hostname}\n`;
|
||||
}
|
||||
|
||||
text += I18n.t("ip_lookup.location");
|
||||
if (location.location) {
|
||||
text += `: ${location.location}\n`;
|
||||
} else {
|
||||
text += `: ${I18n.t("ip_lookup.location_not_found")}\n`;
|
||||
}
|
||||
|
||||
if (location.organization) {
|
||||
text += I18n.t("ip_lookup.organisation");
|
||||
text += `: ${location.organization}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
const $copyRange = $('<p id="copy-range"></p>');
|
||||
$copyRange.html(text.trim().replace(/\n/g, "<br>"));
|
||||
$(document.body).append($copyRange);
|
||||
if (copyText(text, $copyRange[0])) {
|
||||
this.set("copied", true);
|
||||
discourseLater(() => this.set("copied", false), 2000);
|
||||
}
|
||||
$copyRange.remove();
|
||||
},
|
||||
|
||||
deleteOtherAccounts() {
|
||||
this.dialog.yesNoConfirm({
|
||||
message: I18n.t("ip_lookup.confirm_delete_other_accounts"),
|
||||
didConfirm: () => {
|
||||
this.setProperties({
|
||||
other_accounts: null,
|
||||
otherAccountsLoading: true,
|
||||
totalOthersWithSameIP: null,
|
||||
});
|
||||
|
||||
ajax("/admin/users/delete-others-with-same-ip.json", {
|
||||
type: "DELETE",
|
||||
data: {
|
||||
ip: this.ip,
|
||||
exclude: this.userId,
|
||||
order: "trust_level DESC",
|
||||
},
|
||||
})
|
||||
.catch(popupAjaxError)
|
||||
.finally(this.send("lookup"));
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
copy() {
|
||||
let text = `IP: ${this.ip}\n`;
|
||||
const location = this.location;
|
||||
if (location) {
|
||||
if (location.hostname) {
|
||||
text += `${I18n.t("ip_lookup.hostname")}: ${location.hostname}\n`;
|
||||
}
|
||||
|
||||
text += I18n.t("ip_lookup.location");
|
||||
if (location.location) {
|
||||
text += `: ${location.location}\n`;
|
||||
} else {
|
||||
text += `: ${I18n.t("ip_lookup.location_not_found")}\n`;
|
||||
}
|
||||
|
||||
if (location.organization) {
|
||||
text += I18n.t("ip_lookup.organisation");
|
||||
text += `: ${location.organization}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
const $copyRange = $('<p id="copy-range"></p>');
|
||||
$copyRange.html(text.trim().replace(/\n/g, "<br>"));
|
||||
$(document.body).append($copyRange);
|
||||
if (copyText(text, $copyRange[0])) {
|
||||
this.set("copied", true);
|
||||
discourseLater(() => this.set("copied", false), 2000);
|
||||
}
|
||||
$copyRange.remove();
|
||||
}
|
||||
|
||||
@action
|
||||
deleteOtherAccounts() {
|
||||
this.dialog.yesNoConfirm({
|
||||
message: I18n.t("ip_lookup.confirm_delete_other_accounts"),
|
||||
didConfirm: () => {
|
||||
this.setProperties({
|
||||
other_accounts: null,
|
||||
otherAccountsLoading: true,
|
||||
totalOthersWithSameIP: null,
|
||||
});
|
||||
|
||||
ajax("/admin/users/delete-others-with-same-ip.json", {
|
||||
type: "DELETE",
|
||||
data: {
|
||||
ip: this.ip,
|
||||
exclude: this.userId,
|
||||
order: "trust_level DESC",
|
||||
},
|
||||
})
|
||||
.catch(popupAjaxError)
|
||||
.finally(this.send("lookup"));
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
tagName: "tr",
|
||||
});
|
||||
|
||||
@tagName("tr")
|
||||
export default class ModerationHistoryItem extends Component {}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user