Compare commits

..

1 Commits

Author SHA1 Message Date
Sam Saffron
e3a6be3db7
PREF: allow notification ids a larger range
Out of the box we allow 2.2 billion notification, certain exceptionally
bit forums may overflow. This premtively raises the bar to big int.

The trade off is that all forums in the wild store an extra 4 byte in a bunch
of tables.
2023-03-02 15:57:28 +11:00
1126 changed files with 19499 additions and 25182 deletions

View File

@ -159,22 +159,6 @@ jobs:
path: tmp/turbo_rspec_runtime.log
key: rspec-runtime-backend-core
- name: Run Zeitwerk check
if: matrix.build_type == 'backend'
env:
LOAD_PLUGINS: ${{ (matrix.target == 'plugins') && '1' || '0' }}
run: |
if ! bin/rails zeitwerk:check --trace; then
echo
echo "---------------------------------------------"
echo
echo "::error::'bin/rails zeitwerk:check' failed - the app will fail to boot with 'eager_load=true' (e.g. in production)."
echo "To reproduce locally, run 'bin/rails zeitwerk:check'."
echo "Alternatively, you can run your local server/tests with the 'DISCOURSE_ZEITWERK_EAGER_LOAD=1' environment variable."
echo
exit 1
fi
- name: Core RSpec
if: matrix.build_type == 'backend' && matrix.target == 'core'
run: bin/turbo_rspec --verbose
@ -198,11 +182,11 @@ jobs:
- name: Core System Tests
if: matrix.build_type == 'system' && matrix.target == 'core'
run: bin/rspec spec/system
run: PARALLEL_TEST_PROCESSORS=1 bin/turbo_rspec --verbose spec/system
- name: Plugin System Tests
if: matrix.build_type == 'system' && matrix.target == 'plugins'
run: LOAD_PLUGINS=1 bin/rspec plugins/*/spec/system
run: LOAD_PLUGINS=1 PARALLEL_TEST_PROCESSORS=1 bin/turbo_rspec --verbose plugins/*/spec/system
- name: Upload failed system test screenshots
uses: actions/upload-artifact@v3

View File

@ -15,7 +15,7 @@ module.exports = {
"directory-item-value",
"directory-table-header-title",
"loading-spinner",
"directory-item-label",
"mobile-directory-item-label",
],
},
"no-implicit-this": {

View File

@ -18,7 +18,7 @@ else
# this allows us to include the bits of rails we use without pieces we do not.
#
# To issue a rails update bump the version number here
rails_version = "7.0.4.3"
rails_version = "7.0.4.1"
gem "actionmailer", rails_version
gem "actionpack", rails_version
gem "actionview", rails_version

View File

@ -17,25 +17,25 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actionmailer (7.0.4.3)
actionpack (= 7.0.4.3)
actionview (= 7.0.4.3)
activejob (= 7.0.4.3)
activesupport (= 7.0.4.3)
actionmailer (7.0.4.1)
actionpack (= 7.0.4.1)
actionview (= 7.0.4.1)
activejob (= 7.0.4.1)
activesupport (= 7.0.4.1)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.4.3)
actionview (= 7.0.4.3)
activesupport (= 7.0.4.3)
actionpack (7.0.4.1)
actionview (= 7.0.4.1)
activesupport (= 7.0.4.1)
rack (~> 2.0, >= 2.2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actionview (7.0.4.3)
activesupport (= 7.0.4.3)
actionview (7.0.4.1)
activesupport (= 7.0.4.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -44,15 +44,15 @@ GEM
actionview (>= 6.0.a)
active_model_serializers (0.8.4)
activemodel (>= 3.0)
activejob (7.0.4.3)
activesupport (= 7.0.4.3)
activejob (7.0.4.1)
activesupport (= 7.0.4.1)
globalid (>= 0.3.6)
activemodel (7.0.4.3)
activesupport (= 7.0.4.3)
activerecord (7.0.4.3)
activemodel (= 7.0.4.3)
activesupport (= 7.0.4.3)
activesupport (7.0.4.3)
activemodel (7.0.4.1)
activesupport (= 7.0.4.1)
activerecord (7.0.4.1)
activemodel (= 7.0.4.1)
activesupport (= 7.0.4.1)
activesupport (7.0.4.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@ -157,7 +157,7 @@ GEM
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday-net_http (3.0.2)
faraday-retry (2.1.0)
faraday-retry (2.0.0)
faraday (~> 2.0)
fast_blank (1.0.1)
fast_xs (0.8.0)
@ -167,11 +167,10 @@ GEM
gc_tracer (1.5.1)
globalid (1.1.0)
activesupport (>= 5.0)
google-protobuf (3.22.2)
google-protobuf (3.22.2-aarch64-linux)
google-protobuf (3.22.2-arm64-darwin)
google-protobuf (3.22.2-x86_64-darwin)
google-protobuf (3.22.2-x86_64-linux)
google-protobuf (3.22.0)
google-protobuf (3.22.0-arm64-darwin)
google-protobuf (3.22.0-x86_64-darwin)
google-protobuf (3.22.0-x86_64-linux)
guess_html_encoding (0.0.11)
hana (1.3.7)
hashdiff (1.0.1)
@ -219,7 +218,7 @@ GEM
logstash-event (1.2.02)
logstash-logger (0.26.1)
logstash-event (~> 1.2)
logster (2.12.2)
logster (2.11.4)
loofah (2.19.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
@ -240,10 +239,10 @@ GEM
mini_sql (1.4.0)
mini_suffix (0.3.3)
ffi (~> 1.9)
minitest (5.18.0)
minitest (5.17.0)
mocha (2.0.2)
ruby2_keywords (>= 0.0.5)
msgpack (1.6.1)
msgpack (1.6.0)
multi_json (1.15.0)
multi_xml (0.6.0)
mustache (1.1.1)
@ -312,10 +311,10 @@ GEM
parallel (1.22.1)
parallel_tests (4.2.0)
parallel
parser (3.2.1.1)
parser (3.2.1.0)
ast (~> 2.4.1)
pg (1.4.6)
prettier_print (1.2.1)
prettier_print (1.2.0)
progress (3.6.0)
pry (0.14.2)
coderay (~> 1.1)
@ -329,12 +328,12 @@ GEM
puma (6.1.1)
nio4r (~> 2.0)
racc (1.6.2)
rack (2.2.6.4)
rack (2.2.6.2)
rack-mini-profiler (3.0.0)
rack (>= 1.2.0)
rack-protection (3.0.5)
rack
rack-test (2.1.0)
rack-test (2.0.2)
rack (>= 1.3)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
@ -348,9 +347,9 @@ GEM
rails_multisite (4.0.1)
activerecord (> 5.0, < 7.1)
railties (> 5.0, < 7.1)
railties (7.0.4.3)
actionpack (= 7.0.4.3)
activesupport (= 7.0.4.3)
railties (7.0.4.1)
actionpack (= 7.0.4.1)
activesupport (= 7.0.4.1)
method_source
rake (>= 12.2)
thor (~> 1.0)
@ -391,7 +390,7 @@ GEM
rspec-html-matchers (0.10.0)
nokogiri (~> 1)
rspec (>= 3.0.0.a)
rspec-mocks (3.12.4)
rspec-mocks (3.12.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-rails (6.0.1)
@ -412,7 +411,7 @@ GEM
rspec-core (>= 2.14)
rtlcss (0.2.0)
mini_racer (~> 0.6.3)
rubocop (1.48.1)
rubocop (1.47.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.2.0.0)
@ -426,14 +425,14 @@ GEM
parser (>= 3.2.1.0)
rubocop-capybara (2.17.1)
rubocop (~> 1.41)
rubocop-discourse (3.2.0)
rubocop-discourse (3.1.0)
rubocop (>= 1.1.0)
rubocop-rspec (>= 2.0.0)
rubocop-rspec (2.19.0)
rubocop-rspec (2.18.1)
rubocop (~> 1.33)
rubocop-capybara (~> 2.17)
ruby-prof (1.6.1)
ruby-progressbar (1.13.0)
ruby-progressbar (1.12.0)
ruby-readability (0.7.0)
guess_html_encoding (>= 0.0.4)
nokogiri (>= 1.6.0)
@ -442,16 +441,16 @@ GEM
sanitize (6.0.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
sass-embedded (1.59.2)
sass-embedded (1.58.3)
google-protobuf (~> 3.21)
rake (>= 10.0.0)
sass-embedded (1.59.2-aarch64-linux-gnu)
sass-embedded (1.58.3-aarch64-linux-gnu)
google-protobuf (~> 3.21)
sass-embedded (1.59.2-arm64-darwin)
sass-embedded (1.58.3-arm64-darwin)
google-protobuf (~> 3.21)
sass-embedded (1.59.2-x86_64-darwin)
sass-embedded (1.58.3-x86_64-darwin)
google-protobuf (~> 3.21)
sass-embedded (1.59.2-x86_64-linux-gnu)
sass-embedded (1.58.3-x86_64-linux-gnu)
google-protobuf (~> 3.21)
selenium-webdriver (4.8.1)
rexml (~> 3.2, >= 3.2.5)
@ -478,7 +477,7 @@ GEM
sprockets (>= 3.0.0)
sshkey (2.0.0)
stackprof (0.2.23)
syntax_tree (6.0.2)
syntax_tree (6.0.1)
prettier_print (>= 1.2.0)
syntax_tree-disable_ternary (1.0.0)
test-prof (1.2.0)
@ -534,14 +533,14 @@ PLATFORMS
x86_64-linux
DEPENDENCIES
actionmailer (= 7.0.4.3)
actionpack (= 7.0.4.3)
actionview (= 7.0.4.3)
actionmailer (= 7.0.4.1)
actionpack (= 7.0.4.1)
actionview (= 7.0.4.1)
actionview_precompiler
active_model_serializers (~> 0.8.3)
activemodel (= 7.0.4.3)
activerecord (= 7.0.4.3)
activesupport (= 7.0.4.3)
activemodel (= 7.0.4.1)
activerecord (= 7.0.4.1)
activesupport (= 7.0.4.1)
addressable
annotate
aws-sdk-s3
@ -627,7 +626,7 @@ DEPENDENCIES
rack-protection
rails_failover
rails_multisite
railties (= 7.0.4.3)
railties (= 7.0.4.1)
rake
rb-fsevent
rbtrace

View File

@ -1,13 +1,13 @@
import RestAdapter from "discourse/adapters/rest";
import RESTAdapter from "discourse/adapters/rest";
export default class ApiKey extends RestAdapter {
jsonMode = true;
export default RESTAdapter.extend({
jsonMode: true,
basePath() {
return "/admin/api/";
}
},
apiNameFor() {
return "key";
}
}
},
});

View File

@ -1,11 +1,11 @@
import RestAdapter from "discourse/adapters/rest";
export default function buildPluginAdapter(pluginName) {
return class extends RestAdapter {
return RestAdapter.extend({
pathFor(store, type, findArgs) {
return (
"/admin/plugins/" + pluginName + super.pathFor(store, type, findArgs)
"/admin/plugins/" + pluginName + this._super(store, type, findArgs)
);
}
};
},
});
}

View File

@ -1,7 +1,7 @@
import RestAdapter from "discourse/adapters/rest";
export default class CustomizationBase extends RestAdapter {
export default RestAdapter.extend({
basePath() {
return "/admin/customize/";
}
}
},
});

View File

@ -1,7 +1,7 @@
import RestAdapter from "discourse/adapters/rest";
export default class EmailStyle extends RestAdapter {
export default RestAdapter.extend({
pathFor() {
return "/admin/customize/email_style";
}
}
},
});

View File

@ -1,7 +1,7 @@
import RestAdapter from "discourse/adapters/rest";
export default class Embedding extends RestAdapter {
export default RestAdapter.extend({
pathFor() {
return "/admin/customize/embedding";
}
}
},
});

View File

@ -1,7 +1,7 @@
import RestAdapter from "discourse/adapters/rest";
export default class StaffActionLog extends RestAdapter {
export default RestAdapter.extend({
basePath() {
return "/admin/logs/";
}
}
},
});

View File

@ -1,5 +1,5 @@
import RestAdapter from "discourse/adapters/rest";
export default class TagGroup extends RestAdapter {
jsonMode = true;
}
export default RestAdapter.extend({
jsonMode: true,
});

View File

@ -1,10 +1,9 @@
import RestAdapter from "discourse/adapters/rest";
export default class Theme extends RestAdapter {
jsonMode = true;
export default RestAdapter.extend({
basePath() {
return "/admin/";
}
},
afterFindAll(results) {
let map = {};
@ -21,5 +20,7 @@ export default class Theme extends RestAdapter {
theme.set("parentThemes", mappedParents);
});
return results;
}
}
},
jsonMode: true,
});

View File

@ -1,7 +1,7 @@
import RestAdapter from "discourse/adapters/rest";
import RESTAdapter from "discourse/adapters/rest";
export default class WebHookEvent extends RestAdapter {
export default RESTAdapter.extend({
basePath() {
return "/admin/api/";
}
}
},
});

View File

@ -1,7 +1,7 @@
import RestAdapter from "discourse/adapters/rest";
import RESTAdapter from "discourse/adapters/rest";
export default class WebHook extends RestAdapter {
export default RESTAdapter.extend({
basePath() {
return "/admin/api/";
}
}
},
});

View File

@ -56,7 +56,6 @@
{{#if this.hasInactiveThemes}}
{{#each this.inactiveThemes as |theme|}}
<ThemesListItem
@classNames="inactive-theme"
@theme={{theme}}
@navigateToTheme={{action "navigateToTheme" theme}}
/>

View File

@ -2,18 +2,18 @@ import Controller from "@ember/controller";
import { action } from "@ember/object";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default class AdminApiKeysIndexController extends Controller {
loading = false;
export default Controller.extend({
loading: false,
@action
revokeKey(key) {
key.revoke().catch(popupAjaxError);
}
},
@action
undoRevokeKey(key) {
key.undoRevoke().catch(popupAjaxError);
}
},
@action
loadMore() {
@ -35,5 +35,5 @@ export default class AdminApiKeysIndexController extends Controller {
.finally(() => {
this.set("loading", false);
});
}
}
},
});

View File

@ -1,32 +1,37 @@
import { equal } from "@ember/object/computed";
import Controller from "@ember/controller";
import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators";
import { isBlank } from "@ember/utils";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { action, get } from "@ember/object";
import { equal } from "@ember/object/computed";
import showModal from "discourse/lib/show-modal";
import { ajax } from "discourse/lib/ajax";
export default class AdminApiKeysNewController extends Controller {
userModes = [
{ id: "all", name: I18n.t("admin.api.all_users") },
{ id: "single", name: I18n.t("admin.api.single_user") },
];
scopeModes = [
{ id: "granular", name: I18n.t("admin.api.scopes.granular") },
{ id: "read_only", name: I18n.t("admin.api.scopes.read_only") },
{ id: "global", name: I18n.t("admin.api.scopes.global") },
];
globalScopes = null;
scopes = null;
@equal("userMode", "single") showUserSelector;
export default Controller.extend({
userModes: null,
scopeModes: null,
globalScopes: null,
scopes: null,
init() {
super.init(...arguments);
this._super(...arguments);
this.set("userModes", [
{ id: "all", name: I18n.t("admin.api.all_users") },
{ id: "single", name: I18n.t("admin.api.single_user") },
]);
this.set("scopeModes", [
{ id: "granular", name: I18n.t("admin.api.scopes.granular") },
{ id: "read_only", name: I18n.t("admin.api.scopes.read_only") },
{ id: "global", name: I18n.t("admin.api.scopes.global") },
]);
this._loadScopes();
}
},
showUserSelector: equal("userMode", "single"),
@discourseComputed("model.{description,username}", "showUserSelector")
saveDisabled(model, showUserSelector) {
@ -37,12 +42,12 @@ export default class AdminApiKeysNewController extends Controller {
return true;
}
return false;
}
},
@action
updateUsername(selected) {
this.set("model.username", get(selected, "firstObject"));
}
},
@action
changeUserMode(userMode) {
@ -50,12 +55,12 @@ export default class AdminApiKeysNewController extends Controller {
this.model.set("username", null);
}
this.set("userMode", userMode);
}
},
@action
changeScopeMode(scopeMode) {
this.set("scopeMode", scopeMode);
}
},
@action
save() {
@ -72,12 +77,12 @@ export default class AdminApiKeysNewController extends Controller {
}
return this.model.save().catch(popupAjaxError);
}
},
@action
continue() {
this.transitionToRoute("adminApiKeys.show", this.model.id);
}
},
@action
showURLs(urls) {
@ -85,7 +90,7 @@ export default class AdminApiKeysNewController extends Controller {
admin: true,
model: { urls },
});
}
},
_loadScopes() {
return ajax("/admin/api/keys/scopes.json")
@ -97,5 +102,5 @@ export default class AdminApiKeysNewController extends Controller {
this.set("scopes", data.scopes);
})
.catch(popupAjaxError);
}
}
},
});

View File

@ -1,74 +1,66 @@
import { action } from "@ember/object";
import { empty } from "@ember/object/computed";
import Controller from "@ember/controller";
import { bufferedProperty } from "discourse/mixins/buffered-content";
import { empty } from "@ember/object/computed";
import { isEmpty } from "@ember/utils";
import { popupAjaxError } from "discourse/lib/ajax-error";
import showModal from "discourse/lib/show-modal";
export default class AdminApiKeysShowController extends Controller.extend(
bufferedProperty("model")
) {
@empty("model.id") isNew;
export default Controller.extend(bufferedProperty("model"), {
isNew: empty("model.id"),
@action
saveDescription() {
const buffered = this.buffered;
const attrs = buffered.getProperties("description");
actions: {
saveDescription() {
const buffered = this.buffered;
const attrs = buffered.getProperties("description");
this.model
.save(attrs)
.then(() => {
this.set("editingDescription", false);
this.model
.save(attrs)
.then(() => {
this.set("editingDescription", false);
this.rollbackBuffer();
})
.catch(popupAjaxError);
},
cancel() {
const id = this.get("userField.id");
if (isEmpty(id)) {
this.destroyAction(this.userField);
} else {
this.rollbackBuffer();
})
.catch(popupAjaxError);
}
this.set("editing", false);
}
},
@action
cancel() {
const id = this.get("userField.id");
if (isEmpty(id)) {
this.destroyAction(this.userField);
} else {
this.rollbackBuffer();
this.set("editing", false);
}
}
editDescription() {
this.toggleProperty("editingDescription");
if (!this.editingDescription) {
this.rollbackBuffer();
}
},
@action
editDescription() {
this.toggleProperty("editingDescription");
if (!this.editingDescription) {
this.rollbackBuffer();
}
}
revokeKey(key) {
key.revoke().catch(popupAjaxError);
},
@action
revokeKey(key) {
key.revoke().catch(popupAjaxError);
}
deleteKey(key) {
key
.destroyRecord()
.then(() => this.transitionToRoute("adminApiKeys.index"))
.catch(popupAjaxError);
},
@action
deleteKey(key) {
key
.destroyRecord()
.then(() => this.transitionToRoute("adminApiKeys.index"))
.catch(popupAjaxError);
}
undoRevokeKey(key) {
key.undoRevoke().catch(popupAjaxError);
},
@action
undoRevokeKey(key) {
key.undoRevoke().catch(popupAjaxError);
}
@action
showURLs(urls) {
return showModal("admin-api-key-urls", {
admin: true,
model: {
urls,
},
});
}
}
showURLs(urls) {
return showModal("admin-api-key-urls", {
admin: true,
model: {
urls,
},
});
},
},
});

View File

@ -1,3 +1,3 @@
import Controller from "@ember/controller";
export default class AdminApiKeysController extends Controller {}
export default Controller.extend();

View File

