diff --git a/.eslintignore b/.eslintignore
index fbd1b3dd1b..6758d7f68c 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,8 +1,4 @@
app/assets/javascripts/browser-update.js
-app/assets/javascripts/discourse-loader.js
-app/assets/javascripts/env.js
-app/assets/javascripts/main_include_admin.js
-app/assets/javascripts/vendor.js
app/assets/javascripts/locales/i18n.js
app/assets/javascripts/ember-addons/
app/assets/javascripts/discourse/lib/autosize.js
@@ -13,7 +9,6 @@ lib/pretty_text/
plugins/**/lib/javascripts/locale
public/
vendor/
-app/assets/javascripts/discourse/tests/test-boot-rails.js
app/assets/javascripts/discourse/tests/fixtures
node_modules/
spec/
diff --git a/.eslintrc b/.eslintrc
index 621a902c84..4aee50ed2a 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -21,6 +21,7 @@
"fillIn": "off",
"find": "off",
"getSettledState": "off",
+ "globalThis": "readonly",
"hasModule": "off",
"invisible": "off",
"jQuery": "off",
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 6ab6fa2dfc..270ee753d9 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -31,7 +31,7 @@ jobs:
fail-fast: false
matrix:
- build_type: [backend, frontend, frontend-legacy, annotations]
+ build_type: [backend, frontend, annotations]
target: [core, plugins]
exclude:
- build_type: annotations
@@ -153,24 +153,9 @@ jobs:
if: matrix.build_type == 'backend' && matrix.target == 'plugins'
run: bin/rake plugin:spec
- - name: Core QUnit (Legacy)
- if: matrix.build_type == 'frontend-legacy' && matrix.target == 'core'
- run: QUNIT_EMBER_CLI=0 bin/rake qunit:test['1200000']
- timeout-minutes: 30
-
- - name: Wizard QUnit (Legacy)
- if: matrix.build_type == 'frontend-legacy' && matrix.target == 'core'
- run: QUNIT_EMBER_CLI=0 bin/rake qunit:test['600000','/wizard/qunit']
- timeout-minutes: 10
-
- - name: Plugin QUnit (Legacy)
- if: matrix.build_type == 'frontend-legacy' && matrix.target == 'plugins'
- run: QUNIT_EMBER_CLI=0 bin/rake plugin:qunit['*','1200000']
- timeout-minutes: 30
-
- - name: Plugin QUnit (Ember CLI)
+ - name: Plugin QUnit
if: matrix.build_type == 'frontend' && (matrix.target == 'plugins' || matrix.target == 'core-plugins')
- run: QUNIT_EMBER_CLI=1 bin/rake plugin:qunit['*','1200000']
+ run: bin/rake plugin:qunit['*','1200000']
timeout-minutes: 30
- name: Check Annotations
diff --git a/.prettierignore b/.prettierignore
index cbcac8378f..50cabc43a5 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -9,10 +9,6 @@ config/locales/**/*.yml
script/import_scripts/**/*.yml
app/assets/javascripts/browser-update.js
-app/assets/javascripts/discourse-loader.js
-app/assets/javascripts/env.js
-app/assets/javascripts/main_include_admin.js
-app/assets/javascripts/vendor.js
app/assets/javascripts/locales/i18n.js
app/assets/javascripts/ember-addons/
app/assets/javascripts/discourse/lib/autosize.js
@@ -22,7 +18,6 @@ lib/highlight_js/
plugins/**/lib/javascripts/locale
public/
vendor/
-app/assets/javascripts/discourse/tests/test-boot-rails.js
app/assets/javascripts/discourse/tests/fixtures
spec/
node_modules/
diff --git a/Gemfile.lock b/Gemfile.lock
index 6914db5048..4ca163af08 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -107,7 +107,7 @@ GEM
addressable
debug_inspector (1.1.0)
diff-lcs (1.5.0)
- diffy (3.4.1)
+ diffy (3.4.2)
digest (3.1.0)
discourse-ember-rails (0.18.6)
active_model_serializers
@@ -135,7 +135,7 @@ GEM
excon (0.92.3)
execjs (2.8.1)
exifr (1.3.9)
- fabrication (2.28.0)
+ fabrication (2.29.0)
faker (2.21.0)
i18n (>= 1.8.11, < 2)
fakeweb (1.3.0)
@@ -236,12 +236,12 @@ GEM
mini_portile2 (2.8.0)
mini_racer (0.6.2)
libv8-node (~> 16.10.0.0)
- mini_scheduler (0.13.0)
+ mini_scheduler (0.14.0)
sidekiq (>= 4.2.3)
mini_sql (1.4.0)
mini_suffix (0.3.3)
ffi (~> 1.9)
- minitest (5.15.0)
+ minitest (5.16.1)
mocha (1.14.0)
msgpack (1.5.2)
multi_json (1.15.0)
@@ -311,11 +311,11 @@ GEM
openssl (> 2.0, < 3.1)
optimist (3.0.1)
parallel (1.22.1)
- parallel_tests (3.11.0)
+ parallel_tests (3.11.1)
parallel
parser (3.1.2.0)
ast (~> 2.4.1)
- pg (1.3.5)
+ pg (1.4.1)
progress (3.6.0)
pry (0.13.1)
coderay (~> 1.1)
@@ -335,8 +335,8 @@ GEM
rack (>= 1.2.0)
rack-protection (2.2.0)
rack
- rack-test (1.1.0)
- rack (>= 1.0, < 3)
+ rack-test (2.0.2)
+ rack (>= 1.3)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
@@ -410,7 +410,8 @@ GEM
activesupport (>= 3.1, < 7.1)
json-schema (~> 2.2)
railties (>= 3.1, < 7.1)
- rubocop (1.30.1)
+ rubocop (1.31.1)
+ json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.1.0.0)
rainbow (>= 2.2.2, < 4.0)
@@ -450,7 +451,7 @@ GEM
activesupport (>= 3.1)
shoulda-matchers (5.1.0)
activesupport (>= 5.2.0)
- sidekiq (6.4.2)
+ sidekiq (6.5.1)
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.2.0)
@@ -481,7 +482,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
- unicode-display_width (2.1.0)
+ unicode-display_width (2.2.0)
unicorn (6.1.0)
kgio (~> 2.6)
raindrops (~> 0.7)
@@ -497,7 +498,7 @@ GEM
jwt (~> 2.0)
xorcist (1.1.2)
yaml-lint (0.0.10)
- zeitwerk (2.5.4)
+ zeitwerk (2.6.0)
PLATFORMS
aarch64-linux
@@ -637,4 +638,4 @@ DEPENDENCIES
yaml-lint
BUNDLED WITH
- 2.3.13
+ 2.3.16
diff --git a/README.md b/README.md
index 6bda1d93f4..e40a099058 100644
--- a/README.md
+++ b/README.md
@@ -40,8 +40,6 @@ If you want to set up a Discourse forum for production use, see our [**Discourse
If you're looking for business class hosting, see [discourse.org/buy](https://www.discourse.org/buy/).
-If you're looking for our remote work solution, see [teams.discourse.com](https://teams.discourse.com/).
-
## Requirements
Discourse is built for the *next* 10 years of the Internet, so our requirements are high.
diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js
deleted file mode 100644
index 3edc85a7d4..0000000000
--- a/app/assets/javascripts/admin.js
+++ /dev/null
@@ -1,2 +0,0 @@
-//= require main_include_admin
-//= require admin-plugins
diff --git a/app/assets/javascripts/admin/addon/components/admin-report-table-cell.js b/app/assets/javascripts/admin/addon/components/admin-report-table-cell.js
index 7ef4f7d9c1..838d8dbe1d 100644
--- a/app/assets/javascripts/admin/addon/components/admin-report-table-cell.js
+++ b/app/assets/javascripts/admin/addon/components/admin-report-table-cell.js
@@ -16,6 +16,6 @@ export default Component.extend({
type: alias("label.type"),
property: alias("label.mainProperty"),
- formatedValue: alias("computedLabel.formatedValue"),
+ formattedValue: alias("computedLabel.formattedValue"),
value: alias("computedLabel.value"),
});
diff --git a/app/assets/javascripts/admin/addon/components/watched-word-uploader.js b/app/assets/javascripts/admin/addon/components/watched-word-uploader.js
index 8c2864b69b..efbacb9c0b 100644
--- a/app/assets/javascripts/admin/addon/components/watched-word-uploader.js
+++ b/app/assets/javascripts/admin/addon/components/watched-word-uploader.js
@@ -3,7 +3,6 @@ import I18n from "I18n";
import UppyUploadMixin from "discourse/mixins/uppy-upload";
import { alias } from "@ember/object/computed";
import bootbox from "bootbox";
-import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend(UppyUploadMixin, {
type: "txt",
@@ -16,9 +15,8 @@ export default Component.extend(UppyUploadMixin, {
return { skipValidation: true };
},
- @discourseComputed("actionKey")
- data(actionKey) {
- return { action_key: actionKey };
+ _perFileData() {
+ return { action_key: this.actionKey };
},
uploadDone() {
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-badges-show.js b/app/assets/javascripts/admin/addon/controllers/admin-badges-show.js
index 33c8ad620b..91a2687346 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-badges-show.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-badges-show.js
@@ -6,7 +6,7 @@ import { bufferedProperty } from "discourse/mixins/buffered-content";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { propertyNotEqual } from "discourse/lib/computed";
import { equal, reads } from "@ember/object/computed";
-import { run } from "@ember/runloop";
+import { next } from "@ember/runloop";
import { action } from "@ember/object";
import getURL from "discourse-common/lib/get-url";
@@ -33,7 +33,7 @@ export default Controller.extend(bufferedProperty("model"), {
// this is needed because the model doesnt have default values
// and as we are using a bufferedProperty it's not accessible
// in any other way
- run.next(() => {
+ next(() => {
if (this.model) {
if (!this.model.badge_type_id) {
this.model.set(
diff --git a/app/assets/javascripts/admin/addon/mixins/setting-component.js b/app/assets/javascripts/admin/addon/mixins/setting-component.js
index ebf012aae9..78ab3304cf 100644
--- a/app/assets/javascripts/admin/addon/mixins/setting-component.js
+++ b/app/assets/javascripts/admin/addon/mixins/setting-component.js
@@ -150,7 +150,7 @@ export default Mixin.create({
"default_categories_tracking",
"default_categories_muted",
"default_categories_watching_first_post",
- "default_categories_regular",
+ "default_categories_normal",
"default_tags_watching",
"default_tags_tracking",
"default_tags_muted",
diff --git a/app/assets/javascripts/admin/addon/models/report.js b/app/assets/javascripts/admin/addon/models/report.js
index 55c1012eb1..9cd68dc798 100644
--- a/app/assets/javascripts/admin/addon/models/report.js
+++ b/app/assets/javascripts/admin/addon/models/report.js
@@ -354,7 +354,7 @@ const Report = EmberObject.extend({
value,
type,
property: mainProperty,
- formatedValue: value ? escapeExpression(value) : "—",
+ formattedValue: value ? escapeExpression(value) : "—",
};
},
};
@@ -364,7 +364,7 @@ const Report = EmberObject.extend({
_userLabel(properties, row) {
const username = row[properties.username];
- const formatedValue = () => {
+ const formattedValue = () => {
const userId = row[properties.id];
const user = EmberObject.create({
@@ -386,14 +386,14 @@ const Report = EmberObject.extend({
return {
value: username,
- formatedValue: username ? formatedValue() : "—",
+ formattedValue: username ? formattedValue() : "—",
};
},
_topicLabel(properties, row) {
const topicTitle = row[properties.title];
- const formatedValue = () => {
+ const formattedValue = () => {
const topicId = row[properties.id];
const href = getURL(`/t/-/${topicId}`);
return `${escapeExpression(topicTitle)}`;
@@ -401,7 +401,7 @@ const Report = EmberObject.extend({
return {
value: topicTitle,
- formatedValue: topicTitle ? formatedValue() : "—",
+ formattedValue: topicTitle ? formattedValue() : "—",
};
},
@@ -414,7 +414,7 @@ const Report = EmberObject.extend({
return {
property: properties.title,
value: postTitle,
- formatedValue:
+ formattedValue:
postTitle && href
? `${escapeExpression(postTitle)}`
: "—",
@@ -424,14 +424,14 @@ const Report = EmberObject.extend({
_secondsLabel(value) {
return {
value: toNumber(value),
- formatedValue: durationTiny(value),
+ formattedValue: durationTiny(value),
};
},
_percentLabel(value) {
return {
value: toNumber(value),
- formatedValue: value ? `${value}%` : "—",
+ formattedValue: value ? `${value}%` : "—",
};
},
@@ -440,25 +440,25 @@ const Report = EmberObject.extend({
? true
: options.formatNumbers;
- const formatedValue = () => (formatNumbers ? number(value) : value);
+ const formattedValue = () => (formatNumbers ? number(value) : value);
return {
value: toNumber(value),
- formatedValue: value ? formatedValue() : "—",
+ formattedValue: value ? formattedValue() : "—",
};
},
_bytesLabel(value) {
return {
value: toNumber(value),
- formatedValue: I18n.toHumanSize(value),
+ formattedValue: I18n.toHumanSize(value),
};
},
_dateLabel(value, date, format = "LL") {
return {
value,
- formatedValue: value ? date.format(format) : "—",
+ formattedValue: value ? date.format(format) : "—",
};
},
@@ -467,14 +467,14 @@ const Report = EmberObject.extend({
return {
value,
- formatedValue: value ? escaped : "—",
+ formattedValue: value ? escaped : "—",
};
},
_linkLabel(properties, row) {
const property = properties[0];
const value = getURL(row[property]);
- const formatedValue = (href, anchor) => {
+ const formattedValue = (href, anchor) => {
return `${escapeExpression(
anchor
)}`;
@@ -482,7 +482,7 @@ const Report = EmberObject.extend({
return {
value,
- formatedValue: value ? formatedValue(value, row[properties[1]]) : "—",
+ formattedValue: value ? formattedValue(value, row[properties[1]]) : "—",
};
},
diff --git a/app/assets/javascripts/admin/addon/templates/components/admin-report-table-cell.hbs b/app/assets/javascripts/admin/addon/templates/components/admin-report-table-cell.hbs
index fb8f2e4cdc..ab2a766612 100644
--- a/app/assets/javascripts/admin/addon/templates/components/admin-report-table-cell.hbs
+++ b/app/assets/javascripts/admin/addon/templates/components/admin-report-table-cell.hbs
@@ -1 +1 @@
-{{html-safe formatedValue}}
+{{html-safe formattedValue}}
diff --git a/app/assets/javascripts/admin/addon/templates/components/admin-report-table.hbs b/app/assets/javascripts/admin/addon/templates/components/admin-report-table.hbs
index dca6767298..6ff8b0b3e4 100644
--- a/app/assets/javascripts/admin/addon/templates/components/admin-report-table.hbs
+++ b/app/assets/javascripts/admin/addon/templates/components/admin-report-table.hbs
@@ -31,7 +31,7 @@
{{#each totalsForSample as |total|}}
|
- {{total.formatedValue}}
+ {{total.formattedValue}}
|
{{/each}}
diff --git a/app/assets/javascripts/admin/ember-cli-build.js b/app/assets/javascripts/admin/ember-cli-build.js
index 81a8a5e9aa..2171aed7fd 100644
--- a/app/assets/javascripts/admin/ember-cli-build.js
+++ b/app/assets/javascripts/admin/ember-cli-build.js
@@ -3,7 +3,11 @@
const EmberAddon = require("ember-cli/lib/broccoli/ember-addon");
module.exports = function (defaults) {
- let app = new EmberAddon(defaults, {});
+ let app = new EmberAddon(defaults, {
+ autoImport: {
+ publicAssetURL: "",
+ },
+ });
return app.toTree();
};
diff --git a/app/assets/javascripts/admin/package.json b/app/assets/javascripts/admin/package.json
index 76ebe11621..444cb6ac10 100644
--- a/app/assets/javascripts/admin/package.json
+++ b/app/assets/javascripts/admin/package.json
@@ -15,39 +15,30 @@
"start": "ember serve"
},
"dependencies": {
- "ember-auto-import": "^2.2.4",
- "ember-cli-babel": "^7.13.0",
- "ember-cli-htmlbars": "^4.2.0",
- "xss": "^1.0.8",
- "webpack": "^5.67.0"
+ "ember-auto-import": "^2.4.2",
+ "ember-cli-babel": "^7.23.1",
+ "ember-cli-htmlbars": "^6.0.1",
+ "webpack": "^5.73.0",
+ "xss": "^1.0.13"
},
"devDependencies": {
- "@ember/optional-features": "^1.1.0",
- "@glimmer/component": "^1.0.0",
- "babel-eslint": "^10.0.3",
+ "@ember/optional-features": "^2.0.0",
+ "@glimmer/component": "^1.1.2",
"broccoli-asset-rev": "^3.0.0",
"ember-cli": "~3.25.3",
- "ember-cli-dependency-checker": "^3.2.0",
- "ember-cli-eslint": "^5.1.0",
- "ember-cli-inject-live-reload": "^2.0.1",
+ "ember-cli-dependency-checker": "^3.3.1",
+ "ember-cli-inject-live-reload": "^2.1.0",
"ember-cli-sri": "^2.1.1",
- "ember-cli-template-lint": "^1.0.0-beta.3",
- "ember-cli-uglify": "^3.0.0",
+ "ember-cli-terser": "^4.0.2",
"ember-disable-prototype-extensions": "^1.1.3",
- "ember-export-application-global": "^2.0.1",
"ember-load-initializers": "^2.1.1",
- "ember-maybe-import-regenerator": "^0.1.6",
"ember-resolver": "^7.0.0",
"ember-source": "~3.15.0",
- "ember-source-channel-url": "^2.0.1",
- "ember-try": "^2.0.0",
- "eslint": "^7.27.0",
- "eslint-plugin-ember": "^7.7.1",
- "eslint-plugin-node": "^10.0.0",
+ "ember-source-channel-url": "^3.0.0",
"loader.js": "^4.7.0"
},
"engines": {
- "node": "12.* || 14.* || >= 16",
+ "node": "16.* || >= 18",
"npm": "please-use-yarn",
"yarn": ">= 1.21.1"
},
diff --git a/app/assets/javascripts/admin/tests/dummy/app/index.html b/app/assets/javascripts/admin/tests/dummy/app/index.html
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/admin/tests/dummy/app/styles/.gitkeep b/app/assets/javascripts/admin/tests/dummy/app/styles/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/admin/tests/dummy/app/templates/.gitkeep b/app/assets/javascripts/admin/tests/dummy/app/templates/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/admin/tests/dummy/config/.gitkeep b/app/assets/javascripts/admin/tests/dummy/config/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/admin/tests/dummy/public/.gitkeep b/app/assets/javascripts/admin/tests/dummy/public/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/admin/tests/index.html b/app/assets/javascripts/admin/tests/index.html
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/app-boot.js b/app/assets/javascripts/app-boot.js
deleted file mode 100644
index 834d81cc31..0000000000
--- a/app/assets/javascripts/app-boot.js
+++ /dev/null
@@ -1,27 +0,0 @@
-// discourse-skip-module
-
-(function () {
- if (window.unsupportedBrowser) {
- throw "Unsupported browser detected";
- }
-
- let Discourse = requirejs("discourse/app").default.create();
-
- // required for our template compiler
- window.__DISCOURSE_RAW_TEMPLATES = requirejs(
- "discourse-common/lib/raw-templates"
- ).__DISCOURSE_RAW_TEMPLATES;
-
- // required for addons to work without Ember CLI
- // eslint-disable-next-line no-undef
- Object.keys(Ember.TEMPLATES).forEach((k) => {
- if (k.indexOf("select-kit") === 0) {
- // eslint-disable-next-line no-undef
- let template = Ember.TEMPLATES[k];
- define(k, () => template);
- }
- });
-
- // ensure Discourse is added as a global
- window.Discourse = Discourse;
-})();
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
deleted file mode 100644
index dbaf4acf87..0000000000
--- a/app/assets/javascripts/application.js
+++ /dev/null
@@ -1,104 +0,0 @@
-//= require_tree ./truth-helpers/addon
-//= require_tree ./discourse-common/addon
-//= require ./polyfills
-//= require_tree ./select-kit/addon
-//= require ./discourse/app/app
-//= require ./app-boot
-
-// Stuff we need to load first
-//= require ./discourse/app/lib/to-markdown
-//= require ./discourse/app/lib/utilities
-//= require ./discourse/app/lib/user-presence
-//= require ./discourse/app/lib/logout
-//= require ./discourse/app/mixins/singleton
-//= require ./discourse/app/models/rest
-//= require ./discourse/app/models/session
-//= require ./discourse/app/lib/ajax
-//= require ./discourse/app/lib/text
-//= require ./discourse/app/lib/hash
-//= require ./discourse/app/lib/load-script
-//= require ./discourse/app/lib/notification-levels
-//= require ./discourse/app/services/app-events
-//= require ./discourse/app/lib/offset-calculator
-//= require ./discourse/app/lib/lock-on
-//= require ./discourse/app/lib/url
-//= require ./discourse/app/lib/email-provider-default-settings
-//= require ./discourse/app/lib/debounce
-//= require ./discourse/app/lib/quote
-//= require ./discourse/app/lib/key-value-store
-//= require ./discourse/app/lib/computed
-//= require ./discourse/app/lib/formatter
-//= require ./discourse/app/lib/text-direction
-//= require ./discourse/app/lib/eyeline
-//= require ./discourse/app/lib/show-modal
-//= require ./discourse/app/lib/download-calendar
-//= require ./discourse/app/mixins/scrolling
-//= require ./discourse/app/lib/ajax-error
-//= require ./discourse/app/models/result-set
-//= require ./discourse/app/models/store
-//= require ./discourse/app/models/action-summary
-//= require ./discourse/app/models/permission-type
-//= require ./discourse/app/models/category
-//= require ./discourse/app/models/topic
-//= require ./discourse/app/models/draft
-//= require ./discourse/app/models/composer
-//= require ./discourse/app/models/badge-grouping
-//= require ./discourse/app/models/badge
-//= require ./discourse/app/models/permission-type
-//= require ./discourse/app/models/user-action-group
-//= require ./discourse/app/models/trust-level
-//= require ./discourse/app/lib/search
-//= require ./discourse/app/lib/user-search
-//= require ./discourse/app/lib/export-csv
-//= require ./discourse/app/lib/autocomplete
-//= require ./discourse/app/lib/after-transition
-//= require ./discourse/app/lib/safari-hacks
-//= require ./discourse/app/lib/put-cursor-at-end
-//= require_tree ./discourse/app/adapters
-//= require ./discourse/app/models/post-action-type
-//= require ./discourse/app/models/post
-//= require ./discourse/app/lib/posts-with-placeholders
-//= require ./discourse/app/models/post-stream
-//= require ./discourse/app/models/topic-details
-//= require ./discourse/app/models/topic
-//= require ./discourse/app/models/user-action
-//= require ./discourse/app/models/draft
-//= require ./discourse/app/models/composer
-//= require ./discourse/app/models/user-badge
-//= require_tree ./discourse/app/lib
-//= require_tree ./discourse/app/mixins
-//= require ./discourse/app/models/invite
-//= require ./discourse/app/controllers/discovery-sortable
-//= require ./discourse/app/controllers/navigation/default
-//= require ./discourse/app/components/edit-category-panel
-//= require ./discourse/app/lib/link-mentions
-//= require ./discourse/app/components/site-header
-//= require ./discourse/app/components/d-editor
-//= require ./discourse/app/routes/discourse
-//= require ./discourse/app/routes/build-topic-route
-//= require ./discourse/app/routes/restricted-user
-//= require ./discourse/app/routes/user-topic-list
-//= require ./discourse/app/routes/user-activity-stream
-//= require ./discourse/app/routes/topic-from-params
-//= require ./discourse/app/components/text-field
-//= require ./discourse/app/components/conditional-loading-spinner
-//= require ./discourse/app/helpers/user-avatar
-//= require ./discourse/app/helpers/cold-age-class
-//= require ./discourse/app/helpers/loading-spinner
-//= require ./discourse/app/helpers/category-link
-//= require ./discourse/app/lib/export-result
-//= require ./discourse/app/mapping-router
-
-//= require_tree ./discourse/app/controllers
-//= require_tree ./discourse/app/models
-//= require_tree ./discourse/app/components
-//= require_tree ./discourse/app/raw-views
-//= require_tree ./discourse/app/helpers
-//= require_tree ./discourse/app/templates
-//= require_tree ./discourse/app/routes
-//= require_tree ./discourse/app/pre-initializers
-//= require_tree ./discourse/app/initializers
-//= require_tree ./discourse/app/services
-
-//= require_tree ./discourse/app/widgets
-//= require ./widget-runtime
diff --git a/app/assets/javascripts/discourse-common/addon/config/environment.js b/app/assets/javascripts/discourse-common/addon/config/environment.js
index 3e825fa3c8..cc5f76fe17 100644
--- a/app/assets/javascripts/discourse-common/addon/config/environment.js
+++ b/app/assets/javascripts/discourse-common/addon/config/environment.js
@@ -1,3 +1,5 @@
+import deprecated from "discourse-common/lib/deprecated";
+
export const INPUT_DELAY = 250;
let environment = "unknown";
@@ -19,6 +21,9 @@ export function isTesting() {
// eslint-disable-next-line no-undef
let _isLegacy = Ember.VERSION.startsWith("3.12");
export function isLegacyEmber() {
+ deprecated("`isLegacyEmber()` is now deprecated and always returns true", {
+ dropFrom: "3.0.0.beta1",
+ });
return _isLegacy;
}
diff --git a/app/assets/javascripts/discourse-common/addon/lib/debounce.js b/app/assets/javascripts/discourse-common/addon/lib/debounce.js
index dcd2693b78..2bcd387724 100644
--- a/app/assets/javascripts/discourse-common/addon/lib/debounce.js
+++ b/app/assets/javascripts/discourse-common/addon/lib/debounce.js
@@ -1,5 +1,5 @@
-import { debounce, next, run } from "@ember/runloop";
-import { isLegacyEmber, isTesting } from "discourse-common/config/environment";
+import { debounce } from "@ember/runloop";
+import { isTesting } from "discourse-common/config/environment";
/**
Debounce a Javascript function. This means if it's called many times in a time limit it
@@ -7,13 +7,13 @@ import { isLegacyEmber, isTesting } from "discourse-common/config/environment";
Original function will be called with the context and arguments from the last call made.
**/
-let testingFunc = isLegacyEmber() ? run : next;
-
export default function () {
if (isTesting()) {
- // Don't include the time argument (in ms)
+ // Replace the time argument with 10ms
let args = [].slice.call(arguments, 0, -1);
- return testingFunc.apply(void 0, args);
+ args.push(10);
+
+ return debounce.apply(undefined, args);
} else {
return debounce(...arguments);
}
diff --git a/app/assets/javascripts/discourse-common/addon/lib/icon-library.js b/app/assets/javascripts/discourse-common/addon/lib/icon-library.js
index 7a8588f91b..a77aa591f2 100644
--- a/app/assets/javascripts/discourse-common/addon/lib/icon-library.js
+++ b/app/assets/javascripts/discourse-common/addon/lib/icon-library.js
@@ -3,6 +3,7 @@ import attributeHook from "discourse-common/lib/attribute-hook";
import { h } from "virtual-dom";
import { isDevelopment } from "discourse-common/config/environment";
import escape from "discourse-common/lib/escape";
+import deprecated from "discourse-common/lib/deprecated";
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
let _renderers = [];
@@ -159,9 +160,18 @@ registerIconRenderer({
I18n.t(params.title)
)}'>${html}`;
}
+
if (params.translatedtitle) {
+ deprecated(`use 'translatedTitle' option instead of 'translatedtitle'`, {
+ since: "2.9.0.beta6",
+ dropFrom: "2.10.0.beta1",
+ });
+ params.translatedTitle = params.translatedtitle;
+ }
+
+ if (params.translatedTitle) {
html = `${html}`;
}
return html;
diff --git a/app/assets/javascripts/discourse-common/addon/resolver.js b/app/assets/javascripts/discourse-common/addon/resolver.js
index c7914aa912..c0b8162866 100644
--- a/app/assets/javascripts/discourse-common/addon/resolver.js
+++ b/app/assets/javascripts/discourse-common/addon/resolver.js
@@ -93,6 +93,7 @@ export function buildResolver(baseName) {
if (split.length > 1) {
const appBase = `${baseName}/${split[0]}s/`;
const adminBase = "admin/" + split[0] + "s/";
+ const wizardBase = "wizard/" + split[0] + "s/";
// Allow render 'admin/templates/xyz' too
split[1] = split[1].replace(".templates", "").replace("/templates", "");
@@ -101,7 +102,8 @@ export function buildResolver(baseName) {
let dashed = dasherize(split[1].replace(/\./g, "/"));
if (
requirejs.entries[appBase + dashed] ||
- requirejs.entries[adminBase + dashed]
+ requirejs.entries[adminBase + dashed] ||
+ requirejs.entries[wizardBase + dashed]
) {
return split[0] + ":" + dashed;
}
@@ -110,7 +112,8 @@ export function buildResolver(baseName) {
dashed = dasherize(split[1].replace(/\./g, "-"));
if (
requirejs.entries[appBase + dashed] ||
- requirejs.entries[adminBase + dashed]
+ requirejs.entries[adminBase + dashed] ||
+ requirejs.entries[wizardBase + dashed]
) {
return split[0] + ":" + dashed;
}
@@ -253,6 +256,7 @@ export function buildResolver(baseName) {
templates[decamelized.replace(/\_/, "/")] ||
templates[`${baseName}/templates/${withoutType}`] ||
this.findAdminTemplate(parsedName) ||
+ this.findWizardTemplate(parsedName) ||
this.findUnderscoredTemplate(parsedName)
);
},
@@ -296,5 +300,37 @@ export function buildResolver(baseName) {
);
}
},
+
+ findWizardTemplate(parsedName) {
+ let decamelized = decamelize(parsedName.fullNameWithoutType);
+ if (decamelized.startsWith("components")) {
+ let comPath = `wizard/templates/${decamelized}`;
+ const compTemplate =
+ Ember.TEMPLATES[`javascripts/${comPath}`] || Ember.TEMPLATES[comPath];
+ if (compTemplate) {
+ return compTemplate;
+ }
+ }
+
+ if (decamelized === "javascripts/wizard") {
+ return Ember.TEMPLATES["wizard/templates/wizard"];
+ }
+
+ if (
+ decamelized.startsWith("wizard") ||
+ decamelized.startsWith("javascripts/wizard")
+ ) {
+ decamelized = decamelized.replace(/^wizard\_/, "wizard/templates/");
+ decamelized = decamelized.replace(/^wizard\./, "wizard/templates/");
+ decamelized = decamelized.replace(/\./g, "_");
+
+ const dashed = decamelized.replace(/_/g, "-");
+ return (
+ Ember.TEMPLATES[decamelized] ||
+ Ember.TEMPLATES[dashed] ||
+ Ember.TEMPLATES[dashed.replace("wizard-", "wizard/")]
+ );
+ }
+ },
});
}
diff --git a/app/assets/javascripts/discourse-common/addon/utils/decorators.js b/app/assets/javascripts/discourse-common/addon/utils/decorators.js
index 5417a0dabc..b847ad830e 100644
--- a/app/assets/javascripts/discourse-common/addon/utils/decorators.js
+++ b/app/assets/javascripts/discourse-common/addon/utils/decorators.js
@@ -1,5 +1,35 @@
import { on as emberOn } from "@ember/object/evented";
-import { computed, observer } from "@ember/object";
+import { observer } from "@ember/object";
+import {
+ alias as EmberAlias,
+ and as EmberAnd,
+ bool as EmberBool,
+ collect as EmberCollect,
+ empty as EmberEmpty,
+ equal as EmberEqual,
+ filter as EmberFilter,
+ filterBy as EmberFilterBy,
+ gt as EmberGt,
+ gte as EmberGte,
+ lt as EmberLt,
+ lte as EmberLte,
+ map as EmberMap,
+ mapBy as EmberMapBy,
+ match as EmberMatch,
+ max as EmberMax,
+ min as EmberMin,
+ none as EmberNone,
+ not as EmberNot,
+ notEmpty as EmberNotEmpty,
+ oneWay as EmberOneWay,
+ or as EmberOr,
+ reads as EmberReads,
+ setDiff as EmberSetDiff,
+ sort as EmberSort,
+ sum as EmberSum,
+ union as EmberUnion,
+ uniq as EmberUniq,
+} from "@ember/object/computed";
import { bind as emberBind, schedule } from "@ember/runloop";
import decoratorAlias from "discourse-common/utils/decorator-alias";
import extractValue from "discourse-common/utils/extract-value";
@@ -63,31 +93,31 @@ export const observes = decoratorAlias(
"Can not `observe` without property names"
);
-export const alias = macroAlias(computed.alias);
-export const and = macroAlias(computed.and);
-export const bool = macroAlias(computed.bool);
-export const collect = macroAlias(computed.collect);
-export const empty = macroAlias(computed.empty);
-export const equal = macroAlias(computed.equal);
-export const filter = macroAlias(computed.filter);
-export const filterBy = macroAlias(computed.filterBy);
-export const gt = macroAlias(computed.gt);
-export const gte = macroAlias(computed.gte);
-export const lt = macroAlias(computed.lt);
-export const lte = macroAlias(computed.lte);
-export const map = macroAlias(computed.map);
-export const mapBy = macroAlias(computed.mapBy);
-export const match = macroAlias(computed.match);
-export const max = macroAlias(computed.max);
-export const min = macroAlias(computed.min);
-export const none = macroAlias(computed.none);
-export const not = macroAlias(computed.not);
-export const notEmpty = macroAlias(computed.notEmpty);
-export const oneWay = macroAlias(computed.oneWay);
-export const or = macroAlias(computed.or);
-export const reads = macroAlias(computed.reads);
-export const setDiff = macroAlias(computed.setDiff);
-export const sort = macroAlias(computed.sort);
-export const sum = macroAlias(computed.sum);
-export const union = macroAlias(computed.union);
-export const uniq = macroAlias(computed.uniq);
+export const alias = macroAlias(EmberAlias);
+export const and = macroAlias(EmberAnd);
+export const bool = macroAlias(EmberBool);
+export const collect = macroAlias(EmberCollect);
+export const empty = macroAlias(EmberEmpty);
+export const equal = macroAlias(EmberEqual);
+export const filter = macroAlias(EmberFilter);
+export const filterBy = macroAlias(EmberFilterBy);
+export const gt = macroAlias(EmberGt);
+export const gte = macroAlias(EmberGte);
+export const lt = macroAlias(EmberLt);
+export const lte = macroAlias(EmberLte);
+export const map = macroAlias(EmberMap);
+export const mapBy = macroAlias(EmberMapBy);
+export const match = macroAlias(EmberMatch);
+export const max = macroAlias(EmberMax);
+export const min = macroAlias(EmberMin);
+export const none = macroAlias(EmberNone);
+export const not = macroAlias(EmberNot);
+export const notEmpty = macroAlias(EmberNotEmpty);
+export const oneWay = macroAlias(EmberOneWay);
+export const or = macroAlias(EmberOr);
+export const reads = macroAlias(EmberReads);
+export const setDiff = macroAlias(EmberSetDiff);
+export const sort = macroAlias(EmberSort);
+export const sum = macroAlias(EmberSum);
+export const union = macroAlias(EmberUnion);
+export const uniq = macroAlias(EmberUniq);
diff --git a/app/assets/javascripts/discourse-common/ember-cli-build.js b/app/assets/javascripts/discourse-common/ember-cli-build.js
index 81a8a5e9aa..2171aed7fd 100644
--- a/app/assets/javascripts/discourse-common/ember-cli-build.js
+++ b/app/assets/javascripts/discourse-common/ember-cli-build.js
@@ -3,7 +3,11 @@
const EmberAddon = require("ember-cli/lib/broccoli/ember-addon");
module.exports = function (defaults) {
- let app = new EmberAddon(defaults, {});
+ let app = new EmberAddon(defaults, {
+ autoImport: {
+ publicAssetURL: "",
+ },
+ });
return app.toTree();
};
diff --git a/app/assets/javascripts/discourse-common/package.json b/app/assets/javascripts/discourse-common/package.json
index 1cb9e5d26a..6761055ed9 100644
--- a/app/assets/javascripts/discourse-common/package.json
+++ b/app/assets/javascripts/discourse-common/package.json
@@ -21,43 +21,35 @@
"@uppy/drop-target": "^1.1.2",
"@uppy/utils": "^4.0.5",
"@uppy/xhr-upload": "^2.0.7",
- "ember-auto-import": "^2.2.4",
- "ember-cli-babel": "^7.13.0",
- "ember-cli-htmlbars": "^4.2.0",
+ "ember-auto-import": "^2.4.2",
+ "ember-cli-babel": "^7.23.1",
+ "ember-cli-htmlbars": "^6.0.1",
"handlebars": "^4.7.0",
"truth-helpers": "^1.0.0",
- "webpack": "^5.67.0"
+ "webpack": "^5.73.0"
},
"devDependencies": {
- "@ember/optional-features": "^1.1.0",
- "@glimmer/component": "^1.0.0",
- "babel-eslint": "^10.0.3",
+ "@ember/optional-features": "^2.0.0",
+ "@glimmer/component": "^1.1.2",
"broccoli-asset-rev": "^3.0.0",
"ember-cli": "~3.25.3",
- "ember-cli-dependency-checker": "^3.2.0",
- "ember-cli-eslint": "^5.1.0",
- "ember-cli-inject-live-reload": "^2.0.1",
+ "ember-cli-dependency-checker": "^3.3.1",
+ "ember-cli-inject-live-reload": "^2.1.0",
"ember-cli-sri": "^2.1.1",
- "ember-cli-template-lint": "^1.0.0-beta.3",
- "ember-cli-uglify": "^3.0.0",
+ "ember-cli-terser": "^4.0.2",
"ember-disable-prototype-extensions": "^1.1.3",
- "ember-export-application-global": "^2.0.1",
"ember-load-initializers": "^2.1.1",
- "ember-maybe-import-regenerator": "^0.1.6",
"ember-resolver": "^7.0.0",
"ember-source": "~3.15.0",
- "ember-source-channel-url": "^2.0.1",
- "ember-try": "^2.0.0",
- "eslint-plugin-ember": "^7.7.1",
- "eslint-plugin-node": "^10.0.0",
+ "ember-source-channel-url": "^3.0.0",
"loader.js": "^4.7.0"
},
"engines": {
- "node": "12.* || 14.* || >= 16",
+ "node": "16.* || >= 18",
"npm": "please-use-yarn",
"yarn": ">= 1.21.1"
},
"ember": {
- "edition": "octane"
+ "edition": "default"
}
}
diff --git a/app/assets/javascripts/discourse-common/tests/dummy/app/index.html b/app/assets/javascripts/discourse-common/tests/dummy/app/index.html
new file mode 100644
index 0000000000..870306a6ba
--- /dev/null
+++ b/app/assets/javascripts/discourse-common/tests/dummy/app/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+ Dummy
+
+
+
+ {{content-for "head"}}
+
+
+
+
+ {{content-for "head-footer"}}
+
+
+ {{content-for "body"}}
+
+
+
+
+ {{content-for "body-footer"}}
+
+
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse-common/tests/dummy/app/styles/.gitkeep b/app/assets/javascripts/discourse-common/tests/dummy/app/styles/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/discourse-common/tests/dummy/app/templates/.gitkeep b/app/assets/javascripts/discourse-common/tests/dummy/app/templates/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/discourse-common/tests/dummy/config/.gitkeep b/app/assets/javascripts/discourse-common/tests/dummy/config/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/discourse-common/tests/dummy/public/.gitkeep b/app/assets/javascripts/discourse-common/tests/dummy/public/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/discourse-common/tests/index.html b/app/assets/javascripts/discourse-common/tests/index.html
new file mode 100644
index 0000000000..5c291da716
--- /dev/null
+++ b/app/assets/javascripts/discourse-common/tests/index.html
@@ -0,0 +1,39 @@
+
+
+
+
+ Dummy Tests
+
+
+
+ {{content-for "head"}}
+ {{content-for "test-head"}}
+
+
+
+
+
+ {{content-for "head-footer"}}
+ {{content-for "test-head-footer"}}
+
+
+ {{content-for "body"}}
+ {{content-for "test-body"}}
+
+
+
+
+
+
+
+
+
+
+ {{content-for "body-footer"}}
+ {{content-for "test-body-footer"}}
+
+
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse-hbr/ember-cli-build.js b/app/assets/javascripts/discourse-hbr/ember-cli-build.js
new file mode 100644
index 0000000000..2171aed7fd
--- /dev/null
+++ b/app/assets/javascripts/discourse-hbr/ember-cli-build.js
@@ -0,0 +1,13 @@
+"use strict";
+
+const EmberAddon = require("ember-cli/lib/broccoli/ember-addon");
+
+module.exports = function (defaults) {
+ let app = new EmberAddon(defaults, {
+ autoImport: {
+ publicAssetURL: "",
+ },
+ });
+
+ return app.toTree();
+};
diff --git a/app/assets/javascripts/discourse-hbr/package.json b/app/assets/javascripts/discourse-hbr/package.json
index dce9d60e8f..5f245a8b8c 100644
--- a/app/assets/javascripts/discourse-hbr/package.json
+++ b/app/assets/javascripts/discourse-hbr/package.json
@@ -15,43 +15,34 @@
"start": "ember serve"
},
"dependencies": {
- "ember-auto-import": "^2.2.4",
- "ember-cli-babel": "^7.13.0",
- "ember-cli-htmlbars": "^4.2.0",
- "webpack": "^5.67.0"
+ "ember-auto-import": "^2.4.2",
+ "ember-cli-babel": "^7.23.1",
+ "ember-cli-htmlbars": "^6.0.1",
+ "handlebars": "^4.7.6",
+ "webpack": "^5.73.0"
},
"devDependencies": {
- "@ember/optional-features": "^1.1.0",
- "@glimmer/component": "^1.0.0",
- "babel-eslint": "^10.0.3",
+ "@ember/optional-features": "^2.0.0",
+ "@glimmer/component": "^1.1.2",
"broccoli-asset-rev": "^3.0.0",
- "broccoli-stew": "^3.0.0",
"ember-cli": "~3.25.3",
- "ember-cli-dependency-checker": "^3.2.0",
- "ember-cli-eslint": "^5.1.0",
- "ember-cli-inject-live-reload": "^2.0.1",
+ "ember-cli-dependency-checker": "^3.3.1",
+ "ember-cli-inject-live-reload": "^2.1.0",
"ember-cli-sri": "^2.1.1",
- "ember-cli-template-lint": "^1.0.0-beta.3",
- "ember-cli-uglify": "^3.0.0",
+ "ember-cli-terser": "^4.0.2",
"ember-disable-prototype-extensions": "^1.1.3",
- "ember-export-application-global": "^2.0.1",
"ember-load-initializers": "^2.1.1",
- "ember-maybe-import-regenerator": "^0.1.6",
"ember-resolver": "^7.0.0",
"ember-source": "~3.15.0",
- "ember-source-channel-url": "^2.0.1",
- "ember-try": "^2.0.0",
- "eslint-plugin-ember": "^7.7.1",
- "eslint-plugin-node": "^10.0.0",
- "handlebars": "^4.7.6",
+ "ember-source-channel-url": "^3.0.0",
"loader.js": "^4.7.0"
},
"engines": {
- "node": "12.* || 14.* || >= 16",
+ "node": "16.* || >= 18",
"npm": "please-use-yarn",
"yarn": ">= 1.21.1"
},
"ember": {
- "edition": "octane"
+ "edition": "default"
}
}
diff --git a/app/assets/javascripts/discourse-hbr/tests/dummy/app/index.html b/app/assets/javascripts/discourse-hbr/tests/dummy/app/index.html
new file mode 100644
index 0000000000..870306a6ba
--- /dev/null
+++ b/app/assets/javascripts/discourse-hbr/tests/dummy/app/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+ Dummy
+
+
+
+ {{content-for "head"}}
+
+
+
+
+ {{content-for "head-footer"}}
+
+
+ {{content-for "body"}}
+
+
+
+
+ {{content-for "body-footer"}}
+
+
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse-hbr/tests/dummy/app/styles/.gitkeep b/app/assets/javascripts/discourse-hbr/tests/dummy/app/styles/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/discourse-hbr/tests/dummy/app/templates/.gitkeep b/app/assets/javascripts/discourse-hbr/tests/dummy/app/templates/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/discourse-hbr/tests/dummy/config/.gitkeep b/app/assets/javascripts/discourse-hbr/tests/dummy/config/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/discourse-hbr/tests/dummy/public/.gitkeep b/app/assets/javascripts/discourse-hbr/tests/dummy/public/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/discourse-hbr/tests/index.html b/app/assets/javascripts/discourse-hbr/tests/index.html
new file mode 100644
index 0000000000..5c291da716
--- /dev/null
+++ b/app/assets/javascripts/discourse-hbr/tests/index.html
@@ -0,0 +1,39 @@
+
+
+
+
+ Dummy Tests
+
+
+
+ {{content-for "head"}}
+ {{content-for "test-head"}}
+
+
+
+
+
+ {{content-for "head-footer"}}
+ {{content-for "test-head-footer"}}
+
+
+ {{content-for "body"}}
+ {{content-for "test-body"}}
+
+
+
+
+
+
+
+
+
+
+ {{content-for "body-footer"}}
+ {{content-for "test-body-footer"}}
+
+
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse-loader.js b/app/assets/javascripts/discourse-loader.js
deleted file mode 100644
index 3686522bdc..0000000000
--- a/app/assets/javascripts/discourse-loader.js
+++ /dev/null
@@ -1,432 +0,0 @@
-var define, requirejs;
-
-(function () {
- var JS_MODULES = {};
- var ALIASES = {
- "ember-addons/ember-computed-decorators":
- "discourse-common/utils/decorators",
- "discourse/lib/raw-templates": "discourse-common/lib/raw-templates",
- "preload-store": "discourse/lib/preload-store",
- "fixtures/user_fixtures": "discourse/tests/fixtures/user-fixtures",
- };
- var ALIAS_PREPEND = {
- fixtures: "discourse/tests/",
- helpers: "discourse/tests/",
- };
-
- // In future versions of ember we don't need this
- if (typeof Ember !== "undefined") {
- JS_MODULES = {
- jquery: { default: $ },
- "@ember/array": {
- default: Ember.Array,
- A: Ember.A,
- isArray: Ember.isArray,
- },
- "@ember/array/proxy": {
- default: Ember.ArrayProxy,
- },
- "@ember/component": {
- default: Ember.Component,
- },
- "@ember/controller": {
- default: Ember.Controller,
- inject: Ember.inject.controller,
- },
- "@ember/debug": {
- assert: Ember.assert,
- runInDebug: Ember.runInDebug,
- warn: Ember.warn,
- },
- "@ember/object": {
- action: Ember._action,
- default: Ember.Object,
- get: Ember.get,
- getProperties: Ember.getProperties,
- set: Ember.set,
- setProperties: Ember.setProperties,
- computed: Ember.computed,
- defineProperty: Ember.defineProperty,
- observer: Ember.observer,
- },
- "@ember/object/computed": {
- alias: Ember.computed.alias,
- and: Ember.computed.and,
- bool: Ember.computed.bool,
- collect: Ember.computed.collect,
- deprecatingAlias: Ember.computed.deprecatingAlias,
- empty: Ember.computed.empty,
- equal: Ember.computed.equal,
- filter: Ember.computed.filter,
- filterBy: Ember.computed.filterBy,
- gt: Ember.computed.gt,
- gte: Ember.computed.gte,
- intersect: Ember.computed.intersect,
- lt: Ember.computed.lt,
- lte: Ember.computed.lte,
- map: Ember.computed.map,
- mapBy: Ember.computed.mapBy,
- match: Ember.computed.match,
- max: Ember.computed.max,
- min: Ember.computed.min,
- none: Ember.computed.none,
- not: Ember.computed.not,
- notEmpty: Ember.computed.notEmpty,
- oneWay: Ember.computed.oneWay,
- or: Ember.computed.or,
- readOnly: Ember.computed.readOnly,
- reads: Ember.computed.reads,
- setDiff: Ember.computed.setDiff,
- sort: Ember.computed.sort,
- sum: Ember.computed.sum,
- union: Ember.computed.union,
- uniq: Ember.computed.uniq,
- uniqBy: Ember.computed.uniqBy,
- },
- "@ember/object/mixin": { default: Ember.Mixin },
- "@ember/object/proxy": { default: Ember.ObjectProxy },
- "@ember/object/promise-proxy-mixin": { default: Ember.PromiseProxyMixin },
- "@ember/object/evented": {
- default: Ember.Evented,
- on: Ember.on,
- },
- "@ember/routing/route": { default: Ember.Route },
- "@ember/routing/router": { default: Ember.Router },
- "@ember/runloop": {
- bind: Ember.run.bind,
- cancel: Ember.run.cancel,
- debounce: Ember.testing ? Ember.run : Ember.run.debounce,
- later: Ember.run.later,
- next: Ember.run.next,
- once: Ember.run.once,
- run: Ember.run,
- schedule: Ember.run.schedule,
- scheduleOnce: Ember.run.scheduleOnce,
- throttle: Ember.run.throttle,
- },
- "@ember/service": {
- default: Ember.Service,
- inject: Ember.inject.service,
- },
- "@ember/utils": {
- isBlank: Ember.isBlank,
- isEmpty: Ember.isEmpty,
- isNone: Ember.isNone,
- isPresent: Ember.isPresent,
- },
- rsvp: {
- asap: Ember.RSVP.asap,
- all: Ember.RSVP.all,
- allSettled: Ember.RSVP.allSettled,
- race: Ember.RSVP.race,
- hash: Ember.RSVP.hash,
- hashSettled: Ember.RSVP.hashSettled,
- rethrow: Ember.RSVP.rethrow,
- defer: Ember.RSVP.defer,
- denodeify: Ember.RSVP.denodeify,
- resolve: Ember.RSVP.resolve,
- reject: Ember.RSVP.reject,
- map: Ember.RSVP.map,
- filter: Ember.RSVP.filter,
- default: Ember.RSVP,
- Promise: Ember.RSVP.Promise,
- EventTarget: Ember.RSVP.EventTarget,
- },
- "@ember/string": {
- w: Ember.String.w,
- dasherize: Ember.String.dasherize,
- decamelize: Ember.String.decamelize,
- camelize: Ember.String.camelize,
- classify: Ember.String.classify,
- underscore: Ember.String.underscore,
- capitalize: Ember.String.capitalize,
- },
- "@ember/template": {
- htmlSafe: Ember.String.htmlSafe,
- },
- "@ember/application": {
- default: Ember.Application,
- setOwner: Ember.setOwner,
- getOwner: Ember.getOwner,
- },
- "@ember/component/helper": {
- default: Ember.Helper,
- },
- "@ember/component/text-field": {
- default: Ember.TextField,
- },
- "@ember/component/text-area": {
- default: Ember.TextArea,
- },
- "@ember/error": {
- default: Ember.error,
- },
- "@ember/object/internals": {
- guidFor: Ember.guidFor,
- },
- "@ember/test": {
- registerWaiter: Ember.Test && Ember.Test.registerWaiter,
- unregisterWaiter: Ember.Test && Ember.Test.unregisterWaiter,
- },
- I18n: {
- // eslint-disable-next-line
- default: I18n,
- },
- };
- }
-
- var _isArray;
- if (!Array.isArray) {
- _isArray = function (x) {
- return Object.prototype.toString.call(x) === "[object Array]";
- };
- } else {
- _isArray = Array.isArray;
- }
-
- var registry = {};
- var seen = {};
- var FAILED = false;
-
- var uuid = 0;
-
- function tryFinally(tryable, finalizer) {
- try {
- return tryable();
- } finally {
- finalizer();
- }
- }
-
- function unsupportedModule(length) {
- throw new Error(
- "an unsupported module was defined, expected `define(name, deps, module)` instead got: `" +
- length +
- "` arguments to define`"
- );
- }
-
- function deprecatedModule(depricated, useInstead) {
- var warning = "[DEPRECATION] `" + depricated + "` is deprecated.";
- if (useInstead) {
- warning += " Please use `" + useInstead + "` instead.";
- }
- // eslint-disable-next-line no-console
- console.warn(warning);
- }
-
- var defaultDeps = ["require", "exports", "module"];
-
- function Module(name, deps, callback, exports) {
- this.id = uuid++;
- this.name = name;
- this.deps = !deps.length && callback.length ? defaultDeps : deps;
- this.exports = exports || {};
- this.callback = callback;
- this.state = undefined;
- this._require = undefined;
- }
-
- Module.prototype.makeRequire = function () {
- var name = transformForAliases(this.name);
-
- return (
- this._require ||
- (this._require = function (dep) {
- return requirejs(resolve(dep, name));
- })
- );
- };
-
- define = function (name, deps, callback) {
- if (arguments.length < 2) {
- unsupportedModule(arguments.length);
- }
-
- if (!_isArray(deps)) {
- callback = deps;
- deps = [];
- }
-
- registry[name] = new Module(name, deps, callback);
- };
-
- // we don't support all of AMD
- // define.amd = {};
- // we will support petals...
- define.petal = {};
-
- function Alias(path) {
- this.name = path;
- }
-
- define.alias = function (path) {
- return new Alias(path);
- };
-
- function reify(mod, name, rseen) {
- var deps = mod.deps;
- var length = deps.length;
- var reified = new Array(length);
- var dep;
- // TODO: new Module
- // TODO: seen refactor
- var module = {};
-
- for (var i = 0, l = length; i < l; i++) {
- dep = deps[i];
- if (dep === "exports") {
- module.exports = reified[i] = rseen;
- } else if (dep === "require") {
- reified[i] = mod.makeRequire();
- } else if (dep === "module") {
- mod.exports = rseen;
- module = reified[i] = mod;
- } else {
- reified[i] = requireFrom(resolve(dep, name), name);
- }
- }
-
- return {
- deps: reified,
- module: module,
- };
- }
-
- function requireFrom(name, origin) {
- name = transformForAliases(name);
-
- if (name === "discourse") {
- // eslint-disable-next-line no-console
- console.log(
- "discourse has been moved to `discourse/app` - please update your code"
- );
- name = "discourse/app";
- }
-
- if (name === "discourse/models/input-validation") {
- // eslint-disable-next-line no-console
- console.log(
- "input-validation has been removed and should be replaced with `@ember/object`"
- );
- name = "@ember/object";
- }
-
- var mod = JS_MODULES[name] || registry[name];
- if (!mod) {
- throw new Error(
- "Could not find module `" + name + "` imported from `" + origin + "`"
- );
- }
- return requirejs(name);
- }
-
- function missingModule(name) {
- throw new Error("Could not find module " + name);
- }
-
- function transformForAliases(name) {
- var alias = ALIASES[name];
- if (!alias) {
- var segment = name.split("/")[0];
- var prepend = ALIAS_PREPEND[segment];
- if (!prepend) {
- return name;
- }
- alias = prepend + name;
- }
- deprecatedModule(name, alias);
- return alias;
- }
-
- requirejs = require = function (name) {
- name = transformForAliases(name);
- if (JS_MODULES[name]) {
- return JS_MODULES[name];
- }
-
- var mod = registry[name];
-
- if (mod && mod.callback instanceof Alias) {
- mod = registry[mod.callback.name];
- }
-
- if (!mod) {
- missingModule(name);
- }
-
- if (mod.state !== FAILED && seen.hasOwnProperty(name)) {
- return seen[name];
- }
-
- var reified;
- var module;
- var loaded = false;
-
- seen[name] = {}; // placeholder for run-time cycles
-
- tryFinally(
- function () {
- reified = reify(mod, name, seen[name]);
- module = mod.callback.apply(this, reified.deps);
- loaded = true;
- },
- function () {
- if (!loaded) {
- mod.state = FAILED;
- }
- }
- );
-
- var obj;
- if (module === undefined && reified.module.exports) {
- obj = reified.module.exports;
- } else {
- obj = seen[name] = module;
- }
-
- if (
- obj !== null &&
- (typeof obj === "object" || typeof obj === "function") &&
- obj["default"] === undefined
- ) {
- obj["default"] = obj;
- }
-
- return (seen[name] = obj);
- };
- window.requireModule = requirejs;
-
- function resolve(child, name) {
- if (child.charAt(0) !== ".") {
- return child;
- }
-
- var parts = child.split("/");
- var nameParts = name.split("/");
- var parentBase = nameParts.slice(0, -1);
-
- for (var i = 0, l = parts.length; i < l; i++) {
- var part = parts[i];
-
- if (part === "..") {
- if (parentBase.length === 0) {
- throw new Error("Cannot access parent module of root");
- }
- parentBase.pop();
- } else if (part === ".") {
- continue;
- } else {
- parentBase.push(part);
- }
- }
-
- return parentBase.join("/");
- }
-
- requirejs.entries = requirejs._eak_seen = registry;
- requirejs.clear = function () {
- requirejs.entries = requirejs._eak_seen = registry = {};
- seen = {};
- };
-})();
diff --git a/app/assets/javascripts/discourse-shims.js b/app/assets/javascripts/discourse-shims.js
deleted file mode 100644
index 7794b099b8..0000000000
--- a/app/assets/javascripts/discourse-shims.js
+++ /dev/null
@@ -1,67 +0,0 @@
-define("message-bus-client", ["exports"], function (__exports__) {
- __exports__.default = window.MessageBus;
-});
-
-define("ember-buffered-proxy/proxy", ["exports"], function (__exports__) {
- __exports__.default = window.BufferedProxy;
-});
-
-define("bootbox", ["exports"], function (__exports__) {
- __exports__.default = window.bootbox;
-});
-
-define("xss", ["exports"], function (__exports__) {
- __exports__.default = window.filterXSS;
-});
-
-define("@discourse/itsatrap", ["exports"], function (__exports__) {
- __exports__.default = window.ItsATrap;
-});
-
-define("@popperjs/core", ["exports"], function (__exports__) {
- __exports__.default = window.Popper;
- __exports__.createPopper = window.Popper.createPopper;
- __exports__.defaultModifiers = window.Popper.defaultModifiers;
- __exports__.popperGenerator = window.Popper.popperGenerator;
-});
-
-define("tippy.js", ["exports"], function (__exports__) {
- __exports__.default = window.tippy;
-});
-
-define("@uppy/core", ["exports"], function (__exports__) {
- __exports__.default = window.Uppy.Core;
- __exports__.BasePlugin = window.Uppy.Core.BasePlugin;
-});
-
-define("@uppy/aws-s3", ["exports"], function (__exports__) {
- __exports__.default = window.Uppy.AwsS3;
-});
-
-define("@uppy/aws-s3-multipart", ["exports"], function (__exports__) {
- __exports__.default = window.Uppy.AwsS3Multipart;
-});
-
-define("@uppy/xhr-upload", ["exports"], function (__exports__) {
- __exports__.default = window.Uppy.XHRUpload;
-});
-
-define("@uppy/drop-target", ["exports"], function (__exports__) {
- __exports__.default = window.Uppy.DropTarget;
-});
-
-define("@uppy/utils/lib/delay", ["exports"], function (__exports__) {
- __exports__.default = window.Uppy.Utils.delay;
-});
-
-define("@uppy/utils/lib/EventTracker", ["exports"], function (__exports__) {
- __exports__.default = window.Uppy.Utils.EventTracker;
-});
-
-define("@uppy/utils/lib/AbortController", ["exports"], function (__exports__) {
- __exports__.AbortController =
- window.Uppy.Utils.AbortControllerLib.AbortController;
- __exports__.AbortSignal = window.Uppy.Utils.AbortControllerLib.AbortSignal;
- __exports__.createAbortError =
- window.Uppy.Utils.AbortControllerLib.createAbortError;
-});
diff --git a/app/assets/javascripts/discourse-widget-hbs/ember-cli-build.js b/app/assets/javascripts/discourse-widget-hbs/ember-cli-build.js
new file mode 100644
index 0000000000..2171aed7fd
--- /dev/null
+++ b/app/assets/javascripts/discourse-widget-hbs/ember-cli-build.js
@@ -0,0 +1,13 @@
+"use strict";
+
+const EmberAddon = require("ember-cli/lib/broccoli/ember-addon");
+
+module.exports = function (defaults) {
+ let app = new EmberAddon(defaults, {
+ autoImport: {
+ publicAssetURL: "",
+ },
+ });
+
+ return app.toTree();
+};
diff --git a/app/assets/javascripts/discourse-widget-hbs/package.json b/app/assets/javascripts/discourse-widget-hbs/package.json
index eb1fbdf001..d41b35867f 100644
--- a/app/assets/javascripts/discourse-widget-hbs/package.json
+++ b/app/assets/javascripts/discourse-widget-hbs/package.json
@@ -15,43 +15,34 @@
"start": "ember serve"
},
"dependencies": {
- "ember-auto-import": "^2.2.4",
- "ember-cli-babel": "^7.13.0",
- "ember-cli-htmlbars": "^4.2.0",
- "webpack": "^5.67.0"
+ "ember-auto-import": "^2.4.2",
+ "ember-cli-babel": "^7.23.1",
+ "ember-cli-htmlbars": "^6.0.1",
+ "handlebars": "^4.7.6",
+ "webpack": "^5.73.0"
},
"devDependencies": {
- "@ember/optional-features": "^1.1.0",
- "@glimmer/component": "^1.0.0",
- "babel-eslint": "^10.0.3",
+ "@ember/optional-features": "^2.0.0",
+ "@glimmer/component": "^1.1.2",
"broccoli-asset-rev": "^3.0.0",
- "broccoli-stew": "^3.0.0",
"ember-cli": "~3.25.3",
- "ember-cli-dependency-checker": "^3.2.0",
- "ember-cli-eslint": "^5.1.0",
- "ember-cli-inject-live-reload": "^2.0.1",
+ "ember-cli-dependency-checker": "^3.3.1",
+ "ember-cli-inject-live-reload": "^2.1.0",
"ember-cli-sri": "^2.1.1",
- "ember-cli-template-lint": "^1.0.0-beta.3",
- "ember-cli-uglify": "^3.0.0",
+ "ember-cli-terser": "^4.0.2",
"ember-disable-prototype-extensions": "^1.1.3",
- "ember-export-application-global": "^2.0.1",
"ember-load-initializers": "^2.1.1",
- "ember-maybe-import-regenerator": "^0.1.6",
"ember-resolver": "^7.0.0",
"ember-source": "~3.15.0",
- "ember-source-channel-url": "^2.0.1",
- "ember-try": "^2.0.0",
- "eslint-plugin-ember": "^7.7.1",
- "eslint-plugin-node": "^10.0.0",
- "handlebars": "^4.7.6",
+ "ember-source-channel-url": "^3.0.0",
"loader.js": "^4.7.0"
},
"engines": {
- "node": "12.* || 14.* || >= 16",
+ "node": "16.* || >= 18",
"npm": "please-use-yarn",
"yarn": ">= 1.21.1"
},
"ember": {
- "edition": "octane"
+ "edition": "default"
}
}
diff --git a/app/assets/javascripts/discourse-widget-hbs/tests/dummy/app/index.html b/app/assets/javascripts/discourse-widget-hbs/tests/dummy/app/index.html
new file mode 100644
index 0000000000..870306a6ba
--- /dev/null
+++ b/app/assets/javascripts/discourse-widget-hbs/tests/dummy/app/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+ Dummy
+
+
+
+ {{content-for "head"}}
+
+
+
+
+ {{content-for "head-footer"}}
+
+
+ {{content-for "body"}}
+
+
+
+
+ {{content-for "body-footer"}}
+
+
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse-widget-hbs/tests/dummy/app/styles/.gitkeep b/app/assets/javascripts/discourse-widget-hbs/tests/dummy/app/styles/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/discourse-widget-hbs/tests/dummy/app/templates/.gitkeep b/app/assets/javascripts/discourse-widget-hbs/tests/dummy/app/templates/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/discourse-widget-hbs/tests/dummy/config/.gitkeep b/app/assets/javascripts/discourse-widget-hbs/tests/dummy/config/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/discourse-widget-hbs/tests/dummy/public/.gitkeep b/app/assets/javascripts/discourse-widget-hbs/tests/dummy/public/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/discourse-widget-hbs/tests/index.html b/app/assets/javascripts/discourse-widget-hbs/tests/index.html
new file mode 100644
index 0000000000..5c291da716
--- /dev/null
+++ b/app/assets/javascripts/discourse-widget-hbs/tests/index.html
@@ -0,0 +1,39 @@
+
+
+
+
+ Dummy Tests
+
+
+
+ {{content-for "head"}}
+ {{content-for "test-head"}}
+
+
+
+
+
+ {{content-for "head-footer"}}
+ {{content-for "test-head-footer"}}
+
+
+ {{content-for "body"}}
+ {{content-for "test-body"}}
+
+
+
+
+
+
+
+
+
+
+ {{content-for "body-footer"}}
+ {{content-for "test-body-footer"}}
+
+
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/app/app.js b/app/assets/javascripts/discourse/app/app.js
index 19da3d2836..be305596c4 100644
--- a/app/assets/javascripts/discourse/app/app.js
+++ b/app/assets/javascripts/discourse/app/app.js
@@ -81,6 +81,15 @@ const Discourse = Application.extend({
initialize: () => withPluginApi(cb.version, cb.code),
});
});
+
+ window.addEventListener(
+ "load",
+ () => {
+ // The app booted. Remove the splash screen
+ document.querySelector("#d-splash")?.remove();
+ },
+ { once: true }
+ );
},
_registerPluginCode(version, code) {
diff --git a/app/assets/javascripts/discourse/app/components/create-account.js b/app/assets/javascripts/discourse/app/components/create-account.js
index a56b945bc0..695cbc1c33 100644
--- a/app/assets/javascripts/discourse/app/components/create-account.js
+++ b/app/assets/javascripts/discourse/app/components/create-account.js
@@ -1,5 +1,6 @@
import Component from "@ember/component";
import cookie from "discourse/lib/cookie";
+import { bind } from "discourse-common/utils/decorators";
export default Component.extend({
classNames: ["create-account-body"],
@@ -21,6 +22,7 @@ export default Component.extend({
}
},
+ @bind
actionOnEnter(event) {
if (!this.disabled && event.key === "Enter") {
event.preventDefault();
@@ -30,6 +32,7 @@ export default Component.extend({
}
},
+ @bind
selectKitFocus(event) {
const target = document.getElementById(event.target.getAttribute("for"));
if (target?.classList.contains("select-kit")) {
diff --git a/app/assets/javascripts/discourse/app/components/d-editor.js b/app/assets/javascripts/discourse/app/components/d-editor.js
index 37d570b268..e669a5685c 100644
--- a/app/assets/javascripts/discourse/app/components/d-editor.js
+++ b/app/assets/javascripts/discourse/app/components/d-editor.js
@@ -672,6 +672,13 @@ export default Component.extend(TextareaTextManipulation, {
return true;
},
+ @action
+ onEmojiPickerClose() {
+ if (!(this.isDestroyed || this.isDestroying)) {
+ this.set("emojiPickerIsActive", false);
+ }
+ },
+
actions: {
emoji() {
if (this.disabled) {
diff --git a/app/assets/javascripts/discourse/app/components/d-popover.js b/app/assets/javascripts/discourse/app/components/d-popover.js
index 1a3b0ea9b0..930f16348f 100644
--- a/app/assets/javascripts/discourse/app/components/d-popover.js
+++ b/app/assets/javascripts/discourse/app/components/d-popover.js
@@ -80,12 +80,18 @@ export default class DiscoursePopover extends Component {
},
};
+ const target = document
+ .getElementById(this.componentId)
+ .querySelector(
+ ':scope > .d-popover-trigger, :scope > .btn, :scope > [role="button"]'
+ );
+
+ if (!target) {
+ return null;
+ }
+
const instance = tippy(
- document
- .getElementById(this.componentId)
- .querySelector(
- ':scope > .d-popover-trigger, :scope > .btn, :scope > [role="button"]'
- ),
+ target,
Object.assign({}, baseOptions, this.options || {})
);
diff --git a/app/assets/javascripts/discourse/app/components/global-notice.js b/app/assets/javascripts/discourse/app/components/global-notice.js
index 7432143273..b2097becb6 100644
--- a/app/assets/javascripts/discourse/app/components/global-notice.js
+++ b/app/assets/javascripts/discourse/app/components/global-notice.js
@@ -50,6 +50,8 @@ const Notice = EmberObject.extend({
});
export default Component.extend({
+ tagName: "",
+ router: service(),
logsNoticeService: service("logsNotice"),
logNotice: null,
@@ -70,6 +72,10 @@ export default Component.extend({
);
},
+ get visible() {
+ return !this.router.currentRouteName.startsWith("wizard.");
+ },
+
@discourseComputed(
"site.isReadOnly",
"site.wizard_required",
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/categories-section.js b/app/assets/javascripts/discourse/app/components/sidebar/categories-section.js
index f1f4240650..fa96b5eb35 100644
--- a/app/assets/javascripts/discourse/app/components/sidebar/categories-section.js
+++ b/app/assets/javascripts/discourse/app/components/sidebar/categories-section.js
@@ -1,9 +1,15 @@
+import I18n from "I18n";
+
import { cached } from "@glimmer/tracking";
+import { inject as service } from "@ember/service";
+import { action } from "@ember/object";
import GlimmerComponent from "discourse/components/glimmer";
import CategorySectionLink from "discourse/lib/sidebar/categories-section/category-section-link";
export default class SidebarCategoriesSection extends GlimmerComponent {
+ @service router;
+
constructor() {
super(...arguments);
@@ -20,11 +26,32 @@ export default class SidebarCategoriesSection extends GlimmerComponent {
@cached
get sectionLinks() {
- return this.site.trackedCategoriesList.map((trackedCategory) => {
- return new CategorySectionLink({
- category: trackedCategory,
- topicTrackingState: this.topicTrackingState,
- });
- });
+ const links = [];
+
+ for (const category of this.currentUser.sidebarCategories) {
+ links.push(
+ new CategorySectionLink({
+ category,
+ topicTrackingState: this.topicTrackingState,
+ })
+ );
+ }
+
+ return links;
+ }
+
+ get noCategoriesText() {
+ const url = `/u/${this.currentUser.username}/preferences/sidebar`;
+
+ return `${I18n.t(
+ "sidebar.sections.categories.none"
+ )} ${I18n.t(
+ "sidebar.sections.categories.click_to_get_started"
+ )}`;
+ }
+
+ @action
+ editTracked() {
+ this.router.transitionTo("preferences.sidebar", this.currentUser);
}
}
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js b/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js
new file mode 100644
index 0000000000..7c2ff92c3a
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/sidebar/messages-section.js
@@ -0,0 +1,142 @@
+import { cached } from "@glimmer/tracking";
+
+import { getOwner } from "discourse-common/lib/get-owner";
+import GlimmerComponent from "discourse/components/glimmer";
+import { bind } from "discourse-common/utils/decorators";
+import GroupMessageSectionLink from "discourse/lib/sidebar/messages-section/group-message-section-link";
+import PersonalMessageSectionLink from "discourse/lib/sidebar/messages-section/personal-message-section-link";
+
+export const INBOX = "inbox";
+export const UNREAD = "unread";
+const SENT = "sent";
+export const NEW = "new";
+const ARCHIVE = "archive";
+
+export const PERSONAL_MESSAGES_INBOX_FILTERS = [
+ INBOX,
+ NEW,
+ UNREAD,
+ SENT,
+ ARCHIVE,
+];
+
+export const GROUP_MESSAGES_INBOX_FILTERS = [INBOX, NEW, UNREAD, ARCHIVE];
+
+export default class SidebarMessagesSection extends GlimmerComponent {
+ constructor() {
+ super(...arguments);
+
+ this.appEvents.on(
+ "page:changed",
+ this,
+ this._refreshSectionLinksDisplayState
+ );
+
+ this.pmTopicTrackingState
+ .startTracking()
+ .then(this._refreshSectionLinkCounts);
+
+ this._pmTopicTrackingStateKey = "messages-section";
+
+ this.pmTopicTrackingState.onStateChange(
+ this._pmTopicTrackingStateKey,
+ this._refreshSectionLinkCounts
+ );
+ }
+
+ @bind
+ _refreshSectionLinkCounts() {
+ for (const sectionLink of this.allSectionLinks) {
+ sectionLink.refreshCount();
+ }
+ }
+
+ willDestroy() {
+ this.appEvents.off(
+ "page:changed",
+ this,
+ this._refreshSectionLinksDisplayState
+ );
+
+ this.pmTopicTrackingState.offStateChange(
+ this._pmTopicTrackingStateKey,
+ this._refreshSectionLinkCounts
+ );
+ }
+
+ _refreshSectionLinksDisplayState({
+ currentRouteName,
+ currentRouteParentName,
+ currentRouteParams,
+ }) {
+ if (
+ currentRouteParentName !== "userPrivateMessages" &&
+ currentRouteParentName !== "topic"
+ ) {
+ for (const sectionLink of this.allSectionLinks) {
+ sectionLink.collapse();
+ }
+ } else {
+ const attrs = {
+ currentRouteName,
+ currentRouteParams,
+ };
+
+ if (currentRouteParentName === "topic") {
+ const topicController = getOwner(this).lookup("controller:topic");
+
+ if (topicController.model.isPrivateMessage) {
+ attrs.privateMessageTopic = topicController.model;
+ }
+ }
+
+ for (const sectionLink of this.allSectionLinks) {
+ sectionLink.pageChanged(attrs);
+ }
+ }
+ }
+
+ @cached
+ get personalMessagesSectionLinks() {
+ const links = [];
+
+ PERSONAL_MESSAGES_INBOX_FILTERS.forEach((type) => {
+ links.push(
+ new PersonalMessageSectionLink({
+ currentUser: this.currentUser,
+ type,
+ pmTopicTrackingState: this.pmTopicTrackingState,
+ })
+ );
+ });
+
+ return links;
+ }
+
+ @cached
+ get groupMessagesSectionLinks() {
+ const links = [];
+
+ this.currentUser.groupsWithMessages.forEach((group) => {
+ GROUP_MESSAGES_INBOX_FILTERS.forEach((groupMessageLink) => {
+ links.push(
+ new GroupMessageSectionLink({
+ group,
+ type: groupMessageLink,
+ currentUser: this.currentUser,
+ pmTopicTrackingState: this.pmTopicTrackingState,
+ })
+ );
+ });
+ });
+
+ return links;
+ }
+
+ get allSectionLinks() {
+ return [
+ ...this.groupMessagesSectionLinks,
+ ...this.personalMessagesSectionLinks,
+ ];
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/section.js b/app/assets/javascripts/discourse/app/components/sidebar/section.js
index 59e16c49d0..47cc438546 100644
--- a/app/assets/javascripts/discourse/app/components/sidebar/section.js
+++ b/app/assets/javascripts/discourse/app/components/sidebar/section.js
@@ -4,11 +4,27 @@ import { action } from "@ember/object";
import { tracked } from "@glimmer/tracking";
export default class SidebarSection extends GlimmerComponent {
- @tracked displaySection = true;
+ @tracked displaySection;
+ collapsedSidebarSectionKey = `sidebar-section-${this.args.sectionName}-collapsed`;
+
+ constructor() {
+ super(...arguments);
+
+ this.displaySection =
+ this.keyValueStore.getItem(this.collapsedSidebarSectionKey) === undefined
+ ? true
+ : false;
+ }
@action
toggleSectionDisplay() {
this.displaySection = !this.displaySection;
+
+ if (this.displaySection) {
+ this.keyValueStore.remove(this.collapsedSidebarSectionKey);
+ } else {
+ this.keyValueStore.setItem(this.collapsedSidebarSectionKey, true);
+ }
}
get headerCaretIcon() {
diff --git a/app/assets/javascripts/discourse/app/components/sidebar/tags-section.js b/app/assets/javascripts/discourse/app/components/sidebar/tags-section.js
index d5f9ba748f..47a845ceb2 100644
--- a/app/assets/javascripts/discourse/app/components/sidebar/tags-section.js
+++ b/app/assets/javascripts/discourse/app/components/sidebar/tags-section.js
@@ -1,9 +1,15 @@
+import I18n from "I18n";
+
import { cached } from "@glimmer/tracking";
+import { inject as service } from "@ember/service";
+import { action } from "@ember/object";
import GlimmerComponent from "discourse/components/glimmer";
import TagSectionLink from "discourse/lib/sidebar/tags-section/tag-section-link";
export default class SidebarTagsSection extends GlimmerComponent {
+ @service router;
+
constructor() {
super(...arguments);
@@ -20,11 +26,30 @@ export default class SidebarTagsSection extends GlimmerComponent {
@cached
get sectionLinks() {
- return this.currentUser.trackedTags.map((trackedTag) => {
- return new TagSectionLink({
- tag: trackedTag,
- topicTrackingState: this.topicTrackingState,
- });
- });
+ const links = [];
+
+ for (const tagName of this.currentUser.sidebarTagNames) {
+ links.push(
+ new TagSectionLink({
+ tagName,
+ topicTrackingState: this.topicTrackingState,
+ })
+ );
+ }
+
+ return links;
+ }
+
+ get noTagsText() {
+ const url = `/u/${this.currentUser.username}/preferences/sidebar`;
+
+ return `${I18n.t("sidebar.sections.tags.none")} ${I18n.t(
+ "sidebar.sections.tags.click_to_get_started"
+ )}`;
+ }
+
+ @action
+ editTracked() {
+ this.router.transitionTo("preferences.sidebar", this.currentUser);
}
}
diff --git a/app/assets/javascripts/discourse/app/components/user-card-contents.js b/app/assets/javascripts/discourse/app/components/user-card-contents.js
index d755eaf5d1..51d653ddcd 100644
--- a/app/assets/javascripts/discourse/app/components/user-card-contents.js
+++ b/app/assets/javascripts/discourse/app/components/user-card-contents.js
@@ -14,6 +14,7 @@ import { isEmpty } from "@ember/utils";
import { prioritizeNameInUx } from "discourse/lib/settings";
import { dasherize } from "@ember/string";
import { emojiUnescape } from "discourse/lib/text";
+import { escapeExpression } from "discourse/lib/utilities";
export default Component.extend(CardContentsBase, CanCheckEmails, CleansUp, {
elementId: "user-card",
@@ -55,10 +56,9 @@ export default Component.extend(CardContentsBase, CanCheckEmails, CleansUp, {
return this.siteSettings.enable_user_status && this.user.status;
},
- @discourseComputed("user.status")
- userStatusEmoji() {
- const emoji = this.user.status.emoji ?? "mega";
- return emojiUnescape(`:${emoji}:`);
+ @discourseComputed("user.status.emoji")
+ userStatusEmoji(emoji) {
+ return emojiUnescape(escapeExpression(`:${emoji}:`));
},
isSuspendedOrHasBio: or("user.suspend_reason", "user.bio_excerpt"),
diff --git a/app/assets/javascripts/discourse/app/components/user-status-picker.js b/app/assets/javascripts/discourse/app/components/user-status-picker.js
new file mode 100644
index 0000000000..691a4e683c
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/user-status-picker.js
@@ -0,0 +1,57 @@
+import Component from "@ember/component";
+import { action, computed } from "@ember/object";
+import { scheduleOnce } from "@ember/runloop";
+import { emojiUnescape } from "discourse/lib/text";
+import { escapeExpression } from "discourse/lib/utilities";
+
+export default class UserStatusPicker extends Component {
+ tagName = "";
+ isFocused = false;
+ emojiPickerIsActive = false;
+ emoji = null;
+ description = null;
+
+ @computed("emoji")
+ get emojiHtml() {
+ const emoji = escapeExpression(`:${this.emoji}:`);
+ return emojiUnescape(emoji);
+ }
+
+ @action
+ blur() {
+ this.set("isFocused", false);
+ }
+
+ @action
+ emojiSelected(emoji) {
+ this.set("emoji", emoji);
+ this.set("emojiPickerIsActive", false);
+
+ scheduleOnce("afterRender", () => {
+ document.querySelector(".btn-emoji")?.focus();
+ });
+ }
+
+ @action
+ focus() {
+ this.set("isFocused", true);
+ }
+
+ @action
+ onEmojiPickerOutsideClick() {
+ this.set("emojiPickerIsActive", false);
+ }
+
+ @action
+ setDefaultEmoji() {
+ if (!this.emoji) {
+ this.set("emoji", "speech_balloon");
+ }
+ }
+
+ @action
+ toggleEmojiPicker(event) {
+ event.stopPropagation();
+ this.set("emojiPickerIsActive", !this.emojiPickerIsActive);
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/controllers/application.js b/app/assets/javascripts/discourse/app/controllers/application.js
index c07ca32adc..5dcdab3097 100644
--- a/app/assets/javascripts/discourse/app/controllers/application.js
+++ b/app/assets/javascripts/discourse/app/controllers/application.js
@@ -8,11 +8,6 @@ export default Controller.extend({
router: service(),
showSidebar: true,
- @discourseComputed("showSidebar", "currentUser.experimental_sidebar_enabled")
- mainOutletWrapperClasses(showSidebar, experimentalSidebarEnabled) {
- return showSidebar && experimentalSidebarEnabled ? "has-sidebar" : "";
- },
-
@discourseComputed
canSignUp() {
return (
diff --git a/app/assets/javascripts/discourse/app/controllers/composer.js b/app/assets/javascripts/discourse/app/controllers/composer.js
index 37706589b9..e0fbdbf396 100644
--- a/app/assets/javascripts/discourse/app/controllers/composer.js
+++ b/app/assets/javascripts/discourse/app/controllers/composer.js
@@ -136,7 +136,7 @@ export default Controller.extend({
},
set disableSubmit(value) {
- this.set("_disableSubmit", value);
+ return this.set("_disableSubmit", value);
},
@discourseComputed("showPreview")
@@ -233,6 +233,7 @@ export default Controller.extend({
},
isStaffUser: reads("currentUser.staff"),
+ whisperer: reads("currentUser.whisperer"),
canUnlistTopic: and("model.creatingTopic", "isStaffUser"),
@@ -289,12 +290,12 @@ export default Controller.extend({
return SAVE_LABELS[modelAction];
},
- @discourseComputed("isStaffUser", "model.action")
- canWhisper(isStaffUser, modelAction) {
+ @discourseComputed("whisperer", "model.action")
+ canWhisper(whisperer, modelAction) {
return (
this.siteSettings.enable_whispers &&
- isStaffUser &&
- Composer.REPLY === modelAction
+ Composer.REPLY === modelAction &&
+ whisperer
);
},
diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/sidebar.js b/app/assets/javascripts/discourse/app/controllers/preferences/sidebar.js
new file mode 100644
index 0000000000..10358abf31
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/controllers/preferences/sidebar.js
@@ -0,0 +1,41 @@
+import Controller from "@ember/controller";
+import { action } from "@ember/object";
+import { tracked } from "@glimmer/tracking";
+
+import { popupAjaxError } from "discourse/lib/ajax-error";
+
+export default class extends Controller {
+ @tracked saved = false;
+ @tracked selectedSiderbarCategories = [];
+ @tracked selectedSidebarTagNames = [];
+
+ @action
+ tagUpdated(tagNames) {
+ this.selectedSidebarTagNames = tagNames;
+ this.model.set("sidebar_tag_names", tagNames);
+ this.saved = false;
+ }
+
+ @action
+ categoryUpdated(categories) {
+ this.selectedSiderbarCategories = categories;
+ this.model.set("sidebarCategoryIds", categories.mapBy("id"));
+ this.saved = false;
+ }
+
+ @action
+ save() {
+ this.model
+ .save()
+ .then(() => {
+ this.saved = true;
+ this.initialSidebarCategoryIds = this.model.sidebarCategoryIds;
+ this.initialSidebarTagNames = this.model.initialSidebarTagNames;
+ })
+ .catch((error) => {
+ this.model.set("sidebarCategoryIds", this.initialSidebarCategoryIds);
+ this.model.set("sidebar_tag_names", this.initialSidebarTagNames);
+ popupAjaxError(error);
+ });
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/controllers/tag-show.js b/app/assets/javascripts/discourse/app/controllers/tag-show.js
index c3339debfc..e0faae1e25 100644
--- a/app/assets/javascripts/discourse/app/controllers/tag-show.js
+++ b/app/assets/javascripts/discourse/app/controllers/tag-show.js
@@ -181,14 +181,15 @@ export default DiscoverySortableController.extend(
this.tagNotification
.update({ notification_level: notificationLevel })
.then((response) => {
- this.currentUser.set(
- "muted_tag_ids",
- this.currentUser.calculateMutedIds(
- notificationLevel,
- response.responseJson.tag_id,
- "muted_tag_ids"
- )
- );
+ const payload = response.responseJson;
+
+ this.currentUser.setProperties({
+ watched_tags: payload.watched_tags,
+ watching_first_post_tags: payload.watching_first_post_tags,
+ tracked_tags: payload.tracked_tags,
+ muted_tags: payload.muted_tags,
+ regular_tags: payload.regular_tags,
+ });
});
},
}
diff --git a/app/assets/javascripts/discourse/app/controllers/topic.js b/app/assets/javascripts/discourse/app/controllers/topic.js
index 3832df9372..7d4321d6c6 100644
--- a/app/assets/javascripts/discourse/app/controllers/topic.js
+++ b/app/assets/javascripts/discourse/app/controllers/topic.js
@@ -890,7 +890,8 @@ export default Controller.extend(bufferedProperty("model"), {
selectReplies(post) {
ajax(`/posts/${post.id}/reply-ids.json`).then((replies) => {
const replyIds = replies.map((r) => r.id);
- this.selectedPostIds.pushObjects([post.id, ...replyIds]);
+ const postIds = [...this.selectedPostIds, post.id, ...replyIds];
+ this.set("selectedPostIds", [...new Set(postIds)]);
this._forceRefreshPostStream();
});
},
@@ -1699,6 +1700,30 @@ export default Controller.extend(bufferedProperty("model"), {
topic.set("message_archived", true);
break;
}
+ case "stats": {
+ let updateStream = false;
+ ["last_posted_at", "like_count", "posts_count"].forEach(
+ (property) => {
+ const value = data[property];
+ if (typeof value !== "undefined") {
+ topic.set(property, value);
+ updateStream = true;
+ }
+ }
+ );
+
+ if (data["last_poster"]) {
+ topic.details.set("last_poster", data["last_poster"]);
+ updateStream = true;
+ }
+
+ if (updateStream) {
+ postStream
+ .triggerChangedTopicStats()
+ .then((firstPostId) => refresh({ id: firstPostId }));
+ }
+ break;
+ }
default: {
let callback = customPostMessageCallbacks[data.type];
if (callback) {
diff --git a/app/assets/javascripts/discourse/app/controllers/user-status.js b/app/assets/javascripts/discourse/app/controllers/user-status.js
index b43309e9d1..20f025d379 100644
--- a/app/assets/javascripts/discourse/app/controllers/user-status.js
+++ b/app/assets/javascripts/discourse/app/controllers/user-status.js
@@ -1,49 +1,49 @@
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { action } from "@ember/object";
-import { notEmpty } from "@ember/object/computed";
import { inject as service } from "@ember/service";
import { popupAjaxError } from "discourse/lib/ajax-error";
import bootbox from "bootbox";
+import discourseComputed from "discourse-common/utils/decorators";
export default Controller.extend(ModalFunctionality, {
userStatusService: service("user-status"),
+ emoji: null,
description: null,
- statusIsSet: notEmpty("description"),
showDeleteButton: false,
onShow() {
- if (this.currentUser.status) {
- this.setProperties({
- description: this.currentUser.status.description,
- showDeleteButton: true,
- });
- }
+ const status = this.currentUser.status;
+ this.setProperties({
+ emoji: status?.emoji,
+ description: status?.description,
+ showDeleteButton: !!status,
+ });
+ },
+
+ @discourseComputed("emoji", "description")
+ statusIsSet(emoji, description) {
+ return !!emoji && !!description;
},
@action
delete() {
this.userStatusService
.clear()
- .then(() => {
- this._resetModal();
- this.send("closeModal");
- })
+ .then(() => this.send("closeModal"))
.catch((e) => this._handleError(e));
},
@action
saveAndClose() {
- if (this.description) {
- const status = { description: this.description };
- this.userStatusService
- .set(status)
- .then(() => {
- this.send("closeModal");
- })
- .catch((e) => this._handleError(e));
- }
+ const status = { description: this.description, emoji: this.emoji };
+ this.userStatusService
+ .set(status)
+ .then(() => {
+ this.send("closeModal");
+ })
+ .catch((e) => this._handleError(e));
},
_handleError(e) {
@@ -53,9 +53,4 @@ export default Controller.extend(ModalFunctionality, {
popupAjaxError(e);
}
},
-
- _resetModal() {
- this.set("description", null);
- this.set("showDeleteButton", false);
- },
});
diff --git a/app/assets/javascripts/discourse/app/helpers/category-badge.js b/app/assets/javascripts/discourse/app/helpers/category-badge.js
index 887e5fcf97..b9e51674c9 100644
--- a/app/assets/javascripts/discourse/app/helpers/category-badge.js
+++ b/app/assets/javascripts/discourse/app/helpers/category-badge.js
@@ -1,11 +1,12 @@
import { categoryLinkHTML } from "discourse/helpers/category-link";
import { registerUnbound } from "discourse-common/lib/helpers";
+import { isPresent } from "@ember/utils";
registerUnbound("category-badge", function (cat, options) {
return categoryLinkHTML(cat, {
hideParent: options.hideParent,
allowUncategorized: options.allowUncategorized,
categoryStyle: options.categoryStyle,
- link: false,
+ link: isPresent(options.link) ? options.link : false,
});
});
diff --git a/app/assets/javascripts/discourse/app/helpers/route-action.js b/app/assets/javascripts/discourse/app/helpers/route-action.js
index c55052a6dd..d9dcad74e3 100644
--- a/app/assets/javascripts/discourse/app/helpers/route-action.js
+++ b/app/assets/javascripts/discourse/app/helpers/route-action.js
@@ -2,7 +2,7 @@ import { A } from "@ember/array";
import Helper from "@ember/component/helper";
import { computed, get } from "@ember/object";
import { getOwner } from "@ember/application";
-import { run } from "@ember/runloop";
+import { join } from "@ember/runloop";
import { assert, runInDebug } from "@ember/debug";
function getCurrentRouteInfos(router) {
@@ -40,7 +40,7 @@ export function routeAction(actionName, router, ...params) {
return function (...invocationArgs) {
let { action, handler } = getRouteWithAction(router, actionName);
let args = params.concat(invocationArgs);
- return run.join(handler, action, ...args);
+ return join(handler, action, ...args);
};
}
diff --git a/app/assets/javascripts/discourse/app/index.html b/app/assets/javascripts/discourse/app/index.html
index ba049ba54c..ad51651ba8 100644
--- a/app/assets/javascripts/discourse/app/index.html
+++ b/app/assets/javascripts/discourse/app/index.html
@@ -11,13 +11,26 @@
{{content-for "before-script-load"}}
-
-
+
+
+
+
{{content-for "head"}}
+
+
+
+ {{content-for "discourse-stylesheets"}}
+
+
+
+
+
+
+
{{content-for "body"}}
@@ -25,9 +38,8 @@
-
-
+
{{content-for "body-footer"}}
diff --git a/app/assets/javascripts/discourse/app/initializers/inject-objects.js b/app/assets/javascripts/discourse/app/initializers/inject-objects.js
index 6f3c229da6..18fc431d3f 100644
--- a/app/assets/javascripts/discourse/app/initializers/inject-objects.js
+++ b/app/assets/javascripts/discourse/app/initializers/inject-objects.js
@@ -1,54 +1,50 @@
import { setDefaultOwner } from "discourse-common/lib/get-owner";
-import { isLegacyEmber } from "discourse-common/config/environment";
import User from "discourse/models/user";
import Site from "discourse/models/site";
import deprecated from "discourse-common/lib/deprecated";
export default {
name: "inject-objects",
- after: isLegacyEmber() ? null : "export-application-global",
+ after: "export-application-global",
initialize(container, app) {
// This is required for Ember CLI tests to work
setDefaultOwner(app.__container__);
- // Backwards compatibility for Discourse.SiteSettings and Discourse.User
- if (!isLegacyEmber()) {
- Object.defineProperty(app, "SiteSettings", {
- get() {
- deprecated(
- `use injected siteSettings instead of Discourse.SiteSettings`,
- {
- since: "2.8",
- dropFrom: "2.9",
- }
- );
- return container.lookup("site-settings:main");
- },
- });
- Object.defineProperty(app, "User", {
- get() {
- deprecated(
- `import discourse/models/user instead of using Discourse.User`,
- {
- since: "2.8",
- dropFrom: "2.9",
- }
- );
- return User;
- },
- });
- Object.defineProperty(app, "Site", {
- get() {
- deprecated(
- `import discourse/models/site instead of using Discourse.Site`,
- {
- since: "2.8",
- dropFrom: "2.9",
- }
- );
- return Site;
- },
- });
- }
+ Object.defineProperty(app, "SiteSettings", {
+ get() {
+ deprecated(
+ `use injected siteSettings instead of Discourse.SiteSettings`,
+ {
+ since: "2.8",
+ dropFrom: "2.9",
+ }
+ );
+ return container.lookup("site-settings:main");
+ },
+ });
+ Object.defineProperty(app, "User", {
+ get() {
+ deprecated(
+ `import discourse/models/user instead of using Discourse.User`,
+ {
+ since: "2.8",
+ dropFrom: "2.9",
+ }
+ );
+ return User;
+ },
+ });
+ Object.defineProperty(app, "Site", {
+ get() {
+ deprecated(
+ `import discourse/models/site instead of using Discourse.Site`,
+ {
+ since: "2.8",
+ dropFrom: "2.9",
+ }
+ );
+ return Site;
+ },
+ });
},
};
diff --git a/app/assets/javascripts/discourse/app/initializers/subscribe-user-notifications.js b/app/assets/javascripts/discourse/app/initializers/subscribe-user-notifications.js
index 373327f532..bf4235ba85 100644
--- a/app/assets/javascripts/discourse/app/initializers/subscribe-user-notifications.js
+++ b/app/assets/javascripts/discourse/app/initializers/subscribe-user-notifications.js
@@ -108,19 +108,17 @@ export default {
user.notification_channel_position
);
- bus.subscribe(`/user-updates/${user.id}`, (data) => {
- switch (data.type) {
- case "drafts":
- user.updateDraftProperties(data.payload);
- break;
- case "do_not_disturb":
- user.updateDoNotDisturbStatus(data.payload.ends_at);
- break;
- case "user_status":
- user.set("status", data.payload);
- appEvents.trigger("user-status:changed");
- break;
- }
+ bus.subscribe(`/user-drafts/${user.id}`, (data) => {
+ user.updateDraftProperties(data);
+ });
+
+ bus.subscribe(`/do-not-disturb/${user.get("id")}`, (data) => {
+ user.updateDoNotDisturbStatus(data.ends_at);
+ });
+
+ bus.subscribe(`/user-status/${user.id}`, (data) => {
+ user.set("status", data);
+ appEvents.trigger("user-status:changed");
});
const site = container.lookup("site:main");
diff --git a/app/assets/javascripts/discourse/app/lib/d-popover.js b/app/assets/javascripts/discourse/app/lib/d-popover.js
index 052ae5c201..f5d5e131b5 100644
--- a/app/assets/javascripts/discourse/app/lib/d-popover.js
+++ b/app/assets/javascripts/discourse/app/lib/d-popover.js
@@ -1,5 +1,3 @@
-import { isLegacyEmber } from "discourse-common/config/environment";
-import { run } from "@ember/runloop";
import tippy from "tippy.js";
import { iconHTML } from "discourse-common/lib/icon-library";
@@ -39,14 +37,6 @@ export function showPopover(event, options = {}) {
? event.target._tippy
: setup(event.target, options);
- // hangs on legacy ember
- if (!isLegacyEmber) {
- run.begin();
- instance.popper.addEventListener("transitionend", run.end, {
- once: true,
- });
- }
-
if (instance.state.isShown) {
instance.hide();
} else {
@@ -72,8 +62,7 @@ export default function setup(target, options) {
options
);
- // legacy support
- delete tippyOptions.textContent;
+ // legacy support delete tippyOptions.textContent;
delete tippyOptions.htmlContent;
return tippy(target, tippyOptions);
diff --git a/app/assets/javascripts/discourse/app/lib/load-script.js b/app/assets/javascripts/discourse/app/lib/load-script.js
index 2eb5a52c09..5acbbceac4 100644
--- a/app/assets/javascripts/discourse/app/lib/load-script.js
+++ b/app/assets/javascripts/discourse/app/lib/load-script.js
@@ -119,7 +119,7 @@ export function cacheBuster(url) {
if (PUBLIC_JS_VERSIONS) {
let [folder, ...lib] = url.split("/").filter(Boolean);
if (folder === "javascripts") {
- lib = lib.join("/");
+ lib = lib.join("/").toLowerCase();
const versionedPath = PUBLIC_JS_VERSIONS[lib];
if (versionedPath) {
return `/javascripts/${versionedPath}`;
diff --git a/app/assets/javascripts/discourse/app/lib/notification-levels.js b/app/assets/javascripts/discourse/app/lib/notification-levels.js
index 8bd3efaaa2..c3423f5ac2 100644
--- a/app/assets/javascripts/discourse/app/lib/notification-levels.js
+++ b/app/assets/javascripts/discourse/app/lib/notification-levels.js
@@ -1,5 +1,6 @@
const MUTED = 0;
const REGULAR = 1;
+const NORMAL = 1; // alias for REGULAR
const TRACKING = 2;
const WATCHING = 3;
const WATCHING_FIRST_POST = 4;
@@ -9,6 +10,7 @@ export const NotificationLevels = {
WATCHING,
TRACKING,
REGULAR,
+ NORMAL,
MUTED,
};
diff --git a/app/assets/javascripts/discourse/app/lib/page-tracker.js b/app/assets/javascripts/discourse/app/lib/page-tracker.js
index 3cb60f36ff..66316654cf 100644
--- a/app/assets/javascripts/discourse/app/lib/page-tracker.js
+++ b/app/assets/javascripts/discourse/app/lib/page-tracker.js
@@ -42,6 +42,8 @@ export function startPageTracking(router, appEvents, documentTitle) {
url,
title: documentTitle.getTitle(),
currentRouteName: router.currentRouteName,
+ currentRouteParams: router.currentRoute.params,
+ currentRouteParentName: router.currentRoute.parent?.name,
replacedOnlyQueryParams,
});
});
diff --git a/app/assets/javascripts/discourse/app/lib/public-js-versions.js b/app/assets/javascripts/discourse/app/lib/public-js-versions.js
index 393052736c..a8ced62efa 100644
--- a/app/assets/javascripts/discourse/app/lib/public-js-versions.js
+++ b/app/assets/javascripts/discourse/app/lib/public-js-versions.js
@@ -4,7 +4,7 @@
export const PUBLIC_JS_VERSIONS = {
"ace/ace.js": "ace.js/1.4.13/ace.js",
"jsoneditor.js": "@json-editor/json-editor/2.6.1/jsoneditor.js",
- "Chart.min.js": "chart.js/3.5.1/Chart.min.js",
+ "chart.min.js": "chart.js/3.5.1/chart.min.js",
"chartjs-plugin-datalabels.min.js":
"chartjs-plugin-datalabels/2.0.0/chartjs-plugin-datalabels.min.js",
"diffhtml.min.js": "diffhtml/1.0.0-beta.20/diffhtml.min.js",
diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/group-message-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/group-message-section-link.js
new file mode 100644
index 0000000000..ce6ec8bc7d
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/group-message-section-link.js
@@ -0,0 +1,70 @@
+import I18n from "I18n";
+
+import { capitalize } from "@ember/string";
+import MessageSectionLink from "discourse/lib/sidebar/messages-section/message-section-link";
+
+export default class GroupMessageSectionLink extends MessageSectionLink {
+ routeNames = new Set([
+ "userPrivateMessages.group",
+ "userPrivateMessages.groupUnread",
+ "userPrivateMessages.groupNew",
+ "userPrivateMessages.groupArchive",
+ ]);
+
+ get name() {
+ return `group-messages-${this.type}`;
+ }
+
+ get class() {
+ return this.group.name;
+ }
+
+ get route() {
+ if (this._isInbox) {
+ return "userPrivateMessages.group";
+ } else {
+ return `userPrivateMessages.group${capitalize(this.type)}`;
+ }
+ }
+
+ get currentWhen() {
+ if (this._isInbox) {
+ return [...this.routeNames].join(" ");
+ }
+ }
+
+ get models() {
+ return [this.currentUser, this.group.name];
+ }
+
+ get text() {
+ if (this._isInbox) {
+ return this.group.name;
+ } else if (this.count > 0) {
+ return I18n.t(`sidebar.sections.messages.links.${this.type}_with_count`, {
+ count: this.count,
+ });
+ } else {
+ return I18n.t(`sidebar.sections.messages.links.${this.type}`);
+ }
+ }
+
+ pageChanged({ currentRouteName, currentRouteParams, privateMessageTopic }) {
+ if (this._isInbox) {
+ return;
+ }
+
+ if (
+ privateMessageTopic?.allowedGroups?.some(
+ (g) => g.name === this.group.name
+ )
+ ) {
+ this.setDisplayState = true;
+ return;
+ }
+
+ this.setDisplayState =
+ this.routeNames.has(currentRouteName) &&
+ currentRouteParams.name.toLowerCase() === this.group.name.toLowerCase();
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/message-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/message-section-link.js
new file mode 100644
index 0000000000..bef0aef095
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/message-section-link.js
@@ -0,0 +1,74 @@
+import { tracked } from "@glimmer/tracking";
+
+import {
+ INBOX,
+ NEW,
+ UNREAD,
+} from "discourse/components/sidebar/messages-section";
+
+export default class MessageSectionLink {
+ @tracked shouldDisplay = this._isInbox;
+ @tracked count = 0;
+
+ constructor({ group, currentUser, type, pmTopicTrackingState }) {
+ this.group = group;
+ this.currentUser = currentUser;
+ this.type = type;
+ this.pmTopicTrackingState = pmTopicTrackingState;
+ }
+
+ refreshCount() {
+ this._refreshCount();
+ }
+
+ _refreshCount() {
+ if (this.shouldDisplay && this._shouldTrack) {
+ this.count = this.pmTopicTrackingState.lookupCount(this.type, {
+ inboxFilter: this.group ? "group" : "user",
+ groupName: this.group?.name,
+ });
+ }
+ }
+
+ set setDisplayState(value) {
+ const changed = this.shouldDisplay !== value;
+ this.shouldDisplay = value;
+
+ if (changed) {
+ this._refreshCount();
+ }
+ }
+
+ get inboxFilter() {
+ throw "not implemented";
+ }
+
+ expand() {
+ if (this._isInbox) {
+ return;
+ }
+
+ this.setDisplayState = true;
+ }
+
+ collapse() {
+ if (this._isInbox) {
+ return;
+ }
+
+ this.setDisplayState = false;
+ }
+
+ // eslint-disable-next-line no-unused-vars
+ pageChanged({ currentRouteName, currentRouteParams, privateMessageTopic }) {
+ throw "not implemented";
+ }
+
+ get _isInbox() {
+ return this.type === INBOX;
+ }
+
+ get _shouldTrack() {
+ return this.type === NEW || this.type === UNREAD;
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/personal-message-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/personal-message-section-link.js
new file mode 100644
index 0000000000..bbf813f352
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/lib/sidebar/messages-section/personal-message-section-link.js
@@ -0,0 +1,62 @@
+import I18n from "I18n";
+
+import MessageSectionLink from "discourse/lib/sidebar/messages-section/message-section-link";
+
+export default class PersonalMessageSectionLink extends MessageSectionLink {
+ routeNames = new Set([
+ "userPrivateMessages.index",
+ "userPrivateMessages.unread",
+ "userPrivateMessages.sent",
+ "userPrivateMessages.new",
+ "userPrivateMessages.archive",
+ ]);
+
+ get name() {
+ return `personal-messages-${this.type}`;
+ }
+
+ get class() {
+ return `personal-messages`;
+ }
+
+ get route() {
+ if (this._isInbox) {
+ return "userPrivateMessages.index";
+ } else {
+ return `userPrivateMessages.${this.type}`;
+ }
+ }
+
+ get currentWhen() {
+ if (this._isInbox) {
+ return [...this.routeNames].join(" ");
+ }
+ }
+
+ get model() {
+ return this.currentUser;
+ }
+
+ get text() {
+ if (this.count > 0) {
+ return I18n.t(`sidebar.sections.messages.links.${this.type}_with_count`, {
+ count: this.count,
+ });
+ } else {
+ return I18n.t(`sidebar.sections.messages.links.${this.type}`);
+ }
+ }
+
+ pageChanged({ currentRouteName, privateMessageTopic }) {
+ if (this._isInbox) {
+ return;
+ }
+
+ if (privateMessageTopic?.allowedGroups?.length === 0) {
+ this.setDisplayState = true;
+ return;
+ }
+
+ this.setDisplayState = this.routeNames.has(currentRouteName);
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/tags-section/tag-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/tags-section/tag-section-link.js
index cf161a1aff..fda4b8fa29 100644
--- a/app/assets/javascripts/discourse/app/lib/sidebar/tags-section/tag-section-link.js
+++ b/app/assets/javascripts/discourse/app/lib/sidebar/tags-section/tag-section-link.js
@@ -8,8 +8,8 @@ export default class TagSectionLink {
@tracked totalUnread = 0;
@tracked totalNew = 0;
- constructor({ tag, topicTrackingState }) {
- this.tag = tag;
+ constructor({ tagName, topicTrackingState }) {
+ this.tagName = tagName;
this.topicTrackingState = topicTrackingState;
this.refreshCounts();
}
@@ -17,22 +17,22 @@ export default class TagSectionLink {
@bind
refreshCounts() {
this.totalUnread = this.topicTrackingState.countUnread({
- tagId: this.tag,
+ tagId: this.tagName,
});
if (this.totalUnread === 0) {
this.totalNew = this.topicTrackingState.countNew({
- tagId: this.tag,
+ tagId: this.tagName,
});
}
}
get name() {
- return this.tag;
+ return this.tagName;
}
get model() {
- return this.tag;
+ return this.tagName;
}
get currentWhen() {
@@ -44,7 +44,7 @@ export default class TagSectionLink {
}
get text() {
- return this.tag;
+ return this.tagName;
}
get badgeText() {
diff --git a/app/assets/javascripts/discourse/app/lib/svg-sprite-loader.js b/app/assets/javascripts/discourse/app/lib/svg-sprite-loader.js
index d08ebd2304..42f033c413 100644
--- a/app/assets/javascripts/discourse/app/lib/svg-sprite-loader.js
+++ b/app/assets/javascripts/discourse/app/lib/svg-sprite-loader.js
@@ -7,7 +7,8 @@ export function loadSprites(spritePath, spriteName) {
if (!spriteContainer) {
spriteContainer = document.createElement("div");
spriteContainer.id = SVG_CONTAINER_ID;
- document.body.appendChild(spriteContainer);
+ const spriteWrapper = document.querySelector("discourse-assets-icons");
+ spriteWrapper?.appendChild(spriteContainer);
}
let sprites = spriteContainer.querySelector(`.${spriteName}`);
diff --git a/app/assets/javascripts/discourse/app/lib/to-markdown.js b/app/assets/javascripts/discourse/app/lib/to-markdown.js
index c8bbbf7eb5..e6309b9d3e 100644
--- a/app/assets/javascripts/discourse/app/lib/to-markdown.js
+++ b/app/assets/javascripts/discourse/app/lib/to-markdown.js
@@ -6,9 +6,11 @@ const MSO_LIST_CLASSES = [
let tagDecorateCallbacks = [];
let blockDecorateCallbacks = [];
+let textDecorateCallbacks = [];
/**
- * Allows to add support for custom inline markdown/bbcode
+ * Allows to add support for custom inline markdown/bbcode prefixes
+ * to convert nodes back to bbcode.
*
* ```
* addTagDecorateCallback(function (text) {
@@ -29,7 +31,8 @@ export function clearTagDecorateCallbacks() {
}
/**
- * Allows to add support for custom block markdown/bbcode
+ * Allows to add support for custom block markdown/bbcode prefixes
+ * to convert nodes back to bbcode.
*
* ```
* addBlockDecorateCallback(function (text) {
@@ -48,6 +51,30 @@ export function clearBlockDecorateCallbacks() {
blockDecorateCallbacks = [];
}
+/**
+ * Allows to add support for custom text node transformations
+ * based on the next/previous elements.
+ *
+ * ```
+ * addTextDecorateCallback(function (text, nextElement, previousElement) {
+ * if (
+ * startRangeOpts &&
+ * nextElement?.attributes.class?.includes("discourse-local-date") &&
+ * text === "→"
+ * ) {
+ * return "";
+ * }
+ * });
+ * ```
+ */
+export function addTextDecorateCallback(callback) {
+ textDecorateCallbacks.push(callback);
+}
+
+export function clearTextDecorateCallbacks() {
+ textDecorateCallbacks = [];
+}
+
export class Tag {
static named(name) {
const klass = class NamedTag extends Tag {};
@@ -657,9 +684,10 @@ function tagByName(name) {
}
class Element {
- constructor(element, parent, previous, next) {
+ constructor(element, parent, previous, next, metadata) {
this.name = element.name;
this.data = element.data;
+ this.metadata = metadata;
this.children = element.children;
this.attributes = element.attributes;
@@ -682,6 +710,7 @@ class Element {
tag() {
const tag = new (tagByName(this.name) || Tag)();
tag.element = this;
+ tag.metadata = this.metadata;
return tag;
}
@@ -709,6 +738,19 @@ class Element {
}
text = text.replace(/[\s\t]+/g, " ");
+ textDecorateCallbacks.forEach((callback) => {
+ const result = callback.call(
+ this,
+ text,
+ this.next,
+ this.previous,
+ this.metadata
+ );
+
+ if (typeof result !== "undefined") {
+ text = result;
+ }
+ });
return text;
}
@@ -721,8 +763,8 @@ class Element {
return this.parentNames.filter((p) => names.includes(p));
}
- static toMarkdown(element, parent, prev, next) {
- return new Element(element, parent, prev, next).toMarkdown();
+ static toMarkdown(element, parent, prev, next, metadata) {
+ return new Element(element, parent, prev, next, metadata).toMarkdown();
}
static parseChildren(parent) {
@@ -732,12 +774,15 @@ class Element {
static parse(elements, parent = null) {
if (elements) {
let result = [];
+ let metadata = {};
for (let i = 0; i < elements.length; i++) {
const prev = i === 0 ? null : elements[i - 1];
const next = i === elements.length ? null : elements[i + 1];
- result.push(Element.toMarkdown(elements[i], parent, prev, next));
+ result.push(
+ Element.toMarkdown(elements[i], parent, prev, next, metadata)
+ );
}
return result.join("");
diff --git a/app/assets/javascripts/discourse/app/lib/uppy-plugin-base.js b/app/assets/javascripts/discourse/app/lib/uppy-plugin-base.js
index 51e5907ebd..421229e999 100644
--- a/app/assets/javascripts/discourse/app/lib/uppy-plugin-base.js
+++ b/app/assets/javascripts/discourse/app/lib/uppy-plugin-base.js
@@ -1,6 +1,7 @@
import { BasePlugin } from "@uppy/core";
import { Promise } from "rsvp";
import { warn } from "@ember/debug";
+import { isTesting } from "discourse-common/config/environment";
export class UppyPluginBase extends BasePlugin {
constructor(uppy, opts) {
@@ -9,7 +10,9 @@ export class UppyPluginBase extends BasePlugin {
}
_consoleWarn(msg) {
- warn(`[${this.id}] ${msg}`, { id: `discourse.${this.id}` });
+ if (!isTesting()) {
+ warn(`[${this.id}] ${msg}`, { id: `discourse.${this.id}` });
+ }
}
_consoleDebug(msg) {
diff --git a/app/assets/javascripts/discourse/app/lib/url.js b/app/assets/javascripts/discourse/app/lib/url.js
index f9bc2565ca..6c092a4f81 100644
--- a/app/assets/javascripts/discourse/app/lib/url.js
+++ b/app/assets/javascripts/discourse/app/lib/url.js
@@ -24,7 +24,6 @@ const SERVER_SIDE_ONLY = [
/^\/raw\//,
/^\/posts\/\d+\/raw/,
/^\/raw\/\d+/,
- /^\/wizard/,
/\.rss$/,
/\.json$/,
/^\/admin\/upgrade$/,
diff --git a/app/assets/javascripts/discourse/app/mixins/textarea-text-manipulation.js b/app/assets/javascripts/discourse/app/mixins/textarea-text-manipulation.js
index 462324baf2..9e46e0c454 100644
--- a/app/assets/javascripts/discourse/app/mixins/textarea-text-manipulation.js
+++ b/app/assets/javascripts/discourse/app/mixins/textarea-text-manipulation.js
@@ -570,14 +570,12 @@ export default Mixin.create({
this.addText(selected, `:${code}:`);
}
} else {
- let numOfRemovedChars = selected.pre.length - captures[1].length;
- selected.pre = selected.pre.slice(
- 0,
- selected.pre.length - captures[1].length
+ let numOfRemovedChars = captures[1].length;
+ this._insertAt(
+ selected.start - numOfRemovedChars,
+ selected.end,
+ `${code}:`
);
- selected.start -= numOfRemovedChars;
- selected.end -= numOfRemovedChars;
- this.addText(selected, `${code}:`);
}
},
});
diff --git a/app/assets/javascripts/discourse/app/models/nav-item.js b/app/assets/javascripts/discourse/app/models/nav-item.js
index 9b4cec0f76..56d1f9566c 100644
--- a/app/assets/javascripts/discourse/app/models/nav-item.js
+++ b/app/assets/javascripts/discourse/app/models/nav-item.js
@@ -27,7 +27,7 @@ const NavItem = EmberObject.extend({
},
set(value) {
- this.set("_title", value);
+ return this.set("_title", value);
},
},
@@ -56,7 +56,7 @@ const NavItem = EmberObject.extend({
},
set(value) {
- this.set("_displayName", value);
+ return this.set("_displayName", value);
},
},
diff --git a/app/assets/javascripts/discourse/app/models/post-stream.js b/app/assets/javascripts/discourse/app/models/post-stream.js
index 9b6d10a8e5..c03dbb6f45 100644
--- a/app/assets/javascripts/discourse/app/models/post-stream.js
+++ b/app/assets/javascripts/discourse/app/models/post-stream.js
@@ -872,6 +872,17 @@ export default RestModel.extend({
return resolved;
},
+ triggerChangedTopicStats() {
+ if (this.firstPostNotLoaded) {
+ return Promise.reject();
+ }
+
+ return Promise.resolve().then(() => {
+ const firstPost = this.posts.findBy("post_number", 1);
+ return firstPost.id;
+ });
+ },
+
postForPostNumber(postNumber) {
if (!this.hasPosts) {
return;
diff --git a/app/assets/javascripts/discourse/app/models/private-message-topic-tracking-state.js b/app/assets/javascripts/discourse/app/models/private-message-topic-tracking-state.js
index 5a96b7064f..1c95bbfcaa 100644
--- a/app/assets/javascripts/discourse/app/models/private-message-topic-tracking-state.js
+++ b/app/assets/javascripts/discourse/app/models/private-message-topic-tracking-state.js
@@ -1,3 +1,5 @@
+import { Promise } from "rsvp";
+
import EmberObject from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import { bind, on } from "discourse-common/utils/decorators";
@@ -25,16 +27,20 @@ const PrivateMessageTopicTrackingState = EmberObject.extend({
this.statesModificationCounter = 0;
this.isTracking = false;
this.newIncoming = [];
- this.stateChangeCallbacks = {};
+ this.stateChangeCallbacks = new Map();
},
- onStateChange(name, callback) {
- this.stateChangeCallbacks[name] = callback;
+ onStateChange(key, callback) {
+ this.stateChangeCallbacks.set(key, callback);
+ },
+
+ offStateChange(key) {
+ this.stateChangeCallbacks.delete(key);
},
startTracking() {
if (this.isTracking) {
- return;
+ return Promise.resolve();
}
this._establishChannels();
@@ -46,13 +52,13 @@ const PrivateMessageTopicTrackingState = EmberObject.extend({
_establishChannels() {
this.messageBus.subscribe(
- this._userChannel(),
+ this.userChannel(),
this._processMessage.bind(this)
);
this.currentUser.groupsWithMessages?.forEach((group) => {
this.messageBus.subscribe(
- this._groupChannel(group.id),
+ this.groupChannel(group.id),
this._processMessage.bind(this)
);
});
@@ -111,11 +117,11 @@ const PrivateMessageTopicTrackingState = EmberObject.extend({
return this.states.get(topicId);
},
- _userChannel() {
+ userChannel() {
return `${this.CHANNEL_PREFIX}/user/${this.currentUser.id}`;
},
- _groupChannel(groupId) {
+ groupChannel(groupId) {
return `${this.CHANNEL_PREFIX}/group/${groupId}`;
},
@@ -263,7 +269,7 @@ const PrivateMessageTopicTrackingState = EmberObject.extend({
_afterStateChange() {
this.incrementProperty("statesModificationCounter");
- Object.values(this.stateChangeCallbacks).forEach((callback) => callback());
+ this.stateChangeCallbacks.forEach((callback) => callback());
},
});
diff --git a/app/assets/javascripts/discourse/app/models/topic-tracking-state.js b/app/assets/javascripts/discourse/app/models/topic-tracking-state.js
index 109bd65707..0a2a9dac3c 100644
--- a/app/assets/javascripts/discourse/app/models/topic-tracking-state.js
+++ b/app/assets/javascripts/discourse/app/models/topic-tracking-state.js
@@ -32,15 +32,15 @@ function isUnseen(topic) {
return !topic.is_seen;
}
-function hasMutedTags(topicTagIds, mutedTagIds, siteSettings) {
- if (!mutedTagIds || !topicTagIds) {
+function hasMutedTags(topicTags, mutedTags, siteSettings) {
+ if (!mutedTags || !topicTags) {
return false;
}
return (
(siteSettings.remove_muted_tags_from_latest === "always" &&
- topicTagIds.any((tagId) => mutedTagIds.includes(tagId))) ||
+ topicTags.any((topicTag) => mutedTags.includes(topicTag))) ||
(siteSettings.remove_muted_tags_from_latest === "only_muted" &&
- topicTagIds.every((tagId) => mutedTagIds.includes(tagId)))
+ topicTags.every((topicTag) => mutedTags.includes(topicTag)))
);
}
@@ -876,10 +876,9 @@ const TopicTrackingState = EmberObject.extend({
}
if (["new_topic", "latest"].includes(data.message_type)) {
- const mutedTagIds = User.currentProp("muted_tag_ids");
- if (
- hasMutedTags(data.payload.topic_tag_ids, mutedTagIds, this.siteSettings)
- ) {
+ const mutedTags = User.currentProp("muted_tags");
+
+ if (hasMutedTags(data.payload.tags, mutedTags, this.siteSettings)) {
return;
}
}
diff --git a/app/assets/javascripts/discourse/app/models/topic.js b/app/assets/javascripts/discourse/app/models/topic.js
index ccb9f1a675..9b00d32746 100644
--- a/app/assets/javascripts/discourse/app/models/topic.js
+++ b/app/assets/javascripts/discourse/app/models/topic.js
@@ -70,6 +70,7 @@ const Topic = RestModel.extend({
lastPosterUser: alias("lastPoster.user"),
lastPosterGroup: alias("lastPoster.primary_group"),
+ allowedGroups: alias("details.allowed_groups"),
@discourseComputed("posters.[]", "participants.[]", "allowed_user_count")
featuredUsers(posters, participants, allowedUserCount) {
diff --git a/app/assets/javascripts/discourse/app/models/user.js b/app/assets/javascripts/discourse/app/models/user.js
index 086dbf4d37..b24c8d9636 100644
--- a/app/assets/javascripts/discourse/app/models/user.js
+++ b/app/assets/javascripts/discourse/app/models/user.js
@@ -1,7 +1,7 @@
import EmberObject, { computed, get, getProperties } from "@ember/object";
import cookie, { removeCookie } from "discourse/lib/cookie";
import { defaultHomepage, escapeExpression } from "discourse/lib/utilities";
-import { equal, filterBy, gt, or } from "@ember/object/computed";
+import { alias, equal, filterBy, gt, or } from "@ember/object/computed";
import getURL, { getURLWithCDN } from "discourse-common/lib/get-url";
import { A } from "@ember/array";
import Badge from "discourse/models/badge";
@@ -62,6 +62,8 @@ let userFields = [
"primary_group_id",
"flair_group_id",
"user_notification_schedule",
+ "sidebar_category_ids",
+ "sidebar_tag_names",
];
export function addSaveableUserField(fieldName) {
@@ -307,6 +309,35 @@ const User = RestModel.extend({
@discourseComputed("silenced_till")
silencedTillDate: longDate,
+ sidebarCategoryIds: alias("sidebar_category_ids"),
+
+ @discourseComputed("sidebar_tag_names.[]")
+ sidebarTagNames(sidebarTagNames) {
+ if (!sidebarTagNames || sidebarTagNames.length === 0) {
+ return [];
+ }
+
+ return sidebarTagNames;
+ },
+
+ @discourseComputed("sidebar_category_ids.[]")
+ sidebarCategories(sidebarCategoryIds) {
+ if (!sidebarCategoryIds || sidebarCategoryIds.length === 0) {
+ return [];
+ }
+
+ return Site.current().categoriesList.filter((category) => {
+ if (
+ this.siteSettings.suppress_uncategorized_badge &&
+ category.isUncategorizedCategory
+ ) {
+ return false;
+ }
+
+ return sidebarCategoryIds.includes(category.id);
+ });
+ },
+
changeUsername(new_username) {
return ajax(userPath(`${this.username_lower}/preferences/username`), {
type: "PUT",
@@ -385,6 +416,12 @@ const User = RestModel.extend({
}
});
+ ["sidebar_category_ids", "sidebar_tag_names"].forEach((prop) => {
+ if (data[prop]?.length === 0) {
+ data[prop] = null;
+ }
+ });
+
return this._saveUserData(data, updatedState);
},
diff --git a/app/assets/javascripts/discourse/app/pre-initializers/discourse-bootstrap.js b/app/assets/javascripts/discourse/app/pre-initializers/discourse-bootstrap.js
index 17de813fe6..78b9dc698d 100644
--- a/app/assets/javascripts/discourse/app/pre-initializers/discourse-bootstrap.js
+++ b/app/assets/javascripts/discourse/app/pre-initializers/discourse-bootstrap.js
@@ -70,9 +70,8 @@ export default {
}
session.darkModeAvailable =
- document.head.querySelectorAll(
- 'link[media="(prefers-color-scheme: dark)"]'
- ).length > 0;
+ document.querySelectorAll('link[media="(prefers-color-scheme: dark)"]')
+ .length > 0;
session.defaultColorSchemeIsDark = setupData.colorSchemeIsDark === "true";
diff --git a/app/assets/javascripts/discourse/app/pre-initializers/map-routes.js b/app/assets/javascripts/discourse/app/pre-initializers/map-routes.js
index 19933c1480..27b1196a8c 100644
--- a/app/assets/javascripts/discourse/app/pre-initializers/map-routes.js
+++ b/app/assets/javascripts/discourse/app/pre-initializers/map-routes.js
@@ -1,9 +1,5 @@
-import Application from "@ember/application";
-import { isLegacyEmber } from "discourse-common/config/environment";
import { registerRouter, teardownRouter } from "discourse/mapping-router";
-let originalBuildInstance;
-
export default {
name: "map-routes",
after: "inject-discourse-objects",
@@ -12,17 +8,6 @@ export default {
let routerClass = registerRouter(app);
container.registry.register("router:main", routerClass);
this.routerClass = routerClass;
-
- if (isLegacyEmber()) {
- // HACK to fix: https://github.com/emberjs/ember.js/issues/10310
- originalBuildInstance =
- originalBuildInstance || Application.prototype.buildInstance;
-
- Application.prototype.buildInstance = function () {
- this.buildRegistry();
- return originalBuildInstance.apply(this);
- };
- }
},
teardown() {
diff --git a/app/assets/javascripts/discourse/app/routes/app-route-map.js b/app/assets/javascripts/discourse/app/routes/app-route-map.js
index e26451223f..9306fa71de 100644
--- a/app/assets/javascripts/discourse/app/routes/app-route-map.js
+++ b/app/assets/javascripts/discourse/app/routes/app-route-map.js
@@ -166,6 +166,7 @@ export default function () {
this.route("tags");
this.route("interface");
this.route("apps");
+ this.route("sidebar");
this.route("username");
this.route("email");
diff --git a/app/assets/javascripts/discourse/app/routes/application.js b/app/assets/javascripts/discourse/app/routes/application.js
index 0b02e6417f..59aecbb31a 100644
--- a/app/assets/javascripts/discourse/app/routes/application.js
+++ b/app/assets/javascripts/discourse/app/routes/application.js
@@ -48,6 +48,9 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, {
},
toggleSidebar() {
+ // enables CSS transitions, but not on did-insert
+ document.querySelector("body").classList.add("sidebar-animate");
+
this.controllerFor("application").toggleProperty("showSidebar");
},
diff --git a/app/assets/javascripts/discourse/app/routes/preferences-sidebar.js b/app/assets/javascripts/discourse/app/routes/preferences-sidebar.js
new file mode 100644
index 0000000000..f9d430144e
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/routes/preferences-sidebar.js
@@ -0,0 +1,20 @@
+import RestrictedUserRoute from "discourse/routes/restricted-user";
+
+export default RestrictedUserRoute.extend({
+ showFooter: true,
+
+ setupController(controller, user) {
+ const props = {
+ model: user,
+ selectedSiderbarCategories: user.sidebarCategories,
+ initialSidebarCategoryIds: user.sidebarCategoryIds,
+ };
+
+ if (this.siteSettings.tagging_enabled) {
+ props.selectedSidebarTagNames = user.sidebarTagNames;
+ props.initialSidebarTagNames = user.sidebarTagNames;
+ }
+
+ controller.setProperties(props);
+ },
+});
diff --git a/app/assets/javascripts/discourse/app/services/presence.js b/app/assets/javascripts/discourse/app/services/presence.js
index d0394aef89..0219142a01 100644
--- a/app/assets/javascripts/discourse/app/services/presence.js
+++ b/app/assets/javascripts/discourse/app/services/presence.js
@@ -1,6 +1,5 @@
import Service from "@ember/service";
-import EmberObject, { computed, defineProperty } from "@ember/object";
-import { readOnly } from "@ember/object/computed";
+import EmberObject, { computed } from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import {
cancel,
@@ -13,13 +12,14 @@ import {
} from "@ember/runloop";
import Session from "discourse/models/session";
import { Promise } from "rsvp";
-import { isLegacyEmber, isTesting } from "discourse-common/config/environment";
import User from "discourse/models/user";
import userPresent, {
onPresenceChange,
removeOnPresenceChange,
} from "discourse/lib/user-presence";
import { bind } from "discourse-common/utils/decorators";
+import Evented from "@ember/object/evented";
+import { isTesting } from "discourse-common/config/environment";
const PRESENCE_INTERVAL_S = 30;
const PRESENCE_DEBOUNCE_MS = isTesting() ? 0 : 500;
@@ -45,17 +45,12 @@ export class PresenceChannelNotFound extends Error {}
// Instances of this class are handed out to consumers. They act as
// convenient proxies to the PresenceService and PresenceServiceState
-class PresenceChannel extends EmberObject {
+// The 'change' event is fired whenever the users list or the count change
+class PresenceChannel extends EmberObject.extend(Evented) {
init({ name, presenceService }) {
super.init(...arguments);
this.name = name;
this.presenceService = presenceService;
- defineProperty(
- this,
- "_presenceState",
- readOnly(`presenceService._presenceChannelStates.${name}`)
- );
-
this.set("present", false);
this.set("subscribed", false);
}
@@ -91,8 +86,12 @@ class PresenceChannel extends EmberObject {
if (this.subscribed) {
return;
}
- await this.presenceService._subscribe(this, initialData);
+ const state = await this.presenceService._subscribe(this, initialData);
this.set("subscribed", true);
+ this.set("_presenceState", state);
+
+ this._publishChange();
+ state.on("change", this._publishChange);
}
async unsubscribe() {
@@ -101,6 +100,14 @@ class PresenceChannel extends EmberObject {
}
await this.presenceService._unsubscribe(this);
this.set("subscribed", false);
+ this._presenceState.off("change", this._publishChange);
+ this.set("_presenceState", null);
+ this._publishChange();
+ }
+
+ @bind
+ _publishChange() {
+ this.trigger("change", this);
}
@computed("_presenceState.users", "subscribed")
@@ -128,7 +135,7 @@ class PresenceChannel extends EmberObject {
}
}
-class PresenceChannelState extends EmberObject {
+class PresenceChannelState extends EmberObject.extend(Evented) {
init({ name, presenceService }) {
super.init(...arguments);
this.name = name;
@@ -179,6 +186,7 @@ class PresenceChannelState extends EmberObject {
);
this.set("_subscribedCallback", callback);
+ this.trigger("change");
}
// Stop subscribing to updates from the server about this channel
@@ -191,6 +199,7 @@ class PresenceChannelState extends EmberObject {
this.set("_subscribedCallback", null);
this.set("users", null);
this.set("count", null);
+ this.trigger("change");
}
}
@@ -221,6 +230,7 @@ class PresenceChannelState extends EmberObject {
if (this.countOnly && data.count_delta !== undefined) {
this.set("count", this.count + data.count_delta);
+ this.trigger("change");
} else if (
!this.countOnly &&
(data.entering_users || data.leaving_user_ids)
@@ -235,6 +245,7 @@ class PresenceChannelState extends EmberObject {
this.users.removeObjects(toRemove);
}
this.set("count", this.users.length);
+ this.trigger("change");
} else {
// Unexpected message
await this._resubscribe();
@@ -247,7 +258,7 @@ export default class PresenceService extends Service {
init() {
super.init(...arguments);
this._queuedEvents = [];
- this._presenceChannelStates = EmberObject.create();
+ this._presenceChannelStates = new Map();
this._presentProxies = new Map();
this._subscribedProxies = new Map();
this._initialDataRequests = new Map();
@@ -429,7 +440,7 @@ export default class PresenceService extends Service {
this._addSubscribed(channelProxy);
const channelName = channelProxy.name;
- let state = this._presenceChannelStates[channelName];
+ let state = this._presenceChannelStates.get(channelName);
if (!state) {
state = PresenceChannelState.create({
name: channelName,
@@ -438,14 +449,15 @@ export default class PresenceService extends Service {
this._presenceChannelStates.set(channelName, state);
await state.subscribe(initialData);
}
+ return state;
}
_unsubscribe(channelProxy) {
const subscribedCount = this._removeSubscribed(channelProxy);
if (subscribedCount === 0) {
const channelName = channelProxy.name;
- this._presenceChannelStates[channelName].unsubscribe();
- this._presenceChannelStates.set(channelName, undefined);
+ this._presenceChannelStates.get(channelName).unsubscribe();
+ this._presenceChannelStates.delete(channelName);
}
}
@@ -537,13 +549,6 @@ export default class PresenceService extends Service {
}
});
} catch (e) {
- if (e.jqXHR?.status === 403 && isTesting() && isLegacyEmber()) {
- // Legacy testing environment will remove the User.current() value before disposing of controllers/components.
- // Presence often involves making HTTP calls during disposal of components, so this can cause issues.
- // Modern Ember-CLI environment does not require this hack
- return;
- }
-
// Put the failed events back in the queue for next time
this._queuedEvents.unshift(...queue);
if (e.jqXHR?.status === 429) {
diff --git a/app/assets/javascripts/discourse/app/services/user-status.js b/app/assets/javascripts/discourse/app/services/user-status.js
index b152a55698..0b6835f15e 100644
--- a/app/assets/javascripts/discourse/app/services/user-status.js
+++ b/app/assets/javascripts/discourse/app/services/user-status.js
@@ -8,7 +8,7 @@ export default class UserStatusService extends Service {
await ajax({
url: "/user-status.json",
type: "PUT",
- data: { description: status.description },
+ data: status,
});
this.currentUser.set("status", status);
diff --git a/app/assets/javascripts/discourse/app/templates/application.hbs b/app/assets/javascripts/discourse/app/templates/application.hbs
index 7e16b0f910..1a8cf13f5e 100644
--- a/app/assets/javascripts/discourse/app/templates/application.hbs
+++ b/app/assets/javascripts/discourse/app/templates/application.hbs
@@ -15,9 +15,9 @@
{{plugin-outlet name="below-site-header" connectorTagName="div" args=(hash currentPath=router._router.currentPath)}}
-
- {{#if currentUser.experimental_sidebar_enabled}}
-
+
+ {{#if (and currentUser.experimental_sidebar_enabled showSidebar)}}
+
{{/if}}
diff --git a/app/assets/javascripts/discourse/app/templates/components/bookmark-icon.hbs b/app/assets/javascripts/discourse/app/templates/components/bookmark-icon.hbs
index 2a1a2af91c..6d82c0977d 100644
--- a/app/assets/javascripts/discourse/app/templates/components/bookmark-icon.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/bookmark-icon.hbs
@@ -1 +1 @@
-{{d-icon icon translatedtitle=title class=cssClasses}}
+{{d-icon icon translatedTitle=title class=cssClasses}}
diff --git a/app/assets/javascripts/discourse/app/templates/components/categories-boxes-with-topics.hbs b/app/assets/javascripts/discourse/app/templates/components/categories-boxes-with-topics.hbs
index 0a041d8cc0..e25f0d922c 100644
--- a/app/assets/javascripts/discourse/app/templates/components/categories-boxes-with-topics.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/categories-boxes-with-topics.hbs
@@ -1,5 +1,5 @@
{{#each categories as |c|}}
-
+
{{/unless}}
+
{{plugin-outlet name="category-box-below-each-category" args=(hash category=c)}}
diff --git a/app/assets/javascripts/discourse/app/templates/components/categories-boxes.hbs b/app/assets/javascripts/discourse/app/templates/components/categories-boxes.hbs
index d806ed1aaf..95758c17e5 100644
--- a/app/assets/javascripts/discourse/app/templates/components/categories-boxes.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/categories-boxes.hbs
@@ -1,6 +1,7 @@
{{#each categories as |c|}}
{{plugin-outlet name="category-box-before-each-box" args=(hash category=c)}}
-
+
+
{{#unless c.isMuted}}
@@ -15,6 +16,7 @@
{{/if}}
{{/unless}}
+
+
{{plugin-outlet name="category-box-after-each-box" args=(hash category=c)}}
{{/each}}
+
{{plugin-outlet name="category-boxes-after-boxes" args=(hash category=c)}}
diff --git a/app/assets/javascripts/discourse/app/templates/components/d-editor.hbs b/app/assets/javascripts/discourse/app/templates/components/d-editor.hbs
index a8bad744e3..f6ca96a8ea 100644
--- a/app/assets/javascripts/discourse/app/templates/components/d-editor.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/d-editor.hbs
@@ -74,5 +74,5 @@
isEditorFocused=isEditorFocused
initialFilter=this.emojiFilter
emojiSelected=(action "emojiSelected")
- onEmojiPickerClose=(action (mut emojiPickerIsActive) false)
+ onEmojiPickerClose=onEmojiPickerClose
}}
diff --git a/app/assets/javascripts/discourse/app/templates/components/emoji-uploader.hbs b/app/assets/javascripts/discourse/app/templates/components/emoji-uploader.hbs
index 32c118bc3f..519f95e4e2 100644
--- a/app/assets/javascripts/discourse/app/templates/components/emoji-uploader.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/emoji-uploader.hbs
@@ -40,7 +40,7 @@
type="file"
multiple="true"
accept=".png,.gif">
- {{d-button class="btn-primary" computedLabel=buttonLabel icon="plus" action=(action "chooseFiles") disabled=uploading}}
+ {{d-button class="btn-primary" translatedLabel=buttonLabel icon="plus" action=(action "chooseFiles") disabled=uploading}}
diff --git a/app/assets/javascripts/discourse/app/templates/components/global-notice.hbs b/app/assets/javascripts/discourse/app/templates/components/global-notice.hbs
index 08ff36603f..efb640d1be 100644
--- a/app/assets/javascripts/discourse/app/templates/components/global-notice.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/global-notice.hbs
@@ -1,19 +1,27 @@
-{{#each notices as |notice|}}
-
-
- {{#if notice.options.html}}
- {{html-safe notice.options.html}}
- {{/if}}
-
{{html-safe notice.text}}
+
+ {{#if this.visible}}
+ {{#each notices as |notice|}}
+
+
+ {{#if notice.options.html}}
+ {{html-safe notice.options.html}}
+ {{/if}}
- {{#if notice.options.dismissable}}
- {{d-button
- class="btn-flat close"
- icon="times"
- action=(action "dismissNotice")
- actionParam=notice
- }}
- {{/if}}
-
-
-{{/each}}
+
{{html-safe notice.text}}
+
+ {{#if notice.options.dismissable}}
+ {{d-button
+ class="btn-flat close"
+ icon="times"
+ action=(action "dismissNotice")
+ actionParam=notice
+ }}
+ {{/if}}
+
+
+ {{/each}}
+ {{/if}}
+
diff --git a/app/assets/javascripts/discourse/app/templates/components/parent-category-row.hbs b/app/assets/javascripts/discourse/app/templates/components/parent-category-row.hbs
index a00118be6e..19fd656fd0 100644
--- a/app/assets/javascripts/discourse/app/templates/components/parent-category-row.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/parent-category-row.hbs
@@ -1,14 +1,17 @@
{{#unless isHidden}}
{{plugin-outlet name="category-list-above-each-category" args=(hash category=category)}}
+
- |
+ |
{{category-title-link category=category}}
{{plugin-outlet name="below-category-title-link" connectorTagName="div" args=(hash category=category)}}
+
{{#if category.description_excerpt}}
{{dir-span category.description_excerpt htmlSafe="true"}}
{{/if}}
+
{{#if category.isGrandParent}}
@@ -25,10 +28,12 @@
{{/if}}
+
|
{{html-safe category.stat}}
{{category-unread category=category tagName="div" class="unread-new"}}
|
+
{{#unless isMuted}}
{{#if showTopics}}
diff --git a/app/assets/javascripts/discourse/app/templates/components/sidebar.hbs b/app/assets/javascripts/discourse/app/templates/components/sidebar.hbs
index 9c69d3ae7c..1f1e8160fa 100644
--- a/app/assets/javascripts/discourse/app/templates/components/sidebar.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/sidebar.hbs
@@ -1,12 +1,14 @@
-{{#if @shouldDisplay}}
- | |