Version bump

This commit is contained in:
Penar Musaraj 2023-01-25 13:50:36 -05:00
commit c02cbd9645
No known key found for this signature in database
GPG Key ID: E390435D881FF0F7
630 changed files with 15664 additions and 4428 deletions

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

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

View File

@ -18,9 +18,9 @@ permissions:
jobs:
build:
if: "!(github.event_name == 'push' && github.repository == 'discourse/discourse-private-mirror')"
name: ${{ matrix.target }} ${{ matrix.build_type }}
name: ${{ matrix.target }} ${{ matrix.build_type }} ${{ matrix.ruby }}
runs-on: ${{ (matrix.build_type == 'annotations') && 'ubuntu-latest' || 'ubuntu-20.04-8core' }}
container: discourse/discourse_test:slim${{ (matrix.build_type == 'frontend' || matrix.build_type == 'system') && '-browsers' || '' }}
container: discourse/discourse_test:slim${{ (matrix.build_type == 'frontend' || matrix.build_type == 'system') && '-browsers' || '' }}${{ (matrix.ruby == '3.2') && '-ruby-3.2.0' || '' }}
timeout-minutes: 20
env:
@ -38,6 +38,7 @@ jobs:
matrix:
build_type: [backend, frontend, system, annotations]
target: [core, plugins]
ruby: ['3.1']
exclude:
- build_type: annotations
target: plugins
@ -68,9 +69,9 @@ jobs:
uses: actions/cache@v3
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
key: ${{ runner.os }}-${{ matrix.ruby }}-gem-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gem-
${{ runner.os }}-${{ matrix.ruby }}-gem-
- name: Setup gems
run: |

7
.jsdoc Normal file
View File

@ -0,0 +1,7 @@
// jsdoc doesn't accept paths starting with _ (which is the case on github runners)
// so we need to alter the default config
{
"source": {
"excludePattern": ""
}
}

13
Gemfile
View File

@ -18,7 +18,7 @@ else
# this allows us to include the bits of rails we use without pieces we do not.
#
# To issue a rails update bump the version number here
rails_version = "7.0.3.1"
rails_version = "7.0.4.1"
gem "actionmailer", rails_version
gem "actionpack", rails_version
gem "actionview", rails_version
@ -32,8 +32,8 @@ end
gem "json"
# TODO: At the moment Discourse does not work with Sprockets 4, we would need to correct internals
# This is a desired upgrade we should get to.
gem "sprockets", "3.7.2"
# We intend to drop sprockets rather than upgrade to 4.x
gem "sprockets", git: "https://github.com/rails/sprockets", branch: "3.x"
# this will eventually be added to rails,
# allows us to precompile all our templates in the unicorn master
@ -261,12 +261,7 @@ if ENV["IMPORT"] == "1"
gem "parallel", require: false
end
# workaround for openssl 3.0, see
# https://github.com/pushpad/web-push/pull/2
gem "web-push",
require: false,
git: "https://github.com/xfalcox/web-push",
branch: "openssl-3-compat"
gem "web-push"
gem "colored2", require: false
gem "maxminddb"

View File