@ -1,21 +1,19 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { alias, equal } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller";
import { alias, equal } from "@ember/object/computed";
import { i18n, setting } from "discourse/lib/computed";
import I18n from "I18n";
import { ajax } from "discourse/lib/ajax";
import discourseComputed from "discourse-common/utils/decorators";
import { inject as service } from "@ember/service";
export default class AdminBackupsIndexController extends Controller {
@service dialog;
@controller adminBackups;
@alias("adminBackups.model") status;
@i18n("admin.backups.upload.label") uploadLabel;
@setting("backup_location") backupLocation;
@equal("backupLocation", "local") localBackupStorage;
export default Controller.extend({
adminBackups: controller(),
dialog: service(),
status: alias("adminBackups.model"),
uploadLabel: i18n("admin.backups.upload.label"),
backupLocation: setting("backup_location"),
localBackupStorage: equal("backupLocation", "local"),
@discourseComputed("status.allowRestore", "status.isOperationRunning")
restoreTitle(allowRestore, isOperationRunning) {
@ -26,35 +24,35 @@ export default class AdminBackupsIndexController extends Controller {
} else {
return "admin.backups.operations.restore.title";
}
}
},
@action
toggleReadOnlyMode() {
if (!this.site.get("isReadOnly")) {
this.dialog.yesNoConfirm({
message: I18n.t("admin.backups.read_only.enable.confirm"),
didConfirm: () => {
this.set("currentUser.hideReadOnlyAlert", true);
this._toggleReadOnlyMode(true);
},
});
} else {
this._toggleReadOnlyMode(false);
}
}
actions: {
toggleReadOnlyMode() {
if (!this.site.get("isReadOnly")) {
this.dialog.yesNoConfirm({
message: I18n.t("admin.backups.read_only.enable.confirm"),
didConfirm: () => {
this.set("currentUser.hideReadOnlyAlert", true);
this._toggleReadOnlyMode(true);
},
});
} else {
this._toggleReadOnlyMode(false);
}
},
@action
download(backup) {
const link = backup.get("filename");
ajax(`/admin/backups/${link}`, { type: "PUT" }).then(() =>
this.dialog.alert(I18n.t("admin.backups.operations.download.alert"))
);
}
download(backup) {
const link = backup.get("filename");
ajax(`/admin/backups/${link}`, { type: "PUT" }).then(() =>
this.dialog.alert(I18n.t("admin.backups.operations.download.alert"))
);
},
},
_toggleReadOnlyMode(enable) {
ajax("/admin/backups/readonly", {
type: "PUT",
data: { enable },
}).then(() => this.site.set("isReadOnly", enable));
}
}
},
});

View File

@ -1,10 +1,13 @@
import { alias } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller";
import { alias } from "@ember/object/computed";
export default class AdminBackupsLogsController extends Controller {
@controller adminBackups;
export default Controller.extend({
adminBackups: controller(),
status: alias("adminBackups.model"),
@alias("adminBackups.model") status;
init() {
this._super(...arguments);
logs = [];
}
this.logs = [];
},
});

View File

@ -1,8 +1,11 @@
import { and, not } from "@ember/object/computed";
import Controller from "@ember/controller";
export default class AdminBackupsController extends Controller {
@not("model.isOperationRunning") noOperationIsRunning;
@not("rollbackEnabled") rollbackDisabled;
@and("model.canRollback", "model.restoreEnabled", "noOperationIsRunning")
rollbackEnabled;
}
export default Controller.extend({
noOperationIsRunning: not("model.isOperationRunning"),
rollbackEnabled: and(
"model.canRollback",
"model.restoreEnabled",
"noOperationIsRunning"
),
rollbackDisabled: not("rollbackEnabled"),
});

View File

@ -1,19 +1,19 @@
import Controller from "@ember/controller";
import EmberObject, { action } from "@ember/object";
import EmberObject from "@ember/object";
import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators";
import showModal from "discourse/lib/show-modal";
export default class AdminCustomizeColorsController extends Controller {
export default Controller.extend({
@discourseComputed("model.@each.id")
baseColorScheme() {
return this.model.findBy("is_base", true);
}
},
@discourseComputed("model.@each.id")
baseColorSchemes() {
return this.model.filterBy("is_base", true);
}
},
@discourseComputed("baseColorScheme")
baseColors(baseColorScheme) {
@ -22,28 +22,28 @@ export default class AdminCustomizeColorsController extends Controller {
baseColorsHash.set(color.get("name"), color);
});
return baseColorsHash;
}
},
@action
newColorSchemeWithBase(baseKey) {
const base = this.baseColorSchemes.findBy("base_scheme_id", baseKey);
const newColorScheme = base.copy();
newColorScheme.setProperties({
name: I18n.t("admin.customize.colors.new_name"),
base_scheme_id: base.get("base_scheme_id"),
});
newColorScheme.save().then(() => {
this.model.pushObject(newColorScheme);
newColorScheme.set("savingStatus", null);
this.replaceRoute("adminCustomize.colors.show", newColorScheme);
});
}
actions: {
newColorSchemeWithBase(baseKey) {
const base = this.baseColorSchemes.findBy("base_scheme_id", baseKey);
const newColorScheme = base.copy();
newColorScheme.setProperties({
name: I18n.t("admin.customize.colors.new_name"),
base_scheme_id: base.get("base_scheme_id"),
});
newColorScheme.save().then(() => {
this.model.pushObject(newColorScheme);
newColorScheme.set("savingStatus", null);
this.replaceRoute("adminCustomize.colors.show", newColorScheme);
});
},
@action
newColorScheme() {
showModal("admin-color-scheme-select-base", {
model: this.baseColorSchemes,
admin: true,
});
}
}
newColorScheme() {
showModal("admin-color-scheme-select-base", {
model: this.baseColorSchemes,
admin: true,
});
},
},
});

View File

@ -1,38 +1,38 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import Controller from "@ember/controller";
import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators";
import { inject as service } from "@ember/service";
export default class AdminCustomizeEmailStyleEditController extends Controller {
@service dialog;
export default Controller.extend({
dialog: service(),
@discourseComputed("model.isSaving")
saveButtonText(isSaving) {
return isSaving ? I18n.t("saving") : I18n.t("admin.customize.save");
}
},
@discourseComputed("model.changed", "model.isSaving")
saveDisabled(changed, isSaving) {
return !changed || isSaving;
}
},
@action
save() {
if (!this.model.saving) {
this.set("saving", true);
this.model
.update(this.model.getProperties("html", "css"))
.catch((e) => {
const msg =
e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors
? I18n.t("admin.customize.email_style.save_error_with_reason", {
error: e.jqXHR.responseJSON.errors.join(". "),
})
: I18n.t("generic_error");
this.dialog.alert(msg);
})
.finally(() => this.set("model.changed", false));
}
}
}
actions: {
save() {
if (!this.model.saving) {
this.set("saving", true);
this.model
.update(this.model.getProperties("html", "css"))
.catch((e) => {
const msg =
e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors
? I18n.t("admin.customize.email_style.save_error_with_reason", {
error: e.jqXHR.responseJSON.errors.join(". "),
})
: I18n.t("generic_error");
this.dialog.alert(msg);
})
.finally(() => this.set("model.changed", false));
}
},
},
});

View File

@ -1,26 +1,23 @@
import { inject as service } from "@ember/service";
import Controller, { inject as controller } from "@ember/controller";
import I18n from "I18n";
import { action } from "@ember/object";
import { bufferedProperty } from "discourse/mixins/buffered-content";
import discourseComputed from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { inject as service } from "@ember/service";
export default class AdminCustomizeEmailTemplatesEditController extends Controller.extend(
bufferedProperty("emailTemplate")
) {
@service dialog;
@controller adminCustomizeEmailTemplates;
emailTemplate = null;
saved = false;
export default Controller.extend(bufferedProperty("emailTemplate"), {
adminCustomizeEmailTemplates: controller(),
dialog: service(),
emailTemplate: null,
saved: false,
@discourseComputed("buffered.body", "buffered.subject")
saveDisabled(body, subject) {
return (
this.emailTemplate.body === body && this.emailTemplate.subject === subject
);
}
},
@discourseComputed("buffered")
hasMultipleSubjects(buffered) {
@ -29,7 +26,7 @@ export default class AdminCustomizeEmailTemplatesEditController extends Controll
} else {
return buffered.getProperties("id")["id"];
}
}
},
@action
saveChanges() {
@ -41,7 +38,7 @@ export default class AdminCustomizeEmailTemplatesEditController extends Controll
this.set("saved", true);
})
.catch(popupAjaxError);
}
},
@action
revertChanges() {
@ -60,5 +57,5 @@ export default class AdminCustomizeEmailTemplatesEditController extends Controll
.catch(popupAjaxError);
},
});
}
}
},
});

View File

@ -1,13 +1,18 @@
import { sort } from "@ember/object/computed";
import Controller from "@ember/controller";
import { action } from "@ember/object";
import { sort } from "@ember/object/computed";
export default class AdminCustomizeEmailTemplatesController extends Controller {
titleSorting = ["title"];
@sort("emailTemplates", "titleSorting") sortedTemplates;
export default Controller.extend({
sortedTemplates: sort("emailTemplates", "titleSorting"),
init() {
this._super(...arguments);
this.set("titleSorting", ["title"]);
},
@action
onSelectTemplate(template) {
this.transitionToRoute("adminCustomizeEmailTemplates.edit", template);
}
}
},
});

View File

@ -1,52 +1,47 @@
import { action } from "@ember/object";
import { not } from "@ember/object/computed";
import Controller from "@ember/controller";
import { ajax } from "discourse/lib/ajax";
import { bufferedProperty } from "discourse/mixins/buffered-content";
import { not } from "@ember/object/computed";
import { propertyEqual } from "discourse/lib/computed";
export default class AdminCustomizeRobotsTxtController extends Controller.extend(
bufferedProperty("model")
) {
saved = false;
isSaving = false;
export default Controller.extend(bufferedProperty("model"), {
saved: false,
isSaving: false,
saveDisabled: propertyEqual("model.robots_txt", "buffered.robots_txt"),
resetDisabled: not("model.overridden"),
@propertyEqual("model.robots_txt", "buffered.robots_txt") saveDisabled;
actions: {
save() {
this.setProperties({
isSaving: true,
saved: false,
});
@not("model.overridden") resetDisabled;
@action
save() {
this.setProperties({
isSaving: true,
saved: false,
});
ajax("robots.json", {
type: "PUT",
data: { robots_txt: this.buffered.get("robots_txt") },
})
.then((data) => {
this.commitBuffer();
this.set("saved", true);
this.set("model.overridden", data.overridden);
ajax("robots.json", {
type: "PUT",
data: { robots_txt: this.buffered.get("robots_txt") },
})
.finally(() => this.set("isSaving", false));
}
.then((data) => {
this.commitBuffer();
this.set("saved", true);
this.set("model.overridden", data.overridden);
})
.finally(() => this.set("isSaving", false));
},
@action
reset() {
this.setProperties({
isSaving: true,
saved: false,
});
ajax("robots.json", { type: "DELETE" })
.then((data) => {
this.buffered.set("robots_txt", data.robots_txt);
this.commitBuffer();
this.set("saved", true);
this.set("model.overridden", false);
})
.finally(() => this.set("isSaving", false));
}
}
reset() {
this.setProperties({
isSaving: true,
saved: false,
});
ajax("robots.json", { type: "DELETE" })
.then((data) => {
this.buffered.set("robots_txt", data.robots_txt);
this.commitBuffer();
this.set("saved", true);
this.set("model.overridden", false);
})
.finally(() => this.set("isSaving", false));
},
},
});

View File

@ -1,24 +1,21 @@
import { action } from "@ember/object";
import Controller from "@ember/controller";
import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators";
import { url } from "discourse/lib/computed";
export default class AdminCustomizeThemesEditController extends Controller {
section = null;
currentTarget = 0;
maximized = false;
@url("model.id", "/admin/themes/%@/preview") previewUrl;
showAdvanced = false;
editRouteName = "adminCustomizeThemes.edit";
showRouteName = "adminCustomizeThemes.show";
export default Controller.extend({
section: null,
currentTarget: 0,
maximized: false,
previewUrl: url("model.id", "/admin/themes/%@/preview"),
showAdvanced: false,
editRouteName: "adminCustomizeThemes.edit",
showRouteName: "adminCustomizeThemes.show",
setTargetName(name) {
const target = this.get("model.targets").find((t) => t.name === name);
this.set("currentTarget", target && target.id);
}
},
@discourseComputed("currentTarget")
currentTargetName(id) {
@ -26,52 +23,50 @@ export default class AdminCustomizeThemesEditController extends Controller {
(t) => t.id === parseInt(id, 10)
);
return target && target.name;
}
},
@discourseComputed("model.isSaving")
saveButtonText(isSaving) {
return isSaving ? I18n.t("saving") : I18n.t("admin.customize.save");
}
},
@discourseComputed("model.changed", "model.isSaving")
saveDisabled(changed, isSaving) {
return !changed || isSaving;
}
},
@action
save() {
this.set("saving", true);
this.model.saveChanges("theme_fields").finally(() => {
this.set("saving", false);
});
}
actions: {
save() {
this.set("saving", true);
this.model.saveChanges("theme_fields").finally(() => {
this.set("saving", false);
});
},
@action
fieldAdded(target, name) {
this.replaceRoute(this.editRouteName, this.get("model.id"), target, name);
}
fieldAdded(target, name) {
this.replaceRoute(this.editRouteName, this.get("model.id"), target, name);
},
@action
onlyOverriddenChanged(onlyShowOverridden) {
if (onlyShowOverridden) {
if (!this.model.hasEdited(this.currentTargetName, this.fieldName)) {
let firstTarget = this.get("model.targets").find((t) => t.edited);
let firstField = this.get(`model.fields.${firstTarget.name}`).find(
(f) => f.edited
);
onlyOverriddenChanged(onlyShowOverridden) {
if (onlyShowOverridden) {
if (!this.model.hasEdited(this.currentTargetName, this.fieldName)) {
let firstTarget = this.get("model.targets").find((t) => t.edited);
let firstField = this.get(`model.fields.${firstTarget.name}`).find(
(f) => f.edited
);
this.replaceRoute(
this.editRouteName,
this.get("model.id"),
firstTarget.name,
firstField.name
);
this.replaceRoute(
this.editRouteName,
this.get("model.id"),
firstTarget.name,
firstField.name
);
}
}
}
}
},
@action
goBack() {
this.replaceRoute(this.showRouteName, this.model.id);
}
}
goBack() {
this.replaceRoute(this.showRouteName, this.model.id);
},
},
});

View File

