diff --git a/.eslintrc b/.eslintrc
index 4aee50ed2a..1c3b2b261c 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -2,7 +2,8 @@
"extends": "eslint-config-discourse",
"rules": {
"discourse-ember/global-ember": 2,
- "eol-last": 2
+ "eol-last": 2,
+ "no-restricted-globals": 0
},
"globals": {
"_": "off",
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index ab85796b3c..ea69b83263 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -53,5 +53,8 @@ ce3fe2f4c4ddf166949ee3cec3d9ecbf9108ab52
# DEV: Tidy up imports. (#11364)
1c2358ba162eb9f9ba9095c9afe30cf51dd85e04
-# DEV: Sort imports alphabetically (#11382)
+# DEV: Sort imports alphabetically (#11382)
bbe5d8d5cf1220165842985c0e2cd4c454d501cd
+
+# DEV: Template colocation for sidebar files
+95c7cdab941a56686ac5831d2a5c5eca38d780c5
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index aefab9fadb..a0f06bf462 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,42 +1,50 @@
version: 2
updates:
-- package-ecosystem: "github-actions"
- directory: "/"
- schedule:
- interval: "weekly"
-- package-ecosystem: bundler
- directory: "/"
- schedule:
- interval: daily
- time: "08:00"
- timezone: Australia/Sydney
- open-pull-requests-limit: 10
- versioning-strategy: lockfile-only
- allow:
- - dependency-type: direct
- - dependency-type: indirect
- ignore:
- - dependency-name: aws-partitions
- versions:
- - "> 1.329.0"
- - "< 2"
- - dependency-name: aws-sdk-core
- versions:
- - "> 3.99.1"
- - "< 4"
- - dependency-name: aws-sdk-kms
- versions:
- - "> 1.31.0"
- - "< 2"
- - dependency-name: aws-sdk-s3
- versions:
- - "> 1.66.0"
- - "< 2"
- - dependency-name: aws-sdk-sns
- versions:
- - "> 1.25.1"
- - "< 2"
- - dependency-name: aws-sigv4
- versions:
- - "> 1.2.0"
- - "< 2"
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ - package-ecosystem: bundler
+ directory: "/"
+ schedule:
+ interval: daily
+ time: "08:00"
+ timezone: Australia/Sydney
+ open-pull-requests-limit: 10
+ versioning-strategy: lockfile-only
+ allow:
+ - dependency-type: direct
+ - dependency-type: indirect
+ ignore:
+ - dependency-name: aws-partitions
+ versions:
+ - "> 1.329.0"
+ - "< 2"
+ - dependency-name: aws-sdk-core
+ versions:
+ - "> 3.99.1"
+ - "< 4"
+ - dependency-name: aws-sdk-kms
+ versions:
+ - "> 1.31.0"
+ - "< 2"
+ - dependency-name: aws-sdk-s3
+ versions:
+ - "> 1.66.0"
+ - "< 2"
+ - dependency-name: aws-sdk-sns
+ versions:
+ - "> 1.25.1"
+ - "< 2"
+ - dependency-name: aws-sigv4
+ versions:
+ - "> 1.2.0"
+ - "< 2"
+ - package-ecosystem: "npm"
+ directory: "/app/assets/javascripts/"
+ schedule:
+ interval: daily
+ time: "08:00"
+ timezone: Australia/Sydney
+ open-pull-requests-limit: 20
+ versioning-strategy: increase
diff --git a/.github/workflows/licenses.yml b/.github/workflows/licenses.yml
index 755852add2..adefc3998c 100644
--- a/.github/workflows/licenses.yml
+++ b/.github/workflows/licenses.yml
@@ -67,7 +67,7 @@ jobs:
${{ runner.os }}-yarn-
- name: Check RubyGems Licenses
- if: ${{ always() }}
+ if: ${{ !cancelled() }}
run: |
licensed cache
licensed status
@@ -76,14 +76,14 @@ jobs:
run: yarn install
- name: Check Yarn Licenses
- if: ${{ always() }}
+ if: ${{ !cancelled() }}
run: |
yarn global add licensee
yarn global upgrade licensee
licensee --errors-only
- name: Check Ember CLI Workspace Licenses
- if: ${{ always() }}
+ if: ${{ !cancelled() }}
working-directory: ./app/assets/javascripts
run: |
licensee --errors-only
diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml
index 3ce7b5d5f3..59379f06af 100644
--- a/.github/workflows/linting.yml
+++ b/.github/workflows/linting.yml
@@ -64,19 +64,19 @@ jobs:
run: yarn install
- name: Rubocop
- if: ${{ always() }}
+ if: ${{ !cancelled() }}
run: bundle exec rubocop --parallel .
- name: ESLint (core)
- if: ${{ always() }}
+ if: ${{ !cancelled() }}
run: yarn eslint app/assets/javascripts
- name: ESLint (core plugins)
- if: ${{ always() }}
+ if: ${{ !cancelled() }}
run: yarn eslint plugins
- name: Prettier
- if: ${{ always() }}
+ if: ${{ !cancelled() }}
run: |
yarn prettier -v
yarn pprettier --list-different \
@@ -86,7 +86,7 @@ jobs:
"plugins/**/assets/javascripts/**/*.js"
- name: Ember template lint
- if: ${{ always() }}
+ if: ${{ !cancelled() }}
run: |
yarn ember-template-lint \
--no-error-on-unmatched-pattern \
@@ -94,9 +94,9 @@ jobs:
"plugins/**/assets/javascripts/**/*.hbs"
- name: English locale lint (core)
- if: ${{ always() }}
+ if: ${{ !cancelled() }}
run: bundle exec ruby script/i18n_lint.rb "config/**/locales/{client,server}.en.yml"
- name: English locale lint (core plugins)
- if: ${{ always() }}
+ if: ${{ !cancelled() }}
run: bundle exec ruby script/i18n_lint.rb "plugins/**/locales/{client,server}.en.yml"
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index a2c9f200b8..f1356bf73b 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -18,9 +18,9 @@ permissions:
jobs:
build:
name: ${{ matrix.target }} ${{ matrix.build_type }}
- runs-on: ubuntu-latest
- container: discourse/discourse_test:slim${{ startsWith(matrix.build_type, 'frontend') && '-browsers' || '' }}
- timeout-minutes: 60
+ 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' || '' }}
+ timeout-minutes: 20
env:
DISCOURSE_HOSTNAME: www.example.com
@@ -28,19 +28,21 @@ jobs:
RAILS_ENV: test
PGUSER: discourse
PGPASSWORD: discourse
- USES_PARALLEL_DATABASES: ${{ matrix.build_type == 'backend' && matrix.target == 'core' }}
+ USES_PARALLEL_DATABASES: ${{ matrix.build_type == 'backend' }}
strategy:
fail-fast: false
matrix:
- build_type: [backend, frontend, annotations]
+ build_type: [backend, frontend, system, annotations]
target: [core, plugins]
exclude:
- build_type: annotations
target: plugins
- build_type: frontend
target: core # Handled by core_frontend_tests job (below)
+ - build_type: system
+ target: plugins # Enable once at least 1 plugin has system tests
steps:
- uses: actions/checkout@v3
@@ -145,19 +147,46 @@ jobs:
if: steps.app-cache.outputs.cache-hit != 'true'
run: rm -rf tmp/app-cache/uploads && cp -r public/uploads tmp/app-cache/uploads
+ - name: Fetch turbo_rspec_runtime.log cache
+ uses: actions/cache@v3
+ id: test-runtime-cache
+ if: matrix.build_type == 'backend' && matrix.target == 'core'
+ with:
+ path: tmp/turbo_rspec_runtime.log
+ key: rspec-runtime-backend-core
+
- name: Core RSpec
if: matrix.build_type == 'backend' && matrix.target == 'core'
run: bin/turbo_rspec --verbose
- name: Plugin RSpec
if: matrix.build_type == 'backend' && matrix.target == 'plugins'
- run: bin/rake plugin:spec
+ run: bin/rake plugin:turbo_spec
- name: Plugin QUnit
if: matrix.build_type == 'frontend' && matrix.target == 'plugins'
- run: bin/rake plugin:qunit['*','1200000']
+ run: QUNIT_PARALLEL=3 bin/rake plugin:qunit['*','1200000']
timeout-minutes: 30
+ - name: Ember Build for System Tests
+ if: matrix.build_type == 'system'
+ run: bin/ember-cli --build
+
+ - name: Core System Tests
+ if: matrix.build_type == 'system' && matrix.target == 'core'
+ run: bin/system_rspec
+
+ - name: Plugin System Tests
+ if: matrix.build_type == 'system' && matrix.target == 'plugins'
+ run: bin/system_rspec plugins/*/spec/system
+
+ - name: Upload failed system test screenshots
+ uses: actions/upload-artifact@v3
+ if: matrix.build_type == 'system' && failure()
+ with:
+ name: failed-system-test-screenshots
+ path: tmp/screenshots/*.png
+
- name: Check Annotations
if: matrix.build_type == 'annotations'
run: |
@@ -177,14 +206,21 @@ jobs:
core_frontend_tests:
name: core frontend (${{ matrix.browser }})
- runs-on: ubuntu-latest
- container: discourse/discourse_test:slim-browsers
- timeout-minutes: 30
+ runs-on: ubuntu-20.04-8core
+ container:
+ image: discourse/discourse_test:slim-browsers
+ options: --user discourse
+
+ timeout-minutes: 35
strategy:
fail-fast: false
matrix:
- browser: ["Chrome", "Firefox", "Headless Firefox"]
+ browser: ["Chrome", "Firefox ESR", "Firefox Evergreen"]
+
+ env:
+ TESTEM_BROWSER: ${{ (startsWith(matrix.browser, 'Firefox') && 'Firefox') || matrix.browser }}
+ TESTEM_FIREFOX_PATH: ${{ (matrix.browser == 'Firefox Evergreen') && '/opt/firefox-evergreen/firefox' }}
steps:
- uses: actions/checkout@v2
@@ -216,23 +252,16 @@ jobs:
- name: Ember Build
working-directory: ./app/assets/javascripts/discourse
run: |
- sudo -E -u discourse mkdir /tmp/emberbuild
- sudo -E -u discourse -H yarn ember build --environment=test -o /tmp/emberbuild
+ mkdir /tmp/emberbuild
+ yarn ember build --environment=test -o /tmp/emberbuild
- - name: Core QUnit 1
- if: ${{ always() }}
+ - name: Core QUnit
working-directory: ./app/assets/javascripts/discourse
- run: sudo -E -u discourse -H yarn ember exam --path /tmp/emberbuild --split=3 --partition=1 --launch "${{ matrix.browser }}" --random
- timeout-minutes: 20
+ run: yarn ember exam --path /tmp/emberbuild --load-balance --parallel=5 --launch "${{ env.TESTEM_BROWSER }}" --write-execution-file --random
+ timeout-minutes: 15
- - name: Core QUnit 2
+ - uses: actions/upload-artifact@v3
if: ${{ always() }}
- working-directory: ./app/assets/javascripts/discourse
- run: sudo -E -u discourse -H yarn ember exam --path /tmp/emberbuild --split=3 --partition=2 --launch "${{ matrix.browser }}" --random
- timeout-minutes: 20
-
- - name: Core QUnit 3
- if: ${{ always() }}
- working-directory: ./app/assets/javascripts/discourse
- run: sudo -E -u discourse -H yarn ember exam --path /tmp/emberbuild --split=3 --partition=3 --launch "${{ matrix.browser }}" --random
- timeout-minutes: 20
+ with:
+ name: ember-exam-execution-${{matrix.browser}}
+ path: ./app/assets/javascripts/discourse/test-execution-*.json
diff --git a/.prettierignore b/.prettierignore
index fff97dce44..b33a5618e4 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -17,6 +17,7 @@ lib/javascripts/messageformat.js
lib/highlight_js/
plugins/**/lib/javascripts/locale
public/
+!/app/assets/javascripts/discourse/public
vendor/
app/assets/javascripts/discourse/tests/fixtures
spec/
diff --git a/Gemfile b/Gemfile
index b2116cc8ac..e29c6737be 100644
--- a/Gemfile
+++ b/Gemfile
@@ -149,11 +149,14 @@ group :assets do
end
group :test do
+ gem 'capybara', require: false
gem 'webmock', require: false
gem 'fakeweb', require: false
gem 'minitest', require: false
gem 'simplecov', require: false
+ gem 'selenium-webdriver', require: false
gem "test-prof"
+ gem 'webdrivers', require: false
end
group :test, :development do
@@ -170,7 +173,7 @@ group :test, :development do
gem 'shoulda-matchers', require: false
gem 'rspec-html-matchers'
gem 'byebug', require: ENV['RM_INFO'].nil?, platform: :mri
- gem 'rubocop-discourse', require: false, github: 'discourse/rubocop-discourse'
+ gem 'rubocop-discourse', require: false
gem 'parallel_tests'
gem 'rswag-specs'
diff --git a/Gemfile.lock b/Gemfile.lock
index 1478b8c75d..1a47d48853 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -5,14 +5,6 @@ GIT
mail (2.8.0.edge)
mini_mime (>= 0.1.1)
-GIT
- remote: https://github.com/discourse/rubocop-discourse.git
- revision: a5aea6e5f150b1eb7765a805bec0ff618cb718b3
- specs:
- rubocop-discourse (2.5.0)
- rubocop (>= 1.1.0)
- rubocop-rspec (>= 2.0.0)
-
GEM
remote: https://rubygems.org/
specs:
@@ -56,8 +48,8 @@ GEM
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
- addressable (2.8.0)
- public_suffix (>= 2.0.2, < 5.0)
+ addressable (2.8.1)
+ public_suffix (>= 2.0.2, < 6.0)
annotate (3.2.0)
activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0)
@@ -93,17 +85,27 @@ GEM
bootsnap (1.13.0)
msgpack (~> 1.2)
builder (3.2.4)
- bullet (7.0.2)
+ bullet (7.0.3)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
byebug (11.1.3)
+ capybara (3.37.1)
+ addressable
+ matrix
+ mini_mime (>= 0.1.3)
+ nokogiri (~> 1.8)
+ rack (>= 1.6.0)
+ rack-test (>= 0.6.3)
+ regexp_parser (>= 1.5, < 3.0)
+ xpath (~> 3.2)
cbor (0.5.9.6)
certified (1.0.0)
+ childprocess (4.1.0)
chunky_png (1.4.0)
coderay (1.1.3)
colored2 (3.1.2)
concurrent-ruby (1.1.10)
- connection_pool (2.2.5)
+ connection_pool (2.3.0)
cose (1.2.1)
cbor (~> 0.5.9)
openssl-signature_algorithm (~> 1.0)
@@ -111,12 +113,11 @@ GEM
crack (0.4.5)
rexml
crass (1.0.6)
- css_parser (1.11.0)
+ css_parser (1.12.0)
addressable
debug_inspector (1.1.0)
diff-lcs (1.5.0)
diffy (3.4.2)
- digest (3.1.0)
discourse-ember-rails (0.18.6)
active_model_serializers
ember-data-source (>= 1.0.0.beta.5)
@@ -140,36 +141,17 @@ GEM
sprockets (>= 3.3, < 4.1)
ember-source (2.18.2)
erubi (1.11.0)
- excon (0.92.4)
+ excon (0.92.5)
execjs (2.8.1)
exifr (1.3.9)
fabrication (2.30.0)
- faker (2.22.0)
+ faker (2.23.0)
i18n (>= 1.8.11, < 2)
fakeweb (1.3.0)
- faraday (1.10.0)
- faraday-em_http (~> 1.0)
- faraday-em_synchrony (~> 1.0)
- faraday-excon (~> 1.1)
- faraday-httpclient (~> 1.0)
- faraday-multipart (~> 1.0)
- faraday-net_http (~> 1.0)
- faraday-net_http_persistent (~> 1.0)
- faraday-patron (~> 1.0)
- faraday-rack (~> 1.0)
- faraday-retry (~> 1.0)
+ faraday (2.5.2)
+ faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
- faraday-em_http (1.0.0)
- faraday-em_synchrony (1.0.0)
- faraday-excon (1.1.0)
- faraday-httpclient (1.0.1)
- faraday-multipart (1.0.4)
- multipart-post (~> 2)
- faraday-net_http (1.0.1)
- faraday-net_http_persistent (1.2.0)
- faraday-patron (1.0.0)
- faraday-rack (1.0.0)
- faraday-retry (1.0.3)
+ faraday-net_http (3.0.0)
fast_blank (1.0.1)
fast_xs (0.8.0)
fastimage (2.2.6)
@@ -194,7 +176,7 @@ GEM
image_size (>= 1.5, < 4)
in_threads (~> 1.3)
progress (~> 3.0, >= 3.0.1)
- image_size (3.0.2)
+ image_size (3.1.0)
in_threads (1.6.0)
jmespath (1.6.1)
jquery-rails (4.5.0)
@@ -209,7 +191,7 @@ GEM
hana (~> 1.3)
regexp_parser (~> 2.0)
uri_template (~> 0.7)
- jwt (2.4.1)
+ jwt (2.5.0)
kgio (2.11.4)
libv8-node (16.10.0.0)
libv8-node (16.10.0.0-aarch64-linux)
@@ -229,12 +211,13 @@ GEM
logstash-event (1.2.02)
logstash-logger (0.26.1)
logstash-event (~> 1.2)
- logster (2.11.2)
- loofah (2.18.0)
+ logster (2.11.3)
+ loofah (2.19.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
lru_redux (1.1.0)
lz4-ruby (0.3.3)
+ matrix (0.4.2)
maxminddb (0.1.22)
memory_profiler (1.0.0)
message_bus (4.2.0)
@@ -242,36 +225,29 @@ GEM
method_source (1.0.0)
mini_mime (1.1.2)
mini_portile2 (2.8.0)
- mini_racer (0.6.2)
+ mini_racer (0.6.3)
libv8-node (~> 16.10.0.0)
mini_scheduler (0.14.0)
sidekiq (>= 4.2.3)
mini_sql (1.4.0)
mini_suffix (0.3.3)
ffi (~> 1.9)
- minitest (5.16.2)
- mocha (1.14.0)
- msgpack (1.5.4)
+ minitest (5.16.3)
+ mocha (1.15.0)
+ msgpack (1.5.6)
multi_json (1.15.0)
multi_xml (0.6.0)
- multipart-post (2.2.3)
mustache (1.1.1)
net-http (0.2.2)
uri
- net-imap (0.2.3)
- digest
+ net-imap (0.3.0)
net-protocol
- strscan
- net-pop (0.1.1)
- digest
+ net-pop (0.1.2)
net-protocol
- timeout
net-protocol (0.1.3)
timeout
- net-smtp (0.3.1)
- digest
+ net-smtp (0.3.2)
net-protocol
- timeout
nio4r (2.5.8)
nokogiri (1.13.8)
mini_portile2 (~> 2.8.0)
@@ -284,15 +260,20 @@ GEM
racc (~> 1.4)
nokogiri (1.13.8-x86_64-linux)
racc (~> 1.4)
- oauth (0.5.10)
- oauth2 (1.4.7)
- faraday (>= 0.8, < 2.0)
+ oauth (1.1.0)
+ oauth-tty (~> 1.0, >= 1.0.1)
+ snaky_hash (~> 2.0)
+ version_gem (~> 1.1)
+ oauth-tty (1.0.3)
+ version_gem (~> 1.1)
+ oauth2 (1.4.11)
+ faraday (>= 0.17.3, < 3.0)
jwt (>= 1.0, < 3.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
- rack (>= 1.2, < 3)
+ rack (>= 1.2, < 4)
oj (3.13.14)
- omniauth (1.9.1)
+ omniauth (1.9.2)
hashie (>= 3.4.6)
rack (>= 1.6.2, < 3)
omniauth-facebook (9.0.0)
@@ -308,40 +289,40 @@ GEM
omniauth-oauth (1.2.0)
oauth
omniauth (>= 1.0, < 3)
- omniauth-oauth2 (1.7.2)
- oauth2 (~> 1.4)
+ omniauth-oauth2 (1.7.3)
+ oauth2 (>= 1.4, < 3)
omniauth (>= 1.9, < 3)
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
rack
- openssl (3.0.0)
+ openssl (3.0.1)
openssl-signature_algorithm (1.2.1)
openssl (> 2.0, < 3.1)
optimist (3.0.1)
parallel (1.22.1)
- parallel_tests (3.11.1)
+ parallel_tests (3.13.0)
parallel
parser (3.1.2.1)
ast (~> 2.4.1)
pg (1.4.3)
progress (3.6.0)
- pry (0.13.1)
+ pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
- pry-byebug (3.9.0)
+ pry-byebug (3.10.1)
byebug (~> 11.0)
- pry (~> 0.13.0)
+ pry (>= 0.13, < 0.15)
pry-rails (0.3.9)
pry (>= 0.10.4)
- public_suffix (4.0.7)
- puma (5.6.4)
+ public_suffix (5.0.0)
+ puma (5.6.5)
nio4r (~> 2.0)
r2 (0.2.7)
racc (1.6.0)
rack (2.2.4)
rack-mini-profiler (3.0.0)
rack (>= 1.2.0)
- rack-protection (2.2.2)
+ rack-protection (3.0.1)
rack
rack-test (2.0.2)
rack (>= 1.3)
@@ -367,7 +348,7 @@ GEM
rainbow (3.1.1)
raindrops (0.20.0)
rake (13.0.6)
- rb-fsevent (0.11.1)
+ rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
rbtrace (0.4.14)
@@ -376,9 +357,9 @@ GEM
optimist (>= 3.0.0)
rchardet (1.8.0)
redis (4.7.1)
- redis-namespace (1.8.2)
- redis (>= 3.0.4)
- regexp_parser (2.5.0)
+ redis-namespace (1.9.0)
+ redis (>= 4)
+ regexp_parser (2.6.0)
request_store (1.5.1)
rack (>= 1.4)
rexml (3.2.5)
@@ -394,7 +375,7 @@ GEM
rspec-mocks (~> 3.11.0)
rspec-core (3.11.0)
rspec-support (~> 3.11.0)
- rspec-expectations (3.11.0)
+ rspec-expectations (3.11.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0)
rspec-html-matchers (0.10.0)
@@ -411,27 +392,30 @@ GEM
rspec-expectations (~> 3.10)
rspec-mocks (~> 3.10)
rspec-support (~> 3.10)
- rspec-support (3.11.0)
+ rspec-support (3.11.1)
rss (0.2.9)
rexml
- rswag-specs (2.5.1)
+ rswag-specs (2.6.0)
activesupport (>= 3.1, < 7.1)
json-schema (~> 2.2)
railties (>= 3.1, < 7.1)
- rubocop (1.34.1)
+ rubocop (1.36.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.1.2.1)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
- rubocop-ast (>= 1.20.0, < 2.0)
+ rubocop-ast (>= 1.20.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.21.0)
parser (>= 3.1.1.0)
- rubocop-rspec (2.12.1)
- rubocop (~> 1.31)
+ rubocop-discourse (3.0)
+ rubocop (>= 1.1.0)
+ rubocop-rspec (>= 2.0.0)
+ rubocop-rspec (2.13.2)
+ rubocop (~> 1.33)
ruby-prof (1.4.3)
ruby-progressbar (1.11.0)
ruby-readability (0.7.0)
@@ -454,18 +438,26 @@ GEM
seed-fu (2.3.9)
activerecord (>= 3.1)
activesupport (>= 3.1)
- shoulda-matchers (5.1.0)
+ selenium-webdriver (4.5.0)
+ childprocess (>= 0.5, < 5.0)
+ rexml (~> 3.2, >= 3.2.5)
+ rubyzip (>= 1.2.2, < 3.0)
+ websocket (~> 1.0)
+ shoulda-matchers (5.2.0)
activesupport (>= 5.2.0)
- sidekiq (6.5.4)
- connection_pool (>= 2.2.2)
+ sidekiq (6.5.7)
+ connection_pool (>= 2.2.5)
rack (~> 2.0)
- redis (>= 4.5.0)
+ redis (>= 4.5.0, < 5)
simplecov (0.21.2)
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4)
+ snaky_hash (2.0.0)
+ hashie
+ version_gem (~> 1.1)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
@@ -474,9 +466,8 @@ GEM
activesupport (>= 5.2)
sprockets (>= 3.0.0)
sshkey (2.0.0)
- stackprof (0.2.20)
- strscan (3.0.4)
- test-prof (1.0.9)
+ stackprof (0.2.21)
+ test-prof (1.0.10)
thor (1.2.1)
tilt (2.0.11)
timeout (0.3.0)
@@ -487,21 +478,29 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
- unicode-display_width (2.2.0)
+ unicode-display_width (2.3.0)
unicorn (6.1.0)
kgio (~> 2.6)
raindrops (~> 0.7)
uniform_notifier (1.16.0)
uri (0.11.0)
uri_template (0.7.0)
- webmock (3.17.1)
+ version_gem (1.1.0)
+ webdrivers (5.1.0)
+ nokogiri (~> 1.6)
+ rubyzip (>= 1.3.0)
+ selenium-webdriver (~> 4.0)
+ webmock (3.18.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webpush (1.1.0)
hkdf (~> 0.2)
jwt (~> 2.0)
+ websocket (1.2.9)
xorcist (1.1.3)
+ xpath (3.2.0)
+ nokogiri (~> 1.8)
yaml-lint (0.0.10)
zeitwerk (2.6.0)
@@ -533,6 +532,7 @@ DEPENDENCIES
bootsnap
bullet
byebug
+ capybara
cbor
certified
colored2
@@ -617,7 +617,7 @@ DEPENDENCIES
rspec-rails
rss
rswag-specs
- rubocop-discourse!
+ rubocop-discourse
ruby-prof
ruby-readability
rubyzip
@@ -625,6 +625,7 @@ DEPENDENCIES
sassc (= 2.0.1)
sassc-rails
seed-fu
+ selenium-webdriver
shoulda-matchers
sidekiq
simplecov
@@ -637,6 +638,7 @@ DEPENDENCIES
uglifier
unf
unicorn
+ webdrivers
webmock
webpush
xorcist
diff --git a/README.md b/README.md
index e40a099058..c5b36997e3 100644
--- a/README.md
+++ b/README.md
@@ -12,11 +12,9 @@ To learn more about the philosophy and goals of the project, [visit **discourse.
## Screenshots
-
+
-
-
@@ -111,7 +109,7 @@ Discourse logo and “Discourse Forum” ®, Civilized Discourse Construction Ki
## Accessibility
-To guide our ongoing effort to build accessible software we follow the [W3C’s Web Content Accessibility Guidelines (WCAG)](https://www.w3.org/TR/WCAG21/). If you'd like to report an accessibility issue that makes it difficult for you to use Discourse, email accessibility@discourse.org. For more information visit [discourse.org/accessibility](https://discourse.org/accessibility).
+To guide our ongoing effort to build accessible software we follow the [W3C’s Web Content Accessibility Guidelines (WCAG)](https://www.w3.org/TR/WCAG21/). If you'd like to report an accessibility issue that makes it difficult for you to use Discourse, email accessibility@discourse.org. For more information visit [discourse.org/accessibility](https://discourse.org/accessibility).
## Dedication
diff --git a/app/assets/javascripts/admin-plugins.js.erb b/app/assets/javascripts/admin-plugins.js.erb
deleted file mode 100644
index 70010c4644..0000000000
--- a/app/assets/javascripts/admin-plugins.js.erb
+++ /dev/null
@@ -1,11 +0,0 @@
-<%
-DiscoursePluginRegistry.admin_javascripts.each { |js| require_asset(js) }
-
-DiscoursePluginRegistry.each_globbed_asset(admin: true) do |f|
- if File.directory?(f)
- depend_on(f)
- else
- require_asset(f)
- end
-end
-%>
diff --git a/app/assets/javascripts/admin/addon/components/admin-watched-word.js b/app/assets/javascripts/admin/addon/components/admin-watched-word.js
index f0523d374c..7de7e3d8ab 100644
--- a/app/assets/javascripts/admin/addon/components/admin-watched-word.js
+++ b/app/assets/javascripts/admin/addon/components/admin-watched-word.js
@@ -1,12 +1,13 @@
import Component from "@ember/component";
import { alias, equal } from "@ember/object/computed";
-import bootbox from "bootbox";
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(),
isReplace: equal("actionKey", "replace"),
isTag: equal("actionKey", "tag"),
@@ -26,7 +27,7 @@ export default Component.extend({
this.action(this.word);
})
.catch((e) => {
- bootbox.alert(
+ this.dialog.alert(
I18n.t("generic_error_with_reason", {
error: `http: ${e.status} - ${e.body}`,
})
diff --git a/app/assets/javascripts/admin/addon/components/admin-web-hook-event.js b/app/assets/javascripts/admin/addon/components/admin-web-hook-event.js
index fbcf1594e0..dd13384a2d 100644
--- a/app/assets/javascripts/admin/addon/components/admin-web-hook-event.js
+++ b/app/assets/javascripts/admin/addon/components/admin-web-hook-event.js
@@ -2,15 +2,16 @@ import { ensureJSON, plainJSON, prettyJSON } from "discourse/lib/formatter";
import Component from "@ember/component";
import I18n from "I18n";
import { ajax } from "discourse/lib/ajax";
-import bootbox from "bootbox";
import discourseComputed from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error";
+import { inject as service } from "@ember/service";
export default Component.extend({
tagName: "li",
expandDetails: null,
expandDetailsRequestKey: "request",
expandDetailsResponseKey: "response",
+ dialog: service(),
@discourseComputed("model.status")
statusColorClasses(status) {
@@ -52,25 +53,21 @@ export default Component.extend({
actions: {
redeliver() {
- return bootbox.confirm(
- I18n.t("admin.web_hooks.events.redeliver_confirm"),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (result) => {
- if (result) {
- ajax(
- `/admin/api/web_hooks/${this.get(
- "model.web_hook_id"
- )}/events/${this.get("model.id")}/redeliver`,
- { type: "POST" }
- )
- .then((json) => {
- this.set("model", json.web_hook_event);
- })
- .catch(popupAjaxError);
- }
- }
- );
+ return this.dialog.yesNoConfirm({
+ message: I18n.t("admin.web_hooks.events.redeliver_confirm"),
+ didConfirm: () => {
+ return ajax(
+ `/admin/api/web_hooks/${this.get(
+ "model.web_hook_id"
+ )}/events/${this.get("model.id")}/redeliver`,
+ { type: "POST" }
+ )
+ .then((json) => {
+ this.set("model", json.web_hook_event);
+ })
+ .catch(popupAjaxError);
+ },
+ });
},
toggleRequest() {
diff --git a/app/assets/javascripts/admin/addon/components/email-styles-editor.js b/app/assets/javascripts/admin/addon/components/email-styles-editor.js
index e3e980e632..48621b41cf 100644
--- a/app/assets/javascripts/admin/addon/components/email-styles-editor.js
+++ b/app/assets/javascripts/admin/addon/components/email-styles-editor.js
@@ -1,10 +1,11 @@
import Component from "@ember/component";
import I18n from "I18n";
-import bootbox from "bootbox";
import discourseComputed from "discourse-common/utils/decorators";
import { reads } from "@ember/object/computed";
+import { inject as service } from "@ember/service";
export default Component.extend({
+ dialog: service(),
editorId: reads("fieldName"),
@discourseComputed("fieldName")
@@ -33,22 +34,18 @@ export default Component.extend({
actions: {
reset() {
- bootbox.confirm(
- I18n.t("admin.customize.email_style.reset_confirm", {
+ this.dialog.yesNoConfirm({
+ message: I18n.t("admin.customize.email_style.reset_confirm", {
fieldName: I18n.t(`admin.customize.email_style.${this.fieldName}`),
}),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (result) => {
- if (result) {
- this.styles.setField(
- this.fieldName,
- this.styles.get(`default_${this.fieldName}`)
- );
- this.notifyPropertyChange("editorContents");
- }
- }
- );
+ didConfirm: () => {
+ this.styles.setField(
+ this.fieldName,
+ this.styles.get(`default_${this.fieldName}`)
+ );
+ this.notifyPropertyChange("editorContents");
+ },
+ });
},
save() {
this.attrs.save();
diff --git a/app/assets/javascripts/admin/addon/components/ip-lookup.js b/app/assets/javascripts/admin/addon/components/ip-lookup.js
index 920cd39069..222e04b9ab 100644
--- a/app/assets/javascripts/admin/addon/components/ip-lookup.js
+++ b/app/assets/javascripts/admin/addon/components/ip-lookup.js
@@ -3,13 +3,15 @@ import Component from "@ember/component";
import EmberObject from "@ember/object";
import I18n from "I18n";
import { ajax } from "discourse/lib/ajax";
-import bootbox from "bootbox";
import copyText from "discourse/lib/copy-text";
import discourseComputed from "discourse-common/utils/decorators";
import 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(),
@discourseComputed("other_accounts.length", "totalOthersWithSameIP")
otherAccountsToDelete(otherAccountsLength, totalOthersWithSameIP) {
@@ -89,29 +91,27 @@ export default Component.extend({
},
deleteOtherAccounts() {
- bootbox.confirm(
- I18n.t("ip_lookup.confirm_delete_other_accounts"),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (confirmed) => {
- if (confirmed) {
- this.setProperties({
- other_accounts: null,
- otherAccountsLoading: true,
- totalOthersWithSameIP: null,
- });
+ 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",
- },
- }).then(() => this.send("lookup"));
- }
- }
- );
+ 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"));
+ },
+ });
},
},
});
diff --git a/app/assets/javascripts/admin/addon/components/permalink-form.js b/app/assets/javascripts/admin/addon/components/permalink-form.js
index 62c62bdf74..9c0c64de6b 100644
--- a/app/assets/javascripts/admin/addon/components/permalink-form.js
+++ b/app/assets/javascripts/admin/addon/components/permalink-form.js
@@ -1,14 +1,15 @@
import Component from "@ember/component";
import I18n from "I18n";
import Permalink from "admin/models/permalink";
-import bootbox from "bootbox";
import discourseComputed, { bind } from "discourse-common/utils/decorators";
import { fmt } from "discourse/lib/computed";
import { schedule } from "@ember/runloop";
import { action } from "@ember/object";
+import { inject as service } from "@ember/service";
export default Component.extend({
tagName: "",
+ dialog: service(),
formSubmitted: false,
permalinkType: "topic_id",
permalinkTypePlaceholder: fmt("permalinkType", "admin.permalink.%@"),
@@ -29,7 +30,7 @@ export default Component.extend({
@bind
focusPermalink() {
schedule("afterRender", () =>
- this.element.querySelector(".permalink-url")?.focus()
+ document.querySelector(".permalink-url")?.focus()
);
},
@@ -74,7 +75,12 @@ export default Component.extend({
} else {
error = I18n.t("generic_error");
}
- bootbox.alert(error, this.focusPermalink);
+
+ this.dialog.alert({
+ message: error,
+ didConfirm: () => this.focusPermalink(),
+ didCancel: () => this.focusPermalink(),
+ });
}
);
}
diff --git a/app/assets/javascripts/admin/addon/components/screened-ip-address-form.js b/app/assets/javascripts/admin/addon/components/screened-ip-address-form.js
index ae965dd26e..ab553b6a21 100644
--- a/app/assets/javascripts/admin/addon/components/screened-ip-address-form.js
+++ b/app/assets/javascripts/admin/addon/components/screened-ip-address-form.js
@@ -2,8 +2,8 @@ import discourseComputed from "discourse-common/utils/decorators";
import Component from "@ember/component";
import I18n from "I18n";
import ScreenedIpAddress from "admin/models/screened-ip-address";
-import bootbox from "bootbox";
import { schedule } from "@ember/runloop";
+import { inject as service } from "@ember/service";
/**
A form to create an IP address that will be blocked or allowed.
@@ -18,6 +18,7 @@ import { schedule } from "@ember/runloop";
export default Component.extend({
tagName: "form",
+ dialog: service(),
classNames: ["screened-ip-address-form", "inline-form"],
formSubmitted: false,
actionName: "block",
@@ -47,6 +48,12 @@ export default Component.extend({
}
},
+ focusInput() {
+ schedule("afterRender", () => {
+ this.element.querySelector("input").focus();
+ });
+ },
+
actions: {
submit() {
if (!this.formSubmitted) {
@@ -60,22 +67,20 @@ export default Component.extend({
.then((result) => {
this.setProperties({ ip_address: "", formSubmitted: false });
this.action(ScreenedIpAddress.create(result.screened_ip_address));
- schedule("afterRender", () =>
- this.element.querySelector("input").focus()
- );
+ this.focusInput();
})
.catch((e) => {
this.set("formSubmitted", false);
- const msg = e.jqXHR.responseJSON?.errors
+ const message = e.jqXHR.responseJSON?.errors
? I18n.t("generic_error_with_reason", {
error: e.jqXHR.responseJSON.errors.join(". "),
})
: I18n.t("generic_error");
- bootbox.alert(msg, () =>
- schedule("afterRender", () =>
- this.element.querySelector("input").focus()
- )
- );
+ this.dialog.alert({
+ message,
+ didConfirm: () => this.focusInput(),
+ didCancel: () => this.focusInput(),
+ });
});
}
},
diff --git a/app/assets/javascripts/admin/addon/components/tags-uploader.js b/app/assets/javascripts/admin/addon/components/tags-uploader.js
index dcd57a45fd..39425b9b65 100644
--- a/app/assets/javascripts/admin/addon/components/tags-uploader.js
+++ b/app/assets/javascripts/admin/addon/components/tags-uploader.js
@@ -2,10 +2,11 @@ import Component from "@ember/component";
import I18n from "I18n";
import UppyUploadMixin from "discourse/mixins/uppy-upload";
import { alias } from "@ember/object/computed";
-import bootbox from "bootbox";
+import { inject as service } from "@ember/service";
export default Component.extend(UppyUploadMixin, {
type: "csv",
+ dialog: service(),
uploadUrl: "/tags/upload",
addDisabled: alias("uploading"),
elementId: "tag-uploader",
@@ -16,9 +17,8 @@ export default Component.extend(UppyUploadMixin, {
},
uploadDone() {
- bootbox.alert(I18n.t("tagging.upload_successful"), () => {
- this.refresh();
- this.closeModal();
- });
+ this.closeModal();
+ this.refresh();
+ this.dialog.alert(I18n.t("tagging.upload_successful"));
},
});
diff --git a/app/assets/javascripts/admin/addon/components/watched-word-form.js b/app/assets/javascripts/admin/addon/components/watched-word-form.js
index e00032f22c..1d6ba8ccba 100644
--- a/app/assets/javascripts/admin/addon/components/watched-word-form.js
+++ b/app/assets/javascripts/admin/addon/components/watched-word-form.js
@@ -2,13 +2,14 @@ import discourseComputed, { observes } from "discourse-common/utils/decorators";
import Component from "@ember/component";
import I18n from "I18n";
import WatchedWord from "admin/models/watched-word";
-import bootbox from "bootbox";
import { equal } from "@ember/object/computed";
import { isEmpty } from "@ember/utils";
import { schedule } from "@ember/runloop";
+import { inject as service } from "@ember/service";
export default Component.extend({
tagName: "form",
+ dialog: service(),
classNames: ["watched-word-form"],
formSubmitted: false,
actionKey: null,
@@ -55,6 +56,10 @@ export default Component.extend({
});
},
+ focusInput() {
+ schedule("afterRender", () => this.element.querySelector("input").focus());
+ },
+
actions: {
changeSelectedTags(tags) {
this.setProperties({
@@ -98,22 +103,20 @@ export default Component.extend({
isCaseSensitive: false,
});
this.action(WatchedWord.create(result));
- schedule("afterRender", () =>
- this.element.querySelector("input").focus()
- );
+ this.focusInput();
})
.catch((e) => {
this.set("formSubmitted", false);
- const msg = e.jqXHR.responseJSON?.errors
+ const message = e.jqXHR.responseJSON?.errors
? I18n.t("generic_error_with_reason", {
error: e.jqXHR.responseJSON.errors.join(". "),
})
: I18n.t("generic_error");
- bootbox.alert(msg, () =>
- schedule("afterRender", () =>
- this.element.querySelector("input").focus()
- )
- );
+ this.dialog.alert({
+ message,
+ didConfirm: () => this.focusInput(),
+ didCancel: () => this.focusInput(),
+ });
});
}
},
diff --git a/app/assets/javascripts/admin/addon/components/watched-word-uploader.js b/app/assets/javascripts/admin/addon/components/watched-word-uploader.js
index efbacb9c0b..27c43ad576 100644
--- a/app/assets/javascripts/admin/addon/components/watched-word-uploader.js
+++ b/app/assets/javascripts/admin/addon/components/watched-word-uploader.js
@@ -2,7 +2,7 @@ import Component from "@ember/component";
import I18n from "I18n";
import UppyUploadMixin from "discourse/mixins/uppy-upload";
import { alias } from "@ember/object/computed";
-import bootbox from "bootbox";
+import { dialog } from "discourse/lib/uploads";
export default Component.extend(UppyUploadMixin, {
type: "txt",
@@ -21,7 +21,7 @@ export default Component.extend(UppyUploadMixin, {
uploadDone() {
if (this) {
- bootbox.alert(I18n.t("admin.watched_words.form.upload_successful"));
+ dialog.alert(I18n.t("admin.watched_words.form.upload_successful"));
this.done();
}
},
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-backups-index.js b/app/assets/javascripts/admin/addon/controllers/admin-backups-index.js
index c53adeecf9..72a1bd18a5 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-backups-index.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-backups-index.js
@@ -3,11 +3,13 @@ import { alias, equal } from "@ember/object/computed";
import { i18n, setting } from "discourse/lib/computed";
import I18n from "I18n";
import { ajax } from "discourse/lib/ajax";
-import bootbox from "bootbox";
+
import discourseComputed from "discourse-common/utils/decorators";
+import { inject as service } from "@ember/service";
export default Controller.extend({
adminBackups: controller(),
+ dialog: service(),
status: alias("adminBackups.model"),
uploadLabel: i18n("admin.backups.upload.label"),
backupLocation: setting("backup_location"),
@@ -27,17 +29,13 @@ export default Controller.extend({
actions: {
toggleReadOnlyMode() {
if (!this.site.get("isReadOnly")) {
- bootbox.confirm(
- I18n.t("admin.backups.read_only.enable.confirm"),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (confirmed) => {
- if (confirmed) {
- this.set("currentUser.hideReadOnlyAlert", true);
- this._toggleReadOnlyMode(true);
- }
- }
- );
+ this.dialog.yesNoConfirm({
+ message: I18n.t("admin.backups.read_only.enable.confirm"),
+ didConfirm: () => {
+ this.set("currentUser.hideReadOnlyAlert", true);
+ this._toggleReadOnlyMode(true);
+ },
+ });
} else {
this._toggleReadOnlyMode(false);
}
@@ -46,7 +44,7 @@ export default Controller.extend({
download(backup) {
const link = backup.get("filename");
ajax(`/admin/backups/${link}`, { type: "PUT" }).then(() =>
- bootbox.alert(I18n.t("admin.backups.operations.download.alert"))
+ this.dialog.alert(I18n.t("admin.backups.operations.download.alert"))
);
},
},
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-badges/award.js b/app/assets/javascripts/admin/addon/controllers/admin-badges/award.js
index e564840115..93745bd79e 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-badges/award.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-badges/award.js
@@ -1,12 +1,13 @@
import Controller from "@ember/controller";
import I18n from "I18n";
import { ajax } from "discourse/lib/ajax";
-import bootbox from "bootbox";
import { extractError } from "discourse/lib/ajax-error";
import { action } from "@ember/object";
import { tracked } from "@glimmer/tracking";
+import { inject as service } from "@ember/service";
export default class AdminBadgesAwardController extends Controller {
+ @service dialog;
@tracked saving = false;
@tracked replaceBadgeOwners = false;
@tracked grantExistingHolders = false;
@@ -84,7 +85,7 @@ export default class AdminBadgesAwardController extends Controller {
})
.finally(() => (this.saving = false));
} else {
- bootbox.alert(I18n.t("admin.badges.mass_award.aborted"));
+ this.dialog.alert(I18n.t("admin.badges.mass_award.aborted"));
}
}
}
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-badges/show.js b/app/assets/javascripts/admin/addon/controllers/admin-badges/show.js
index 29d95f2c0f..2b38081826 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-badges/show.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-badges/show.js
@@ -1,7 +1,7 @@
import Controller, { inject as controller } from "@ember/controller";
import { observes } from "discourse-common/utils/decorators";
import I18n from "I18n";
-import bootbox from "bootbox";
+
import { bufferedProperty } from "discourse/mixins/buffered-content";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { next } from "@ember/runloop";
@@ -17,8 +17,9 @@ const ICON = "icon";
export default class AdminBadgesShowController extends Controller.extend(
bufferedProperty("model")
) {
- @controller adminBadges;
@service router;
+ @service dialog;
+ @controller adminBadges;
@tracked saving = false;
@tracked savingStatus = "";
@@ -81,8 +82,8 @@ export default class AdminBadgesShowController extends Controller.extend(
}
get hasQuery() {
- let modelQuery = this.model.query;
- let bufferedQuery = this.bufferedQuery;
+ let modelQuery = this.model.get("query");
+ let bufferedQuery = this.buffered.get("query");
if (bufferedQuery) {
return bufferedQuery.trim().length > 0;
@@ -216,23 +217,19 @@ export default class AdminBadgesShowController extends Controller.extend(
return;
}
- return bootbox.confirm(
- I18n.t("admin.badges.delete_confirm"),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (result) => {
- if (result) {
- model
- .destroy()
- .then(() => {
- adminBadges.removeObject(model);
- this.transitionToRoute("adminBadges.index");
- })
- .catch(() => {
- bootbox.alert(I18n.t("generic_error"));
- });
- }
- }
- );
+ return this.dialog.yesNoConfirm({
+ message: I18n.t("admin.badges.delete_confirm"),
+ didConfirm: () => {
+ model
+ .destroy()
+ .then(() => {
+ adminBadges.removeObject(model);
+ this.transitionToRoute("adminBadges.index");
+ })
+ .catch(() => {
+ this.dialog.alert(I18n.t("generic_error"));
+ });
+ },
+ });
}
}
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-customize-colors-show.js b/app/assets/javascripts/admin/addon/controllers/admin-customize-colors-show.js
index 80b5e465c2..3df2ee25d4 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-customize-colors-show.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-customize-colors-show.js
@@ -1,11 +1,12 @@
import Controller from "@ember/controller";
import I18n from "I18n";
-import bootbox from "bootbox";
import discourseLater from "discourse-common/lib/later";
import { action, computed } from "@ember/object";
import { clipboardCopy } from "discourse/lib/utilities";
+import { inject as service } from "@ember/service";
export default class AdminCustomizeColorsShowController extends Controller {
+ @service dialog;
onlyOverridden = false;
@computed("model.colors.[]", "onlyOverridden")
@@ -73,18 +74,14 @@ export default class AdminCustomizeColorsShowController extends Controller {
@action
destroy() {
- return bootbox.confirm(
- I18n.t("admin.customize.colors.delete_confirm"),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (result) => {
- if (result) {
- this.model.destroy().then(() => {
- this.allColors.removeObject(this.model);
- this.replaceRoute("adminCustomize.colors");
- });
- }
- }
- );
+ return this.dialog.yesNoConfirm({
+ message: I18n.t("admin.customize.colors.delete_confirm"),
+ didConfirm: () => {
+ return this.model.destroy().then(() => {
+ this.allColors.removeObject(this.model);
+ this.replaceRoute("adminCustomize.colors");
+ });
+ },
+ });
}
}
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-customize-email-style-edit.js b/app/assets/javascripts/admin/addon/controllers/admin-customize-email-style-edit.js
index 79813399b2..ad664f79b5 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-customize-email-style-edit.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-customize-email-style-edit.js
@@ -1,9 +1,11 @@
import Controller from "@ember/controller";
import I18n from "I18n";
-import bootbox from "bootbox";
import discourseComputed from "discourse-common/utils/decorators";
+import { inject as service } from "@ember/service";
export default Controller.extend({
+ dialog: service(),
+
@discourseComputed("model.isSaving")
saveButtonText(isSaving) {
return isSaving ? I18n.t("saving") : I18n.t("admin.customize.save");
@@ -27,7 +29,7 @@ export default Controller.extend({
error: e.jqXHR.responseJSON.errors.join(". "),
})
: I18n.t("generic_error");
- bootbox.alert(msg);
+ this.dialog.alert(msg);
})
.finally(() => this.set("model.changed", false));
}
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js b/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js
index 2418c785e0..61ffda925e 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-customize-themes-show.js
@@ -16,10 +16,12 @@ import { makeArray } from "discourse-common/lib/helpers";
import { popupAjaxError } from "discourse/lib/ajax-error";
import showModal from "discourse/lib/show-modal";
import { url } from "discourse/lib/computed";
+import { inject as service } from "@ember/service";
const THEME_UPLOAD_VAR = 2;
export default Controller.extend({
+ dialog: service(),
downloadUrl: url("model.id", "/admin/customize/themes/%@/export"),
previewUrl: url("model.id", "/admin/themes/%@/preview"),
addButtonDisabled: empty("selectedChildThemeId"),
@@ -345,16 +347,10 @@ export default Controller.extend({
},
removeUpload(upload) {
- return bootbox.confirm(
- I18n.t("admin.customize.theme.delete_upload_confirm"),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (result) => {
- if (result) {
- this.model.removeField(upload);
- }
- }
- );
+ return this.dialog.yesNoConfirm({
+ message: I18n.t("admin.customize.theme.delete_upload_confirm"),
+ didConfirm: () => this.model.removeField(upload),
+ });
},
removeChildTheme(theme) {
@@ -364,23 +360,19 @@ export default Controller.extend({
},
destroy() {
- return bootbox.confirm(
- I18n.t("admin.customize.delete_confirm", {
+ return this.dialog.yesNoConfirm({
+ message: I18n.t("admin.customize.delete_confirm", {
theme_name: this.get("model.name"),
}),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (result) => {
- if (result) {
- const model = this.model;
- model.setProperties({ recentlyInstalled: false });
- model.destroyRecord().then(() => {
- this.allThemes.removeObject(model);
- this.transitionToRoute("adminCustomizeThemes");
- });
- }
- }
- );
+ didConfirm: () => {
+ const model = this.model;
+ model.setProperties({ recentlyInstalled: false });
+ model.destroyRecord().then(() => {
+ this.allThemes.removeObject(model);
+ this.transitionToRoute("adminCustomizeThemes");
+ });
+ },
+ });
},
switchType() {
@@ -398,16 +390,10 @@ export default Controller.extend({
});
}
- bootbox.confirm(
+ return this.dialog.yesNoConfirm({
message,
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (result) => {
- if (result) {
- this.commitSwitchType();
- }
- }
- );
+ didConfirm: () => this.commitSwitchType(),
+ });
},
enableComponent() {
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-email-index.js b/app/assets/javascripts/admin/addon/controllers/admin-email-index.js
index 91161bbe60..dddec41d1c 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-email-index.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-email-index.js
@@ -1,11 +1,15 @@
import Controller from "@ember/controller";
import I18n from "I18n";
import { ajax } from "discourse/lib/ajax";
-import bootbox from "bootbox";
import { empty } from "@ember/object/computed";
import { observes } from "discourse-common/utils/decorators";
+import { inject as service } from "@ember/service";
+import { htmlSafe } from "@ember/template";
+import { escapeExpression } from "discourse/lib/utilities";
export default Controller.extend({
+ dialog: service(),
+
/**
Is the "send test email" button disabled?
@@ -44,13 +48,17 @@ export default Controller.extend({
)
.catch((e) => {
if (e.jqXHR.responseJSON?.errors) {
- bootbox.alert(
- I18n.t("admin.email.error", {
- server_error: e.jqXHR.responseJSON.errors[0],
- })
- );
+ this.dialog.alert({
+ message: htmlSafe(
+ I18n.t("admin.email.error", {
+ server_error: escapeExpression(
+ e.jqXHR.responseJSON.errors[0]
+ ),
+ })
+ ),
+ });
} else {
- bootbox.alert(I18n.t("admin.email.test_error"));
+ this.dialog.alert({ message: I18n.t("admin.email.test_error") });
}
})
.finally(() => this.set("sendingEmail", false));
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-email-preview-digest.js b/app/assets/javascripts/admin/addon/controllers/admin-email-preview-digest.js
index 1a6fbf2184..50d74d35d8 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-email-preview-digest.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-email-preview-digest.js
@@ -1,14 +1,14 @@
import { empty, notEmpty, or } from "@ember/object/computed";
import Controller from "@ember/controller";
import EmailPreview from "admin/models/email-preview";
-import bootbox from "bootbox";
import { get } from "@ember/object";
import { popupAjaxError } from "discourse/lib/ajax-error";
+import { inject as service } from "@ember/service";
export default Controller.extend({
+ dialog: service(),
username: null,
lastSeen: null,
-
emailEmpty: empty("email"),
sendEmailDisabled: or("emailEmpty", "sendingEmail"),
showSendEmailForm: notEmpty("model.html_content"),
@@ -50,7 +50,7 @@ export default Controller.extend({
EmailPreview.sendDigest(this.username, this.lastSeen, this.email)
.then((result) => {
if (result.errors) {
- bootbox.alert(result.errors);
+ this.dialog.alert(result.errors);
} else {
this.set("sentEmail", true);
}
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-emojis.js b/app/assets/javascripts/admin/addon/controllers/admin-emojis.js
index 7ab8590163..eab68cf088 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-emojis.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-emojis.js
@@ -2,12 +2,13 @@ import EmberObject, { action, computed } from "@ember/object";
import Controller from "@ember/controller";
import I18n from "I18n";
import { ajax } from "discourse/lib/ajax";
-import bootbox from "bootbox";
import { sort } from "@ember/object/computed";
+import { inject as service } from "@ember/service";
const ALL_FILTER = "all";
export default Controller.extend({
+ dialog: service(),
filter: null,
sorting: null,
@@ -72,19 +73,17 @@ export default Controller.extend({
@action
destroyEmoji(emoji) {
- return bootbox.confirm(
- I18n.t("admin.emoji.delete_confirm", { name: emoji.get("name") }),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (destroy) => {
- if (destroy) {
- return ajax("/admin/customize/emojis/" + emoji.get("name"), {
- type: "DELETE",
- }).then(() => {
- this.model.removeObject(emoji);
- });
- }
- }
- );
+ this.dialog.yesNoConfirm({
+ message: I18n.t("admin.emoji.delete_confirm", {
+ name: emoji.get("name"),
+ }),
+ didConfirm: () => {
+ return ajax("/admin/customize/emojis/" + emoji.get("name"), {
+ type: "DELETE",
+ }).then(() => {
+ this.model.removeObject(emoji);
+ });
+ },
+ });
},
});
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-ip-addresses.js b/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-ip-addresses.js
index df734004ec..0e8b43f9c1 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-ip-addresses.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-ip-addresses.js
@@ -2,13 +2,14 @@ import Controller from "@ember/controller";
import I18n from "I18n";
import { INPUT_DELAY } from "discourse-common/config/environment";
import ScreenedIpAddress from "admin/models/screened-ip-address";
-import bootbox from "bootbox";
import discourseDebounce from "discourse-common/lib/debounce";
import { exportEntity } from "discourse/lib/export-csv";
import { observes } from "discourse-common/utils/decorators";
import { outputExportResult } from "discourse/lib/export-result";
+import { inject as service } from "@ember/service";
export default Controller.extend({
+ dialog: service(),
loading: false,
filter: null,
savedIpAddress: null,
@@ -59,13 +60,13 @@ export default Controller.extend({
.then(() => this.set("savedIpAddress", null))
.catch((e) => {
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
- bootbox.alert(
+ this.dialog.alert(
I18n.t("generic_error_with_reason", {
error: e.jqXHR.responseJSON.errors.join(". "),
})
);
} else {
- bootbox.alert(I18n.t("generic_error"));
+ this.dialog.alert(I18n.t("generic_error"));
}
if (wasEditing) {
record.set("editing", true);
@@ -74,33 +75,29 @@ export default Controller.extend({
},
destroy(record) {
- return bootbox.confirm(
- I18n.t("admin.logs.screened_ips.delete_confirm", {
+ return this.dialog.yesNoConfirm({
+ message: I18n.t("admin.logs.screened_ips.delete_confirm", {
ip_address: record.get("ip_address"),
}),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (result) => {
- if (result) {
- record
- .destroy()
- .then((deleted) => {
- if (deleted) {
- this.model.removeObject(record);
- } else {
- bootbox.alert(I18n.t("generic_error"));
- }
- })
- .catch((e) => {
- bootbox.alert(
- I18n.t("generic_error_with_reason", {
- error: `http: ${e.status} - ${e.body}`,
- })
- );
- });
- }
- }
- );
+ didConfirm: () => {
+ return record
+ .destroy()
+ .then((deleted) => {
+ if (deleted) {
+ this.model.removeObject(record);
+ } else {
+ this.dialog.alert(I18n.t("generic_error"));
+ }
+ })
+ .catch((e) => {
+ this.dialog.alert(
+ I18n.t("generic_error_with_reason", {
+ error: `http: ${e.status} - ${e.body}`,
+ })
+ );
+ });
+ },
+ });
},
recordAdded(arg) {
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js b/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js
index 9993a19fe5..07372d2f10 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js
@@ -2,12 +2,13 @@ import Controller from "@ember/controller";
import I18n from "I18n";
import { INPUT_DELAY } from "discourse-common/config/environment";
import Permalink from "admin/models/permalink";
-import bootbox from "bootbox";
import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "discourse-common/utils/decorators";
import { clipboardCopy } from "discourse/lib/utilities";
+import { inject as service } from "@ember/service";
export default Controller.extend({
+ dialog: service(),
loading: false,
filter: null,
@@ -34,27 +35,23 @@ export default Controller.extend({
},
destroy(record) {
- return bootbox.confirm(
- I18n.t("admin.permalink.delete_confirm"),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (result) => {
- if (result) {
- record.destroy().then(
- (deleted) => {
- if (deleted) {
- this.model.removeObject(record);
- } else {
- bootbox.alert(I18n.t("generic_error"));
- }
- },
- function () {
- bootbox.alert(I18n.t("generic_error"));
+ return this.dialog.yesNoConfirm({
+ message: I18n.t("admin.permalink.delete_confirm"),
+ didConfirm: () => {
+ return record.destroy().then(
+ (deleted) => {
+ if (deleted) {
+ this.model.removeObject(record);
+ } else {
+ this.dialog.alert(I18n.t("generic_error"));
}
- );
- }
- }
- );
+ },
+ function () {
+ this.dialog.alert(I18n.t("generic_error"));
+ }
+ );
+ },
+ });
},
},
});
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-site-text-edit.js b/app/assets/javascripts/admin/addon/controllers/admin-site-text-edit.js
index cc7bac3267..8bcfc9b9da 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-site-text-edit.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-site-text-edit.js
@@ -1,11 +1,13 @@
import Controller from "@ember/controller";
import I18n from "I18n";
-import bootbox from "bootbox";
import { bufferedProperty } from "discourse/mixins/buffered-content";
import discourseComputed from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error";
+import { action } from "@ember/object";
+import { inject as service } from "@ember/service";
export default Controller.extend(bufferedProperty("siteText"), {
+ dialog: service(),
saved: false,
queryParams: ["locale"],
@@ -14,35 +16,36 @@ export default Controller.extend(bufferedProperty("siteText"), {
return this.siteText.value === value;
},
- actions: {
- saveChanges() {
- const attrs = this.buffered.getProperties("value");
- attrs.locale = this.locale;
+ @action
+ saveChanges() {
+ const attrs = this.buffered.getProperties("value");
+ attrs.locale = this.locale;
- this.siteText
- .save(attrs)
- .then(() => {
- this.commitBuffer();
- this.set("saved", true);
- })
- .catch(popupAjaxError);
- },
+ this.siteText
+ .save(attrs)
+ .then(() => {
+ this.commitBuffer();
+ this.set("saved", true);
+ })
+ .catch(popupAjaxError);
+ },
- revertChanges() {
- this.set("saved", false);
+ @action
+ revertChanges() {
+ this.set("saved", false);
- bootbox.confirm(I18n.t("admin.site_text.revert_confirm"), (result) => {
- if (result) {
- this.siteText
- .revert(this.locale)
- .then((props) => {
- const buffered = this.buffered;
- buffered.setProperties(props);
- this.commitBuffer();
- })
- .catch(popupAjaxError);
- }
- });
- },
+ this.dialog.yesNoConfirm({
+ message: I18n.t("admin.site_text.revert_confirm"),
+ didConfirm: () => {
+ this.siteText
+ .revert(this.locale)
+ .then((props) => {
+ const buffered = this.buffered;
+ buffered.setProperties(props);
+ this.commitBuffer();
+ })
+ .catch(popupAjaxError);
+ },
+ });
},
});
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-site-text-index.js b/app/assets/javascripts/admin/addon/controllers/admin-site-text-index.js
index 2f2b708d17..1ec7abbbea 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-site-text-index.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-site-text-index.js
@@ -42,21 +42,11 @@ export default Controller.extend({
}
},
- @discourseComputed("locale")
- showFallbackLocaleWarning() {
- return (
- this.siteSettings.allow_user_locale &&
- this.siteSettings.set_locale_from_accept_language_header &&
- this.fallbackLocaleFullName
- );
- },
-
actions: {
edit(siteText) {
this.transitionToRoute("adminSiteText.edit", siteText.get("id"), {
queryParams: {
locale: this.locale,
- localeFullName: this.availableLocales[this.locale],
},
});
},
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-user-badges.js b/app/assets/javascripts/admin/addon/controllers/admin-user-badges.js
index 9461374085..b36a22af0c 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-user-badges.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-user-badges.js
@@ -2,13 +2,14 @@ import Controller, { inject as controller } from "@ember/controller";
import { alias, sort } from "@ember/object/computed";
import GrantBadgeController from "discourse/mixins/grant-badge-controller";
import I18n from "I18n";
-import bootbox from "bootbox";
import discourseComputed from "discourse-common/utils/decorators";
import { next } from "@ember/runloop";
import { popupAjaxError } from "discourse/lib/ajax-error";
+import { inject as service } from "@ember/service";
export default Controller.extend(GrantBadgeController, {
adminUser: controller(),
+ dialog: service(),
user: alias("adminUser.model"),
userBadges: alias("model"),
allBadges: alias("badges"),
@@ -90,18 +91,14 @@ export default Controller.extend(GrantBadgeController, {
},
revokeBadge(userBadge) {
- return bootbox.confirm(
- I18n.t("admin.badges.revoke_confirm"),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (result) => {
- if (result) {
- userBadge.revoke().then(() => {
- this.model.removeObject(userBadge);
- });
- }
- }
- );
+ return this.dialog.yesNoConfirm({
+ message: I18n.t("admin.badges.revoke_confirm"),
+ didConfirm: () => {
+ return userBadge.revoke().then(() => {
+ this.model.removeObject(userBadge);
+ });
+ },
+ });
},
},
});
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-user-index.js b/app/assets/javascripts/admin/addon/controllers/admin-user-index.js
index 807bb39932..b6859b9ce0 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-user-index.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-user-index.js
@@ -6,17 +6,16 @@ import CanCheckEmails from "discourse/mixins/can-check-emails";
import Controller from "@ember/controller";
import I18n from "I18n";
import { ajax } from "discourse/lib/ajax";
-import bootbox from "bootbox";
import discourseComputed from "discourse-common/utils/decorators";
import getURL from "discourse-common/lib/get-url";
import { htmlSafe } from "@ember/template";
-import { iconHTML } from "discourse-common/lib/icon-library";
import { extractError, popupAjaxError } from "discourse/lib/ajax-error";
import { inject as service } from "@ember/service";
import showModal from "discourse/lib/show-modal";
export default Controller.extend(CanCheckEmails, {
router: service(),
+ dialog: service(),
adminTools: service(),
originalPrimaryGroupId: null,
customGroupIdsBuffer: null,
@@ -130,7 +129,7 @@ export default Controller.extend(CanCheckEmails, {
groupAdded(added) {
this.model
.groupAdded(added)
- .catch(() => bootbox.alert(I18n.t("generic_error")));
+ .catch(() => this.dialog.alert(I18n.t("generic_error")));
},
groupRemoved(groupId) {
@@ -141,7 +140,7 @@ export default Controller.extend(CanCheckEmails, {
this.set("originalPrimaryGroupId", null);
}
})
- .catch(() => bootbox.alert(I18n.t("generic_error")));
+ .catch(() => this.dialog.alert(I18n.t("generic_error")));
},
@discourseComputed("ssoLastPayload")
@@ -156,16 +155,16 @@ export default Controller.extend(CanCheckEmails, {
.then(() => DiscourseURL.redirectTo("/"))
.catch((e) => {
if (e.status === 404) {
- bootbox.alert(I18n.t("admin.impersonate.not_found"));
+ this.dialog.alert(I18n.t("admin.impersonate.not_found"));
} else {
- bootbox.alert(I18n.t("admin.impersonate.invalid"));
+ this.dialog.alert(I18n.t("admin.impersonate.invalid"));
}
});
},
logOut() {
return this.model
.logOut()
- .then(() => bootbox.alert(I18n.t("admin.user.logged_out")));
+ .then(() => this.dialog.alert(I18n.t("admin.user.logged_out")));
},
resetBounceScore() {
return this.model.resetBounceScore();
@@ -188,13 +187,15 @@ export default Controller.extend(CanCheckEmails, {
const error = I18n.t("admin.user.deactivate_failed", {
error: this._formatError(e),
});
- bootbox.alert(error);
+ this.dialog.alert(error);
});
},
sendActivationEmail() {
return this.model
.sendActivationEmail()
- .then(() => bootbox.alert(I18n.t("admin.user.activation_email_sent")))
+ .then(() =>
+ this.dialog.alert(I18n.t("admin.user.activation_email_sent"))
+ )
.catch(popupAjaxError);
},
activate() {
@@ -210,7 +211,7 @@ export default Controller.extend(CanCheckEmails, {
const error = I18n.t("admin.user.activate_failed", {
error: this._formatError(e),
});
- bootbox.alert(error);
+ this.dialog.alert(error);
});
},
revokeAdmin() {
@@ -221,7 +222,7 @@ export default Controller.extend(CanCheckEmails, {
.grantAdmin()
.then((result) => {
if (result.email_confirmation_required) {
- bootbox.alert(I18n.t("admin.user.grant_admin_confirm"));
+ this.dialog.alert(I18n.t("admin.user.grant_admin_confirm"));
}
})
.catch((error) => {
@@ -255,7 +256,7 @@ export default Controller.extend(CanCheckEmails, {
I18n.t("admin.user.trust_level_change_failed", {
error: this._formatError(e),
});
- bootbox.alert(error);
+ this.dialog.alert(error);
});
},
restoreTrustLevel() {
@@ -275,7 +276,7 @@ export default Controller.extend(CanCheckEmails, {
I18n.t("admin.user.trust_level_change_failed", {
error: this._formatError(e),
});
- bootbox.alert(error);
+ this.dialog.alert(error);
});
},
unsilence() {
@@ -287,7 +288,6 @@ export default Controller.extend(CanCheckEmails, {
anonymize() {
const user = this.model;
- const message = I18n.t("admin.user.anonymize_confirm");
const performAnonymize = () => {
this.model
@@ -302,31 +302,32 @@ export default Controller.extend(CanCheckEmails, {
document.location = getURL("/admin/users/list/active");
}
} else {
- bootbox.alert(I18n.t("admin.user.anonymize_failed"));
+ this.dialog.alert(I18n.t("admin.user.anonymize_failed"));
if (data.user) {
user.setProperties(data.user);
}
}
})
- .catch(() => bootbox.alert(I18n.t("admin.user.anonymize_failed")));
+ .catch(() =>
+ this.dialog.alert(I18n.t("admin.user.anonymize_failed"))
+ );
};
- const buttons = [
- {
- label: I18n.t("composer.cancel"),
- class: "cancel",
- link: true,
- },
- {
- label: I18n.t("admin.user.anonymize_yes"),
- class: "btn btn-danger",
- icon: iconHTML("exclamation-triangle"),
- callback: () => {
- performAnonymize();
- },
- },
- ];
- bootbox.dialog(message, buttons, { classes: "delete-user-modal" });
+ this.dialog.alert({
+ message: I18n.t("admin.user.anonymize_confirm"),
+ class: "delete-user-modal",
+ buttons: [
+ {
+ icon: "exclamation-triangle",
+ label: I18n.t("admin.user.anonymize_yes"),
+ class: "btn-danger",
+ action: () => performAnonymize(),
+ },
+ {
+ label: I18n.t("composer.cancel"),
+ },
+ ],
+ });
},
disableSecondFactor() {
@@ -345,11 +346,10 @@ export default Controller.extend(CanCheckEmails, {
destroy() {
const postCount = this.get("model.post_count");
const maxPostCount = this.siteSettings.delete_all_posts_max;
- const message = I18n.t("admin.user.delete_confirm");
const location = document.location.pathname;
const performDestroy = (block) => {
- bootbox.dialog(I18n.t("admin.user.deleting_user"));
+ this.dialog.notice(I18n.t("admin.user.deleting_user"));
let formData = { context: location };
if (block) {
formData["block_email"] = true;
@@ -369,38 +369,39 @@ export default Controller.extend(CanCheckEmails, {
document.location = getURL("/admin/users/list/active");
}
} else {
- bootbox.alert(I18n.t("admin.user.delete_failed"));
+ this.dialog.alert(I18n.t("admin.user.delete_failed"));
}
})
.catch(() => {
- bootbox.alert(I18n.t("admin.user.delete_failed"));
+ this.dialog.alert(I18n.t("admin.user.delete_failed"));
});
};
- const buttons = [
- {
- label: I18n.t("composer.cancel"),
- class: "btn",
- link: true,
- },
- {
- icon: iconHTML("exclamation-triangle"),
- label: I18n.t("admin.user.delete_and_block"),
- class: "btn btn-danger",
- callback: () => {
- performDestroy(true);
+ this.dialog.alert({
+ title: I18n.t("admin.user.delete_confirm_title"),
+ message: I18n.t("admin.user.delete_confirm"),
+ class: "delete-user-modal",
+ buttons: [
+ {
+ label: I18n.t("admin.user.delete_dont_block"),
+ class: "btn-primary",
+ action: () => {
+ return performDestroy(false);
+ },
},
- },
- {
- label: I18n.t("admin.user.delete_dont_block"),
- class: "btn btn-primary",
- callback: () => {
- performDestroy(false);
+ {
+ icon: "exclamation-triangle",
+ label: I18n.t("admin.user.delete_and_block"),
+ class: "btn-danger",
+ action: () => {
+ return performDestroy(true);
+ },
},
- },
- ];
-
- bootbox.dialog(message, buttons, { classes: "delete-user-modal" });
+ {
+ label: I18n.t("composer.cancel"),
+ },
+ ],
+ });
},
promptTargetUser() {
@@ -439,12 +440,12 @@ export default Controller.extend(CanCheckEmails, {
model: this.model,
});
} else {
- bootbox.alert(I18n.t("admin.user.merge_failed"));
+ this.dialog.alert(I18n.t("admin.user.merge_failed"));
}
})
.catch(() => {
AdminUser.find(user.id).then((u) => user.setProperties(u));
- bootbox.alert(I18n.t("admin.user.merge_failed"));
+ this.dialog.alert(I18n.t("admin.user.merge_failed"));
});
},
@@ -532,7 +533,7 @@ export default Controller.extend(CanCheckEmails, {
data: { primary_group_id: primaryGroupId },
})
.then(() => this.set("originalPrimaryGroupId", primaryGroupId))
- .catch(() => bootbox.alert(I18n.t("generic_error")));
+ .catch(() => this.dialog.alert(I18n.t("generic_error")));
},
resetPrimaryGroup() {
@@ -540,16 +541,10 @@ export default Controller.extend(CanCheckEmails, {
},
deleteSSORecord() {
- return bootbox.confirm(
- I18n.t("admin.user.discourse_connect.confirm_delete"),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (confirmed) => {
- if (confirmed) {
- return this.model.deleteSSORecord();
- }
- }
- );
+ return this.dialog.yesNoConfirm({
+ message: I18n.t("admin.user.discourse_connect.confirm_delete"),
+ didConfirm: () => this.model.deleteSSORecord(),
+ });
},
checkSsoEmail() {
@@ -607,7 +602,7 @@ export default Controller.extend(CanCheckEmails, {
let error;
AdminUser.find(user.get("id")).then((u) => user.setProperties(u));
error = extractError(e) || I18n.t("admin.user.delete_posts_failed");
- bootbox.alert(error);
+ this.dialog.alert(error);
});
};
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-users-list-show.js b/app/assets/javascripts/admin/addon/controllers/admin-users-list-show.js
index abd59ab1f1..e51e7c3a5c 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-users-list-show.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-users-list-show.js
@@ -59,10 +59,10 @@ export default Controller.extend(CanCheckEmails, {
page,
})
.then((result) => {
- if (result && result.length > 0) {
- this._results[page] = result;
- this.set("model", this._results.flat());
- } else {
+ this._results[page] = result;
+ this.set("model", this._results.flat());
+
+ if (result.length === 0) {
this._canLoadMore = false;
}
})
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-watched-words-action.js b/app/assets/javascripts/admin/addon/controllers/admin-watched-words-action.js
index d84d80615d..a1b158a677 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-watched-words-action.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-watched-words-action.js
@@ -2,16 +2,17 @@ import Controller, { inject as controller } from "@ember/controller";
import I18n from "I18n";
import WatchedWord from "admin/models/watched-word";
import { ajax } from "discourse/lib/ajax";
-import bootbox from "bootbox";
import discourseComputed from "discourse-common/utils/decorators";
import { fmt } from "discourse/lib/computed";
import { or } from "@ember/object/computed";
import { schedule } from "@ember/runloop";
import showModal from "discourse/lib/show-modal";
+import { inject as service } from "@ember/service";
export default Controller.extend({
adminWatchedWords: controller(),
actionNameKey: null,
+ dialog: service(),
downloadLink: fmt(
"actionNameKey",
"/admin/customize/watched_words/action/%@/download"
@@ -93,25 +94,21 @@ export default Controller.extend({
clearAll() {
const actionKey = this.actionNameKey;
- bootbox.confirm(
- I18n.t("admin.watched_words.clear_all_confirm", {
+ this.dialog.yesNoConfirm({
+ message: I18n.t("admin.watched_words.clear_all_confirm", {
action: I18n.t("admin.watched_words.actions." + actionKey),
}),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (result) => {
- if (result) {
- ajax(`/admin/customize/watched_words/action/${actionKey}.json`, {
- type: "DELETE",
- }).then(() => {
- const action = this.findAction(actionKey);
- if (action) {
- action.set("words", []);
- }
- });
- }
- }
- );
+ didConfirm: () => {
+ ajax(`/admin/customize/watched_words/action/${actionKey}.json`, {
+ type: "DELETE",
+ }).then(() => {
+ const action = this.findAction(actionKey);
+ if (action) {
+ action.set("words", []);
+ }
+ });
+ },
+ });
},
},
});
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-web-hooks-show.js b/app/assets/javascripts/admin/addon/controllers/admin-web-hooks-show.js
index a952f4e546..ba75e29199 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-web-hooks-show.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-web-hooks-show.js
@@ -2,15 +2,16 @@ import Controller, { inject as controller } from "@ember/controller";
import EmberObject from "@ember/object";
import I18n from "I18n";
import { alias } from "@ember/object/computed";
-import bootbox from "bootbox";
import discourseComputed from "discourse-common/utils/decorators";
import { extractDomainFromUrl } from "discourse/lib/utilities";
import { isAbsoluteURL } from "discourse-common/lib/get-url";
import { isEmpty } from "@ember/utils";
import { popupAjaxError } from "discourse/lib/ajax-error";
+import { inject as service } from "@ember/service";
export default Controller.extend({
adminWebHooks: controller(),
+ dialog: service(),
eventTypes: alias("adminWebHooks.eventTypes"),
defaultEventTypes: alias("adminWebHooks.defaultEventTypes"),
contentTypes: alias("adminWebHooks.contentTypes"),
@@ -113,39 +114,28 @@ export default Controller.extend({
domain.match(/127\.\d+\.\d+\.\d+/) ||
isAbsoluteURL(url)
) {
- return bootbox.confirm(
- I18n.t("admin.web_hooks.warn_local_payload_url"),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (result) => {
- if (result) {
- return saveWebHook();
- }
- }
- );
+ return this.dialog.yesNoConfirm({
+ message: I18n.t("admin.web_hooks.warn_local_payload_url"),
+ didConfirm: () => saveWebHook(),
+ });
}
return saveWebHook();
},
destroy() {
- return bootbox.confirm(
- I18n.t("admin.web_hooks.delete_confirm"),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (result) => {
- if (result) {
- const model = this.model;
- model
- .destroyRecord()
- .then(() => {
- this.adminWebHooks.get("model").removeObject(model);
- this.transitionToRoute("adminWebHooks");
- })
- .catch(popupAjaxError);
- }
- }
- );
+ return this.dialog.yesNoConfirm({
+ message: I18n.t("admin.web_hooks.delete_confirm"),
+ didConfirm: () => {
+ this.model
+ .destroyRecord()
+ .then(() => {
+ this.adminWebHooks.get("model").removeObject(this.model);
+ this.transitionToRoute("adminWebHooks");
+ })
+ .catch(popupAjaxError);
+ },
+ });
},
},
});
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-web-hooks.js b/app/assets/javascripts/admin/addon/controllers/admin-web-hooks.js
index 6e1acf97dc..405f859413 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-web-hooks.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-web-hooks.js
@@ -1,30 +1,29 @@
import Controller from "@ember/controller";
import I18n from "I18n";
-import bootbox from "bootbox";
import { popupAjaxError } from "discourse/lib/ajax-error";
+import { inject as service } from "@ember/service";
+import { action } from "@ember/object";
export default Controller.extend({
- actions: {
- destroy(webhook) {
- return bootbox.confirm(
- I18n.t("admin.web_hooks.delete_confirm"),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (result) => {
- if (result) {
- webhook
- .destroyRecord()
- .then(() => {
- this.model.removeObject(webhook);
- })
- .catch(popupAjaxError);
- }
- }
- );
- },
+ dialog: service(),
- loadMore() {
- this.model.loadMore();
- },
+ @action
+ destroy(webhook) {
+ return this.dialog.yesNoConfirm({
+ message: I18n.t("admin.web_hooks.delete_confirm"),
+ didConfirm: () => {
+ webhook
+ .destroyRecord()
+ .then(() => {
+ this.model.removeObject(webhook);
+ })
+ .catch(popupAjaxError);
+ },
+ });
+ },
+
+ @action
+ loadMore() {
+ this.model.loadMore();
},
});
diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-edit-badge-groupings.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-edit-badge-groupings.js
index aa7d5665fa..c453a4f8b4 100644
--- a/app/assets/javascripts/admin/addon/controllers/modals/admin-edit-badge-groupings.js
+++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-edit-badge-groupings.js
@@ -3,10 +3,12 @@ import Controller from "@ember/controller";
import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { ajax } from "discourse/lib/ajax";
-import bootbox from "bootbox";
import { observes } from "discourse-common/utils/decorators";
+import { inject as service } from "@ember/service";
export default Controller.extend(ModalFunctionality, {
+ dialog: service(),
+
@observes("model")
modelChanged() {
const model = this.model;
@@ -78,7 +80,7 @@ export default Controller.extend(ModalFunctionality, {
this.setProperties({ model: null, workingCopy: null });
this.send("closeModal");
},
- () => bootbox.alert(I18n.t("generic_error"))
+ () => this.dialog.alert(I18n.t("generic_error"))
);
},
},
diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-install-theme.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-install-theme.js
index 0290760180..d42446ddbb 100644
--- a/app/assets/javascripts/admin/addon/controllers/modals/admin-install-theme.js
+++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-install-theme.js
@@ -24,7 +24,7 @@ export default Controller.extend(ModalFunctionality, {
keyGenUrl: "/admin/themes/generate_key_pair",
importUrl: "/admin/themes/import",
recordType: "theme",
- checkPrivate: match("uploadUrl", /^ssh\:\/\/.*\@.*\.git$|.*\@.*\:.*\.git$/),
+ checkPrivate: match("uploadUrl", /^ssh:\/\/.+@.+$|.+@.+:.+$/),
localFile: null,
uploadUrl: null,
uploadName: null,
@@ -93,10 +93,7 @@ export default Controller.extend(ModalFunctionality, {
this._keyLoading = true;
ajax(this.keyGenUrl, { type: "POST" })
.then((pair) => {
- this.setProperties({
- privateKey: pair.private_key,
- publicKey: pair.public_key,
- });
+ this.set("publicKey", pair.public_key);
})
.catch(popupAjaxError)
.finally(() => {
@@ -139,7 +136,6 @@ export default Controller.extend(ModalFunctionality, {
this.setProperties({
duplicateRemoteThemeWarning: null,
privateChecked: false,
- privateKey: null,
localFile: null,
uploadUrl: null,
publicKey: null,
@@ -216,7 +212,7 @@ export default Controller.extend(ModalFunctionality, {
};
if (this.privateChecked) {
- options.data.private_key = this.privateKey;
+ options.data.public_key = this.publicKey;
}
}
@@ -239,10 +235,10 @@ export default Controller.extend(ModalFunctionality, {
this.send("closeModal");
})
.then(() => {
- this.setProperties({ privateKey: null, publicKey: null });
+ this.set("publicKey", null);
})
.catch((error) => {
- if (!this.privateKey || this.themeCannotBeInstalled) {
+ if (!this.publicKey || this.themeCannotBeInstalled) {
return popupAjaxError(error);
}
diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-reseed.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-reseed.js
index 672f6d8db3..744921b02a 100644
--- a/app/assets/javascripts/admin/addon/controllers/modals/admin-reseed.js
+++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-reseed.js
@@ -2,9 +2,10 @@ import Controller from "@ember/controller";
import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { ajax } from "discourse/lib/ajax";
-import bootbox from "bootbox";
+import { inject as service } from "@ember/service";
export default Controller.extend(ModalFunctionality, {
+ dialog: service(),
loading: true,
reseeding: false,
categories: null,
@@ -35,11 +36,11 @@ export default Controller.extend(ModalFunctionality, {
},
type: "POST",
})
- .then(
- () => this.send("closeModal"),
- () => bootbox.alert(I18n.t("generic_error"))
- )
- .finally(() => this.set("reseeding", false));
+ .catch(() => this.dialog.alert(I18n.t("generic_error")))
+ .finally(() => {
+ this.set("reseeding", false);
+ this.send("closeModal");
+ });
},
},
});
diff --git a/app/assets/javascripts/admin/addon/routes/admin-backups.js b/app/assets/javascripts/admin/addon/routes/admin-backups.js
index 063eeec9d7..c78ef4ca1c 100644
--- a/app/assets/javascripts/admin/addon/routes/admin-backups.js
+++ b/app/assets/javascripts/admin/addon/routes/admin-backups.js
@@ -6,14 +6,15 @@ import I18n from "I18n";
import PreloadStore from "discourse/lib/preload-store";
import User from "discourse/models/user";
import { ajax } from "discourse/lib/ajax";
-import bootbox from "bootbox";
import { extractError } from "discourse/lib/ajax-error";
import getURL from "discourse-common/lib/get-url";
import showModal from "discourse/lib/show-modal";
-
+import { inject as service } from "@ember/service";
const LOG_CHANNEL = "/admin/backups/logs";
export default DiscourseRoute.extend({
+ dialog: service(),
+
activate() {
this.messageBus.subscribe(LOG_CHANNEL, (log) => {
if (log.message === "[STARTED]") {
@@ -28,7 +29,7 @@ export default DiscourseRoute.extend({
"model.isOperationRunning",
false
);
- bootbox.alert(
+ this.dialog.alert(
I18n.t("admin.backups.operations.failed", {
operation: log.operation,
})
@@ -77,88 +78,72 @@ export default DiscourseRoute.extend({
this.transitionTo("admin.backups.logs");
Backup.start(withUploads).then((result) => {
if (!result.success) {
- bootbox.alert(result.message);
+ this.dialog.alert(result.message);
}
});
},
destroyBackup(backup) {
- bootbox.confirm(
- I18n.t("admin.backups.operations.destroy.confirm"),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (confirmed) => {
- if (confirmed) {
- backup
- .destroy()
- .then(() =>
- this.controllerFor("adminBackupsIndex")
- .get("model")
- .removeObject(backup)
- );
- }
- }
- );
+ return this.dialog.yesNoConfirm({
+ message: I18n.t("admin.backups.operations.destroy.confirm"),
+ didConfirm: () => {
+ backup
+ .destroy()
+ .then(() =>
+ this.controllerFor("adminBackupsIndex")
+ .get("model")
+ .removeObject(backup)
+ );
+ },
+ });
},
startRestore(backup) {
- bootbox.confirm(
- I18n.t("admin.backups.operations.restore.confirm"),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (confirmed) => {
- if (confirmed) {
- this.transitionTo("admin.backups.logs");
- backup.restore();
- }
- }
- );
+ this.dialog.yesNoConfirm({
+ message: I18n.t("admin.backups.operations.restore.confirm"),
+ didConfirm: () => {
+ this.transitionTo("admin.backups.logs");
+ backup.restore();
+ },
+ });
},
cancelOperation() {
- bootbox.confirm(
- I18n.t("admin.backups.operations.cancel.confirm"),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (confirmed) => {
- if (confirmed) {
- Backup.cancel().then(() => {
- this.controllerFor("adminBackups").set(
- "model.isOperationRunning",
- false
- );
- });
- }
- }
- );
+ this.dialog.yesNoConfirm({
+ message: I18n.t("admin.backups.operations.cancel.confirm"),
+ didConfirm: () => {
+ Backup.cancel().then(() => {
+ this.controllerFor("adminBackups").set(
+ "model.isOperationRunning",
+ false
+ );
+ });
+ },
+ });
},
rollback() {
- bootbox.confirm(
- I18n.t("admin.backups.operations.rollback.confirm"),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (confirmed) => {
- if (confirmed) {
- Backup.rollback().then((result) => {
- if (!result.success) {
- bootbox.alert(result.message);
- } else {
- // redirect to homepage (session might be lost)
- window.location = getURL("/");
- }
- });
- }
- }
- );
+ return this.dialog.yesNoConfirm({
+ message: I18n.t("admin.backups.operations.rollback.confirm"),
+ didConfirm: () => {
+ Backup.rollback().then((result) => {
+ if (!result.success) {
+ this.dialog.alert(result.message);
+ } else {
+ // redirect to homepage (session might be lost)
+ window.location = getURL("/");
+ }
+ });
+ },
+ });
},
uploadSuccess(filename) {
- bootbox.alert(I18n.t("admin.backups.upload.success", { filename }));
+ this.dialog.alert(I18n.t("admin.backups.upload.success", { filename }));
},
uploadError(filename, message) {
- bootbox.alert(
+ this.dialog.alert(
I18n.t("admin.backups.upload.error", { filename, message })
);
},
@@ -173,7 +158,7 @@ export default DiscourseRoute.extend({
);
})
.catch((error) => {
- bootbox.alert(
+ this.dialog.alert(
I18n.t("admin.backups.backup_storage_error", {
error_message: extractError(error),
})
diff --git a/app/assets/javascripts/admin/addon/routes/admin-badges/show.js b/app/assets/javascripts/admin/addon/routes/admin-badges/show.js
index 4d1eb1db76..b17d253b66 100644
--- a/app/assets/javascripts/admin/addon/routes/admin-badges/show.js
+++ b/app/assets/javascripts/admin/addon/routes/admin-badges/show.js
@@ -2,11 +2,13 @@ import Badge from "discourse/models/badge";
import I18n from "I18n";
import Route from "@ember/routing/route";
import { ajax } from "discourse/lib/ajax";
-import bootbox from "bootbox";
import { action, get } from "@ember/object";
import showModal from "discourse/lib/show-modal";
+import { inject as service } from "@ember/service";
export default class AdminBadgesShowRoute extends Route {
+ @service dialog;
+
serialize(m) {
return { badge_id: get(m, "id") || "new" };
}
@@ -58,7 +60,7 @@ export default class AdminBadgesShowRoute extends Route {
badge.set("preview_loading", false);
// eslint-disable-next-line no-console
console.error(error);
- bootbox.alert("Network error");
+ this.dialog.alert("Network error");
});
}
}
diff --git a/app/assets/javascripts/admin/addon/services/admin-tools.js b/app/assets/javascripts/admin/addon/services/admin-tools.js
index 17266be822..3d06845508 100644
--- a/app/assets/javascripts/admin/addon/services/admin-tools.js
+++ b/app/assets/javascripts/admin/addon/services/admin-tools.js
@@ -1,7 +1,7 @@
import AdminUser from "admin/models/admin-user";
import I18n from "I18n";
import { Promise } from "rsvp";
-import Service from "@ember/service";
+import Service, { inject as service } from "@ember/service";
import { ajax } from "discourse/lib/ajax";
import bootbox from "bootbox";
import { getOwner } from "discourse-common/lib/get-owner";
@@ -12,6 +12,8 @@ import showModal from "discourse/lib/show-modal";
// and the admin application. Use this if you need front end code to access admin
// modules. Inject it optionally, and if it exists go to town!
export default Service.extend({
+ dialog: service(),
+
showActionLogs(target, filters) {
const controller = getOwner(target).lookup(
"controller:adminLogs.staffActionLogs"
@@ -120,7 +122,7 @@ export default Service.extend({
}
})
.catch(() => {
- bootbox.alert(I18n.t("admin.user.delete_failed"));
+ this.dialog.alert(I18n.t("admin.user.delete_failed"));
reject();
});
},
diff --git a/app/assets/javascripts/admin/addon/templates/admin-badges/show.hbs b/app/assets/javascripts/admin/addon/templates/admin-badges/show.hbs
index 1cf2bbab5a..31b16af4cc 100644
--- a/app/assets/javascripts/admin/addon/templates/admin-badges/show.hbs
+++ b/app/assets/javascripts/admin/addon/templates/admin-badges/show.hbs
@@ -97,21 +97,21 @@
-
diff --git a/app/assets/javascripts/admin/addon/templates/components/composer-fullscreen-prompt.hbs b/app/assets/javascripts/admin/addon/templates/components/composer-fullscreen-prompt.hbs
new file mode 100644
index 0000000000..e75db03048
--- /dev/null
+++ b/app/assets/javascripts/admin/addon/templates/components/composer-fullscreen-prompt.hbs
@@ -0,0 +1,3 @@
+
+ {{html-safe (i18n "composer.exit_fullscreen_prompt")}}
+
diff --git a/app/assets/javascripts/admin/addon/templates/logs/screened-ip-addresses.hbs b/app/assets/javascripts/admin/addon/templates/logs/screened-ip-addresses.hbs
index 56af8c935f..63489dcdee 100644
--- a/app/assets/javascripts/admin/addon/templates/logs/screened-ip-addresses.hbs
+++ b/app/assets/javascripts/admin/addon/templates/logs/screened-ip-addresses.hbs
@@ -63,9 +63,7 @@
{{#if item.editing}}
-
- {{i18n "cancel"}}
-
+
{{else}}
diff --git a/app/assets/javascripts/admin/addon/templates/site-text-index.hbs b/app/assets/javascripts/admin/addon/templates/site-text-index.hbs
index b10fa0d239..ff74b4cf7f 100644
--- a/app/assets/javascripts/admin/addon/templates/site-text-index.hbs
+++ b/app/assets/javascripts/admin/addon/templates/site-text-index.hbs
@@ -1,39 +1,60 @@
{{i18n "admin.site_text.description"}}
-
+
-
+
-
+
- {{#if this.showFallbackLocaleWarning}}
-
- {{d-icon "exclamation-circle"}}
- {{i18n "admin.site_text.fallback_locale_warning" fallback=this.fallbackLocaleFullName}}
-
- {{/if}}
-
{{#if this.siteTexts.extras.recommended}}
{{i18n "admin.site_text.recommended"}}
{{/if}}
{{#each this.siteTexts as |siteText|}}
-
+
{{/each}}
{{#if this.siteTexts.extras.has_more}}
diff --git a/app/assets/javascripts/admin/package.json b/app/assets/javascripts/admin/package.json
index 3d66a9d06c..aa8025a052 100644
--- a/app/assets/javascripts/admin/package.json
+++ b/app/assets/javascripts/admin/package.json
@@ -17,9 +17,9 @@
"dependencies": {
"ember-auto-import": "^2.4.2",
"ember-cli-babel": "^7.26.10",
- "ember-cli-htmlbars": "^6.1.0",
- "webpack": "^5.73.0",
- "xss": "^1.0.13"
+ "ember-cli-htmlbars": "^6.1.1",
+ "webpack": "^5.74.0",
+ "xss": "^1.0.14"
},
"devDependencies": {
"@ember/optional-features": "^2.0.0",
diff --git a/app/assets/javascripts/discourse-common/addon/lib/icon-library.js b/app/assets/javascripts/discourse-common/addon/lib/icon-library.js
index f7434318f5..a969a2a116 100644
--- a/app/assets/javascripts/discourse-common/addon/lib/icon-library.js
+++ b/app/assets/javascripts/discourse-common/addon/lib/icon-library.js
@@ -27,7 +27,7 @@ const REPLACEMENTS = {
"notification.group_mentioned": "users",
"notification.quoted": "quote-right",
"notification.replied": "reply",
- "notification.posted": "reply",
+ "notification.posted": "discourse-bell-exclamation",
"notification.edited": "pencil-alt",
"notification.bookmark_reminder": "discourse-bookmark-clock",
"notification.liked": "heart",
diff --git a/app/assets/javascripts/discourse-common/addon/lib/raw-handlebars.js b/app/assets/javascripts/discourse-common/addon/lib/raw-handlebars.js
index f49e427317..6d805d3366 100644
--- a/app/assets/javascripts/discourse-common/addon/lib/raw-handlebars.js
+++ b/app/assets/javascripts/discourse-common/addon/lib/raw-handlebars.js
@@ -73,9 +73,10 @@ if (Handlebars.Compiler) {
RawHandlebars.JavaScriptCompiler;
RawHandlebars.JavaScriptCompiler.prototype.namespace = "RawHandlebars";
- RawHandlebars.precompile = function (value, asObject) {
+ RawHandlebars.precompile = function (value, asObject, { plugins = [] } = {}) {
let ast = Handlebars.parse(value);
replaceGet(ast);
+ plugins.forEach((plugin) => plugin(ast));
let options = {
knownHelpers: {
@@ -96,9 +97,10 @@ if (Handlebars.Compiler) {
);
};
- RawHandlebars.compile = function (string) {
+ RawHandlebars.compile = function (string, { plugins = [] } = {}) {
let ast = Handlebars.parse(string);
replaceGet(ast);
+ plugins.forEach((plugin) => plugin(ast));
// this forces us to rewrite helpers
let options = { data: true, stringParams: true };
diff --git a/app/assets/javascripts/discourse-common/addon/resolver.js b/app/assets/javascripts/discourse-common/addon/resolver.js
index 88f2bdceb1..462bec5212 100644
--- a/app/assets/javascripts/discourse-common/addon/resolver.js
+++ b/app/assets/javascripts/discourse-common/addon/resolver.js
@@ -38,55 +38,68 @@ const DEPRECATED_MODULES = new Map(
since: "2.4.0",
dropFrom: "2.9.0.beta1",
},
+ // Deprecations below are silenced because they're in widespread use, and upgrading
+ // themes/plugins right now would break their compatibility with the stable branch.
+ // These should be unsilenced for the release of 2.9.0 stable.
"store:main": {
newName: "service:store",
since: "2.8.0.beta8",
dropFrom: "2.9.0.beta1",
+ silent: true,
},
"search-service:main": {
newName: "service:search",
since: "2.8.0.beta8",
dropFrom: "2.9.0.beta1",
+ silent: true,
},
"key-value-store:main": {
newName: "service:key-value-store",
since: "2.9.0.beta7",
dropFrom: "3.0.0",
+ silent: true,
},
"pm-topic-tracking-state:main": {
newName: "service:pm-topic-tracking-state",
since: "2.9.0.beta7",
dropFrom: "3.0.0",
+ silent: true,
},
"message-bus:main": {
newName: "service:message-bus",
since: "2.9.0.beta7",
dropFrom: "3.0.0",
+ silent: true,
},
"site-settings:main": {
newName: "service:site-settings",
since: "2.9.0.beta7",
dropFrom: "3.0.0",
+ silent: true,
},
"current-user:main": {
newName: "service:current-user",
since: "2.9.0.beta7",
dropFrom: "3.0.0",
+ silent: true,
},
"session:main": {
newName: "service:session",
since: "2.9.0.beta7",
dropFrom: "3.0.0",
+ silent: true,
},
"site:main": {
newName: "service:site",
since: "2.9.0.beta7",
dropFrom: "3.0.0",
+ silent: true,
},
"topic-tracking-state:main": {
newName: "service:topic-tracking-state",
since: "2.9.0.beta7",
dropFrom: "3.0.0",
+ silent: true,
},
})
);
@@ -138,13 +151,15 @@ export function buildResolver(baseName) {
_normalize(fullName) {
const deprecationInfo = DEPRECATED_MODULES.get(fullName);
if (deprecationInfo) {
- deprecated(
- `"${fullName}" is deprecated, use "${deprecationInfo.newName}" instead`,
- {
- since: deprecationInfo.since,
- dropFrom: deprecationInfo.dropFrom,
- }
- );
+ if (!deprecationInfo.silent) {
+ deprecated(
+ `"${fullName}" is deprecated, use "${deprecationInfo.newName}" instead`,
+ {
+ since: deprecationInfo.since,
+ dropFrom: deprecationInfo.dropFrom,
+ }
+ );
+ }
fullName = deprecationInfo.newName;
}
diff --git a/app/assets/javascripts/discourse-common/addon/utils/handle-descriptor.js b/app/assets/javascripts/discourse-common/addon/utils/handle-descriptor.js
index eb15763279..d37e19111f 100644
--- a/app/assets/javascripts/discourse-common/addon/utils/handle-descriptor.js
+++ b/app/assets/javascripts/discourse-common/addon/utils/handle-descriptor.js
@@ -1,17 +1,18 @@
-import EmberObject, { computed, get } from "@ember/object";
+import CoreObject from "@ember/object/core";
+import { computed, get } from "@ember/object";
import extractValue from "./extract-value";
export default function handleDescriptor(target, key, desc, params = []) {
const val = extractValue(desc);
- if (typeof val === "function" && target instanceof EmberObject) {
+ if (typeof val === "function" && target instanceof CoreObject) {
// We're in a native class, so convert the method to a getter first
desc.writable = false;
desc.initializer = undefined;
desc.value = undefined;
desc.get = callUserSuppliedGet(params, val);
- return computed(target, key, desc);
+ return computed(...params)(target, key, desc);
} else {
return {
enumerable: desc.enumerable,
diff --git a/app/assets/javascripts/discourse-common/package.json b/app/assets/javascripts/discourse-common/package.json
index e4302cbfbc..54748e892f 100644
--- a/app/assets/javascripts/discourse-common/package.json
+++ b/app/assets/javascripts/discourse-common/package.json
@@ -23,11 +23,11 @@
"@uppy/xhr-upload": "^2.1.2",
"ember-auto-import": "^2.4.2",
"ember-cli-babel": "^7.26.10",
- "ember-cli-htmlbars": "^6.1.0",
+ "ember-cli-htmlbars": "^6.1.1",
"ember-resolver": "^8.0.3",
"handlebars": "^4.7.0",
"truth-helpers": "^1.0.0",
- "webpack": "^5.73.0"
+ "webpack": "^5.74.0"
},
"devDependencies": {
"@ember/optional-features": "^2.0.0",
diff --git a/app/assets/javascripts/discourse-hbr/package.json b/app/assets/javascripts/discourse-hbr/package.json
index 9a194a88f5..b0fd547a63 100644
--- a/app/assets/javascripts/discourse-hbr/package.json
+++ b/app/assets/javascripts/discourse-hbr/package.json
@@ -17,9 +17,9 @@
"dependencies": {
"ember-auto-import": "^2.4.2",
"ember-cli-babel": "^7.26.10",
- "ember-cli-htmlbars": "^6.1.0",
+ "ember-cli-htmlbars": "^6.1.1",
"handlebars": "^4.7.6",
- "webpack": "^5.73.0"
+ "webpack": "^5.74.0"
},
"devDependencies": {
"@ember/optional-features": "^2.0.0",
diff --git a/app/assets/javascripts/discourse-hbr/raw-handlebars-compiler.js b/app/assets/javascripts/discourse-hbr/raw-handlebars-compiler.js
index f6376ca5db..06dce15943 100644
--- a/app/assets/javascripts/discourse-hbr/raw-handlebars-compiler.js
+++ b/app/assets/javascripts/discourse-hbr/raw-handlebars-compiler.js
@@ -134,7 +134,19 @@ TemplateCompiler.prototype.initializeFeatures =
function initializeFeatures() {};
TemplateCompiler.prototype.processString = function (string, relativePath) {
- let filename = relativePath.replace(/^templates\//, "").replace(/\.hbr$/, "");
+ let filename;
+
+ const pluginName = relativePath.match(/^discourse\/plugins\/([^\/]+)\//)?.[1];
+
+ if (pluginName) {
+ filename = relativePath
+ .replace(`discourse/plugins/${pluginName}/`, "")
+ .replace(/^(discourse\/)?templates\//, "javascripts/");
+ } else {
+ filename = relativePath.replace(/^templates\//, "");
+ }
+
+ filename = filename.replace(/\.hbr$/, "");
return (
'import { template as compiler } from "discourse-common/lib/raw-handlebars";\n' +
diff --git a/app/assets/javascripts/discourse-js-processor.js b/app/assets/javascripts/discourse-js-processor.js
new file mode 100644
index 0000000000..5deb20f011
--- /dev/null
+++ b/app/assets/javascripts/discourse-js-processor.js
@@ -0,0 +1,110 @@
+/* global Babel:true */
+
+// This is executed in mini_racer to provide the JS logic for lib/discourse_js_processor.rb
+
+const makeEmberTemplateCompilerPlugin =
+ require("babel-plugin-ember-template-compilation").default;
+const precompile = require("ember-template-compiler").precompile;
+const Handlebars = require("handlebars").default;
+
+function manipulateAstNodeForTheme(node, themeId) {
+ // Magically add theme id as the first param for each of these helpers)
+ if (
+ node.path.parts &&
+ ["theme-i18n", "theme-prefix", "theme-setting"].includes(node.path.parts[0])
+ ) {
+ if (node.params.length === 1) {
+ node.params.unshift({
+ type: "NumberLiteral",
+ value: themeId,
+ original: themeId,
+ loc: { start: {}, end: {} },
+ });
+ }
+ }
+}
+
+function buildEmberTemplateManipulatorPlugin(themeId) {
+ return function () {
+ return {
+ name: "theme-template-manipulator",
+ visitor: {
+ SubExpression: (node) => manipulateAstNodeForTheme(node, themeId),
+ MustacheStatement: (node) => manipulateAstNodeForTheme(node, themeId),
+ },
+ };
+ };
+}
+
+function buildTemplateCompilerBabelPlugins({ themeId }) {
+ let compileFunction = precompile;
+ if (themeId) {
+ compileFunction = (src, opts) => {
+ return precompile(src, {
+ ...opts,
+ plugins: {
+ ast: [buildEmberTemplateManipulatorPlugin(themeId)],
+ },
+ });
+ };
+ }
+
+ return [
+ require("widget-hbs-compiler").WidgetHbsCompiler,
+ [
+ makeEmberTemplateCompilerPlugin(() => compileFunction),
+ { enableLegacyModules: ["ember-cli-htmlbars"] },
+ ],
+ ];
+}
+
+function buildThemeRawHbsTemplateManipulatorPlugin(themeId) {
+ return function (ast) {
+ ["SubExpression", "MustacheStatement"].forEach((pass) => {
+ let visitor = new Handlebars.Visitor();
+ visitor.mutating = true;
+ visitor[pass] = (node) => manipulateAstNodeForTheme(node, themeId);
+ visitor.accept(ast);
+ });
+ };
+}
+
+exports.compileRawTemplate = function (source, themeId) {
+ try {
+ const RawHandlebars = require("raw-handlebars").default;
+ const plugins = [];
+ if (themeId) {
+ plugins.push(buildThemeRawHbsTemplateManipulatorPlugin(themeId));
+ }
+ return RawHandlebars.precompile(source, false, { plugins }).toString();
+ } catch (error) {
+ // Workaround for https://github.com/rubyjs/mini_racer/issues/262
+ error.message = JSON.stringify(error.message);
+ throw error;
+ }
+};
+
+exports.transpile = function (
+ source,
+ { moduleId, filename, skipModule, themeId, commonPlugins } = {}
+) {
+ const plugins = [];
+ plugins.push(...buildTemplateCompilerBabelPlugins({ themeId }));
+ if (moduleId && !skipModule) {
+ plugins.push(["transform-modules-amd", { noInterop: true }]);
+ }
+ plugins.push(...commonPlugins);
+
+ try {
+ return Babel.transform(source, {
+ moduleId,
+ filename,
+ ast: false,
+ plugins,
+ }).code;
+ } catch (error) {
+ // Workaround for https://github.com/rubyjs/mini_racer/issues/262
+ error.message = JSON.stringify(error.message);
+ throw error;
+ }
+};
diff --git a/app/assets/javascripts/discourse-plugins/index.js b/app/assets/javascripts/discourse-plugins/index.js
new file mode 100644
index 0000000000..0f3612cbfe
--- /dev/null
+++ b/app/assets/javascripts/discourse-plugins/index.js
@@ -0,0 +1,213 @@
+"use strict";
+
+const path = require("path");
+const WatchedDir = require("broccoli-source").WatchedDir;
+const Funnel = require("broccoli-funnel");
+const mergeTrees = require("broccoli-merge-trees");
+const fs = require("fs");
+const concat = require("broccoli-concat");
+const RawHandlebarsCompiler = require("discourse-hbr/raw-handlebars-compiler");
+
+function fixLegacyExtensions(tree) {
+ return new Funnel(tree, {
+ getDestinationPath: function (relativePath) {
+ if (relativePath.endsWith(".es6")) {
+ return relativePath.slice(0, -4);
+ } else if (relativePath.endsWith(".raw.hbs")) {
+ return relativePath.replace(".raw.hbs", ".hbr");
+ }
+ return relativePath;
+ },
+ });
+}
+
+const COLOCATED_CONNECTOR_REGEX =
+ /^(?.*)\/connectors\/(?[^\/]+)\/(?[^\/\.]+)\.(?.+)$/;
+
+// Having connector templates and js in the same directory causes a clash
+// when outputting es6 modules. This shim separates colocated connectors
+// into separate js / template locations.
+function unColocateConnectors(tree) {
+ return new Funnel(tree, {
+ getDestinationPath: function (relativePath) {
+ const match = relativePath.match(COLOCATED_CONNECTOR_REGEX);
+ if (
+ match &&
+ match.groups.extension === "hbs" &&
+ !match.groups.prefix.endsWith("/templates")
+ ) {
+ const { prefix, outlet, name } = match.groups;
+ return `${prefix}/templates/connectors/${outlet}/${name}.hbs`;
+ }
+ if (
+ match &&
+ match.groups.extension === "js" &&
+ match.groups.prefix.endsWith("/templates")
+ ) {
+ // Some plugins are colocating connector JS under `/templates`
+ const { prefix, outlet, name } = match.groups;
+ const newPrefix = prefix.slice(0, -"/templates".length);
+ return `${newPrefix}/connectors/${outlet}/${name}.js`;
+ }
+ return relativePath;
+ },
+ });
+}
+
+function namespaceModules(tree, pluginDirectoryName) {
+ return new Funnel(tree, {
+ getDestinationPath: function (relativePath) {
+ return `discourse/plugins/${pluginDirectoryName}/${relativePath}`;
+ },
+ });
+}
+
+function parsePluginName(pluginRbPath) {
+ const pluginRb = fs.readFileSync(pluginRbPath, "utf8");
+ // Match parsing logic in `lib/plugin/metadata.rb`
+ for (const line of pluginRb.split("\n")) {
+ if (line.startsWith("#")) {
+ const [attribute, value] = line.slice(1).split(":", 2);
+ if (attribute.trim() === "name") {
+ return value.trim();
+ }
+ }
+ }
+ throw new Error(
+ `Unable to parse plugin name from metadata in ${pluginRbPath}`
+ );
+}
+
+module.exports = {
+ name: require("./package").name,
+
+ pluginInfos() {
+ const root = path.resolve("../../../../plugins");
+ const pluginDirectories = fs
+ .readdirSync(root, { withFileTypes: true })
+ .filter(
+ (dirent) =>
+ (dirent.isDirectory() || dirent.isSymbolicLink()) &&
+ !dirent.name.startsWith(".") &&
+ fs.existsSync(path.resolve(root, dirent.name, "plugin.rb"))
+ );
+
+ return pluginDirectories.map((directory) => {
+ const directoryName = directory.name;
+ const pluginName = parsePluginName(
+ path.resolve(root, directoryName, "plugin.rb")
+ );
+ const jsDirectory = path.resolve(
+ root,
+ directoryName,
+ "assets/javascripts"
+ );
+ const adminJsDirectory = path.resolve(
+ root,
+ directoryName,
+ "admin/assets/javascripts"
+ );
+ const testDirectory = path.resolve(
+ root,
+ directoryName,
+ "test/javascripts"
+ );
+ const hasJs = fs.existsSync(jsDirectory);
+ const hasAdminJs = fs.existsSync(adminJsDirectory);
+ const hasTests = fs.existsSync(testDirectory);
+ return {
+ pluginName,
+ directoryName,
+ jsDirectory,
+ adminJsDirectory,
+ testDirectory,
+ hasJs,
+ hasAdminJs,
+ hasTests,
+ };
+ });
+ },
+
+ generatePluginsTree() {
+ const appTree = this._generatePluginAppTree();
+ const testTree = this._generatePluginTestTree();
+ const adminTree = this._generatePluginAdminTree();
+ return mergeTrees([appTree, testTree, adminTree]);
+ },
+
+ _generatePluginAppTree() {
+ const trees = this.pluginInfos()
+ .filter((p) => p.hasJs)
+ .map(({ pluginName, directoryName, jsDirectory }) =>
+ this._buildAppTree({
+ directory: jsDirectory,
+ pluginName,
+ outputFile: `assets/plugins/${directoryName}.js`,
+ })
+ );
+ return mergeTrees(trees);
+ },
+
+ _generatePluginAdminTree() {
+ const trees = this.pluginInfos()
+ .filter((p) => p.hasAdminJs)
+ .map(({ pluginName, directoryName, adminJsDirectory }) =>
+ this._buildAppTree({
+ directory: adminJsDirectory,
+ pluginName,
+ outputFile: `assets/plugins/${directoryName}_admin.js`,
+ })
+ );
+ return mergeTrees(trees);
+ },
+
+ _buildAppTree({ directory, pluginName, outputFile }) {
+ let tree = new WatchedDir(directory);
+
+ tree = fixLegacyExtensions(tree);
+ tree = unColocateConnectors(tree);
+ tree = namespaceModules(tree, pluginName);
+
+ tree = RawHandlebarsCompiler(tree);
+ tree = this.compileTemplates(tree);
+
+ tree = this.processedAddonJsFiles(tree);
+
+ return concat(mergeTrees([tree]), {
+ inputFiles: ["**/*.js"],
+ outputFile,
+ allowNone: true,
+ });
+ },
+
+ _generatePluginTestTree() {
+ const trees = this.pluginInfos()
+ .filter((p) => p.hasTests)
+ .map(({ pluginName, directoryName, testDirectory }) => {
+ let tree = new WatchedDir(testDirectory);
+
+ tree = fixLegacyExtensions(tree);
+ tree = namespaceModules(tree, pluginName);
+ tree = this.processedAddonJsFiles(tree);
+
+ return concat(mergeTrees([tree]), {
+ inputFiles: ["**/*.js"],
+ outputFile: `assets/plugins/test/${directoryName}_tests.js`,
+ allowNone: true,
+ });
+ });
+ return mergeTrees(trees);
+ },
+
+ shouldCompileTemplates() {
+ // The base Addon implementation checks for template
+ // files in the addon directories. We need to override that
+ // check so that the template compiler always runs.
+ return true;
+ },
+
+ treeFor() {
+ // This addon doesn't contribute any 'real' trees to the app
+ return;
+ },
+};
diff --git a/app/assets/javascripts/discourse-plugins/package.json b/app/assets/javascripts/discourse-plugins/package.json
new file mode 100644
index 0000000000..3f05f3dada
--- /dev/null
+++ b/app/assets/javascripts/discourse-plugins/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "discourse-plugins",
+ "version": "1.0.0",
+ "description": "An addon providing a broccoli tree for each Discourse plugin",
+ "author": "Discourse",
+ "license": "GPL-2.0-only",
+ "keywords": [
+ "ember-addon"
+ ],
+ "repository": "",
+ "dependencies": {
+ "ember-auto-import": "^2.4.2",
+ "ember-cli-babel": "^7.26.10",
+ "ember-cli-htmlbars": "^6.1.1",
+ "discourse-widget-hbs": "1.0"
+ },
+ "engines": {
+ "node": "16.* || >= 18",
+ "npm": "please-use-yarn",
+ "yarn": ">= 1.21.1"
+ },
+ "ember": {
+ "edition": "default"
+ }
+}
diff --git a/app/assets/javascripts/discourse-widget-hbs/package.json b/app/assets/javascripts/discourse-widget-hbs/package.json
index e3d73242b9..a4e6228951 100644
--- a/app/assets/javascripts/discourse-widget-hbs/package.json
+++ b/app/assets/javascripts/discourse-widget-hbs/package.json
@@ -17,9 +17,9 @@
"dependencies": {
"ember-auto-import": "^2.4.2",
"ember-cli-babel": "^7.26.10",
- "ember-cli-htmlbars": "^6.1.0",
+ "ember-cli-htmlbars": "^6.1.1",
"handlebars": "^4.7.6",
- "webpack": "^5.73.0"
+ "webpack": "^5.74.0"
},
"devDependencies": {
"@ember/optional-features": "^2.0.0",
diff --git a/app/assets/javascripts/discourse/app/adapters/rest.js b/app/assets/javascripts/discourse/app/adapters/rest.js
index fcd7e4175b..6209ea9cca 100644
--- a/app/assets/javascripts/discourse/app/adapters/rest.js
+++ b/app/assets/javascripts/discourse/app/adapters/rest.js
@@ -47,20 +47,22 @@ export default EmberObject.extend({
appendQueryParams(path, findArgs, extension) {
if (findArgs) {
if (typeof findArgs === "object") {
- const queryString = Object.keys(findArgs)
- .reject((k) => !findArgs[k])
- .map((k) => k + "=" + encodeURIComponent(findArgs[k]));
+ const urlSearchParams = new URLSearchParams();
- if (queryString.length) {
- return `${path}${extension ? extension : ""}?${queryString.join(
- "&"
- )}`;
+ for (const [key, value] of Object.entries(findArgs)) {
+ if (value) {
+ urlSearchParams.set(key, value);
+ }
+ }
+
+ const queryString = urlSearchParams.toString();
+
+ if (queryString) {
+ return `${path}${extension || ""}?${queryString}`;
}
} else {
// It's serializable as a string if not an object
- return `${path}/${encodeURIComponent(findArgs)}${
- extension ? extension : ""
- }`;
+ return `${path}/${encodeURIComponent(findArgs)}${extension || ""}`;
}
}
return path;
diff --git a/app/assets/javascripts/discourse/app/adapters/topic-list.js b/app/assets/javascripts/discourse/app/adapters/topic-list.js
index fb6a0c80d5..31fbd179dd 100644
--- a/app/assets/javascripts/discourse/app/adapters/topic-list.js
+++ b/app/assets/javascripts/discourse/app/adapters/topic-list.js
@@ -8,18 +8,18 @@ export function finderFor(filter, params) {
let url = getURL("/") + filter + ".json";
if (params) {
- const keys = Object.keys(params),
- encoded = [];
+ const urlSearchParams = new URLSearchParams();
- keys.forEach(function (p) {
- const value = encodeURI(params[p]);
+ for (const [key, value] of Object.entries(params)) {
if (typeof value !== "undefined") {
- encoded.push(p + "=" + value);
+ urlSearchParams.set(key, value);
}
- });
+ }
- if (encoded.length > 0) {
- url += "?" + encoded.join("&");
+ const queryString = urlSearchParams.toString();
+
+ if (queryString) {
+ url += `?${queryString}`;
}
}
return ajax(url);
@@ -32,7 +32,7 @@ export default RestAdapter.extend({
const params = findArgs.params;
return PreloadStore.getAndRemove(
- "topic_list_" + filter,
+ "topic_list",
finderFor(filter, params)
).then(function (result) {
result.filter = filter;
diff --git a/app/assets/javascripts/discourse/app/components/badge-title.js b/app/assets/javascripts/discourse/app/components/badge-title.js
index cb62249521..ec8db2e8da 100644
--- a/app/assets/javascripts/discourse/app/components/badge-title.js
+++ b/app/assets/javascripts/discourse/app/components/badge-title.js
@@ -2,13 +2,12 @@ import Component from "@ember/component";
import { action } from "@ember/object";
import I18n from "I18n";
import { ajax } from "discourse/lib/ajax";
-import bootbox from "bootbox";
+import { inject as service } from "@ember/service";
export default Component.extend({
+ dialog: service(),
tagName: "",
-
selectableUserBadges: null,
-
_selectedUserBadgeId: null,
_isSaved: false,
_isSaving: false,
@@ -42,7 +41,7 @@ export default Component.extend({
this.currentUser.set("title", selectedUserBadge?.badge?.name || "");
},
() => {
- bootbox.alert(I18n.t("generic_error"));
+ this.dialog.alert(I18n.t("generic_error"));
}
)
.finally(() => this.set("_isSaving", false));
diff --git a/app/assets/javascripts/discourse/app/components/basic-topic-list.js b/app/assets/javascripts/discourse/app/components/basic-topic-list.js
index 14aa4d5f34..ca8fae3318 100644
--- a/app/assets/javascripts/discourse/app/components/basic-topic-list.js
+++ b/app/assets/javascripts/discourse/app/components/basic-topic-list.js
@@ -50,11 +50,7 @@ export default Component.extend({
`.indicator-topic-${data.topic_id}`
).classList;
- if (data.show_indicator) {
- nodeClassList.remove("read");
- } else {
- nodeClassList.add("read");
- }
+ nodeClassList.toggle("read", !data.show_indicator);
});
}
});
diff --git a/app/assets/javascripts/discourse/app/components/bookmark.js b/app/assets/javascripts/discourse/app/components/bookmark.js
index b5739fa66c..0ede10b3c2 100644
--- a/app/assets/javascripts/discourse/app/components/bookmark.js
+++ b/app/assets/javascripts/discourse/app/components/bookmark.js
@@ -18,14 +18,16 @@ import { and, notEmpty } from "@ember/object/computed";
import { popupAjaxError } from "discourse/lib/ajax-error";
import discourseLater from "discourse-common/lib/later";
+import { inject as service } from "@ember/service";
+
const BOOKMARK_BINDINGS = {
enter: { handler: "saveAndClose" },
"d d": { handler: "delete" },
};
export default Component.extend({
+ dialog: service(),
tagName: "",
-
errorMessage: null,
selectedReminderType: null,
_closeWithoutSaving: null,
@@ -227,7 +229,7 @@ export default Component.extend({
_handleSaveError(e) {
this._savingBookmarkManually = false;
if (typeof e === "string") {
- bootbox.alert(e);
+ this.dialog.alert(e);
} else {
popupAjaxError(e);
}
diff --git a/app/assets/javascripts/discourse/app/components/bootstrap_mode_notice.js b/app/assets/javascripts/discourse/app/components/bootstrap_mode_notice.js
index f1328b9eb6..69914cfa04 100644
--- a/app/assets/javascripts/discourse/app/components/bootstrap_mode_notice.js
+++ b/app/assets/javascripts/discourse/app/components/bootstrap_mode_notice.js
@@ -1,9 +1,11 @@
-import GlimmerComponent from "@glimmer/component";
+import Component from "@glimmer/component";
import { htmlSafe } from "@ember/template";
import I18n from "I18n";
import { inject as service } from "@ember/service";
+import { action } from "@ember/object";
+import showModal from "discourse/lib/show-modal";
-export default class BootstrapModeNotice extends GlimmerComponent {
+export default class BootstrapModeNotice extends Component {
@service siteSettings;
@service site;
@@ -19,4 +21,9 @@ export default class BootstrapModeNotice extends GlimmerComponent {
return htmlSafe(I18n.t(msg, { count: bootstrapModeMinUsers }));
}
+
+ @action
+ inviteUsers() {
+ showModal("create-invite");
+ }
}
diff --git a/app/assets/javascripts/discourse/app/components/composer-body.js b/app/assets/javascripts/discourse/app/components/composer-body.js
index f343633735..0827c1f3ad 100644
--- a/app/assets/javascripts/discourse/app/components/composer-body.js
+++ b/app/assets/javascripts/discourse/app/components/composer-body.js
@@ -89,11 +89,6 @@ export default Component.extend(KeyEnterEscape, {
passive: false,
});
});
-
- if (this._visualViewportResizing()) {
- this.viewportResize();
- window.visualViewport.addEventListener("resize", this.viewportResize);
- }
},
@bind
@@ -107,6 +102,11 @@ export default Component.extend(KeyEnterEscape, {
const minHeight = parseInt(getComputedStyle(this.element).minHeight, 10);
size = Math.max(minHeight, size);
+ this.set("composer.composerHeight", `${size}px`);
+ this.keyValueStore.set({
+ key: "composerHeight",
+ value: this.get("composer.composerHeight"),
+ });
document.documentElement.style.setProperty(
"--composer-height",
size ? `${size}px` : ""
@@ -169,42 +169,9 @@ export default Component.extend(KeyEnterEscape, {
throttle(this, this.performDragHandler, event, THROTTLE_RATE);
},
- @bind
- viewportResize() {
- const composerVH = window.visualViewport.height * 0.01,
- doc = document.documentElement;
-
- doc.style.setProperty("--composer-vh", `${composerVH}px`);
-
- const viewportWindowDiff =
- this.windowInnerHeight - window.visualViewport.height;
-
- viewportWindowDiff > 0
- ? doc.classList.add("keyboard-visible")
- : doc.classList.remove("keyboard-visible");
-
- // adds bottom padding when using a hardware keyboard and the accessory bar is visible
- // accessory bar height is 55px, using 75 allows a small buffer
- doc.style.setProperty(
- "--composer-ipad-padding",
- `${viewportWindowDiff < 75 ? viewportWindowDiff : 0}px`
- );
- },
-
- _visualViewportResizing() {
- return (
- (this.capabilities.isIpadOS || this.site.mobileView) &&
- window.visualViewport !== undefined
- );
- },
-
didInsertElement() {
this._super(...arguments);
- if (this._visualViewportResizing()) {
- this.set("windowInnerHeight", window.innerHeight);
- }
-
this.setupComposerResizeEvents();
const triggerOpen = () => {
@@ -224,10 +191,6 @@ export default Component.extend(KeyEnterEscape, {
willDestroyElement() {
this._super(...arguments);
- if (this._visualViewportResizing()) {
- window.visualViewport.removeEventListener("resize", this.viewportResize);
- }
-
START_DRAG_EVENTS.forEach((startDragEvent) => {
this.element
.querySelector(".grippie")
diff --git a/app/assets/javascripts/discourse/app/components/composer-editor.js b/app/assets/javascripts/discourse/app/components/composer-editor.js
index 83d14ad641..545039b79d 100644
--- a/app/assets/javascripts/discourse/app/components/composer-editor.js
+++ b/app/assets/javascripts/discourse/app/components/composer-editor.js
@@ -816,7 +816,6 @@ export default Component.extend(ComposerUploadUppy, {
extraButtons(toolbar) {
toolbar.addButton({
- tabindex: "0",
id: "quote",
group: "fontStyles",
icon: "far-comment",
diff --git a/app/assets/javascripts/discourse/app/components/composer-fullscreen-prompt.js b/app/assets/javascripts/discourse/app/components/composer-fullscreen-prompt.js
new file mode 100644
index 0000000000..742bb3c18e
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/composer-fullscreen-prompt.js
@@ -0,0 +1,3 @@
+import templateOnly from "@ember/component/template-only";
+
+export default templateOnly();
diff --git a/app/assets/javascripts/discourse/app/components/composer-messages.js b/app/assets/javascripts/discourse/app/components/composer-messages.js
index 2f00c76a49..40832b5df1 100644
--- a/app/assets/javascripts/discourse/app/components/composer-messages.js
+++ b/app/assets/javascripts/discourse/app/components/composer-messages.js
@@ -5,8 +5,10 @@ import LinkLookup from "discourse/lib/link-lookup";
import { not } from "@ember/object/computed";
import { scheduleOnce } from "@ember/runloop";
import showModal from "discourse/lib/show-modal";
+import { ajax } from "discourse/lib/ajax";
let _messagesCache = {};
+let _recipient_names = [];
export default Component.extend({
classNameBindings: [":composer-popup-container", "hidden"],
@@ -18,6 +20,7 @@ export default Component.extend({
_similarTopicsMessage: null,
_yourselfConfirm: null,
similarTopics: null,
+ usersNotSeen: null,
hidden: not("composer.viewOpenOrFullscreen"),
@@ -119,6 +122,53 @@ export default Component.extend({
const composer = this.composer;
if (composer.get("privateMessage")) {
const recipients = composer.targetRecipientsArray;
+ const recipient_names = recipients
+ .filter((r) => r.type === "user")
+ .map(({ name }) => name);
+
+ if (
+ recipient_names.length > 0 &&
+ recipient_names.length !== _recipient_names.length &&
+ !recipient_names.every((v, i) => v === _recipient_names[i])
+ ) {
+ _recipient_names = recipient_names;
+
+ ajax(`/composer_messages/user_not_seen_in_a_while`, {
+ type: "GET",
+ data: {
+ usernames: recipient_names,
+ },
+ }).then((response) => {
+ if (
+ response.user_count > 0 &&
+ this.get("usersNotSeen") !== response.usernames.join("-")
+ ) {
+ this.set("usersNotSeen", response.usernames.join("-"));
+ this.messagesByTemplate["education"] = undefined;
+
+ let usernames = [];
+ response.usernames.forEach((username, index) => {
+ usernames[
+ index
+ ] = `@${username}`;
+ });
+
+ let body_key = "composer.user_not_seen_in_a_while.single";
+ if (response.user_count > 1) {
+ body_key = "composer.user_not_seen_in_a_while.multiple";
+ }
+ const message = composer.store.createRecord("composer-message", {
+ id: "user-not-seen",
+ templateName: "education",
+ body: I18n.t(body_key, {
+ usernames: usernames.join(", "),
+ time_ago: response.time_ago,
+ }),
+ });
+ this.send("popup", message);
+ }
+ });
+ }
if (
recipients.length > 0 &&
@@ -128,7 +178,7 @@ export default Component.extend({
this._yourselfConfirm ||
composer.store.createRecord("composer-message", {
id: "yourself_confirm",
- templateName: "custom-body",
+ templateName: "education",
title: I18n.t("composer.yourself_confirm.title"),
body: I18n.t("composer.yourself_confirm.body"),
});
diff --git a/app/assets/javascripts/discourse/app/components/create-topics-notice.js b/app/assets/javascripts/discourse/app/components/create-topics-notice.js
index 687cec3228..da6f089ef6 100644
--- a/app/assets/javascripts/discourse/app/components/create-topics-notice.js
+++ b/app/assets/javascripts/discourse/app/components/create-topics-notice.js
@@ -4,11 +4,13 @@ import I18n from "I18n";
import LivePostCounts from "discourse/models/live-post-counts";
import { alias } from "@ember/object/computed";
import { htmlSafe } from "@ember/template";
+import { inject as service } from "@ember/service";
export default Component.extend({
classNameBindings: ["hidden:hidden", ":create-topics-notice"],
enabled: false,
+ router: service(),
publicTopicCount: null,
publicPostCount: null,
@@ -37,14 +39,16 @@ export default Component.extend({
}
},
- @discourseComputed()
- shouldSee() {
- const user = this.currentUser;
+ @discourseComputed(
+ "siteSettings.show_create_topics_notice",
+ "router.currentRouteName"
+ )
+ shouldSee(showCreateTopicsNotice, currentRouteName) {
return (
- user &&
- user.get("admin") &&
- this.siteSettings.show_create_topics_notice &&
- !this.site.get("wizard_required")
+ this.currentUser?.get("admin") &&
+ showCreateTopicsNotice &&
+ !this.site.get("wizard_required") &&
+ !currentRouteName.startsWith("wizard")
);
},
diff --git a/app/assets/javascripts/discourse/app/components/d-document.js b/app/assets/javascripts/discourse/app/components/d-document.js
index 76b111a3ec..7e8fef1bd3 100644
--- a/app/assets/javascripts/discourse/app/components/d-document.js
+++ b/app/assets/javascripts/discourse/app/components/d-document.js
@@ -1,7 +1,6 @@
import Component from "@ember/component";
import I18n from "I18n";
import { bind } from "discourse-common/utils/decorators";
-import bootbox from "bootbox";
import logout from "discourse/lib/logout";
import { inject as service } from "@ember/service";
import { setLogoffCallback } from "discourse/lib/ajax";
@@ -14,6 +13,7 @@ export function addPluginDocumentTitleCounter(counterFunction) {
export default Component.extend({
tagName: "",
documentTitle: service(),
+ dialog: service(),
_showingLogout: false,
didInsertElement() {
@@ -44,16 +44,23 @@ export default Component.extend({
);
},
- _updateNotifications() {
+ _updateNotifications(opts) {
if (!this.currentUser) {
return;
}
- const count =
- pluginCounterFunctions.reduce((sum, fn) => sum + fn(), 0) +
- this.currentUser.unread_notifications +
- this.currentUser.unread_high_priority_notifications;
- this.documentTitle.updateNotificationCount(count);
+ let count = pluginCounterFunctions.reduce((sum, fn) => sum + fn(), 0);
+ if (this.currentUser.redesigned_user_menu_enabled) {
+ count += this.currentUser.all_unread_notifications_count;
+ if (this.currentUser.unseen_reviewable_count) {
+ count += this.currentUser.unseen_reviewable_count;
+ }
+ } else {
+ count +=
+ this.currentUser.unread_notifications +
+ this.currentUser.unread_high_priority_notifications;
+ }
+ this.documentTitle.updateNotificationCount(count, { forced: opts?.forced });
},
@bind
@@ -74,13 +81,12 @@ export default Component.extend({
this._showingLogout = true;
this.messageBus.stop();
- bootbox.dialog(
- I18n.t("logout"),
- { label: I18n.t("refresh"), callback: logout },
- {
- onEscape: () => logout(),
- backdrop: "static",
- }
- );
+
+ this.dialog.alert({
+ message: I18n.t("logout"),
+ confirmButtonLabel: "refresh",
+ didConfirm: () => logout(),
+ didCancel: () => logout(),
+ });
},
});
diff --git a/app/assets/javascripts/discourse/app/components/d-editor.js b/app/assets/javascripts/discourse/app/components/d-editor.js
index e6fdda2695..f6d800b06d 100644
--- a/app/assets/javascripts/discourse/app/components/d-editor.js
+++ b/app/assets/javascripts/discourse/app/components/d-editor.js
@@ -50,7 +50,7 @@ let _createCallbacks = [];
class Toolbar {
constructor(opts) {
- const { site, siteSettings } = opts;
+ const { siteSettings, capabilities } = opts;
this.shortcuts = {};
this.context = null;
@@ -106,16 +106,16 @@ class Toolbar {
}),
});
- this.addButton({
- id: "code",
- group: "insertions",
- shortcut: "E",
- preventFocus: true,
- trimLeading: true,
- action: (...args) => this.context.send("formatCode", args),
- });
+ if (!capabilities.touch) {
+ this.addButton({
+ id: "code",
+ group: "insertions",
+ shortcut: "E",
+ preventFocus: true,
+ trimLeading: true,
+ action: (...args) => this.context.send("formatCode", args),
+ });
- if (!site.mobileView) {
this.addButton({
id: "bullet",
group: "extras",
@@ -354,7 +354,7 @@ export default Component.extend(TextareaTextManipulation, {
@discourseComputed()
toolbar() {
const toolbar = new Toolbar(
- this.getProperties("site", "siteSettings", "showLink")
+ this.getProperties("site", "siteSettings", "showLink", "capabilities")
);
toolbar.context = this;
@@ -363,6 +363,12 @@ export default Component.extend(TextareaTextManipulation, {
if (this.extraButtons) {
this.extraButtons(toolbar);
}
+
+ const firstButton = toolbar.groups.mapBy("buttons").flat().firstObject;
+ if (firstButton) {
+ firstButton.tabindex = 0;
+ }
+
return toolbar;
},
@@ -702,6 +708,7 @@ export default Component.extend(TextareaTextManipulation, {
this.applySurround(selected, head, tail, exampleKey, opts),
applyList: (head, exampleKey, opts) =>
this._applyList(selected, head, exampleKey, opts),
+ formatCode: (...args) => this.send("formatCode", args),
addText: (text) => this.addText(selected, text),
getText: () => this.value,
toggleDirection: () => this._toggleDirection(),
diff --git a/app/assets/javascripts/discourse/app/components/d-navigation.js b/app/assets/javascripts/discourse/app/components/d-navigation.js
index 32f8756b42..2153fc5f9c 100644
--- a/app/assets/javascripts/discourse/app/components/d-navigation.js
+++ b/app/assets/javascripts/discourse/app/components/d-navigation.js
@@ -1,7 +1,6 @@
import Component from "@ember/component";
import FilterModeMixin from "discourse/mixins/filter-mode";
import NavItem from "discourse/models/nav-item";
-import bootbox from "bootbox";
import discourseComputed from "discourse-common/utils/decorators";
import { NotificationLevels } from "discourse/lib/notification-levels";
import { getOwner } from "discourse-common/lib/get-owner";
@@ -9,7 +8,7 @@ import { inject as service } from "@ember/service";
export default Component.extend(FilterModeMixin, {
router: service(),
-
+ dialog: service(),
tagName: "",
// Should be a `readOnly` instead but some themes/plugins still pass
@@ -158,7 +157,7 @@ export default Component.extend(FilterModeMixin, {
clickCreateTopicButton() {
if (this.categoryReadOnlyBanner && !this.hasDraft) {
- bootbox.alert(this.categoryReadOnlyBanner);
+ this.dialog.alert(this.categoryReadOnlyBanner);
} else {
this.createTopic();
}
diff --git a/app/assets/javascripts/discourse/app/components/d-tooltip.js b/app/assets/javascripts/discourse/app/components/d-tooltip.js
index 90d654c833..e462e2d542 100644
--- a/app/assets/javascripts/discourse/app/components/d-tooltip.js
+++ b/app/assets/javascripts/discourse/app/components/d-tooltip.js
@@ -26,6 +26,7 @@ export default class DiscourseTooltip extends Component {
const parent = viewBounds.parentElement;
this._tippyInstance = tippy(parent, {
content: element,
+ trigger: this.capabilities.touch ? "click" : "mouseenter",
theme: "d-tooltip",
arrow: false,
placement: "bottom-start",
diff --git a/app/assets/javascripts/discourse/app/components/emoji-picker.js b/app/assets/javascripts/discourse/app/components/emoji-picker.js
index 8f3aad5fa6..3e0646f893 100644
--- a/app/assets/javascripts/discourse/app/components/emoji-picker.js
+++ b/app/assets/javascripts/discourse/app/components/emoji-picker.js
@@ -41,6 +41,10 @@ export default Component.extend({
usePopper: true,
placement: "auto", // one of popper.js' placements, see https://popper.js.org/docs/v2/constructors/#options
initialFilter: "",
+ elements: {
+ searchInput: ".emoji-picker-search-container input",
+ picker: ".emoji-picker-emoji-area",
+ },
init() {
this._super(...arguments);
@@ -245,8 +249,116 @@ export default Component.extend({
@action
keydown(event) {
- if (event.code === "Escape") {
+ const arrowKeys = ["ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight"];
+ const emojis = document.querySelectorAll(".emoji-picker-emoji-area .emoji");
+
+ let currentEmoji;
+
+ this.set(
+ "hoveredEmoji",
+ this._codeWithDiversity(event.target.title, this.selectedDiversity)
+ );
+
+ if (
+ event.key === "ArrowDown" &&
+ this._focusedOn(this.elements.searchInput)
+ ) {
+ emojis[0].focus();
+ event.preventDefault();
+ return false;
+ }
+
+ if (event.key === "Escape") {
this.onClose(event);
+ const path = event.path || (event.composedPath && event.composedPath());
+
+ const fromChatComposer = path.find((e) =>
+ e?.classList?.contains("chat-composer-container")
+ );
+
+ const fromTopicComposer = path.find((e) =>
+ e?.classList?.contains("d-editor")
+ );
+
+ if (fromTopicComposer) {
+ document.querySelector(".d-editor-input")?.focus();
+ } else if (fromChatComposer) {
+ document.querySelector(".chat-composer-input")?.focus();
+ } else {
+ document.querySelector("textarea")?.focus();
+ }
+
+ return false;
+ }
+
+ if (arrowKeys.includes(event.key)) {
+ if (!this._focusedOn(this.elements.picker)) {
+ return;
+ }
+
+ Array.from(emojis).find((e, index) => {
+ currentEmoji = index;
+ return e.isEqualNode(event.target);
+ });
+
+ if (event.key === "ArrowRight") {
+ let nextEmoji = currentEmoji + 1;
+
+ if (nextEmoji < emojis.length) {
+ emojis[nextEmoji].focus();
+ } else if (nextEmoji >= emojis.length) {
+ emojis[0].focus();
+ }
+ }
+
+ if (event.key === "ArrowLeft") {
+ const previousEmoji = currentEmoji - 1;
+ if (currentEmoji > 0) {
+ emojis[previousEmoji].focus();
+ }
+ }
+
+ const active = emojis[currentEmoji];
+
+ if (event.key === "ArrowDown") {
+ // source: https://stackoverflow.com/a/49090383/349424
+ // look for same element type with
+ // - higher offsetTop
+ // - same offsetLeft
+ const emojiBelow = [...emojis]
+ .filter((c) => c.offsetTop > active.offsetTop)
+ .find((c) => c.offsetLeft === active.offsetLeft);
+
+ emojiBelow?.focus();
+ }
+
+ if (event.key === "ArrowUp") {
+ // look for same element type with
+ // - lower offsetTop
+ // - same offsetLeft
+ const emojiAbove = [...emojis]
+ .reverse()
+ .filter((c) => c.offsetTop < active.offsetTop)
+ .find((c) => c.offsetLeft === active.offsetLeft);
+
+ if (emojiAbove) {
+ emojiAbove.focus();
+ } else {
+ document.querySelector(this.elements.searchInput).focus();
+ }
+ }
+
+ event.preventDefault();
+ return false;
+ }
+
+ if (event.key === "Enter") {
+ if (!this._focusedOn(".emoji")) {
+ return;
+ }
+ this.onEmojiSelection(event);
+ this.onClose(event);
+ event.preventDefault();
return false;
}
},
@@ -256,6 +368,11 @@ export default Component.extend({
this._applyFilter(event.target.value);
},
+ _focusedOn(item) {
+ // returns the item currently being focused on
+ return document.activeElement.closest(item) ? document.activeElement : null;
+ },
+
_applyFilter(filter) {
const emojiPicker = document.querySelector(".emoji-picker");
const results = document.querySelector(".emoji-picker-emoji-area .results");
@@ -286,8 +403,9 @@ export default Component.extend({
_replaceEmoji(code) {
const escaped = emojiUnescape(`:${escapeExpression(code)}:`, {
lazy: true,
+ tabIndex: "0",
});
- return htmlSafe(`${escaped}`);
+ return htmlSafe(escaped);
},
_codeWithDiversity(code, selectedDiversity) {
diff --git a/app/assets/javascripts/discourse/app/components/glimmer.js b/app/assets/javascripts/discourse/app/components/glimmer.js
deleted file mode 100644
index b3142a9060..0000000000
--- a/app/assets/javascripts/discourse/app/components/glimmer.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import GlimmerComponent from "@glimmer/component";
-import { inject as service } from "@ember/service";
-
-/*
- Glimmer components are not EmberObjects, and therefore do not support automatic
- injection of the things defined in `pre-initializers/inject-discourse-objects`.
-
- This base class provides an alternative. All these references are looked up lazily,
- so the performance impact should be negligible
-*/
-
-export default class DiscourseGlimmerComponent extends GlimmerComponent {
- @service appEvents;
- @service store;
- @service("search") searchService;
- @service keyValueStore;
- @service pmTopicTrackingState;
- @service siteSettings;
- @service messageBus;
- @service currentUser;
- @service session;
- @service site;
- @service topicTrackingState;
-}
diff --git a/app/assets/javascripts/discourse/app/components/global-notice.js b/app/assets/javascripts/discourse/app/components/global-notice.js
index 170421cb13..c0bd0bcd54 100644
--- a/app/assets/javascripts/discourse/app/components/global-notice.js
+++ b/app/assets/javascripts/discourse/app/components/global-notice.js
@@ -77,6 +77,7 @@ export default Component.extend({
@discourseComputed(
"site.isReadOnly",
+ "site.isStaffWritesOnly",
"siteSettings.login_required",
"siteSettings.disable_emails",
"siteSettings.global_notice",
@@ -85,6 +86,7 @@ export default Component.extend({
)
notices(
isReadOnly,
+ isStaffWritesOnly,
loginRequired,
disableEmails,
globalNotice,
@@ -111,7 +113,14 @@ export default Component.extend({
);
}
- if (isReadOnly) {
+ if (isStaffWritesOnly) {
+ notices.push(
+ Notice.create({
+ text: I18n.t("staff_writes_only_mode.enabled"),
+ id: "alert-staff-writes-only",
+ })
+ );
+ } else if (isReadOnly) {
notices.push(
Notice.create({
text: I18n.t("read_only_mode.enabled"),
diff --git a/app/assets/javascripts/discourse/app/components/group-manage-email-settings.js b/app/assets/javascripts/discourse/app/components/group-manage-email-settings.js
index ffa586b32a..786f2a3a5b 100644
--- a/app/assets/javascripts/discourse/app/components/group-manage-email-settings.js
+++ b/app/assets/javascripts/discourse/app/components/group-manage-email-settings.js
@@ -2,11 +2,12 @@ import Component from "@ember/component";
import { isEmpty } from "@ember/utils";
import discourseComputed, { on } from "discourse-common/utils/decorators";
import I18n from "I18n";
-import bootbox from "bootbox";
+import { inject as service } from "@ember/service";
import { action } from "@ember/object";
export default Component.extend({
tagName: "",
+ dialog: service(),
imapSettingsValid: false,
smtpSettingsValid: false,
@@ -71,16 +72,11 @@ export default Component.extend({
this.group.smtp_enabled &&
this._anySmtpFieldsFilled()
) {
- bootbox.confirm(
- I18n.t("groups.manage.email.smtp_disable_confirm"),
- (result) => {
- if (!result) {
- this.group.set("smtp_enabled", true);
- } else {
- this.group.set("imap_enabled", false);
- }
- }
- );
+ this.dialog.confirm({
+ message: I18n.t("groups.manage.email.smtp_disable_confirm"),
+ didConfirm: () => this.group.set("smtp_enabled", true),
+ didCancel: () => this.group.set("imap_enabled", false),
+ });
}
this.group.set("smtp_enabled", event.target.checked);
@@ -93,14 +89,10 @@ export default Component.extend({
this.group.imap_enabled &&
this._anyImapFieldsFilled()
) {
- bootbox.confirm(
- I18n.t("groups.manage.email.imap_disable_confirm"),
- (result) => {
- if (!result) {
- this.group.set("imap_enabled", true);
- }
- }
- );
+ this.dialog.confirm({
+ message: I18n.t("groups.manage.email.imap_disable_confirm"),
+ didConfirm: () => this.group.set("imap_enabled", true),
+ });
}
this.group.set("imap_enabled", event.target.checked);
diff --git a/app/assets/javascripts/discourse/app/components/group-membership-button.js b/app/assets/javascripts/discourse/app/components/group-membership-button.js
index 4c8239aedc..bad2835a19 100644
--- a/app/assets/javascripts/discourse/app/components/group-membership-button.js
+++ b/app/assets/javascripts/discourse/app/components/group-membership-button.js
@@ -1,13 +1,14 @@
import Component from "@ember/component";
import I18n from "I18n";
-import bootbox from "bootbox";
import cookie from "discourse/lib/cookie";
import discourseComputed from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error";
+import { inject as service } from "@ember/service";
import showModal from "discourse/lib/show-modal";
export default Component.extend({
classNames: ["group-membership-button"],
+ dialog: service(),
@discourseComputed("model.public_admission", "userIsGroupUser")
canJoinGroup(publicAdmission, userIsGroupUser) {
@@ -73,16 +74,11 @@ export default Component.extend({
if (this.model.public_admission) {
this.removeFromGroup();
} else {
- return bootbox.confirm(
- I18n.t("groups.confirm_leave"),
- I18n.t("no_value"),
- I18n.t("yes_value"),
- (result) => {
- result
- ? this.removeFromGroup()
- : this.set("updatingMembership", false);
- }
- );
+ return this.dialog.yesNoConfirm({
+ message: I18n.t("groups.confirm_leave"),
+ didConfirm: () => this.removeFromGroup(),
+ didCancel: () => this.set("updatingMembership", false),
+ });
}
},
diff --git a/app/assets/javascripts/discourse/app/components/group-post.js b/app/assets/javascripts/discourse/app/components/group-post.js
index 6aba0da4b0..074f856b40 100644
--- a/app/assets/javascripts/discourse/app/components/group-post.js
+++ b/app/assets/javascripts/discourse/app/components/group-post.js
@@ -3,6 +3,7 @@ import discourseComputed from "discourse-common/utils/decorators";
import getURL from "discourse-common/lib/get-url";
import { prioritizeNameInUx } from "discourse/lib/settings";
import { propertyEqual } from "discourse/lib/computed";
+import { userPath } from "discourse/lib/url";
export default Component.extend({
classNameBindings: [
@@ -35,4 +36,9 @@ export default Component.extend({
return `group-${postUser.primary_group_name}`;
}
},
+
+ @discourseComputed("post.user.username")
+ userUrl(username) {
+ return userPath(username.toLowerCase());
+ },
});
diff --git a/app/assets/javascripts/discourse/app/components/navigation-item.js b/app/assets/javascripts/discourse/app/components/navigation-item.js
index 2686d526ce..6c64f2c61b 100644
--- a/app/assets/javascripts/discourse/app/components/navigation-item.js
+++ b/app/assets/javascripts/discourse/app/components/navigation-item.js
@@ -42,13 +42,14 @@ export default Component.extend(FilterModeMixin, {
const content = this.content;
let href = content.get("href");
- let queryParams = [];
+ let urlSearchParams = new URLSearchParams();
+ let addParamsEvenIfEmpty = false;
// Include the category id if the option is present
if (content.get("includeCategoryId")) {
let categoryId = this.get("content.category.id");
if (categoryId) {
- queryParams.push(`category_id=${categoryId}`);
+ urlSearchParams.set("category_id", categoryId);
}
}
@@ -56,12 +57,12 @@ export default Component.extend(FilterModeMixin, {
// If no query param is present, add an empty one to ensure a ? is
// appended to the URL.
if (content.currentRouteQueryParams) {
- if (content.currentRouteQueryParams.filter && queryParams.length === 0) {
- queryParams.push("");
+ if (content.currentRouteQueryParams.filter) {
+ addParamsEvenIfEmpty = true;
}
if (content.currentRouteQueryParams.f) {
- queryParams.push(`f=${content.currentRouteQueryParams.f}`);
+ urlSearchParams.set("f", content.currentRouteQueryParams.f);
}
}
@@ -69,11 +70,12 @@ export default Component.extend(FilterModeMixin, {
this.siteSettings.desktop_category_page_style ===
"categories_and_latest_topics_created_date"
) {
- queryParams.push("order=created");
+ urlSearchParams.set("order", "created");
}
- if (queryParams.length) {
- href += `?${queryParams.join("&")}`;
+ const queryString = urlSearchParams.toString();
+ if (addParamsEvenIfEmpty || queryString) {
+ href += `?${queryString}`;
}
this.set("hrefLink", href);
diff --git a/app/assets/javascripts/discourse/app/components/notification-consent-banner.js b/app/assets/javascripts/discourse/app/components/notification-consent-banner.js
index d19abc998d..23c7b79472 100644
--- a/app/assets/javascripts/discourse/app/components/notification-consent-banner.js
+++ b/app/assets/javascripts/discourse/app/components/notification-consent-banner.js
@@ -32,7 +32,7 @@ export default DesktopNotificationConfig.extend({
this.siteSettings.push_notifications_prompt &&
!isNotSupported &&
this.currentUser &&
- anyPosts &&
+ (this.capabilities.isPwa || anyPosts) &&
Notification.permission !== "denied" &&
Notification.permission !== "granted" &&
!isEnabled &&
diff --git a/app/assets/javascripts/discourse/app/components/pick-files-button.js b/app/assets/javascripts/discourse/app/components/pick-files-button.js
index 3f2482ad50..991ffe1592 100644
--- a/app/assets/javascripts/discourse/app/components/pick-files-button.js
+++ b/app/assets/javascripts/discourse/app/components/pick-files-button.js
@@ -1,5 +1,4 @@
import Component from "@ember/component";
-import bootbox from "bootbox";
import { isBlank } from "@ember/utils";
import {
authorizedExtensions,
@@ -8,6 +7,7 @@ import {
import { action } from "@ember/object";
import discourseComputed, { bind } from "discourse-common/utils/decorators";
import I18n from "I18n";
+import { inject as service } from "@ember/service";
// This picker is intended to be used with UppyUploadMixin or with
// ComposerUploadUppy, which is why there are no change events registered
@@ -18,6 +18,7 @@ import I18n from "I18n";
// is sometimes useful if you need to do something outside the uppy upload with
// the file, such as directly using JSON or CSV data from a file in JS.
export default Component.extend({
+ dialog: service(),
fileInputId: null,
fileInputClass: null,
fileInputDisabled: false,
@@ -87,7 +88,7 @@ export default Component.extend({
const message = I18n.t("pick_files_button.unsupported_file_picked", {
types: this.acceptedFileTypesString,
});
- bootbox.alert(message);
+ this.dialog.alert(message);
return;
}
this.onFilesPicked(files);
diff --git a/app/assets/javascripts/discourse/app/components/reviewable-item.js b/app/assets/javascripts/discourse/app/components/reviewable-item.js
index 9843038de4..ff67e672a8 100644
--- a/app/assets/javascripts/discourse/app/components/reviewable-item.js
+++ b/app/assets/javascripts/discourse/app/components/reviewable-item.js
@@ -151,11 +151,11 @@ export default Component.extend({
// "fast track" to update the current user's reviewable count before the message bus finds out.
if (performResult.reviewable_count !== undefined) {
- this.currentUser.set(
- "reviewable_count",
+ this.currentUser.updateReviewableCount(
performResult.reviewable_count
);
}
+
if (performResult.unseen_reviewable_count !== undefined) {
this.currentUser.set(
"unseen_reviewable_count",
diff --git a/app/assets/javascripts/discourse/app/components/sidebar.hbs b/app/assets/javascripts/discourse/app/components/sidebar.hbs
new file mode 100644
index 0000000000..65d3b9240f
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar.hbs
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/app/assets/javascripts/discourse/app/components/sidebar.js b/app/assets/javascripts/discourse/app/components/sidebar.js
index afb309f54f..f4316c392a 100644
--- a/app/assets/javascripts/discourse/app/components/sidebar.js
+++ b/app/assets/javascripts/discourse/app/components/sidebar.js
@@ -1,15 +1,18 @@
-import GlimmerComponent from "discourse/components/glimmer";
+import Component from "@glimmer/component";
import { bind } from "discourse-common/utils/decorators";
+import { inject as service } from "@ember/service";
+
+export default class Sidebar extends Component {
+ @service appEvents;
+ @service site;
+ @service currentUser;
-export default class Sidebar extends GlimmerComponent {
constructor() {
super(...arguments);
if (this.site.mobileView) {
document.addEventListener("click", this.collapseSidebar);
}
- // This appEvent handler is experimental and should not be relied on as an extension point yet.
- this.appEvents.on("sidebar:scroll-to-element", this, this.#scrollToElement);
}
@bind
@@ -33,68 +36,9 @@ export default class Sidebar extends GlimmerComponent {
}
}
- #scrollToElement(destinationElement) {
- const topPadding = 10;
-
- const sidebarContainerElement =
- document.querySelector(".sidebar-container");
-
- const distanceFromTop =
- document.getElementsByClassName(destinationElement)[0].offsetTop -
- topPadding;
-
- this.#setMissingHeightForScroll(sidebarContainerElement, distanceFromTop);
-
- sidebarContainerElement.scrollTop = distanceFromTop;
- }
-
- #setMissingHeightForScroll(sidebarContainerElement, distanceFromTop) {
- const allSections = document.getElementsByClassName(
- "sidebar-section-wrapper"
- );
-
- const lastSectionElement = allSections[allSections.length - 1];
-
- const lastSectionBottomPadding = parseInt(
- lastSectionElement.style.paddingBottom?.replace("px", "") || 0,
- 10
- );
-
- const headerOffset = parseInt(
- document.documentElement.style.getPropertyValue("--header-offset"),
- 10
- );
-
- let allSectionsHeight = 0;
-
- for (const section of allSections) {
- allSectionsHeight +=
- section.clientHeight +
- parseInt(
- window.getComputedStyle(section).marginBottom.replace("px", ""),
- 10
- );
- }
-
- const missingHeight =
- sidebarContainerElement.clientHeight -
- headerOffset +
- lastSectionBottomPadding -
- (allSectionsHeight - distanceFromTop);
-
- lastSectionElement.style.paddingBottom =
- missingHeight > 0 ? `${missingHeight}px` : null;
- }
-
willDestroy() {
if (this.site.mobileView) {
document.removeEventListener("click", this.collapseSidebar);
}
-
- this.appEvents.off(
- "sidebar:scroll-to-element",
- this,
- this.#scrollToElement
- );
}
}
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/anonymous/categories-section.hbs b/app/assets/javascripts/discourse/app/components/sidebar/anonymous/categories-section.hbs
new file mode 100644
index 0000000000..c59afa73c5
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/anonymous/categories-section.hbs
@@ -0,0 +1,21 @@
+
+
+ {{#each this.sectionLinks as |sectionLink|}}
+
+
+ {{/each}}
+
+
+
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/anonymous/categories-section.js b/app/assets/javascripts/discourse/app/components/sidebar/anonymous/categories-section.js
new file mode 100644
index 0000000000..40043a8171
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/anonymous/categories-section.js
@@ -0,0 +1,31 @@
+import { inject as service } from "@ember/service";
+import { canDisplayCategory } from "discourse/lib/sidebar/helpers";
+import SidebarCommonCategoriesSection from "discourse/components/sidebar/common/categories-section";
+
+export default class SidebarAnonymousCategoriesSection extends SidebarCommonCategoriesSection {
+ @service site;
+
+ get categories() {
+ let categories = this.site.categoriesList;
+
+ if (this.siteSettings.default_sidebar_categories) {
+ const defaultCategoryIds = this.siteSettings.default_sidebar_categories
+ .split("|")
+ .map((categoryId) => parseInt(categoryId, 10));
+
+ categories = categories.filter((category) =>
+ defaultCategoryIds.includes(category.id)
+ );
+ } else {
+ categories = categories
+ .filter(
+ (category) =>
+ canDisplayCategory(category, this.siteSettings) &&
+ !category.parent_category_id
+ )
+ .slice(0, 5);
+ }
+
+ return categories;
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/anonymous/community-section.js b/app/assets/javascripts/discourse/app/components/sidebar/anonymous/community-section.js
new file mode 100644
index 0000000000..30d47e5b03
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/anonymous/community-section.js
@@ -0,0 +1,36 @@
+import SidebarCommonCommunitySection from "discourse/components/sidebar/common/community-section";
+import EverythingSectionLink from "discourse/lib/sidebar/common/community-section/everything-section-link";
+import AboutSectionLink from "discourse/lib/sidebar/common/community-section/about-section-link";
+import FAQSectionLink from "discourse/lib/sidebar/common/community-section/faq-section-link";
+import GroupsSectionLink from "discourse/lib/sidebar/common/community-section/groups-section-link";
+import UsersSectionLink from "discourse/lib/sidebar/common/community-section/users-section-link";
+import BadgesSectionLink from "discourse/lib/sidebar/common/community-section/badges-section-link";
+
+export default class SidebarAnonymousCommunitySection extends SidebarCommonCommunitySection {
+ get defaultMainSectionLinks() {
+ const defaultLinks = [
+ EverythingSectionLink,
+ UsersSectionLink,
+ FAQSectionLink,
+ ];
+
+ defaultLinks.splice(
+ this.displayShortSiteDescription ? 0 : 2,
+ 0,
+ AboutSectionLink
+ );
+
+ return defaultLinks;
+ }
+
+ get displayShortSiteDescription() {
+ return (
+ !this.currentUser &&
+ (this.siteSettings.short_site_description || "").length > 0
+ );
+ }
+
+ get defaultMoreSectionLinks() {
+ return [GroupsSectionLink, BadgesSectionLink];
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/anonymous/sections.hbs b/app/assets/javascripts/discourse/app/components/sidebar/anonymous/sections.hbs
new file mode 100644
index 0000000000..68ea9dde5b
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/anonymous/sections.hbs
@@ -0,0 +1,8 @@
+
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/anonymous/sections.js b/app/assets/javascripts/discourse/app/components/sidebar/anonymous/sections.js
new file mode 100644
index 0000000000..6cb84571db
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/anonymous/sections.js
@@ -0,0 +1,6 @@
+import Component from "@glimmer/component";
+import { inject as service } from "@ember/service";
+
+export default class SidebarAnonymousSections extends Component {
+ @service siteSettings;
+}
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/anonymous/tags-section.hbs b/app/assets/javascripts/discourse/app/components/sidebar/anonymous/tags-section.hbs
new file mode 100644
index 0000000000..b8b339f7e2
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/anonymous/tags-section.hbs
@@ -0,0 +1,18 @@
+
+
+ {{#each this.sectionLinks as |sectionLink|}}
+
+
+ {{/each}}
+
+
+
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/anonymous/tags-section.js b/app/assets/javascripts/discourse/app/components/sidebar/anonymous/tags-section.js
new file mode 100644
index 0000000000..0dc3804ba5
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/anonymous/tags-section.js
@@ -0,0 +1,27 @@
+import { cached } from "@glimmer/tracking";
+import Component from "@glimmer/component";
+import { inject as service } from "@ember/service";
+import TagSectionLink from "discourse/lib/sidebar/user/tags-section/tag-section-link";
+
+export default class SidebarAnonymousTagsSection extends Component {
+ @service router;
+ @service topicTrackingState;
+ @service site;
+
+ @cached
+ get sectionLinks() {
+ let tags;
+
+ if (this.site.anonymous_default_sidebar_tags) {
+ tags = this.site.anonymous_default_sidebar_tags;
+ } else {
+ tags = this.site.top_tags.slice(0, 5);
+ }
+ return tags.map((tagName) => {
+ return new TagSectionLink({
+ tagName,
+ topicTrackingState: this.topicTrackingState,
+ });
+ });
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/common/all-categories-section-link.hbs b/app/assets/javascripts/discourse/app/components/sidebar/common/all-categories-section-link.hbs
new file mode 100644
index 0000000000..9eb404a3e4
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/common/all-categories-section-link.hbs
@@ -0,0 +1,7 @@
+
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/common/all-categories-section-link.js b/app/assets/javascripts/discourse/app/components/sidebar/common/all-categories-section-link.js
new file mode 100644
index 0000000000..742bb3c18e
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/common/all-categories-section-link.js
@@ -0,0 +1,3 @@
+import templateOnly from "@ember/component/template-only";
+
+export default templateOnly();
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/common/all-tags-section-link.hbs b/app/assets/javascripts/discourse/app/components/sidebar/common/all-tags-section-link.hbs
new file mode 100644
index 0000000000..ae342f9d50
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/common/all-tags-section-link.hbs
@@ -0,0 +1,7 @@
+
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/common/all-tags-section-link.js b/app/assets/javascripts/discourse/app/components/sidebar/common/all-tags-section-link.js
new file mode 100644
index 0000000000..742bb3c18e
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/common/all-tags-section-link.js
@@ -0,0 +1,3 @@
+import templateOnly from "@ember/component/template-only";
+
+export default templateOnly();
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/common/categories-section.js b/app/assets/javascripts/discourse/app/components/sidebar/common/categories-section.js
new file mode 100644
index 0000000000..9a9cd4e081
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/common/categories-section.js
@@ -0,0 +1,29 @@
+import Component from "@glimmer/component";
+import { cached } from "@glimmer/tracking";
+import { inject as service } from "@ember/service";
+
+import CategorySectionLink from "discourse/lib/sidebar/user/categories-section/category-section-link";
+
+export default class SidebarCommonCategoriesSection extends Component {
+ @service topicTrackingState;
+ @service siteSettings;
+
+ // Override in child
+ get categories() {}
+
+ @cached
+ get sectionLinks() {
+ return this.categories
+ .sort((a, b) => a.name.localeCompare(b.name))
+ .reduce((links, category) => {
+ links.push(
+ new CategorySectionLink({
+ category,
+ topicTrackingState: this.topicTrackingState,
+ })
+ );
+
+ return links;
+ }, []);
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/templates/components/sidebar/community-section.hbs b/app/assets/javascripts/discourse/app/components/sidebar/common/community-section.hbs
similarity index 65%
rename from app/assets/javascripts/discourse/app/templates/components/sidebar/community-section.hbs
rename to app/assets/javascripts/discourse/app/components/sidebar/common/community-section.hbs
index 94696fa648..b7b54e3173 100644
--- a/app/assets/javascripts/discourse/app/templates/components/sidebar/community-section.hbs
+++ b/app/assets/javascripts/discourse/app/components/sidebar/common/community-section.hbs
@@ -1,13 +1,16 @@
+ {{#if this.displayShortSiteDescription}}
+
+ {{this.siteSettings.short_site_description}}
+
+ {{/if}}
+
{{#each this.sectionLinks as |sectionLink|}}
+ @models={{sectionLink.models}}
+ @prefixType={{sectionLink.prefixType}}
+ @prefixValue={{sectionLink.prefixValue}} />
{{/each}}
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/common/community-section.js b/app/assets/javascripts/discourse/app/components/sidebar/common/community-section.js
new file mode 100644
index 0000000000..9a2e5174d8
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/common/community-section.js
@@ -0,0 +1,94 @@
+import Component from "@glimmer/component";
+import { inject as service } from "@ember/service";
+import { tracked } from "@glimmer/tracking";
+
+import {
+ customSectionLinks,
+ secondaryCustomSectionLinks,
+} from "discourse/lib/sidebar/custom-community-section-links";
+
+export default class SidebarCommunitySection extends Component {
+ @service router;
+ @service topicTrackingState;
+ @service currentUser;
+ @service appEvents;
+ @service siteSettings;
+
+ @tracked sectionLinks;
+ @tracked moreSectionLinks;
+ @tracked moreSecondarySectionLinks;
+
+ callbackId;
+ headerActionsIcon;
+ headerActions;
+
+ constructor() {
+ super(...arguments);
+
+ this.refreshSectionLinks();
+
+ this.callbackId = this.topicTrackingState.onStateChange(() => {
+ this.sectionLinks.forEach((sectionLink) => {
+ sectionLink.onTopicTrackingStateChange();
+ });
+ });
+ }
+
+ willDestroy() {
+ this.sectionLinks.forEach((sectionLink) => sectionLink.teardown());
+ this.topicTrackingState.offStateChange(this.callbackId);
+ }
+
+ // Override in child
+ get defaultMainSectionLinks() {
+ return [];
+ }
+
+ // Override in child
+ get defaultMoreSectionLinks() {
+ return [];
+ }
+
+ // Override in child
+ get defaultMoreSecondarySectionLinks() {
+ return [];
+ }
+
+ refreshSectionLinks() {
+ this.moreSectionLinks = this.#initializeSectionLinks([
+ ...this.defaultMoreSectionLinks,
+ ...customSectionLinks,
+ ]);
+
+ this.moreSecondarySectionLinks = this.#initializeSectionLinks([
+ ...this.defaultMoreSecondarySectionLinks,
+ ...secondaryCustomSectionLinks,
+ ]);
+
+ this.sectionLinks = this.#initializeSectionLinks(
+ this.defaultMainSectionLinks
+ );
+ }
+
+ #initializeSectionLinks(sectionLinkClasses) {
+ return sectionLinkClasses.reduce((links, sectionLinkClass) => {
+ const sectionLink = this.#initializeSectionLink(sectionLinkClass);
+
+ if (sectionLink.shouldDisplay) {
+ links.push(sectionLink);
+ }
+
+ return links;
+ }, []);
+ }
+
+ #initializeSectionLink(sectionLinkClass) {
+ return new sectionLinkClass({
+ topicTrackingState: this.topicTrackingState,
+ currentUser: this.currentUser,
+ appEvents: this.appEvents,
+ router: this.router,
+ siteSettings: this.siteSettings,
+ });
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/community-section.js b/app/assets/javascripts/discourse/app/components/sidebar/community-section.js
deleted file mode 100644
index e704cfe18d..0000000000
--- a/app/assets/javascripts/discourse/app/components/sidebar/community-section.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import GlimmerComponent from "discourse/components/glimmer";
-import Composer from "discourse/models/composer";
-import { getOwner } from "discourse-common/lib/get-owner";
-import PermissionType from "discourse/models/permission-type";
-import {
- customSectionLinks,
- secondaryCustomSectionLinks,
-} from "discourse/lib/sidebar/custom-community-section-links";
-import EverythingSectionLink from "discourse/lib/sidebar/community-section/everything-section-link";
-import TrackedSectionLink from "discourse/lib/sidebar/community-section/tracked-section-link";
-import MyPostsSectionLink from "discourse/lib/sidebar/community-section/my-posts-section-link";
-import GroupsSectionLink from "discourse/lib/sidebar/community-section/groups-section-link";
-import UsersSectionLink from "discourse/lib/sidebar/community-section/users-section-link";
-import AboutSectionLink from "discourse/lib/sidebar/community-section/about-section-link";
-import FAQSectionLink from "discourse/lib/sidebar/community-section/faq-section-link";
-import AdminSectionLink from "discourse/lib/sidebar/community-section/admin-section-link";
-
-import { inject as service } from "@ember/service";
-import { action } from "@ember/object";
-import { next } from "@ember/runloop";
-
-const MAIN_SECTION_LINKS = [
- EverythingSectionLink,
- TrackedSectionLink,
- MyPostsSectionLink,
-];
-
-const ADMIN_MAIN_SECTION_LINKS = [AdminSectionLink];
-
-const MORE_SECTION_LINKS = [GroupsSectionLink, UsersSectionLink];
-const MORE_SECONDARY_SECTION_LINKS = [AboutSectionLink, FAQSectionLink];
-
-export default class SidebarCommunitySection extends GlimmerComponent {
- @service router;
-
- moreSectionLinks = [...MORE_SECTION_LINKS, ...customSectionLinks].map(
- (sectionLinkClass) => {
- return this.#initializeSectionLink(sectionLinkClass);
- }
- );
-
- moreSecondarySectionLinks = [
- ...MORE_SECONDARY_SECTION_LINKS,
- ...secondaryCustomSectionLinks,
- ].map((sectionLinkClass) => {
- return this.#initializeSectionLink(sectionLinkClass);
- });
-
- #mainSectionLinks = this.currentUser.staff
- ? [...MAIN_SECTION_LINKS, ...ADMIN_MAIN_SECTION_LINKS]
- : [...MAIN_SECTION_LINKS];
-
- sectionLinks = this.#mainSectionLinks.map((sectionLinkClass) => {
- return this.#initializeSectionLink(sectionLinkClass);
- });
-
- willDestroy() {
- this.sectionLinks.forEach((sectionLink) => sectionLink.teardown());
- }
-
- @action
- composeTopic() {
- const composerArgs = {
- action: Composer.CREATE_TOPIC,
- draftKey: Composer.NEW_TOPIC_KEY,
- };
-
- const controller = getOwner(this).lookup("controller:navigation/category");
- const category = controller.category;
-
- if (category && category.permission === PermissionType.FULL) {
- composerArgs.categoryId = category.id;
- }
-
- next(() => {
- getOwner(this).lookup("controller:composer").open(composerArgs);
- });
- }
-
- #initializeSectionLink(sectionLinkClass) {
- return new sectionLinkClass({
- topicTrackingState: this.topicTrackingState,
- currentUser: this.currentUser,
- appEvents: this.appEvents,
- router: this.router,
- siteSettings: this.siteSettings,
- });
- }
-}
diff --git a/app/assets/javascripts/discourse/app/templates/components/sidebar/footer.hbs b/app/assets/javascripts/discourse/app/components/sidebar/footer.hbs
similarity index 100%
rename from app/assets/javascripts/discourse/app/templates/components/sidebar/footer.hbs
rename to app/assets/javascripts/discourse/app/components/sidebar/footer.hbs
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/footer.js b/app/assets/javascripts/discourse/app/components/sidebar/footer.js
new file mode 100644
index 0000000000..68b8c7afe7
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/footer.js
@@ -0,0 +1,12 @@
+import Component from "@glimmer/component";
+import { getOwner } from "discourse-common/lib/get-owner";
+import { inject as service } from "@ember/service";
+
+export default class SidebarFooter extends Component {
+ @service site;
+ @service siteSettings;
+
+ get capabilities() {
+ return getOwner(this).lookup("capabilities:main");
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/hamburger-dropdown.hbs b/app/assets/javascripts/discourse/app/components/sidebar/hamburger-dropdown.hbs
new file mode 100644
index 0000000000..5262f58f6f
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/hamburger-dropdown.hbs
@@ -0,0 +1,12 @@
+
+
+
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/hamburger-dropdown.js b/app/assets/javascripts/discourse/app/components/sidebar/hamburger-dropdown.js
new file mode 100644
index 0000000000..a7888ce2d9
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/hamburger-dropdown.js
@@ -0,0 +1,13 @@
+import Component from "@glimmer/component";
+import { action } from "@ember/object";
+import { inject as service } from "@ember/service";
+
+export default class SidebarHamburgerDropdown extends Component {
+ @service appEvents;
+ @service currentUser;
+
+ @action
+ triggerRenderedAppEvent() {
+ this.appEvents.trigger("sidebar-hamburger-dropdown:rendered");
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/templates/components/sidebar/header-sidebar-toggle.hbs b/app/assets/javascripts/discourse/app/components/sidebar/header-sidebar-toggle.hbs
similarity index 100%
rename from app/assets/javascripts/discourse/app/templates/components/sidebar/header-sidebar-toggle.hbs
rename to app/assets/javascripts/discourse/app/components/sidebar/header-sidebar-toggle.hbs
diff --git a/app/assets/javascripts/discourse/app/templates/components/sidebar/more-section-link.hbs b/app/assets/javascripts/discourse/app/components/sidebar/more-section-link.hbs
similarity index 72%
rename from app/assets/javascripts/discourse/app/templates/components/sidebar/more-section-link.hbs
rename to app/assets/javascripts/discourse/app/components/sidebar/more-section-link.hbs
index 2958b61365..321beab314 100644
--- a/app/assets/javascripts/discourse/app/templates/components/sidebar/more-section-link.hbs
+++ b/app/assets/javascripts/discourse/app/components/sidebar/more-section-link.hbs
@@ -8,4 +8,6 @@
@currentWhen={{@sectionLink.currentWhen}}
@badgeText={{@sectionLink.badgeText}}
@model={{@sectionLink.model}}
- @models={{@sectionLink.models}} />
+ @models={{@sectionLink.models}}
+ @prefixType={{@sectionLink.prefixType}}
+ @prefixValue={{@sectionLink.prefixValue}} />
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/more-section-links.hbs b/app/assets/javascripts/discourse/app/components/sidebar/more-section-links.hbs
new file mode 100644
index 0000000000..154f08b906
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/more-section-links.hbs
@@ -0,0 +1,36 @@
+{{#if this.activeSectionLink}}
+
+{{/if}}
+
+
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/more-section-links.js b/app/assets/javascripts/discourse/app/components/sidebar/more-section-links.js
index d339ad6edf..3bcbd787a6 100644
--- a/app/assets/javascripts/discourse/app/components/sidebar/more-section-links.js
+++ b/app/assets/javascripts/discourse/app/components/sidebar/more-section-links.js
@@ -4,12 +4,13 @@ import { inject as service } from "@ember/service";
import { isEmpty } from "@ember/utils";
import { bind } from "discourse-common/utils/decorators";
-import GlimmerComponent from "discourse/components/glimmer";
+import Component from "@glimmer/component";
+
+export default class SidebarMoreSectionLinks extends Component {
+ @service router;
-export default class SidebarMoreSectionLinks extends GlimmerComponent {
@tracked shouldDisplaySectionLinks = false;
@tracked activeSectionLink;
- @service router;
#allLinks = [...this.args.sectionLinks, ...this.args.secondarySectionLinks];
@@ -72,8 +73,8 @@ export default class SidebarMoreSectionLinks extends GlimmerComponent {
}
@action
- toggleSectionLinks() {
- this.shouldDisplaySectionLinks = !this.shouldDisplaySectionLinks;
+ toggleSectionLinks(element) {
+ this.shouldDisplaySectionLinks = element.target.hasAttribute("open");
}
#removeClickEventListener() {
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/section-header.hbs b/app/assets/javascripts/discourse/app/components/sidebar/section-header.hbs
new file mode 100644
index 0000000000..55839c39e3
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/section-header.hbs
@@ -0,0 +1,13 @@
+{{#if @collapsable}}
+
+
+ {{yield}}
+
+{{else}}
+
+{{/if}}
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/section-header.js b/app/assets/javascripts/discourse/app/components/sidebar/section-header.js
new file mode 100644
index 0000000000..742bb3c18e
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/section-header.js
@@ -0,0 +1,3 @@
+import templateOnly from "@ember/component/template-only";
+
+export default templateOnly();
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/section-link-prefix.hbs b/app/assets/javascripts/discourse/app/components/sidebar/section-link-prefix.hbs
new file mode 100644
index 0000000000..0e4d9600d3
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/section-link-prefix.hbs
@@ -0,0 +1,21 @@
+{{#if @prefixValue}}
+
+ {{#if (eq @prefixType "image")}}
+
+ {{/if}}
+
+ {{#if (eq @prefixType "text")}}
+
+ {{@prefixValue}}
+
+ {{/if}}
+
+ {{#if (eq @prefixType "icon")}}
+ {{d-icon @prefixValue class="prefix-icon"}}
+ {{/if}}
+
+ {{#if @prefixBadge}}
+ {{d-icon @prefixBadge class="prefix-badge"}}
+ {{/if}}
+
+{{/if}}
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/section-link-prefix.js b/app/assets/javascripts/discourse/app/components/sidebar/section-link-prefix.js
new file mode 100644
index 0000000000..742bb3c18e
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/section-link-prefix.js
@@ -0,0 +1,3 @@
+import templateOnly from "@ember/component/template-only";
+
+export default templateOnly();
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/section-link.hbs b/app/assets/javascripts/discourse/app/components/sidebar/section-link.hbs
new file mode 100644
index 0000000000..570e57590f
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/section-link.hbs
@@ -0,0 +1,68 @@
+
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/section-link.js b/app/assets/javascripts/discourse/app/components/sidebar/section-link.js
index 32dc9eae3b..16385ec1f9 100644
--- a/app/assets/javascripts/discourse/app/components/sidebar/section-link.js
+++ b/app/assets/javascripts/discourse/app/components/sidebar/section-link.js
@@ -1,7 +1,7 @@
-import GlimmerComponent from "@glimmer/component";
+import Component from "@glimmer/component";
import { htmlSafe } from "@ember/template";
-export default class SectionLink extends GlimmerComponent {
+export default class SectionLink extends Component {
willDestroy() {
if (this.args.willDestroy) {
this.args.willDestroy();
@@ -9,7 +9,17 @@ export default class SectionLink extends GlimmerComponent {
}
get classNames() {
- return `${this.args.class} sidebar-section-link sidebar-section-link-${this.args.linkName}`;
+ let classNames = [
+ "sidebar-section-link",
+ `sidebar-section-link-${this.args.linkName}`,
+ "sidebar-row",
+ ];
+
+ if (this.args.class) {
+ classNames.push(this.args.class);
+ }
+
+ return classNames.join(" ");
}
get models() {
diff --git a/app/assets/javascripts/discourse/app/templates/components/sidebar/section-message.hbs b/app/assets/javascripts/discourse/app/components/sidebar/section-message.hbs
similarity index 54%
rename from app/assets/javascripts/discourse/app/templates/components/sidebar/section-message.hbs
rename to app/assets/javascripts/discourse/app/components/sidebar/section-message.hbs
index d3d92dad94..cb81be1053 100644
--- a/app/assets/javascripts/discourse/app/templates/components/sidebar/section-message.hbs
+++ b/app/assets/javascripts/discourse/app/components/sidebar/section-message.hbs
@@ -1,4 +1,4 @@
-
@@ -834,224 +834,224 @@
{{i18n "emoji_picker.travel_&_places"}}
- {{replace-emoji ":earth_africa:" (hash lazy=true)}}
- {{replace-emoji ":earth_americas:" (hash lazy=true)}}
- {{replace-emoji ":earth_asia:" (hash lazy=true)}}
- {{replace-emoji ":globe_with_meridians:" (hash lazy=true)}}
- {{replace-emoji ":world_map:" (hash lazy=true)}}
- {{replace-emoji ":japan:" (hash lazy=true)}}
- {{replace-emoji ":compass:" (hash lazy=true)}}
- {{replace-emoji ":mountain_snow:" (hash lazy=true)}}
- {{replace-emoji ":mountain:" (hash lazy=true)}}
- {{replace-emoji ":volcano:" (hash lazy=true)}}
- {{replace-emoji ":mount_fuji:" (hash lazy=true)}}
- {{replace-emoji ":camping:" (hash lazy=true)}}
- {{replace-emoji ":beach_umbrella:" (hash lazy=true)}}
- {{replace-emoji ":desert:" (hash lazy=true)}}
- {{replace-emoji ":desert_island:" (hash lazy=true)}}
- {{replace-emoji ":national_park:" (hash lazy=true)}}
- {{replace-emoji ":stadium:" (hash lazy=true)}}
- {{replace-emoji ":classical_building:" (hash lazy=true)}}
- {{replace-emoji ":building_construction:" (hash lazy=true)}}
- {{replace-emoji ":brick:" (hash lazy=true)}}
- {{replace-emoji ":rock:" (hash lazy=true)}}
- {{replace-emoji ":wood:" (hash lazy=true)}}
- {{replace-emoji ":hut:" (hash lazy=true)}}
- {{replace-emoji ":houses:" (hash lazy=true)}}
- {{replace-emoji ":derelict_house:" (hash lazy=true)}}
- {{replace-emoji ":house:" (hash lazy=true)}}
- {{replace-emoji ":house_with_garden:" (hash lazy=true)}}
- {{replace-emoji ":office:" (hash lazy=true)}}
- {{replace-emoji ":post_office:" (hash lazy=true)}}
- {{replace-emoji ":european_post_office:" (hash lazy=true)}}
- {{replace-emoji ":hospital:" (hash lazy=true)}}
- {{replace-emoji ":bank:" (hash lazy=true)}}
- {{replace-emoji ":hotel:" (hash lazy=true)}}
- {{replace-emoji ":love_hotel:" (hash lazy=true)}}
- {{replace-emoji ":convenience_store:" (hash lazy=true)}}
- {{replace-emoji ":school:" (hash lazy=true)}}
- {{replace-emoji ":department_store:" (hash lazy=true)}}
- {{replace-emoji ":factory:" (hash lazy=true)}}
- {{replace-emoji ":japanese_castle:" (hash lazy=true)}}
- {{replace-emoji ":european_castle:" (hash lazy=true)}}
- {{replace-emoji ":wedding:" (hash lazy=true)}}
- {{replace-emoji ":tokyo_tower:" (hash lazy=true)}}
- {{replace-emoji ":statue_of_liberty:" (hash lazy=true)}}
- {{replace-emoji ":church:" (hash lazy=true)}}
- {{replace-emoji ":mosque:" (hash lazy=true)}}
- {{replace-emoji ":hindu_temple:" (hash lazy=true)}}
- {{replace-emoji ":synagogue:" (hash lazy=true)}}
- {{replace-emoji ":shinto_shrine:" (hash lazy=true)}}
- {{replace-emoji ":kaaba:" (hash lazy=true)}}
- {{replace-emoji ":fountain:" (hash lazy=true)}}
- {{replace-emoji ":tent:" (hash lazy=true)}}
- {{replace-emoji ":foggy:" (hash lazy=true)}}
- {{replace-emoji ":night_with_stars:" (hash lazy=true)}}
- {{replace-emoji ":cityscape:" (hash lazy=true)}}
- {{replace-emoji ":sunrise_over_mountains:" (hash lazy=true)}}
- {{replace-emoji ":sunrise:" (hash lazy=true)}}
- {{replace-emoji ":city_sunset:" (hash lazy=true)}}
- {{replace-emoji ":city_sunrise:" (hash lazy=true)}}
- {{replace-emoji ":bridge_at_night:" (hash lazy=true)}}
- {{replace-emoji ":hotsprings:" (hash lazy=true)}}
- {{replace-emoji ":carousel_horse:" (hash lazy=true)}}
- {{replace-emoji ":playground_slide:" (hash lazy=true)}}
- {{replace-emoji ":ferris_wheel:" (hash lazy=true)}}
- {{replace-emoji ":roller_coaster:" (hash lazy=true)}}
- {{replace-emoji ":barber:" (hash lazy=true)}}
- {{replace-emoji ":circus_tent:" (hash lazy=true)}}
- {{replace-emoji ":steam_locomotive:" (hash lazy=true)}}
- {{replace-emoji ":railway_car:" (hash lazy=true)}}
- {{replace-emoji ":bullettrain_side:" (hash lazy=true)}}
- {{replace-emoji ":bullettrain_front:" (hash lazy=true)}}
- {{replace-emoji ":train2:" (hash lazy=true)}}
- {{replace-emoji ":metro:" (hash lazy=true)}}
- {{replace-emoji ":light_rail:" (hash lazy=true)}}
- {{replace-emoji ":station:" (hash lazy=true)}}
- {{replace-emoji ":tram:" (hash lazy=true)}}
- {{replace-emoji ":monorail:" (hash lazy=true)}}
- {{replace-emoji ":mountain_railway:" (hash lazy=true)}}
- {{replace-emoji ":train:" (hash lazy=true)}}
- {{replace-emoji ":bus:" (hash lazy=true)}}
- {{replace-emoji ":oncoming_bus:" (hash lazy=true)}}
- {{replace-emoji ":trolleybus:" (hash lazy=true)}}
- {{replace-emoji ":minibus:" (hash lazy=true)}}
- {{replace-emoji ":ambulance:" (hash lazy=true)}}
- {{replace-emoji ":fire_engine:" (hash lazy=true)}}
- {{replace-emoji ":police_car:" (hash lazy=true)}}
- {{replace-emoji ":oncoming_police_car:" (hash lazy=true)}}
- {{replace-emoji ":taxi:" (hash lazy=true)}}
- {{replace-emoji ":oncoming_taxi:" (hash lazy=true)}}
- {{replace-emoji ":red_car:" (hash lazy=true)}}
- {{replace-emoji ":oncoming_automobile:" (hash lazy=true)}}
- {{replace-emoji ":blue_car:" (hash lazy=true)}}
- {{replace-emoji ":pickup_truck:" (hash lazy=true)}}
- {{replace-emoji ":truck:" (hash lazy=true)}}
- {{replace-emoji ":articulated_lorry:" (hash lazy=true)}}
- {{replace-emoji ":tractor:" (hash lazy=true)}}
- {{replace-emoji ":racing_car:" (hash lazy=true)}}
- {{replace-emoji ":motorcycle:" (hash lazy=true)}}
- {{replace-emoji ":motor_scooter:" (hash lazy=true)}}
- {{replace-emoji ":manual_wheelchair:" (hash lazy=true)}}
- {{replace-emoji ":motorized_wheelchair:" (hash lazy=true)}}
- {{replace-emoji ":auto_rickshaw:" (hash lazy=true)}}
- {{replace-emoji ":bike:" (hash lazy=true)}}
- {{replace-emoji ":kick_scooter:" (hash lazy=true)}}
- {{replace-emoji ":skateboard:" (hash lazy=true)}}
- {{replace-emoji ":roller_skate:" (hash lazy=true)}}
- {{replace-emoji ":busstop:" (hash lazy=true)}}
- {{replace-emoji ":motorway:" (hash lazy=true)}}
- {{replace-emoji ":railway_track:" (hash lazy=true)}}
- {{replace-emoji ":oil_drum:" (hash lazy=true)}}
- {{replace-emoji ":fuelpump:" (hash lazy=true)}}
- {{replace-emoji ":wheel:" (hash lazy=true)}}
- {{replace-emoji ":rotating_light:" (hash lazy=true)}}
- {{replace-emoji ":traffic_light:" (hash lazy=true)}}
- {{replace-emoji ":vertical_traffic_light:" (hash lazy=true)}}
- {{replace-emoji ":stop_sign:" (hash lazy=true)}}
- {{replace-emoji ":construction:" (hash lazy=true)}}
- {{replace-emoji ":anchor:" (hash lazy=true)}}
- {{replace-emoji ":ring_buoy:" (hash lazy=true)}}
- {{replace-emoji ":sailboat:" (hash lazy=true)}}
- {{replace-emoji ":canoe:" (hash lazy=true)}}
- {{replace-emoji ":speedboat:" (hash lazy=true)}}
- {{replace-emoji ":passenger_ship:" (hash lazy=true)}}
- {{replace-emoji ":ferry:" (hash lazy=true)}}
- {{replace-emoji ":motor_boat:" (hash lazy=true)}}
- {{replace-emoji ":ship:" (hash lazy=true)}}
- {{replace-emoji ":airplane:" (hash lazy=true)}}
- {{replace-emoji ":small_airplane:" (hash lazy=true)}}
- {{replace-emoji ":flight_departure:" (hash lazy=true)}}
- {{replace-emoji ":flight_arrival:" (hash lazy=true)}}
- {{replace-emoji ":parachute:" (hash lazy=true)}}
- {{replace-emoji ":seat:" (hash lazy=true)}}
- {{replace-emoji ":helicopter:" (hash lazy=true)}}
- {{replace-emoji ":suspension_railway:" (hash lazy=true)}}
- {{replace-emoji ":mountain_cableway:" (hash lazy=true)}}
- {{replace-emoji ":aerial_tramway:" (hash lazy=true)}}
- {{replace-emoji ":artificial_satellite:" (hash lazy=true)}}
- {{replace-emoji ":rocket:" (hash lazy=true)}}
- {{replace-emoji ":flying_saucer:" (hash lazy=true)}}
- {{replace-emoji ":bellhop_bell:" (hash lazy=true)}}
- {{replace-emoji ":luggage:" (hash lazy=true)}}
- {{replace-emoji ":hourglass:" (hash lazy=true)}}
- {{replace-emoji ":hourglass_flowing_sand:" (hash lazy=true)}}
- {{replace-emoji ":watch:" (hash lazy=true)}}
- {{replace-emoji ":alarm_clock:" (hash lazy=true)}}
- {{replace-emoji ":stopwatch:" (hash lazy=true)}}
- {{replace-emoji ":timer_clock:" (hash lazy=true)}}
- {{replace-emoji ":mantelpiece_clock:" (hash lazy=true)}}
- {{replace-emoji ":clock12:" (hash lazy=true)}}
- {{replace-emoji ":clock1230:" (hash lazy=true)}}
- {{replace-emoji ":clock1:" (hash lazy=true)}}
- {{replace-emoji ":clock130:" (hash lazy=true)}}
- {{replace-emoji ":clock2:" (hash lazy=true)}}
- {{replace-emoji ":clock230:" (hash lazy=true)}}
- {{replace-emoji ":clock3:" (hash lazy=true)}}
- {{replace-emoji ":clock330:" (hash lazy=true)}}
- {{replace-emoji ":clock4:" (hash lazy=true)}}
- {{replace-emoji ":clock430:" (hash lazy=true)}}
- {{replace-emoji ":clock5:" (hash lazy=true)}}
- {{replace-emoji ":clock530:" (hash lazy=true)}}
- {{replace-emoji ":clock6:" (hash lazy=true)}}
- {{replace-emoji ":clock630:" (hash lazy=true)}}
- {{replace-emoji ":clock7:" (hash lazy=true)}}
- {{replace-emoji ":clock730:" (hash lazy=true)}}
- {{replace-emoji ":clock8:" (hash lazy=true)}}
- {{replace-emoji ":clock830:" (hash lazy=true)}}
- {{replace-emoji ":clock9:" (hash lazy=true)}}
- {{replace-emoji ":clock930:" (hash lazy=true)}}
- {{replace-emoji ":clock10:" (hash lazy=true)}}
- {{replace-emoji ":clock1030:" (hash lazy=true)}}
- {{replace-emoji ":clock11:" (hash lazy=true)}}
- {{replace-emoji ":clock1130:" (hash lazy=true)}}
- {{replace-emoji ":new_moon:" (hash lazy=true)}}
- {{replace-emoji ":waxing_crescent_moon:" (hash lazy=true)}}
- {{replace-emoji ":first_quarter_moon:" (hash lazy=true)}}
- {{replace-emoji ":waxing_gibbous_moon:" (hash lazy=true)}}
- {{replace-emoji ":full_moon:" (hash lazy=true)}}
- {{replace-emoji ":waning_gibbous_moon:" (hash lazy=true)}}
- {{replace-emoji ":last_quarter_moon:" (hash lazy=true)}}
- {{replace-emoji ":waning_crescent_moon:" (hash lazy=true)}}
- {{replace-emoji ":crescent_moon:" (hash lazy=true)}}
- {{replace-emoji ":new_moon_with_face:" (hash lazy=true)}}
- {{replace-emoji ":first_quarter_moon_with_face:" (hash lazy=true)}}
- {{replace-emoji ":last_quarter_moon_with_face:" (hash lazy=true)}}
- {{replace-emoji ":thermometer:" (hash lazy=true)}}
- {{replace-emoji ":sunny:" (hash lazy=true)}}
- {{replace-emoji ":full_moon_with_face:" (hash lazy=true)}}
- {{replace-emoji ":sun_with_face:" (hash lazy=true)}}
- {{replace-emoji ":ringer_planet:" (hash lazy=true)}}
- {{replace-emoji ":star:" (hash lazy=true)}}
- {{replace-emoji ":star2:" (hash lazy=true)}}
- {{replace-emoji ":stars:" (hash lazy=true)}}
- {{replace-emoji ":milky_way:" (hash lazy=true)}}
- {{replace-emoji ":cloud:" (hash lazy=true)}}
- {{replace-emoji ":partly_sunny:" (hash lazy=true)}}
- {{replace-emoji ":cloud_with_lightning_and_rain:" (hash lazy=true)}}
- {{replace-emoji ":sun_behind_small_cloud:" (hash lazy=true)}}
- {{replace-emoji ":sun_behind_large_cloud:" (hash lazy=true)}}
- {{replace-emoji ":sun_behind_rain_cloud:" (hash lazy=true)}}
- {{replace-emoji ":cloud_with_rain:" (hash lazy=true)}}
- {{replace-emoji ":cloud_with_snow:" (hash lazy=true)}}
- {{replace-emoji ":cloud_with_lightning:" (hash lazy=true)}}
- {{replace-emoji ":tornado:" (hash lazy=true)}}
- {{replace-emoji ":fog:" (hash lazy=true)}}
- {{replace-emoji ":wind_face:" (hash lazy=true)}}
- {{replace-emoji ":cyclone:" (hash lazy=true)}}
- {{replace-emoji ":rainbow:" (hash lazy=true)}}
- {{replace-emoji ":closed_umbrella:" (hash lazy=true)}}
- {{replace-emoji ":open_umbrella:" (hash lazy=true)}}
- {{replace-emoji ":umbrella:" (hash lazy=true)}}
- {{replace-emoji ":parasol_on_ground:" (hash lazy=true)}}
- {{replace-emoji ":zap:" (hash lazy=true)}}
- {{replace-emoji ":snowflake:" (hash lazy=true)}}
- {{replace-emoji ":snowman_with_snow:" (hash lazy=true)}}
- {{replace-emoji ":snowman:" (hash lazy=true)}}
- {{replace-emoji ":comet:" (hash lazy=true)}}
- {{replace-emoji ":fire:" (hash lazy=true)}}
- {{replace-emoji ":droplet:" (hash lazy=true)}}
- {{replace-emoji ":ocean:" (hash lazy=true)}}
+ {{replace-emoji ":earth_africa:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":earth_americas:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":earth_asia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":globe_with_meridians:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":world_map:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":japan:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":compass:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mountain_snow:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mountain:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":volcano:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mount_fuji:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":camping:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":beach_umbrella:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":desert:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":desert_island:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":national_park:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":stadium:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":classical_building:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":building_construction:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":brick:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":rock:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":wood:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":hut:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":houses:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":derelict_house:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":house:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":house_with_garden:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":office:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":post_office:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":european_post_office:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":hospital:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bank:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":hotel:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":love_hotel:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":convenience_store:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":school:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":department_store:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":factory:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":japanese_castle:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":european_castle:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":wedding:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tokyo_tower:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":statue_of_liberty:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":church:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mosque:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":hindu_temple:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":synagogue:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":shinto_shrine:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":kaaba:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":fountain:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tent:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":foggy:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":night_with_stars:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cityscape:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sunrise_over_mountains:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sunrise:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":city_sunset:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":city_sunrise:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bridge_at_night:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":hotsprings:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":carousel_horse:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":playground_slide:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ferris_wheel:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":roller_coaster:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":barber:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":circus_tent:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":steam_locomotive:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":railway_car:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bullettrain_side:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bullettrain_front:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":train2:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":metro:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":light_rail:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":station:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tram:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":monorail:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mountain_railway:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":train:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bus:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":oncoming_bus:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":trolleybus:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":minibus:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ambulance:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":fire_engine:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":police_car:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":oncoming_police_car:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":taxi:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":oncoming_taxi:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":red_car:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":oncoming_automobile:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":blue_car:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":pickup_truck:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":truck:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":articulated_lorry:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tractor:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":racing_car:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":motorcycle:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":motor_scooter:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":manual_wheelchair:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":motorized_wheelchair:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":auto_rickshaw:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bike:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":kick_scooter:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":skateboard:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":roller_skate:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":busstop:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":motorway:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":railway_track:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":oil_drum:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":fuelpump:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":wheel:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":rotating_light:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":traffic_light:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":vertical_traffic_light:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":stop_sign:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":construction:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":anchor:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ring_buoy:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sailboat:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":canoe:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":speedboat:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":passenger_ship:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ferry:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":motor_boat:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ship:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":airplane:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":small_airplane:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":flight_departure:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":flight_arrival:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":parachute:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":seat:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":helicopter:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":suspension_railway:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mountain_cableway:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":aerial_tramway:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":artificial_satellite:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":rocket:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":flying_saucer:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bellhop_bell:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":luggage:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":hourglass:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":hourglass_flowing_sand:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":watch:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":alarm_clock:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":stopwatch:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":timer_clock:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mantelpiece_clock:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock12:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock1230:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock1:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock130:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock2:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock230:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock3:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock330:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock4:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock430:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock5:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock530:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock6:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock630:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock7:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock730:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock8:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock830:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock9:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock930:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock10:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock1030:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock11:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clock1130:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":new_moon:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":waxing_crescent_moon:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":first_quarter_moon:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":waxing_gibbous_moon:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":full_moon:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":waning_gibbous_moon:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":last_quarter_moon:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":waning_crescent_moon:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":crescent_moon:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":new_moon_with_face:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":first_quarter_moon_with_face:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":last_quarter_moon_with_face:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":thermometer:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sunny:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":full_moon_with_face:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sun_with_face:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ringer_planet:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":star:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":star2:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":stars:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":milky_way:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cloud:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":partly_sunny:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cloud_with_lightning_and_rain:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sun_behind_small_cloud:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sun_behind_large_cloud:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sun_behind_rain_cloud:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cloud_with_rain:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cloud_with_snow:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cloud_with_lightning:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tornado:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":fog:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":wind_face:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cyclone:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":rainbow:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":closed_umbrella:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":open_umbrella:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":umbrella:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":parasol_on_ground:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":zap:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":snowflake:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":snowman_with_snow:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":snowman:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":comet:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":fire:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":droplet:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ocean:" (hash lazy=true tabIndex="0")}}
@@ -1059,92 +1059,92 @@
{{i18n "emoji_picker.activities"}}
- {{replace-emoji ":jack_o_lantern:" (hash lazy=true)}}
- {{replace-emoji ":christmas_tree:" (hash lazy=true)}}
- {{replace-emoji ":fireworks:" (hash lazy=true)}}
- {{replace-emoji ":sparkler:" (hash lazy=true)}}
- {{replace-emoji ":firecracker:" (hash lazy=true)}}
- {{replace-emoji ":sparkles:" (hash lazy=true)}}
- {{replace-emoji ":balloon:" (hash lazy=true)}}
- {{replace-emoji ":tada:" (hash lazy=true)}}
- {{replace-emoji ":confetti_ball:" (hash lazy=true)}}
- {{replace-emoji ":tanabata_tree:" (hash lazy=true)}}
- {{replace-emoji ":bamboo:" (hash lazy=true)}}
- {{replace-emoji ":dolls:" (hash lazy=true)}}
- {{replace-emoji ":flags:" (hash lazy=true)}}
- {{replace-emoji ":wind_chime:" (hash lazy=true)}}
- {{replace-emoji ":rice_scene:" (hash lazy=true)}}
- {{replace-emoji ":red_gift_envelope:" (hash lazy=true)}}
- {{replace-emoji ":ribbon:" (hash lazy=true)}}
- {{replace-emoji ":gift:" (hash lazy=true)}}
- {{replace-emoji ":reminder_ribbon:" (hash lazy=true)}}
- {{replace-emoji ":tickets:" (hash lazy=true)}}
- {{replace-emoji ":ticket:" (hash lazy=true)}}
- {{replace-emoji ":medal_military:" (hash lazy=true)}}
- {{replace-emoji ":trophy:" (hash lazy=true)}}
- {{replace-emoji ":medal_sports:" (hash lazy=true)}}
- {{replace-emoji ":1st_place_medal:" (hash lazy=true)}}
- {{replace-emoji ":2nd_place_medal:" (hash lazy=true)}}
- {{replace-emoji ":3rd_place_medal:" (hash lazy=true)}}
- {{replace-emoji ":soccer:" (hash lazy=true)}}
- {{replace-emoji ":baseball:" (hash lazy=true)}}
- {{replace-emoji ":softball:" (hash lazy=true)}}
- {{replace-emoji ":basketball:" (hash lazy=true)}}
- {{replace-emoji ":volleyball:" (hash lazy=true)}}
- {{replace-emoji ":football:" (hash lazy=true)}}
- {{replace-emoji ":rugby_football:" (hash lazy=true)}}
- {{replace-emoji ":tennis:" (hash lazy=true)}}
- {{replace-emoji ":flying_disc:" (hash lazy=true)}}
- {{replace-emoji ":bowling:" (hash lazy=true)}}
- {{replace-emoji ":cricket_bat_and_ball:" (hash lazy=true)}}
- {{replace-emoji ":field_hockey:" (hash lazy=true)}}
- {{replace-emoji ":ice_hockey:" (hash lazy=true)}}
- {{replace-emoji ":lacrosse:" (hash lazy=true)}}
- {{replace-emoji ":ping_pong:" (hash lazy=true)}}
- {{replace-emoji ":badminton:" (hash lazy=true)}}
- {{replace-emoji ":boxing_glove:" (hash lazy=true)}}
- {{replace-emoji ":martial_arts_uniform:" (hash lazy=true)}}
- {{replace-emoji ":goal_net:" (hash lazy=true)}}
- {{replace-emoji ":golf:" (hash lazy=true)}}
- {{replace-emoji ":ice_skate:" (hash lazy=true)}}
- {{replace-emoji ":fishing_pole_and_fish:" (hash lazy=true)}}
- {{replace-emoji ":diving_mask:" (hash lazy=true)}}
- {{replace-emoji ":running_shirt_with_sash:" (hash lazy=true)}}
- {{replace-emoji ":ski:" (hash lazy=true)}}
- {{replace-emoji ":sled:" (hash lazy=true)}}
- {{replace-emoji ":curling_stone:" (hash lazy=true)}}
- {{replace-emoji ":dart:" (hash lazy=true)}}
- {{replace-emoji ":yo-yo:" (hash lazy=true)}}
- {{replace-emoji ":kite:" (hash lazy=true)}}
- {{replace-emoji ":8ball:" (hash lazy=true)}}
- {{replace-emoji ":crystal_ball:" (hash lazy=true)}}
- {{replace-emoji ":magic_wand:" (hash lazy=true)}}
- {{replace-emoji ":nazar_amulet:" (hash lazy=true)}}
- {{replace-emoji ":hamsa:" (hash lazy=true)}}
- {{replace-emoji ":video_game:" (hash lazy=true)}}
- {{replace-emoji ":joystick:" (hash lazy=true)}}
- {{replace-emoji ":slot_machine:" (hash lazy=true)}}
- {{replace-emoji ":game_die:" (hash lazy=true)}}
- {{replace-emoji ":jigsaw:" (hash lazy=true)}}
- {{replace-emoji ":teddy_bear:" (hash lazy=true)}}
- {{replace-emoji ":piñata:" (hash lazy=true)}}
- {{replace-emoji ":mirror_ball:" (hash lazy=true)}}
- {{replace-emoji ":nesting_dolls:" (hash lazy=true)}}
- {{replace-emoji ":spades:" (hash lazy=true)}}
- {{replace-emoji ":hearts:" (hash lazy=true)}}
- {{replace-emoji ":diamonds:" (hash lazy=true)}}
- {{replace-emoji ":clubs:" (hash lazy=true)}}
- {{replace-emoji ":chess_pawn:" (hash lazy=true)}}
- {{replace-emoji ":black_joker:" (hash lazy=true)}}
- {{replace-emoji ":mahjong:" (hash lazy=true)}}
- {{replace-emoji ":flower_playing_cards:" (hash lazy=true)}}
- {{replace-emoji ":performing_arts:" (hash lazy=true)}}
- {{replace-emoji ":framed_picture:" (hash lazy=true)}}
- {{replace-emoji ":art:" (hash lazy=true)}}
- {{replace-emoji ":thread:" (hash lazy=true)}}
- {{replace-emoji ":sewing_needle:" (hash lazy=true)}}
- {{replace-emoji ":yarn:" (hash lazy=true)}}
- {{replace-emoji ":knot:" (hash lazy=true)}}
+ {{replace-emoji ":jack_o_lantern:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":christmas_tree:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":fireworks:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sparkler:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":firecracker:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sparkles:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":balloon:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tada:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":confetti_ball:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tanabata_tree:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bamboo:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":dolls:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":flags:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":wind_chime:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":rice_scene:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":red_gift_envelope:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ribbon:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":gift:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":reminder_ribbon:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tickets:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ticket:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":medal_military:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":trophy:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":medal_sports:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":1st_place_medal:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":2nd_place_medal:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":3rd_place_medal:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":soccer:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":baseball:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":softball:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":basketball:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":volleyball:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":football:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":rugby_football:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tennis:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":flying_disc:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bowling:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cricket_bat_and_ball:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":field_hockey:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ice_hockey:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":lacrosse:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ping_pong:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":badminton:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":boxing_glove:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":martial_arts_uniform:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":goal_net:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":golf:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ice_skate:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":fishing_pole_and_fish:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":diving_mask:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":running_shirt_with_sash:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ski:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sled:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":curling_stone:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":dart:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":yo-yo:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":kite:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":8ball:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":crystal_ball:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":magic_wand:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":nazar_amulet:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":hamsa:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":video_game:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":joystick:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":slot_machine:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":game_die:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":jigsaw:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":teddy_bear:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":piñata:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mirror_ball:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":nesting_dolls:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":spades:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":hearts:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":diamonds:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clubs:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":chess_pawn:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":black_joker:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mahjong:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":flower_playing_cards:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":performing_arts:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":framed_picture:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":art:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":thread:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sewing_needle:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":yarn:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":knot:" (hash lazy=true tabIndex="0")}}
@@ -1152,261 +1152,261 @@
{{i18n "emoji_picker.objects"}}
- {{replace-emoji ":eyeglasses:" (hash lazy=true)}}
- {{replace-emoji ":dark_sunglasses:" (hash lazy=true)}}
- {{replace-emoji ":goggles:" (hash lazy=true)}}
- {{replace-emoji ":lab_coat:" (hash lazy=true)}}
- {{replace-emoji ":safety_vest:" (hash lazy=true)}}
- {{replace-emoji ":necktie:" (hash lazy=true)}}
- {{replace-emoji ":tshirt:" (hash lazy=true)}}
- {{replace-emoji ":jeans:" (hash lazy=true)}}
- {{replace-emoji ":scarf:" (hash lazy=true)}}
- {{replace-emoji ":gloves:" (hash lazy=true)}}
- {{replace-emoji ":coat:" (hash lazy=true)}}
- {{replace-emoji ":socks:" (hash lazy=true)}}
- {{replace-emoji ":dress:" (hash lazy=true)}}
- {{replace-emoji ":kimono:" (hash lazy=true)}}
- {{replace-emoji ":sari:" (hash lazy=true)}}
- {{replace-emoji ":one_piece_swimsuit:" (hash lazy=true)}}
- {{replace-emoji ":briefs:" (hash lazy=true)}}
- {{replace-emoji ":shorts:" (hash lazy=true)}}
- {{replace-emoji ":bikini:" (hash lazy=true)}}
- {{replace-emoji ":womans_clothes:" (hash lazy=true)}}
- {{replace-emoji ":purse:" (hash lazy=true)}}
- {{replace-emoji ":handbag:" (hash lazy=true)}}
- {{replace-emoji ":pouch:" (hash lazy=true)}}
- {{replace-emoji ":shopping:" (hash lazy=true)}}
- {{replace-emoji ":school_satchel:" (hash lazy=true)}}
- {{replace-emoji ":thong_sandal:" (hash lazy=true)}}
- {{replace-emoji ":mans_shoe:" (hash lazy=true)}}
- {{replace-emoji ":athletic_shoe:" (hash lazy=true)}}
- {{replace-emoji ":hiking_boot:" (hash lazy=true)}}
- {{replace-emoji ":flat_shoe:" (hash lazy=true)}}
- {{replace-emoji ":high_heel:" (hash lazy=true)}}
- {{replace-emoji ":sandal:" (hash lazy=true)}}
- {{replace-emoji ":ballet_shoes:" (hash lazy=true)}}
- {{replace-emoji ":boot:" (hash lazy=true)}}
- {{replace-emoji ":crown:" (hash lazy=true)}}
- {{replace-emoji ":womans_hat:" (hash lazy=true)}}
- {{replace-emoji ":tophat:" (hash lazy=true)}}
- {{replace-emoji ":mortar_board:" (hash lazy=true)}}
- {{replace-emoji ":billed_cap:" (hash lazy=true)}}
- {{replace-emoji ":military_helmet:" (hash lazy=true)}}
- {{replace-emoji ":rescue_worker_helmet:" (hash lazy=true)}}
- {{replace-emoji ":prayer_beads:" (hash lazy=true)}}
- {{replace-emoji ":lipstick:" (hash lazy=true)}}
- {{replace-emoji ":ring:" (hash lazy=true)}}
- {{replace-emoji ":gem:" (hash lazy=true)}}
- {{replace-emoji ":mute:" (hash lazy=true)}}
- {{replace-emoji ":speaker:" (hash lazy=true)}}
- {{replace-emoji ":sound:" (hash lazy=true)}}
- {{replace-emoji ":loud_sound:" (hash lazy=true)}}
- {{replace-emoji ":loudspeaker:" (hash lazy=true)}}
- {{replace-emoji ":mega:" (hash lazy=true)}}
- {{replace-emoji ":postal_horn:" (hash lazy=true)}}
- {{replace-emoji ":bell:" (hash lazy=true)}}
- {{replace-emoji ":no_bell:" (hash lazy=true)}}
- {{replace-emoji ":musical_score:" (hash lazy=true)}}
- {{replace-emoji ":musical_note:" (hash lazy=true)}}
- {{replace-emoji ":notes:" (hash lazy=true)}}
- {{replace-emoji ":studio_microphone:" (hash lazy=true)}}
- {{replace-emoji ":level_slider:" (hash lazy=true)}}
- {{replace-emoji ":control_knobs:" (hash lazy=true)}}
- {{replace-emoji ":microphone:" (hash lazy=true)}}
- {{replace-emoji ":headphones:" (hash lazy=true)}}
- {{replace-emoji ":radio:" (hash lazy=true)}}
- {{replace-emoji ":saxophone:" (hash lazy=true)}}
- {{replace-emoji ":accordion:" (hash lazy=true)}}
- {{replace-emoji ":guitar:" (hash lazy=true)}}
- {{replace-emoji ":musical_keyboard:" (hash lazy=true)}}
- {{replace-emoji ":trumpet:" (hash lazy=true)}}
- {{replace-emoji ":violin:" (hash lazy=true)}}
- {{replace-emoji ":banjo:" (hash lazy=true)}}
- {{replace-emoji ":drum:" (hash lazy=true)}}
- {{replace-emoji ":long_drum:" (hash lazy=true)}}
- {{replace-emoji ":iphone:" (hash lazy=true)}}
- {{replace-emoji ":calling:" (hash lazy=true)}}
- {{replace-emoji ":phone:" (hash lazy=true)}}
- {{replace-emoji ":telephone_receiver:" (hash lazy=true)}}
- {{replace-emoji ":pager:" (hash lazy=true)}}
- {{replace-emoji ":fax:" (hash lazy=true)}}
- {{replace-emoji ":battery:" (hash lazy=true)}}
- {{replace-emoji ":low_battery:" (hash lazy=true)}}
- {{replace-emoji ":electric_plug:" (hash lazy=true)}}
- {{replace-emoji ":computer:" (hash lazy=true)}}
- {{replace-emoji ":desktop_computer:" (hash lazy=true)}}
- {{replace-emoji ":printer:" (hash lazy=true)}}
- {{replace-emoji ":keyboard:" (hash lazy=true)}}
- {{replace-emoji ":computer_mouse:" (hash lazy=true)}}
- {{replace-emoji ":trackball:" (hash lazy=true)}}
- {{replace-emoji ":minidisc:" (hash lazy=true)}}
- {{replace-emoji ":floppy_disk:" (hash lazy=true)}}
- {{replace-emoji ":cd:" (hash lazy=true)}}
- {{replace-emoji ":dvd:" (hash lazy=true)}}
- {{replace-emoji ":abacus:" (hash lazy=true)}}
- {{replace-emoji ":movie_camera:" (hash lazy=true)}}
- {{replace-emoji ":film_strip:" (hash lazy=true)}}
- {{replace-emoji ":film_projector:" (hash lazy=true)}}
- {{replace-emoji ":clapper:" (hash lazy=true)}}
- {{replace-emoji ":tv:" (hash lazy=true)}}
- {{replace-emoji ":camera:" (hash lazy=true)}}
- {{replace-emoji ":camera_flash:" (hash lazy=true)}}
- {{replace-emoji ":video_camera:" (hash lazy=true)}}
- {{replace-emoji ":vhs:" (hash lazy=true)}}
- {{replace-emoji ":mag:" (hash lazy=true)}}
- {{replace-emoji ":mag_right:" (hash lazy=true)}}
- {{replace-emoji ":candle:" (hash lazy=true)}}
- {{replace-emoji ":bulb:" (hash lazy=true)}}
- {{replace-emoji ":flashlight:" (hash lazy=true)}}
- {{replace-emoji ":izakaya_lantern:" (hash lazy=true)}}
- {{replace-emoji ":diya_lamp:" (hash lazy=true)}}
- {{replace-emoji ":notebook_with_decorative_cover:" (hash lazy=true)}}
- {{replace-emoji ":closed_book:" (hash lazy=true)}}
- {{replace-emoji ":open_book:" (hash lazy=true)}}
- {{replace-emoji ":green_book:" (hash lazy=true)}}
- {{replace-emoji ":blue_book:" (hash lazy=true)}}
- {{replace-emoji ":orange_book:" (hash lazy=true)}}
- {{replace-emoji ":books:" (hash lazy=true)}}
- {{replace-emoji ":notebook:" (hash lazy=true)}}
- {{replace-emoji ":ledger:" (hash lazy=true)}}
- {{replace-emoji ":page_with_curl:" (hash lazy=true)}}
- {{replace-emoji ":scroll:" (hash lazy=true)}}
- {{replace-emoji ":page_facing_up:" (hash lazy=true)}}
- {{replace-emoji ":newspaper:" (hash lazy=true)}}
- {{replace-emoji ":newspaper_roll:" (hash lazy=true)}}
- {{replace-emoji ":bookmark_tabs:" (hash lazy=true)}}
- {{replace-emoji ":bookmark:" (hash lazy=true)}}
- {{replace-emoji ":label:" (hash lazy=true)}}
- {{replace-emoji ":moneybag:" (hash lazy=true)}}
- {{replace-emoji ":coin:" (hash lazy=true)}}
- {{replace-emoji ":yen:" (hash lazy=true)}}
- {{replace-emoji ":dollar:" (hash lazy=true)}}
- {{replace-emoji ":euro:" (hash lazy=true)}}
- {{replace-emoji ":pound:" (hash lazy=true)}}
- {{replace-emoji ":money_with_wings:" (hash lazy=true)}}
- {{replace-emoji ":credit_card:" (hash lazy=true)}}
- {{replace-emoji ":receipt:" (hash lazy=true)}}
- {{replace-emoji ":chart:" (hash lazy=true)}}
- {{replace-emoji ":email:" (hash lazy=true)}}
- {{replace-emoji ":e-mail:" (hash lazy=true)}}
- {{replace-emoji ":incoming_envelope:" (hash lazy=true)}}
- {{replace-emoji ":envelope_with_arrow:" (hash lazy=true)}}
- {{replace-emoji ":outbox_tray:" (hash lazy=true)}}
- {{replace-emoji ":inbox_tray:" (hash lazy=true)}}
- {{replace-emoji ":package:" (hash lazy=true)}}
- {{replace-emoji ":mailbox:" (hash lazy=true)}}
- {{replace-emoji ":mailbox_closed:" (hash lazy=true)}}
- {{replace-emoji ":mailbox_with_mail:" (hash lazy=true)}}
- {{replace-emoji ":mailbox_with_no_mail:" (hash lazy=true)}}
- {{replace-emoji ":postbox:" (hash lazy=true)}}
- {{replace-emoji ":ballot_box:" (hash lazy=true)}}
- {{replace-emoji ":pencil2:" (hash lazy=true)}}
- {{replace-emoji ":black_nib:" (hash lazy=true)}}
- {{replace-emoji ":fountain_pen:" (hash lazy=true)}}
- {{replace-emoji ":pen:" (hash lazy=true)}}
- {{replace-emoji ":paintbrush:" (hash lazy=true)}}
- {{replace-emoji ":crayon:" (hash lazy=true)}}
- {{replace-emoji ":memo:" (hash lazy=true)}}
- {{replace-emoji ":briefcase:" (hash lazy=true)}}
- {{replace-emoji ":file_folder:" (hash lazy=true)}}
- {{replace-emoji ":open_file_folder:" (hash lazy=true)}}
- {{replace-emoji ":card_index_dividers:" (hash lazy=true)}}
- {{replace-emoji ":date:" (hash lazy=true)}}
- {{replace-emoji ":calendar:" (hash lazy=true)}}
- {{replace-emoji ":spiral_notepad:" (hash lazy=true)}}
- {{replace-emoji ":spiral_calendar:" (hash lazy=true)}}
- {{replace-emoji ":card_index:" (hash lazy=true)}}
- {{replace-emoji ":chart_with_upwards_trend:" (hash lazy=true)}}
- {{replace-emoji ":chart_with_downwards_trend:" (hash lazy=true)}}
- {{replace-emoji ":bar_chart:" (hash lazy=true)}}
- {{replace-emoji ":clipboard:" (hash lazy=true)}}
- {{replace-emoji ":pushpin:" (hash lazy=true)}}
- {{replace-emoji ":round_pushpin:" (hash lazy=true)}}
- {{replace-emoji ":paperclip:" (hash lazy=true)}}
- {{replace-emoji ":paperclips:" (hash lazy=true)}}
- {{replace-emoji ":straight_ruler:" (hash lazy=true)}}
- {{replace-emoji ":triangular_ruler:" (hash lazy=true)}}
- {{replace-emoji ":scissors:" (hash lazy=true)}}
- {{replace-emoji ":card_file_box:" (hash lazy=true)}}
- {{replace-emoji ":file_cabinet:" (hash lazy=true)}}
- {{replace-emoji ":wastebasket:" (hash lazy=true)}}
- {{replace-emoji ":lock:" (hash lazy=true)}}
- {{replace-emoji ":unlock:" (hash lazy=true)}}
- {{replace-emoji ":lock_with_ink_pen:" (hash lazy=true)}}
- {{replace-emoji ":closed_lock_with_key:" (hash lazy=true)}}
- {{replace-emoji ":key:" (hash lazy=true)}}
- {{replace-emoji ":old_key:" (hash lazy=true)}}
- {{replace-emoji ":hammer:" (hash lazy=true)}}
- {{replace-emoji ":axe:" (hash lazy=true)}}
- {{replace-emoji ":pick:" (hash lazy=true)}}
- {{replace-emoji ":hammer_and_pick:" (hash lazy=true)}}
- {{replace-emoji ":hammer_and_wrench:" (hash lazy=true)}}
- {{replace-emoji ":dagger:" (hash lazy=true)}}
- {{replace-emoji ":crossed_swords:" (hash lazy=true)}}
- {{replace-emoji ":gun:" (hash lazy=true)}}
- {{replace-emoji ":boomerang:" (hash lazy=true)}}
- {{replace-emoji ":bow_and_arrow:" (hash lazy=true)}}
- {{replace-emoji ":shield:" (hash lazy=true)}}
- {{replace-emoji ":carpentry_saw:" (hash lazy=true)}}
- {{replace-emoji ":wrench:" (hash lazy=true)}}
- {{replace-emoji ":screwdriver:" (hash lazy=true)}}
- {{replace-emoji ":nut_and_bolt:" (hash lazy=true)}}
- {{replace-emoji ":gear:" (hash lazy=true)}}
- {{replace-emoji ":clamp:" (hash lazy=true)}}
- {{replace-emoji ":balance_scale:" (hash lazy=true)}}
- {{replace-emoji ":probing_cane:" (hash lazy=true)}}
- {{replace-emoji ":link:" (hash lazy=true)}}
- {{replace-emoji ":chains:" (hash lazy=true)}}
- {{replace-emoji ":hook:" (hash lazy=true)}}
- {{replace-emoji ":toolbox:" (hash lazy=true)}}
- {{replace-emoji ":magnet:" (hash lazy=true)}}
- {{replace-emoji ":ladder:" (hash lazy=true)}}
- {{replace-emoji ":alembic:" (hash lazy=true)}}
- {{replace-emoji ":test_tube:" (hash lazy=true)}}
- {{replace-emoji ":petri_dish:" (hash lazy=true)}}
- {{replace-emoji ":dna:" (hash lazy=true)}}
- {{replace-emoji ":microscope:" (hash lazy=true)}}
- {{replace-emoji ":telescope:" (hash lazy=true)}}
- {{replace-emoji ":satellite:" (hash lazy=true)}}
- {{replace-emoji ":syringe:" (hash lazy=true)}}
- {{replace-emoji ":drop_of_blood:" (hash lazy=true)}}
- {{replace-emoji ":pill:" (hash lazy=true)}}
- {{replace-emoji ":adhesive_bandage:" (hash lazy=true)}}
- {{replace-emoji ":crutch:" (hash lazy=true)}}
- {{replace-emoji ":stethoscope:" (hash lazy=true)}}
- {{replace-emoji ":xray:" (hash lazy=true)}}
- {{replace-emoji ":door:" (hash lazy=true)}}
- {{replace-emoji ":elevator:" (hash lazy=true)}}
- {{replace-emoji ":mirror:" (hash lazy=true)}}
- {{replace-emoji ":window:" (hash lazy=true)}}
- {{replace-emoji ":bed:" (hash lazy=true)}}
- {{replace-emoji ":couch_and_lamp:" (hash lazy=true)}}
- {{replace-emoji ":chair:" (hash lazy=true)}}
- {{replace-emoji ":toilet:" (hash lazy=true)}}
- {{replace-emoji ":plunger:" (hash lazy=true)}}
- {{replace-emoji ":shower:" (hash lazy=true)}}
- {{replace-emoji ":bathtub:" (hash lazy=true)}}
- {{replace-emoji ":mouse_trap:" (hash lazy=true)}}
- {{replace-emoji ":razor:" (hash lazy=true)}}
- {{replace-emoji ":lotion_bottle:" (hash lazy=true)}}
- {{replace-emoji ":safety_pin:" (hash lazy=true)}}
- {{replace-emoji ":broom:" (hash lazy=true)}}
- {{replace-emoji ":basket:" (hash lazy=true)}}
- {{replace-emoji ":roll_of_toilet_paper:" (hash lazy=true)}}
- {{replace-emoji ":bucket:" (hash lazy=true)}}
- {{replace-emoji ":soap:" (hash lazy=true)}}
- {{replace-emoji ":bubbles:" (hash lazy=true)}}
- {{replace-emoji ":toothbrush:" (hash lazy=true)}}
- {{replace-emoji ":sponge:" (hash lazy=true)}}
- {{replace-emoji ":fire_extinguisher:" (hash lazy=true)}}
- {{replace-emoji ":shopping_cart:" (hash lazy=true)}}
- {{replace-emoji ":smoking:" (hash lazy=true)}}
- {{replace-emoji ":coffin:" (hash lazy=true)}}
- {{replace-emoji ":headstone:" (hash lazy=true)}}
- {{replace-emoji ":funeral_urn:" (hash lazy=true)}}
- {{replace-emoji ":moyai:" (hash lazy=true)}}
- {{replace-emoji ":placard:" (hash lazy=true)}}
- {{replace-emoji ":identification_card:" (hash lazy=true)}}
+ {{replace-emoji ":eyeglasses:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":dark_sunglasses:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":goggles:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":lab_coat:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":safety_vest:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":necktie:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tshirt:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":jeans:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":scarf:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":gloves:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":coat:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":socks:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":dress:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":kimono:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sari:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":one_piece_swimsuit:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":briefs:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":shorts:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bikini:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":womans_clothes:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":purse:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":handbag:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":pouch:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":shopping:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":school_satchel:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":thong_sandal:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mans_shoe:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":athletic_shoe:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":hiking_boot:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":flat_shoe:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":high_heel:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sandal:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ballet_shoes:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":boot:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":crown:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":womans_hat:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tophat:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mortar_board:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":billed_cap:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":military_helmet:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":rescue_worker_helmet:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":prayer_beads:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":lipstick:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ring:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":gem:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mute:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":speaker:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sound:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":loud_sound:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":loudspeaker:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mega:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":postal_horn:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bell:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":no_bell:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":musical_score:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":musical_note:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":notes:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":studio_microphone:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":level_slider:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":control_knobs:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":microphone:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":headphones:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":radio:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":saxophone:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":accordion:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":guitar:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":musical_keyboard:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":trumpet:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":violin:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":banjo:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":drum:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":long_drum:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":iphone:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":calling:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":phone:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":telephone_receiver:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":pager:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":fax:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":battery:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":low_battery:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":electric_plug:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":computer:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":desktop_computer:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":printer:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":keyboard:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":computer_mouse:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":trackball:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":minidisc:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":floppy_disk:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cd:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":dvd:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":abacus:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":movie_camera:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":film_strip:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":film_projector:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clapper:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tv:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":camera:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":camera_flash:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":video_camera:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":vhs:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mag:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mag_right:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":candle:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bulb:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":flashlight:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":izakaya_lantern:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":diya_lamp:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":notebook_with_decorative_cover:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":closed_book:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":open_book:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":green_book:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":blue_book:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":orange_book:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":books:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":notebook:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ledger:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":page_with_curl:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":scroll:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":page_facing_up:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":newspaper:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":newspaper_roll:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bookmark_tabs:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bookmark:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":label:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":moneybag:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":coin:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":yen:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":dollar:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":euro:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":pound:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":money_with_wings:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":credit_card:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":receipt:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":chart:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":email:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":e-mail:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":incoming_envelope:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":envelope_with_arrow:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":outbox_tray:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":inbox_tray:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":package:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mailbox:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mailbox_closed:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mailbox_with_mail:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mailbox_with_no_mail:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":postbox:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ballot_box:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":pencil2:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":black_nib:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":fountain_pen:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":pen:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":paintbrush:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":crayon:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":memo:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":briefcase:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":file_folder:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":open_file_folder:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":card_index_dividers:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":date:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":calendar:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":spiral_notepad:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":spiral_calendar:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":card_index:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":chart_with_upwards_trend:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":chart_with_downwards_trend:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bar_chart:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clipboard:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":pushpin:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":round_pushpin:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":paperclip:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":paperclips:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":straight_ruler:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":triangular_ruler:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":scissors:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":card_file_box:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":file_cabinet:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":wastebasket:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":lock:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":unlock:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":lock_with_ink_pen:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":closed_lock_with_key:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":key:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":old_key:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":hammer:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":axe:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":pick:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":hammer_and_pick:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":hammer_and_wrench:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":dagger:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":crossed_swords:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":gun:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":boomerang:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bow_and_arrow:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":shield:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":carpentry_saw:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":wrench:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":screwdriver:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":nut_and_bolt:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":gear:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clamp:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":balance_scale:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":probing_cane:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":link:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":chains:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":hook:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":toolbox:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":magnet:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ladder:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":alembic:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":test_tube:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":petri_dish:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":dna:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":microscope:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":telescope:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":satellite:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":syringe:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":drop_of_blood:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":pill:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":adhesive_bandage:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":crutch:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":stethoscope:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":xray:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":door:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":elevator:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mirror:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":window:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bed:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":couch_and_lamp:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":chair:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":toilet:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":plunger:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":shower:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bathtub:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mouse_trap:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":razor:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":lotion_bottle:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":safety_pin:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":broom:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":basket:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":roll_of_toilet_paper:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bucket:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":soap:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bubbles:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":toothbrush:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sponge:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":fire_extinguisher:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":shopping_cart:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":smoking:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":coffin:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":headstone:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":funeral_urn:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":moyai:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":placard:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":identification_card:" (hash lazy=true tabIndex="0")}}
@@ -1414,227 +1414,227 @@
{{i18n "emoji_picker.symbols"}}
- {{replace-emoji ":atm:" (hash lazy=true)}}
- {{replace-emoji ":put_litter_in_its_place:" (hash lazy=true)}}
- {{replace-emoji ":potable_water:" (hash lazy=true)}}
- {{replace-emoji ":wheelchair:" (hash lazy=true)}}
- {{replace-emoji ":mens:" (hash lazy=true)}}
- {{replace-emoji ":womens:" (hash lazy=true)}}
- {{replace-emoji ":restroom:" (hash lazy=true)}}
- {{replace-emoji ":baby_symbol:" (hash lazy=true)}}
- {{replace-emoji ":wc:" (hash lazy=true)}}
- {{replace-emoji ":passport_control:" (hash lazy=true)}}
- {{replace-emoji ":customs:" (hash lazy=true)}}
- {{replace-emoji ":baggage_claim:" (hash lazy=true)}}
- {{replace-emoji ":left_luggage:" (hash lazy=true)}}
- {{replace-emoji ":warning:" (hash lazy=true)}}
- {{replace-emoji ":children_crossing:" (hash lazy=true)}}
- {{replace-emoji ":no_entry:" (hash lazy=true)}}
- {{replace-emoji ":no_entry_sign:" (hash lazy=true)}}
- {{replace-emoji ":no_bicycles:" (hash lazy=true)}}
- {{replace-emoji ":no_smoking:" (hash lazy=true)}}
- {{replace-emoji ":do_not_litter:" (hash lazy=true)}}
- {{replace-emoji ":non-potable_water:" (hash lazy=true)}}
- {{replace-emoji ":no_pedestrians:" (hash lazy=true)}}
- {{replace-emoji ":no_mobile_phones:" (hash lazy=true)}}
- {{replace-emoji ":underage:" (hash lazy=true)}}
- {{replace-emoji ":radioactive:" (hash lazy=true)}}
- {{replace-emoji ":biohazard:" (hash lazy=true)}}
- {{replace-emoji ":arrow_up:" (hash lazy=true)}}
- {{replace-emoji ":arrow_upper_right:" (hash lazy=true)}}
- {{replace-emoji ":arrow_right:" (hash lazy=true)}}
- {{replace-emoji ":arrow_lower_right:" (hash lazy=true)}}
- {{replace-emoji ":arrow_down:" (hash lazy=true)}}
- {{replace-emoji ":arrow_lower_left:" (hash lazy=true)}}
- {{replace-emoji ":arrow_left:" (hash lazy=true)}}
- {{replace-emoji ":arrow_upper_left:" (hash lazy=true)}}
- {{replace-emoji ":arrow_up_down:" (hash lazy=true)}}
- {{replace-emoji ":left_right_arrow:" (hash lazy=true)}}
- {{replace-emoji ":leftwards_arrow_with_hook:" (hash lazy=true)}}
- {{replace-emoji ":arrow_right_hook:" (hash lazy=true)}}
- {{replace-emoji ":arrow_heading_up:" (hash lazy=true)}}
- {{replace-emoji ":arrow_heading_down:" (hash lazy=true)}}
- {{replace-emoji ":arrows_clockwise:" (hash lazy=true)}}
- {{replace-emoji ":arrows_counterclockwise:" (hash lazy=true)}}
- {{replace-emoji ":back:" (hash lazy=true)}}
- {{replace-emoji ":end:" (hash lazy=true)}}
- {{replace-emoji ":on:" (hash lazy=true)}}
- {{replace-emoji ":soon:" (hash lazy=true)}}
- {{replace-emoji ":top:" (hash lazy=true)}}
- {{replace-emoji ":place_of_worship:" (hash lazy=true)}}
- {{replace-emoji ":atom_symbol:" (hash lazy=true)}}
- {{replace-emoji ":om:" (hash lazy=true)}}
- {{replace-emoji ":star_of_david:" (hash lazy=true)}}
- {{replace-emoji ":wheel_of_dharma:" (hash lazy=true)}}
- {{replace-emoji ":yin_yang:" (hash lazy=true)}}
- {{replace-emoji ":latin_cross:" (hash lazy=true)}}
- {{replace-emoji ":orthodox_cross:" (hash lazy=true)}}
- {{replace-emoji ":star_and_crescent:" (hash lazy=true)}}
- {{replace-emoji ":peace_symbol:" (hash lazy=true)}}
- {{replace-emoji ":menorah:" (hash lazy=true)}}
- {{replace-emoji ":six_pointed_star:" (hash lazy=true)}}
- {{replace-emoji ":aries:" (hash lazy=true)}}
- {{replace-emoji ":taurus:" (hash lazy=true)}}
- {{replace-emoji ":gemini:" (hash lazy=true)}}
- {{replace-emoji ":cancer:" (hash lazy=true)}}
- {{replace-emoji ":leo:" (hash lazy=true)}}
- {{replace-emoji ":virgo:" (hash lazy=true)}}
- {{replace-emoji ":libra:" (hash lazy=true)}}
- {{replace-emoji ":scorpius:" (hash lazy=true)}}
- {{replace-emoji ":sagittarius:" (hash lazy=true)}}
- {{replace-emoji ":capricorn:" (hash lazy=true)}}
- {{replace-emoji ":aquarius:" (hash lazy=true)}}
- {{replace-emoji ":pisces:" (hash lazy=true)}}
- {{replace-emoji ":ophiuchus:" (hash lazy=true)}}
- {{replace-emoji ":twisted_rightwards_arrows:" (hash lazy=true)}}
- {{replace-emoji ":repeat:" (hash lazy=true)}}
- {{replace-emoji ":repeat_one:" (hash lazy=true)}}
- {{replace-emoji ":arrow_forward:" (hash lazy=true)}}
- {{replace-emoji ":fast_forward:" (hash lazy=true)}}
- {{replace-emoji ":next_track_button:" (hash lazy=true)}}
- {{replace-emoji ":play_or_pause_button:" (hash lazy=true)}}
- {{replace-emoji ":arrow_backward:" (hash lazy=true)}}
- {{replace-emoji ":rewind:" (hash lazy=true)}}
- {{replace-emoji ":previous_track_button:" (hash lazy=true)}}
- {{replace-emoji ":arrow_up_small:" (hash lazy=true)}}
- {{replace-emoji ":arrow_double_up:" (hash lazy=true)}}
- {{replace-emoji ":arrow_down_small:" (hash lazy=true)}}
- {{replace-emoji ":arrow_double_down:" (hash lazy=true)}}
- {{replace-emoji ":pause_button:" (hash lazy=true)}}
- {{replace-emoji ":stop_button:" (hash lazy=true)}}
- {{replace-emoji ":record_button:" (hash lazy=true)}}
- {{replace-emoji ":eject_button:" (hash lazy=true)}}
- {{replace-emoji ":cinema:" (hash lazy=true)}}
- {{replace-emoji ":low_brightness:" (hash lazy=true)}}
- {{replace-emoji ":high_brightness:" (hash lazy=true)}}
- {{replace-emoji ":signal_strength:" (hash lazy=true)}}
- {{replace-emoji ":vibration_mode:" (hash lazy=true)}}
- {{replace-emoji ":mobile_phone_off:" (hash lazy=true)}}
- {{replace-emoji ":female_sign:" (hash lazy=true)}}
- {{replace-emoji ":male_sign:" (hash lazy=true)}}
- {{replace-emoji ":transgender_symbol:" (hash lazy=true)}}
- {{replace-emoji ":heavy_multiplication_x:" (hash lazy=true)}}
- {{replace-emoji ":heavy_plus_sign:" (hash lazy=true)}}
- {{replace-emoji ":heavy_minus_sign:" (hash lazy=true)}}
- {{replace-emoji ":heavy_division_sign:" (hash lazy=true)}}
- {{replace-emoji ":heavy_equals_sign:" (hash lazy=true)}}
- {{replace-emoji ":infinity:" (hash lazy=true)}}
- {{replace-emoji ":bangbang:" (hash lazy=true)}}
- {{replace-emoji ":interrobang:" (hash lazy=true)}}
- {{replace-emoji ":question:" (hash lazy=true)}}
- {{replace-emoji ":grey_question:" (hash lazy=true)}}
- {{replace-emoji ":grey_exclamation:" (hash lazy=true)}}
- {{replace-emoji ":exclamation:" (hash lazy=true)}}
- {{replace-emoji ":wavy_dash:" (hash lazy=true)}}
- {{replace-emoji ":currency_exchange:" (hash lazy=true)}}
- {{replace-emoji ":heavy_dollar_sign:" (hash lazy=true)}}
- {{replace-emoji ":medical_symbol:" (hash lazy=true)}}
- {{replace-emoji ":recycle:" (hash lazy=true)}}
- {{replace-emoji ":fleur_de_lis:" (hash lazy=true)}}
- {{replace-emoji ":trident:" (hash lazy=true)}}
- {{replace-emoji ":name_badge:" (hash lazy=true)}}
- {{replace-emoji ":beginner:" (hash lazy=true)}}
- {{replace-emoji ":o:" (hash lazy=true)}}
- {{replace-emoji ":white_check_mark:" (hash lazy=true)}}
- {{replace-emoji ":ballot_box_with_check:" (hash lazy=true)}}
- {{replace-emoji ":heavy_check_mark:" (hash lazy=true)}}
- {{replace-emoji ":x:" (hash lazy=true)}}
- {{replace-emoji ":negative_squared_cross_mark:" (hash lazy=true)}}
- {{replace-emoji ":curly_loop:" (hash lazy=true)}}
- {{replace-emoji ":loop:" (hash lazy=true)}}
- {{replace-emoji ":part_alternation_mark:" (hash lazy=true)}}
- {{replace-emoji ":eight_spoked_asterisk:" (hash lazy=true)}}
- {{replace-emoji ":eight_pointed_black_star:" (hash lazy=true)}}
- {{replace-emoji ":sparkle:" (hash lazy=true)}}
- {{replace-emoji ":copyright:" (hash lazy=true)}}
- {{replace-emoji ":registered:" (hash lazy=true)}}
- {{replace-emoji ":tm:" (hash lazy=true)}}
- {{replace-emoji ":hash:" (hash lazy=true)}}
- {{replace-emoji ":asterisk:" (hash lazy=true)}}
- {{replace-emoji ":zero:" (hash lazy=true)}}
- {{replace-emoji ":one:" (hash lazy=true)}}
- {{replace-emoji ":two:" (hash lazy=true)}}
- {{replace-emoji ":three:" (hash lazy=true)}}
- {{replace-emoji ":four:" (hash lazy=true)}}
- {{replace-emoji ":five:" (hash lazy=true)}}
- {{replace-emoji ":six:" (hash lazy=true)}}
- {{replace-emoji ":seven:" (hash lazy=true)}}
- {{replace-emoji ":eight:" (hash lazy=true)}}
- {{replace-emoji ":nine:" (hash lazy=true)}}
- {{replace-emoji ":keycap_ten:" (hash lazy=true)}}
- {{replace-emoji ":capital_abcd:" (hash lazy=true)}}
- {{replace-emoji ":abcd:" (hash lazy=true)}}
- {{replace-emoji ":1234:" (hash lazy=true)}}
- {{replace-emoji ":symbols:" (hash lazy=true)}}
- {{replace-emoji ":abc:" (hash lazy=true)}}
- {{replace-emoji ":a:" (hash lazy=true)}}
- {{replace-emoji ":ab:" (hash lazy=true)}}
- {{replace-emoji ":b:" (hash lazy=true)}}
- {{replace-emoji ":cl:" (hash lazy=true)}}
- {{replace-emoji ":cool:" (hash lazy=true)}}
- {{replace-emoji ":free:" (hash lazy=true)}}
- {{replace-emoji ":information_source:" (hash lazy=true)}}
- {{replace-emoji ":id:" (hash lazy=true)}}
- {{replace-emoji ":m:" (hash lazy=true)}}
- {{replace-emoji ":new:" (hash lazy=true)}}
- {{replace-emoji ":ng:" (hash lazy=true)}}
- {{replace-emoji ":o2:" (hash lazy=true)}}
- {{replace-emoji ":ok:" (hash lazy=true)}}
- {{replace-emoji ":parking:" (hash lazy=true)}}
- {{replace-emoji ":sos:" (hash lazy=true)}}
- {{replace-emoji ":up:" (hash lazy=true)}}
- {{replace-emoji ":vs:" (hash lazy=true)}}
- {{replace-emoji ":koko:" (hash lazy=true)}}
- {{replace-emoji ":sa:" (hash lazy=true)}}
- {{replace-emoji ":u6708:" (hash lazy=true)}}
- {{replace-emoji ":u6709:" (hash lazy=true)}}
- {{replace-emoji ":u6307:" (hash lazy=true)}}
- {{replace-emoji ":ideograph_advantage:" (hash lazy=true)}}
- {{replace-emoji ":u5272:" (hash lazy=true)}}
- {{replace-emoji ":u7121:" (hash lazy=true)}}
- {{replace-emoji ":u7981:" (hash lazy=true)}}
- {{replace-emoji ":accept:" (hash lazy=true)}}
- {{replace-emoji ":u7533:" (hash lazy=true)}}
- {{replace-emoji ":u5408:" (hash lazy=true)}}
- {{replace-emoji ":u7a7a:" (hash lazy=true)}}
- {{replace-emoji ":congratulations:" (hash lazy=true)}}
- {{replace-emoji ":secret:" (hash lazy=true)}}
- {{replace-emoji ":u55b6:" (hash lazy=true)}}
- {{replace-emoji ":u6e80:" (hash lazy=true)}}
- {{replace-emoji ":red_circle:" (hash lazy=true)}}
- {{replace-emoji ":orange_circle:" (hash lazy=true)}}
- {{replace-emoji ":yellow_circle:" (hash lazy=true)}}
- {{replace-emoji ":green_circle:" (hash lazy=true)}}
- {{replace-emoji ":large_blue_circle:" (hash lazy=true)}}
- {{replace-emoji ":purple_circle:" (hash lazy=true)}}
- {{replace-emoji ":brown_circle:" (hash lazy=true)}}
- {{replace-emoji ":black_circle:" (hash lazy=true)}}
- {{replace-emoji ":white_circle:" (hash lazy=true)}}
- {{replace-emoji ":red_square:" (hash lazy=true)}}
- {{replace-emoji ":orange_square:" (hash lazy=true)}}
- {{replace-emoji ":yellow_square:" (hash lazy=true)}}
- {{replace-emoji ":green_square:" (hash lazy=true)}}
- {{replace-emoji ":blue_square:" (hash lazy=true)}}
- {{replace-emoji ":purple_square:" (hash lazy=true)}}
- {{replace-emoji ":brown_square:" (hash lazy=true)}}
- {{replace-emoji ":black_large_square:" (hash lazy=true)}}
- {{replace-emoji ":white_large_square:" (hash lazy=true)}}
- {{replace-emoji ":black_medium_square:" (hash lazy=true)}}
- {{replace-emoji ":white_medium_square:" (hash lazy=true)}}
- {{replace-emoji ":black_medium_small_square:" (hash lazy=true)}}
- {{replace-emoji ":white_medium_small_square:" (hash lazy=true)}}
- {{replace-emoji ":black_small_square:" (hash lazy=true)}}
- {{replace-emoji ":white_small_square:" (hash lazy=true)}}
- {{replace-emoji ":large_orange_diamond:" (hash lazy=true)}}
- {{replace-emoji ":large_blue_diamond:" (hash lazy=true)}}
- {{replace-emoji ":small_orange_diamond:" (hash lazy=true)}}
- {{replace-emoji ":small_blue_diamond:" (hash lazy=true)}}
- {{replace-emoji ":small_red_triangle:" (hash lazy=true)}}
- {{replace-emoji ":small_red_triangle_down:" (hash lazy=true)}}
- {{replace-emoji ":diamond_shape_with_a_dot_inside:" (hash lazy=true)}}
- {{replace-emoji ":radio_button:" (hash lazy=true)}}
- {{replace-emoji ":white_square_button:" (hash lazy=true)}}
- {{replace-emoji ":black_square_button:" (hash lazy=true)}}
+ {{replace-emoji ":atm:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":put_litter_in_its_place:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":potable_water:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":wheelchair:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mens:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":womens:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":restroom:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":baby_symbol:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":wc:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":passport_control:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":customs:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":baggage_claim:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":left_luggage:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":warning:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":children_crossing:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":no_entry:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":no_entry_sign:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":no_bicycles:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":no_smoking:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":do_not_litter:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":non-potable_water:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":no_pedestrians:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":no_mobile_phones:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":underage:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":radioactive:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":biohazard:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrow_up:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrow_upper_right:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrow_right:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrow_lower_right:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrow_down:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrow_lower_left:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrow_left:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrow_upper_left:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrow_up_down:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":left_right_arrow:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":leftwards_arrow_with_hook:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrow_right_hook:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrow_heading_up:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrow_heading_down:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrows_clockwise:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrows_counterclockwise:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":back:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":end:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":on:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":soon:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":top:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":place_of_worship:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":atom_symbol:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":om:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":star_of_david:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":wheel_of_dharma:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":yin_yang:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":latin_cross:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":orthodox_cross:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":star_and_crescent:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":peace_symbol:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":menorah:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":six_pointed_star:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":aries:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":taurus:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":gemini:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cancer:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":leo:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":virgo:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":libra:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":scorpius:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sagittarius:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":capricorn:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":aquarius:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":pisces:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ophiuchus:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":twisted_rightwards_arrows:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":repeat:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":repeat_one:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrow_forward:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":fast_forward:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":next_track_button:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":play_or_pause_button:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrow_backward:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":rewind:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":previous_track_button:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrow_up_small:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrow_double_up:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrow_down_small:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":arrow_double_down:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":pause_button:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":stop_button:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":record_button:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":eject_button:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cinema:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":low_brightness:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":high_brightness:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":signal_strength:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":vibration_mode:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mobile_phone_off:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":female_sign:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":male_sign:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":transgender_symbol:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":heavy_multiplication_x:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":heavy_plus_sign:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":heavy_minus_sign:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":heavy_division_sign:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":heavy_equals_sign:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":infinity:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bangbang:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":interrobang:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":question:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":grey_question:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":grey_exclamation:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":exclamation:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":wavy_dash:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":currency_exchange:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":heavy_dollar_sign:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":medical_symbol:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":recycle:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":fleur_de_lis:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":trident:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":name_badge:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":beginner:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":o:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":white_check_mark:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ballot_box_with_check:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":heavy_check_mark:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":x:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":negative_squared_cross_mark:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":curly_loop:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":loop:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":part_alternation_mark:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":eight_spoked_asterisk:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":eight_pointed_black_star:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sparkle:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":copyright:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":registered:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tm:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":hash:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":asterisk:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":zero:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":one:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":two:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":three:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":four:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":five:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":six:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":seven:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":eight:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":nine:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":keycap_ten:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":capital_abcd:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":abcd:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":1234:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":symbols:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":abc:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":a:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ab:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":b:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cl:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cool:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":free:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":information_source:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":id:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":m:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":new:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ng:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":o2:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ok:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":parking:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sos:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":up:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":vs:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":koko:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sa:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":u6708:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":u6709:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":u6307:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ideograph_advantage:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":u5272:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":u7121:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":u7981:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":accept:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":u7533:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":u5408:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":u7a7a:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":congratulations:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":secret:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":u55b6:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":u6e80:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":red_circle:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":orange_circle:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":yellow_circle:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":green_circle:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":large_blue_circle:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":purple_circle:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":brown_circle:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":black_circle:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":white_circle:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":red_square:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":orange_square:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":yellow_square:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":green_square:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":blue_square:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":purple_square:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":brown_square:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":black_large_square:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":white_large_square:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":black_medium_square:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":white_medium_square:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":black_medium_small_square:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":white_medium_small_square:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":black_small_square:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":white_small_square:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":large_orange_diamond:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":large_blue_diamond:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":small_orange_diamond:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":small_blue_diamond:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":small_red_triangle:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":small_red_triangle_down:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":diamond_shape_with_a_dot_inside:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":radio_button:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":white_square_button:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":black_square_button:" (hash lazy=true tabIndex="0")}}
@@ -1642,274 +1642,274 @@
{{i18n "emoji_picker.flags"}}
- {{replace-emoji ":checkered_flag:" (hash lazy=true)}}
- {{replace-emoji ":triangular_flag_on_post:" (hash lazy=true)}}
- {{replace-emoji ":crossed_flags:" (hash lazy=true)}}
- {{replace-emoji ":black_flag:" (hash lazy=true)}}
- {{replace-emoji ":white_flag:" (hash lazy=true)}}
- {{replace-emoji ":rainbow_flag:" (hash lazy=true)}}
- {{replace-emoji ":transgender_flag:" (hash lazy=true)}}
- {{replace-emoji ":pirate_flag:" (hash lazy=true)}}
- {{replace-emoji ":ascension_island:" (hash lazy=true)}}
- {{replace-emoji ":andorra:" (hash lazy=true)}}
- {{replace-emoji ":united_arab_emirates:" (hash lazy=true)}}
- {{replace-emoji ":afghanistan:" (hash lazy=true)}}
- {{replace-emoji ":antigua_barbuda:" (hash lazy=true)}}
- {{replace-emoji ":anguilla:" (hash lazy=true)}}
- {{replace-emoji ":albania:" (hash lazy=true)}}
- {{replace-emoji ":armenia:" (hash lazy=true)}}
- {{replace-emoji ":angola:" (hash lazy=true)}}
- {{replace-emoji ":antarctica:" (hash lazy=true)}}
- {{replace-emoji ":argentina:" (hash lazy=true)}}
- {{replace-emoji ":american_samoa:" (hash lazy=true)}}
- {{replace-emoji ":austria:" (hash lazy=true)}}
- {{replace-emoji ":australia:" (hash lazy=true)}}
- {{replace-emoji ":aruba:" (hash lazy=true)}}
- {{replace-emoji ":aland_islands:" (hash lazy=true)}}
- {{replace-emoji ":azerbaijan:" (hash lazy=true)}}
- {{replace-emoji ":bosnia_herzegovina:" (hash lazy=true)}}
- {{replace-emoji ":barbados:" (hash lazy=true)}}
- {{replace-emoji ":bangladesh:" (hash lazy=true)}}
- {{replace-emoji ":belgium:" (hash lazy=true)}}
- {{replace-emoji ":burkina_faso:" (hash lazy=true)}}
- {{replace-emoji ":bulgaria:" (hash lazy=true)}}
- {{replace-emoji ":bahrain:" (hash lazy=true)}}
- {{replace-emoji ":burundi:" (hash lazy=true)}}
- {{replace-emoji ":benin:" (hash lazy=true)}}
- {{replace-emoji ":st_barthelemy:" (hash lazy=true)}}
- {{replace-emoji ":bermuda:" (hash lazy=true)}}
- {{replace-emoji ":brunei:" (hash lazy=true)}}
- {{replace-emoji ":bolivia:" (hash lazy=true)}}
- {{replace-emoji ":caribbean_netherlands:" (hash lazy=true)}}
- {{replace-emoji ":brazil:" (hash lazy=true)}}
- {{replace-emoji ":bahamas:" (hash lazy=true)}}
- {{replace-emoji ":bhutan:" (hash lazy=true)}}
- {{replace-emoji ":bouvet_island:" (hash lazy=true)}}
- {{replace-emoji ":botswana:" (hash lazy=true)}}
- {{replace-emoji ":belarus:" (hash lazy=true)}}
- {{replace-emoji ":belize:" (hash lazy=true)}}
- {{replace-emoji ":canada:" (hash lazy=true)}}
- {{replace-emoji ":cocos_islands:" (hash lazy=true)}}
- {{replace-emoji ":congo_kinshasa:" (hash lazy=true)}}
- {{replace-emoji ":central_african_republic:" (hash lazy=true)}}
- {{replace-emoji ":congo_brazzaville:" (hash lazy=true)}}
- {{replace-emoji ":switzerland:" (hash lazy=true)}}
- {{replace-emoji ":cote_divoire:" (hash lazy=true)}}
- {{replace-emoji ":cook_islands:" (hash lazy=true)}}
- {{replace-emoji ":chile:" (hash lazy=true)}}
- {{replace-emoji ":cameroon:" (hash lazy=true)}}
- {{replace-emoji ":cn:" (hash lazy=true)}}
- {{replace-emoji ":colombia:" (hash lazy=true)}}
- {{replace-emoji ":clipperton_island:" (hash lazy=true)}}
- {{replace-emoji ":costa_rica:" (hash lazy=true)}}
- {{replace-emoji ":cuba:" (hash lazy=true)}}
- {{replace-emoji ":cape_verde:" (hash lazy=true)}}
- {{replace-emoji ":curacao:" (hash lazy=true)}}
- {{replace-emoji ":christmas_island:" (hash lazy=true)}}
- {{replace-emoji ":cyprus:" (hash lazy=true)}}
- {{replace-emoji ":czech_republic:" (hash lazy=true)}}
- {{replace-emoji ":de:" (hash lazy=true)}}
- {{replace-emoji ":diego_garcia:" (hash lazy=true)}}
- {{replace-emoji ":djibouti:" (hash lazy=true)}}
- {{replace-emoji ":denmark:" (hash lazy=true)}}
- {{replace-emoji ":dominica:" (hash lazy=true)}}
- {{replace-emoji ":dominican_republic:" (hash lazy=true)}}
- {{replace-emoji ":algeria:" (hash lazy=true)}}
- {{replace-emoji ":ceuta_and_melilla:" (hash lazy=true)}}
- {{replace-emoji ":ecuador:" (hash lazy=true)}}
- {{replace-emoji ":estonia:" (hash lazy=true)}}
- {{replace-emoji ":egypt:" (hash lazy=true)}}
- {{replace-emoji ":western_sahara:" (hash lazy=true)}}
- {{replace-emoji ":eritrea:" (hash lazy=true)}}
- {{replace-emoji ":es:" (hash lazy=true)}}
- {{replace-emoji ":ethiopia:" (hash lazy=true)}}
- {{replace-emoji ":eu:" (hash lazy=true)}}
- {{replace-emoji ":finland:" (hash lazy=true)}}
- {{replace-emoji ":fiji:" (hash lazy=true)}}
- {{replace-emoji ":falkland_islands:" (hash lazy=true)}}
- {{replace-emoji ":micronesia:" (hash lazy=true)}}
- {{replace-emoji ":faroe_islands:" (hash lazy=true)}}
- {{replace-emoji ":fr:" (hash lazy=true)}}
- {{replace-emoji ":gabon:" (hash lazy=true)}}
- {{replace-emoji ":uk:" (hash lazy=true)}}
- {{replace-emoji ":grenada:" (hash lazy=true)}}
- {{replace-emoji ":georgia:" (hash lazy=true)}}
- {{replace-emoji ":french_guiana:" (hash lazy=true)}}
- {{replace-emoji ":guernsey:" (hash lazy=true)}}
- {{replace-emoji ":ghana:" (hash lazy=true)}}
- {{replace-emoji ":gibraltar:" (hash lazy=true)}}
- {{replace-emoji ":greenland:" (hash lazy=true)}}
- {{replace-emoji ":gambia:" (hash lazy=true)}}
- {{replace-emoji ":guinea:" (hash lazy=true)}}
- {{replace-emoji ":guadeloupe:" (hash lazy=true)}}
- {{replace-emoji ":equatorial_guinea:" (hash lazy=true)}}
- {{replace-emoji ":greece:" (hash lazy=true)}}
- {{replace-emoji ":south_georgia_south_sandwich_islands:" (hash lazy=true)}}
- {{replace-emoji ":guatemala:" (hash lazy=true)}}
- {{replace-emoji ":guam:" (hash lazy=true)}}
- {{replace-emoji ":guinea_bissau:" (hash lazy=true)}}
- {{replace-emoji ":guyana:" (hash lazy=true)}}
- {{replace-emoji ":hong_kong:" (hash lazy=true)}}
- {{replace-emoji ":heard_and_mc_donald_islands:" (hash lazy=true)}}
- {{replace-emoji ":honduras:" (hash lazy=true)}}
- {{replace-emoji ":croatia:" (hash lazy=true)}}
- {{replace-emoji ":haiti:" (hash lazy=true)}}
- {{replace-emoji ":hungary:" (hash lazy=true)}}
- {{replace-emoji ":canary_islands:" (hash lazy=true)}}
- {{replace-emoji ":indonesia:" (hash lazy=true)}}
- {{replace-emoji ":ireland:" (hash lazy=true)}}
- {{replace-emoji ":israel:" (hash lazy=true)}}
- {{replace-emoji ":isle_of_man:" (hash lazy=true)}}
- {{replace-emoji ":india:" (hash lazy=true)}}
- {{replace-emoji ":british_indian_ocean_territory:" (hash lazy=true)}}
- {{replace-emoji ":iraq:" (hash lazy=true)}}
- {{replace-emoji ":iran:" (hash lazy=true)}}
- {{replace-emoji ":iceland:" (hash lazy=true)}}
- {{replace-emoji ":it:" (hash lazy=true)}}
- {{replace-emoji ":jersey:" (hash lazy=true)}}
- {{replace-emoji ":jamaica:" (hash lazy=true)}}
- {{replace-emoji ":jordan:" (hash lazy=true)}}
- {{replace-emoji ":jp:" (hash lazy=true)}}
- {{replace-emoji ":kenya:" (hash lazy=true)}}
- {{replace-emoji ":kyrgyzstan:" (hash lazy=true)}}
- {{replace-emoji ":cambodia:" (hash lazy=true)}}
- {{replace-emoji ":kiribati:" (hash lazy=true)}}
- {{replace-emoji ":comoros:" (hash lazy=true)}}
- {{replace-emoji ":st_kitts_nevis:" (hash lazy=true)}}
- {{replace-emoji ":north_korea:" (hash lazy=true)}}
- {{replace-emoji ":kr:" (hash lazy=true)}}
- {{replace-emoji ":kuwait:" (hash lazy=true)}}
- {{replace-emoji ":cayman_islands:" (hash lazy=true)}}
- {{replace-emoji ":kazakhstan:" (hash lazy=true)}}
- {{replace-emoji ":laos:" (hash lazy=true)}}
- {{replace-emoji ":lebanon:" (hash lazy=true)}}
- {{replace-emoji ":st_lucia:" (hash lazy=true)}}
- {{replace-emoji ":liechtenstein:" (hash lazy=true)}}
- {{replace-emoji ":sri_lanka:" (hash lazy=true)}}
- {{replace-emoji ":liberia:" (hash lazy=true)}}
- {{replace-emoji ":lesotho:" (hash lazy=true)}}
- {{replace-emoji ":lithuania:" (hash lazy=true)}}
- {{replace-emoji ":luxembourg:" (hash lazy=true)}}
- {{replace-emoji ":latvia:" (hash lazy=true)}}
- {{replace-emoji ":libya:" (hash lazy=true)}}
- {{replace-emoji ":morocco:" (hash lazy=true)}}
- {{replace-emoji ":monaco:" (hash lazy=true)}}
- {{replace-emoji ":moldova:" (hash lazy=true)}}
- {{replace-emoji ":montenegro:" (hash lazy=true)}}
- {{replace-emoji ":st_martin:" (hash lazy=true)}}
- {{replace-emoji ":madagascar:" (hash lazy=true)}}
- {{replace-emoji ":marshall_islands:" (hash lazy=true)}}
- {{replace-emoji ":macedonia:" (hash lazy=true)}}
- {{replace-emoji ":mali:" (hash lazy=true)}}
- {{replace-emoji ":myanmar:" (hash lazy=true)}}
- {{replace-emoji ":mongolia:" (hash lazy=true)}}
- {{replace-emoji ":macau:" (hash lazy=true)}}
- {{replace-emoji ":northern_mariana_islands:" (hash lazy=true)}}
- {{replace-emoji ":martinique:" (hash lazy=true)}}
- {{replace-emoji ":mauritania:" (hash lazy=true)}}
- {{replace-emoji ":montserrat:" (hash lazy=true)}}
- {{replace-emoji ":malta:" (hash lazy=true)}}
- {{replace-emoji ":mauritius:" (hash lazy=true)}}
- {{replace-emoji ":maldives:" (hash lazy=true)}}
- {{replace-emoji ":malawi:" (hash lazy=true)}}
- {{replace-emoji ":mexico:" (hash lazy=true)}}
- {{replace-emoji ":malaysia:" (hash lazy=true)}}
- {{replace-emoji ":mozambique:" (hash lazy=true)}}
- {{replace-emoji ":namibia:" (hash lazy=true)}}
- {{replace-emoji ":new_caledonia:" (hash lazy=true)}}
- {{replace-emoji ":niger:" (hash lazy=true)}}
- {{replace-emoji ":norfolk_island:" (hash lazy=true)}}
- {{replace-emoji ":nigeria:" (hash lazy=true)}}
- {{replace-emoji ":nicaragua:" (hash lazy=true)}}
- {{replace-emoji ":netherlands:" (hash lazy=true)}}
- {{replace-emoji ":norway:" (hash lazy=true)}}
- {{replace-emoji ":nepal:" (hash lazy=true)}}
- {{replace-emoji ":nauru:" (hash lazy=true)}}
- {{replace-emoji ":niue:" (hash lazy=true)}}
- {{replace-emoji ":new_zealand:" (hash lazy=true)}}
- {{replace-emoji ":oman:" (hash lazy=true)}}
- {{replace-emoji ":panama:" (hash lazy=true)}}
- {{replace-emoji ":peru:" (hash lazy=true)}}
- {{replace-emoji ":french_polynesia:" (hash lazy=true)}}
- {{replace-emoji ":papua_new_guinea:" (hash lazy=true)}}
- {{replace-emoji ":philippines:" (hash lazy=true)}}
- {{replace-emoji ":pakistan:" (hash lazy=true)}}
- {{replace-emoji ":poland:" (hash lazy=true)}}
- {{replace-emoji ":st_pierre_miquelon:" (hash lazy=true)}}
- {{replace-emoji ":pitcairn_islands:" (hash lazy=true)}}
- {{replace-emoji ":puerto_rico:" (hash lazy=true)}}
- {{replace-emoji ":palestinian_territories:" (hash lazy=true)}}
- {{replace-emoji ":portugal:" (hash lazy=true)}}
- {{replace-emoji ":palau:" (hash lazy=true)}}
- {{replace-emoji ":paraguay:" (hash lazy=true)}}
- {{replace-emoji ":qatar:" (hash lazy=true)}}
- {{replace-emoji ":reunion:" (hash lazy=true)}}
- {{replace-emoji ":romania:" (hash lazy=true)}}
- {{replace-emoji ":serbia:" (hash lazy=true)}}
- {{replace-emoji ":ru:" (hash lazy=true)}}
- {{replace-emoji ":rwanda:" (hash lazy=true)}}
- {{replace-emoji ":saudi_arabia:" (hash lazy=true)}}
- {{replace-emoji ":solomon_islands:" (hash lazy=true)}}
- {{replace-emoji ":seychelles:" (hash lazy=true)}}
- {{replace-emoji ":sudan:" (hash lazy=true)}}
- {{replace-emoji ":sweden:" (hash lazy=true)}}
- {{replace-emoji ":singapore:" (hash lazy=true)}}
- {{replace-emoji ":st_helena:" (hash lazy=true)}}
- {{replace-emoji ":slovenia:" (hash lazy=true)}}
- {{replace-emoji ":svalbard_and_jan_mayen:" (hash lazy=true)}}
- {{replace-emoji ":slovakia:" (hash lazy=true)}}
- {{replace-emoji ":sierra_leone:" (hash lazy=true)}}
- {{replace-emoji ":san_marino:" (hash lazy=true)}}
- {{replace-emoji ":senegal:" (hash lazy=true)}}
- {{replace-emoji ":somalia:" (hash lazy=true)}}
- {{replace-emoji ":suriname:" (hash lazy=true)}}
- {{replace-emoji ":south_sudan:" (hash lazy=true)}}
- {{replace-emoji ":sao_tome_principe:" (hash lazy=true)}}
- {{replace-emoji ":el_salvador:" (hash lazy=true)}}
- {{replace-emoji ":sint_maarten:" (hash lazy=true)}}
- {{replace-emoji ":syria:" (hash lazy=true)}}
- {{replace-emoji ":swaziland:" (hash lazy=true)}}
- {{replace-emoji ":tristan_da_cunha:" (hash lazy=true)}}
- {{replace-emoji ":turks_caicos_islands:" (hash lazy=true)}}
- {{replace-emoji ":chad:" (hash lazy=true)}}
- {{replace-emoji ":french_southern_territories:" (hash lazy=true)}}
- {{replace-emoji ":togo:" (hash lazy=true)}}
- {{replace-emoji ":thailand:" (hash lazy=true)}}
- {{replace-emoji ":tajikistan:" (hash lazy=true)}}
- {{replace-emoji ":tokelau:" (hash lazy=true)}}
- {{replace-emoji ":timor_leste:" (hash lazy=true)}}
- {{replace-emoji ":turkmenistan:" (hash lazy=true)}}
- {{replace-emoji ":tunisia:" (hash lazy=true)}}
- {{replace-emoji ":tonga:" (hash lazy=true)}}
- {{replace-emoji ":tr:" (hash lazy=true)}}
- {{replace-emoji ":trinidad_tobago:" (hash lazy=true)}}
- {{replace-emoji ":tuvalu:" (hash lazy=true)}}
- {{replace-emoji ":taiwan:" (hash lazy=true)}}
- {{replace-emoji ":tanzania:" (hash lazy=true)}}
- {{replace-emoji ":ukraine:" (hash lazy=true)}}
- {{replace-emoji ":uganda:" (hash lazy=true)}}
- {{replace-emoji ":us_outlying_islands:" (hash lazy=true)}}
- {{replace-emoji ":united_nations:" (hash lazy=true)}}
- {{replace-emoji ":us:" (hash lazy=true)}}
- {{replace-emoji ":uruguay:" (hash lazy=true)}}
- {{replace-emoji ":uzbekistan:" (hash lazy=true)}}
- {{replace-emoji ":vatican_city:" (hash lazy=true)}}
- {{replace-emoji ":st_vincent_grenadines:" (hash lazy=true)}}
- {{replace-emoji ":venezuela:" (hash lazy=true)}}
- {{replace-emoji ":british_virgin_islands:" (hash lazy=true)}}
- {{replace-emoji ":us_virgin_islands:" (hash lazy=true)}}
- {{replace-emoji ":vietnam:" (hash lazy=true)}}
- {{replace-emoji ":vanuatu:" (hash lazy=true)}}
- {{replace-emoji ":wallis_futuna:" (hash lazy=true)}}
- {{replace-emoji ":samoa:" (hash lazy=true)}}
- {{replace-emoji ":kosovo:" (hash lazy=true)}}
- {{replace-emoji ":yemen:" (hash lazy=true)}}
- {{replace-emoji ":mayotte:" (hash lazy=true)}}
- {{replace-emoji ":south_africa:" (hash lazy=true)}}
- {{replace-emoji ":zambia:" (hash lazy=true)}}
- {{replace-emoji ":zimbabwe:" (hash lazy=true)}}
- {{replace-emoji ":england:" (hash lazy=true)}}
- {{replace-emoji ":scotland:" (hash lazy=true)}}
- {{replace-emoji ":wales:" (hash lazy=true)}}
+ {{replace-emoji ":checkered_flag:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":triangular_flag_on_post:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":crossed_flags:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":black_flag:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":white_flag:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":rainbow_flag:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":transgender_flag:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":pirate_flag:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ascension_island:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":andorra:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":united_arab_emirates:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":afghanistan:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":antigua_barbuda:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":anguilla:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":albania:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":armenia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":angola:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":antarctica:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":argentina:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":american_samoa:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":austria:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":australia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":aruba:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":aland_islands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":azerbaijan:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bosnia_herzegovina:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":barbados:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bangladesh:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":belgium:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":burkina_faso:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bulgaria:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bahrain:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":burundi:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":benin:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":st_barthelemy:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bermuda:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":brunei:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bolivia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":caribbean_netherlands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":brazil:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bahamas:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bhutan:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":bouvet_island:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":botswana:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":belarus:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":belize:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":canada:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cocos_islands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":congo_kinshasa:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":central_african_republic:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":congo_brazzaville:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":switzerland:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cote_divoire:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cook_islands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":chile:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cameroon:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cn:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":colombia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":clipperton_island:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":costa_rica:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cuba:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cape_verde:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":curacao:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":christmas_island:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cyprus:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":czech_republic:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":de:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":diego_garcia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":djibouti:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":denmark:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":dominica:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":dominican_republic:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":algeria:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ceuta_and_melilla:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ecuador:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":estonia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":egypt:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":western_sahara:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":eritrea:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":es:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ethiopia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":eu:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":finland:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":fiji:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":falkland_islands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":micronesia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":faroe_islands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":fr:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":gabon:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":uk:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":grenada:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":georgia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":french_guiana:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":guernsey:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ghana:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":gibraltar:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":greenland:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":gambia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":guinea:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":guadeloupe:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":equatorial_guinea:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":greece:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":south_georgia_south_sandwich_islands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":guatemala:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":guam:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":guinea_bissau:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":guyana:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":hong_kong:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":heard_and_mc_donald_islands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":honduras:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":croatia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":haiti:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":hungary:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":canary_islands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":indonesia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ireland:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":israel:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":isle_of_man:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":india:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":british_indian_ocean_territory:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":iraq:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":iran:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":iceland:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":it:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":jersey:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":jamaica:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":jordan:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":jp:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":kenya:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":kyrgyzstan:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cambodia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":kiribati:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":comoros:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":st_kitts_nevis:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":north_korea:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":kr:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":kuwait:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":cayman_islands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":kazakhstan:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":laos:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":lebanon:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":st_lucia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":liechtenstein:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sri_lanka:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":liberia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":lesotho:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":lithuania:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":luxembourg:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":latvia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":libya:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":morocco:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":monaco:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":moldova:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":montenegro:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":st_martin:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":madagascar:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":marshall_islands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":macedonia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mali:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":myanmar:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mongolia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":macau:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":northern_mariana_islands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":martinique:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mauritania:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":montserrat:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":malta:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mauritius:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":maldives:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":malawi:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mexico:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":malaysia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mozambique:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":namibia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":new_caledonia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":niger:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":norfolk_island:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":nigeria:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":nicaragua:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":netherlands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":norway:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":nepal:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":nauru:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":niue:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":new_zealand:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":oman:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":panama:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":peru:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":french_polynesia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":papua_new_guinea:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":philippines:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":pakistan:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":poland:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":st_pierre_miquelon:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":pitcairn_islands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":puerto_rico:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":palestinian_territories:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":portugal:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":palau:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":paraguay:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":qatar:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":reunion:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":romania:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":serbia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ru:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":rwanda:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":saudi_arabia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":solomon_islands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":seychelles:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sudan:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sweden:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":singapore:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":st_helena:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":slovenia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":svalbard_and_jan_mayen:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":slovakia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sierra_leone:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":san_marino:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":senegal:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":somalia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":suriname:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":south_sudan:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sao_tome_principe:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":el_salvador:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":sint_maarten:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":syria:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":swaziland:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tristan_da_cunha:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":turks_caicos_islands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":chad:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":french_southern_territories:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":togo:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":thailand:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tajikistan:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tokelau:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":timor_leste:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":turkmenistan:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tunisia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tonga:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tr:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":trinidad_tobago:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tuvalu:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":taiwan:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":tanzania:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":ukraine:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":uganda:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":us_outlying_islands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":united_nations:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":us:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":uruguay:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":uzbekistan:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":vatican_city:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":st_vincent_grenadines:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":venezuela:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":british_virgin_islands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":us_virgin_islands:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":vietnam:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":vanuatu:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":wallis_futuna:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":samoa:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":kosovo:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":yemen:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":mayotte:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":south_africa:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":zambia:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":zimbabwe:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":england:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":scotland:" (hash lazy=true tabIndex="0")}}
+ {{replace-emoji ":wales:" (hash lazy=true tabIndex="0")}}
diff --git a/app/assets/javascripts/discourse/app/templates/components/emoji-picker.hbs b/app/assets/javascripts/discourse/app/templates/components/emoji-picker.hbs
index d9c2156911..d35c0e5e2f 100644
--- a/app/assets/javascripts/discourse/app/templates/components/emoji-picker.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/emoji-picker.hbs
@@ -37,7 +37,7 @@
{{#each this.recentEmojis as |emoji|}}
- {{replace-emoji (concat ":" emoji ":") (hash lazy=true)}}
+ {{replace-emoji (concat ":" emoji ":") (hash lazy=true tabIndex="0" class="recent-emoji")}}
{{/each}}
@@ -55,7 +55,9 @@
{{#if emojis.length}}
{{#each emojis as |emoji|}}
- 
+
+
+
{{/each}}
{{/if}}
diff --git a/app/assets/javascripts/discourse/app/templates/components/expand-post.hbs b/app/assets/javascripts/discourse/app/templates/components/expand-post.hbs
index 79b9709fb0..119ffede11 100644
--- a/app/assets/javascripts/discourse/app/templates/components/expand-post.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/expand-post.hbs
@@ -1,11 +1,9 @@
{{#if this.item.truncated}}
- {{#if this.expanded}}
-
+
+ {{#if this.expanded}}
{{d-icon "chevron-up"}}
-
- {{else}}
-
+ {{else}}
{{d-icon "chevron-down"}}
-
- {{/if}}
+ {{/if}}
+
{{/if}}
diff --git a/app/assets/javascripts/discourse/app/templates/components/group-post.hbs b/app/assets/javascripts/discourse/app/templates/components/group-post.hbs
index 3a83f4b677..a442d7f077 100644
--- a/app/assets/javascripts/discourse/app/templates/components/group-post.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/group-post.hbs
@@ -1,11 +1,9 @@
- |
+{{~raw-plugin-outlet name="topic-list-after-main-link"}}
+
{{#if showPosters}}
{{raw "list/posters-column" posters=topic.featuredUsers}}
{{/if}}
diff --git a/app/assets/javascripts/discourse/app/templates/modal/flag.hbs b/app/assets/javascripts/discourse/app/templates/modal/flag.hbs
index 863c482895..a890ce311e 100644
--- a/app/assets/javascripts/discourse/app/templates/modal/flag.hbs
+++ b/app/assets/javascripts/discourse/app/templates/modal/flag.hbs
@@ -18,7 +18,7 @@
{{#if this.canTakeAction}}
-
+
{{/if}}
{{#if this.showDeleteSpammer}}
diff --git a/app/assets/javascripts/discourse/app/templates/preferences/notifications.hbs b/app/assets/javascripts/discourse/app/templates/preferences/notifications.hbs
index 3d1f882f90..6f142e25a1 100644
--- a/app/assets/javascripts/discourse/app/templates/preferences/notifications.hbs
+++ b/app/assets/javascripts/discourse/app/templates/preferences/notifications.hbs
@@ -33,7 +33,7 @@
-{{#if this.siteSettings.enable_personal_messages}}
+{{#if this.showMessageSettings}}
diff --git a/app/assets/javascripts/discourse/app/templates/preferences/users.hbs b/app/assets/javascripts/discourse/app/templates/preferences/users.hbs
index ebf6b46a74..5589f14d47 100644
--- a/app/assets/javascripts/discourse/app/templates/preferences/users.hbs
+++ b/app/assets/javascripts/discourse/app/templates/preferences/users.hbs
@@ -21,7 +21,7 @@
{{i18n "user.muted_users_instructions"}}
-{{#if this.siteSettings.enable_personal_messages}}
+{{#if this.showMessageSettings}}
diff --git a/app/assets/javascripts/discourse/app/templates/topic.hbs b/app/assets/javascripts/discourse/app/templates/topic.hbs
index 3d49a7357c..41fa19fd1d 100644
--- a/app/assets/javascripts/discourse/app/templates/topic.hbs
+++ b/app/assets/javascripts/discourse/app/templates/topic.hbs
@@ -52,7 +52,7 @@
{{else}}
{{#unless this.model.is_warning}}
- {{#if this.siteSettings.enable_personal_messages}}
+ {{#if this.canSendPms}}
{{else}}
diff --git a/app/assets/javascripts/discourse/app/templates/user-card.hbs b/app/assets/javascripts/discourse/app/templates/user-card.hbs
index ba2df96cac..9407edab97 100644
--- a/app/assets/javascripts/discourse/app/templates/user-card.hbs
+++ b/app/assets/javascripts/discourse/app/templates/user-card.hbs
@@ -2,6 +2,5 @@
{{/if}}
-
-
+
diff --git a/app/assets/javascripts/discourse/app/templates/user-selector-autocomplete.hbr b/app/assets/javascripts/discourse/app/templates/user-selector-autocomplete.hbr
index 797bd64e35..4b62706363 100644
--- a/app/assets/javascripts/discourse/app/templates/user-selector-autocomplete.hbr
+++ b/app/assets/javascripts/discourse/app/templates/user-selector-autocomplete.hbr
@@ -5,8 +5,15 @@
{{avatar user imageSize="tiny"}}
{{format-username user.username}}
- {{user.name}}
- {{decorate-username-selector user.username}}
+ {{#if user.status}}
+ {{emoji user.status.emoji}}
+
+ {{user.status.description}}
+
+ {{#if user.status.ends_at}}
+ {{format-age user.status.ends_at}}
+ {{/if}}
+ {{/if}}
{{/each}}
diff --git a/app/assets/javascripts/discourse/app/templates/user.hbs b/app/assets/javascripts/discourse/app/templates/user.hbs
index c990875aaf..c3b1e4e419 100644
--- a/app/assets/javascripts/discourse/app/templates/user.hbs
+++ b/app/assets/javascripts/discourse/app/templates/user.hbs
@@ -59,14 +59,17 @@
{{/if}}
+
{{#if this.canMuteOrIgnoreUser}}
{{/if}}
- {{#if this.currentUser.staff}}
+
+ {{#if this.displayTopLevelAdminButton}}
{{d-icon "wrench"}}{{i18n "admin.user.show_admin_profile"}}
{{/if}}
+
{{#if this.canExpandProfile}}
@@ -74,6 +77,12 @@
{{/if}}
+
+ {{#if (and this.site.mobileView this.currentUser.redesigned_user_page_nav_enabled)}}
+
+
+
+ {{/if}}
@@ -216,7 +225,7 @@
{{/if}}
{{#if this.canDeleteUser}}
-
+
{{/if}}
@@ -224,36 +233,67 @@
{{/unless}}
-
-
-
- {{#unless this.model.profile_hidden}}
- {{i18n 'user.summary.title'}}
- {{i18n 'user.activity_stream'}}
- {{/unless}}
- {{#if this.showNotificationsTab}}
-
-
- {{d-icon "comment" class="glyph"}}{{i18n 'user.notifications'}}
-
-
- {{/if}}
- {{#if this.showPrivateMessages}}
- {{d-icon "far-envelope"}}{{i18n 'user.private_messages'}}
- {{/if}}
- {{#if this.canInviteToForum}}
- {{d-icon "user-plus"}}{{i18n 'user.invited.title'}}
- {{/if}}
- {{#if this.showBadges}}
- {{d-icon "certificate"}}{{i18n 'badges.title'}}
- {{/if}}
-
- {{#if this.model.can_edit}}
- {{d-icon "cog"}}{{i18n 'user.preferences'}}
- {{/if}}
-
-
- {{outlet}}
-
+
+ {{#if this.currentUser.redesigned_user_page_nav_enabled}}
+
+
+
+ {{#if (or this.site.desktopView (not this.displayUserNav))}}
+
+ {{outlet}}
+
+ {{/if}}
+
+ {{else}}
+
+
+
+ {{#unless this.model.profile_hidden}}
+ {{i18n 'user.summary.title'}}
+ {{i18n 'user.activity_stream'}}
+ {{/unless}}
+
+ {{#if this.showNotificationsTab}}
+
+
+ {{d-icon "comment" class="glyph"}}{{i18n 'user.notifications'}}
+
+
+ {{/if}}
+
+ {{#if this.showPrivateMessages}}
+ {{d-icon "far-envelope"}}{{i18n 'user.private_messages'}}
+ {{/if}}
+
+ {{#if this.canInviteToForum}}
+ {{d-icon "user-plus"}}{{i18n 'user.invited.title'}}
+ {{/if}}
+
+ {{#if this.showBadges}}
+ {{d-icon "certificate"}}{{i18n 'badges.title'}}
+ {{/if}}
+
+
+
+ {{#if this.model.can_edit}}
+ {{d-icon "cog"}}{{i18n 'user.preferences'}}
+ {{/if}}
+
+
+
+ {{outlet}}
+
+ {{/if}}
diff --git a/app/assets/javascripts/discourse/app/templates/user/activity.hbs b/app/assets/javascripts/discourse/app/templates/user/activity.hbs
index 5709d95e79..a115f1cf06 100644
--- a/app/assets/javascripts/discourse/app/templates/user/activity.hbs
+++ b/app/assets/javascripts/discourse/app/templates/user/activity.hbs
@@ -1,43 +1,46 @@
-
-
+
+
+ {{#if this.canDownloadPosts}}
+
+ {{/if}}
+{{/unless}}
{{outlet}}
diff --git a/app/assets/javascripts/discourse/app/templates/user/stream.hbs b/app/assets/javascripts/discourse/app/templates/user/stream.hbs
index 90788aa4ef..a7fad6bd0f 100644
--- a/app/assets/javascripts/discourse/app/templates/user/stream.hbs
+++ b/app/assets/javascripts/discourse/app/templates/user/stream.hbs
@@ -1,11 +1,7 @@
{{#if this.model.stream.noContent}}
- {{#if this.model.isAnotherUsersPage}}
- {{this.model.emptyStateOthers}}
- {{else}}
-
- {{/if}}
+
{{/if}}
diff --git a/app/assets/javascripts/discourse/app/widgets/header.js b/app/assets/javascripts/discourse/app/widgets/header.js
index 8fef0b8e65..0a8398f141 100644
--- a/app/assets/javascripts/discourse/app/widgets/header.js
+++ b/app/assets/javascripts/discourse/app/widgets/header.js
@@ -392,6 +392,15 @@ createWidget("revamped-hamburger-menu-wrapper", {
];
},
+ click(event) {
+ if (
+ event.target.closest(".sidebar-section-header-button") ||
+ event.target.closest(".sidebar-section-link")
+ ) {
+ this.sendWidgetAction("toggleHamburger");
+ }
+ },
+
clickOutside() {
this.sendWidgetAction("toggleHamburger");
},
@@ -407,14 +416,21 @@ createWidget("revamped-user-menu-wrapper", {
new RenderGlimmer(
this,
"div.widget-component-connector",
- hbs``
+ hbs``,
+ {
+ closeUserMenu: this.closeUserMenu.bind(this),
+ }
),
];
},
- clickOutside() {
+ closeUserMenu() {
this.sendWidgetAction("toggleUserMenu");
},
+
+ clickOutside() {
+ this.closeUserMenu();
+ },
});
export default createWidget("header", {
@@ -589,16 +605,21 @@ export default createWidget("header", {
toggleHamburger() {
if (
this.siteSettings.enable_experimental_sidebar_hamburger &&
- (this.attrs.sidebarEnabled || this.site.mobileView)
+ this.attrs.sidebarEnabled
) {
this.sendWidgetAction("toggleSidebar");
} else {
this.state.hamburgerVisible = !this.state.hamburgerVisible;
this.toggleBodyScrolling(this.state.hamburgerVisible);
- // auto focus on first link in dropdown
schedule("afterRender", () => {
- document.querySelector(".hamburger-panel .menu-links a")?.focus();
+ if (this.siteSettings.enable_experimental_sidebar_hamburger) {
+ // Remove focus from hamburger toggle button
+ document.querySelector("#toggle-hamburger-menu").blur();
+ } else {
+ // auto focus on first link in dropdown
+ document.querySelector(".hamburger-panel .menu-links a")?.focus();
+ }
});
}
},
diff --git a/app/assets/javascripts/discourse/app/widgets/post-cooked.js b/app/assets/javascripts/discourse/app/widgets/post-cooked.js
index 754312cc29..62941eaf60 100644
--- a/app/assets/javascripts/discourse/app/widgets/post-cooked.js
+++ b/app/assets/javascripts/discourse/app/widgets/post-cooked.js
@@ -6,6 +6,8 @@ import { iconHTML } from "discourse-common/lib/icon-library";
import { isValidLink } from "discourse/lib/click-track";
import { number } from "discourse/lib/formatter";
import { spinnerHTML } from "discourse/helpers/loading-spinner";
+import { escape } from "pretty-text/sanitizer";
+import domFromString from "discourse-common/lib/dom-from-string";
let _beforeAdoptDecorators = [];
let _afterAdoptDecorators = [];
@@ -31,6 +33,8 @@ function createDetachedElement(nodeName) {
}
export default class PostCooked {
+ originalQuoteContents = null;
+
constructor(attrs, decoratorHelper, currentUser) {
this.attrs = attrs;
this.expanding = false;
@@ -52,13 +56,12 @@ export default class PostCooked {
}
init() {
+ this.originalQuoteContents = null;
const cookedDiv = this._computeCooked();
- const $cookedDiv = $(cookedDiv);
-
- this._insertQuoteControls($cookedDiv);
- this._showLinkCounts($cookedDiv);
- this._applySearchHighlight($cookedDiv);
+ this._insertQuoteControls(cookedDiv);
+ this._showLinkCounts(cookedDiv);
+ this._applySearchHighlight(cookedDiv);
this._decorateAndAdopt(cookedDiv);
return cookedDiv;
@@ -72,8 +75,7 @@ export default class PostCooked {
_afterAdoptDecorators.forEach((d) => d(cooked, this.decoratorHelper));
}
- _applySearchHighlight($html) {
- const html = $html[0];
+ _applySearchHighlight(html) {
const highlight = this.attrs.highlightTerm;
if (highlight && highlight.length > 2) {
@@ -89,7 +91,7 @@ export default class PostCooked {
}
}
- _showLinkCounts($html) {
+ _showLinkCounts(html) {
const linkCounts = this.attrs.linkCounts;
if (!linkCounts) {
return;
@@ -99,7 +101,7 @@ export default class PostCooked {
// for that one (the best element is the most significant one to the
// viewer)
const bestElements = new Map();
- $html[0].querySelectorAll("aside.onebox").forEach((onebox) => {
+ html.querySelectorAll("aside.onebox").forEach((onebox) => {
// look in headings first
for (let i = 1; i <= 6; ++i) {
const hLinks = onebox.querySelectorAll(`h${i} a[href]`);
@@ -121,10 +123,8 @@ export default class PostCooked {
return;
}
- $html.find("a[href]").each((i, e) => {
- const $link = $(e);
- const href = $link.attr("href");
-
+ html.querySelectorAll("a[href]").forEach((link) => {
+ const href = link.getAttribute("href");
let valid = href === lc.url;
// this might be an attachment
@@ -132,24 +132,29 @@ export default class PostCooked {
valid = href.includes(lc.url);
}
- // Match server-side behaviour for internal links with query params
+ // match server-side behavior for internal links with query params
if (lc.internal && /\?/.test(href)) {
valid = href.split("?")[0] === lc.url;
}
// don't display badge counts on category badge & oneboxes (unless when explicitly stated)
- if (valid && isValidLink($link[0])) {
- const $onebox = $link.closest(".onebox");
+ if (valid && isValidLink(link)) {
+ const onebox = link.closest(".onebox");
+
if (
- $onebox.length === 0 ||
- !bestElements.has($onebox[0]) ||
- bestElements.get($onebox[0]) === $link[0]
+ !onebox ||
+ !bestElements.has(onebox) ||
+ bestElements.get(onebox) === link
) {
const title = I18n.t("topic_map.clicks", { count: lc.clicks });
- $link.append(
- ` ${number(
- lc.clicks
- )}`
+
+ link.appendChild(document.createTextNode(" "));
+ link.appendChild(
+ domFromString(
+ `${number(
+ lc.clicks
+ )}`
+ )[0]
);
}
}
@@ -157,21 +162,31 @@ export default class PostCooked {
});
}
- _toggleQuote($aside) {
+ async _toggleQuote(aside) {
if (this.expanding) {
return;
}
this.expanding = true;
- const blockQuote = $aside[0].querySelector("blockquote");
- $aside.data("expanded", !$aside.data("expanded"));
+ const blockQuote = aside.querySelector("blockquote");
- const finished = () => (this.expanding = false);
+ if (!blockQuote) {
+ return;
+ }
+
+ if (aside.dataset.expanded) {
+ delete aside.dataset.expanded;
+ } else {
+ aside.dataset.expanded = true;
+ }
+
+ const quoteId = blockQuote.id;
+
+ if (aside.dataset.expanded) {
+ this._updateQuoteElements(aside, "chevron-up");
- if ($aside.data("expanded")) {
- this._updateQuoteElements($aside, "chevron-up");
// Show expanded quote
- $aside.data("original-contents", blockQuote.innerHTML);
+ this.originalQuoteContents.set(quoteId, blockQuote.innerHTML);
const originalText =
blockQuote.textContent.trim() ||
@@ -179,51 +194,45 @@ export default class PostCooked {
blockQuote.innerHTML = spinnerHTML;
- let topicId = this.attrs.topicId;
- if ($aside.data("topic")) {
- topicId = $aside.data("topic");
- }
+ const topicId = parseInt(aside.dataset.topic || this.attrs.topicId, 10);
+ const postId = parseInt(aside.dataset.post, 10);
- const postId = parseInt($aside.data("post"), 10);
- topicId = parseInt(topicId, 10);
+ try {
+ const result = await ajax(`/posts/by_number/${topicId}/${postId}`);
- ajax(`/posts/by_number/${topicId}/${postId}`)
- .then((result) => {
- const post = this.decoratorHelper.getModel();
- const quotedPosts = post.quoted || {};
- quotedPosts[result.id] = result;
- post.set("quoted", quotedPosts);
+ const post = this.decoratorHelper.getModel();
+ const quotedPosts = post.quoted || {};
+ quotedPosts[result.id] = result;
+ post.set("quoted", quotedPosts);
- const div = createDetachedElement("div");
- div.classList.add("expanded-quote");
- div.dataset.postId = result.id;
- div.innerHTML = result.cooked;
+ const div = createDetachedElement("div");
+ div.classList.add("expanded-quote");
+ div.dataset.postId = result.id;
+ div.innerHTML = result.cooked;
- this._decorateAndAdopt(div);
+ this._decorateAndAdopt(div);
- highlightHTML(div, originalText, {
- matchCase: true,
- });
-
- blockQuote.innerHTML = "";
- blockQuote.appendChild(div);
- finished();
- })
- .catch((e) => {
- if ([403, 404].includes(e.jqXHR.status)) {
- const icon = e.jqXHR.status === 403 ? "lock" : "far-trash-alt";
- blockQuote.innerHTML = `${iconHTML(
- icon
- )}
`;
- }
+ highlightHTML(div, originalText, {
+ matchCase: true,
});
+
+ blockQuote.innerHTML = "";
+ blockQuote.appendChild(div);
+ } catch (e) {
+ if ([403, 404].includes(e.jqXHR.status)) {
+ const icon = e.jqXHR.status === 403 ? "lock" : "far-trash-alt";
+ blockQuote.innerHTML = `${iconHTML(
+ icon
+ )}
`;
+ }
+ }
} else {
// Hide expanded quote
- this._updateQuoteElements($aside, "chevron-down");
- blockQuote.innerHTML = $aside.data("original-contents");
- finished();
+ this._updateQuoteElements(aside, "chevron-down");
+ blockQuote.innerHTML = this.originalQuoteContents.get(blockQuote.id);
}
- return false;
+
+ this.expanding = false;
}
_urlForPostNumber(postNumber) {
@@ -232,72 +241,103 @@ export default class PostCooked {
: this.attrs.topicUrl;
}
- _updateQuoteElements($aside, desc) {
- let navLink = "";
+ _updateQuoteElements(aside, desc) {
const quoteTitle = I18n.t("post.follow_quote");
- let postNumber = $aside.data("post");
- let topicNumber = $aside.data("topic");
+ const postNumber = aside.dataset.post;
+ const topicNumber = aside.dataset.topic;
// If we have a post reference
- if (topicNumber && topicNumber === this.attrs.topicId && postNumber) {
- let icon = iconHTML("arrow-up");
+ let navLink = "";
+ if (
+ topicNumber &&
+ postNumber &&
+ topicNumber === this.attrs.topicId?.toString()
+ ) {
+ const icon = iconHTML("arrow-up");
navLink = `${icon}`;
}
// Only add the expand/contract control if it's not a full post
+ const titleElement = aside.querySelector(".title");
let expandContract = "";
- const isExpanded = $aside.data("expanded") === true;
- if (!$aside.data("full")) {
- let icon = iconHTML(desc, { title: "post.expand_collapse" });
- const quoteId = $aside.find("blockquote").attr("id");
- expandContract = ``;
- $(".title", $aside).css("cursor", "pointer");
- }
- if (this.ignoredUsers && this.ignoredUsers.length > 0) {
- const username = $aside.find(".title").text().trim().slice(0, -1);
- if (username.length > 0 && this.ignoredUsers.includes(username)) {
- $aside.find("p").remove();
- $aside.addClass("ignored-user");
+
+ if (!aside.dataset.full) {
+ const icon = iconHTML(desc, { title: "post.expand_collapse" });
+ const quoteId = aside.querySelector("blockquote")?.id;
+
+ if (quoteId) {
+ const isExpanded = aside.dataset.expanded === "true";
+ expandContract = ``;
+
+ if (titleElement) {
+ titleElement.style.cursor = "pointer";
+ }
}
}
- $(".quote-controls", $aside).html(expandContract + navLink);
+
+ if (this.ignoredUsers?.length && titleElement) {
+ const username = titleElement.innerText.trim().slice(0, -1);
+
+ if (username.length > 0 && this.ignoredUsers.includes(username)) {
+ aside.querySelectorAll("p").forEach((el) => el.remove());
+ aside.classList.add("ignored-user");
+ }
+ }
+
+ const quoteControls = aside.querySelector(".quote-controls");
+ if (quoteControls) {
+ quoteControls.innerHTML = expandContract + navLink;
+ }
}
- _insertQuoteControls($html) {
- const $quotes = $html.find("aside.quote");
- if ($quotes.length === 0) {
+ _insertQuoteControls(html) {
+ const quotes = html.querySelectorAll("aside.quote");
+ if (quotes.length === 0) {
return;
}
- $quotes.each((index, e) => {
- const $aside = $(e);
- if ($aside.data("post")) {
- const quoteId = `quote-id-${$aside.data("topic")}-${$aside.data(
- "post"
- )}-${index}`;
- $aside.find("blockquote").attr("id", quoteId);
+ this.originalQuoteContents = new Map();
- this._updateQuoteElements($aside, "chevron-down");
- const $title = $(".title", $aside);
+ quotes.forEach((aside, index) => {
+ if (aside.dataset.post) {
+ const quoteId = `quote-id-${aside.dataset.topic}-${aside.dataset.post}-${index}`;
+
+ const blockquote = aside.querySelector("blockquote");
+ if (blockquote) {
+ blockquote.id = quoteId;
+ }
+
+ this._updateQuoteElements(aside, "chevron-down");
+ const title = aside.querySelector(".title");
+
+ if (!title) {
+ return;
+ }
// If post/topic is not found then display username, skip controls
- if (e.classList.contains("quote-post-not-found") && $title.length) {
- e.querySelector(".title").innerHTML = e.dataset.username;
+ if (aside.classList.contains("quote-post-not-found")) {
+ if (aside.dataset.username) {
+ title.innerHTML = escape(aside.dataset.username);
+ } else {
+ title.remove();
+ }
+
return;
}
// Unless it's a full quote, allow click to expand
- if (!($aside.data("full") || $title.data("has-quote-controls"))) {
- $title.on("click", (e2) => {
- let $target = $(e2.target);
- if ($target.closest("a").length) {
+ if (!aside.dataset.full && !title.dataset.hasQuoteControls) {
+ title.addEventListener("click", (e) => {
+ if (e.target.closest("a")) {
return true;
}
- this._toggleQuote($aside);
+
+ this._toggleQuote(aside);
});
- $title.data("has-quote-controls", true);
+
+ title.dataset.hasQuoteControls = true;
}
}
});
diff --git a/app/assets/javascripts/discourse/app/widgets/post-menu.js b/app/assets/javascripts/discourse/app/widgets/post-menu.js
index 7389b2b24c..6c0e520ee2 100644
--- a/app/assets/javascripts/discourse/app/widgets/post-menu.js
+++ b/app/assets/javascripts/discourse/app/widgets/post-menu.js
@@ -17,8 +17,8 @@ const LIKE_ACTION = 2;
const VIBRATE_DURATION = 5;
const _builders = {};
-let _extraButtons = {};
export let apiExtraButtons = {};
+let _extraButtons = {};
let _buttonsToRemove = {};
export function addButton(name, builder) {
@@ -26,9 +26,12 @@ export function addButton(name, builder) {
}
export function resetPostMenuExtraButtons() {
- _buttonsToRemove = {};
- apiExtraButtons = {};
+ for (const key of Object.keys(apiExtraButtons)) {
+ delete apiExtraButtons[key];
+ }
+
_extraButtons = {};
+ _buttonsToRemove = {};
}
export function removeButton(name, callback) {
@@ -147,41 +150,45 @@ function likeCount(attrs, state) {
registerButton("like-count", likeCount);
-registerButton("like", (attrs) => {
- if (!attrs.showLike) {
- return likeCount(attrs);
+registerButton(
+ "like",
+ (attrs, _state, _siteSettings, _settings, currentUser) => {
+ if (!attrs.showLike) {
+ return likeCount(attrs);
+ }
+
+ const className = attrs.liked
+ ? "toggle-like has-like fade-out"
+ : "toggle-like like";
+
+ const button = {
+ action: "like",
+ icon: attrs.liked ? "d-liked" : "d-unliked",
+ className,
+ before: "like-count",
+ data: {
+ "post-id": attrs.id,
+ },
+ };
+
+ // If the user has already liked the post and doesn't have permission
+ // to undo that operation, then indicate via the title that they've liked it
+ // and disable the button. Otherwise, set the title even if the user
+ // is anonymous (meaning they don't currently have permission to like);
+ // this is important for accessibility.
+ if (attrs.liked && !attrs.canToggleLike) {
+ button.title = "post.controls.has_liked";
+ } else {
+ button.title = attrs.liked
+ ? "post.controls.undo_like"
+ : "post.controls.like";
+ }
+ if (currentUser && !attrs.canToggleLike) {
+ button.disabled = true;
+ }
+ return button;
}
-
- const className = attrs.liked
- ? "toggle-like has-like fade-out"
- : "toggle-like like";
-
- const button = {
- action: "like",
- icon: attrs.liked ? "d-liked" : "d-unliked",
- className,
- before: "like-count",
- data: {
- "post-id": attrs.id,
- },
- };
-
- // If the user has already liked the post and doesn't have permission
- // to undo that operation, then indicate via the title that they've liked it
- // and disable the button. Otherwise, set the title even if the user
- // is anonymous (meaning they don't currently have permission to like);
- // this is important for accessibility.
- if (attrs.liked && !attrs.canToggleLike) {
- button.title = "post.controls.has_liked";
- button.disabled = true;
- } else {
- button.title = attrs.liked
- ? "post.controls.undo_like"
- : "post.controls.like";
- }
-
- return button;
-});
+);
registerButton("flag-count", (attrs) => {
let className = "button-count";
diff --git a/app/assets/javascripts/discourse/app/widgets/post.js b/app/assets/javascripts/discourse/app/widgets/post.js
index e4354c844a..e2fa3ab3c0 100644
--- a/app/assets/javascripts/discourse/app/widgets/post.js
+++ b/app/assets/javascripts/discourse/app/widgets/post.js
@@ -11,7 +11,6 @@ import I18n from "I18n";
import PostCooked from "discourse/widgets/post-cooked";
import { Promise } from "rsvp";
import RawHtml from "discourse/widgets/raw-html";
-import bootbox from "bootbox";
import { dateNode } from "discourse/helpers/node";
import { h } from "virtual-dom";
import hbs from "discourse/widgets/hbs-compiler";
@@ -814,6 +813,7 @@ export function addPostClassesCallback(callback) {
export default createWidget("post", {
buildKey: (attrs) => `post-${attrs.id}`,
+ services: ["dialog"],
shadowTree: true,
buildAttributes(attrs) {
@@ -918,7 +918,7 @@ export default createWidget("post", {
const { remaining, max } = result;
const threshold = Math.ceil(max * 0.1);
if (remaining === threshold) {
- bootbox.alert(I18n.t("post.few_likes_left"));
+ this.dialog.alert(I18n.t("post.few_likes_left"));
kvs.set({ key: "lastWarnedLikes", value: Date.now() });
}
},
diff --git a/app/assets/javascripts/discourse/app/widgets/poster-name.js b/app/assets/javascripts/discourse/app/widgets/poster-name.js
index 50c1f39203..77a79fed30 100644
--- a/app/assets/javascripts/discourse/app/widgets/poster-name.js
+++ b/app/assets/javascripts/discourse/app/widgets/poster-name.js
@@ -42,6 +42,20 @@ export default createWidget("poster-name", {
showGlyph: true,
},
+ didRenderWidget() {
+ if (this.attrs.user) {
+ this.attrs.user.trackStatus();
+ this.attrs.user.on("status-changed", this, "scheduleRerender");
+ }
+ },
+
+ willRerenderWidget() {
+ if (this.attrs.user) {
+ this.attrs.user.off("status-changed", this, "scheduleRerender");
+ this.attrs.user.stopTrackingStatus();
+ }
+ },
+
// TODO: Allow extensibility
posterGlyph(attrs) {
if (attrs.moderator || attrs.groupModerator) {
@@ -151,8 +165,12 @@ export default createWidget("poster-name", {
afterNameContents(attrs) {
const contents = [];
- if (this.siteSettings.enable_user_status && attrs.userStatus) {
- contents.push(this.attach("post-user-status", attrs.userStatus));
+ if (
+ this.siteSettings.enable_user_status &&
+ attrs.user &&
+ attrs.user.status
+ ) {
+ contents.push(this.attach("post-user-status", attrs.user.status));
}
contents.push(...applyDecorators(this, "after-name", attrs, this.state));
return contents;
diff --git a/app/assets/javascripts/discourse/app/widgets/render-glimmer.js b/app/assets/javascripts/discourse/app/widgets/render-glimmer.js
index 3adc020614..e4db7d87ca 100644
--- a/app/assets/javascripts/discourse/app/widgets/render-glimmer.js
+++ b/app/assets/javascripts/discourse/app/widgets/render-glimmer.js
@@ -34,6 +34,44 @@ html(){
}
```
+You can also include function references in the `data` object, and use them as actions within the Ember component.
+You will need to `bind` the function to ensure it maintains a reference to the widget, and you'll need to manually
+call `this.scheduleRerender()` after making any changes to widget state (the normal widget auto-rerendering does not apply).
+
+Note that the @bind decorator will only work if you're using class-based Widget syntax. When using createWidget, you'll need to
+call `.bind(this)` manually when passing the function to RenderGlimmer.
+
+For example:
+```
+createWidget("my-widget", {
+ tagName: "div",
+ buildKey: () => `my-widget`,
+
+ defaultState() {
+ return { counter: 0 };
+ },
+
+ html(args, state){
+ return [
+ new RenderGlimmer(
+ this,
+ "div.my-wrapper-class",
+ hbs``,
+ {
+ counter: state.counter,
+ incrementCounter: this.incrementCounter.bind(this),
+ }
+ ),
+ ]
+ },
+
+ incrementCounter() {
+ this.state.counter++;
+ this.scheduleRerender();
+ },
+});
+```
+
*/
export default class RenderGlimmer {
@@ -70,6 +108,16 @@ export default class RenderGlimmer {
}
update(prev) {
+ if (
+ prev.template.__id !== this.template.__id ||
+ prev.tagName !== this.tagName
+ ) {
+ // Totally different component, but the widget framework guessed it was the
+ // same widget. Destroy old component and re-init the new one.
+ prev.destroy();
+ return this.init();
+ }
+
this._componentInfo = prev._componentInfo;
if (prev.data !== this.data) {
this._componentInfo.data = this.data;
diff --git a/app/assets/javascripts/discourse/app/widgets/topic-admin-menu.js b/app/assets/javascripts/discourse/app/widgets/topic-admin-menu.js
index 0a468a18e0..d019fe8046 100644
--- a/app/assets/javascripts/discourse/app/widgets/topic-admin-menu.js
+++ b/app/assets/javascripts/discourse/app/widgets/topic-admin-menu.js
@@ -76,33 +76,44 @@ createWidget("topic-admin-menu-button", {
showAdminMenu(e) {
this.state.expanded = true;
- let $button;
+ let button;
if (e === undefined) {
- $button = $(".keyboard-target-admin-menu");
+ button = document.querySelector(".keyboard-target-admin-menu");
} else {
- $button = $(e.target.closest("button"));
+ button = e.target.closest("button");
}
- const position = $button.position(),
- SPACING = 3,
- MENU_WIDTH = 217;
+ const position = { top: button.offsetTop, left: button.offsetLeft };
+ const spacing = 3;
+ const menuWidth = 212;
- const rtl = $("html").hasClass("rtl");
- position.outerHeight = $button.outerHeight();
-
- if (rtl) {
- position.left -= MENU_WIDTH - $button.outerWidth();
- }
+ const rtl = document.documentElement.classList.contains("html.rtl");
+ const buttonDOMRect = button.getBoundingClientRect();
+ position.outerHeight = buttonDOMRect.height;
if (this.attrs.openUpwards) {
if (rtl) {
- position.left -= $button[0].offsetWidth + SPACING;
+ position.left -= buttonDOMRect.width + spacing;
} else {
- position.left += $button[0].offsetWidth + SPACING;
+ position.left += buttonDOMRect.width + spacing;
}
} else {
- position.top += $button[0].offsetHeight + SPACING;
+ if (rtl) {
+ if (buttonDOMRect.left < menuWidth) {
+ position.left += 0;
+ } else {
+ position.left -= menuWidth - buttonDOMRect.width;
+ }
+ } else {
+ const offsetRight = window.innerWidth - buttonDOMRect.right;
+
+ if (offsetRight < menuWidth) {
+ position.left -= menuWidth - buttonDOMRect.width;
+ }
+ }
+
+ position.top += buttonDOMRect.height + spacing;
}
this.state.position = position;
@@ -323,8 +334,9 @@ export default createWidget("topic-admin-menu", {
if (attrs.openUpwards) {
const documentHeight = $(document).height();
- const mainHeight = $("#main").height();
- let bottom = documentHeight - top - 70 - $("#main").offset().top;
+ const mainHeight = $(".ember-application").height();
+ let bottom =
+ documentHeight - top - 70 - $(".ember-application").offset().top;
if (documentHeight > mainHeight) {
bottom = bottom - (documentHeight - mainHeight) - outerHeight;
diff --git a/app/assets/javascripts/discourse/app/widgets/user-menu.js b/app/assets/javascripts/discourse/app/widgets/user-menu.js
index 3e4efc53b6..49e589e767 100644
--- a/app/assets/javascripts/discourse/app/widgets/user-menu.js
+++ b/app/assets/javascripts/discourse/app/widgets/user-menu.js
@@ -156,7 +156,7 @@ createWidget("user-menu-links", {
glyphs.push(this.bookmarksGlyph());
- if (this.siteSettings.enable_personal_messages || this.currentUser.staff) {
+ if (this.currentUser?.allowPersonalMessages) {
glyphs.push(this.messagesGlyph());
}
diff --git a/app/assets/javascripts/discourse/app/widgets/user-status-bubble.js b/app/assets/javascripts/discourse/app/widgets/user-status-bubble.js
index d2cc0be9a8..ec814670ad 100644
--- a/app/assets/javascripts/discourse/app/widgets/user-status-bubble.js
+++ b/app/assets/javascripts/discourse/app/widgets/user-status-bubble.js
@@ -10,7 +10,7 @@ export default createWidget("user-status-bubble", {
const until = moment
.tz(attrs.ends_at, this.currentUser.timezone)
.format(I18n.t("dates.long_date_without_year"));
- title += `\n${I18n.t("user_status.until")} ${until}`;
+ title += `\n${I18n.t("until")} ${until}`;
}
return this.attach("emoji", { name: attrs.emoji, title });
diff --git a/app/assets/javascripts/discourse/config/optional-features.json b/app/assets/javascripts/discourse/config/optional-features.json
index b5f4e8fa7b..9effd838a6 100644
--- a/app/assets/javascripts/discourse/config/optional-features.json
+++ b/app/assets/javascripts/discourse/config/optional-features.json
@@ -1,5 +1,4 @@
{
"application-template-wrapper": false,
- "default-async-observers": true,
- "jquery-integration": true
+ "default-async-observers": true
}
diff --git a/app/assets/javascripts/discourse/ember-cli-build.js b/app/assets/javascripts/discourse/ember-cli-build.js
index 0224c3383a..8fa1004651 100644
--- a/app/assets/javascripts/discourse/ember-cli-build.js
+++ b/app/assets/javascripts/discourse/ember-cli-build.js
@@ -14,6 +14,7 @@ const SILENCED_WARN_PREFIXES = [
"Setting the `jquery-integration` optional feature flag",
"The Ember Classic edition has been deprecated",
"Setting the `template-only-glimmer-components` optional feature flag to `false`",
+ "DEPRECATION: Invoking the `` component with positional arguments is deprecated",
];
module.exports = function (defaults) {
@@ -29,6 +30,16 @@ module.exports = function (defaults) {
}
};
+ // Silence warnings which go straight to console.warn (e.g. template compiler deprecations)
+ /* eslint-disable no-console */
+ const oldConsoleWarn = console.warn.bind(console);
+ console.warn = (message, ...args) => {
+ if (!SILENCED_WARN_PREFIXES.some((prefix) => message.startsWith(prefix))) {
+ return oldConsoleWarn(message, ...args);
+ }
+ };
+ /* eslint-enable no-console */
+
const isProduction = EmberApp.env().includes("production");
const isTest = EmberApp.env().includes("test");
@@ -134,6 +145,13 @@ module.exports = function (defaults) {
"/app/assets/javascripts/discourse/public/assets/scripts/module-shims.js"
);
+ const discoursePluginsTree = app.project
+ .findAddonByName("discourse-plugins")
+ .generatePluginsTree();
+
+ const terserPlugin = app.project.findAddonByName("ember-cli-terser");
+ const applyTerser = (tree) => terserPlugin.postprocessTree("all", tree);
+
return mergeTrees([
createI18nTree(discourseRoot, vendorJs),
app.toTree(),
@@ -142,18 +160,20 @@ module.exports = function (defaults) {
files: ["highlight-test-bundle.min.js"],
destDir: "assets/highlightjs",
}),
- concat(mergeTrees([app.options.adminTree]), {
- outputFile: `assets/admin.js`,
- }),
- concat(mergeTrees([app.options.wizardTree]), {
- outputFile: `assets/wizard.js`,
- }),
- prettyTextEngine(vendorJs, "discourse-markdown"),
- concat("public/assets/scripts", {
- outputFile: `assets/start-discourse.js`,
- headerFiles: [`start-app.js`],
- inputFiles: [`discourse-boot.js`],
- }),
+ applyTerser(
+ concat(mergeTrees([app.options.adminTree]), {
+ inputFiles: ["**/*.js"],
+ outputFile: `assets/admin.js`,
+ })
+ ),
+ applyTerser(
+ concat(mergeTrees([app.options.wizardTree]), {
+ inputFiles: ["**/*.js"],
+ outputFile: `assets/wizard.js`,
+ })
+ ),
+ applyTerser(prettyTextEngine(app)),
generateScriptsTree(app),
+ applyTerser(discoursePluginsTree),
]);
};
diff --git a/app/assets/javascripts/discourse/lib/bootstrap-json/index.js b/app/assets/javascripts/discourse/lib/bootstrap-json/index.js
index a236d7aefe..b2b243fdc5 100644
--- a/app/assets/javascripts/discourse/lib/bootstrap-json/index.js
+++ b/app/assets/javascripts/discourse/lib/bootstrap-json/index.js
@@ -5,7 +5,8 @@ const fetch = require("node-fetch");
const { encode } = require("html-entities");
const cleanBaseURL = require("clean-base-url");
const path = require("path");
-const { promises: fs } = require("fs");
+const fs = require("fs");
+const fsPromises = fs.promises;
const { JSDOM } = require("jsdom");
const { shouldLoadPluginTestJs } = require("discourse/lib/plugin-js");
const { Buffer } = require("node:buffer");
@@ -231,7 +232,7 @@ async function applyBootstrap(bootstrap, template, response, baseURL, preload) {
async function buildFromBootstrap(proxy, baseURL, req, response, preload) {
try {
- const template = await fs.readFile(
+ const template = await fsPromises.readFile(
path.join(cwd(), "dist", "index.html"),
"utf8"
);
@@ -239,6 +240,24 @@ async function buildFromBootstrap(proxy, baseURL, req, response, preload) {
let url = new URL(`${proxy}${baseURL}bootstrap.json`);
url.searchParams.append("for_url", req.url);
+ const forUrlSearchParams = new URL(req.url, "https://dummy-origin.invalid")
+ .searchParams;
+
+ const mobileView = forUrlSearchParams.get("mobile_view");
+ if (mobileView) {
+ url.searchParams.append("mobile_view", mobileView);
+ }
+
+ const reqUrlSafeMode = forUrlSearchParams.get("safe_mode");
+ if (reqUrlSafeMode) {
+ url.searchParams.append("safe_mode", reqUrlSafeMode);
+ }
+
+ const reqUrlPreviewThemeId = forUrlSearchParams.get("preview_theme_id");
+ if (reqUrlPreviewThemeId) {
+ url.searchParams.append("preview_theme_id", reqUrlPreviewThemeId);
+ }
+
const res = await fetch(url, { headers: req.headers });
const json = await res.json();
@@ -319,7 +338,7 @@ async function handleRequest(proxy, baseURL, req, res) {
const newCSP = csp
.replaceAll(proxy, `http://${originalHost}`)
- .replaceAll("script-src ", `script-src ${emberCliAdditions}`);
+ .replaceAll("script-src ", `script-src ${emberCliAdditions} `);
res.set("content-security-policy", newCSP);
}
@@ -360,12 +379,56 @@ module.exports = {
contentFor(type, config) {
if (shouldLoadPluginTestJs() && type === "test-plugin-js") {
- return `
-
-
- `;
+ const scripts = [];
+
+ const pluginInfos = this.app.project
+ .findAddonByName("discourse-plugins")
+ .pluginInfos();
+
+ for (const {
+ pluginName,
+ directoryName,
+ hasJs,
+ hasAdminJs,
+ } of pluginInfos) {
+ if (hasJs) {
+ scripts.push({
+ src: `plugins/${directoryName}.js`,
+ name: pluginName,
+ });
+ }
+
+ if (fs.existsSync(`../plugins/${directoryName}_extras.js.erb`)) {
+ scripts.push({
+ src: `plugins/${directoryName}_extras.js`,
+ name: pluginName,
+ });
+ }
+
+ if (hasAdminJs) {
+ scripts.push({
+ src: `plugins/${directoryName}_admin.js`,
+ name: pluginName,
+ });
+ }
+ }
+
+ return scripts
+ .map(
+ ({ src, name }) =>
+ ``
+ )
+ .join("\n");
} else if (shouldLoadPluginTestJs() && type === "test-plugin-tests-js") {
- return ``;
+ return this.app.project
+ .findAddonByName("discourse-plugins")
+ .pluginInfos()
+ .filter(({ hasTests }) => hasTests)
+ .map(
+ ({ directoryName, pluginName }) =>
+ ``
+ )
+ .join("\n");
}
},
@@ -387,6 +450,16 @@ to serve API requests. For example:
const rawMiddleware = express.raw({ type: () => true, limit: "100mb" });
+ app.use(
+ "/favicon.ico",
+ express.static(
+ path.join(
+ __dirname,
+ "../../../../../../public/images/discourse-logo-sketch-small.png"
+ )
+ )
+ );
+
app.use(rawMiddleware, async (req, res, next) => {
try {
if (this.shouldForwardRequest(req)) {
diff --git a/app/assets/javascripts/discourse/lib/dialog-holder/addon/components/dialog-holder.hbs b/app/assets/javascripts/discourse/lib/dialog-holder/addon/components/dialog-holder.hbs
new file mode 100644
index 0000000000..46e2cd5aaf
--- /dev/null
+++ b/app/assets/javascripts/discourse/lib/dialog-holder/addon/components/dialog-holder.hbs
@@ -0,0 +1,33 @@
+
+
+
+ {{#if this.dialog.type}}
+
+ {{#if this.dialog.title}}
+
+ {{/if}}
+
+ {{#if this.dialog.message}}
+
+ {{this.dialog.message}}
+
+ {{/if}}
+
+ {{#if (notEq this.dialog.type "notice")}}
+
+ {{/if}}
+
+ {{/if}}
+
diff --git a/app/assets/javascripts/discourse/lib/dialog-holder/addon/components/dialog-holder.js b/app/assets/javascripts/discourse/lib/dialog-holder/addon/components/dialog-holder.js
new file mode 100644
index 0000000000..4a2bde4f3d
--- /dev/null
+++ b/app/assets/javascripts/discourse/lib/dialog-holder/addon/components/dialog-holder.js
@@ -0,0 +1,16 @@
+import Component from "@glimmer/component";
+import { inject as service } from "@ember/service";
+import { action } from "@ember/object";
+
+export default class DialogHolder extends Component {
+ @service dialog;
+
+ @action
+ async handleButtonAction(btn) {
+ if (btn.action && typeof btn.action === "function") {
+ await btn.action();
+ }
+
+ this.dialog.cancel();
+ }
+}
diff --git a/app/assets/javascripts/discourse/lib/dialog-holder/addon/services/dialog.js b/app/assets/javascripts/discourse/lib/dialog-holder/addon/services/dialog.js
new file mode 100644
index 0000000000..fa072595ed
--- /dev/null
+++ b/app/assets/javascripts/discourse/lib/dialog-holder/addon/services/dialog.js
@@ -0,0 +1,152 @@
+import Service from "@ember/service";
+import A11yDialog from "a11y-dialog";
+import { bind } from "discourse-common/utils/decorators";
+
+export default Service.extend({
+ message: null,
+ type: null,
+ dialogInstance: null,
+
+ title: null,
+ titleElementId: null,
+
+ confirmButtonIcon: null,
+ confirmButtonLabel: null,
+ cancelButtonLabel: null,
+ shouldDisplayCancel: null,
+
+ didConfirm: null,
+ didCancel: null,
+ buttons: null,
+ class: null,
+ _confirming: false,
+
+ dialog(params) {
+ const {
+ message,
+ type,
+ title,
+
+ confirmButtonIcon,
+ confirmButtonLabel = "ok_value",
+ cancelButtonLabel = "cancel_value",
+ shouldDisplayCancel,
+
+ didConfirm,
+ didCancel,
+ buttons,
+ } = params;
+
+ const element = document.getElementById("dialog-holder");
+
+ this.setProperties({
+ message,
+ type,
+ dialogInstance: new A11yDialog(element),
+
+ title,
+ titleElementId: title !== null ? "dialog-title" : null,
+
+ confirmButtonLabel,
+ confirmButtonIcon,
+ cancelButtonLabel,
+ shouldDisplayCancel,
+
+ didConfirm,
+ didCancel,
+ buttons,
+ class: params.class,
+ });
+
+ this.dialogInstance.show();
+
+ this.dialogInstance.on("hide", () => {
+ if (!this._confirming && this.didCancel) {
+ this.didCancel();
+ }
+
+ this.reset();
+ });
+ },
+
+ alert(params) {
+ // support string param for easier porting of bootbox.alert
+ if (typeof params === "string") {
+ return this.dialog({
+ message: params,
+ type: "alert",
+ });
+ }
+
+ return this.dialog({
+ ...params,
+ type: "alert",
+ });
+ },
+
+ confirm(params) {
+ return this.dialog({
+ ...params,
+ shouldDisplayCancel: true,
+ buttons: null,
+ type: "confirm",
+ });
+ },
+
+ notice(message) {
+ return this.dialog({
+ message,
+ type: "notice",
+ });
+ },
+
+ yesNoConfirm(params) {
+ return this.confirm({
+ ...params,
+ confirmButtonLabel: "yes_value",
+ cancelButtonLabel: "no_value",
+ });
+ },
+
+ reset() {
+ this.setProperties({
+ message: null,
+ type: null,
+ dialogInstance: null,
+
+ title: null,
+ titleElementId: null,
+
+ confirmButtonLabel: null,
+ confirmButtonIcon: null,
+ cancelButtonLabel: null,
+ shouldDisplayCancel: null,
+
+ didConfirm: null,
+ didCancel: null,
+ buttons: null,
+ class: null,
+
+ _confirming: false,
+ });
+ },
+
+ willDestroy() {
+ this.dialogInstance?.destroy();
+ this.reset();
+ },
+
+ @bind
+ didConfirmWrapped() {
+ if (this.didConfirm) {
+ this.didConfirm();
+ }
+ this._confirming = true;
+ this.dialogInstance.hide();
+ },
+
+ @bind
+ cancel() {
+ this.dialogInstance.hide();
+ },
+});
diff --git a/app/assets/javascripts/discourse/lib/dialog-holder/app/components/dialog-holder.js b/app/assets/javascripts/discourse/lib/dialog-holder/app/components/dialog-holder.js
new file mode 100644
index 0000000000..151d46784a
--- /dev/null
+++ b/app/assets/javascripts/discourse/lib/dialog-holder/app/components/dialog-holder.js
@@ -0,0 +1 @@
+export { default } from "dialog-holder/components/dialog-holder";
diff --git a/app/assets/javascripts/discourse/lib/dialog-holder/index.js b/app/assets/javascripts/discourse/lib/dialog-holder/index.js
new file mode 100644
index 0000000000..b5b31da4a4
--- /dev/null
+++ b/app/assets/javascripts/discourse/lib/dialog-holder/index.js
@@ -0,0 +1,9 @@
+"use strict";
+
+module.exports = {
+ name: require("./package").name,
+
+ isDevelopingAddon() {
+ return true;
+ },
+};
diff --git a/app/assets/javascripts/discourse/lib/dialog-holder/package.json b/app/assets/javascripts/discourse/lib/dialog-holder/package.json
new file mode 100644
index 0000000000..b82d6b7da3
--- /dev/null
+++ b/app/assets/javascripts/discourse/lib/dialog-holder/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "dialog-holder",
+ "keywords": [
+ "ember-addon"
+ ],
+ "dependencies": {
+ "a11y-dialog": "7.5.0",
+ "ember-auto-import": "^2.4.2",
+ "ember-cli-babel": "^7.26.10",
+ "ember-cli-htmlbars": "^6.1.0"
+ },
+ "devDependencies": {
+ "webpack": "^5.73.0"
+ }
+}
diff --git a/app/assets/javascripts/discourse/lib/dialog-holder/yarn.lock b/app/assets/javascripts/discourse/lib/dialog-holder/yarn.lock
new file mode 100644
index 0000000000..d81a3899f1
--- /dev/null
+++ b/app/assets/javascripts/discourse/lib/dialog-holder/yarn.lock
@@ -0,0 +1,3659 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@ampproject/remapping@^2.1.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d"
+ integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.1.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@babel/code-frame@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a"
+ integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==
+ dependencies:
+ "@babel/highlight" "^7.18.6"
+
+"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8":
+ version "7.18.13"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.13.tgz#6aff7b350a1e8c3e40b029e46cbe78e24a913483"
+ integrity sha512-5yUzC5LqyTFp2HLmDoxGQelcdYgSpP9xsnMWBphAscOdFrHSAVbLNzWiy32sVNDqJRDiJK6klfDnAgu6PAGSHw==
+
+"@babel/core@^7.12.0", "@babel/core@^7.16.7", "@babel/core@^7.3.4":
+ version "7.18.13"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.13.tgz#9be8c44512751b05094a4d3ab05fc53a47ce00ac"
+ integrity sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==
+ dependencies:
+ "@ampproject/remapping" "^2.1.0"
+ "@babel/code-frame" "^7.18.6"
+ "@babel/generator" "^7.18.13"
+ "@babel/helper-compilation-targets" "^7.18.9"
+ "@babel/helper-module-transforms" "^7.18.9"
+ "@babel/helpers" "^7.18.9"
+ "@babel/parser" "^7.18.13"
+ "@babel/template" "^7.18.10"
+ "@babel/traverse" "^7.18.13"
+ "@babel/types" "^7.18.13"
+ convert-source-map "^1.7.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.1"
+ semver "^6.3.0"
+
+"@babel/generator@^7.18.13":
+ version "7.18.13"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.13.tgz#59550cbb9ae79b8def15587bdfbaa388c4abf212"
+ integrity sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==
+ dependencies:
+ "@babel/types" "^7.18.13"
+ "@jridgewell/gen-mapping" "^0.3.2"
+ jsesc "^2.5.1"
+
+"@babel/helper-annotate-as-pure@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
+ integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==
+ dependencies:
+ "@babel/types" "^7.18.6"
+
+"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz#acd4edfd7a566d1d51ea975dff38fd52906981bb"
+ integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==
+ dependencies:
+ "@babel/helper-explode-assignable-expression" "^7.18.6"
+ "@babel/types" "^7.18.9"
+
+"@babel/helper-compilation-targets@^7.12.0", "@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz#69e64f57b524cde3e5ff6cc5a9f4a387ee5563bf"
+ integrity sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==
+ dependencies:
+ "@babel/compat-data" "^7.18.8"
+ "@babel/helper-validator-option" "^7.18.6"
+ browserslist "^4.20.2"
+ semver "^6.3.0"
+
+"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.18.9":
+ version "7.18.13"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.13.tgz#63e771187bd06d234f95fdf8bd5f8b6429de6298"
+ integrity sha512-hDvXp+QYxSRL+23mpAlSGxHMDyIGChm0/AwTfTAAK5Ufe40nCsyNdaYCGuK91phn/fVu9kqayImRDkvNAgdrsA==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-function-name" "^7.18.9"
+ "@babel/helper-member-expression-to-functions" "^7.18.9"
+ "@babel/helper-optimise-call-expression" "^7.18.6"
+ "@babel/helper-replace-supers" "^7.18.9"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+
+"@babel/helper-create-regexp-features-plugin@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz#3e35f4e04acbbf25f1b3534a657610a000543d3c"
+ integrity sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ regexpu-core "^5.1.0"
+
+"@babel/helper-define-polyfill-provider@^0.3.2":
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz#bd10d0aca18e8ce012755395b05a79f45eca5073"
+ integrity sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg==
+ dependencies:
+ "@babel/helper-compilation-targets" "^7.17.7"
+ "@babel/helper-plugin-utils" "^7.16.7"
+ debug "^4.1.1"
+ lodash.debounce "^4.0.8"
+ resolve "^1.14.2"
+ semver "^6.1.2"
+
+"@babel/helper-environment-visitor@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be"
+ integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==
+
+"@babel/helper-explode-assignable-expression@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096"
+ integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==
+ dependencies:
+ "@babel/types" "^7.18.6"
+
+"@babel/helper-function-name@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz#940e6084a55dee867d33b4e487da2676365e86b0"
+ integrity sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==
+ dependencies:
+ "@babel/template" "^7.18.6"
+ "@babel/types" "^7.18.9"
+
+"@babel/helper-hoist-variables@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678"
+ integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==
+ dependencies:
+ "@babel/types" "^7.18.6"
+
+"@babel/helper-member-expression-to-functions@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815"
+ integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==
+ dependencies:
+ "@babel/types" "^7.18.9"
+
+"@babel/helper-module-imports@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e"
+ integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==
+ dependencies:
+ "@babel/types" "^7.18.6"
+
+"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz#5a1079c005135ed627442df31a42887e80fcb712"
+ integrity sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-module-imports" "^7.18.6"
+ "@babel/helper-simple-access" "^7.18.6"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ "@babel/helper-validator-identifier" "^7.18.6"
+ "@babel/template" "^7.18.6"
+ "@babel/traverse" "^7.18.9"
+ "@babel/types" "^7.18.9"
+
+"@babel/helper-optimise-call-expression@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe"
+ integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==
+ dependencies:
+ "@babel/types" "^7.18.6"
+
+"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz#4b8aea3b069d8cb8a72cdfe28ddf5ceca695ef2f"
+ integrity sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==
+
+"@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519"
+ integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-wrap-function" "^7.18.9"
+ "@babel/types" "^7.18.9"
+
+"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz#1092e002feca980fbbb0bd4d51b74a65c6a500e6"
+ integrity sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-member-expression-to-functions" "^7.18.9"
+ "@babel/helper-optimise-call-expression" "^7.18.6"
+ "@babel/traverse" "^7.18.9"
+ "@babel/types" "^7.18.9"
+
+"@babel/helper-simple-access@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea"
+ integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==
+ dependencies:
+ "@babel/types" "^7.18.6"
+
+"@babel/helper-skip-transparent-expression-wrappers@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz#778d87b3a758d90b471e7b9918f34a9a02eb5818"
+ integrity sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==
+ dependencies:
+ "@babel/types" "^7.18.9"
+
+"@babel/helper-split-export-declaration@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075"
+ integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==
+ dependencies:
+ "@babel/types" "^7.18.6"
+
+"@babel/helper-string-parser@^7.18.10":
+ version "7.18.10"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56"
+ integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==
+
+"@babel/helper-validator-identifier@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076"
+ integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==
+
+"@babel/helper-validator-option@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8"
+ integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==
+
+"@babel/helper-wrap-function@^7.18.9":
+ version "7.18.11"
+ resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.18.11.tgz#bff23ace436e3f6aefb61f85ffae2291c80ed1fb"
+ integrity sha512-oBUlbv+rjZLh2Ks9SKi4aL7eKaAXBWleHzU89mP0G6BMUlRxSckk9tSIkgDGydhgFxHuGSlBQZfnaD47oBEB7w==
+ dependencies:
+ "@babel/helper-function-name" "^7.18.9"
+ "@babel/template" "^7.18.10"
+ "@babel/traverse" "^7.18.11"
+ "@babel/types" "^7.18.10"
+
+"@babel/helpers@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.9.tgz#4bef3b893f253a1eced04516824ede94dcfe7ff9"
+ integrity sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==
+ dependencies:
+ "@babel/template" "^7.18.6"
+ "@babel/traverse" "^7.18.9"
+ "@babel/types" "^7.18.9"
+
+"@babel/highlight@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
+ integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.18.6"
+ chalk "^2.0.0"
+ js-tokens "^4.0.0"
+
+"@babel/parser@^7.18.10", "@babel/parser@^7.18.13":
+ version "7.18.13"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.13.tgz#5b2dd21cae4a2c5145f1fbd8ca103f9313d3b7e4"
+ integrity sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==
+
+"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2"
+ integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz#a11af19aa373d68d561f08e0a57242350ed0ec50"
+ integrity sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9"
+ "@babel/plugin-proposal-optional-chaining" "^7.18.9"
+
+"@babel/plugin-proposal-async-generator-functions@^7.18.10":
+ version "7.18.10"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz#85ea478c98b0095c3e4102bff3b67d306ed24952"
+ integrity sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-remap-async-to-generator" "^7.18.9"
+ "@babel/plugin-syntax-async-generators" "^7.8.4"
+
+"@babel/plugin-proposal-class-properties@^7.16.5", "@babel/plugin-proposal-class-properties@^7.16.7", "@babel/plugin-proposal-class-properties@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3"
+ integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-proposal-class-static-block@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz#8aa81d403ab72d3962fc06c26e222dacfc9b9020"
+ integrity sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/plugin-syntax-class-static-block" "^7.14.5"
+
+"@babel/plugin-proposal-decorators@^7.13.5", "@babel/plugin-proposal-decorators@^7.16.7":
+ version "7.18.10"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.18.10.tgz#788650d01e518a8a722eb8b3055dd9d73ecb7a35"
+ integrity sha512-wdGTwWF5QtpTY/gbBtQLAiCnoxfD4qMbN87NYZle1dOZ9Os8Y6zXcKrIaOU8W+TIvFUWVGG9tUgNww3CjXRVVw==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-replace-supers" "^7.18.9"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ "@babel/plugin-syntax-decorators" "^7.18.6"
+
+"@babel/plugin-proposal-dynamic-import@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz#72bcf8d408799f547d759298c3c27c7e7faa4d94"
+ integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.3"
+
+"@babel/plugin-proposal-export-namespace-from@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz#5f7313ab348cdb19d590145f9247540e94761203"
+ integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+
+"@babel/plugin-proposal-json-strings@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz#7e8788c1811c393aff762817e7dbf1ebd0c05f0b"
+ integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/plugin-syntax-json-strings" "^7.8.3"
+
+"@babel/plugin-proposal-logical-assignment-operators@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz#8148cbb350483bf6220af06fa6db3690e14b2e23"
+ integrity sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+
+"@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1"
+ integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+
+"@babel/plugin-proposal-numeric-separator@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz#899b14fbafe87f053d2c5ff05b36029c62e13c75"
+ integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+
+"@babel/plugin-proposal-object-rest-spread@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz#f9434f6beb2c8cae9dfcf97d2a5941bbbf9ad4e7"
+ integrity sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==
+ dependencies:
+ "@babel/compat-data" "^7.18.8"
+ "@babel/helper-compilation-targets" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+ "@babel/plugin-transform-parameters" "^7.18.8"
+
+"@babel/plugin-proposal-optional-catch-binding@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb"
+ integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
+
+"@babel/plugin-proposal-optional-chaining@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz#e8e8fe0723f2563960e4bf5e9690933691915993"
+ integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+
+"@babel/plugin-proposal-private-methods@^7.16.5", "@babel/plugin-proposal-private-methods@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea"
+ integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-proposal-private-property-in-object@^7.16.5", "@babel/plugin-proposal-private-property-in-object@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz#a64137b232f0aca3733a67eb1a144c192389c503"
+ integrity sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ "@babel/helper-create-class-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
+
+"@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e"
+ integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-syntax-async-generators@^7.8.4":
+ version "7.8.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
+ integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-class-properties@^7.12.13":
+ version "7.12.13"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10"
+ integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.12.13"
+
+"@babel/plugin-syntax-class-static-block@^7.14.5":
+ version "7.14.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406"
+ integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.14.5"
+
+"@babel/plugin-syntax-decorators@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.18.6.tgz#2e45af22835d0b0f8665da2bfd4463649ce5dbc1"
+ integrity sha512-fqyLgjcxf/1yhyZ6A+yo1u9gJ7eleFQod2lkaUsF9DQ7sbbY3Ligym3L0+I2c0WmqNKDpoD9UTb1AKP3qRMOAQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-syntax-dynamic-import@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3"
+ integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-export-namespace-from@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a"
+ integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.3"
+
+"@babel/plugin-syntax-import-assertions@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz#cd6190500a4fa2fe31990a963ffab4b63e4505e4"
+ integrity sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-syntax-json-strings@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a"
+ integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-logical-assignment-operators@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
+ integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9"
+ integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-numeric-separator@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97"
+ integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-object-rest-spread@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871"
+ integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-optional-catch-binding@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1"
+ integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-optional-chaining@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a"
+ integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-private-property-in-object@^7.14.5":
+ version "7.14.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad"
+ integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.14.5"
+
+"@babel/plugin-syntax-top-level-await@^7.14.5":
+ version "7.14.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c"
+ integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.14.5"
+
+"@babel/plugin-syntax-typescript@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz#1c09cd25795c7c2b8a4ba9ae49394576d4133285"
+ integrity sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-arrow-functions@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz#19063fcf8771ec7b31d742339dac62433d0611fe"
+ integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-async-to-generator@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz#ccda3d1ab9d5ced5265fdb13f1882d5476c71615"
+ integrity sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==
+ dependencies:
+ "@babel/helper-module-imports" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-remap-async-to-generator" "^7.18.6"
+
+"@babel/plugin-transform-block-scoped-functions@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8"
+ integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-block-scoping@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz#f9b7e018ac3f373c81452d6ada8bd5a18928926d"
+ integrity sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+
+"@babel/plugin-transform-classes@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.9.tgz#90818efc5b9746879b869d5ce83eb2aa48bbc3da"
+ integrity sha512-EkRQxsxoytpTlKJmSPYrsOMjCILacAjtSVkd4gChEe2kXjFCun3yohhW5I7plXJhCemM0gKsaGMcO8tinvCA5g==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-function-name" "^7.18.9"
+ "@babel/helper-optimise-call-expression" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-replace-supers" "^7.18.9"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ globals "^11.1.0"
+
+"@babel/plugin-transform-computed-properties@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz#2357a8224d402dad623caf6259b611e56aec746e"
+ integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+
+"@babel/plugin-transform-destructuring@^7.18.9":
+ version "7.18.13"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz#9e03bc4a94475d62b7f4114938e6c5c33372cbf5"
+ integrity sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+
+"@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8"
+ integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-duplicate-keys@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e"
+ integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+
+"@babel/plugin-transform-exponentiation-operator@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd"
+ integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==
+ dependencies:
+ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-for-of@^7.18.8":
+ version "7.18.8"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1"
+ integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-function-name@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0"
+ integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==
+ dependencies:
+ "@babel/helper-compilation-targets" "^7.18.9"
+ "@babel/helper-function-name" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.18.9"
+
+"@babel/plugin-transform-literals@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc"
+ integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+
+"@babel/plugin-transform-member-expression-literals@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e"
+ integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-modules-amd@^7.13.0", "@babel/plugin-transform-modules-amd@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz#8c91f8c5115d2202f277549848874027d7172d21"
+ integrity sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+ babel-plugin-dynamic-import-node "^2.3.3"
+
+"@babel/plugin-transform-modules-commonjs@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz#afd243afba166cca69892e24a8fd8c9f2ca87883"
+ integrity sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-simple-access" "^7.18.6"
+ babel-plugin-dynamic-import-node "^2.3.3"
+
+"@babel/plugin-transform-modules-systemjs@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.9.tgz#545df284a7ac6a05125e3e405e536c5853099a06"
+ integrity sha512-zY/VSIbbqtoRoJKo2cDTewL364jSlZGvn0LKOf9ntbfxOvjfmyrdtEEOAdswOswhZEb8UH3jDkCKHd1sPgsS0A==
+ dependencies:
+ "@babel/helper-hoist-variables" "^7.18.6"
+ "@babel/helper-module-transforms" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-validator-identifier" "^7.18.6"
+ babel-plugin-dynamic-import-node "^2.3.3"
+
+"@babel/plugin-transform-modules-umd@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9"
+ integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz#c89bfbc7cc6805d692f3a49bc5fc1b630007246d"
+ integrity sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-new-target@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz#d128f376ae200477f37c4ddfcc722a8a1b3246a8"
+ integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-object-super@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c"
+ integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-replace-supers" "^7.18.6"
+
+"@babel/plugin-transform-parameters@^7.18.8":
+ version "7.18.8"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz#ee9f1a0ce6d78af58d0956a9378ea3427cccb48a"
+ integrity sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-property-literals@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3"
+ integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-regenerator@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz#585c66cb84d4b4bf72519a34cfce761b8676ca73"
+ integrity sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+ regenerator-transform "^0.15.0"
+
+"@babel/plugin-transform-reserved-words@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz#b1abd8ebf8edaa5f7fe6bbb8d2133d23b6a6f76a"
+ integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-runtime@^7.13.9":
+ version "7.18.10"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz#37d14d1fa810a368fd635d4d1476c0154144a96f"
+ integrity sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ==
+ dependencies:
+ "@babel/helper-module-imports" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.9"
+ babel-plugin-polyfill-corejs2 "^0.3.2"
+ babel-plugin-polyfill-corejs3 "^0.5.3"
+ babel-plugin-polyfill-regenerator "^0.4.0"
+ semver "^6.3.0"
+
+"@babel/plugin-transform-shorthand-properties@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9"
+ integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-spread@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.9.tgz#6ea7a6297740f381c540ac56caf75b05b74fb664"
+ integrity sha512-39Q814wyoOPtIB/qGopNIL9xDChOE1pNU0ZY5dO0owhiVt/5kFm4li+/bBtwc7QotG0u5EPzqhZdjMtmqBqyQA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9"
+
+"@babel/plugin-transform-sticky-regex@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc"
+ integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-template-literals@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e"
+ integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+
+"@babel/plugin-transform-typeof-symbol@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz#c8cea68263e45addcd6afc9091429f80925762c0"
+ integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+
+"@babel/plugin-transform-typescript@^7.13.0":
+ version "7.18.12"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.18.12.tgz#712e9a71b9e00fde9f8c0238e0cceee86ab2f8fd"
+ integrity sha512-2vjjam0cum0miPkenUbQswKowuxs/NjMwIKEq0zwegRxXk12C9YOF9STXnaUptITOtOJHKHpzvvWYOjbm6tc0w==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/plugin-syntax-typescript" "^7.18.6"
+
+"@babel/plugin-transform-unicode-escapes@^7.18.10":
+ version "7.18.10"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz#1ecfb0eda83d09bbcb77c09970c2dd55832aa246"
+ integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+
+"@babel/plugin-transform-unicode-regex@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca"
+ integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/polyfill@^7.11.5":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.12.1.tgz#1f2d6371d1261bbd961f3c5d5909150e12d0bd96"
+ integrity sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g==
+ dependencies:
+ core-js "^2.6.5"
+ regenerator-runtime "^0.13.4"
+
+"@babel/preset-env@^7.16.5", "@babel/preset-env@^7.16.7":
+ version "7.18.10"
+ resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.10.tgz#83b8dfe70d7eea1aae5a10635ab0a5fe60dfc0f4"
+ integrity sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA==
+ dependencies:
+ "@babel/compat-data" "^7.18.8"
+ "@babel/helper-compilation-targets" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-validator-option" "^7.18.6"
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6"
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9"
+ "@babel/plugin-proposal-async-generator-functions" "^7.18.10"
+ "@babel/plugin-proposal-class-properties" "^7.18.6"
+ "@babel/plugin-proposal-class-static-block" "^7.18.6"
+ "@babel/plugin-proposal-dynamic-import" "^7.18.6"
+ "@babel/plugin-proposal-export-namespace-from" "^7.18.9"
+ "@babel/plugin-proposal-json-strings" "^7.18.6"
+ "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9"
+ "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6"
+ "@babel/plugin-proposal-numeric-separator" "^7.18.6"
+ "@babel/plugin-proposal-object-rest-spread" "^7.18.9"
+ "@babel/plugin-proposal-optional-catch-binding" "^7.18.6"
+ "@babel/plugin-proposal-optional-chaining" "^7.18.9"
+ "@babel/plugin-proposal-private-methods" "^7.18.6"
+ "@babel/plugin-proposal-private-property-in-object" "^7.18.6"
+ "@babel/plugin-proposal-unicode-property-regex" "^7.18.6"
+ "@babel/plugin-syntax-async-generators" "^7.8.4"
+ "@babel/plugin-syntax-class-properties" "^7.12.13"
+ "@babel/plugin-syntax-class-static-block" "^7.14.5"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.3"
+ "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+ "@babel/plugin-syntax-import-assertions" "^7.18.6"
+ "@babel/plugin-syntax-json-strings" "^7.8.3"
+ "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+ "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+ "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
+ "@babel/plugin-syntax-top-level-await" "^7.14.5"
+ "@babel/plugin-transform-arrow-functions" "^7.18.6"
+ "@babel/plugin-transform-async-to-generator" "^7.18.6"
+ "@babel/plugin-transform-block-scoped-functions" "^7.18.6"
+ "@babel/plugin-transform-block-scoping" "^7.18.9"
+ "@babel/plugin-transform-classes" "^7.18.9"
+ "@babel/plugin-transform-computed-properties" "^7.18.9"
+ "@babel/plugin-transform-destructuring" "^7.18.9"
+ "@babel/plugin-transform-dotall-regex" "^7.18.6"
+ "@babel/plugin-transform-duplicate-keys" "^7.18.9"
+ "@babel/plugin-transform-exponentiation-operator" "^7.18.6"
+ "@babel/plugin-transform-for-of" "^7.18.8"
+ "@babel/plugin-transform-function-name" "^7.18.9"
+ "@babel/plugin-transform-literals" "^7.18.9"
+ "@babel/plugin-transform-member-expression-literals" "^7.18.6"
+ "@babel/plugin-transform-modules-amd" "^7.18.6"
+ "@babel/plugin-transform-modules-commonjs" "^7.18.6"
+ "@babel/plugin-transform-modules-systemjs" "^7.18.9"
+ "@babel/plugin-transform-modules-umd" "^7.18.6"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.18.6"
+ "@babel/plugin-transform-new-target" "^7.18.6"
+ "@babel/plugin-transform-object-super" "^7.18.6"
+ "@babel/plugin-transform-parameters" "^7.18.8"
+ "@babel/plugin-transform-property-literals" "^7.18.6"
+ "@babel/plugin-transform-regenerator" "^7.18.6"
+ "@babel/plugin-transform-reserved-words" "^7.18.6"
+ "@babel/plugin-transform-shorthand-properties" "^7.18.6"
+ "@babel/plugin-transform-spread" "^7.18.9"
+ "@babel/plugin-transform-sticky-regex" "^7.18.6"
+ "@babel/plugin-transform-template-literals" "^7.18.9"
+ "@babel/plugin-transform-typeof-symbol" "^7.18.9"
+ "@babel/plugin-transform-unicode-escapes" "^7.18.10"
+ "@babel/plugin-transform-unicode-regex" "^7.18.6"
+ "@babel/preset-modules" "^0.1.5"
+ "@babel/types" "^7.18.10"
+ babel-plugin-polyfill-corejs2 "^0.3.2"
+ babel-plugin-polyfill-corejs3 "^0.5.3"
+ babel-plugin-polyfill-regenerator "^0.4.0"
+ core-js-compat "^3.22.1"
+ semver "^6.3.0"
+
+"@babel/preset-modules@^0.1.5":
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9"
+ integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-proposal-unicode-property-regex" "^7.4.4"
+ "@babel/plugin-transform-dotall-regex" "^7.4.4"
+ "@babel/types" "^7.4.4"
+ esutils "^2.0.2"
+
+"@babel/runtime@7.12.18":
+ version "7.12.18"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.18.tgz#af137bd7e7d9705a412b3caaf991fe6aaa97831b"
+ integrity sha512-BogPQ7ciE6SYAUPtlm9tWbgI9+2AgqSam6QivMgXgAT+fKbgppaj4ZX15MHeLC1PVF5sNk70huBu20XxWOs8Cg==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
+"@babel/runtime@^7.8.4":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
+ integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
+"@babel/template@^7.18.10", "@babel/template@^7.18.6":
+ version "7.18.10"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71"
+ integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==
+ dependencies:
+ "@babel/code-frame" "^7.18.6"
+ "@babel/parser" "^7.18.10"
+ "@babel/types" "^7.18.10"
+
+"@babel/traverse@^7.18.11", "@babel/traverse@^7.18.13", "@babel/traverse@^7.18.9":
+ version "7.18.13"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.13.tgz#5ab59ef51a997b3f10c4587d648b9696b6cb1a68"
+ integrity sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==
+ dependencies:
+ "@babel/code-frame" "^7.18.6"
+ "@babel/generator" "^7.18.13"
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-function-name" "^7.18.9"
+ "@babel/helper-hoist-variables" "^7.18.6"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ "@babel/parser" "^7.18.13"
+ "@babel/types" "^7.18.13"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
+"@babel/types@^7.18.10", "@babel/types@^7.18.13", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.4.4":
+ version "7.18.13"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.13.tgz#30aeb9e514f4100f7c1cb6e5ba472b30e48f519a"
+ integrity sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==
+ dependencies:
+ "@babel/helper-string-parser" "^7.18.10"
+ "@babel/helper-validator-identifier" "^7.18.6"
+ to-fast-properties "^2.0.0"
+
+"@ember-data/rfc395-data@^0.0.4":
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/@ember-data/rfc395-data/-/rfc395-data-0.0.4.tgz#ecb86efdf5d7733a76ff14ea651a1b0ed1f8a843"
+ integrity sha512-tGRdvgC9/QMQSuSuJV45xoyhI0Pzjm7A9o/MVVA3HakXIImJbbzx/k/6dO9CUEQXIyS2y0fW6C1XaYOG7rY0FQ==
+
+"@ember/edition-utils@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@ember/edition-utils/-/edition-utils-1.2.0.tgz#a039f542dc14c8e8299c81cd5abba95e2459cfa6"
+ integrity sha512-VmVq/8saCaPdesQmftPqbFtxJWrzxNGSQ+e8x8LLe3Hjm36pJ04Q8LeORGZkAeOhldoUX9seLGmSaHeXkIqoog==
+
+"@embroider/macros@^1.0.0":
+ version "1.8.3"
+ resolved "https://registry.yarnpkg.com/@embroider/macros/-/macros-1.8.3.tgz#2f0961ab8871f6ad819630208031d705b357757e"
+ integrity sha512-gnIOfTL/pUkoD6oI7JyWOqXlVIUgZM+CnbH10/YNtZr2K0hij9eZQMdgjOZZVgN0rKOFw9dIREqc1ygrJHRYQA==
+ dependencies:
+ "@embroider/shared-internals" "1.8.3"
+ assert-never "^1.2.1"
+ babel-import-util "^1.1.0"
+ ember-cli-babel "^7.26.6"
+ find-up "^5.0.0"
+ lodash "^4.17.21"
+ resolve "^1.20.0"
+ semver "^7.3.2"
+
+"@embroider/shared-internals@1.8.3", "@embroider/shared-internals@^1.0.0":
+ version "1.8.3"
+ resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-1.8.3.tgz#52d868dc80016e9fe983552c0e516f437bf9b9f9"
+ integrity sha512-N5Gho6Qk8z5u+mxLCcMYAoQMbN4MmH+z2jXwQHVs859bxuZTxwF6kKtsybDAASCtd2YGxEmzcc1Ja/wM28824w==
+ dependencies:
+ babel-import-util "^1.1.0"
+ ember-rfc176-data "^0.3.17"
+ fs-extra "^9.1.0"
+ js-string-escape "^1.0.1"
+ lodash "^4.17.21"
+ resolve-package-path "^4.0.1"
+ semver "^7.3.5"
+ typescript-memoize "^1.0.1"
+
+"@jridgewell/gen-mapping@^0.1.0":
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996"
+ integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==
+ dependencies:
+ "@jridgewell/set-array" "^1.0.0"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+
+"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2":
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
+ integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==
+ dependencies:
+ "@jridgewell/set-array" "^1.0.1"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/resolve-uri@^3.0.3":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
+ integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
+
+"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
+ integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
+
+"@jridgewell/source-map@^0.3.2":
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb"
+ integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/sourcemap-codec@^1.4.10":
+ version "1.4.14"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
+ integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
+
+"@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.9":
+ version "0.3.15"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774"
+ integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.0.3"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+
+"@types/eslint-scope@^3.7.3":
+ version "3.7.4"
+ resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16"
+ integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==
+ dependencies:
+ "@types/eslint" "*"
+ "@types/estree" "*"
+
+"@types/eslint@*":
+ version "8.4.6"
+ resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.6.tgz#7976f054c1bccfcf514bff0564c0c41df5c08207"
+ integrity sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==
+ dependencies:
+ "@types/estree" "*"
+ "@types/json-schema" "*"
+
+"@types/estree@*":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2"
+ integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==
+
+"@types/estree@^0.0.51":
+ version "0.0.51"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40"
+ integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==
+
+"@types/fs-extra@^5.0.5":
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.1.0.tgz#2a325ef97901504a3828718c390d34b8426a10a1"
+ integrity sha512-AInn5+UBFIK9FK5xc9yP5e3TQSPNNgjHByqYcj9g5elVBnDQcQL7PlO1CIRy2gWlbwK7UPYqi7vRvFA44dCmYQ==
+ dependencies:
+ "@types/node" "*"
+
+"@types/glob@*":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb"
+ integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==
+ dependencies:
+ "@types/minimatch" "*"
+ "@types/node" "*"
+
+"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
+ version "7.0.11"
+ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
+ integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
+
+"@types/minimatch@*", "@types/minimatch@^3.0.3", "@types/minimatch@^3.0.4":
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40"
+ integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==
+
+"@types/node@*":
+ version "18.7.11"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.11.tgz#486e72cfccde88da24e1f23ff1b7d8bfb64e6250"
+ integrity sha512-KZhFpSLlmK/sdocfSAjqPETTMd0ug6HIMIAwkwUpU79olnZdQtMxpQP+G1wDzCH7na+FltSIhbaZuKdwZ8RDrw==
+
+"@types/rimraf@^2.0.2":
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.5.tgz#368fb04d59630b727fc05a74d2ca557f64a8ef98"
+ integrity sha512-YyP+VfeaqAyFmXoTh3HChxOQMyjByRMsHU7kc5KOJkSlXudhMhQIALbYV7rHh/l8d2lX3VUQzprrcAgWdRuU8g==
+ dependencies:
+ "@types/glob" "*"
+ "@types/node" "*"
+
+"@types/symlink-or-copy@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@types/symlink-or-copy/-/symlink-or-copy-1.2.0.tgz#4151a81b4052c80bc2becbae09f3a9ec010a9c7a"
+ integrity sha512-Lja2xYuuf2B3knEsga8ShbOdsfNOtzT73GyJmZyY7eGl2+ajOqrs8yM5ze0fsSoYwvA6bw7/Qr7OZ7PEEmYwWg==
+
+"@webassemblyjs/ast@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"
+ integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==
+ dependencies:
+ "@webassemblyjs/helper-numbers" "1.11.1"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
+
+"@webassemblyjs/floating-point-hex-parser@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f"
+ integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==
+
+"@webassemblyjs/helper-api-error@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16"
+ integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==
+
+"@webassemblyjs/helper-buffer@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5"
+ integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==
+
+"@webassemblyjs/helper-numbers@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae"
+ integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==
+ dependencies:
+ "@webassemblyjs/floating-point-hex-parser" "1.11.1"
+ "@webassemblyjs/helper-api-error" "1.11.1"
+ "@xtuc/long" "4.2.2"
+
+"@webassemblyjs/helper-wasm-bytecode@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1"
+ integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==
+
+"@webassemblyjs/helper-wasm-section@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a"
+ integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.1"
+ "@webassemblyjs/helper-buffer" "1.11.1"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
+ "@webassemblyjs/wasm-gen" "1.11.1"
+
+"@webassemblyjs/ieee754@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614"
+ integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==
+ dependencies:
+ "@xtuc/ieee754" "^1.2.0"
+
+"@webassemblyjs/leb128@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5"
+ integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==
+ dependencies:
+ "@xtuc/long" "4.2.2"
+
+"@webassemblyjs/utf8@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff"
+ integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==
+
+"@webassemblyjs/wasm-edit@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6"
+ integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.1"
+ "@webassemblyjs/helper-buffer" "1.11.1"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
+ "@webassemblyjs/helper-wasm-section" "1.11.1"
+ "@webassemblyjs/wasm-gen" "1.11.1"
+ "@webassemblyjs/wasm-opt" "1.11.1"
+ "@webassemblyjs/wasm-parser" "1.11.1"
+ "@webassemblyjs/wast-printer" "1.11.1"
+
+"@webassemblyjs/wasm-gen@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76"
+ integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.1"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
+ "@webassemblyjs/ieee754" "1.11.1"
+ "@webassemblyjs/leb128" "1.11.1"
+ "@webassemblyjs/utf8" "1.11.1"
+
+"@webassemblyjs/wasm-opt@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2"
+ integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.1"
+ "@webassemblyjs/helper-buffer" "1.11.1"
+ "@webassemblyjs/wasm-gen" "1.11.1"
+ "@webassemblyjs/wasm-parser" "1.11.1"
+
+"@webassemblyjs/wasm-parser@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199"
+ integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.1"
+ "@webassemblyjs/helper-api-error" "1.11.1"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
+ "@webassemblyjs/ieee754" "1.11.1"
+ "@webassemblyjs/leb128" "1.11.1"
+ "@webassemblyjs/utf8" "1.11.1"
+
+"@webassemblyjs/wast-printer@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0"
+ integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.1"
+ "@xtuc/long" "4.2.2"
+
+"@xtuc/ieee754@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
+ integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==
+
+"@xtuc/long@4.2.2":
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
+ integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
+
+a11y-dialog@7.5.0:
+ version "7.5.0"
+ resolved "https://registry.yarnpkg.com/a11y-dialog/-/a11y-dialog-7.5.0.tgz#1540627b18e3b1e266e0dcbdb5d1e7ac52079fe1"
+ integrity sha512-UF7cy4lfZQtvjRV5N4xdWFba+Pb1qW6FPp0p58dLjMTJ4PwIGGekTbmqUt3etBBRo9HbTqhlNsXQhzIuXeJpng==
+ dependencies:
+ focusable-selectors "^0.3.1"
+
+acorn-import-assertions@^1.7.6:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9"
+ integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==
+
+acorn@^8.5.0, acorn@^8.7.1:
+ version "8.8.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
+ integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
+
+ajv-formats@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520"
+ integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==
+ dependencies:
+ ajv "^8.0.0"
+
+ajv-keywords@^3.5.2:
+ version "3.5.2"
+ resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
+ integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
+
+ajv-keywords@^5.0.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16"
+ integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==
+ dependencies:
+ fast-deep-equal "^3.1.3"
+
+ajv@^6.12.4, ajv@^6.12.5:
+ version "6.12.6"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+ integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.4.1"
+ uri-js "^4.2.2"
+
+ajv@^8.0.0, ajv@^8.8.0:
+ version "8.11.0"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f"
+ integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ json-schema-traverse "^1.0.0"
+ require-from-string "^2.0.2"
+ uri-js "^4.2.2"
+
+amd-name-resolver@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/amd-name-resolver/-/amd-name-resolver-1.3.1.tgz#ffe71c683c6e7191fc4ae1bb3aaed15abea135d9"
+ integrity sha512-26qTEWqZQ+cxSYygZ4Cf8tsjDBLceJahhtewxtKZA3SRa4PluuqYCuheemDQD+7Mf5B7sr+zhTDWAHDh02a1Dw==
+ dependencies:
+ ensure-posix-path "^1.0.1"
+ object-hash "^1.3.1"
+
+ansi-styles@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
+ integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
+ dependencies:
+ color-convert "^1.9.0"
+
+array-equal@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
+ integrity sha512-H3LU5RLiSsGXPhN+Nipar0iR0IofH+8r89G2y1tBKxQ/agagKyAjhkAFDRBfodP2caPrNKHpAWNIM/c9yeL7uA==
+
+assert-never@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/assert-never/-/assert-never-1.2.1.tgz#11f0e363bf146205fb08193b5c7b90f4d1cf44fe"
+ integrity sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==
+
+async-disk-cache@^1.2.1:
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/async-disk-cache/-/async-disk-cache-1.3.5.tgz#cc6206ed79bb6982b878fc52e0505e4f52b62a02"
+ integrity sha512-VZpqfR0R7CEOJZ/0FOTgWq70lCrZyS1rkI8PXugDUkTKyyAUgZ2zQ09gLhMkEn+wN8LYeUTPxZdXtlX/kmbXKQ==
+ dependencies:
+ debug "^2.1.3"
+ heimdalljs "^0.2.3"
+ istextorbinary "2.1.0"
+ mkdirp "^0.5.0"
+ rimraf "^2.5.3"
+ rsvp "^3.0.18"
+ username-sync "^1.0.2"
+
+async-disk-cache@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/async-disk-cache/-/async-disk-cache-2.1.0.tgz#e0f37b187ed8c41a5991518a9556d206ae2843a2"
+ integrity sha512-iH+boep2xivfD9wMaZWkywYIURSmsL96d6MoqrC94BnGSvXE4Quf8hnJiHGFYhw/nLeIa1XyRaf4vvcvkwAefg==
+ dependencies:
+ debug "^4.1.1"
+ heimdalljs "^0.2.3"
+ istextorbinary "^2.5.1"
+ mkdirp "^0.5.0"
+ rimraf "^3.0.0"
+ rsvp "^4.8.5"
+ username-sync "^1.0.2"
+
+async-promise-queue@^1.0.3:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/async-promise-queue/-/async-promise-queue-1.0.5.tgz#cb23bce9fce903a133946a700cc85f27f09ea49d"
+ integrity sha512-xi0aQ1rrjPWYmqbwr18rrSKbSaXIeIwSd1J4KAgVfkq8utNbdZoht7GfvfY6swFUAMJ9obkc4WPJmtGwl+B8dw==
+ dependencies:
+ async "^2.4.1"
+ debug "^2.6.8"
+
+async@^2.4.1:
+ version "2.6.4"
+ resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
+ integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==
+ dependencies:
+ lodash "^4.17.14"
+
+at-least-node@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
+ integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
+
+babel-import-util@^1.1.0, babel-import-util@^1.2.0:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/babel-import-util/-/babel-import-util-1.2.2.tgz#1027560e143a4a68b1758e71d4fadc661614e495"
+ integrity sha512-8HgkHWt5WawRFukO30TuaL9EiDUOdvyKtDwLma4uBNeUSDbOO0/hiPfavrOWxSS6J6TKXfukWHZ3wiqZhJ8ONQ==
+
+babel-loader@^8.0.6:
+ version "8.2.5"
+ resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.5.tgz#d45f585e654d5a5d90f5350a779d7647c5ed512e"
+ integrity sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==
+ dependencies:
+ find-cache-dir "^3.3.1"
+ loader-utils "^2.0.0"
+ make-dir "^3.1.0"
+ schema-utils "^2.6.5"
+
+babel-plugin-debug-macros@^0.3.4:
+ version "0.3.4"
+ resolved "https://registry.yarnpkg.com/babel-plugin-debug-macros/-/babel-plugin-debug-macros-0.3.4.tgz#22961d0cb851a80654cece807a8b4b73d85c6075"
+ integrity sha512-wfel/vb3pXfwIDZUrkoDrn5FHmlWI96PCJ3UCDv2a86poJ3EQrnArNW5KfHSVJ9IOgxHbo748cQt7sDU+0KCEw==
+ dependencies:
+ semver "^5.3.0"
+
+babel-plugin-dynamic-import-node@^2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
+ integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==
+ dependencies:
+ object.assign "^4.1.0"
+
+babel-plugin-ember-data-packages-polyfill@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/babel-plugin-ember-data-packages-polyfill/-/babel-plugin-ember-data-packages-polyfill-0.1.2.tgz#21154c095ddc703722b1fb8bb06c126c0b6d77dc"
+ integrity sha512-kTHnOwoOXfPXi00Z8yAgyD64+jdSXk3pknnS7NlqnCKAU6YDkXZ4Y7irl66kaZjZn0FBBt0P4YOZFZk85jYOww==
+ dependencies:
+ "@ember-data/rfc395-data" "^0.0.4"
+
+babel-plugin-ember-modules-api-polyfill@^3.5.0:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-ember-modules-api-polyfill/-/babel-plugin-ember-modules-api-polyfill-3.5.0.tgz#27b6087fac75661f779f32e60f94b14d0e9f6965"
+ integrity sha512-pJajN/DkQUnStw0Az8c6khVcMQHgzqWr61lLNtVeu0g61LRW0k9jyK7vaedrHDWGe/Qe8sxG5wpiyW9NsMqFzA==
+ dependencies:
+ ember-rfc176-data "^0.3.17"
+
+babel-plugin-ember-template-compilation@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/babel-plugin-ember-template-compilation/-/babel-plugin-ember-template-compilation-1.0.2.tgz#e0695b8ad5a8fe6b2cbdff1eadb01cf402731ad6"
+ integrity sha512-4HBMksmlYsWEf/C/n3uW5rkBRbUp4FNaspzdQTAHgLbfCJnkLze8R6i6sUSge48y/Wne7mx+vcImI1o6rlUwXQ==
+ dependencies:
+ babel-import-util "^1.2.0"
+ line-column "^1.0.2"
+ magic-string "^0.26.0"
+ string.prototype.matchall "^4.0.5"
+
+babel-plugin-htmlbars-inline-precompile@^5.2.1, babel-plugin-htmlbars-inline-precompile@^5.3.0:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-5.3.1.tgz#5ba272e2e4b6221522401f5f1d98a73b1de38787"
+ integrity sha512-QWjjFgSKtSRIcsBhJmEwS2laIdrA6na8HAlc/pEAhjHgQsah/gMiBFRZvbQTy//hWxR4BMwV7/Mya7q5H8uHeA==
+ dependencies:
+ babel-plugin-ember-modules-api-polyfill "^3.5.0"
+ line-column "^1.0.2"
+ magic-string "^0.25.7"
+ parse-static-imports "^1.1.0"
+ string.prototype.matchall "^4.0.5"
+
+babel-plugin-module-resolver@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-module-resolver/-/babel-plugin-module-resolver-3.2.0.tgz#ddfa5e301e3b9aa12d852a9979f18b37881ff5a7"
+ integrity sha512-tjR0GvSndzPew/Iayf4uICWZqjBwnlMWjSx6brryfQ81F9rxBVqwDJtFCV8oOs0+vJeefK9TmdZtkIFdFe1UnA==
+ dependencies:
+ find-babel-config "^1.1.0"
+ glob "^7.1.2"
+ pkg-up "^2.0.0"
+ reselect "^3.0.1"
+ resolve "^1.4.0"
+
+babel-plugin-polyfill-corejs2@^0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz#e4c31d4c89b56f3cf85b92558954c66b54bd972d"
+ integrity sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q==
+ dependencies:
+ "@babel/compat-data" "^7.17.7"
+ "@babel/helper-define-polyfill-provider" "^0.3.2"
+ semver "^6.1.1"
+
+babel-plugin-polyfill-corejs3@^0.5.3:
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz#d7e09c9a899079d71a8b670c6181af56ec19c5c7"
+ integrity sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==
+ dependencies:
+ "@babel/helper-define-polyfill-provider" "^0.3.2"
+ core-js-compat "^3.21.0"
+
+babel-plugin-polyfill-regenerator@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.0.tgz#8f51809b6d5883e07e71548d75966ff7635527fe"
+ integrity sha512-RW1cnryiADFeHmfLS+WW/G431p1PsW5qdRdz0SDRi7TKcUgc7Oh/uXkT7MZ/+tGsT1BkczEAmD5XjUyJ5SWDTw==
+ dependencies:
+ "@babel/helper-define-polyfill-provider" "^0.3.2"
+
+babel-plugin-syntax-dynamic-import@^6.18.0:
+ version "6.18.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da"
+ integrity sha512-MioUE+LfjCEz65Wf7Z/Rm4XCP5k2c+TbMd2Z2JKc7U9uwjBhAfNPE48KC4GTGKhppMeYVepwDBNO/nGY6NYHBA==
+
+balanced-match@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+ integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+big.js@^5.2.2:
+ version "5.2.2"
+ resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
+ integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
+
+"binaryextensions@1 || 2", binaryextensions@^2.1.2:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.3.0.tgz#1d269cbf7e6243ea886aa41453c3651ccbe13c22"
+ integrity sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==
+
+blank-object@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/blank-object/-/blank-object-1.0.2.tgz#f990793fbe9a8c8dd013fb3219420bec81d5f4b9"
+ integrity sha512-kXQ19Xhoghiyw66CUiGypnuRpWlbHAzY/+NyvqTEdTfhfQGH1/dbEMYiXju7fYKIFePpzp/y9dsu5Cu/PkmawQ==
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+ integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+broccoli-babel-transpiler@^7.8.0:
+ version "7.8.1"
+ resolved "https://registry.yarnpkg.com/broccoli-babel-transpiler/-/broccoli-babel-transpiler-7.8.1.tgz#a5dc04cf4f59de98124fc128683ab2b83e5d28c1"
+ integrity sha512-6IXBgfRt7HZ61g67ssBc6lBb3Smw3DPZ9dEYirgtvXWpRZ2A9M22nxy6opEwJDgDJzlu/bB7ToppW33OFkA1gA==
+ dependencies:
+ "@babel/core" "^7.12.0"
+ "@babel/polyfill" "^7.11.5"
+ broccoli-funnel "^2.0.2"
+ broccoli-merge-trees "^3.0.2"
+ broccoli-persistent-filter "^2.2.1"
+ clone "^2.1.2"
+ hash-for-dep "^1.4.7"
+ heimdalljs "^0.2.1"
+ heimdalljs-logger "^0.1.9"
+ json-stable-stringify "^1.0.1"
+ rsvp "^4.8.4"
+ workerpool "^3.1.1"
+
+broccoli-debug@^0.6.4, broccoli-debug@^0.6.5:
+ version "0.6.5"
+ resolved "https://registry.yarnpkg.com/broccoli-debug/-/broccoli-debug-0.6.5.tgz#164a5cdafd8936e525e702bf8f91f39d758e2e78"
+ integrity sha512-RIVjHvNar9EMCLDW/FggxFRXqpjhncM/3qq87bn/y+/zR9tqEkHvTqbyOc4QnB97NO2m6342w4wGkemkaeOuWg==
+ dependencies:
+ broccoli-plugin "^1.2.1"
+ fs-tree-diff "^0.5.2"
+ heimdalljs "^0.2.1"
+ heimdalljs-logger "^0.1.7"
+ symlink-or-copy "^1.1.8"
+ tree-sync "^1.2.2"
+
+broccoli-funnel@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/broccoli-funnel/-/broccoli-funnel-2.0.2.tgz#0edf629569bc10bd02cc525f74b9a38e71366a75"
+ integrity sha512-/vDTqtv7ipjEZQOVqO4vGDVAOZyuYzQ/EgGoyewfOgh1M7IQAToBKZI0oAQPgMBeFPPlIbfMuAngk+ohPBuaHQ==
+ dependencies:
+ array-equal "^1.0.0"
+ blank-object "^1.0.1"
+ broccoli-plugin "^1.3.0"
+ debug "^2.2.0"
+ fast-ordered-set "^1.0.0"
+ fs-tree-diff "^0.5.3"
+ heimdalljs "^0.2.0"
+ minimatch "^3.0.0"
+ mkdirp "^0.5.0"
+ path-posix "^1.0.0"
+ rimraf "^2.4.3"
+ symlink-or-copy "^1.0.0"
+ walk-sync "^0.3.1"
+
+broccoli-funnel@^3.0.8:
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/broccoli-funnel/-/broccoli-funnel-3.0.8.tgz#f5b62e2763c3918026a15a3c833edc889971279b"
+ integrity sha512-ng4eIhPYiXqMw6SyGoxPHR3YAwEd2lr9FgBI1CyTbspl4txZovOsmzFkMkGAlu88xyvYXJqHiM2crfLa65T1BQ==
+ dependencies:
+ array-equal "^1.0.0"
+ broccoli-plugin "^4.0.7"
+ debug "^4.1.1"
+ fs-tree-diff "^2.0.1"
+ heimdalljs "^0.2.0"
+ minimatch "^3.0.0"
+ walk-sync "^2.0.2"
+
+broccoli-kitchen-sink-helpers@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/broccoli-kitchen-sink-helpers/-/broccoli-kitchen-sink-helpers-0.3.1.tgz#77c7c18194b9664163ec4fcee2793444926e0c06"
+ integrity sha512-gqYnKSJxBSjj/uJqeuRAzYVbmjWhG0mOZ8jrp6+fnUIOgLN6MvI7XxBECDHkYMIFPJ8Smf4xaI066Q2FqQDnXg==
+ dependencies:
+ glob "^5.0.10"
+ mkdirp "^0.5.1"
+
+broccoli-merge-trees@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/broccoli-merge-trees/-/broccoli-merge-trees-3.0.2.tgz#f33b451994225522b5c9bcf27d59decfd8ba537d"
+ integrity sha512-ZyPAwrOdlCddduFbsMyyFzJUrvW6b04pMvDiAQZrCwghlvgowJDY+EfoXn+eR1RRA5nmGHJ+B68T63VnpRiT1A==
+ dependencies:
+ broccoli-plugin "^1.3.0"
+ merge-trees "^2.0.0"
+
+broccoli-merge-trees@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/broccoli-merge-trees/-/broccoli-merge-trees-4.2.0.tgz#692d3c163ecea08c5714a9434d664e628919f47c"
+ integrity sha512-nTrQe5AQtCrW4enLRvbD/vTLHqyW2tz+vsLXQe4IEaUhepuMGVKJJr+I8n34Vu6fPjmPLwTjzNC8izMIDMtHPw==
+ dependencies:
+ broccoli-plugin "^4.0.2"
+ merge-trees "^2.0.0"
+
+broccoli-node-api@^1.6.0, broccoli-node-api@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/broccoli-node-api/-/broccoli-node-api-1.7.0.tgz#391aa6edecd2a42c63c111b4162956b2fa288cb6"
+ integrity sha512-QIqLSVJWJUVOhclmkmypJJH9u9s/aWH4+FH6Q6Ju5l+Io4dtwqdPUNmDfw40o6sxhbZHhqGujDJuHTML1wG8Yw==
+
+broccoli-node-info@^2.1.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/broccoli-node-info/-/broccoli-node-info-2.2.0.tgz#feb01c13020792f429e01d7f7845dc5b3a7932b3"
+ integrity sha512-VabSGRpKIzpmC+r+tJueCE5h8k6vON7EIMMWu6d/FyPdtijwLQ7QvzShEw+m3mHoDzUaj/kiZsDYrS8X2adsBg==
+
+broccoli-output-wrapper@^3.2.5:
+ version "3.2.5"
+ resolved "https://registry.yarnpkg.com/broccoli-output-wrapper/-/broccoli-output-wrapper-3.2.5.tgz#514b17801c92922a2c2f87fd145df2a25a11bc5f"
+ integrity sha512-bQAtwjSrF4Nu0CK0JOy5OZqw9t5U0zzv2555EA/cF8/a8SLDTIetk9UgrtMVw7qKLKdSpOZ2liZNeZZDaKgayw==
+ dependencies:
+ fs-extra "^8.1.0"
+ heimdalljs-logger "^0.1.10"
+ symlink-or-copy "^1.2.0"
+
+broccoli-persistent-filter@^2.2.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/broccoli-persistent-filter/-/broccoli-persistent-filter-2.3.1.tgz#4a052e0e0868b344c3a2977e35a3d497aa9eca72"
+ integrity sha512-hVsmIgCDrl2NFM+3Gs4Cr2TA6UPaIZip99hN8mtkaUPgM8UeVnCbxelCvBjUBHo0oaaqP5jzqqnRVvb568Yu5g==
+ dependencies:
+ async-disk-cache "^1.2.1"
+ async-promise-queue "^1.0.3"
+ broccoli-plugin "^1.0.0"
+ fs-tree-diff "^2.0.0"
+ hash-for-dep "^1.5.0"
+ heimdalljs "^0.2.1"
+ heimdalljs-logger "^0.1.7"
+ mkdirp "^0.5.1"
+ promise-map-series "^0.2.1"
+ rimraf "^2.6.1"
+ rsvp "^4.7.0"
+ symlink-or-copy "^1.0.1"
+ sync-disk-cache "^1.3.3"
+ walk-sync "^1.0.0"
+
+broccoli-persistent-filter@^3.1.2:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/broccoli-persistent-filter/-/broccoli-persistent-filter-3.1.3.tgz#aca815bf3e3b0247bd0a7b567fdb0d0e08c99cc2"
+ integrity sha512-Q+8iezprZzL9voaBsDY3rQVl7c7H5h+bvv8SpzCZXPZgfBFCbx7KFQ2c3rZR6lW5k4Kwoqt7jG+rZMUg67Gwxw==
+ dependencies:
+ async-disk-cache "^2.0.0"
+ async-promise-queue "^1.0.3"
+ broccoli-plugin "^4.0.3"
+ fs-tree-diff "^2.0.0"
+ hash-for-dep "^1.5.0"
+ heimdalljs "^0.2.1"
+ heimdalljs-logger "^0.1.7"
+ promise-map-series "^0.2.1"
+ rimraf "^3.0.0"
+ symlink-or-copy "^1.0.1"
+ sync-disk-cache "^2.0.0"
+
+broccoli-plugin@^1.0.0, broccoli-plugin@^1.2.1, broccoli-plugin@^1.3.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/broccoli-plugin/-/broccoli-plugin-1.3.1.tgz#a26315732fb99ed2d9fb58f12a1e14e986b4fabd"
+ integrity sha512-DW8XASZkmorp+q7J4EeDEZz+LoyKLAd2XZULXyD9l4m9/hAKV3vjHmB1kiUshcWAYMgTP1m2i4NnqCE/23h6AQ==
+ dependencies:
+ promise-map-series "^0.2.1"
+ quick-temp "^0.1.3"
+ rimraf "^2.3.4"
+ symlink-or-copy "^1.1.8"
+
+broccoli-plugin@^4.0.0, broccoli-plugin@^4.0.2, broccoli-plugin@^4.0.3, broccoli-plugin@^4.0.7:
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/broccoli-plugin/-/broccoli-plugin-4.0.7.tgz#dd176a85efe915ed557d913744b181abe05047db"
+ integrity sha512-a4zUsWtA1uns1K7p9rExYVYG99rdKeGRymW0qOCNkvDPHQxVi3yVyJHhQbM3EZwdt2E0mnhr5e0c/bPpJ7p3Wg==
+ dependencies:
+ broccoli-node-api "^1.7.0"
+ broccoli-output-wrapper "^3.2.5"
+ fs-merger "^3.2.1"
+ promise-map-series "^0.3.0"
+ quick-temp "^0.1.8"
+ rimraf "^3.0.2"
+ symlink-or-copy "^1.3.1"
+
+broccoli-source@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/broccoli-source/-/broccoli-source-2.1.2.tgz#e9ae834f143b607e9ec114ade66731500c38b90b"
+ integrity sha512-1lLayO4wfS0c0Sj50VfHJXNWf94FYY0WUhxj0R77thbs6uWI7USiOWFqQV5dRmhAJnoKaGN4WyLGQbgjgiYFwQ==
+
+broccoli-source@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/broccoli-source/-/broccoli-source-3.0.1.tgz#fd581b2f3877ca1338f724f6ef70acec8c7e1444"
+ integrity sha512-ZbGVQjivWi0k220fEeIUioN6Y68xjMy0xiLAc0LdieHI99gw+tafU8w0CggBDYVNsJMKUr006AZaM7gNEwCxEg==
+ dependencies:
+ broccoli-node-api "^1.6.0"
+
+browserslist@^4.14.5, browserslist@^4.20.2, browserslist@^4.21.3:
+ version "4.21.3"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a"
+ integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==
+ dependencies:
+ caniuse-lite "^1.0.30001370"
+ electron-to-chromium "^1.4.202"
+ node-releases "^2.0.6"
+ update-browserslist-db "^1.0.5"
+
+buffer-from@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
+ integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
+
+calculate-cache-key-for-tree@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/calculate-cache-key-for-tree/-/calculate-cache-key-for-tree-2.0.0.tgz#7ac57f149a4188eacb0a45b210689215d3fef8d6"
+ integrity sha512-Quw8a6y8CPmRd6eU+mwypktYCwUcf8yVFIRbNZ6tPQEckX9yd+EBVEPC/GSZZrMWH9e7Vz4pT7XhpmyApRByLQ==
+ dependencies:
+ json-stable-stringify "^1.0.1"
+
+call-bind@^1.0.0, call-bind@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
+ integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
+ dependencies:
+ function-bind "^1.1.1"
+ get-intrinsic "^1.0.2"
+
+can-symlink@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/can-symlink/-/can-symlink-1.0.0.tgz#97b607d8a84bb6c6e228b902d864ecb594b9d219"
+ integrity sha512-RbsNrFyhwkx+6psk/0fK/Q9orOUr9VMxohGd8vTa4djf4TGLfblBgUfqZChrZuW0Q+mz2eBPFLusw9Jfukzmhg==
+ dependencies:
+ tmp "0.0.28"
+
+caniuse-lite@^1.0.30001370:
+ version "1.0.30001382"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001382.tgz#4d37f0d0b6fffb826c8e5e1c0f4bf8ce592db949"
+ integrity sha512-2rtJwDmSZ716Pxm1wCtbPvHtbDWAreTPxXbkc5RkKglow3Ig/4GNGazDI9/BVnXbG/wnv6r3B5FEbkfg9OcTGg==
+
+chalk@^2.0.0:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+ integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+ dependencies:
+ ansi-styles "^3.2.1"
+ escape-string-regexp "^1.0.5"
+ supports-color "^5.3.0"
+
+chrome-trace-event@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
+ integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==
+
+clean-up-path@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/clean-up-path/-/clean-up-path-1.0.0.tgz#de9e8196519912e749c9eaf67c13d64fac72a3e5"
+ integrity sha512-PHGlEF0Z6976qQyN6gM7kKH6EH0RdfZcc8V+QhFe36eRxV0SMH5OUBZG7Bxa9YcreNzyNbK63cGiZxdSZgosRw==
+
+clone@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
+ integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==
+
+color-convert@^1.9.0:
+ version "1.9.3"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
+ integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
+ dependencies:
+ color-name "1.1.3"
+
+color-name@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
+ integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
+
+commander@^2.20.0:
+ version "2.20.3"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
+ integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
+
+commondir@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
+ integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+ integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+
+convert-source-map@^1.7.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
+ integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==
+ dependencies:
+ safe-buffer "~5.1.1"
+
+core-js-compat@^3.21.0, core-js-compat@^3.22.1:
+ version "3.24.1"
+ resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.24.1.tgz#d1af84a17e18dfdd401ee39da9996f9a7ba887de"
+ integrity sha512-XhdNAGeRnTpp8xbD+sR/HFDK9CbeeeqXT6TuofXh3urqEevzkWmLRgrVoykodsw8okqo2pu1BOmuCKrHx63zdw==
+ dependencies:
+ browserslist "^4.21.3"
+ semver "7.0.0"
+
+core-js@^2.6.5:
+ version "2.6.12"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
+ integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
+
+css-loader@^5.2.0:
+ version "5.2.7"
+ resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.7.tgz#9b9f111edf6fb2be5dc62525644cbc9c232064ae"
+ integrity sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==
+ dependencies:
+ icss-utils "^5.1.0"
+ loader-utils "^2.0.0"
+ postcss "^8.2.15"
+ postcss-modules-extract-imports "^3.0.0"
+ postcss-modules-local-by-default "^4.0.0"
+ postcss-modules-scope "^3.0.0"
+ postcss-modules-values "^4.0.0"
+ postcss-value-parser "^4.1.0"
+ schema-utils "^3.0.0"
+ semver "^7.3.5"
+
+cssesc@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
+ integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
+
+debug@^2.1.3, debug@^2.2.0, debug@^2.6.8:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
+debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+ dependencies:
+ ms "2.1.2"
+
+define-properties@^1.1.3, define-properties@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1"
+ integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==
+ dependencies:
+ has-property-descriptors "^1.0.0"
+ object-keys "^1.1.1"
+
+editions@^1.1.1:
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b"
+ integrity sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==
+
+editions@^2.2.0:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/editions/-/editions-2.3.1.tgz#3bc9962f1978e801312fbd0aebfed63b49bfe698"
+ integrity sha512-ptGvkwTvGdGfC0hfhKg0MT+TRLRKGtUiWGBInxOm5pz7ssADezahjCUaYuZ8Dr+C05FW0AECIIPt4WBxVINEhA==
+ dependencies:
+ errlop "^2.0.0"
+ semver "^6.3.0"
+
+electron-to-chromium@^1.4.202:
+ version "1.4.227"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.227.tgz#28e46e2a701fed3188db3ca7bf0a3a475e484046"
+ integrity sha512-I9VVajA3oswIJOUFg2PSBqrHLF5Y+ahIfjOV9+v6uYyBqFZutmPxA6fxocDUUmgwYevRWFu1VjLyVG3w45qa/g==
+
+ember-auto-import@^2.4.2:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/ember-auto-import/-/ember-auto-import-2.4.2.tgz#d4d3bc6885a11cf124f606f5c37169bdf76e37ae"
+ integrity sha512-REh+1aJWpTkvN42a/ga41OuRpUsSW7UQfPr2wPtYx56o/xoSNhVBXejy7yV9ObrkN7gogz6fs2xZwih5cOwpYg==
+ dependencies:
+ "@babel/core" "^7.16.7"
+ "@babel/plugin-proposal-class-properties" "^7.16.7"
+ "@babel/plugin-proposal-decorators" "^7.16.7"
+ "@babel/preset-env" "^7.16.7"
+ "@embroider/macros" "^1.0.0"
+ "@embroider/shared-internals" "^1.0.0"
+ babel-loader "^8.0.6"
+ babel-plugin-ember-modules-api-polyfill "^3.5.0"
+ babel-plugin-htmlbars-inline-precompile "^5.2.1"
+ babel-plugin-syntax-dynamic-import "^6.18.0"
+ broccoli-debug "^0.6.4"
+ broccoli-funnel "^3.0.8"
+ broccoli-merge-trees "^4.2.0"
+ broccoli-plugin "^4.0.0"
+ broccoli-source "^3.0.0"
+ css-loader "^5.2.0"
+ debug "^4.3.1"
+ fs-extra "^10.0.0"
+ fs-tree-diff "^2.0.0"
+ handlebars "^4.3.1"
+ js-string-escape "^1.0.1"
+ lodash "^4.17.19"
+ mini-css-extract-plugin "^2.5.2"
+ parse5 "^6.0.1"
+ resolve "^1.20.0"
+ resolve-package-path "^4.0.3"
+ semver "^7.3.4"
+ style-loader "^2.0.0"
+ typescript-memoize "^1.0.0-alpha.3"
+ walk-sync "^3.0.0"
+
+ember-cli-babel-plugin-helpers@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ember-cli-babel-plugin-helpers/-/ember-cli-babel-plugin-helpers-1.1.1.tgz#5016b80cdef37036c4282eef2d863e1d73576879"
+ integrity sha512-sKvOiPNHr5F/60NLd7SFzMpYPte/nnGkq/tMIfXejfKHIhaiIkYFqX8Z9UFTKWLLn+V7NOaby6niNPZUdvKCRw==
+
+ember-cli-babel@^7.26.10, ember-cli-babel@^7.26.6:
+ version "7.26.11"
+ resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.26.11.tgz#50da0fe4dcd99aada499843940fec75076249a9f"
+ integrity sha512-JJYeYjiz/JTn34q7F5DSOjkkZqy8qwFOOxXfE6pe9yEJqWGu4qErKxlz8I22JoVEQ/aBUO+OcKTpmctvykM9YA==
+ dependencies:
+ "@babel/core" "^7.12.0"
+ "@babel/helper-compilation-targets" "^7.12.0"
+ "@babel/plugin-proposal-class-properties" "^7.16.5"
+ "@babel/plugin-proposal-decorators" "^7.13.5"
+ "@babel/plugin-proposal-private-methods" "^7.16.5"
+ "@babel/plugin-proposal-private-property-in-object" "^7.16.5"
+ "@babel/plugin-transform-modules-amd" "^7.13.0"
+ "@babel/plugin-transform-runtime" "^7.13.9"
+ "@babel/plugin-transform-typescript" "^7.13.0"
+ "@babel/polyfill" "^7.11.5"
+ "@babel/preset-env" "^7.16.5"
+ "@babel/runtime" "7.12.18"
+ amd-name-resolver "^1.3.1"
+ babel-plugin-debug-macros "^0.3.4"
+ babel-plugin-ember-data-packages-polyfill "^0.1.2"
+ babel-plugin-ember-modules-api-polyfill "^3.5.0"
+ babel-plugin-module-resolver "^3.2.0"
+ broccoli-babel-transpiler "^7.8.0"
+ broccoli-debug "^0.6.4"
+ broccoli-funnel "^2.0.2"
+ broccoli-source "^2.1.2"
+ calculate-cache-key-for-tree "^2.0.0"
+ clone "^2.1.2"
+ ember-cli-babel-plugin-helpers "^1.1.1"
+ ember-cli-version-checker "^4.1.0"
+ ensure-posix-path "^1.0.2"
+ fixturify-project "^1.10.0"
+ resolve-package-path "^3.1.0"
+ rimraf "^3.0.1"
+ semver "^5.5.0"
+
+ember-cli-htmlbars@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-6.1.0.tgz#97150c2a6f9a981475599d74817df66f5d816c2b"
+ integrity sha512-kT+MA2JsNLk10HpxAjpB5HHtR0WCoxZEUwLsy/BBww5lXmsrf34QzmTw7SL6DabZVxs+YCb9RhU9KTmFygGxCg==
+ dependencies:
+ "@ember/edition-utils" "^1.2.0"
+ babel-plugin-ember-template-compilation "^1.0.0"
+ babel-plugin-htmlbars-inline-precompile "^5.3.0"
+ broccoli-debug "^0.6.5"
+ broccoli-persistent-filter "^3.1.2"
+ broccoli-plugin "^4.0.3"
+ ember-cli-version-checker "^5.1.2"
+ fs-tree-diff "^2.0.1"
+ hash-for-dep "^1.5.1"
+ heimdalljs-logger "^0.1.10"
+ js-string-escape "^1.0.1"
+ semver "^7.3.4"
+ silent-error "^1.1.1"
+ walk-sync "^2.2.0"
+
+ember-cli-version-checker@^4.1.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/ember-cli-version-checker/-/ember-cli-version-checker-4.1.1.tgz#27b938228306cb0dbc4f74e95c536cdd6448e499"
+ integrity sha512-bzEWsTMXUGEJfxcAGWPe6kI7oHEGD3jaxUWDYPTqzqGhNkgPwXTBgoWs9zG1RaSMaOPFnloWuxRcoHi4TrYS3Q==
+ dependencies:
+ resolve-package-path "^2.0.0"
+ semver "^6.3.0"
+ silent-error "^1.1.1"
+
+ember-cli-version-checker@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/ember-cli-version-checker/-/ember-cli-version-checker-5.1.2.tgz#649c7b6404902e3b3d69c396e054cea964911ab0"
+ integrity sha512-rk7GY+FmLn/2e22HsZs0Ycrz8HQ1W3Fv+2TFOuEFW9optnDXDgkntPBIl6gact/LHsfBM5RKbM3dHsIIeLgl0Q==
+ dependencies:
+ resolve-package-path "^3.1.0"
+ semver "^7.3.4"
+ silent-error "^1.1.1"
+
+ember-rfc176-data@^0.3.17:
+ version "0.3.17"
+ resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.17.tgz#d4fc6c33abd6ef7b3440c107a28e04417b49860a"
+ integrity sha512-EVzTTKqxv9FZbEh6Ktw56YyWRAA0MijKvl7H8C06wVF+8f/cRRz3dXxa4nkwjzyVwx4rzKGuIGq77hxJAQhWWw==
+
+emojis-list@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
+ integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
+
+enhanced-resolve@^5.10.0:
+ version "5.10.0"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz#0dc579c3bb2a1032e357ac45b8f3a6f3ad4fb1e6"
+ integrity sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==
+ dependencies:
+ graceful-fs "^4.2.4"
+ tapable "^2.2.0"
+
+ensure-posix-path@^1.0.0, ensure-posix-path@^1.0.1, ensure-posix-path@^1.0.2, ensure-posix-path@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz#3c62bdb19fa4681544289edb2b382adc029179ce"
+ integrity sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw==
+
+errlop@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/errlop/-/errlop-2.2.0.tgz#1ff383f8f917ae328bebb802d6ca69666a42d21b"
+ integrity sha512-e64Qj9+4aZzjzzFpZC7p5kmm/ccCrbLhAJplhsDXQFs87XTsXwOpH4s1Io2s90Tau/8r2j9f4l/thhDevRjzxw==
+
+es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.5:
+ version "1.20.1"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.1.tgz#027292cd6ef44bd12b1913b828116f54787d1814"
+ integrity sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==
+ dependencies:
+ call-bind "^1.0.2"
+ es-to-primitive "^1.2.1"
+ function-bind "^1.1.1"
+ function.prototype.name "^1.1.5"
+ get-intrinsic "^1.1.1"
+ get-symbol-description "^1.0.0"
+ has "^1.0.3"
+ has-property-descriptors "^1.0.0"
+ has-symbols "^1.0.3"
+ internal-slot "^1.0.3"
+ is-callable "^1.2.4"
+ is-negative-zero "^2.0.2"
+ is-regex "^1.1.4"
+ is-shared-array-buffer "^1.0.2"
+ is-string "^1.0.7"
+ is-weakref "^1.0.2"
+ object-inspect "^1.12.0"
+ object-keys "^1.1.1"
+ object.assign "^4.1.2"
+ regexp.prototype.flags "^1.4.3"
+ string.prototype.trimend "^1.0.5"
+ string.prototype.trimstart "^1.0.5"
+ unbox-primitive "^1.0.2"
+
+es-module-lexer@^0.9.0:
+ version "0.9.3"
+ resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19"
+ integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==
+
+es-to-primitive@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
+ integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
+ dependencies:
+ is-callable "^1.1.4"
+ is-date-object "^1.0.1"
+ is-symbol "^1.0.2"
+
+escalade@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
+ integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
+
+escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+ integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
+
+eslint-scope@5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
+ integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
+ dependencies:
+ esrecurse "^4.3.0"
+ estraverse "^4.1.1"
+
+esrecurse@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
+ integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+ dependencies:
+ estraverse "^5.2.0"
+
+estraverse@^4.1.1:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
+ integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
+
+estraverse@^5.2.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
+ integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+
+esutils@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
+ integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+
+events@^3.2.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
+ integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
+
+fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
+ integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+
+fast-json-stable-stringify@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
+ integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
+
+fast-ordered-set@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/fast-ordered-set/-/fast-ordered-set-1.0.3.tgz#3fbb36634f7be79e4f7edbdb4a357dee25d184eb"
+ integrity sha512-MxBW4URybFszOx1YlACEoK52P6lE3xiFcPaGCUZ7QQOZ6uJXKo++Se8wa31SjcZ+NC/fdAWX7UtKEfaGgHS2Vg==
+ dependencies:
+ blank-object "^1.0.1"
+
+find-babel-config@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-1.2.0.tgz#a9b7b317eb5b9860cda9d54740a8c8337a2283a2"
+ integrity sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA==
+ dependencies:
+ json5 "^0.5.1"
+ path-exists "^3.0.0"
+
+find-cache-dir@^3.3.1:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b"
+ integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==
+ dependencies:
+ commondir "^1.0.1"
+ make-dir "^3.0.2"
+ pkg-dir "^4.1.0"
+
+find-up@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
+ integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==
+ dependencies:
+ locate-path "^2.0.0"
+
+find-up@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
+ integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
+ dependencies:
+ locate-path "^5.0.0"
+ path-exists "^4.0.0"
+
+find-up@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
+ integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+ dependencies:
+ locate-path "^6.0.0"
+ path-exists "^4.0.0"
+
+fixturify-project@^1.10.0:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/fixturify-project/-/fixturify-project-1.10.0.tgz#091c452a9bb15f09b6b9cc7cf5c0ad559f1d9aad"
+ integrity sha512-L1k9uiBQuN0Yr8tA9Noy2VSQ0dfg0B8qMdvT7Wb5WQKc7f3dn3bzCbSrqlb+etLW+KDV4cBC7R1OvcMg3kcxmA==
+ dependencies:
+ fixturify "^1.2.0"
+ tmp "^0.0.33"
+
+fixturify@^1.2.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/fixturify/-/fixturify-1.3.0.tgz#163c468093c7c4d90b70cde39fd6325f6528b25d"
+ integrity sha512-tL0svlOy56pIMMUQ4bU1xRe6NZbFSa/ABTWMxW2mH38lFGc9TrNAKWcMBQ7eIjo3wqSS8f2ICabFaatFyFmrVQ==
+ dependencies:
+ "@types/fs-extra" "^5.0.5"
+ "@types/minimatch" "^3.0.3"
+ "@types/rimraf" "^2.0.2"
+ fs-extra "^7.0.1"
+ matcher-collection "^2.0.0"
+
+focusable-selectors@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/focusable-selectors/-/focusable-selectors-0.3.1.tgz#7eacbca8dc6cc8d7f7563e5f5cc3699b91e20aaa"
+ integrity sha512-5JLtr0e1YJIfmnVlpLiG+av07dd0Xkf/KfswsXcei5KmLfdwOysTQsjF058ynXniujb1fvev7nql1x+CkC5ikw==
+
+fs-extra@^10.0.0:
+ version "10.1.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
+ integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
+fs-extra@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
+ integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
+ dependencies:
+ graceful-fs "^4.1.2"
+ jsonfile "^4.0.0"
+ universalify "^0.1.0"
+
+fs-extra@^8.0.1, fs-extra@^8.1.0:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
+ integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^4.0.0"
+ universalify "^0.1.0"
+
+fs-extra@^9.1.0:
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
+ integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
+ dependencies:
+ at-least-node "^1.0.0"
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
+fs-merger@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/fs-merger/-/fs-merger-3.2.1.tgz#a225b11ae530426138294b8fbb19e82e3d4e0b3b"
+ integrity sha512-AN6sX12liy0JE7C2evclwoo0aCG3PFulLjrTLsJpWh/2mM+DinhpSGqYLbHBBbIW1PLRNcFhJG8Axtz8mQW3ug==
+ dependencies:
+ broccoli-node-api "^1.7.0"
+ broccoli-node-info "^2.1.0"
+ fs-extra "^8.0.1"
+ fs-tree-diff "^2.0.1"
+ walk-sync "^2.2.0"
+
+fs-tree-diff@^0.5.2, fs-tree-diff@^0.5.3, fs-tree-diff@^0.5.6:
+ version "0.5.9"
+ resolved "https://registry.yarnpkg.com/fs-tree-diff/-/fs-tree-diff-0.5.9.tgz#a4ec6182c2f5bd80b9b83c8e23e4522e6f5fd946"
+ integrity sha512-872G8ax0kHh01m9n/2KDzgYwouKza0Ad9iFltBpNykvROvf2AGtoOzPJgGx125aolGPER3JuC7uZFrQ7bG1AZw==
+ dependencies:
+ heimdalljs-logger "^0.1.7"
+ object-assign "^4.1.0"
+ path-posix "^1.0.0"
+ symlink-or-copy "^1.1.8"
+
+fs-tree-diff@^2.0.0, fs-tree-diff@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/fs-tree-diff/-/fs-tree-diff-2.0.1.tgz#343e4745ab435ec39ebac5f9059ad919cd034afa"
+ integrity sha512-x+CfAZ/lJHQqwlD64pYM5QxWjzWhSjroaVsr8PW831zOApL55qPibed0c+xebaLWVr2BnHFoHdrwOv8pzt8R5A==
+ dependencies:
+ "@types/symlink-or-copy" "^1.2.0"
+ heimdalljs-logger "^0.1.7"
+ object-assign "^4.1.0"
+ path-posix "^1.0.0"
+ symlink-or-copy "^1.1.8"
+
+fs-updater@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/fs-updater/-/fs-updater-1.0.4.tgz#2329980f99ae9176e9a0e84f7637538a182ce63b"
+ integrity sha512-0pJX4mJF/qLsNEwTct8CdnnRdagfb+LmjRPJ8sO+nCnAZLW0cTmz4rTgU25n+RvTuWSITiLKrGVJceJPBIPlKg==
+ dependencies:
+ can-symlink "^1.0.0"
+ clean-up-path "^1.0.0"
+ heimdalljs "^0.2.5"
+ heimdalljs-logger "^0.1.9"
+ rimraf "^2.6.2"
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+ integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
+
+function-bind@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+ integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+function.prototype.name@^1.1.5:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621"
+ integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.3"
+ es-abstract "^1.19.0"
+ functions-have-names "^1.2.2"
+
+functions-have-names@^1.2.2:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
+ integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
+
+gensync@^1.0.0-beta.2:
+ version "1.0.0-beta.2"
+ resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
+ integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
+
+get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598"
+ integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==
+ dependencies:
+ function-bind "^1.1.1"
+ has "^1.0.3"
+ has-symbols "^1.0.3"
+
+get-symbol-description@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6"
+ integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==
+ dependencies:
+ call-bind "^1.0.2"
+ get-intrinsic "^1.1.1"
+
+glob-to-regexp@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
+ integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
+
+glob@^5.0.10:
+ version "5.0.15"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
+ integrity sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==
+ dependencies:
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "2 || 3"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+glob@^7.1.2, glob@^7.1.3:
+ version "7.2.3"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
+ integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.1.1"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+globals@^11.1.0:
+ version "11.12.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
+ integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
+
+graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9:
+ version "4.2.10"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
+ integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
+
+handlebars@^4.3.1:
+ version "4.7.7"
+ resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
+ integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==
+ dependencies:
+ minimist "^1.2.5"
+ neo-async "^2.6.0"
+ source-map "^0.6.1"
+ wordwrap "^1.0.0"
+ optionalDependencies:
+ uglify-js "^3.1.4"
+
+has-bigints@^1.0.1, has-bigints@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
+ integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==
+
+has-flag@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+ integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
+
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
+has-property-descriptors@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861"
+ integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==
+ dependencies:
+ get-intrinsic "^1.1.1"
+
+has-symbols@^1.0.2, has-symbols@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
+ integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+
+has-tostringtag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
+ integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==
+ dependencies:
+ has-symbols "^1.0.2"
+
+has@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+ integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+ dependencies:
+ function-bind "^1.1.1"
+
+hash-for-dep@^1.4.7, hash-for-dep@^1.5.0, hash-for-dep@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/hash-for-dep/-/hash-for-dep-1.5.1.tgz#497754b39bee2f1c4ade4521bfd2af0a7c1196e3"
+ integrity sha512-/dQ/A2cl7FBPI2pO0CANkvuuVi/IFS5oTyJ0PsOb6jW6WbVW1js5qJXMJTNbWHXBIPdFTWFbabjB+mE0d+gelw==
+ dependencies:
+ broccoli-kitchen-sink-helpers "^0.3.1"
+ heimdalljs "^0.2.3"
+ heimdalljs-logger "^0.1.7"
+ path-root "^0.1.1"
+ resolve "^1.10.0"
+ resolve-package-path "^1.0.11"
+
+heimdalljs-logger@^0.1.10, heimdalljs-logger@^0.1.7, heimdalljs-logger@^0.1.9:
+ version "0.1.10"
+ resolved "https://registry.yarnpkg.com/heimdalljs-logger/-/heimdalljs-logger-0.1.10.tgz#90cad58aabb1590a3c7e640ddc6a4cd3a43faaf7"
+ integrity sha512-pO++cJbhIufVI/fmB/u2Yty3KJD0TqNPecehFae0/eps0hkZ3b4Zc/PezUMOpYuHFQbA7FxHZxa305EhmjLj4g==
+ dependencies:
+ debug "^2.2.0"
+ heimdalljs "^0.2.6"
+
+heimdalljs@^0.2.0, heimdalljs@^0.2.1, heimdalljs@^0.2.3, heimdalljs@^0.2.5, heimdalljs@^0.2.6:
+ version "0.2.6"
+ resolved "https://registry.yarnpkg.com/heimdalljs/-/heimdalljs-0.2.6.tgz#b0eebabc412813aeb9542f9cc622cb58dbdcd9fe"
+ integrity sha512-o9bd30+5vLBvBtzCPwwGqpry2+n0Hi6H1+qwt6y+0kwRHGGF8TFIhJPmnuM0xO97zaKrDZMwO/V56fAnn8m/tA==
+ dependencies:
+ rsvp "~3.2.1"
+
+icss-utils@^5.0.0, icss-utils@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
+ integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+internal-slot@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"
+ integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==
+ dependencies:
+ get-intrinsic "^1.1.0"
+ has "^1.0.3"
+ side-channel "^1.0.4"
+
+is-bigint@^1.0.1:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
+ integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==
+ dependencies:
+ has-bigints "^1.0.1"
+
+is-boolean-object@^1.1.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719"
+ integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==
+ dependencies:
+ call-bind "^1.0.2"
+ has-tostringtag "^1.0.0"
+
+is-callable@^1.1.4, is-callable@^1.2.4:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945"
+ integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==
+
+is-core-module@^2.9.0:
+ version "2.10.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed"
+ integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==
+ dependencies:
+ has "^1.0.3"
+
+is-date-object@^1.0.1:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f"
+ integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==
+ dependencies:
+ has-tostringtag "^1.0.0"
+
+is-negative-zero@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150"
+ integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==
+
+is-number-object@^1.0.4:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc"
+ integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==
+ dependencies:
+ has-tostringtag "^1.0.0"
+
+is-regex@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
+ integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
+ dependencies:
+ call-bind "^1.0.2"
+ has-tostringtag "^1.0.0"
+
+is-shared-array-buffer@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79"
+ integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==
+ dependencies:
+ call-bind "^1.0.2"
+
+is-string@^1.0.5, is-string@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd"
+ integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==
+ dependencies:
+ has-tostringtag "^1.0.0"
+
+is-symbol@^1.0.2, is-symbol@^1.0.3:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
+ integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==
+ dependencies:
+ has-symbols "^1.0.2"
+
+is-weakref@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2"
+ integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==
+ dependencies:
+ call-bind "^1.0.2"
+
+isarray@1.0.0, isarray@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+ integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
+
+isobject@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
+ integrity sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==
+ dependencies:
+ isarray "1.0.0"
+
+istextorbinary@2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/istextorbinary/-/istextorbinary-2.1.0.tgz#dbed2a6f51be2f7475b68f89465811141b758874"
+ integrity sha512-kT1g2zxZ5Tdabtpp9VSdOzW9lb6LXImyWbzbQeTxoRtHhurC9Ej9Wckngr2+uepPL09ky/mJHmN9jeJPML5t6A==
+ dependencies:
+ binaryextensions "1 || 2"
+ editions "^1.1.1"
+ textextensions "1 || 2"
+
+istextorbinary@^2.5.1:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/istextorbinary/-/istextorbinary-2.6.0.tgz#60776315fb0fa3999add276c02c69557b9ca28ab"
+ integrity sha512-+XRlFseT8B3L9KyjxxLjfXSLMuErKDsd8DBNrsaxoViABMEZlOSCstwmw0qpoFX3+U6yWU1yhLudAe6/lETGGA==
+ dependencies:
+ binaryextensions "^2.1.2"
+ editions "^2.2.0"
+ textextensions "^2.5.0"
+
+jest-worker@^27.4.5:
+ version "27.5.1"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0"
+ integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==
+ dependencies:
+ "@types/node" "*"
+ merge-stream "^2.0.0"
+ supports-color "^8.0.0"
+
+js-string-escape@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef"
+ integrity sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==
+
+js-tokens@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+ integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+jsesc@^2.5.1:
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
+ integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
+
+jsesc@~0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
+ integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==
+
+json-parse-even-better-errors@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
+ integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
+
+json-schema-traverse@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
+ integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
+json-schema-traverse@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
+ integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
+
+json-stable-stringify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
+ integrity sha512-i/J297TW6xyj7sDFa7AmBPkQvLIxWr2kKPWI26tXydnZrzVAocNqn5DMNT1Mzk0vit1V5UkRM7C1KdVNp7Lmcg==
+ dependencies:
+ jsonify "~0.0.0"
+
+json5@^0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
+ integrity sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==
+
+json5@^2.1.2, json5@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
+ integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
+
+jsonfile@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
+ integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
+jsonfile@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
+ integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
+ dependencies:
+ universalify "^2.0.0"
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
+jsonify@~0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
+ integrity sha512-trvBk1ki43VZptdBI5rIlG4YOzyeH/WefQt5rj1grasPn4iiZWKet8nkgc4GlsAylaztn0qZfUYOiTsASJFdNA==
+
+line-column@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/line-column/-/line-column-1.0.2.tgz#d25af2936b6f4849172b312e4792d1d987bc34a2"
+ integrity sha512-Ktrjk5noGYlHsVnYWh62FLVs4hTb8A3e+vucNZMgPeAOITdshMSgv4cCZQeRDjm7+goqmo6+liZwTXo+U3sVww==
+ dependencies:
+ isarray "^1.0.0"
+ isobject "^2.0.0"
+
+loader-runner@^4.2.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1"
+ integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
+
+loader-utils@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
+ integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
+ dependencies:
+ big.js "^5.2.2"
+ emojis-list "^3.0.0"
+ json5 "^2.1.2"
+
+locate-path@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
+ integrity sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==
+ dependencies:
+ p-locate "^2.0.0"
+ path-exists "^3.0.0"
+
+locate-path@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
+ integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
+ dependencies:
+ p-locate "^4.1.0"
+
+locate-path@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
+ integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+ dependencies:
+ p-locate "^5.0.0"
+
+lodash.debounce@^4.0.8:
+ version "4.0.8"
+ resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
+ integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
+
+lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+lru-cache@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
+ integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
+ dependencies:
+ yallist "^4.0.0"
+
+magic-string@^0.25.7:
+ version "0.25.9"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
+ integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==
+ dependencies:
+ sourcemap-codec "^1.4.8"
+
+magic-string@^0.26.0:
+ version "0.26.2"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.26.2.tgz#5331700e4158cd6befda738bb6b0c7b93c0d4432"
+ integrity sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==
+ dependencies:
+ sourcemap-codec "^1.4.8"
+
+make-dir@^3.0.2, make-dir@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
+ integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
+ dependencies:
+ semver "^6.0.0"
+
+matcher-collection@^1.0.0, matcher-collection@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/matcher-collection/-/matcher-collection-1.1.2.tgz#1076f506f10ca85897b53d14ef54f90a5c426838"
+ integrity sha512-YQ/teqaOIIfUHedRam08PB3NK7Mjct6BvzRnJmpGDm8uFXpNr1sbY4yuflI5JcEs6COpYA0FpRQhSDBf1tT95g==
+ dependencies:
+ minimatch "^3.0.2"
+
+matcher-collection@^2.0.0, matcher-collection@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/matcher-collection/-/matcher-collection-2.0.1.tgz#90be1a4cf58d6f2949864f65bb3b0f3e41303b29"
+ integrity sha512-daE62nS2ZQsDg9raM0IlZzLmI2u+7ZapXBwdoeBUKAYERPDDIc0qNqA8E0Rp2D+gspKR7BgIFP52GeujaGXWeQ==
+ dependencies:
+ "@types/minimatch" "^3.0.3"
+ minimatch "^3.0.2"
+
+merge-stream@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
+ integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+
+merge-trees@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/merge-trees/-/merge-trees-2.0.0.tgz#a560d796e566c5d9b2c40472a2967cca48d85161"
+ integrity sha512-5xBbmqYBalWqmhYm51XlohhkmVOua3VAUrrWh8t9iOkaLpS6ifqm/UVuUjQCeDVJ9Vx3g2l6ihfkbLSTeKsHbw==
+ dependencies:
+ fs-updater "^1.0.4"
+ heimdalljs "^0.2.5"
+
+mime-db@1.52.0:
+ version "1.52.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.27:
+ version "2.1.35"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
+mini-css-extract-plugin@^2.5.2:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz#9a1251d15f2035c342d99a468ab9da7a0451b71e"
+ integrity sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg==
+ dependencies:
+ schema-utils "^4.0.0"
+
+"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+ integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimist@^1.2.5, minimist@^1.2.6:
+ version "1.2.6"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
+ integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
+
+mkdirp@^0.5.0, mkdirp@^0.5.1:
+ version "0.5.6"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
+ integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
+ dependencies:
+ minimist "^1.2.6"
+
+mktemp@~0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/mktemp/-/mktemp-0.4.0.tgz#6d0515611c8a8c84e484aa2000129b98e981ff0b"
+ integrity sha512-IXnMcJ6ZyTuhRmJSjzvHSRhlVPiN9Jwc6e59V0bEJ0ba6OBeX2L0E+mRN1QseeOF4mM+F1Rit6Nh7o+rl2Yn/A==
+
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+ integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
+
+ms@2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+nanoid@^3.3.4:
+ version "3.3.4"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
+ integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
+
+neo-async@^2.6.0, neo-async@^2.6.2:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
+ integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
+
+node-releases@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
+ integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==
+
+object-assign@4.1.1, object-assign@^4.1.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+ integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
+
+object-hash@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df"
+ integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==
+
+object-inspect@^1.12.0, object-inspect@^1.9.0:
+ version "1.12.2"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea"
+ integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==
+
+object-keys@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
+ integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
+
+object.assign@^4.1.0, object.assign@^4.1.2:
+ version "4.1.4"
+ resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f"
+ integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.4"
+ has-symbols "^1.0.3"
+ object-keys "^1.1.1"
+
+once@^1.3.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
+ dependencies:
+ wrappy "1"
+
+os-tmpdir@~1.0.1, os-tmpdir@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
+ integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
+
+p-limit@^1.1.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
+ integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==
+ dependencies:
+ p-try "^1.0.0"
+
+p-limit@^2.2.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
+ integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
+ dependencies:
+ p-try "^2.0.0"
+
+p-limit@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
+ integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
+ dependencies:
+ yocto-queue "^0.1.0"
+
+p-locate@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
+ integrity sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==
+ dependencies:
+ p-limit "^1.1.0"
+
+p-locate@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
+ integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
+ dependencies:
+ p-limit "^2.2.0"
+
+p-locate@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
+ integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+ dependencies:
+ p-limit "^3.0.2"
+
+p-try@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
+ integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==
+
+p-try@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
+ integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
+
+parse-static-imports@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/parse-static-imports/-/parse-static-imports-1.1.0.tgz#ae2f18f18da1a993080ae406a5219455c0bbad5d"
+ integrity sha512-HlxrZcISCblEV0lzXmAHheH/8qEkKgmqkdxyHTPbSqsTUV8GzqmN1L+SSti+VbNPfbBO3bYLPHDiUs2avbAdbA==
+
+parse5@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
+ integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
+
+path-exists@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
+ integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==
+
+path-exists@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
+ integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+ integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
+
+path-parse@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
+ integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+
+path-posix@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/path-posix/-/path-posix-1.0.0.tgz#06b26113f56beab042545a23bfa88003ccac260f"
+ integrity sha512-1gJ0WpNIiYcQydgg3Ed8KzvIqTsDpNwq+cjBCssvBtuTWjEqY1AW+i+OepiEMqDCzyro9B2sLAe4RBPajMYFiA==
+
+path-root-regex@^0.1.0:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d"
+ integrity sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==
+
+path-root@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7"
+ integrity sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==
+ dependencies:
+ path-root-regex "^0.1.0"
+
+picocolors@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
+ integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
+
+pkg-dir@^4.1.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
+ integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
+ dependencies:
+ find-up "^4.0.0"
+
+pkg-up@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f"
+ integrity sha512-fjAPuiws93rm7mPUu21RdBnkeZNrbfCFCwfAhPWY+rR3zG0ubpe5cEReHOw5fIbfmsxEV/g2kSxGTATY3Bpnwg==
+ dependencies:
+ find-up "^2.1.0"
+
+postcss-modules-extract-imports@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d"
+ integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==
+
+postcss-modules-local-by-default@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c"
+ integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==
+ dependencies:
+ icss-utils "^5.0.0"
+ postcss-selector-parser "^6.0.2"
+ postcss-value-parser "^4.1.0"
+
+postcss-modules-scope@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06"
+ integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==
+ dependencies:
+ postcss-selector-parser "^6.0.4"
+
+postcss-modules-values@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c"
+ integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==
+ dependencies:
+ icss-utils "^5.0.0"
+
+postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
+ version "6.0.10"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
+ integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
+ dependencies:
+ cssesc "^3.0.0"
+ util-deprecate "^1.0.2"
+
+postcss-value-parser@^4.1.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
+ integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
+
+postcss@^8.2.15:
+ version "8.4.16"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c"
+ integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==
+ dependencies:
+ nanoid "^3.3.4"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
+
+promise-map-series@^0.2.1:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/promise-map-series/-/promise-map-series-0.2.3.tgz#c2d377afc93253f6bd03dbb77755eb88ab20a847"
+ integrity sha512-wx9Chrutvqu1N/NHzTayZjE1BgIwt6SJykQoCOic4IZ9yUDjKyVYrpLa/4YCNsV61eRENfs29hrEquVuB13Zlw==
+ dependencies:
+ rsvp "^3.0.14"
+
+promise-map-series@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/promise-map-series/-/promise-map-series-0.3.0.tgz#41873ca3652bb7a042b387d538552da9b576f8a1"
+ integrity sha512-3npG2NGhTc8BWBolLLf8l/92OxMGaRLbqvIh9wjCHhDXNvk4zsxaTaCpiCunW09qWPrN2zeNSNwRLVBrQQtutA==
+
+punycode@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
+ integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
+
+quick-temp@^0.1.3, quick-temp@^0.1.5, quick-temp@^0.1.8:
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/quick-temp/-/quick-temp-0.1.8.tgz#bab02a242ab8fb0dd758a3c9776b32f9a5d94408"
+ integrity sha512-YsmIFfD9j2zaFwJkzI6eMG7y0lQP7YeWzgtFgNl38pGWZBSXJooZbOWwkcRot7Vt0Fg9L23pX0tqWU3VvLDsiA==
+ dependencies:
+ mktemp "~0.4.0"
+ rimraf "^2.5.4"
+ underscore.string "~3.3.4"
+
+randombytes@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
+ integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
+ dependencies:
+ safe-buffer "^5.1.0"
+
+regenerate-unicode-properties@^10.0.1:
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56"
+ integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==
+ dependencies:
+ regenerate "^1.4.2"
+
+regenerate@^1.4.2:
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
+ integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
+
+regenerator-runtime@^0.13.4:
+ version "0.13.9"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
+ integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
+
+regenerator-transform@^0.15.0:
+ version "0.15.0"
+ resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537"
+ integrity sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==
+ dependencies:
+ "@babel/runtime" "^7.8.4"
+
+regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3:
+ version "1.4.3"
+ resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac"
+ integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.3"
+ functions-have-names "^1.2.2"
+
+regexpu-core@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.1.0.tgz#2f8504c3fd0ebe11215783a41541e21c79942c6d"
+ integrity sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA==
+ dependencies:
+ regenerate "^1.4.2"
+ regenerate-unicode-properties "^10.0.1"
+ regjsgen "^0.6.0"
+ regjsparser "^0.8.2"
+ unicode-match-property-ecmascript "^2.0.0"
+ unicode-match-property-value-ecmascript "^2.0.0"
+
+regjsgen@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d"
+ integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==
+
+regjsparser@^0.8.2:
+ version "0.8.4"
+ resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f"
+ integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==
+ dependencies:
+ jsesc "~0.5.0"
+
+require-from-string@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
+ integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+
+reselect@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147"
+ integrity sha512-b/6tFZCmRhtBMa4xGqiiRp9jh9Aqi2A687Lo265cN0/QohJQEBPiQ52f4QB6i0eF3yp3hmLL21LSGBcML2dlxA==
+
+resolve-package-path@^1.0.11:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/resolve-package-path/-/resolve-package-path-1.2.7.tgz#2a7bc37ad96865e239330e3102c31322847e652e"
+ integrity sha512-fVEKHGeK85bGbVFuwO9o1aU0n3vqQGrezPc51JGu9UTXpFQfWq5qCeKxyaRUSvephs+06c5j5rPq/dzHGEo8+Q==
+ dependencies:
+ path-root "^0.1.1"
+ resolve "^1.10.0"
+
+resolve-package-path@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-package-path/-/resolve-package-path-2.0.0.tgz#7f258ab86ff074fff4ff8027a28f94d17d6fb1df"
+ integrity sha512-/CLuzodHO2wyyHTzls5Qr+EFeG6RcW4u6//gjYvUfcfyuplIX1SSccU+A5A9A78Gmezkl3NBkFAMxLbzTY9TJA==
+ dependencies:
+ path-root "^0.1.1"
+ resolve "^1.13.1"
+
+resolve-package-path@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/resolve-package-path/-/resolve-package-path-3.1.0.tgz#35faaa5d54a9c7dd481eb7c4b2a44410c9c763d8"
+ integrity sha512-2oC2EjWbMJwvSN6Z7DbDfJMnD8MYEouaLn5eIX0j8XwPsYCVIyY9bbnX88YHVkbr8XHqvZrYbxaLPibfTYKZMA==
+ dependencies:
+ path-root "^0.1.1"
+ resolve "^1.17.0"
+
+resolve-package-path@^4.0.1, resolve-package-path@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/resolve-package-path/-/resolve-package-path-4.0.3.tgz#31dab6897236ea6613c72b83658d88898a9040aa"
+ integrity sha512-SRpNAPW4kewOaNUt8VPqhJ0UMxawMwzJD8V7m1cJfdSTK9ieZwS6K7Dabsm4bmLFM96Z5Y/UznrpG5kt1im8yA==
+ dependencies:
+ path-root "^0.1.1"
+
+resolve@^1.10.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.20.0, resolve@^1.4.0:
+ version "1.22.1"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
+ integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
+ dependencies:
+ is-core-module "^2.9.0"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
+rimraf@^2.2.8, rimraf@^2.3.4, rimraf@^2.4.3, rimraf@^2.5.3, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2:
+ version "2.7.1"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
+ integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
+ dependencies:
+ glob "^7.1.3"
+
+rimraf@^3.0.0, rimraf@^3.0.1, rimraf@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
+ integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
+ dependencies:
+ glob "^7.1.3"
+
+rsvp@^3.0.14, rsvp@^3.0.18:
+ version "3.6.2"
+ resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a"
+ integrity sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==
+
+rsvp@^4.7.0, rsvp@^4.8.4, rsvp@^4.8.5:
+ version "4.8.5"
+ resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
+ integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
+
+rsvp@~3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.2.1.tgz#07cb4a5df25add9e826ebc67dcc9fd89db27d84a"
+ integrity sha512-Rf4YVNYpKjZ6ASAmibcwTNciQ5Co5Ztq6iZPEykHpkoflnD/K5ryE/rHehFsTm4NJj8nKDhbi3eKBWGogmNnkg==
+
+safe-buffer@^5.1.0:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+schema-utils@^2.6.5:
+ version "2.7.1"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7"
+ integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==
+ dependencies:
+ "@types/json-schema" "^7.0.5"
+ ajv "^6.12.4"
+ ajv-keywords "^3.5.2"
+
+schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281"
+ integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==
+ dependencies:
+ "@types/json-schema" "^7.0.8"
+ ajv "^6.12.5"
+ ajv-keywords "^3.5.2"
+
+schema-utils@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7"
+ integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==
+ dependencies:
+ "@types/json-schema" "^7.0.9"
+ ajv "^8.8.0"
+ ajv-formats "^2.1.1"
+ ajv-keywords "^5.0.0"
+
+semver@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
+ integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
+
+semver@^5.3.0, semver@^5.5.0:
+ version "5.7.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+ integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
+semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
+ integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+
+semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
+ version "7.3.7"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
+ integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
+ dependencies:
+ lru-cache "^6.0.0"
+
+serialize-javascript@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
+ integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==
+ dependencies:
+ randombytes "^2.1.0"
+
+side-channel@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
+ integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
+ dependencies:
+ call-bind "^1.0.0"
+ get-intrinsic "^1.0.2"
+ object-inspect "^1.9.0"
+
+silent-error@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/silent-error/-/silent-error-1.1.1.tgz#f72af5b0d73682a2ba1778b7e32cd8aa7c2d8662"
+ integrity sha512-n4iEKyNcg4v6/jpb3c0/iyH2G1nzUNl7Gpqtn/mHIJK9S/q/7MCfoO4rwVOoO59qPFIc0hVHvMbiOJ0NdtxKKw==
+ dependencies:
+ debug "^2.2.0"
+
+source-map-js@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
+ integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
+
+source-map-support@~0.5.20:
+ version "0.5.21"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
+ integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
+ dependencies:
+ buffer-from "^1.0.0"
+ source-map "^0.6.0"
+
+source-map@^0.6.0, source-map@^0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+ integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+sourcemap-codec@^1.4.8:
+ version "1.4.8"
+ resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
+ integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
+
+sprintf-js@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"
+ integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
+
+string.prototype.matchall@^4.0.5:
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz#8e6ecb0d8a1fb1fda470d81acecb2dba057a481d"
+ integrity sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.3"
+ es-abstract "^1.19.1"
+ get-intrinsic "^1.1.1"
+ has-symbols "^1.0.3"
+ internal-slot "^1.0.3"
+ regexp.prototype.flags "^1.4.1"
+ side-channel "^1.0.4"
+
+string.prototype.trimend@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0"
+ integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.4"
+ es-abstract "^1.19.5"
+
+string.prototype.trimstart@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef"
+ integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.4"
+ es-abstract "^1.19.5"
+
+style-loader@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c"
+ integrity sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==
+ dependencies:
+ loader-utils "^2.0.0"
+ schema-utils "^3.0.0"
+
+supports-color@^5.3.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
+ integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
+ dependencies:
+ has-flag "^3.0.0"
+
+supports-color@^8.0.0:
+ version "8.1.1"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
+ integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
+ dependencies:
+ has-flag "^4.0.0"
+
+supports-preserve-symlinks-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
+ integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+
+symlink-or-copy@^1.0.0, symlink-or-copy@^1.0.1, symlink-or-copy@^1.1.8, symlink-or-copy@^1.2.0, symlink-or-copy@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/symlink-or-copy/-/symlink-or-copy-1.3.1.tgz#9506dd64d8e98fa21dcbf4018d1eab23e77f71fe"
+ integrity sha512-0K91MEXFpBUaywiwSSkmKjnGcasG/rVBXFLJz5DrgGabpYD6N+3yZrfD6uUIfpuTu65DZLHi7N8CizHc07BPZA==
+
+sync-disk-cache@^1.3.3:
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/sync-disk-cache/-/sync-disk-cache-1.3.4.tgz#53a2c5a09d8f4bb53160bce182a456ad71574024"
+ integrity sha512-GlkGeM81GPPEKz/lH7QUTbvqLq7K/IUTuaKDSMulP9XQ42glqNJIN/RKgSOw4y8vxL1gOVvj+W7ruEO4s36eCw==
+ dependencies:
+ debug "^2.1.3"
+ heimdalljs "^0.2.3"
+ mkdirp "^0.5.0"
+ rimraf "^2.2.8"
+ username-sync "^1.0.2"
+
+sync-disk-cache@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/sync-disk-cache/-/sync-disk-cache-2.1.0.tgz#01e879edc41c34a01fcdda5b39d47dd496e154a6"
+ integrity sha512-vngT2JmkSapgq0z7uIoYtB9kWOOzMihAAYq/D3Pjm/ODOGMgS4r++B+OZ09U4hWR6EaOdy9eqQ7/8ygbH3wehA==
+ dependencies:
+ debug "^4.1.1"
+ heimdalljs "^0.2.6"
+ mkdirp "^0.5.0"
+ rimraf "^3.0.0"
+ username-sync "^1.0.2"
+
+tapable@^2.1.1, tapable@^2.2.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
+ integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
+
+terser-webpack-plugin@^5.1.3:
+ version "5.3.5"
+ resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.5.tgz#f7d82286031f915a4f8fb81af4bd35d2e3c011bc"
+ integrity sha512-AOEDLDxD2zylUGf/wxHxklEkOe2/r+seuyOWujejFrIxHf11brA1/dWQNIgXa1c6/Wkxgu7zvv0JhOWfc2ELEA==
+ dependencies:
+ "@jridgewell/trace-mapping" "^0.3.14"
+ jest-worker "^27.4.5"
+ schema-utils "^3.1.1"
+ serialize-javascript "^6.0.0"
+ terser "^5.14.1"
+
+terser@^5.14.1:
+ version "5.15.0"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.15.0.tgz#e16967894eeba6e1091509ec83f0c60e179f2425"
+ integrity sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==
+ dependencies:
+ "@jridgewell/source-map" "^0.3.2"
+ acorn "^8.5.0"
+ commander "^2.20.0"
+ source-map-support "~0.5.20"
+
+"textextensions@1 || 2", textextensions@^2.5.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-2.6.0.tgz#d7e4ab13fe54e32e08873be40d51b74229b00fc4"
+ integrity sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ==
+
+tmp@0.0.28:
+ version "0.0.28"
+ resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.28.tgz#172735b7f614ea7af39664fa84cf0de4e515d120"
+ integrity sha512-c2mmfiBmND6SOVxzogm1oda0OJ1HZVIk/5n26N59dDTh80MUeavpiCls4PGAdkX1PFkKokLpcf7prSjCeXLsJg==
+ dependencies:
+ os-tmpdir "~1.0.1"
+
+tmp@^0.0.33:
+ version "0.0.33"
+ resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
+ integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
+ dependencies:
+ os-tmpdir "~1.0.2"
+
+to-fast-properties@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
+ integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==
+
+tree-sync@^1.2.2:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/tree-sync/-/tree-sync-1.4.0.tgz#314598d13abaf752547d9335b8f95d9a137100d6"
+ integrity sha512-YvYllqh3qrR5TAYZZTXdspnIhlKAYezPYw11ntmweoceu4VK+keN356phHRIIo1d+RDmLpHZrUlmxga2gc9kSQ==
+ dependencies:
+ debug "^2.2.0"
+ fs-tree-diff "^0.5.6"
+ mkdirp "^0.5.1"
+ quick-temp "^0.1.5"
+ walk-sync "^0.3.3"
+
+typescript-memoize@^1.0.0-alpha.3, typescript-memoize@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/typescript-memoize/-/typescript-memoize-1.1.0.tgz#4a8f512d06fc995167c703a3592219901db8bc79"
+ integrity sha512-LQPKVXK8QrBBkL/zclE6YgSWn0I8ew5m0Lf+XL00IwMhlotqRLlzHV+BRrljVQIc+NohUAuQP7mg4HQwrx5Xbg==
+
+uglify-js@^3.1.4:
+ version "3.17.0"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.0.tgz#55bd6e9d19ce5eef0d5ad17cd1f587d85b180a85"
+ integrity sha512-aTeNPVmgIMPpm1cxXr2Q/nEbvkmV8yq66F3om7X3P/cvOXQ0TMQ64Wk63iyT1gPlmdmGzjGpyLh1f3y8MZWXGg==
+
+unbox-primitive@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"
+ integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==
+ dependencies:
+ call-bind "^1.0.2"
+ has-bigints "^1.0.2"
+ has-symbols "^1.0.3"
+ which-boxed-primitive "^1.0.2"
+
+underscore.string@~3.3.4:
+ version "3.3.6"
+ resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.6.tgz#ad8cf23d7423cb3b53b898476117588f4e2f9159"
+ integrity sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==
+ dependencies:
+ sprintf-js "^1.1.1"
+ util-deprecate "^1.0.2"
+
+unicode-canonical-property-names-ecmascript@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc"
+ integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==
+
+unicode-match-property-ecmascript@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3"
+ integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==
+ dependencies:
+ unicode-canonical-property-names-ecmascript "^2.0.0"
+ unicode-property-aliases-ecmascript "^2.0.0"
+
+unicode-match-property-value-ecmascript@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714"
+ integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==
+
+unicode-property-aliases-ecmascript@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8"
+ integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==
+
+universalify@^0.1.0:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
+ integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
+
+universalify@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
+ integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
+
+update-browserslist-db@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz#be06a5eedd62f107b7c19eb5bcefb194411abf38"
+ integrity sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==
+ dependencies:
+ escalade "^3.1.1"
+ picocolors "^1.0.0"
+
+uri-js@^4.2.2:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
+ integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
+ dependencies:
+ punycode "^2.1.0"
+
+username-sync@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/username-sync/-/username-sync-1.0.3.tgz#ae41c5c8a4c8c2ecc1443a7d0742742bd7e36732"
+ integrity sha512-m/7/FSqjJNAzF2La448c/aEom0gJy7HY7Y509h6l0ePvEkFictAGptwWaj1msWJ38JbfEDOUoE8kqFee9EHKdA==
+
+util-deprecate@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+ integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+
+walk-sync@^0.3.1, walk-sync@^0.3.3:
+ version "0.3.4"
+ resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-0.3.4.tgz#cf78486cc567d3a96b5b2237c6108017a5ffb9a4"
+ integrity sha512-ttGcuHA/OBnN2pcM6johpYlEms7XpO5/fyKIr48541xXedan4roO8cS1Q2S/zbbjGH/BarYDAMeS2Mi9HE5Tig==
+ dependencies:
+ ensure-posix-path "^1.0.0"
+ matcher-collection "^1.0.0"
+
+walk-sync@^1.0.0:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-1.1.4.tgz#81049f3d8095479b49574cfa5f558d7a252b127d"
+ integrity sha512-nowc9thB/Jg0KW4TgxoRjLLYRPvl3DB/98S89r4ZcJqq2B0alNcKDh6pzLkBSkPMzRSMsJghJHQi79qw0YWEkA==
+ dependencies:
+ "@types/minimatch" "^3.0.3"
+ ensure-posix-path "^1.1.0"
+ matcher-collection "^1.1.1"
+
+walk-sync@^2.0.2, walk-sync@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-2.2.0.tgz#80786b0657fcc8c0e1c0b1a042a09eae2966387a"
+ integrity sha512-IC8sL7aB4/ZgFcGI2T1LczZeFWZ06b3zoHH7jBPyHxOtIIz1jppWHjjEXkOFvFojBVAK9pV7g47xOZ4LW3QLfg==
+ dependencies:
+ "@types/minimatch" "^3.0.3"
+ ensure-posix-path "^1.1.0"
+ matcher-collection "^2.0.0"
+ minimatch "^3.0.4"
+
+walk-sync@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-3.0.0.tgz#67f882925021e20569a1edd560b8da31da8d171c"
+ integrity sha512-41TvKmDGVpm2iuH7o+DAOt06yyu/cSHpX3uzAwetzASvlNtVddgIjXIb2DfB/Wa20B1Jo86+1Dv1CraSU7hWdw==
+ dependencies:
+ "@types/minimatch" "^3.0.4"
+ ensure-posix-path "^1.1.0"
+ matcher-collection "^2.0.1"
+ minimatch "^3.0.4"
+
+watchpack@^2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
+ integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
+ dependencies:
+ glob-to-regexp "^0.4.1"
+ graceful-fs "^4.1.2"
+
+webpack-sources@^3.2.3:
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
+ integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
+
+webpack@^5.73.0:
+ version "5.74.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
+ integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
+ dependencies:
+ "@types/eslint-scope" "^3.7.3"
+ "@types/estree" "^0.0.51"
+ "@webassemblyjs/ast" "1.11.1"
+ "@webassemblyjs/wasm-edit" "1.11.1"
+ "@webassemblyjs/wasm-parser" "1.11.1"
+ acorn "^8.7.1"
+ acorn-import-assertions "^1.7.6"
+ browserslist "^4.14.5"
+ chrome-trace-event "^1.0.2"
+ enhanced-resolve "^5.10.0"
+ es-module-lexer "^0.9.0"
+ eslint-scope "5.1.1"
+ events "^3.2.0"
+ glob-to-regexp "^0.4.1"
+ graceful-fs "^4.2.9"
+ json-parse-even-better-errors "^2.3.1"
+ loader-runner "^4.2.0"
+ mime-types "^2.1.27"
+ neo-async "^2.6.2"
+ schema-utils "^3.1.0"
+ tapable "^2.1.1"
+ terser-webpack-plugin "^5.1.3"
+ watchpack "^2.4.0"
+ webpack-sources "^3.2.3"
+
+which-boxed-primitive@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
+ integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==
+ dependencies:
+ is-bigint "^1.0.1"
+ is-boolean-object "^1.1.0"
+ is-number-object "^1.0.4"
+ is-string "^1.0.5"
+ is-symbol "^1.0.3"
+
+wordwrap@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
+ integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
+
+workerpool@^3.1.1:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-3.1.2.tgz#b34e79243647decb174b7481ab5b351dc565c426"
+ integrity sha512-WJFA0dGqIK7qj7xPTqciWBH5DlJQzoPjsANvc3Y4hNB0SScT+Emjvt0jPPkDBUjBNngX1q9hHgt1Gfwytu6pug==
+ dependencies:
+ "@babel/core" "^7.3.4"
+ object-assign "4.1.1"
+ rsvp "^4.8.4"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+
+yallist@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+ integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+
+yocto-queue@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
+ integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
diff --git a/app/assets/javascripts/discourse/lib/pretty-text-engine.js b/app/assets/javascripts/discourse/lib/pretty-text-engine.js
index f52ca30b2b..08d91f1877 100644
--- a/app/assets/javascripts/discourse/lib/pretty-text-engine.js
+++ b/app/assets/javascripts/discourse/lib/pretty-text-engine.js
@@ -1,21 +1,28 @@
-const babel = require("broccoli-babel-transpiler");
const mergeTrees = require("broccoli-merge-trees");
const funnel = require("broccoli-funnel");
-const path = require("path");
const concat = require("broccoli-concat");
+const WatchedDir = require("broccoli-source").WatchedDir;
+const Funnel = require("broccoli-funnel");
-module.exports = function prettyTextEngine(vendorJs, engine) {
- let engineTree = babel(`../pretty-text/engines/${engine}`, {
- plugins: ["@babel/plugin-transform-modules-amd"],
- moduleIds: true,
+module.exports = function prettyTextEngine(app) {
+ let babelAddon = app.project.findAddonByName("ember-cli-babel");
- getModuleId(name) {
- return `pretty-text/engines/${engine}/${path.basename(name)}`;
+ const sourceTree = new WatchedDir(
+ "../pretty-text/engines/discourse-markdown"
+ );
+ const namespacedTree = new Funnel(sourceTree, {
+ getDestinationPath: function (relativePath) {
+ return `pretty-text/engines/discourse-markdown/${relativePath}`;
},
});
- let markdownIt = funnel(vendorJs, { files: ["markdown-it.js"] });
+ const engineTree = babelAddon.transpileTree(namespacedTree);
+
+ let markdownIt = funnel("../node_modules/markdown-it/dist", {
+ files: ["markdown-it.js"],
+ });
return concat(mergeTrees([engineTree, markdownIt]), {
- outputFile: `assets/${engine}.js`,
+ inputFiles: ["**/*.js"],
+ outputFile: `assets/markdown-it-bundle.js`,
});
};
diff --git a/app/assets/javascripts/discourse/lib/scripts.js b/app/assets/javascripts/discourse/lib/scripts.js
index 3c4572b113..ac73f27f4a 100644
--- a/app/assets/javascripts/discourse/lib/scripts.js
+++ b/app/assets/javascripts/discourse/lib/scripts.js
@@ -35,5 +35,21 @@ module.exports = function scriptsTree(app) {
trees.push(transpiledWithDecodedSourcemap);
}
+ // start-discourse.js is a combination of start-app and discourse-boot
+ let startDiscourseTree = funnel(`public/assets/scripts`, {
+ files: ["start-app.js", "discourse-boot.js"],
+ destDir: "scripts",
+ });
+ startDiscourseTree = babelAddon.transpileTree(
+ startDiscourseTree,
+ babelConfig
+ );
+ startDiscourseTree = concat(startDiscourseTree, {
+ outputFile: `assets/start-discourse.js`,
+ headerFiles: [`scripts/start-app.js`],
+ inputFiles: [`scripts/discourse-boot.js`],
+ });
+ trees.push(startDiscourseTree);
+
return mergeTrees(trees);
};
diff --git a/app/assets/javascripts/discourse/package.json b/app/assets/javascripts/discourse/package.json
index 7b7b6c4289..5ba3b11d91 100644
--- a/app/assets/javascripts/discourse/package.json
+++ b/app/assets/javascripts/discourse/package.json
@@ -2,10 +2,10 @@
"name": "discourse",
"version": "0.0.0",
"private": true,
- "description": "Small description for discourse-frontend goes here",
+ "description": "A platform for community discussion. Free, open, simple.",
"repository": "",
- "license": "MIT",
- "author": "",
+ "license": "GPL-2.0-only",
+ "author": "Discourse",
"directories": {
"doc": "doc",
"test": "tests"
@@ -16,23 +16,27 @@
"test": "ember test"
},
"dependencies": {
- "@babel/core": "^7.18.5",
- "@babel/standalone": "^7.18.12",
+ "@babel/core": "^7.19.3",
+ "@babel/standalone": "^7.19.3",
"@discourse/itsatrap": "^2.0.10",
"@ember/jquery": "^2.0.0",
"@ember/optional-features": "^2.0.0",
+ "@ember/render-modifiers": "^2.0.4",
"@ember/test-helpers": "^2.8.1",
"@glimmer/component": "^1.1.2",
"@glimmer/syntax": "^0.84.2",
"@glimmer/tracking": "^1.1.2",
- "@popperjs/core": "^2.11.5",
+ "@popperjs/core": "^2.11.6",
"@uppy/aws-s3": "^2.2.1",
"@uppy/aws-s3-multipart": "^2.4.1",
"@uppy/core": "^2.3.1",
"@uppy/drop-target": "^1.1.3",
"@uppy/utils": "^4.1.0",
"@uppy/xhr-upload": "^2.1.2",
+ "a11y-dialog": "7.5.2",
"admin": "^1.0.0",
+ "discourse-plugins": "^1.0.0",
+ "babel-plugin-ember-template-compilation": "^1.0.2",
"bootstrap": "3.4.1",
"broccoli-asset-rev": "^3.0.0",
"deepmerge": "^4.2.2",
@@ -49,25 +53,27 @@
"ember-cli-babel": "^7.26.10",
"ember-cli-dependency-checker": "^3.3.1",
"ember-cli-deprecation-workflow": "^2.1.0",
- "ember-cli-htmlbars": "^6.1.0",
+ "ember-cli-htmlbars": "^6.1.1",
"ember-cli-inject-live-reload": "^2.1.0",
+ "ember-cli-progress-ci": "1.0.0",
"ember-cli-sri": "^2.1.1",
"ember-cli-terser": "^4.0.2",
- "ember-exam": "^7.0.1",
+ "ember-exam": "^8.0.0",
"ember-export-application-global": "^2.0.1",
"ember-load-initializers": "^2.1.1",
+ "ember-modifier": "^3.2.7",
+ "ember-on-resize-modifier": "^1.1.0",
"ember-qunit": "^5.1.5",
"ember-rfc176-data": "^0.3.17",
"ember-source": "~3.28.8",
"ember-test-selectors": "^6.0.0",
- "ember-modifier": "^3.2.7",
- "@ember/render-modifiers": "^2.0.4",
"eslint": "^7.32.0",
"eslint-plugin-qunit": "^6.2.0",
"html-entities": "^2.3.3",
"js-yaml": "^4.1.0",
"jsdom": "^20.0.0",
"loader.js": "^4.7.0",
+ "markdown-it": "^13.0.1",
"message-bus-client": "^4.2.0",
"messageformat": "0.1.5",
"node-fetch": "^2.6.6",
@@ -75,12 +81,12 @@
"pretty-text": "^1.0.0",
"qunit": "^2.19.1",
"qunit-dom": "^2.0.0",
- "sass": "^1.53.0",
+ "sass": "^1.55.0",
"select-kit": "^1.0.0",
"sinon": "^14.0.0",
"tippy.js": "^6.3.7",
"virtual-dom": "^2.1.1",
- "webpack": "^5.73.0",
+ "webpack": "^5.74.0",
"wizard": "^1.0.0"
},
"engines": {
@@ -93,12 +99,13 @@
},
"ember-addon": {
"paths": [
+ "lib/dialog-holder",
"lib/bootstrap-json"
]
},
"devDependencies": {
"ember-cached-decorator-polyfill": "^0.1.4",
"ember-cli-deprecation-workflow": "^2.1.0",
- "ember-exam": "^7.0.1"
+ "ember-exam": "^8.0.0"
}
}
diff --git a/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js b/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js
index 7dab11a480..d9d96d27b9 100644
--- a/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js
+++ b/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js
@@ -4,15 +4,46 @@
}
// TODO: Remove this and have resolver find the templates
- const prefix = "discourse/templates/";
+ const discoursePrefix = "discourse/templates/";
const adminPrefix = "admin/templates/";
const wizardPrefix = "wizard/templates/";
- let len = prefix.length;
+ const discoursePrefixLength = discoursePrefix.length;
+
+ const pluginRegex = /^discourse\/plugins\/([^\/]+)\//;
+ const themeRegex = /^discourse\/theme-([^\/]+)\//;
+
Object.keys(requirejs.entries).forEach(function (key) {
- if (key.startsWith(prefix)) {
- Ember.TEMPLATES[key.slice(len)] = require(key).default;
+ let templateKey;
+ let pluginName;
+ let themeId;
+ if (key.startsWith(discoursePrefix)) {
+ templateKey = key.slice(discoursePrefixLength);
} else if (key.startsWith(adminPrefix) || key.startsWith(wizardPrefix)) {
- Ember.TEMPLATES[key] = require(key).default;
+ templateKey = key;
+ } else if (
+ (pluginName = key.match(pluginRegex)?.[1]) &&
+ key.includes("/templates/") &&
+ require(key).default.__id // really is a template
+ ) {
+ // This logic mimics the old sprockets compilation system which used to
+ // output templates directly to `Ember.TEMPLATES` with this naming logic
+ templateKey = key.slice(`discourse/plugins/${pluginName}/`.length);
+ templateKey = templateKey.replace("discourse/templates/", "");
+ templateKey = `javascripts/${templateKey}`;
+ } else if (
+ (themeId = key.match(themeRegex)?.[1]) &&
+ key.includes("/templates/")
+ ) {
+ // And likewise for themes - this mimics the old logic
+ templateKey = key.slice(`discourse/theme-${themeId}/`.length);
+ templateKey = templateKey.replace("discourse/templates/", "");
+ if (!templateKey.startsWith("javascripts/")) {
+ templateKey = `javascripts/${templateKey}`;
+ }
+ }
+
+ if (templateKey) {
+ Ember.TEMPLATES[templateKey] = require(key).default;
}
});
diff --git a/app/assets/javascripts/discourse/public/assets/scripts/discourse-test-listen-boot.js b/app/assets/javascripts/discourse/public/assets/scripts/discourse-test-listen-boot.js
index 48a7c880f6..bc903572c7 100644
--- a/app/assets/javascripts/discourse/public/assets/scripts/discourse-test-listen-boot.js
+++ b/app/assets/javascripts/discourse/public/assets/scripts/discourse-test-listen-boot.js
@@ -27,4 +27,4 @@ document.body.insertAdjacentHTML(
`
);
-require('discourse/tests/test-boot-ember-cli');
+require("discourse/tests/test-boot-ember-cli");
diff --git a/app/assets/javascripts/discourse/public/assets/scripts/discourse-test-load-dynamic-js.js b/app/assets/javascripts/discourse/public/assets/scripts/discourse-test-load-dynamic-js.js
new file mode 100644
index 0000000000..fc0237c622
--- /dev/null
+++ b/app/assets/javascripts/discourse/public/assets/scripts/discourse-test-load-dynamic-js.js
@@ -0,0 +1,28 @@
+const dynamicJsTemplate = document.querySelector("#dynamic-test-js");
+
+const params = new URLSearchParams(document.location.search);
+const skipPlugins = params.get("qunit_skip_plugins");
+
+for (const element of dynamicJsTemplate.content.childNodes) {
+ if (skipPlugins && element.dataset?.discoursePlugin) {
+ continue;
+ }
+
+ if (
+ element.tagName === "SCRIPT" &&
+ element.innerHTML.includes("EmberENV.TESTS_FILE_LOADED")
+ ) {
+ // Inline script introduced by ember-cli. Incompatible with CSP and our custom plugin JS loading system
+ // https://github.com/ember-cli/ember-cli/blob/04a38fda2c/lib/utilities/ember-app-utils.js#L131
+ // We re-implement in test-boot-ember-cli.js
+ continue;
+ }
+
+ const clone = element.cloneNode(true);
+
+ if (clone.tagName === "SCRIPT") {
+ clone.async = false;
+ }
+
+ document.querySelector("discourse-dynamic-test-js").appendChild(clone);
+}
diff --git a/app/assets/javascripts/discourse/public/assets/scripts/discourse-test-trigger-ember-cli-boot.js b/app/assets/javascripts/discourse/public/assets/scripts/discourse-test-trigger-ember-cli-boot.js
new file mode 100644
index 0000000000..65f629d8a0
--- /dev/null
+++ b/app/assets/javascripts/discourse/public/assets/scripts/discourse-test-trigger-ember-cli-boot.js
@@ -0,0 +1 @@
+require("discourse/tests/test-boot-ember-cli");
diff --git a/app/assets/javascripts/discourse/public/assets/scripts/module-shims.js b/app/assets/javascripts/discourse/public/assets/scripts/module-shims.js
index c0071eb9fa..a8b8118e99 100644
--- a/app/assets/javascripts/discourse/public/assets/scripts/module-shims.js
+++ b/app/assets/javascripts/discourse/public/assets/scripts/module-shims.js
@@ -18,3 +18,16 @@ define("ember-addons/ember-computed-decorators", [
);
return decorators;
});
+
+// Based on https://github.com/emberjs/ember-jquery-legacy
+// The addon has out-of-date dependences, but it's super simple so we can reproduce here instead:
+define("ember-jquery-legacy", ["exports"], function (exports) {
+ exports.normalizeEvent = function (e) {
+ if (e instanceof Event) {
+ return e;
+ }
+ // __originalEvent is a private escape hatch of Ember's EventDispatcher to allow accessing `originalEvent` without
+ // triggering a deprecation message.
+ return e.__originalEvent || e.originalEvent;
+ };
+});
diff --git a/app/assets/javascripts/discourse/scripts/splash-screen.js b/app/assets/javascripts/discourse/scripts/splash-screen.js
new file mode 100644
index 0000000000..a8a56af7ee
--- /dev/null
+++ b/app/assets/javascripts/discourse/scripts/splash-screen.js
@@ -0,0 +1,66 @@
+// This script is inlined in `_discourse_splash.html.erb
+const DELAY_TARGET = 2000;
+const POLLING_INTERVAL = 50;
+
+const splashSvgTemplate = document.querySelector(".splash-svg-template");
+const splashTemplateClone = splashSvgTemplate.content.cloneNode(true);
+const svgElement = splashTemplateClone.querySelector("svg");
+
+const svgString = new XMLSerializer().serializeToString(svgElement);
+const encodedSvg = btoa(svgString);
+
+const splashWrapper = document.querySelector("#d-splash");
+const splashImage =
+ splashWrapper && splashWrapper.querySelector(".preloader-image");
+
+if (splashImage) {
+ splashImage.src = `data:image/svg+xml;base64,${encodedSvg}`;
+
+ const connectStart = performance.timing.connectStart || 0;
+ const targetTime = connectStart + DELAY_TARGET;
+
+ let splashInterval;
+ let discourseReady;
+
+ const swapSplash = () => {
+ splashWrapper &&
+ splashWrapper.style.setProperty("--animation-state", "running");
+ svgElement && svgElement.style.setProperty("--animation-state", "running");
+
+ const newSvgString = new XMLSerializer().serializeToString(svgElement);
+ const newEncodedSvg = btoa(newSvgString);
+
+ splashImage.src = `data:image/svg+xml;base64,${newEncodedSvg}`;
+
+ performance.mark("discourse-splash-visible");
+
+ clearSplashInterval();
+ };
+
+ const clearSplashInterval = () => {
+ clearInterval(splashInterval);
+ splashInterval = null;
+ };
+
+ (() => {
+ splashInterval = setInterval(() => {
+ if (discourseReady) {
+ clearSplashInterval();
+ }
+
+ if (Date.now() > targetTime) {
+ swapSplash();
+ }
+ }, POLLING_INTERVAL);
+ })();
+
+ document.addEventListener(
+ "discourse-ready",
+ () => {
+ discourseReady = true;
+ splashWrapper && splashWrapper.remove();
+ performance.mark("discourse-splash-removed");
+ },
+ { once: true }
+ );
+}
diff --git a/app/assets/javascripts/discourse/testem.js b/app/assets/javascripts/discourse/testem.js
index 67261a0fb6..1545a94f55 100644
--- a/app/assets/javascripts/discourse/testem.js
+++ b/app/assets/javascripts/discourse/testem.js
@@ -2,12 +2,12 @@ const TapReporter = require("testem/lib/reporters/tap_reporter");
const { shouldLoadPluginTestJs } = require("discourse/lib/plugin-js");
class Reporter {
+ failReports = [];
+
constructor() {
this._tapReporter = new TapReporter(...arguments);
}
- failReports = [];
-
reportMetadata(tag, metadata) {
if (tag === "summary-line") {
process.stdout.write(`\n${metadata.message}\n`);
@@ -44,7 +44,7 @@ module.exports = {
launch_in_ci: ["Chrome"],
// launch_in_dev: ["Chrome"] // Ember-CLI always launches testem in 'CI' mode
tap_failed_tests_only: false,
- parallel: 1, // disable parallel tests for stability
+ parallel: -1,
browser_start_timeout: 120,
browser_args: {
Chrome: [
@@ -60,15 +60,16 @@ module.exports = {
"--js-flags=--max_old_space_size=4096",
].filter(Boolean),
Firefox: ["-headless", "--width=1440", "--height=900"],
- "Headless Firefox": ["--width=1440", "--height=900"],
- },
- browser_paths: {
- "Headless Firefox": "/opt/firefox-evergreen/firefox",
},
reporter: Reporter,
};
-const target = `http://localhost:${process.env.UNICORN_PORT || "3000"}`;
+if (process.env.TESTEM_FIREFOX_PATH) {
+ module.exports.browser_paths ||= {};
+ module.exports.browser_paths["Firefox"] = process.env.TESTEM_FIREFOX_PATH;
+}
+
+const target = `http://127.0.0.1:${process.env.UNICORN_PORT || "3000"}`;
if (process.argv.includes("-t")) {
// Running testem without ember cli. Probably for theme-qunit
@@ -95,13 +96,7 @@ if (process.argv.includes("-t")) {
} else if (shouldLoadPluginTestJs()) {
// Running with ember cli, but we want to pass through plugin request to Rails
module.exports.proxies = {
- "/assets/discourse/tests/active-plugins.js": {
- target,
- },
- "/assets/admin-plugins.js": {
- target,
- },
- "/assets/discourse/tests/plugin-tests.js": {
+ "/assets/plugins/*_extra.js": {
target,
},
"/plugins/": {
diff --git a/app/assets/javascripts/discourse/tests/acceptance/admin-badges-show-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-badges-show-test.js
index 531342e2ff..2464e770b9 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/admin-badges-show-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/admin-badges-show-test.js
@@ -3,8 +3,9 @@ import {
exists,
query,
} from "discourse/tests/helpers/qunit-helpers";
-import { click, visit } from "@ember/test-helpers";
+import { click, fillIn, settled, visit } from "@ember/test-helpers";
import { test } from "qunit";
+import { set } from "@ember/object";
acceptance("Admin - Badges - Show", function (needs) {
needs.user();
@@ -37,6 +38,24 @@ acceptance("Admin - Badges - Show", function (needs) {
exists(".image-uploader"),
"image uploader becomes visible after clicking the upload image radio button"
);
+
+ // SQL fields
+ assert.false(exists("label[for=query]"), "sql input is hidden by default");
+ set(this.siteSettings, "enable_badge_sql", true);
+ await settled();
+ assert.true(exists("label[for=query]"), "sql input shows when enabled");
+
+ assert.false(
+ exists("input[name=auto_revoke]"),
+ "does not show sql-specific options when query is blank"
+ );
+
+ await fillIn(".ace-wrapper textarea", "SELECT 1");
+
+ assert.true(
+ exists("input[name=auto_revoke]"),
+ "shows sql-specific options when query is present"
+ );
});
test("existing badge that has an icon", async function (assert) {
diff --git a/app/assets/javascripts/discourse/tests/acceptance/admin-emails-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-emails-test.js
index 7d7772422e..c61bade52d 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/admin-emails-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/admin-emails-test.js
@@ -51,7 +51,11 @@ acceptance("Admin - Emails", function (needs) {
await fillIn(".admin-controls input", "test@example.com");
await click(".btn-primary");
- assert.ok(query(".bootbox.modal").innerText.includes("some error"));
- await click(".bootbox .btn-primary");
+ assert.ok(query("#dialog-holder").innerText.includes("some error"));
+ assert.ok(
+ query("#dialog-holder .dialog-body b"),
+ "Error message can contain html"
+ );
+ await click(".dialog-overlay");
});
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/admin-install-theme-modal-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-install-theme-modal-test.js
index 27eaf158d7..7ecab0566e 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/admin-install-theme-modal-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/admin-install-theme-modal-test.js
@@ -79,6 +79,15 @@ acceptance("Admin - Themes - Install modal", function (needs) {
await fillIn(urlInput, "git@github.com:discourse/discourse.git");
assert.ok(query(publicKey), "shows public key for valid github repo url");
+
+ await fillIn(urlInput, "git@github.com:discourse/discourse");
+ assert.ok(query(publicKey), "shows public key for valid github repo url");
+
+ await fillIn(urlInput, "git@github.com/discourse/discourse");
+ assert.notOk(
+ query(publicKey),
+ "does not shows public key for valid github repo url"
+ );
});
test("modal can be auto-opened with the right query params", async function (assert) {
diff --git a/app/assets/javascripts/discourse/tests/acceptance/admin-site-text-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-site-text-test.js
index 6770f01c01..38cfee1af7 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/admin-site-text-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/admin-site-text-test.js
@@ -51,9 +51,9 @@ acceptance("Admin - Site Texts", function (needs) {
// Revert the changes
await click(".revert-site-text");
- assert.ok(exists(".bootbox.modal"));
+ assert.ok(exists("#dialog-holder .dialog-content"));
- await click(".bootbox.modal .btn-primary");
+ await click("#dialog-holder .btn-primary");
assert.ok(!exists(".saved"));
assert.ok(!exists(".revert-site-text"));
diff --git a/app/assets/javascripts/discourse/tests/acceptance/admin-user-index-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-user-index-test.js
index 193c44cf0b..8e14be163d 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/admin-user-index-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/admin-user-index-test.js
@@ -10,6 +10,8 @@ import I18n from "I18n";
import { SECOND_FACTOR_METHODS } from "discourse/models/user";
const { TOTP, BACKUP_CODE, SECURITY_KEY } = SECOND_FACTOR_METHODS;
+let deleteAndBlock = null;
+
acceptance("Admin - User Index", function (needs) {
needs.user();
needs.pretender((server, helper) => {
@@ -97,6 +99,34 @@ acceptance("Admin - User Index", function (needs) {
allowed_methods: [TOTP, BACKUP_CODE, SECURITY_KEY],
});
});
+
+ server.get("/admin/users/5.json", () => {
+ return helper.response({
+ id: 5,
+ username: "user5",
+ name: null,
+ avatar_template: "/letter_avatar_proxy/v4/letter/b/f0a364/{size}.png",
+ active: true,
+ can_be_deleted: true,
+ post_count: 0,
+ });
+ });
+
+ server.delete("/admin/users/5.json", (request) => {
+ const data = helper.parsePostData(request.requestBody);
+
+ if (data.block_email || data.block_ip || data.block_urls) {
+ deleteAndBlock = true;
+ } else {
+ deleteAndBlock = false;
+ }
+
+ return helper.response({});
+ });
+ });
+
+ needs.hooks.beforeEach(() => {
+ deleteAndBlock = null;
});
test("can edit username", async function (assert) {
@@ -195,12 +225,13 @@ acceptance("Admin - User Index", function (needs) {
test("grant admin - shows the confirmation bootbox", async function (assert) {
await visit("/admin/users/3/user1");
await click(".grant-admin");
- assert.ok(exists(".bootbox"));
+ assert.ok(exists(".dialog-content"));
assert.strictEqual(
I18n.t("admin.user.grant_admin_confirm"),
- query(".modal-body").textContent.trim()
+ query(".dialog-body").textContent.trim()
);
- await click(".bootbox .btn-primary");
+
+ await click(".dialog-footer .btn-primary");
});
test("grant admin - redirects to the 2fa page", async function (assert) {
@@ -212,4 +243,27 @@ acceptance("Admin - User Index", function (needs) {
"user is redirected to the 2FA page"
);
});
+
+ test("delete user - delete without blocking works as expected", async function (assert) {
+ await visit("/admin/users/5/user5");
+ await click(".btn-user-delete");
+
+ assert.equal(
+ query("#dialog-title").textContent,
+ I18n.t("admin.user.delete_confirm_title"),
+ "dialog has a title"
+ );
+
+ await click(".dialog-footer .btn-primary");
+
+ assert.notOk(deleteAndBlock, "user does not get blocked");
+ });
+
+ test("delete user - delete and block works as expected", async function (assert) {
+ await visit("/admin/users/5/user5");
+ await click(".btn-user-delete");
+ await click(".dialog-footer .btn-danger");
+
+ assert.ok(deleteAndBlock, "user does not get blocked");
+ });
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/admin-users-list-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-users-list-test.js
index e899cf9d3d..6e051abe6b 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/admin-users-list-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/admin-users-list-test.js
@@ -3,7 +3,7 @@ import {
exists,
query,
} from "discourse/tests/helpers/qunit-helpers";
-import { click, visit } from "@ember/test-helpers";
+import { click, fillIn, visit } from "@ember/test-helpers";
import I18n from "I18n";
import { test } from "qunit";
@@ -17,6 +17,17 @@ acceptance("Admin - Users List", function (needs) {
assert.ok(!exists(".user:nth-of-type(1) .email small"), "escapes email");
});
+ test("searching users with no matches", async function (assert) {
+ await visit("/admin/users/list/active");
+
+ await fillIn(".controls.username input", "doesntexist");
+
+ assert.equal(
+ query(".users-list-container").innerText,
+ I18n.t("search.no_results")
+ );
+ });
+
test("sorts users", async function (assert) {
await visit("/admin/users/list/active");
diff --git a/app/assets/javascripts/discourse/tests/acceptance/admin-watched-words-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-watched-words-test.js
index e2d6b66702..37cb1c8797 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/admin-watched-words-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/admin-watched-words-test.js
@@ -58,9 +58,8 @@ acceptance("Admin - Watched Words", function (needs) {
test("add words", async function (assert) {
await visit("/admin/customize/watched_words/action/block");
- click(".show-words-checkbox");
- fillIn(".watched-word-form input", "poutine");
-
+ await click(".show-words-checkbox");
+ await fillIn(".watched-word-form input", "poutine");
await click(".watched-word-form button");
let found = [];
@@ -74,21 +73,20 @@ acceptance("Admin - Watched Words", function (needs) {
assert.strictEqual(count(".watched-words-list .case-sensitive"), 0);
});
- test("add case-sensitve words", async function (assert) {
+ test("add case-sensitive words", async function (assert) {
await visit("/admin/customize/watched_words/action/block");
- click(".show-words-checkbox");
- fillIn(".watched-word-form input", "Discourse");
- click(".case-sensitivity-checkbox");
-
+ await click(".show-words-checkbox");
+ await fillIn(".watched-word-form input", "Discourse");
+ await click(".case-sensitivity-checkbox");
await click(".watched-word-form button");
assert
.dom(".watched-words-list .watched-word")
.hasText(`Discourse ${I18n.t("admin.watched_words.case_sensitive")}`);
- fillIn(".watched-word-form input", "discourse");
- click(".case-sensitivity-checkbox");
+ await fillIn(".watched-word-form input", "discourse");
+ await click(".case-sensitivity-checkbox");
await click(".watched-word-form button");
assert
diff --git a/app/assets/javascripts/discourse/tests/acceptance/bookmarks-test.js b/app/assets/javascripts/discourse/tests/acceptance/bookmarks-test.js
index b2164921f8..e46c80057c 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/bookmarks-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/bookmarks-test.js
@@ -303,8 +303,8 @@ acceptance("Bookmarking", function (needs) {
});
test("The topic level bookmark button deletes all bookmarks if several posts on the topic are bookmarked", async function (assert) {
- const yesButton = "a.btn-primary";
- const noButton = "a.btn-default";
+ const yesButton = ".dialog-footer .btn-primary";
+ const noButton = ".dialog-footer .btn-default";
await visit("/t/internationalization-localization/280");
await openBookmarkModal(1);
@@ -336,6 +336,7 @@ acceptance("Bookmarking", function (needs) {
// open the modal and accept deleting
await click("#topic-footer-button-bookmark");
+ // pauseTest();
await click(yesButton);
assert.ok(
@@ -412,7 +413,7 @@ acceptance("Bookmarking", function (needs) {
"the footer button says Clear Bookmarks because there is more than one"
);
await click("#topic-footer-button-bookmark");
- await click("a.btn-primary");
+ await click(".dialog-footer .btn-primary");
assert.ok(
!exists(".topic-post:first-child button.bookmark.bookmarked"),
diff --git a/app/assets/javascripts/discourse/tests/acceptance/bootstrap-mode-notice-test.js b/app/assets/javascripts/discourse/tests/acceptance/bootstrap-mode-notice-test.js
index 5f11ac4d47..9d8812ccf3 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/bootstrap-mode-notice-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/bootstrap-mode-notice-test.js
@@ -1,6 +1,7 @@
import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
import { test } from "qunit";
-import { click, currentURL, visit } from "@ember/test-helpers";
+import { click, currentURL, settled, visit } from "@ember/test-helpers";
+import { set } from "@ember/object";
acceptance("Bootstrap Mode Notice", function (needs) {
needs.user();
@@ -26,18 +27,21 @@ acceptance("Bootstrap Mode Notice", function (needs) {
);
await click(".bootstrap-invite-button");
- assert.strictEqual(
- currentURL(),
- "/u/eviltrout/invited/pending",
- "it transitions to the invite page"
- );
+ assert.ok(exists(".create-invite-modal"), "opens create invite modal");
- await visit("/");
await click(".bootstrap-wizard-link");
assert.strictEqual(
currentURL(),
"/wizard/steps/hello-world",
"it transitions to the wizard page"
);
+
+ await visit("/");
+ set(this.siteSettings, "bootstrap_mode_enabled", false);
+ await settled();
+ assert.ok(
+ !exists(".bootstrap-mode-notice"),
+ "removes the notice when bootstrap mode is disabled"
+ );
});
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/categories-test.js b/app/assets/javascripts/discourse/tests/acceptance/categories-test.js
index d0b91e934a..8f84f5ab79 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/categories-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/categories-test.js
@@ -6,6 +6,10 @@ import {
import { visit } from "@ember/test-helpers";
import { test } from "qunit";
+import PreloadStore from "discourse/lib/preload-store";
+import discoveryFixtures from "discourse/tests/fixtures/discovery-fixtures";
+import { cloneJSON } from "discourse-common/lib/object";
+
acceptance("Categories - 'categories_only'", function (needs) {
needs.settings({
desktop_category_page_style: "categories_only",
@@ -99,3 +103,41 @@ acceptance(
});
}
);
+
+acceptance("Categories - preloadStore handling", function () {
+ const styles = [
+ "categories_only",
+ "categories_with_featured_topics",
+ "categories_and_latest_topics_created_date",
+ "categories_and_latest_topics",
+ "categories_and_top_topics",
+ "categories_boxes",
+ "categories_boxes_with_topics",
+ "subcategories_with_featured_topics",
+ ];
+
+ for (const style of styles) {
+ test(`${style} deletes data from PreloadStore to ensure it isn't left for another route`, async function (assert) {
+ this.siteSettings.desktop_category_page_style = style;
+ PreloadStore.store(
+ "topic_list",
+ cloneJSON(discoveryFixtures["/latest.json"])
+ );
+ PreloadStore.store(
+ "categories_list",
+ cloneJSON(discoveryFixtures["/categories.json"])
+ );
+
+ await visit(`/categories`);
+
+ assert.true(
+ PreloadStore.get("topic_list") === undefined,
+ `topic_list is removed from preloadStore for ${style}`
+ );
+ assert.true(
+ PreloadStore.get("categories_list") === undefined,
+ `topic_list is removed from preloadStore for ${style}`
+ );
+ });
+ }
+});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/category-banner-test.js b/app/assets/javascripts/discourse/tests/acceptance/category-banner-test.js
index 8e9f67798d..d6874ab1c2 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/category-banner-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/category-banner-test.js
@@ -44,7 +44,7 @@ acceptance("Category Banners", function (needs) {
await visit("/c/test-read-only-without-banner");
await click("#create-topic");
- assert.ok(!visible(".bootbox.modal"), "it does not pop up a modal");
+ assert.ok(!visible(".dialog-body"), "it does not pop up a modal");
assert.ok(
!visible(".category-read-only-banner"),
"it does not show a banner"
@@ -55,10 +55,10 @@ acceptance("Category Banners", function (needs) {
await visit("/c/test-read-only-with-banner");
await click("#create-topic");
- assert.ok(visible(".bootbox.modal"), "it pops up a modal");
+ assert.ok(visible(".dialog-body"), "it pops up a modal");
- await click(".modal-footer>.btn-primary");
- assert.ok(!visible(".bootbox.modal"), "it closes the modal");
+ await click(".dialog-footer .btn-primary");
+ assert.ok(!visible(".dialog-body"), "it closes the modal");
assert.ok(visible(".category-read-only-banner"), "it shows a banner");
assert.strictEqual(
count(".category-read-only-banner .inner"),
diff --git a/app/assets/javascripts/discourse/tests/acceptance/category-edit-test.js b/app/assets/javascripts/discourse/tests/acceptance/category-edit-test.js
index eb703cd6da..5042f2c614 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/category-edit-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/category-edit-test.js
@@ -164,14 +164,13 @@ acceptance("Category Edit", function (needs) {
await fillIn(".email-in", "duplicate@example.com");
await click("#save-category");
- assert.ok(visible(".bootbox"));
assert.strictEqual(
- query(".bootbox .modal-body").innerHTML,
+ query(".dialog-body").textContent.trim(),
"duplicate email"
);
- await click(".bootbox .btn-primary");
- assert.ok(!visible(".bootbox"));
+ await click(".dialog-footer .btn-primary");
+ assert.ok(!visible(".dialog-body"));
});
test("Subcategory list settings", async function (assert) {
diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js
index 3743d61c55..a2002935f0 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/composer-actions-test.js
@@ -9,8 +9,6 @@ import {
import { click, fillIn, visit } from "@ember/test-helpers";
import Draft from "discourse/models/draft";
import I18n from "I18n";
-import { Promise } from "rsvp";
-import { _clearSnapshots } from "select-kit/components/composer-actions";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import sinon from "sinon";
import { test } from "qunit";
@@ -118,9 +116,8 @@ acceptance("Composer Actions", function (needs) {
});
test("replying to post - reply_as_new_topic", async function (assert) {
- sinon
- .stub(Draft, "get")
- .returns(Promise.resolve({ draft: "", draft_sequence: 0 }));
+ sinon.stub(Draft, "get").resolves({ draft: "", draft_sequence: 0 });
+
const composerActions = selectKit(".composer-actions");
const categoryChooser = selectKit(".title-wrapper .category-chooser");
const categoryChooserReplyArea = selectKit(".reply-area .category-chooser");
@@ -406,13 +403,11 @@ acceptance("Composer Actions", function (needs) {
});
function stubDraftResponse() {
- sinon.stub(Draft, "get").returns(
- Promise.resolve({
- draft:
- '{"reply":"dum de dum da ba.","action":"createTopic","title":"dum da ba dum dum","categoryId":null,"archetypeId":"regular","metaData":null,"composerTime":540879,"typingTime":3400}',
- draft_sequence: 0,
- })
- );
+ sinon.stub(Draft, "get").resolves({
+ draft:
+ '{"reply":"dum de dum da ba.","action":"createTopic","title":"dum da ba dum dum","categoryId":null,"archetypeId":"regular","metaData":null,"composerTime":540879,"typingTime":3400}',
+ draft_sequence: 0,
+ });
}
acceptance("Composer Actions With New Topic Draft", function (needs) {
@@ -423,67 +418,69 @@ acceptance("Composer Actions With New Topic Draft", function (needs) {
needs.site({
can_tag_topics: true,
});
- needs.hooks.beforeEach(() => _clearSnapshots());
- needs.hooks.afterEach(() => _clearSnapshots());
+
+ needs.hooks.afterEach(() => toggleCheckDraftPopup(false));
test("shared draft", async function (assert) {
+ updateCurrentUser({ has_topic_draft: true });
stubDraftResponse();
- try {
- toggleCheckDraftPopup(true);
+ toggleCheckDraftPopup(true);
- const composerActions = selectKit(".composer-actions");
- const tags = selectKit(".mini-tag-chooser");
+ await visit("/");
+ await click("button.open-draft");
- await visit("/");
- await click("#create-topic");
+ await fillIn(
+ "#reply-title",
+ "This is the new text for the title using 'quotes'"
+ );
+ await fillIn(".d-editor-input", "This is the new text for the post");
- await fillIn(
- "#reply-title",
- "This is the new text for the title using 'quotes'"
- );
+ const tags = selectKit(".mini-tag-chooser");
+ await tags.expand();
+ await tags.selectRowByValue("monkey");
- await fillIn(".d-editor-input", "This is the new text for the post");
- await tags.expand();
- await tags.selectRowByValue("monkey");
- await composerActions.expand();
- await composerActions.selectRowByValue("shared_draft");
+ const composerActions = selectKit(".composer-actions");
+ await composerActions.expand();
+ await composerActions.selectRowByValue("shared_draft");
- assert.strictEqual(tags.header().value(), "monkey", "tags are not reset");
+ assert.strictEqual(tags.header().value(), "monkey", "tags are not reset");
+ assert.strictEqual(
+ query("#reply-title").value,
+ "This is the new text for the title using 'quotes'"
+ );
- assert.strictEqual(
- query("#reply-title").value,
- "This is the new text for the title using 'quotes'"
- );
-
- assert.strictEqual(
- query("#reply-control .btn-primary.create .d-button-label").innerText,
- I18n.t("composer.create_shared_draft")
- );
- assert.strictEqual(
- count(".composer-actions svg.d-icon-far-clipboard"),
- 1,
- "shared draft icon is visible"
- );
-
- assert.strictEqual(count("#reply-control.composing-shared-draft"), 1);
- await click(".modal-footer .btn.btn-default");
- } finally {
- toggleCheckDraftPopup(false);
- }
+ assert.strictEqual(
+ query("#reply-control .btn-primary.create .d-button-label").innerText,
+ I18n.t("composer.create_shared_draft")
+ );
+ assert.strictEqual(
+ count(".composer-actions svg.d-icon-far-clipboard"),
+ 1,
+ "shared draft icon is visible"
+ );
});
test("reply_as_new_topic with new_topic draft", async function (assert) {
await visit("/t/internationalization-localization/280");
await click(".create.reply");
+
+ stubDraftResponse();
+
const composerActions = selectKit(".composer-actions");
await composerActions.expand();
- stubDraftResponse();
await composerActions.selectRowByValue("reply_as_new_topic");
+
assert.strictEqual(
query(".bootbox .modal-body").innerText,
I18n.t("composer.composer_actions.reply_as_new_topic.confirm")
);
- await click(".modal-footer .btn.btn-default");
+ await click(".modal-footer .btn.btn-primary");
+
+ assert.ok(
+ query(".d-editor-input").value.startsWith(
+ "Continuing the discussion from"
+ )
+ );
});
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-editor-mentions-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-editor-mentions-test.js
index ea6f29bc0e..6329a25769 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/composer-editor-mentions-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/composer-editor-mentions-test.js
@@ -1,12 +1,31 @@
import { test } from "qunit";
import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers";
-import { acceptance, query } from "discourse/tests/helpers/qunit-helpers";
+import {
+ acceptance,
+ exists,
+ fakeTime,
+ loggedInUser,
+ query,
+} from "discourse/tests/helpers/qunit-helpers";
import { setCaretPosition } from "discourse/lib/utilities";
acceptance("Composer - editor mentions", function (needs) {
+ let clock = null;
+ const status = {
+ emoji: "tooth",
+ description: "off to dentist",
+ ends_at: "2100-02-01T09:00:00.000Z",
+ };
+
needs.user();
needs.settings({ enable_mentions: true });
+ needs.hooks.afterEach(() => {
+ if (clock) {
+ clock.restore();
+ }
+ });
+
needs.pretender((server, helper) => {
server.get("/u/search/users", () => {
return helper.response({
@@ -16,6 +35,7 @@ acceptance("Composer - editor mentions", function (needs) {
name: "Some User",
avatar_template:
"https://avatars.discourse.org/v3/letter/t/41988e/{size}.png",
+ status,
},
{
username: "user2",
@@ -104,4 +124,37 @@ acceptance("Composer - editor mentions", function (needs) {
"should replace mention correctly"
);
});
+
+ test("shows status on search results when mentioning a user", async function (assert) {
+ const timezone = loggedInUser().timezone;
+ const now = moment(status.ends_at).add(-1, "hour").format();
+ clock = fakeTime(now, timezone, true);
+
+ await visit("/");
+ await click("#create-topic");
+
+ // emulate typing in "abc @u"
+ const editor = query(".d-editor-input");
+ await fillIn(".d-editor-input", "@");
+ await setCaretPosition(editor, 5);
+ await triggerKeyEvent(".d-editor-input", "keyup", "@");
+ await fillIn(".d-editor-input", "@u");
+ await setCaretPosition(editor, 6);
+ await triggerKeyEvent(".d-editor-input", "keyup", "U");
+
+ assert.ok(
+ exists(`.autocomplete .emoji[title='${status.emoji}']`),
+ "status emoji is shown"
+ );
+ assert.equal(
+ query(".autocomplete .status-description").textContent.trim(),
+ status.description,
+ "status description is shown"
+ );
+ assert.equal(
+ query(".autocomplete .relative-date").textContent.trim(),
+ "1h",
+ "status expiration time is shown"
+ );
+ });
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-hyperlink-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-hyperlink-test.js
index 274a262020..852e548dae 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/composer-hyperlink-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/composer-hyperlink-test.js
@@ -98,5 +98,13 @@ acceptance("Composer - Hyperlink", function (needs) {
query(".link-url").value.includes("http"),
"replaces link url field with internal link"
);
+
+ await triggerKeyEvent(".insert-link", "keydown", "Escape");
+
+ assert.strictEqual(
+ document.activeElement.classList.contains("d-editor-input"),
+ true,
+ "focus stays on composer after dismissing modal using Esc key"
+ );
});
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-messages-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-messages-test.js
new file mode 100644
index 0000000000..25f9cae229
--- /dev/null
+++ b/app/assets/javascripts/discourse/tests/acceptance/composer-messages-test.js
@@ -0,0 +1,41 @@
+import {
+ acceptance,
+ exists,
+ query,
+} from "discourse/tests/helpers/qunit-helpers";
+import { click, triggerKeyEvent, visit } from "@ember/test-helpers";
+import { test } from "qunit";
+import I18n from "I18n";
+
+acceptance("Composer - Messages", function (needs) {
+ needs.user();
+ needs.pretender((server, helper) => {
+ server.get("/composer_messages/user_not_seen_in_a_while", () => {
+ return helper.response({
+ user_count: 1,
+ usernames: ["charlie"],
+ time_ago: "1 year ago",
+ });
+ });
+ });
+
+ test("Shows warning in composer if user hasn't been seen in a long time.", async function (assert) {
+ await visit("/u/charlie");
+ await click("button.compose-pm");
+ assert.ok(
+ !exists(".composer-popup"),
+ "composer warning is not shown by default"
+ );
+ await triggerKeyEvent(".d-editor-input", "keyup", "Space");
+ assert.ok(exists(".composer-popup"), "shows composer warning message");
+ assert.ok(
+ query(".composer-popup").innerHTML.includes(
+ I18n.t("composer.user_not_seen_in_a_while.single", {
+ usernames: ['@charlie'],
+ time_ago: "1 year ago",
+ })
+ ),
+ "warning message has correct body"
+ );
+ });
+});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-tags-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-tags-test.js
index 3ea0d2fe9e..fcea997085 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/composer-tags-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/composer-tags-test.js
@@ -1,5 +1,6 @@
import {
acceptance,
+ exists,
query,
updateCurrentUser,
} from "discourse/tests/helpers/qunit-helpers";
@@ -98,4 +99,28 @@ acceptance("Composer - Tags", function (needs) {
await click("#reply-control button.create");
assert.notStrictEqual(currentURL(), "/");
});
+
+ test("users who cannot tag PMs do not see the selector", async function (assert) {
+ await visit("/u/charlie");
+ await click("button.compose-pm");
+
+ assert.notOk(exists(".composer-fields .mini-tag-chooser"));
+ });
+});
+
+acceptance("Composer - Tags (PMs)", function (needs) {
+ needs.user();
+ needs.pretender((server, helper) => {
+ server.post("/uploads/lookup-urls", () => {
+ return helper.response([]);
+ });
+ });
+ needs.site({ can_tag_topics: true, can_tag_pms: true });
+
+ test("users who can tag PMs see the selector", async function (assert) {
+ await visit("/u/charlie");
+ await click("button.compose-pm");
+
+ assert.ok(exists(".composer-fields .mini-tag-chooser"));
+ });
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js
index 5c60c6086b..88c616e3fd 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/composer-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/composer-test.js
@@ -1,4 +1,11 @@
-import { click, currentURL, fillIn, settled, visit } from "@ember/test-helpers";
+import {
+ click,
+ currentURL,
+ fillIn,
+ settled,
+ triggerEvent,
+ visit,
+} from "@ember/test-helpers";
import { toggleCheckDraftPopup } from "discourse/controllers/composer";
import { cloneJSON } from "discourse-common/lib/object";
import TopicFixtures from "discourse/tests/fixtures/topic";
@@ -21,8 +28,8 @@ import {
import selectKit from "discourse/tests/helpers/select-kit-helper";
import I18n from "I18n";
import { test } from "qunit";
-import { Promise } from "rsvp";
import sinon from "sinon";
+import pretender, { response } from "discourse/tests/helpers/create-pretender";
acceptance("Composer", function (needs) {
needs.user({
@@ -57,6 +64,67 @@ acceptance("Composer", function (needs) {
});
});
+ needs.hooks.afterEach(() => toggleCheckDraftPopup(false));
+
+ test("Composer is opened", async function (assert) {
+ await visit("/");
+ await click("#create-topic");
+
+ assert.strictEqual(
+ document.documentElement.style.getPropertyValue("--composer-height"),
+ "var(--new-topic-composer-height, 400px)",
+ "sets --composer-height to 400px when creating topic"
+ );
+
+ await fillIn(
+ ".d-editor-input",
+ "this is the *content* of a new topic post"
+ );
+ await click(".toggle-minimize");
+ assert.strictEqual(
+ document.documentElement.style.getPropertyValue("--composer-height"),
+ "40px",
+ "sets --composer-height to 40px when composer is minimized to draft mode"
+ );
+
+ await click(".toggle-fullscreen");
+ assert.strictEqual(
+ document.documentElement.style.getPropertyValue("--composer-height"),
+ "var(--new-topic-composer-height, 400px)",
+ "sets --composer-height back to 400px when composer is opened from draft mode"
+ );
+
+ await fillIn(".d-editor-input", "");
+ await click(".toggle-minimize");
+ assert.strictEqual(
+ document.documentElement.style.getPropertyValue("--composer-height"),
+ "",
+ "removes --composer-height property when composer is closed"
+ );
+ });
+
+ test("Composer height adjustment", async function (assert) {
+ await visit("/");
+ await click("#create-topic");
+ await triggerEvent(document.querySelector(".grippie"), "mousedown");
+ await triggerEvent(document.querySelector(".grippie"), "mousemove");
+ await triggerEvent(document.querySelector(".grippie"), "mouseup");
+ await visit("/"); // reload page
+ await click("#create-topic");
+
+ const expectedHeight = localStorage.getItem(
+ "__test_discourse_composerHeight"
+ );
+ const actualHeight =
+ document.documentElement.style.getPropertyValue("--composer-height");
+
+ assert.strictEqual(
+ expectedHeight,
+ actualHeight,
+ "Updated height is persistent"
+ );
+ });
+
test("composer controls", async function (assert) {
await visit("/");
assert.ok(exists("#create-topic"), "the create button is visible");
@@ -143,9 +211,9 @@ acceptance("Composer", function (needs) {
await fillIn("#reply-title", "this title triggers an error");
await fillIn(".d-editor-input", "this is the *content* of a post");
await click("#reply-control button.create");
- assert.ok(exists(".bootbox.modal"), "it pops up an error message");
- await click(".bootbox.modal a.btn-primary");
- assert.ok(!exists(".bootbox.modal"), "it dismisses the error");
+ assert.ok(exists(".dialog-body"), "it pops up an error message");
+ await click(".dialog-footer .btn-primary");
+ assert.ok(!exists(".dialog-body"), "it dismisses the error");
assert.ok(exists(".d-editor-input"), "the composer input is visible");
});
@@ -184,13 +252,14 @@ acceptance("Composer", function (needs) {
await fillIn("#reply-title", "This title doesn't matter");
await fillIn(".d-editor-input", "custom message");
await click("#reply-control button.create");
+
assert.strictEqual(
- query(".bootbox .modal-body").innerText,
+ query("#dialog-holder .dialog-body").innerText,
"This is a custom response"
);
assert.strictEqual(currentURL(), "/", "it doesn't change routes");
- await click(".bootbox .btn-primary");
+ await click(".dialog-footer .btn-primary");
assert.strictEqual(
currentURL(),
"/faq",
@@ -278,7 +347,10 @@ acceptance("Composer", function (needs) {
await fillIn(".d-editor-input", "this is the content of the first reply");
await visit("/t/this-is-a-test-topic/9");
- assert.strictEqual(currentURL(), "/t/this-is-a-test-topic/9");
+ assert.ok(
+ currentURL().startsWith("/t/this-is-a-test-topic/9"),
+ "moves to second topic"
+ );
await click("#topic-footer-buttons .btn.create");
assert.ok(
exists(".discard-draft-modal.modal"),
@@ -370,34 +442,27 @@ acceptance("Composer", function (needs) {
test("Editing a post stages new content", async function (assert) {
await visit("/t/internationalization-localization/280");
- await click(".topic-post:nth-of-type(1) button.show-more-actions");
- await click(".topic-post:nth-of-type(1) button.edit");
+ await click(".topic-post button.show-more-actions");
+ await click(".topic-post button.edit");
await fillIn(".d-editor-input", "will return empty json");
await fillIn("#reply-title", "This is the new text for the title");
- // when this promise resolves, the request had already started because
- // this promise will be resolved by the pretender
- const promise = new Promise((resolve) => {
- window.resolveLastPromise = resolve;
+ pretender.put("/posts/:post_id", async () => {
+ // at this point, request is in flight, so post is staged
+ assert.strictEqual(count(".topic-post.staged"), 1);
+ assert.ok(query(".topic-post").classList.contains("staged"));
+ assert.strictEqual(
+ query(".topic-post.staged .cooked").innerText.trim(),
+ "will return empty json"
+ );
+
+ return response(200, {});
});
- // click to trigger the save, but wait until the request starts
- click("#reply-control button.create");
- await promise;
+ await click("#reply-control button.create");
- // at this point, request is in flight, so post is staged
- assert.strictEqual(count(".topic-post.staged"), 1);
- assert.ok(query(".topic-post:nth-of-type(1)").className.includes("staged"));
- assert.strictEqual(
- query(".topic-post.staged .cooked").innerText.trim(),
- "will return empty json"
- );
-
- // finally, finish request and wait for last render
- window.resolveLastPromise();
await visit("/t/internationalization-localization/280");
-
assert.strictEqual(count(".topic-post.staged"), 0);
});
@@ -509,6 +574,12 @@ acceptance("Composer", function (needs) {
"it expands composer to full screen"
);
+ assert.strictEqual(
+ count(".composer-fullscreen-prompt"),
+ 1,
+ "the exit fullscreen prompt is visible"
+ );
+
await click(".toggle-fullscreen");
assert.strictEqual(
@@ -535,6 +606,34 @@ acceptance("Composer", function (needs) {
);
});
+ test("Composer fullscreen submit button", async function (assert) {
+ await visit("/t/this-is-a-test-topic/9");
+ await click(".topic-post:nth-of-type(1) button.reply");
+
+ assert.strictEqual(
+ count("#reply-control.open"),
+ 1,
+ "it starts in open state by default"
+ );
+
+ await click(".toggle-fullscreen");
+
+ assert.strictEqual(
+ count("#reply-control button.create"),
+ 1,
+ "it shows composer submit button in fullscreen"
+ );
+
+ await fillIn(".d-editor-input", "too short");
+ await click("#reply-control button.create");
+
+ assert.strictEqual(
+ count("#reply-control.open"),
+ 1,
+ "it goes back to open state if there's errors"
+ );
+ });
+
test("Composer can toggle between reply and createTopic", async function (assert) {
await visit("/t/this-is-a-test-topic/9");
await click(".topic-post:nth-of-type(1) button.reply");
@@ -687,109 +786,91 @@ acceptance("Composer", function (needs) {
});
test("Checks for existing draft", async function (assert) {
- try {
- toggleCheckDraftPopup(true);
+ toggleCheckDraftPopup(true);
- await visit("/t/internationalization-localization/280");
+ await visit("/t/internationalization-localization/280");
- await click(".topic-post:nth-of-type(1) button.show-more-actions");
- await click(".topic-post:nth-of-type(1) button.edit");
+ await click(".topic-post:nth-of-type(1) button.show-more-actions");
+ await click(".topic-post:nth-of-type(1) button.edit");
- assert.strictEqual(
- query(".modal-body").innerText,
- I18n.t("drafts.abandon.confirm")
- );
+ assert.strictEqual(
+ query(".dialog-body").innerText,
+ I18n.t("drafts.abandon.confirm")
+ );
- await click(".modal-footer .btn.btn-default");
- } finally {
- toggleCheckDraftPopup(false);
- }
+ await click(".dialog-footer .btn-resume-editing");
});
test("Can switch states without abandon popup", async function (assert) {
- try {
- toggleCheckDraftPopup(true);
+ toggleCheckDraftPopup(true);
- await visit("/t/internationalization-localization/280");
+ await visit("/t/internationalization-localization/280");
- const longText = "a".repeat(256);
+ const longText = "a".repeat(256);
- sinon.stub(Draft, "get").returns(
- Promise.resolve({
- draft: null,
- draft_sequence: 0,
- })
- );
+ sinon.stub(Draft, "get").resolves({
+ draft: null,
+ draft_sequence: 0,
+ });
- await click(".btn-primary.create.btn");
+ await click(".btn-primary.create.btn");
- await fillIn(".d-editor-input", longText);
+ await fillIn(".d-editor-input", longText);
- assert.ok(
- exists(
- '.action-title a[href="/t/internationalization-localization/280"]'
- ),
- "the mode should be: reply to post"
- );
+ assert.ok(
+ exists(
+ '.action-title a[href="/t/internationalization-localization/280"]'
+ ),
+ "the mode should be: reply to post"
+ );
- await click("article#post_3 button.reply");
+ await click("article#post_3 button.reply");
- const composerActions = selectKit(".composer-actions");
- await composerActions.expand();
- await composerActions.selectRowByValue("reply_as_new_topic");
+ const composerActions = selectKit(".composer-actions");
+ await composerActions.expand();
+ await composerActions.selectRowByValue("reply_as_new_topic");
- assert.ok(!exists(".modal-body"), "abandon popup shouldn't come");
+ assert.ok(!exists(".modal-body"), "abandon popup shouldn't come");
- assert.ok(
- query(".d-editor-input").value.includes(longText),
- "entered text should still be there"
- );
+ assert.ok(
+ query(".d-editor-input").value.includes(longText),
+ "entered text should still be there"
+ );
- assert.ok(
- !exists(
- '.action-title a[href="/t/internationalization-localization/280"]'
- ),
- "mode should have changed"
- );
- } finally {
- toggleCheckDraftPopup(false);
- }
+ assert.ok(
+ !exists(
+ '.action-title a[href="/t/internationalization-localization/280"]'
+ ),
+ "mode should have changed"
+ );
});
test("Loading draft also replaces the recipients", async function (assert) {
- try {
- toggleCheckDraftPopup(true);
+ toggleCheckDraftPopup(true);
- sinon.stub(Draft, "get").returns(
- Promise.resolve({
- draft:
- '{"reply":"hello","action":"privateMessage","title":"hello","categoryId":null,"archetypeId":"private_message","metaData":null,"recipients":"codinghorror","composerTime":9159,"typingTime":2500}',
- draft_sequence: 0,
- })
- );
+ sinon.stub(Draft, "get").resolves({
+ draft:
+ '{"reply":"hello","action":"privateMessage","title":"hello","categoryId":null,"archetypeId":"private_message","metaData":null,"recipients":"codinghorror","composerTime":9159,"typingTime":2500}',
+ draft_sequence: 0,
+ });
- await visit("/u/charlie");
- await click("button.compose-pm");
- await click(".modal .btn-default");
+ await visit("/u/charlie");
+ await click("button.compose-pm");
+ await click(".dialog-footer .btn-resume-editing");
- const privateMessageUsers = selectKit("#private-message-users");
- assert.strictEqual(privateMessageUsers.header().value(), "codinghorror");
- } finally {
- toggleCheckDraftPopup(false);
- }
+ const privateMessageUsers = selectKit("#private-message-users");
+ assert.strictEqual(privateMessageUsers.header().value(), "codinghorror");
});
test("Loads tags and category from draft payload", async function (assert) {
updateCurrentUser({ has_topic_draft: true });
- sinon.stub(Draft, "get").returns(
- Promise.resolve({
- draft:
- '{"reply":"Hey there","action":"createTopic","title":"Draft topic","categoryId":2,"tags":["fun", "times"],"archetypeId":"regular","metaData":null,"composerTime":25269,"typingTime":8100}',
- draft_sequence: 0,
- draft_key: NEW_TOPIC_KEY,
- })
- );
+ sinon.stub(Draft, "get").resolves({
+ draft:
+ '{"reply":"Hey there","action":"createTopic","title":"Draft topic","categoryId":2,"tags":["fun", "times"],"archetypeId":"regular","metaData":null,"composerTime":25269,"typingTime":8100}',
+ draft_sequence: 0,
+ draft_key: NEW_TOPIC_KEY,
+ });
await visit("/latest");
assert.strictEqual(
diff --git a/app/assets/javascripts/discourse/tests/acceptance/composer-uploads-uppy-test.js b/app/assets/javascripts/discourse/tests/acceptance/composer-uploads-uppy-test.js
index c966f796d4..0ff8883870 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/composer-uploads-uppy-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/composer-uploads-uppy-test.js
@@ -6,8 +6,7 @@ import {
query,
} from "discourse/tests/helpers/qunit-helpers";
import { withPluginApi } from "discourse/lib/plugin-api";
-import bootbox from "bootbox";
-import { authorizedExtensions } from "discourse/lib/uploads";
+import { authorizedExtensions, dialog } from "discourse/lib/uploads";
import { click, fillIn, settled, visit } from "@ember/test-helpers";
import I18n from "I18n";
import { skip, test } from "qunit";
@@ -125,15 +124,16 @@ acceptance("Uppy Composer Attachment - Upload Placeholder", function (needs) {
const image2 = createFile("avatar2.png");
const done = assert.async();
appEvents.on("composer:uploads-aborted", async () => {
+ await settled();
assert.strictEqual(
- query(".bootbox .modal-body").innerHTML,
+ query(".dialog-body").textContent.trim(),
I18n.t("post.errors.too_many_dragged_and_dropped_files", {
count: 2,
}),
"it should warn about too many files added"
);
- await click(".modal-footer .btn-primary");
+ await click(".dialog-footer .btn-primary");
done();
});
@@ -149,8 +149,9 @@ acceptance("Uppy Composer Attachment - Upload Placeholder", function (needs) {
const done = assert.async();
appEvents.on("composer:uploads-aborted", async () => {
+ await settled();
assert.strictEqual(
- query(".bootbox .modal-body").innerHTML,
+ query(".dialog-body").textContent.trim(),
I18n.t("post.errors.upload_not_authorized", {
authorized_extensions: authorizedExtensions(
false,
@@ -160,7 +161,7 @@ acceptance("Uppy Composer Attachment - Upload Placeholder", function (needs) {
"it should warn about unauthorized extensions"
);
- await click(".modal-footer .btn-primary");
+ await click(".dialog-footer .btn-primary");
done();
});
@@ -438,14 +439,14 @@ acceptance("Uppy Composer Attachment - Upload Error", function (needs) {
appEvents.on("composer:upload-error", async () => {
sinon.assert.calledOnce(stub);
+ await settled();
assert.strictEqual(
- query(".bootbox .modal-body").innerHTML,
+ query(".dialog-body").textContent.trim(),
"There was an error uploading the file, the gif was way too cool.",
"it should show the error message from the server"
);
- await click(".modal-footer .btn-primary");
-
+ await click(".dialog-footer .btn-primary");
done();
});
@@ -465,7 +466,7 @@ acceptance("Uppy Composer Attachment - Upload Handler", function (needs) {
api.addComposerUploadHandler(["png"], (files) => {
const file = files[0];
const isNativeFile = file instanceof File ? "WAS" : "WAS NOT";
- bootbox.alert(
+ dialog.alert(
`This is an upload handler test for ${file.name}. The file ${isNativeFile} a native file object.`
);
});
@@ -480,12 +481,13 @@ acceptance("Uppy Composer Attachment - Upload Handler", function (needs) {
const done = assert.async();
appEvents.on("composer:uploads-aborted", async () => {
+ await settled();
assert.strictEqual(
- query(".bootbox .modal-body").innerHTML,
+ query(".dialog-body").textContent.trim(),
"This is an upload handler test for handler-test.png. The file WAS a native file object.",
- "it should show the bootbox triggered by the upload handler"
+ "it should show the dialog triggered by the upload handler"
);
- await click(".modal-footer .btn");
+ await click(".dialog-footer .btn-primary");
done();
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/do-not-disturb-test.js b/app/assets/javascripts/discourse/tests/acceptance/do-not-disturb-test.js
index eb1f7e3bfb..e96f21f77d 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/do-not-disturb-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/do-not-disturb-test.js
@@ -100,3 +100,124 @@ acceptance("Do not disturb", function (needs) {
);
});
});
+
+acceptance("Do not disturb - new user menu", function (needs) {
+ needs.user({ redesigned_user_menu_enabled: true });
+ needs.pretender((server, helper) => {
+ server.post("/do-not-disturb.json", () => {
+ const now = new Date();
+ now.setHours(now.getHours() + 1);
+ return helper.response({ ends_at: now });
+ });
+ server.delete("/do-not-disturb.json", () =>
+ helper.response({ success: true })
+ );
+ });
+
+ test("when turned off, it is turned on from modal", async function (assert) {
+ updateCurrentUser({ do_not_disturb_until: null });
+
+ await visit("/");
+ await click(".header-dropdown-toggle.current-user");
+ await click("#user-menu-button-profile");
+ await click("#quick-access-profile .do-not-disturb .btn");
+
+ assert.ok(exists(".do-not-disturb-modal"), "modal to choose time appears");
+
+ let tiles = queryAll(".do-not-disturb-tile");
+ assert.ok(tiles.length === 4, "There are 4 duration choices");
+
+ await click(tiles[0]);
+
+ assert.ok(query(".do-not-disturb-modal.hidden"), "modal is hidden");
+
+ assert.ok(
+ exists(".header-dropdown-toggle .do-not-disturb-background .d-icon-moon"),
+ "moon icon is present in header"
+ );
+ });
+
+ test("Can be invoked via keyboard", async function (assert) {
+ updateCurrentUser({ do_not_disturb_until: null });
+
+ await visit("/");
+ await click(".header-dropdown-toggle.current-user");
+ await click("#user-menu-button-profile");
+ await click("#quick-access-profile .do-not-disturb .btn");
+
+ assert.ok(exists(".do-not-disturb-modal"), "DND modal is displayed");
+
+ assert.strictEqual(
+ count(".do-not-disturb-tile"),
+ 4,
+ "There are 4 duration choices"
+ );
+
+ await triggerKeyEvent(
+ ".do-not-disturb-tile:nth-child(1)",
+ "keydown",
+ "Enter"
+ );
+
+ assert.ok(
+ query(".do-not-disturb-modal.hidden"),
+ "DND modal is hidden after making a choice"
+ );
+
+ assert.ok(
+ exists(".header-dropdown-toggle .do-not-disturb-background .d-icon-moon"),
+ "moon icon is shown in header avatar"
+ );
+ });
+
+ test("when turned on, it can be turned off", async function (assert) {
+ const now = new Date();
+ now.setHours(now.getHours() + 1);
+ updateCurrentUser({ do_not_disturb_until: now });
+
+ await visit("/");
+
+ assert.ok(
+ exists(".do-not-disturb-background"),
+ "The active moon icon is shown"
+ );
+
+ await click(".header-dropdown-toggle.current-user");
+ await click("#user-menu-button-profile");
+ assert.strictEqual(
+ query(".do-not-disturb .relative-date").textContent.trim(),
+ "1h",
+ "the Do Not Disturb button shows how much time is left for DND mode"
+ );
+ assert.ok(
+ exists(".do-not-disturb .d-icon-toggle-on"),
+ "the Do Not Disturb button has the toggle-on icon"
+ );
+
+ await click("#quick-access-profile .do-not-disturb .btn");
+
+ assert.notOk(
+ exists(".do-not-disturb-background"),
+ "The active moon icons are removed"
+ );
+ assert.notOk(
+ exists(".do-not-disturb .relative-date"),
+ "the text showing how much time is left for DND mode is gone"
+ );
+ assert.ok(
+ exists(".do-not-disturb .d-icon-toggle-off"),
+ "the Do Not Disturb button has the toggle-off icon"
+ );
+ });
+
+ test("user menu gets closed when the DnD modal is opened", async function (assert) {
+ this.siteSettings.enable_user_status = true;
+
+ await visit("/");
+ await click(".header-dropdown-toggle.current-user");
+ await click("#user-menu-button-profile");
+ await click("#quick-access-profile .do-not-disturb .btn");
+
+ assert.notOk(exists(".user-menu"));
+ });
+});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/emoji-picker-test.js b/app/assets/javascripts/discourse/tests/acceptance/emoji-picker-test.js
index 5faac2f7dc..6245f6cfa8 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/emoji-picker-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/emoji-picker-test.js
@@ -5,7 +5,7 @@ import {
query,
queryAll,
} from "discourse/tests/helpers/qunit-helpers";
-import { click, fillIn, visit } from "@ember/test-helpers";
+import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers";
import { test } from "qunit";
acceptance("EmojiPicker", function (needs) {
@@ -179,4 +179,99 @@ acceptance("EmojiPicker", function (needs) {
"it stores diversity scale"
);
});
+
+ test("emoji can be selected with keyboard", async function (assert) {
+ const searchInput = ".emoji-picker-search-container input";
+ await visit("/t/internationalization-localization/280");
+ await click("#topic-footer-buttons .btn.create");
+ await click(".emoji.btn");
+
+ let emojis = document.querySelectorAll(
+ ".emoji-picker-emoji-area img.emoji"
+ );
+
+ assert.strictEqual(
+ document.activeElement,
+ document.querySelector(searchInput),
+ "search input is focused by default"
+ );
+
+ await triggerKeyEvent(searchInput, "keydown", "ArrowDown");
+ assert.strictEqual(
+ document.activeElement,
+ emojis[0],
+ "ArrowDown from search focuses on the first emoji result"
+ );
+
+ await triggerKeyEvent(document.activeElement, "keydown", "ArrowRight");
+ assert.strictEqual(
+ document.activeElement,
+ emojis[1],
+ "ArrowRight from first emoji focuses on the second emoji"
+ );
+
+ await triggerKeyEvent(document.activeElement, "keydown", "ArrowLeft");
+ assert.strictEqual(
+ document.activeElement,
+ emojis[0],
+ "ArrowLeft from second emoji focuses on the first emoji"
+ );
+
+ await triggerKeyEvent(document.activeElement, "keydown", "ArrowRight");
+ await triggerKeyEvent(document.activeElement, "keydown", "Enter");
+ assert.strictEqual(
+ document.querySelector(".d-editor-input").value,
+ ":smiley:",
+ "Pressing enter inserts the emoji markup in the composer"
+ );
+
+ await click("#topic-footer-buttons .btn.create");
+ await click(".emoji.btn");
+ await triggerKeyEvent(searchInput, "keydown", "ArrowDown");
+ emojis = document.querySelectorAll(".emoji-picker-emoji-area img.emoji");
+
+ assert.strictEqual(
+ document.activeElement,
+ document.querySelector(".emoji-picker-emoji-area .emoji.recent-emoji"),
+ "ArrowDown focuses on the first emoji result (recent emoji)"
+ );
+
+ await triggerKeyEvent(document.activeElement, "keydown", "ArrowDown");
+ assert.strictEqual(
+ document.activeElement,
+ document.querySelector(".emojis-container .emoji[title='grinning']"),
+ "ArrowDown again focuses on the first emoji result in a section"
+ );
+
+ await triggerKeyEvent(document.activeElement, "keydown", "ArrowRight");
+ await triggerKeyEvent(document.activeElement, "keydown", "ArrowRight");
+ await triggerKeyEvent(document.activeElement, "keydown", "ArrowRight");
+
+ assert.strictEqual(
+ document.activeElement,
+ emojis[4],
+ "ArrowRight moves focus to next right element"
+ );
+
+ await triggerKeyEvent(document.activeElement, "keydown", "ArrowUp");
+
+ assert.strictEqual(
+ document.activeElement,
+ document.querySelector(searchInput),
+ "ArrowUp from first row items moves focus to input"
+ );
+ });
+
+ test("emoji picker can be dismissed with escape key", async function (assert) {
+ await visit("/t/internationalization-localization/280");
+ await click("#topic-footer-buttons .btn.create");
+ await click("button.emoji.btn");
+ await triggerKeyEvent(document.activeElement, "keydown", "Escape");
+ assert.notOk(exists(".emoji-picker"));
+ assert.strictEqual(
+ document.activeElement,
+ document.querySelector("textarea"),
+ "escaping from emoji picker focuses back on input"
+ );
+ });
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/group-manage-email-settings-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-manage-email-settings-test.js
index cff0acf17b..9fb6577981 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/group-manage-email-settings-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/group-manage-email-settings-test.js
@@ -126,12 +126,12 @@ acceptance(
await click("#enable_smtp");
assert.strictEqual(
- query(".modal-body").innerText,
+ query(".dialog-body").innerText.trim(),
I18n.t("groups.manage.email.smtp_disable_confirm"),
"shows a confirm dialogue warning SMTP settings will be wiped"
);
- await click(".modal-footer .btn.btn-primary");
+ await click(".dialog-footer .btn-primary");
});
test("enabling IMAP, testing, and saving", async function (assert) {
@@ -202,11 +202,11 @@ acceptance(
await click("#enable_imap");
assert.strictEqual(
- query(".modal-body").innerText,
+ query(".dialog-body").innerText.trim(),
I18n.t("groups.manage.email.imap_disable_confirm"),
"shows a confirm dialogue warning IMAP settings will be wiped"
);
- await click(".modal-footer .btn.btn-primary");
+ await click(".dialog-footer .btn-primary");
});
}
);
@@ -362,11 +362,11 @@ acceptance(
await click(".test-smtp-settings");
assert.strictEqual(
- query(".modal-body").innerText,
+ query(".dialog-body").innerText.trim(),
"There was an issue with the SMTP credentials provided, check the username and password and try again.",
"shows a dialogue with the error message from the server"
);
- await click(".modal-footer .btn.btn-primary");
+ await click(".dialog-footer .btn-primary");
});
}
);
diff --git a/app/assets/javascripts/discourse/tests/acceptance/group-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-test.js
index 194fdab4b2..445e403350 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/group-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/group-test.js
@@ -275,14 +275,21 @@ acceptance("Group - Authenticated", function (needs) {
await click(".group-details-button button.btn-danger");
assert.strictEqual(
- query(".bootbox .modal-body").innerHTML,
+ query(".dialog-body").textContent.trim(),
I18n.t("admin.groups.delete_with_messages_confirm", {
count: 2,
}),
"it should warn about orphan messages"
);
- await click(".modal-footer .btn-default");
+ await click(".dialog-footer .btn-default");
+
+ await visit("/g/discourse/activity/posts");
+
+ assert.ok(
+ ".user-stream-item a.avatar-link[href='/u/awesomerobot']",
+ "avatar link contains href (is tabbable)"
+ );
});
test("Moderator Viewing Group", async function (assert) {
diff --git a/app/assets/javascripts/discourse/tests/acceptance/login-with-email-test.js b/app/assets/javascripts/discourse/tests/acceptance/login-with-email-test.js
index ee9718f0fa..15f83901d2 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/login-with-email-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/login-with-email-test.js
@@ -6,6 +6,10 @@ import {
import { click, fillIn, visit } from "@ember/test-helpers";
import I18n from "I18n";
import { test } from "qunit";
+import DiscourseURL from "discourse/lib/url";
+import sinon from "sinon";
+
+const TOKEN = "sometoken";
acceptance("Login with email", function (needs) {
needs.settings({
@@ -18,6 +22,20 @@ acceptance("Login with email", function (needs) {
server.post("/u/email-login", () =>
helper.response({ success: "OK", user_found: userFound })
);
+
+ server.get(`/session/email-login/${TOKEN}.json`, () =>
+ helper.response({
+ token: TOKEN,
+ can_login: true,
+ token_email: "blah@example.com",
+ })
+ );
+
+ server.post(`/session/email-login/${TOKEN}`, () =>
+ helper.response({
+ success: true,
+ })
+ );
});
test("with email button", async function (assert) {
@@ -83,4 +101,21 @@ acceptance("Login with email", function (needs) {
userFound = false;
});
+
+ test("finish login UI", async function (assert) {
+ await visit(`/session/email-login/${TOKEN}`);
+ sinon.stub(DiscourseURL, "redirectTo");
+ await click(".email-login .btn-primary");
+ assert.true(DiscourseURL.redirectTo.calledWith("/"), "redirects to home");
+ });
+
+ test("finish login UI - safe mode", async function (assert) {
+ await visit(`/session/email-login/${TOKEN}?safe_mode=no_themes,no_plugins`);
+ sinon.stub(DiscourseURL, "redirectTo");
+ await click(".email-login .btn-primary");
+ assert.true(
+ DiscourseURL.redirectTo.calledWith("/?safe_mode=no_themes%2Cno_plugins"),
+ "redirects to home with safe mode"
+ );
+ });
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/mobile-pan-test.js b/app/assets/javascripts/discourse/tests/acceptance/mobile-pan-test.js
index 8ef7a1b70d..6fc8ea63c2 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/mobile-pan-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/mobile-pan-test.js
@@ -7,15 +7,6 @@ import {
import { click, triggerEvent, visit } from "@ember/test-helpers";
async function triggerSwipeStart(touchTarget) {
- // some tests are shown in a zoom viewport.
- // boundingClientRect is affected by the zoom and need to be multiplied by the zoom effect.
- // EG: if the element has a zoom of 50%, this DOUBLES the x and y positions and offsets.
- // The numbers you get from getBoundingClientRect are seen as twice as large... however, the
- // touch input still deals with the base inputs, not doubled. This allows us to convert for those environments.
- let zoom = parseFloat(
- window.getComputedStyle(document.querySelector("#ember-testing")).zoom || 1
- );
-
// Other tests are shown in a transformed viewport, and this is a multiple for the offsets
let scale = parseFloat(
window
@@ -26,13 +17,11 @@ async function triggerSwipeStart(touchTarget) {
const touchStart = {
touchTarget,
x:
- zoom *
- (touchTarget.getBoundingClientRect().x +
- (scale * touchTarget.offsetWidth) / 2),
+ touchTarget.getBoundingClientRect().x +
+ (scale * touchTarget.offsetWidth) / 2,
y:
- zoom *
- (touchTarget.getBoundingClientRect().y +
- (scale * touchTarget.offsetHeight) / 2),
+ touchTarget.getBoundingClientRect().y +
+ (scale * touchTarget.offsetHeight) / 2,
};
const touch = new Touch({
identifier: "test",
diff --git a/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-multi-template-test.js b/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-multi-template-test.js
index 8caa688682..2c1ced058a 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-multi-template-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/plugin-outlet-multi-template-test.js
@@ -3,7 +3,6 @@ import {
count,
query,
} from "discourse/tests/helpers/qunit-helpers";
-import { clearCache } from "discourse/lib/plugin-connectors";
import { hbs } from "ember-cli-htmlbars";
import { test } from "qunit";
import { visit } from "@ember/test-helpers";
@@ -14,7 +13,6 @@ const GOODBYE =
acceptance("Plugin Outlet - Multi Template", function (needs) {
needs.hooks.beforeEach(() => {
- clearCache();
// eslint-disable-next-line no-undef
Ember.TEMPLATES[HELLO] = hbs`Hello`;
// eslint-disable-next-line no-undef
@@ -26,7 +24,6 @@ acceptance("Plugin Outlet - Multi Template", function (needs) {
delete Ember.TEMPLATES[HELLO];
// eslint-disable-next-line no-undef
delete Ember.TEMPLATES[GOODBYE];
- clearCache();
});
test("Renders a template into the outlet", async function (assert) {
diff --git a/app/assets/javascripts/discourse/tests/acceptance/preferences-test.js b/app/assets/javascripts/discourse/tests/acceptance/preferences-test.js
index e8bc847fec..27f91bf1e3 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/preferences-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/preferences-test.js
@@ -233,7 +233,7 @@ acceptance("User Preferences", function (needs) {
await click(".new-security-key");
assert.ok(exists("#security-key-name"), "shows security key name input");
- fillIn("#security-key-name", "");
+ await fillIn("#security-key-name", "");
// The following tests can only run when Webauthn is enabled. This is not
// always the case, for example on a browser running on a non-standard port
@@ -474,63 +474,6 @@ acceptance("User Preferences when badges are disabled", function (needs) {
});
});
-acceptance(
- "User can select a topic to feature on profile if site setting in enabled",
- function (needs) {
- needs.user();
- needs.settings({ allow_featured_topic_on_user_profiles: true });
- needs.pretender((server, helper) => {
- server.put("/u/eviltrout/feature-topic", () => {
- return helper.response({
- success: true,
- });
- });
- });
-
- test("setting featured topic on profile", async function (assert) {
- await visit("/u/eviltrout/preferences/profile");
-
- assert.ok(
- !exists(".featured-topic-link"),
- "no featured topic link to present"
- );
- assert.ok(
- !exists(".clear-feature-topic-on-profile-btn"),
- "clear button not present"
- );
-
- const selectTopicBtn = query(
- ".feature-topic-on-profile-btn:nth-of-type(1)"
- );
- assert.ok(exists(selectTopicBtn), "feature topic button is present");
-
- await click(selectTopicBtn);
-
- assert.ok(
- exists(".feature-topic-on-profile"),
- "topic picker modal is open"
- );
-
- const topicRadioBtn = query(
- 'input[name="choose_topic_id"]:nth-of-type(1)'
- );
- assert.ok(exists(topicRadioBtn), "Topic options are prefilled");
- await click(topicRadioBtn);
-
- await click(".save-featured-topic-on-profile");
-
- assert.ok(
- exists(".featured-topic-link"),
- "link to featured topic is present"
- );
- assert.ok(
- exists(".clear-feature-topic-on-profile-btn"),
- "clear button is present"
- );
- });
- }
-);
-
acceptance("Custom User Fields", function (needs) {
needs.user();
needs.site({
diff --git a/app/assets/javascripts/discourse/tests/acceptance/redirect-to-top-test.js b/app/assets/javascripts/discourse/tests/acceptance/redirect-to-top-test.js
index 8774ebf8e4..ac40d43db0 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/redirect-to-top-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/redirect-to-top-test.js
@@ -8,9 +8,6 @@ import { test } from "qunit";
acceptance("Redirect to Top", function (needs) {
needs.pretender((server, helper) => {
- server.get("/top.json?period=weekly", () => {
- return helper.response(DiscoveryFixtures["/latest.json"]);
- });
server.get("/top/monthly.json", () => {
return helper.response(DiscoveryFixtures["/latest.json"]);
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/review-test.js b/app/assets/javascripts/discourse/tests/acceptance/review-test.js
index d6659ffa3c..3272b8bd15 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/review-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/review-test.js
@@ -2,8 +2,10 @@ import {
acceptance,
count,
exists,
+ loggedInUser,
publishToMessageBus,
query,
+ updateCurrentUser,
visible,
} from "discourse/tests/helpers/qunit-helpers";
import { click, fillIn, visit } from "@ember/test-helpers";
@@ -210,7 +212,8 @@ acceptance("Review", function (needs) {
);
});
- test("Reviewables can become stale", async function (assert) {
+ test("Reviewables can become stale when redesigned_user_menu_enabled is false", async function (assert) {
+ updateCurrentUser({ redesigned_user_menu_enabled: false });
await visit("/review");
const reviewable = query(`[data-reviewable-id="1234"]`);
@@ -238,4 +241,34 @@ acceptance("Review", function (needs) {
assert.strictEqual(count(".stale-help"), 0);
});
+
+ test("Reviewables can become stale when redesigned_user_menu_enabled is true", async function (assert) {
+ updateCurrentUser({ redesigned_user_menu_enabled: true });
+ await visit("/review");
+
+ const reviewable = query(`[data-reviewable-id="1234"]`);
+ assert.notOk(reviewable.className.includes("reviewable-stale"));
+ assert.strictEqual(
+ count(`[data-reviewable-id="1234"] .status .pending`),
+ 1
+ );
+ assert.ok(!exists(".stale-help"));
+
+ await publishToMessageBus(`/reviewable_counts/${loggedInUser().id}`, {
+ review_count: 1,
+ updates: {
+ 1234: { last_performing_username: "foo", status: 1 },
+ },
+ });
+
+ assert.ok(reviewable.className.includes("reviewable-stale"));
+ assert.strictEqual(count("[data-reviewable-id=1234] .status .approved"), 1);
+ assert.strictEqual(count(".stale-help"), 1);
+ assert.ok(query(".stale-help").innerText.includes("foo"));
+
+ await visit("/");
+ await visit("/review"); // reload review
+
+ assert.strictEqual(count(".stale-help"), 0);
+ });
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/share-topic-test.js b/app/assets/javascripts/discourse/tests/acceptance/share-topic-test.js
index be66565f7a..47847c6c67 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/share-topic-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/share-topic-test.js
@@ -139,3 +139,24 @@ acceptance("Share url with badges disabled - desktop", function (needs) {
);
});
});
+
+acceptance("With username in share links disabled - desktop", function (needs) {
+ needs.user();
+ needs.settings({ allow_username_in_share_links: false });
+
+ needs.pretender((server, helper) => {
+ server.get("/c/feature/find_by_slug.json", () =>
+ helper.response(200, CategoryFixtures["/c/1/show.json"])
+ );
+ });
+
+ test("topic footer button - username in share links disabled - desktop", async function (assert) {
+ await visit("/t/internationalization-localization/280");
+ await click("#topic-footer-button-share-and-invite");
+
+ assert.notOk(
+ query("input.invite-link").value.includes("?u=eviltrout"),
+ "it doesn't add the username param when username in share links are disabled"
+ );
+ });
+});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-anonymous-categories-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-anonymous-categories-section-test.js
new file mode 100644
index 0000000000..ef1a442676
--- /dev/null
+++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-anonymous-categories-section-test.js
@@ -0,0 +1,74 @@
+import { test } from "qunit";
+import { visit } from "@ember/test-helpers";
+import {
+ acceptance,
+ exists,
+ queryAll,
+} from "discourse/tests/helpers/qunit-helpers";
+import Site from "discourse/models/site";
+
+acceptance("Sidebar - Anonymous Categories Section", function (needs) {
+ needs.settings({
+ enable_experimental_sidebar_hamburger: true,
+ enable_sidebar: true,
+ });
+
+ test("category section links", async function (assert) {
+ await visit("/");
+
+ const categories = queryAll(
+ ".sidebar-section-categories .sidebar-section-link-wrapper"
+ );
+ assert.strictEqual(categories.length, 6);
+ assert.strictEqual(categories[0].textContent.trim(), "bug");
+ assert.strictEqual(categories[1].textContent.trim(), "dev");
+ assert.strictEqual(categories[2].textContent.trim(), "feature");
+ assert.strictEqual(categories[3].textContent.trim(), "support");
+ assert.strictEqual(categories[4].textContent.trim(), "ux");
+
+ assert.ok(
+ exists("a.sidebar-section-link-all-categories"),
+ "all categories link is visible"
+ );
+ });
+
+ test("category section links in sidebar when default_sidebar_categories site setting has been configured", async function (assert) {
+ this.siteSettings.default_sidebar_categories = "3|13|1";
+ await visit("/");
+
+ const categories = queryAll(
+ ".sidebar-section-categories .sidebar-section-link-wrapper"
+ );
+
+ assert.strictEqual(categories.length, 4);
+ assert.strictEqual(categories[0].textContent.trim(), "blog");
+ assert.strictEqual(categories[1].textContent.trim(), "bug");
+ assert.strictEqual(categories[2].textContent.trim(), "meta");
+
+ assert.ok(
+ exists("a.sidebar-section-link-all-categories"),
+ "all categories link is visible"
+ );
+ });
+
+ test("default uncategorized category section links is not shown when allow_uncategorized_topics is disabled", async function (assert) {
+ this.siteSettings.allow_uncategorized_topics = false;
+ this.siteSettings.fixed_category_positions = true;
+ const site = Site.current();
+
+ const firstCategory = Site.current().categories.find((category) => {
+ return !category.parent_category_id;
+ });
+
+ site.set("uncategorized_category_id", firstCategory.id);
+
+ await visit("/");
+
+ assert.notOk(
+ exists(
+ `.sidebar-section-categories .sidebar-section-link-${firstCategory.slug}`
+ ),
+ "category section link is not shown in sidebar after being marked as uncategorized"
+ );
+ });
+});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-anonymous-community-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-anonymous-community-section-test.js
new file mode 100644
index 0000000000..c01d2357d9
--- /dev/null
+++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-anonymous-community-section-test.js
@@ -0,0 +1,110 @@
+import I18n from "I18n";
+
+import { test } from "qunit";
+
+import {
+ acceptance,
+ exists,
+ query,
+ queryAll,
+} from "discourse/tests/helpers/qunit-helpers";
+import { click, visit } from "@ember/test-helpers";
+
+acceptance("Sidebar - Anonymous user - Community Section", function (needs) {
+ needs.settings({
+ enable_experimental_sidebar_hamburger: true,
+ enable_sidebar: true,
+ });
+
+ test("display short site description site setting when it is set", async function (assert) {
+ this.siteSettings.short_site_description =
+ "This is a short description about the site";
+
+ await visit("/");
+
+ assert.strictEqual(
+ query(
+ ".sidebar-section-community .sidebar-section-message"
+ ).textContent.trim(),
+ this.siteSettings.short_site_description,
+ "displays the short site description under the community section"
+ );
+
+ const sectionLinks = queryAll(
+ ".sidebar-section-community .sidebar-section-link"
+ );
+
+ assert.strictEqual(
+ sectionLinks[0].textContent.trim(),
+ I18n.t("sidebar.sections.community.links.about.content"),
+ "displays the about section link first"
+ );
+ });
+
+ test("everything, users, about and FAQ section links are shown by default ", async function (assert) {
+ await visit("/");
+
+ const sectionLinks = queryAll(
+ ".sidebar-section-community .sidebar-section-link"
+ );
+
+ assert.strictEqual(
+ sectionLinks[0].textContent.trim(),
+ I18n.t("sidebar.sections.community.links.everything.content"),
+ "displays the everything section link first"
+ );
+
+ assert.strictEqual(
+ sectionLinks[1].textContent.trim(),
+ I18n.t("sidebar.sections.community.links.users.content"),
+ "displays the users section link second"
+ );
+
+ assert.strictEqual(
+ sectionLinks[2].textContent.trim(),
+ I18n.t("sidebar.sections.community.links.about.content"),
+ "displays the about section link third"
+ );
+
+ assert.strictEqual(
+ sectionLinks[3].textContent.trim(),
+ I18n.t("sidebar.sections.community.links.faq.content"),
+ "displays the FAQ section link last"
+ );
+ });
+
+ test("users section link is not shown when hide_user_profiles_from_public site setting is enabled", async function (assert) {
+ this.siteSettings.hide_user_profiles_from_public = true;
+
+ await visit("/");
+
+ assert.notOk(
+ exists(".sidebar-section-community .sidebar-section-link-users"),
+ "users section link is not shown in sidebar"
+ );
+ });
+
+ test("groups and badges section links are shown in more...", async function (assert) {
+ await visit("/");
+
+ await click(
+ ".sidebar-section-community .sidebar-more-section-links-details-summary"
+ );
+
+ const sectionLinks = queryAll(
+ ".sidebar-more-section-links-details-content-main .sidebar-section-link"
+ );
+
+ assert.strictEqual(
+ sectionLinks[0].textContent.trim(),
+ I18n.t("sidebar.sections.community.links.groups.content"),
+ "displays the groups section link first"
+ );
+
+ assert.strictEqual(
+ sectionLinks[1].textContent.trim(),
+ I18n.t("sidebar.sections.community.links.badges.content"),
+ "displays the badges section link second"
+ );
+ });
+});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-anonymous-tags-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-anonymous-tags-section-test.js
new file mode 100644
index 0000000000..dc2ba51e56
--- /dev/null
+++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-anonymous-tags-section-test.js
@@ -0,0 +1,89 @@
+import { test } from "qunit";
+import { visit } from "@ember/test-helpers";
+import {
+ acceptance,
+ exists,
+ queryAll,
+} from "discourse/tests/helpers/qunit-helpers";
+
+acceptance("Sidebar - Anonymous Tags Section", function (needs) {
+ needs.settings({
+ enable_experimental_sidebar_hamburger: true,
+ enable_sidebar: true,
+ suppress_uncategorized_badge: false,
+ tagging_enabled: true,
+ });
+
+ needs.site({
+ top_tags: ["design", "development", "fun"],
+ });
+
+ test("tag section links", async function (assert) {
+ await visit("/");
+
+ const categories = queryAll(
+ ".sidebar-section-tags .sidebar-section-link-wrapper"
+ );
+ assert.strictEqual(categories.length, 4);
+ assert.strictEqual(categories[0].textContent.trim(), "design");
+ assert.strictEqual(categories[1].textContent.trim(), "development");
+ assert.strictEqual(categories[2].textContent.trim(), "fun");
+
+ assert.ok(
+ exists("a.sidebar-section-link-all-tags"),
+ "all tags link is visible"
+ );
+ });
+});
+
+acceptance("Sidebar - Anonymous Tags Section - default tags", function (needs) {
+ needs.settings({
+ enable_experimental_sidebar_hamburger: true,
+ enable_sidebar: true,
+ suppress_uncategorized_badge: false,
+ tagging_enabled: true,
+ });
+
+ needs.site({
+ top_tags: ["design", "development", "fun"],
+ anonymous_default_sidebar_tags: ["random", "meta"],
+ });
+
+ test("tag section links", async function (assert) {
+ await visit("/");
+
+ const categories = queryAll(
+ ".sidebar-section-tags .sidebar-section-link-wrapper"
+ );
+ assert.strictEqual(categories.length, 3);
+ assert.strictEqual(categories[0].textContent.trim(), "random");
+ assert.strictEqual(categories[1].textContent.trim(), "meta");
+
+ assert.ok(
+ exists("a.sidebar-section-link-all-tags"),
+ "all tags link is visible"
+ );
+ });
+});
+
+acceptance(
+ "Sidebar - Anonymous Tags Section - Tagging disabled",
+ function (needs) {
+ needs.settings({
+ enable_experimental_sidebar_hamburger: true,
+ enable_sidebar: true,
+ suppress_uncategorized_badge: false,
+ tagging_enabled: false,
+ });
+
+ needs.site({
+ top_tags: ["design", "development", "fun"],
+ });
+
+ test("tag section links", async function (assert) {
+ await visit("/");
+
+ assert.ok(!exists(".sidebar-section-tags"), "section is not visible");
+ });
+ }
+);
diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-anonymous-user-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-anonymous-user-test.js
new file mode 100644
index 0000000000..d350dd1aaa
--- /dev/null
+++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-anonymous-user-test.js
@@ -0,0 +1,65 @@
+import { test } from "qunit";
+
+import { click, visit } from "@ember/test-helpers";
+
+import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
+
+acceptance("Sidebar - Anonymous User", function (needs) {
+ needs.settings({
+ enable_experimental_sidebar_hamburger: true,
+ enable_sidebar: true,
+ });
+
+ test("sidebar is displayed", async function (assert) {
+ await visit("/");
+
+ assert.ok(
+ document.body.classList.contains("has-sidebar-page"),
+ "adds sidebar utility class to body"
+ );
+
+ assert.ok(
+ exists(".sidebar-container"),
+ "sidebar exists for anonymous user"
+ );
+
+ assert.ok(
+ exists(".header-sidebar-toggle"),
+ "toggle button for anonymous user"
+ );
+ });
+
+ test("sidebar hamburger panel dropdown when sidebar has been disabled", async function (assert) {
+ this.siteSettings.enable_sidebar = false;
+
+ await visit("/");
+ await click(".hamburger-dropdown");
+
+ assert.ok(
+ exists(".sidebar-hamburger-dropdown .sidebar-sections-anonymous"),
+ "sidebar hamburger panel dropdown renders anonymous sidebar sections"
+ );
+ });
+});
+
+acceptance("Sidebar - Anonymous User - Login Required", function (needs) {
+ needs.settings({
+ enable_experimental_sidebar_hamburger: true,
+ enable_sidebar: true,
+ login_required: true,
+ });
+
+ test("sidebar and toggle button is hidden", async function (assert) {
+ await visit("/");
+
+ assert.ok(
+ !exists(".sidebar-container"),
+ "sidebar is hidden for anonymous user"
+ );
+
+ assert.ok(
+ !exists(".header-sidebar-toggle"),
+ "toggle button is hidden for anonymous user"
+ );
+ });
+});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-mobile-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-mobile-test.js
index 74970e2510..64bfe6c65e 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-mobile-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-mobile-test.js
@@ -18,7 +18,10 @@ acceptance("Sidebar - Mobile - User with sidebar enabled", function (needs) {
test("hidden by default", async function (assert) {
await visit("/");
- assert.ok(!exists(".sidebar-container"), "sidebar is not displayed");
+ assert.ok(
+ !exists(".sidebar-hamburger-dropdown"),
+ "sidebar is not displayed"
+ );
});
test("clicking outside sidebar collapses it", async function (assert) {
@@ -26,50 +29,33 @@ acceptance("Sidebar - Mobile - User with sidebar enabled", function (needs) {
await click(".hamburger-dropdown");
- assert.ok(exists(".sidebar-container"), "sidebar is displayed");
+ assert.ok(exists(".sidebar-hamburger-dropdown"), "sidebar is displayed");
await click("#main-outlet");
- assert.ok(!exists(".sidebar-container"), "sidebar is collapsed");
+ assert.ok(!exists(".sidebar-hamburger-dropdown"), "sidebar is collapsed");
});
test("clicking on a link or button in sidebar collapses it", async function (assert) {
await visit("/");
await click(".hamburger-dropdown");
- await click(".sidebar-section-link-tracked");
+ await click(".sidebar-section-community .sidebar-section-header-button");
assert.ok(
- !exists(".sidebar-container"),
+ !exists(".sidebar-hamburger-dropdown"),
"sidebar is collapsed when a button in sidebar is clicked"
);
await click(".hamburger-dropdown");
- await click(".sidebar-section-header-link");
+ await click(".sidebar-section-community .sidebar-section-link-everything");
assert.ok(
- !exists(".sidebar-container"),
+ !exists(".sidebar-hamburger-dropdown"),
"sidebar is collapsed when a link in sidebar is clicked"
);
});
- test("collapsing sidebar sections does not collapse sidebar", async function (assert) {
- await visit("/");
-
- await click(".hamburger-dropdown");
- await click(".sidebar-section-header-caret");
-
- assert.ok(
- !exists(".sidebar-section-community .sidebar-section-content"),
- "topics section is collapsed"
- );
-
- assert.ok(
- exists(".sidebar-container"),
- "sidebar is not collapsed when clicking on caret to collapse a section in sidebar"
- );
- });
-
test("button to toggle between mobile and desktop view", async function (assert) {
await visit("/");
await click(".hamburger-dropdown");
diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js
index 70c371c139..e83b1a05e4 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js
@@ -1,6 +1,5 @@
import { test } from "qunit";
import I18n from "I18n";
-
import { click, visit } from "@ember/test-helpers";
import {
acceptance,
@@ -9,7 +8,6 @@ import {
queryAll,
} from "discourse/tests/helpers/qunit-helpers";
import { withPluginApi } from "discourse/lib/plugin-api";
-import { resetSidebarSection } from "discourse/lib/sidebar/custom-sections";
import { bind } from "discourse-common/utils/decorators";
acceptance("Sidebar - Plugin API", function (needs) {
@@ -21,7 +19,6 @@ acceptance("Sidebar - Plugin API", function (needs) {
});
needs.hooks.afterEach(() => {
- resetSidebarSection();
linkDestroy = undefined;
sectionDestroy = undefined;
});
@@ -37,14 +34,6 @@ acceptance("Sidebar - Plugin API", function (needs) {
return "test-chat-channels";
}
- get route() {
- return "discovery.latest";
- }
-
- get title() {
- return "chat channels title";
- }
-
get text() {
return "chat channels text";
}
@@ -208,16 +197,9 @@ acceptance("Sidebar - Plugin API", function (needs) {
await visit("/");
- assert.strictEqual(
- query(".sidebar-section-test-chat-channels .sidebar-section-header-link")
- .title,
- "chat channels title",
- "displays header with correct title attribute"
- );
-
assert.strictEqual(
query(
- ".sidebar-section-test-chat-channels .sidebar-section-header-link"
+ ".sidebar-section-test-chat-channels .sidebar-section-header-text"
).textContent.trim(),
"chat channels text",
"displays header with correct text"
@@ -273,31 +255,31 @@ acceptance("Sidebar - Plugin API", function (needs) {
);
assert.strictEqual(
- links[0].children.item(0).style.color,
+ links[0].children[0].style.color,
"rgb(255, 0, 0)",
"has correct prefix color"
);
assert.strictEqual(
- $(links[0].children.item(0).children.item(0)).hasClass("d-icon-hashtag"),
+ links[0].children[0].children[0].classList.contains("d-icon-hashtag"),
true,
"displays prefix icon"
);
assert.strictEqual(
- $(links[0].children.item(0).children.item(1)).hasClass("d-icon-lock"),
+ links[0].children[0].children[1].classList.contains("d-icon-lock"),
true,
"displays prefix icon badge"
);
assert.strictEqual(
- $(links[0].children.item(2).children.item(0)).hasClass("d-icon-circle"),
+ links[0].children[2].children[0].classList.contains("d-icon-circle"),
true,
"displays suffix icon"
);
assert.strictEqual(
- $(links[1].children[1])[0].textContent.trim(),
+ links[1].children[1].textContent.trim(),
"dev channel text",
"displays second link with correct text"
);
@@ -309,19 +291,19 @@ acceptance("Sidebar - Plugin API", function (needs) {
);
assert.strictEqual(
- links[1].children.item(0).style.color,
+ links[1].children[0].style.color,
"",
"has no color style when value is invalid"
);
assert.strictEqual(
- $(links[1].children)[0].textContent.trim(),
+ links[1].children[0].textContent.trim(),
"test text",
"displays prefix text"
);
assert.strictEqual(
- $(links[2].children[1])[0].textContent.trim(),
+ links[2].children[1].textContent.trim(),
"fun channel text",
"displays third link with correct text"
);
@@ -333,7 +315,7 @@ acceptance("Sidebar - Plugin API", function (needs) {
);
assert.strictEqual(
- $(links[2].children.item(0).children).attr("src"),
+ links[2].children[0].children[0].getAttribute("src"),
"/test.png",
"uses correct prefix image url"
);
@@ -367,14 +349,6 @@ acceptance("Sidebar - Plugin API", function (needs) {
return "test-chat-channels";
}
- get route() {
- return "discovery.latest";
- }
-
- get title() {
- return "chat channels title";
- }
-
get text() {
return "chat channels text";
}
@@ -404,7 +378,7 @@ acceptance("Sidebar - Plugin API", function (needs) {
assert.strictEqual(
query(
- ".sidebar-section-test-chat-channels .sidebar-section-header-link"
+ ".sidebar-section-test-chat-channels .sidebar-section-header-text"
).textContent.trim(),
"chat channels text",
"displays header with correct text"
@@ -500,22 +474,22 @@ acceptance("Sidebar - Plugin API", function (needs) {
await visit("/");
- const customlatestSectionLink = query(
+ const customLatestSectionLink = query(
".sidebar-section-community .sidebar-section-link-latest"
);
assert.ok(
- customlatestSectionLink,
+ customLatestSectionLink,
"adds custom latest section link to community section"
);
assert.ok(
- customlatestSectionLink.href.endsWith("/latest"),
+ customLatestSectionLink.href.endsWith("/latest"),
"sets the right href attribute for the custom latest section link"
);
assert.strictEqual(
- customlatestSectionLink.textContent.trim(),
+ customLatestSectionLink.textContent.trim(),
I18n.t("filters.latest.title"),
"displays the right text for custom latest section link"
);
diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-categories-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-categories-section-test.js
similarity index 79%
rename from app/assets/javascripts/discourse/tests/acceptance/sidebar-categories-section-test.js
rename to app/assets/javascripts/discourse/tests/acceptance/sidebar-user-categories-section-test.js
index 7abc849dbb..8737c9eb9e 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-categories-section-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-categories-section-test.js
@@ -7,6 +7,7 @@ import {
exists,
publishToMessageBus,
query,
+ queryAll,
updateCurrentUser,
} from "discourse/tests/helpers/qunit-helpers";
@@ -16,10 +17,10 @@ import categoryFixture from "discourse/tests/fixtures/category-fixtures";
import { cloneJSON } from "discourse-common/lib/object";
acceptance(
- "Sidebar - Categories Section - suppress_uncategorized_badge enabled",
+ "Sidebar - Logged on user - Categories Section - allow_uncategorized_topics disabled",
function (needs) {
needs.settings({
- suppress_uncategorized_badge: true,
+ allow_uncategorized_topics: false,
enable_experimental_sidebar_hamburger: true,
enable_sidebar: true,
});
@@ -41,7 +42,9 @@ acceptance(
await visit("/");
assert.strictEqual(
- count(".sidebar-section-categories .sidebar-section-link"),
+ count(
+ ".sidebar-section-categories .sidebar-section-link:not(.sidebar-section-link-all-categories)"
+ ),
1,
"there should only be one section link under the section"
);
@@ -54,10 +57,10 @@ acceptance(
}
);
-acceptance("Sidebar - Categories Section", function (needs) {
+acceptance("Sidebar - Logged on user - Categories Section", function (needs) {
needs.user({
sidebar_category_ids: [],
- sidebar_tag_names: [],
+ sidebar_tags: [],
});
needs.settings({
@@ -84,18 +87,22 @@ acceptance("Sidebar - Categories Section", function (needs) {
const categories = Site.current().categories;
const category1 = categories[0];
const category2 = categories[1];
- updateCurrentUser({ sidebar_category_ids: [category1.id, category2.id] });
- return { category1, category2 };
+ const category3 = categories[5];
+
+ updateCurrentUser({
+ sidebar_category_ids: [category1.id, category2.id, category3.id],
+ });
+
+ return { category1, category2, category3 };
};
test("clicking on section header link", async function (assert) {
await visit("/t/280");
- await click(".sidebar-section-categories .sidebar-section-header-link");
+ await click(".sidebar-section-categories .sidebar-section-header");
- assert.strictEqual(
- currentURL(),
- "/categories",
- "it should transition to the categories page"
+ assert.notOk(
+ exists(".sidebar-section-categories .sidebar-section-content"),
+ "hides the content of the section"
);
});
@@ -144,34 +151,55 @@ acceptance("Sidebar - Categories Section", function (needs) {
);
});
- test("category section links uses the bullet style even when category_style site setting has been configured", async function (assert) {
- this.siteSettings.category_style = "box";
- const { category1 } = setupUserSidebarCategories();
+ test("category section links are sorted by category name alphabetically", async function (assert) {
+ const { category1, category2, category3 } = setupUserSidebarCategories();
+
+ category3.set("name", "aBC");
+ category2.set("name", "abc");
+ category1.set("name", "efg");
await visit("/");
- assert.ok(
- exists(
- `.sidebar-section-categories .sidebar-section-link-${category1.slug} .badge-wrapper.bullet`
- ),
- "category badge uses the bullet style"
+ const categorySectionLinks = queryAll(
+ ".sidebar-section-categories .sidebar-section-link:not(.sidebar-section-link-all-categories)"
+ );
+
+ const categoryNames = [...categorySectionLinks].map((categorySectionLink) =>
+ categorySectionLink.textContent.trim()
+ );
+
+ assert.deepEqual(
+ categoryNames,
+ ["abc", "aBC", "efg"],
+ "category section links are displayed in the right order"
);
});
test("category section links", async function (assert) {
- const { category1, category2 } = setupUserSidebarCategories();
+ const { category1, category2, category3 } = setupUserSidebarCategories();
await visit("/");
assert.strictEqual(
- count(".sidebar-section-categories .sidebar-section-link"),
- 2,
- "there should only be two section link under the section"
+ count(
+ ".sidebar-section-categories .sidebar-section-link:not(.sidebar-section-link-all-categories)"
+ ),
+ 3,
+ "there should only be 3 section link under the section"
);
assert.ok(
- exists(`.sidebar-section-link-${category1.slug} .badge-category`),
- "category1 section link is rendered with category badge"
+ exists(
+ `.sidebar-section-link-${category1.slug} .prefix-icon.d-icon-square-full`
+ ),
+ "category1 section link is rendered with right prefix icon"
+ );
+
+ assert.ok(
+ exists(
+ `.sidebar-section-link-${category1.slug} .sidebar-section-link-prefix[style="color: #${category1.color}"]`
+ ),
+ "category1 section link is rendered with right prefix icon color"
);
assert.strictEqual(
@@ -217,6 +245,32 @@ acceptance("Sidebar - Categories Section", function (needs) {
exists(`.sidebar-section-link-${category2.slug}.active`),
"the category2 section link is marked as active"
);
+
+ assert.ok(
+ exists(
+ `.sidebar-section-link-${category3.slug} .sidebar-section-link-prefix .prefix-badge.d-icon-lock`
+ ),
+ "category3 section link is rendered with lock prefix badge icon as it is read restricted"
+ );
+ });
+
+ test("category section link have the right title", async function (assert) {
+ const categories = Site.current().categories;
+
+ // Category with link HTML tag in description
+ const category = categories.find((c) => c.id === 28);
+
+ updateCurrentUser({
+ sidebar_category_ids: [category.id],
+ });
+
+ await visit("/");
+
+ assert.strictEqual(
+ query(`.sidebar-section-link-${category.slug}`).title,
+ category.description_text,
+ "category description without HTML entity is used as the link's title"
+ );
});
test("visiting category discovery new route", async function (assert) {
diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-community-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-community-section-test.js
similarity index 85%
rename from app/assets/javascripts/discourse/tests/acceptance/sidebar-community-section-test.js
rename to app/assets/javascripts/discourse/tests/acceptance/sidebar-user-community-section-test.js
index 5dbcee7748..f034d089f4 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-community-section-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-community-section-test.js
@@ -1,5 +1,5 @@
import I18n from "I18n";
-import { test } from "qunit";
+import { skip, test } from "qunit";
import {
click,
currentRouteName,
@@ -21,7 +21,7 @@ import { withPluginApi } from "discourse/lib/plugin-api";
import Site from "discourse/models/site";
import { NotificationLevels } from "discourse/lib/notification-levels";
-acceptance("Sidebar - Community Section", function (needs) {
+acceptance("Sidebar - Logged on user - Community Section", function (needs) {
needs.user({
tracked_tags: ["tag1"],
watched_tags: ["tag2"],
@@ -67,8 +67,8 @@ acceptance("Sidebar - Community Section", function (needs) {
);
});
- test("clicking on section caret button", async function (assert) {
- await visit("/");
+ test("clicking on section header link", async function (assert) {
+ await visit("/t/280");
assert.ok(
exists(".sidebar-section-community .sidebar-section-content"),
@@ -76,19 +76,19 @@ acceptance("Sidebar - Community Section", function (needs) {
);
assert.strictEqual(
- query(".sidebar-section-community .sidebar-section-header-caret").title,
+ query(".sidebar-section-community .sidebar-section-header").title,
I18n.t("sidebar.toggle_section"),
"caret has the right title"
);
- await click(".sidebar-section-community .sidebar-section-header-caret");
+ await click(".sidebar-section-community .sidebar-section-header");
- assert.ok(
- !exists(".sidebar-section-community .sidebar-section-content"),
- "hides content section"
+ assert.notOk(
+ exists(".sidebar-section-community .sidebar-section-content"),
+ "hides the content of the section"
);
- await click(".sidebar-section-community .sidebar-section-header-caret");
+ await click(".sidebar-section-community .sidebar-section-header");
assert.ok(
exists(".sidebar-section-community .sidebar-section-content"),
@@ -96,31 +96,8 @@ acceptance("Sidebar - Community Section", function (needs) {
);
});
- test("clicking on section header link", async function (assert) {
- await visit("/t/280");
- await click(".sidebar-section-community .sidebar-section-header-link");
-
- assert.strictEqual(
- currentURL(),
- "/latest",
- "it should transition to the homepage"
- );
-
- assert.strictEqual(
- count(".sidebar-section-community .sidebar-section-link.active"),
- 1,
- "only one link is marked as active"
- );
-
- assert.ok(
- exists(
- ".sidebar-section-community .sidebar-section-link-everything.active"
- ),
- "the everything link is marked as active"
- );
- });
-
- test("clicking on more... link", async function (assert) {
+ // TODO(tgxworld): Flaky probably due to assertions running before event listener callbacks have completed.
+ skip("clicking on more... link", async function (assert) {
await visit("/");
await click(
@@ -252,6 +229,52 @@ acceptance("Sidebar - Community Section", function (needs) {
);
});
+ test("users section link is not shown when enable_user_directory site setting is disabled", async function (assert) {
+ this.siteSettings.enable_user_directory = false;
+
+ await visit("/");
+
+ await click(
+ ".sidebar-section-community .sidebar-more-section-links-details-summary"
+ );
+
+ assert.notOk(
+ exists(".sidebar-section-community .sidebar-section-link-users"),
+ "users section link is not displayed in sidebar"
+ );
+ });
+
+ test("clicking on badges link", async function (assert) {
+ await visit("/");
+
+ await click(
+ ".sidebar-section-community .sidebar-more-section-links-details-summary"
+ );
+
+ await click(".sidebar-section-community .sidebar-section-link-badges");
+
+ assert.strictEqual(
+ currentURL(),
+ "/badges",
+ "it should transition to the badges url"
+ );
+ });
+
+ test("badges section link is not shown when badges has been disabled", async function (assert) {
+ this.siteSettings.enable_badges = false;
+
+ await visit("/");
+
+ await click(
+ ".sidebar-section-community .sidebar-more-section-links-details-summary"
+ );
+
+ assert.notOk(
+ exists(".sidebar-section-community .sidebar-section-link-badges"),
+ "badges section link is not shown in sidebar"
+ );
+ });
+
test("clicking on groups link", async function (assert) {
await visit("/t/280");
@@ -299,6 +322,21 @@ acceptance("Sidebar - Community Section", function (needs) {
);
});
+ test("groups section link is not shown when enable_group_directory site setting has been disabled", async function (assert) {
+ this.siteSettings.enable_group_directory = false;
+
+ await visit("/");
+
+ await click(
+ ".sidebar-section-community .sidebar-more-section-links-details-summary"
+ );
+
+ assert.notOk(
+ exists(".sidebar-section-community .sidebar-section-link-groups"),
+ "groups section link is not shown in sidebar"
+ );
+ });
+
test("navigating to about from sidebar", async function (assert) {
await visit("/");
@@ -672,6 +710,86 @@ acceptance("Sidebar - Community Section", function (needs) {
);
});
+ test("review link is not shown when user cannot review", async function (assert) {
+ updateCurrentUser({ can_review: false });
+
+ await visit("/");
+
+ assert.notOk(
+ exists(".sidebar-section-community .sidebar-section-link-review"),
+ "review link is not shown"
+ );
+
+ await click(
+ ".sidebar-section-community .sidebar-more-section-links-details-summary"
+ );
+
+ assert.notOk(
+ exists(".sidebar-section-community .sidebar-section-link-review"),
+ "review link is not shown"
+ );
+ });
+
+ test("review link when user can review", async function (assert) {
+ updateCurrentUser({
+ can_review: true,
+ reviewable_count: 0,
+ });
+
+ await visit("/reivew");
+
+ assert.notOk(
+ exists(".sidebar-section-community .sidebar-section-link-review.active"),
+ "review link is shown as active when visiting the review route even if there are no pending reviewables"
+ );
+
+ await visit("/");
+
+ assert.notOk(
+ exists(".sidebar-section-community .sidebar-section-link-review"),
+ "review link is not shown as part of the main section links"
+ );
+
+ await click(
+ ".sidebar-section-community .sidebar-more-section-links-details-summary"
+ );
+
+ assert.ok(
+ exists(
+ ".sidebar-section-community .sidebar-more-section-links-details-content .sidebar-section-link-review"
+ ),
+ "review link is displayed in the more drawer"
+ );
+
+ await publishToMessageBus("/reviewable_counts", {
+ reviewable_count: 34,
+ });
+
+ assert.ok(
+ exists(".sidebar-section-community .sidebar-section-link-review"),
+ "review link is shown as part of the main section links"
+ );
+
+ assert.strictEqual(
+ query(
+ ".sidebar-section-community .sidebar-section-link-review .sidebar-section-link-content-badge"
+ ).textContent.trim(),
+ "34 pending",
+ "displays the pending reviewable count"
+ );
+
+ await click(
+ ".sidebar-section-community .sidebar-more-section-links-details-summary"
+ );
+
+ assert.notOk(
+ exists(
+ ".sidebar-section-community .sidebar-more-section-links-details-content .sidebar-section-link-review"
+ ),
+ "review link is not displayed in the more drawer"
+ );
+ });
+
test("new and unread count for tracked link", async function (assert) {
const categories = Site.current().categories;
diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-messages-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-messages-section-test.js
similarity index 96%
rename from app/assets/javascripts/discourse/tests/acceptance/sidebar-messages-section-test.js
rename to app/assets/javascripts/discourse/tests/acceptance/sidebar-user-messages-section-test.js
index 801f6746ec..c42ba0d83e 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-messages-section-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-messages-section-test.js
@@ -12,14 +12,14 @@ import {
import { NotificationLevels } from "discourse/lib/notification-levels";
acceptance(
- "Sidebar - Messages Section - enable_personal_messages disabled",
+ "Sidebar - Logged on user - Messages Section - user not in personal_message_enabled_groups",
function (needs) {
- needs.user();
+ needs.user({ moderator: false, admin: false });
needs.settings({
enable_experimental_sidebar_hamburger: true,
enable_sidebar: true,
- enable_personal_messages: false,
+ personal_message_enabled_groups: "13", // trust_level_3 auto group ID;
});
test("clicking on section header button", async function (assert) {
@@ -34,7 +34,7 @@ acceptance(
);
acceptance(
- "Sidebar - Messages Section - enable_personal_messages enabled",
+ "Sidebar - Logged on user - Messages Section - user in personal_message_enabled_groups",
function (needs) {
needs.user();
@@ -82,12 +82,11 @@ acceptance(
test("clicking on section header link", async function (assert) {
await visit("/");
- await click(".sidebar-section-messages .sidebar-section-header-link");
+ await click(".sidebar-section-messages .sidebar-section-header");
- assert.strictEqual(
- currentURL(),
- `/u/eviltrout/messages`,
- "it should transition to the user's messages"
+ assert.notOk(
+ exists(".sidebar-section-messages .sidebar-section-content"),
+ "hides the content of the section"
);
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-tags-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-tags-section-test.js
similarity index 78%
rename from app/assets/javascripts/discourse/tests/acceptance/sidebar-tags-section-test.js
rename to app/assets/javascripts/discourse/tests/acceptance/sidebar-user-tags-section-test.js
index 4d370ea55a..29f082fdc7 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-tags-section-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-tags-section-test.js
@@ -7,31 +7,35 @@ import {
exists,
publishToMessageBus,
query,
+ queryAll,
updateCurrentUser,
} from "discourse/tests/helpers/qunit-helpers";
import discoveryFixture from "discourse/tests/fixtures/discovery-fixtures";
import { cloneJSON } from "discourse-common/lib/object";
-acceptance("Sidebar - Tags section - tagging disabled", function (needs) {
- needs.settings({
- tagging_enabled: false,
- enable_experimental_sidebar_hamburger: true,
- enable_sidebar: true,
- });
+acceptance(
+ "Sidebar - Logged on user - Tags section - tagging disabled",
+ function (needs) {
+ needs.settings({
+ tagging_enabled: false,
+ enable_experimental_sidebar_hamburger: true,
+ enable_sidebar: true,
+ });
- needs.user();
+ needs.user();
- test("tags section is not shown", async function (assert) {
- await visit("/");
+ test("tags section is not shown", async function (assert) {
+ await visit("/");
- assert.ok(
- !exists(".sidebar-section-tags"),
- "does not display the tags section"
- );
- });
-});
+ assert.ok(
+ !exists(".sidebar-section-tags"),
+ "does not display the tags section"
+ );
+ });
+ }
+);
-acceptance("Sidebar - Tags section", function (needs) {
+acceptance("Sidebar - Logged on user - Tags section", function (needs) {
needs.settings({
tagging_enabled: true,
enable_experimental_sidebar_hamburger: true,
@@ -42,7 +46,18 @@ acceptance("Sidebar - Tags section", function (needs) {
tracked_tags: ["tag1"],
watched_tags: ["tag2", "tag3"],
watching_first_post_tags: [],
- sidebar_tag_names: ["tag1", "tag2", "tag3"],
+ sidebar_tags: [
+ { name: "tag2", pm_only: false },
+ { name: "tag1", pm_only: false },
+ {
+ name: "tag4",
+ pm_only: true,
+ },
+ {
+ name: "tag3",
+ pm_only: false,
+ },
+ ],
});
needs.pretender((server, helper) => {
@@ -52,6 +67,20 @@ acceptance("Sidebar - Tags section", function (needs) {
});
});
+ server.get("/topics/private-messages-tags/:username/:tagId", () => {
+ const topics = [
+ { id: 1, posters: [] },
+ { id: 2, posters: [] },
+ { id: 3, posters: [] },
+ ];
+
+ return helper.response({
+ topic_list: {
+ topics,
+ },
+ });
+ });
+
["latest", "top", "new", "unread"].forEach((type) => {
server.get(`/tag/:tagId/l/${type}.json`, () => {
return helper.response(
@@ -61,17 +90,6 @@ acceptance("Sidebar - Tags section", function (needs) {
});
});
- test("clicking on section header link", async function (assert) {
- await visit("/");
- await click(".sidebar-section-tags .sidebar-section-header-link");
-
- assert.strictEqual(
- currentURL(),
- "/tags",
- "it should transition to the tags page"
- );
- });
-
test("clicking on section header button", async function (assert) {
await visit("/");
await click(".sidebar-section-tags .sidebar-section-header-button");
@@ -85,7 +103,7 @@ acceptance("Sidebar - Tags section", function (needs) {
test("section content when user has not added any tags", async function (assert) {
updateCurrentUser({
- sidebar_tag_names: [],
+ sidebar_tags: [],
});
await visit("/");
@@ -101,13 +119,33 @@ acceptance("Sidebar - Tags section", function (needs) {
);
});
+ test("tag section links are sorted alphabetically by tag's name", async function (assert) {
+ await visit("/");
+
+ const tagSectionLinks = queryAll(
+ ".sidebar-section-tags .sidebar-section-link:not(.sidebar-section-link-all-tags)"
+ );
+
+ const tagNames = [...tagSectionLinks].map((tagSectionLink) =>
+ tagSectionLink.textContent.trim()
+ );
+
+ assert.deepEqual(
+ tagNames,
+ ["tag1", "tag2", "tag3", "tag4"],
+ "tag section links are displayed in the right order"
+ );
+ });
+
test("tag section links for user", async function (assert) {
await visit("/");
assert.strictEqual(
- count(".sidebar-section-tags .sidebar-section-link"),
- 3,
- "3 section links under the section"
+ count(
+ ".sidebar-section-tags .sidebar-section-link:not(.sidebar-section-link-all-tags)"
+ ),
+ 4,
+ "4 section links under the section"
);
assert.strictEqual(
@@ -167,6 +205,29 @@ acceptance("Sidebar - Tags section", function (needs) {
);
});
+ test("private message tag section links for user", async function (assert) {
+ await visit("/");
+
+ await click(".sidebar-section-link-tag4");
+
+ assert.strictEqual(
+ currentURL(),
+ "/u/eviltrout/messages/tags/tag4",
+ "it should transition to user's private message tag4 tag page"
+ );
+
+ assert.strictEqual(
+ count(".sidebar-section-tags .sidebar-section-link.active"),
+ 1,
+ "only one link is marked as active"
+ );
+
+ assert.ok(
+ exists(`.sidebar-section-link-tag4.active`),
+ "the tag4 section link is marked as active"
+ );
+ });
+
test("visiting tag discovery top route", async function (assert) {
await visit(`/tag/tag1/l/top`);
diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-test.js
similarity index 84%
rename from app/assets/javascripts/discourse/tests/acceptance/sidebar-test.js
rename to app/assets/javascripts/discourse/tests/acceptance/sidebar-user-test.js
index f75d4b2248..5f5aa93fca 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-test.js
@@ -1,25 +1,10 @@
import I18n from "I18n";
-
import { test } from "qunit";
import { click, visit } from "@ember/test-helpers";
import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
-acceptance("Sidebar - Anon User", function () {
- // Don't show sidebar for anon user until we know what we want to display
- test("sidebar is not displayed", async function (assert) {
- await visit("/");
-
- assert.ok(
- !document.body.classList.contains("has-sidebar-page"),
- "does not add sidebar utility class to body"
- );
-
- assert.ok(!exists(".sidebar-container"));
- });
-});
-
acceptance(
- "Sidebar - Experimental sidebar and hamburger setting disabled",
+ "Sidebar - Logged on user - Experimental sidebar and hamburger setting disabled",
function (needs) {
needs.user();
@@ -27,7 +12,7 @@ acceptance(
enable_experimental_sidebar_hamburger: false,
});
- test("clicking header hamburger icon displays old hamburger drodown", async function (assert) {
+ test("clicking header hamburger icon displays old hamburger dropdown", async function (assert) {
await visit("/");
await click(".hamburger-dropdown");
@@ -37,7 +22,7 @@ acceptance(
);
acceptance(
- "Sidebar - Experimental sidebar and hamburger setting enabled - Sidebar disabled",
+ "Sidebar - Logged on user - Experimental sidebar and hamburger setting enabled - Sidebar disabled",
function (needs) {
needs.user();
@@ -115,13 +100,6 @@ acceptance(
exists(".sidebar-container"),
"does not display the sidebar on wizard route"
);
-
- await click(".hamburger-dropdown");
-
- assert.ok(
- exists(".sidebar-hamburger-dropdown"),
- "navigation around the site can still be done via the sidebar hamburger"
- );
});
test("showing and hiding sidebar", async function (assert) {
diff --git a/app/assets/javascripts/discourse/tests/acceptance/tags-test.js b/app/assets/javascripts/discourse/tests/acceptance/tags-test.js
index 73ef18d357..cfeb1b2944 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/tags-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/tags-test.js
@@ -502,7 +502,6 @@ acceptance("Tag info", function (needs) {
await visit("/tags/c/feature/2/planters");
await click(".nav-item_latest a[href]");
- // await pauseTest();
assert.strictEqual(currentURL(), "/tags/c/feature/2/planters/l/latest");
await click(".nav-item_top a[href]");
diff --git a/app/assets/javascripts/discourse/tests/acceptance/topic-discovery-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-discovery-test.js
index ebc2c46f8f..5106f49861 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/topic-discovery-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/topic-discovery-test.js
@@ -201,15 +201,11 @@ acceptance("Topic Discovery | Footer", function (needs) {
});
needs.pretender((server, helper) => {
- server.get("/c/dev/7/l/latest.json", () => {
+ server.get("/c/dev/7/l/latest.json", (request) => {
const json = cloneJSON(discoveryFixtures["/c/dev/7/l/latest.json"]);
- json.topic_list.more_topics_url = "/c/dev/7/l/latest.json?page=2";
- return helper.response(json);
- });
-
- server.get("/c/dev/7/l/latest.json?page=2", () => {
- const json = cloneJSON(discoveryFixtures["/c/dev/7/l/latest.json"]);
- json.topic_list.more_topics_url = null;
+ if (!request.queryParams.page) {
+ json.topic_list.more_topics_url = "/c/dev/7/l/latest.json?page=2";
+ }
return helper.response(json);
});
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/topic-discovery-tracked-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-discovery-tracked-test.js
index f60f66ac0b..5fbb9f3be8 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/topic-discovery-tracked-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/topic-discovery-tracked-test.js
@@ -69,6 +69,13 @@ acceptance("Topic Discovery Tracked", function (needs) {
"the categories nav item is displayed when tracked filter is not present"
);
+ await visit("/categories");
+
+ assert.ok(
+ exists("#navigation-bar li.categories"),
+ "the categories nav item is displayed on categories route when tracked filter is not present"
+ );
+
await visit("/?f=tracked");
assert.ok(
diff --git a/app/assets/javascripts/discourse/tests/acceptance/topic-edit-timer-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-edit-timer-test.js
index ab4755bd4a..ad6f4a6082 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/topic-edit-timer-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/topic-edit-timer-test.js
@@ -247,22 +247,18 @@ acceptance("Topic - Edit timer", function (needs) {
test("schedule publish to category - last custom date and time", async function (assert) {
updateCurrentUser({ moderator: true });
-
await visit("/t/internationalization-localization");
- await click(".toggle-admin-menu");
- await click(".admin-topic-timer-update button");
-
- await click("#tap_tile_custom");
- await click(".modal-close");
await click(".toggle-admin-menu");
await click(".admin-topic-timer-update button");
-
assert.notOk(
exists("#tap_tile_last_custom"),
- "it does not show last custom if the custom date and time was not filled and valid"
+ "it does not show last custom if the custom date and time was not filled before"
);
+ await click(".modal-close");
+ await click(".toggle-admin-menu");
+ await click(".admin-topic-timer-update button");
await click("#tap_tile_custom");
await fillIn(".tap-tile-date-input .date-picker", "2100-11-24");
await fillIn("#custom-time", "10:30");
diff --git a/app/assets/javascripts/discourse/tests/acceptance/topic-user-status-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-user-status-test.js
new file mode 100644
index 0000000000..c8960b64fb
--- /dev/null
+++ b/app/assets/javascripts/discourse/tests/acceptance/topic-user-status-test.js
@@ -0,0 +1,127 @@
+import {
+ acceptance,
+ exists,
+ publishToMessageBus,
+ query,
+ queryAll,
+} from "discourse/tests/helpers/qunit-helpers";
+import { visit } from "@ember/test-helpers";
+import { test } from "qunit";
+import TopicFixtures from "discourse/tests/fixtures/topic";
+import { cloneJSON } from "discourse-common/lib/object";
+
+acceptance("Topic - User Status", function (needs) {
+ const status = { emoji: "tooth", description: "off to dentist" };
+
+ needs.user();
+ needs.pretender((server, helper) => {
+ server.get("/t/299/1.json", () => {
+ const response = cloneJSON(TopicFixtures["/t/299/1.json"]);
+ response.post_stream.posts.forEach((post) => {
+ post.user_status = status;
+ });
+
+ return helper.response(200, response);
+ });
+ });
+
+ test("shows user status next to avatar on posts", async function (assert) {
+ this.siteSettings.enable_user_status = true;
+ await visit("/t/-/299/1");
+
+ assert.equal(
+ queryAll(".topic-post .user-status-message").length,
+ 3,
+ "all posts has user status"
+ );
+ });
+});
+
+acceptance("Topic - User Status - live updates", function (needs) {
+ const userId = 1;
+ const status = { emoji: "tooth", description: "off to dentist" };
+
+ needs.user();
+ needs.pretender((server, helper) => {
+ server.get("/t/299/1.json", () => {
+ const response = cloneJSON(TopicFixtures["/t/299/1.json"]);
+ response.post_stream.posts.forEach((post) => {
+ post.user_id = userId;
+ post.user_status = { emoji: "tooth", description: "off to dentist" };
+ });
+
+ return helper.response(200, response);
+ });
+ });
+
+ test("updating status", async function (assert) {
+ this.siteSettings.enable_user_status = true;
+
+ await visit("/t/-/299/1");
+ assert.equal(
+ queryAll(".topic-post .user-status-message").length,
+ 3,
+ "all posts has user status"
+ );
+ assert.ok(
+ query(".topic-post .user-status-message .emoji").src.includes(
+ status.emoji
+ ),
+ "status emoji is correct"
+ );
+
+ const newStatus = { emoji: "surfing_man", description: "surfing" };
+ await publishToMessageBus(`/user-status`, { [userId]: newStatus });
+
+ assert.equal(
+ queryAll(".topic-post .user-status-message").length,
+ 3,
+ "all posts has user status"
+ );
+ assert.ok(
+ query(".topic-post .user-status-message .emoji").src.includes(
+ newStatus.emoji
+ ),
+ "status emoji is correct"
+ );
+ });
+
+ test("removing status and setting again", async function (assert) {
+ this.siteSettings.enable_user_status = true;
+
+ await visit("/t/-/299/1");
+ assert.equal(
+ queryAll(".topic-post .user-status-message").length,
+ 3,
+ "all posts has user status"
+ );
+ assert.ok(
+ query(".topic-post .user-status-message .emoji").src.includes(
+ status.emoji
+ ),
+ "status emoji is correct"
+ );
+
+ await publishToMessageBus(`/user-status`, { [userId]: null });
+
+ assert.notOk(
+ exists(".topic-post .user-status-message"),
+ "status on all posts has disappeared"
+ );
+
+ const newStatus = { emoji: "surfing_man", description: "surfing" };
+ await publishToMessageBus(`/user-status`, { [userId]: newStatus });
+
+ assert.equal(
+ queryAll(".topic-post .user-status-message").length,
+ 3,
+ "all posts have user status"
+ );
+ assert.ok(
+ query(".topic-post .user-status-message .emoji").src.includes(
+ newStatus.emoji
+ ),
+ "status emoji is correct"
+ );
+ });
+});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-activity-all-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-activity-all-test.js
index 701b9852e4..f0e6f9732a 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/user-activity-all-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/user-activity-all-test.js
@@ -1,9 +1,11 @@
-import { acceptance, exists, query } from "../helpers/qunit-helpers";
+import { acceptance, query } from "../helpers/qunit-helpers";
import { test } from "qunit";
import { visit } from "@ember/test-helpers";
import I18n from "I18n";
acceptance("User Activity / All - empty state", function (needs) {
+ const currentUser = "eviltrout";
+ const anotherUser = "charlie";
needs.user();
needs.pretender((server, helper) => {
@@ -14,17 +16,19 @@ acceptance("User Activity / All - empty state", function (needs) {
});
});
- test("When looking at own activity it renders the empty state panel", async function (assert) {
- await visit("/u/eviltrout/activity");
- assert.ok(exists("div.empty-state"));
- });
-
- test("When looking at another user activity it renders the 'No activity' message", async function (assert) {
- await visit("/u/charlie/activity");
- assert.ok(exists("div.alert-info"));
- assert.strictEqual(
- query("div.alert-info").innerText.trim(),
- I18n.t("user_activity.no_activity_others")
+ test("When looking at own activity page", async function (assert) {
+ await visit(`/u/${currentUser}/activity`);
+ assert.equal(
+ query("div.empty-state span.empty-state-title").innerText,
+ I18n.t("user_activity.no_activity_title")
);
});
+
+ test("When looking at another user's activity page", async function (assert) {
+ await visit(`/u/${anotherUser}/activity`);
+ assert.equal(
+ query("div.empty-state span.empty-state-title").innerText,
+ I18n.t("user_activity.no_activity_title")
+ ); // the same title as when looking at own page
+ });
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-activity-likes-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-activity-likes-test.js
index fea8c445be..56559f9bd0 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/user-activity-likes-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/user-activity-likes-test.js
@@ -1,9 +1,11 @@
-import { acceptance, exists, query } from "../helpers/qunit-helpers";
+import { acceptance, query } from "../helpers/qunit-helpers";
import { test } from "qunit";
import { visit } from "@ember/test-helpers";
import I18n from "I18n";
acceptance("User Activity / Likes - empty state", function (needs) {
+ const currentUser = "eviltrout";
+ const anotherUser = "charlie";
needs.user();
needs.pretender((server, helper) => {
@@ -14,17 +16,19 @@ acceptance("User Activity / Likes - empty state", function (needs) {
});
});
- test("When looking at own activity it renders the empty state panel", async function (assert) {
- await visit("/u/eviltrout/activity/likes-given");
- assert.ok(exists("div.empty-state"));
+ test("When looking at own likes page", async function (assert) {
+ await visit(`/u/${currentUser}/activity/likes-given`);
+ assert.equal(
+ query("div.empty-state span.empty-state-title").innerText,
+ I18n.t("user_activity.no_likes_title")
+ );
});
- test("When looking at another user activity it renders the 'No activity' message", async function (assert) {
- await visit("/u/charlie/activity/likes-given");
- assert.ok(exists("div.alert-info"));
- assert.strictEqual(
- query("div.alert-info").innerText.trim(),
- I18n.t("user_activity.no_likes_others")
+ test("When looking at another user's likes page", async function (assert) {
+ await visit(`/u/${anotherUser}/activity/likes-given`);
+ assert.equal(
+ query("div.empty-state span.empty-state-title").innerText,
+ I18n.t("user_activity.no_likes_title_others", { username: anotherUser })
);
});
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-activity-replies-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-activity-replies-test.js
index b0b7690a98..431c0d2871 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/user-activity-replies-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/user-activity-replies-test.js
@@ -1,9 +1,12 @@
-import { acceptance, exists, query } from "../helpers/qunit-helpers";
+import { acceptance, query } from "../helpers/qunit-helpers";
import { test } from "qunit";
-import { visit } from "@ember/test-helpers";
+import { click, visit } from "@ember/test-helpers";
import I18n from "I18n";
acceptance("User Activity / Replies - empty state", function (needs) {
+ const currentUser = "eviltrout";
+ const anotherUser = "charlie";
+
needs.user();
needs.pretender((server, helper) => {
@@ -14,17 +17,55 @@ acceptance("User Activity / Replies - empty state", function (needs) {
});
});
- test("When looking at own activity it renders the empty state panel", async function (assert) {
- await visit("/u/eviltrout/activity/replies");
- assert.ok(exists("div.empty-state"));
+ test("When looking at own replies page", async function (assert) {
+ await visit(`/u/${currentUser}/activity/replies`);
+ assert.equal(
+ query("div.empty-state span.empty-state-title").innerText,
+ I18n.t("user_activity.no_replies_title")
+ );
});
- test("When looking at another user activity it renders the 'No activity' message", async function (assert) {
- await visit("/u/charlie/activity/replies");
- assert.ok(exists("div.alert-info"));
- assert.strictEqual(
- query("div.alert-info").innerText.trim(),
- I18n.t("user_activity.no_replies_others")
+ test("When looking at another user's replies page", async function (assert) {
+ await visit(`/u/${anotherUser}/activity/replies`);
+ assert.equal(
+ query("div.empty-state span.empty-state-title").innerText,
+ I18n.t("user_activity.no_replies_title_others", { username: anotherUser })
+ );
+ });
+});
+
+acceptance("User Activity / Replies - Download All", function (needs) {
+ const currentUser = "eviltrout";
+ const anotherUser = "charlie";
+ needs.user();
+ needs.pretender((server, helper) => {
+ server.post("/export_csv/export_entity.json", () => {
+ return helper.response({});
+ });
+ });
+
+ test("Can see and trigger download for own data replies", async function (assert) {
+ await visit(`/u/${currentUser}/activity`);
+
+ assert.ok(query(".user-additional-controls .btn"), "button exists");
+
+ await click(".user-additional-controls .btn");
+ await click("#dialog-holder .btn-primary");
+
+ assert.equal(
+ query(".dialog-body").innerText.trim(),
+ I18n.t("user.download_archive.success")
+ );
+
+ await click("#dialog-holder .btn-primary");
+ });
+
+ test("Cannot see 'Download All' button for another user", async function (assert) {
+ await visit(`/u/${anotherUser}/activity`);
+
+ assert.notOk(
+ query(".user-additional-controls .btn"),
+ "download button is not present"
);
});
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-drafts-stream-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-drafts-stream-test.js
index 2ea3fb5b54..fef99da02c 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/user-drafts-stream-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/user-drafts-stream-test.js
@@ -18,9 +18,9 @@ acceptance("User Drafts", function (needs) {
assert.strictEqual(count(".user-stream-item"), 3, "has drafts");
await click(".user-stream-item:first-child .remove-draft");
- assert.ok(visible(".bootbox"));
+ assert.ok(visible(".dialog-body"));
- await click(".bootbox .btn-primary");
+ await click(".dialog-footer .btn-primary");
assert.strictEqual(
count(".user-stream-item"),
2,
@@ -62,5 +62,12 @@ acceptance("User Drafts", function (needs) {
),
"shows the excerpt"
);
+
+ assert.ok(
+ query(".user-stream-item:nth-child(2) a.avatar-link").href.endsWith(
+ "/u/eviltrout"
+ ),
+ "has correct avatar link"
+ );
});
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-menu-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-menu-test.js
index e0d125177f..da24b409fd 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/user-menu-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/user-menu-test.js
@@ -1,4 +1,4 @@
-import { click, visit } from "@ember/test-helpers";
+import { click, currentURL, visit } from "@ember/test-helpers";
import {
acceptance,
exists,
@@ -6,6 +6,7 @@ import {
publishToMessageBus,
query,
queryAll,
+ updateCurrentUser,
} from "discourse/tests/helpers/qunit-helpers";
import { test } from "qunit";
import { cloneJSON } from "discourse-common/lib/object";
@@ -13,13 +14,25 @@ import { withPluginApi } from "discourse/lib/plugin-api";
import { NOTIFICATION_TYPES } from "discourse/tests/fixtures/concerns/notification-types";
import UserMenuFixtures from "discourse/tests/fixtures/user-menu";
import TopicFixtures from "discourse/tests/fixtures/topic";
+import { Promise } from "rsvp";
+import { later } from "@ember/runloop";
import I18n from "I18n";
acceptance("User menu", function (needs) {
needs.user({
redesigned_user_menu_enabled: true,
unread_high_priority_notifications: 73,
+ trust_level: 3,
+ grouped_unread_notifications: {
+ [NOTIFICATION_TYPES.replied]: 2,
+ },
});
+
+ needs.settings({
+ allow_anonymous_posting: true,
+ anonymous_posting_min_trust_level: 3,
+ });
+
let requestHeaders = {};
needs.pretender((server, helper) => {
@@ -38,9 +51,54 @@ acceptance("User menu", function (needs) {
requestHeaders = {};
});
+ test("notifications panel has a11y attributes", async function (assert) {
+ await visit("/");
+ await click(".d-header-icons .current-user");
+ const panel = query("#quick-access-all-notifications");
+ assert.strictEqual(panel.getAttribute("tabindex"), "-1");
+ assert.strictEqual(
+ panel.querySelector("ul").getAttribute("aria-labelledby"),
+ "user-menu-button-all-notifications"
+ );
+ });
+
+ test("mentions notifications panel has a11y attributes", async function (assert) {
+ await visit("/");
+ await click(".d-header-icons .current-user");
+ await click("#user-menu-button-mentions");
+ const panel = query("#quick-access-mentions");
+ assert.strictEqual(panel.getAttribute("tabindex"), "-1");
+ assert.strictEqual(
+ panel.querySelector("ul").getAttribute("aria-labelledby"),
+ "user-menu-button-mentions"
+ );
+ });
+
+ test("profile panel has a11y attributes", async function (assert) {
+ await visit("/");
+ await click(".d-header-icons .current-user");
+ await click("#user-menu-button-profile");
+ const panel = query("#quick-access-profile");
+ assert.strictEqual(panel.getAttribute("tabindex"), "-1");
+ assert.strictEqual(
+ panel.querySelector("ul").getAttribute("aria-labelledby"),
+ "user-menu-button-profile"
+ );
+ });
+
test("clicking on an unread notification", async function (assert) {
await visit("/");
await click(".d-header-icons .current-user");
+
+ let repliesBadgeNotification = query(
+ "#user-menu-button-replies .badge-notification"
+ );
+ assert.strictEqual(
+ repliesBadgeNotification.textContent.trim(),
+ "2",
+ "badge shows the right count"
+ );
+
await click(".user-menu ul li.replied a");
assert.strictEqual(
@@ -48,6 +106,132 @@ acceptance("User menu", function (needs) {
123, // id is from the fixtures in fixtures/notification-fixtures.js
"the Discourse-Clear-Notifications request header is set to the notification id in the next ajax request"
);
+
+ await click(".d-header-icons .current-user");
+ repliesBadgeNotification = query(
+ "#user-menu-button-replies .badge-notification"
+ );
+ assert.strictEqual(
+ repliesBadgeNotification.textContent.trim(),
+ "1",
+ "badge shows count reduced by one"
+ );
+ });
+
+ test("clicking on user menu items", async function (assert) {
+ await visit("/");
+ await click(".d-header-icons .current-user");
+ await click("#user-menu-button-review-queue");
+ await click("#quick-access-review-queue li.reviewable.pending a");
+
+ assert.strictEqual(
+ currentURL(),
+ "/review/17",
+ "clicking on an item results in navigation to the item's page"
+ );
+ assert.notOk(
+ exists(".user-menu"),
+ "clicking on an item closes the menu after navigating"
+ );
+
+ await click(".d-header-icons .current-user");
+ await click("#user-menu-button-review-queue");
+ await click("#quick-access-review-queue li.reviewable.pending a");
+
+ assert.strictEqual(
+ currentURL(),
+ "/review/17",
+ "clicking on the same item again keeps on the same page"
+ );
+ assert.notOk(
+ exists(".user-menu"),
+ "clicking on the same item again closes the menu"
+ );
+
+ await click(".d-header-icons .current-user");
+ await click("#user-menu-button-review-queue");
+ // this may not be ideal because it actually attempts to open a new tab
+ // which gets blocked by the browser, but otherwise it seems harmless and
+ // doesn't cause the test to fail. if it causes problems for you, feel free
+ // to remove the ctrl+click tests.
+ await click("#quick-access-review-queue li.reviewable.reviewed a", {
+ ctrlKey: true,
+ });
+ assert.strictEqual(
+ currentURL(),
+ "/review/17",
+ "ctrl-clicking on an item doesn't navigate to a new page"
+ );
+ assert.ok(
+ exists(".user-menu"),
+ "ctrl-clicking on an item doesn't close the menu"
+ );
+ });
+
+ test("tabs have title attributes", async function (assert) {
+ withPluginApi("0.1", (api) => {
+ api.registerUserMenuTab((UserMenuTab) => {
+ return class extends UserMenuTab {
+ get id() {
+ return "tiny-tab-1";
+ }
+
+ get count() {
+ return this.currentUser.get("unread_high_priority_notifications");
+ }
+
+ get icon() {
+ return "wrench";
+ }
+
+ get panelComponent() {
+ return "d-button";
+ }
+
+ get title() {
+ return `Custom title: ${this.count}`;
+ }
+ };
+ });
+ });
+
+ const expectedTitles = {
+ "user-menu-button-all-notifications": I18n.t(
+ "user_menu.tabs.all_notifications"
+ ),
+ "user-menu-button-replies": I18n.t("user_menu.tabs.replies_with_unread", {
+ count: 2,
+ }),
+ "user-menu-button-mentions": I18n.t("user_menu.tabs.mentions"),
+ "user-menu-button-likes": I18n.t("user_menu.tabs.likes"),
+ "user-menu-button-watching": I18n.t("user_menu.tabs.watching"),
+ "user-menu-button-messages": I18n.t("user_menu.tabs.messages"),
+ "user-menu-button-bookmarks": I18n.t("user_menu.tabs.bookmarks"),
+ "user-menu-button-tiny-tab-1": "Custom title: 73",
+ "user-menu-button-review-queue": I18n.t("user_menu.tabs.review_queue"),
+ "user-menu-button-other-notifications": I18n.t(
+ "user_menu.tabs.other_notifications"
+ ),
+ "user-menu-button-profile": I18n.t("user_menu.tabs.profile"),
+ };
+ await visit("/");
+ await click(".d-header-icons .current-user");
+ for (const [key, title] of Object.entries(expectedTitles)) {
+ assert.strictEqual(
+ query(`#${key}`).title,
+ title,
+ `${key} tab has the right title`
+ );
+ }
+
+ await publishToMessageBus(`/notification/${loggedInUser().id}`, {
+ unread_high_priority_notifications: 22,
+ });
+ assert.strictEqual(
+ query("#user-menu-button-tiny-tab-1").title,
+ "Custom title: 22",
+ "tabs titles can update dynamically"
+ );
});
test("tabs added via the plugin API", async function (assert) {
@@ -97,11 +281,13 @@ acceptance("User menu", function (needs) {
"user-menu-button-replies": "1",
"user-menu-button-mentions": "2",
"user-menu-button-likes": "3",
- "user-menu-button-messages": "4",
- "user-menu-button-bookmarks": "5",
- "user-menu-button-custom-tab-1": "6",
- "user-menu-button-custom-tab-2": "7",
- "user-menu-button-review-queue": "8",
+ "user-menu-button-watching": "4",
+ "user-menu-button-messages": "5",
+ "user-menu-button-bookmarks": "6",
+ "user-menu-button-custom-tab-1": "7",
+ "user-menu-button-custom-tab-2": "8",
+ "user-menu-button-review-queue": "9",
+ "user-menu-button-other-notifications": "10",
};
await visit("/");
@@ -128,7 +314,7 @@ acceptance("User menu", function (needs) {
);
assert.strictEqual(
query(".tabs-list.bottom-tabs .btn").dataset.tabNumber,
- "9",
+ "11",
"bottom tab has the correct data-tab-number"
);
@@ -178,15 +364,408 @@ acceptance("User menu", function (needs) {
"the tab's content is now displayed in the panel"
);
});
+
+ test("notifications tab applies model transformations registered by plugins", async function (assert) {
+ withPluginApi("0.1", (api) => {
+ api.registerModelTransformer("notification", (notifications) => {
+ notifications.forEach((notification, index) => {
+ if (notification.fancy_title) {
+ notification.fancy_title = `pluginNotificationTransformer ${index} ${notification.fancy_title}`;
+ }
+ });
+ });
+ });
+
+ await visit("/");
+ await click(".d-header-icons .current-user");
+
+ const notifications = queryAll(
+ "#quick-access-all-notifications ul li.notification"
+ );
+ assert.strictEqual(
+ notifications[0].textContent.replace(/\s+/g, " ").trim(),
+ "velesin pluginNotificationTransformer 0 edited topic 443"
+ );
+ assert.strictEqual(
+ notifications[1].textContent.replace(/\s+/g, " ").trim(),
+ "velesin pluginNotificationTransformer 1 some title"
+ );
+ });
+
+ test("bookmarks tab applies model transformations registered by plugins", async function (assert) {
+ withPluginApi("0.1", (api) => {
+ api.registerModelTransformer("bookmark", (bookmarks) => {
+ bookmarks.forEach((bookmark) => {
+ if (bookmark.title) {
+ bookmark.title = `pluginBookmarkTransformer ${bookmark.title}`;
+ }
+ });
+ });
+ });
+
+ await visit("/");
+ await click(".d-header-icons .current-user");
+ await click("#user-menu-button-bookmarks");
+
+ const bookmarks = queryAll("#quick-access-bookmarks ul li.bookmark");
+ assert.strictEqual(
+ bookmarks[0].textContent.replace(/\s+/g, " ").trim(),
+ "osama pluginBookmarkTransformer Test poll topic hello world"
+ );
+ });
+
+ test("messages tab applies model transformations registered by plugins", async function (assert) {
+ withPluginApi("0.1", (api) => {
+ api.registerModelTransformer("topic", (topics) => {
+ topics.forEach((topic) => {
+ topic.fancy_title = `pluginTransformer#1 ${topic.fancy_title}`;
+ });
+ });
+ api.registerModelTransformer("topic", async (topics) => {
+ // sleep 1 ms
+ await new Promise((resolve) => later(resolve, 1));
+ topics.forEach((topic) => {
+ topic.fancy_title = `pluginTransformer#2 ${topic.fancy_title}`;
+ });
+ });
+ });
+
+ await visit("/");
+ await click(".d-header-icons .current-user");
+ await click("#user-menu-button-messages");
+
+ const messages = queryAll("#quick-access-messages ul li.message");
+ assert.strictEqual(
+ messages[0].textContent.replace(/\s+/g, " ").trim(),
+ "mixtape pluginTransformer#2 pluginTransformer#1 BUG: Can not render emoji properly"
+ );
+ });
+
+ test("the profile tab", async function (assert) {
+ updateCurrentUser({ draft_count: 13 });
+ await visit("/");
+ await click(".d-header-icons .current-user");
+ await click("#user-menu-button-profile");
+
+ const summaryLink = query("#quick-access-profile ul li.summary a");
+ assert.ok(
+ summaryLink.href.endsWith("/u/eviltrout/summary"),
+ "has a link to the summary page of the user"
+ );
+ assert.strictEqual(
+ summaryLink.textContent.trim(),
+ I18n.t("user.summary.title"),
+ "summary link has the right label"
+ );
+ assert.ok(
+ summaryLink.querySelector(".d-icon-user"),
+ "summary link has the right icon"
+ );
+
+ const activityLink = query("#quick-access-profile ul li.activity a");
+ assert.ok(
+ activityLink.href.endsWith("/u/eviltrout/activity"),
+ "has a link to the activity page of the user"
+ );
+ assert.strictEqual(
+ activityLink.textContent.trim(),
+ I18n.t("user.activity_stream"),
+ "activity link has the right label"
+ );
+ assert.ok(
+ activityLink.querySelector(".d-icon-stream"),
+ "activity link has the right icon"
+ );
+
+ const invitesLink = query("#quick-access-profile ul li.invites a");
+ assert.ok(
+ invitesLink.href.endsWith("/u/eviltrout/invited"),
+ "has a link to the invites page of the user"
+ );
+ assert.strictEqual(
+ invitesLink.textContent.trim(),
+ I18n.t("user.invited.title"),
+ "invites link has the right label"
+ );
+ assert.ok(
+ invitesLink.querySelector(".d-icon-user-plus"),
+ "invites link has the right icon"
+ );
+
+ await click("header.d-header"); // close the menu
+ updateCurrentUser({ can_invite_to_forum: false });
+ await click(".d-header-icons .current-user");
+ await click("#user-menu-button-profile");
+
+ assert.notOk(
+ exists("#quick-access-profile ul li.invites"),
+ "invites link not shown when the user can't invite"
+ );
+
+ const dratsLink = query("#quick-access-profile ul li.drafts a");
+ assert.ok(
+ dratsLink.href.endsWith("/u/eviltrout/activity/drafts"),
+ "has a link to the drafts page of the user"
+ );
+ assert.strictEqual(
+ dratsLink.textContent.trim(),
+ I18n.t("drafts.label_with_count", { count: 13 }),
+ "drafts link has the right label with count of the user's drafts"
+ );
+ assert.ok(
+ dratsLink.querySelector(".d-icon-pencil-alt"),
+ "drafts link has the right icon"
+ );
+
+ const preferencesLink = query("#quick-access-profile ul li.preferences a");
+ assert.ok(
+ preferencesLink.href.endsWith("/u/eviltrout/preferences"),
+ "has a link to the preferences page of the user"
+ );
+ assert.strictEqual(
+ preferencesLink.textContent.trim(),
+ I18n.t("user.preferences"),
+ "preferences link has the right label"
+ );
+ assert.ok(
+ preferencesLink.querySelector(".d-icon-cog"),
+ "preferences link has the right icon"
+ );
+
+ let doNotDisturbButton = query(
+ "#quick-access-profile ul li.do-not-disturb .btn"
+ );
+ assert.strictEqual(
+ doNotDisturbButton.textContent
+ .replaceAll(/\s+/g, " ")
+ .replaceAll(/\u200B/g, "")
+ .trim(),
+ I18n.t("do_not_disturb.label"),
+ "Do Not Disturb button has the right label"
+ );
+ assert.ok(
+ doNotDisturbButton.querySelector(".d-icon-toggle-off"),
+ "Do Not Disturb button has the right icon"
+ );
+
+ await click("header.d-header"); // close the menu
+ const date = new Date();
+ date.setHours(date.getHours() + 2);
+ updateCurrentUser({ do_not_disturb_until: date.toISOString() });
+ await click(".d-header-icons .current-user");
+ await click("#user-menu-button-profile");
+
+ doNotDisturbButton = query(
+ "#quick-access-profile ul li.do-not-disturb .btn"
+ );
+ assert.strictEqual(
+ doNotDisturbButton.textContent
+ .replaceAll(/\s+/g, " ")
+ .replaceAll(/\u200B/g, "")
+ .trim(),
+ `${I18n.t("do_not_disturb.label")} 2h`,
+ "Do Not Disturb button has the right label when Do Not Disturb is enabled"
+ );
+ assert.ok(
+ doNotDisturbButton.querySelector(".d-icon-toggle-on"),
+ "Do Not Disturb button has the right icon when Do Not Disturb is enabled"
+ );
+
+ let toggleAnonButton = query(
+ "#quick-access-profile ul li.enable-anonymous .btn"
+ );
+ assert.strictEqual(
+ toggleAnonButton.textContent
+ .replaceAll(/\s+/g, " ")
+ .replaceAll(/\u200B/g, "")
+ .trim(),
+ I18n.t("switch_to_anon"),
+ "toggle anonymous button has the right label when the user isn't anonymous"
+ );
+ assert.ok(
+ toggleAnonButton.querySelector(".d-icon-user-secret"),
+ "toggle anonymous button has the right icon when the user isn't anonymous"
+ );
+
+ await click("header.d-header"); // close the menu
+ updateCurrentUser({ is_anonymous: true });
+ await click(".d-header-icons .current-user");
+ await click("#user-menu-button-profile");
+
+ toggleAnonButton = query(
+ "#quick-access-profile ul li.disable-anonymous .btn"
+ );
+ assert.strictEqual(
+ toggleAnonButton.textContent
+ .replaceAll(/\s+/g, " ")
+ .replaceAll(/\u200B/g, "")
+ .trim(),
+ I18n.t("switch_from_anon"),
+ "toggle anonymous button has the right label when the user is anonymous"
+ );
+ assert.ok(
+ toggleAnonButton.querySelector(".d-icon-ban"),
+ "toggle anonymous button has the right icon when the user is anonymous"
+ );
+
+ await click("header.d-header"); // close the menu
+ updateCurrentUser({ is_anonymous: false, trust_level: 2 });
+ await click(".d-header-icons .current-user");
+ await click("#user-menu-button-profile");
+
+ assert.notOk(
+ exists("#quick-access-profile ul li.enable-anonymous"),
+ "toggle anon button isn't shown when the user can't use it"
+ );
+ assert.notOk(
+ exists("#quick-access-profile ul li.disable-anonymous"),
+ "toggle anon button isn't shown when the user can't use it"
+ );
+
+ await click("header.d-header"); // close the menu
+ updateCurrentUser({ is_anonymous: true, trust_level: 2 });
+ this.siteSettings.allow_anonymous_posting = false;
+ this.siteSettings.anonymous_posting_min_trust_level = 3;
+ await click(".d-header-icons .current-user");
+ await click("#user-menu-button-profile");
+
+ assert.ok(
+ exists("#quick-access-profile ul li.disable-anonymous"),
+ "toggle anon button is always shown if the user is anonymous"
+ );
+
+ await click("header.d-header"); // close the menu
+ updateCurrentUser({ is_anonymous: false, trust_level: 4 });
+ this.siteSettings.allow_anonymous_posting = false;
+ this.siteSettings.anonymous_posting_min_trust_level = 3;
+ await click(".d-header-icons .current-user");
+ await click("#user-menu-button-profile");
+
+ assert.notOk(
+ exists("#quick-access-profile ul li.enable-anonymous"),
+ "toggle anon button is not shown if the allow_anonymous_posting setting is false"
+ );
+
+ await click("header.d-header"); // close the menu
+ updateCurrentUser({ is_anonymous: false, trust_level: 2 });
+ this.siteSettings.allow_anonymous_posting = true;
+ this.siteSettings.anonymous_posting_min_trust_level = 3;
+ await click(".d-header-icons .current-user");
+ await click("#user-menu-button-profile");
+
+ assert.notOk(
+ exists("#quick-access-profile ul li.enable-anonymous"),
+ "toggle anon button is not shown if the user doesn't have a high enough trust level"
+ );
+
+ const logoutButton = query("#quick-access-profile ul li.logout .btn");
+ assert.strictEqual(
+ logoutButton.textContent
+ .replaceAll(/\s+/g, " ")
+ .replaceAll(/\u200B/g, "")
+ .trim(),
+ I18n.t("user.log_out"),
+ "logout button has the right label"
+ );
+ assert.ok(
+ logoutButton.querySelector(".d-icon-sign-out-alt"),
+ "logout button has the right icon"
+ );
+ });
+
+ test("the active tab can be clicked again to navigate to a page", async function (assert) {
+ withPluginApi("0.1", (api) => {
+ api.registerUserMenuTab((UserMenuTab) => {
+ return class extends UserMenuTab {
+ get id() {
+ return "custom-tab-1";
+ }
+
+ get icon() {
+ return "wrench";
+ }
+
+ get panelComponent() {
+ return "d-button";
+ }
+
+ get linkWhenActive() {
+ return "/u/eviltrout/preferences";
+ }
+ };
+ });
+
+ api.registerUserMenuTab((UserMenuTab) => {
+ return class extends UserMenuTab {
+ get id() {
+ return "custom-tab-2";
+ }
+
+ get icon() {
+ return "plus";
+ }
+
+ get panelComponent() {
+ return "d-button";
+ }
+ };
+ });
+ });
+ await visit("/");
+ await click(".d-header-icons .current-user");
+ await click("#user-menu-button-all-notifications");
+ assert.strictEqual(
+ currentURL(),
+ "/u/eviltrout/notifications",
+ "clicking on active tab navigates to the page it links to"
+ );
+ assert.notOk(exists(".user-menu"), "user menu is closed after navigating");
+
+ const tabs = [
+ ["#user-menu-button-custom-tab-1", "/u/eviltrout/preferences/account"],
+ ["#user-menu-button-replies", "/u/eviltrout/notifications/responses"],
+ ["#user-menu-button-messages", "/u/eviltrout/messages"],
+ ["#user-menu-button-mentions", "/u/eviltrout/notifications/mentions"],
+ ["#user-menu-button-bookmarks", "/u/eviltrout/activity/bookmarks"],
+ ["#user-menu-button-likes", "/u/eviltrout/notifications/likes-received"],
+ ["#user-menu-button-custom-tab-2", null],
+ ["#user-menu-button-review-queue", "/review"],
+ ["#user-menu-button-profile", "/u/eviltrout/summary"],
+ ];
+ for (const [id, expectedLink] of tabs) {
+ await click(".d-header-icons .current-user");
+ await click(id);
+ await click(id);
+ if (expectedLink) {
+ assert.strictEqual(
+ currentURL(),
+ expectedLink,
+ `clicking on the ${id} tab navigates to ${expectedLink}`
+ );
+ assert.notOk(
+ exists(".user-menu"),
+ "user menu is closed after navigating"
+ );
+ } else {
+ assert.ok(
+ exists(".user-menu"),
+ "user menu remains open if tab doesn't link to anywhere"
+ );
+ }
+ await click("#site-logo");
+ }
+ });
});
acceptance("User menu - Dismiss button", function (needs) {
needs.user({
redesigned_user_menu_enabled: true,
unread_high_priority_notifications: 10,
- grouped_unread_high_priority_notifications: {
+ grouped_unread_notifications: {
[NOTIFICATION_TYPES.bookmark_reminder]: 103,
[NOTIFICATION_TYPES.private_message]: 89,
+ [NOTIFICATION_TYPES.votes_released]: 1,
+ [NOTIFICATION_TYPES.code_review_commit_approved]: 3,
},
});
@@ -372,4 +951,29 @@ acceptance("User menu - Dismiss button", function (needs) {
"mark-read request is sent without a confirmation modal"
);
});
+
+ test("doesn't show confirmation modal for the other notifications list", async function (assert) {
+ await visit("/");
+ await click(".d-header-icons .current-user");
+
+ await click("#user-menu-button-other-notifications");
+ let othersBadgeNotification = query(
+ "#user-menu-button-other-notifications .badge-notification"
+ );
+ assert.strictEqual(
+ othersBadgeNotification.textContent.trim(),
+ "4",
+ "badge shows the right count"
+ );
+
+ await click(".user-menu .notifications-dismiss");
+
+ assert.ok(
+ !exists("#user-menu-button-other-notifications .badge-notification")
+ );
+ assert.ok(
+ markRead,
+ "mark-read request is sent without a confirmation modal"
+ );
+ });
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-account-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-account-test.js
new file mode 100644
index 0000000000..67ddd63241
--- /dev/null
+++ b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-account-test.js
@@ -0,0 +1,38 @@
+import { acceptance, query } from "discourse/tests/helpers/qunit-helpers";
+import { click, visit } from "@ember/test-helpers";
+import { test } from "qunit";
+import DiscourseURL from "discourse/lib/url";
+import I18n from "I18n";
+import sinon from "sinon";
+
+acceptance("User Profile - Account - Self Delete", function (needs) {
+ needs.user({
+ username: "charlie",
+ });
+
+ needs.pretender((server, helper) => {
+ server.delete("/u/charlie.json", () => helper.response({ success: true }));
+ });
+
+ test("Delete dialog", async function (assert) {
+ sinon.stub(DiscourseURL, "redirectAbsolute");
+
+ await visit("/u/charlie/preferences/account");
+ await click(".delete-account .btn-danger");
+
+ await click(".dialog-footer .btn-danger");
+
+ assert.strictEqual(
+ query(".dialog-body").textContent.trim(),
+ I18n.t("user.deleted_yourself"),
+ "confirmation dialog is shown"
+ );
+
+ await click(".dialog-footer .btn-primary");
+
+ assert.ok(
+ DiscourseURL.redirectAbsolute.calledWith("/"),
+ "redirects to home after deleting"
+ );
+ });
+});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-interface-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-interface-test.js
index 50cf773d12..7c36de796d 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-interface-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-interface-test.js
@@ -11,6 +11,7 @@ import Session from "discourse/models/session";
import Site from "discourse/models/site";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import { test } from "qunit";
+import userFixtures from "discourse/tests/fixtures/user-fixtures";
acceptance("User Preferences - Interface", function (needs) {
needs.user();
@@ -147,6 +148,14 @@ acceptance(
success: "OK",
});
});
+ server.get("/color-scheme-stylesheet/3.json", () => {
+ return helper.response({
+ new_href: "3.css",
+ });
+ });
+ server.get("/u/charlie.json", () => {
+ return helper.response(userFixtures["/u/charlie.json"]);
+ });
});
test("show option to disable dark mode", async function (assert) {
@@ -310,5 +319,37 @@ acceptance(
"resets dark scheme dropdown"
);
});
+
+ test("preview the color scheme only in current user's profile", async function (assert) {
+ let site = Site.current();
+
+ site.set("default_dark_color_scheme", { id: 1, name: "Dark" });
+ site.set("user_color_schemes", [
+ { id: 2, name: "Cool Breeze" },
+ { id: 3, name: "Dark Night", is_dark: true },
+ ]);
+
+ await visit("/u/eviltrout/preferences/interface");
+
+ await selectKit(".light-color-scheme .combobox").expand();
+ await selectKit(".light-color-scheme .combobox").selectRowByValue(3);
+
+ assert.ok(
+ document.querySelector("link#cs-preview-light").href.endsWith("/3.css"),
+ "correct stylesheet loaded"
+ );
+
+ document.querySelector("link#cs-preview-light").remove();
+
+ await visit("/u/charlie/preferences/interface");
+
+ await selectKit(".light-color-scheme .combobox").expand();
+ await selectKit(".light-color-scheme .combobox").selectRowByValue(3);
+
+ assert.notOk(
+ document.querySelector("link#cs-preview-light"),
+ "stylesheet not loaded"
+ );
+ });
}
);
diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-profile-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-profile-test.js
index 3abd43494c..1bf8cfa1f4 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-profile-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-profile-test.js
@@ -1,9 +1,69 @@
-import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
-import { visit } from "@ember/test-helpers";
+import {
+ acceptance,
+ exists,
+ query,
+} from "discourse/tests/helpers/qunit-helpers";
+import { click, visit } from "@ember/test-helpers";
import { test } from "qunit";
+acceptance("User - Preferences - Profile - Featured topic", function (needs) {
+ needs.user();
+
+ needs.settings({ allow_featured_topic_on_user_profiles: true });
+
+ needs.pretender((server, helper) => {
+ server.put("/u/eviltrout/feature-topic", () =>
+ helper.response({ success: true })
+ );
+ });
+
+ test("setting featured topic on profile", async function (assert) {
+ await visit("/u/eviltrout/preferences/profile");
+
+ assert.ok(
+ !exists(".featured-topic-link"),
+ "no featured topic link to present"
+ );
+ assert.ok(
+ !exists(".clear-feature-topic-on-profile-btn"),
+ "clear button not present"
+ );
+
+ await click(".feature-topic-on-profile-btn");
+
+ assert.ok(
+ exists(".feature-topic-on-profile"),
+ "topic picker modal is open"
+ );
+
+ await click(query('input[name="choose_topic_id"]'));
+ await click(".save-featured-topic-on-profile");
+
+ assert.ok(
+ exists(".featured-topic-link"),
+ "link to featured topic is present"
+ );
+ assert.ok(
+ exists(".clear-feature-topic-on-profile-btn"),
+ "clear button is present"
+ );
+ });
+
+ test("focused item after closing feature topic modal", async function (assert) {
+ await visit("/u/eviltrout/preferences/profile");
+ await click(".feature-topic-on-profile-btn");
+ await click(".modal-close");
+
+ assert.equal(
+ document.activeElement,
+ query(".feature-topic-on-profile-btn"),
+ "it keeps focus on the feature topic button"
+ );
+ });
+});
+
acceptance(
- "User profile preferences without default calendar set",
+ "User - Preferences - Profile - No default calendar set",
function (needs) {
needs.user({ default_calendar: "none_selected" });
@@ -19,7 +79,7 @@ acceptance(
);
acceptance(
- "User profile preferences with default calendar set",
+ "User - Preferences - Profile - Default calendar set",
function (needs) {
needs.user({ default_calendar: "google" });
diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-sidebar-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-sidebar-test.js
index 6a2d3d429e..5e48f669bd 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-sidebar-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-sidebar-test.js
@@ -12,7 +12,7 @@ import selectKit from "discourse/tests/helpers/select-kit-helper";
acceptance("User Preferences - Sidebar", function (needs) {
needs.user({
sidebar_category_ids: [],
- sidebar_tag_names: [],
+ sidebar_tags: [],
});
needs.settings({
@@ -39,7 +39,14 @@ acceptance("User Preferences - Sidebar", function (needs) {
// This request format will cause an error
return helper.response(400, {});
} else {
- return helper.response({ user: {} });
+ return helper.response({
+ user: {
+ sidebar_tags: [
+ { name: "monkey", pm_only: false },
+ { name: "gazelle", pm_only: false },
+ ],
+ },
+ });
}
});
});
@@ -77,9 +84,9 @@ acceptance("User Preferences - Sidebar", function (needs) {
"contains the right request body to update user's sidebar category links"
);
- assert.ok(exists(".modal-body"), "error message is displayed");
+ assert.ok(exists(".dialog-body"), "error message is displayed");
- await click(".modal .d-button-label");
+ await click(".dialog-footer .btn-primary");
assert.ok(
!exists(".sidebar-section-categories .sidebar-section-link-howto"),
@@ -121,7 +128,7 @@ acceptance("User Preferences - Sidebar", function (needs) {
});
test("user encountering error when adding tags to sidebar", async function (assert) {
- updateCurrentUser({ sidebar_tag_names: ["monkey"] });
+ updateCurrentUser({ sidebar_tags: [{ name: "monkey", pm_only: false }] });
await visit("/");
@@ -145,9 +152,9 @@ acceptance("User Preferences - Sidebar", function (needs) {
"contains the right request body to update user's sidebar tag links"
);
- assert.ok(exists(".modal-body"), "error message is displayed");
+ assert.ok(exists(".dialog-body"), "error message is displayed");
- await click(".modal .d-button-label");
+ await click(".dialog-footer .btn-primary");
assert.ok(
!exists(".sidebar-section-tags .sidebar-section-link-gazelle"),
diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-profile-summary-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-profile-summary-test.js
index 7734a0b504..169cb95748 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/user-profile-summary-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/user-profile-summary-test.js
@@ -3,12 +3,14 @@ import {
exists,
query,
} from "discourse/tests/helpers/qunit-helpers";
-import { visit } from "@ember/test-helpers";
+import { click, visit } from "@ember/test-helpers";
import { test } from "qunit";
import I18n from "I18n";
import userFixtures from "discourse/tests/fixtures/user-fixtures";
import { cloneJSON } from "discourse-common/lib/object";
+let deleteAndBlock = null;
+
acceptance("User Profile - Summary", function (needs) {
needs.user();
needs.pretender((server, helper) => {
@@ -113,3 +115,58 @@ acceptance("User Profile - Summary - Stats", function (needs) {
);
});
});
+
+acceptance("User Profile - Summary - Admin", function (needs) {
+ needs.user({
+ username: "eviltrout",
+ });
+
+ needs.pretender((server, helper) => {
+ server.get("/admin/users/5.json", () => {
+ return helper.response({
+ id: 5,
+ username: "charlie",
+ name: null,
+ avatar_template: "/letter_avatar_proxy/v4/letter/b/f0a364/{size}.png",
+ active: true,
+ });
+ });
+ server.delete("/admin/users/5.json", (request) => {
+ const data = helper.parsePostData(request.requestBody);
+
+ if (data.block_email || data.block_ip || data.block_urls) {
+ deleteAndBlock = true;
+ } else {
+ deleteAndBlock = false;
+ }
+
+ return helper.response({});
+ });
+ });
+
+ needs.hooks.beforeEach(() => {
+ deleteAndBlock = null;
+ });
+
+ test("Delete only action", async function (assert) {
+ await visit("/u/charlie/summary");
+ await click(".btn-delete-user");
+ await click(".dialog-footer .btn-primary");
+
+ assert.notOk(deleteAndBlock, "first button does not block user");
+ });
+
+ test("Delete and block", async function (assert) {
+ await visit("/u/charlie/summary");
+ await click(".btn-delete-user");
+
+ assert.equal(
+ query("#dialog-title").textContent,
+ I18n.t("admin.user.delete_confirm_title"),
+ "dialog has a title"
+ );
+
+ await click(".dialog-footer .btn-danger");
+ assert.ok(deleteAndBlock, "second button also block user");
+ });
+});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-status-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-status-test.js
index 3da4866d61..ccef190ca1 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/user-status-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/user-status-test.js
@@ -80,8 +80,9 @@ acceptance("User Status", function (needs) {
await click(".menu-links-row .user-preferences-link");
assert.equal(
- query("div.quick-access-panel li.user-status span.d-button-label")
- .innerText,
+ query(
+ "div.quick-access-panel li.user-status span.d-button-label"
+ ).textContent.trim(),
userStatus,
"shows user status description on the menu"
);
@@ -177,8 +178,9 @@ acceptance("User Status", function (needs) {
await click(".header-dropdown-toggle.current-user");
await click(".menu-links-row .user-preferences-link");
assert.equal(
- query("div.quick-access-panel li.user-status span.d-button-label")
- .innerText,
+ query(
+ "div.quick-access-panel li.user-status span.d-button-label"
+ ).textContent.trim(),
userStatus,
"shows user status description on the menu"
);
@@ -205,8 +207,9 @@ acceptance("User Status", function (needs) {
await click(".header-dropdown-toggle.current-user");
await click(".menu-links-row .user-preferences-link");
assert.equal(
- query("div.quick-access-panel li.user-status span.d-button-label")
- .innerText,
+ query(
+ "div.quick-access-panel li.user-status span.d-button-label"
+ ).textContent.trim(),
updatedStatus,
"shows user status description on the menu"
);
@@ -243,8 +246,9 @@ acceptance("User Status", function (needs) {
await click(".menu-links-row .user-preferences-link");
assert.equal(
- query("div.quick-access-panel li.user-status span.relative-date")
- .innerText,
+ query(
+ "div.quick-access-panel li.user-status span.relative-date"
+ ).textContent.trim(),
"1h",
"shows user status timer on the menu"
);
@@ -340,3 +344,94 @@ acceptance("User Status", function (needs) {
);
});
});
+
+acceptance("User Status - new user menu", function (needs) {
+ const userStatus = "off to dentist";
+ const userStatusEmoji = "tooth";
+ const userId = 1;
+ const userTimezone = "UTC";
+
+ needs.user({
+ id: userId,
+ timezone: userTimezone,
+ redesigned_user_menu_enabled: true,
+ });
+
+ needs.pretender((server, helper) => {
+ server.put("/user-status.json", () => {
+ publishToMessageBus(`/user-status/${userId}`, {
+ description: userStatus,
+ emoji: userStatusEmoji,
+ });
+ return helper.response({ success: true });
+ });
+ server.delete("/user-status.json", () => {
+ publishToMessageBus(`/user-status/${userId}`, null);
+ return helper.response({ success: true });
+ });
+ });
+
+ test("doesn't show the user status button on the menu by default", async function (assert) {
+ this.siteSettings.enable_user_status = false;
+
+ await visit("/");
+ await click(".header-dropdown-toggle.current-user");
+ await click("#user-menu-button-profile");
+
+ assert.notOk(exists("li.set-user-status"));
+ });
+
+ test("shows the user status button on the menu when enabled in settings", async function (assert) {
+ this.siteSettings.enable_user_status = true;
+
+ await visit("/");
+ await click(".header-dropdown-toggle.current-user");
+ await click("#user-menu-button-profile");
+
+ assert.ok(exists("li.set-user-status .btn"), "shows the button");
+ assert.ok(
+ exists("li.set-user-status svg.d-icon-plus-circle"),
+ "shows the icon on the button"
+ );
+ });
+
+ test("shows user status on the button", async function (assert) {
+ this.siteSettings.enable_user_status = true;
+ updateCurrentUser({
+ status: { description: userStatus, emoji: userStatusEmoji },
+ });
+
+ await visit("/");
+ await click(".header-dropdown-toggle.current-user");
+ await click("#user-menu-button-profile");
+
+ assert.equal(
+ query("li.set-user-status .item-label").textContent.trim(),
+ userStatus,
+ "shows user status description on the menu"
+ );
+
+ assert.equal(
+ query("li.set-user-status .emoji").alt,
+ `${userStatusEmoji}`,
+ "shows user status emoji on the menu"
+ );
+
+ assert.equal(
+ query(".header-dropdown-toggle .user-status-background img.emoji").alt,
+ `:${userStatusEmoji}:`,
+ "shows user status emoji on the user avatar in the header"
+ );
+ });
+
+ test("user menu gets closed when the user status modal is opened", async function (assert) {
+ this.siteSettings.enable_user_status = true;
+
+ await visit("/");
+ await click(".header-dropdown-toggle.current-user");
+ await click("#user-menu-button-profile");
+ await click(".set-user-status button");
+
+ assert.notOk(exists(".user-menu"));
+ });
+});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-test.js
index 54711f0aff..e54642d5fe 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/user-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/user-test.js
@@ -1,3 +1,4 @@
+import I18n from "I18n";
import EmberObject from "@ember/object";
import User from "discourse/models/user";
import selectKit from "discourse/tests/helpers/select-kit-helper";
@@ -6,10 +7,12 @@ import userFixtures from "discourse/tests/fixtures/user-fixtures";
import {
acceptance,
exists,
+ publishToMessageBus,
query,
queryAll,
updateCurrentUser,
} from "discourse/tests/helpers/qunit-helpers";
+import * as logout from "discourse/lib/logout";
import { click, currentRouteName, visit } from "@ember/test-helpers";
import { cloneJSON } from "discourse-common/lib/object";
import { test } from "qunit";
@@ -305,3 +308,27 @@ acceptance(
});
}
);
+
+acceptance("User - Logout", function (needs) {
+ needs.user({ username: "eviltrout" });
+
+ test("Dialog works", async function (assert) {
+ sinon.stub(logout, "default");
+ await visit("/u/eviltrout");
+ await publishToMessageBus("/logout");
+
+ assert.ok(exists(".dialog-body"));
+ assert.ok(
+ !exists(".dialog-footer .btn-default"),
+ "no cancel button present"
+ );
+ assert.strictEqual(
+ query(".dialog-footer .btn-primary").innerText,
+ I18n.t("home"),
+ "primary dialog button is present"
+ );
+
+ await click(".dialog-overlay");
+ assert.ok(logout.default.called, "logout helper was called");
+ });
+});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/welcome-topic-banner-test.js b/app/assets/javascripts/discourse/tests/acceptance/welcome-topic-banner-test.js
index 2e7ee79a01..273b85ecf0 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/welcome-topic-banner-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/welcome-topic-banner-test.js
@@ -8,9 +8,9 @@ acceptance("Welcome Topic Banner", function (needs) {
test("Navigation", async function (assert) {
await visit("/");
- assert.ok(exists(".welcome-topic-banner"), "has the welcome topic banner");
+ assert.ok(exists(".welcome-cta"), "has the welcome topic banner");
assert.ok(
- exists("button.welcome-topic-cta"),
+ exists("button.welcome-cta__button"),
"has the welcome topic edit button"
);
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/wizard-test.js b/app/assets/javascripts/discourse/tests/acceptance/wizard-test.js
index 51395f65cb..f3dbdb5e97 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/wizard-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/wizard-test.js
@@ -14,6 +14,10 @@ acceptance("Wizard", function (needs) {
test("Wizard starts", async function (assert) {
await visit("/wizard");
assert.ok(exists(".wizard-container"));
+ assert.notOk(
+ exists(".d-header-wrap"),
+ "header is not rendered on wizard pages"
+ );
assert.strictEqual(currentRouteName(), "wizard.step");
});
@@ -58,6 +62,15 @@ acceptance("Wizard", function (needs) {
"shows finish on an intermediate step"
);
+ await click(".wizard-container__button.finish");
+ assert.strictEqual(
+ currentURL(),
+ "/latest",
+ "it should transition to the homepage"
+ );
+
+ await visit("/wizard/steps/styling");
+
await click(".wizard-container__button.next");
assert.ok(
exists(".wizard-container__text-input#company_name"),
diff --git a/app/assets/javascripts/discourse/tests/active-plugins.js.erb b/app/assets/javascripts/discourse/tests/active-plugins.js.erb
deleted file mode 100644
index 6b2958541e..0000000000
--- a/app/assets/javascripts/discourse/tests/active-plugins.js.erb
+++ /dev/null
@@ -1,11 +0,0 @@
-<%
- DiscoursePluginRegistry.javascripts.each { |js| require_asset(js) }
- DiscoursePluginRegistry.handlebars.each { |hb| require_asset(hb) }
- DiscoursePluginRegistry.each_globbed_asset do |f|
- if File.directory?(f)
- depend_on(f)
- else
- require_asset(f)
- end
- end
-%>
diff --git a/app/assets/javascripts/discourse/tests/addons/truth-helpers/integration/helpers/includes-test.js b/app/assets/javascripts/discourse/tests/addons/truth-helpers/integration/helpers/includes-test.js
new file mode 100644
index 0000000000..adf2000528
--- /dev/null
+++ b/app/assets/javascripts/discourse/tests/addons/truth-helpers/integration/helpers/includes-test.js
@@ -0,0 +1,43 @@
+import { assert, module, test } from "qunit";
+import { setupRenderingTest } from "discourse/tests/helpers/component-test";
+import { render } from "@ember/test-helpers";
+import { hbs } from "ember-cli-htmlbars";
+import { exists } from "discourse/tests/helpers/qunit-helpers";
+
+module("Addons | truth-helpers | Integration | includes", function (hooks) {
+ setupRenderingTest(hooks);
+
+ test("when using an array", async function () {
+ this.foo = [1];
+ this.bar = 1;
+ await render(
+ hbs`{{#if (includes foo bar)}}{{/if}}`
+ );
+
+ assert.ok(exists(".test"), "it returns true when element is found");
+
+ this.bar = 2;
+ await render(
+ hbs`{{#if (includes foo bar)}}{{/if}}`
+ );
+
+ assert.notOk(exists(".test"), "it returns false when element is not found");
+ });
+
+ test("when using a string", async function () {
+ this.foo = "foo";
+ this.bar = "f";
+ await render(
+ hbs`{{#if (includes foo bar)}}{{/if}}`
+ );
+
+ assert.ok(exists(".test"), "it returns true when element is found");
+
+ this.bar = "b";
+ await render(
+ hbs`{{#if (includes foo bar)}}{{/if}}`
+ );
+
+ assert.notOk(exists(".test"), "it returns false when element is not found");
+ });
+});
diff --git a/app/assets/javascripts/discourse/tests/core-tests.js b/app/assets/javascripts/discourse/tests/core-tests.js
deleted file mode 100644
index 1890b65888..0000000000
--- a/app/assets/javascripts/discourse/tests/core-tests.js
+++ /dev/null
@@ -1,3 +0,0 @@
-//= require_tree ./acceptance
-//= require_tree ./integration
-//= require_tree ./unit
diff --git a/app/assets/javascripts/discourse/tests/fixtures/drafts.js b/app/assets/javascripts/discourse/tests/fixtures/drafts.js
index bfb3d3acea..96c0061fd9 100644
--- a/app/assets/javascripts/discourse/tests/fixtures/drafts.js
+++ b/app/assets/javascripts/discourse/tests/fixtures/drafts.js
@@ -26,7 +26,7 @@ export default {
draft_key: "topic_280",
sequence: 0,
draft_username: "eviltrout",
- avatar_template: "/letter_avatar_proxy/v2/letter/p/a87d85/{size}.png",
+ avatar_template: "/user_avatar/localhost/eviltrout/{size}/2_1.png",
data: '{"reply":"The last reply to this topic was 6 months ago. Your reply will bump the topic to the top of its list.","action":"reply","categoryId":8,"archetypeId":"regular","metaData":null,"composerTime":139499,"typingTime":6100}',
topic_id: 280,
username: "zogstrip",
diff --git a/app/assets/javascripts/discourse/tests/fixtures/notification-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/notification-fixtures.js
index a3ff25c5d8..4097c2de19 100644
--- a/app/assets/javascripts/discourse/tests/fixtures/notification-fixtures.js
+++ b/app/assets/javascripts/discourse/tests/fixtures/notification-fixtures.js
@@ -10,6 +10,7 @@ export default {
post_number: 1,
topic_id: 130,
slug: "lorem-ipsum-dolor-sit-amet",
+ fancy_title: "edited topic 443",
data: {
topic_title: "edited topic 443",
display_username: "velesin",
@@ -26,6 +27,7 @@ export default {
post_number: 1,
topic_id: 1234,
slug: "a-slug",
+ fancy_title: "some title",
data: { topic_title: "some title", display_username: "velesin" },
},
{
diff --git a/app/assets/javascripts/discourse/tests/fixtures/session-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/session-fixtures.js
index 08a365ecaa..d99b6ab825 100644
--- a/app/assets/javascripts/discourse/tests/fixtures/session-fixtures.js
+++ b/app/assets/javascripts/discourse/tests/fixtures/session-fixtures.js
@@ -33,7 +33,21 @@ export default {
timezone: "Australia/Brisbane",
skip_new_user_tips: false,
can_review: true,
- ignored_users: []
+ ignored_users: [],
+ groups: [
+ {
+ id: 10,
+ automatic: true,
+ name: "trust_level_0",
+ display_name: "trust_level_0",
+ },
+ {
+ id: 11,
+ automatic: true,
+ name: "trust_level_1",
+ display_name: "trust_level_1",
+ }
+ ]
},
},
};
diff --git a/app/assets/javascripts/discourse/tests/fixtures/site-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/site-fixtures.js
index 784178a11d..9c7475e196 100644
--- a/app/assets/javascripts/discourse/tests/fixtures/site-fixtures.js
+++ b/app/assets/javascripts/discourse/tests/fixtures/site-fixtures.js
@@ -61,7 +61,7 @@ export default {
{
id: 3,
name: "meta",
- color: "aaa",
+ color: "aaaaaa",
text_color: "FFFFFF",
slug: "meta",
topic_count: 122,
diff --git a/app/assets/javascripts/discourse/tests/fixtures/topic.js b/app/assets/javascripts/discourse/tests/fixtures/topic.js
index 5a5d220be2..fa37c17c5c 100644
--- a/app/assets/javascripts/discourse/tests/fixtures/topic.js
+++ b/app/assets/javascripts/discourse/tests/fixtures/topic.js
@@ -5799,6 +5799,7 @@ export default {
avatar_template: "/images/avatar.png",
},
},
+ tags: ["foo", "baz"],
},
"/t/2481/1.json": {
post_stream: {
diff --git a/app/assets/javascripts/discourse/tests/fixtures/user-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/user-fixtures.js
index 4ed74958a7..471f7c47fc 100644
--- a/app/assets/javascripts/discourse/tests/fixtures/user-fixtures.js
+++ b/app/assets/javascripts/discourse/tests/fixtures/user-fixtures.js
@@ -2499,6 +2499,7 @@ export default {
last_posted_at: null,
last_seen_at: null,
created_at: "2019-03-06T19:06:20.340Z",
+ can_delete_account: true,
can_edit: true,
can_edit_username: true,
can_edit_email: true,
diff --git a/app/assets/javascripts/discourse/tests/fixtures/user-menu.js b/app/assets/javascripts/discourse/tests/fixtures/user-menu.js
index a96893ea66..a645cce16a 100644
--- a/app/assets/javascripts/discourse/tests/fixtures/user-menu.js
+++ b/app/assets/javascripts/discourse/tests/fixtures/user-menu.js
@@ -136,52 +136,6 @@ export default {
primary_group_id: null,
},
],
- fancy_title: "BUG: Can not render emoji properly :confused:",
- slug: "bug-can-not-render-emoji-properly",
- posts_count: 1,
- reply_count: 0,
- highest_post_number: 2,
- image_url: null,
- created_at: "2019-07-26T01:29:24.008Z",
- last_posted_at: "2019-07-26T01:29:24.177Z",
- bumped: true,
- bumped_at: "2019-07-26T01:29:24.177Z",
- unseen: false,
- last_read_post_number: 2,
- unread_posts: 0,
- pinned: false,
- unpinned: null,
- visible: true,
- closed: false,
- archived: false,
- notification_level: 3,
- bookmarked: false,
- bookmarks: [],
- liked: false,
- views: 5,
- like_count: 0,
- has_summary: false,
- archetype: "private_message",
- last_poster_username: "mixtape",
- category_id: null,
- pinned_globally: false,
- featured_link: null,
- posters: [
- {
- extras: "latest single",
- description: "Original Poster, Most Recent Poster",
- user_id: 13,
- primary_group_id: null,
- },
- ],
- participants: [
- {
- extras: "latest",
- description: null,
- user_id: 13,
- primary_group_id: null,
- },
- ],
}
],
}
diff --git a/app/assets/javascripts/discourse/tests/helpers/component-test.js b/app/assets/javascripts/discourse/tests/helpers/component-test.js
index c23b4f1542..4e79dd558a 100644
--- a/app/assets/javascripts/discourse/tests/helpers/component-test.js
+++ b/app/assets/javascripts/discourse/tests/helpers/component-test.js
@@ -7,7 +7,6 @@ import { autoLoadModules } from "discourse/initializers/auto-load-modules";
import QUnit, { test } from "qunit";
import { setupRenderingTest as emberSetupRenderingTest } from "ember-qunit";
import { currentSettings } from "discourse/tests/helpers/site-settings";
-import { testCleanup } from "discourse/tests/helpers/qunit-helpers";
import { injectServiceIntoService } from "discourse/pre-initializers/inject-discourse-objects";
export function setupRenderingTest(hooks) {
@@ -16,11 +15,7 @@ export function setupRenderingTest(hooks) {
hooks.beforeEach(function () {
if (!hooks.usingDiscourseModule) {
this.siteSettings = currentSettings();
-
- if (!this.registry) {
- this.registry = this.owner.__registry__;
- }
-
+ this.registry ||= this.owner.__registry__;
this.container = this.owner;
}
@@ -30,6 +25,23 @@ export function setupRenderingTest(hooks) {
const currentUser = User.create({
username: "eviltrout",
timezone: "Australia/Brisbane",
+ name: "Robin Ward",
+ admin: false,
+ moderator: false,
+ groups: [
+ {
+ id: 10,
+ automatic: true,
+ name: "trust_level_0",
+ display_name: "trust_level_0",
+ },
+ {
+ id: 11,
+ automatic: true,
+ name: "trust_level_1",
+ display_name: "trust_level_1",
+ },
+ ],
});
this.currentUser = currentUser;
this.owner.unregister("service:current-user");
@@ -60,12 +72,6 @@ export function setupRenderingTest(hooks) {
$.fn.autocomplete = function () {};
});
-
- if (!hooks.usingDiscourseModule) {
- hooks.afterEach(function () {
- testCleanup(this.container);
- });
- }
}
export default function (name, hooks, opts) {
diff --git a/app/assets/javascripts/discourse/tests/helpers/create-pretender.js b/app/assets/javascripts/discourse/tests/helpers/create-pretender.js
index 40317640ee..faddd058ad 100644
--- a/app/assets/javascripts/discourse/tests/helpers/create-pretender.js
+++ b/app/assets/javascripts/discourse/tests/helpers/create-pretender.js
@@ -1,7 +1,6 @@
import Pretender from "pretender";
import User from "discourse/models/user";
import getURL from "discourse-common/lib/get-url";
-import { Promise } from "rsvp";
export function parsePostData(query) {
const result = {};
@@ -506,14 +505,11 @@ export function applyDefaultHandlers(pretender) {
pretender.put("/posts/:post_id", async (request) => {
const data = parsePostData(request.requestBody);
+
if (data.post.raw === "this will 409") {
return response(409, { errors: ["edit conflict"] });
- } else if (data.post.raw === "will return empty json") {
- window.resolveLastPromise();
- return new Promise((resolve) => {
- window.resolveLastPromise = resolve;
- }).then(() => response(200, {}));
}
+
data.post.id = request.params.post_id;
data.post.version = 2;
return response(200, data.post);
@@ -674,6 +670,12 @@ export function applyDefaultHandlers(pretender) {
},
];
+ if (request.queryParams.filter) {
+ store = store.filter((user) =>
+ user.username.includes(request.queryParams.filter)
+ );
+ }
+
const showEmails = request.queryParams.show_emails;
if (showEmails === "false") {
diff --git a/app/assets/javascripts/discourse/tests/helpers/notification-items-helper.js b/app/assets/javascripts/discourse/tests/helpers/notification-types-helper.js
similarity index 85%
rename from app/assets/javascripts/discourse/tests/helpers/notification-items-helper.js
rename to app/assets/javascripts/discourse/tests/helpers/notification-types-helper.js
index 4f05a666cb..5c9c10481e 100644
--- a/app/assets/javascripts/discourse/tests/helpers/notification-items-helper.js
+++ b/app/assets/javascripts/discourse/tests/helpers/notification-types-helper.js
@@ -1,4 +1,4 @@
-import { getRenderDirector } from "discourse/lib/notification-item";
+import { getRenderDirector } from "discourse/lib/notification-types-manager";
import sessionFixtures from "discourse/tests/fixtures/session-fixtures";
import User from "discourse/models/user";
import Site from "discourse/models/site";
diff --git a/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js
index 35660ee8c4..4de353d143 100644
--- a/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js
+++ b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js
@@ -15,7 +15,6 @@ import { getApplication, getContext, settled } from "@ember/test-helpers";
import { getOwner } from "discourse-common/lib/get-owner";
import { run } from "@ember/runloop";
import { setupApplicationTest } from "ember-qunit";
-import { Promise } from "rsvp";
import Site from "discourse/models/site";
import User from "discourse/models/user";
import { _clearSnapshots } from "select-kit/components/composer-actions";
@@ -73,8 +72,10 @@ import { clearTagsHtmlCallbacks } from "discourse/lib/render-tags";
import { clearToolbarCallbacks } from "discourse/components/d-editor";
import { clearExtraHeaderIcons } from "discourse/widgets/header";
import { resetSidebarSection } from "discourse/lib/sidebar/custom-sections";
-import { resetNotificationTypeRenderers } from "discourse/lib/notification-item";
+import { resetNotificationTypeRenderers } from "discourse/lib/notification-types-manager";
import { resetUserMenuTabs } from "discourse/lib/user-menu/tab";
+import { reset as resetLinkLookup } from "discourse/lib/link-lookup";
+import { resetModelTransformers } from "discourse/lib/model-transformers";
export function currentUser() {
return User.create(sessionFixtures["/session/current.json"].current_user);
@@ -159,7 +160,6 @@ export function testCleanup(container, app) {
});
}
- localStorage.clear();
User.resetCurrent();
resetExtraClasses();
clearOutletCache();
@@ -206,6 +206,8 @@ export function testCleanup(container, app) {
resetNotificationTypeRenderers();
clearExtraHeaderIcons();
resetUserMenuTabs();
+ resetLinkLookup();
+ resetModelTransformers();
}
export function discourseModule(name, options) {
@@ -223,8 +225,6 @@ export function discourseModule(name, options) {
this.siteSettings = currentSettings();
});
- hooks.afterEach(() => testCleanup(this.container));
-
this.getController = function (controllerName, properties) {
let controller = this.container.lookup(`controller:${controllerName}`);
controller.application = {};
@@ -252,7 +252,6 @@ export function discourseModule(name, options) {
},
afterEach() {
options?.afterEach?.call(this);
- testCleanup(this.container);
},
});
}
@@ -452,15 +451,11 @@ QUnit.assert.containsInstance = function (collection, klass, message) {
};
export async function selectDate(selector, date) {
- return new Promise((resolve) => {
- const elem = document.querySelector(selector);
- elem.value = date;
- const evt = new Event("input", { bubbles: true, cancelable: false });
- elem.dispatchEvent(evt);
- elem.blur();
-
- resolve();
- });
+ const elem = document.querySelector(selector);
+ elem.value = date;
+ const evt = new Event("input", { bubbles: true, cancelable: false });
+ elem.dispatchEvent(evt);
+ elem.blur();
}
export function queryAll(selector, context) {
@@ -495,10 +490,12 @@ export function exists(selector) {
export async function publishToMessageBus(channelPath, ...args) {
args = cloneJSON(args);
- MessageBus.callbacks
- .filterBy("channel", channelPath)
- .forEach((c) => c.func(...args));
+ const promises = MessageBus.callbacks
+ .filterBy("channel", channelPath)
+ .map((callback) => callback.func(...args));
+
+ await Promise.allSettled(promises);
await settled();
}
diff --git a/app/assets/javascripts/discourse/tests/helpers/reviewable-items-helper.js b/app/assets/javascripts/discourse/tests/helpers/reviewable-types-helper.js
similarity index 85%
rename from app/assets/javascripts/discourse/tests/helpers/reviewable-items-helper.js
rename to app/assets/javascripts/discourse/tests/helpers/reviewable-types-helper.js
index 4a0c30f1ce..5a0b2fc528 100644
--- a/app/assets/javascripts/discourse/tests/helpers/reviewable-items-helper.js
+++ b/app/assets/javascripts/discourse/tests/helpers/reviewable-types-helper.js
@@ -1,4 +1,4 @@
-import { getRenderDirector } from "discourse/lib/reviewable-item";
+import { getRenderDirector } from "discourse/lib/reviewable-types-manager";
import sessionFixtures from "discourse/tests/fixtures/session-fixtures";
import User from "discourse/models/user";
import Site from "discourse/models/site";
diff --git a/app/assets/javascripts/discourse/tests/helpers/site-settings.js b/app/assets/javascripts/discourse/tests/helpers/site-settings.js
index 6201942429..7909b030a2 100644
--- a/app/assets/javascripts/discourse/tests/helpers/site-settings.js
+++ b/app/assets/javascripts/discourse/tests/helpers/site-settings.js
@@ -13,6 +13,7 @@ const ORIGINAL_SETTINGS = {
post_menu: "like|share|flag|edit|bookmark|delete|admin|reply",
post_menu_hidden_items: "flag|bookmark|edit|delete|admin",
share_links: "twitter|facebook|email",
+ allow_username_in_share_links: true,
category_colors:
"BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|27AA5B|B3B5B4|E45735",
enable_mobile_theme: true,
@@ -94,10 +95,12 @@ const ORIGINAL_SETTINGS = {
desktop_category_page_style: "categories_and_latest_topics",
enable_mentions: true,
enable_personal_messages: true,
+ personal_message_enabled_groups: "11", // TL1 group
unicode_usernames: false,
- secure_media: false,
+ secure_uploads: false,
external_emoji_url: "",
remove_muted_tags_from_latest: "always",
+ enable_group_directory: true,
};
let siteSettings = Object.assign({}, ORIGINAL_SETTINGS);
diff --git a/app/assets/javascripts/discourse/tests/helpers/widget-test.js b/app/assets/javascripts/discourse/tests/helpers/widget-test.js
deleted file mode 100644
index 93f16c38a3..0000000000
--- a/app/assets/javascripts/discourse/tests/helpers/widget-test.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import { addPretenderCallback } from "discourse/tests/helpers/qunit-helpers";
-import componentTest from "discourse/tests/helpers/component-test";
-import { moduleForComponent } from "ember-qunit";
-import { warn } from "@ember/debug";
-import deprecated from "discourse-common/lib/deprecated";
-
-export function moduleForWidget(name, options = {}) {
- warn(
- "moduleForWidget will not work in the Ember CLI environment. Please upgrade your tests.",
- { id: "module-for-widget" }
- );
- return;
-
- let fullName = `widget:${name}`;
- addPretenderCallback(fullName, options.pretend);
-
- moduleForComponent(
- name,
- fullName,
- Object.assign(
- { integration: true },
- { beforeEach: options.beforeEach, afterEach: options.afterEach }
- )
- );
-}
-
-export function widgetTest(name, opts) {
- deprecated("Use `componentTest` instead of `widgetTest`");
- return componentTest(name, opts);
-}
diff --git a/app/assets/javascripts/discourse/tests/index.html b/app/assets/javascripts/discourse/tests/index.html
index e8c2e10b16..3277f0b1cc 100644
--- a/app/assets/javascripts/discourse/tests/index.html
+++ b/app/assets/javascripts/discourse/tests/index.html
@@ -47,18 +47,25 @@
-
+
- {{content-for "test-plugin-js"}}
-
-
- {{content-for "test-plugin-tests-js"}}
-
-
- {{content-for "body-footer"}} {{content-for "test-body-footer"}}
+
+ {{content-for "test-plugin-js"}}
+
+
+ {{content-for "test-plugin-tests-js"}}
+
+
+ {{content-for "body-footer"}} {{content-for "test-body-footer"}}
+
+
+
+
+
+
+
+