@ -6,37 +6,36 @@ GIT
mini_mime (>= 0.1.1)
GIT
remote: https://github.com/xfalcox/web-push
revision: 369df8f475a4cd4832a7679bec16576665f24d24
branch: openssl-3-compat
remote: https://github.com/rails/sprockets
revision: f4d3dae71ef29c44b75a49cfbf8032cce07b423a
branch: 3.x
specs:
web-push (2.1.0)
hkdf (~> 1.0)
jwt (~> 2.0)
openssl (~> 3.0)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
GEM
remote: https://rubygems.org/
specs:
actionmailer (7.0.3.1)
actionpack (= 7.0.3.1)
actionview (= 7.0.3.1)
activejob (= 7.0.3.1)
activesupport (= 7.0.3.1)
actionmailer (7.0.4.1)
actionpack (= 7.0.4.1)
actionview (= 7.0.4.1)
activejob (= 7.0.4.1)
activesupport (= 7.0.4.1)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.3.1)
actionview (= 7.0.3.1)
activesupport (= 7.0.3.1)
actionpack (7.0.4.1)
actionview (= 7.0.4.1)
activesupport (= 7.0.4.1)
rack (~> 2.0, >= 2.2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actionview (7.0.3.1)
activesupport (= 7.0.3.1)
actionview (7.0.4.1)
activesupport (= 7.0.4.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -45,15 +44,15 @@ GEM
actionview (>= 6.0.a)
active_model_serializers (0.8.4)
activemodel (>= 3.0)
activejob (7.0.3.1)
activesupport (= 7.0.3.1)
activejob (7.0.4.1)
activesupport (= 7.0.4.1)
globalid (>= 0.3.6)
activemodel (7.0.3.1)
activesupport (= 7.0.3.1)
activerecord (7.0.3.1)
activemodel (= 7.0.3.1)
activesupport (= 7.0.3.1)
activesupport (7.0.3.1)
activemodel (7.0.4.1)
activesupport (= 7.0.4.1)
activerecord (7.0.4.1)
activemodel (= 7.0.4.1)
activesupport (= 7.0.4.1)
activesupport (7.0.4.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@ -111,7 +110,7 @@ GEM
chunky_png (1.4.0)
coderay (1.1.3)
colored2 (3.1.2)
concurrent-ruby (1.1.10)
concurrent-ruby (1.2.0)
connection_pool (2.3.0)
cose (1.3.0)
cbor (~> 0.5.9)
@ -120,8 +119,9 @@ GEM
crack (0.4.5)
rexml
crass (1.0.6)
css_parser (1.13.0)
css_parser (1.14.0)
addressable
date (3.3.3)
debug_inspector (1.1.0)
diff-lcs (1.5.0)
diffy (3.4.2)
@ -137,15 +137,15 @@ GEM
ecma-re-validator (0.4.0)
regexp_parser (~> 2.2)
email_reply_trimmer (0.1.13)
erubi (1.11.0)
excon (0.96.0)
erubi (1.12.0)
excon (0.97.2)
execjs (2.8.1)
exifr (1.3.10)
fabrication (2.30.0)
faker (2.23.0)
i18n (>= 1.8.11, < 2)
fakeweb (1.3.0)
faraday (2.7.2)
faraday (2.7.4)
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday-net_http (3.0.2)
@ -157,7 +157,7 @@ GEM
ffi (1.15.5)
fspath (3.1.2)
gc_tracer (1.5.1)
globalid (1.0.0)
globalid (1.0.1)
activesupport (>= 5.0)
guess_html_encoding (0.0.11)
hana (1.3.7)
@ -215,7 +215,7 @@ GEM
matrix (0.4.2)
maxminddb (0.1.22)
memory_profiler (1.0.1)
message_bus (4.3.1)
message_bus (4.3.2)
rack (>= 1.1.3)
method_source (1.0.0)
mini_mime (1.1.2)
@ -236,7 +236,8 @@ GEM
mustache (1.1.1)
net-http (0.3.2)
uri
net-imap (0.3.1)
net-imap (0.3.4)
date
net-protocol
net-pop (0.1.2)
net-protocol
@ -245,16 +246,16 @@ GEM
net-smtp (0.3.3)
net-protocol
nio4r (2.5.8)
nokogiri (1.13.10)
nokogiri (1.14.0)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
nokogiri (1.13.10-aarch64-linux)
nokogiri (1.14.0-aarch64-linux)
racc (~> 1.4)
nokogiri (1.13.10-arm64-darwin)
nokogiri (1.14.0-arm64-darwin)
racc (~> 1.4)
nokogiri (1.13.10-x86_64-darwin)
nokogiri (1.14.0-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.13.10-x86_64-linux)
nokogiri (1.14.0-x86_64-linux)
racc (~> 1.4)
oauth (1.1.0)
oauth-tty (~> 1.0, >= 1.0.1)
@ -296,7 +297,7 @@ GEM
openssl (> 2.0, < 3.1)
optimist (3.0.1)
parallel (1.22.1)
parallel_tests (4.0.0)
parallel_tests (4.1.0)
parallel
parser (3.2.0.0)
ast (~> 2.4.1)
@ -316,7 +317,7 @@ GEM
nio4r (~> 2.0)
r2 (0.2.7)
racc (1.6.2)
rack (2.2.5)
rack (2.2.6.2)
rack-mini-profiler (3.0.0)
rack (>= 1.2.0)
rack-protection (3.0.5)
@ -326,7 +327,7 @@ GEM
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.4.4)
rails-html-sanitizer (1.5.0)
loofah (~> 2.19, >= 2.19.1)
rails_failover (0.8.1)
activerecord (> 6.0, < 7.1)
@ -335,9 +336,9 @@ GEM
rails_multisite (4.0.1)
activerecord (> 5.0, < 7.1)
railties (> 5.0, < 7.1)
railties (7.0.3.1)
actionpack (= 7.0.3.1)
activesupport (= 7.0.3.1)
railties (7.0.4.1)
actionpack (= 7.0.4.1)
activesupport (= 7.0.4.1)
method_source
rake (>= 12.2)
thor (~> 1.0)
@ -354,9 +355,9 @@ GEM
optimist (>= 3.0.0)
rchardet (1.8.0)
redis (4.8.0)
redis-namespace (1.9.0)
redis-namespace (1.10.0)
redis (>= 4)
regexp_parser (2.6.1)
regexp_parser (2.6.2)
request_store (1.5.1)
rack (>= 1.4)
rexml (3.2.5)
@ -378,7 +379,7 @@ GEM
rspec-html-matchers (0.10.0)
nokogiri (~> 1)
rspec (>= 3.0.0.a)
rspec-mocks (3.12.2)
rspec-mocks (3.12.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-rails (6.0.1)
@ -397,7 +398,7 @@ GEM
json-schema (>= 2.2, < 4.0)
railties (>= 3.1, < 7.1)
rspec-core (>= 2.14)
rubocop (1.43.0)
rubocop (1.44.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.2.0.0)
@ -409,11 +410,14 @@ GEM
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.24.1)
parser (>= 3.1.1.0)
rubocop-capybara (2.17.0)
rubocop (~> 1.41)
rubocop-discourse (3.0.3)
rubocop (>= 1.1.0)
rubocop-rspec (>= 2.0.0)
rubocop-rspec (2.16.0)
rubocop-rspec (2.18.1)
rubocop (~> 1.33)
rubocop-capybara (~> 2.17)
ruby-prof (1.4.5)
ruby-progressbar (1.11.0)
ruby-readability (0.7.0)
@ -433,7 +437,7 @@ GEM
sprockets (> 3.0)
sprockets-rails
tilt
selenium-webdriver (4.7.1)
selenium-webdriver (4.8.0)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
@ -452,9 +456,6 @@ GEM
snaky_hash (2.0.1)
hashie
version_gem (~> 1.1, >= 1.1.1)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.4.2)
actionpack (>= 5.2)
activesupport (>= 5.2)
@ -483,6 +484,10 @@ GEM
uri (0.12.0)
uri_template (0.7.0)
version_gem (1.1.1)
web-push (3.0.0)
hkdf (~> 1.0)
jwt (~> 2.0)
openssl (~> 3.0)
webdrivers (5.2.0)
nokogiri (~> 1.6)
rubyzip (>= 1.3.0)
@ -496,7 +501,7 @@ GEM
xorcist (1.1.3)
xpath (3.2.0)
nokogiri (~> 1.8)
yaml-lint (0.0.10)
yaml-lint (0.1.2)
zeitwerk (2.6.6)
PLATFORMS
@ -509,14 +514,14 @@ PLATFORMS
x86_64-linux
DEPENDENCIES
actionmailer (= 7.0.3.1)
actionpack (= 7.0.3.1)
actionview (= 7.0.3.1)
actionmailer (= 7.0.4.1)
actionpack (= 7.0.4.1)
actionview (= 7.0.4.1)
actionview_precompiler
active_model_serializers (~> 0.8.3)
activemodel (= 7.0.3.1)
activerecord (= 7.0.3.1)
activesupport (= 7.0.3.1)
activemodel (= 7.0.4.1)
activerecord (= 7.0.4.1)
activesupport (= 7.0.4.1)
addressable
annotate
aws-sdk-s3
@ -601,7 +606,7 @@ DEPENDENCIES
rack-protection
rails_failover
rails_multisite
railties (= 7.0.3.1)
railties (= 7.0.4.1)
rake
rb-fsevent
rbtrace
@ -627,7 +632,7 @@ DEPENDENCIES
shoulda-matchers
sidekiq
simplecov
sprockets (= 3.7.2)
sprockets!
sprockets-rails
sshkey
stackprof
@ -638,7 +643,7 @@ DEPENDENCIES
uglifier
unf
unicorn
web-push!
web-push
webdrivers
webmock
webrick

View File

@ -30,7 +30,7 @@ To get your environment setup, follow the community setup guide for your operati
If you're familiar with how Rails works and are comfortable setting up your own environment, you can also try out the [**Discourse Advanced Developer Guide**](docs/DEVELOPER-ADVANCED.md), which is aimed primarily at Ubuntu and macOS environments.
Before you get started, ensure you have the following minimum versions: [Ruby 2.7+](https://www.ruby-lang.org/en/downloads/), [PostgreSQL 13+](https://www.postgresql.org/download/), [Redis 6.2+](https://redis.io/download). If you're having trouble, please see our [**TROUBLESHOOTING GUIDE**](docs/TROUBLESHOOTING.md) first!
Before you get started, ensure you have the following minimum versions: [Ruby 3.1+](https://www.ruby-lang.org/en/downloads/), [PostgreSQL 13](https://www.postgresql.org/download/), [Redis 7](https://redis.io/download). If you're having trouble, please see our [**TROUBLESHOOTING GUIDE**](docs/TROUBLESHOOTING.md) first!
## Setting up Discourse
@ -51,7 +51,7 @@ Discourse supports the **latest, stable releases** of all major browsers and pla
| Microsoft Edge | | |
| Mozilla Firefox | | |
Additionally, we aim to support Safari on iOS 12.5+ until January 2023 (Discourse 3.0).
Additionally, we aim to support Safari on iOS 15.7+.
## Built With

View File

@ -57,7 +57,6 @@
/>
{{/if}}
<div class="penalty-history">{{html-safe this.penaltyHistory}}</div>
{{else}}
{{#if (eq this.penaltyType "suspend")}}
<div class="cant-suspend">{{i18n "admin.user.cant_suspend"}}</div>
@ -65,6 +64,7 @@
<div class="cant-silence">{{i18n "admin.user.cant_silence"}}</div>
{{/if}}
{{/if}}
<div class="penalty-history">{{html-safe this.penaltyHistory}}</div>
</ConditionalLoadingSpinner>
</DModalBody>

View File

@ -15,7 +15,7 @@
"start": "ember serve"
},
"dependencies": {
"ember-auto-import": "^2.5.0",
"ember-auto-import": "^2.6.0",
"ember-cli-babel": "^7.26.10",
"ember-cli-htmlbars": "^6.1.1",
"webpack": "^5.75.0",
@ -24,7 +24,7 @@
"devDependencies": {
"@babel/core": "^7.20.12",
"@ember/optional-features": "^2.0.0",
"@embroider/test-setup": "^2.0.2",
"@embroider/test-setup": "^2.1.0",
"@glimmer/component": "^1.1.2",
"broccoli-asset-rev": "^3.0.0",
"ember-cli": "~3.28.5",

View File

@ -252,6 +252,11 @@ async function buildFromBootstrap(proxy, baseURL, req, response, preload) {
url.searchParams.append("safe_mode", reqUrlSafeMode);
}
const enableSidebar = forUrlSearchParams.get("enable_sidebar");
if (enableSidebar) {
url.searchParams.append("enable_sidebar", enableSidebar);
}
const reqUrlPreviewThemeId = forUrlSearchParams.get("preview_theme_id");
if (reqUrlPreviewThemeId) {
url.searchParams.append("preview_theme_id", reqUrlPreviewThemeId);
@ -458,6 +463,13 @@ to serve API requests. For example:
baseURL = rootURL === "" ? "/" : cleanBaseURL(rootURL || baseURL);
const rawMiddleware = express.raw({ type: () => true, limit: "100mb" });
const pathRestrictedRawMiddleware = (req, res, next) => {
if (this.shouldHandleRequest(req, baseURL)) {
return rawMiddleware(req, res, next);
} else {
return next();
}
};
app.use(
"/favicon.ico",
@ -469,9 +481,9 @@ to serve API requests. For example:
)
);
app.use(rawMiddleware, async (req, res, next) => {
app.use(pathRestrictedRawMiddleware, async (req, res, next) => {
try {
if (this.shouldForwardRequest(req, baseURL)) {
if (this.shouldHandleRequest(req, baseURL)) {
await handleRequest(proxy, baseURL, req, res);
} else {
// Fixes issues when using e.g. "localhost" instead of loopback IP address
@ -492,7 +504,7 @@ to serve API requests. For example:
});
},
shouldForwardRequest(request, baseURL) {
shouldHandleRequest(request, baseURL) {
if (
[
`${baseURL}tests/index.html`,
@ -508,6 +520,10 @@ to serve API requests. For example:
return false;
}
if (request.path.startsWith(`${baseURL}message-bus/`)) {
return false;
}
return true;
},
};

View File

@ -24,7 +24,7 @@
"discourse-plugins": "1.0.0",
"express": "^4.18.2",
"html-entities": "^2.3.3",
"jsdom": "^21.0.0",
"jsdom": "^21.1.0",
"node-fetch": "^3.3.0"
}
}

View File

@ -9,7 +9,7 @@
],
"dependencies": {
"a11y-dialog": "7.5.2",
"ember-auto-import": "^2.5.0",
"ember-auto-import": "^2.6.0",
"ember-cli-babel": "^7.26.10",
"ember-cli-htmlbars": "^6.1.1"
},

View File

@ -21,7 +21,7 @@
"@uppy/drop-target": "^2.0.1",
"@uppy/utils": "^5.1.1",
"@uppy/xhr-upload": "^3.0.4",
"ember-auto-import": "^2.5.0",
"ember-auto-import": "^2.6.0",
"ember-cli-babel": "^7.26.10",
"ember-cli-htmlbars": "^6.1.1",
"ember-resolver": "^8.0.3",
@ -32,7 +32,7 @@
"devDependencies": {
"@babel/core": "^7.20.12",
"@ember/optional-features": "^2.0.0",
"@embroider/test-setup": "^2.0.2",
"@embroider/test-setup": "^2.1.0",
"@glimmer/component": "^1.1.2",
"broccoli-asset-rev": "^3.0.0",
"ember-cli": "~3.28.5",

View File

@ -15,7 +15,7 @@
"start": "ember serve"
},
"dependencies": {
"ember-auto-import": "^2.5.0",
"ember-auto-import": "^2.6.0",
"ember-cli-babel": "^7.26.10",
"ember-cli-htmlbars": "^6.1.1",
"handlebars": "^4.7.6",
@ -24,7 +24,7 @@
"devDependencies": {
"@babel/core": "^7.20.12",
"@ember/optional-features": "^2.0.0",
"@embroider/test-setup": "^2.0.2",
"@embroider/test-setup": "^2.1.0",
"@glimmer/component": "^1.1.2",
"broccoli-asset-rev": "^3.0.0",
"ember-cli": "~3.28.5",

View File

@ -10,7 +10,7 @@
"repository": "",
"dependencies": {
"discourse-widget-hbs": "1.0.0",
"ember-auto-import": "^2.5.0",
"ember-auto-import": "^2.6.0",
"ember-cli": "~3.28.5",
"ember-cli-babel": "^7.26.10",
"ember-cli-htmlbars": "^6.1.1",

View File

@ -15,7 +15,7 @@
"start": "ember serve"
},
"dependencies": {
"ember-auto-import": "^2.5.0",
"ember-auto-import": "^2.6.0",
"ember-cli-babel": "^7.26.10",
"ember-cli-htmlbars": "^6.1.1",
"handlebars": "^4.7.6",
@ -24,7 +24,7 @@
"devDependencies": {
"@babel/core": "^7.20.12",
"@ember/optional-features": "^2.0.0",
"@embroider/test-setup": "^2.0.2",
"@embroider/test-setup": "^2.1.0",
"@glimmer/component": "^1.1.2",
"@glimmer/syntax": "^0.84.2",
"broccoli-asset-rev": "^3.0.0",

View File

@ -39,22 +39,24 @@ export default DropdownSelectBoxComponent.extend({
});
}
if (this.bulkSelection.some((m) => !m.primary)) {
items.push({
id: "setPrimary",
name: I18n.t("groups.members.make_all_primary"),
description: I18n.t("groups.members.make_all_primary_description"),
icon: "id-card",
});
}
if (this.currentUser.staff) {
if (this.bulkSelection.some((m) => !m.primary)) {
items.push({
id: "setPrimary",
name: I18n.t("groups.members.make_all_primary"),
description: I18n.t("groups.members.make_all_primary_description"),
icon: "id-card",
});
}
if (this.bulkSelection.some((m) => m.primary)) {
items.push({
id: "unsetPrimary",
name: I18n.t("groups.members.remove_all_primary"),
description: I18n.t("groups.members.remove_all_primary_description"),
icon: "id-card",
});
if (this.bulkSelection.some((m) => m.primary)) {
items.push({
id: "unsetPrimary",
name: I18n.t("groups.members.remove_all_primary"),
description: I18n.t("groups.members.remove_all_primary_description"),
icon: "id-card",
});
}
}
return items;

View File

@ -106,8 +106,6 @@ export default Component.extend(ComposerUploadUppy, {
fileUploadElementId: "file-uploader",
mobileFileUploaderId: "mobile-file-upload",
// TODO (martin) Remove this once the chat plugin is using the new composerEventPrefix
eventPrefix: "composer",
composerEventPrefix: "composer",
uploadType: "composer",
uppyId: "composer-editor-uppy",

View File

@ -11,7 +11,31 @@ export default Component.extend({
attributeBindings: ["ariaCurrent:aria-current", "title"],
ariaCurrent: computed("router.currentRouteName", "route", function () {
return this.router.currentRouteName === this.route ? "page" : null;
}),
ariaCurrent: computed(
"router.currentRouteName",
"router.currentRoute.parent.name",
"route",
"ariaCurrentContext",
function () {
let ariaCurrentValue = "page";
// when there are multiple levels of navigation
// we want the active parent to get `aria-current="page"`
// and the active child to get `aria-current="location"`
if (this.ariaCurrentContext === "subNav") {
ariaCurrentValue = "location";
} else if (this.ariaCurrentContext === "parentNav") {
if (
this.router.currentRouteName !== this.route && // not the current route
this.router.currentRoute.parent.name.includes(this.route) // but is the current parent route
) {
return "page";
}
}
return this.router.currentRouteName === this.route
? ariaCurrentValue
: null;
}
),
});

View File

@ -43,6 +43,15 @@ export default DropdownSelectBoxComponent.extend({
icon: "shield-alt",
});
}
} else if (this.canEditGroup && !this.member.owner) {
items.push({
id: "makeOwner",
name: I18n.t("groups.members.make_owner"),
description: I18n.t("groups.members.make_owner_description", {
username: this.get("member.username"),
}),
icon: "shield-alt",
});
}
if (this.currentUser.staff) {

View File

@ -1,55 +1,63 @@
<section class="user-navigation user-navigation-primary">
<HorizontalOverflowNav @className="main-nav nav user-nav">
<HorizontalOverflowNav
@className="main-nav nav user-nav"
@ariaLabel="User primary"
>
{{#unless @user.profile_hidden}}
<li class="summary">
<LinkTo @route="user.summary">
{{d-icon "user"}}
<span>{{i18n "user.summary.title"}}</span>
</LinkTo>
</li>
<DNavigationItem @route="user.summary" @class="user-nav__summary">
{{d-icon "user"}}
<span>{{i18n "user.summary.title"}}</span>
</DNavigationItem>
<DNavigationItem
@route="userActivity"
@class="user-nav__activity"
@ariaCurrentContext="parentNav"
>
{{d-icon "stream"}}
<span>{{i18n "user.activity_stream"}}</span>
</DNavigationItem>
<li class="user-activity">
<LinkTo @route="userActivity">
{{d-icon "stream"}}
<span>{{i18n "user.activity_stream"}}</span>
</LinkTo>
</li>
{{/unless}}
{{#if @showNotificationsTab}}
<li class="user-notifications">
<LinkTo @route="userNotifications">
{{d-icon "bell" class="glyph"}}
<span>{{i18n "user.notifications"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="userNotifications"
@class="user-nav__notifications"
@ariaCurrentContext="parentNav"
>
{{d-icon "bell" class="glyph"}}
<span>{{i18n "user.notifications"}}</span>
</DNavigationItem>
{{/if}}
{{#if @showPrivateMessages}}
<li class="private-messages">
<LinkTo @route="userPrivateMessages">
{{d-icon "envelope"}}
<span>{{i18n "user.private_messages"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="userPrivateMessages"
@class="user-nav__personal-messages"
@ariaCurrentContext="parentNav"
>
{{d-icon "envelope"}}
<span>{{i18n "user.private_messages"}}</span>
</DNavigationItem>
{{/if}}
{{#if @canInviteToForum}}
<li class="invited">
<LinkTo @route="userInvited">
{{d-icon "user-plus"}}
<span>{{i18n "user.invited.title"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="userInvited"
@class="user-nav__invites"
@ariaCurrentContext="parentNav"
>
{{d-icon "user-plus"}}
<span>{{i18n "user.invited.title"}}</span>
</DNavigationItem>
{{/if}}
{{#if @showBadges}}
<li class="badges">
<LinkTo @route="user.badges">
{{d-icon "certificate"}}
<span>{{i18n "badges.title"}}</span>
</LinkTo>
</li>
<DNavigationItem @route="user.badges" @class="user-nav__badges">
{{d-icon "certificate"}}
<span>{{i18n "badges.title"}}</span>
</DNavigationItem>
{{/if}}
<PluginOutlet
@ -59,16 +67,18 @@
/>
{{#if @user.can_edit}}
<li class="preferences">
<LinkTo @route="preferences">
{{d-icon "cog"}}
<span>{{i18n "user.preferences"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="preferences"
@class="user-nav__preferences"
@ariaCurrentContext="parentNav"
>
{{d-icon "cog"}}
<span>{{i18n "user.preferences"}}</span>
</DNavigationItem>
{{/if}}
{{#if (and this.site.mobileView this.currentUser.staff)}}
<li class="admin">
<li class="user-nav__admin">
<a href={{@user.adminPath}}>
{{d-icon "wrench"}}
<span>{{i18n "admin.user.manage_user"}}</span>

View File

@ -1,44 +1,77 @@
<DNavigationItem @route="userActivity.index">
<DNavigationItem
@route="userActivity.index"
@class="user-nav__activity-all"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "stream"}}
<span>{{i18n "user.filters.all"}}</span>
</DNavigationItem>
<DNavigationItem @route="userActivity.topics">
<DNavigationItem
@route="userActivity.topics"
@class="user-nav__activity-topics"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "list-ul"}}
<span>{{i18n "user_action_groups.4"}}</span>
</DNavigationItem>
<DNavigationItem @route="userActivity.replies">
<DNavigationItem
@route="userActivity.replies"
@class="user-nav__activity-replies"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "reply"}}
<span>{{i18n "user_action_groups.5"}}</span>
</DNavigationItem>
{{#if @user.showRead}}
<DNavigationItem @route="userActivity.read" @title={{i18n "user.read_help"}}>
<DNavigationItem
@route="userActivity.read"
@class="user-nav__activity-read"
@title={{i18n "user.read_help"}}
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "history"}}
<span>{{i18n "user.read"}}</span>
</DNavigationItem>
{{/if}}
{{#if @user.showDrafts}}
<DNavigationItem @route="userActivity.drafts">
<DNavigationItem
@route="userActivity.drafts"
@class="user-nav__activity-drafts"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "pencil-alt"}}
<span>{{@draftLabel}}</span>
</DNavigationItem>
{{/if}}
{{#if (gt @model.pending_posts_count 0)}}
<DNavigationItem @route="userActivity.pending">
<DNavigationItem
@route="userActivity.pending"
@class="user-nav__activity-pending"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "clock"}}
<span>{{@pendingLabel}}</span>
</DNavigationItem>
{{/if}}
<DNavigationItem @route="userActivity.likesGiven">
<DNavigationItem
@route="userActivity.likesGiven"
@class="user-nav__activity-likes"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "heart"}}
<span>{{i18n "user_action_groups.1"}}</span>
</DNavigationItem>
{{#if @user.showBookmarks}}
<DNavigationItem @route="userActivity.bookmarks">
<DNavigationItem
@route="userActivity.bookmarks"
@class="user-nav__activity-bookmarks"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "bookmark"}}
<span>{{i18n "user_action_groups.3"}}</span>
</DNavigationItem>

View File

@ -1,39 +1,49 @@
<li>
<LinkTo @route="userNotifications.index">
{{d-icon "bell"}}
<span>{{i18n "user.filters.all"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="userNotifications.index"
@class="user-nav__notifications-all"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "bell"}}
<span>{{i18n "user.filters.all"}}</span>
</DNavigationItem>
<li>
<LinkTo @route="userNotifications.responses">
{{d-icon "reply"}}
<span>{{i18n "user_action_groups.6"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="userNotifications.responses"
@class="user-nav__notifications-responses"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "reply"}}
<span>{{i18n "user_action_groups.6"}}</span>
</DNavigationItem>
<li>
<LinkTo @route="userNotifications.likesReceived">
{{d-icon "heart"}}
<span>{{i18n "user_action_groups.2"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="userNotifications.likesReceived"
@class="user-nav__notifications-likes"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "heart"}}
<span>{{i18n "user_action_groups.2"}}</span>
</DNavigationItem>
{{#if @siteSettings.enable_mentions}}
<li>
<LinkTo @route="userNotifications.mentions">
{{d-icon "at"}}
<span>{{i18n "user_action_groups.7"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="userNotifications.mentions"
@class="user-nav__notifications-mentions"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "at"}}
<span>{{i18n "user_action_groups.7"}}</span>
</DNavigationItem>
{{/if}}
<li>
<LinkTo @route="userNotifications.edits">
{{d-icon "pencil-alt"}}
<span>{{i18n "user_action_groups.11"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="userNotifications.edits"
@class="user-nav__notifications-edits"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "pencil-alt"}}
<span>{{i18n "user_action_groups.11"}}</span>
</DNavigationItem>
<PluginOutlet
@name="user-notifications-bottom"

View File

@ -1,68 +1,86 @@
<li class="nav-account">
<LinkTo @route="preferences.account">
{{d-icon "user"}}
<span>{{i18n "user.preferences_nav.account"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="preferences.account"
@class="user-nav__preferences-account"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "user"}}
<span>{{i18n "user.preferences_nav.account"}}</span>
</DNavigationItem>
<li class="nav-security">
<LinkTo @route="preferences.security">
{{d-icon "lock"}}
<span>{{i18n "user.preferences_nav.security"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="preferences.security"
@class="user-nav__preferences-security"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "lock"}}
<span>{{i18n "user.preferences_nav.security"}}</span>
</DNavigationItem>
<li class="nav-profile">
<LinkTo @route="preferences.profile">
{{d-icon "user"}}
<span>{{i18n "user.preferences_nav.profile"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="preferences.profile"
@class="user-nav__preferences-profile"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "user"}}
<span>{{i18n "user.preferences_nav.profile"}}</span>
</DNavigationItem>
<li class="nav-emails">
<LinkTo @route="preferences.emails">
{{d-icon "envelope"}}
<span>{{i18n "user.preferences_nav.emails"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="preferences.emails"
@class="user-nav__preferences-emails"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "envelope"}}
<span>{{i18n "user.preferences_nav.emails"}}</span>
</DNavigationItem>
<li class="nav-notifications">
<LinkTo @route="preferences.notifications">
{{d-icon "bell"}}
<span>{{i18n "user.preferences_nav.notifications"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="preferences.notifications"
@class="user-nav__preferences-notifications"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "bell"}}
<span>{{i18n "user.preferences_nav.notifications"}}</span>
</DNavigationItem>
{{#if @model.can_change_tracking_preferences}}
<li class="nav-tracking">
<LinkTo @route="preferences.tracking">
{{d-icon "plus"}}
<span>{{i18n "user.preferences_nav.tracking"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="preferences.tracking"
@class="user-nav__preferences-tracking"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "plus"}}
<span>{{i18n "user.preferences_nav.tracking"}}</span>
</DNavigationItem>
{{/if}}
<li class="indent nav-users">
<LinkTo @route="preferences.users">
{{d-icon "users"}}
<span>{{i18n "user.preferences_nav.users"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="preferences.users"
@class="user-nav__preferences-users"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "users"}}
<span>{{i18n "user.preferences_nav.users"}}</span>
</DNavigationItem>
<li class="nav-interface">
<LinkTo @route="preferences.interface">
{{d-icon "desktop"}}
<span>{{i18n "user.preferences_nav.interface"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="preferences.interface"
@class="user-nav__preferences-interface"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "desktop"}}
<span>{{i18n "user.preferences_nav.interface"}}</span>
</DNavigationItem>
{{#if (not (eq @siteSettings.navigation_menu "legacy"))}}
<li class="indent nav-sidebar">
<LinkTo @route="preferences.sidebar">
{{d-icon "bars"}}
<span>{{i18n "user.preferences_nav.sidebar"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="preferences.sidebar"
@class="user-nav__preferences-sidebar"
@ariaCurrentContext={{@ariaCurrentContext}}
>
{{d-icon "bars"}}
<span>{{i18n "user.preferences_nav.sidebar"}}</span>
</DNavigationItem>
{{/if}}
<PluginOutlet

View File

@ -134,17 +134,17 @@ export default Controller.extend({
case "removeMembers":
return ajax(`/groups/${this.model.id}/members.json`, {
type: "DELETE",
data: { user_ids: selection.map((u) => u.id).join(",") },
data: { user_ids: selection.mapBy("id").join(",") },
}).then(() => {
this.model.reloadMembers(this.memberParams, true);
this.set("isBulk", false);
});
case "makeOwners":
return ajax(`/admin/groups/${this.model.id}/owners.json`, {
return ajax(`/groups/${this.model.id}/owners.json`, {
type: "PUT",
data: {
group: { usernames: selection.map((u) => u.username).join(",") },
usernames: selection.mapBy("username").join(","),
},
}).then(() => {
selection.forEach((s) => s.set("owner", true));

View File

@ -114,6 +114,17 @@ export default Controller.extend(ModalFunctionality, {
);
},
permanentlyDeleteRevisions(postId) {
this.dialog.yesNoConfirm({
message: I18n.t("post.revisions.controls.destroy_confirm"),
didConfirm: () => {
Post.permanentlyDeleteRevisions(postId).then(() => {
this.send("closeModal");
});
},
});
},
show(postId, postVersion) {
Post.showRevision(postId, postVersion).then(() =>
this.refresh(postId, postVersion)
@ -162,6 +173,7 @@ export default Controller.extend(ModalFunctionality, {
},
displayRevisions: gt("model.version_count", 2),
displayGoToFirst: propertyGreaterThan(
"model.current_revision",
"model.first_revision"
@ -215,6 +227,15 @@ export default Controller.extend(ModalFunctionality, {
return this.currentUser && this.currentUser.get("staff");
},
@discourseComputed("model.previous_hidden")
displayPermanentlyDeleteButton(previousHidden) {
return (
this.siteSettings.can_permanently_delete &&
this.currentUser?.staff &&
previousHidden
);
},
isEitherRevisionHidden: or("model.previous_hidden", "model.current_hidden"),
@discourseComputed(
@ -352,6 +373,9 @@ export default Controller.extend(ModalFunctionality, {
hideVersion() {
this.hide(this.get("model.post_id"), this.get("model.current_revision"));
},
permanentlyDeleteVersions() {
this.permanentlyDeleteRevisions(this.get("model.post_id"));
},
showVersion() {
this.show(this.get("model.post_id"), this.get("model.current_revision"));
},

View File

@ -11,6 +11,7 @@ const EMAIL_LEVELS = {
};
export default Controller.extend({
subpageTitle: I18n.t("user.preferences_nav.emails"),
emailMessagesLevelAway: equal(
"model.user_option.email_messages_level",
EMAIL_LEVELS.ONLY_WHEN_AWAY

View File

@ -36,6 +36,7 @@ export default Controller.extend({
preferencesController: controller("preferences"),
makeColorSchemeDefault: true,
canPreviewColorScheme: propertyEqual("model.id", "currentUser.id"),
subpageTitle: I18n.t("user.preferences_nav.interface"),
init() {
this._super(...arguments);

View File

@ -5,6 +5,8 @@ import { NotificationLevels } from "discourse/lib/notification-levels";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default Controller.extend({
subpageTitle: I18n.t("user.preferences_nav.notifications"),
init() {
this._super(...arguments);

View File

@ -12,6 +12,7 @@ import { inject as service } from "@ember/service";
export default Controller.extend({
dialog: service(),
subpageTitle: I18n.t("user.preferences_nav.profile"),
init() {
this._super(...arguments);
this.saveAttrNames = [

View File

@ -15,7 +15,7 @@ const DEFAULT_AUTH_TOKENS_COUNT = 2;
export default Controller.extend(CanCheckEmails, {
passwordProgress: null,
subpageTitle: I18n.t("user.preferences_nav.security"),
showAllAuthTokens: false,
@discourseComputed("model.is_anonymous")

View File

@ -12,6 +12,7 @@ export default class extends Controller {
@tracked saved = false;
@tracked selectedSidebarCategories = [];
@tracked selectedSidebarTagNames = [];
subpageTitle = I18n.t("user.preferences_nav.sidebar");
saveAttrNames = [
"sidebar_category_ids",

View File

@ -86,8 +86,7 @@ export default {
messageBus.baseUrl =
siteSettings.long_polling_base_url.replace(/\/$/, "") + "/";
messageBus.enableChunkedEncoding =
isProduction() && siteSettings.enable_chunked_encoding;
messageBus.enableChunkedEncoding = siteSettings.enable_chunked_encoding;
if (messageBus.baseUrl !== "/") {
messageBus.ajax = function (opts) {

View File

@ -1,30 +0,0 @@
import I18n from "I18n";
import { withPluginApi } from "discourse/lib/plugin-api";
function setupMessage(api) {
const isSafari = navigator.vendor === "Apple Computer, Inc.";
if (!isSafari) {
return;
}
let safariMajorVersion = navigator.userAgent.match(/Version\/(\d+)\./)?.[1];
safariMajorVersion = safariMajorVersion
? parseInt(safariMajorVersion, 10)
: null;
if (safariMajorVersion && safariMajorVersion <= 13) {
api.addGlobalNotice(
I18n.t("safari_13_warning"),
"browser-deprecation-warning",
{ dismissable: true, dismissDuration: moment.duration(1, "week") }
);
}
}
export default {
name: "safari-13-deprecation",
initialize() {
withPluginApi("0.8.37", setupMessage);
},
};

View File

@ -5,7 +5,6 @@ import User from "discourse/models/user";
import { ajax } from "discourse/lib/ajax";
import getURL, { samePrefix } from "discourse-common/lib/get-url";
import { isTesting } from "discourse-common/config/environment";
import { selectedText } from "discourse/lib/utilities";
import { wantsNewWindow } from "discourse/lib/intercept-click";
import deprecated from "discourse-common/lib/deprecated";
import { getOwner } from "discourse-common/lib/get-owner";
@ -80,14 +79,6 @@ export default {
return true;
}
// Cancel click if triggered as part of selection.
const selection = window.getSelection();
if (selection.type === "Range" || selection.rangeCount > 0) {
if (selectedText() !== "") {
return true;
}
}
const link = e.currentTarget;
const tracking = isValidLink(link);

View File

@ -1,3 +1,4 @@
import I18n from "I18n";
import renderTag from "discourse/lib/render-tag";
let callbacks = null;
@ -56,7 +57,8 @@ export default function (topic, params) {
}
if (customHtml || (tags && tags.length > 0)) {
buffer = "<div class='discourse-tags'>";
buffer = `<div class='discourse-tags' role='list'
aria-label=${I18n.t("tagging.tags")}>`;
if (tags) {
for (let i = 0; i < tags.length; i++) {
buffer +=

View File

@ -74,7 +74,7 @@ export default Mixin.create(UploadDebugging, {
}
},
// TODO (martin) This and _onPreProcessComplete will need to be tweaked
// NOTE: This and _onPreProcessComplete will need to be tweaked
// if we ever add support for "determinate" preprocessors for uppy, which
// means the progress will have a value rather than a started/complete
// state ("indeterminate").

View File

@ -31,7 +31,6 @@ export default Mixin.create({
_instrumentUploadTimings() {
if (!this._performanceApiSupport()) {
// TODO (martin) (2021-01-23) Check if FireFox fixed this yet.
warn(
"Some browsers do not return a PerformanceMeasure when calling performance.mark, disabling instrumentation. See https://developer.mozilla.org/en-US/docs/Web/API/Performance/measure#return_value and https://bugzilla.mozilla.org/show_bug.cgi?id=1724645",
{ id: "discourse.upload-debugging" }

View File

@ -148,9 +148,9 @@ const Group = RestModel.extend({
},
async addOwners(usernames, filter, notifyUsers) {
const response = await ajax(`/admin/groups/${this.id}/owners.json`, {
const response = await ajax(`/groups/${this.id}/owners.json`, {
type: "PUT",
data: { group: { usernames, notify_users: notifyUsers } },
data: { usernames, notify_users: notifyUsers },
});
if (filter) {

View File

@ -461,6 +461,12 @@ Post.reopenClass({
});
},
permanentlyDeleteRevisions(postId) {
return ajax(`/posts/${postId}/revisions/permanently_delete`, {
type: "DELETE",
});
},
showRevision(postId, version) {
return ajax(`/posts/${postId}/revisions/${version}/show`, {
type: "PUT",

View File

@ -471,6 +471,10 @@ const Topic = RestModel.extend({
!deleted_by.staff &&
!deleted_by.groups.some(
(group) => group.name === this.category.reviewable_by_group_name
) &&
!(
this.siteSettings.tl4_delete_posts_and_topics &&
deleted_by.trust_level >= 4
)
) {
DiscourseURL.redirectTo("/");

View File

@ -2,6 +2,7 @@ import RestrictedUserRoute from "discourse/routes/restricted-user";
import UserBadge from "discourse/models/user-badge";
import showModal from "discourse/lib/show-modal";
import { action } from "@ember/object";
import I18n from "I18n";
export default RestrictedUserRoute.extend({
showFooter: true,
@ -32,6 +33,7 @@ export default RestrictedUserRoute.extend({
newPrimaryGroupInput: user.get("primary_group_id"),
newFlairGroupId: user.get("flair_group_id"),
newStatus: user.status,
subpageTitle: I18n.t("user.preferences_nav.account"),
});
},

View File

@ -1,7 +1,19 @@
import RestrictedUserRoute from "discourse/routes/restricted-user";
import I18n from "I18n";
import { inject as service } from "@ember/service";
export default RestrictedUserRoute.extend({
router: service(),
model() {
return this.modelFor("user");
},
titleToken() {
let controller = this.controllerFor(this.router.currentRouteName);
let subpageTitle = controller?.subpageTitle;
return subpageTitle
? `${subpageTitle} - ${I18n.t("user.preferences")}`
: I18n.t("user.preferences");
},
});

View File

@ -136,7 +136,21 @@ export default DiscourseRoute.extend(FilterModeMixin, {
noSubcategories,
loading: false,
});
this.searchService.set("searchContext", model.tag.searchContext);
if (model.category || model.additionalTags) {
const tagIntersectionSearchContext = {
type: "tagIntersection",
tagId: model.tag.id,
tag: model.tag,
additionalTags: model.additionalTags || null,
categoryId: model.category?.id || null,
category: model.category || null,
};
this.searchService.set("searchContext", tagIntersectionSearchContext);
} else {
this.searchService.set("searchContext", model.tag.searchContext);
}
},
titleToken() {

View File

@ -2,6 +2,7 @@ import { action } from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import DiscourseRoute from "discourse/routes/discourse";
import { Promise } from "rsvp";
import I18n from "I18n";
export default DiscourseRoute.extend({
queryParams: {
@ -50,6 +51,10 @@ export default DiscourseRoute.extend({
this.render("user_bookmarks");
},
titleToken() {
return I18n.t("user_action_groups.3");
},
@action
didTransition() {
this.controllerFor("user-activity")._showFooter();

View File

@ -38,6 +38,10 @@ export default DiscourseRoute.extend({
this.appEvents.off("draft:destroyed", this, this.refresh);
},
titleToken() {
return I18n.t("user_action_groups.15");
},
@action
didTransition() {
this.controllerFor("user-activity")._showFooter();

View File

@ -25,4 +25,8 @@ export default UserActivityStreamRoute.extend({
return { title, body };
},
titleToken() {
return I18n.t("user.filters.all");
},
});

View File

@ -25,6 +25,10 @@ export default UserActivityStreamRoute.extend({
return { title, body };
},
titleToken() {
return I18n.t("user_action_groups.1");
},
@action
didTransition() {
this.controllerFor("application").set("showFooter", true);

View File

@ -42,6 +42,10 @@ export default UserTopicListRoute.extend({
return { title, body };
},
titleToken() {
return `${I18n.t("user.read")}`;
},
@action
triggerRefresh() {
this.refresh();

View File

@ -29,6 +29,10 @@ export default UserActivityStreamRoute.extend({
return { title, body };
},
titleToken() {
return I18n.t("user_action_groups.5");
},
@action
didTransition() {
this.controllerFor("application").set("showFooter", true);

View File

@ -50,6 +50,10 @@ export default UserTopicListRoute.extend({
return { title, body };
},
titleToken() {
return I18n.t("user_action_groups.4");
},
@action
triggerRefresh() {
this.refresh();

View File

@ -1,4 +1,5 @@
import DiscourseRoute from "discourse/routes/discourse";
import I18n from "I18n";
export default DiscourseRoute.extend({
model() {
@ -19,4 +20,8 @@ export default DiscourseRoute.extend({
setupController(controller, user) {
this.controllerFor("user-activity").set("model", user);
},
titleToken() {
return I18n.t("user.activity_stream");
},
});

View File

@ -2,6 +2,7 @@ import DiscourseRoute from "discourse/routes/discourse";
import UserBadge from "discourse/models/user-badge";
import ViewingActionType from "discourse/mixins/viewing-action-type";
import { action } from "@ember/object";
import I18n from "I18n";
export default DiscourseRoute.extend(ViewingActionType, {
model() {
@ -20,6 +21,10 @@ export default DiscourseRoute.extend(ViewingActionType, {
this.render("user/badges");
},
titleToken() {
return I18n.t("badges.title");
},
@action
didTransition() {
this.controllerFor("application").set("showFooter", true);

View File

@ -1,6 +1,7 @@
import DiscourseRoute from "discourse/routes/discourse";
import Invite from "discourse/models/invite";
import { action } from "@ember/object";
import I18n from "I18n";
export default DiscourseRoute.extend({
model(params) {
@ -27,6 +28,10 @@ export default DiscourseRoute.extend({
});
},
titleToken() {
return I18n.t("user.invited." + this.inviteFilter + "_tab");
},
@action
triggerRefresh() {
this.refresh();

View File

@ -1,4 +1,5 @@
import DiscourseRoute from "discourse/routes/discourse";
import I18n from "I18n";
export default DiscourseRoute.extend({
setupController(controller) {
@ -10,4 +11,8 @@ export default DiscourseRoute.extend({
can_see_invite_details,
});
},
titleToken() {
return I18n.t("user.invited.title");
},
});

View File

@ -1,6 +1,11 @@
import UserAction from "discourse/models/user-action";
import UserActivityStreamRoute from "discourse/routes/user-activity-stream";
import I18n from "I18n";
export default UserActivityStreamRoute.extend({
userActionType: UserAction.TYPES["edits"],
titleToken() {
return I18n.t("user_action_groups.11");
},
});

View File

@ -1,4 +1,5 @@
import DiscourseRoute from "discourse/routes/discourse";
import I18n from "I18n";
export default DiscourseRoute.extend({
controllerName: "user-notifications",
@ -6,6 +7,10 @@ export default DiscourseRoute.extend({
this.render("user/notifications-index");
},
titleToken() {
return I18n.t("user.filters.all");
},
afterModel(model) {
if (!model) {
this.transitionTo("userNotifications.responses");

View File

@ -1,6 +1,11 @@
import UserAction from "discourse/models/user-action";
import UserActivityStreamRoute from "discourse/routes/user-activity-stream";
import I18n from "I18n";
export default UserActivityStreamRoute.extend({
userActionType: UserAction.TYPES["likes_received"],
titleToken() {
return I18n.t("user_action_groups.1");
},
});

View File

@ -1,6 +1,11 @@
import UserAction from "discourse/models/user-action";
import UserActivityStreamRoute from "discourse/routes/user-activity-stream";
import I18n from "I18n";
export default UserActivityStreamRoute.extend({
userActionType: UserAction.TYPES["mentions"],
titleToken() {
return I18n.t("user_action_groups.7");
},
});

View File

@ -1,6 +1,11 @@
import UserAction from "discourse/models/user-action";
import UserActivityStreamRoute from "discourse/routes/user-activity-stream";
import I18n from "I18n";
export default UserActivityStreamRoute.extend({
userActionType: UserAction.TYPES["replies"],
titleToken() {
return I18n.t("user_action_groups.6");
},
});

View File

@ -1,6 +1,7 @@
import DiscourseRoute from "discourse/routes/discourse";
import ViewingActionType from "discourse/mixins/viewing-action-type";
import { action } from "@ember/object";
import I18n from "I18n";
export default DiscourseRoute.extend(ViewingActionType, {
controllerName: "user-notifications",
@ -31,4 +32,8 @@ export default DiscourseRoute.extend(ViewingActionType, {
controller.set("user", this.modelFor("user"));
this.viewingActionType(-1);
},
titleToken() {
return I18n.t("user.notifications");
},
});

View File

@ -1,4 +1,5 @@
import DiscourseRoute from "discourse/routes/discourse";
import I18n from "I18n";
export default DiscourseRoute.extend({
showFooter: true,
@ -11,4 +12,8 @@ export default DiscourseRoute.extend({
return user.summary();
},
titleToken() {
return I18n.t("user.summary.title");
},
});

View File

@ -1,5 +1,4 @@
import DiscourseRoute from "discourse/routes/discourse";
import I18n from "I18n";
import User from "discourse/models/user";
import { action } from "@ember/object";
import { bind } from "discourse-common/utils/decorators";
@ -98,9 +97,7 @@ export default DiscourseRoute.extend({
titleToken() {
const username = this.modelFor("user").username;
if (username) {
return [I18n.t("user.profile"), username];
}
return username ? username : null;
},
@action

View File

@ -1,5 +1,5 @@
<div class="badge-title">
<section class="user-content">
<section class="user-content" id="user-content">
<form class="form-horizontal">
<div class="control-group">
<div class="controls">

View File

@ -181,20 +181,6 @@
</div>
</section>
{{#if this.siteSettings.tagging_enabled}}
<section class="field minimum-required-tags">
<label for="category-minimum-tags">
{{i18n "category.minimum_required_tags"}}
</label>
<TextField
@value={{this.category.minimum_required_tags}}
@id="category-minimum-tags"
@type="number"
@min="0"
/>
</section>
{{/if}}
<section class="field num-auto-bump-daily">
<label for="category-number-daily-bump">
{{i18n "category.num_auto_bump_daily"}}
@ -378,4 +364,4 @@
@name="category-custom-settings"
@args={{hash category=this.category}}
@tagName="section"
/>
/>

View File

@ -1,3 +1,14 @@
<section class="field minimum-required-tags">
<label for="category-minimum-tags">
{{i18n "category.minimum_required_tags"}}
</label>
<TextField
@value={{this.category.minimum_required_tags}}
@id="category-minimum-tags"
@type="number"
@min="0"
/>
</section>
<section class="field allowed-tags">
<label>{{i18n "category.tags_allowed_tags"}}</label>
<TagChooser
@ -71,4 +82,4 @@
@class="add-required-tag-group"
/>
</section>
</section>
</section>

View File

@ -16,6 +16,7 @@
name="message"
class="flag-message"
placeholder={{this.customPlaceholder}}
aria-label={{i18n "flagging.notify_user_textarea_label"}}
@value={{this.message}}
/>
<div
@ -48,6 +49,7 @@
name="message"
class="flag-message"
placeholder={{this.customPlaceholder}}
aria-label={{i18n "flagging.notify_moderators_textarea_label"}}
@value={{this.message}}
/>
<div

View File

@ -1,7 +1,10 @@
{{! template-lint-disable no-down-event-binding }}
{{! template-lint-disable no-invalid-interactive }}
<div class="horizontal-overflow-nav {{if this.hasScroll 'has-scroll'}}">
<nav
class="horizontal-overflow-nav {{if this.hasScroll 'has-scroll'}}"
aria-label={{@ariaLabel}}
>
{{#if this.hasScroll}}
<a
role="button"
@ -43,4 +46,4 @@
{{d-icon "chevron-right"}}
</a>
{{/if}}
</div>
</nav>

View File

@ -111,7 +111,9 @@
{{#if this.hasStatus}}
<h3 class="user-status">
{{html-safe this.userStatusEmoji}}
{{this.user.status.description}}
<span class="user-status__description">
{{this.user.status.description}}
</span>
{{format-date this.user.status.ends_at format="tiny"}}
</h3>
{{/if}}

View File

@ -54,6 +54,7 @@
<BulkGroupMemberDropdown
@bulkSelection={{this.bulkSelection}}
@canAdminGroup={{this.model.can_admin_group}}
@canEditGroup={{this.model.can_edit_group}}
@onChange={{action "actOnSelection" this.bulkSelection}}
/>
{{/if}}
@ -148,6 +149,7 @@
<GroupMemberDropdown
@member={{m}}
@canAdminGroup={{this.model.can_admin_group}}
@canEditGroup={{this.model.can_edit_group}}
@onChange={{action "actOnGroup" m}}
/>
{{/if}}

View File

@ -16,6 +16,6 @@
<PluginOutlet @name="group-activity-bottom" @connectorTagName="li" />
</MobileNav>
</section>
<section class="user-content">
<section class="user-content" id="user-content">
{{outlet}}
</section>

View File

@ -12,6 +12,6 @@
{{/each}}
</MobileNav>
</section>
<section class="user-content">
<section class="user-content" id="user-content">
{{outlet}}
</section>

View File

@ -12,6 +12,6 @@
</li>
</MobileNav>
</section>
<section class="user-content">
<section class="user-content" id="user-content">
{{outlet}}
</section>

View File

@ -1,4 +1,4 @@
<section class="user-content">
<section class="user-content" id="user-content">
{{#if this.model.permissions}}
<label class="group-category-permissions-desc">
{{i18n "groups.permissions.description"}}

View File

@ -44,6 +44,7 @@
<GroupMemberDropdown
@member={{user}}
@canAdminGroup={{this.model.can_admin_group}}
@canEditGroup={{this.model.can_edit_group}}
@onChange={{action "actOnGroup" user}}
/>
{{/if}}

View File

@ -250,6 +250,16 @@
@disabled={{this.loading}}
/>
{{/if}}
{{#if this.displayPermanentlyDeleteButton}}
<DButton
@action={{action "permanentlyDeleteVersions"}}
@icon="far-trash-alt"
@label="post.revisions.controls.destroy"
@class="btn-danger"
@disabled={{this.loading}}
/>
{{/if}}
</div>
</div>
{{/if}}
{{/if}}

View File

@ -5,7 +5,7 @@
{{i18n "groups.membership_request.reason"}}
</label>
<ExpandingTextArea @value={{this.reason}} />
<ExpandingTextArea @value={{this.reason}} @maxlength="280" />
</div>
</DModalBody>

View File

@ -1,11 +1,12 @@
{{#if this.currentUser.redesigned_user_page_nav_enabled}}
<DSection @pageClass="user-preferences" />
<div class="user-navigation user-navigation-secondary">
<HorizontalOverflowNav>
<HorizontalOverflowNav @ariaLabel="User secondary - preferences">
<UserNav::PreferencesNav
@currentUser={{this.currentUser}}
@model={{this.model}}
@siteSettings={{this.siteSettings}}
@ariaCurrentContext="subNav"
/>
</HorizontalOverflowNav>
</div>
@ -103,7 +104,7 @@
</DSection>
{{/if}}
<section class="user-content user-preferences">
<section class="user-content user-preferences" id="user-content">
<PluginOutlet
@name="above-user-preferences"
@tagName="span"

View File

@ -2,6 +2,7 @@
{{#if this.canInviteToForum}}
<LoadMore
@class="user-content"
@id="user-content"
@selector=".user-invite-list tr"
@action={{action "loadMore"}}
>

View File

@ -3,7 +3,7 @@
<DSection @pageClass="user-invites" />
<div class="user-navigation user-navigation-secondary">
<HorizontalOverflowNav>
<HorizontalOverflowNav @ariaLabel="User secondary - invites">
<NavItem
@route="userInvited.show"
@routeParam="pending"

View File

@ -1,43 +1,45 @@
<UserNav::MessagesSecondaryNav>
<li class="messages-group-latest">
<LinkTo @route="userPrivateMessages.group.index" @model={{this.groupName}}>
{{d-icon "envelope"}}
<span>{{i18n "categories.latest"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="userPrivateMessages.group.index"
@class="user-nav__messages-group-latest"
@model={{this.groupName}}
@ariaCurrentContext="subNav"
>
{{d-icon "envelope"}}
<span>{{i18n "categories.latest"}}</span>
</DNavigationItem>
{{#if this.viewingSelf}}
<li class="messages-group-new">
<LinkTo
@route="userPrivateMessages.group.new"
@model={{this.groupName}}
class="new"
>
{{d-icon "exclamation-circle"}}
<span>{{this.newLinkText}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="userPrivateMessages.group.new"
@model={{this.groupName}}
@class="user-nav__messages-group-new"
@ariaCurrentContext="subNav"
>
{{d-icon "exclamation-circle"}}
<span>{{this.newLinkText}}</span>
</DNavigationItem>
<li class="messages-group-unread">
<LinkTo
@route="userPrivateMessages.group.unread"
@model={{this.groupName}}
class="unread"
>
{{d-icon "plus-circle"}}
<span>{{this.unreadLinkText}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="userPrivateMessages.group.unread"
@model={{this.groupName}}
@class="user-nav__messages-group-unread"
@ariaCurrentContext="subNav"
>
{{d-icon "plus-circle"}}
<span>{{this.unreadLinkText}}</span>
</DNavigationItem>
<li class="messages-group-archive">
<LinkTo
@route="userPrivateMessages.group.archive"
@model={{this.groupName}}
>
{{d-icon "archive"}}
<span>{{i18n "user.messages.archive"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="userPrivateMessages.group.archive"
@class="user-nav__messages-group-archive"
@model={{this.groupName}}
@ariaCurrentContext="subNav"
>
{{d-icon "archive"}}
<span>{{i18n "user.messages.archive"}}</span>
</DNavigationItem>
{{/if}}
</UserNav::MessagesSecondaryNav>

View File

@ -5,50 +5,59 @@
{{/if}}
<UserNav::MessagesSecondaryNav>
<li class="messages-latest">
<LinkTo @route="userPrivateMessages.user.index" @model={{this.model}}>
{{d-icon "envelope"}}
<span>{{i18n "categories.latest"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="userPrivateMessages.user.index"
@class="user-nav__messages-latest"
@model={{this.model}}
@ariaCurrentContext="subNav"
>
{{d-icon "envelope"}}
<span>{{i18n "categories.latest"}}</span>
</DNavigationItem>
<li class="messages-sent">
<LinkTo @route="userPrivateMessages.user.sent" @model={{this.model}}>
{{d-icon "reply"}}
<span>{{i18n "user.messages.sent"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="userPrivateMessages.user.sent"
@class="user-nav__messages-sent"
@model={{this.model}}
@ariaCurrentContext="subNav"
>
{{d-icon "reply"}}
<span>{{i18n "user.messages.sent"}}</span>
</DNavigationItem>
{{#if this.viewingSelf}}
<li class="messages-new">
<LinkTo
@route="userPrivateMessages.user.new"
@model={{this.model}}
class="new"
>
{{d-icon "exclamation-circle"}}
<span>{{this.newLinkText}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="userPrivateMessages.user.new"
@class="user-nav__messages-new"
@model={{this.model}}
@ariaCurrentContext="subNav"
>
{{d-icon "exclamation-circle"}}
<span>{{this.newLinkText}}</span>
</DNavigationItem>
<DNavigationItem
@route="userPrivateMessages.user.unread"
@class="user-nav__messages-unread"
@model={{this.model}}
@ariaCurrentContext="subNav"
>
{{d-icon "plus-circle"}}
<span>{{this.unreadLinkText}}</span>
</DNavigationItem>
<li class="messages-unread">
<LinkTo
@route="userPrivateMessages.user.unread"
@model={{this.model}}
class="unread"
>
{{d-icon "plus-circle"}}
<span>{{this.unreadLinkText}}</span>
</LinkTo>
</li>
{{/if}}
<li class="messages-archive">
<LinkTo @route="userPrivateMessages.user.archive" @model={{this.model}}>
{{d-icon "archive"}}
<span>{{i18n "user.messages.archive"}}</span>
</LinkTo>
</li>
<DNavigationItem
@route="userPrivateMessages.user.archive"
@class="user-nav__messages-archive"
@model={{this.model}}
@ariaCurrentContext="subNav"
>
{{d-icon "archive"}}
<span>{{i18n "user.messages.archive"}}</span>
</DNavigationItem>
</UserNav::MessagesSecondaryNav>
{{outlet}}

View File

@ -10,6 +10,9 @@
{{this.primaryGroup}}"
>
<DSection @class="user-main">
<a href="#user-content" id="skip-link" class="skip-link__user-nav">
{{i18n "skip_user_nav"}}
</a>
<section
class="{{if this.collapsedInfo 'collapsed-info'}}
about
@ -111,65 +114,12 @@
@args={{hash model=this.model}}
/>
<UserProfileAvatar @user={{this.model}} @tagName="" />
<section class="controls">
<ul>
{{#if this.model.can_send_private_message_to_user}}
<li>
<DButton
@class="btn-primary compose-pm"
@action={{route-action "composePrivateMessage" this.model}}
@icon="envelope"
@label="user.private_message"
/>
</li>
{{/if}}
{{#if this.canMuteOrIgnoreUser}}
<li>
<UserNotificationsDropdown
@user={{this.model}}
@value={{this.userNotificationLevel}}
@updateNotificationLevel={{action
"updateNotificationLevel"
}}
/>
</li>
{{/if}}
{{#if this.displayTopLevelAdminButton}}
<li><a
href={{this.model.adminPath}}
class="btn btn-default"
>{{d-icon "wrench"}}<span class="d-button-label">{{i18n
"admin.user.show_admin_profile"
}}</span></a></li>
{{/if}}
<PluginOutlet
@name="user-profile-controls"
@connectorTagName="li"
@args={{hash model=this.model}}
/>
{{#if this.canExpandProfile}}
<li>
<DButton
@ariaExpanded={{this.collapsedInfoState.isExpanded}}
@ariaLabel={{this.collapsedInfoState.ariaLabel}}
@ariaControls="collapsed-info-panel"
@class="btn-default"
@label={{concat "user." this.collapsedInfoState.label}}
@icon={{this.collapsedInfoState.icon}}
@action={{action this.collapsedInfoState.action}}
/>
</li>
{{/if}}
</ul>
</section>
<div class="primary-textual">
<div class="user-profile-names">
<h1 class={{if this.nameFirst "full-name" "username"}}>
<div
class="{{if this.nameFirst 'full-name' 'username'}}
user-profile-names__primary"
>
{{if
this.nameFirst
this.model.name
@ -179,15 +129,22 @@
{{#if this.model.status}}
<UserStatusMessage @status={{this.model.status}} />
{{/if}}
</h1>
<h2 class={{if this.nameFirst "username" "full-name"}}>{{#if
</div>
<div
class="{{if this.nameFirst 'username' 'full-name'}}
user-profile-names__secondary"
>{{#if
this.nameFirst
}}{{this.model.username}}{{else}}{{this.model.name}}{{/if}}</h2>
}}{{this.model.username}}{{else}}{{this.model.name}}{{/if}}</div>
{{#if this.model.staged}}
<h2 class="staged">{{i18n "user.staged"}}</h2>
<div class="staged user-profile-names__secondary">{{i18n
"user.staged"
}}</div>
{{/if}}
{{#if this.model.title}}
<h3>{{this.model.title}}</h3>
<div
class="user-profile-names__title"
>{{this.model.title}}</div>
{{/if}}
<PluginOutlet
@name="user-post-names"
@ -198,9 +155,10 @@
</div>
{{#if this.showFeaturedTopic}}
<h3 class="featured-topic">
<span>{{i18n "user.featured_topic"}}</span>
<LinkTo
<div class="featured-topic user-profile__featured-topic">
<span title={{i18n "user.featured_topic"}}>
{{d-icon "book"~}}
</span><LinkTo
@route="topic"
@models={{array
this.model.featured_topic.slug
@ -209,19 +167,20 @@
>{{html-safe
(replace-emoji this.model.featured_topic.fancy_title)
}}</LinkTo>
</h3>
</div>
{{/if}}
<h3 class="location-and-website">
<div
class="location-and-website user-profile__location-and-website"
>
{{#if this.model.location}}<div
class="user-profile-location"
>{{d-icon "map-marker-alt"}}
>{{d-icon "map-marker-alt"~}}
{{this.model.location}}</div>{{/if}}
{{#if this.model.website_name}}
<div class="user-profile-website">
{{d-icon "globe"}}
{{#if this.linkWebsite}}
{{! template-lint-disable }}
<div class="user-profile-website">{{d-icon "globe"~}}
{{#if this.linkWebsite~}}
{{~! template-lint-disable ~}}
<a
href={{this.model.website}}
rel="noopener {{unless
@ -243,7 +202,7 @@
@connectorTagName="div"
@args={{hash model=this.model}}
/>
</h3>
</div>
<div class="bio">
{{#if this.model.suspended}}
@ -310,6 +269,62 @@
@args={{hash model=this.model}}
/>
</div>
<section class="controls">
<ul>
{{#if this.model.can_send_private_message_to_user}}
<li>
<DButton
@class="btn-primary compose-pm"
@action={{route-action "composePrivateMessage" this.model}}
@icon="envelope"
@label="user.private_message"
/>
</li>
{{/if}}
{{#if this.canMuteOrIgnoreUser}}
<li>
<UserNotificationsDropdown
@user={{this.model}}
@value={{this.userNotificationLevel}}
@updateNotificationLevel={{action
"updateNotificationLevel"
}}
/>
</li>
{{/if}}
{{#if this.displayTopLevelAdminButton}}
<li><a
href={{this.model.adminPath}}
class="btn btn-default"
>{{d-icon "wrench"}}<span class="d-button-label">{{i18n
"admin.user.show_admin_profile"
}}</span></a></li>
{{/if}}
<PluginOutlet
@name="user-profile-controls"
@connectorTagName="li"
@args={{hash model=this.model}}
/>
{{#if this.canExpandProfile}}
<li>
<DButton
@ariaExpanded={{this.collapsedInfoState.isExpanded}}
@ariaLabel={{this.collapsedInfoState.ariaLabel}}
@ariaControls="collapsed-info-panel"
@class="btn-default"
@label={{concat "user." this.collapsedInfoState.label}}
@icon={{this.collapsedInfoState.icon}}
@action={{action this.collapsedInfoState.action}}
/>
</li>
{{/if}}
</ul>
</section>
</div>
<PluginOutlet
@name="user-profile-above-collapsed-info"
@ -402,7 +417,6 @@
</dl>
<PluginOutlet
@name="user-profile-secondary"
@tagName="span"
@connectorTagName="div"
@args={{hash model=this.model}}
/>

View File

@ -2,7 +2,7 @@
<DSection @pageClass="user-activity" />
<div class="user-navigation user-navigation-secondary">
<HorizontalOverflowNav>
<HorizontalOverflowNav @ariaLabel="User secondary - activity">
<UserNav::ActivityNav
@user={{this.user}}
@viewingSelf={{this.viewingSelf}}
@ -10,6 +10,7 @@
@siteSettings={{this.siteSettings}}
@draftLabel={{this.draftLabel}}
@pendingLabel={{this.pendingLabel}}
@ariaCurrentContext="subNav"
/>
</HorizontalOverflowNav>
</div>
@ -88,6 +89,6 @@
{{/if}}
{{/if}}
<section class="user-content">
<section class="user-content" id="user-content">
{{outlet}}
</section>

View File

@ -1,4 +1,4 @@
<DSection @pageClass="user-badges" @class="user-content">
<DSection @pageClass="user-badges" @class="user-content" id="user-content">
<p class="favorite-count">
{{i18n
"badges.favorite_count"

View File

@ -14,6 +14,7 @@
<HorizontalOverflowNav
@className="messages-nav"
@ariaLabel="User secondary - messages"
id="user-navigation-secondary__horizontal-nav"
/>
@ -183,7 +184,7 @@
{{/unless}}
{{/if}}
<section class="user-content">
<section class="user-content" id="user-content">
{{#unless this.currentUser.redesigned_user_page_nav_enabled}}
<div class="list-actions">
{{#if this.site.mobileView}}

View File

@ -2,10 +2,11 @@
<DSection @pageClass="user-notifications" />
<div class="user-navigation user-navigation-secondary">
<HorizontalOverflowNav>
<HorizontalOverflowNav @ariaLabel="User secondary - notifications">
<UserNav::NotificationsNav
@model={{this.model}}
@siteSettings={{this.siteSettings}}
@ariaCurrentContext="subNav"
/>
</HorizontalOverflowNav>
@ -79,7 +80,7 @@
{{/if}}
{{/if}}
<section class="user-content">
<section class="user-content" id="user-content">
<LoadMore
@class="notification-history user-stream"
@selector=".user-stream .notification"

View File

@ -1,5 +1,5 @@
<DSection @pageClass="user-summary" @tagName="">
<div class="user-content">
<div class="user-content" id="user-content">
<PluginOutlet
@name="above-user-summary-stats"
@args={{hash model=this.model user=this.user}}

View File

@ -70,7 +70,7 @@ createWidgetFrom(QuickAccessPanel, "quick-access-bookmarks", {
href,
title: bookmark.name,
content: bookmark.title,
username: bookmark.user.username,
username: bookmark.user?.username,
});
},

View File

@ -415,13 +415,42 @@ createWidget("search-menu-assistant", {
const content = [];
const { suggestionKeyword, term } = attrs;
let prefix = term?.split(suggestionKeyword)[0].trim() || "";
if (prefix.length) {
prefix = `${prefix} `;
let prefix;
if (suggestionKeyword !== "+") {
prefix = term?.split(suggestionKeyword)[0].trim() || "";
if (prefix.length) {
prefix = `${prefix} `;
}
}
switch (suggestionKeyword) {
case "+":
attrs.results.forEach((item) => {
if (item.additionalTags) {
prefix = term?.split(" ").slice(0, -1).join(" ").trim() || "";
} else {
prefix = term?.split("#")[0].trim() || "";
}
if (prefix.length) {
prefix = `${prefix} `;
}
content.push(
this.attach("search-menu-assistant-item", {
prefix,
tag: item.tagName,
additionalTags: item.additionalTags,
category: item.category,
slug: term,
withInLabel: attrs.withInLabel,
isIntersection: true,
})
);
});
break;
case "#":
attrs.results.forEach((item) => {
if (item.model) {
@ -572,6 +601,36 @@ createWidget("search-menu-initial-options", {
})
);
break;
case "tagIntersection":
let tagTerm;
if (ctx.additionalTags) {
const tags = [ctx.tagId, ...ctx.additionalTags];
tagTerm = `${term} tags:${tags.join("+")}`;
} else {
tagTerm = `${term} #${ctx.tagId}`;
}
let suggestionOptions = {
tagName: ctx.tagId,
additionalTags: ctx.additionalTags,
};
if (ctx.category) {
const categorySlug = ctx.category.parentCategory
? `#${ctx.category.parentCategory.slug}:${ctx.category.slug}`
: `#${ctx.category.slug}`;
suggestionOptions.categoryName = categorySlug;
suggestionOptions.category = ctx.category;
tagTerm = tagTerm + ` ${categorySlug}`;
}
content.push(
this.attach("search-menu-assistant", {
term: tagTerm,
suggestionKeyword: "+",
results: [suggestionOptions],
withInLabel: true,
})
);
break;
case "user":
content.push(
this.attach("search-menu-assistant-item", {
@ -617,7 +676,7 @@ createWidget("search-menu-initial-options", {
slug: term,
extraHint: I18n.t("search.enter_hint"),
label: [
h("span.keyword", `${term} `),
h("span.keyword", `${term}`),
opts.withLabel
? h("span.label-suffix", I18n.t("search.in_topics_posts"))
: null,
@ -677,11 +736,20 @@ createWidget("search-menu-assistant-item", {
link: false,
})
);
} else if (attrs.tag) {
attributes.href = getURL(`/tag/${attrs.tag}`);
content.push(iconNode("tag"));
content.push(h("span.search-item-tag", attrs.tag));
// category and tag combination
if (attrs.tag && attrs.isIntersection) {
attributes.href = getURL(`/tag/${attrs.tag}`);
content.push(h("span.search-item-tag", [iconNode("tag"), attrs.tag]));
}
} else if (attrs.tag) {
if (attrs.isIntersection && attrs.additionalTags?.length) {
const tags = [attrs.tag, ...attrs.additionalTags];
content.push(h("span.search-item-tag", `tags:${tags.join("+")}`));
} else {
attributes.href = getURL(`/tag/${attrs.tag}`);
content.push(h("span.search-item-tag", [iconNode("tag"), attrs.tag]));
}
} else if (attrs.user) {
const userResult = [
avatarImg("small", {

View File

@ -10,8 +10,7 @@ const browsers = [
];
if (isCI || isProduction) {
// https://meta.discourse.org/t/224747
browsers.push("Safari 12");
browsers.push("Safari 15");
}
module.exports = {

View File

@ -17,13 +17,13 @@
},
"dependencies": {
"@babel/core": "^7.20.12",
"@babel/standalone": "^7.20.12",
"@babel/standalone": "^7.20.13",
"@discourse/backburner.js": "^2.7.1-0",
"@discourse/itsatrap": "^2.0.10",
"@ember-compat/tracked-built-ins": "^0.9.1",
"@ember/jquery": "^2.0.0",
"@ember/optional-features": "^2.0.0",
"@ember/render-modifiers": "^2.0.4",
"@ember/render-modifiers": "^2.0.5",
"@ember/test-helpers": "^2.9.3",
"@glimmer/component": "^1.1.2",
"@glimmer/syntax": "^0.84.2",
@ -49,7 +49,7 @@
"discourse-hbr": "1.0.0",
"discourse-plugins": "1.0.0",
"discourse-widget-hbs": "1.0.0",
"ember-auto-import": "^2.5.0",
"ember-auto-import": "^2.6.0",
"ember-auto-import-chunks-json-generator": "^1.0.0",
"ember-buffered-proxy": "^2.1.1",
"ember-cached-decorator-polyfill": "^1.0.1",
@ -69,25 +69,25 @@
"ember-modifier": "^4.0.0",
"ember-on-resize-modifier": "^1.1.0",
"ember-qunit": "^6.1.1",
"ember-rfc176-data": "^0.3.17",
"ember-rfc176-data": "^0.3.18",
"ember-source": "~3.28.11",
"ember-test-selectors": "^6.0.0",
"eslint": "^8.31.0",
"eslint": "^8.32.0",
"eslint-plugin-qunit": "^7.3.4",
"handlebars": "^4.7.7",
"html-entities": "^2.3.3",
"imports-loader": "^4.0.1",
"js-yaml": "^4.1.0",
"jsdom": "^21.0.0",
"jsdom": "^21.1.0",
"loader.js": "^4.7.0",
"markdown-it": "^13.0.1",
"message-bus-client": "^4.3.0",
"message-bus-client": "^4.3.2",
"messageformat": "0.1.5",
"pretender": "^3.4.7",
"pretty-text": "1.0.0",
"qunit": "^2.19.3",
"qunit": "^2.19.4",
"qunit-dom": "^2.0.0",
"sass": "^1.57.0",
"sass": "^1.57.1",
"select-kit": "1.0.0",
"sinon": "^15.0.1",
"source-map": "^0.7.4",

View File

@ -1,7 +1,14 @@
/* eslint-disable no-var */ // `let` is not supported in very old browsers
(function () {
if (!window.WeakMap || !window.Promise || typeof globalThis === "undefined") {
if (
!window.WeakMap ||
!window.Promise ||
typeof globalThis === "undefined" ||
!String.prototype.replaceAll ||
!CSS.supports ||
!CSS.supports("aspect-ratio: 1")
) {
window.unsupportedBrowser = true;
} else {
// Some implementations of `WeakMap.prototype.has` do not accept false

View File

@ -77,6 +77,8 @@ acceptance("Category Edit", function (needs) {
test("Editing required tag groups", async function (assert) {
await visit("/c/bug/edit/tags");
assert.ok(exists(".minimum-required-tags"));
assert.ok(exists(".required-tag-groups"));
assert.strictEqual(count(".required-tag-group-row"), 0);

View File

@ -1269,7 +1269,7 @@ acceptance("Composer - Default category", function (needs) {
name: "General",
slug: "general",
permission: 1,
ltopic_template: null,
topic_template: null,
},
{
id: 2,
@ -1306,7 +1306,7 @@ acceptance("Composer - Uncategorized category", function (needs) {
name: "General",
slug: "general",
permission: 1,
ltopic_template: null,
topic_template: null,
},
{
id: 2,
@ -1337,7 +1337,7 @@ acceptance("Composer - default category not set", function (needs) {
name: "General",
slug: "general",
permission: 1,
ltopic_template: null,
topic_template: null,
},
{
id: 2,

View File

@ -39,7 +39,7 @@ acceptance("Group Members", function (needs) {
needs.user();
needs.pretender((server, helper) => {
server.put("/admin/groups/47/owners.json", () => {
server.put("/groups/47/owners.json", () => {
return helper.response({ success: true });
});
});
@ -72,10 +72,9 @@ acceptance("Group Members", function (needs) {
);
});
test("Shows bulk actions", async function (assert) {
test("Shows bulk actions as an admin user", async function (assert) {
await visit("/g/discourse");
assert.ok(exists("button.bulk-select"));
await click("button.bulk-select");
await click(queryAll("input.bulk-select")[0]);
@ -83,7 +82,50 @@ acceptance("Group Members", function (needs) {
const memberDropdown = selectKit(".bulk-group-member-dropdown");
await memberDropdown.expand();
await memberDropdown.selectRowByValue("makeOwners");
assert.ok(
exists('[data-value="removeMembers"]'),
"it includes remove member option"
);
assert.ok(
exists('[data-value="makeOwners"]'),
"it includes make owners option"
);
assert.ok(
exists('[data-value="setPrimary"]'),
"it includes set primary option"
);
});
test("Shows bulk actions as a group owner", async function (assert) {
updateCurrentUser({ moderator: false, admin: false });
await visit("/g/discourse");
await click("button.bulk-select");
await click(queryAll("input.bulk-select")[0]);
await click(queryAll("input.bulk-select")[1]);
const memberDropdown = selectKit(".bulk-group-member-dropdown");
await memberDropdown.expand();
assert.ok(
exists('[data-value="removeMembers"]'),
"it includes remove member option"
);
assert.ok(
exists('[data-value="makeOwners"]'),
"it includes make owners option"
);
assert.notOk(
exists('[data-value="setPrimary"]'),
"it does not include set primary (staff only) option"
);
});
test("Bulk actions - Menu, Select all and Clear all buttons", async function (assert) {

View File

@ -73,7 +73,7 @@ acceptance("Search - Anonymous", function (needs) {
query(
".search-menu .results ul.search-menu-initial-options li:first-child .search-item-slug"
).innerText.trim(),
`dev ${I18n.t("search.in_topics_posts")}`,
`dev${I18n.t("search.in_topics_posts")}`,
"shows topic search as first dropdown item"
);
@ -757,6 +757,47 @@ acceptance("Search - assistant", function (needs) {
return helper.response(searchFixtures["search/query"]);
});
server.get("/tag/dev/notifications", () => {
return helper.response({
tag_notification: { id: "dev", notification_level: 2 },
});
});
server.get("/tags/c/bug/1/dev/l/latest.json", () => {
return helper.response({
users: [],
primary_groups: [],
topic_list: {
can_create_topic: true,
draft: null,
draft_key: "new_topic",
draft_sequence: 1,
per_page: 30,
tags: [
{
id: 1,
name: "dev",
topic_count: 1,
},
],
topics: [],
},
});
});
server.get("/tags/intersection/dev/foo.json", () => {
return helper.response({
topic_list: {
can_create_topic: true,
draft: null,
draft_key: "new_topic",
draft_sequence: 1,
per_page: 30,
topics: [],
},
});
});
server.get("/u/search/users", () => {
return helper.response({
users: [
@ -810,13 +851,92 @@ acceptance("Search - assistant", function (needs) {
query(
".search-menu .results ul.search-menu-assistant .search-item-prefix"
).innerText,
"sam "
"sam"
);
await click(firstCategory);
assert.strictEqual(query("#search-term").value, `sam #${firstResultSlug}`);
});
test("Shows category / tag combination shortcut when both are present", async function (assert) {
await visit("/tags/c/bug/dev");
await click("#search-button");
assert.strictEqual(
query(".search-menu .results ul.search-menu-assistant .category-name")
.innerText,
"bug",
"Category is displayed"
);
assert.strictEqual(
query(".search-menu .results ul.search-menu-assistant .search-item-tag")
.innerText,
"dev",
"Tag is displayed"
);
});
test("Updates tag / category combination search suggestion when typing", async function (assert) {
await visit("/tags/c/bug/dev");
await click("#search-button");
await fillIn("#search-term", "foo bar");
assert.strictEqual(
query(
".search-menu .results ul.search-menu-assistant .search-item-prefix"
).innerText,
"foo bar",
"Input is applied to search query"
);
assert.strictEqual(
query(".search-menu .results ul.search-menu-assistant .category-name")
.innerText,
"bug"
);
assert.strictEqual(
query(".search-menu .results ul.search-menu-assistant .search-item-tag")
.innerText,
"dev",
"Tag is displayed"
);
});
test("Shows tag combination shortcut when visiting tag intersection", async function (assert) {
await visit("/tags/intersection/dev/foo");
await click("#search-button");
assert.strictEqual(
query(".search-menu .results ul.search-menu-assistant .search-item-tag")
.innerText,
"tags:dev+foo",
"Tags are displayed"
);
});
test("Updates tag intersection search suggestion when typing", async function (assert) {
await visit("/tags/intersection/dev/foo");
await click("#search-button");
await fillIn("#search-term", "foo bar");
assert.strictEqual(
query(
".search-menu .results ul.search-menu-assistant .search-item-prefix"
).innerText,
"foo bar",
"Input is applied to search query"
);
assert.strictEqual(
query(".search-menu .results ul.search-menu-assistant .search-item-tag")
.innerText,
"tags:dev+foo",
"Tags are displayed"
);
});
test("shows in: shortcuts", async function (assert) {
await visit("/");
await click("#search-button");

View File

@ -346,17 +346,31 @@ function testUserPrivateMessagesWithGroupMessages(needs, customUserProps) {
await publishUnreadToMessageBus({ topicId: 1 });
await publishNewToMessageBus({ topicId: 2 });
assert.strictEqual(
query(".messages-nav li a.new").innerText.trim(),
I18n.t("user.messages.new_with_count", { count: 1 }),
"displays the right count"
);
if (customUserProps?.redesigned_user_page_nav_enabled) {
assert.strictEqual(
query(".user-nav__messages-new").innerText.trim(),
I18n.t("user.messages.new_with_count", { count: 1 }),
"displays the right count"
);
assert.strictEqual(
query(".messages-nav li a.unread").innerText.trim(),
I18n.t("user.messages.unread_with_count", { count: 1 }),
"displays the right count"
);
assert.strictEqual(
query(".user-nav__messages-unread").innerText.trim(),
I18n.t("user.messages.unread_with_count", { count: 1 }),
"displays the right count"
);
} else {
assert.strictEqual(
query(".messages-nav li a.new").innerText.trim(),
I18n.t("user.messages.new_with_count", { count: 1 }),
"displays the right count"
);
assert.strictEqual(
query(".messages-nav li a.unread").innerText.trim(),
I18n.t("user.messages.unread_with_count", { count: 1 }),
"displays the right count"
);
}
});
test("incoming new messages while viewing new", async function (assert) {
@ -364,11 +378,19 @@ function testUserPrivateMessagesWithGroupMessages(needs, customUserProps) {
await publishNewToMessageBus({ topicId: 1 });
assert.strictEqual(
query(".messages-nav li a.new").innerText.trim(),
I18n.t("user.messages.new_with_count", { count: 1 }),
"displays the right count"
);
if (customUserProps?.redesigned_user_page_nav_enabled) {
assert.strictEqual(
query(".messages-nav .user-nav__messages-new").innerText.trim(),
I18n.t("user.messages.new_with_count", { count: 1 }),
"displays the right count"
);
} else {
assert.strictEqual(
query(".messages-nav li a.new").innerText.trim(),
I18n.t("user.messages.new_with_count", { count: 1 }),
"displays the right count"
);
}
assert.ok(exists(".show-mores"), "displays the topic incoming info");
});
@ -378,11 +400,19 @@ function testUserPrivateMessagesWithGroupMessages(needs, customUserProps) {
await publishUnreadToMessageBus();
assert.strictEqual(
query(".messages-nav li a.unread").innerText.trim(),
I18n.t("user.messages.unread_with_count", { count: 1 }),
"displays the right count"
);
if (customUserProps?.redesigned_user_page_nav_enabled) {
assert.strictEqual(
query(".messages-nav .user-nav__messages-unread").innerText.trim(),
I18n.t("user.messages.unread_with_count", { count: 1 }),
"displays the right count"
);
} else {
assert.strictEqual(
query(".messages-nav li a.unread").innerText.trim(),
I18n.t("user.messages.unread_with_count", { count: 1 }),
"displays the right count"
);
}
assert.ok(exists(".show-mores"), "displays the topic incoming info");
});
@ -393,33 +423,65 @@ function testUserPrivateMessagesWithGroupMessages(needs, customUserProps) {
await publishUnreadToMessageBus({ groupIds: [14], topicId: 1 });
await publishNewToMessageBus({ groupIds: [14], topicId: 2 });
assert.strictEqual(
query(".messages-nav li a.unread").innerText.trim(),
I18n.t("user.messages.unread_with_count", { count: 1 }),
"displays the right count"
);
if (customUserProps?.redesigned_user_page_nav_enabled) {
assert.strictEqual(
query(
".messages-nav .user-nav__messages-group-unread"
).innerText.trim(),
I18n.t("user.messages.unread_with_count", { count: 1 }),
"displays the right count"
);
assert.strictEqual(
query(".messages-nav li a.new").innerText.trim(),
I18n.t("user.messages.new_with_count", { count: 1 }),
"displays the right count"
);
assert.strictEqual(
query(".messages-nav .user-nav__messages-group-new").innerText.trim(),
I18n.t("user.messages.new_with_count", { count: 1 }),
"displays the right count"
);
assert.ok(exists(".show-mores"), "displays the topic incoming info");
assert.ok(exists(".show-mores"), "displays the topic incoming info");
await visit("/u/charlie/messages/unread");
await visit("/u/charlie/messages/unread");
assert.strictEqual(
query(".messages-nav li a.unread").innerText.trim(),
I18n.t("user.messages.unread"),
"displays the right count"
);
assert.strictEqual(
query(".messages-nav .user-nav__messages-unread").innerText.trim(),
I18n.t("user.messages.unread"),
"displays the right count"
);
assert.strictEqual(
query(".messages-nav li a.new").innerText.trim(),
I18n.t("user.messages.new"),
"displays the right count"
);
assert.strictEqual(
query(".messages-nav .user-nav__messages-new").innerText.trim(),
I18n.t("user.messages.new"),
"displays the right count"
);
} else {
assert.strictEqual(
query(".messages-nav a.unread").innerText.trim(),
I18n.t("user.messages.unread_with_count", { count: 1 }),
"displays the right count"
);
assert.strictEqual(
query(".messages-nav a.new").innerText.trim(),
I18n.t("user.messages.new_with_count", { count: 1 }),
"displays the right count"
);
assert.ok(exists(".show-mores"), "displays the topic incoming info");
await visit("/u/charlie/messages/unread");
assert.strictEqual(
query(".messages-nav a.unread").innerText.trim(),
I18n.t("user.messages.unread"),
"displays the right count"
);
assert.strictEqual(
query(".messages-nav a.new").innerText.trim(),
I18n.t("user.messages.new"),
"displays the right count"
);
}
});
test("incoming messages is not tracked on non user messages route", async function (assert) {
@ -452,11 +514,19 @@ function testUserPrivateMessagesWithGroupMessages(needs, customUserProps) {
await click(".btn.dismiss-read");
await click("#dismiss-read-confirm");
assert.strictEqual(
query(".messages-nav li a.unread").innerText.trim(),
I18n.t("user.messages.unread"),
"displays the right count"
);
if (customUserProps?.redesigned_user_page_nav_enabled) {
assert.strictEqual(
query(".user-nav__messages-unread").innerText.trim(),
I18n.t("user.messages.unread"),
"displays the right count"
);
} else {
assert.strictEqual(
query(".messages-nav li a.unread").innerText.trim(),
I18n.t("user.messages.unread"),
"displays the right count"
);
}
assert.strictEqual(
count(".topic-list-item"),
@ -518,11 +588,19 @@ function testUserPrivateMessagesWithGroupMessages(needs, customUserProps) {
await click(".btn.dismiss-read");
assert.strictEqual(
query(".messages-nav li a.new").innerText.trim(),
I18n.t("user.messages.new"),
"displays the right count"
);
if (customUserProps?.redesigned_user_page_nav_enabled) {
assert.strictEqual(
query(".messages-nav .user-nav__messages-new").innerText.trim(),
I18n.t("user.messages.new"),
"displays the right count"
);
} else {
assert.strictEqual(
query(".messages-nav li a.new").innerText.trim(),
I18n.t("user.messages.new"),
"displays the right count"
);
}
assert.strictEqual(
count(".topic-list-item"),
@ -701,7 +779,11 @@ function testUserPrivateMessagesWithGroupMessages(needs, customUserProps) {
"User personal inbox is selected in dropdown"
);
await click(".messages-sent");
if (customUserProps?.redesigned_user_page_nav_enabled) {
await click(".user-nav__messages-sent");
} else {
await click(".messages-sent");
}
assert.strictEqual(
messagesDropdown.header().name(),
@ -724,7 +806,11 @@ function testUserPrivateMessagesWithGroupMessages(needs, customUserProps) {
"Group inbox is selected in dropdown"
);
await click(".messages-group-new");
if (customUserProps?.redesigned_user_page_nav_enabled) {
await click(".user-nav__messages-group-new");
} else {
await click(".messages-group-new");
}
assert.strictEqual(
messagesDropdown.header().name(),

View File

@ -393,6 +393,24 @@ module("Integration | Component | select-kit/single-select", function (hooks) {
);
});
test("row index", async function (assert) {
this.setProperties({
content: [
{ id: 1, name: "john" },
{ id: 2, name: "jane" },
],
value: null,
});
await render(
hbs`<SingleSelect @value={{this.value}} @content={{this.content}} />`
);
await this.subject.expand();
assert.dom('.select-kit-row[data-index="0"][data-value="1"]').exists();
assert.dom('.select-kit-row[data-index="1"][data-value="2"]').exists();
});
test("options.verticalOffset", async function (assert) {
setDefaultState(this, { verticalOffset: -50 });
await render(hbs`
@ -411,4 +429,17 @@ module("Integration | Component | select-kit/single-select", function (hooks) {
assert.ok(header.bottom > body.top, "it correctly offsets the body");
});
test("options.expandedOnInsert", async function (assert) {
setDefaultState(this);
await render(hbs`
<SingleSelect
@value={{this.value}}
@content={{this.content}}
@options={{hash expandedOnInsert=true}}
/>
`);
assert.dom(".single-select.is-expanded").exists();
});
});

View File

@ -32,5 +32,21 @@ module(
const contentDiv = query(CONTENT_DIV_SELECTOR);
assert.strictEqual(contentDiv.innerText, '"quote"');
});
test("Renders the notification content with no username when username is not present", async function (assert) {
this.set("args", {
content: "content",
username: undefined,
});
await render(
hbs`<MountWidget @widget="quick-access-item" @args={{this.args}} />`
);
const contentDiv = query(CONTENT_DIV_SELECTOR);
const usernameSpan = query("li a div span");
assert.strictEqual(contentDiv.innerText, "content");
assert.strictEqual(usernameSpan.innerText, "");
});
}
);

View File

@ -1,4 +1,4 @@
import { module, test } from "qunit";
import { module, test, todo } from "qunit";
import EmberObject from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators";
import { withPluginApi } from "discourse/lib/plugin-api";
@ -87,4 +87,33 @@ module("Unit | Utility | plugin-api", function (hooks) {
assert.strictEqual(thingy.keep, "hey!");
assert.strictEqual(thingy.prop, "g'day");
});
todo("modifyClass works with getters", function (assert) {
let Base = EmberObject.extend({
get foo() {
throw new Error("base getter called");
},
});
getOwner(this).register("test-class:main", Base, {
instantiate: false,
});
// Performing this lookup triggers `factory._onLookup`. In DEBUG builds, that invokes injectedPropertyAssertion()
// https://github.com/emberjs/ember.js/blob/36505f1b42/packages/%40ember/-internals/runtime/lib/system/core_object.js#L1144-L1163
// Which in turn invokes `factory.proto()`.
// This puts things in a state which will trigger https://github.com/emberjs/ember.js/issues/18860 when a native getter is overridden.
withPluginApi("1.1.0", (api) => {
api.modifyClass("test-class:main", {
get foo() {
return "modified getter";
},
});
});
const obj = Base.create();
assert.true(true, "no error thrown while merging mixin with getter");
assert.strictEqual(obj.foo, "modified getter", "returns correct result");
});
});

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