@ -1,4 +1,4 @@
import { inject as service } from "@ember/service";
import { COMPONENTS, THEMES } from "admin/models/theme";
import {
empty,
filterBy,
@ -6,9 +6,8 @@ import {
match,
notEmpty,
} from "@ember/object/computed";
import { COMPONENTS, THEMES } from "admin/models/theme";
import Controller from "@ember/controller";
import EmberObject, { action } from "@ember/object";
import EmberObject from "@ember/object";
import I18n from "I18n";
import ThemeSettings from "admin/models/theme-settings";
import discourseComputed from "discourse-common/utils/decorators";
@ -16,35 +15,31 @@ import { makeArray } from "discourse-common/lib/helpers";
import { popupAjaxError } from "discourse/lib/ajax-error";
import showModal from "discourse/lib/show-modal";
import { url } from "discourse/lib/computed";
import { inject as service } from "@ember/service";
const THEME_UPLOAD_VAR = 2;
export default class AdminCustomizeThemesShowController extends Controller {
@service dialog;
editRouteName = "adminCustomizeThemes.edit";
@url("model.id", "/admin/customize/themes/%@/export") downloadUrl;
@url("model.id", "/admin/themes/%@/preview") previewUrl;
@empty("selectedChildThemeId") addButtonDisabled;
@mapBy("model.parentThemes", "name") parentThemesNames;
@filterBy("allThemes", "component", false) availableParentThemes;
@filterBy("availableParentThemes", "isActive") availableActiveParentThemes;
@mapBy("availableParentThemes", "name") availableThemesNames;
@mapBy("availableActiveParentThemes", "name") availableActiveThemesNames;
@filterBy("availableChildThemes", "hasParents") availableActiveChildThemes;
@mapBy("availableChildThemes", "name") availableComponentsNames;
@mapBy("availableActiveChildThemes", "name") availableActiveComponentsNames;
@mapBy("model.childThemes", "name") childThemesNames;
@filterBy("model.theme_fields", "target", "extra_js") extraFiles;
@notEmpty("settings") hasSettings;
@notEmpty("translations") hasTranslations;
@match("model.remote_theme.remote_url", /^http(s)?:\/\//) sourceIsHttp;
export default Controller.extend({
dialog: service(),
downloadUrl: url("model.id", "/admin/customize/themes/%@/export"),
previewUrl: url("model.id", "/admin/themes/%@/preview"),
addButtonDisabled: empty("selectedChildThemeId"),
editRouteName: "adminCustomizeThemes.edit",
parentThemesNames: mapBy("model.parentThemes", "name"),
availableParentThemes: filterBy("allThemes", "component", false),
availableActiveParentThemes: filterBy("availableParentThemes", "isActive"),
availableThemesNames: mapBy("availableParentThemes", "name"),
availableActiveThemesNames: mapBy("availableActiveParentThemes", "name"),
availableActiveChildThemes: filterBy("availableChildThemes", "hasParents"),
availableComponentsNames: mapBy("availableChildThemes", "name"),
availableActiveComponentsNames: mapBy("availableActiveChildThemes", "name"),
childThemesNames: mapBy("model.childThemes", "name"),
extraFiles: filterBy("model.theme_fields", "target", "extra_js"),
@discourseComputed("model.component", "model.remote_theme")
showCheckboxes() {
return !this.model.component || this.model.remote_theme;
}
},
@discourseComputed("model.editedFields")
editedFieldsFormatted() {
@ -62,13 +57,13 @@ export default class AdminCustomizeThemesShowController extends Controller {
descriptions.push(resultString);
});
return descriptions;
}
},
@discourseComputed("colorSchemeId", "model.color_scheme_id")
colorSchemeChanged(colorSchemeId, existingId) {
colorSchemeId = colorSchemeId === null ? null : parseInt(colorSchemeId, 10);
return colorSchemeId !== existingId;
}
},
@discourseComputed("availableChildThemes", "model.childThemes.[]", "model")
selectableChildThemes(available, childThemes) {
@ -78,7 +73,7 @@ export default class AdminCustomizeThemesShowController extends Controller {
: available.filter((theme) => !childThemes.includes(theme));
return themes.length === 0 ? null : themes;
}
}
},
@discourseComputed("model.parentThemes.[]")
relativesSelectorSettingsForComponent() {
@ -96,7 +91,7 @@ export default class AdminCustomizeThemesShowController extends Controller {
allThemes: this.allThemes,
setDefaultValuesLabel: I18n.t("admin.customize.theme.add_all_themes"),
});
}
},
@discourseComputed("model.parentThemes.[]")
relativesSelectorSettingsForTheme() {
@ -114,7 +109,7 @@ export default class AdminCustomizeThemesShowController extends Controller {
allThemes: this.allThemes,
setDefaultValuesLabel: I18n.t("admin.customize.theme.add_all"),
});
}
},
@discourseComputed("allThemes", "model.component", "model")
availableChildThemes(allThemes) {
@ -124,36 +119,40 @@ export default class AdminCustomizeThemesShowController extends Controller {
(theme) => theme.get("id") !== themeId && theme.get("component")
);
}
}
},
@discourseComputed("model.component")
convertKey(component) {
const type = component ? "component" : "theme";
return `admin.customize.theme.convert_${type}`;
}
},
@discourseComputed("model.component")
convertIcon(component) {
return component ? "cube" : "";
}
},
@discourseComputed("model.component")
convertTooltip(component) {
const type = component ? "component" : "theme";
return `admin.customize.theme.convert_${type}_tooltip`;
}
},
@discourseComputed("model.settings")
settings(settings) {
return settings.map((setting) => ThemeSettings.create(setting));
}
},
hasSettings: notEmpty("settings"),
@discourseComputed("model.translations")
translations(translations) {
return translations.map((setting) =>
ThemeSettings.create({ ...setting, textarea: true })
);
}
},
hasTranslations: notEmpty("translations"),
@discourseComputed(
"model.remote_theme.local_version",
@ -162,12 +161,12 @@ export default class AdminCustomizeThemesShowController extends Controller {
)
hasOverwrittenHistory(localVersion, remoteVersion, commitsBehind) {
return localVersion !== remoteVersion && commitsBehind === -1;
}
},
@discourseComputed("model.remoteError", "updatingRemote")
showRemoteError(errorMessage, updating) {
return errorMessage && !updating;
}
},
@discourseComputed(
"model.remote_theme.remote_url",
@ -176,13 +175,13 @@ export default class AdminCustomizeThemesShowController extends Controller {
)
finishInstall(remoteUrl, localVersion, commitsBehind) {
return remoteUrl && !localVersion && !commitsBehind;
}
},
editedFieldsForTarget(target) {
return this.get("model.editedFields").filter(
(field) => field.target === target
);
}
},
commitSwitchType() {
const model = this.model;
@ -223,8 +222,7 @@ export default class AdminCustomizeThemesShowController extends Controller {
});
})
.catch(popupAjaxError);
}
},
transitionToEditRoute() {
this.transitionToRoute(
this.editRouteName,
@ -232,7 +230,8 @@ export default class AdminCustomizeThemesShowController extends Controller {
"common",
"scss"
);
}
},
sourceIsHttp: match("model.remote_theme.remote_url", /^http(s)?:\/\//),
@discourseComputed(
"model.remote_theme.remote_url",
@ -242,186 +241,168 @@ export default class AdminCustomizeThemesShowController extends Controller {
return remoteThemeBranch
? `${remoteThemeUrl.replace(/\.git$/, "")}/tree/${remoteThemeBranch}`
: remoteThemeUrl;
}
},
@discourseComputed("model.user.id", "model.default")
showConvert(userId, defaultTheme) {
return userId > 0 && !defaultTheme;
}
},
@action
updateToLatest() {
this.set("updatingRemote", true);
this.model
.updateToLatest()
.catch(popupAjaxError)
.finally(() => {
this.set("updatingRemote", false);
actions: {
updateToLatest() {
this.set("updatingRemote", true);
this.model
.updateToLatest()
.catch(popupAjaxError)
.finally(() => {
this.set("updatingRemote", false);
});
},
checkForThemeUpdates() {
this.set("updatingRemote", true);
this.model
.checkForUpdates()
.catch(popupAjaxError)
.finally(() => {
this.set("updatingRemote", false);
});
},
addUploadModal() {
showModal("admin-add-upload", { admin: true, name: "" });
},
addUpload(info) {
let model = this.model;
model.setField("common", info.name, "", info.upload_id, THEME_UPLOAD_VAR);
model.saveChanges("theme_fields").catch((e) => popupAjaxError(e));
},
cancelChangeScheme() {
this.set("colorSchemeId", this.get("model.color_scheme_id"));
},
changeScheme() {
let schemeId = this.colorSchemeId;
this.set(
"model.color_scheme_id",
schemeId === null ? null : parseInt(schemeId, 10)
);
this.model.saveChanges("color_scheme_id");
},
startEditingName() {
this.set("oldName", this.get("model.name"));
this.set("editingName", true);
},
cancelEditingName() {
this.set("model.name", this.oldName);
this.set("editingName", false);
},
finishedEditingName() {
this.model.saveChanges("name");
this.set("editingName", false);
},
editTheme() {
if (this.get("model.remote_theme.is_git")) {
this.dialog.confirm({
message: I18n.t("admin.customize.theme.edit_confirm"),
didConfirm: () => this.transitionToEditRoute(),
});
} else {
this.transitionToEditRoute();
}
},
applyDefault() {
const model = this.model;
model.saveChanges("default").then(() => {
if (model.get("default")) {
this.allThemes.forEach((theme) => {
if (theme !== model && theme.get("default")) {
theme.set("default", false);
}
});
}
});
}
},
@action
checkForThemeUpdates() {
this.set("updatingRemote", true);
this.model
.checkForUpdates()
.catch(popupAjaxError)
.finally(() => {
this.set("updatingRemote", false);
applyUserSelectable() {
this.model.saveChanges("user_selectable");
},
applyAutoUpdateable() {
this.model.saveChanges("auto_update");
},
addChildTheme() {
let themeId = parseInt(this.selectedChildThemeId, 10);
let theme = this.allThemes.findBy("id", themeId);
this.model.addChildTheme(theme).then(() => this.store.findAll("theme"));
},
removeUpload(upload) {
return this.dialog.yesNoConfirm({
message: I18n.t("admin.customize.theme.delete_upload_confirm"),
didConfirm: () => this.model.removeField(upload),
});
}
},
@action
addUploadModal() {
showModal("admin-add-upload", { admin: true, name: "" });
}
removeChildTheme(theme) {
this.model
.removeChildTheme(theme)
.then(() => this.store.findAll("theme"));
},
@action
addUpload(info) {
let model = this.model;
model.setField("common", info.name, "", info.upload_id, THEME_UPLOAD_VAR);
model.saveChanges("theme_fields").catch((e) => popupAjaxError(e));
}
@action
cancelChangeScheme() {
this.set("colorSchemeId", this.get("model.color_scheme_id"));
}
@action
changeScheme() {
let schemeId = this.colorSchemeId;
this.set(
"model.color_scheme_id",
schemeId === null ? null : parseInt(schemeId, 10)
);
this.model.saveChanges("color_scheme_id");
}
@action
startEditingName() {
this.set("oldName", this.get("model.name"));
this.set("editingName", true);
}
@action
cancelEditingName() {
this.set("model.name", this.oldName);
this.set("editingName", false);
}
@action
finishedEditingName() {
this.model.saveChanges("name");
this.set("editingName", false);
}
@action
editTheme() {
if (this.get("model.remote_theme.is_git")) {
this.dialog.confirm({
message: I18n.t("admin.customize.theme.edit_confirm"),
didConfirm: () => this.transitionToEditRoute(),
destroy() {
return this.dialog.yesNoConfirm({
message: I18n.t("admin.customize.delete_confirm", {
theme_name: this.get("model.name"),
}),
didConfirm: () => {
const model = this.model;
model.setProperties({ recentlyInstalled: false });
model.destroyRecord().then(() => {
this.allThemes.removeObject(model);
this.transitionToRoute("adminCustomizeThemes");
});
},
});
} else {
this.transitionToEditRoute();
}
}
},
@action
applyDefault() {
const model = this.model;
model.saveChanges("default").then(() => {
if (model.get("default")) {
this.allThemes.forEach((theme) => {
if (theme !== model && theme.get("default")) {
theme.set("default", false);
}
switchType() {
const relatives = this.get("model.component")
? this.get("model.parentThemes")
: this.get("model.childThemes");
let message = I18n.t(`${this.convertKey}_alert_generic`);
if (relatives && relatives.length > 0) {
message = I18n.t(`${this.convertKey}_alert`, {
relatives: relatives
.map((relative) => relative.get("name"))
.join(", "),
});
}
});
}
@action
applyUserSelectable() {
this.model.saveChanges("user_selectable");
}
@action
applyAutoUpdateable() {
this.model.saveChanges("auto_update");
}
@action
addChildTheme() {
let themeId = parseInt(this.selectedChildThemeId, 10);
let theme = this.allThemes.findBy("id", themeId);
this.model.addChildTheme(theme).then(() => this.store.findAll("theme"));
}
@action
removeUpload(upload) {
return this.dialog.yesNoConfirm({
message: I18n.t("admin.customize.theme.delete_upload_confirm"),
didConfirm: () => this.model.removeField(upload),
});
}
@action
removeChildTheme(theme) {
this.model.removeChildTheme(theme).then(() => this.store.findAll("theme"));
}
@action
destroyTheme() {
return this.dialog.yesNoConfirm({
message: I18n.t("admin.customize.delete_confirm", {
theme_name: this.get("model.name"),
}),
didConfirm: () => {
const model = this.model;
model.setProperties({ recentlyInstalled: false });
model.destroyRecord().then(() => {
this.allThemes.removeObject(model);
this.transitionToRoute("adminCustomizeThemes");
});
},
});
}
@action
switchType() {
const relatives = this.get("model.component")
? this.get("model.parentThemes")
: this.get("model.childThemes");
let message = I18n.t(`${this.convertKey}_alert_generic`);
if (relatives && relatives.length > 0) {
message = I18n.t(`${this.convertKey}_alert`, {
relatives: relatives.map((relative) => relative.get("name")).join(", "),
return this.dialog.yesNoConfirm({
message,
didConfirm: () => this.commitSwitchType(),
});
}
},
return this.dialog.yesNoConfirm({
message,
didConfirm: () => this.commitSwitchType(),
});
}
enableComponent() {
this.model.set("enabled", true);
this.model
.saveChanges("enabled")
.catch(() => this.model.set("enabled", false));
},
@action
enableComponent() {
this.model.set("enabled", true);
this.model
.saveChanges("enabled")
.catch(() => this.model.set("enabled", false));
}
@action
disableComponent() {
this.model.set("enabled", false);
this.model
.saveChanges("enabled")
.catch(() => this.model.set("enabled", true));
}
}
disableComponent() {
this.model.set("enabled", false);
this.model
.saveChanges("enabled")
.catch(() => this.model.set("enabled", true));
},
},
});

View File

@ -2,21 +2,21 @@ import Controller from "@ember/controller";
import { THEMES } from "admin/models/theme";
import discourseComputed from "discourse-common/utils/decorators";
export default class AdminCustomizeThemesController extends Controller {
currentTab = THEMES;
export default Controller.extend({
currentTab: THEMES,
@discourseComputed("model", "model.@each.component")
fullThemes(themes) {
return themes.filter((t) => !t.get("component"));
}
},
@discourseComputed("model", "model.@each.component")
childThemes(themes) {
return themes.filter((t) => t.get("component"));
}
},
@discourseComputed("model.content")
installedThemes(content) {
return content || [];
}
}
},
});

View File

@ -1,9 +1,9 @@
import { computed } from "@ember/object";
import Controller, { inject as controller } from "@ember/controller";
import AdminDashboard from "admin/models/admin-dashboard";
import I18n from "I18n";
import PeriodComputationMixin from "admin/mixins/period-computation";
import Report from "admin/models/report";
import { computed } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators";
import getURL from "discourse-common/lib/get-url";
import { makeArray } from "discourse-common/lib/helpers";
@ -15,49 +15,41 @@ function staticReport(reportType) {
});
}
export default class AdminDashboardGeneralController extends Controller.extend(
PeriodComputationMixin
) {
@controller("exception") exceptionController;
isLoading = false;
dashboardFetchedAt = null;
@setting("log_search_queries") logSearchQueriesEnabled;
@staticReport("users_by_type") usersByTypeReport;
@staticReport("users_by_trust_level") usersByTrustLevelReport;
@staticReport("storage_report") storageReport;
export default Controller.extend(PeriodComputationMixin, {
isLoading: false,
dashboardFetchedAt: null,
exceptionController: controller("exception"),
logSearchQueriesEnabled: setting("log_search_queries"),
@discourseComputed("siteSettings.dashboard_general_tab_activity_metrics")
activityMetrics(metrics) {
return (metrics || "").split("|").filter(Boolean);
}
},
@computed("siteSettings.dashboard_hidden_reports")
get hiddenReports() {
hiddenReports: computed("siteSettings.dashboard_hidden_reports", function () {
return (this.siteSettings.dashboard_hidden_reports || "")
.split("|")
.filter(Boolean);
}
}),
@computed("activityMetrics", "hiddenReports")
get isActivityMetricsVisible() {
return (
this.activityMetrics.length &&
this.activityMetrics.some((x) => !this.hiddenReports.includes(x))
);
}
isActivityMetricsVisible: computed(
"activityMetrics",
"hiddenReports",
function () {
return (
this.activityMetrics.length &&
this.activityMetrics.some((x) => !this.hiddenReports.includes(x))
);
}
),
@computed("hiddenReports")
get isSearchReportsVisible() {
isSearchReportsVisible: computed("hiddenReports", function () {
return ["top_referred_topics", "trending_search"].some(
(x) => !this.hiddenReports.includes(x)
);
}
}),
@computed("hiddenReports")
get isCommunityHealthVisible() {
isCommunityHealthVisible: computed("hiddenReports", function () {
return [
"consolidated_page_views",
"signups",
@ -67,7 +59,7 @@ export default class AdminDashboardGeneralController extends Controller.extend(
"daily_engaged_users",
"new_contributors",
].some((x) => !this.hiddenReports.includes(x));
}
}),
@discourseComputed
activityMetricsFilters() {
@ -75,14 +67,14 @@ export default class AdminDashboardGeneralController extends Controller.extend(
startDate: this.lastMonth,
endDate: this.today,
};
}
},
@discourseComputed
topReferredTopicsOptions() {
return {
table: { total: false, limit: 8 },
};
}
},
@discourseComputed
topReferredTopicsFilters() {
@ -90,7 +82,7 @@ export default class AdminDashboardGeneralController extends Controller.extend(
startDate: moment().subtract(6, "days").startOf("day"),
endDate: this.today,
};
}
},
@discourseComputed
trendingSearchFilters() {
@ -98,21 +90,25 @@ export default class AdminDashboardGeneralController extends Controller.extend(
startDate: moment().subtract(1, "month").startOf("day"),
endDate: this.today,
};
}
},
@discourseComputed
trendingSearchOptions() {
return {
table: { total: false, limit: 8 },
};
}
},
@discourseComputed
trendingSearchDisabledLabel() {
return I18n.t("admin.dashboard.reports.trending_search.disabled", {
basePath: getURL(""),
});
}
},
usersByTypeReport: staticReport("users_by_type"),
usersByTrustLevelReport: staticReport("users_by_trust_level"),
storageReport: staticReport("storage_report"),
fetchDashboard() {
if (this.isLoading) {
@ -141,14 +137,14 @@ export default class AdminDashboardGeneralController extends Controller.extend(
})
.finally(() => this.set("isLoading", false));
}
}
},
@discourseComputed("startDate", "endDate")
filters(startDate, endDate) {
return { startDate, endDate };
}
},
_reportsForPeriodURL(period) {
return getURL(`/admin?period=${period}`);
}
}
},
});

View File

@ -1,12 +1,10 @@
import { computed } from "@ember/object";
import Controller from "@ember/controller";
import PeriodComputationMixin from "admin/mixins/period-computation";
import { computed } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators";
import getURL from "discourse-common/lib/get-url";
export default class AdminDashboardModerationController extends Controller.extend(
PeriodComputationMixin
) {
export default Controller.extend(PeriodComputationMixin, {
@discourseComputed
flagsStatusOptions() {
return {
@ -15,15 +13,17 @@ export default class AdminDashboardModerationController extends Controller.exten
perPage: 10,
},
};
}
},
@computed("siteSettings.dashboard_hidden_reports")
get isModeratorsActivityVisible() {
return !(this.siteSettings.dashboard_hidden_reports || "")
.split("|")
.filter(Boolean)
.includes("moderators_activity");
}
isModeratorsActivityVisible: computed(
"siteSettings.dashboard_hidden_reports",
function () {
return !(this.siteSettings.dashboard_hidden_reports || "")
.split("|")
.filter(Boolean)
.includes("moderators_activity");
}
),
@discourseComputed
userFlaggingRatioOptions() {
@ -33,19 +33,19 @@ export default class AdminDashboardModerationController extends Controller.exten
perPage: 10,
},
};
}
},
@discourseComputed("startDate", "endDate")
filters(startDate, endDate) {
return { startDate, endDate };
}
},
@discourseComputed("lastWeek", "endDate")
lastWeekfilters(startDate, endDate) {
return { startDate, endDate };
}
},
_reportsForPeriodURL(period) {
return getURL(`/admin/dashboard/moderation?period=${period}`);
}
}
},
});

View File

@ -2,10 +2,10 @@ import Controller from "@ember/controller";
import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseComputed from "discourse-common/utils/decorators";
import discourseDebounce from "discourse-common/lib/debounce";
import { action, get } from "@ember/object";
import { get } from "@ember/object";
export default class AdminDashboardReportsController extends Controller {
filter = null;
export default Controller.extend({
filter: null,
@discourseComputed(
"model.[]",
@ -29,14 +29,15 @@ export default class AdminDashboardReportsController extends Controller {
reports = reports.filter((report) => !hiddenReports.includes(report.type));
return reports;
}
},
@action
filterReports(filter) {
discourseDebounce(this, this._performFiltering, filter, INPUT_DELAY);
}
actions: {
filterReports(filter) {
discourseDebounce(this, this._performFiltering, filter, INPUT_DELAY);
},
},
_performFiltering(filter) {
this.set("filter", filter);
}
}
},
});

View File

@ -1,19 +1,17 @@
import { action, computed } from "@ember/object";
import Controller, { inject as controller } from "@ember/controller";
import AdminDashboard from "admin/models/admin-dashboard";
import VersionCheck from "admin/models/version-check";
import { computed } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators";
import { setting } from "discourse/lib/computed";
const PROBLEMS_CHECK_MINUTES = 1;
export default class AdminDashboardController extends Controller {
@controller("exception") exceptionController;
isLoading = false;
dashboardFetchedAt = null;
@setting("version_checks") showVersionChecks;
export default Controller.extend({
isLoading: false,
dashboardFetchedAt: null,
exceptionController: controller("exception"),
showVersionChecks: setting("version_checks"),
@discourseComputed(
"lowPriorityProblems.length",
@ -23,29 +21,25 @@ export default class AdminDashboardController extends Controller {
const problemsLength =
lowPriorityProblemsLength + highPriorityProblemsLength;
return this.currentUser.admin && problemsLength > 0;
}
},
@computed("siteSettings.dashboard_visible_tabs")
get visibleTabs() {
visibleTabs: computed("siteSettings.dashboard_visible_tabs", function () {
return (this.siteSettings.dashboard_visible_tabs || "")
.split("|")
.filter(Boolean);
}
}),
@computed("visibleTabs")
get isModerationTabVisible() {
isModerationTabVisible: computed("visibleTabs", function () {
return this.visibleTabs.includes("moderation");
}
}),
@computed("visibleTabs")
get isSecurityTabVisible() {
isSecurityTabVisible: computed("visibleTabs", function () {
return this.visibleTabs.includes("security");
}
}),
@computed("visibleTabs")
get isReportsTabVisible() {
isReportsTabVisible: computed("visibleTabs", function () {
return this.visibleTabs.includes("reports");
}
}),
fetchProblems() {
if (this.isLoadingProblems) {
@ -59,7 +53,7 @@ export default class AdminDashboardController extends Controller {
) {
this._loadProblems();
}
}
},
fetchDashboard() {
const versionChecks = this.siteSettings.version_checks;
@ -94,7 +88,7 @@ export default class AdminDashboardController extends Controller {
this.set("isLoading", false);
});
}
}
},
_loadProblems() {
this.setProperties({
@ -114,15 +108,16 @@ export default class AdminDashboardController extends Controller {
);
})
.finally(() => this.set("loadingProblems", false));
}
},
@discourseComputed("problemsFetchedAt")
problemsTimestamp(problemsFetchedAt) {
return moment(problemsFetchedAt).locale("en").format("LLL");
}
},
@action
refreshProblems() {
this._loadProblems();
}
}
actions: {
refreshProblems() {
this._loadProblems();
},
},
});

View File

@ -1,31 +1,31 @@
import { action } from "@ember/object";
import Controller from "@ember/controller";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default class AdminEmailAdvancedTestController extends Controller {
email = null;
text = null;
elided = null;
format = null;
loading = null;
export default Controller.extend({
email: null,
text: null,
elided: null,
format: null,
loading: null,
@action
run() {
this.set("loading", true);
actions: {
run() {
this.set("loading", true);
ajax("/admin/email/advanced-test", {
type: "POST",
data: { email: this.email },
})
.then((data) => {
this.setProperties({
text: data.text,
elided: data.elided,
format: data.format,
});
ajax("/admin/email/advanced-test", {
type: "POST",
data: { email: this.email },
})
.catch(popupAjaxError)
.finally(() => this.set("loading", false));
}
}
.then((data) => {
this.setProperties({
text: data.text,
elided: data.elided,
format: data.format,
});
})
.catch(popupAjaxError)
.finally(() => this.set("loading", false));
},
},
});

View File

@ -1,18 +1,18 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "@ember-decorators/object";
import { observes } from "discourse-common/utils/decorators";
import { action } from "@ember/object";
export default class AdminEmailBouncedController extends AdminEmailLogsController {
export default AdminEmailLogsController.extend({
@action
handleShowIncomingEmail(id, event) {
event?.preventDefault();
this.send("showIncomingEmail", id);
}
},
@observes("filter.{status,user,address,type}")
filterEmailLogs() {
discourseDebounce(this, this.loadLogs, INPUT_DELAY);
}
}
},
});

View File

