Merge branch 'main' into theme-cards
This commit is contained in:
commit
c446caa6e9
|
@ -10,7 +10,7 @@ permissions:
|
||||||
jobs:
|
jobs:
|
||||||
triage:
|
triage:
|
||||||
if: github.actor != 'discourse-translator-bot'
|
if: github.actor != 'discourse-translator-bot'
|
||||||
runs-on: debian-12
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@v5
|
- uses: actions/labeler@v5
|
||||||
|
|
|
@ -17,7 +17,7 @@ jobs:
|
||||||
build:
|
build:
|
||||||
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
|
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
|
||||||
name: run
|
name: run
|
||||||
runs-on: debian-12
|
runs-on: ${{ (github.repository != 'discourse/discourse' && 'ubuntu-latest') || 'debian-12' }}
|
||||||
container: discourse/discourse_test:slim
|
container: discourse/discourse_test:slim
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ jobs:
|
||||||
build:
|
build:
|
||||||
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
|
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
|
||||||
name: run
|
name: run
|
||||||
runs-on: debian-12
|
runs-on: ${{ (github.repository != 'discourse/discourse' && 'ubuntu-latest') || 'debian-12' }}
|
||||||
container: discourse/discourse_test:slim
|
container: discourse/discourse_test:slim
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ jobs:
|
||||||
tests:
|
tests:
|
||||||
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
|
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
|
||||||
name: Tests with Ruby ${{ matrix.ruby }}
|
name: Tests with Ruby ${{ matrix.ruby }}
|
||||||
runs-on: debian-12
|
runs-on: ${{ (github.repository != 'discourse/discourse' && 'ubuntu-latest') || 'debian-12' }}
|
||||||
container: discourse/discourse_test:slim
|
container: discourse/discourse_test:slim
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
|
||||||
|
@ -126,11 +126,11 @@ jobs:
|
||||||
if: steps.app-cache.outputs.cache-hit != 'true'
|
if: steps.app-cache.outputs.cache-hit != 'true'
|
||||||
run: rm -rf tmp/app-cache/uploads && cp -r public/uploads tmp/app-cache/uploads
|
run: rm -rf tmp/app-cache/uploads && cp -r public/uploads tmp/app-cache/uploads
|
||||||
|
|
||||||
# - name: Check core database drift
|
# - name: Check core database drift
|
||||||
# run: |
|
# run: |
|
||||||
# mkdir /tmp/intermediate_db
|
# mkdir /tmp/intermediate_db
|
||||||
# ./migrations/scripts/schema_generator /tmp/intermediate_db/base_migration.sql
|
# ./migrations/scripts/schema_generator /tmp/intermediate_db/base_migration.sql
|
||||||
# diff -u migrations/common/intermediate_db_schema/000_base_schema.sql /tmp/intermediate_db/base_migration.sql
|
# diff -u migrations/common/intermediate_db_schema/000_base_schema.sql /tmp/intermediate_db/base_migration.sql
|
||||||
|
|
||||||
- name: RSpec
|
- name: RSpec
|
||||||
run: bin/rspec --default-path migrations/spec
|
run: bin/rspec --default-path migrations/spec
|
||||||
|
|
|
@ -4,14 +4,14 @@ on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
from:
|
from:
|
||||||
description: 'Starting ref (exclusive). Can be a tag, branch or commit ref. `latest-release` refers to the last beta version bump.'
|
description: "Starting ref (exclusive). Can be a tag, branch or commit ref. `latest-release` refers to the last beta version bump."
|
||||||
required: true
|
required: true
|
||||||
default: 'latest-release'
|
default: "latest-release"
|
||||||
type: string
|
type: string
|
||||||
to:
|
to:
|
||||||
description: 'Ending ref (inclusive). Can be a tag, branch or commit ref. `HEAD` refers to the most recent commit.'
|
description: "Ending ref (inclusive). Can be a tag, branch or commit ref. `HEAD` refers to the most recent commit."
|
||||||
required: true
|
required: true
|
||||||
default: 'HEAD'
|
default: "HEAD"
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -20,7 +20,7 @@ permissions:
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: run
|
name: run
|
||||||
runs-on: debian-12
|
runs-on: ${{ (github.repository != 'discourse/discourse' && 'ubuntu-latest') || 'debian-12' }}
|
||||||
container: discourse/discourse_test:slim
|
container: discourse/discourse_test:slim
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
env:
|
env:
|
||||||
|
@ -87,7 +87,7 @@ jobs:
|
||||||
|
|
||||||
echo "From: $from_ref - $(git rev-parse --short $from_ref) - ${{ steps.dates.outputs.from }}" >> $GITHUB_STEP_SUMMARY
|
echo "From: $from_ref - $(git rev-parse --short $from_ref) - ${{ steps.dates.outputs.from }}" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "To: $to_ref - $(git rev-parse --short $to_ref) - ${{ steps.dates.outputs.to }}" >> $GITHUB_STEP_SUMMARY
|
echo "To: $to_ref - $(git rev-parse --short $to_ref) - ${{ steps.dates.outputs.to }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "---" >> $GITHUB_STEP_SUMMARY
|
echo "---" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
name: 'Close stale PRs'
|
name: "Close stale PRs"
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '30 1 * * *'
|
- cron: "30 1 * * *"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -9,11 +9,11 @@ permissions:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: debian-12
|
runs-on: ${{ (github.repository != 'discourse/discourse' && 'ubuntu-latest') || 'debian-12' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9
|
- uses: actions/stale@v9
|
||||||
with:
|
with:
|
||||||
days-before-stale: 60
|
days-before-stale: 60
|
||||||
days-before-close: 14
|
days-before-close: 14
|
||||||
stale-pr-message: 'This pull request has been automatically marked as stale because it has been open for 60 days with no activity. To keep it open, remove the stale tag, push code, or add a comment. Otherwise, it will be closed in 14 days.'
|
stale-pr-message: "This pull request has been automatically marked as stale because it has been open for 60 days with no activity. To keep it open, remove the stale tag, push code, or add a comment. Otherwise, it will be closed in 14 days."
|
||||||
exempt-pr-labels: dependencies
|
exempt-pr-labels: dependencies
|
||||||
|
|
|
@ -29,7 +29,7 @@ jobs:
|
||||||
build:
|
build:
|
||||||
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
|
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
|
||||||
name: ${{ matrix.target }} ${{ matrix.build_type }} # Update fetch-job-id step if changing this
|
name: ${{ matrix.target }} ${{ matrix.build_type }} # Update fetch-job-id step if changing this
|
||||||
runs-on: ${{ ((github.ref == 'refs/heads/main') && 'debian-12') || 'ubuntu-22.04-8core' }}
|
runs-on: ${{ (github.repository != 'discourse/discourse' && 'ubuntu-22.04-8core') || 'debian-12' }}
|
||||||
container: discourse/discourse_test:slim${{ (matrix.build_type == 'frontend' || matrix.build_type == 'system') && '-browsers' || '' }}
|
container: discourse/discourse_test:slim${{ (matrix.build_type == 'frontend' || matrix.build_type == 'system') && '-browsers' || '' }}
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ jobs:
|
||||||
DISCOURSE_TURBO_RSPEC_RETRY_AND_LOG_FLAKY_TESTS: ${{ (matrix.build_type == 'system' || matrix.build_type == 'backend') && github.ref == 'refs/heads/main' && '1' }}
|
DISCOURSE_TURBO_RSPEC_RETRY_AND_LOG_FLAKY_TESTS: ${{ (matrix.build_type == 'system' || matrix.build_type == 'backend') && github.ref == 'refs/heads/main' && '1' }}
|
||||||
CHEAP_SOURCE_MAPS: "1"
|
CHEAP_SOURCE_MAPS: "1"
|
||||||
TESTEM_DEFAULT_BROWSER: Chrome
|
TESTEM_DEFAULT_BROWSER: Chrome
|
||||||
|
MINIO_RUNNER_INSTALL_DIR: /home/discourse/.minio_runner
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
@ -255,6 +256,13 @@ jobs:
|
||||||
if: matrix.build_type == 'system'
|
if: matrix.build_type == 'system'
|
||||||
run: bin/ember-cli --build
|
run: bin/ember-cli --build
|
||||||
|
|
||||||
|
- name: Minio cache
|
||||||
|
if: matrix.build_type == 'system' && matrix.target == 'core'
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ env.MINIO_RUNNER_INSTALL_DIR }}
|
||||||
|
key: ${{ runner.os }}-${{ steps.container-envs.outputs.ruby_version }}-${{ steps.container-envs.outputs.debian_release }}-gem-${{ hashFiles('**/Gemfile.lock') }}
|
||||||
|
|
||||||
- name: Ensure latest minio binary installed for Core System Tests
|
- name: Ensure latest minio binary installed for Core System Tests
|
||||||
if: matrix.build_type == 'system' && matrix.target == 'core'
|
if: matrix.build_type == 'system' && matrix.target == 'core'
|
||||||
run: bundle exec ruby script/install_minio_binaries.rb
|
run: bundle exec ruby script/install_minio_binaries.rb
|
||||||
|
@ -358,7 +366,7 @@ jobs:
|
||||||
core_frontend_tests:
|
core_frontend_tests:
|
||||||
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
|
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
|
||||||
name: core frontend (${{ matrix.browser }})
|
name: core frontend (${{ matrix.browser }})
|
||||||
runs-on: ${{ ((github.ref == 'refs/heads/main') && 'debian-12') || 'ubuntu-22.04-8core' }}
|
runs-on: ${{ (github.repository != 'discourse/discourse' && 'ubuntu-22.04-8core') || 'debian-12' }}
|
||||||
container:
|
container:
|
||||||
image: discourse/discourse_test:slim-browsers
|
image: discourse/discourse_test:slim-browsers
|
||||||
options: --user discourse
|
options: --user discourse
|
||||||
|
@ -397,7 +405,7 @@ jobs:
|
||||||
- name: Core QUnit
|
- name: Core QUnit
|
||||||
working-directory: ./app/assets/javascripts/discourse
|
working-directory: ./app/assets/javascripts/discourse
|
||||||
run: |
|
run: |
|
||||||
pnpm ember exam --path /tmp/emberbuild --load-balance --parallel=$(($(nproc) / 2 + 1)) --launch "${{ env.TESTEM_BROWSER }}" --write-execution-file --random
|
pnpm ember exam --path /tmp/emberbuild --load-balance --parallel=$(($(nproc) / 2)) --launch "${{ env.TESTEM_BROWSER }}" --write-execution-file --random
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
|
|
|
@ -45,6 +45,7 @@ reviewed:
|
||||||
- concurrent-ruby # MIT
|
- concurrent-ruby # MIT
|
||||||
- css_parser # MIT
|
- css_parser # MIT
|
||||||
- drb # BSD-2-Clause
|
- drb # BSD-2-Clause
|
||||||
|
- dry-initializer # MIT
|
||||||
- excon # MIT
|
- excon # MIT
|
||||||
- faraday-em_http # MIT
|
- faraday-em_http # MIT
|
||||||
- faraday-em_synchrony # MIT
|
- faraday-em_synchrony # MIT
|
||||||
|
|
2
Gemfile
2
Gemfile
|
@ -287,3 +287,5 @@ group :migrations, optional: true do
|
||||||
# CLI
|
# CLI
|
||||||
gem "ruby-progressbar"
|
gem "ruby-progressbar"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
gem "dry-initializer", "~> 3.1"
|
||||||
|
|
53
Gemfile.lock
53
Gemfile.lock
|
@ -132,9 +132,10 @@ GEM
|
||||||
literate_randomizer
|
literate_randomizer
|
||||||
docile (1.4.1)
|
docile (1.4.1)
|
||||||
drb (2.2.1)
|
drb (2.2.1)
|
||||||
|
dry-initializer (3.1.1)
|
||||||
email_reply_trimmer (0.1.13)
|
email_reply_trimmer (0.1.13)
|
||||||
erubi (1.13.0)
|
erubi (1.13.0)
|
||||||
excon (0.111.0)
|
excon (0.112.0)
|
||||||
execjs (2.9.1)
|
execjs (2.9.1)
|
||||||
exifr (1.4.0)
|
exifr (1.4.0)
|
||||||
extralite-bundle (2.8.2)
|
extralite-bundle (2.8.2)
|
||||||
|
@ -142,8 +143,9 @@ GEM
|
||||||
faker (2.23.0)
|
faker (2.23.0)
|
||||||
i18n (>= 1.8.11, < 2)
|
i18n (>= 1.8.11, < 2)
|
||||||
fakeweb (1.3.0)
|
fakeweb (1.3.0)
|
||||||
faraday (2.11.0)
|
faraday (2.12.0)
|
||||||
faraday-net_http (>= 2.0, < 3.4)
|
faraday-net_http (>= 2.0, < 3.4)
|
||||||
|
json
|
||||||
logger
|
logger
|
||||||
faraday-net_http (3.3.0)
|
faraday-net_http (3.3.0)
|
||||||
net-http
|
net-http
|
||||||
|
@ -158,16 +160,16 @@ GEM
|
||||||
fspath (3.1.2)
|
fspath (3.1.2)
|
||||||
globalid (1.2.1)
|
globalid (1.2.1)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
google-protobuf (4.28.1-aarch64-linux)
|
google-protobuf (4.28.2-aarch64-linux)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
rake (>= 13)
|
rake (>= 13)
|
||||||
google-protobuf (4.28.1-arm64-darwin)
|
google-protobuf (4.28.2-arm64-darwin)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
rake (>= 13)
|
rake (>= 13)
|
||||||
google-protobuf (4.28.1-x86_64-darwin)
|
google-protobuf (4.28.2-x86_64-darwin)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
rake (>= 13)
|
rake (>= 13)
|
||||||
google-protobuf (4.28.1-x86_64-linux)
|
google-protobuf (4.28.2-x86_64-linux)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
rake (>= 13)
|
rake (>= 13)
|
||||||
guess_html_encoding (0.0.11)
|
guess_html_encoding (0.0.11)
|
||||||
|
@ -178,7 +180,7 @@ GEM
|
||||||
reline
|
reline
|
||||||
htmlentities (4.3.4)
|
htmlentities (4.3.4)
|
||||||
http_accept_language (2.1.1)
|
http_accept_language (2.1.1)
|
||||||
i18n (1.14.5)
|
i18n (1.14.6)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
image_optim (0.31.3)
|
image_optim (0.31.3)
|
||||||
exifr (~> 1.2, >= 1.2.2)
|
exifr (~> 1.2, >= 1.2.2)
|
||||||
|
@ -189,7 +191,7 @@ GEM
|
||||||
image_size (3.4.0)
|
image_size (3.4.0)
|
||||||
in_threads (1.6.0)
|
in_threads (1.6.0)
|
||||||
io-console (0.7.2)
|
io-console (0.7.2)
|
||||||
irb (1.14.0)
|
irb (1.14.1)
|
||||||
rdoc (>= 4.0.0)
|
rdoc (>= 4.0.0)
|
||||||
reline (>= 0.4.2)
|
reline (>= 0.4.2)
|
||||||
iso8601 (0.13.0)
|
iso8601 (0.13.0)
|
||||||
|
@ -234,7 +236,7 @@ GEM
|
||||||
net-smtp
|
net-smtp
|
||||||
matrix (0.4.2)
|
matrix (0.4.2)
|
||||||
maxminddb (0.1.22)
|
maxminddb (0.1.22)
|
||||||
memory_profiler (1.0.2)
|
memory_profiler (1.1.0)
|
||||||
message_bus (4.3.8)
|
message_bus (4.3.8)
|
||||||
rack (>= 1.1.3)
|
rack (>= 1.1.3)
|
||||||
messageformat-wrapper (1.1.0)
|
messageformat-wrapper (1.1.0)
|
||||||
|
@ -344,7 +346,7 @@ GEM
|
||||||
psych (5.1.2)
|
psych (5.1.2)
|
||||||
stringio
|
stringio
|
||||||
public_suffix (6.0.1)
|
public_suffix (6.0.1)
|
||||||
puma (6.4.2)
|
puma (6.4.3)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rack (2.2.9)
|
rack (2.2.9)
|
||||||
|
@ -404,10 +406,10 @@ GEM
|
||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
request_store (1.7.0)
|
request_store (1.7.0)
|
||||||
rack (>= 1.4)
|
rack (>= 1.4)
|
||||||
rexml (3.3.7)
|
rexml (3.3.8)
|
||||||
rinku (2.0.6)
|
rinku (2.0.6)
|
||||||
rotp (6.3.0)
|
rotp (6.3.0)
|
||||||
rouge (4.3.0)
|
rouge (4.4.0)
|
||||||
rqrcode (2.2.0)
|
rqrcode (2.2.0)
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
rqrcode_core (~> 1.0)
|
rqrcode_core (~> 1.0)
|
||||||
|
@ -426,7 +428,7 @@ GEM
|
||||||
rspec-html-matchers (0.10.0)
|
rspec-html-matchers (0.10.0)
|
||||||
nokogiri (~> 1)
|
nokogiri (~> 1)
|
||||||
rspec (>= 3.0.0.a)
|
rspec (>= 3.0.0.a)
|
||||||
rspec-mocks (3.13.1)
|
rspec-mocks (3.13.2)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.13.0)
|
rspec-support (~> 3.13.0)
|
||||||
rspec-multi-mock (0.3.1)
|
rspec-multi-mock (0.3.1)
|
||||||
|
@ -473,12 +475,12 @@ GEM
|
||||||
rubocop-rspec_rails (>= 2.30.0)
|
rubocop-rspec_rails (>= 2.30.0)
|
||||||
rubocop-factory_bot (2.26.1)
|
rubocop-factory_bot (2.26.1)
|
||||||
rubocop (~> 1.61)
|
rubocop (~> 1.61)
|
||||||
rubocop-rails (2.26.1)
|
rubocop-rails (2.26.2)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
rubocop (>= 1.52.0, < 2.0)
|
rubocop (>= 1.52.0, < 2.0)
|
||||||
rubocop-ast (>= 1.31.1, < 2.0)
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
rubocop-rspec (3.0.5)
|
rubocop-rspec (3.1.0)
|
||||||
rubocop (~> 1.61)
|
rubocop (~> 1.61)
|
||||||
rubocop-rspec_rails (2.30.0)
|
rubocop-rspec_rails (2.30.0)
|
||||||
rubocop (~> 1.61)
|
rubocop (~> 1.61)
|
||||||
|
@ -503,9 +505,9 @@ GEM
|
||||||
google-protobuf (>= 3.25, < 5.0)
|
google-protobuf (>= 3.25, < 5.0)
|
||||||
sassc-embedded (1.77.7)
|
sassc-embedded (1.77.7)
|
||||||
sass-embedded (~> 1.77)
|
sass-embedded (~> 1.77)
|
||||||
selenium-devtools (0.128.0)
|
selenium-devtools (0.129.0)
|
||||||
selenium-webdriver (~> 4.2)
|
selenium-webdriver (~> 4.2)
|
||||||
selenium-webdriver (4.24.0)
|
selenium-webdriver (4.25.0)
|
||||||
base64 (~> 0.2)
|
base64 (~> 0.2)
|
||||||
logger (~> 1.4)
|
logger (~> 1.4)
|
||||||
rexml (~> 3.2, >= 3.2.5)
|
rexml (~> 3.2, >= 3.2.5)
|
||||||
|
@ -527,7 +529,7 @@ GEM
|
||||||
snaky_hash (2.0.1)
|
snaky_hash (2.0.1)
|
||||||
hashie
|
hashie
|
||||||
version_gem (~> 1.1, >= 1.1.1)
|
version_gem (~> 1.1, >= 1.1.1)
|
||||||
sprockets (3.7.4)
|
sprockets (3.7.5)
|
||||||
base64
|
base64
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
rack (> 1, < 3)
|
rack (> 1, < 3)
|
||||||
|
@ -535,10 +537,10 @@ GEM
|
||||||
actionpack (>= 6.1)
|
actionpack (>= 6.1)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
sprockets (>= 3.0.0)
|
sprockets (>= 3.0.0)
|
||||||
sqlite3 (2.0.4-aarch64-linux-gnu)
|
sqlite3 (2.1.0-aarch64-linux-gnu)
|
||||||
sqlite3 (2.0.4-arm64-darwin)
|
sqlite3 (2.1.0-arm64-darwin)
|
||||||
sqlite3 (2.0.4-x86_64-darwin)
|
sqlite3 (2.1.0-x86_64-darwin)
|
||||||
sqlite3 (2.0.4-x86_64-linux-gnu)
|
sqlite3 (2.1.0-x86_64-linux-gnu)
|
||||||
sshkey (3.0.0)
|
sshkey (3.0.0)
|
||||||
stackprof (0.2.26)
|
stackprof (0.2.26)
|
||||||
stringio (3.1.1)
|
stringio (3.1.1)
|
||||||
|
@ -553,10 +555,10 @@ GEM
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
tzinfo-data (1.2024.2)
|
tzinfo-data (1.2024.2)
|
||||||
tzinfo (>= 1.0.0)
|
tzinfo (>= 1.0.0)
|
||||||
uglifier (4.2.0)
|
uglifier (4.2.1)
|
||||||
execjs (>= 0.3.0, < 3)
|
execjs (>= 0.3.0, < 3)
|
||||||
unf (0.2.0)
|
unf (0.2.0)
|
||||||
unicode-display_width (2.5.0)
|
unicode-display_width (2.6.0)
|
||||||
unicorn (6.1.0)
|
unicorn (6.1.0)
|
||||||
kgio (~> 2.6)
|
kgio (~> 2.6)
|
||||||
raindrops (~> 0.7)
|
raindrops (~> 0.7)
|
||||||
|
@ -566,7 +568,7 @@ GEM
|
||||||
web-push (3.0.1)
|
web-push (3.0.1)
|
||||||
jwt (~> 2.0)
|
jwt (~> 2.0)
|
||||||
openssl (~> 3.0)
|
openssl (~> 3.0)
|
||||||
webmock (3.23.1)
|
webmock (3.24.0)
|
||||||
addressable (>= 2.8.0)
|
addressable (>= 2.8.0)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff (>= 0.4.0, < 2.0.0)
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
|
@ -623,6 +625,7 @@ DEPENDENCIES
|
||||||
discourse-fonts
|
discourse-fonts
|
||||||
discourse-seed-fu
|
discourse-seed-fu
|
||||||
discourse_dev_assets
|
discourse_dev_assets
|
||||||
|
dry-initializer (~> 3.1)
|
||||||
email_reply_trimmer
|
email_reply_trimmer
|
||||||
excon
|
excon
|
||||||
execjs
|
execjs
|
||||||
|
|
|
@ -82,7 +82,7 @@ export default class AdminConfigAreasAboutContactInformation extends Component {
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="communityOwner"
|
@name="communityOwner"
|
||||||
@title={{i18n "admin.config_areas.about.community_owner"}}
|
@title={{i18n "admin.config_areas.about.community_owner"}}
|
||||||
@subtitle={{i18n "admin.config_areas.about.community_owner_help"}}
|
@description={{i18n "admin.config_areas.about.community_owner_help"}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
>
|
>
|
||||||
|
@ -96,7 +96,7 @@ export default class AdminConfigAreasAboutContactInformation extends Component {
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="contactEmail"
|
@name="contactEmail"
|
||||||
@title={{i18n "admin.config_areas.about.contact_email"}}
|
@title={{i18n "admin.config_areas.about.contact_email"}}
|
||||||
@subtitle={{i18n "admin.config_areas.about.contact_email_help"}}
|
@description={{i18n "admin.config_areas.about.contact_email_help"}}
|
||||||
@type="email"
|
@type="email"
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
|
@ -111,7 +111,7 @@ export default class AdminConfigAreasAboutContactInformation extends Component {
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="contactURL"
|
@name="contactURL"
|
||||||
@title={{i18n "admin.config_areas.about.contact_url"}}
|
@title={{i18n "admin.config_areas.about.contact_url"}}
|
||||||
@subtitle={{i18n "admin.config_areas.about.contact_url_help"}}
|
@description={{i18n "admin.config_areas.about.contact_url_help"}}
|
||||||
@type="url"
|
@type="url"
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
|
@ -126,7 +126,7 @@ export default class AdminConfigAreasAboutContactInformation extends Component {
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="contactUsername"
|
@name="contactUsername"
|
||||||
@title={{i18n "admin.config_areas.about.site_contact_name"}}
|
@title={{i18n "admin.config_areas.about.site_contact_name"}}
|
||||||
@subtitle={{i18n "admin.config_areas.about.site_contact_name_help"}}
|
@description={{i18n "admin.config_areas.about.site_contact_name_help"}}
|
||||||
@onSet={{this.setContactUsername}}
|
@onSet={{this.setContactUsername}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
|
@ -143,7 +143,7 @@ export default class AdminConfigAreasAboutContactInformation extends Component {
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="contactGroupName"
|
@name="contactGroupName"
|
||||||
@title={{i18n "admin.config_areas.about.site_contact_group"}}
|
@title={{i18n "admin.config_areas.about.site_contact_group"}}
|
||||||
@subtitle={{i18n "admin.config_areas.about.site_contact_group_help"}}
|
@description={{i18n "admin.config_areas.about.site_contact_group_help"}}
|
||||||
@onSet={{this.setContactGroup}}
|
@onSet={{this.setContactGroup}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
|
|
|
@ -98,7 +98,7 @@ export default class AdminConfigAreasAboutGeneralSettings extends Component {
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="aboutBannerImage"
|
@name="aboutBannerImage"
|
||||||
@title={{i18n "admin.config_areas.about.banner_image"}}
|
@title={{i18n "admin.config_areas.about.banner_image"}}
|
||||||
@subtitle={{i18n "admin.config_areas.about.banner_image_help"}}
|
@description={{i18n "admin.config_areas.about.banner_image_help"}}
|
||||||
@onSet={{this.setImage}}
|
@onSet={{this.setImage}}
|
||||||
as |field|
|
as |field|
|
||||||
>
|
>
|
||||||
|
|
|
@ -58,7 +58,7 @@ export default class AdminConfigAreasAboutYourOrganization extends Component {
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="companyName"
|
@name="companyName"
|
||||||
@title={{i18n "admin.config_areas.about.company_name"}}
|
@title={{i18n "admin.config_areas.about.company_name"}}
|
||||||
@subtitle={{i18n "admin.config_areas.about.company_name_help"}}
|
@description={{i18n "admin.config_areas.about.company_name_help"}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
>
|
>
|
||||||
|
@ -75,7 +75,7 @@ export default class AdminConfigAreasAboutYourOrganization extends Component {
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="governingLaw"
|
@name="governingLaw"
|
||||||
@title={{i18n "admin.config_areas.about.governing_law"}}
|
@title={{i18n "admin.config_areas.about.governing_law"}}
|
||||||
@subtitle={{i18n "admin.config_areas.about.governing_law_help"}}
|
@description={{i18n "admin.config_areas.about.governing_law_help"}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
>
|
>
|
||||||
|
@ -89,7 +89,7 @@ export default class AdminConfigAreasAboutYourOrganization extends Component {
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="cityForDisputes"
|
@name="cityForDisputes"
|
||||||
@title={{i18n "admin.config_areas.about.city_for_disputes"}}
|
@title={{i18n "admin.config_areas.about.city_for_disputes"}}
|
||||||
@subtitle={{i18n "admin.config_areas.about.city_for_disputes_help"}}
|
@description={{i18n "admin.config_areas.about.city_for_disputes_help"}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
>
|
>
|
||||||
|
|
|
@ -61,7 +61,7 @@ export default class AdminFilteredSiteSettings extends Component {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ConditionalLoadingSpinner @condition={{this.loading}}>
|
<ConditionalLoadingSpinner @condition={{this.loading}}>
|
||||||
<section class="form-horizontal settings">
|
<section class="admin-filtered-site-settings form-horizontal settings">
|
||||||
{{#each this.visibleSettings as |setting|}}
|
{{#each this.visibleSettings as |setting|}}
|
||||||
<SiteSetting @setting={{setting}} />
|
<SiteSetting @setting={{setting}} />
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { htmlSafe } from "@ember/template";
|
||||||
|
import DButton from "discourse/components/d-button";
|
||||||
|
import icon from "discourse-common/helpers/d-icon";
|
||||||
|
|
||||||
|
export default class AdminNotice extends Component {
|
||||||
|
@action
|
||||||
|
dismiss() {
|
||||||
|
this.args.dismissCallback(this.args.problem);
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="notice">
|
||||||
|
<div class="message">
|
||||||
|
{{if @icon (icon @icon)}}
|
||||||
|
{{htmlSafe @problem.message}}
|
||||||
|
</div>
|
||||||
|
<DButton
|
||||||
|
@action={{this.dismiss}}
|
||||||
|
@label="admin.dashboard.dismiss_notice"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
}
|
|
@ -24,6 +24,10 @@ export default class AdminPluginConfigPage extends Component {
|
||||||
return classes.join(" ");
|
return classes.join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get actionsOutletName() {
|
||||||
|
return `admin-plugin-config-page-actions-${this.args.plugin.kebabCaseName}`;
|
||||||
|
}
|
||||||
|
|
||||||
linkText(navLink) {
|
linkText(navLink) {
|
||||||
if (navLink.label) {
|
if (navLink.label) {
|
||||||
return i18n(navLink.label);
|
return i18n(navLink.label);
|
||||||
|
@ -68,10 +72,12 @@ export default class AdminPluginConfigPage extends Component {
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</:tabs>
|
</:tabs>
|
||||||
<:actions as |actions|>
|
<:actions as |actions|>
|
||||||
<PluginOutlet
|
<div class={{this.actionsOutletName}}>
|
||||||
@name="admin-plugin-config-page-actions"
|
<PluginOutlet
|
||||||
@outletArgs={{hash plugin=@plugin actions=actions}}
|
@name={{this.actionsOutletName}}
|
||||||
/>
|
@outletArgs={{hash plugin=@plugin actions=actions}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</:actions>
|
</:actions>
|
||||||
</AdminPageHeader>
|
</AdminPageHeader>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { hash } from "@ember/helper";
|
||||||
|
import { LinkTo } from "@ember/routing";
|
||||||
|
import concatClass from "discourse/helpers/concat-class";
|
||||||
|
import dIcon from "discourse-common/helpers/d-icon";
|
||||||
|
import i18n from "discourse-common/helpers/i18n";
|
||||||
|
import {
|
||||||
|
DangerButton,
|
||||||
|
DefaultButton,
|
||||||
|
PrimaryButton,
|
||||||
|
} from "admin/components/admin-page-action-button";
|
||||||
|
|
||||||
|
export default class AdminSectionLandingItem extends Component {
|
||||||
|
get title() {
|
||||||
|
if (this.args.titleLabelTranslated) {
|
||||||
|
return this.args.titleLabelTranslated;
|
||||||
|
} else if (this.args.titleLabel) {
|
||||||
|
return i18n(this.args.titleLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
if (this.args.descriptionLabelTranslated) {
|
||||||
|
return this.args.descriptionLabelTranslated;
|
||||||
|
} else if (this.args.descriptionLabel) {
|
||||||
|
return i18n(this.args.descriptionLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get tagline() {
|
||||||
|
if (this.args.taglineLabelTranslated) {
|
||||||
|
return this.args.taglineLabelTranslated;
|
||||||
|
} else if (this.args.taglineLabel) {
|
||||||
|
return i18n(this.args.taglineLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class={{concatClass "admin-section-landing-item" (if @icon "-has-icon")}}
|
||||||
|
...attributes
|
||||||
|
>
|
||||||
|
{{#if @imageUrl}}
|
||||||
|
<img class="admin-section-landing-item__image" src={{@imageUrl}} />
|
||||||
|
{{/if}}
|
||||||
|
{{#if @icon}}
|
||||||
|
<div class="admin-section-landing-item__icon">
|
||||||
|
{{dIcon @icon}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
<div class="admin-section-landing-item__content">
|
||||||
|
{{#if this.tagline}}
|
||||||
|
<h4 class="admin-section-landing-item__tagline">{{this.tagline}}</h4>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if @titleRoute}}
|
||||||
|
<LinkTo @route={{@titleRoute}}><h3
|
||||||
|
class="admin-section-landing-item__title"
|
||||||
|
>{{this.title}}</h3></LinkTo>
|
||||||
|
{{else}}
|
||||||
|
<h3 class="admin-section-landing-item__title">{{this.title}}</h3>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<p
|
||||||
|
class="admin-section-landing-item__description"
|
||||||
|
>{{this.description}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="admin-section-landing-item__buttons">
|
||||||
|
{{yield
|
||||||
|
(hash Primary=PrimaryButton Default=DefaultButton Danger=DangerButton)
|
||||||
|
to="buttons"
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
const AdminSectionLandingWrapper = <template>
|
||||||
|
<div class="admin-section-landing-wrapper" ...attributes>
|
||||||
|
{{yield}}
|
||||||
|
</div>
|
||||||
|
</template>;
|
||||||
|
|
||||||
|
export default AdminSectionLandingWrapper;
|
|
@ -0,0 +1,78 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { concat } from "@ember/helper";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { eq } from "truth-helpers";
|
||||||
|
import ConditionalLoadingSection from "discourse/components/conditional-loading-section";
|
||||||
|
import DButton from "discourse/components/d-button";
|
||||||
|
import concatClass from "discourse/helpers/concat-class";
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import icon from "discourse-common/helpers/d-icon";
|
||||||
|
import i18n from "discourse-common/helpers/i18n";
|
||||||
|
import AdminNotice from "admin/components/admin-notice";
|
||||||
|
|
||||||
|
export default class DashboardProblems extends Component {
|
||||||
|
@action
|
||||||
|
async dismissProblem(problem) {
|
||||||
|
try {
|
||||||
|
await ajax(`/admin/admin_notices/${problem.id}`, { type: "DELETE" });
|
||||||
|
this.args.problems.removeObject(problem);
|
||||||
|
} catch (error) {
|
||||||
|
popupAjaxError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get problems() {
|
||||||
|
return this.args.problems.sortBy("priority");
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
{{#if @problems.length}}
|
||||||
|
<div class="section dashboard-problems">
|
||||||
|
<div class="section-title">
|
||||||
|
<h2>
|
||||||
|
{{icon "heart"}}
|
||||||
|
{{i18n "admin.dashboard.problems_found"}}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-body">
|
||||||
|
<ConditionalLoadingSection @isLoading={{@loadingProblems}}>
|
||||||
|
<div class="problem-messages">
|
||||||
|
<ul>
|
||||||
|
{{#each this.problems as |problem|}}
|
||||||
|
<li
|
||||||
|
class={{concatClass
|
||||||
|
"dashboard-problem"
|
||||||
|
(concat "priority-" problem.priority)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AdminNotice
|
||||||
|
@icon={{if
|
||||||
|
(eq problem.priority "high")
|
||||||
|
"triangle-exclamation"
|
||||||
|
}}
|
||||||
|
@problem={{problem}}
|
||||||
|
@dismissCallback={{this.dismissProblem}}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="actions">
|
||||||
|
<DButton
|
||||||
|
@action={{@refreshProblems}}
|
||||||
|
@icon="arrows-rotate"
|
||||||
|
@label="admin.dashboard.refresh_problems"
|
||||||
|
class="btn-default"
|
||||||
|
/>
|
||||||
|
{{i18n "admin.dashboard.last_checked"}}:
|
||||||
|
{{@problemsTimestamp}}
|
||||||
|
</p>
|
||||||
|
</ConditionalLoadingSection>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</template>
|
||||||
|
}
|
|
@ -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 "triangle-exclamation"}}
|
|
||||||
{{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}}
|
|
||||||
@icon="arrows-rotate"
|
|
||||||
@label="admin.dashboard.refresh_problems"
|
|
||||||
class="btn-default"
|
|
||||||
/>
|
|
||||||
{{i18n "admin.dashboard.last_checked"}}:
|
|
||||||
{{this.problemsTimestamp}}
|
|
||||||
</p>
|
|
||||||
</ConditionalLoadingSection>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
|
@ -1,3 +0,0 @@
|
||||||
import Component from "@ember/component";
|
|
||||||
|
|
||||||
export default class DashboardProblems extends Component {}
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { service } from "@ember/service";
|
||||||
|
import { modifier } from "ember-modifier";
|
||||||
|
import highlightSyntax from "discourse/lib/highlight-syntax";
|
||||||
|
|
||||||
|
export default class HighlightedCode extends Component {
|
||||||
|
@service session;
|
||||||
|
@service siteSettings;
|
||||||
|
|
||||||
|
highlight = modifier(async (element) => {
|
||||||
|
const code = document.createElement("code");
|
||||||
|
code.classList.add(`lang-${this.args.lang}`);
|
||||||
|
code.textContent = this.args.code;
|
||||||
|
|
||||||
|
const pre = document.createElement("pre");
|
||||||
|
pre.appendChild(code);
|
||||||
|
|
||||||
|
element.replaceChildren(pre);
|
||||||
|
await highlightSyntax(pre, this.siteSettings, this.session);
|
||||||
|
});
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div {{this.highlight}}></div>
|
||||||
|
</template>
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
<pre><code class="lang-{{this.lang}}">{{this.code}}</code></pre>
|
|
|
@ -1,11 +0,0 @@
|
||||||
import Component from "@ember/component";
|
|
||||||
import { observes, on } from "@ember-decorators/object";
|
|
||||||
import highlightSyntax from "discourse/lib/highlight-syntax";
|
|
||||||
|
|
||||||
export default class HighlightedCode extends Component {
|
|
||||||
@on("didInsertElement")
|
|
||||||
@observes("code")
|
|
||||||
_refresh() {
|
|
||||||
highlightSyntax(this.element, this.siteSettings, this.session);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,16 +18,6 @@ export default class AdminDashboardController extends Controller {
|
||||||
|
|
||||||
@setting("version_checks") showVersionChecks;
|
@setting("version_checks") showVersionChecks;
|
||||||
|
|
||||||
@discourseComputed(
|
|
||||||
"lowPriorityProblems.length",
|
|
||||||
"highPriorityProblems.length"
|
|
||||||
)
|
|
||||||
foundProblems(lowPriorityProblemsLength, highPriorityProblemsLength) {
|
|
||||||
const problemsLength =
|
|
||||||
lowPriorityProblemsLength + highPriorityProblemsLength;
|
|
||||||
return this.currentUser.admin && problemsLength > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed("siteSettings.dashboard_visible_tabs")
|
@computed("siteSettings.dashboard_visible_tabs")
|
||||||
get visibleTabs() {
|
get visibleTabs() {
|
||||||
return (this.siteSettings.dashboard_visible_tabs || "")
|
return (this.siteSettings.dashboard_visible_tabs || "")
|
||||||
|
@ -106,16 +96,7 @@ export default class AdminDashboardController extends Controller {
|
||||||
});
|
});
|
||||||
|
|
||||||
AdminDashboard.fetchProblems()
|
AdminDashboard.fetchProblems()
|
||||||
.then((model) => {
|
.then((model) => this.set("problems", model.problems))
|
||||||
this.set(
|
|
||||||
"highPriorityProblems",
|
|
||||||
model.problems.filterBy("priority", "high")
|
|
||||||
);
|
|
||||||
this.set(
|
|
||||||
"lowPriorityProblems",
|
|
||||||
model.problems.filterBy("priority", "low")
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.finally(() => this.set("loadingProblems", false));
|
.finally(() => this.set("loadingProblems", false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -78,8 +78,8 @@ export default class AdminUserIndexController extends Controller.extend(
|
||||||
@discourseComputed("model.associated_accounts")
|
@discourseComputed("model.associated_accounts")
|
||||||
associatedAccounts(associatedAccounts) {
|
associatedAccounts(associatedAccounts) {
|
||||||
return associatedAccounts
|
return associatedAccounts
|
||||||
.map((provider) => `${provider.name} (${provider.description})`)
|
?.map((provider) => `${provider.name} (${provider.description})`)
|
||||||
.join(", ");
|
?.join(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
@discourseComputed("model.user_fields.[]")
|
@discourseComputed("model.user_fields.[]")
|
||||||
|
@ -319,6 +319,16 @@ export default class AdminUserIndexController extends Controller.extend(
|
||||||
return this.model.silence();
|
return this.model.silence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
deleteAssociatedAccounts() {
|
||||||
|
this.dialog.yesNoConfirm({
|
||||||
|
message: I18n.t("admin.user.delete_associated_accounts_confirm"),
|
||||||
|
didConfirm: () => {
|
||||||
|
this.model.deleteAssociatedAccounts().catch(popupAjaxError);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
anonymize() {
|
anonymize() {
|
||||||
const user = this.model;
|
const user = this.model;
|
||||||
|
|
|
@ -24,6 +24,10 @@ export default class AdminPlugin {
|
||||||
return this.name.replaceAll("-", "_");
|
return this.name.replaceAll("-", "_");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get kebabCaseName() {
|
||||||
|
return this.name.replaceAll(" ", "-").replaceAll("_", "-");
|
||||||
|
}
|
||||||
|
|
||||||
get translatedCategoryName() {
|
get translatedCategoryName() {
|
||||||
// We do this because the site setting list is grouped by category,
|
// We do this because the site setting list is grouped by category,
|
||||||
// with plugins that have their root site setting key defined as `plugins:`
|
// with plugins that have their root site setting key defined as `plugins:`
|
||||||
|
|
|
@ -287,6 +287,17 @@ export default class AdminUser extends User {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteAssociatedAccounts() {
|
||||||
|
return ajax(`/admin/users/${this.id}/delete_associated_accounts`, {
|
||||||
|
type: "PUT",
|
||||||
|
data: {
|
||||||
|
context: window.location.pathname,
|
||||||
|
},
|
||||||
|
}).then(() => {
|
||||||
|
this.set("associated_accounts", []);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
destroy(formData) {
|
destroy(formData) {
|
||||||
return ajax(`/admin/users/${this.id}.json`, {
|
return ajax(`/admin/users/${this.id}.json`, {
|
||||||
type: "DELETE",
|
type: "DELETE",
|
||||||
|
|
|
@ -239,5 +239,13 @@ export default function () {
|
||||||
path: "/whats-new",
|
path: "/whats-new",
|
||||||
resetNamespace: true,
|
resetNamespace: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.route(
|
||||||
|
"adminSection",
|
||||||
|
{ path: "/section", resetNamespace: true },
|
||||||
|
function () {
|
||||||
|
this.route("account");
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,7 @@
|
||||||
|
|
||||||
<DashboardProblems
|
<DashboardProblems
|
||||||
@loadingProblems={{this.loadingProblems}}
|
@loadingProblems={{this.loadingProblems}}
|
||||||
@foundProblems={{this.foundProblems}}
|
@problems={{this.problems}}
|
||||||
@lowPriorityProblems={{this.lowPriorityProblems}}
|
|
||||||
@highPriorityProblems={{this.highPriorityProblems}}
|
|
||||||
@problemsTimestamp={{this.problemsTimestamp}}
|
@problemsTimestamp={{this.problemsTimestamp}}
|
||||||
@refreshProblems={{action "refreshProblems"}}
|
@refreshProblems={{action "refreshProblems"}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<AdminPageHeader
|
||||||
|
@titleLabel="admin.section_landing_pages.account.title"
|
||||||
|
@hideTabs={{true}}
|
||||||
|
>
|
||||||
|
<:breadcrumbs>
|
||||||
|
<DBreadcrumbsItem
|
||||||
|
@path="/admin/section/account"
|
||||||
|
@label={{i18n "admin.section_landing_pages.account.title"}}
|
||||||
|
/>
|
||||||
|
</:breadcrumbs>
|
||||||
|
</AdminPageHeader>
|
||||||
|
|
||||||
|
<AdminSectionLandingWrapper>
|
||||||
|
<AdminSectionLandingItem
|
||||||
|
@icon="box-archive"
|
||||||
|
@titleLabel="admin.section_landing_pages.account.backups.title"
|
||||||
|
@descriptionLabel="admin.section_landing_pages.account.backups.description"
|
||||||
|
@titleRoute="admin.backups"
|
||||||
|
/>
|
||||||
|
<AdminSectionLandingItem
|
||||||
|
@icon="gift"
|
||||||
|
@titleLabel="admin.section_landing_pages.account.whats_new.title"
|
||||||
|
@descriptionLabel="admin.section_landing_pages.account.whats_new.description"
|
||||||
|
@titleRoute="admin.whatsNew"
|
||||||
|
/>
|
||||||
|
</AdminSectionLandingWrapper>
|
|
@ -157,6 +157,17 @@
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
{{#if (and this.currentUser.admin this.associatedAccounts)}}
|
||||||
|
<div class="controls">
|
||||||
|
<DButton
|
||||||
|
@action={{this.deleteAssociatedAccounts}}
|
||||||
|
@icon="trash-can"
|
||||||
|
@label="admin.users.delete_associated_accounts.text"
|
||||||
|
@title="admin.users.delete_associated_accounts.title"
|
||||||
|
class="btn-danger"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
|
|
@ -14,32 +14,32 @@
|
||||||
"start": "ember serve"
|
"start": "ember serve"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.7",
|
||||||
"@ember/string": "^4.0.0",
|
"@ember/string": "^4.0.0",
|
||||||
"discourse-common": "workspace:1.0.0",
|
"discourse-common": "workspace:1.0.0",
|
||||||
"ember-cli-babel": "^8.2.0",
|
"ember-cli-babel": "^8.2.0",
|
||||||
"ember-cli-htmlbars": "^6.3.0",
|
"ember-cli-htmlbars": "^6.3.0",
|
||||||
"ember-template-imports": "^4.1.1"
|
"ember-template-imports": "^4.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ember/optional-features": "^2.1.0",
|
"@ember/optional-features": "^2.1.0",
|
||||||
"@embroider/test-setup": "^4.0.0",
|
"@embroider/test-setup": "^4.0.0",
|
||||||
"@glimmer/component": "^1.1.2",
|
"@glimmer/component": "^1.1.2",
|
||||||
"@types/jquery": "^3.5.30",
|
"@types/jquery": "^3.5.31",
|
||||||
"@types/qunit": "^2.19.10",
|
"@types/qunit": "^2.19.10",
|
||||||
"@types/rsvp": "^4.0.9",
|
"@types/rsvp": "^4.0.9",
|
||||||
"broccoli-asset-rev": "^3.0.0",
|
"broccoli-asset-rev": "^3.0.0",
|
||||||
"ember-cli": "~5.11.0",
|
"ember-cli": "~5.12.0",
|
||||||
"ember-cli-inject-live-reload": "^2.1.0",
|
"ember-cli-inject-live-reload": "^2.1.0",
|
||||||
"ember-cli-sri": "^2.1.1",
|
"ember-cli-sri": "^2.1.1",
|
||||||
"ember-cli-terser": "^4.0.2",
|
"ember-cli-terser": "^4.0.2",
|
||||||
"ember-disable-prototype-extensions": "^1.1.3",
|
"ember-disable-prototype-extensions": "^1.1.3",
|
||||||
"ember-load-initializers": "^2.1.1",
|
"ember-load-initializers": "^3.0.1",
|
||||||
"ember-resolver": "^13.0.0",
|
"ember-resolver": "^13.0.2",
|
||||||
"ember-source": "~5.5.0",
|
"ember-source": "~5.5.0",
|
||||||
"ember-source-channel-url": "^3.0.0",
|
"ember-source-channel-url": "^3.0.0",
|
||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"webpack": "^5.94.0"
|
"webpack": "^5.95.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18",
|
"node": ">= 18",
|
||||||
|
|
|
@ -9,17 +9,17 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"a11y-dialog": "8.1.1",
|
"a11y-dialog": "8.1.1",
|
||||||
"ember-auto-import": "^2.7.4",
|
"ember-auto-import": "^2.8.1",
|
||||||
"ember-cli-babel": "^8.2.0",
|
"ember-cli-babel": "^8.2.0",
|
||||||
"ember-cli-htmlbars": "^6.3.0",
|
"ember-cli-htmlbars": "^6.3.0",
|
||||||
"ember-template-imports": "^4.1.1",
|
"ember-template-imports": "^4.1.2",
|
||||||
"truth-helpers": "workspace:1.0.0"
|
"truth-helpers": "workspace:1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jquery": "^3.5.30",
|
"@types/jquery": "^3.5.31",
|
||||||
"@types/qunit": "^2.19.10",
|
"@types/qunit": "^2.19.10",
|
||||||
"@types/rsvp": "^4.0.9",
|
"@types/rsvp": "^4.0.9",
|
||||||
"webpack": "^5.94.0"
|
"webpack": "^5.95.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18",
|
"node": ">= 18",
|
||||||
|
|
|
@ -44,11 +44,12 @@ export function registerRawHelpers(hbs, handlebarsClass, owner) {
|
||||||
}
|
}
|
||||||
let list = get(this, contextName);
|
let list = get(this, contextName);
|
||||||
let output = [];
|
let output = [];
|
||||||
let innerContext = { ...options.contexts[0] };
|
let innerContext = options.contexts[0];
|
||||||
for (let i = 0; i < list.length; i++) {
|
for (let i = 0; i < list.length; i++) {
|
||||||
innerContext[localName] = list[i];
|
innerContext[localName] = list[i];
|
||||||
output.push(options.fn(innerContext));
|
output.push(options.fn(innerContext));
|
||||||
}
|
}
|
||||||
|
delete innerContext[localName];
|
||||||
return output.join("");
|
return output.join("");
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
"start": "ember serve"
|
"start": "ember serve"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.7",
|
||||||
"@ember/string": "^4.0.0",
|
"@ember/string": "^4.0.0",
|
||||||
"@uppy/aws-s3": "3.0.6",
|
"@uppy/aws-s3": "3.0.6",
|
||||||
"@uppy/aws-s3-multipart": "3.1.3",
|
"@uppy/aws-s3-multipart": "3.1.3",
|
||||||
|
@ -23,10 +23,10 @@
|
||||||
"@uppy/utils": "5.4.3",
|
"@uppy/utils": "5.4.3",
|
||||||
"@uppy/xhr-upload": "3.1.1",
|
"@uppy/xhr-upload": "3.1.1",
|
||||||
"discourse-i18n": "workspace:1.0.0",
|
"discourse-i18n": "workspace:1.0.0",
|
||||||
"ember-auto-import": "^2.7.4",
|
"ember-auto-import": "^2.8.1",
|
||||||
"ember-cli-babel": "^8.2.0",
|
"ember-cli-babel": "^8.2.0",
|
||||||
"ember-cli-htmlbars": "^6.3.0",
|
"ember-cli-htmlbars": "^6.3.0",
|
||||||
"ember-resolver": "^13.0.0",
|
"ember-resolver": "^13.0.2",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"truth-helpers": "workspace:1.0.0"
|
"truth-helpers": "workspace:1.0.0"
|
||||||
},
|
},
|
||||||
|
@ -34,20 +34,20 @@
|
||||||
"@ember/optional-features": "^2.1.0",
|
"@ember/optional-features": "^2.1.0",
|
||||||
"@embroider/test-setup": "^4.0.0",
|
"@embroider/test-setup": "^4.0.0",
|
||||||
"@glimmer/component": "^1.1.2",
|
"@glimmer/component": "^1.1.2",
|
||||||
"@types/jquery": "^3.5.30",
|
"@types/jquery": "^3.5.31",
|
||||||
"@types/qunit": "^2.19.10",
|
"@types/qunit": "^2.19.10",
|
||||||
"@types/rsvp": "^4.0.9",
|
"@types/rsvp": "^4.0.9",
|
||||||
"broccoli-asset-rev": "^3.0.0",
|
"broccoli-asset-rev": "^3.0.0",
|
||||||
"ember-cli": "~5.11.0",
|
"ember-cli": "~5.12.0",
|
||||||
"ember-cli-inject-live-reload": "^2.1.0",
|
"ember-cli-inject-live-reload": "^2.1.0",
|
||||||
"ember-cli-sri": "^2.1.1",
|
"ember-cli-sri": "^2.1.1",
|
||||||
"ember-cli-terser": "^4.0.2",
|
"ember-cli-terser": "^4.0.2",
|
||||||
"ember-disable-prototype-extensions": "^1.1.3",
|
"ember-disable-prototype-extensions": "^1.1.3",
|
||||||
"ember-load-initializers": "^2.1.1",
|
"ember-load-initializers": "^3.0.1",
|
||||||
"ember-source": "~5.5.0",
|
"ember-source": "~5.5.0",
|
||||||
"ember-source-channel-url": "^3.0.0",
|
"ember-source-channel-url": "^3.0.0",
|
||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"webpack": "^5.94.0"
|
"webpack": "^5.95.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18",
|
"node": ">= 18",
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
"@embroider/addon-shim": "^1.8.9",
|
"@embroider/addon-shim": "^1.8.9",
|
||||||
"discourse-common": "workspace:1.0.0",
|
"discourse-common": "workspace:1.0.0",
|
||||||
"discourse-i18n": "workspace:1.0.0",
|
"discourse-i18n": "workspace:1.0.0",
|
||||||
"ember-auto-import": "^2.7.4",
|
"ember-auto-import": "^2.8.1",
|
||||||
"markdown-it": "14.0.0",
|
"markdown-it": "14.0.0",
|
||||||
"pretty-text": "workspace:1.0.0",
|
"pretty-text": "workspace:1.0.0",
|
||||||
"truth-helpers": "workspace:1.0.0",
|
"truth-helpers": "workspace:1.0.0",
|
||||||
|
|
|
@ -8,18 +8,18 @@
|
||||||
"ember-addon"
|
"ember-addon"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.7",
|
||||||
"deprecation-silencer": "workspace:1.0.0",
|
"deprecation-silencer": "workspace:1.0.0",
|
||||||
"discourse-hbr": "workspace:1.0.0",
|
"discourse-hbr": "workspace:1.0.0",
|
||||||
"discourse-widget-hbs": "workspace:1.0.0",
|
"discourse-widget-hbs": "workspace:1.0.0",
|
||||||
"ember-cli-babel": "^8.2.0",
|
"ember-cli-babel": "^8.2.0",
|
||||||
"ember-cli-htmlbars": "^6.3.0",
|
"ember-cli-htmlbars": "^6.3.0",
|
||||||
"ember-template-imports": "^4.1.1",
|
"ember-template-imports": "^4.1.2",
|
||||||
"ember-this-fallback": "^0.4.0"
|
"ember-this-fallback": "^0.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"ember-cli": "~5.11.0",
|
"ember-cli": "~5.12.0",
|
||||||
"webpack": "^5.94.0"
|
"webpack": "^5.95.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18",
|
"node": ">= 18",
|
||||||
|
|
|
@ -14,8 +14,8 @@
|
||||||
"start": "ember serve"
|
"start": "ember serve"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.7",
|
||||||
"ember-auto-import": "^2.7.4",
|
"ember-auto-import": "^2.8.1",
|
||||||
"ember-cli-babel": "^8.2.0",
|
"ember-cli-babel": "^8.2.0",
|
||||||
"ember-cli-htmlbars": "^6.3.0",
|
"ember-cli-htmlbars": "^6.3.0",
|
||||||
"handlebars": "^4.7.8"
|
"handlebars": "^4.7.8"
|
||||||
|
@ -26,17 +26,17 @@
|
||||||
"@glimmer/component": "^1.1.2",
|
"@glimmer/component": "^1.1.2",
|
||||||
"@glimmer/syntax": "^0.92.3",
|
"@glimmer/syntax": "^0.92.3",
|
||||||
"broccoli-asset-rev": "^3.0.0",
|
"broccoli-asset-rev": "^3.0.0",
|
||||||
"ember-cli": "~5.11.0",
|
"ember-cli": "~5.12.0",
|
||||||
"ember-cli-inject-live-reload": "^2.1.0",
|
"ember-cli-inject-live-reload": "^2.1.0",
|
||||||
"ember-cli-sri": "^2.1.1",
|
"ember-cli-sri": "^2.1.1",
|
||||||
"ember-cli-terser": "^4.0.2",
|
"ember-cli-terser": "^4.0.2",
|
||||||
"ember-disable-prototype-extensions": "^1.1.3",
|
"ember-disable-prototype-extensions": "^1.1.3",
|
||||||
"ember-load-initializers": "^2.1.1",
|
"ember-load-initializers": "^3.0.1",
|
||||||
"ember-resolver": "^13.0.0",
|
"ember-resolver": "^13.0.2",
|
||||||
"ember-source": "~5.5.0",
|
"ember-source": "~5.5.0",
|
||||||
"ember-source-channel-url": "^3.0.0",
|
"ember-source-channel-url": "^3.0.0",
|
||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"webpack": "^5.94.0"
|
"webpack": "^5.95.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18",
|
"node": ">= 18",
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { tracked } from "@glimmer/tracking";
|
||||||
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||||
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
|
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import { registerWaiter } from "@ember/test";
|
import { buildWaiter } from "@ember/test-waiters";
|
||||||
import { modifier } from "ember-modifier";
|
import { modifier } from "ember-modifier";
|
||||||
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
|
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
|
||||||
import loadAce from "discourse/lib/load-ace-editor";
|
import loadAce from "discourse/lib/load-ace-editor";
|
||||||
|
@ -11,6 +11,7 @@ import { isTesting } from "discourse-common/config/environment";
|
||||||
import { bind } from "discourse-common/utils/decorators";
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
|
|
||||||
|
const WAITER = buildWaiter("ace-editor");
|
||||||
const COLOR_VARS_REGEX =
|
const COLOR_VARS_REGEX =
|
||||||
/\$(primary|secondary|tertiary|quaternary|header_background|header_primary|highlight|danger|success|love)(\s|;|-(low|medium|high))/g;
|
/\$(primary|secondary|tertiary|quaternary|header_background|header_primary|highlight|danger|success|love)(\s|;|-(low|medium|high))/g;
|
||||||
|
|
||||||
|
@ -72,11 +73,10 @@ export default class AceEditor extends Component {
|
||||||
this.editor.getSession().setValue(this.args.content || "");
|
this.editor.getSession().setValue(this.args.content || "");
|
||||||
this.skipChangePropagation = false;
|
this.skipChangePropagation = false;
|
||||||
|
|
||||||
if (isTesting()) {
|
const token = WAITER.beginAsync();
|
||||||
let finished = false;
|
this.editor.renderer.once("afterRender", () => WAITER.endAsync(token));
|
||||||
registerWaiter(() => finished);
|
|
||||||
this.editor.renderer.once("afterRender", () => (finished = true));
|
return () => WAITER.endAsync(token);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -94,7 +94,7 @@ export default class AceEditor extends Component {
|
||||||
this.appEvents.on("ace:resize", this.resize);
|
this.appEvents.on("ace:resize", this.resize);
|
||||||
window.addEventListener("resize", this.resize);
|
window.addEventListener("resize", this.resize);
|
||||||
this._darkModeListener = window.matchMedia("(prefers-color-scheme: dark)");
|
this._darkModeListener = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
this._darkModeListener.addListener(this.setAceTheme);
|
this._darkModeListener.addEventListener("change", this.setAceTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
willDestroy() {
|
willDestroy() {
|
||||||
|
@ -102,7 +102,7 @@ export default class AceEditor extends Component {
|
||||||
|
|
||||||
this.editor?.destroy();
|
this.editor?.destroy();
|
||||||
|
|
||||||
this._darkModeListener?.removeListener(this.setAceTheme);
|
this._darkModeListener?.removeEventListener("change", this.setAceTheme);
|
||||||
window.removeEventListener("resize", this.resize);
|
window.removeEventListener("resize", this.resize);
|
||||||
this.appEvents.off("ace:resize", this.resize);
|
this.appEvents.off("ace:resize", this.resize);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,100 +1,110 @@
|
||||||
{{#each this.categories as |c|}}
|
<PluginOutlet
|
||||||
<PluginOutlet
|
@name="categories-boxes-wrapper"
|
||||||
@name="category-box-before-each-box"
|
@outletArgs={{hash categories=this.categories}}
|
||||||
@outletArgs={{hash category=c}}
|
>
|
||||||
/>
|
{{#each this.categories as |c|}}
|
||||||
|
<PluginOutlet
|
||||||
<div
|
@name="category-box-before-each-box"
|
||||||
style={{category-color-variable c.color}}
|
@outletArgs={{hash category=c}}
|
||||||
data-category-id={{c.id}}
|
/>
|
||||||
data-notification-level={{c.notificationLevelString}}
|
|
||||||
data-url={{c.url}}
|
|
||||||
class="category category-box category-box-{{c.slug}}
|
|
||||||
{{if c.isMuted 'muted'}}"
|
|
||||||
>
|
|
||||||
<div class="category-box-inner">
|
|
||||||
{{#unless c.isMuted}}
|
|
||||||
<div class="category-logo">
|
|
||||||
{{#if c.uploaded_logo.url}}
|
|
||||||
<CategoryLogo @category={{c}} />
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/unless}}
|
|
||||||
|
|
||||||
<div class="category-details">
|
|
||||||
<div class="category-box-heading">
|
|
||||||
<a class="parent-box-link" href={{c.url}}>
|
|
||||||
<h3>
|
|
||||||
<CategoryTitleBefore @category={{c}} />
|
|
||||||
{{#if c.read_restricted}}
|
|
||||||
{{d-icon this.lockIcon}}
|
|
||||||
{{/if}}
|
|
||||||
{{c.displayName}}
|
|
||||||
</h3>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{category-color-variable c.color}}
|
||||||
|
data-category-id={{c.id}}
|
||||||
|
data-notification-level={{c.notificationLevelString}}
|
||||||
|
data-url={{c.url}}
|
||||||
|
class="category category-box category-box-{{c.slug}}
|
||||||
|
{{if c.isMuted 'muted'}}"
|
||||||
|
>
|
||||||
|
<div class="category-box-inner">
|
||||||
{{#unless c.isMuted}}
|
{{#unless c.isMuted}}
|
||||||
<div class="description">
|
<div class="category-logo">
|
||||||
{{html-safe c.description_excerpt}}
|
{{#if c.uploaded_logo.url}}
|
||||||
|
<CategoryLogo @category={{c}} />
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
<div class="category-details">
|
||||||
|
<div class="category-box-heading">
|
||||||
|
<a class="parent-box-link" href={{c.url}}>
|
||||||
|
<h3>
|
||||||
|
<CategoryTitleBefore @category={{c}} />
|
||||||
|
{{#if c.read_restricted}}
|
||||||
|
{{d-icon this.lockIcon}}
|
||||||
|
{{/if}}
|
||||||
|
{{c.displayName}}
|
||||||
|
</h3>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if c.isGrandParent}}
|
{{#unless c.isMuted}}
|
||||||
{{#each c.subcategories as |subcategory|}}
|
<div class="description">
|
||||||
<div
|
{{html-safe c.description_excerpt}}
|
||||||
data-category-id={{subcategory.id}}
|
|
||||||
data-notification-level={{subcategory.notificationLevelString}}
|
|
||||||
style={{border-color subcategory.color}}
|
|
||||||
class="subcategory with-subcategories
|
|
||||||
{{if subcategory.uploaded_logo.url 'has-logo' 'no-logo'}}"
|
|
||||||
>
|
|
||||||
<div class="subcategory-box-inner">
|
|
||||||
<CategoryTitleLink @tagName="h4" @category={{subcategory}} />
|
|
||||||
{{#if subcategory.subcategories}}
|
|
||||||
<div class="subcategories">
|
|
||||||
{{#each subcategory.subcategories as |subsubcategory|}}
|
|
||||||
{{#unless subsubcategory.isMuted}}
|
|
||||||
<span class="subcategory">
|
|
||||||
<CategoryTitleBefore @category={{subsubcategory}} />
|
|
||||||
{{category-link subsubcategory hideParent="true"}}
|
|
||||||
</span>
|
|
||||||
{{/unless}}
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
{{else if c.subcategories}}
|
|
||||||
<div class="subcategories">
|
|
||||||
{{#each c.subcategories as |sc|}}
|
|
||||||
<a class="subcategory" href={{sc.url}}>
|
|
||||||
<span class="subcategory-image-placeholder">
|
|
||||||
{{#if sc.uploaded_logo.url}}
|
|
||||||
<CategoryLogo @category={{sc}} />
|
|
||||||
{{/if}}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{{category-link sc hideParent="true"}}
|
|
||||||
</a>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
|
||||||
{{/unless}}
|
{{#if c.isGrandParent}}
|
||||||
|
{{#each c.subcategories as |subcategory|}}
|
||||||
|
<div
|
||||||
|
data-category-id={{subcategory.id}}
|
||||||
|
data-notification-level={{subcategory.notificationLevelString}}
|
||||||
|
style={{border-color subcategory.color}}
|
||||||
|
class="subcategory with-subcategories
|
||||||
|
{{if subcategory.uploaded_logo.url 'has-logo' 'no-logo'}}"
|
||||||
|
>
|
||||||
|
<div class="subcategory-box-inner">
|
||||||
|
<CategoryTitleLink
|
||||||
|
@tagName="h4"
|
||||||
|
@category={{subcategory}}
|
||||||
|
/>
|
||||||
|
{{#if subcategory.subcategories}}
|
||||||
|
<div class="subcategories">
|
||||||
|
{{#each subcategory.subcategories as |subsubcategory|}}
|
||||||
|
{{#unless subsubcategory.isMuted}}
|
||||||
|
<span class="subcategory">
|
||||||
|
<CategoryTitleBefore
|
||||||
|
@category={{subsubcategory}}
|
||||||
|
/>
|
||||||
|
{{category-link subsubcategory hideParent="true"}}
|
||||||
|
</span>
|
||||||
|
{{/unless}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
{{else if c.subcategories}}
|
||||||
|
<div class="subcategories">
|
||||||
|
{{#each c.subcategories as |sc|}}
|
||||||
|
<a class="subcategory" href={{sc.url}}>
|
||||||
|
<span class="subcategory-image-placeholder">
|
||||||
|
{{#if sc.uploaded_logo.url}}
|
||||||
|
<CategoryLogo @category={{sc}} />
|
||||||
|
{{/if}}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{{category-link sc hideParent="true"}}
|
||||||
|
</a>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{/unless}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PluginOutlet
|
||||||
|
@name="category-box-below-each-category"
|
||||||
|
@outletArgs={{hash category=c}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PluginOutlet
|
|
||||||
@name="category-box-below-each-category"
|
|
||||||
@outletArgs={{hash category=c}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<PluginOutlet
|
<PluginOutlet
|
||||||
@name="category-box-after-each-box"
|
@name="category-box-after-each-box"
|
||||||
@outletArgs={{hash category=c}}
|
@outletArgs={{hash category=c}}
|
||||||
/>
|
/>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
</PluginOutlet>
|
||||||
|
|
||||||
<PluginOutlet
|
<PluginOutlet
|
||||||
@name="category-boxes-after-boxes"
|
@name="category-boxes-after-boxes"
|
||||||
|
|
|
@ -1,49 +1,16 @@
|
||||||
{{#if this.categories}}
|
<PluginOutlet
|
||||||
{{#if this.filteredCategories}}
|
@name="categories-only-wrapper"
|
||||||
<table class="category-list {{if this.showTopics 'with-topics'}}">
|
@outletArgs={{hash categories=this.categories}}
|
||||||
<thead>
|
>
|
||||||
<tr>
|
{{#if this.categories}}
|
||||||
<th class="category"><span
|
{{#if this.filteredCategories}}
|
||||||
role="heading"
|
<table class="category-list {{if this.showTopics 'with-topics'}}">
|
||||||
aria-level="2"
|
|
||||||
id="categories-only-category"
|
|
||||||
>{{i18n "categories.category"}}</span></th>
|
|
||||||
<th class="topics">{{i18n "categories.topics"}}</th>
|
|
||||||
{{#if this.showTopics}}
|
|
||||||
<th class="latest">{{i18n "categories.latest"}}</th>
|
|
||||||
{{/if}}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody aria-labelledby="categories-only-category">
|
|
||||||
{{#each this.categories as |category|}}
|
|
||||||
<ParentCategoryRow
|
|
||||||
@category={{category}}
|
|
||||||
@showTopics={{this.showTopics}}
|
|
||||||
/>
|
|
||||||
{{/each}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if this.mutedCategories}}
|
|
||||||
<div class="muted-categories">
|
|
||||||
<a href class="muted-categories-link" {{on "click" this.toggleShowMuted}}>
|
|
||||||
<h3 class="muted-categories-heading">{{i18n "categories.muted"}}</h3>
|
|
||||||
{{#if this.mutedToggleIcon}}
|
|
||||||
{{d-icon this.mutedToggleIcon}}
|
|
||||||
{{/if}}
|
|
||||||
</a>
|
|
||||||
<table
|
|
||||||
class="category-list
|
|
||||||
{{if this.showTopics 'with-topics'}}
|
|
||||||
{{unless this.showMutedCategories 'hidden'}}"
|
|
||||||
>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="category"><span
|
<th class="category"><span
|
||||||
role="heading"
|
role="heading"
|
||||||
aria-level="2"
|
aria-level="2"
|
||||||
id="categories-only-category-muted"
|
id="categories-only-category"
|
||||||
>{{i18n "categories.category"}}</span></th>
|
>{{i18n "categories.category"}}</span></th>
|
||||||
<th class="topics">{{i18n "categories.topics"}}</th>
|
<th class="topics">{{i18n "categories.topics"}}</th>
|
||||||
{{#if this.showTopics}}
|
{{#if this.showTopics}}
|
||||||
|
@ -51,19 +18,61 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody aria-labelledby="categories-only-category-muted">
|
<tbody aria-labelledby="categories-only-category">
|
||||||
{{#each this.categories as |category|}}
|
{{#each this.categories as |category|}}
|
||||||
<ParentCategoryRow
|
<ParentCategoryRow
|
||||||
@category={{category}}
|
@category={{category}}
|
||||||
@showTopics={{this.showTopics}}
|
@showTopics={{this.showTopics}}
|
||||||
@listType="muted"
|
|
||||||
/>
|
/>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.mutedCategories}}
|
||||||
|
<div class="muted-categories">
|
||||||
|
<a
|
||||||
|
href
|
||||||
|
class="muted-categories-link"
|
||||||
|
{{on "click" this.toggleShowMuted}}
|
||||||
|
>
|
||||||
|
<h3 class="muted-categories-heading">{{i18n "categories.muted"}}</h3>
|
||||||
|
{{#if this.mutedToggleIcon}}
|
||||||
|
{{d-icon this.mutedToggleIcon}}
|
||||||
|
{{/if}}
|
||||||
|
</a>
|
||||||
|
<table
|
||||||
|
class="category-list
|
||||||
|
{{if this.showTopics 'with-topics'}}
|
||||||
|
{{unless this.showMutedCategories 'hidden'}}"
|
||||||
|
>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="category"><span
|
||||||
|
role="heading"
|
||||||
|
aria-level="2"
|
||||||
|
id="categories-only-category-muted"
|
||||||
|
>{{i18n "categories.category"}}</span></th>
|
||||||
|
<th class="topics">{{i18n "categories.topics"}}</th>
|
||||||
|
{{#if this.showTopics}}
|
||||||
|
<th class="latest">{{i18n "categories.latest"}}</th>
|
||||||
|
{{/if}}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody aria-labelledby="categories-only-category-muted">
|
||||||
|
{{#each this.categories as |category|}}
|
||||||
|
<ParentCategoryRow
|
||||||
|
@category={{category}}
|
||||||
|
@showTopics={{this.showTopics}}
|
||||||
|
@listType="muted"
|
||||||
|
/>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
</PluginOutlet>
|
||||||
|
|
||||||
<PluginOutlet
|
<PluginOutlet
|
||||||
@name="below-categories-only"
|
@name="below-categories-only"
|
||||||
|
|
|
@ -1,21 +1,26 @@
|
||||||
<section class="field category-name-fields">
|
<PluginOutlet
|
||||||
{{#unless this.category.isUncategorizedCategory}}
|
@name="category-name-fields-details"
|
||||||
|
@outletArgs={{hash category=this.category}}
|
||||||
|
>
|
||||||
|
<section class="field category-name-fields">
|
||||||
|
{{#unless this.category.isUncategorizedCategory}}
|
||||||
|
<section class="field-item">
|
||||||
|
<label>{{i18n "category.name"}}</label>
|
||||||
|
<TextField
|
||||||
|
@value={{this.category.name}}
|
||||||
|
@placeholderKey="category.name_placeholder"
|
||||||
|
@maxlength="50"
|
||||||
|
class="category-name"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
{{/unless}}
|
||||||
<section class="field-item">
|
<section class="field-item">
|
||||||
<label>{{i18n "category.name"}}</label>
|
<label>{{i18n "category.slug"}}</label>
|
||||||
<TextField
|
<TextField
|
||||||
@value={{this.category.name}}
|
@value={{this.category.slug}}
|
||||||
@placeholderKey="category.name_placeholder"
|
@placeholderKey="category.slug_placeholder"
|
||||||
@maxlength="50"
|
@maxlength="255"
|
||||||
class="category-name"
|
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
{{/unless}}
|
|
||||||
<section class="field-item">
|
|
||||||
<label>{{i18n "category.slug"}}</label>
|
|
||||||
<TextField
|
|
||||||
@value={{this.category.slug}}
|
|
||||||
@placeholderKey="category.slug_placeholder"
|
|
||||||
@maxlength="255"
|
|
||||||
/>
|
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</PluginOutlet>
|
|
@ -222,9 +222,7 @@ export default class GlimmerSiteHeader extends Component {
|
||||||
}
|
}
|
||||||
).finished;
|
).finished;
|
||||||
|
|
||||||
if (isTesting()) {
|
waitForPromise(animationFinished);
|
||||||
waitForPromise(animationFinished);
|
|
||||||
}
|
|
||||||
|
|
||||||
cloakElement.animate([{ opacity: 0 }], { fill: "forwards" });
|
cloakElement.animate([{ opacity: 0 }], { fill: "forwards" });
|
||||||
cloakElement.style.display = "block";
|
cloakElement.style.display = "block";
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { tracked } from "@glimmer/tracking";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { waitForPromise } from "@ember/test-waiters";
|
import { waitForPromise } from "@ember/test-waiters";
|
||||||
import { create } from "virtual-dom";
|
import { create } from "virtual-dom";
|
||||||
import { isTesting } from "discourse-common/config/environment";
|
|
||||||
import { iconNode } from "discourse-common/lib/icon-library";
|
import { iconNode } from "discourse-common/lib/icon-library";
|
||||||
|
|
||||||
export default class JsonSchemaEditorModal extends Component {
|
export default class JsonSchemaEditorModal extends Component {
|
||||||
|
@ -38,9 +37,7 @@ export default class JsonSchemaEditorModal extends Component {
|
||||||
@action
|
@action
|
||||||
async buildJsonEditor(element) {
|
async buildJsonEditor(element) {
|
||||||
const promise = import("@json-editor/json-editor");
|
const promise = import("@json-editor/json-editor");
|
||||||
if (isTesting()) {
|
waitForPromise(promise);
|
||||||
waitForPromise(promise);
|
|
||||||
}
|
|
||||||
const { JSONEditor } = await promise;
|
const { JSONEditor } = await promise;
|
||||||
|
|
||||||
JSONEditor.defaults.options.theme = "barebones";
|
JSONEditor.defaults.options.theme = "barebones";
|
||||||
|
|
|
@ -77,7 +77,8 @@ export default class TopicAdminMenu extends Component {
|
||||||
return (
|
return (
|
||||||
this.currentUser?.canManageTopic ||
|
this.currentUser?.canManageTopic ||
|
||||||
this.details?.can_archive_topic ||
|
this.details?.can_archive_topic ||
|
||||||
this.details?.can_close_topic
|
this.details?.can_close_topic ||
|
||||||
|
this.details?.can_split_merge_topic
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,39 +1,29 @@
|
||||||
import Component from "@glimmer/component";
|
|
||||||
import { hash } from "@ember/helper";
|
import { hash } from "@ember/helper";
|
||||||
import { service } from "@ember/service";
|
|
||||||
import { htmlSafe } from "@ember/template";
|
import { htmlSafe } from "@ember/template";
|
||||||
import PluginOutlet from "discourse/components/plugin-outlet";
|
import PluginOutlet from "discourse/components/plugin-outlet";
|
||||||
import coldAgeClass from "discourse/helpers/cold-age-class";
|
import coldAgeClass from "discourse/helpers/cold-age-class";
|
||||||
import concatClass from "discourse/helpers/concat-class";
|
import concatClass from "discourse/helpers/concat-class";
|
||||||
import element from "discourse/helpers/element";
|
|
||||||
import formatDate from "discourse/helpers/format-date";
|
import formatDate from "discourse/helpers/format-date";
|
||||||
|
|
||||||
export default class ActivityColumn extends Component {
|
const ActivityColumn = <template>
|
||||||
@service siteSettings;
|
<td
|
||||||
|
title={{htmlSafe @topic.bumpedAtTitle}}
|
||||||
get wrapperElement() {
|
class={{concatClass
|
||||||
return element(this.args.tagName ?? "td");
|
"activity"
|
||||||
}
|
(coldAgeClass @topic.createdAt startDate=@topic.bumpedAt class="")
|
||||||
|
}}
|
||||||
<template>
|
...attributes
|
||||||
<this.wrapperElement
|
>
|
||||||
title={{htmlSafe @topic.bumpedAtTitle}}
|
<a
|
||||||
class={{concatClass
|
href={{@topic.lastPostUrl}}
|
||||||
"activity"
|
class="post-activity"
|
||||||
(coldAgeClass @topic.createdAt startDate=@topic.bumpedAt class="")
|
>{{! no whitespace
|
||||||
}}
|
|
||||||
...attributes
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href={{@topic.lastPostUrl}}
|
|
||||||
class="post-activity"
|
|
||||||
>{{! no whitespace
|
|
||||||
}}<PluginOutlet
|
}}<PluginOutlet
|
||||||
@name="topic-list-before-relative-date"
|
@name="topic-list-before-relative-date"
|
||||||
@outletArgs={{hash topic=@topic}}
|
@outletArgs={{hash topic=@topic}}
|
||||||
/>
|
/>
|
||||||
{{~formatDate @topic.bumpedAt format="tiny" noTitle="true"~}}
|
{{~formatDate @topic.bumpedAt format="tiny" noTitle="true"~}}
|
||||||
</a>
|
</a>
|
||||||
</this.wrapperElement>
|
</td>
|
||||||
</template>
|
</template>;
|
||||||
}
|
export default ActivityColumn;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { on } from "@ember/modifier";
|
import { on } from "@ember/modifier";
|
||||||
import { htmlSafe } from "@ember/template";
|
import { htmlSafe } from "@ember/template";
|
||||||
import TopicEntrance from "discourse/components/topic-list/topic-entrance";
|
|
||||||
import TopicPostBadges from "discourse/components/topic-post-badges";
|
import TopicPostBadges from "discourse/components/topic-post-badges";
|
||||||
import TopicStatus from "discourse/components/topic-status";
|
import TopicStatus from "discourse/components/topic-status";
|
||||||
import formatAge from "discourse/helpers/format-age";
|
import formatAge from "discourse/helpers/format-age";
|
||||||
|
@ -30,13 +29,11 @@ const FeaturedTopic = <template>
|
||||||
@url={{@topic.lastUnreadUrl}}
|
@url={{@topic.lastUnreadUrl}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TopicEntrance @topic={{@topic}}>
|
<a
|
||||||
<a
|
{{on "click" onTimestampClick}}
|
||||||
{{on "click" onTimestampClick}}
|
href={{@topic.lastPostUrl}}
|
||||||
href={{@topic.lastPostUrl}}
|
class="last-posted-at"
|
||||||
class="last-posted-at"
|
>{{formatAge @topic.last_posted_at}}</a>
|
||||||
>{{formatAge @topic.last_posted_at}}</a>
|
|
||||||
</TopicEntrance>
|
|
||||||
</div>
|
</div>
|
||||||
</template>;
|
</template>;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { concat, hash } from "@ember/helper";
|
import { concat, hash } from "@ember/helper";
|
||||||
import { service } from "@ember/service";
|
|
||||||
import PluginOutlet from "discourse/components/plugin-outlet";
|
import PluginOutlet from "discourse/components/plugin-outlet";
|
||||||
import PostsCountColumn from "discourse/components/topic-list/posts-count-column";
|
import PostsCountColumn from "discourse/components/topic-list/posts-count-column";
|
||||||
import TopicPostBadges from "discourse/components/topic-post-badges";
|
import TopicPostBadges from "discourse/components/topic-post-badges";
|
||||||
|
@ -16,12 +15,8 @@ import topicFeaturedLink from "discourse/helpers/topic-featured-link";
|
||||||
import topicLink from "discourse/helpers/topic-link";
|
import topicLink from "discourse/helpers/topic-link";
|
||||||
|
|
||||||
export default class LatestTopicListItem extends Component {
|
export default class LatestTopicListItem extends Component {
|
||||||
@service appEvents;
|
|
||||||
|
|
||||||
get tagClassNames() {
|
get tagClassNames() {
|
||||||
if (this.args.topic.tags) {
|
return this.args.topic.tags?.map((tagName) => `tag-${tagName}`);
|
||||||
return this.args.topic.tags.map((tagName) => `tag-${tagName}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -11,8 +11,6 @@ import i18n from "discourse-common/helpers/i18n";
|
||||||
|
|
||||||
export default class TopicList extends Component {
|
export default class TopicList extends Component {
|
||||||
@service currentUser;
|
@service currentUser;
|
||||||
@service router;
|
|
||||||
@service siteSettings;
|
|
||||||
|
|
||||||
@tracked lastCheckedElementId;
|
@tracked lastCheckedElementId;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ import Component from "@glimmer/component";
|
||||||
import { hash } from "@ember/helper";
|
import { hash } from "@ember/helper";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import PluginOutlet from "discourse/components/plugin-outlet";
|
import PluginOutlet from "discourse/components/plugin-outlet";
|
||||||
import TopicEntrance from "discourse/components/topic-list/topic-entrance";
|
|
||||||
import element from "discourse/helpers/element";
|
import element from "discourse/helpers/element";
|
||||||
import number from "discourse/helpers/number";
|
import number from "discourse/helpers/number";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
|
@ -55,17 +54,13 @@ export default class PostsCountColumn extends Component {
|
||||||
<this.wrapperElement
|
<this.wrapperElement
|
||||||
class="num posts-map posts {{this.likesHeat}} topic-list-data"
|
class="num posts-map posts {{this.likesHeat}} topic-list-data"
|
||||||
>
|
>
|
||||||
<TopicEntrance
|
<a href={{@topic.firstPostUrl}} class="badge-posts">
|
||||||
@topic={{@topic}}
|
|
||||||
@title={{this.title}}
|
|
||||||
@triggerClass="btn-link posts-map badge-posts {{this.likesHeat}}"
|
|
||||||
>
|
|
||||||
<PluginOutlet
|
<PluginOutlet
|
||||||
@name="topic-list-before-reply-count"
|
@name="topic-list-before-reply-count"
|
||||||
@outletArgs={{hash topic=@topic}}
|
@outletArgs={{hash topic=@topic}}
|
||||||
/>
|
/>
|
||||||
{{number @topic.replyCount noTitle="true"}}
|
{{number @topic.replyCount noTitle="true"}}
|
||||||
</TopicEntrance>
|
</a>
|
||||||
</this.wrapperElement>
|
</this.wrapperElement>
|
||||||
</template>
|
</template>
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { hash } from "@ember/helper";
|
||||||
|
import { on } from "@ember/modifier";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { service } from "@ember/service";
|
||||||
|
import PluginOutlet from "discourse/components/plugin-outlet";
|
||||||
|
import ActionList from "discourse/components/topic-list/action-list";
|
||||||
|
import ParticipantGroups from "discourse/components/topic-list/participant-groups";
|
||||||
|
import TopicExcerpt from "discourse/components/topic-list/topic-excerpt";
|
||||||
|
import TopicLink from "discourse/components/topic-list/topic-link";
|
||||||
|
import UnreadIndicator from "discourse/components/topic-list/unread-indicator";
|
||||||
|
import TopicPostBadges from "discourse/components/topic-post-badges";
|
||||||
|
import TopicStatus from "discourse/components/topic-status";
|
||||||
|
import categoryLink from "discourse/helpers/category-link";
|
||||||
|
import discourseTags from "discourse/helpers/discourse-tags";
|
||||||
|
import topicFeaturedLink from "discourse/helpers/topic-featured-link";
|
||||||
|
import { groupPath } from "discourse/lib/url";
|
||||||
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
import I18n from "discourse-i18n";
|
||||||
|
|
||||||
|
export default class TopicCell extends Component {
|
||||||
|
@service currentUser;
|
||||||
|
@service messageBus;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
|
||||||
|
if (this.includeUnreadIndicator) {
|
||||||
|
this.messageBus.subscribe(this.unreadIndicatorChannel, this.onMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
willDestroy() {
|
||||||
|
super.willDestroy(...arguments);
|
||||||
|
|
||||||
|
this.messageBus.unsubscribe(this.unreadIndicatorChannel, this.onMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
onMessage(data) {
|
||||||
|
const nodeClassList = document.querySelector(
|
||||||
|
`.indicator-topic-${data.topic_id}`
|
||||||
|
).classList;
|
||||||
|
|
||||||
|
nodeClassList.toggle("read", !data.show_indicator);
|
||||||
|
}
|
||||||
|
|
||||||
|
get unreadIndicatorChannel() {
|
||||||
|
return `/private-messages/unread-indicator/${this.args.topic.id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
get includeUnreadIndicator() {
|
||||||
|
return typeof this.args.topic.unread_by_group_member !== "undefined";
|
||||||
|
}
|
||||||
|
|
||||||
|
get unreadClass() {
|
||||||
|
return this.args.topic.unread_by_group_member ? "" : "read";
|
||||||
|
}
|
||||||
|
|
||||||
|
get newDotText() {
|
||||||
|
return this.currentUser?.trust_level > 0
|
||||||
|
? ""
|
||||||
|
: I18n.t("filters.new.lower_title");
|
||||||
|
}
|
||||||
|
|
||||||
|
get participantGroups() {
|
||||||
|
if (!this.args.topic.participant_groups) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.args.topic.participant_groups.map((name) => ({
|
||||||
|
name,
|
||||||
|
url: groupPath(name),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
onTitleFocus(event) {
|
||||||
|
event.target.classList.add("selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
onTitleBlur(event) {
|
||||||
|
event.target.classList.remove("selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<td class="main-link clearfix topic-list-data" colspan="1">
|
||||||
|
<PluginOutlet
|
||||||
|
@name="topic-list-before-link"
|
||||||
|
@outletArgs={{hash topic=@topic}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span class="link-top-line">
|
||||||
|
{{~! no whitespace ~}}
|
||||||
|
<PluginOutlet
|
||||||
|
@name="topic-list-before-status"
|
||||||
|
@outletArgs={{hash topic=@topic}}
|
||||||
|
/>
|
||||||
|
{{~! no whitespace ~}}
|
||||||
|
<TopicStatus @topic={{@topic}} />
|
||||||
|
{{~! no whitespace ~}}
|
||||||
|
<TopicLink
|
||||||
|
{{on "focus" this.onTitleFocus}}
|
||||||
|
{{on "blur" this.onTitleBlur}}
|
||||||
|
@topic={{@topic}}
|
||||||
|
class="raw-link raw-topic-link"
|
||||||
|
/>
|
||||||
|
{{~#if @topic.featured_link~}}
|
||||||
|
|
||||||
|
{{~topicFeaturedLink @topic}}
|
||||||
|
{{~/if~}}
|
||||||
|
<PluginOutlet
|
||||||
|
@name="topic-list-after-title"
|
||||||
|
@outletArgs={{hash topic=@topic}}
|
||||||
|
/>
|
||||||
|
{{~! no whitespace ~}}
|
||||||
|
<UnreadIndicator
|
||||||
|
@includeUnreadIndicator={{this.includeUnreadIndicator}}
|
||||||
|
@topicId={{@topic.id}}
|
||||||
|
class={{this.unreadClass}}
|
||||||
|
/>
|
||||||
|
{{~#if @showTopicPostBadges~}}
|
||||||
|
<TopicPostBadges
|
||||||
|
@unreadPosts={{@topic.unread_posts}}
|
||||||
|
@unseen={{@topic.unseen}}
|
||||||
|
@newDotText={{this.newDotText}}
|
||||||
|
@url={{@topic.lastUnreadUrl}}
|
||||||
|
/>
|
||||||
|
{{~/if~}}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="link-bottom-line">
|
||||||
|
{{#unless @hideCategory}}
|
||||||
|
{{#unless @topic.isPinnedUncategorized}}
|
||||||
|
<PluginOutlet
|
||||||
|
@name="topic-list-before-category"
|
||||||
|
@outletArgs={{hash topic=@topic}}
|
||||||
|
/>
|
||||||
|
{{categoryLink @topic.category}}
|
||||||
|
{{/unless}}
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
{{discourseTags @topic mode="list" tagsForUser=@tagsForUser}}
|
||||||
|
|
||||||
|
{{#if this.participantGroups}}
|
||||||
|
<ParticipantGroups @groups={{this.participantGroups}} />
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<ActionList
|
||||||
|
@topic={{@topic}}
|
||||||
|
@postNumbers={{@topic.liked_post_numbers}}
|
||||||
|
@icon="heart"
|
||||||
|
class="likes"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if @expandPinned}}
|
||||||
|
<TopicExcerpt @topic={{@topic}} />
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<PluginOutlet
|
||||||
|
@name="topic-list-main-link-bottom"
|
||||||
|
@outletArgs={{hash topic=@topic}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
|
}
|
|
@ -1,111 +0,0 @@
|
||||||
import Component from "@glimmer/component";
|
|
||||||
import { fn } from "@ember/helper";
|
|
||||||
import { on } from "@ember/modifier";
|
|
||||||
import { action } from "@ember/object";
|
|
||||||
import { service } from "@ember/service";
|
|
||||||
import { htmlSafe } from "@ember/template";
|
|
||||||
import DiscourseURL from "discourse/lib/url";
|
|
||||||
import icon from "discourse-common/helpers/d-icon";
|
|
||||||
import i18n from "discourse-common/helpers/i18n";
|
|
||||||
import I18n from "discourse-i18n";
|
|
||||||
import DMenu from "float-kit/components/d-menu";
|
|
||||||
|
|
||||||
function entranceDate(dt, showTime) {
|
|
||||||
const today = new Date();
|
|
||||||
|
|
||||||
if (dt.toDateString() === today.toDateString()) {
|
|
||||||
return moment(dt).format(I18n.t("dates.time"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dt.getYear() === today.getYear()) {
|
|
||||||
// No year
|
|
||||||
return moment(dt).format(
|
|
||||||
showTime
|
|
||||||
? I18n.t("dates.long_date_without_year_with_linebreak")
|
|
||||||
: I18n.t("dates.long_no_year_no_time")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return moment(dt).format(
|
|
||||||
showTime
|
|
||||||
? I18n.t("dates.long_date_with_year_with_linebreak")
|
|
||||||
: I18n.t("dates.long_date_with_year_without_time")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class TopicEntrance extends Component {
|
|
||||||
@service historyStore;
|
|
||||||
|
|
||||||
get createdDate() {
|
|
||||||
return new Date(this.args.topic.created_at);
|
|
||||||
}
|
|
||||||
|
|
||||||
get bumpedDate() {
|
|
||||||
return new Date(this.args.topic.bumped_at);
|
|
||||||
}
|
|
||||||
|
|
||||||
get showTime() {
|
|
||||||
return (
|
|
||||||
this.bumpedDate.getTime() - this.createdDate.getTime() <
|
|
||||||
1000 * 60 * 60 * 24 * 2
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get topDate() {
|
|
||||||
return entranceDate(this.createdDate, this.showTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
get bottomDate() {
|
|
||||||
return entranceDate(this.bumpedDate, this.showTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
jumpTo(destination) {
|
|
||||||
this.historyStore.set("lastTopicIdViewed", this.args.topic.id);
|
|
||||||
DiscourseURL.routeTo(destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<DMenu
|
|
||||||
@title={{@title}}
|
|
||||||
@ariaLabel={{@title}}
|
|
||||||
@placement="center"
|
|
||||||
@autofocus={{true}}
|
|
||||||
@triggerClass={{@triggerClass}}
|
|
||||||
>
|
|
||||||
<:trigger>
|
|
||||||
{{yield}}
|
|
||||||
</:trigger>
|
|
||||||
|
|
||||||
<:content>
|
|
||||||
<div id="topic-entrance" class="--glimmer">
|
|
||||||
<button
|
|
||||||
{{on "click" (fn this.jumpTo @topic.url)}}
|
|
||||||
aria-label={{i18n
|
|
||||||
"topic_entrance.sr_jump_top_button"
|
|
||||||
date=this.topDate
|
|
||||||
}}
|
|
||||||
title={{i18n "topic_entrance.jump_top_button_title"}}
|
|
||||||
class="btn btn-default full jump-top"
|
|
||||||
>
|
|
||||||
{{icon "backward-step"}}
|
|
||||||
{{htmlSafe this.topDate}}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
{{on "click" (fn this.jumpTo @topic.lastPostUrl)}}
|
|
||||||
aria-label={{i18n
|
|
||||||
"topic_entrance.sr_jump_bottom_button"
|
|
||||||
date=this.bottomDate
|
|
||||||
}}
|
|
||||||
title={{i18n "topic_entrance.jump_bottom_button_title"}}
|
|
||||||
class="btn btn-default full jump-bottom"
|
|
||||||
>
|
|
||||||
{{htmlSafe this.bottomDate}}
|
|
||||||
{{icon "forward-step"}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</:content>
|
|
||||||
</DMenu>
|
|
||||||
</template>
|
|
||||||
}
|
|
|
@ -9,7 +9,6 @@ import icon from "discourse-common/helpers/d-icon";
|
||||||
import i18n from "discourse-common/helpers/i18n";
|
import i18n from "discourse-common/helpers/i18n";
|
||||||
|
|
||||||
export default class TopicListHeaderColumn extends Component {
|
export default class TopicListHeaderColumn extends Component {
|
||||||
@service modal;
|
|
||||||
@service router;
|
@service router;
|
||||||
|
|
||||||
get localizedName() {
|
get localizedName() {
|
||||||
|
|
|
@ -8,16 +8,13 @@ import { service } from "@ember/service";
|
||||||
import { modifier } from "ember-modifier";
|
import { modifier } from "ember-modifier";
|
||||||
import { eq, gt } from "truth-helpers";
|
import { eq, gt } from "truth-helpers";
|
||||||
import PluginOutlet from "discourse/components/plugin-outlet";
|
import PluginOutlet from "discourse/components/plugin-outlet";
|
||||||
import ActionList from "discourse/components/topic-list/action-list";
|
|
||||||
import ActivityColumn from "discourse/components/topic-list/activity-column";
|
import ActivityColumn from "discourse/components/topic-list/activity-column";
|
||||||
import ParticipantGroups from "discourse/components/topic-list/participant-groups";
|
|
||||||
import PostCountOrBadges from "discourse/components/topic-list/post-count-or-badges";
|
import PostCountOrBadges from "discourse/components/topic-list/post-count-or-badges";
|
||||||
import PostersColumn from "discourse/components/topic-list/posters-column";
|
import PostersColumn from "discourse/components/topic-list/posters-column";
|
||||||
import PostsCountColumn from "discourse/components/topic-list/posts-count-column";
|
import PostsCountColumn from "discourse/components/topic-list/posts-count-column";
|
||||||
|
import TopicCell from "discourse/components/topic-list/topic-cell";
|
||||||
import TopicExcerpt from "discourse/components/topic-list/topic-excerpt";
|
import TopicExcerpt from "discourse/components/topic-list/topic-excerpt";
|
||||||
import TopicLink from "discourse/components/topic-list/topic-link";
|
import TopicLink from "discourse/components/topic-list/topic-link";
|
||||||
import UnreadIndicator from "discourse/components/topic-list/unread-indicator";
|
|
||||||
import TopicPostBadges from "discourse/components/topic-post-badges";
|
|
||||||
import TopicStatus from "discourse/components/topic-status";
|
import TopicStatus from "discourse/components/topic-status";
|
||||||
import { topicTitleDecorators } from "discourse/components/topic-title";
|
import { topicTitleDecorators } from "discourse/components/topic-title";
|
||||||
import avatar from "discourse/helpers/avatar";
|
import avatar from "discourse/helpers/avatar";
|
||||||
|
@ -28,18 +25,12 @@ import formatDate from "discourse/helpers/format-date";
|
||||||
import number from "discourse/helpers/number";
|
import number from "discourse/helpers/number";
|
||||||
import topicFeaturedLink from "discourse/helpers/topic-featured-link";
|
import topicFeaturedLink from "discourse/helpers/topic-featured-link";
|
||||||
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
||||||
import DiscourseURL, { groupPath } from "discourse/lib/url";
|
import DiscourseURL from "discourse/lib/url";
|
||||||
import icon from "discourse-common/helpers/d-icon";
|
import icon from "discourse-common/helpers/d-icon";
|
||||||
import i18n from "discourse-common/helpers/i18n";
|
import i18n from "discourse-common/helpers/i18n";
|
||||||
import { bind } from "discourse-common/utils/decorators";
|
|
||||||
import I18n from "discourse-i18n";
|
|
||||||
|
|
||||||
export default class TopicListItem extends Component {
|
export default class TopicListItem extends Component {
|
||||||
@service appEvents;
|
|
||||||
@service currentUser;
|
|
||||||
@service historyStore;
|
@service historyStore;
|
||||||
@service messageBus;
|
|
||||||
@service router;
|
|
||||||
@service site;
|
@service site;
|
||||||
@service siteSettings;
|
@service siteSettings;
|
||||||
|
|
||||||
|
@ -60,58 +51,10 @@ export default class TopicListItem extends Component {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super(...arguments);
|
|
||||||
|
|
||||||
if (this.includeUnreadIndicator) {
|
|
||||||
this.messageBus.subscribe(this.unreadIndicatorChannel, this.onMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
willDestroy() {
|
|
||||||
super.willDestroy(...arguments);
|
|
||||||
|
|
||||||
this.messageBus.unsubscribe(this.unreadIndicatorChannel, this.onMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
@bind
|
|
||||||
onMessage(data) {
|
|
||||||
const nodeClassList = document.querySelector(
|
|
||||||
`.indicator-topic-${data.topic_id}`
|
|
||||||
).classList;
|
|
||||||
|
|
||||||
nodeClassList.toggle("read", !data.show_indicator);
|
|
||||||
}
|
|
||||||
|
|
||||||
get unreadIndicatorChannel() {
|
|
||||||
return `/private-messages/unread-indicator/${this.args.topic.id}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get includeUnreadIndicator() {
|
|
||||||
return typeof this.args.topic.unread_by_group_member !== "undefined";
|
|
||||||
}
|
|
||||||
|
|
||||||
get isSelected() {
|
get isSelected() {
|
||||||
return this.args.selected?.includes(this.args.topic);
|
return this.args.selected?.includes(this.args.topic);
|
||||||
}
|
}
|
||||||
|
|
||||||
get participantGroups() {
|
|
||||||
if (!this.args.topic.participant_groups) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.args.topic.participant_groups.map((name) => ({
|
|
||||||
name,
|
|
||||||
url: groupPath(name),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
get newDotText() {
|
|
||||||
return this.currentUser?.trust_level > 0
|
|
||||||
? ""
|
|
||||||
: I18n.t("filters.new.lower_title");
|
|
||||||
}
|
|
||||||
|
|
||||||
get tagClassNames() {
|
get tagClassNames() {
|
||||||
return this.args.topic.tags?.map((tagName) => `tag-${tagName}`);
|
return this.args.topic.tags?.map((tagName) => `tag-${tagName}`);
|
||||||
}
|
}
|
||||||
|
@ -135,10 +78,6 @@ export default class TopicListItem extends Component {
|
||||||
return this.site.desktopView && this.args.focusLastVisitedTopic;
|
return this.site.desktopView && this.args.focusLastVisitedTopic;
|
||||||
}
|
}
|
||||||
|
|
||||||
get unreadClass() {
|
|
||||||
return this.args.topic.unread_by_group_member ? "" : "read";
|
|
||||||
}
|
|
||||||
|
|
||||||
navigateToTopic(topic, href) {
|
navigateToTopic(topic, href) {
|
||||||
this.historyStore.set("lastTopicIdViewed", topic.id);
|
this.historyStore.set("lastTopicIdViewed", topic.id);
|
||||||
DiscourseURL.routeTo(href || topic.url);
|
DiscourseURL.routeTo(href || topic.url);
|
||||||
|
@ -154,16 +93,6 @@ export default class TopicListItem extends Component {
|
||||||
element.classList.add("highlighted");
|
element.classList.add("highlighted");
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
|
||||||
onTitleFocus(event) {
|
|
||||||
event.target.classList.add("selected");
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
onTitleBlur(event) {
|
|
||||||
event.target.classList.remove("selected");
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
applyTitleDecorators(element) {
|
applyTitleDecorators(element) {
|
||||||
const rawTopicLink = element.querySelector(".raw-topic-link");
|
const rawTopicLink = element.querySelector(".raw-topic-link");
|
||||||
|
@ -300,85 +229,13 @@ export default class TopicListItem extends Component {
|
||||||
</td>
|
</td>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<td class="main-link clearfix topic-list-data" colspan="1">
|
<TopicCell
|
||||||
<PluginOutlet
|
@topic={{@topic}}
|
||||||
@name="topic-list-before-link"
|
@showTopicPostBadges={{@showTopicPostBadges}}
|
||||||
@outletArgs={{hash topic=@topic}}
|
@hideCategory={{@hideCategory}}
|
||||||
/>
|
@tagsForUser={{@tagsForUser}}
|
||||||
|
@expandPinned={{this.expandPinned}}
|
||||||
<span class="link-top-line">
|
/>
|
||||||
{{~! no whitespace ~}}
|
|
||||||
<PluginOutlet
|
|
||||||
@name="topic-list-before-status"
|
|
||||||
@outletArgs={{hash topic=@topic}}
|
|
||||||
/>
|
|
||||||
{{~! no whitespace ~}}
|
|
||||||
<TopicStatus @topic={{@topic}} />
|
|
||||||
{{~! no whitespace ~}}
|
|
||||||
<TopicLink
|
|
||||||
{{on "focus" this.onTitleFocus}}
|
|
||||||
{{on "blur" this.onTitleBlur}}
|
|
||||||
@topic={{@topic}}
|
|
||||||
class="raw-link raw-topic-link"
|
|
||||||
/>
|
|
||||||
{{~#if @topic.featured_link~}}
|
|
||||||
|
|
||||||
{{~topicFeaturedLink @topic}}
|
|
||||||
{{~/if~}}
|
|
||||||
<PluginOutlet
|
|
||||||
@name="topic-list-after-title"
|
|
||||||
@outletArgs={{hash topic=@topic}}
|
|
||||||
/>
|
|
||||||
{{~! no whitespace ~}}
|
|
||||||
<UnreadIndicator
|
|
||||||
@includeUnreadIndicator={{this.includeUnreadIndicator}}
|
|
||||||
@topicId={{@topic.id}}
|
|
||||||
class={{this.unreadClass}}
|
|
||||||
/>
|
|
||||||
{{~#if @showTopicPostBadges~}}
|
|
||||||
<TopicPostBadges
|
|
||||||
@unreadPosts={{@topic.unread_posts}}
|
|
||||||
@unseen={{@topic.unseen}}
|
|
||||||
@newDotText={{this.newDotText}}
|
|
||||||
@url={{@topic.lastUnreadUrl}}
|
|
||||||
/>
|
|
||||||
{{~/if~}}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div class="link-bottom-line">
|
|
||||||
{{#unless @hideCategory}}
|
|
||||||
{{#unless @topic.isPinnedUncategorized}}
|
|
||||||
<PluginOutlet
|
|
||||||
@name="topic-list-before-category"
|
|
||||||
@outletArgs={{hash topic=@topic}}
|
|
||||||
/>
|
|
||||||
{{categoryLink @topic.category}}
|
|
||||||
{{/unless}}
|
|
||||||
{{/unless}}
|
|
||||||
|
|
||||||
{{discourseTags @topic mode="list" tagsForUser=@tagsForUser}}
|
|
||||||
|
|
||||||
{{#if this.participantGroups}}
|
|
||||||
<ParticipantGroups @groups={{this.participantGroups}} />
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<ActionList
|
|
||||||
@topic={{@topic}}
|
|
||||||
@postNumbers={{@topic.liked_post_numbers}}
|
|
||||||
@icon="heart"
|
|
||||||
class="likes"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if this.expandPinned}}
|
|
||||||
<TopicExcerpt @topic={{@topic}} />
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<PluginOutlet
|
|
||||||
@name="topic-list-main-link-bottom"
|
|
||||||
@outletArgs={{hash topic=@topic}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<PluginOutlet
|
<PluginOutlet
|
||||||
@name="topic-list-after-main-link"
|
@name="topic-list-after-main-link"
|
||||||
|
|
|
@ -2,7 +2,6 @@ import Component from "@glimmer/component";
|
||||||
import { tracked } from "@glimmer/tracking";
|
import { tracked } from "@glimmer/tracking";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import optionalService from "discourse/lib/optional-service";
|
|
||||||
import { bind } from "discourse-common/utils/decorators";
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
export default class TopicTimeline extends Component {
|
export default class TopicTimeline extends Component {
|
||||||
|
@ -13,8 +12,6 @@ export default class TopicTimeline extends Component {
|
||||||
@tracked docked = false;
|
@tracked docked = false;
|
||||||
@tracked dockedBottom = false;
|
@tracked dockedBottom = false;
|
||||||
|
|
||||||
adminTools = optionalService();
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ export default class UserController extends Controller.extend(CanCheckEmails) {
|
||||||
@service currentUser;
|
@service currentUser;
|
||||||
@service router;
|
@service router;
|
||||||
@service dialog;
|
@service dialog;
|
||||||
@optionalService("admin-tools") adminTools;
|
@optionalService adminTools;
|
||||||
|
|
||||||
@controller("user-notifications") userNotifications;
|
@controller("user-notifications") userNotifications;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { concat } from "@ember/helper";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import FKLabel from "discourse/form-kit/components/fk/label";
|
import FKLabel from "discourse/form-kit/components/fk/label";
|
||||||
import FKMeta from "discourse/form-kit/components/fk/meta";
|
import FKMeta from "discourse/form-kit/components/fk/meta";
|
||||||
import FormText from "discourse/form-kit/components/fk/text";
|
import FKText from "discourse/form-kit/components/fk/text";
|
||||||
import concatClass from "discourse/helpers/concat-class";
|
import concatClass from "discourse/helpers/concat-class";
|
||||||
import i18n from "discourse-common/helpers/i18n";
|
import i18n from "discourse-common/helpers/i18n";
|
||||||
|
|
||||||
|
@ -60,10 +60,10 @@ export default class FKControlWrapper extends Component {
|
||||||
</FKLabel>
|
</FKLabel>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if @field.subtitle}}
|
{{#if @field.description}}
|
||||||
<FormText
|
<FKText
|
||||||
class="form-kit__container-subtitle"
|
class="form-kit__container-description"
|
||||||
>{{@field.subtitle}}</FormText>
|
>{{@field.description}}</FKText>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -93,12 +93,7 @@ export default class FKControlWrapper extends Component {
|
||||||
{{yield components}}
|
{{yield components}}
|
||||||
</@component>
|
</@component>
|
||||||
|
|
||||||
<FKMeta
|
<FKMeta @value={{@value}} @field={{@field}} @error={{this.error}} />
|
||||||
@description={{@field.description}}
|
|
||||||
@value={{@value}}
|
|
||||||
@field={{@field}}
|
|
||||||
@error={{this.error}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { hash } from "@ember/helper";
|
||||||
import FKFieldset from "discourse/form-kit/components/fk/fieldset";
|
import FKFieldset from "discourse/form-kit/components/fk/fieldset";
|
||||||
import FKControlRadioGroupRadio from "./radio-group/radio";
|
import FKControlRadioGroupRadio from "./radio-group/radio";
|
||||||
|
|
||||||
// eslint-disable-next-line ember/no-empty-glimmer-component-classes
|
|
||||||
export default class FKControlRadioGroup extends Component {
|
export default class FKControlRadioGroup extends Component {
|
||||||
static controlType = "radio-group";
|
static controlType = "radio-group";
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,6 @@ export default class FKField extends Component {
|
||||||
this.field = this.args.registerField(this.name, {
|
this.field = this.args.registerField(this.name, {
|
||||||
triggerRevalidationFor: this.args.triggerRevalidationFor,
|
triggerRevalidationFor: this.args.triggerRevalidationFor,
|
||||||
title: this.args.title,
|
title: this.args.title,
|
||||||
subtitle: this.args.subtitle,
|
|
||||||
description: this.args.description,
|
description: this.args.description,
|
||||||
showTitle: this.args.showTitle,
|
showTitle: this.args.showTitle,
|
||||||
collectionIndex: this.args.collectionIndex,
|
collectionIndex: this.args.collectionIndex,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import FKCharCounter from "discourse/form-kit/components/fk/char-counter";
|
import FKCharCounter from "discourse/form-kit/components/fk/char-counter";
|
||||||
import FKErrors from "discourse/form-kit/components/fk/errors";
|
import FKErrors from "discourse/form-kit/components/fk/errors";
|
||||||
import FKText from "discourse/form-kit/components/fk/text";
|
|
||||||
|
|
||||||
export default class FKMeta extends Component {
|
export default class FKMeta extends Component {
|
||||||
get shouldRenderCharCounter() {
|
get shouldRenderCharCounter() {
|
||||||
|
@ -9,12 +8,7 @@ export default class FKMeta extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
get shouldRenderMeta() {
|
get shouldRenderMeta() {
|
||||||
return (
|
return this.showMeta && (this.shouldRenderCharCounter || this.args.error);
|
||||||
this.showMeta &&
|
|
||||||
(this.shouldRenderCharCounter ||
|
|
||||||
this.args.error ||
|
|
||||||
this.args.description?.length)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get showMeta() {
|
get showMeta() {
|
||||||
|
@ -26,8 +20,6 @@ export default class FKMeta extends Component {
|
||||||
<div class="form-kit__meta">
|
<div class="form-kit__meta">
|
||||||
{{#if @error}}
|
{{#if @error}}
|
||||||
<FKErrors @id={{@field.errorId}} @error={{@error}} />
|
<FKErrors @id={{@field.errorId}} @error={{@error}} />
|
||||||
{{else if @description}}
|
|
||||||
<FKText class="form-kit__meta-description">{{@description}}</FKText>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if this.shouldRenderCharCounter}}
|
{{#if this.shouldRenderCharCounter}}
|
||||||
|
|
|
@ -12,11 +12,8 @@ export default {
|
||||||
* with the hashtag type via api.registerHashtagType. The default
|
* with the hashtag type via api.registerHashtagType. The default
|
||||||
* ones in core are CategoryHashtagType and TagHashtagType.
|
* ones in core are CategoryHashtagType and TagHashtagType.
|
||||||
*/
|
*/
|
||||||
initialize(owner) {
|
initialize() {
|
||||||
this.site = owner.lookup("service:site");
|
|
||||||
|
|
||||||
const cssTag = document.createElement("style");
|
const cssTag = document.createElement("style");
|
||||||
cssTag.type = "text/css";
|
|
||||||
cssTag.id = "hashtag-css-generator";
|
cssTag.id = "hashtag-css-generator";
|
||||||
cssTag.innerHTML = Object.values(getHashtagTypeClasses())
|
cssTag.innerHTML = Object.values(getHashtagTypeClasses())
|
||||||
.map((hashtagType) => hashtagType.generatePreloadedCssClasses())
|
.map((hashtagType) => hashtagType.generatePreloadedCssClasses())
|
||||||
|
|
|
@ -8,14 +8,6 @@ export default {
|
||||||
const capabilities = owner.lookup("service:capabilities");
|
const capabilities = owner.lookup("service:capabilities");
|
||||||
|
|
||||||
if (siteSettings.composer_media_optimization_image_enabled) {
|
if (siteSettings.composer_media_optimization_image_enabled) {
|
||||||
// NOTE: There are various performance issues with the Canvas
|
|
||||||
// in iOS Safari that are causing crashes when processing images
|
|
||||||
// with spikes of over 100% CPU usage. The cause of this is unknown,
|
|
||||||
// but profiling points to CanvasRenderingContext2D.getImageData()
|
|
||||||
// and CanvasRenderingContext2D.drawImage().
|
|
||||||
//
|
|
||||||
// Until Safari makes some progress with OffscreenCanvas or other
|
|
||||||
// alternatives we cannot support this workflow.
|
|
||||||
if (
|
if (
|
||||||
capabilities.isIOS &&
|
capabilities.isIOS &&
|
||||||
!siteSettings.composer_ios_media_optimisation_image_enabled
|
!siteSettings.composer_ios_media_optimisation_image_enabled
|
||||||
|
@ -23,6 +15,22 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restrict feature to browsers that support OffscreenCanvas
|
||||||
|
if (typeof OffscreenCanvas === "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!("createImageBitmap" in self)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// prior to v18, Safari has WASM memory growth bugs
|
||||||
|
// eg https://github.com/emscripten-core/emscripten/issues/19144
|
||||||
|
let match = window.navigator.userAgent.match(/Mobile\/([0-9]+)\./);
|
||||||
|
let safariVersion = match ? parseInt(match[1], 10) : null;
|
||||||
|
if (capabilities.isSafari && safariVersion && safariVersion < 18) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
addComposerUploadPreProcessor(
|
addComposerUploadPreProcessor(
|
||||||
UppyMediaOptimization,
|
UppyMediaOptimization,
|
||||||
({ isMobileDevice }) => {
|
({ isMobileDevice }) => {
|
||||||
|
|
|
@ -7,10 +7,10 @@ export default {
|
||||||
|
|
||||||
Sharing.addSource({
|
Sharing.addSource({
|
||||||
id: "twitter",
|
id: "twitter",
|
||||||
icon: "fab-twitter",
|
icon: "fab-x-twitter",
|
||||||
generateUrl(link, title, quote = "") {
|
generateUrl(link, title, quote = "") {
|
||||||
const text = quote ? `"${quote}" -- ` : title;
|
const text = quote ? `"${quote}" -- ` : title;
|
||||||
return `http://twitter.com/intent/tweet?url=${encodeURIComponent(
|
return `http://x.com/intent/tweet?url=${encodeURIComponent(
|
||||||
link
|
link
|
||||||
)}&text=${encodeURIComponent(text)}`;
|
)}&text=${encodeURIComponent(text)}`;
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,23 +4,39 @@ import discourseLater from "discourse-common/lib/later";
|
||||||
// Send bg color to webview so iOS status bar matches site theme
|
// Send bg color to webview so iOS status bar matches site theme
|
||||||
export default {
|
export default {
|
||||||
after: "inject-objects",
|
after: "inject-objects",
|
||||||
|
retryCount: 0,
|
||||||
|
|
||||||
initialize(owner) {
|
initialize(owner) {
|
||||||
const caps = owner.lookup("service:capabilities");
|
const caps = owner.lookup("service:capabilities");
|
||||||
if (caps.isAppWebview) {
|
if (caps.isAppWebview) {
|
||||||
window
|
window
|
||||||
.matchMedia("(prefers-color-scheme: dark)")
|
.matchMedia("(prefers-color-scheme: dark)")
|
||||||
.addListener(this.updateAppBackground);
|
.addEventListener("change", this.updateAppBackground);
|
||||||
this.updateAppBackground();
|
this.updateAppBackground();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateAppBackground() {
|
|
||||||
|
updateAppBackground(delay = 500) {
|
||||||
discourseLater(() => {
|
discourseLater(() => {
|
||||||
const header = document.querySelector(".d-header-wrap .d-header");
|
if (this.headerBgColor()) {
|
||||||
if (header) {
|
postRNWebviewMessage("headerBg", this.headerBgColor());
|
||||||
const styles = window.getComputedStyle(header);
|
} else {
|
||||||
postRNWebviewMessage("headerBg", styles.backgroundColor);
|
this.retry();
|
||||||
}
|
}
|
||||||
}, 500);
|
}, delay);
|
||||||
|
},
|
||||||
|
|
||||||
|
headerBgColor() {
|
||||||
|
const header = document.querySelector(".d-header-wrap .d-header");
|
||||||
|
if (header) {
|
||||||
|
return window.getComputedStyle(header)?.backgroundColor;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
retry() {
|
||||||
|
if (this.retryCount < 2) {
|
||||||
|
this.retryCount++;
|
||||||
|
this.updateAppBackground(1000);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
import DEBUG from "@glimmer/env";
|
import { buildWaiter } from "@ember/test-waiters";
|
||||||
import { registerWaiter } from "@ember/test";
|
|
||||||
import { isTesting } from "discourse-common/config/environment";
|
const WAITER = buildWaiter("after-frame-paint");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs `callback` shortly after the next browser Frame is produced.
|
* Runs `callback` shortly after the next browser Frame is produced.
|
||||||
* ref: https://webperf.tips/tip/measuring-paint-time
|
* ref: https://webperf.tips/tip/measuring-paint-time
|
||||||
*/
|
*/
|
||||||
export default function runAfterFramePaint(callback) {
|
export default function runAfterFramePaint(callback) {
|
||||||
let done = false;
|
const token = WAITER.beginAsync();
|
||||||
|
|
||||||
if (DEBUG && isTesting()) {
|
|
||||||
registerWaiter(() => done);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queue a "before Render Steps" callback via requestAnimationFrame.
|
// Queue a "before Render Steps" callback via requestAnimationFrame.
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
|
@ -21,7 +17,7 @@ export default function runAfterFramePaint(callback) {
|
||||||
|
|
||||||
// Setup the callback to run in a Task
|
// Setup the callback to run in a Task
|
||||||
messageChannel.port1.onmessage = () => {
|
messageChannel.port1.onmessage = () => {
|
||||||
done = true;
|
WAITER.endAsync(token);
|
||||||
callback();
|
callback();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
import { waitForPromise } from "@ember/test-waiters";
|
import { waitForPromise } from "@ember/test-waiters";
|
||||||
import { isTesting } from "discourse-common/config/environment";
|
|
||||||
|
|
||||||
export default async function loadAce() {
|
export default async function loadAce() {
|
||||||
const promise = import("discourse/static/ace-editor-bundle");
|
const promise = import("discourse/static/ace-editor-bundle");
|
||||||
if (isTesting()) {
|
waitForPromise(promise);
|
||||||
waitForPromise(promise);
|
|
||||||
}
|
|
||||||
return await promise;
|
return await promise;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,28 @@
|
||||||
import { run } from "@ember/runloop";
|
import { run } from "@ember/runloop";
|
||||||
import { registerWaiter } from "@ember/test";
|
import { buildWaiter } from "@ember/test-waiters";
|
||||||
import { Promise } from "rsvp";
|
import { Promise } from "rsvp";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
import { PUBLIC_JS_VERSIONS } from "discourse/lib/public-js-versions";
|
import { PUBLIC_JS_VERSIONS } from "discourse/lib/public-js-versions";
|
||||||
import { isTesting } from "discourse-common/config/environment";
|
|
||||||
import getURL, { getURLWithCDN } from "discourse-common/lib/get-url";
|
import getURL, { getURLWithCDN } from "discourse-common/lib/get-url";
|
||||||
|
|
||||||
|
const WAITER = buildWaiter("load-script");
|
||||||
const _loaded = {};
|
const _loaded = {};
|
||||||
const _loading = {};
|
const _loading = {};
|
||||||
|
|
||||||
function loadWithTag(path, cb) {
|
function loadWithTag(path, cb) {
|
||||||
const head = document.getElementsByTagName("head")[0];
|
const head = document.getElementsByTagName("head")[0];
|
||||||
|
|
||||||
let finished = false;
|
|
||||||
let s = document.createElement("script");
|
let s = document.createElement("script");
|
||||||
s.src = path;
|
s.src = path;
|
||||||
|
|
||||||
if (isTesting()) {
|
const token = WAITER.beginAsync();
|
||||||
registerWaiter(() => finished);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't leave it hanging if something goes wrong
|
// Don't leave it hanging if something goes wrong
|
||||||
s.onerror = function () {
|
s.onerror = function () {
|
||||||
finished = true;
|
WAITER.endAsync(token);
|
||||||
};
|
};
|
||||||
|
|
||||||
s.onload = s.onreadystatechange = function (_, abort) {
|
s.onload = s.onreadystatechange = function (_, abort) {
|
||||||
finished = true;
|
|
||||||
if (
|
if (
|
||||||
abort ||
|
abort ||
|
||||||
!s.readyState ||
|
!s.readyState ||
|
||||||
|
@ -38,6 +34,8 @@ function loadWithTag(path, cb) {
|
||||||
run(null, cb);
|
run(null, cb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WAITER.endAsync(token);
|
||||||
};
|
};
|
||||||
|
|
||||||
head.appendChild(s);
|
head.appendChild(s);
|
||||||
|
|
|
@ -1,35 +1,5 @@
|
||||||
import { Promise } from "rsvp";
|
|
||||||
import { helperContext } from "discourse-common/lib/helpers";
|
|
||||||
|
|
||||||
// Chrome and Firefox use a native method to do Image -> Bitmap Array (it happens of the main thread!)
|
|
||||||
// Safari < 15 uses the `<img async>` element due to https://bugs.webkit.org/show_bug.cgi?id=182424
|
|
||||||
// Safari > 15 still uses `<img async>` due to their buggy createImageBitmap not handling EXIF rotation
|
|
||||||
async function fileToDrawable(file) {
|
async function fileToDrawable(file) {
|
||||||
const caps = helperContext().capabilities;
|
return await createImageBitmap(file);
|
||||||
|
|
||||||
if ("createImageBitmap" in self && !caps.isApple) {
|
|
||||||
return await createImageBitmap(file);
|
|
||||||
} else {
|
|
||||||
const url = URL.createObjectURL(file);
|
|
||||||
const img = new Image();
|
|
||||||
img.decoding = "async";
|
|
||||||
img.src = url;
|
|
||||||
const loaded = new Promise((resolve, reject) => {
|
|
||||||
img.onload = () => resolve();
|
|
||||||
img.onerror = () => reject(Error("Image loading error"));
|
|
||||||
});
|
|
||||||
|
|
||||||
if (img.decode) {
|
|
||||||
// Nice off-thread way supported in Safari/Chrome.
|
|
||||||
// Safari throws on decode if the source is SVG.
|
|
||||||
// https://bugs.webkit.org/show_bug.cgi?id=188347
|
|
||||||
await img.decode().catch(() => null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always await loaded, as we may have bailed due to the Safari bug above.
|
|
||||||
await loaded;
|
|
||||||
return img;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawableToImageData(drawable) {
|
function drawableToImageData(drawable) {
|
||||||
|
@ -40,17 +10,7 @@ function drawableToImageData(drawable) {
|
||||||
sw = width,
|
sw = width,
|
||||||
sh = height;
|
sh = height;
|
||||||
|
|
||||||
const offscreenCanvasSupported = typeof OffscreenCanvas !== "undefined";
|
let canvas = new OffscreenCanvas(width, height);
|
||||||
|
|
||||||
// Make canvas same size as image
|
|
||||||
let canvas;
|
|
||||||
if (offscreenCanvasSupported) {
|
|
||||||
canvas = new OffscreenCanvas(width, height);
|
|
||||||
} else {
|
|
||||||
canvas = document.createElement("canvas");
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw image onto canvas
|
// Draw image onto canvas
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
|
@ -60,10 +20,6 @@ function drawableToImageData(drawable) {
|
||||||
ctx.drawImage(drawable, sx, sy, sw, sh, 0, 0, width, height);
|
ctx.drawImage(drawable, sx, sy, sw, sh, 0, 0, width, height);
|
||||||
const imageData = ctx.getImageData(0, 0, width, height);
|
const imageData = ctx.getImageData(0, 0, width, height);
|
||||||
|
|
||||||
if (!offscreenCanvasSupported) {
|
|
||||||
canvas.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
return imageData;
|
return imageData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,16 @@ import { computed } from "@ember/object";
|
||||||
import { getOwner } from "@ember/owner";
|
import { getOwner } from "@ember/owner";
|
||||||
import { dasherize } from "@ember/string";
|
import { dasherize } from "@ember/string";
|
||||||
|
|
||||||
export default function (name) {
|
export default function (target, name, descriptor) {
|
||||||
return computed(function (defaultName) {
|
name ??= target;
|
||||||
|
|
||||||
|
const decorator = computed(function (defaultName) {
|
||||||
return getOwner(this).lookup(`service:${name || dasherize(defaultName)}`);
|
return getOwner(this).lookup(`service:${name || dasherize(defaultName)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (descriptor) {
|
||||||
|
return decorator(target, name, descriptor);
|
||||||
|
} else {
|
||||||
|
return decorator;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
// docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version
|
// docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version
|
||||||
// using the format described at https://keepachangelog.com/en/1.0.0/.
|
// using the format described at https://keepachangelog.com/en/1.0.0/.
|
||||||
|
|
||||||
export const PLUGIN_API_VERSION = "1.37.1";
|
export const PLUGIN_API_VERSION = "1.37.2";
|
||||||
|
|
||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
import { h } from "virtual-dom";
|
import { h } from "virtual-dom";
|
||||||
|
@ -2646,7 +2646,7 @@ class PluginApi {
|
||||||
/**
|
/**
|
||||||
* Changes the lock icon used for a sidebar category section link to indicate that a category is read restricted.
|
* Changes the lock icon used for a sidebar category section link to indicate that a category is read restricted.
|
||||||
*
|
*
|
||||||
* @param {String} Name of a FontAwesome 5 icon
|
* @param {String} Name of a FontAwesome icon
|
||||||
*/
|
*/
|
||||||
registerCustomCategorySectionLinkLockIcon(icon) {
|
registerCustomCategorySectionLinkLockIcon(icon) {
|
||||||
return registerCustomCategoryLockIcon(icon);
|
return registerCustomCategoryLockIcon(icon);
|
||||||
|
@ -2670,7 +2670,7 @@ class PluginApi {
|
||||||
* @param {string} arg.categoryId - The id of the category
|
* @param {string} arg.categoryId - The id of the category
|
||||||
* @param {string} arg.prefixType - The type of prefix to use. Can be "icon", "image", "text" or "span".
|
* @param {string} arg.prefixType - The type of prefix to use. Can be "icon", "image", "text" or "span".
|
||||||
* @param {string} arg.prefixValue - The value of the prefix to use.
|
* @param {string} arg.prefixValue - The value of the prefix to use.
|
||||||
* For "icon", pass in the name of a FontAwesome 5 icon.
|
* For "icon", pass in the name of a FontAwesome icon.
|
||||||
* For "image", pass in the src of the image.
|
* For "image", pass in the src of the image.
|
||||||
* For "text", pass in the text to display.
|
* For "text", pass in the text to display.
|
||||||
* For "span", pass in an array containing two hex color values. Example: `[FF0000, 000000]`.
|
* For "span", pass in an array containing two hex color values. Example: `[FF0000, 000000]`.
|
||||||
|
@ -2706,7 +2706,7 @@ class PluginApi {
|
||||||
*
|
*
|
||||||
* @param {Object} arg - An object
|
* @param {Object} arg - An object
|
||||||
* @param {string} arg.tagName - The name of the tag
|
* @param {string} arg.tagName - The name of the tag
|
||||||
* @param {string} arg.prefixValue - The name of a FontAwesome 5 icon.
|
* @param {string} arg.prefixValue - The name of a FontAwesome icon.
|
||||||
* @param {string} arg.prefixColor - The color represented using hexadecimal to use for the prefix. Example: "#FF0000" or "#FFF".
|
* @param {string} arg.prefixColor - The color represented using hexadecimal to use for the prefix. Example: "#FF0000" or "#FFF".
|
||||||
*/
|
*/
|
||||||
registerCustomTagSectionLinkPrefixIcon({
|
registerCustomTagSectionLinkPrefixIcon({
|
||||||
|
@ -3002,7 +3002,7 @@ class PluginApi {
|
||||||
* return class extends UserMenuTab {
|
* return class extends UserMenuTab {
|
||||||
* id = "custom-tab-id";
|
* id = "custom-tab-id";
|
||||||
* panelComponent = MyCustomPanelGlimmerComponent;
|
* panelComponent = MyCustomPanelGlimmerComponent;
|
||||||
* icon = "some-fa5-icon";
|
* icon = "some-fa-icon";
|
||||||
*
|
*
|
||||||
* get shouldDisplay() {
|
* get shouldDisplay() {
|
||||||
* return this.siteSettings.enable_custom_tab && this.currentUser.admin;
|
* return this.siteSettings.enable_custom_tab && this.currentUser.admin;
|
||||||
|
|
|
@ -16,7 +16,7 @@ export let secondaryCustomSectionLinks = [];
|
||||||
* @param {string} [args.route] - The Ember route name to generate the href attribute for the link.
|
* @param {string} [args.route] - The Ember route name to generate the href attribute for the link.
|
||||||
* @param {string} [args.href] - The href attribute for the link.
|
* @param {string} [args.href] - The href attribute for the link.
|
||||||
* @param {string} [args.title] - The title attribute for the link.
|
* @param {string} [args.title] - The title attribute for the link.
|
||||||
* @param {string} [args.icon] - The FontAwesome 5 icon to display for the link.
|
* @param {string} [args.icon] - The FontAwesome icon to display for the link.
|
||||||
* @param {Boolean} [secondary] - Determines whether the section link should be added to the main or secondary section in the "More..." links drawer.
|
* @param {Boolean} [secondary] - Determines whether the section link should be added to the main or secondary section in the "More..." links drawer.
|
||||||
*/
|
*/
|
||||||
export function addSectionLink(args, secondary) {
|
export function addSectionLink(args, secondary) {
|
||||||
|
|
|
@ -58,4 +58,5 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{~raw-plugin-outlet name="topic-list-after-columns"}}
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -29,6 +29,8 @@ export default class MediaOptimizationWorkerService extends Service {
|
||||||
workerUrl = getAbsoluteURL("/javascripts/media-optimization-worker.js");
|
workerUrl = getAbsoluteURL("/javascripts/media-optimization-worker.js");
|
||||||
currentComposerUploadData = null;
|
currentComposerUploadData = null;
|
||||||
promiseResolvers = null;
|
promiseResolvers = null;
|
||||||
|
workerDoneCount = 0;
|
||||||
|
workerPendingCount = 0;
|
||||||
|
|
||||||
async optimizeImage(data, opts = {}) {
|
async optimizeImage(data, opts = {}) {
|
||||||
this.promiseResolvers = this.promiseResolvers || {};
|
this.promiseResolvers = this.promiseResolvers || {};
|
||||||
|
@ -98,6 +100,7 @@ export default class MediaOptimizationWorkerService extends Service {
|
||||||
},
|
},
|
||||||
[imageData.data.buffer]
|
[imageData.data.buffer]
|
||||||
);
|
);
|
||||||
|
this.workerPendingCount++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +150,9 @@ export default class MediaOptimizationWorkerService extends Service {
|
||||||
this.workerInstalled = false;
|
this.workerInstalled = false;
|
||||||
this.worker.terminate();
|
this.worker.terminate();
|
||||||
this.worker = null;
|
this.worker = null;
|
||||||
|
this.workerDoneCount = 0;
|
||||||
}
|
}
|
||||||
|
this.workerPendingCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerMessageHandler() {
|
registerMessageHandler() {
|
||||||
|
@ -163,6 +168,13 @@ export default class MediaOptimizationWorkerService extends Service {
|
||||||
|
|
||||||
this.promiseResolvers[e.data.fileId](optimizedFile);
|
this.promiseResolvers[e.data.fileId](optimizedFile);
|
||||||
|
|
||||||
|
this.workerDoneCount++;
|
||||||
|
this.workerPendingCount--;
|
||||||
|
if (this.workerDoneCount > 4 && this.workerPendingCount === 0) {
|
||||||
|
this.logIfDebug("Terminating worker to release memory in WASM.");
|
||||||
|
this.stopWorker();
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "error":
|
case "error":
|
||||||
this.logIfDebug(
|
this.logIfDebug(
|
||||||
|
@ -174,6 +186,7 @@ export default class MediaOptimizationWorkerService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.promiseResolvers[e.data.fileId]();
|
this.promiseResolvers[e.data.fileId]();
|
||||||
|
this.workerPendingCount--;
|
||||||
break;
|
break;
|
||||||
case "installed":
|
case "installed":
|
||||||
this.logIfDebug("Worker installed.");
|
this.logIfDebug("Worker installed.");
|
||||||
|
|
|
@ -1,32 +1,43 @@
|
||||||
{{body-class "user-badges-page"}}
|
{{body-class "user-badges-page"}}
|
||||||
|
|
||||||
<section class="user-content" id="user-content">
|
<section class="user-content" id="user-content">
|
||||||
{{#if this.siteSettings.max_favorite_badges}}
|
<PluginOutlet
|
||||||
<p class="favorite-count">
|
@name="user-badges-content"
|
||||||
{{i18n
|
@outletArgs={{hash
|
||||||
"badges.favorite_count"
|
sortedBadges=this.sortedBadges
|
||||||
count=this.favoriteBadges.length
|
maxFavBadges=this.siteSettings.max_favorite_badges
|
||||||
max=this.siteSettings.max_favorite_badges
|
favoriteBadges=this.favoriteBadges
|
||||||
}}
|
canFavoriteMoreBadges=this.canFavoriteMoreBadges
|
||||||
</p>
|
favorite=this.favorite
|
||||||
{{/if}}
|
}}
|
||||||
|
>
|
||||||
|
{{#if this.siteSettings.max_favorite_badges}}
|
||||||
|
<p class="favorite-count">
|
||||||
|
{{i18n
|
||||||
|
"badges.favorite_count"
|
||||||
|
count=this.favoriteBadges.length
|
||||||
|
max=this.siteSettings.max_favorite_badges
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
<div class="badge-group-list">
|
<div class="badge-group-list">
|
||||||
{{#each this.sortedBadges as |ub|}}
|
{{#each this.sortedBadges as |ub|}}
|
||||||
<BadgeCard
|
<BadgeCard
|
||||||
@badge={{ub.badge}}
|
@badge={{ub.badge}}
|
||||||
@count={{ub.count}}
|
@count={{ub.count}}
|
||||||
@canFavorite={{ub.can_favorite}}
|
@canFavorite={{ub.can_favorite}}
|
||||||
@isFavorite={{ub.is_favorite}}
|
@isFavorite={{ub.is_favorite}}
|
||||||
@username={{this.username}}
|
@username={{this.username}}
|
||||||
@canFavoriteMoreBadges={{this.canFavoriteMoreBadges}}
|
@canFavoriteMoreBadges={{this.canFavoriteMoreBadges}}
|
||||||
@onFavoriteClick={{action "favorite" ub}}
|
@onFavoriteClick={{action "favorite" ub}}
|
||||||
@filterUser="true"
|
@filterUser="true"
|
||||||
|
/>
|
||||||
|
{{/each}}
|
||||||
|
<PluginOutlet
|
||||||
|
@name="after-user-profile-badges"
|
||||||
|
@outletArgs={{hash user=this.user.model}}
|
||||||
/>
|
/>
|
||||||
{{/each}}
|
</div>
|
||||||
<PluginOutlet
|
</PluginOutlet>
|
||||||
@name="after-user-profile-badges"
|
|
||||||
@outletArgs={{hash user=this.user.model}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
|
@ -62,6 +62,10 @@ export const ButtonClass = {
|
||||||
attributes["aria-pressed"] = attrs.ariaPressed;
|
attributes["aria-pressed"] = attrs.ariaPressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (attrs.ariaLive) {
|
||||||
|
attributes["aria-live"] = attrs.ariaLive;
|
||||||
|
}
|
||||||
|
|
||||||
if (attrs.tabAttrs) {
|
if (attrs.tabAttrs) {
|
||||||
const tab = attrs.tabAttrs;
|
const tab = attrs.tabAttrs;
|
||||||
attributes["aria-selected"] = tab["aria-selected"];
|
attributes["aria-selected"] = tab["aria-selected"];
|
||||||
|
|
|
@ -354,6 +354,7 @@ registerButton("copyLink", () => {
|
||||||
icon: "d-post-share",
|
icon: "d-post-share",
|
||||||
className: "post-action-menu__copy-link",
|
className: "post-action-menu__copy-link",
|
||||||
title: "post.controls.copy_title",
|
title: "post.controls.copy_title",
|
||||||
|
ariaLive: "polite",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ const DeprecationSilencer = require("deprecation-silencer");
|
||||||
const { compatBuild } = require("@embroider/compat");
|
const { compatBuild } = require("@embroider/compat");
|
||||||
const { Webpack } = require("@embroider/webpack");
|
const { Webpack } = require("@embroider/webpack");
|
||||||
const { StatsWriterPlugin } = require("webpack-stats-plugin");
|
const { StatsWriterPlugin } = require("webpack-stats-plugin");
|
||||||
|
const { RetryChunkLoadPlugin } = require("webpack-retry-chunk-load-plugin");
|
||||||
const withSideWatch = require("./lib/with-side-watch");
|
const withSideWatch = require("./lib/with-side-watch");
|
||||||
const RawHandlebarsCompiler = require("discourse-hbr/raw-handlebars-compiler");
|
const RawHandlebarsCompiler = require("discourse-hbr/raw-handlebars-compiler");
|
||||||
const crypto = require("crypto");
|
const crypto = require("crypto");
|
||||||
|
@ -223,6 +224,11 @@ module.exports = function (defaults) {
|
||||||
return JSON.stringify(output, null, 2);
|
return JSON.stringify(output, null, 2);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
new RetryChunkLoadPlugin({
|
||||||
|
retryDelay: 200,
|
||||||
|
maxRetries: 2,
|
||||||
|
chunks: ["assets/discourse.js"],
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,11 +15,11 @@
|
||||||
"test": "ember test"
|
"test": "ember test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@faker-js/faker": "^9.0.0",
|
"@faker-js/faker": "^9.0.3",
|
||||||
"@glimmer/syntax": "^0.92.3",
|
"@glimmer/syntax": "^0.92.3",
|
||||||
"@highlightjs/cdn-assets": "^11.10.0",
|
"@highlightjs/cdn-assets": "^11.10.0",
|
||||||
"@json-editor/json-editor": "2.15.1",
|
"@json-editor/json-editor": "2.15.1",
|
||||||
"@messageformat/core": "^3.3.0",
|
"@messageformat/core": "^3.4.0",
|
||||||
"@messageformat/runtime": "^3.0.1",
|
"@messageformat/runtime": "^3.0.1",
|
||||||
"ace-builds": "^1.36.2",
|
"ace-builds": "^1.36.2",
|
||||||
"decorator-transforms": "^2.0.0",
|
"decorator-transforms": "^2.0.0",
|
||||||
|
@ -35,8 +35,8 @@
|
||||||
"pretty-text": "workspace:1.0.0"
|
"pretty-text": "workspace:1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.7",
|
||||||
"@babel/standalone": "^7.25.6",
|
"@babel/standalone": "^7.25.7",
|
||||||
"@colors/colors": "^1.6.0",
|
"@colors/colors": "^1.6.0",
|
||||||
"@discourse/backburner.js": "^2.7.1-0",
|
"@discourse/backburner.js": "^2.7.1-0",
|
||||||
"@discourse/itsatrap": "^2.0.10",
|
"@discourse/itsatrap": "^2.0.10",
|
||||||
|
@ -47,17 +47,17 @@
|
||||||
"@ember/string": "^4.0.0",
|
"@ember/string": "^4.0.0",
|
||||||
"@ember/test-helpers": "^4.0.4",
|
"@ember/test-helpers": "^4.0.4",
|
||||||
"@ember/test-waiters": "^3.1.0",
|
"@ember/test-waiters": "^3.1.0",
|
||||||
"@embroider/compat": "^3.6.1",
|
"@embroider/compat": "^3.6.2",
|
||||||
"@embroider/core": "^3.4.15",
|
"@embroider/core": "^3.4.17",
|
||||||
"@embroider/macros": "^1.13.1",
|
"@embroider/macros": "^1.16.7",
|
||||||
"@embroider/router": "^2.1.8",
|
"@embroider/router": "^2.1.8",
|
||||||
"@embroider/webpack": "^4.0.5",
|
"@embroider/webpack": "^4.0.6",
|
||||||
"@floating-ui/dom": "^1.6.10",
|
"@floating-ui/dom": "^1.6.11",
|
||||||
"@glimmer/component": "^1.1.2",
|
"@glimmer/component": "^1.1.2",
|
||||||
"@glimmer/tracking": "^1.1.2",
|
"@glimmer/tracking": "^1.1.2",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"@swc/core": "^1.7.26",
|
"@swc/core": "^1.7.26",
|
||||||
"@types/jquery": "^3.5.30",
|
"@types/jquery": "^3.5.31",
|
||||||
"@types/qunit": "^2.19.10",
|
"@types/qunit": "^2.19.10",
|
||||||
"@types/rsvp": "^4.0.9",
|
"@types/rsvp": "^4.0.9",
|
||||||
"@uppy/aws-s3": "3.0.6",
|
"@uppy/aws-s3": "3.0.6",
|
||||||
|
@ -81,10 +81,10 @@
|
||||||
"discourse-i18n": "workspace:1.0.0",
|
"discourse-i18n": "workspace:1.0.0",
|
||||||
"discourse-markdown-it": "workspace:1.0.0",
|
"discourse-markdown-it": "workspace:1.0.0",
|
||||||
"discourse-plugins": "workspace:1.0.0",
|
"discourse-plugins": "workspace:1.0.0",
|
||||||
"ember-auto-import": "^2.7.4",
|
"ember-auto-import": "^2.8.1",
|
||||||
"ember-buffered-proxy": "^2.1.1",
|
"ember-buffered-proxy": "^2.1.1",
|
||||||
"ember-cached-decorator-polyfill": "^1.0.2",
|
"ember-cached-decorator-polyfill": "^1.0.2",
|
||||||
"ember-cli": "~5.11.0",
|
"ember-cli": "~5.12.0",
|
||||||
"ember-cli-app-version": "^7.0.0",
|
"ember-cli-app-version": "^7.0.0",
|
||||||
"ember-cli-babel": "^8.2.0",
|
"ember-cli-babel": "^8.2.0",
|
||||||
"ember-cli-deprecation-workflow": "^3.0.2",
|
"ember-cli-deprecation-workflow": "^3.0.2",
|
||||||
|
@ -95,36 +95,37 @@
|
||||||
"ember-cli-terser": "^4.0.2",
|
"ember-cli-terser": "^4.0.2",
|
||||||
"ember-decorators": "^6.1.1",
|
"ember-decorators": "^6.1.1",
|
||||||
"ember-exam": "^9.0.0",
|
"ember-exam": "^9.0.0",
|
||||||
"ember-load-initializers": "^2.1.1",
|
"ember-load-initializers": "^3.0.1",
|
||||||
"ember-modifier": "^4.2.0",
|
"ember-modifier": "^4.2.0",
|
||||||
"ember-on-resize-modifier": "^2.0.2",
|
"ember-on-resize-modifier": "^2.0.2",
|
||||||
"ember-production-deprecations": "workspace:1.0.0",
|
"ember-production-deprecations": "workspace:1.0.0",
|
||||||
"ember-qunit": "^8.1.0",
|
"ember-qunit": "^8.1.0",
|
||||||
"ember-source": "~5.5.0",
|
"ember-source": "~5.5.0",
|
||||||
"ember-template-imports": "^4.1.1",
|
"ember-template-imports": "^4.1.2",
|
||||||
"ember-test-selectors": "^7.0.0",
|
"ember-test-selectors": "^7.0.0",
|
||||||
"float-kit": "workspace:1.0.0",
|
"float-kit": "workspace:1.0.0",
|
||||||
"html-entities": "^2.5.2",
|
"html-entities": "^2.5.2",
|
||||||
"imports-loader": "^5.0.0",
|
"imports-loader": "^5.0.0",
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jsuites": "^5.6.0",
|
"jsuites": "^5.6.5",
|
||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"make-plural": "^7.4.0",
|
"make-plural": "^7.4.0",
|
||||||
"message-bus-client": "^4.3.8",
|
"message-bus-client": "^4.3.8",
|
||||||
"pretender": "^3.4.7",
|
"pretender": "^3.4.7",
|
||||||
"qunit": "^2.22.0",
|
"qunit": "^2.22.0",
|
||||||
"qunit-dom": "^3.2.0",
|
"qunit-dom": "^3.2.1",
|
||||||
"sass": "^1.77.7",
|
"sass": "^1.77.7",
|
||||||
"select-kit": "workspace:1.0.0",
|
"select-kit": "workspace:1.0.0",
|
||||||
"sinon": "^19.0.1",
|
"sinon": "^19.0.2",
|
||||||
"source-map": "^0.7.4",
|
"source-map": "^0.7.4",
|
||||||
"terser": "^5.32.0",
|
"terser": "^5.34.1",
|
||||||
"testem": "^3.15.2",
|
"testem": "^3.15.2",
|
||||||
"truth-helpers": "workspace:1.0.0",
|
"truth-helpers": "workspace:1.0.0",
|
||||||
"util": "^0.12.5",
|
"util": "^0.12.5",
|
||||||
"virtual-dom": "^2.1.1",
|
"virtual-dom": "^2.1.1",
|
||||||
"webpack": "^5.94.0",
|
"webpack": "^5.95.0",
|
||||||
|
"webpack-retry-chunk-load-plugin": "^3.1.1",
|
||||||
"webpack-stats-plugin": "^1.1.3",
|
"webpack-stats-plugin": "^1.1.3",
|
||||||
"xss": "^1.0.15"
|
"xss": "^1.0.15"
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,6 @@ import {
|
||||||
acceptance,
|
acceptance,
|
||||||
count,
|
count,
|
||||||
exists,
|
exists,
|
||||||
query,
|
|
||||||
} from "discourse/tests/helpers/qunit-helpers";
|
} from "discourse/tests/helpers/qunit-helpers";
|
||||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||||
|
|
||||||
|
@ -60,13 +59,6 @@ acceptance("Dashboard", function (needs) {
|
||||||
exists(".admin-report.new-contributors"),
|
exists(".admin-report.new-contributors"),
|
||||||
"new-contributors report"
|
"new-contributors report"
|
||||||
);
|
);
|
||||||
assert.strictEqual(
|
|
||||||
query(
|
|
||||||
".section.dashboard-problems .problem-messages ul li:first-child"
|
|
||||||
).innerHTML.trim(),
|
|
||||||
"Houston...",
|
|
||||||
"displays problems"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("moderation tab", async function (assert) {
|
test("moderation tab", async function (assert) {
|
||||||
|
|
|
@ -142,12 +142,6 @@ class FieldHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasSubtitle(subtitle, message) {
|
|
||||||
this.context
|
|
||||||
.dom(this.element.querySelector(".form-kit__container-subtitle"))
|
|
||||||
.hasText(subtitle, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
hasDescription(description, message) {
|
hasDescription(description, message) {
|
||||||
switch (this.element.dataset.controlType) {
|
switch (this.element.dataset.controlType) {
|
||||||
case "checkbox": {
|
case "checkbox": {
|
||||||
|
@ -162,7 +156,7 @@ class FieldHelper {
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
this.context
|
this.context
|
||||||
.dom(this.element.querySelector(".form-kit__meta-description"))
|
.dom(this.element.querySelector(".form-kit__container-description"))
|
||||||
.hasText(description, message);
|
.hasText(description, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ module(
|
||||||
test("default", async function (assert) {
|
test("default", async function (assert) {
|
||||||
await render(<template>
|
await render(<template>
|
||||||
<Form as |form|>
|
<Form as |form|>
|
||||||
<form.Field @name="foo" @title="Foo" @subtitle="Bar" as |field|>
|
<form.Field @name="foo" @title="Foo" @description="Bar" as |field|>
|
||||||
<field.Custom>
|
<field.Custom>
|
||||||
<input class="custom-test" />
|
<input class="custom-test" />
|
||||||
</field.Custom>
|
</field.Custom>
|
||||||
|
@ -20,7 +20,7 @@ module(
|
||||||
</template>);
|
</template>);
|
||||||
|
|
||||||
assert.dom(".form-kit__container-title").hasText("Foo (optional)");
|
assert.dom(".form-kit__container-title").hasText("Foo (optional)");
|
||||||
assert.dom(".form-kit__container-subtitle").hasText("Bar");
|
assert.dom(".form-kit__container-description").hasText("Bar");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -35,18 +35,6 @@ module("Integration | Component | FormKit | Field", function (hooks) {
|
||||||
assert.dom(".form-kit__row .form-kit__col.--col-8").hasText("Test");
|
assert.dom(".form-kit__row .form-kit__col.--col-8").hasText("Test");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("@subtitle", async function (assert) {
|
|
||||||
await render(<template>
|
|
||||||
<Form as |form|>
|
|
||||||
<form.Field @name="foo" @title="Foo" @subtitle="foo foo" as |field|>
|
|
||||||
<field.Input />
|
|
||||||
</form.Field>
|
|
||||||
</Form>
|
|
||||||
</template>);
|
|
||||||
|
|
||||||
assert.form().field("foo").hasSubtitle("foo foo");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("@description", async function (assert) {
|
test("@description", async function (assert) {
|
||||||
await render(<template>
|
await render(<template>
|
||||||
<Form as |form|>
|
<Form as |form|>
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import { render, settled } from "@ember/test-helpers";
|
||||||
|
import { module, test } from "qunit";
|
||||||
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
|
import HighlightedCode from "admin/components/highlighted-code";
|
||||||
|
|
||||||
|
module("Integration | Component | highlighted-code", function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test("highlighting code", async function (assert) {
|
||||||
|
await render(<template>
|
||||||
|
<HighlightedCode @lang="ruby" @code="def test; end" />
|
||||||
|
</template>);
|
||||||
|
|
||||||
|
assert.dom("code.lang-ruby.hljs .hljs-keyword").hasText("def");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("large code blocks are not highlighted", async function (assert) {
|
||||||
|
const longCodeBlock = "puts a\n".repeat(15000);
|
||||||
|
|
||||||
|
await render(<template>
|
||||||
|
<HighlightedCode @lang="ruby" @code={{longCodeBlock}} />
|
||||||
|
</template>);
|
||||||
|
|
||||||
|
assert.dom("pre code").hasText(longCodeBlock);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("highlighting code with lang=auto", async function (assert) {
|
||||||
|
await render(<template>
|
||||||
|
<HighlightedCode @lang="auto" @code="def test; end" />
|
||||||
|
</template>);
|
||||||
|
|
||||||
|
assert.dom("code.hljs").hasNoClass("lang-auto", "lang-auto is removed");
|
||||||
|
assert.dom("code.hljs").hasClass(/language-/, "language is detected");
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom("code.hljs")
|
||||||
|
.hasNoAttribute(
|
||||||
|
"data-unknown-hljs-lang",
|
||||||
|
"language is found from language- class"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("re-highlights the code when it changes", async function (assert) {
|
||||||
|
class State {
|
||||||
|
@tracked code = "def foo; end";
|
||||||
|
}
|
||||||
|
|
||||||
|
const testState = new State();
|
||||||
|
|
||||||
|
await render(<template>
|
||||||
|
<HighlightedCode @lang="ruby" @code={{testState.code}} />
|
||||||
|
{{testState.code}}
|
||||||
|
</template>);
|
||||||
|
|
||||||
|
assert.dom("code.lang-ruby.hljs .hljs-title").hasText("foo");
|
||||||
|
|
||||||
|
testState.code = "def bar; end";
|
||||||
|
await settled();
|
||||||
|
|
||||||
|
assert.dom("code.lang-ruby.hljs .hljs-title").hasText("bar");
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,61 +0,0 @@
|
||||||
import { render } from "@ember/test-helpers";
|
|
||||||
import { hbs } from "ember-cli-htmlbars";
|
|
||||||
import { module, test } from "qunit";
|
|
||||||
import highlightSyntax from "discourse/lib/highlight-syntax";
|
|
||||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
|
||||||
import { query } from "discourse/tests/helpers/qunit-helpers";
|
|
||||||
|
|
||||||
const LONG_CODE_BLOCK = "puts a\n".repeat(15000);
|
|
||||||
|
|
||||||
module("Integration | Component | highlighted-code", function (hooks) {
|
|
||||||
setupRenderingTest(hooks);
|
|
||||||
|
|
||||||
test("highlighting code", async function (assert) {
|
|
||||||
this.set("code", "def test; end");
|
|
||||||
|
|
||||||
await render(hbs`<HighlightedCode @lang="ruby" @code={{this.code}} />`);
|
|
||||||
|
|
||||||
assert.strictEqual(
|
|
||||||
query("code.lang-ruby.hljs .hljs-keyword").innerText.trim(),
|
|
||||||
"def"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("large code blocks are not highlighted", async function (assert) {
|
|
||||||
this.set("code", LONG_CODE_BLOCK);
|
|
||||||
|
|
||||||
await render(hbs`<HighlightedCode @lang="ruby" @code={{this.code}} />`);
|
|
||||||
|
|
||||||
assert.strictEqual(query("code").innerText.trim(), LONG_CODE_BLOCK.trim());
|
|
||||||
});
|
|
||||||
|
|
||||||
test("highlighting code with lang=auto", async function (assert) {
|
|
||||||
this.set("code", "def test; end");
|
|
||||||
|
|
||||||
await render(hbs`<HighlightedCode @lang="auto" @code={{this.code}} />`);
|
|
||||||
|
|
||||||
const codeElement = query("code.hljs");
|
|
||||||
|
|
||||||
assert.notOk(
|
|
||||||
codeElement.classList.contains("lang-auto"),
|
|
||||||
"lang-auto is removed"
|
|
||||||
);
|
|
||||||
assert.ok(
|
|
||||||
Array.from(codeElement.classList).some((className) => {
|
|
||||||
return className.startsWith("language-");
|
|
||||||
}),
|
|
||||||
"language is detected"
|
|
||||||
);
|
|
||||||
|
|
||||||
await highlightSyntax(
|
|
||||||
codeElement.parentElement, // <pre>
|
|
||||||
this.siteSettings,
|
|
||||||
this.session
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.notOk(
|
|
||||||
codeElement.dataset.unknownHljsLang,
|
|
||||||
"language is found from language- class"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -8,6 +8,7 @@ import raw from "discourse/helpers/raw";
|
||||||
import rawRenderGlimmer from "discourse/lib/raw-render-glimmer";
|
import rawRenderGlimmer from "discourse/lib/raw-render-glimmer";
|
||||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
import { compile } from "discourse-common/lib/raw-handlebars";
|
import { compile } from "discourse-common/lib/raw-handlebars";
|
||||||
|
import { RUNTIME_OPTIONS } from "discourse-common/lib/raw-handlebars-helpers";
|
||||||
import {
|
import {
|
||||||
addRawTemplate,
|
addRawTemplate,
|
||||||
removeRawTemplate,
|
removeRawTemplate,
|
||||||
|
@ -121,4 +122,26 @@ module("Integration | Helper | raw", function (hooks) {
|
||||||
|
|
||||||
assert.dom("span").hasText("foo 1 foo 2");
|
assert.dom("span").hasText("foo 1 foo 2");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("#each helper handles getters", async function (assert) {
|
||||||
|
const template = `
|
||||||
|
{{#each items as |item|}}
|
||||||
|
{{string}} {{item}}
|
||||||
|
{{/each}}
|
||||||
|
`;
|
||||||
|
const compiledTemplate = compile(template);
|
||||||
|
|
||||||
|
class Test {
|
||||||
|
items = [1, 2];
|
||||||
|
|
||||||
|
get string() {
|
||||||
|
return "foo";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const object = new Test();
|
||||||
|
|
||||||
|
const output = compiledTemplate(object, RUNTIME_OPTIONS);
|
||||||
|
assert.true(/\s*foo 1\s*foo 2\s*/.test(output));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,8 +22,8 @@ module("Unit | Utility | icon-library", function (hooks) {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("convert icon names", function (assert) {
|
test("convert icon names", function (assert) {
|
||||||
const fa5Icon = convertIconClass("fab fa-facebook");
|
const faIcon = convertIconClass("fab fa-facebook");
|
||||||
assert.ok(iconHTML(fa5Icon).includes("fab-facebook"), "FA 5 syntax");
|
assert.ok(iconHTML(faIcon).includes("fab-facebook"), "FA syntax");
|
||||||
|
|
||||||
const iconC = convertIconClass(" fab fa-facebook ");
|
const iconC = convertIconClass(" fab fa-facebook ");
|
||||||
assert.ok(!iconHTML(iconC).includes(" "), "trims whitespace");
|
assert.ok(!iconHTML(iconC).includes(" "), "trims whitespace");
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import Service from "@ember/service";
|
||||||
|
import { render } from "@ember/test-helpers";
|
||||||
|
import { hbs } from "ember-cli-htmlbars";
|
||||||
|
import { module, test } from "qunit";
|
||||||
|
import optionalService from "discourse/lib/optional-service";
|
||||||
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
|
|
||||||
|
class FooService extends Service {
|
||||||
|
name = "foo";
|
||||||
|
}
|
||||||
|
|
||||||
|
class BarService extends Service {
|
||||||
|
name = "bar";
|
||||||
|
}
|
||||||
|
|
||||||
|
const EmberObjectComponent = Component.extend({
|
||||||
|
name: "",
|
||||||
|
layout: hbs`<span class="ember-object-component">{{this.foo.name}} {{this.baz.name}}</span>`,
|
||||||
|
|
||||||
|
foo: optionalService(),
|
||||||
|
baz: optionalService("bar"),
|
||||||
|
});
|
||||||
|
|
||||||
|
class NativeComponent extends Component {
|
||||||
|
@optionalService foo;
|
||||||
|
@optionalService("bar") baz;
|
||||||
|
|
||||||
|
name = "";
|
||||||
|
layout = hbs`<span class="native-component">{{this.foo.name}} {{this.baz.name}}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
module("Unit | Utils | optional-service", function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
hooks.beforeEach(function () {
|
||||||
|
this.registry.register("service:foo", FooService);
|
||||||
|
this.registry.register("service:bar", BarService);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("optionalService works in EmberObject classes", async function (assert) {
|
||||||
|
this.registry.register(
|
||||||
|
"component:ember-object-component",
|
||||||
|
EmberObjectComponent
|
||||||
|
);
|
||||||
|
|
||||||
|
await render(hbs`<EmberObjectComponent />`);
|
||||||
|
|
||||||
|
assert.dom(".ember-object-component").hasText("foo bar");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("optionalService works in native classes", async function (assert) {
|
||||||
|
this.registry.register("component:native-component", NativeComponent);
|
||||||
|
|
||||||
|
await render(hbs`<NativeComponent />`);
|
||||||
|
|
||||||
|
assert.dom(".native-component").hasText("foo bar");
|
||||||
|
});
|
||||||
|
});
|
|
@ -14,32 +14,32 @@
|
||||||
"start": "ember serve"
|
"start": "ember serve"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.7",
|
||||||
"ember-auto-import": "^2.7.4",
|
"ember-auto-import": "^2.8.1",
|
||||||
"ember-cli-babel": "^8.2.0",
|
"ember-cli-babel": "^8.2.0",
|
||||||
"ember-cli-htmlbars": "^6.3.0",
|
"ember-cli-htmlbars": "^6.3.0",
|
||||||
"ember-template-imports": "^4.1.1",
|
"ember-template-imports": "^4.1.2",
|
||||||
"truth-helpers": "workspace:1.0.0"
|
"truth-helpers": "workspace:1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ember/optional-features": "^2.1.0",
|
"@ember/optional-features": "^2.1.0",
|
||||||
"@embroider/test-setup": "^4.0.0",
|
"@embroider/test-setup": "^4.0.0",
|
||||||
"@glimmer/component": "^1.1.2",
|
"@glimmer/component": "^1.1.2",
|
||||||
"@types/jquery": "^3.5.30",
|
"@types/jquery": "^3.5.31",
|
||||||
"@types/qunit": "^2.19.10",
|
"@types/qunit": "^2.19.10",
|
||||||
"@types/rsvp": "^4.0.9",
|
"@types/rsvp": "^4.0.9",
|
||||||
"broccoli-asset-rev": "^3.0.0",
|
"broccoli-asset-rev": "^3.0.0",
|
||||||
"ember-cli": "~5.11.0",
|
"ember-cli": "~5.12.0",
|
||||||
"ember-cli-inject-live-reload": "^2.1.0",
|
"ember-cli-inject-live-reload": "^2.1.0",
|
||||||
"ember-cli-sri": "^2.1.1",
|
"ember-cli-sri": "^2.1.1",
|
||||||
"ember-cli-terser": "^4.0.2",
|
"ember-cli-terser": "^4.0.2",
|
||||||
"ember-disable-prototype-extensions": "^1.1.3",
|
"ember-disable-prototype-extensions": "^1.1.3",
|
||||||
"ember-load-initializers": "^2.1.1",
|
"ember-load-initializers": "^3.0.1",
|
||||||
"ember-resolver": "^13.0.0",
|
"ember-resolver": "^13.0.2",
|
||||||
"ember-source": "~5.5.0",
|
"ember-source": "~5.5.0",
|
||||||
"ember-source-channel-url": "^3.0.0",
|
"ember-source-channel-url": "^3.0.0",
|
||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"webpack": "^5.94.0"
|
"webpack": "^5.95.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18",
|
"node": ">= 18",
|
||||||
|
|
|
@ -14,10 +14,10 @@
|
||||||
"start": "ember serve"
|
"start": "ember serve"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.7",
|
||||||
"discourse-common": "workspace:1.0.0",
|
"discourse-common": "workspace:1.0.0",
|
||||||
"discourse-i18n": "workspace:1.0.0",
|
"discourse-i18n": "workspace:1.0.0",
|
||||||
"ember-auto-import": "^2.7.4",
|
"ember-auto-import": "^2.8.1",
|
||||||
"ember-cli-babel": "^8.2.0",
|
"ember-cli-babel": "^8.2.0",
|
||||||
"ember-cli-htmlbars": "^6.3.0",
|
"ember-cli-htmlbars": "^6.3.0",
|
||||||
"xss": "^1.0.15"
|
"xss": "^1.0.15"
|
||||||
|
@ -26,21 +26,21 @@
|
||||||
"@ember/optional-features": "^2.1.0",
|
"@ember/optional-features": "^2.1.0",
|
||||||
"@embroider/test-setup": "^4.0.0",
|
"@embroider/test-setup": "^4.0.0",
|
||||||
"@glimmer/component": "^1.1.2",
|
"@glimmer/component": "^1.1.2",
|
||||||
"@types/jquery": "^3.5.30",
|
"@types/jquery": "^3.5.31",
|
||||||
"@types/qunit": "^2.19.10",
|
"@types/qunit": "^2.19.10",
|
||||||
"@types/rsvp": "^4.0.9",
|
"@types/rsvp": "^4.0.9",
|
||||||
"broccoli-asset-rev": "^3.0.0",
|
"broccoli-asset-rev": "^3.0.0",
|
||||||
"ember-cli": "~5.11.0",
|
"ember-cli": "~5.12.0",
|
||||||
"ember-cli-inject-live-reload": "^2.1.0",
|
"ember-cli-inject-live-reload": "^2.1.0",
|
||||||
"ember-cli-sri": "^2.1.1",
|
"ember-cli-sri": "^2.1.1",
|
||||||
"ember-cli-terser": "^4.0.2",
|
"ember-cli-terser": "^4.0.2",
|
||||||
"ember-disable-prototype-extensions": "^1.1.3",
|
"ember-disable-prototype-extensions": "^1.1.3",
|
||||||
"ember-load-initializers": "^2.1.1",
|
"ember-load-initializers": "^3.0.1",
|
||||||
"ember-resolver": "^13.0.0",
|
"ember-resolver": "^13.0.2",
|
||||||
"ember-source": "~5.5.0",
|
"ember-source": "~5.5.0",
|
||||||
"ember-source-channel-url": "^3.0.0",
|
"ember-source-channel-url": "^3.0.0",
|
||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"webpack": "^5.94.0"
|
"webpack": "^5.95.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18",
|
"node": ">= 18",
|
||||||
|
|
|
@ -14,33 +14,33 @@
|
||||||
"start": "ember serve"
|
"start": "ember serve"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.7",
|
||||||
"@ember/string": "^4.0.0",
|
"@ember/string": "^4.0.0",
|
||||||
"discourse-i18n": "workspace:1.0.0",
|
"discourse-i18n": "workspace:1.0.0",
|
||||||
"ember-auto-import": "^2.7.4",
|
"ember-auto-import": "^2.8.1",
|
||||||
"ember-cli-babel": "^8.2.0",
|
"ember-cli-babel": "^8.2.0",
|
||||||
"ember-cli-htmlbars": "^6.3.0",
|
"ember-cli-htmlbars": "^6.3.0",
|
||||||
"ember-template-imports": "^4.1.1"
|
"ember-template-imports": "^4.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ember/optional-features": "^2.1.0",
|
"@ember/optional-features": "^2.1.0",
|
||||||
"@embroider/test-setup": "^4.0.0",
|
"@embroider/test-setup": "^4.0.0",
|
||||||
"@glimmer/component": "^1.1.2",
|
"@glimmer/component": "^1.1.2",
|
||||||
"@types/jquery": "^3.5.30",
|
"@types/jquery": "^3.5.31",
|
||||||
"@types/qunit": "^2.19.10",
|
"@types/qunit": "^2.19.10",
|
||||||
"@types/rsvp": "^4.0.9",
|
"@types/rsvp": "^4.0.9",
|
||||||
"broccoli-asset-rev": "^3.0.0",
|
"broccoli-asset-rev": "^3.0.0",
|
||||||
"ember-cli": "~5.11.0",
|
"ember-cli": "~5.12.0",
|
||||||
"ember-cli-inject-live-reload": "^2.1.0",
|
"ember-cli-inject-live-reload": "^2.1.0",
|
||||||
"ember-cli-sri": "^2.1.1",
|
"ember-cli-sri": "^2.1.1",
|
||||||
"ember-cli-terser": "^4.0.2",
|
"ember-cli-terser": "^4.0.2",
|
||||||
"ember-disable-prototype-extensions": "^1.1.3",
|
"ember-disable-prototype-extensions": "^1.1.3",
|
||||||
"ember-load-initializers": "^2.1.1",
|
"ember-load-initializers": "^3.0.1",
|
||||||
"ember-resolver": "^13.0.0",
|
"ember-resolver": "^13.0.2",
|
||||||
"ember-source": "~5.5.0",
|
"ember-source": "~5.5.0",
|
||||||
"ember-source-channel-url": "^3.0.0",
|
"ember-source-channel-url": "^3.0.0",
|
||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"webpack": "^5.94.0"
|
"webpack": "^5.95.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18",
|
"node": ">= 18",
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
"license": "GPL-2.0-only",
|
"license": "GPL-2.0-only",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/standalone": "^7.25.6",
|
"@babel/standalone": "^7.25.7",
|
||||||
"@zxing/text-encoding": "^0.9.0",
|
"@zxing/text-encoding": "^0.9.0",
|
||||||
"babel-plugin-ember-template-compilation": "^2.3.0",
|
"babel-plugin-ember-template-compilation": "^2.3.0",
|
||||||
"content-tag": "^2.0.1",
|
"content-tag": "^2.0.1",
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"polyfill-crypto.getrandomvalues": "^1.0.0",
|
"polyfill-crypto.getrandomvalues": "^1.0.0",
|
||||||
"terser": "^5.32.0",
|
"terser": "^5.34.1",
|
||||||
"decorator-transforms": "^2.0.0"
|
"decorator-transforms": "^2.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embroider/addon-shim": "^1.8.9",
|
"@embroider/addon-shim": "^1.8.9",
|
||||||
"ember-auto-import": "^2.7.4"
|
"ember-auto-import": "^2.8.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18",
|
"node": ">= 18",
|
||||||
|
|
|
@ -1083,6 +1083,7 @@ a.inline-editable-field {
|
||||||
@import "common/admin/api";
|
@import "common/admin/api";
|
||||||
@import "common/admin/backups";
|
@import "common/admin/backups";
|
||||||
@import "common/admin/plugins";
|
@import "common/admin/plugins";
|
||||||
|
@import "common/admin/site-settings";
|
||||||
@import "common/admin/admin_config_area";
|
@import "common/admin/admin_config_area";
|
||||||
@import "common/admin/admin_reports";
|
@import "common/admin/admin_reports";
|
||||||
@import "common/admin/admin_report";
|
@import "common/admin/admin_report";
|
||||||
|
@ -1093,6 +1094,7 @@ a.inline-editable-field {
|
||||||
@import "common/admin/admin_report_stacked_line_chart";
|
@import "common/admin/admin_report_stacked_line_chart";
|
||||||
@import "common/admin/admin_report_table";
|
@import "common/admin/admin_report_table";
|
||||||
@import "common/admin/admin_report_inline_table";
|
@import "common/admin/admin_report_inline_table";
|
||||||
|
@import "common/admin/admin_section_landing_page";
|
||||||
@import "common/admin/admin_page_header";
|
@import "common/admin/admin_page_header";
|
||||||
@import "common/admin/admin_intro";
|
@import "common/admin/admin_intro";
|
||||||
@import "common/admin/admin_emojis";
|
@import "common/admin/admin_emojis";
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
.admin-section-landing-wrapper {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(16em, 1fr));
|
||||||
|
gap: 1em 2em;
|
||||||
|
margin-top: 1em;
|
||||||
|
padding-top: 1em;
|
||||||
|
|
||||||
|
.admin-section-landing-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: subgrid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-row: span 2;
|
||||||
|
gap: 0;
|
||||||
|
margin-bottom: 2em;
|
||||||
|
|
||||||
|
@include breakpoint("mobile-extra-large", min-width) {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.-has-icon {
|
||||||
|
grid-template-columns: 1fr 8fr;
|
||||||
|
|
||||||
|
.admin-section-landing-item__buttons {
|
||||||
|
grid-column: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tagline {
|
||||||
|
font-size: var(--font-down-1);
|
||||||
|
font-weight: normal;
|
||||||
|
color: var(--primary-high);
|
||||||
|
margin: 0;
|
||||||
|
letter-spacing: 0.1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
margin: 0;
|
||||||
|
line-height: var(--line-height-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__description {
|
||||||
|
color: var(--primary-high);
|
||||||
|
margin: 0.25em 0 0.5em;
|
||||||
|
line-height: var(--line-height-large);
|
||||||
|
align-self: start;
|
||||||
|
|
||||||
|
@include breakpoint("mobile-extra-large", min-width) {
|
||||||
|
max-width: 17em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
font-size: var(--font-up-3);
|
||||||
|
color: var(--primary-low-mid);
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__buttons {
|
||||||
|
grid-row: 2;
|
||||||
|
grid-column: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -246,16 +246,21 @@
|
||||||
.problem-messages {
|
.problem-messages {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
|
||||||
&.priority-high {
|
|
||||||
background-color: var(--danger-low);
|
|
||||||
border: 1px solid var(--danger-medium);
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
margin: 0 0 0 1.25em;
|
margin: 0 0 0 1.25em;
|
||||||
|
|
||||||
li.dashboard-problem {
|
li.dashboard-problem {
|
||||||
padding: 0.5em 0.5em;
|
padding: 0.5em 0.5em;
|
||||||
|
|
||||||
|
.notice {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
margin-right: var(--space-4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,13 +10,13 @@
|
||||||
.admin-plugins-list {
|
.admin-plugins-list {
|
||||||
@media screen and (min-width: 550px) {
|
@media screen and (min-width: 550px) {
|
||||||
.admin-plugins-list__row {
|
.admin-plugins-list__row {
|
||||||
grid-template-columns: 0.25fr repeat(4, 1fr);
|
grid-template-columns: 0fr repeat(4, 1fr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include breakpoint(mobile-extra-large) {
|
@include breakpoint(mobile-extra-large) {
|
||||||
.admin-plugins-list__row {
|
.admin-plugins-list__row {
|
||||||
grid-template-columns: 0.25fr repeat(3, 1fr);
|
grid-template-columns: 0fr repeat(3, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-plugins-list {
|
.admin-plugins-list {
|
||||||
|
@ -175,16 +175,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-plugin-config-area {
|
.admin-plugin-config-area {
|
||||||
&__settings {
|
|
||||||
.admin-site-settings-filter-controls {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-label {
|
|
||||||
margin-left: 18px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__empty-list {
|
&__empty-list {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
border: 1px solid var(--primary-low);
|
border: 1px solid var(--primary-low);
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
.admin-controls.admin-site-settings-filter-controls
|
||||||
|
.controls
|
||||||
|
.admin-site-settings-filter-controls__input {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-controls.admin-site-settings-filter-controls .menu-toggle {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-plugin-config-area {
|
||||||
|
&__settings {
|
||||||
|
.admin-site-settings-filter-controls {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-filtered-site-settings {
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-label {
|
||||||
|
margin-left: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -329,6 +329,7 @@
|
||||||
.badge-posts {
|
.badge-posts {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
display: inline-block;
|
||||||
padding: 15px 5px;
|
padding: 15px 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1098,6 +1098,10 @@ a.mention,
|
||||||
a.mention-group {
|
a.mention-group {
|
||||||
// linked
|
// linked
|
||||||
@include mention;
|
@include mention;
|
||||||
|
|
||||||
|
.user-status-message {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mention .emoji {
|
.mention .emoji {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue