Compare commits
1 Commits
main
...
a-quote-fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
241e92a1c8 |
@ -12,6 +12,3 @@ indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.hbs]
|
||||
insert_final_newline = false
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
app/assets/javascripts/browser-update.js
|
||||
app/assets/javascripts/locales/i18n.js
|
||||
app/assets/javascripts/ember-addons/
|
||||
app/assets/javascripts/discourse/lib/autosize.js
|
||||
lib/javascripts/locale/
|
||||
lib/javascripts/messageformat.js
|
||||
lib/javascripts/messageformat-lookup.js
|
||||
lib/highlight_js/
|
||||
lib/pretty_text/
|
||||
plugins/**/lib/javascripts/locale
|
||||
public/
|
||||
@ -12,4 +14,3 @@ node_modules/
|
||||
spec/
|
||||
dist/
|
||||
tmp/
|
||||
documentation/
|
||||
|
||||
@ -58,16 +58,3 @@ bbe5d8d5cf1220165842985c0e2cd4c454d501cd
|
||||
|
||||
# DEV: Template colocation for sidebar files
|
||||
95c7cdab941a56686ac5831d2a5c5eca38d780c5
|
||||
|
||||
# DEV: Apply prettier to hbs files
|
||||
c8e2e37fa77d3c3c69c7572866017e9bb92befa3
|
||||
|
||||
# DEV: Apply syntax_tree to...
|
||||
5a003715d366e1d871f9fcb0656dc9e23e9c2259
|
||||
64171730827c58df26a7ad75f0e58f17c2add118
|
||||
b0fda61a8e75c81e3458c8af9d2afe9d32183457
|
||||
cb932d6ee1b3b3571e4d4d9118635e2dbf58f0ef
|
||||
0cf6421716d0908da57ad7743a2decb08588b48a
|
||||
7c77cc6a580d7cb49f8c19ceee8cfdd08862259d
|
||||
436b3b392b9c917510d4ff0d73a5167cd3eb936c
|
||||
055310cea496519a996b9c3bf4dc7e716cfe62ba
|
||||
|
||||
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@ -10,7 +10,7 @@ updates:
|
||||
interval: daily
|
||||
time: "08:00"
|
||||
timezone: Australia/Sydney
|
||||
open-pull-requests-limit: 20
|
||||
open-pull-requests-limit: 10
|
||||
versioning-strategy: lockfile-only
|
||||
allow:
|
||||
- dependency-type: direct
|
||||
|
||||
2
.github/labeler.yml
vendored
2
.github/labeler.yml
vendored
@ -1,2 +0,0 @@
|
||||
chat:
|
||||
- plugins/chat/**/*
|
||||
14
.github/workflows/labeler.yml
vendored
14
.github/workflows/labeler.yml
vendored
@ -1,14 +0,0 @@
|
||||
name: "Pull Request Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v4
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
14
.github/workflows/licenses.yml
vendored
14
.github/workflows/licenses.yml
vendored
@ -15,7 +15,6 @@ permissions:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: "!(github.event_name == 'push' && github.repository == 'discourse/discourse-private-mirror')"
|
||||
name: run
|
||||
runs-on: ubuntu-latest
|
||||
container: discourse/discourse_test:slim
|
||||
@ -49,11 +48,14 @@ jobs:
|
||||
|
||||
- name: Setup licensed
|
||||
run: |
|
||||
gem install licensed
|
||||
# gem install licensed
|
||||
# Workaround for https://github.com/github/licensed/issues/521
|
||||
gem install specific_install
|
||||
gem specific_install https://github.com/CvX/licensed.git -b bundler-compat
|
||||
|
||||
- name: Get yarn cache directory
|
||||
id: yarn-cache-dir
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- name: Yarn cache
|
||||
uses: actions/cache@v3
|
||||
@ -65,7 +67,7 @@ jobs:
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Check RubyGems Licenses
|
||||
if: ${{ !cancelled() }}
|
||||
if: ${{ always() }}
|
||||
run: |
|
||||
licensed cache
|
||||
licensed status
|
||||
@ -74,14 +76,14 @@ jobs:
|
||||
run: yarn install
|
||||
|
||||
- name: Check Yarn Licenses
|
||||
if: ${{ !cancelled() }}
|
||||
if: ${{ always() }}
|
||||
run: |
|
||||
yarn global add licensee
|
||||
yarn global upgrade licensee
|
||||
licensee --errors-only
|
||||
|
||||
- name: Check Ember CLI Workspace Licenses
|
||||
if: ${{ !cancelled() }}
|
||||
if: ${{ always() }}
|
||||
working-directory: ./app/assets/javascripts
|
||||
run: |
|
||||
licensee --errors-only
|
||||
|
||||
30
.github/workflows/linting.yml
vendored
30
.github/workflows/linting.yml
vendored
@ -15,16 +15,12 @@ permissions:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: "!(github.event_name == 'push' && github.repository == 'discourse/discourse-private-mirror')"
|
||||
name: run
|
||||
runs-on: ubuntu-latest
|
||||
container: discourse/discourse_test:slim
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Set working directory owner
|
||||
run: chown root:root .
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 1
|
||||
@ -53,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Get yarn cache directory
|
||||
id: yarn-cache-dir
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- name: Yarn cache
|
||||
uses: actions/cache@v3
|
||||
@ -68,37 +64,29 @@ jobs:
|
||||
run: yarn install
|
||||
|
||||
- name: Rubocop
|
||||
if: ${{ !cancelled() }}
|
||||
if: ${{ always() }}
|
||||
run: bundle exec rubocop --parallel .
|
||||
|
||||
- name: syntax_tree
|
||||
if: ${{ !cancelled() }}
|
||||
run: |
|
||||
set -E
|
||||
bundle exec stree check Gemfile $(git ls-files '*.rb') $(git ls-files '*.rake')
|
||||
|
||||
- name: ESLint (core)
|
||||
if: ${{ !cancelled() }}
|
||||
if: ${{ always() }}
|
||||
run: yarn eslint app/assets/javascripts
|
||||
|
||||
- name: ESLint (core plugins)
|
||||
if: ${{ !cancelled() }}
|
||||
if: ${{ always() }}
|
||||
run: yarn eslint plugins
|
||||
|
||||
- name: Prettier
|
||||
if: ${{ !cancelled() }}
|
||||
if: ${{ always() }}
|
||||
run: |
|
||||
yarn prettier -v
|
||||
yarn pprettier --list-different \
|
||||
"app/assets/stylesheets/**/*.scss" \
|
||||
"app/assets/javascripts/**/*.js" \
|
||||
"app/assets/javascripts/**/*.hbs" \
|
||||
"plugins/**/assets/stylesheets/**/*.scss" \
|
||||
"plugins/**/assets/javascripts/**/*.js" \
|
||||
"plugins/**/assets/javascripts/**/*.hbs" \
|
||||
"plugins/**/assets/javascripts/**/*.js"
|
||||
|
||||
- name: Ember template lint
|
||||
if: ${{ !cancelled() }}
|
||||
if: ${{ always() }}
|
||||
run: |
|
||||
yarn ember-template-lint \
|
||||
--no-error-on-unmatched-pattern \
|
||||
@ -106,9 +94,9 @@ jobs:
|
||||
"plugins/**/assets/javascripts/**/*.hbs"
|
||||
|
||||
- name: English locale lint (core)
|
||||
if: ${{ !cancelled() }}
|
||||
if: ${{ always() }}
|
||||
run: bundle exec ruby script/i18n_lint.rb "config/**/locales/{client,server}.en.yml"
|
||||
|
||||
- name: English locale lint (core plugins)
|
||||
if: ${{ !cancelled() }}
|
||||
if: ${{ always() }}
|
||||
run: bundle exec ruby script/i18n_lint.rb "plugins/**/locales/{client,server}.en.yml"
|
||||
|
||||
123
.github/workflows/tests.yml
vendored
123
.github/workflows/tests.yml
vendored
@ -17,11 +17,10 @@ permissions:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: "!(github.event_name == 'push' && github.repository == 'discourse/discourse-private-mirror')"
|
||||
name: ${{ matrix.target }} ${{ matrix.build_type }} ${{ matrix.ruby }}
|
||||
runs-on: ${{ (matrix.build_type == 'annotations') && 'ubuntu-latest' || 'ubuntu-20.04-8core' }}
|
||||
container: discourse/discourse_test:slim${{ (matrix.build_type == 'frontend' || matrix.build_type == 'system') && '-browsers' || '' }}${{ (matrix.ruby == '3.1') && '-ruby-3.1.0' || '' }}
|
||||
timeout-minutes: 20
|
||||
name: ${{ matrix.target }} ${{ matrix.build_type }} ${{ ((matrix.ember_cli_plugin_assets == '1') && '(ember-cli-compiled plugin js)') || ''}}
|
||||
runs-on: ubuntu-latest
|
||||
container: discourse/discourse_test:slim${{ startsWith(matrix.build_type, 'frontend') && '-browsers' || '' }}
|
||||
timeout-minutes: 60
|
||||
|
||||
env:
|
||||
DISCOURSE_HOSTNAME: www.example.com
|
||||
@ -29,26 +28,28 @@ jobs:
|
||||
RAILS_ENV: test
|
||||
PGUSER: discourse
|
||||
PGPASSWORD: discourse
|
||||
USES_PARALLEL_DATABASES: ${{ matrix.build_type == 'backend' || matrix.build_type == 'system' }}
|
||||
CAPBYARA_DEFAULT_MAX_WAIT_TIME: 4
|
||||
USES_PARALLEL_DATABASES: ${{ matrix.build_type == 'backend' && matrix.target == 'core' }}
|
||||
EMBER_CLI_PLUGIN_ASSETS: ${{ matrix.ember_cli_plugin_assets }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
matrix:
|
||||
build_type: [backend, frontend, system, annotations]
|
||||
build_type: [backend, frontend, annotations]
|
||||
target: [core, plugins]
|
||||
ruby: ['3.2']
|
||||
ember_cli_plugin_assets: ['1', '0']
|
||||
exclude:
|
||||
- build_type: annotations
|
||||
target: plugins
|
||||
- build_type: frontend
|
||||
target: core # Handled by core_frontend_tests job (below)
|
||||
- target: core
|
||||
ember_cli_plugin_assets: '1'
|
||||
- target: plugins
|
||||
build_type: backend
|
||||
ember_cli_plugin_assets: '1'
|
||||
|
||||
steps:
|
||||
- name: Set working directory owner
|
||||
run: chown root:root .
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 1
|
||||
@ -72,9 +73,9 @@ jobs:
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: vendor/bundle
|
||||
key: ${{ runner.os }}-${{ matrix.ruby }}-gem-${{ hashFiles('**/Gemfile.lock') }}
|
||||
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.ruby }}-gem-
|
||||
${{ runner.os }}-gem-
|
||||
|
||||
- name: Setup gems
|
||||
run: |
|
||||
@ -87,7 +88,7 @@ jobs:
|
||||
|
||||
- name: Get yarn cache directory
|
||||
id: yarn-cache-dir
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- name: Yarn cache
|
||||
uses: actions/cache@v3
|
||||
@ -151,66 +152,19 @@ 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: Run Zeitwerk check
|
||||
if: matrix.build_type == 'backend'
|
||||
env:
|
||||
LOAD_PLUGINS: ${{ (matrix.target == 'plugins') && '1' || '0' }}
|
||||
run: |
|
||||
if ! bin/rails zeitwerk:check --trace; then
|
||||
echo
|
||||
echo "---------------------------------------------"
|
||||
echo
|
||||
echo "::error::'bin/rails zeitwerk:check' failed - the app will fail to boot with 'eager_load=true' (e.g. in production)."
|
||||
echo "To reproduce locally, run 'bin/rails zeitwerk:check'."
|
||||
echo "Alternatively, you can run your local server/tests with the 'DISCOURSE_ZEITWERK_EAGER_LOAD=1' environment variable."
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Core RSpec
|
||||
if: matrix.build_type == 'backend' && matrix.target == 'core'
|
||||
run: bin/turbo_rspec --verbose
|
||||
|
||||
- name: Plugin RSpec
|
||||
if: matrix.build_type == 'backend' && matrix.target == 'plugins'
|
||||
run: bin/rake plugin:turbo_spec
|
||||
run: bin/rake plugin:spec
|
||||
|
||||
- name: Plugin QUnit
|
||||
if: matrix.build_type == 'frontend' && matrix.target == 'plugins'
|
||||
run: QUNIT_PARALLEL=3 bin/rake plugin:qunit['*','1200000']
|
||||
run: 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: Setup Webdriver
|
||||
if: matrix.build_type == 'system'
|
||||
run: bin/rails runner "require 'webdrivers'; Webdrivers::Chromedriver.update"
|
||||
|
||||
- name: Core System Tests
|
||||
if: matrix.build_type == 'system' && matrix.target == 'core'
|
||||
run: bin/rspec spec/system
|
||||
|
||||
- name: Plugin System Tests
|
||||
if: matrix.build_type == 'system' && matrix.target == 'plugins'
|
||||
run: LOAD_PLUGINS=1 bin/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/capybara/*.png
|
||||
|
||||
- name: Check Annotations
|
||||
if: matrix.build_type == 'annotations'
|
||||
run: |
|
||||
@ -229,26 +183,18 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
|
||||
core_frontend_tests:
|
||||
if: "!(github.event_name == 'push' && github.repository == 'discourse/discourse-private-mirror')"
|
||||
name: core frontend (${{ matrix.browser }})
|
||||
runs-on: ubuntu-20.04-8core
|
||||
container:
|
||||
image: discourse/discourse_test:slim-browsers
|
||||
options: --user discourse
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
container: discourse/discourse_test:slim-browsers
|
||||
timeout-minutes: 35
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
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' }}
|
||||
browser: ["Chrome", "Firefox", "Headless Firefox"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
@ -259,7 +205,7 @@ jobs:
|
||||
|
||||
- name: Get yarn cache directory
|
||||
id: yarn-cache-dir
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- name: Yarn cache
|
||||
uses: actions/cache@v3
|
||||
@ -277,16 +223,23 @@ jobs:
|
||||
- name: Ember Build
|
||||
working-directory: ./app/assets/javascripts/discourse
|
||||
run: |
|
||||
mkdir /tmp/emberbuild
|
||||
yarn ember build --environment=test -o /tmp/emberbuild
|
||||
sudo -E -u discourse mkdir /tmp/emberbuild
|
||||
sudo -E -u discourse -H yarn ember build --environment=test -o /tmp/emberbuild
|
||||
|
||||
- name: Core QUnit
|
||||
- name: Core QUnit 1
|
||||
if: ${{ always() }}
|
||||
working-directory: ./app/assets/javascripts/discourse
|
||||
run: yarn ember exam --path /tmp/emberbuild --load-balance --parallel=5 --launch "${{ env.TESTEM_BROWSER }}" --write-execution-file --random
|
||||
run: sudo -E -u discourse -H yarn ember exam --path /tmp/emberbuild --split=3 --partition=1 --launch "${{ matrix.browser }}" --random
|
||||
timeout-minutes: 15
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- name: Core QUnit 2
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: ember-exam-execution-${{matrix.browser}}
|
||||
path: ./app/assets/javascripts/discourse/test-execution-*.json
|
||||
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: 15
|
||||
|
||||
- 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: 15
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -39,7 +39,6 @@
|
||||
!/plugins/discourse-narrative-bot
|
||||
!/plugins/discourse-presence
|
||||
!/plugins/lazy-yt/
|
||||
!/plugins/chat/
|
||||
!/plugins/poll/
|
||||
!/plugins/styleguide
|
||||
/plugins/*/auto_generated/
|
||||
@ -50,9 +49,6 @@
|
||||
/vendor/data/GeoLite2-City.mmdb
|
||||
/vendor/data/GeoLite2-ASN.mmdb
|
||||
|
||||
# We provide a .sample but people can use newer versions if they want to
|
||||
.ruby-version
|
||||
|
||||
# Front-end
|
||||
dist
|
||||
node_modules
|
||||
|
||||
21
.jsdoc
21
.jsdoc
@ -1,21 +0,0 @@
|
||||
// jsdoc doesn't accept paths starting with _ (which is the case on github runners)
|
||||
// so we need to alter the default config
|
||||
{
|
||||
"source": {
|
||||
"excludePattern": ""
|
||||
},
|
||||
"templates": {
|
||||
"default": {
|
||||
"includeDate": false
|
||||
}
|
||||
},
|
||||
"opts": {
|
||||
"template": "./node_modules/tidy-jsdoc",
|
||||
"prism-theme": "prism-custom",
|
||||
"encoding": "utf8",
|
||||
"recurse": true
|
||||
},
|
||||
"metadata": {
|
||||
"title": "Discourse"
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
sources:
|
||||
bundler: true
|
||||
|
||||
allowed:
|
||||
- 0bsd
|
||||
- apache-2.0
|
||||
@ -13,20 +12,8 @@ allowed:
|
||||
|
||||
ignored:
|
||||
bundler:
|
||||
- cgi # Ruby (default gem)
|
||||
- date # Ruby (default gem)
|
||||
- digest # Ruby (default gem)
|
||||
- io-wait # Ruby (default gem)
|
||||
- json # Ruby (default gem)
|
||||
- net-http # Ruby (default gem)
|
||||
- net-protocol # Ruby (default gem)
|
||||
- openssl # Ruby (default gem)
|
||||
- racc # Ruby (default gem)
|
||||
- rchardet # LGPL
|
||||
- ruby2_keywords # Ruby (default gem)
|
||||
- strscan # Ruby (default gem)
|
||||
- timeout # Ruby (default gem)
|
||||
- uri # Ruby (default gem)
|
||||
- rchardet # Ruby terms
|
||||
- strscan # Ruby
|
||||
|
||||
reviewed:
|
||||
bundler:
|
||||
@ -39,24 +26,32 @@ reviewed:
|
||||
- faraday-em_synchrony # MIT
|
||||
- faraday-excon # MIT
|
||||
- faraday-httpclient # MIT
|
||||
- faraday-net_http # MIT
|
||||
- faraday-patron # MIT
|
||||
- faraday-net_http # MIT
|
||||
- faraday-rack # MIT
|
||||
- highline # Ruby or GPL-2.0
|
||||
- highline # GPL-2.0 OR Ruby terms
|
||||
- htmlentities # MIT
|
||||
- image_size # MIT
|
||||
- io-wait # Ruby terms
|
||||
- json # Ruby terms
|
||||
- jwt # MIT
|
||||
- kgio # LGPL-2.1+
|
||||
- logstash-event # Apache-2.0
|
||||
- net-imap # Ruby (bundled gem)
|
||||
- net-pop # Ruby (bundled gem)
|
||||
- net-smtp # Ruby (bundled gem)
|
||||
- net-http # Ruby
|
||||
- net-imap # Ruby
|
||||
- net-pop # Ruby
|
||||
- net-protocol # Ruby
|
||||
- net-smtp # Ruby
|
||||
- omniauth # MIT
|
||||
- pg # Ruby
|
||||
- openssl # Ruby terms
|
||||
- pg # Ruby terms
|
||||
- r2 # Apache-2.0 (Twitter)
|
||||
- racc # Ruby terms
|
||||
- raindrops # LGPL-2.1+
|
||||
- rubyzip # Ruby
|
||||
- rubyzip # Ruby terms
|
||||
- sidekiq # LGPL (Sidekiq)
|
||||
- tilt # MIT
|
||||
- tilt
|
||||
- timeout # Ruby
|
||||
- unf # BSD-2-Clause
|
||||
- unicorn # Ruby or GPLv2/GPLv3
|
||||
- unicorn
|
||||
- uri # Ruby
|
||||
|
||||
@ -11,8 +11,7 @@
|
||||
"packages": {
|
||||
"@fortawesome/fontawesome-free": "*",
|
||||
"ember-template-lint-plugin-discourse": "*",
|
||||
"squoosh": "2.0.0",
|
||||
"taffydb": "2.6.2"
|
||||
"squoosh": "2.0.0"
|
||||
},
|
||||
"corrections": true
|
||||
}
|
||||
@ -3,7 +3,6 @@ plugins/**/assets/stylesheets/vendor/
|
||||
plugins/**/assets/javascripts/vendor/
|
||||
plugins/**/config/locales/**/*.yml
|
||||
plugins/**/config/*.yml
|
||||
documentation/
|
||||
package.json
|
||||
config/locales/**/*.yml
|
||||
!config/locales/**/*.en*.yml
|
||||
@ -27,6 +26,7 @@ dist/
|
||||
tmp/
|
||||
|
||||
**/*.rb
|
||||
**/*.hbs
|
||||
**/*.html
|
||||
**/*.json
|
||||
**/*.md
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
inherit_gem:
|
||||
rubocop-discourse: stree-compat.yml
|
||||
rubocop-discourse: default.yml
|
||||
|
||||
# Still work to do in ensuring we don't link old files
|
||||
Discourse/NoAddReferenceOrAliasesActiveRecordMigration:
|
||||
@ -7,7 +7,3 @@ Discourse/NoAddReferenceOrAliasesActiveRecordMigration:
|
||||
|
||||
Discourse/NoResetColumnInformationInMigrations:
|
||||
Enabled: true
|
||||
|
||||
Lint/Debugger:
|
||||
Exclude:
|
||||
- script/**/*
|
||||
|
||||
@ -1 +1 @@
|
||||
3.2.1
|
||||
2.7.2
|
||||
|
||||
2
.streerc
2
.streerc
@ -1,2 +0,0 @@
|
||||
--print-width=100
|
||||
--plugins=plugin/trailing_comma,disable_ternary
|
||||
@ -3,9 +3,6 @@ module.exports = {
|
||||
extends: "discourse:recommended",
|
||||
|
||||
rules: {
|
||||
"no-action-modifiers": true,
|
||||
"no-args-paths": true,
|
||||
"no-attrs-in-components": true,
|
||||
"no-capital-arguments": false, // TODO: we extensively use `args` argument name
|
||||
"no-curly-component-invocation": {
|
||||
allow: [
|
||||
@ -15,17 +12,11 @@ module.exports = {
|
||||
"directory-item-value",
|
||||
"directory-table-header-title",
|
||||
"loading-spinner",
|
||||
"directory-item-label",
|
||||
"mobile-directory-item-label",
|
||||
],
|
||||
},
|
||||
"no-implicit-this": {
|
||||
allow: ["loading-spinner"],
|
||||
},
|
||||
// Begin prettier compatibility
|
||||
"eol-last": false,
|
||||
"self-closing-void-elements": false,
|
||||
"block-indentation": false,
|
||||
quotes: false,
|
||||
// End prettier compatibility
|
||||
},
|
||||
};
|
||||
|
||||
313
Gemfile
313
Gemfile
@ -1,51 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
source "https://rubygems.org"
|
||||
source 'https://rubygems.org'
|
||||
# if there is a super emergency and rubygems is playing up, try
|
||||
#source 'http://production.cf.rubygems.org'
|
||||
|
||||
gem "bootsnap", require: false, platform: :mri
|
||||
gem 'bootsnap', require: false, platform: :mri
|
||||
|
||||
def rails_master?
|
||||
ENV["RAILS_MASTER"] == "1"
|
||||
ENV["RAILS_MASTER"] == '1'
|
||||
end
|
||||
|
||||
if rails_master?
|
||||
gem "arel", git: "https://github.com/rails/arel.git"
|
||||
gem "rails", git: "https://github.com/rails/rails.git"
|
||||
gem 'arel', git: 'https://github.com/rails/arel.git'
|
||||
gem 'rails', git: 'https://github.com/rails/rails.git'
|
||||
else
|
||||
# NOTE: Until rubygems gives us optional dependencies we are stuck with this needing to be explicit
|
||||
# this allows us to include the bits of rails we use without pieces we do not.
|
||||
#
|
||||
# To issue a rails update bump the version number here
|
||||
rails_version = "7.0.4.3"
|
||||
gem "actionmailer", rails_version
|
||||
gem "actionpack", rails_version
|
||||
gem "actionview", rails_version
|
||||
gem "activemodel", rails_version
|
||||
gem "activerecord", rails_version
|
||||
gem "activesupport", rails_version
|
||||
gem "railties", rails_version
|
||||
gem "sprockets-rails"
|
||||
rails_version = '7.0.3.1'
|
||||
gem 'actionmailer', rails_version
|
||||
gem 'actionpack', rails_version
|
||||
gem 'actionview', rails_version
|
||||
gem 'activemodel', rails_version
|
||||
gem 'activerecord', rails_version
|
||||
gem 'activesupport', rails_version
|
||||
gem 'railties', rails_version
|
||||
gem 'sprockets-rails'
|
||||
end
|
||||
|
||||
gem "json"
|
||||
gem 'json'
|
||||
|
||||
# TODO: At the moment Discourse does not work with Sprockets 4, we would need to correct internals
|
||||
# We intend to drop sprockets rather than upgrade to 4.x
|
||||
gem "sprockets", git: "https://github.com/rails/sprockets", branch: "3.x"
|
||||
# This is a desired upgrade we should get to.
|
||||
gem 'sprockets', '3.7.2'
|
||||
|
||||
# this will eventually be added to rails,
|
||||
# allows us to precompile all our templates in the unicorn master
|
||||
gem "actionview_precompiler", require: false
|
||||
gem 'actionview_precompiler', require: false
|
||||
|
||||
gem "discourse-seed-fu"
|
||||
gem 'seed-fu'
|
||||
|
||||
gem "mail", git: "https://github.com/discourse/mail.git"
|
||||
gem "mini_mime"
|
||||
gem "mini_suffix"
|
||||
gem 'mail', git: 'https://github.com/discourse/mail.git'
|
||||
gem 'mini_mime'
|
||||
gem 'mini_suffix'
|
||||
|
||||
gem "redis"
|
||||
gem 'redis'
|
||||
|
||||
# This is explicitly used by Sidekiq and is an optional dependency.
|
||||
# We tell Sidekiq to use the namespace "sidekiq" which triggers this
|
||||
@ -53,231 +53,222 @@ gem "redis"
|
||||
# redis namespace support is optional
|
||||
# We already namespace stuff in DiscourseRedis, so we should consider
|
||||
# just using a single implementation in core vs having 2 namespace implementations
|
||||
gem "redis-namespace"
|
||||
gem 'redis-namespace'
|
||||
|
||||
# NOTE: AM serializer gets a lot slower with recent updates
|
||||
# we used an old branch which is the fastest one out there
|
||||
# are long term goal here is to fork this gem so we have a
|
||||
# better maintained living fork
|
||||
gem "active_model_serializers", "~> 0.8.3"
|
||||
gem 'active_model_serializers', '~> 0.8.3'
|
||||
|
||||
gem "http_accept_language", require: false
|
||||
gem 'http_accept_language', require: false
|
||||
|
||||
gem "discourse-fonts", require: "discourse_fonts"
|
||||
# Ember related gems need to be pinned cause they control client side
|
||||
# behavior, we will push these versions up when upgrading ember
|
||||
gem 'discourse-ember-rails', '0.18.6', require: 'ember-rails'
|
||||
gem 'discourse-ember-source', '~> 3.12.2'
|
||||
gem 'ember-handlebars-template', '0.8.0'
|
||||
gem 'discourse-fonts', require: 'discourse_fonts'
|
||||
|
||||
gem "message_bus"
|
||||
gem 'barber'
|
||||
|
||||
gem "rails_multisite"
|
||||
gem 'message_bus'
|
||||
|
||||
gem "fast_xs", platform: :ruby
|
||||
gem 'rails_multisite'
|
||||
|
||||
gem "xorcist"
|
||||
gem 'fast_xs', platform: :ruby
|
||||
|
||||
gem "fastimage"
|
||||
gem 'xorcist'
|
||||
|
||||
gem "aws-sdk-s3", require: false
|
||||
gem "aws-sdk-sns", require: false
|
||||
gem "excon", require: false
|
||||
gem "unf", require: false
|
||||
gem 'fastimage'
|
||||
|
||||
gem "email_reply_trimmer"
|
||||
gem 'aws-sdk-s3', require: false
|
||||
gem 'aws-sdk-sns', require: false
|
||||
gem 'excon', require: false
|
||||
gem 'unf', require: false
|
||||
|
||||
gem "image_optim"
|
||||
gem "multi_json"
|
||||
gem "mustache"
|
||||
gem "nokogiri"
|
||||
gem "loofah"
|
||||
gem "css_parser", require: false
|
||||
gem 'email_reply_trimmer'
|
||||
|
||||
gem "omniauth"
|
||||
gem "omniauth-facebook"
|
||||
gem "omniauth-twitter"
|
||||
gem "omniauth-github"
|
||||
gem 'image_optim'
|
||||
gem 'multi_json'
|
||||
gem 'mustache'
|
||||
gem 'nokogiri'
|
||||
gem 'loofah'
|
||||
gem 'css_parser', require: false
|
||||
|
||||
gem "omniauth-oauth2", require: false
|
||||
gem 'omniauth'
|
||||
gem 'omniauth-facebook'
|
||||
gem 'omniauth-twitter'
|
||||
gem 'omniauth-github'
|
||||
|
||||
gem "omniauth-google-oauth2"
|
||||
gem 'omniauth-oauth2', require: false
|
||||
|
||||
gem 'omniauth-google-oauth2'
|
||||
|
||||
# pending: https://github.com/ohler55/oj/issues/789
|
||||
gem "oj", "3.13.14"
|
||||
gem 'oj', '3.13.14'
|
||||
|
||||
gem "pg"
|
||||
gem "mini_sql"
|
||||
gem "pry-rails", require: false
|
||||
gem "pry-byebug", require: false
|
||||
gem "rtlcss", require: false
|
||||
gem "rake"
|
||||
gem 'pg'
|
||||
gem 'mini_sql'
|
||||
gem 'pry-rails', require: false
|
||||
gem 'pry-byebug', require: false
|
||||
gem 'r2', require: false
|
||||
gem 'rake'
|
||||
|
||||
gem "thor", require: false
|
||||
gem "diffy", require: false
|
||||
gem "rinku"
|
||||
gem "sidekiq"
|
||||
gem "mini_scheduler"
|
||||
gem 'thor', require: false
|
||||
gem 'diffy', require: false
|
||||
gem 'rinku'
|
||||
gem 'sidekiq'
|
||||
gem 'mini_scheduler'
|
||||
|
||||
gem "execjs", require: false
|
||||
gem "mini_racer"
|
||||
gem 'execjs', require: false
|
||||
gem 'mini_racer'
|
||||
|
||||
gem "highline", require: false
|
||||
gem 'highline', require: false
|
||||
|
||||
gem "rack"
|
||||
gem 'rack'
|
||||
|
||||
gem "rack-protection" # security
|
||||
gem "cbor", require: false
|
||||
gem "cose", require: false
|
||||
gem "addressable"
|
||||
gem "json_schemer"
|
||||
gem 'rack-protection' # security
|
||||
gem 'cbor', require: false
|
||||
gem 'cose', require: false
|
||||
gem 'addressable'
|
||||
gem 'json_schemer'
|
||||
|
||||
gem "net-smtp", require: false
|
||||
gem "net-imap", require: false
|
||||
gem "net-pop", require: false
|
||||
gem "digest", require: false
|
||||
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.1")
|
||||
# net-smtp, net-imap and net-pop were removed from default gems in Ruby 3.1
|
||||
gem "net-smtp", "~> 0.2.1", require: false
|
||||
gem "net-imap", "~> 0.2.1", require: false
|
||||
gem "net-pop", "~> 0.1.1", require: false
|
||||
gem "digest", "3.0.0", require: false
|
||||
end
|
||||
|
||||
# Gems used only for assets and not required in production environments by default.
|
||||
# Allow everywhere for now cause we are allowing asset debugging in production
|
||||
group :assets do
|
||||
gem "uglifier"
|
||||
gem 'uglifier'
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem "capybara", require: false
|
||||
gem "webmock", require: false
|
||||
gem "fakeweb", require: false
|
||||
gem "minitest", require: false
|
||||
gem "simplecov", require: false
|
||||
gem "selenium-webdriver", require: false
|
||||
gem 'webmock', require: false
|
||||
gem 'fakeweb', require: false
|
||||
gem 'minitest', require: false
|
||||
gem 'simplecov', require: false
|
||||
gem "test-prof"
|
||||
gem "webdrivers", require: false
|
||||
end
|
||||
|
||||
group :test, :development do
|
||||
gem "rspec"
|
||||
gem "listen", require: false
|
||||
gem "certified", require: false
|
||||
gem "fabrication", require: false
|
||||
gem "mocha", require: false
|
||||
gem 'rspec'
|
||||
gem 'listen', require: false
|
||||
gem 'certified', require: false
|
||||
gem 'fabrication', require: false
|
||||
gem 'mocha', require: false
|
||||
|
||||
gem "rb-fsevent", require: RUBY_PLATFORM =~ /darwin/i ? "rb-fsevent" : false
|
||||
gem 'rb-fsevent', require: RUBY_PLATFORM =~ /darwin/i ? 'rb-fsevent' : false
|
||||
|
||||
gem "rspec-rails"
|
||||
gem 'rspec-rails'
|
||||
|
||||
gem "shoulda-matchers", require: false
|
||||
gem "rspec-html-matchers"
|
||||
gem "byebug", require: ENV["RM_INFO"].nil?, platform: :mri
|
||||
gem "rubocop-discourse", require: false
|
||||
gem "parallel_tests"
|
||||
gem 'shoulda-matchers', require: false
|
||||
gem 'rspec-html-matchers'
|
||||
gem 'byebug', require: ENV['RM_INFO'].nil?, platform: :mri
|
||||
gem 'rubocop-discourse', require: false, github: 'discourse/rubocop-discourse'
|
||||
gem 'parallel_tests'
|
||||
|
||||
gem "rswag-specs"
|
||||
gem 'rswag-specs'
|
||||
|
||||
gem "annotate"
|
||||
|
||||
gem "syntax_tree"
|
||||
gem "syntax_tree-disable_ternary"
|
||||
gem 'annotate'
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem "ruby-prof", require: false, platform: :mri
|
||||
gem "bullet", require: !!ENV["BULLET"]
|
||||
gem "better_errors", platform: :mri, require: !!ENV["BETTER_ERRORS"]
|
||||
gem "binding_of_caller"
|
||||
gem "yaml-lint"
|
||||
gem "yard"
|
||||
gem 'ruby-prof', require: false, platform: :mri
|
||||
gem 'bullet', require: !!ENV['BULLET']
|
||||
gem 'better_errors', platform: :mri, require: !!ENV['BETTER_ERRORS']
|
||||
gem 'binding_of_caller'
|
||||
gem 'yaml-lint'
|
||||
end
|
||||
|
||||
if ENV["ALLOW_DEV_POPULATE"] == "1"
|
||||
gem "discourse_dev_assets"
|
||||
gem "faker", "~> 2.16"
|
||||
gem 'discourse_dev_assets'
|
||||
gem 'faker', "~> 2.16"
|
||||
else
|
||||
group :development, :test do
|
||||
gem "discourse_dev_assets"
|
||||
gem "faker", "~> 2.16"
|
||||
gem 'discourse_dev_assets'
|
||||
gem 'faker', "~> 2.16"
|
||||
end
|
||||
end
|
||||
|
||||
# this is an optional gem, it provides a high performance replacement
|
||||
# to String#blank? a method that is called quite frequently in current
|
||||
# ActiveRecord, this may change in the future
|
||||
gem "fast_blank", platform: :ruby
|
||||
gem 'fast_blank', platform: :ruby
|
||||
|
||||
# this provides a very efficient lru cache
|
||||
gem "lru_redux"
|
||||
gem 'lru_redux'
|
||||
|
||||
gem "htmlentities", require: false
|
||||
gem 'htmlentities', require: false
|
||||
|
||||
# IMPORTANT: mini profiler monkey patches, so it better be required last
|
||||
# If you want to amend mini profiler to do the monkey patches in the railties
|
||||
# we are open to it. by deferring require to the initializer we can configure discourse installs without it
|
||||
|
||||
gem "rack-mini-profiler", require: ["enable_rails_patches"]
|
||||
gem 'rack-mini-profiler', require: ['enable_rails_patches']
|
||||
|
||||
gem "unicorn", require: false, platform: :ruby
|
||||
gem "puma", require: false
|
||||
gem "rbtrace", require: false, platform: :mri
|
||||
gem "gc_tracer", require: false, platform: :mri
|
||||
gem 'unicorn', require: false, platform: :ruby
|
||||
gem 'puma', require: false
|
||||
gem 'rbtrace', require: false, platform: :mri
|
||||
gem 'gc_tracer', require: false, platform: :mri
|
||||
|
||||
# required for feed importing and embedding
|
||||
gem "ruby-readability", require: false
|
||||
gem 'ruby-readability', require: false
|
||||
|
||||
# rss gem is a bundled gem from Ruby 3 onwards
|
||||
gem "rss", require: false
|
||||
gem 'rss', require: false
|
||||
|
||||
gem "stackprof", require: false, platform: :mri
|
||||
gem "memory_profiler", require: false, platform: :mri
|
||||
gem 'stackprof', require: false, platform: :mri
|
||||
gem 'memory_profiler', require: false, platform: :mri
|
||||
|
||||
gem "cppjieba_rb", require: false
|
||||
gem 'cppjieba_rb', require: false
|
||||
|
||||
gem "lograge", require: false
|
||||
gem "logstash-event", require: false
|
||||
gem "logstash-logger", require: false
|
||||
gem "logster"
|
||||
gem 'lograge', require: false
|
||||
gem 'logstash-event', require: false
|
||||
gem 'logstash-logger', require: false
|
||||
gem 'logster'
|
||||
|
||||
# These are forks of sassc and sassc-rails with dart-sass support
|
||||
gem "dartsass-ruby"
|
||||
gem "dartsass-sprockets"
|
||||
# NOTE: later versions of sassc are causing a segfault, possibly dependent on processer architecture
|
||||
# and until resolved should be locked at 2.0.1
|
||||
gem 'sassc', '2.0.1', require: false
|
||||
gem "sassc-rails"
|
||||
|
||||
gem "rotp", require: false
|
||||
gem 'rotp', require: false
|
||||
|
||||
gem "rqrcode"
|
||||
gem 'rqrcode'
|
||||
|
||||
gem "rubyzip", require: false
|
||||
gem 'rubyzip', require: false
|
||||
|
||||
gem "sshkey", require: false
|
||||
gem 'sshkey', require: false
|
||||
|
||||
gem "rchardet", require: false
|
||||
gem "lz4-ruby", require: false, platform: :ruby
|
||||
gem 'rchardet', require: false
|
||||
gem 'lz4-ruby', require: false, platform: :ruby
|
||||
|
||||
gem "sanitize"
|
||||
gem 'sanitize'
|
||||
|
||||
if ENV["IMPORT"] == "1"
|
||||
gem "mysql2"
|
||||
gem "redcarpet"
|
||||
gem 'mysql2'
|
||||
gem 'redcarpet'
|
||||
|
||||
# NOTE: in import mode the version of sqlite can matter a lot, so we stick it to a specific one
|
||||
gem "sqlite3", "~> 1.3", ">= 1.3.13"
|
||||
gem "ruby-bbcode-to-md", git: "https://github.com/nlalonde/ruby-bbcode-to-md"
|
||||
gem "reverse_markdown"
|
||||
gem "tiny_tds"
|
||||
gem "csv"
|
||||
|
||||
gem "parallel", require: false
|
||||
gem 'sqlite3', '~> 1.3', '>= 1.3.13'
|
||||
gem 'ruby-bbcode-to-md', git: 'https://github.com/nlalonde/ruby-bbcode-to-md'
|
||||
gem 'reverse_markdown'
|
||||
gem 'tiny_tds'
|
||||
gem 'csv'
|
||||
end
|
||||
|
||||
gem "web-push"
|
||||
gem "colored2", require: false
|
||||
gem "maxminddb"
|
||||
gem 'webpush', require: false
|
||||
gem 'colored2', require: false
|
||||
gem 'maxminddb'
|
||||
|
||||
gem "rails_failover", require: false
|
||||
|
||||
gem "faraday"
|
||||
gem "faraday-retry"
|
||||
gem 'rails_failover', require: false
|
||||
|
||||
# workaround for faraday-net_http, see
|
||||
# https://github.com/ruby/net-imap/issues/16#issuecomment-803086765
|
||||
gem "net-http"
|
||||
|
||||
# workaround for prometheus-client
|
||||
gem "webrick", require: false
|
||||
|
||||
# Workaround until Ruby ships with cgi version 0.3.6 or higher.
|
||||
gem "cgi", ">= 0.3.6", require: false
|
||||
|
||||
gem "tzinfo-data"
|
||||
gem 'net-http'
|
||||
|
||||
483
Gemfile.lock
483
Gemfile.lock
@ -6,36 +6,35 @@ GIT
|
||||
mini_mime (>= 0.1.1)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/rails/sprockets
|
||||
revision: f4d3dae71ef29c44b75a49cfbf8032cce07b423a
|
||||
branch: 3.x
|
||||
remote: https://github.com/discourse/rubocop-discourse.git
|
||||
revision: a5aea6e5f150b1eb7765a805bec0ff618cb718b3
|
||||
specs:
|
||||
sprockets (3.7.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
rubocop-discourse (2.5.0)
|
||||
rubocop (>= 1.1.0)
|
||||
rubocop-rspec (>= 2.0.0)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actionmailer (7.0.4.3)
|
||||
actionpack (= 7.0.4.3)
|
||||
actionview (= 7.0.4.3)
|
||||
activejob (= 7.0.4.3)
|
||||
activesupport (= 7.0.4.3)
|
||||
actionmailer (7.0.3.1)
|
||||
actionpack (= 7.0.3.1)
|
||||
actionview (= 7.0.3.1)
|
||||
activejob (= 7.0.3.1)
|
||||
activesupport (= 7.0.3.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (7.0.4.3)
|
||||
actionview (= 7.0.4.3)
|
||||
activesupport (= 7.0.4.3)
|
||||
actionpack (7.0.3.1)
|
||||
actionview (= 7.0.3.1)
|
||||
activesupport (= 7.0.3.1)
|
||||
rack (~> 2.0, >= 2.2.0)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actionview (7.0.4.3)
|
||||
activesupport (= 7.0.4.3)
|
||||
actionview (7.0.3.1)
|
||||
activesupport (= 7.0.3.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
@ -44,15 +43,15 @@ GEM
|
||||
actionview (>= 6.0.a)
|
||||
active_model_serializers (0.8.4)
|
||||
activemodel (>= 3.0)
|
||||
activejob (7.0.4.3)
|
||||
activesupport (= 7.0.4.3)
|
||||
activejob (7.0.3.1)
|
||||
activesupport (= 7.0.3.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.0.4.3)
|
||||
activesupport (= 7.0.4.3)
|
||||
activerecord (7.0.4.3)
|
||||
activemodel (= 7.0.4.3)
|
||||
activesupport (= 7.0.4.3)
|
||||
activesupport (7.0.4.3)
|
||||
activemodel (7.0.3.1)
|
||||
activesupport (= 7.0.3.1)
|
||||
activerecord (7.0.3.1)
|
||||
activemodel (= 7.0.3.1)
|
||||
activesupport (= 7.0.3.1)
|
||||
activesupport (7.0.3.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
@ -82,62 +81,51 @@ GEM
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.5.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
barber (0.12.2)
|
||||
ember-source (>= 1.0, < 3.1)
|
||||
execjs (>= 1.2, < 3)
|
||||
better_errors (2.9.1)
|
||||
coderay (>= 1.0.0)
|
||||
erubi (>= 1.0.0)
|
||||
rack (>= 0.9.0)
|
||||
binding_of_caller (1.0.0)
|
||||
debug_inspector (>= 0.0.1)
|
||||
bootsnap (1.16.0)
|
||||
bootsnap (1.13.0)
|
||||
msgpack (~> 1.2)
|
||||
builder (3.2.4)
|
||||
bullet (7.0.7)
|
||||
bullet (7.0.3)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
byebug (11.1.3)
|
||||
capybara (3.38.0)
|
||||
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)
|
||||
cgi (0.3.6)
|
||||
chunky_png (1.4.0)
|
||||
coderay (1.1.3)
|
||||
colored2 (3.1.2)
|
||||
concurrent-ruby (1.2.2)
|
||||
concurrent-ruby (1.1.10)
|
||||
connection_pool (2.3.0)
|
||||
cose (1.3.0)
|
||||
cose (1.2.1)
|
||||
cbor (~> 0.5.9)
|
||||
openssl-signature_algorithm (~> 1.0)
|
||||
cppjieba_rb (0.4.2)
|
||||
crack (0.4.5)
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
css_parser (1.14.0)
|
||||
css_parser (1.12.0)
|
||||
addressable
|
||||
dartsass-ruby (3.0.1)
|
||||
sass-embedded (~> 1.54)
|
||||
dartsass-sprockets (3.0.0)
|
||||
dartsass-ruby (~> 3.0)
|
||||
railties (>= 4.0.0)
|
||||
sprockets (> 3.0)
|
||||
sprockets-rails
|
||||
tilt
|
||||
date (3.3.3)
|
||||
debug_inspector (1.1.0)
|
||||
diff-lcs (1.5.0)
|
||||
diffy (3.4.2)
|
||||
digest (3.1.1)
|
||||
digest (3.1.0)
|
||||
discourse-ember-rails (0.18.6)
|
||||
active_model_serializers
|
||||
ember-data-source (>= 1.0.0.beta.5)
|
||||
ember-handlebars-template (>= 0.1.1, < 1.0)
|
||||
ember-source (>= 1.1.0)
|
||||
jquery-rails (>= 1.0.17)
|
||||
railties (>= 3.1)
|
||||
discourse-ember-source (3.12.2.3)
|
||||
discourse-fonts (0.0.9)
|
||||
discourse-seed-fu (2.3.12)
|
||||
activerecord (>= 3.1)
|
||||
activesupport (>= 3.1)
|
||||
discourse_dev_assets (0.0.4)
|
||||
faker (~> 2.16)
|
||||
literate_randomizer
|
||||
@ -145,61 +133,83 @@ GEM
|
||||
ecma-re-validator (0.4.0)
|
||||
regexp_parser (~> 2.2)
|
||||
email_reply_trimmer (0.1.13)
|
||||
erubi (1.12.0)
|
||||
excon (0.99.0)
|
||||
ember-data-source (3.0.2)
|
||||
ember-source (>= 2, < 3.0)
|
||||
ember-handlebars-template (0.8.0)
|
||||
barber (>= 0.11.0)
|
||||
sprockets (>= 3.3, < 4.1)
|
||||
ember-source (2.18.2)
|
||||
erubi (1.11.0)
|
||||
excon (0.92.4)
|
||||
execjs (2.8.1)
|
||||
exifr (1.3.10)
|
||||
exifr (1.3.9)
|
||||
fabrication (2.30.0)
|
||||
faker (2.23.0)
|
||||
faker (2.22.0)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
fakeweb (1.3.0)
|
||||
faraday (2.7.4)
|
||||
faraday-net_http (>= 2.0, < 3.1)
|
||||
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)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-net_http (3.0.2)
|
||||
faraday-retry (2.1.0)
|
||||
faraday (~> 2.0)
|
||||
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)
|
||||
fast_blank (1.0.1)
|
||||
fast_xs (0.8.0)
|
||||
fastimage (2.2.6)
|
||||
ffi (1.15.5)
|
||||
fspath (3.1.2)
|
||||
gc_tracer (1.5.1)
|
||||
globalid (1.1.0)
|
||||
globalid (1.0.0)
|
||||
activesupport (>= 5.0)
|
||||
google-protobuf (3.22.2)
|
||||
google-protobuf (3.22.2-aarch64-linux)
|
||||
google-protobuf (3.22.2-arm64-darwin)
|
||||
google-protobuf (3.22.2-x86_64-darwin)
|
||||
google-protobuf (3.22.2-x86_64-linux)
|
||||
guess_html_encoding (0.0.11)
|
||||
hana (1.3.7)
|
||||
hashdiff (1.0.1)
|
||||
hashie (5.0.0)
|
||||
highline (2.1.0)
|
||||
hkdf (1.0.0)
|
||||
highline (2.0.3)
|
||||
hkdf (0.3.0)
|
||||
htmlentities (4.3.4)
|
||||
http_accept_language (2.1.1)
|
||||
i18n (1.12.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
image_optim (0.31.3)
|
||||
image_optim (0.31.1)
|
||||
exifr (~> 1.2, >= 1.2.2)
|
||||
fspath (~> 3.0)
|
||||
image_size (>= 1.5, < 4)
|
||||
in_threads (~> 1.3)
|
||||
progress (~> 3.0, >= 3.0.1)
|
||||
image_size (3.2.0)
|
||||
image_size (3.1.0)
|
||||
in_threads (1.6.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.6.3)
|
||||
json-schema (3.0.0)
|
||||
addressable (>= 2.8)
|
||||
json_schemer (0.2.23)
|
||||
jmespath (1.6.1)
|
||||
jquery-rails (4.5.0)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (2.6.2)
|
||||
json-schema (2.8.1)
|
||||
addressable (>= 2.4)
|
||||
json_schemer (0.2.21)
|
||||
ecma-re-validator (~> 0.3)
|
||||
hana (~> 1.3)
|
||||
regexp_parser (~> 2.0)
|
||||
uri_template (~> 0.7)
|
||||
jwt (2.7.0)
|
||||
jwt (2.5.0)
|
||||
kgio (2.11.4)
|
||||
libv8-node (16.10.0.0)
|
||||
libv8-node (16.10.0.0-aarch64-linux)
|
||||
@ -207,7 +217,7 @@ GEM
|
||||
libv8-node (16.10.0.0-x86_64-darwin)
|
||||
libv8-node (16.10.0.0-x86_64-darwin-19)
|
||||
libv8-node (16.10.0.0-x86_64-linux)
|
||||
listen (3.8.0)
|
||||
listen (3.7.1)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
literate_randomizer (0.4.0)
|
||||
@ -219,69 +229,73 @@ GEM
|
||||
logstash-event (1.2.02)
|
||||
logstash-logger (0.26.1)
|
||||
logstash-event (~> 1.2)
|
||||
logster (2.12.2)
|
||||
loofah (2.19.1)
|
||||
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.1)
|
||||
message_bus (4.3.2)
|
||||
memory_profiler (1.0.0)
|
||||
message_bus (4.2.0)
|
||||
rack (>= 1.1.3)
|
||||
method_source (1.0.0)
|
||||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.8.1)
|
||||
mini_portile2 (2.8.0)
|
||||
mini_racer (0.6.3)
|
||||
libv8-node (~> 16.10.0.0)
|
||||
mini_scheduler (0.15.0)
|
||||
sidekiq (>= 4.2.3, < 7.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.18.0)
|
||||
mocha (2.0.2)
|
||||
ruby2_keywords (>= 0.0.5)
|
||||
msgpack (1.6.1)
|
||||
minitest (5.16.3)
|
||||
mocha (1.14.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.3.2)
|
||||
net-http (0.2.2)
|
||||
uri
|
||||
net-imap (0.3.4)
|
||||
date
|
||||
net-imap (0.2.3)
|
||||
digest
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
strscan
|
||||
net-pop (0.1.1)
|
||||
digest
|
||||
net-protocol
|
||||
net-protocol (0.2.1)
|
||||
timeout
|
||||
net-smtp (0.3.3)
|
||||
net-protocol (0.1.3)
|
||||
timeout
|
||||
net-smtp (0.3.1)
|
||||
digest
|
||||
net-protocol
|
||||
timeout
|
||||
nio4r (2.5.8)
|
||||
nokogiri (1.14.2)
|
||||
nokogiri (1.13.8)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.14.2-aarch64-linux)
|
||||
nokogiri (1.13.8-aarch64-linux)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.14.2-arm64-darwin)
|
||||
nokogiri (1.13.8-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.14.2-x86_64-darwin)
|
||||
nokogiri (1.13.8-x86_64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.14.2-x86_64-linux)
|
||||
nokogiri (1.13.8-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
oauth (1.1.0)
|
||||
oauth-tty (~> 1.0, >= 1.0.1)
|
||||
snaky_hash (~> 2.0)
|
||||
version_gem (~> 1.1)
|
||||
oauth-tty (1.0.5)
|
||||
version_gem (~> 1.1, >= 1.1.1)
|
||||
oauth2 (1.4.11)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
oauth-tty (1.0.3)
|
||||
version_gem (~> 1.1)
|
||||
oauth2 (1.4.7)
|
||||
faraday (>= 0.8, < 2.0)
|
||||
jwt (>= 1.0, < 3.0)
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 4)
|
||||
rack (>= 1.2, < 3)
|
||||
oj (3.13.14)
|
||||
omniauth (1.9.2)
|
||||
hashie (>= 3.4.6)
|
||||
@ -299,25 +313,24 @@ GEM
|
||||
omniauth-oauth (1.2.0)
|
||||
oauth
|
||||
omniauth (>= 1.0, < 3)
|
||||
omniauth-oauth2 (1.7.3)
|
||||
oauth2 (>= 1.4, < 3)
|
||||
omniauth-oauth2 (1.7.2)
|
||||
oauth2 (~> 1.4)
|
||||
omniauth (>= 1.9, < 3)
|
||||
omniauth-twitter (1.4.0)
|
||||
omniauth-oauth (~> 1.1)
|
||||
rack
|
||||
openssl (3.1.0)
|
||||
openssl-signature_algorithm (1.3.0)
|
||||
openssl (> 2.0)
|
||||
openssl (3.0.1)
|
||||
openssl-signature_algorithm (1.2.1)
|
||||
openssl (> 2.0, < 3.1)
|
||||
optimist (3.0.1)
|
||||
parallel (1.22.1)
|
||||
parallel_tests (4.2.0)
|
||||
parallel_tests (3.12.1)
|
||||
parallel
|
||||
parser (3.2.1.1)
|
||||
parser (3.1.2.1)
|
||||
ast (~> 2.4.1)
|
||||
pg (1.4.6)
|
||||
prettier_print (1.2.1)
|
||||
pg (1.4.3)
|
||||
progress (3.6.0)
|
||||
pry (0.14.2)
|
||||
pry (0.14.1)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
pry-byebug (3.10.1)
|
||||
@ -325,22 +338,23 @@ GEM
|
||||
pry (>= 0.13, < 0.15)
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (5.0.1)
|
||||
puma (6.1.1)
|
||||
public_suffix (5.0.0)
|
||||
puma (5.6.5)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.6.2)
|
||||
rack (2.2.6.4)
|
||||
r2 (0.2.7)
|
||||
racc (1.6.0)
|
||||
rack (2.2.4)
|
||||
rack-mini-profiler (3.0.0)
|
||||
rack (>= 1.2.0)
|
||||
rack-protection (3.0.5)
|
||||
rack-protection (2.2.2)
|
||||
rack
|
||||
rack-test (2.1.0)
|
||||
rack-test (2.0.2)
|
||||
rack (>= 1.3)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.5.0)
|
||||
loofah (~> 2.19, >= 2.19.1)
|
||||
rails-html-sanitizer (1.4.3)
|
||||
loofah (~> 2.3)
|
||||
rails_failover (0.8.1)
|
||||
activerecord (> 6.0, < 7.1)
|
||||
concurrent-ruby
|
||||
@ -348,15 +362,15 @@ GEM
|
||||
rails_multisite (4.0.1)
|
||||
activerecord (> 5.0, < 7.1)
|
||||
railties (> 5.0, < 7.1)
|
||||
railties (7.0.4.3)
|
||||
actionpack (= 7.0.4.3)
|
||||
activesupport (= 7.0.4.3)
|
||||
railties (7.0.3.1)
|
||||
actionpack (= 7.0.3.1)
|
||||
activesupport (= 7.0.3.1)
|
||||
method_source
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
zeitwerk (~> 2.5)
|
||||
rainbow (3.1.1)
|
||||
raindrops (0.20.1)
|
||||
raindrops (0.20.0)
|
||||
rake (13.0.6)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.10.1)
|
||||
@ -366,163 +380,139 @@ GEM
|
||||
msgpack (>= 0.4.3)
|
||||
optimist (>= 3.0.0)
|
||||
rchardet (1.8.0)
|
||||
redis (4.8.1)
|
||||
redis-namespace (1.10.0)
|
||||
redis (4.7.1)
|
||||
redis-namespace (1.9.0)
|
||||
redis (>= 4)
|
||||
regexp_parser (2.7.0)
|
||||
regexp_parser (2.5.0)
|
||||
request_store (1.5.1)
|
||||
rack (>= 1.4)
|
||||
rexml (3.2.5)
|
||||
rinku (2.0.6)
|
||||
rotp (6.2.2)
|
||||
rotp (6.2.0)
|
||||
rqrcode (2.1.2)
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 1.0)
|
||||
rqrcode_core (1.2.0)
|
||||
rspec (3.12.0)
|
||||
rspec-core (~> 3.12.0)
|
||||
rspec-expectations (~> 3.12.0)
|
||||
rspec-mocks (~> 3.12.0)
|
||||
rspec-core (3.12.1)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-expectations (3.12.2)
|
||||
rspec (3.11.0)
|
||||
rspec-core (~> 3.11.0)
|
||||
rspec-expectations (~> 3.11.0)
|
||||
rspec-mocks (~> 3.11.0)
|
||||
rspec-core (3.11.0)
|
||||
rspec-support (~> 3.11.0)
|
||||
rspec-expectations (3.11.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-support (~> 3.11.0)
|
||||
rspec-html-matchers (0.10.0)
|
||||
nokogiri (~> 1)
|
||||
rspec (>= 3.0.0.a)
|
||||
rspec-mocks (3.12.4)
|
||||
rspec-mocks (3.11.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-rails (6.0.1)
|
||||
actionpack (>= 6.1)
|
||||
activesupport (>= 6.1)
|
||||
railties (>= 6.1)
|
||||
rspec-core (~> 3.11)
|
||||
rspec-expectations (~> 3.11)
|
||||
rspec-mocks (~> 3.11)
|
||||
rspec-support (~> 3.11)
|
||||
rspec-support (3.12.0)
|
||||
rspec-support (~> 3.11.0)
|
||||
rspec-rails (5.1.2)
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
rspec-core (~> 3.10)
|
||||
rspec-expectations (~> 3.10)
|
||||
rspec-mocks (~> 3.10)
|
||||
rspec-support (~> 3.10)
|
||||
rspec-support (3.11.1)
|
||||
rss (0.2.9)
|
||||
rexml
|
||||
rswag-specs (2.8.0)
|
||||
rswag-specs (2.5.1)
|
||||
activesupport (>= 3.1, < 7.1)
|
||||
json-schema (>= 2.2, < 4.0)
|
||||
json-schema (~> 2.2)
|
||||
railties (>= 3.1, < 7.1)
|
||||
rspec-core (>= 2.14)
|
||||
rtlcss (0.2.0)
|
||||
mini_racer (~> 0.6.3)
|
||||
rubocop (1.48.1)
|
||||
rubocop (1.36.0)
|
||||
json (~> 2.3)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.2.0.0)
|
||||
parser (>= 3.1.2.1)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.26.0, < 2.0)
|
||||
rubocop-ast (>= 1.20.1, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.27.0)
|
||||
parser (>= 3.2.1.0)
|
||||
rubocop-capybara (2.17.1)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-discourse (3.2.0)
|
||||
rubocop (>= 1.1.0)
|
||||
rubocop-rspec (>= 2.0.0)
|
||||
rubocop-rspec (2.19.0)
|
||||
unicode-display_width (>= 1.4.0, < 3.0)
|
||||
rubocop-ast (1.21.0)
|
||||
parser (>= 3.1.1.0)
|
||||
rubocop-rspec (2.13.1)
|
||||
rubocop (~> 1.33)
|
||||
rubocop-capybara (~> 2.17)
|
||||
ruby-prof (1.6.1)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-prof (1.4.3)
|
||||
ruby-progressbar (1.11.0)
|
||||
ruby-readability (0.7.0)
|
||||
guess_html_encoding (>= 0.0.4)
|
||||
nokogiri (>= 1.6.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
sanitize (6.0.1)
|
||||
sanitize (6.0.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
sass-embedded (1.59.2)
|
||||
google-protobuf (~> 3.21)
|
||||
rake (>= 10.0.0)
|
||||
sass-embedded (1.59.2-aarch64-linux-gnu)
|
||||
google-protobuf (~> 3.21)
|
||||
sass-embedded (1.59.2-arm64-darwin)
|
||||
google-protobuf (~> 3.21)
|
||||
sass-embedded (1.59.2-x86_64-darwin)
|
||||
google-protobuf (~> 3.21)
|
||||
sass-embedded (1.59.2-x86_64-linux-gnu)
|
||||
google-protobuf (~> 3.21)
|
||||
selenium-webdriver (4.8.1)
|
||||
rexml (~> 3.2, >= 3.2.5)
|
||||
rubyzip (>= 1.2.2, < 3.0)
|
||||
websocket (~> 1.0)
|
||||
shoulda-matchers (5.3.0)
|
||||
sassc (2.0.1)
|
||||
ffi (~> 1.9)
|
||||
rake
|
||||
sassc-rails (2.1.2)
|
||||
railties (>= 4.0.0)
|
||||
sassc (>= 2.0)
|
||||
sprockets (> 3.0)
|
||||
sprockets-rails
|
||||
tilt
|
||||
seed-fu (2.3.9)
|
||||
activerecord (>= 3.1)
|
||||
activesupport (>= 3.1)
|
||||
shoulda-matchers (5.2.0)
|
||||
activesupport (>= 5.2.0)
|
||||
sidekiq (6.5.8)
|
||||
connection_pool (>= 2.2.5, < 3)
|
||||
sidekiq (6.5.6)
|
||||
connection_pool (>= 2.2.5)
|
||||
rack (~> 2.0)
|
||||
redis (>= 4.5.0, < 5)
|
||||
simplecov (0.22.0)
|
||||
simplecov (0.21.2)
|
||||
docile (~> 1.1)
|
||||
simplecov-html (~> 0.11)
|
||||
simplecov_json_formatter (~> 0.1)
|
||||
simplecov-html (0.12.3)
|
||||
simplecov_json_formatter (0.1.4)
|
||||
snaky_hash (2.0.1)
|
||||
snaky_hash (2.0.0)
|
||||
hashie
|
||||
version_gem (~> 1.1, >= 1.1.1)
|
||||
version_gem (~> 1.1)
|
||||
sprockets (3.7.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.4.2)
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
sprockets (>= 3.0.0)
|
||||
sshkey (2.0.0)
|
||||
stackprof (0.2.23)
|
||||
syntax_tree (6.0.2)
|
||||
prettier_print (>= 1.2.0)
|
||||
syntax_tree-disable_ternary (1.0.0)
|
||||
test-prof (1.2.0)
|
||||
stackprof (0.2.21)
|
||||
strscan (3.0.4)
|
||||
test-prof (1.0.10)
|
||||
thor (1.2.1)
|
||||
tilt (2.1.0)
|
||||
timeout (0.3.2)
|
||||
tzinfo (2.0.6)
|
||||
tilt (2.0.11)
|
||||
timeout (0.3.0)
|
||||
tzinfo (2.0.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
tzinfo-data (1.2022.7)
|
||||
tzinfo (>= 1.0.0)
|
||||
uglifier (4.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (2.4.2)
|
||||
unicode-display_width (2.3.0)
|
||||
unicorn (6.1.0)
|
||||
kgio (~> 2.6)
|
||||
raindrops (~> 0.7)
|
||||
uniform_notifier (1.16.0)
|
||||
uri (0.12.0)
|
||||
uri (0.11.0)
|
||||
uri_template (0.7.0)
|
||||
version_gem (1.1.1)
|
||||
web-push (3.0.0)
|
||||
hkdf (~> 1.0)
|
||||
jwt (~> 2.0)
|
||||
openssl (~> 3.0)
|
||||
webdrivers (5.2.0)
|
||||
nokogiri (~> 1.6)
|
||||
rubyzip (>= 1.3.0)
|
||||
selenium-webdriver (~> 4.0)
|
||||
version_gem (1.1.0)
|
||||
webmock (3.18.1)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webrick (1.7.0)
|
||||
websocket (1.2.9)
|
||||
webpush (1.1.0)
|
||||
hkdf (~> 0.2)
|
||||
jwt (~> 2.0)
|
||||
xorcist (1.1.3)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
yaml-lint (0.1.2)
|
||||
yard (0.9.28)
|
||||
webrick (~> 1.7.0)
|
||||
zeitwerk (2.6.7)
|
||||
yaml-lint (0.0.10)
|
||||
zeitwerk (2.6.0)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux
|
||||
@ -534,46 +524,42 @@ PLATFORMS
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
actionmailer (= 7.0.4.3)
|
||||
actionpack (= 7.0.4.3)
|
||||
actionview (= 7.0.4.3)
|
||||
actionmailer (= 7.0.3.1)
|
||||
actionpack (= 7.0.3.1)
|
||||
actionview (= 7.0.3.1)
|
||||
actionview_precompiler
|
||||
active_model_serializers (~> 0.8.3)
|
||||
activemodel (= 7.0.4.3)
|
||||
activerecord (= 7.0.4.3)
|
||||
activesupport (= 7.0.4.3)
|
||||
activemodel (= 7.0.3.1)
|
||||
activerecord (= 7.0.3.1)
|
||||
activesupport (= 7.0.3.1)
|
||||
addressable
|
||||
annotate
|
||||
aws-sdk-s3
|
||||
aws-sdk-sns
|
||||
barber
|
||||
better_errors
|
||||
binding_of_caller
|
||||
bootsnap
|
||||
bullet
|
||||
byebug
|
||||
capybara
|
||||
cbor
|
||||
certified
|
||||
cgi (>= 0.3.6)
|
||||
colored2
|
||||
cose
|
||||
cppjieba_rb
|
||||
css_parser
|
||||
dartsass-ruby
|
||||
dartsass-sprockets
|
||||
diffy
|
||||
digest
|
||||
discourse-ember-rails (= 0.18.6)
|
||||
discourse-ember-source (~> 3.12.2)
|
||||
discourse-fonts
|
||||
discourse-seed-fu
|
||||
discourse_dev_assets
|
||||
email_reply_trimmer
|
||||
ember-handlebars-template (= 0.8.0)
|
||||
excon
|
||||
execjs
|
||||
fabrication
|
||||
faker (~> 2.16)
|
||||
fakeweb
|
||||
faraday
|
||||
faraday-retry
|
||||
fast_blank
|
||||
fast_xs
|
||||
fastimage
|
||||
@ -606,9 +592,6 @@ DEPENDENCIES
|
||||
multi_json
|
||||
mustache
|
||||
net-http
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
nokogiri
|
||||
oj (= 3.13.14)
|
||||
omniauth
|
||||
@ -622,12 +605,13 @@ DEPENDENCIES
|
||||
pry-byebug
|
||||
pry-rails
|
||||
puma
|
||||
r2
|
||||
rack
|
||||
rack-mini-profiler
|
||||
rack-protection
|
||||
rails_failover
|
||||
rails_multisite
|
||||
railties (= 7.0.4.3)
|
||||
railties (= 7.0.3.1)
|
||||
rake
|
||||
rb-fsevent
|
||||
rbtrace
|
||||
@ -642,35 +626,30 @@ DEPENDENCIES
|
||||
rspec-rails
|
||||
rss
|
||||
rswag-specs
|
||||
rtlcss
|
||||
rubocop-discourse
|
||||
rubocop-discourse!
|
||||
ruby-prof
|
||||
ruby-readability
|
||||
rubyzip
|
||||
sanitize
|
||||
selenium-webdriver
|
||||
sassc (= 2.0.1)
|
||||
sassc-rails
|
||||
seed-fu
|
||||
shoulda-matchers
|
||||
sidekiq
|
||||
simplecov
|
||||
sprockets!
|
||||
sprockets (= 3.7.2)
|
||||
sprockets-rails
|
||||
sshkey
|
||||
stackprof
|
||||
syntax_tree
|
||||
syntax_tree-disable_ternary
|
||||
test-prof
|
||||
thor
|
||||
tzinfo-data
|
||||
uglifier
|
||||
unf
|
||||
unicorn
|
||||
web-push
|
||||
webdrivers
|
||||
webmock
|
||||
webrick
|
||||
webpush
|
||||
xorcist
|
||||
yaml-lint
|
||||
yard
|
||||
|
||||
BUNDLED WITH
|
||||
2.4.4
|
||||
2.3.18
|
||||
|
||||
@ -30,7 +30,7 @@ To get your environment setup, follow the community setup guide for your operati
|
||||
|
||||
If you're familiar with how Rails works and are comfortable setting up your own environment, you can also try out the [**Discourse Advanced Developer Guide**](docs/DEVELOPER-ADVANCED.md), which is aimed primarily at Ubuntu and macOS environments.
|
||||
|
||||
Before you get started, ensure you have the following minimum versions: [Ruby 3.2+](https://www.ruby-lang.org/en/downloads/), [PostgreSQL 13](https://www.postgresql.org/download/), [Redis 7](https://redis.io/download). If you're having trouble, please see our [**TROUBLESHOOTING GUIDE**](docs/TROUBLESHOOTING.md) first!
|
||||
Before you get started, ensure you have the following minimum versions: [Ruby 2.7+](https://www.ruby-lang.org/en/downloads/), [PostgreSQL 13+](https://www.postgresql.org/download/), [Redis 6.2+](https://redis.io/download). If you're having trouble, please see our [**TROUBLESHOOTING GUIDE**](docs/TROUBLESHOOTING.md) first!
|
||||
|
||||
## Setting up Discourse
|
||||
|
||||
@ -51,7 +51,7 @@ Discourse supports the **latest, stable releases** of all major browsers and pla
|
||||
| Microsoft Edge | | |
|
||||
| Mozilla Firefox | | |
|
||||
|
||||
Additionally, we aim to support Safari on iOS 15.7+.
|
||||
Additionally, we aim to support Safari on iOS 12.5+ until January 2023 (Discourse 3.0).
|
||||
|
||||
## Built With
|
||||
|
||||
@ -91,7 +91,7 @@ The original Discourse code contributors can be found in [**AUTHORS.MD**](docs/A
|
||||
|
||||
## Copyright / License
|
||||
|
||||
Copyright 2014 - 2023 Civilized Discourse Construction Kit, Inc.
|
||||
Copyright 2014 - 2022 Civilized Discourse Construction Kit, Inc.
|
||||
|
||||
Licensed under the GNU General Public License Version 2.0 (or later);
|
||||
you may not use this work except in compliance with the License.
|
||||
|
||||
@ -21,7 +21,8 @@
|
||||
"line-stream": "0.0.0",
|
||||
"regenerator-transform": "0.10.1",
|
||||
"source-map": "0.1.43",
|
||||
"sourcemap-validator": "1.1.1"
|
||||
"sourcemap-validator": "1.1.1",
|
||||
"xmldom": "0.1.31"
|
||||
},
|
||||
"corrections": true,
|
||||
"ignore": [
|
||||
|
||||
11
app/assets/javascripts/admin-plugins.js.erb
Normal file
11
app/assets/javascripts/admin-plugins.js.erb
Normal file
@ -0,0 +1,11 @@
|
||||
<%
|
||||
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
|
||||
%>
|
||||
@ -1,13 +1,13 @@
|
||||
import RestAdapter from "discourse/adapters/rest";
|
||||
import RESTAdapter from "discourse/adapters/rest";
|
||||
|
||||
export default class ApiKey extends RestAdapter {
|
||||
jsonMode = true;
|
||||
export default RESTAdapter.extend({
|
||||
jsonMode: true,
|
||||
|
||||
basePath() {
|
||||
return "/admin/api/";
|
||||
}
|
||||
},
|
||||
|
||||
apiNameFor() {
|
||||
return "key";
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import RestAdapter from "discourse/adapters/rest";
|
||||
|
||||
export default function buildPluginAdapter(pluginName) {
|
||||
return class extends RestAdapter {
|
||||
return RestAdapter.extend({
|
||||
pathFor(store, type, findArgs) {
|
||||
return (
|
||||
"/admin/plugins/" + pluginName + super.pathFor(store, type, findArgs)
|
||||
"/admin/plugins/" + pluginName + this._super(store, type, findArgs)
|
||||
);
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import RestAdapter from "discourse/adapters/rest";
|
||||
|
||||
export default class CustomizationBase extends RestAdapter {
|
||||
export default RestAdapter.extend({
|
||||
basePath() {
|
||||
return "/admin/customize/";
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import RestAdapter from "discourse/adapters/rest";
|
||||
|
||||
export default class EmailStyle extends RestAdapter {
|
||||
export default RestAdapter.extend({
|
||||
pathFor() {
|
||||
return "/admin/customize/email_style";
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import RestAdapter from "discourse/adapters/rest";
|
||||
|
||||
export default class Embedding extends RestAdapter {
|
||||
export default RestAdapter.extend({
|
||||
pathFor() {
|
||||
return "/admin/customize/embedding";
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import RestAdapter from "discourse/adapters/rest";
|
||||
|
||||
export default class StaffActionLog extends RestAdapter {
|
||||
export default RestAdapter.extend({
|
||||
basePath() {
|
||||
return "/admin/logs/";
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import RestAdapter from "discourse/adapters/rest";
|
||||
|
||||
export default class TagGroup extends RestAdapter {
|
||||
jsonMode = true;
|
||||
}
|
||||
export default RestAdapter.extend({
|
||||
jsonMode: true,
|
||||
});
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import RestAdapter from "discourse/adapters/rest";
|
||||
|
||||
export default class Theme extends RestAdapter {
|
||||
jsonMode = true;
|
||||
export default RestAdapter.extend({
|
||||
basePath() {
|
||||
return "/admin/";
|
||||
}
|
||||
},
|
||||
|
||||
afterFindAll(results) {
|
||||
let map = {};
|
||||
@ -21,5 +20,7 @@ export default class Theme extends RestAdapter {
|
||||
theme.set("parentThemes", mappedParents);
|
||||
});
|
||||
return results;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
jsonMode: true,
|
||||
});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import RestAdapter from "discourse/adapters/rest";
|
||||
import RESTAdapter from "discourse/adapters/rest";
|
||||
|
||||
export default class WebHookEvent extends RestAdapter {
|
||||
export default RESTAdapter.extend({
|
||||
basePath() {
|
||||
return "/admin/api/";
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import RestAdapter from "discourse/adapters/rest";
|
||||
import RESTAdapter from "discourse/adapters/rest";
|
||||
|
||||
export default class WebHook extends RestAdapter {
|
||||
export default RESTAdapter.extend({
|
||||
basePath() {
|
||||
return "/admin/api/";
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1 +0,0 @@
|
||||
<div class="ace">{{this.content}}</div>
|
||||
@ -1,33 +1,31 @@
|
||||
import { action } from "@ember/object";
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import { observes, on } from "@ember-decorators/object";
|
||||
import Component from "@ember/component";
|
||||
import getURL from "discourse-common/lib/get-url";
|
||||
import loadScript from "discourse/lib/load-script";
|
||||
import I18n from "I18n";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import { bind, observes } from "discourse-common/utils/decorators";
|
||||
import { on } from "@ember/object/evented";
|
||||
|
||||
const COLOR_VARS_REGEX =
|
||||
/\$(primary|secondary|tertiary|quaternary|header_background|header_primary|highlight|danger|success|love)(\s|;|-(low|medium|high))/g;
|
||||
|
||||
@classNames("ace-wrapper")
|
||||
export default class AceEditor extends Component {
|
||||
mode = "css";
|
||||
disabled = false;
|
||||
htmlPlaceholder = false;
|
||||
_editor = null;
|
||||
_skipContentChangeEvent = null;
|
||||
export default Component.extend({
|
||||
mode: "css",
|
||||
classNames: ["ace-wrapper"],
|
||||
_editor: null,
|
||||
_skipContentChangeEvent: null,
|
||||
disabled: false,
|
||||
htmlPlaceholder: false,
|
||||
|
||||
@observes("editorId")
|
||||
editorIdChanged() {
|
||||
if (this.autofocus) {
|
||||
this.send("focus");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
didRender() {
|
||||
this._skipContentChangeEvent = false;
|
||||
}
|
||||
},
|
||||
|
||||
@observes("content")
|
||||
contentChanged() {
|
||||
@ -35,14 +33,14 @@ export default class AceEditor extends Component {
|
||||
if (this._editor && !this._skipContentChangeEvent) {
|
||||
this._editor.getSession().setValue(content);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@observes("mode")
|
||||
modeChanged() {
|
||||
if (this._editor && !this._skipContentChangeEvent) {
|
||||
this._editor.getSession().setMode("ace/mode/" + this.mode);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@observes("placeholder")
|
||||
placeholderChanged() {
|
||||
@ -51,12 +49,12 @@ export default class AceEditor extends Component {
|
||||
placeholder: this.placeholder,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@observes("disabled")
|
||||
disabledStateChanged() {
|
||||
this.changeDisabledState();
|
||||
}
|
||||
},
|
||||
|
||||
changeDisabledState() {
|
||||
const editor = this._editor;
|
||||
@ -69,10 +67,9 @@ export default class AceEditor extends Component {
|
||||
});
|
||||
editor.container.parentNode.setAttribute("data-disabled", disabled);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@on("willDestroyElement")
|
||||
_destroyEditor() {
|
||||
_destroyEditor: on("willDestroyElement", function () {
|
||||
if (this._editor) {
|
||||
this._editor.destroy();
|
||||
this._editor = null;
|
||||
@ -83,16 +80,16 @@ export default class AceEditor extends Component {
|
||||
}
|
||||
|
||||
$(window).off("ace:resize");
|
||||
}
|
||||
}),
|
||||
|
||||
resize() {
|
||||
if (this._editor) {
|
||||
this._editor.resize();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
super.didInsertElement(...arguments);
|
||||
this._super(...arguments);
|
||||
loadScript("/javascripts/ace/ace.js").then(() => {
|
||||
window.ace.require(["ace/ace"], (loadedAce) => {
|
||||
loadedAce.config.set("loadWorkerFromBlob", false);
|
||||
@ -156,13 +153,13 @@ export default class AceEditor extends Component {
|
||||
this._darkModeListener.addListener(this.setAceTheme);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
if (this._darkModeListener) {
|
||||
this._darkModeListener.removeListener(this.setAceTheme);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@bind
|
||||
setAceTheme() {
|
||||
@ -173,7 +170,7 @@ export default class AceEditor extends Component {
|
||||
this._editor.setTheme(
|
||||
`ace/theme/${schemeType === "dark" ? "chaos" : "chrome"}`
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
warnSCSSDeprecations() {
|
||||
if (
|
||||
@ -200,20 +197,21 @@ export default class AceEditor extends Component {
|
||||
|
||||
this._editor.getSession().setAnnotations(warnings);
|
||||
|
||||
this.setWarning?.(
|
||||
this.setWarning(
|
||||
warnings.length
|
||||
? I18n.t("admin.customize.theme.scss_color_variables_warning")
|
||||
: false
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
focus() {
|
||||
if (this._editor) {
|
||||
this._editor.focus();
|
||||
this._editor.navigateFileEnd();
|
||||
}
|
||||
}
|
||||
actions: {
|
||||
focus() {
|
||||
if (this._editor) {
|
||||
this._editor.focus();
|
||||
this._editor.navigateFileEnd();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
_overridePlaceholder(loadedAce) {
|
||||
const originalPlaceholderSetter =
|
||||
@ -241,5 +239,5 @@ export default class AceEditor extends Component {
|
||||
|
||||
this.$updatePlaceholder();
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
{{#if this.hasFormattedLogs}}
|
||||
<pre>{{this.formattedLogs}}</pre>
|
||||
{{else}}
|
||||
<p>{{this.noLogsMessage}}</p>
|
||||
{{/if}}
|
||||
{{#if this.showLoadingSpinner}}
|
||||
<div class="spinner small"></div>
|
||||
{{/if}}
|
||||
@ -1,26 +1,28 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import { observes, on } from "@ember-decorators/object";
|
||||
import { observes, on } from "discourse-common/utils/decorators";
|
||||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import { scheduleOnce } from "@ember/runloop";
|
||||
|
||||
@classNames("admin-backups-logs")
|
||||
export default class AdminBackupsLogs extends Component {
|
||||
showLoadingSpinner = false;
|
||||
hasFormattedLogs = false;
|
||||
noLogsMessage = I18n.t("admin.backups.logs.none");
|
||||
formattedLogs = "";
|
||||
index = 0;
|
||||
export default Component.extend({
|
||||
classNames: ["admin-backups-logs"],
|
||||
showLoadingSpinner: false,
|
||||
hasFormattedLogs: false,
|
||||
noLogsMessage: I18n.t("admin.backups.logs.none"),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this._reset();
|
||||
},
|
||||
|
||||
_reset() {
|
||||
this.setProperties({ formattedLogs: "", index: 0 });
|
||||
}
|
||||
},
|
||||
|
||||
_scrollDown() {
|
||||
const div = this.element;
|
||||
div.scrollTop = div.scrollHeight;
|
||||
}
|
||||
},
|
||||
|
||||
@on("init")
|
||||
@observes("logs.[]")
|
||||
@ -29,7 +31,7 @@ export default class AdminBackupsLogs extends Component {
|
||||
this._reset(); // reset the cached logs whenever the model is reset
|
||||
this.renderLogs();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_updateFormattedLogsFunc() {
|
||||
const logs = this.logs;
|
||||
@ -53,13 +55,13 @@ export default class AdminBackupsLogs extends Component {
|
||||
this.renderLogs();
|
||||
|
||||
scheduleOnce("afterRender", this, this._scrollDown);
|
||||
}
|
||||
},
|
||||
|
||||
@on("init")
|
||||
@observes("logs.[]")
|
||||
_updateFormattedLogs() {
|
||||
discourseDebounce(this, this._updateFormattedLogsFunc, 150);
|
||||
}
|
||||
},
|
||||
|
||||
renderLogs() {
|
||||
const formattedLogs = this.formattedLogs;
|
||||
@ -74,5 +76,5 @@ export default class AdminBackupsLogs extends Component {
|
||||
} else {
|
||||
this.set("showLoadingSpinner", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
<div class="field">{{i18n this.name}}</div>
|
||||
<div class="value">
|
||||
{{#if this.editing}}
|
||||
<TextField
|
||||
@value={{this.buffer}}
|
||||
@autofocus="autofocus"
|
||||
@autocomplete="off"
|
||||
/>
|
||||
{{else}}
|
||||
<a href {{on "click" this.edit}} class="inline-editable-field">
|
||||
<span>{{this.value}}</span>
|
||||
</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="controls">
|
||||
{{#if this.editing}}
|
||||
<DButton
|
||||
@class="btn-default"
|
||||
@action={{action "save"}}
|
||||
@label="admin.user_fields.save"
|
||||
/>
|
||||
<a href {{on "click" this.edit}}>{{i18n "cancel"}}</a>
|
||||
{{else}}
|
||||
<DButton
|
||||
@class="btn-default"
|
||||
@action={{action "edit"}}
|
||||
@icon="pencil-alt"
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
@ -1,22 +1,24 @@
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
import { action } from "@ember/object";
|
||||
export default Component.extend({
|
||||
tagName: "",
|
||||
|
||||
@tagName("")
|
||||
export default class AdminEditableField extends Component {
|
||||
buffer = "";
|
||||
editing = false;
|
||||
buffer: "",
|
||||
editing: false,
|
||||
|
||||
@action
|
||||
edit(event) {
|
||||
event?.preventDefault();
|
||||
this.set("buffer", this.value);
|
||||
this.toggleProperty("editing");
|
||||
}
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.set("editing", false);
|
||||
},
|
||||
|
||||
@action
|
||||
save() {
|
||||
// Action has to toggle 'editing' property.
|
||||
this.action(this.buffer);
|
||||
}
|
||||
}
|
||||
actions: {
|
||||
edit() {
|
||||
this.set("buffer", this.value);
|
||||
this.toggleProperty("editing");
|
||||
},
|
||||
|
||||
save() {
|
||||
// Action has to toggle 'editing' property.
|
||||
this.action(this.buffer);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
<div class="form-element label-area">
|
||||
{{#if this.label}}
|
||||
<label>{{i18n this.label}}</label>
|
||||
{{else}}
|
||||
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="form-element input-area">
|
||||
{{#if this.wrapLabel}}
|
||||
<label>{{yield}}</label>
|
||||
{{else}}
|
||||
{{yield}}
|
||||
{{/if}}
|
||||
</div>
|
||||
@ -1,5 +1,4 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
|
||||
@classNames("row")
|
||||
export default class AdminFormRow extends Component {}
|
||||
export default Component.extend({
|
||||
classNames: ["row"],
|
||||
});
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
import loadScript from "discourse/lib/load-script";
|
||||
|
||||
@tagName("canvas")
|
||||
export default class AdminGraph extends Component {
|
||||
type = "line";
|
||||
export default Component.extend({
|
||||
tagName: "canvas",
|
||||
type: "line",
|
||||
|
||||
refreshChart() {
|
||||
const ctx = this.element.getContext("2d");
|
||||
@ -50,11 +49,11 @@ export default class AdminGraph extends Component {
|
||||
};
|
||||
|
||||
this._chart = new window.Chart(ctx, config);
|
||||
}
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
loadScript("/javascripts/Chart.min.js").then(() =>
|
||||
this.refreshChart.apply(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
<div class="admin-controls">
|
||||
<nav>
|
||||
<ul class="nav nav-pills">
|
||||
{{yield}}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
@ -1,5 +1,4 @@
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
|
||||
@tagName("")
|
||||
export default class AdminNav extends Component {}
|
||||
export default Component.extend({
|
||||
tagName: "",
|
||||
});
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
<div
|
||||
class="suspended-count {{this.suspendedCountClass}}"
|
||||
title={{i18n "admin.user.last_six_months"}}
|
||||
>
|
||||
<label>{{i18n "admin.user.suspended_count"}}</label>
|
||||
<span>{{this.user.penalty_counts.suspended}}</span>
|
||||
</div>
|
||||
<div
|
||||
class="silenced-count {{this.silencedCountClass}}"
|
||||
title={{i18n "admin.user.last_six_months"}}
|
||||
>
|
||||
<label>{{i18n "admin.user.silenced_count"}}</label>
|
||||
<span>{{this.user.penalty_counts.silenced}}</span>
|
||||
</div>
|
||||
@ -1,16 +1,16 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
@classNames("penalty-history")
|
||||
export default class AdminPenaltyHistory extends Component {
|
||||
export default Component.extend({
|
||||
classNames: ["penalty-history"],
|
||||
|
||||
@discourseComputed("user.penalty_counts.suspended")
|
||||
suspendedCountClass(count) {
|
||||
if (count > 0) {
|
||||
return "danger";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("user.penalty_counts.silenced")
|
||||
silencedCountClass(count) {
|
||||
@ -18,5 +18,5 @@ export default class AdminPenaltyHistory extends Component {
|
||||
return "danger";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
<div class="penalty-post-controls">
|
||||
<label>
|
||||
<div class="penalty-post-label">
|
||||
{{html-safe (i18n "admin.user.penalty_post_actions")}}
|
||||
</div>
|
||||
</label>
|
||||
<ComboBox
|
||||
@value={{this.postAction}}
|
||||
@content={{this.penaltyActions}}
|
||||
@onChange={{action "penaltyChanged"}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{{#if this.editing}}
|
||||
<div class="penalty-post-edit">
|
||||
<Textarea @value={{this.postEdit}} class="post-editor" />
|
||||
</div>
|
||||
{{/if}}
|
||||
@ -1,41 +0,0 @@
|
||||
import { action } from "@ember/object";
|
||||
import { equal } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import discourseComputed, {
|
||||
afterRender,
|
||||
} from "discourse-common/utils/decorators";
|
||||
import I18n from "I18n";
|
||||
|
||||
const ACTIONS = ["delete", "delete_replies", "edit", "none"];
|
||||
|
||||
export default class AdminPenaltyPostAction extends Component {
|
||||
postId = null;
|
||||
postAction = null;
|
||||
postEdit = null;
|
||||
|
||||
@equal("postAction", "edit") editing;
|
||||
@discourseComputed
|
||||
penaltyActions() {
|
||||
return ACTIONS.map((id) => {
|
||||
return { id, name: I18n.t(`admin.user.penalty_post_${id}`) };
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
penaltyChanged(postAction) {
|
||||
this.set("postAction", postAction);
|
||||
|
||||
// If we switch to edit mode, jump to the edit textarea
|
||||
if (postAction === "edit") {
|
||||
this._focusEditTextarea();
|
||||
}
|
||||
}
|
||||
|
||||
@afterRender
|
||||
_focusEditTextarea() {
|
||||
const elem = this.element;
|
||||
const body = elem.closest(".modal-body");
|
||||
body.scrollTo(0, body.clientHeight);
|
||||
elem.querySelector(".post-editor").focus();
|
||||
}
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
<div class="penalty-reason-controls">
|
||||
{{#if (eq @penaltyType "suspend")}}
|
||||
<label class="suspend-reason-title">{{i18n
|
||||
"admin.user.suspend_reason_title"
|
||||
}}</label>
|
||||
<ComboBox
|
||||
@content={{this.reasons}}
|
||||
@value={{this.selectedReason}}
|
||||
@class="suspend-reason"
|
||||
@onChange={{this.setSelectedReason}}
|
||||
/>
|
||||
|
||||
{{#if this.isCustomReason}}
|
||||
<TextField
|
||||
@value={{this.customReason}}
|
||||
@class="suspend-reason"
|
||||
@onChange={{this.setCustomReason}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{else if (eq @penaltyType "silence")}}
|
||||
<label class="silence-reason-title">{{html-safe
|
||||
(i18n "admin.user.silence_reason_label")
|
||||
}}</label>
|
||||
<TextField
|
||||
@value={{this.customReason}}
|
||||
@class="silence-reason"
|
||||
@onChange={{this.setCustomReason}}
|
||||
@placeholderKey="admin.user.silence_reason_placeholder"
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="penalty-message-controls">
|
||||
<label>{{i18n "admin.user.suspend_message"}}</label>
|
||||
<Textarea
|
||||
@value={{this.message}}
|
||||
class="suspend-message"
|
||||
placeholder={{i18n "admin.user.suspend_message_placeholder"}}
|
||||
/>
|
||||
</div>
|
||||
@ -1,55 +0,0 @@
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import { equal } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import { action } from "@ember/object";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import I18n from "I18n";
|
||||
|
||||
const CUSTOM_REASON_KEY = "custom";
|
||||
|
||||
@tagName("")
|
||||
export default class AdminPenaltyReason extends Component {
|
||||
selectedReason = CUSTOM_REASON_KEY;
|
||||
customReason = "";
|
||||
|
||||
reasonKeys = [
|
||||
"not_listening_to_staff",
|
||||
"consuming_staff_time",
|
||||
"combative",
|
||||
"in_wrong_place",
|
||||
"no_constructive_purpose",
|
||||
CUSTOM_REASON_KEY,
|
||||
];
|
||||
|
||||
@equal("selectedReason", CUSTOM_REASON_KEY) isCustomReason;
|
||||
|
||||
@discourseComputed("reasonKeys")
|
||||
reasons(keys) {
|
||||
return keys.map((key) => {
|
||||
return { id: key, name: I18n.t(`admin.user.suspend_reasons.${key}`) };
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
setSelectedReason(value) {
|
||||
this.set("selectedReason", value);
|
||||
this.setReason();
|
||||
}
|
||||
|
||||
@action
|
||||
setCustomReason(value) {
|
||||
this.set("customReason", value);
|
||||
this.setReason();
|
||||
}
|
||||
|
||||
setReason() {
|
||||
if (this.isCustomReason) {
|
||||
this.set("reason", this.customReason);
|
||||
} else {
|
||||
this.set(
|
||||
"reason",
|
||||
I18n.t(`admin.user.suspend_reasons.${this.selectedReason}`)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
<div class="penalty-similar-users">
|
||||
<p class="alert alert-warning">
|
||||
{{html-safe
|
||||
(i18n
|
||||
"admin.user.other_matches"
|
||||
(hash count=this.user.similar_users_count username=this.user.username)
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{{i18n "username"}}</th>
|
||||
<th>{{i18n "last_seen"}}</th>
|
||||
<th>{{i18n "admin.user.topics_entered"}}</th>
|
||||
<th>{{i18n "admin.user.posts_read_count"}}</th>
|
||||
<th>{{i18n "admin.user.time_read"}}</th>
|
||||
<th>{{i18n "created"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{{#each this.user.similar_users as |user|}}
|
||||
<tr>
|
||||
<td>
|
||||
<Input
|
||||
@type="checkbox"
|
||||
disabled={{not (get user this.penaltyField)}}
|
||||
{{on "click" (action "selectUserId" user.id)}}
|
||||
/>
|
||||
</td>
|
||||
<td>{{avatar user imageSize="small"}} {{user.username}}</td>
|
||||
<td>{{format-duration user.last_seen_age}}</td>
|
||||
<td>{{number user.topics_entered}}</td>
|
||||
<td>{{number user.posts_read_count}}</td>
|
||||
<td>{{format-duration user.time_read}}</td>
|
||||
<td>{{format-duration user.created_at_age}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -1,29 +0,0 @@
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
import { action } from "@ember/object";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
@tagName("")
|
||||
export default class AdminPenaltySimilarUsers extends Component {
|
||||
@discourseComputed("penaltyType")
|
||||
penaltyField(penaltyType) {
|
||||
if (penaltyType === "suspend") {
|
||||
return "can_be_suspended";
|
||||
} else if (penaltyType === "silence") {
|
||||
return "can_be_silenced";
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
selectUserId(userId, event) {
|
||||
if (!this.selectedUserIds) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target.checked) {
|
||||
this.selectedUserIds.pushObject(userId);
|
||||
} else {
|
||||
this.selectedUserIds.removeObject(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
<div class="chart-canvas-container">
|
||||
<canvas class="chart-canvas"></canvas>
|
||||
</div>
|
||||
@ -1,4 +1,3 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import Report from "admin/models/report";
|
||||
import Component from "@ember/component";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
@ -8,31 +7,31 @@ import { number } from "discourse/lib/formatter";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
@classNames("admin-report-chart")
|
||||
export default class AdminReportChart extends Component {
|
||||
limit = 8;
|
||||
total = 0;
|
||||
options = null;
|
||||
export default Component.extend({
|
||||
classNames: ["admin-report-chart"],
|
||||
limit: 8,
|
||||
total: 0,
|
||||
options: null,
|
||||
|
||||
didInsertElement() {
|
||||
super.didInsertElement(...arguments);
|
||||
this._super(...arguments);
|
||||
|
||||
window.addEventListener("resize", this._resizeHandler);
|
||||
}
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
super.willDestroyElement(...arguments);
|
||||
this._super(...arguments);
|
||||
|
||||
window.removeEventListener("resize", this._resizeHandler);
|
||||
|
||||
this._resetChart();
|
||||
}
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
super.didReceiveAttrs(...arguments);
|
||||
this._super(...arguments);
|
||||
|
||||
discourseDebounce(this, this._scheduleChartRendering, 100);
|
||||
}
|
||||
},
|
||||
|
||||
_scheduleChartRendering() {
|
||||
schedule("afterRender", () => {
|
||||
@ -41,7 +40,7 @@ export default class AdminReportChart extends Component {
|
||||
this.element && this.element.querySelector(".chart-canvas")
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_renderChart(model, chartCanvas) {
|
||||
if (!chartCanvas) {
|
||||
@ -100,7 +99,7 @@ export default class AdminReportChart extends Component {
|
||||
this._buildChartConfig(data, this.options)
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_buildChartConfig(data, options) {
|
||||
return {
|
||||
@ -162,21 +161,21 @@ export default class AdminReportChart extends Component {
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
_resetChart() {
|
||||
if (this._chart) {
|
||||
this._chart.destroy();
|
||||
this._chart = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_applyChartGrouping(model, data, options) {
|
||||
return Report.collapse(model, data, options.chartGrouping);
|
||||
}
|
||||
},
|
||||
|
||||
@bind
|
||||
_resizeHandler() {
|
||||
discourseDebounce(this, this._scheduleChartRendering, 500);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
<div class="cell title">
|
||||
{{#if this.model.icon}}
|
||||
{{d-icon this.model.icon}}
|
||||
{{/if}}
|
||||
<a href={{this.model.reportUrl}}>{{this.model.title}}</a>
|
||||
</div>
|
||||
|
||||
<div class="cell value today-count">{{number this.model.todayCount}}</div>
|
||||
|
||||
<div
|
||||
class="cell value yesterday-count {{this.model.yesterdayTrend}}"
|
||||
title={{this.model.yesterdayCountTitle}}
|
||||
>
|
||||
{{number this.model.yesterdayCount}}
|
||||
{{d-icon this.model.yesterdayTrendIcon}}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="cell value sevendays-count {{this.model.sevenDaysTrend}}"
|
||||
title={{this.model.sevenDaysCountTitle}}
|
||||
>
|
||||
{{number this.model.lastSevenDaysCount}}
|
||||
{{d-icon this.model.sevenDaysTrendIcon}}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="cell value thirty-days-count {{this.model.thirtyDaysTrend}}"
|
||||
title={{this.model.thirtyDaysCountTitle}}
|
||||
>
|
||||
{{number this.model.lastThirtyDaysCount}}
|
||||
|
||||
{{#if this.model.canDisplayTrendIcon}}
|
||||
{{d-icon this.model.thirtyDaysTrendIcon}}
|
||||
{{/if}}
|
||||
</div>
|
||||
@ -1,6 +1,6 @@
|
||||
import { attributeBindings, classNames } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
classNames: ["admin-report-counters"],
|
||||
|
||||
@classNames("admin-report-counters")
|
||||
@attributeBindings("model.description:title")
|
||||
export default class AdminReportCounters extends Component {}
|
||||
attributeBindings: ["model.description:title"],
|
||||
});
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
<td class="title">
|
||||
{{#if this.report.icon}}
|
||||
{{d-icon this.report.icon}}
|
||||
{{/if}}
|
||||
<a href={{this.report.reportUrl}}>{{this.report.title}}</a>
|
||||
</td>
|
||||
|
||||
<td class="value">{{number this.report.todayCount}}</td>
|
||||
|
||||
<td
|
||||
class="value {{this.report.yesterdayTrend}}"
|
||||
title={{this.report.yesterdayCountTitle}}
|
||||
>
|
||||
{{number this.report.yesterdayCount}}
|
||||
{{d-icon this.report.yesterdayTrendIcon}}
|
||||
</td>
|
||||
|
||||
<td
|
||||
class="value {{this.report.sevenDaysTrend}}"
|
||||
title={{this.report.sevenDaysCountTitle}}
|
||||
>
|
||||
{{number this.report.lastSevenDaysCount}}
|
||||
{{d-icon this.report.sevenDaysTrendIcon}}
|
||||
</td>
|
||||
|
||||
<td
|
||||
class="value {{this.report.thirtyDaysTrend}}"
|
||||
title={{this.report.thirtyDaysCountTitle}}
|
||||
>
|
||||
{{number this.report.lastThirtyDaysCount}}
|
||||
{{d-icon this.report.thirtyDaysTrendIcon}}
|
||||
</td>
|
||||
|
||||
{{#if this.allTime}}
|
||||
<td class="value">{{number this.report.total}}</td>
|
||||
{{/if}}
|
||||
@ -1,12 +1,11 @@
|
||||
import { classNameBindings, tagName } from "@ember-decorators/component";
|
||||
import { match } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
|
||||
@tagName("tr")
|
||||
@classNameBindings("reverseColors")
|
||||
export default class AdminReportCounts extends Component {
|
||||
allTime = true;
|
||||
|
||||
@match("report.type", /^(time_to_first_response|topics_with_no_response)$/)
|
||||
reverseColors;
|
||||
}
|
||||
import { match } from "@ember/object/computed";
|
||||
export default Component.extend({
|
||||
allTime: true,
|
||||
tagName: "tr",
|
||||
reverseColors: match(
|
||||
"report.type",
|
||||
/^(time_to_first_response|topics_with_no_response)$/
|
||||
),
|
||||
classNameBindings: ["reverseColors"],
|
||||
});
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
<div class="table-container">
|
||||
{{#each this.model.data as |data|}}
|
||||
<a class="table-cell user-{{data.key}}" href={{data.url}}>
|
||||
<span class="label">
|
||||
{{#if data.icon}}
|
||||
{{d-icon data.icon}}
|
||||
{{/if}}
|
||||
{{data.x}}
|
||||
</span>
|
||||
<span class="value">
|
||||
{{number data.y}}
|
||||
</span>
|
||||
</a>
|
||||
{{/each}}
|
||||
</div>
|
||||
@ -1,5 +1,4 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
|
||||
@classNames("admin-report-inline-table")
|
||||
export default class AdminReportInlineTable extends Component {}
|
||||
export default Component.extend({
|
||||
classNames: ["admin-report-inline-table"],
|
||||
});
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
<td class="title"><a
|
||||
href={{this.report.reportUrl}}
|
||||
>{{this.report.title}}</a></td>
|
||||
<td class="value">{{this.report.todayCount}}</td>
|
||||
<td class="value">{{this.report.yesterdayCount}}</td>
|
||||
<td class="value">{{this.report.sevenDaysAgoCount}}</td>
|
||||
<td class="value">{{this.report.thirtyDaysAgoCount}}</td>
|
||||
<td class="value"></td>
|
||||
@ -1,5 +1,4 @@
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
|
||||
@tagName("tr")
|
||||
export default class AdminReportPerDayCounts extends Component {}
|
||||
export default Component.extend({
|
||||
tagName: "tr",
|
||||
});
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
<div class="chart-canvas-container">
|
||||
<canvas class="chart-canvas"></canvas>
|
||||
</div>
|
||||
@ -1,4 +1,3 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import Report from "admin/models/report";
|
||||
import Component from "@ember/component";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
@ -8,31 +7,32 @@ import { number } from "discourse/lib/formatter";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
@classNames("admin-report-chart", "admin-report-stacked-chart")
|
||||
export default class AdminReportStackedChart extends Component {
|
||||
export default Component.extend({
|
||||
classNames: ["admin-report-chart", "admin-report-stacked-chart"],
|
||||
|
||||
didInsertElement() {
|
||||
super.didInsertElement(...arguments);
|
||||
this._super(...arguments);
|
||||
|
||||
window.addEventListener("resize", this._resizeHandler);
|
||||
}
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
super.willDestroyElement(...arguments);
|
||||
this._super(...arguments);
|
||||
|
||||
window.removeEventListener("resize", this._resizeHandler);
|
||||
this._resetChart();
|
||||
}
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
super.didReceiveAttrs(...arguments);
|
||||
this._super(...arguments);
|
||||
|
||||
discourseDebounce(this, this._scheduleChartRendering, 100);
|
||||
}
|
||||
},
|
||||
|
||||
@bind
|
||||
_resizeHandler() {
|
||||
discourseDebounce(this, this._scheduleChartRendering, 500);
|
||||
}
|
||||
},
|
||||
|
||||
_scheduleChartRendering() {
|
||||
schedule("afterRender", () => {
|
||||
@ -45,7 +45,7 @@ export default class AdminReportStackedChart extends Component {
|
||||
this.element.querySelector(".chart-canvas")
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_renderChart(model, chartCanvas) {
|
||||
if (!chartCanvas) {
|
||||
@ -79,7 +79,7 @@ export default class AdminReportStackedChart extends Component {
|
||||
|
||||
this._chart = new window.Chart(context, this._buildChartConfig(data));
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_buildChartConfig(data) {
|
||||
return {
|
||||
@ -150,10 +150,10 @@ export default class AdminReportStackedChart extends Component {
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
_resetChart() {
|
||||
this._chart?.destroy();
|
||||
this._chart = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
{{#if this.showBackupStats}}
|
||||
<div class="backups">
|
||||
<h3 class="storage-stats-title">
|
||||
<a href={{get-url "/admin/backups"}}>{{d-icon "archive"}}
|
||||
{{i18n "admin.dashboard.backups"}}</a>
|
||||
</h3>
|
||||
<p>
|
||||
{{#if this.backupStats.free_bytes}}
|
||||
{{i18n
|
||||
"admin.dashboard.space_used_and_free"
|
||||
usedSize=this.usedBackupSpace
|
||||
freeSize=this.freeBackupSpace
|
||||
}}
|
||||
{{else}}
|
||||
{{i18n "admin.dashboard.space_used" usedSize=this.usedBackupSpace}}
|
||||
{{/if}}
|
||||
|
||||
<br />
|
||||
{{i18n
|
||||
"admin.dashboard.backup_count"
|
||||
count=this.backupStats.count
|
||||
location=this.backupLocationName
|
||||
}}
|
||||
|
||||
{{#if this.backupStats.last_backup_taken_at}}
|
||||
<br />
|
||||
{{html-safe
|
||||
(i18n
|
||||
"admin.dashboard.lastest_backup"
|
||||
date=(format-date
|
||||
this.backupStats.last_backup_taken_at leaveAgo="true"
|
||||
)
|
||||
)
|
||||
}}
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="uploads">
|
||||
<h3 class="storage-stats-title">{{d-icon "upload"}}
|
||||
{{i18n "admin.dashboard.uploads"}}</h3>
|
||||
<p>
|
||||
{{#if this.uploadStats.free_bytes}}
|
||||
{{i18n
|
||||
"admin.dashboard.space_used_and_free"
|
||||
usedSize=this.usedUploadSpace
|
||||
freeSize=this.freeUploadSpace
|
||||
}}
|
||||
{{else}}
|
||||
{{i18n "admin.dashboard.space_used" usedSize=this.usedUploadSpace}}
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
@ -1,45 +1,43 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { setting } from "discourse/lib/computed";
|
||||
|
||||
@classNames("admin-report-storage-stats")
|
||||
export default class AdminReportStorageStats extends Component {
|
||||
@setting("backup_location") backupLocation;
|
||||
export default Component.extend({
|
||||
classNames: ["admin-report-storage-stats"],
|
||||
|
||||
@alias("model.data.backups") backupStats;
|
||||
|
||||
@alias("model.data.uploads") uploadStats;
|
||||
backupLocation: setting("backup_location"),
|
||||
backupStats: alias("model.data.backups"),
|
||||
uploadStats: alias("model.data.uploads"),
|
||||
|
||||
@discourseComputed("backupStats")
|
||||
showBackupStats(stats) {
|
||||
return stats && this.currentUser.admin;
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("backupLocation")
|
||||
backupLocationName(backupLocation) {
|
||||
return I18n.t(`admin.backups.location.${backupLocation}`);
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("backupStats.used_bytes")
|
||||
usedBackupSpace(bytes) {
|
||||
return I18n.toHumanSize(bytes);
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("backupStats.free_bytes")
|
||||
freeBackupSpace(bytes) {
|
||||
return I18n.toHumanSize(bytes);
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("uploadStats.used_bytes")
|
||||
usedUploadSpace(bytes) {
|
||||
return I18n.toHumanSize(bytes);
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("uploadStats.free_bytes")
|
||||
freeUploadSpace(bytes) {
|
||||
return I18n.toHumanSize(bytes);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1 +0,0 @@
|
||||
{{html-safe this.formattedValue}}
|
||||
@ -1,27 +1,21 @@
|
||||
import {
|
||||
attributeBindings,
|
||||
classNameBindings,
|
||||
classNames,
|
||||
tagName,
|
||||
} from "@ember-decorators/component";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
@tagName("td")
|
||||
@classNames("admin-report-table-cell")
|
||||
@classNameBindings("type", "property")
|
||||
@attributeBindings("value:title")
|
||||
export default class AdminReportTableCell extends Component {
|
||||
options = null;
|
||||
|
||||
@alias("label.type") type;
|
||||
@alias("label.mainProperty") property;
|
||||
@alias("computedLabel.formattedValue") formattedValue;
|
||||
@alias("computedLabel.value") value;
|
||||
export default Component.extend({
|
||||
tagName: "td",
|
||||
classNames: ["admin-report-table-cell"],
|
||||
classNameBindings: ["type", "property"],
|
||||
attributeBindings: ["value:title"],
|
||||
options: null,
|
||||
|
||||
@discourseComputed("label", "data", "options")
|
||||
computedLabel(label, data, options) {
|
||||
return label.compute(data, options || {});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
type: alias("label.type"),
|
||||
property: alias("label.mainProperty"),
|
||||
formattedValue: alias("computedLabel.formattedValue"),
|
||||
value: alias("computedLabel.value"),
|
||||
});
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
{{#if this.showSortingUI}}
|
||||
<DButton
|
||||
@action={{this.sortByLabel}}
|
||||
@icon={{this.sortIcon}}
|
||||
@class="sort-btn"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.label.htmlTitle}}
|
||||
<span class="title">{{html-safe this.label.htmlTitle}}</span>
|
||||
{{else}}
|
||||
<span class="title">{{this.label.title}}</span>
|
||||
{{/if}}
|
||||
@ -1,24 +1,19 @@
|
||||
import {
|
||||
attributeBindings,
|
||||
classNameBindings,
|
||||
classNames,
|
||||
tagName,
|
||||
} from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
@tagName("th")
|
||||
@classNames("admin-report-table-header")
|
||||
@classNameBindings("label.mainProperty", "label.type", "isCurrentSort")
|
||||
@attributeBindings("label.title:title")
|
||||
export default class AdminReportTableHeader extends Component {
|
||||
export default Component.extend({
|
||||
tagName: "th",
|
||||
classNames: ["admin-report-table-header"],
|
||||
classNameBindings: ["label.mainProperty", "label.type", "isCurrentSort"],
|
||||
attributeBindings: ["label.title:title"],
|
||||
|
||||
@discourseComputed("currentSortLabel.sortProperty", "label.sortProperty")
|
||||
isCurrentSort(currentSortField, labelSortField) {
|
||||
return currentSortField === labelSortField;
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("currentSortDirection")
|
||||
sortIcon(currentSortDirection) {
|
||||
return currentSortDirection === 1 ? "caret-up" : "caret-down";
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
{{#each this.labels as |label|}}
|
||||
<AdminReportTableCell
|
||||
@label={{label}}
|
||||
@data={{this.data}}
|
||||
@options={{this.options}}
|
||||
/>
|
||||
{{/each}}
|
||||
@ -1,8 +1,6 @@
|
||||
import { classNames, tagName } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
|
||||
@tagName("tr")
|
||||
@classNames("admin-report-table-row")
|
||||
export default class AdminReportTableRow extends Component {
|
||||
options = null;
|
||||
}
|
||||
export default Component.extend({
|
||||
tagName: "tr",
|
||||
classNames: ["admin-report-table-row"],
|
||||
options: null,
|
||||
});
|
||||
|
||||
@ -1,84 +0,0 @@
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
{{#if this.model.computedLabels}}
|
||||
{{#each this.model.computedLabels as |label|}}
|
||||
<AdminReportTableHeader
|
||||
@showSortingUI={{this.showSortingUI}}
|
||||
@currentSortDirection={{this.sortDirection}}
|
||||
@currentSortLabel={{this.sortLabel}}
|
||||
@label={{label}}
|
||||
@sortByLabel={{action "sortByLabel" label}}
|
||||
/>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
{{#each this.model.data as |data|}}
|
||||
<th>{{data.x}}</th>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each this.paginatedData as |data|}}
|
||||
<AdminReportTableRow
|
||||
@data={{data}}
|
||||
@labels={{this.model.computedLabels}}
|
||||
@options={{this.options}}
|
||||
/>
|
||||
{{/each}}
|
||||
|
||||
{{#if this.showTotalForSample}}
|
||||
<tr class="total-row">
|
||||
<td colspan={{this.totalsForSample.length}}>
|
||||
{{i18n "admin.dashboard.reports.totals_for_sample"}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="admin-report-table-row">
|
||||
{{#each this.totalsForSample as |total|}}
|
||||
<td class="admin-report-table-cell {{total.type}} {{total.property}}">
|
||||
{{total.formattedValue}}
|
||||
</td>
|
||||
{{/each}}
|
||||
</tr>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showTotal}}
|
||||
<tr class="total-row">
|
||||
<td colspan="2">
|
||||
{{i18n "admin.dashboard.reports.total"}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="admin-report-table-row">
|
||||
<td class="admin-report-table-cell date x">—</td>
|
||||
<td class="admin-report-table-cell number y">{{number
|
||||
this.model.total
|
||||
}}</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showAverage}}
|
||||
<tr class="total-row">
|
||||
<td colspan="2">
|
||||
{{i18n "admin.dashboard.reports.average_for_sample"}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="admin-report-table-row">
|
||||
<td class="admin-report-table-cell date x">—</td>
|
||||
<td class="admin-report-table-cell number y">{{number
|
||||
this.averageForSample
|
||||
}}</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="pagination">
|
||||
{{#each this.pages as |pageState|}}
|
||||
<DButton
|
||||
@translatedLabel={{pageState.page}}
|
||||
@action={{action "changePage"}}
|
||||
@actionParam={{pageState.index}}
|
||||
@class={{pageState.class}}
|
||||
/>
|
||||
{{/each}}
|
||||
</div>
|
||||
@ -1,26 +1,22 @@
|
||||
import { action } from "@ember/object";
|
||||
import { classNameBindings, classNames } from "@ember-decorators/component";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { makeArray } from "discourse-common/lib/helpers";
|
||||
|
||||
const PAGES_LIMIT = 8;
|
||||
|
||||
@classNameBindings("sortable", "twoColumns")
|
||||
@classNames("admin-report-table")
|
||||
export default class AdminReportTable extends Component {
|
||||
sortable = false;
|
||||
sortDirection = 1;
|
||||
|
||||
@alias("options.perPage") perPage;
|
||||
|
||||
page = 0;
|
||||
export default Component.extend({
|
||||
classNameBindings: ["sortable", "twoColumns"],
|
||||
classNames: ["admin-report-table"],
|
||||
sortable: false,
|
||||
sortDirection: 1,
|
||||
perPage: alias("options.perPage"),
|
||||
page: 0,
|
||||
|
||||
@discourseComputed("model.computedLabels.length")
|
||||
twoColumns(labelsLength) {
|
||||
return labelsLength === 2;
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed(
|
||||
"totalsForSample",
|
||||
@ -35,12 +31,12 @@ export default class AdminReportTable extends Component {
|
||||
.reduce((s, v) => s + v, 0);
|
||||
|
||||
return sum >= 1 && total && datesFiltering;
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("model.total", "options.total", "twoColumns")
|
||||
showTotal(reportTotal, total, twoColumns) {
|
||||
return reportTotal && total && twoColumns;
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed(
|
||||
"model.{average,data}",
|
||||
@ -54,17 +50,17 @@ export default class AdminReportTable extends Component {
|
||||
sampleTotalValue &&
|
||||
hasTwoColumns
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("totalsForSample.1.value", "model.data.length")
|
||||
averageForSample(totals, count) {
|
||||
return (totals / count).toFixed(0);
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("model.data.length")
|
||||
showSortingUI(dataLength) {
|
||||
return dataLength >= 5;
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("totalsForSampleRow", "model.computedLabels")
|
||||
totalsForSample(row, labels) {
|
||||
@ -74,7 +70,7 @@ export default class AdminReportTable extends Component {
|
||||
computedLabel.property = label.mainProperty;
|
||||
return computedLabel;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("model.data", "model.computedLabels")
|
||||
totalsForSampleRow(rows, labels) {
|
||||
@ -102,7 +98,7 @@ export default class AdminReportTable extends Component {
|
||||
});
|
||||
|
||||
return totalsRow;
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("sortLabel", "sortDirection", "model.data.[]")
|
||||
sortedData(sortLabel, sortDirection, data) {
|
||||
@ -122,7 +118,7 @@ export default class AdminReportTable extends Component {
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("sortedData.[]", "perPage", "page")
|
||||
paginatedData(data, perPage, page) {
|
||||
@ -132,7 +128,7 @@ export default class AdminReportTable extends Component {
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("model.data", "perPage", "page")
|
||||
pages(data, perPage, page) {
|
||||
@ -160,19 +156,19 @@ export default class AdminReportTable extends Component {
|
||||
}
|
||||
|
||||
return pages;
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
changePage(page) {
|
||||
this.set("page", page);
|
||||
}
|
||||
actions: {
|
||||
changePage(page) {
|
||||
this.set("page", page);
|
||||
},
|
||||
|
||||
@action
|
||||
sortByLabel(label) {
|
||||
if (this.sortLabel === label) {
|
||||
this.set("sortDirection", this.sortDirection === 1 ? -1 : 1);
|
||||
} else {
|
||||
this.set("sortLabel", label);
|
||||
}
|
||||
}
|
||||
}
|
||||
sortByLabel(label) {
|
||||
if (this.sortLabel === label) {
|
||||
this.set("sortDirection", this.sortDirection === 1 ? -1 : 1);
|
||||
} else {
|
||||
this.set("sortLabel", label);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
<td class="title">{{this.report.title}}</td>
|
||||
<td class="value">
|
||||
<LinkTo @route="adminUsersList.show" @model="newuser">
|
||||
{{number (value-at-tl this.report.data level="0")}}
|
||||
</LinkTo>
|
||||
</td>
|
||||
<td class="value">
|
||||
<LinkTo @route="adminUsersList.show" @model="basic">
|
||||
{{number (value-at-tl this.report.data level="1")}}
|
||||
</LinkTo>
|
||||
</td>
|
||||
<td class="value">
|
||||
<LinkTo @route="adminUsersList.show" @model="member">
|
||||
{{number (value-at-tl this.report.data level="2")}}
|
||||
</LinkTo>
|
||||
</td>
|
||||
<td class="value">
|
||||
<LinkTo @route="adminUsersList.show" @model="regular">
|
||||
{{number (value-at-tl this.report.data level="3")}}
|
||||
</LinkTo>
|
||||
</td>
|
||||
<td class="value">
|
||||
<LinkTo @route="adminUsersList.show" @model="leader">
|
||||
{{number (value-at-tl this.report.data level="4")}}
|
||||
</LinkTo>
|
||||
</td>
|
||||
@ -1,5 +1,4 @@
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
|
||||
@tagName("tr")
|
||||
export default class AdminReportTrustLevelCounts extends Component {}
|
||||
export default Component.extend({
|
||||
tagName: "tr",
|
||||
});
|
||||
|
||||
@ -1,244 +0,0 @@
|
||||
{{#unless this.isHidden}}
|
||||
{{#if this.isEnabled}}
|
||||
<ConditionalLoadingSection @isLoading={{this.isLoading}}>
|
||||
{{#if this.showHeader}}
|
||||
<div class="header">
|
||||
{{#if this.showTitle}}
|
||||
<ul class="breadcrumb">
|
||||
{{#if this.showAllReportsLink}}
|
||||
<li class="item all-reports">
|
||||
<LinkTo @route="admin.dashboardReports" class="report-url">
|
||||
{{i18n "admin.dashboard.all_reports"}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
|
||||
{{#unless this.showNotFoundError}}
|
||||
<li class="item separator">|</li>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
|
||||
{{#unless this.showNotFoundError}}
|
||||
<li class="item report">
|
||||
<a href={{this.model.reportUrl}} class="report-url">
|
||||
{{this.model.title}}
|
||||
</a>
|
||||
|
||||
{{#if this.model.description}}
|
||||
{{#if this.model.description_link}}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={{this.model.description_link}}
|
||||
class="info"
|
||||
data-tooltip={{this.model.description}}
|
||||
>
|
||||
{{d-icon "question-circle"}}
|
||||
</a>
|
||||
{{else}}
|
||||
<span
|
||||
class="info"
|
||||
data-tooltip={{this.model.description}}
|
||||
>
|
||||
{{d-icon "question-circle"}}
|
||||
</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/unless}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.shouldDisplayTrend}}
|
||||
<div class="trend {{this.model.trend}}">
|
||||
<span class="value" title={{this.model.trendTitle}}>
|
||||
{{#if this.model.average}}
|
||||
{{number this.model.currentAverage}}{{#if
|
||||
this.model.percent
|
||||
}}%{{/if}}
|
||||
{{else}}
|
||||
{{number this.model.currentTotal noTitle="true"}}{{#if
|
||||
this.model.percent
|
||||
}}%{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if this.model.trendIcon}}
|
||||
{{d-icon this.model.trendIcon class="icon"}}
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="body">
|
||||
<div class="main">
|
||||
{{#if this.showError}}
|
||||
{{#if this.showTimeoutError}}
|
||||
<div class="alert alert-error report-alert timeout">
|
||||
{{d-icon "exclamation-triangle"}}
|
||||
<span>{{i18n "admin.dashboard.timeout_error"}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showExceptionError}}
|
||||
<div class="alert alert-error report-alert exception">
|
||||
{{d-icon "exclamation-triangle"}}
|
||||
<span>{{i18n "admin.dashboard.exception_error"}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showNotFoundError}}
|
||||
<div class="alert alert-error report-alert not-found">
|
||||
{{d-icon "exclamation-triangle"}}
|
||||
<span>{{i18n "admin.dashboard.not_found_error"}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{#if this.hasData}}
|
||||
{{#if this.currentMode}}
|
||||
{{component
|
||||
this.modeComponent
|
||||
model=this.model
|
||||
options=this.options
|
||||
}}
|
||||
|
||||
{{#if this.model.relatedReport}}
|
||||
<AdminReport
|
||||
@showFilteringUI={{false}}
|
||||
@dataSourceName={{this.model.relatedReport.type}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{#if this.rateLimitationString}}
|
||||
<div class="alert alert-error report-alert rate-limited">
|
||||
{{d-icon "thermometer-three-quarters"}}
|
||||
<span>{{this.rateLimitationString}}</span>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="alert alert-info report-alert no-data">
|
||||
{{d-icon "chart-pie"}}
|
||||
{{#if this.model.reportUrl}}
|
||||
<a href={{this.model.reportUrl}} class="report-url">
|
||||
<span>
|
||||
{{#if this.model.title}}
|
||||
{{this.model.title}}
|
||||
—
|
||||
{{/if}}
|
||||
{{i18n "admin.dashboard.reports.no_data"}}
|
||||
</span>
|
||||
</a>
|
||||
{{else}}
|
||||
<span>{{i18n "admin.dashboard.reports.no_data"}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if this.showFilteringUI}}
|
||||
<div class="filters">
|
||||
{{#if this.showModes}}
|
||||
<div class="modes">
|
||||
{{#each this.displayedModes as |displayedMode|}}
|
||||
<DButton
|
||||
@action={{action "onChangeMode"}}
|
||||
@actionParam={{displayedMode.mode}}
|
||||
@class={{displayedMode.cssClass}}
|
||||
@icon={{displayedMode.icon}}
|
||||
/>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.isChartMode}}
|
||||
{{#if this.model.average}}
|
||||
<span class="average-chart">
|
||||
{{i18n "admin.dashboard.reports.average_chart_label"}}
|
||||
</span>
|
||||
{{/if}}
|
||||
<div class="chart-groupings">
|
||||
{{#each this.chartGroupings as |chartGrouping|}}
|
||||
<DButton
|
||||
@label={{chartGrouping.label}}
|
||||
@action={{action "changeGrouping" chartGrouping.id}}
|
||||
@class={{chartGrouping.class}}
|
||||
@disabled={{chartGrouping.disabled}}
|
||||
/>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showDatesOptions}}
|
||||
<div class="control">
|
||||
<span class="label">
|
||||
{{i18n "admin.dashboard.reports.dates"}}
|
||||
</span>
|
||||
|
||||
<div class="input">
|
||||
<DateTimeInputRange
|
||||
@from={{this.startDate}}
|
||||
@to={{this.endDate}}
|
||||
@onChange={{action "onChangeDateRange"}}
|
||||
@showFromTime={{false}}
|
||||
@showToTime={{false}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#each this.model.available_filters as |filter|}}
|
||||
<div class="control">
|
||||
<span class="label">
|
||||
{{i18n
|
||||
(concat
|
||||
"admin.dashboard.reports.filters." filter.id ".label"
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
|
||||
<div class="input">
|
||||
{{component
|
||||
(concat "report-filters/" filter.type)
|
||||
model=this.model
|
||||
filter=filter
|
||||
applyFilter=(action "applyFilter")
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
|
||||
<div class="control">
|
||||
<div class="input">
|
||||
<DButton
|
||||
@class="btn-default export-csv-btn"
|
||||
@action={{action "exportCsv"}}
|
||||
@label="admin.export_csv.button_text"
|
||||
@icon="download"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if this.showRefresh}}
|
||||
<div class="control">
|
||||
<div class="input">
|
||||
<DButton
|
||||
@class="refresh-report-btn btn-primary"
|
||||
@action={{action "refreshReport"}}
|
||||
@label="admin.dashboard.reports.refresh_report"
|
||||
@icon="sync"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</ConditionalLoadingSection>
|
||||
{{else}}
|
||||
<div class="alert alert-info">
|
||||
{{html-safe this.disabledLabel}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
@ -1,7 +1,6 @@
|
||||
import { classNameBindings, classNames } from "@ember-decorators/component";
|
||||
import { alias, and, equal, notEmpty, or } from "@ember/object/computed";
|
||||
import EmberObject, { action, computed } from "@ember/object";
|
||||
import Report, { DAILY_LIMIT_DAYS, SCHEMA_VERSION } from "admin/models/report";
|
||||
import { alias, and, equal, notEmpty, or } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import ReportLoader from "discourse/lib/reports-loader";
|
||||
@ -22,58 +21,51 @@ const TABLE_OPTIONS = {
|
||||
|
||||
const CHART_OPTIONS = {};
|
||||
|
||||
@classNameBindings(
|
||||
"isHidden:hidden",
|
||||
"isHidden::is-visible",
|
||||
"isEnabled",
|
||||
"isLoading",
|
||||
"dasherizedDataSourceName"
|
||||
)
|
||||
@classNames("admin-report")
|
||||
export default class AdminReport extends Component {
|
||||
isEnabled = true;
|
||||
disabledLabel = I18n.t("admin.dashboard.disabled");
|
||||
isLoading = false;
|
||||
rateLimitationString = null;
|
||||
dataSourceName = null;
|
||||
report = null;
|
||||
model = null;
|
||||
reportOptions = null;
|
||||
forcedModes = null;
|
||||
showAllReportsLink = false;
|
||||
filters = null;
|
||||
showTrend = false;
|
||||
showHeader = true;
|
||||
showTitle = true;
|
||||
showFilteringUI = false;
|
||||
export default Component.extend({
|
||||
classNameBindings: [
|
||||
"isHidden:hidden",
|
||||
"isHidden::is-visible",
|
||||
"isEnabled",
|
||||
"isLoading",
|
||||
"dasherizedDataSourceName",
|
||||
],
|
||||
classNames: ["admin-report"],
|
||||
isEnabled: true,
|
||||
disabledLabel: I18n.t("admin.dashboard.disabled"),
|
||||
isLoading: false,
|
||||
rateLimitationString: null,
|
||||
dataSourceName: null,
|
||||
report: null,
|
||||
model: null,
|
||||
reportOptions: null,
|
||||
forcedModes: null,
|
||||
showAllReportsLink: false,
|
||||
filters: null,
|
||||
showTrend: false,
|
||||
showHeader: true,
|
||||
showTitle: true,
|
||||
showFilteringUI: false,
|
||||
showDatesOptions: alias("model.dates_filtering"),
|
||||
showRefresh: or("showDatesOptions", "model.available_filters.length"),
|
||||
shouldDisplayTrend: and("showTrend", "model.prev_period"),
|
||||
endDate: null,
|
||||
startDate: null,
|
||||
|
||||
@alias("model.dates_filtering") showDatesOptions;
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
@or("showDatesOptions", "model.available_filters.length") showRefresh;
|
||||
this._reports = [];
|
||||
},
|
||||
|
||||
@and("showTrend", "model.prev_period") shouldDisplayTrend;
|
||||
|
||||
endDate = null;
|
||||
startDate = null;
|
||||
|
||||
@or("showTimeoutError", "showExceptionError", "showNotFoundError") showError;
|
||||
@equal("model.error", "not_found") showNotFoundError;
|
||||
@equal("model.error", "timeout") showTimeoutError;
|
||||
@equal("model.error", "exception") showExceptionError;
|
||||
@notEmpty("model.data") hasData;
|
||||
|
||||
_reports = [];
|
||||
|
||||
@computed("siteSettings.dashboard_hidden_reports")
|
||||
get isHidden() {
|
||||
isHidden: computed("siteSettings.dashboard_hidden_reports", function () {
|
||||
return (this.siteSettings.dashboard_hidden_reports || "")
|
||||
.split("|")
|
||||
.filter(Boolean)
|
||||
.includes(this.dataSourceName);
|
||||
}
|
||||
}),
|
||||
|
||||
didReceiveAttrs() {
|
||||
super.didReceiveAttrs(...arguments);
|
||||
this._super(...arguments);
|
||||
|
||||
let startDate = moment();
|
||||
if (this.filters && isPresent(this.filters.startDate)) {
|
||||
@ -96,35 +88,42 @@ export default class AdminReport extends Component {
|
||||
} else if (this.dataSourceName) {
|
||||
this._fetchReport();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
showError: or("showTimeoutError", "showExceptionError", "showNotFoundError"),
|
||||
showNotFoundError: equal("model.error", "not_found"),
|
||||
showTimeoutError: equal("model.error", "timeout"),
|
||||
showExceptionError: equal("model.error", "exception"),
|
||||
|
||||
hasData: notEmpty("model.data"),
|
||||
|
||||
@discourseComputed("dataSourceName", "model.type")
|
||||
dasherizedDataSourceName(dataSourceName, type) {
|
||||
return (dataSourceName || type || "undefined").replace(/_/g, "-");
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("dataSourceName", "model.type")
|
||||
dataSource(dataSourceName, type) {
|
||||
dataSourceName = dataSourceName || type;
|
||||
return `/admin/reports/${dataSourceName}`;
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("displayedModes.length")
|
||||
showModes(displayedModesLength) {
|
||||
return displayedModesLength > 1;
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("currentMode")
|
||||
isChartMode(currentMode) {
|
||||
return currentMode === "chart";
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
changeGrouping(grouping) {
|
||||
this.send("refreshReport", {
|
||||
chartGrouping: grouping,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("currentMode", "model.modes", "forcedModes")
|
||||
displayedModes(currentMode, reportModes, forcedModes) {
|
||||
@ -140,12 +139,12 @@ export default class AdminReport extends Component {
|
||||
icon: mode === "table" ? "table" : "signal",
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("currentMode")
|
||||
modeComponent(currentMode) {
|
||||
return `admin-report-${currentMode.replace(/_/g, "-")}`;
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed(
|
||||
"dataSourceName",
|
||||
@ -179,7 +178,7 @@ export default class AdminReport extends Component {
|
||||
.join(":");
|
||||
|
||||
return reportKey;
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("options.chartGrouping", "model.chartData.length")
|
||||
chartGroupings(grouping, count) {
|
||||
@ -193,7 +192,7 @@ export default class AdminReport extends Component {
|
||||
class: `chart-grouping ${grouping === id ? "active" : "inactive"}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
onChangeDateRange(range) {
|
||||
@ -201,7 +200,7 @@ export default class AdminReport extends Component {
|
||||
startDate: range.from,
|
||||
endDate: range.to,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
applyFilter(id, value) {
|
||||
@ -216,7 +215,7 @@ export default class AdminReport extends Component {
|
||||
this.send("refreshReport", {
|
||||
filters: customFilters,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
refreshReport(options = {}) {
|
||||
@ -239,7 +238,7 @@ export default class AdminReport extends Component {
|
||||
? this.get("filters.customFilters")
|
||||
: options.filters,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
exportCsv() {
|
||||
@ -255,7 +254,7 @@ export default class AdminReport extends Component {
|
||||
}
|
||||
|
||||
exportEntity("report", args).then(outputExportResult);
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
onChangeMode(mode) {
|
||||
@ -264,7 +263,7 @@ export default class AdminReport extends Component {
|
||||
this.send("refreshReport", {
|
||||
chartGrouping: null,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_computeReport() {
|
||||
if (!this.element || this.isDestroying || this.isDestroyed) {
|
||||
@ -307,7 +306,7 @@ export default class AdminReport extends Component {
|
||||
}
|
||||
|
||||
this._renderReport(report, this.forcedModes, this.currentMode);
|
||||
}
|
||||
},
|
||||
|
||||
_renderReport(report, forcedModes, currentMode) {
|
||||
const modes = forcedModes ? forcedModes.split(",") : report.modes;
|
||||
@ -318,9 +317,11 @@ export default class AdminReport extends Component {
|
||||
currentMode,
|
||||
options: this._buildOptions(currentMode, report),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_fetchReport() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.setProperties({ isLoading: true, rateLimitationString: null });
|
||||
|
||||
next(() => {
|
||||
@ -348,7 +349,7 @@ export default class AdminReport extends Component {
|
||||
|
||||
ReportLoader.enqueue(this.dataSourceName, payload.data, callback);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_buildPayload(facets) {
|
||||
let payload = { data: { facets } };
|
||||
@ -374,7 +375,7 @@ export default class AdminReport extends Component {
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
},
|
||||
|
||||
_buildOptions(mode, report) {
|
||||
if (mode === "table") {
|
||||
@ -392,7 +393,7 @@ export default class AdminReport extends Component {
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_loadReport(jsonReport) {
|
||||
Report.fillMissingDates(jsonReport, { filledField: "chartData" });
|
||||
@ -422,5 +423,5 @@ export default class AdminReport extends Component {
|
||||
}
|
||||
|
||||
return Report.create(jsonReport);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,130 +0,0 @@
|
||||
<div class="edit-main-nav admin-controls">
|
||||
<nav>
|
||||
<ul class="nav nav-pills target">
|
||||
{{#each this.visibleTargets as |target|}}
|
||||
<li>
|
||||
<LinkTo
|
||||
@route={{this.editRouteName}}
|
||||
@models={{array this.theme.id target.name this.fieldName}}
|
||||
@replace={{true}}
|
||||
title={{this.field.title}}
|
||||
class={{if target.edited "edited" "blank"}}
|
||||
>
|
||||
{{#if target.error}}{{d-icon "exclamation-triangle"}}{{/if}}
|
||||
{{#if target.icon}}{{d-icon target.icon}}{{/if}}
|
||||
{{i18n (concat "admin.customize.theme." target.name)}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/each}}
|
||||
|
||||
{{#if this.allowAdvanced}}
|
||||
<li>
|
||||
<a
|
||||
{{on "click" this.toggleShowAdvanced}}
|
||||
href
|
||||
title={{i18n
|
||||
(concat
|
||||
"admin.customize.theme."
|
||||
(if this.showAdvanced "hide_advanced" "show_advanced")
|
||||
)
|
||||
}}
|
||||
class="no-text"
|
||||
>
|
||||
{{d-icon
|
||||
(if this.showAdvanced "angle-double-left" "angle-double-right")
|
||||
}}
|
||||
</a>
|
||||
</li>
|
||||
{{/if}}
|
||||
<li class="spacer"></li>
|
||||
<li>
|
||||
<label>
|
||||
<Input
|
||||
@type="checkbox"
|
||||
@checked={{this.onlyOverridden}}
|
||||
{{on
|
||||
"click"
|
||||
(action this.onlyOverriddenChanged value="target.checked")
|
||||
}}
|
||||
/>
|
||||
{{i18n "admin.customize.theme.hide_unused_fields"}}
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="admin-controls">
|
||||
<nav>
|
||||
<ul class="nav nav-pills fields">
|
||||
{{#each this.visibleFields as |field|}}
|
||||
<li>
|
||||
<LinkTo
|
||||
@route={{this.editRouteName}}
|
||||
@models={{array this.theme.id this.currentTargetName field.name}}
|
||||
@replace={{true}}
|
||||
title={{field.title}}
|
||||
class={{if field.edited "edited" "blank"}}
|
||||
>
|
||||
{{#if field.error}}{{d-icon "exclamation-triangle"}}{{/if}}
|
||||
{{#if field.icon}}{{d-icon field.icon}}{{/if}}
|
||||
{{field.translatedName}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/each}}
|
||||
|
||||
{{#if this.showAddField}}
|
||||
<li>
|
||||
{{#if this.addingField}}
|
||||
<Input
|
||||
@type={{this.text}}
|
||||
@value={{this.newFieldName}}
|
||||
@enter={{action "addField"}}
|
||||
@escape-press={{action "cancelAddField"}}
|
||||
/>
|
||||
<DButton
|
||||
@class="ok"
|
||||
@action={{action "addField" this.newFieldName}}
|
||||
@icon="check"
|
||||
/>
|
||||
<DButton
|
||||
@class="cancel"
|
||||
@action={{action "cancelAddField"}}
|
||||
@icon="times"
|
||||
/>
|
||||
{{else}}
|
||||
<a href {{on "click" this.toggleAddField}} class="no-text">
|
||||
{{d-icon "plus"}}
|
||||
</a>
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
<li class="spacer"></li>
|
||||
<li>
|
||||
<a href {{on "click" this.toggleMaximize}} class="no-text">
|
||||
{{d-icon this.maximizeIcon}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{{#if this.error}}
|
||||
<pre class="field-error">{{this.error}}</pre>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.warning}}
|
||||
<pre class="field-warning">{{html-safe this.warning}}</pre>
|
||||
{{/if}}
|
||||
|
||||
<AceEditor
|
||||
@content={{this.activeSection}}
|
||||
@editorId={{this.editorId}}
|
||||
@mode={{this.activeSectionMode}}
|
||||
@autofocus="true"
|
||||
@placeholder={{this.placeholder}}
|
||||
@htmlPlaceholder={{true}}
|
||||
@save={{this.save}}
|
||||
@setWarning={{action "setWarning"}}
|
||||
/>
|
||||
@ -3,13 +3,10 @@ import I18n from "I18n";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { fmt } from "discourse/lib/computed";
|
||||
import { isDocumentRTL } from "discourse/lib/text-direction";
|
||||
import { action, computed } from "@ember/object";
|
||||
import { next } from "@ember/runloop";
|
||||
|
||||
export default class AdminThemeEditor extends Component {
|
||||
warning = null;
|
||||
|
||||
@fmt("fieldName", "currentTargetName", "%@|%@") editorId;
|
||||
export default Component.extend({
|
||||
warning: null,
|
||||
|
||||
@discourseComputed("theme.targets", "onlyOverridden", "showAdvanced")
|
||||
visibleTargets(targets, onlyOverridden, showAdvanced) {
|
||||
@ -22,7 +19,7 @@ export default class AdminThemeEditor extends Component {
|
||||
}
|
||||
return target.edited;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("currentTargetName", "onlyOverridden", "theme.fields")
|
||||
visibleFields(targetName, onlyOverridden, fields) {
|
||||
@ -31,7 +28,7 @@ export default class AdminThemeEditor extends Component {
|
||||
fields = fields.filter((field) => field.edited);
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("currentTargetName", "fieldName")
|
||||
activeSectionMode(targetName, fieldName) {
|
||||
@ -45,7 +42,7 @@ export default class AdminThemeEditor extends Component {
|
||||
return "scss";
|
||||
}
|
||||
return fieldName && fieldName.includes("scss") ? "scss" : "html";
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("currentTargetName", "fieldName")
|
||||
placeholder(targetName, fieldName) {
|
||||
@ -60,27 +57,30 @@ export default class AdminThemeEditor extends Component {
|
||||
});
|
||||
}
|
||||
return "";
|
||||
}
|
||||
},
|
||||
|
||||
@computed("fieldName", "currentTargetName", "theme")
|
||||
get activeSection() {
|
||||
return this.theme.getField(this.currentTargetName, this.fieldName);
|
||||
}
|
||||
@discourseComputed("fieldName", "currentTargetName", "theme")
|
||||
activeSection: {
|
||||
get(fieldName, target, model) {
|
||||
return model.getField(target, fieldName);
|
||||
},
|
||||
set(value, fieldName, target, model) {
|
||||
model.setField(target, fieldName, value);
|
||||
return value;
|
||||
},
|
||||
},
|
||||
|
||||
set activeSection(value) {
|
||||
this.theme.setField(this.currentTargetName, this.fieldName, value);
|
||||
return value;
|
||||
}
|
||||
editorId: fmt("fieldName", "currentTargetName", "%@|%@"),
|
||||
|
||||
@discourseComputed("maximized")
|
||||
maximizeIcon(maximized) {
|
||||
return maximized ? "discourse-compress" : "discourse-expand";
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("currentTargetName", "theme.targets")
|
||||
showAddField(currentTargetName, targets) {
|
||||
return targets.find((t) => t.name === currentTargetName).customNames;
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed(
|
||||
"currentTargetName",
|
||||
@ -89,45 +89,46 @@ export default class AdminThemeEditor extends Component {
|
||||
)
|
||||
error(target, fieldName) {
|
||||
return this.theme.getError(target, fieldName);
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
toggleShowAdvanced(event) {
|
||||
event?.preventDefault();
|
||||
this.toggleProperty("showAdvanced");
|
||||
}
|
||||
actions: {
|
||||
toggleShowAdvanced() {
|
||||
this.toggleProperty("showAdvanced");
|
||||
},
|
||||
|
||||
@action
|
||||
toggleAddField(event) {
|
||||
event?.preventDefault();
|
||||
this.toggleProperty("addingField");
|
||||
}
|
||||
toggleAddField() {
|
||||
this.toggleProperty("addingField");
|
||||
},
|
||||
|
||||
@action
|
||||
toggleMaximize(event) {
|
||||
event?.preventDefault();
|
||||
this.toggleProperty("maximized");
|
||||
next(() => this.appEvents.trigger("ace:resize"));
|
||||
}
|
||||
cancelAddField() {
|
||||
this.set("addingField", false);
|
||||
},
|
||||
|
||||
@action
|
||||
cancelAddField() {
|
||||
this.set("addingField", false);
|
||||
}
|
||||
addField(name) {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
name = name.replace(/[^a-zA-Z0-9-_/]/g, "");
|
||||
this.theme.setField(this.currentTargetName, name, "");
|
||||
this.setProperties({ newFieldName: "", addingField: false });
|
||||
this.fieldAdded(this.currentTargetName, name);
|
||||
},
|
||||
|
||||
@action
|
||||
addField(name) {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
name = name.replace(/[^a-zA-Z0-9-_/]/g, "");
|
||||
this.theme.setField(this.currentTargetName, name, "");
|
||||
this.setProperties({ newFieldName: "", addingField: false });
|
||||
this.fieldAdded(this.currentTargetName, name);
|
||||
}
|
||||
toggleMaximize() {
|
||||
this.toggleProperty("maximized");
|
||||
next(() => this.appEvents.trigger("ace:resize"));
|
||||
},
|
||||
|
||||
@action
|
||||
setWarning(message) {
|
||||
this.set("warning", message);
|
||||
}
|
||||
}
|
||||
onlyOverriddenChanged(value) {
|
||||
this.onlyOverriddenChanged(value);
|
||||
},
|
||||
|
||||
save() {
|
||||
this.attrs.save();
|
||||
},
|
||||
|
||||
setWarning(message) {
|
||||
this.set("warning", message);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,114 +0,0 @@
|
||||
<div class="user-field">
|
||||
{{#if (or this.isEditing (not this.userField.id))}}
|
||||
<AdminFormRow @label="admin.user_fields.type">
|
||||
<ComboBox
|
||||
@content={{this.fieldTypes}}
|
||||
@value={{this.buffered.field_type}}
|
||||
@onChange={{action (mut this.buffered.field_type)}}
|
||||
/>
|
||||
</AdminFormRow>
|
||||
|
||||
<AdminFormRow @label="admin.user_fields.name">
|
||||
<Input
|
||||
@value={{this.buffered.name}}
|
||||
class="user-field-name"
|
||||
maxlength="255"
|
||||
/>
|
||||
</AdminFormRow>
|
||||
|
||||
<AdminFormRow @label="admin.user_fields.description">
|
||||
<Input
|
||||
@value={{this.buffered.description}}
|
||||
class="user-field-desc"
|
||||
maxlength="255"
|
||||
/>
|
||||
</AdminFormRow>
|
||||
|
||||
{{#if this.bufferedFieldType.hasOptions}}
|
||||
<AdminFormRow @label="admin.user_fields.options">
|
||||
<ValueList @values={{this.buffered.options}} @inputType="array" />
|
||||
</AdminFormRow>
|
||||
{{/if}}
|
||||
|
||||
<AdminFormRow @wrapLabel="true">
|
||||
<Input @type="checkbox" @checked={{this.buffered.editable}} />
|
||||
<span>{{i18n "admin.user_fields.editable.title"}}</span>
|
||||
</AdminFormRow>
|
||||
|
||||
<AdminFormRow @wrapLabel="true">
|
||||
<Input @type="checkbox" @checked={{this.buffered.required}} />
|
||||
<span>{{i18n "admin.user_fields.required.title"}}</span>
|
||||
</AdminFormRow>
|
||||
|
||||
<AdminFormRow @wrapLabel="true">
|
||||
<Input @type="checkbox" @checked={{this.buffered.show_on_profile}} />
|
||||
<span>{{i18n "admin.user_fields.show_on_profile.title"}}</span>
|
||||
</AdminFormRow>
|
||||
|
||||
<AdminFormRow @wrapLabel="true">
|
||||
<Input @type="checkbox" @checked={{this.buffered.show_on_user_card}} />
|
||||
<span>{{i18n "admin.user_fields.show_on_user_card.title"}}</span>
|
||||
</AdminFormRow>
|
||||
|
||||
<AdminFormRow @wrapLabel="true">
|
||||
<Input @type="checkbox" @checked={{this.buffered.searchable}} />
|
||||
<span>{{i18n "admin.user_fields.searchable.title"}}</span>
|
||||
</AdminFormRow>
|
||||
|
||||
<AdminFormRow>
|
||||
<DButton
|
||||
@action={{action "save"}}
|
||||
@class="btn-primary save"
|
||||
@icon="check"
|
||||
@label="admin.user_fields.save"
|
||||
/>
|
||||
<DButton
|
||||
@action={{action "cancel"}}
|
||||
@class="btn-danger cancel"
|
||||
@icon="times"
|
||||
@label="admin.user_fields.cancel"
|
||||
/>
|
||||
</AdminFormRow>
|
||||
{{else}}
|
||||
<div class="row">
|
||||
<div class="form-display">
|
||||
<b class="name">{{this.userField.name}}</b>
|
||||
<br />
|
||||
<span class="description">{{html-safe
|
||||
this.userField.description
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="form-display field-type">{{this.fieldName}}</div>
|
||||
<div class="form-element controls">
|
||||
<DButton
|
||||
@action={{action "edit"}}
|
||||
@class="btn-default edit"
|
||||
@icon="pencil-alt"
|
||||
@label="admin.user_fields.edit"
|
||||
/>
|
||||
<DButton
|
||||
@action={{this.destroyAction}}
|
||||
@actionParam={{this.userField}}
|
||||
@class="btn-danger cancel"
|
||||
@icon="far-trash-alt"
|
||||
@label="admin.user_fields.delete"
|
||||
/>
|
||||
<DButton
|
||||
@action={{this.moveUpAction}}
|
||||
@actionParam={{this.userField}}
|
||||
@class="btn-default"
|
||||
@icon="arrow-up"
|
||||
@disabled={{this.cantMoveUp}}
|
||||
/>
|
||||
<DButton
|
||||
@action={{this.moveDownAction}}
|
||||
@actionParam={{this.userField}}
|
||||
@class="btn-default"
|
||||
@icon="arrow-down"
|
||||
@disabled={{this.cantMoveDown}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">{{this.flags}}</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
@ -1,20 +0,0 @@
|
||||
<span
|
||||
role="button"
|
||||
onclick={{action "deleteWord"}}
|
||||
class="delete-word-record"
|
||||
>{{d-icon "times"}}</span>
|
||||
{{this.word.word}}
|
||||
{{#if (or this.isReplace this.isLink)}}
|
||||
→
|
||||
<span class="replacement">{{this.word.replacement}}</span>
|
||||
{{else if this.isTag}}
|
||||
→
|
||||
{{#each this.tags as |tag|}}
|
||||
<span class="tag">{{tag}}</span>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
{{#if this.isCaseSensitive}}
|
||||
<span class="case-sensitive">{{i18n
|
||||
"admin.watched_words.case_sensitive"
|
||||
}}</span>
|
||||
{{/if}}
|
||||
@ -1,27 +1,22 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { alias, equal } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import { alias, equal } from "@ember/object/computed";
|
||||
import bootbox from "bootbox";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { action } from "@ember/object";
|
||||
import I18n from "I18n";
|
||||
|
||||
@classNames("watched-word")
|
||||
export default class AdminWatchedWord extends Component {
|
||||
@service dialog;
|
||||
export default Component.extend({
|
||||
classNames: ["watched-word"],
|
||||
|
||||
@equal("actionKey", "replace") isReplace;
|
||||
|
||||
@equal("actionKey", "tag") isTag;
|
||||
|
||||
@equal("actionKey", "link") isLink;
|
||||
|
||||
@alias("word.case_sensitive") isCaseSensitive;
|
||||
isReplace: equal("actionKey", "replace"),
|
||||
isTag: equal("actionKey", "tag"),
|
||||
isLink: equal("actionKey", "link"),
|
||||
isCaseSensitive: alias("word.case_sensitive"),
|
||||
|
||||
@discourseComputed("word.replacement")
|
||||
tags(replacement) {
|
||||
return replacement.split(",");
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
deleteWord() {
|
||||
@ -31,11 +26,11 @@ export default class AdminWatchedWord extends Component {
|
||||
this.action(this.word);
|
||||
})
|
||||
.catch((e) => {
|
||||
this.dialog.alert(
|
||||
bootbox.alert(
|
||||
I18n.t("generic_error_with_reason", {
|
||||
error: `http: ${e.status} - ${e.body}`,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ["hook-event"],
|
||||
typeName: alias("type.name"),
|
||||
|
||||
@discourseComputed("typeName")
|
||||
name(typeName) {
|
||||
return I18n.t(`admin.web_hooks.${typeName}_event.name`);
|
||||
},
|
||||
|
||||
@discourseComputed("typeName")
|
||||
details(typeName) {
|
||||
return I18n.t(`admin.web_hooks.${typeName}_event.details`);
|
||||
},
|
||||
|
||||
@discourseComputed("model.[]", "typeName")
|
||||
eventTypeExists(eventTypes, typeName) {
|
||||
return eventTypes.any((event) => event.name === typeName);
|
||||
},
|
||||
|
||||
@discourseComputed("eventTypeExists")
|
||||
enabled: {
|
||||
get(eventTypeExists) {
|
||||
return eventTypeExists;
|
||||
},
|
||||
set(value, eventTypeExists) {
|
||||
const type = this.type;
|
||||
const model = this.model;
|
||||
// add an association when not exists
|
||||
if (value !== eventTypeExists) {
|
||||
if (value) {
|
||||
model.addObject(type);
|
||||
} else {
|
||||
model.removeObjects(
|
||||
model.filter((eventType) => eventType.name === type.name)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,110 @@
|
||||
import { ensureJSON, plainJSON, prettyJSON } from "discourse/lib/formatter";
|
||||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
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) {
|
||||
if (!status) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (status >= 200 && status <= 299) {
|
||||
return "text-successful";
|
||||
} else {
|
||||
return "text-danger";
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("model.created_at")
|
||||
createdAt(createdAt) {
|
||||
return moment(createdAt).format("YYYY-MM-DD HH:mm:ss");
|
||||
},
|
||||
|
||||
@discourseComputed("model.duration")
|
||||
completion(duration) {
|
||||
const seconds = Math.floor(duration / 10.0) / 100.0;
|
||||
return I18n.t("admin.web_hooks.events.completed_in", { count: seconds });
|
||||
},
|
||||
|
||||
@discourseComputed("expandDetails")
|
||||
expandRequestIcon(expandDetails) {
|
||||
return expandDetails === this.expandDetailsRequestKey
|
||||
? "ellipsis-h"
|
||||
: "ellipsis-v";
|
||||
},
|
||||
|
||||
@discourseComputed("expandDetails")
|
||||
expandResponseIcon(expandDetails) {
|
||||
return expandDetails === this.expandDetailsResponseKey
|
||||
? "ellipsis-h"
|
||||
: "ellipsis-v";
|
||||
},
|
||||
|
||||
actions: {
|
||||
redeliver() {
|
||||
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() {
|
||||
const expandDetailsKey = this.expandDetailsRequestKey;
|
||||
|
||||
if (this.expandDetails !== expandDetailsKey) {
|
||||
let headers = Object.assign(
|
||||
{
|
||||
"Request URL": this.get("model.request_url"),
|
||||
"Request method": "POST",
|
||||
},
|
||||
ensureJSON(this.get("model.headers"))
|
||||
);
|
||||
this.setProperties({
|
||||
headers: plainJSON(headers),
|
||||
body: prettyJSON(this.get("model.payload")),
|
||||
expandDetails: expandDetailsKey,
|
||||
bodyLabel: I18n.t("admin.web_hooks.events.payload"),
|
||||
});
|
||||
} else {
|
||||
this.set("expandDetails", null);
|
||||
}
|
||||
},
|
||||
|
||||
toggleResponse() {
|
||||
const expandDetailsKey = this.expandDetailsResponseKey;
|
||||
|
||||
if (this.expandDetails !== expandDetailsKey) {
|
||||
this.setProperties({
|
||||
headers: plainJSON(this.get("model.response_headers")),
|
||||
body: this.get("model.response_body"),
|
||||
expandDetails: expandDetailsKey,
|
||||
bodyLabel: I18n.t("admin.web_hooks.events.body"),
|
||||
});
|
||||
} else {
|
||||
this.set("expandDetails", null);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,39 @@
|
||||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
|
||||
export default Component.extend({
|
||||
classes: ["text-muted", "text-danger", "text-successful", "text-muted"],
|
||||
icons: ["far-circle", "times-circle", "circle", "circle"],
|
||||
circleIcon: null,
|
||||
deliveryStatus: null,
|
||||
|
||||
@discourseComputed("deliveryStatuses", "model.last_delivery_status")
|
||||
status(deliveryStatuses, lastDeliveryStatus) {
|
||||
return deliveryStatuses.find((s) => s.id === lastDeliveryStatus);
|
||||
},
|
||||
|
||||
@discourseComputed("status.id", "icons")
|
||||
icon(statusId, icons) {
|
||||
return icons[statusId - 1];
|
||||
},
|
||||
|
||||
@discourseComputed("status.id", "classes")
|
||||
class(statusId, classes) {
|
||||
return classes[statusId - 1];
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
this.set(
|
||||
"circleIcon",
|
||||
htmlSafe(iconHTML(this.icon, { class: this.class }))
|
||||
);
|
||||
this.set(
|
||||
"deliveryStatus",
|
||||
I18n.t(`admin.web_hooks.delivery_status.${this.get("status.name")}`)
|
||||
);
|
||||
},
|
||||
});
|
||||
@ -1,15 +1,14 @@
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default class AdminWrapper extends Component {
|
||||
export default Component.extend({
|
||||
didInsertElement() {
|
||||
super.didInsertElement(...arguments);
|
||||
this._super(...arguments);
|
||||
document.querySelector("html").classList.add("admin-area");
|
||||
document.querySelector("body").classList.add("admin-interface");
|
||||
}
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
super.willDestroyElement(...arguments);
|
||||
this._super(...arguments);
|
||||
document.querySelector("html").classList.remove("admin-area");
|
||||
document.querySelector("body").classList.remove("admin-interface");
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
|
||||
@tagName("")
|
||||
export default class CancelLink extends Component {}
|
||||
export default Component.extend({
|
||||
tagName: "",
|
||||
});
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
{{#if this.onlyHex}}<span class="add-on">#</span>{{/if}}<TextField
|
||||
@class="hex-input"
|
||||
@value={{this.hexValue}}
|
||||
@maxlength={{this.maxlength}}
|
||||
@input={{action "onHexInput" value="target.value"}}
|
||||
/>
|
||||
<input
|
||||
class="picker"
|
||||
type="color"
|
||||
value={{this.normalizedHexValue}}
|
||||
{{on "input" this.onPickerInput}}
|
||||
/>
|
||||
@ -1,7 +1,6 @@
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import { action, computed } from "@ember/object";
|
||||
import Component from "@ember/component";
|
||||
import { observes } from "@ember-decorators/object";
|
||||
import { observes } from "discourse-common/utils/decorators";
|
||||
|
||||
/**
|
||||
An input field for a color.
|
||||
@ -10,20 +9,20 @@ import { observes } from "@ember-decorators/object";
|
||||
@param brightnessValue is a number from 0 to 255 representing the brightness of the color. See ColorSchemeColor.
|
||||
@params valid is a boolean indicating if the input field is a valid color.
|
||||
**/
|
||||
@classNames("color-picker")
|
||||
export default class ColorInput extends Component {
|
||||
onlyHex = true;
|
||||
styleSelection = true;
|
||||
export default Component.extend({
|
||||
classNames: ["color-picker"],
|
||||
|
||||
@computed("onlyHex")
|
||||
get maxlength() {
|
||||
onlyHex: true,
|
||||
|
||||
styleSelection: true,
|
||||
|
||||
maxlength: computed("onlyHex", function () {
|
||||
return this.onlyHex ? 6 : null;
|
||||
}
|
||||
}),
|
||||
|
||||
@computed("hexValue")
|
||||
get normalizedHexValue() {
|
||||
normalizedHexValue: computed("hexValue", function () {
|
||||
return this.normalize(this.hexValue);
|
||||
}
|
||||
}),
|
||||
|
||||
normalize(color) {
|
||||
if (this._valid(color)) {
|
||||
@ -41,19 +40,19 @@ export default class ColorInput extends Component {
|
||||
}
|
||||
}
|
||||
return color;
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
onHexInput(color) {
|
||||
if (this.attrs.onChangeColor) {
|
||||
this.attrs.onChangeColor(this.normalize(color || ""));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
onPickerInput(event) {
|
||||
this.set("hexValue", event.target.value.replace("#", ""));
|
||||
}
|
||||
},
|
||||
|
||||
@observes("hexValue", "brightnessValue", "valid")
|
||||
hexValueChanged() {
|
||||
@ -66,9 +65,9 @@ export default class ColorInput extends Component {
|
||||
if (this._valid()) {
|
||||
this.element.querySelector(".picker").value = this.normalize(hex);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_valid(color = this.hexValue) {
|
||||
return /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(color);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
<div class="admin-new-feature-item">
|
||||
<div class="new-feature-emoji">{{this.item.emoji}}</div>
|
||||
<div class="new-feature-content">
|
||||
<div class="header">
|
||||
{{#if this.item.link}}
|
||||
<a
|
||||
href={{this.item.link}}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>{{this.item.title}}</a>
|
||||
{{else}}
|
||||
{{this.item.title}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="feature-description">{{this.item.description}}</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,3 +1,3 @@
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default class DashboardNewFeatureItem extends Component {}
|
||||
export default Component.extend({});
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
{{#if this.newFeatures}}
|
||||
<div class="section-title">
|
||||
<h2>{{replace-emoji (i18n "admin.dashboard.new_features.title")}}</h2>
|
||||
</div>
|
||||
|
||||
<div class="section-body {{this.columnCountClass}}">
|
||||
{{#each this.newFeatures as |feature|}}
|
||||
<DashboardNewFeatureItem @item={{feature}} @tagName="" />
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="section-footer">
|
||||
{{#if this.releaseNotesLink}}
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
href={{this.releaseNotesLink}}
|
||||
class="btn btn-primary new-features-release-notes"
|
||||
>
|
||||
{{i18n "admin.dashboard.new_features.learn_more"}}
|
||||
</a>
|
||||
{{/if}}
|
||||
<DButton
|
||||
@label="admin.dashboard.new_features.dismiss"
|
||||
@class="btn-default new-features-dismiss"
|
||||
@action={{this.dismissNewFeatures}}
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
@ -1,19 +1,18 @@
|
||||
import { classNameBindings, classNames } from "@ember-decorators/component";
|
||||
import Component from "@ember/component";
|
||||
import { action, computed } from "@ember/object";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
|
||||
@classNames("section", "dashboard-new-features")
|
||||
@classNameBindings("hasUnseenFeatures:ordered-first")
|
||||
export default class DashboardNewFeatures extends Component {
|
||||
newFeatures = null;
|
||||
releaseNotesLink = null;
|
||||
export default Component.extend({
|
||||
newFeatures: null,
|
||||
classNames: ["section", "dashboard-new-features"],
|
||||
classNameBindings: ["hasUnseenFeatures:ordered-first"],
|
||||
releaseNotesLink: null,
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
ajax("/admin/dashboard/new-features.json").then((json) => {
|
||||
if (this.isDestroying || this.isDestroyed) {
|
||||
if (!this.element || this.isDestroying || this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -23,17 +22,16 @@ export default class DashboardNewFeatures extends Component {
|
||||
releaseNotesLink: json.release_notes_link,
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@computed("newFeatures")
|
||||
get columnCountClass() {
|
||||
columnCountClass: computed("newFeatures", function () {
|
||||
return this.newFeatures.length > 2 ? "three-or-more-items" : "";
|
||||
}
|
||||
}),
|
||||
|
||||
@action
|
||||
dismissNewFeatures() {
|
||||
ajax("/admin/dashboard/mark-new-features-as-seen.json", {
|
||||
type: "PUT",
|
||||
}).then(() => this.set("hasUnseenFeatures", false));
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,58 +0,0 @@
|
||||
{{#if this.foundProblems}}
|
||||
<div class="section dashboard-problems">
|
||||
<div class="section-title">
|
||||
<h2>
|
||||
{{d-icon "heart"}}
|
||||
{{i18n "admin.dashboard.problems_found"}}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="section-body">
|
||||
<ConditionalLoadingSection @isLoading={{this.loadingProblems}}>
|
||||
{{#if this.highPriorityProblems.length}}
|
||||
<div class="problem-messages priority-high">
|
||||
<ul>
|
||||
{{#each this.highPriorityProblems as |problem|}}
|
||||
<li
|
||||
class={{concat
|
||||
"dashboard-problem "
|
||||
"priority-"
|
||||
problem.priority
|
||||
}}
|
||||
>
|
||||
{{d-icon "exclamation-triangle"}}
|
||||
{{html-safe problem.message}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="problem-messages priority-low">
|
||||
<ul>
|
||||
{{#each this.lowPriorityProblems as |problem|}}
|
||||
<li
|
||||
class={{concat
|
||||
"dashboard-problem "
|
||||
"priority-"
|
||||
problem.priority
|
||||
}}
|
||||
>{{html-safe problem.message}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p class="actions">
|
||||
<DButton
|
||||
@action={{this.refreshProblems}}
|
||||
@class="btn-default"
|
||||
@icon="sync"
|
||||
@label="admin.dashboard.refresh_problems"
|
||||
/>
|
||||
{{i18n "admin.dashboard.last_checked"}}:
|
||||
{{this.problemsTimestamp}}
|
||||
</p>
|
||||
</ConditionalLoadingSection>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
@ -1,3 +1,3 @@
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default class DashboardProblems extends Component {}
|
||||
export default Component.extend({});
|
||||
|
||||
@ -1,44 +0,0 @@
|
||||
<div class="row">
|
||||
<div class="admin-controls">
|
||||
<nav>
|
||||
<ul class="nav nav-pills">
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="adminCustomizeEmailStyle.edit"
|
||||
@model="html"
|
||||
@replace={{true}}
|
||||
>
|
||||
{{i18n "admin.customize.email_style.html"}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li>
|
||||
<LinkTo
|
||||
@route="adminCustomizeEmailStyle.edit"
|
||||
@model="css"
|
||||
@replace={{true}}
|
||||
>
|
||||
{{i18n "admin.customize.email_style.css"}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AceEditor
|
||||
@content={{this.editorContents}}
|
||||
@mode={{this.currentEditorMode}}
|
||||
@editorId={{this.editorId}}
|
||||
@save={{@save}}
|
||||
/>
|
||||
|
||||
<div class="admin-footer">
|
||||
<div class="buttons">
|
||||
<DButton
|
||||
@action={{action "reset"}}
|
||||
@disabled={{this.resetDisabled}}
|
||||
@class="btn-default"
|
||||
@label="admin.customize.email_style.reset"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,19 +1,17 @@
|
||||
import { action, computed } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { reads } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { reads } from "@ember/object/computed";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default class EmailStylesEditor extends Component {
|
||||
@service dialog;
|
||||
|
||||
@reads("fieldName") editorId;
|
||||
export default Component.extend({
|
||||
dialog: service(),
|
||||
editorId: reads("fieldName"),
|
||||
|
||||
@discourseComputed("fieldName")
|
||||
currentEditorMode(fieldName) {
|
||||
return fieldName === "css" ? "scss" : fieldName;
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("fieldName", "styles.html", "styles.css")
|
||||
resetDisabled(fieldName) {
|
||||
@ -21,31 +19,36 @@ export default class EmailStylesEditor extends Component {
|
||||
this.get(`styles.${fieldName}`) ===
|
||||
this.get(`styles.default_${fieldName}`)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@computed("styles", "fieldName")
|
||||
get editorContents() {
|
||||
return this.styles[this.fieldName];
|
||||
}
|
||||
@discourseComputed("styles", "fieldName")
|
||||
editorContents: {
|
||||
get(styles, fieldName) {
|
||||
return styles[fieldName];
|
||||
},
|
||||
set(value, styles, fieldName) {
|
||||
styles.setField(fieldName, value);
|
||||
return value;
|
||||
},
|
||||
},
|
||||
|
||||
set editorContents(value) {
|
||||
this.styles.setField(this.fieldName, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@action
|
||||
reset() {
|
||||
this.dialog.yesNoConfirm({
|
||||
message: I18n.t("admin.customize.email_style.reset_confirm", {
|
||||
fieldName: I18n.t(`admin.customize.email_style.${this.fieldName}`),
|
||||
}),
|
||||
didConfirm: () => {
|
||||
this.styles.setField(
|
||||
this.fieldName,
|
||||
this.styles.get(`default_${this.fieldName}`)
|
||||
);
|
||||
this.notifyPropertyChange("editorContents");
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
actions: {
|
||||
reset() {
|
||||
this.dialog.yesNoConfirm({
|
||||
message: I18n.t("admin.customize.email_style.reset_confirm", {
|
||||
fieldName: I18n.t(`admin.customize.email_style.${this.fieldName}`),
|
||||
}),
|
||||
didConfirm: () => {
|
||||
this.styles.setField(
|
||||
this.fieldName,
|
||||
this.styles.get(`default_${this.fieldName}`)
|
||||
);
|
||||
this.notifyPropertyChange("editorContents");
|
||||
},
|
||||
});
|
||||
},
|
||||
save() {
|
||||
this.attrs.save();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,66 +0,0 @@
|
||||
{{#if this.editing}}
|
||||
<td class="editing-input">
|
||||
<div class="label">{{i18n "admin.embedding.host"}}</div>
|
||||
<Input
|
||||
@value={{this.buffered.host}}
|
||||
placeholder="example.com"
|
||||
@enter={{action "save"}}
|
||||
class="host-name"
|
||||
autofocus={{true}}
|
||||
/>
|
||||
</td>
|
||||
<td class="editing-input">
|
||||
<div class="label">{{i18n "admin.embedding.allowed_paths"}}</div>
|
||||
<Input
|
||||
@value={{this.buffered.allowed_paths}}
|
||||
placeholder="/blog/.*"
|
||||
@enter={{action "save"}}
|
||||
class="path-allowlist"
|
||||
/>
|
||||
</td>
|
||||
<td class="editing-input">
|
||||
<div class="label">{{i18n "admin.embedding.category"}}</div>
|
||||
<CategoryChooser
|
||||
@value={{this.categoryId}}
|
||||
@class="small"
|
||||
@onChange={{action (mut this.categoryId)}}
|
||||
/>
|
||||
</td>
|
||||
<td class="editing-controls">
|
||||
<DButton
|
||||
@icon="check"
|
||||
@action={{action "save"}}
|
||||
@class="btn-primary"
|
||||
@disabled={{this.cantSave}}
|
||||
/>
|
||||
<DButton
|
||||
@icon="times"
|
||||
@action={{action "cancel"}}
|
||||
@class="btn-danger"
|
||||
@disabled={{this.host.isSaving}}
|
||||
/>
|
||||
</td>
|
||||
{{else}}
|
||||
<td>
|
||||
<div class="label">{{i18n "admin.embedding.host"}}</div>
|
||||
{{this.host.host}}
|
||||
</td>
|
||||
<td>
|
||||
<div class="label">
|
||||
{{i18n "admin.embedding.allowed_paths"}}
|
||||
</div>
|
||||
{{this.host.allowed_paths}}
|
||||
</td>
|
||||
<td>
|
||||
<div class="label">{{i18n "admin.embedding.category"}}</div>
|
||||
{{category-badge this.host.category allowUncategorized=true}}
|
||||
</td>
|
||||
<td class="controls">
|
||||
<DButton @icon="pencil-alt" @action={{action "edit"}} />
|
||||
<DButton
|
||||
@icon="far-trash-alt"
|
||||
@action={{action "delete"}}
|
||||
@class="btn-danger"
|
||||
/>
|
||||
</td>
|
||||
{{/if}}
|
||||
@ -1,91 +1,83 @@
|
||||
import { action } from "@ember/object";
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { or } from "@ember/object/computed";
|
||||
import Category from "discourse/models/category";
|
||||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import bootbox from "bootbox";
|
||||
import { bufferedProperty } from "discourse/mixins/buffered-content";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import { or } from "@ember/object/computed";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
|
||||
@tagName("tr")
|
||||
export default class EmbeddableHost extends Component.extend(
|
||||
bufferedProperty("host")
|
||||
) {
|
||||
@service dialog;
|
||||
editToggled = false;
|
||||
categoryId = null;
|
||||
category = null;
|
||||
export default Component.extend(bufferedProperty("host"), {
|
||||
editToggled: false,
|
||||
tagName: "tr",
|
||||
categoryId: null,
|
||||
category: null,
|
||||
|
||||
@or("host.isNew", "editToggled") editing;
|
||||
editing: or("host.isNew", "editToggled"),
|
||||
|
||||
init() {
|
||||
super.init(...arguments);
|
||||
this._super(...arguments);
|
||||
|
||||
const host = this.host;
|
||||
const categoryId = host.category_id || this.site.uncategorized_category_id;
|
||||
const category = Category.findById(categoryId);
|
||||
|
||||
host.set("category", category);
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("buffered.host", "host.isSaving")
|
||||
cantSave(host, isSaving) {
|
||||
return isSaving || isEmpty(host);
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
edit() {
|
||||
this.set("categoryId", this.get("host.category.id"));
|
||||
this.set("editToggled", true);
|
||||
}
|
||||
actions: {
|
||||
edit() {
|
||||
this.set("categoryId", this.get("host.category.id"));
|
||||
this.set("editToggled", true);
|
||||
},
|
||||
|
||||
@action
|
||||
save() {
|
||||
if (this.cantSave) {
|
||||
return;
|
||||
}
|
||||
save() {
|
||||
if (this.cantSave) {
|
||||
return;
|
||||
}
|
||||
|
||||
const props = this.buffered.getProperties(
|
||||
"host",
|
||||
"allowed_paths",
|
||||
"class_name"
|
||||
);
|
||||
props.category_id = this.categoryId;
|
||||
const props = this.buffered.getProperties(
|
||||
"host",
|
||||
"allowed_paths",
|
||||
"class_name"
|
||||
);
|
||||
props.category_id = this.categoryId;
|
||||
|
||||
const host = this.host;
|
||||
const host = this.host;
|
||||
|
||||
host
|
||||
.save(props)
|
||||
.then(() => {
|
||||
host.set("category", Category.findById(this.categoryId));
|
||||
host
|
||||
.save(props)
|
||||
.then(() => {
|
||||
host.set("category", Category.findById(this.categoryId));
|
||||
this.set("editToggled", false);
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
|
||||
delete() {
|
||||
bootbox.confirm(I18n.t("admin.embedding.confirm_delete"), (result) => {
|
||||
if (result) {
|
||||
this.host.destroyRecord().then(() => {
|
||||
this.deleteHost(this.host);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
cancel() {
|
||||
const host = this.host;
|
||||
if (host.get("isNew")) {
|
||||
this.deleteHost(host);
|
||||
} else {
|
||||
this.rollbackBuffer();
|
||||
this.set("editToggled", false);
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
}
|
||||
|
||||
@action
|
||||
delete() {
|
||||
return this.dialog.confirm({
|
||||
message: I18n.t("admin.embedding.confirm_delete"),
|
||||
didConfirm: () => {
|
||||
return this.host.destroyRecord().then(() => {
|
||||
this.deleteHost(this.host);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
cancel() {
|
||||
const host = this.host;
|
||||
if (host.get("isNew")) {
|
||||
this.deleteHost(host);
|
||||
} else {
|
||||
this.rollbackBuffer();
|
||||
this.set("editToggled", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user