@ -1,22 +1,21 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { empty } from "@ember/object/computed";
import Controller from "@ember/controller";
import I18n from "I18n";
import { ajax } from "discourse/lib/ajax";
import { observes } from "@ember-decorators/object";
import { empty } from "@ember/object/computed";
import { observes } from "discourse-common/utils/decorators";
import { inject as service } from "@ember/service";
import { htmlSafe } from "@ember/template";
import { escapeExpression } from "discourse/lib/utilities";
export default class AdminEmailIndexController extends Controller {
@service dialog;
export default Controller.extend({
dialog: service(),
/**
Is the "send test email" button disabled?
@property sendTestEmailDisabled
**/
@empty("testEmailAddress") sendTestEmailDisabled;
sendTestEmailDisabled: empty("testEmailAddress"),
/**
Clears the 'sentTestEmail' property on successful send.
@ -26,40 +25,43 @@ export default class AdminEmailIndexController extends Controller {
@observes("testEmailAddress")
testEmailAddressChanged() {
this.set("sentTestEmail", false);
}
},
/**
Sends a test email to the currently entered email address
actions: {
/**
Sends a test email to the currently entered email address
@method sendTestEmail
**/
@action
sendTestEmail() {
this.setProperties({
sendingEmail: true,
sentTestEmail: false,
});
@method sendTestEmail
**/
sendTestEmail() {
this.setProperties({
sendingEmail: true,
sentTestEmail: false,
});
ajax("/admin/email/test", {
type: "POST",
data: { email_address: this.testEmailAddress },
})
.then((response) =>
this.set("sentTestEmailMessage", response.sent_test_email_message)
)
.catch((e) => {
if (e.jqXHR.responseJSON?.errors) {
this.dialog.alert({
message: htmlSafe(
I18n.t("admin.email.error", {
server_error: escapeExpression(e.jqXHR.responseJSON.errors[0]),
})
),
});
} else {
this.dialog.alert({ message: I18n.t("admin.email.test_error") });
}
ajax("/admin/email/test", {
type: "POST",
data: { email_address: this.testEmailAddress },
})
.finally(() => this.set("sendingEmail", false));
}
}
.then((response) =>
this.set("sentTestEmailMessage", response.sent_test_email_message)
)
.catch((e) => {
if (e.jqXHR.responseJSON?.errors) {
this.dialog.alert({
message: htmlSafe(
I18n.t("admin.email.error", {
server_error: escapeExpression(
e.jqXHR.responseJSON.errors[0]
),
})
),
});
} else {
this.dialog.alert({ message: I18n.t("admin.email.test_error") });
}
})
.finally(() => this.set("sendingEmail", false));
},
},
});

View File

@ -1,11 +1,14 @@
import Controller from "@ember/controller";
import EmailLog from "admin/models/email-log";
import EmberObject, { action } from "@ember/object";
import EmberObject from "@ember/object";
export default class AdminEmailLogsController extends Controller {
loading = false;
filter = EmberObject.create();
export default Controller.extend({
loading: false,
init() {
this._super(...arguments);
this.set("filter", EmberObject.create());
},
loadLogs(sourceModel, loadMore) {
if ((loadMore && this.loading) || this.get("model.allLoaded")) {
return;
@ -35,10 +38,11 @@ export default class AdminEmailLogsController extends Controller {
}
})
.finally(() => this.set("loading", false));
}
},
@action
loadMore() {
this.loadLogs(EmailLog, true);
}
}
actions: {
loadMore() {
this.loadLogs(EmailLog, true);
},
},
});

View File

@ -1,67 +1,66 @@
import { inject as service } from "@ember/service";
import { empty, notEmpty, or } from "@ember/object/computed";
import Controller from "@ember/controller";
import EmailPreview from "admin/models/email-preview";
import { action, get } from "@ember/object";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { inject as service } from "@ember/service";
export default class AdminEmailPreviewDigestController extends Controller {
@service dialog;
username = null;
lastSeen = null;
@empty("email") emailEmpty;
@or("emailEmpty", "sendingEmail") sendEmailDisabled;
@notEmpty("model.html_content") showSendEmailForm;
@empty("model.html_content") htmlEmpty;
export default Controller.extend({
dialog: service(),
username: null,
lastSeen: null,
emailEmpty: empty("email"),
sendEmailDisabled: or("emailEmpty", "sendingEmail"),
showSendEmailForm: notEmpty("model.html_content"),
htmlEmpty: empty("model.html_content"),
@action
toggleShowHtml(event) {
event?.preventDefault();
this.toggleProperty("showHtml");
}
},
@action
updateUsername(selected) {
this.set("username", get(selected, "firstObject"));
}
actions: {
updateUsername(selected) {
this.set("username", get(selected, "firstObject"));
},
@action
refresh() {
const model = this.model;
refresh() {
const model = this.model;
this.set("loading", true);
this.set("sentEmail", false);
this.set("loading", true);
this.set("sentEmail", false);
let username = this.username;
if (!username) {
username = this.currentUser.get("username");
this.set("username", username);
}
let username = this.username;
if (!username) {
username = this.currentUser.get("username");
this.set("username", username);
}
EmailPreview.findDigest(username, this.lastSeen).then((email) => {
model.setProperties(email.getProperties("html_content", "text_content"));
this.set("loading", false);
});
}
@action
sendEmail() {
this.set("sendingEmail", true);
this.set("sentEmail", false);
EmailPreview.sendDigest(this.username, this.lastSeen, this.email)
.then((result) => {
if (result.errors) {
this.dialog.alert(result.errors);
} else {
this.set("sentEmail", true);
}
})
.catch(popupAjaxError)
.finally(() => {
this.set("sendingEmail", false);
EmailPreview.findDigest(username, this.lastSeen).then((email) => {
model.setProperties(
email.getProperties("html_content", "text_content")
);
this.set("loading", false);
});
}
}
},
sendEmail() {
this.set("sendingEmail", true);
this.set("sentEmail", false);
EmailPreview.sendDigest(this.username, this.lastSeen, this.email)
.then((result) => {
if (result.errors) {
this.dialog.alert(result.errors);
} else {
this.set("sentEmail", true);
}
})
.catch(popupAjaxError)
.finally(() => {
this.set("sendingEmail", false);
});
},
},
});

View File

@ -1,18 +1,18 @@
import { action } from "@ember/object";
import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import { INPUT_DELAY } from "discourse-common/config/environment";
import IncomingEmail from "admin/models/incoming-email";
import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "@ember-decorators/object";
import { observes } from "discourse-common/utils/decorators";
export default class AdminEmailReceivedController extends AdminEmailLogsController {
export default AdminEmailLogsController.extend({
@observes("filter.{status,from,to,subject}")
filterIncomingEmails() {
discourseDebounce(this, this.loadLogs, IncomingEmail, INPUT_DELAY);
}
},
@action
loadMore() {
this.loadLogs(IncomingEmail, true);
}
}
actions: {
loadMore() {
this.loadLogs(IncomingEmail, true);
},
},
});

View File

@ -2,23 +2,24 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import { INPUT_DELAY } from "discourse-common/config/environment";
import IncomingEmail from "admin/models/incoming-email";
import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "@ember-decorators/object";
import { observes } from "discourse-common/utils/decorators";
import { action } from "@ember/object";
export default class AdminEmailRejectedController extends AdminEmailLogsController {
export default AdminEmailLogsController.extend({
@observes("filter.{status,from,to,subject,error}")
filterIncomingEmails() {
discourseDebounce(this, this.loadLogs, IncomingEmail, INPUT_DELAY);
}
},
@action
handleShowIncomingEmail(id, event) {
event?.preventDefault();
this.send("showIncomingEmail", id);
}
},
@action
loadMore() {
this.loadLogs(IncomingEmail, true);
}
}
actions: {
loadMore() {
this.loadLogs(IncomingEmail, true);
},
},
});

View File

@ -1,11 +1,11 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "@ember-decorators/object";
import { observes } from "discourse-common/utils/decorators";
export default class AdminEmailSentController extends AdminEmailLogsController {
export default AdminEmailLogsController.extend({
@observes("filter.{status,user,address,type,reply_key}")
filterEmailLogs() {
discourseDebounce(this, this.loadLogs, INPUT_DELAY);
}
}
},
});

View File

@ -1,11 +1,11 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "@ember-decorators/object";
import { observes } from "discourse-common/utils/decorators";
export default class AdminEmailSkippedController extends AdminEmailLogsController {
export default AdminEmailLogsController.extend({
@observes("filter.{status,user,address,type}")
filterEmailLogs() {
discourseDebounce(this, this.loadLogs, INPUT_DELAY);
}
}
},
});

View File

@ -1,18 +1,17 @@
import { action } from "@ember/object";
import Controller from "@ember/controller";
import discourseComputed from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default class AdminEmbeddingController extends Controller {
saved = false;
embedding = null;
export default Controller.extend({
saved: false,
embedding: null,
// show settings if we have at least one created host
@discourseComputed("embedding.embeddable_hosts.@each.isCreated")
showSecondary() {
const hosts = this.get("embedding.embeddable_hosts");
return hosts.length && hosts.findBy("isCreated");
}
},
@discourseComputed("embedding.base_url")
embeddingCode(baseUrl) {
@ -34,28 +33,27 @@ export default class AdminEmbeddingController extends Controller {
</script>`;
return html;
}
},
@action
saveChanges() {
const embedding = this.embedding;
const updates = embedding.getProperties(embedding.get("fields"));
actions: {
saveChanges() {
const embedding = this.embedding;
const updates = embedding.getProperties(embedding.get("fields"));
this.set("saved", false);
this.embedding
.update(updates)
.then(() => this.set("saved", true))
.catch(popupAjaxError);
}
this.set("saved", false);
this.embedding
.update(updates)
.then(() => this.set("saved", true))
.catch(popupAjaxError);
},
@action
addHost() {
const host = this.store.createRecord("embeddable-host");
this.get("embedding.embeddable_hosts").pushObject(host);
}
addHost() {
const host = this.store.createRecord("embeddable-host");
this.get("embedding.embeddable_hosts").pushObject(host);
},
@action
deleteHost(host) {
this.get("embedding.embeddable_hosts").removeObject(host);
}
}
deleteHost(host) {
this.get("embedding.embeddable_hosts").removeObject(host);
},
},
});

View File

@ -1,46 +1,49 @@
import { inject as service } from "@ember/service";
import { sort } from "@ember/object/computed";
import EmberObject, { action, computed } from "@ember/object";
import Controller from "@ember/controller";
import I18n from "I18n";
import { ajax } from "discourse/lib/ajax";
import { sort } from "@ember/object/computed";
import { inject as service } from "@ember/service";
const ALL_FILTER = "all";
export default class AdminEmojisController extends Controller {
@service dialog;
export default Controller.extend({
dialog: service(),
filter: null,
sorting: null,
filter = null;
sorting = null;
@sort("filteredEmojis.[]", "sorting") sortedEmojis;
init() {
super.init(...arguments);
this._super(...arguments);
this.setProperties({
filter: ALL_FILTER,
sorting: ["group", "name"],
});
}
},
@computed("model")
get emojiGroups() {
return this.model.mapBy("group").uniq();
}
sortedEmojis: sort("filteredEmojis.[]", "sorting"),
@computed("emojiGroups.[]")
get sortingGroups() {
return [ALL_FILTER].concat(this.emojiGroups);
}
emojiGroups: computed("model", {
get() {
return this.model.mapBy("group").uniq();
},
}),
@computed("model.[]", "filter")
get filteredEmojis() {
if (!this.filter || this.filter === ALL_FILTER) {
return this.model;
} else {
return this.model.filterBy("group", this.filter);
}
}
sortingGroups: computed("emojiGroups.[]", {
get() {
return [ALL_FILTER].concat(this.emojiGroups);
},
}),
filteredEmojis: computed("model.[]", "filter", {
get() {
if (!this.filter || this.filter === ALL_FILTER) {
return this.model;
} else {
return this.model.filterBy("group", this.filter);
}
},
}),
_highlightEmojiList() {
const customEmojiListEl = document.querySelector("#custom_emoji");
@ -53,12 +56,12 @@ export default class AdminEmojisController extends Controller {
customEmojiListEl.classList.remove("highlighted");
});
}
}
},
@action
filterGroups(value) {
this.set("filter", value);
}
},
@action
emojiUploaded(emoji, group) {
@ -66,7 +69,7 @@ export default class AdminEmojisController extends Controller {
emoji.group = group;
this.model.pushObject(EmberObject.create(emoji));
this._highlightEmojiList();
}
},
@action
destroyEmoji(emoji) {
@ -82,5 +85,5 @@ export default class AdminEmojisController extends Controller {
});
},
});
}
}
},
});

View File

@ -1,24 +1,23 @@
import { action } from "@ember/object";
import Controller from "@ember/controller";
import ScreenedEmail from "admin/models/screened-email";
import { exportEntity } from "discourse/lib/export-csv";
import { outputExportResult } from "discourse/lib/export-result";
export default class AdminLogsScreenedEmailsController extends Controller {
loading = false;
export default Controller.extend({
loading: false,
@action
clearBlock(row) {
row.clearBlock().then(function () {
// feeling lazy
window.location.reload();
});
}
actions: {
clearBlock(row) {
row.clearBlock().then(function () {
// feeling lazy
window.location.reload();
});
},
@action
exportScreenedEmailList() {
exportEntity("screened_email").then(outputExportResult);
}
exportScreenedEmailList() {
exportEntity("screened_email").then(outputExportResult);
},
},
show() {
this.set("loading", true);
@ -26,5 +25,5 @@ export default class AdminLogsScreenedEmailsController extends Controller {
this.set("model", result);
this.set("loading", false);
});
}
}
},
});

View File

@ -1,32 +1,31 @@
import { inject as service } from "@ember/service";
import Controller from "@ember/controller";
import I18n from "I18n";
import { INPUT_DELAY } from "discourse-common/config/environment";
import ScreenedIpAddress from "admin/models/screened-ip-address";
import discourseDebounce from "discourse-common/lib/debounce";
import { exportEntity } from "discourse/lib/export-csv";
import { observes } from "@ember-decorators/object";
import { observes } from "discourse-common/utils/decorators";
import { outputExportResult } from "discourse/lib/export-result";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
export default class AdminLogsScreenedIpAddressesController extends Controller {
@service dialog;
loading = false;
filter = null;
savedIpAddress = null;
export default Controller.extend({
dialog: service(),
loading: false,
filter: null,
savedIpAddress: null,
_debouncedShow() {
this.set("loading", true);
ScreenedIpAddress.findAll(this.filter).then((result) => {
this.setProperties({ model: result, loading: false });
});
}
},
@observes("filter")
show() {
discourseDebounce(this, this._debouncedShow, INPUT_DELAY);
}
},
@action
edit(record, event) {
@ -35,86 +34,81 @@ export default class AdminLogsScreenedIpAddressesController extends Controller {
this.set("savedIpAddress", record.get("ip_address"));
}
record.set("editing", true);
}
},
@action
allow(record) {
record.set("action_name", "do_nothing");
record.save();
}
actions: {
allow(record) {
record.set("action_name", "do_nothing");
record.save();
},
@action
block(record) {
record.set("action_name", "block");
record.save();
}
block(record) {
record.set("action_name", "block");
record.save();
},
@action
cancel(record) {
const savedIpAddress = this.savedIpAddress;
if (savedIpAddress && record.get("editing")) {
record.set("ip_address", savedIpAddress);
}
record.set("editing", false);
}
cancel(record) {
const savedIpAddress = this.savedIpAddress;
if (savedIpAddress && record.get("editing")) {
record.set("ip_address", savedIpAddress);
}
record.set("editing", false);
},
@action
save(record) {
const wasEditing = record.get("editing");
record.set("editing", false);
record
.save()
.then(() => this.set("savedIpAddress", null))
.catch((e) => {
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
this.dialog.alert(
I18n.t("generic_error_with_reason", {
error: e.jqXHR.responseJSON.errors.join(". "),
})
);
} else {
this.dialog.alert(I18n.t("generic_error"));
}
if (wasEditing) {
record.set("editing", true);
}
});
}
@action
destroyRecord(record) {
return this.dialog.yesNoConfirm({
message: I18n.t("admin.logs.screened_ips.delete_confirm", {
ip_address: record.get("ip_address"),
}),
didConfirm: () => {
return record
.destroy()
.then((deleted) => {
if (deleted) {
this.model.removeObject(record);
} else {
this.dialog.alert(I18n.t("generic_error"));
}
})
.catch((e) => {
save(record) {
const wasEditing = record.get("editing");
record.set("editing", false);
record
.save()
.then(() => this.set("savedIpAddress", null))
.catch((e) => {
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
this.dialog.alert(
I18n.t("generic_error_with_reason", {
error: `http: ${e.status} - ${e.body}`,
error: e.jqXHR.responseJSON.errors.join(". "),
})
);
});
},
});
}
} else {
this.dialog.alert(I18n.t("generic_error"));
}
if (wasEditing) {
record.set("editing", true);
}
});
},
@action
recordAdded(arg) {
this.model.unshiftObject(arg);
}
destroy(record) {
return this.dialog.yesNoConfirm({
message: I18n.t("admin.logs.screened_ips.delete_confirm", {
ip_address: record.get("ip_address"),
}),
didConfirm: () => {
return record
.destroy()
.then((deleted) => {
if (deleted) {
this.model.removeObject(record);
} else {
this.dialog.alert(I18n.t("generic_error"));
}
})
.catch((e) => {
this.dialog.alert(
I18n.t("generic_error_with_reason", {
error: `http: ${e.status} - ${e.body}`,
})
);
});
},
});
},
@action
exportScreenedIpList() {
exportEntity("screened_ip").then(outputExportResult);
}
}
recordAdded(arg) {
this.model.unshiftObject(arg);
},
exportScreenedIpList() {
exportEntity("screened_ip").then(outputExportResult);
},
},
});

View File

@ -1,11 +1,10 @@
import { action } from "@ember/object";
import Controller from "@ember/controller";
import ScreenedUrl from "admin/models/screened-url";
import { exportEntity } from "discourse/lib/export-csv";
import { outputExportResult } from "discourse/lib/export-result";
export default class AdminLogsScreenedUrlsController extends Controller {
loading = false;
export default Controller.extend({
loading: false,
show() {
this.set("loading", true);
@ -13,10 +12,11 @@ export default class AdminLogsScreenedUrlsController extends Controller {
this.set("model", result);
this.set("loading", false);
});
}
},
@action
exportScreenedUrlList() {
exportEntity("screened_url").then(outputExportResult);
}
}
actions: {
exportScreenedUrlList() {
exportEntity("screened_url").then(outputExportResult);
},
},
});

View File

@ -7,21 +7,22 @@ import { outputExportResult } from "discourse/lib/export-result";
import { scheduleOnce } from "@ember/runloop";
import showModal from "discourse/lib/show-modal";
export default class AdminLogsStaffActionLogsController extends Controller {
queryParams = ["filters"];
model = null;
filters = null;
userHistoryActions = null;
export default Controller.extend({
queryParams: ["filters"],
model: null,
filters: null,
userHistoryActions: null,
@discourseComputed("filters.action_name")
actionFilter(name) {
return name ? I18n.t("admin.logs.staff_actions.actions." + name) : null;
}
},
@discourseComputed("filters")
filtersExists(filters) {
return filters && Object.keys(filters).length > 0;
}
},
_refresh() {
this.store.findAll("staff-action-log", this.filters).then((result) => {
@ -43,11 +44,11 @@ export default class AdminLogsStaffActionLogsController extends Controller {
);
}
});
}
},
scheduleRefresh() {
scheduleOnce("afterRender", this, this._refresh);
}
},
resetFilters() {
this.setProperties({
@ -55,7 +56,7 @@ export default class AdminLogsStaffActionLogsController extends Controller {
filters: EmberObject.create(),
});
this.scheduleRefresh();
}
},
changeFilters(props) {
this.set("model", EmberObject.create({ loadingMore: true }));
@ -75,7 +76,7 @@ export default class AdminLogsStaffActionLogsController extends Controller {
this.send("onFiltersChange", this.filters);
this.scheduleRefresh();
}
},
@action
filterActionIdChanged(filterActionId) {
@ -86,7 +87,7 @@ export default class AdminLogsStaffActionLogsController extends Controller {
.action_id,
});
}
}
},
@action
clearFilter(key, event) {
@ -101,14 +102,14 @@ export default class AdminLogsStaffActionLogsController extends Controller {
} else {
this.changeFilters({ [key]: null });
}
}
},
@action
clearAllFilters(event) {
event?.preventDefault();
this.set("filterActionId", null);
this.resetFilters();
}
},
@action
filterByAction(logItem, event) {
@ -118,35 +119,35 @@ export default class AdminLogsStaffActionLogsController extends Controller {
action_id: logItem.get("action"),
custom_type: logItem.get("custom_type"),
});
}
},
@action
filterByStaffUser(acting_user, event) {
event?.preventDefault();
this.changeFilters({ acting_user: acting_user.username });
}
},
@action
filterByTargetUser(target_user, event) {
event?.preventDefault();
this.changeFilters({ target_user: target_user.username });
}
},
@action
filterBySubject(subject, event) {
event?.preventDefault();
this.changeFilters({ subject });
}
},
@action
exportStaffActionLogs() {
exportEntity("staff_action").then(outputExportResult);
}
},
@action
loadMore() {
this.model.loadMore();
}
},
@action
showDetailsModal(model, event) {
@ -156,7 +157,7 @@ export default class AdminLogsStaffActionLogsController extends Controller {
admin: true,
modalClass: "log-details-modal",
});
}
},
@action
showCustomDetailsModal(model, event) {
@ -167,5 +168,5 @@ export default class AdminLogsStaffActionLogsController extends Controller {
modalClass: "history-modal",
});
modal.loadDiff();
}
}
},
});

View File

@ -1,63 +1,59 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { or } from "@ember/object/computed";
import Controller from "@ember/controller";
import I18n from "I18n";
import { INPUT_DELAY } from "discourse-common/config/environment";
import Permalink from "admin/models/permalink";
import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "@ember-decorators/object";
import { observes } from "discourse-common/utils/decorators";
import { clipboardCopy } from "discourse/lib/utilities";
import { inject as service } from "@ember/service";
import { or } from "@ember/object/computed";
export default class AdminPermalinksController extends Controller {
@service dialog;
loading = false;
filter = null;
@or("model.length", "filter") showSearch;
export default Controller.extend({
dialog: service(),
loading: false,
filter: null,
showSearch: or("model.length", "filter"),
_debouncedShow() {
Permalink.findAll(this.filter).then((result) => {
this.set("model", result);
this.set("loading", false);
});
}
},
@observes("filter")
show() {
discourseDebounce(this, this._debouncedShow, INPUT_DELAY);
}
},
@action
recordAdded(arg) {
this.model.unshiftObject(arg);
}
actions: {
recordAdded(arg) {
this.model.unshiftObject(arg);
},
@action
copyUrl(pl) {
let linkElement = document.querySelector(`#admin-permalink-${pl.id}`);
clipboardCopy(linkElement.textContent);
}
copyUrl(pl) {
let linkElement = document.querySelector(`#admin-permalink-${pl.id}`);
clipboardCopy(linkElement.textContent);
},
@action
destroyRecord(record) {
return this.dialog.yesNoConfirm({
message: I18n.t("admin.permalink.delete_confirm"),
didConfirm: () => {
return record.destroy().then(
(deleted) => {
if (deleted) {
this.model.removeObject(record);
} else {
destroy(record) {
return this.dialog.yesNoConfirm({
message: I18n.t("admin.permalink.delete_confirm"),
didConfirm: () => {
return record.destroy().then(
(deleted) => {
if (deleted) {
this.model.removeObject(record);
} else {
this.dialog.alert(I18n.t("generic_error"));
}
},
function () {
this.dialog.alert(I18n.t("generic_error"));
}
},
function () {
this.dialog.alert(I18n.t("generic_error"));
}
);
},
});
}
}
);
},
});
},
},
});

View File

@ -1,18 +1,18 @@
import { inject as service } from "@ember/service";
import Controller from "@ember/controller";
import { inject as service } from "@ember/service";
export default class AdminPluginsController extends Controller {
@service router;
export default Controller.extend({
router: service(),
get adminRoutes() {
return this.allAdminRoutes.filter((r) => this.routeExists(r.full_location));
}
},
get brokenAdminRoutes() {
return this.allAdminRoutes.filter(
(r) => !this.routeExists(r.full_location)
);
}
},
get allAdminRoutes() {
return this.model
@ -21,7 +21,7 @@ export default class AdminPluginsController extends Controller {
return p.admin_route;
})
.filter(Boolean);
}
},
routeExists(routeName) {
try {
@ -30,5 +30,5 @@ export default class AdminPluginsController extends Controller {
} catch (e) {
return false;
}
}
}
},
});

View File

@ -1,12 +1,12 @@
import Controller from "@ember/controller";
import discourseComputed from "discourse-common/utils/decorators";
export default class AdminReportsShowController extends Controller {
queryParams = ["start_date", "end_date", "filters", "chart_grouping", "mode"];
start_date = null;
end_date = null;
filters = null;
chart_grouping = null;
export default Controller.extend({
queryParams: ["start_date", "end_date", "filters", "chart_grouping", "mode"],
start_date: null,
end_date: null,
filters: null,
chart_grouping: null,
@discourseComputed("model.type")
reportOptions(type) {
@ -19,5 +19,5 @@ export default class AdminReportsShowController extends Controller {
options.chartGrouping = this.chart_grouping;
return options;
}
}
},
});

View File

@ -2,19 +2,24 @@ import Controller from "@ember/controller";
import I18n from "I18n";
export const DEFAULT_PERIOD = "yearly";
export default class AdminSearchLogsIndexController extends Controller {
loading = false;
period = DEFAULT_PERIOD;
searchType = "all";
searchTypeOptions = [
{
id: "all",
name: I18n.t("admin.logs.search_logs.types.all_search_types"),
},
{ id: "header", name: I18n.t("admin.logs.search_logs.types.header") },
{
id: "full_page",
name: I18n.t("admin.logs.search_logs.types.full_page"),
},
];
}
export default Controller.extend({
loading: false,
period: DEFAULT_PERIOD,
searchType: "all",
init() {
this._super(...arguments);
this.searchTypeOptions = [
{
id: "all",
name: I18n.t("admin.logs.search_logs.types.all_search_types"),
},
{ id: "header", name: I18n.t("admin.logs.search_logs.types.header") },
{
id: "full_page",
name: I18n.t("admin.logs.search_logs.types.full_page"),
},
];
},
});

View File

@ -2,24 +2,29 @@ import Controller from "@ember/controller";
import { DEFAULT_PERIOD } from "admin/controllers/admin-search-logs-index";
import I18n from "I18n";
export default class AdminSearchLogsTermController extends Controller {
loading = false;
term = null;
period = DEFAULT_PERIOD;
searchType = "all";
searchTypeOptions = [
{
id: "all",
name: I18n.t("admin.logs.search_logs.types.all_search_types"),
},
{ id: "header", name: I18n.t("admin.logs.search_logs.types.header") },
{
id: "full_page",
name: I18n.t("admin.logs.search_logs.types.full_page"),
},
{
id: "click_through_only",
name: I18n.t("admin.logs.search_logs.types.click_through_only"),
},
];
}
export default Controller.extend({
loading: false,
term: null,
period: DEFAULT_PERIOD,
searchType: "all",
init() {
this._super(...arguments);
this.searchTypeOptions = [
{
id: "all",
name: I18n.t("admin.logs.search_logs.types.all_search_types"),
},
{ id: "header", name: I18n.t("admin.logs.search_logs.types.header") },
{
id: "full_page",
name: I18n.t("admin.logs.search_logs.types.full_page"),
},
{
id: "click_through_only",
name: I18n.t("admin.logs.search_logs.types.click_through_only"),
},
];
},
});

View File

@ -1,18 +1,17 @@
import Controller, { inject as controller } from "@ember/controller";
import discourseComputed from "discourse-common/utils/decorators";
export default class AdminSiteSettingsCategoryController extends Controller {
@controller adminSiteSettings;
categoryNameKey = null;
export default Controller.extend({
adminSiteSettings: controller(),
categoryNameKey: null,
@discourseComputed("adminSiteSettings.visibleSiteSettings", "categoryNameKey")
category(categories, nameKey) {
return (categories || []).findBy("nameKey", nameKey);
}
},
@discourseComputed("category")
filteredContent(category) {
return category ? category.siteSettings : [];
}
}
},
});

View File

@ -1,19 +1,16 @@
import { alias } from "@ember/object/computed";
import Controller from "@ember/controller";
import I18n from "I18n";
import { INPUT_DELAY } from "discourse-common/config/environment";
import { alias } from "@ember/object/computed";
import { isEmpty } from "@ember/utils";
import { debounce } from "discourse-common/utils/decorators";
import { observes } from "@ember-decorators/object";
import { debounce, observes } from "discourse-common/utils/decorators";
import { action } from "@ember/object";
export default class AdminSiteSettingsController extends Controller {
filter = null;
@alias("model") allSiteSettings;
visibleSiteSettings = null;
onlyOverridden = false;
export default Controller.extend({
filter: null,
allSiteSettings: alias("model"),
visibleSiteSettings: null,
onlyOverridden: false,
filterContentNow(category) {
// If we have no content, don't bother filtering anything
@ -112,13 +109,9 @@ export default class AdminSiteSettingsController extends Controller {
"adminSiteSettingsCategory",
category || "all_results"
);
}
},
@observes("filter", "onlyOverridden", "model")
optsChanged() {
this.filterContent();
}
@debounce(INPUT_DELAY)
filterContent() {
if (this._skipBounce) {
@ -126,12 +119,12 @@ export default class AdminSiteSettingsController extends Controller {
} else {
this.filterContentNow(this.categoryNameKey);
}
}
},
@action
clearFilter() {
this.setProperties({ filter: "", onlyOverridden: false });
}
},
@action
toggleMenu() {
@ -139,5 +132,5 @@ export default class AdminSiteSettingsController extends Controller {
["mobile-closed", "mobile-open"].forEach((state) => {
adminDetail.classList.toggle(state);
});
}
}
},
});

View File

@ -1,23 +1,23 @@
import { action } from "@ember/object";
import Controller from "@ember/controller";
import discourseComputed from "discourse-common/utils/decorators";
import discourseDebounce from "discourse-common/lib/debounce";
let lastSearch;
export default class AdminSiteTextIndexController extends Controller {
searching = false;
siteTexts = null;
preferred = false;
queryParams = ["q", "overridden", "locale"];
locale = null;
q = null;
overridden = false;
export default Controller.extend({
searching: false,
siteTexts: null,
preferred: false,
queryParams: ["q", "overridden", "locale"],
locale: null,
q: null,
overridden: false,
init() {
super.init(...arguments);
this._super(...arguments);
this.set("locale", this.siteSettings.default_locale);
}
},
_performSearch() {
this.store
@ -26,12 +26,12 @@ export default class AdminSiteTextIndexController extends Controller {
this.set("siteTexts", results);
})
.finally(() => this.set("searching", false));
}
},
@discourseComputed()
availableLocales() {
return JSON.parse(this.siteSettings.available_locales);
}
},
@discourseComputed("locale")
fallbackLocaleFullName() {
@ -40,41 +40,39 @@ export default class AdminSiteTextIndexController extends Controller {
return l.value === this.siteTexts.extras.fallback_locale;
}).name;
}
}
},
@action
edit(siteText) {
this.transitionToRoute("adminSiteText.edit", siteText.get("id"), {
queryParams: {
locale: this.locale,
},
});
}
actions: {
edit(siteText) {
this.transitionToRoute("adminSiteText.edit", siteText.get("id"), {
queryParams: {
locale: this.locale,
},
});
},
@action
toggleOverridden() {
this.toggleProperty("overridden");
this.set("searching", true);
discourseDebounce(this, this._performSearch, 400);
}
@action
search() {
const q = this.q;
if (q !== lastSearch) {
toggleOverridden() {
this.toggleProperty("overridden");
this.set("searching", true);
discourseDebounce(this, this._performSearch, 400);
lastSearch = q;
}
}
},
@action
updateLocale(value) {
this.setProperties({
searching: true,
locale: value,
});
search() {
const q = this.q;
if (q !== lastSearch) {
this.set("searching", true);
discourseDebounce(this, this._performSearch, 400);
lastSearch = q;
}
},
discourseDebounce(this, this._performSearch, 400);
}
}
updateLocale(value) {
this.setProperties({
searching: true,
locale: value,
});
discourseDebounce(this, this._performSearch, 400);
},
},
});

View File

@ -1,25 +1,25 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { alias, sort } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller";
import { alias, sort } from "@ember/object/computed";
import GrantBadgeController from "discourse/mixins/grant-badge-controller";
import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators";
import { next } from "@ember/runloop";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { inject as service } from "@ember/service";
export default class AdminUserBadgesController extends Controller.extend(
GrantBadgeController
) {
@service dialog;
@controller adminUser;
export default Controller.extend(GrantBadgeController, {
adminUser: controller(),
dialog: service(),
user: alias("adminUser.model"),
userBadges: alias("model"),
allBadges: alias("badges"),
sortedBadges: sort("model", "badgeSortOrder"),
@alias("adminUser.model") user;
@alias("model") userBadges;
@alias("badges") allBadges;
@sort("model", "badgeSortOrder") sortedBadges;
init() {
this._super(...arguments);
badgeSortOrder = ["granted_at:desc"];
this.badgeSortOrder = ["granted_at:desc"];
},
@discourseComputed("model", "model.[]", "model.expandedBadges.[]")
groupedBadges() {
@ -59,47 +59,46 @@ export default class AdminUserBadgesController extends Controller.extend(
});
return expanded.sortBy("granted_at").reverse();
}
},
@action
expandGroup(userBadge) {
const model = this.model;
model.set("expandedBadges", model.get("expandedBadges") || []);
model.get("expandedBadges").pushObject(userBadge.badge.id);
}
actions: {
expandGroup(userBadge) {
const model = this.model;
model.set("expandedBadges", model.get("expandedBadges") || []);
model.get("expandedBadges").pushObject(userBadge.badge.id);
},
@action
grantBadge() {
this.grantBadge(
this.selectedBadgeId,
this.get("user.username"),
this.badgeReason
).then(
() => {
this.set("badgeReason", "");
next(() => {
// Update the selected badge ID after the combobox has re-rendered.
const newSelectedBadge = this.grantableBadges[0];
if (newSelectedBadge) {
this.set("selectedBadgeId", newSelectedBadge.get("id"));
}
});
},
function (error) {
popupAjaxError(error);
}
);
}
grantBadge() {
this.grantBadge(
this.selectedBadgeId,
this.get("user.username"),
this.badgeReason
).then(
() => {
this.set("badgeReason", "");
next(() => {
// Update the selected badge ID after the combobox has re-rendered.
const newSelectedBadge = this.grantableBadges[0];
if (newSelectedBadge) {
this.set("selectedBadgeId", newSelectedBadge.get("id"));
}
});
},
function (error) {
popupAjaxError(error);
}
);
},
@action
revokeBadge(userBadge) {
return this.dialog.yesNoConfirm({
message: I18n.t("admin.badges.revoke_confirm"),
didConfirm: () => {
return userBadge.revoke().then(() => {
this.model.removeObject(userBadge);
});
},
});
}
}
revokeBadge(userBadge) {
return this.dialog.yesNoConfirm({
message: I18n.t("admin.badges.revoke_confirm"),
didConfirm: () => {
return userBadge.revoke().then(() => {
this.model.removeObject(userBadge);
});
},
});
},
},
});

View File

@ -1,74 +1,73 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { gte, sort } from "@ember/object/computed";
import Controller from "@ember/controller";
import I18n from "I18n";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { inject as service } from "@ember/service";
const MAX_FIELDS = 30;
export default class AdminUserFieldsController extends Controller {
@service dialog;
export default Controller.extend({
dialog: service(),
fieldTypes: null,
createDisabled: gte("model.length", MAX_FIELDS),
sortedFields: sort("model", "fieldSortOrder"),
fieldTypes = null;
init() {
this._super(...arguments);
@gte("model.length", MAX_FIELDS) createDisabled;
@sort("model", "fieldSortOrder") sortedFields;
this.fieldSortOrder = ["position"];
},
fieldSortOrder = ["position"];
@action
createField() {
const f = this.store.createRecord("user-field", {
field_type: "text",
position: MAX_FIELDS,
});
this.model.pushObject(f);
}
@action
moveUp(f) {
const idx = this.sortedFields.indexOf(f);
if (idx) {
const prev = this.sortedFields.objectAt(idx - 1);
const prevPos = prev.get("position");
prev.update({ position: f.get("position") });
f.update({ position: prevPos });
}
}
@action
moveDown(f) {
const idx = this.sortedFields.indexOf(f);
if (idx > -1) {
const next = this.sortedFields.objectAt(idx + 1);
const nextPos = next.get("position");
next.update({ position: f.get("position") });
f.update({ position: nextPos });
}
}
@action
destroyField(f) {
const model = this.model;
// Only confirm if we already been saved
if (f.get("id")) {
this.dialog.yesNoConfirm({
message: I18n.t("admin.user_fields.delete_confirm"),
didConfirm: () => {
return f
.destroyRecord()
.then(function () {
model.removeObject(f);
})
.catch(popupAjaxError);
},
actions: {
createField() {
const f = this.store.createRecord("user-field", {
field_type: "text",
position: MAX_FIELDS,
});
} else {
model.removeObject(f);
}
}
}
this.model.pushObject(f);
},
moveUp(f) {
const idx = this.sortedFields.indexOf(f);
if (idx) {
const prev = this.sortedFields.objectAt(idx - 1);
const prevPos = prev.get("position");
prev.update({ position: f.get("position") });
f.update({ position: prevPos });
}
},
moveDown(f) {
const idx = this.sortedFields.indexOf(f);
if (idx > -1) {
const next = this.sortedFields.objectAt(idx + 1);
const nextPos = next.get("position");
next.update({ position: f.get("position") });
f.update({ position: nextPos });
}
},
destroy(f) {
const model = this.model;
// Only confirm if we already been saved
if (f.get("id")) {
this.dialog.yesNoConfirm({
message: I18n.t("admin.user_fields.delete_confirm"),
didConfirm: () => {
return f
.destroyRecord()
.then(function () {
model.removeObject(f);
})
.catch(popupAjaxError);
},
});
} else {
model.removeObject(f);
}
},
},
});

View File

@ -1,2 +1,2 @@
import Controller from "@ember/controller";
export default class AdminUserController extends Controller {}
export default Controller.extend();

View File

@ -1,6 +1,4 @@
import { action } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators";
import { observes } from "@ember-decorators/object";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import AdminUser from "admin/models/admin-user";
import CanCheckEmails from "discourse/mixins/can-check-emails";
import Controller from "@ember/controller";
@ -9,55 +7,41 @@ import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseDebounce from "discourse-common/lib/debounce";
import { i18n } from "discourse/lib/computed";
export default class AdminUsersListShowController extends Controller.extend(
CanCheckEmails
) {
model = null;
query = null;
order = null;
asc = null;
showEmails = false;
refreshing = false;
listFilter = null;
selectAll = false;
export default Controller.extend(CanCheckEmails, {
model: null,
query: null,
order: null,
asc: null,
showEmails: false,
refreshing: false,
listFilter: null,
selectAll: false,
searchHint: i18n("search_hint"),
@i18n("search_hint") searchHint;
init() {
this._super(...arguments);
_page = 1;
_results = [];
_canLoadMore = true;
this._page = 1;
this._results = [];
this._canLoadMore = true;
},
@discourseComputed("query")
title(query) {
return I18n.t("admin.users.titles." + query);
}
@discourseComputed("showEmails")
columnCount(showEmails) {
let colCount = 7; // note that the first column is hardcoded in the template
if (showEmails) {
colCount += 1;
}
if (this.siteSettings.must_approve_users) {
colCount += 1;
}
return colCount;
}
},
@observes("listFilter")
_filterUsers() {
discourseDebounce(this, this.resetFilters, INPUT_DELAY);
}
},
resetFilters() {
this._page = 1;
this._results = [];
this._canLoadMore = true;
this._refreshUsers();
}
},
_refreshUsers() {
if (!this._canLoadMore) {
@ -85,17 +69,17 @@ export default class AdminUsersListShowController extends Controller.extend(
.finally(() => {
this.set("refreshing", false);
});
}
},
@action
loadMore() {
this._page += 1;
this._refreshUsers();
}
actions: {
loadMore() {
this._page += 1;
this._refreshUsers();
},
@action
toggleEmailVisibility() {
this.toggleProperty("showEmails");
this.resetFilters();
}
}
toggleEmailVisibility() {
this.toggleProperty("showEmails");
this.resetFilters();
},
},
});

View File

@ -1,35 +1,32 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { or } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller";
import I18n from "I18n";
import WatchedWord from "admin/models/watched-word";
import { ajax } from "discourse/lib/ajax";
import discourseComputed from "discourse-common/utils/decorators";
import { fmt } from "discourse/lib/computed";
import { or } from "@ember/object/computed";
import { schedule } from "@ember/runloop";
import showModal from "discourse/lib/show-modal";
import { inject as service } from "@ember/service";
export default class AdminWatchedWordsActionController extends Controller {
@service dialog;
@controller adminWatchedWords;
actionNameKey = null;
@fmt("actionNameKey", "/admin/customize/watched_words/action/%@/download")
downloadLink;
@or("adminWatchedWords.showWords", "adminWatchedWords.filter")
showWordsList;
export default Controller.extend({
adminWatchedWords: controller(),
actionNameKey: null,
dialog: service(),
downloadLink: fmt(
"actionNameKey",
"/admin/customize/watched_words/action/%@/download"
),
showWordsList: or("adminWatchedWords.showWords", "adminWatchedWords.filter"),
findAction(actionName) {
return (this.adminWatchedWords.model || []).findBy("nameKey", actionName);
}
},
@discourseComputed("actionNameKey", "adminWatchedWords.model")
currentAction(actionName) {
return this.findAction(actionName);
}
},
@discourseComputed("currentAction.words.[]")
regexpError(words) {
@ -40,81 +37,78 @@ export default class AdminWatchedWordsActionController extends Controller {
return I18n.t("admin.watched_words.invalid_regex", { word });
}
}
}
},
@discourseComputed("actionNameKey")
actionDescription(actionNameKey) {
return I18n.t("admin.watched_words.action_descriptions." + actionNameKey);
}
},
@action
recordAdded(arg) {
const foundAction = this.findAction(this.actionNameKey);
if (!foundAction) {
return;
}
actions: {
recordAdded(arg) {
const action = this.findAction(this.actionNameKey);
if (!action) {
return;
}
foundAction.words.unshiftObject(arg);
schedule("afterRender", () => {
// remove from other actions lists
let match = null;
this.adminWatchedWords.model.forEach((otherAction) => {
if (match) {
return;
}
if (otherAction.nameKey !== this.actionNameKey) {
match = otherAction.words.findBy("id", arg.id);
action.words.unshiftObject(arg);
schedule("afterRender", () => {
// remove from other actions lists
let match = null;
this.adminWatchedWords.model.forEach((otherAction) => {
if (match) {
otherAction.words.removeObject(match);
return;
}
}
});
});
}
@action
recordRemoved(arg) {
if (this.currentAction) {
this.currentAction.words.removeObject(arg);
}
}
@action
uploadComplete() {
WatchedWord.findAll().then((data) => {
this.adminWatchedWords.set("model", data);
});
}
@action
test() {
WatchedWord.findAll().then((data) => {
this.adminWatchedWords.set("model", data);
showModal("admin-watched-word-test", {
admin: true,
model: this.currentAction,
});
});
}
@action
clearAll() {
const actionKey = this.actionNameKey;
this.dialog.yesNoConfirm({
message: I18n.t("admin.watched_words.clear_all_confirm", {
action: I18n.t("admin.watched_words.actions." + actionKey),
}),
didConfirm: () => {
ajax(`/admin/customize/watched_words/action/${actionKey}.json`, {
type: "DELETE",
}).then(() => {
const foundAction = this.findAction(actionKey);
if (foundAction) {
foundAction.set("words", []);
if (otherAction.nameKey !== this.actionNameKey) {
match = otherAction.words.findBy("id", arg.id);
if (match) {
otherAction.words.removeObject(match);
}
}
});
},
});
}
}
});
},
recordRemoved(arg) {
if (this.currentAction) {
this.currentAction.words.removeObject(arg);
}
},
uploadComplete() {
WatchedWord.findAll().then((data) => {
this.adminWatchedWords.set("model", data);
});
},
test() {
WatchedWord.findAll().then((data) => {
this.adminWatchedWords.set("model", data);
showModal("admin-watched-word-test", {
admin: true,
model: this.currentAction,
});
});
},
clearAll() {
const actionKey = this.actionNameKey;
this.dialog.yesNoConfirm({
message: I18n.t("admin.watched_words.clear_all_confirm", {
action: I18n.t("admin.watched_words.actions." + actionKey),
}),
didConfirm: () => {
ajax(`/admin/customize/watched_words/action/${actionKey}.json`, {
type: "DELETE",
}).then(() => {
const action = this.findAction(actionKey);
if (action) {
action.set("words", []);
}
});
},
});
},
},
});

View File

@ -3,11 +3,11 @@ import EmberObject, { action } from "@ember/object";
import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseDebounce from "discourse-common/lib/debounce";
import { isEmpty } from "@ember/utils";
import { observes } from "@ember-decorators/object";
import { observes } from "discourse-common/utils/decorators";
export default class AdminWatchedWordsController extends Controller {
filter = null;
showWords = false;
export default Controller.extend({
filter: null,
showWords: false,
_filterContent() {
if (isEmpty(this.allWatchedWords)) {
@ -36,17 +36,17 @@ export default class AdminWatchedWordsController extends Controller {
);
});
this.set("model", model);
}
},
@observes("filter")
filterContent() {
discourseDebounce(this, this._filterContent, INPUT_DELAY);
}
},
@action
clearFilter() {
this.set("filter", "");
}
},
@action
toggleMenu() {
@ -54,5 +54,5 @@ export default class AdminWatchedWordsController extends Controller {
["mobile-closed", "mobile-open"].forEach((state) => {
adminDetail.classList.toggle(state);
});
}
}
},
});

View File

@ -1,24 +1,23 @@
import { inject as service } from "@ember/service";
import { alias } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller";
import EmberObject, { action } from "@ember/object";
import I18n from "I18n";
import { alias } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators";
import { isEmpty } from "@ember/utils";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { inject as service } from "@ember/service";
export default class AdminWebHooksEditController extends Controller {
@service dialog;
@controller adminWebHooks;
@alias("adminWebHooks.eventTypes") eventTypes;
@alias("adminWebHooks.defaultEventTypes") defaultEventTypes;
@alias("adminWebHooks.contentTypes") contentTypes;
export default Controller.extend({
adminWebHooks: controller(),
dialog: service(),
eventTypes: alias("adminWebHooks.eventTypes"),
defaultEventTypes: alias("adminWebHooks.defaultEventTypes"),
contentTypes: alias("adminWebHooks.contentTypes"),
@discourseComputed
showTagsFilter() {
return this.siteSettings.tagging_enabled;
}
},
@discourseComputed("model.isSaving", "saved", "saveButtonDisabled")
savingStatus(isSaving, saved, saveButtonDisabled) {
@ -30,14 +29,14 @@ export default class AdminWebHooksEditController extends Controller {
// Use side effect of validation to clear saved text
this.set("saved", false);
return "";
}
},
@discourseComputed("model.isNew")
saveButtonText(isNew) {
return isNew
? I18n.t("admin.web_hooks.create")
: I18n.t("admin.web_hooks.save");
}
},
@discourseComputed("model.secret")
secretValidation(secret) {
@ -56,7 +55,7 @@ export default class AdminWebHooksEditController extends Controller {
});
}
}
}
},
@discourseComputed("model.wildcard_web_hook", "model.web_hook_event_types.[]")
eventTypeValidation(isWildcard, eventTypes) {
@ -66,7 +65,7 @@ export default class AdminWebHooksEditController extends Controller {
reason: I18n.t("admin.web_hooks.event_type_missing"),
});
}
}
},
@discourseComputed(
"model.isSaving",
@ -83,7 +82,7 @@ export default class AdminWebHooksEditController extends Controller {
return isSaving
? false
: secretValidation || eventTypeValidation || isEmpty(payloadUrl);
}
},
@action
async save() {
@ -98,5 +97,5 @@ export default class AdminWebHooksEditController extends Controller {
} catch (e) {
popupAjaxError(e);
}
}
}
},
});

View File

@ -1,19 +1,18 @@
import { inject as service } from "@ember/service";
import { alias } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller";
import I18n from "I18n";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { inject as service } from "@ember/service";
import { action } from "@ember/object";
import { alias } from "@ember/object/computed";
export default class AdminWebHooksIndexController extends Controller {
@service dialog;
@controller adminWebHooks;
@alias("adminWebHooks.contentTypes") contentTypes;
@alias("adminWebHooks.defaultEventTypes") defaultEventTypes;
@alias("adminWebHooks.deliveryStatuses") deliveryStatuses;
@alias("adminWebHooks.eventTypes") eventTypes;
@alias("adminWebHooks.model") model;
export default Controller.extend({
adminWebHooks: controller(),
dialog: service(),
contentTypes: alias("adminWebHooks.contentTypes"),
defaultEventTypes: alias("adminWebHooks.defaultEventTypes"),
deliveryStatuses: alias("adminWebHooks.deliveryStatuses"),
eventTypes: alias("adminWebHooks.eventTypes"),
model: alias("adminWebHooks.model"),
@action
destroy(webhook) {
@ -28,10 +27,10 @@ export default class AdminWebHooksIndexController extends Controller {
}
},
});
}
},
@action
loadMore() {
this.model.loadMore();
}
}
},
});

View File

@ -1,18 +1,18 @@
import { inject as service } from "@ember/service";
import Controller, { inject as controller } from "@ember/controller";
import { action } from "@ember/object";
import I18n from "I18n";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { inject as service } from "@ember/service";
export default class AdminWebHooksShowController extends Controller {
@service dialog;
@service router;
@controller adminWebHooks;
export default Controller.extend({
adminWebHooks: controller(),
dialog: service(),
router: service(),
@action
edit() {
return this.router.transitionTo("adminWebHooks.edit", this.model);
}
},
@action
destroy() {
@ -28,5 +28,5 @@ export default class AdminWebHooksShowController extends Controller {
}
},
});
}
}
},
});

View File

@ -1,3 +1,3 @@
import Controller from "@ember/controller";
export default class AdminWebHooksController extends Controller {}
export default Controller.extend({});

View File

@ -1,20 +1,20 @@
import { inject as service } from "@ember/service";
import Controller from "@ember/controller";
import { dasherize } from "@ember/string";
import discourseComputed from "discourse-common/utils/decorators";
import { inject as service } from "@ember/service";
export default class AdminController extends Controller {
@service router;
export default Controller.extend({
router: service(),
@discourseComputed("siteSettings.enable_group_directory")
showGroups(enableGroupDirectory) {
return !enableGroupDirectory;
}
},
@discourseComputed("siteSettings.enable_badges")
showBadges(enableBadges) {
return this.currentUser.get("admin") && enableBadges;
}
},
@discourseComputed("router._router.currentPath")
adminContentsClassName(currentPath) {
@ -37,5 +37,5 @@ export default class AdminController extends Controller {
}
return cssClasses;
}
}
},
});

View File

@ -1,8 +1,6 @@
import { action } from "@ember/object";
import { and, not } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller";
import discourseComputed from "discourse-common/utils/decorators";
import { observes } from "@ember-decorators/object";
import { and, not } from "@ember/object/computed";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { ajax } from "discourse/lib/ajax";
@ -55,20 +53,18 @@ const SCSS_VARIABLE_NAMES = [
"love-low",
];
export default class AdminAddUploadController extends Controller.extend(
ModalFunctionality
) {
@controller adminCustomizeThemesShow;
export default Controller.extend(ModalFunctionality, {
adminCustomizeThemesShow: controller(),
uploadUrl = "/admin/themes/upload_asset";
@and("nameValid", "fileSelected") enabled;
@not("enabled") disabled;
uploadUrl: "/admin/themes/upload_asset",
onShow() {
this.set("name", null);
this.set("fileSelected", false);
}
},
enabled: and("nameValid", "fileSelected"),
disabled: not("enabled"),
@discourseComputed("name", "adminCustomizeThemesShow.model.theme_fields")
errorMessage(name, themeFields) {
@ -93,54 +89,54 @@ export default class AdminAddUploadController extends Controller.extend(
}
return null;
}
},
@discourseComputed("errorMessage")
nameValid(errorMessage) {
return null === errorMessage;
}
},
@observes("name")
uploadChanged() {
const file = $("#file-input")[0];
this.set("fileSelected", file && file.files[0]);
}
},
@action
updateName() {
let name = this.name;
if (isEmpty(name)) {
name = $("#file-input")[0].files[0].name;
this.set("name", name.split(".")[0]);
}
this.uploadChanged();
}
actions: {
updateName() {
let name = this.name;
if (isEmpty(name)) {
name = $("#file-input")[0].files[0].name;
this.set("name", name.split(".")[0]);
}
this.uploadChanged();
},
@action
upload() {
const file = $("#file-input")[0].files[0];
upload() {
const file = $("#file-input")[0].files[0];
const options = {
type: "POST",
processData: false,
contentType: false,
data: new FormData(),
};
const options = {
type: "POST",
processData: false,
contentType: false,
data: new FormData(),
};
options.data.append("file", file);
options.data.append("file", file);
ajax(this.uploadUrl, options)
.then((result) => {
const upload = {
upload_id: result.upload_id,
name: this.name,
original_filename: file.name,
};
this.adminCustomizeThemesShow.send("addUpload", upload);
this.send("closeModal");
})
.catch((e) => {
popupAjaxError(e);
});
}
}
ajax(this.uploadUrl, options)
.then((result) => {
const upload = {
upload_id: result.upload_id,
name: this.name,
original_filename: file.name,
};
this.adminCustomizeThemesShow.send("addUpload", upload);
this.send("closeModal");
})
.catch((e) => {
popupAjaxError(e);
});
},
},
});

View File

@ -4,12 +4,39 @@ import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators";
import { escapeExpression } from "discourse/lib/utilities";
export default class AdminBadgePreviewController extends Controller {
@alias("model.sample") sample;
@alias("model.errors") errors;
@alias("model.grant_count") count;
export default Controller.extend({
sample: alias("model.sample"),
errors: alias("model.errors"),
count: alias("model.grant_count"),
@map("model.sample", (grant) => {
@discourseComputed("count", "sample.length")
countWarning(count, sampleLength) {
if (count <= 10) {
return sampleLength !== count;
} else {
return sampleLength !== 10;
}
},
@discourseComputed("model.query_plan")
hasQueryPlan(queryPlan) {
return !!queryPlan;
},
@discourseComputed("model.query_plan")
queryPlanHtml(queryPlan) {
let output = `<pre class="badge-query-plan">`;
queryPlan.forEach((linehash) => {
output += escapeExpression(linehash["QUERY PLAN"]);
output += "<br>";
});
output += "</pre>";
return output;
},
processedSample: map("model.sample", (grant) => {
let i18nKey = "admin.badges.preview.grant.with";
const i18nParams = { username: escapeExpression(grant.username) };
@ -28,33 +55,5 @@ export default class AdminBadgePreviewController extends Controller {
}
return I18n.t(i18nKey, i18nParams);
})
processedSample;
@discourseComputed("count", "sample.length")
countWarning(count, sampleLength) {
if (count <= 10) {
return sampleLength !== count;
} else {
return sampleLength !== 10;
}
}
@discourseComputed("model.query_plan")
hasQueryPlan(queryPlan) {
return !!queryPlan;
}
@discourseComputed("model.query_plan")
queryPlanHtml(queryPlan) {
let output = `<pre class="badge-query-plan">`;
queryPlan.forEach((linehash) => {
output += escapeExpression(linehash["QUERY PLAN"]);
output += "<br>";
});
output += "</pre>";
return output;
}
}
}),
});

View File

@ -1,29 +1,27 @@
import { action } from "@ember/object";
import Controller, { inject as controller } from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
export default class AdminColorSchemeSelectBaseController extends Controller.extend(
ModalFunctionality
) {
@controller adminCustomizeColors;
export default Controller.extend(ModalFunctionality, {
adminCustomizeColors: controller(),
selectedBaseThemeId = null;
selectedBaseThemeId: null,
init() {
super.init(...arguments);
this._super(...arguments);
const defaultScheme = this.get(
"adminCustomizeColors.baseColorSchemes.0.base_scheme_id"
);
this.set("selectedBaseThemeId", defaultScheme);
}
},
@action
selectBase() {
this.adminCustomizeColors.send(
"newColorSchemeWithBase",
this.selectedBaseThemeId
);
this.send("closeModal");
}
}
actions: {
selectBase() {
this.adminCustomizeColors.send(
"newColorSchemeWithBase",
this.selectedBaseThemeId
);
this.send("closeModal");
},
},
});

View File

@ -1,21 +1,18 @@
import { alias } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller";
import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { action } from "@ember/object";
import { alias } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators";
export default class AdminDeletePostsConfirmationController extends Controller.extend(
ModalFunctionality
) {
@controller adminUserIndex;
@alias("model.username") username;
@alias("model.post_count") postCount;
export default Controller.extend(ModalFunctionality, {
adminUserIndex: controller(),
username: alias("model.username"),
postCount: alias("model.post_count"),
onShow() {
this.set("value", null);
}
},
@discourseComputed("username", "postCount")
text(username, postCount) {
@ -23,27 +20,27 @@ export default class AdminDeletePostsConfirmationController extends Controller.e
username,
postCount,
});
}
},
@discourseComputed("username")
deleteButtonText(username) {
return I18n.t(`admin.user.delete_posts.confirmation.delete`, {
username,
});
}
},
@discourseComputed("value", "text")
deleteDisabled(value, text) {
return !value || text !== value;
}
},
@action
confirm() {
this.adminUserIndex.send("deleteAllPosts");
}
},
@action
close() {
this.send("closeModal");
}
}
},
});

View File

@ -1,8 +1,6 @@
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
export default class AdminDeleteUserPostsProgressController extends Controller.extend(
ModalFunctionality
) {
deletedPercentage = 0;
}
export default Controller.extend(ModalFunctionality, {
deletedPercentage: 0,
});

View File

@ -1,16 +1,13 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { A } from "@ember/array";
import Controller from "@ember/controller";
import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { ajax } from "discourse/lib/ajax";
import { observes } from "discourse-common/utils/decorators";
import { inject as service } from "@ember/service";
export default class AdminEditBadgeGroupingsController extends Controller.extend(
ModalFunctionality
) {
@service dialog;
export default Controller.extend(ModalFunctionality, {
dialog: service(),
@observes("model")
modelChanged() {
@ -25,7 +22,7 @@ export default class AdminEditBadgeGroupingsController extends Controller.extend
}
this.set("workingCopy", copy);
}
},
moveItem(item, delta) {
const copy = this.workingCopy;
@ -36,68 +33,55 @@ export default class AdminEditBadgeGroupingsController extends Controller.extend
copy.removeAt(index);
copy.insertAt(index + delta, item);
}
},
@action
up(item) {
this.moveItem(item, -1);
}
actions: {
up(item) {
this.moveItem(item, -1);
},
down(item) {
this.moveItem(item, 1);
},
delete(item) {
this.workingCopy.removeObject(item);
},
cancel() {
this.setProperties({ model: null, workingCopy: null });
this.send("closeModal");
},
edit(item) {
item.set("editing", true);
},
save(item) {
item.set("editing", false);
},
add() {
const obj = this.store.createRecord("badge-grouping", {
editing: true,
name: I18n.t("admin.badges.badge_grouping"),
});
this.workingCopy.pushObject(obj);
},
saveAll() {
let items = this.workingCopy;
const groupIds = items.map((i) => i.get("id") || -1);
const names = items.map((i) => i.get("name"));
@action
down(item) {
this.moveItem(item, 1);
}
@action
delete(item) {
this.workingCopy.removeObject(item);
}
@action
cancel() {
this.setProperties({ model: null, workingCopy: null });
this.send("closeModal");
}
@action
edit(item) {
item.set("editing", true);
}
@action
save(item) {
item.set("editing", false);
}
@action
add() {
const obj = this.store.createRecord("badge-grouping", {
editing: true,
name: I18n.t("admin.badges.badge_grouping"),
});
this.workingCopy.pushObject(obj);
}
@action
saveAll() {
let items = this.workingCopy;
const groupIds = items.map((i) => i.get("id") || -1);
const names = items.map((i) => i.get("name"));
ajax("/admin/badges/badge_groupings", {
data: { ids: groupIds, names },
type: "POST",
}).then(
(data) => {
items = this.model;
items.clear();
data.badge_groupings.forEach((g) => {
items.pushObject(this.store.createRecord("badge-grouping", g));
});
this.setProperties({ model: null, workingCopy: null });
this.send("closeModal");
},
() => this.dialog.alert(I18n.t("generic_error"))
);
}
}
ajax("/admin/badges/badge_groupings", {
data: { ids: groupIds, names },
type: "POST",
}).then(
(data) => {
items = this.model;
items.clear();
data.badge_groupings.forEach((g) => {
items.pushObject(this.store.createRecord("badge-grouping", g));
});
this.setProperties({ model: null, workingCopy: null });
this.send("closeModal");
},
() => this.dialog.alert(I18n.t("generic_error"))
);
},
},
});

View File

@ -5,17 +5,15 @@ import discourseComputed from "discourse-common/utils/decorators";
import { longDate } from "discourse/lib/formatter";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default class AdminIncomingEmailController extends Controller.extend(
ModalFunctionality
) {
export default Controller.extend(ModalFunctionality, {
@discourseComputed("model.date")
date(d) {
return longDate(d);
}
},
load(id) {
return IncomingEmail.find(id).then((result) => this.set("model", result));
}
},
loadFromBounced(id) {
return IncomingEmail.findByBounced(id)
@ -24,5 +22,5 @@ export default class AdminIncomingEmailController extends Controller.extend(
this.send("closeModal");
popupAjaxError(error);
});
}
}
},
});

View File

@ -1,46 +1,46 @@
import { alias, equal, match } from "@ember/object/computed";
import { COMPONENTS, THEMES } from "admin/models/theme";
import Controller, { inject as controller } from "@ember/controller";
import discourseComputed from "discourse-common/utils/decorators";
import { observes } from "@ember-decorators/object";
import { alias, equal, match } from "@ember/object/computed";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { POPULAR_THEMES } from "discourse-common/helpers/popular-themes";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { action, set } from "@ember/object";
import { set } from "@ember/object";
const MIN_NAME_LENGTH = 4;
export default class AdminInstallThemeController extends Controller.extend(
ModalFunctionality
) {
@controller adminCustomizeThemes;
@controller("adminCustomizeThemes") themesController;
export default Controller.extend(ModalFunctionality, {
adminCustomizeThemes: controller(),
themesController: controller("adminCustomizeThemes"),
popular: equal("selection", "popular"),
local: equal("selection", "local"),
remote: equal("selection", "remote"),
create: equal("selection", "create"),
directRepoInstall: equal("selection", "directRepoInstall"),
selection: "popular",
loading: false,
keyGenUrl: "/admin/themes/generate_key_pair",
importUrl: "/admin/themes/import",
recordType: "theme",
checkPrivate: match("uploadUrl", /^ssh:\/\/.+@.+$|.+@.+:.+$/),
localFile: null,
uploadUrl: null,
uploadName: null,
advancedVisible: false,
selectedType: alias("themesController.currentTab"),
component: equal("selectedType", COMPONENTS),
urlPlaceholder: "https://github.com/discourse/sample_theme",
@equal("selection", "popular") popular;
@equal("selection", "local") local;
@equal("selection", "remote") remote;
@equal("selection", "create") create;
@equal("selection", "directRepoInstall") directRepoInstall;
selection = "popular";
loading = false;
keyGenUrl = "/admin/themes/generate_key_pair";
importUrl = "/admin/themes/import";
recordType = "theme";
@match("uploadUrl", /^ssh:\/\/.+@.+$|.+@.+:.+$/) checkPrivate;
localFile = null;
uploadUrl = null;
uploadName = null;
advancedVisible = false;
@alias("themesController.currentTab") selectedType;
@equal("selectedType", COMPONENTS) component;
urlPlaceholder = "https://github.com/discourse/sample_theme";
init() {
this._super(...arguments);
createTypes = [
{ name: I18n.t("admin.customize.theme.theme"), value: THEMES },
{ name: I18n.t("admin.customize.theme.component"), value: COMPONENTS },
];
this.createTypes = [
{ name: I18n.t("admin.customize.theme.theme"), value: THEMES },
{ name: I18n.t("admin.customize.theme.component"), value: COMPONENTS },
];
},
@discourseComputed("themesController.installedThemes")
themes(installedThemes) {
@ -52,7 +52,7 @@ export default class AdminInstallThemeController extends Controller.extend(
}
return t;
});
}
},
@discourseComputed(
"loading",
@ -78,12 +78,12 @@ export default class AdminInstallThemeController extends Controller.extend(
(isLocal && !localFile) ||
(isCreate && nameTooShort)
);
}
},
@discourseComputed("name")
nameTooShort(name) {
return !name || name.length < MIN_NAME_LENGTH;
}
},
@discourseComputed("component")
placeholder(component) {
@ -92,7 +92,7 @@ export default class AdminInstallThemeController extends Controller.extend(
} else {
return I18n.t("admin.customize.theme.theme_name");
}
}
},
@observes("checkPrivate")
privateWasChecked() {
@ -108,7 +108,7 @@ export default class AdminInstallThemeController extends Controller.extend(
this._keyLoading = false;
});
}
}
},
@discourseComputed("selection", "themeCannotBeInstalled")
submitLabel(selection, themeCannotBeInstalled) {
@ -119,12 +119,12 @@ export default class AdminInstallThemeController extends Controller.extend(
return `admin.customize.theme.${
selection === "create" ? "create" : "install"
}`;
}
},
@discourseComputed("checkPrivate", "publicKey")
showPublicKey(checkPrivate, publicKey) {
return checkPrivate && publicKey;
}
},
onClose() {
this.setProperties({
@ -140,7 +140,7 @@ export default class AdminInstallThemeController extends Controller.extend(
repoName: null,
repoUrl: null,
});
}
},
themeHasSameUrl(theme, url) {
const themeUrl = theme.remote_theme && theme.remote_theme.remote_url;
@ -149,101 +149,100 @@ export default class AdminInstallThemeController extends Controller.extend(
url &&
url.replace(/\.git$/, "") === themeUrl.replace(/\.git$/, "")
);
}
},
@action
uploadLocaleFile() {
this.set("localFile", $("#file-input")[0].files[0]);
}
actions: {
uploadLocaleFile() {
this.set("localFile", $("#file-input")[0].files[0]);
},
@action
toggleAdvanced() {
this.toggleProperty("advancedVisible");
}
toggleAdvanced() {
this.toggleProperty("advancedVisible");
},
@action
installThemeFromList(url) {
this.set("uploadUrl", url);
this.send("installTheme");
}
installThemeFromList(url) {
this.set("uploadUrl", url);
this.send("installTheme");
},
@action
installTheme() {
if (this.create) {
this.set("loading", true);
const theme = this.store.createRecord(this.recordType);
theme
.save({ name: this.name, component: this.component })
.then(() => {
this.themesController.send("addTheme", theme);
this.send("closeModal");
})
.catch(popupAjaxError)
.finally(() => this.set("loading", false));
installTheme() {
if (this.create) {
this.set("loading", true);
const theme = this.store.createRecord(this.recordType);
theme
.save({ name: this.name, component: this.component })
.then(() => {
this.themesController.send("addTheme", theme);
this.send("closeModal");
})
.catch(popupAjaxError)
.finally(() => this.set("loading", false));
return;
}
let options = {
type: "POST",
};
if (this.local) {
options.processData = false;
options.contentType = false;
options.data = new FormData();
options.data.append("theme", this.localFile);
}
if (this.remote || this.popular || this.directRepoInstall) {
const duplicate = this.themesController.model.content.find((theme) =>
this.themeHasSameUrl(theme, this.uploadUrl)
);
if (duplicate && !this.duplicateRemoteThemeWarning) {
const warning = I18n.t("admin.customize.theme.duplicate_remote_theme", {
name: duplicate.name,
});
this.set("duplicateRemoteThemeWarning", warning);
return;
}
options.data = {
remote: this.uploadUrl,
branch: this.branch,
public_key: this.publicKey,
let options = {
type: "POST",
};
}
// User knows that theme cannot be installed, but they want to continue
// to force install it.
if (this.themeCannotBeInstalled) {
options.data["force"] = true;
}
if (this.local) {
options.processData = false;
options.contentType = false;
options.data = new FormData();
options.data.append("theme", this.localFile);
}
if (this.get("model.user_id")) {
// Used by theme-creator
options.data["user_id"] = this.get("model.user_id");
}
this.set("loading", true);
ajax(this.importUrl, options)
.then((result) => {
const theme = this.store.createRecord(this.recordType, result.theme);
this.adminCustomizeThemes.send("addTheme", theme);
this.send("closeModal");
})
.then(() => {
this.set("publicKey", null);
})
.catch((error) => {
if (!this.publicKey || this.themeCannotBeInstalled) {
return popupAjaxError(error);
}
this.set(
"themeCannotBeInstalled",
I18n.t("admin.customize.theme.force_install")
if (this.remote || this.popular || this.directRepoInstall) {
const duplicate = this.themesController.model.content.find((theme) =>
this.themeHasSameUrl(theme, this.uploadUrl)
);
})
.finally(() => this.set("loading", false));
}
}
if (duplicate && !this.duplicateRemoteThemeWarning) {
const warning = I18n.t(
"admin.customize.theme.duplicate_remote_theme",
{ name: duplicate.name }
);
this.set("duplicateRemoteThemeWarning", warning);
return;
}
options.data = {
remote: this.uploadUrl,
branch: this.branch,
public_key: this.publicKey,
};
}
// User knows that theme cannot be installed, but they want to continue
// to force install it.
if (this.themeCannotBeInstalled) {
options.data["force"] = true;
}
if (this.get("model.user_id")) {
// Used by theme-creator
options.data["user_id"] = this.get("model.user_id");
}
this.set("loading", true);
ajax(this.importUrl, options)
.then((result) => {
const theme = this.store.createRecord(this.recordType, result.theme);
this.adminCustomizeThemes.send("addTheme", theme);
this.send("closeModal");
})
.then(() => {
this.set("publicKey", null);
})
.catch((error) => {
if (!this.publicKey || this.themeCannotBeInstalled) {
return popupAjaxError(error);
}
this.set(
"themeCannotBeInstalled",
I18n.t("admin.customize.theme.force_install")
);
})
.finally(() => this.set("loading", false));
},
},
});

View File

@ -1,21 +1,18 @@
import { alias } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller";
import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { action } from "@ember/object";
import { alias } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators";
export default class AdminMergeUsersConfirmationController extends Controller.extend(
ModalFunctionality
) {
@controller adminUserIndex;
@alias("model.username") username;
@alias("model.targetUsername") targetUsername;
export default Controller.extend(ModalFunctionality, {
adminUserIndex: controller(),
username: alias("model.username"),
targetUsername: alias("model.targetUsername"),
onShow() {
this.set("value", null);
}
},
@discourseComputed("username", "targetUsername")
text(username, targetUsername) {
@ -23,28 +20,28 @@ export default class AdminMergeUsersConfirmationController extends Controller.ex
username,
targetUsername,
});
}
},
@discourseComputed("username")
mergeButtonText(username) {
return I18n.t(`admin.user.merge.confirmation.transfer_and_delete`, {
username,
});
}
},
@discourseComputed("value", "text")
mergeDisabled(value, text) {
return !value || text !== value;
}
},
@action
confirm() {
this.adminUserIndex.send("merge", this.targetUsername);
this.send("closeModal");
}
},
@action
close() {
this.send("closeModal");
}
}
},
});

View File

@ -4,18 +4,16 @@ import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { bind } from "discourse-common/utils/decorators";
export default class AdminMergeUsersProgressController extends Controller.extend(
ModalFunctionality
) {
message = I18n.t("admin.user.merging_user");
export default Controller.extend(ModalFunctionality, {
message: I18n.t("admin.user.merging_user"),
onShow() {
this.messageBus.subscribe("/merge_user", this.onMessage);
}
},
onClose() {
this.messageBus.unsubscribe("/merge_user", this.onMessage);
}
},
@bind
onMessage(data) {
@ -32,5 +30,5 @@ export default class AdminMergeUsersProgressController extends Controller.extend
} else if (data.failed) {
this.set("message", I18n.t("admin.user.merge_failed"));
}
}
}
},
});

View File

@ -1,46 +1,43 @@
import { alias } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller";
import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { action, get } from "@ember/object";
import { alias } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators";
export default class AdminMergeUsersPromptController extends Controller.extend(
ModalFunctionality
) {
@controller adminUserIndex;
@alias("model.username") username;
export default Controller.extend(ModalFunctionality, {
adminUserIndex: controller(),
username: alias("model.username"),
onShow() {
this.set("targetUsername", null);
}
},
@discourseComputed("username", "targetUsername")
mergeDisabled(username, targetUsername) {
return !targetUsername || username === targetUsername;
}
},
@discourseComputed("username")
mergeButtonText(username) {
return I18n.t(`admin.user.merge.confirmation.transfer_and_delete`, {
username,
});
}
},
@action
showConfirmation() {
this.send("closeModal");
this.adminUserIndex.send("showMergeConfirmation", this.targetUsername);
}
},
@action
close() {
this.send("closeModal");
}
},
@action
updateUsername(selected) {
this.set("targetUsername", get(selected, "firstObject"));
}
}
},
});

View File

@ -1,31 +1,29 @@
import { inject as service } from "@ember/service";
import Controller from "@ember/controller";
import { action } from "@ember/object";
import { next } from "@ember/runloop";
import { inject as service } from "@ember/service";
import { isEmpty } from "@ember/utils";
import discourseComputed from "discourse-common/utils/decorators";
import { extractError } from "discourse/lib/ajax-error";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import I18n from "I18n";
export default class AdminPenalizeUserController extends Controller.extend(
ModalFunctionality
) {
@service dialog;
export default Controller.extend(ModalFunctionality, {
dialog: service(),
loadingUser = false;
errorMessage = null;
penaltyType = null;
penalizeUntil = null;
reason = null;
message = null;
postId = null;
postAction = null;
postEdit = null;
user = null;
otherUserIds = null;
loading = false;
confirmClose = false;
loadingUser: false,
errorMessage: null,
penaltyType: null,
penalizeUntil: null,
reason: null,
message: null,
postId: null,
postAction: null,
postEdit: null,
user: null,
otherUserIds: null,
loading: false,
confirmClose: false,
onShow() {
this.setProperties({
@ -46,11 +44,11 @@ export default class AdminPenalizeUserController extends Controller.extend(
message: null,
confirmClose: false,
});
}
},
finishedSetup() {
this.set("penalizeUntil", this.user?.next_penalty);
}
},
beforeClose() {
if (this.confirmClose) {
@ -75,7 +73,7 @@ export default class AdminPenalizeUserController extends Controller.extend(
return false;
}
}
},
@discourseComputed("penaltyType")
modalTitle(penaltyType) {
@ -84,7 +82,7 @@ export default class AdminPenalizeUserController extends Controller.extend(
} else if (penaltyType === "silence") {
return "admin.user.silence_modal_title";
}
}
},
@discourseComputed("penaltyType")
buttonLabel(penaltyType) {
@ -93,7 +91,7 @@ export default class AdminPenalizeUserController extends Controller.extend(
} else if (penaltyType === "silence") {
return "admin.user.silence";
}
}
},
@discourseComputed(
"user.penalty_counts.suspended",
@ -104,7 +102,7 @@ export default class AdminPenalizeUserController extends Controller.extend(
SUSPENDED: suspendedCount,
SILENCED: silencedCount,
});
}
},
@discourseComputed("penaltyType", "user.canSuspend", "user.canSilence")
canPenalize(penaltyType, canSuspend, canSilence) {
@ -115,12 +113,12 @@ export default class AdminPenalizeUserController extends Controller.extend(
}
return false;
}
},
@discourseComputed("penalizing", "penalizeUntil", "reason")
submitDisabled(penalizing, penalizeUntil, reason) {
return penalizing || isEmpty(penalizeUntil) || !reason || reason.length < 1;
}
},
@action
async penalizeUser() {
@ -166,5 +164,5 @@ export default class AdminPenalizeUserController extends Controller.extend(
} finally {
this.set("penalizing", false);
}
}
}
},
});

View File

@ -1,19 +1,15 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import Controller from "@ember/controller";
import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { ajax } from "discourse/lib/ajax";
import { inject as service } from "@ember/service";
export default class AdminReseedController extends Controller.extend(
ModalFunctionality
) {
@service dialog;
loading = true;
reseeding = false;
categories = null;
topics = null;
export default Controller.extend(ModalFunctionality, {
dialog: service(),
loading: true,
reseeding: false,
categories: null,
topics: null,
onShow() {
ajax("/admin/customize/reseed")
@ -24,26 +20,27 @@ export default class AdminReseedController extends Controller.extend(
});
})
.finally(() => this.set("loading", false));
}
},
_extractSelectedIds(items) {
return items.filter((item) => item.selected).map((item) => item.id);
}
},
@action
reseed() {
this.set("reseeding", true);
ajax("/admin/customize/reseed", {
data: {
category_ids: this._extractSelectedIds(this.categories),
topic_ids: this._extractSelectedIds(this.topics),
},
type: "POST",
})
.catch(() => this.dialog.alert(I18n.t("generic_error")))
.finally(() => {
this.set("reseeding", false);
this.send("closeModal");
});
}
}
actions: {
reseed() {
this.set("reseeding", true);
ajax("/admin/customize/reseed", {
data: {
category_ids: this._extractSelectedIds(this.categories),
topic_ids: this._extractSelectedIds(this.topics),
},
type: "POST",
})
.catch(() => this.dialog.alert(I18n.t("generic_error")))
.finally(() => {
this.set("reseeding", false);
this.send("closeModal");
});
},
},
});

View File

@ -1,6 +1,4 @@
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
export default class AdminStaffActionLogDetailsController extends Controller.extend(
ModalFunctionality
) {}
export default Controller.extend(ModalFunctionality);

View File

@ -1,39 +1,35 @@
import { action } from "@ember/object";
import Controller, { inject as controller } from "@ember/controller";
import discourseComputed from "discourse-common/utils/decorators";
import ModalFunctionality from "discourse/mixins/modal-functionality";
export default class AdminStartBackupController extends Controller.extend(
ModalFunctionality
) {
@controller adminBackupsLogs;
export default Controller.extend(ModalFunctionality, {
adminBackupsLogs: controller(),
@discourseComputed
warningMessage() {
// this is never shown here, but we may want to show different
// messages in plugins
return "";
}
},
@discourseComputed
yesLabel() {
return "yes_value";
}
},
@action
startBackupWithUploads() {
this.send("closeModal");
this.send("startBackup", true);
}
actions: {
startBackupWithUploads() {
this.send("closeModal");
this.send("startBackup", true);
},
@action
startBackupWithoutUploads() {
this.send("closeModal");
this.send("startBackup", false);
}
startBackupWithoutUploads() {
this.send("closeModal");
this.send("startBackup", false);
},
@action
cancel() {
this.send("closeModal");
}
}
cancel() {
this.send("closeModal");
},
},
});

View File

@ -2,9 +2,7 @@ import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { ajax } from "discourse/lib/ajax";
export default class AdminThemeChangeController extends Controller.extend(
ModalFunctionality
) {
export default Controller.extend(ModalFunctionality, {
loadDiff() {
this.set("loading", true);
ajax(
@ -13,5 +11,5 @@ export default class AdminThemeChangeController extends Controller.extend(
this.set("loading", false);
this.set("diff", diff.side_by_side);
});
}
}
},
});

View File

@ -1,32 +1,30 @@
import { observes, on } from "@ember-decorators/object";
import { observes, on } from "discourse-common/utils/decorators";
import Controller from "@ember/controller";
import { action } from "@ember/object";
import ModalFunctionality from "discourse/mixins/modal-functionality";
export default class AdminUploadedImageListController extends Controller.extend(
ModalFunctionality
) {
export default Controller.extend(ModalFunctionality, {
@on("init")
@observes("model.value")
_setup() {
const value = this.get("model.value");
this.set("images", value && value.length ? value.split("|") : []);
}
},
@action
remove(url, event) {
event?.preventDefault();
this.images.removeObject(url);
}
},
@action
uploadDone({ url }) {
this.images.addObject(url);
}
actions: {
uploadDone({ url }) {
this.images.addObject(url);
},
@action
close() {
this.save(this.images.join("|"));
this.send("closeModal");
}
}
close() {
this.save(this.images.join("|"));
this.send("closeModal");
},
},
});

View File

@ -1,19 +1,16 @@
import { equal } from "@ember/object/computed";
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import discourseComputed from "discourse-common/utils/decorators";
import { equal } from "@ember/object/computed";
import {
createWatchedWordRegExp,
toWatchedWord,
} from "discourse-common/utils/watched-words";
export default class AdminWatchedWordTestController extends Controller.extend(
ModalFunctionality
) {
@equal("model.nameKey", "replace") isReplace;
@equal("model.nameKey", "tag") isTag;
@equal("model.nameKey", "link") isLink;
export default Controller.extend(ModalFunctionality, {
isReplace: equal("model.nameKey", "replace"),
isTag: equal("model.nameKey", "tag"),
isLink: equal("model.nameKey", "link"),
@discourseComputed(
"value",
@ -74,5 +71,5 @@ export default class AdminWatchedWordTestController extends Controller.extend(
return matches;
}
}
}
},
});

View File

@ -2,7 +2,4 @@ import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import ModalUpdateExistingUsers from "discourse/mixins/modal-update-existing-users";
export default class SiteSettingDefaultCategoriesController extends Controller.extend(
ModalFunctionality,
ModalUpdateExistingUsers
) {}
export default Controller.extend(ModalFunctionality, ModalUpdateExistingUsers);

View File

@ -2,7 +2,7 @@ import Helper from "@ember/component/helper";
import { iconHTML } from "discourse-common/lib/icon-library";
import { htmlSafe } from "@ember/template";
export default class DispositionIcon extends Helper {
export default Helper.extend({
compute([disposition]) {
if (!disposition) {
return null;
@ -24,5 +24,5 @@ export default class DispositionIcon extends Helper {
}
}
return htmlSafe(iconHTML(icon, { title }));
}
}
},
});

View File

@ -7,17 +7,19 @@ const GENERAL_ATTRIBUTES = [
"release_notes_link",
];
export default class AdminDashboard extends EmberObject {
static fetch() {
const AdminDashboard = EmberObject.extend({});
AdminDashboard.reopenClass({
fetch() {
return ajax("/admin/dashboard.json").then((json) => {
const model = AdminDashboard.create();
model.set("version_check", json.version_check);
return model;
});
}
},
static fetchGeneral() {
fetchGeneral() {
return ajax("/admin/dashboard/general.json").then((json) => {
const model = AdminDashboard.create();
@ -32,13 +34,15 @@ export default class AdminDashboard extends EmberObject {
return model;
});
}
},
static fetchProblems() {
fetchProblems() {
return ajax("/admin/dashboard/problems.json").then((json) => {
const model = AdminDashboard.create(json);
model.set("loaded", true);
return model;
});
}
}
},
});
export default AdminDashboard;

View File

@ -10,30 +10,14 @@ import { popupAjaxError } from "discourse/lib/ajax-error";
import { propertyNotEqual } from "discourse/lib/computed";
import { userPath } from "discourse/lib/url";
export default class AdminUser extends User {
static find(user_id) {
return ajax(`/admin/users/${user_id}.json`).then((result) => {
result.loadedDetails = true;
return AdminUser.create(result);
});
}
const wrapAdmin = (user) => (user ? AdminUser.create(user) : null);
static findAll(query, userFilter) {
return ajax(`/admin/users/list/${query}.json`, {
data: userFilter,
}).then((users) => users.map((u) => AdminUser.create(u)));
}
const AdminUser = User.extend({
adminUserView: true,
customGroups: filter("groups", (g) => !g.automatic && Group.create(g)),
automaticGroups: filter("groups", (g) => g.automatic && Group.create(g)),
adminUserView = true;
@filter("groups", (g) => !g.automatic && Group.create(g)) customGroups;
@filter("groups", (g) => g.automatic && Group.create(g)) automaticGroups;
@or("active", "staged") canViewProfile;
@gt("bounce_score", 0) canResetBounceScore;
@propertyNotEqual("originalTrustLevel", "trust_level") dirty;
@lt("trust_level", 4) canLockTrustLevel;
@not("staff") canSuspend;
@not("staff") canSilence;
canViewProfile: or("active", "staged"),
@discourseComputed("bounce_score", "reset_bounce_score_after")
bounceScore(bounce_score, reset_bounce_score_after) {
@ -44,7 +28,7 @@ export default class AdminUser extends User {
} else {
return bounce_score;
}
}
},
@discourseComputed("bounce_score")
bounceScoreExplanation(bounce_score) {
@ -55,12 +39,14 @@ export default class AdminUser extends User {
} else {
return I18n.t("admin.user.bounce_score_explanation.threshold_reached");
}
}
},
@discourseComputed
bounceLink() {
return getURL("/admin/email/bounced");
}
},
canResetBounceScore: gt("bounce_score", 0),
resetBounceScore() {
return ajax(`/admin/users/${this.id}/reset_bounce_score`, {
@ -71,14 +57,14 @@ export default class AdminUser extends User {
reset_bounce_score_after: null,
})
);
}
},
groupAdded(added) {
return ajax(`/admin/users/${this.id}/groups`, {
type: "POST",
data: { group_id: added.id },
}).then(() => this.groups.pushObject(added));
}
},
groupRemoved(groupId) {
return ajax(`/admin/users/${this.id}/groups/${groupId}`, {
@ -89,13 +75,13 @@ export default class AdminUser extends User {
this.set("primary_group_id", null);
}
});
}
},
deleteAllPosts() {
return ajax(`/admin/users/${this.get("id")}/delete_posts_batch`, {
type: "PUT",
});
}
},
revokeAdmin() {
return ajax(`/admin/users/${this.id}/revoke_admin`, {
@ -111,7 +97,7 @@ export default class AdminUser extends User {
can_delete_all_posts: resp.can_delete_all_posts,
});
});
}
},
grantAdmin(data) {
return ajax(`/admin/users/${this.id}/grant_admin`, {
@ -128,7 +114,7 @@ export default class AdminUser extends User {
return resp;
});
}
},
revokeModeration() {
return ajax(`/admin/users/${this.id}/revoke_moderation`, {
@ -144,7 +130,7 @@ export default class AdminUser extends User {
});
})
.catch(popupAjaxError);
}
},
grantModeration() {
return ajax(`/admin/users/${this.id}/grant_moderation`, {
@ -160,7 +146,7 @@ export default class AdminUser extends User {
});
})
.catch(popupAjaxError);
}
},
disableSecondFactor() {
return ajax(`/admin/users/${this.id}/disable_second_factor`, {
@ -170,7 +156,7 @@ export default class AdminUser extends User {
this.set("second_factor_enabled", false);
})
.catch(popupAjaxError);
}
},
approve(approvedBy) {
return ajax(`/admin/users/${this.id}/approve`, {
@ -182,76 +168,83 @@ export default class AdminUser extends User {
approved_by: approvedBy,
});
});
}
},
setOriginalTrustLevel() {
this.set("originalTrustLevel", this.trust_level);
}
},
dirty: propertyNotEqual("originalTrustLevel", "trust_level"),
saveTrustLevel() {
return ajax(`/admin/users/${this.id}/trust_level`, {
type: "PUT",
data: { level: this.trust_level },
});
}
},
restoreTrustLevel() {
this.set("trust_level", this.originalTrustLevel);
}
},
lockTrustLevel(locked) {
return ajax(`/admin/users/${this.id}/trust_level_lock`, {
type: "PUT",
data: { locked: !!locked },
});
}
},
canLockTrustLevel: lt("trust_level", 4),
canSuspend: not("staff"),
canSilence: not("staff"),
@discourseComputed("suspended_till", "suspended_at")
suspendDuration(suspendedTill, suspendedAt) {
suspendedAt = moment(suspendedAt);
suspendedTill = moment(suspendedTill);
return suspendedAt.format("L") + " - " + suspendedTill.format("L");
}
},
suspend(data) {
return ajax(`/admin/users/${this.id}/suspend`, {
type: "PUT",
data,
}).then((result) => this.setProperties(result.suspension));
}
},
unsuspend() {
return ajax(`/admin/users/${this.id}/unsuspend`, {
type: "PUT",
}).then((result) => this.setProperties(result.suspension));
}
},
logOut() {
return ajax("/admin/users/" + this.id + "/log_out", {
type: "POST",
data: { username_or_email: this.username },
});
}
},
impersonate() {
return ajax("/admin/impersonate", {
type: "POST",
data: { username_or_email: this.username },
});
}
},
activate() {
return ajax(`/admin/users/${this.id}/activate`, {
type: "PUT",
});
}
},
deactivate() {
return ajax(`/admin/users/${this.id}/deactivate`, {
type: "PUT",
data: { context: document.location.pathname },
});
}
},
unsilence() {
this.set("silencingUser", true);
@ -261,7 +254,7 @@ export default class AdminUser extends User {
})
.then((result) => this.setProperties(result.unsilence))
.finally(() => this.set("silencingUser", false));
}
},
silence(data) {
this.set("silencingUser", true);
@ -272,20 +265,20 @@ export default class AdminUser extends User {
})
.then((result) => this.setProperties(result.silence))
.finally(() => this.set("silencingUser", false));
}
},
sendActivationEmail() {
return ajax(userPath("action/send_activation_email"), {
type: "POST",
data: { username: this.username },
});
}
},
anonymize() {
return ajax(`/admin/users/${this.id}/anonymize.json`, {
type: "PUT",
});
}
},
destroy(formData) {
return ajax(`/admin/users/${this.id}.json`, {
@ -302,14 +295,14 @@ export default class AdminUser extends User {
.catch(() => {
this.find(this.id).then((u) => this.setProperties(u));
});
}
},
merge(formData) {
return ajax(`/admin/users/${this.id}/merge.json`, {
type: "POST",
data: formData,
});
}
},
loadDetails() {
if (this.loadedDetails) {
@ -320,29 +313,23 @@ export default class AdminUser extends User {
const userProperties = Object.assign(result, { loadedDetails: true });
this.setProperties(userProperties);
});
}
},
@discourseComputed("tl3_requirements")
tl3Requirements(requirements) {
if (requirements) {
return this.store.createRecord("tl3Requirements", requirements);
}
}
},
@discourseComputed("suspended_by")
suspendedBy(user) {
return user ? AdminUser.create(user) : null;
}
suspendedBy: wrapAdmin,
@discourseComputed("silenced_by")
silencedBy(user) {
return user ? AdminUser.create(user) : null;
}
silencedBy: wrapAdmin,
@discourseComputed("approved_by")
approvedBy(user) {
return user ? AdminUser.create(user) : null;
}
approvedBy: wrapAdmin,
deleteSSORecord() {
return ajax(`/admin/users/${this.id}/sso_record.json`, {
@ -352,5 +339,22 @@ export default class AdminUser extends User {
this.set("single_sign_on_record", null);
})
.catch(popupAjaxError);
}
}
},
});
AdminUser.reopenClass({
find(user_id) {
return ajax(`/admin/users/${user_id}.json`).then((result) => {
result.loadedDetails = true;
return AdminUser.create(result);
});
},
findAll(query, userFilter) {
return ajax(`/admin/users/list/${query}.json`, {
data: userFilter,
}).then((users) => users.map((u) => AdminUser.create(u)));
},
});
export default AdminUser;

View File

@ -1,26 +1,24 @@
import { computed } from "@ember/object";
import AdminUser from "admin/models/admin-user";
import RestModel from "discourse/models/rest";
import { ajax } from "discourse/lib/ajax";
import { computed } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators";
import { fmt } from "discourse/lib/computed";
export default class ApiKey extends RestModel {
@fmt("truncated_key", "%@...") truncatedKey;
@computed("_user")
get user() {
return this._user;
}
set user(value) {
if (value && !(value instanceof AdminUser)) {
this.set("_user", AdminUser.create(value));
} else {
this.set("_user", value);
}
return this._user;
}
const ApiKey = RestModel.extend({
user: computed("_user", {
get() {
return this._user;
},
set(key, value) {
if (value && !(value instanceof AdminUser)) {
this.set("_user", AdminUser.create(value));
} else {
this.set("_user", value);
}
return this._user;
},
}),
@discourseComputed("description")
shortDescription(description) {
@ -28,28 +26,32 @@ export default class ApiKey extends RestModel {
return description;
}
return `${description.substring(0, 40)}...`;
}
},
truncatedKey: fmt("truncated_key", "%@..."),
revoke() {
return ajax(`${this.basePath}/revoke`, {
type: "POST",
}).then((result) => this.setProperties(result.api_key));
}
},
undoRevoke() {
return ajax(`${this.basePath}/undo-revoke`, {
type: "POST",
}).then((result) => this.setProperties(result.api_key));
}
},
createProperties() {
return this.getProperties("description", "username", "scopes");
}
},
@discourseComputed()
basePath() {
return this.store
.adapterFor("api-key")
.pathFor(this.store, "api-key", this.id);
}
}
},
});
export default ApiKey;

View File

@ -1,12 +1,12 @@
import { not } from "@ember/object/computed";
import EmberObject from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators";
import { not } from "@ember/object/computed";
export default class BackupStatus extends EmberObject {
@not("restoreEnabled") restoreDisabled;
export default EmberObject.extend({
restoreDisabled: not("restoreEnabled"),
@discourseComputed("allowRestore", "isOperationRunning")
restoreEnabled(allowRestore, isOperationRunning) {
return allowRestore && !isOperationRunning;
}
}
},
});

View File

@ -2,12 +2,25 @@ import EmberObject from "@ember/object";
import MessageBus from "message-bus-client";
import { ajax } from "discourse/lib/ajax";
export default class Backup extends EmberObject {
static find() {
return ajax("/admin/backups.json");
}
const Backup = EmberObject.extend({
destroy() {
return ajax("/admin/backups/" + this.filename, { type: "DELETE" });
},
static start(withUploads) {
restore() {
return ajax("/admin/backups/" + this.filename + "/restore", {
type: "POST",
data: { client_id: MessageBus.clientId },
});
},
});
Backup.reopenClass({
find() {
return ajax("/admin/backups.json");
},
start(withUploads) {
if (withUploads === undefined) {
withUploads = true;
}
@ -18,28 +31,19 @@ export default class Backup extends EmberObject {
client_id: MessageBus.clientId,
},
});
}
},
static cancel() {
cancel() {
return ajax("/admin/backups/cancel.json", {
type: "DELETE",
});
}
},
static rollback() {
rollback() {
return ajax("/admin/backups/rollback.json", {
type: "POST",
});
}
},
});
destroy() {
return ajax("/admin/backups/" + this.filename, { type: "DELETE" });
}
restore() {
return ajax("/admin/backups/" + this.filename + "/restore", {
type: "POST",
data: { client_id: MessageBus.clientId },
});
}
}
export default Backup;

View File

@ -1,19 +1,19 @@
import discourseComputed from "discourse-common/utils/decorators";
import { observes, on } from "@ember-decorators/object";
import discourseComputed, {
observes,
on,
} from "discourse-common/utils/decorators";
import EmberObject from "@ember/object";
import I18n from "I18n";
import { propertyNotEqual } from "discourse/lib/computed";
export default class ColorSchemeColor extends EmberObject {
// Whether the current value is different than Discourse's default color scheme.
@propertyNotEqual("hex", "default_hex") overridden;
const ColorSchemeColor = EmberObject.extend({
@on("init")
startTrackingChanges() {
this.set("originals", { hex: this.hex || "FFFFFF" });
// force changed property to be recalculated
this.notifyPropertyChange("hex");
}
},
// Whether value has changed since it was last saved.
@discourseComputed("hex")
@ -26,23 +26,26 @@ export default class ColorSchemeColor extends EmberObject {
}
return false;
}
},
// Whether the current value is different than Discourse's default color scheme.
overridden: propertyNotEqual("hex", "default_hex"),
// Whether the saved value is different than Discourse's default color scheme.
@discourseComputed("default_hex", "hex")
savedIsOverriden(defaultHex) {
return this.originals.hex !== defaultHex;
}
},
revert() {
this.set("hex", this.default_hex);
}
},
undo() {
if (this.originals) {
this.set("hex", this.originals.hex);
}
}
},
@discourseComputed("name")
translatedName(name) {
@ -51,7 +54,7 @@ export default class ColorSchemeColor extends EmberObject {
} else {
return name;
}
}
},
@discourseComputed("name")
description(name) {
@ -60,7 +63,7 @@ export default class ColorSchemeColor extends EmberObject {
} else {
return "";
}
}
},
/**
brightness returns a number between 0 (darkest) to 255 (brightest).
@ -87,17 +90,19 @@ export default class ColorSchemeColor extends EmberObject {
1000
);
}
}
},
@observes("hex")
hexValueChanged() {
if (this.hex) {
this.set("hex", this.hex.toString().replace(/[^0-9a-fA-F]/g, ""));
}
}
},
@discourseComputed("hex")
valid(hex) {
return hex.match(/^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/) !== null;
}
}
},
});
export default ColorSchemeColor;

View File

@ -1,4 +1,3 @@
import { not } from "@ember/object/computed";
import { A } from "@ember/array";
import ArrayProxy from "@ember/array/proxy";
import ColorSchemeColor from "admin/models/color-scheme-color";
@ -6,56 +5,26 @@ import EmberObject from "@ember/object";
import I18n from "I18n";
import { ajax } from "discourse/lib/ajax";
import discourseComputed from "discourse-common/utils/decorators";
import { not } from "@ember/object/computed";
class ColorSchemes extends ArrayProxy {}
export default class ColorScheme extends EmberObject {
static findAll() {
const colorSchemes = ColorSchemes.create({ content: [], loading: true });
return ajax("/admin/color_schemes").then((all) => {
all.forEach((colorScheme) => {
colorSchemes.pushObject(
ColorScheme.create({
id: colorScheme.id,
name: colorScheme.name,
is_base: colorScheme.is_base,
theme_id: colorScheme.theme_id,
theme_name: colorScheme.theme_name,
base_scheme_id: colorScheme.base_scheme_id,
user_selectable: colorScheme.user_selectable,
colors: colorScheme.colors.map((c) => {
return ColorSchemeColor.create({
name: c.name,
hex: c.hex,
default_hex: c.default_hex,
is_advanced: c.is_advanced,
});
}),
})
);
});
return colorSchemes;
});
}
@not("id") newRecord;
const ColorScheme = EmberObject.extend({
init() {
super.init(...arguments);
this._super(...arguments);
this.startTrackingChanges();
}
},
@discourseComputed
description() {
return "" + this.name;
}
},
startTrackingChanges() {
this.set("originals", {
name: this.name,
user_selectable: this.user_selectable,
});
}
},
schemeJson() {
const buffer = [];
@ -64,7 +33,7 @@ export default class ColorScheme extends EmberObject {
});
return [`"${this.name}": {`, buffer.join(",\n"), "}"].join("\n");
}
},
copy() {
const newScheme = ColorScheme.create({
@ -78,7 +47,7 @@ export default class ColorScheme extends EmberObject {
);
});
return newScheme;
}
},
@discourseComputed(
"name",
@ -101,7 +70,7 @@ export default class ColorScheme extends EmberObject {
}
return false;
}
},
@discourseComputed("changed")
disableSave(changed) {
@ -110,7 +79,9 @@ export default class ColorScheme extends EmberObject {
}
return !changed || this.saving || this.colors.any((c) => !c.get("valid"));
}
},
newRecord: not("id"),
save(opts) {
if (this.is_base || this.disableSave) {
@ -153,7 +124,7 @@ export default class ColorScheme extends EmberObject {
this.setProperties({ savingStatus: I18n.t("saved"), saving: false });
this.notifyPropertyChange("description");
});
}
},
updateUserSelectable(value) {
if (!this.id) {
@ -166,11 +137,45 @@ export default class ColorScheme extends EmberObject {
dataType: "json",
contentType: "application/json",
});
}
},
destroy() {
if (this.id) {
return ajax(`/admin/color_schemes/${this.id}`, { type: "DELETE" });
}
}
}
},
});
const ColorSchemes = ArrayProxy.extend({});
ColorScheme.reopenClass({
findAll() {
const colorSchemes = ColorSchemes.create({ content: [], loading: true });
return ajax("/admin/color_schemes").then((all) => {
all.forEach((colorScheme) => {
colorSchemes.pushObject(
ColorScheme.create({
id: colorScheme.id,
name: colorScheme.name,
is_base: colorScheme.is_base,
theme_id: colorScheme.theme_id,
theme_name: colorScheme.theme_name,
base_scheme_id: colorScheme.base_scheme_id,
user_selectable: colorScheme.user_selectable,
colors: colorScheme.colors.map((c) => {
return ColorSchemeColor.create({
name: c.name,
hex: c.hex,
default_hex: c.default_hex,
is_advanced: c.is_advanced,
});
}),
})
);
});
return colorSchemes;
});
},
});
export default ColorScheme;

View File

@ -3,8 +3,10 @@ import EmberObject from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import getURL from "discourse-common/lib/get-url";
export default class EmailLog extends EmberObject {
static create(attrs) {
const EmailLog = EmberObject.extend({});
EmailLog.reopenClass({
create(attrs) {
attrs = attrs || {};
if (attrs.user) {
@ -15,10 +17,10 @@ export default class EmailLog extends EmberObject {
attrs.post_url = getURL(attrs.post_url);
}
return super.create(attrs);
}
return this._super(attrs);
},
static findAll(filter, offset) {
findAll(filter, offset) {
filter = filter || {};
offset = offset || 0;
@ -28,5 +30,7 @@ export default class EmailLog extends EmberObject {
return ajax(`/admin/email/${status}.json?offset=${offset}`, {
data: filter,
}).then((logs) => logs.map((log) => EmailLog.create(log)));
}
}
},
});
export default EmailLog;

View File

@ -1,21 +1,25 @@
import EmberObject from "@ember/object";
import { ajax } from "discourse/lib/ajax";
export default class EmailPreview extends EmberObject {
static findDigest(username, lastSeenAt) {
return ajax("/admin/email/preview-digest.json", {
data: { last_seen_at: lastSeenAt || oneWeekAgo(), username },
}).then((result) => EmailPreview.create(result));
}
static sendDigest(username, lastSeenAt, email) {
return ajax("/admin/email/send-digest.json", {
type: "POST",
data: { last_seen_at: lastSeenAt || oneWeekAgo(), username, email },
});
}
}
const EmailPreview = EmberObject.extend({});
export function oneWeekAgo() {
return moment().locale("en").subtract(7, "days").format("YYYY-MM-DD");
}
EmailPreview.reopenClass({
findDigest(username, lastSeenAt) {
return ajax("/admin/email/preview-digest.json", {
data: { last_seen_at: lastSeenAt || oneWeekAgo(), username },
}).then((result) => EmailPreview.create(result));
},
sendDigest(username, lastSeenAt, email) {
return ajax("/admin/email/send-digest.json", {
type: "POST",
data: { last_seen_at: lastSeenAt || oneWeekAgo(), username, email },
});
},
});
export default EmailPreview;

View File

@ -1,10 +1,14 @@
import EmberObject from "@ember/object";
import { ajax } from "discourse/lib/ajax";
export default class EmailSettings extends EmberObject {
static find() {
const EmailSettings = EmberObject.extend({});
EmailSettings.reopenClass({
find() {
return ajax("/admin/email.json").then(function (settings) {
return EmailSettings.create(settings);
});
}
}
},
});
export default EmailSettings;

View File

@ -1,10 +1,10 @@
import RestModel from "discourse/models/rest";
export default class EmailStyle extends RestModel {
changed = false;
export default RestModel.extend({
changed: false,
setField(fieldName, value) {
this.set(`${fieldName}`, value);
this.set("changed", true);
}
}
},
});

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