Version bump

This commit is contained in:
Neil Lalonde 2021-11-15 11:15:15 -05:00
commit fcbfd7eccd
No known key found for this signature in database
GPG Key ID: FF871CA9037D0A91
939 changed files with 13227 additions and 8800 deletions

View File

@ -5,14 +5,41 @@
"eol-last": 2
},
"globals": {
"_": "off",
"acceptance": "off",
"asyncRender": "off",
"bootbox": "off",
"click": "off",
"count": "off",
"currentPath": "off",
"currentRouteName": "off",
"currentURL": "off",
"currentUser": "off",
"Discourse": "off",
"exists": "off",
"fillIn": "off",
"find": "off",
"getSettledState": "off",
"hasModule": "off",
"invisible": "off",
"jQuery": "off",
"keyboardHelper": "off",
"keyEvent": "off",
"moduleFor": "off",
"moduleForComponent": "off",
"testStart": "off",
"testDone": "off",
"pauseTest": "off",
"Pretender": "off",
"query": "off",
"queryAll": "off",
"QUnit": "off",
"sandbox": "off",
"sinon": "off",
"currentURL": "off",
"invisible": "off",
"test": "off",
"testDone": "off",
"testStart": "off",
"triggerEvent": "off",
"visible": "off",
"count": "off"
"visit": "off",
"waitUntil": "off"
}
}

View File

@ -61,11 +61,11 @@ jobs:
- name: ESLint (core)
if: ${{ always() }}
run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern app/assets/javascripts
run: yarn eslint app/assets/javascripts
- name: ESLint (core plugins)
if: ${{ always() }}
run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern plugins/**/{test,assets}/javascripts
run: yarn eslint plugins
- name: Prettier
if: ${{ always() }}
@ -73,9 +73,9 @@ jobs:
yarn prettier -v
yarn prettier --list-different \
"app/assets/stylesheets/**/*.scss" \
"app/assets/javascripts/**/*.{js,es6}" \
"app/assets/javascripts/**/*.js" \
"plugins/**/assets/stylesheets/**/*.scss" \
"plugins/**/assets/javascripts/**/*.{js,es6}"
"plugins/**/assets/javascripts/**/*.js"
- name: Ember template lint
if: ${{ always() }}

View File

@ -181,9 +181,14 @@ group :development do
gem 'yaml-lint'
end
group ENV["ALLOW_DEV_POPULATE"] == "1" ? :production : :development do
if ENV["ALLOW_DEV_POPULATE"] == "1"
gem 'discourse_dev_assets'
gem 'faker', "~> 2.16"
else
group :development do
gem 'discourse_dev_assets'
gem 'faker', "~> 2.16"
end
end
# this is an optional gem, it provides a high performance replacement

View File

@ -129,7 +129,7 @@ GEM
sprockets (>= 3.3, < 4.1)
ember-source (2.18.2)
erubi (1.10.0)
excon (0.87.0)
excon (0.88.0)
execjs (2.8.1)
exifr (1.3.9)
fabrication (2.22.0)
@ -171,23 +171,23 @@ GEM
hkdf (0.3.0)
htmlentities (4.3.4)
http_accept_language (2.1.1)
i18n (1.8.10)
i18n (1.8.11)
concurrent-ruby (~> 1.0)
image_optim (0.31.0)
image_optim (0.31.1)
exifr (~> 1.2, >= 1.2.2)
fspath (~> 3.0)
image_size (>= 1.5, < 3)
image_size (>= 1.5, < 4)
in_threads (~> 1.3)
progress (~> 3.0, >= 3.0.1)
image_size (2.1.2)
image_size (3.0.1)
in_threads (1.5.4)
ipaddr (1.2.2)
ipaddr (1.2.3)
jmespath (1.4.0)
jquery-rails (4.4.0)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (2.6.0)
json (2.6.1)
json-schema (2.8.1)
addressable (>= 2.4)
json_schemer (0.2.18)
@ -197,12 +197,10 @@ GEM
uri_template (~> 0.7)
jwt (2.3.0)
kgio (2.11.4)
libv8-node (15.14.0.1)
libv8-node (15.14.0.1-arm64-darwin-20)
libv8-node (15.14.0.1-x86_64-darwin-18)
libv8-node (15.14.0.1-x86_64-darwin-19)
libv8-node (15.14.0.1-x86_64-darwin-20)
libv8-node (15.14.0.1-x86_64-linux)
libv8-node (16.10.0.0)
libv8-node (16.10.0.0-x86_64-darwin)
libv8-node (16.10.0.0-x86_64-darwin-19)
libv8-node (16.10.0.0-x86_64-linux)
listen (3.7.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
@ -215,7 +213,7 @@ GEM
logstash-event (1.2.02)
logstash-logger (0.26.1)
logstash-event (~> 1.2)
logster (2.9.7)
logster (2.9.8)
loofah (2.12.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
@ -228,8 +226,8 @@ GEM
method_source (1.0.0)
mini_mime (1.1.2)
mini_portile2 (2.6.1)
mini_racer (0.4.0)
libv8-node (~> 15.14.0.0)
mini_racer (0.5.0)
libv8-node (~> 16.10.0.0)
mini_scheduler (0.13.0)
sidekiq (>= 4.2.3)
mini_sql (1.1.3)
@ -254,7 +252,7 @@ GEM
racc (~> 1.4)
nokogiri (1.12.5-x86_64-linux)
racc (~> 1.4)
oauth (0.5.6)
oauth (0.5.8)
oauth2 (1.4.7)
faraday (>= 0.8, < 2.0)
jwt (>= 1.0, < 3.0)
@ -265,7 +263,7 @@ GEM
omniauth (1.9.1)
hashie (>= 3.4.6)
rack (>= 1.6.2, < 3)
omniauth-facebook (8.0.0)
omniauth-facebook (9.0.0)
omniauth-oauth2 (~> 1.2)
omniauth-github (1.4.0)
omniauth (~> 1.5)
@ -278,7 +276,7 @@ GEM
omniauth-oauth (1.2.0)
oauth
omniauth (>= 1.0, < 3)
omniauth-oauth2 (1.7.1)
omniauth-oauth2 (1.7.2)
oauth2 (~> 1.4)
omniauth (>= 1.9, < 3)
omniauth-twitter (1.4.0)
@ -325,7 +323,7 @@ GEM
activerecord (~> 6.0)
concurrent-ruby
railties (~> 6.0)
rails_multisite (3.1.0)
rails_multisite (4.0.0)
activerecord (> 5.0, < 7)
railties (> 5.0, < 7)
railties (6.1.4.1)
@ -381,7 +379,7 @@ GEM
rspec-expectations (~> 3.10)
rspec-mocks (~> 3.10)
rspec-support (~> 3.10)
rspec-support (3.10.2)
rspec-support (3.10.3)
rss (0.2.9)
rexml
rswag-specs (2.4.0)
@ -389,7 +387,7 @@ GEM
json-schema (~> 2.2)
railties (>= 3.1, < 7.0)
rtlit (0.0.5)
rubocop (1.22.1)
rubocop (1.22.3)
parallel (~> 1.10)
parser (>= 3.0.0.0)
rainbow (>= 2.2.2, < 4.0)
@ -398,12 +396,12 @@ GEM
rubocop-ast (>= 1.12.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.12.0)
rubocop-ast (1.13.0)
parser (>= 3.0.1.1)
rubocop-discourse (2.4.2)
rubocop (>= 1.1.0)
rubocop-rspec (>= 2.0.0)
rubocop-rspec (2.5.0)
rubocop-rspec (2.6.0)
rubocop (~> 1.19)
ruby-prof (1.4.3)
ruby-progressbar (1.11.0)
@ -429,7 +427,7 @@ GEM
activesupport (>= 3.1)
shoulda-matchers (5.0.0)
activesupport (>= 5.2.0)
sidekiq (6.2.2)
sidekiq (6.3.1)
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.2.0)
@ -442,9 +440,9 @@ GEM
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.2.2)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets-rails (3.3.0)
actionpack (>= 5.2)
activesupport (>= 5.2)
sprockets (>= 3.0.0)
sshkey (2.0.0)
stackprof (0.2.17)
@ -473,7 +471,7 @@ GEM
jwt (~> 2.0)
xorcist (1.1.2)
yaml-lint (0.0.10)
zeitwerk (2.4.2)
zeitwerk (2.5.1)
PLATFORMS
arm64-darwin-20

View File

@ -33,7 +33,7 @@ export default Component.extend({
}
},
_updateFormattedLogsFunc: function () {
_updateFormattedLogsFunc() {
const logs = this.logs;
if (logs.length === 0) {
return;
@ -48,7 +48,7 @@ export default Component.extend({
}
// update the formatted logs & cache index
this.setProperties({
formattedLogs: formattedLogs,
formattedLogs,
index: logs.length,
});
// force rerender

View File

@ -24,7 +24,7 @@ export default Component.extend({
const config = {
type: this.type,
data: data,
data,
options: {
responsive: true,
plugins: {

View File

@ -114,7 +114,7 @@ export default Component.extend({
this.fieldAdded(this.currentTargetName, name);
},
toggleMaximize: function () {
toggleMaximize() {
this.toggleProperty("maximized");
next(() => this.appEvents.trigger("ace:resize"));
},

View File

@ -40,7 +40,7 @@ export default Component.extend({
},
@observes("hexValue", "brightnessValue", "valid")
hexValueChanged: function () {
hexValueChanged() {
const hex = this.hexValue;
let text = this.element.querySelector("input.hex-input");

View File

@ -63,7 +63,7 @@ export default Component.extend({
},
_addValue(value, secret) {
this.collection.addObject({ key: value, secret: secret });
this.collection.addObject({ key: value, secret });
this._saveValues();
},

View File

@ -1,14 +1,15 @@
import Component from "@ember/component";
import I18n from "I18n";
import UploadMixin from "discourse/mixins/upload";
import UppyUploadMixin from "discourse/mixins/uppy-upload";
import { alias } from "@ember/object/computed";
import bootbox from "bootbox";
export default Component.extend(UploadMixin, {
export default Component.extend(UppyUploadMixin, {
type: "csv",
uploadUrl: "/tags/upload",
addDisabled: alias("uploading"),
elementId: "tag-uploader",
preventDirectS3Uploads: true,
validateUploadedFilesOptions() {
return { csvOnly: true };

View File

@ -1,10 +1,8 @@
import { and, gt } from "@ember/object/computed";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import Component from "@ember/component";
import { escape } from "pretty-text/sanitizer";
import { iconHTML } from "discourse-common/lib/icon-library";
import { isTesting } from "discourse-common/config/environment";
import { schedule } from "@ember/runloop";
const MAX_COMPONENTS = 4;
@ -22,36 +20,6 @@ export default Component.extend({
}
},
init() {
this._super(...arguments);
this.scheduleAnimation();
},
@observes("theme.selected")
triggerAnimation() {
this.animate();
},
scheduleAnimation() {
schedule("afterRender", () => {
this.animate(true);
});
},
animate(isInitial) {
const $container = $(this.element);
const $list = $(this.element.querySelector(".components-list"));
if ($list.length === 0 || isTesting()) {
return;
}
const duration = 300;
if (this.get("theme.selected")) {
this.collapseComponentsList($container, $list, duration);
} else if (!isInitial) {
this.expandComponentsList($container, $list, duration);
}
},
@discourseComputed(
"theme.component",
"theme.childThemes.@each.name",
@ -91,54 +59,6 @@ export default Component.extend({
return childrenCount - MAX_COMPONENTS;
},
expandComponentsList($container, $list, duration) {
$container.css("height", `${$container.height()}px`);
$list.css("display", "");
$container.animate(
{
height: `${$container.height() + $list.outerHeight(true)}px`,
},
{
duration,
done: () => {
$list.css("display", "");
$container.css("height", "");
},
}
);
$list.animate(
{
opacity: 1,
},
{
duration,
}
);
},
collapseComponentsList($container, $list, duration) {
$container.animate(
{
height: `${$container.height() - $list.outerHeight(true)}px`,
},
{
duration,
done: () => {
$list.css("display", "none");
$container.css("height", "");
},
}
);
$list.animate(
{
opacity: 0,
},
{
duration,
}
);
},
actions: {
toggleChildrenExpanded() {
this.toggleProperty("childrenExpanded");

View File

@ -1,15 +1,16 @@
import Component from "@ember/component";
import I18n from "I18n";
import UploadMixin from "discourse/mixins/upload";
import UppyUploadMixin from "discourse/mixins/uppy-upload";
import { alias } from "@ember/object/computed";
import bootbox from "bootbox";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend(UploadMixin, {
export default Component.extend(UppyUploadMixin, {
type: "txt",
classNames: "watched-words-uploader",
uploadUrl: "/admin/customize/watched_words/upload",
addDisabled: alias("uploading"),
preventDirectS3Uploads: true,
validateUploadedFilesOptions() {
return { skipValidation: true };

View File

@ -1,14 +1,39 @@
import Controller from "@ember/controller";
import { action } from "@ember/object";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default Controller.extend({
actions: {
revokeKey(key) {
key.revoke().catch(popupAjaxError);
},
loading: false,
undoRevokeKey(key) {
key.undoRevoke().catch(popupAjaxError);
},
@action
revokeKey(key) {
key.revoke().catch(popupAjaxError);
},
@action
undoRevokeKey(key) {
key.undoRevoke().catch(popupAjaxError);
},
@action
loadMore() {
if (this.loading || this.model.loaded) {
return;
}
const limit = 50;
this.set("loading", true);
this.store
.findAll("api-key", { offset: this.model.length, limit })
.then((keys) => {
this.model.addObjects(keys);
if (keys.length < limit) {
this.model.set("loaded", true);
}
})
.finally(() => {
this.set("loading", false);
});
},
});

View File

@ -10,7 +10,8 @@ import { ajax } from "discourse/lib/ajax";
export default Controller.extend({
userModes: null,
useGlobalKey: false,
scopeModes: null,
globalScopes: null,
scopes: null,
init() {
@ -20,6 +21,13 @@ export default Controller.extend({
{ 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();
},
@ -49,14 +57,23 @@ export default Controller.extend({
this.set("userMode", userMode);
},
@action
changeScopeMode(scopeMode) {
this.set("scopeMode", scopeMode);
},
@action
save() {
if (!this.useGlobalKey) {
if (this.scopeMode === "granular") {
const selectedScopes = Object.values(this.scopes)
.flat()
.filterBy("selected");
this.model.set("scopes", selectedScopes);
} else if (this.scopeMode === "read_only") {
this.model.set("scopes", [this.globalScopes.findBy("key", "read")]);
} else if (this.scopeMode === "all") {
this.model.set("scopes", null);
}
return this.model.save().catch(popupAjaxError);
@ -78,6 +95,10 @@ export default Controller.extend({
_loadScopes() {
return ajax("/admin/api/keys/scopes.json")
.then((data) => {
// remove global scopes because there is a different dropdown
this.set("globalScopes", data.scopes.global);
delete data.scopes.global;
this.set("scopes", data.scopes);
})
.catch(popupAjaxError);

View File

@ -12,6 +12,9 @@ export default Controller.extend({
uploadLabel: i18n("admin.backups.upload.label"),
backupLocation: setting("backup_location"),
localBackupStorage: equal("backupLocation", "local"),
enableExperimentalBackupUploader: setting(
"enable_experimental_backup_uploader"
),
@discourseComputed("status.allowRestore", "status.isOperationRunning")
restoreTitle(allowRestore, isOperationRunning) {

View File

@ -70,7 +70,7 @@ export default Controller.extend(bufferedProperty("model"), {
},
@observes("model.id")
_resetSaving: function () {
_resetSaving() {
this.set("saving", false);
this.set("savingStatus", "");
},

View File

@ -15,11 +15,11 @@ export default Controller.extend({
},
actions: {
revert: function (color) {
revert(color) {
color.revert();
},
undo: function (color) {
undo(color) {
color.undo();
},
@ -68,7 +68,7 @@ export default Controller.extend({
});
},
save: function () {
save() {
this.model.save();
},
@ -76,7 +76,7 @@ export default Controller.extend({
this.model.updateUserSelectable(this.get("model.user_selectable"));
},
destroy: function () {
destroy() {
const model = this.model;
return bootbox.confirm(
I18n.t("admin.customize.colors.delete_confirm"),

View File

@ -12,7 +12,7 @@ export default Controller.extend({
editRouteName: "adminCustomizeThemes.edit",
showRouteName: "adminCustomizeThemes.show",
setTargetName: function (name) {
setTargetName(name) {
const target = this.get("model.targets").find((t) => t.name === name);
this.set("currentTarget", target && target.id);
},

View File

@ -19,7 +19,7 @@ export default Controller.extend({
@method testEmailAddressChanged
**/
@observes("testEmailAddress")
testEmailAddressChanged: function () {
testEmailAddressChanged() {
this.set("sentTestEmail", false);
},
@ -29,7 +29,7 @@ export default Controller.extend({
@method sendTestEmail
**/
sendTestEmail: function () {
sendTestEmail() {
this.setProperties({
sendingEmail: true,
sentTestEmail: false,

View File

@ -121,7 +121,7 @@ export default Controller.extend({
},
filterBySubject(subject) {
this.changeFilters({ subject: subject });
this.changeFilters({ subject });
},
exportStaffActionLogs() {

View File

@ -37,7 +37,7 @@ export default Controller.extend({
textArea.remove();
},
destroy: function (record) {
destroy(record) {
return bootbox.confirm(
I18n.t("admin.permalink.delete_confirm"),
I18n.t("no_value"),

View File

@ -3,7 +3,7 @@ import discourseComputed from "discourse-common/utils/decorators";
export default Controller.extend({
@discourseComputed
adminRoutes: function () {
adminRoutes() {
return this.model
.map((p) => {
if (p.get("enabled")) {

View File

@ -49,7 +49,7 @@ export default Controller.extend(GrantBadgeController, {
let result = {
badge: badges[0].badge,
granted_at: lastGranted,
badges: badges,
badges,
count: badges.length,
grouped: true,
};
@ -61,7 +61,7 @@ export default Controller.extend(GrantBadgeController, {
},
actions: {
expandGroup: function (userBadge) {
expandGroup(userBadge) {
const model = this.model;
model.set("expandedBadges", model.get("expandedBadges") || []);
model.get("expandedBadges").pushObject(userBadge.badge.id);

View File

@ -592,7 +592,7 @@ export default Controller.extend(CanCheckEmails, {
(deletedPosts * 100) / user.get("post_count")
);
progressModal.setProperties({
deletedPercentage: deletedPercentage,
deletedPercentage,
});
performDelete(progressModal);
}

View File

@ -4,7 +4,7 @@ import { ajax } from "discourse/lib/ajax";
const EmailSettings = EmberObject.extend({});
EmailSettings.reopenClass({
find: function () {
find() {
return ajax("/admin/email.json").then(function (settings) {
return EmailSettings.create(settings);
});

View File

@ -5,7 +5,7 @@ import { ajax } from "discourse/lib/ajax";
import discourseComputed from "discourse-common/utils/decorators";
const Permalink = EmberObject.extend({
save: function () {
save() {
return ajax("/admin/permalinks.json", {
type: "POST",
data: {
@ -17,16 +17,16 @@ const Permalink = EmberObject.extend({
},
@discourseComputed("category_id")
category: function (category_id) {
category(category_id) {
return Category.findById(category_id);
},
@discourseComputed("external_url")
linkIsExternal: function (external_url) {
linkIsExternal(external_url) {
return !DiscourseURL.isInternal(external_url);
},
destroy: function () {
destroy() {
return ajax("/admin/permalinks/" + this.id + ".json", {
type: "DELETE",
});
@ -34,12 +34,12 @@ const Permalink = EmberObject.extend({
});
Permalink.reopenClass({
findAll: function (filter) {
return ajax("/admin/permalinks.json", { data: { filter: filter } }).then(
function (permalinks) {
return permalinks.map((p) => Permalink.create(p));
}
);
findAll(filter) {
return ajax("/admin/permalinks.json", { data: { filter } }).then(function (
permalinks
) {
return permalinks.map((p) => Permalink.create(p));
});
},
});

View File

@ -672,7 +672,7 @@ Report.reopenClass({
Report.fillMissingDates(json.report);
}
const model = Report.create({ type: type });
const model = Report.create({ type });
model.setProperties(json.report);
if (json.report.related_report) {

View File

@ -9,7 +9,7 @@ const ScreenedEmail = EmberObject.extend({
return I18n.t("admin.logs.screened_actions." + action);
},
clearBlock: function () {
clearBlock() {
return ajax("/admin/logs/screened_emails/" + this.id, {
type: "DELETE",
});
@ -17,7 +17,7 @@ const ScreenedEmail = EmberObject.extend({
});
ScreenedEmail.reopenClass({
findAll: function () {
findAll() {
return ajax("/admin/logs/screened_emails.json").then(function (
screened_emails
) {

View File

@ -42,7 +42,7 @@ const ScreenedIpAddress = EmberObject.extend({
ScreenedIpAddress.reopenClass({
findAll(filter) {
return ajax("/admin/logs/screened_ip_addresses.json", {
data: { filter: filter },
data: { filter },
}).then((screened_ips) =>
screened_ips.map((b) => ScreenedIpAddress.create(b))
);

View File

@ -11,7 +11,7 @@ const ScreenedUrl = EmberObject.extend({
});
ScreenedUrl.reopenClass({
findAll: function () {
findAll() {
return ajax("/admin/logs/screened_urls.json").then(function (
screened_urls
) {

View File

@ -44,7 +44,7 @@ export default RestModel.extend({
},
groupFinder(term) {
return Group.findAll({ term: term, ignore_automatic: false });
return Group.findAll({ term, ignore_automatic: false });
},
@discourseComputed("wildcard_web_hook", "web_hook_event_types.[]")

View File

@ -32,7 +32,7 @@ export default DiscourseRoute.extend({
});
controller.setProperties({
badgeGroupings: badgeGroupings,
badgeGroupings,
badgeTypes: json.badge_types,
protectedSystemFields: json.admin_badges.protected_system_fields,
badgeTriggers,

View File

@ -2,6 +2,7 @@ import { COMPONENTS, THEMES } from "admin/models/theme";
import I18n from "I18n";
import Route from "@ember/routing/route";
import { scrollTop } from "discourse/mixins/scroll-top";
import bootbox from "bootbox";
export function showUnassignedComponentWarning(theme, callback) {
bootbox.confirm(
@ -39,8 +40,8 @@ export default Route.extend({
});
controller.setProperties({
model: model,
parentController: parentController,
model,
parentController,
allThemes: parentController.get("model"),
colorSchemeId: model.get("color_scheme_id"),
colorSchemes: parentController.get("model.extras.color_schemes"),

View File

@ -3,7 +3,7 @@ import EmberObject from "@ember/object";
import { ajax } from "discourse/lib/ajax";
export default DiscourseRoute.extend({
model: function () {
model() {
return ajax("/admin/customize/emojis.json").then(function (emojis) {
return emojis.map(function (emoji) {
return EmberObject.create(emoji);

View File

@ -1,7 +1,7 @@
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
redirect: function () {
redirect() {
this.transitionTo("adminLogs.staffActionLogs");
},
});

View File

@ -1,11 +1,11 @@
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
renderTemplate: function () {
renderTemplate() {
this.render("admin/templates/logs/screened-emails", { into: "adminLogs" });
},
setupController: function () {
setupController() {
return this.controllerFor("adminLogsScreenedEmails").show();
},
});

View File

@ -1,11 +1,11 @@
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
renderTemplate: function () {
renderTemplate() {
this.render("admin/templates/logs/screened-urls", { into: "adminLogs" });
},
setupController: function () {
setupController() {
return this.controllerFor("adminLogsScreenedUrls").show();
},
});

View File

@ -24,7 +24,7 @@ export default Route.extend({
controller.setProperties({
siteText,
saved: false,
localeFullName: localeFullName,
localeFullName,
});
},
});

View File

@ -2,11 +2,11 @@ import DiscourseRoute from "discourse/routes/discourse";
import UserField from "admin/models/user-field";
export default DiscourseRoute.extend({
model: function () {
model() {
return this.store.findAll("user-field");
},
setupController: function (controller, model) {
setupController(controller, model) {
controller.setProperties({ model, fieldTypes: UserField.fieldTypes() });
},
});

View File

@ -1,7 +1,7 @@
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
redirect: function () {
redirect() {
this.transitionTo("adminUsersList");
},
});

View File

@ -1,7 +1,7 @@
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
beforeModel: function () {
beforeModel() {
this.transitionTo("adminUsersList.show", "active");
},
});

View File

@ -5,67 +5,71 @@
label="admin.api.new_key"}}
{{#if model}}
<table class="api-keys grid">
<thead>
<th>{{i18n "admin.api.key"}}</th>
<th>{{i18n "admin.api.description"}}</th>
<th>{{i18n "admin.api.user"}}</th>
<th>{{i18n "admin.api.created"}}</th>
<th>{{i18n "admin.api.last_used"}}</th>
<th>&nbsp;</th>
</thead>
<tbody>
{{#each model as |k|}}
<tr class={{if k.revoked_at "revoked"}}>
<td class="key">
{{#if k.revoked_at}}{{d-icon "times-circle"}}{{/if}}
{{k.truncatedKey}}
</td>
<td class="key-description">
{{k.shortDescription}}
</td>
<td class="key-user">
<div class="label">{{i18n "admin.api.user"}}</div>
{{#if k.user}}
{{#link-to "adminUser" k.user}}
{{avatar k.user imageSize="small"}}
{{/link-to}}
{{else}}
{{i18n "admin.api.all_users"}}
{{/if}}
</td>
<td class="key-created">
<div class="label">{{i18n "admin.api.created"}}</div>
{{format-date k.created_at}}
</td>
<td class="key-last-used">
<div class="label">{{i18n "admin.api.last_used"}}</div>
{{#if k.last_used_at}}
{{format-date k.last_used_at}}
{{else}}
{{i18n "admin.api.never_used"}}
{{/if}}
</td>
<td class="key-controls">
{{d-button action=(route-action "show" k) icon="far-eye" title="admin.api.show_details"}}
{{#if k.revoked_at}}
{{d-button
action=(action "undoRevokeKey")
actionParam=k icon="undo"
title="admin.api.undo_revoke"}}
{{else}}
{{d-button
class="btn-danger"
action=(action "revokeKey")
actionParam=k
icon="times"
title="admin.api.revoke"}}
{{/if}}
</td>
</tr>
{{/each}}
</tbody>
</table>
{{#load-more selector=".api-keys tr" action=(action "loadMore")}}
<table class="api-keys grid">
<thead>
<th>{{i18n "admin.api.key"}}</th>
<th>{{i18n "admin.api.description"}}</th>
<th>{{i18n "admin.api.user"}}</th>
<th>{{i18n "admin.api.created"}}</th>
<th>{{i18n "admin.api.last_used"}}</th>
<th>&nbsp;</th>
</thead>
<tbody>
{{#each model as |k|}}
<tr class={{if k.revoked_at "revoked"}}>
<td class="key">
{{#if k.revoked_at}}{{d-icon "times-circle"}}{{/if}}
{{k.truncatedKey}}
</td>
<td class="key-description">
{{k.shortDescription}}
</td>
<td class="key-user">
<div class="label">{{i18n "admin.api.user"}}</div>
{{#if k.user}}
{{#link-to "adminUser" k.user}}
{{avatar k.user imageSize="small"}}
{{/link-to}}
{{else}}
{{i18n "admin.api.all_users"}}
{{/if}}
</td>
<td class="key-created">
<div class="label">{{i18n "admin.api.created"}}</div>
{{format-date k.created_at}}
</td>
<td class="key-last-used">
<div class="label">{{i18n "admin.api.last_used"}}</div>
{{#if k.last_used_at}}
{{format-date k.last_used_at}}
{{else}}
{{i18n "admin.api.never_used"}}
{{/if}}
</td>
<td class="key-controls">
{{d-button action=(route-action "show" k) icon="far-eye" title="admin.api.show_details"}}
{{#if k.revoked_at}}
{{d-button
action=(action "undoRevokeKey")
actionParam=k icon="undo"
title="admin.api.undo_revoke"}}
{{else}}
{{d-button
class="btn-danger"
action=(action "revokeKey")
actionParam=k
icon="times"
title="admin.api.revoke"}}
{{/if}}
</td>
</tr>
{{/each}}
</tbody>
</table>
{{/load-more}}
{{conditional-loading-spinner condition=loading}}
{{else}}
<p>{{i18n "admin.api.none"}}</p>
{{/if}}

View File

@ -36,12 +36,18 @@
{{/admin-form-row}}
{{/if}}
{{#admin-form-row label="admin.api.use_global_key"}}
{{input type="checkbox" checked=useGlobalKey}}
{{#admin-form-row label="admin.api.scope_mode"}}
{{combo-box content=scopeModes value=scopeMode onChange=(action "changeScopeMode")}}
{{#if (eq scopeMode "read_only")}}
<p>{{i18n "admin.api.scopes.descriptions.global.read"}}</p>
{{else if (eq scopeMode "global")}}
<p>{{i18n "admin.api.scopes.global_description"}}</p>
{{/if}}
{{/admin-form-row}}
{{#unless useGlobalKey}}
<div class="scopes-title">{{i18n "admin.api.scopes.title"}}</div>
{{#if (eq scopeMode "granular")}}
<h2 class="scopes-title">{{i18n "admin.api.scopes.title"}}</h2>
<p>{{i18n "admin.api.scopes.description"}}</p>
<table class="scopes-table grid">
<thead>
@ -82,7 +88,7 @@
{{/each-in}}
</tbody>
</table>
{{/unless}}
{{/if}}
{{d-button icon="check" label="admin.api.save" action=(action "save") class="btn-primary" disabled=saveDisabled}}
{{/if}}

View File

@ -83,7 +83,7 @@
{{/admin-form-row}}
{{#if model.api_key_scopes.length}}
<div class="scopes-title">{{i18n "admin.api.scopes.title"}}</div>
<h2 class="scopes-title">{{i18n "admin.api.scopes.title"}}</h2>
<table class="scopes-table grid">
<thead>

View File

@ -8,7 +8,11 @@
title="admin.backups.upload.title"
class="btn-default"}}
{{else}}
{{backup-uploader done=(route-action "remoteUploadSuccess")}}
{{#if enableExperimentalBackupUploader}}
{{uppy-backup-uploader done=(route-action "remoteUploadSuccess")}}
{{else}}
{{backup-uploader done=(route-action "remoteUploadSuccess")}}
{{/if}}
{{/if}}
{{#if site.isReadOnly}}

View File

@ -18,6 +18,7 @@
class="watched-word-input-field"
tags=selectedTags
onChange=(action "changeSelectedTags")
everyTag=true
options=(hash
allowAny=true
disabled=formSubmitted

View File

@ -6,6 +6,7 @@
{{emoji-uploader
emojiGroups=emojiGroups
done=(action "emojiUploaded")
id="emoji-uploader"
}}
<hr>

View File

@ -1,17 +1,21 @@
<div class="permalink-title">
<h2>{{i18n "admin.permalink.title"}}</h2>
</div>
<h1>{{i18n "admin.permalink.title"}}</h1>
<div class="permalink-description">
<span>{{i18n "admin.permalink.description"}}</span>
</div>
<div class="permalink-search">
{{text-field value=filter class="url-input" placeholderKey="admin.permalink.form.filter" autocorrect="off" autocapitalize="off"}}
</div>
{{permalink-form action=(action "recordAdded")}}
<br>
{{#conditional-loading-spinner condition=loading}}
{{#if model.length}}
<div class="permalink-search">
{{text-field
value=filter
class="url-input"
placeholderKey="admin.permalink.form.filter"
autocorrect="off"
autocapitalize="off"
}}
</div>
<table class="admin-logs-table permalinks grid">
<thead class="heading-container">
<th class="col heading first url">{{i18n "admin.permalink.url"}}</th>
@ -21,7 +25,14 @@
<tbody>
{{#each model as |pl|}}
<tr class="admin-list-item">
<td class="col first url">{{d-button title="admin.permalink.copy_to_clipboard" icon="far-clipboard" action=(action "copyUrl" pl)}} <span id="admin-permalink-{{pl.id}}" title={{pl.url}}>{{pl.url}}</span></td>
<td class="col first url">
{{flat-button
title="admin.permalink.copy_to_clipboard"
icon="far-clipboard"
action=(action "copyUrl" pl)
}}
<span id="admin-permalink-{{pl.id}}" title={{pl.url}}>{{pl.url}}</span>
</td>
<td class="col destination">
{{#if pl.topic_id}}
<a href={{pl.topic_url}}>{{pl.topic_title}}</a>

View File

@ -9,7 +9,11 @@
icon="download"
label="admin.watched_words.download"}}
{{watched-word-uploader uploading=uploading actionKey=actionNameKey done=(action "uploadComplete")}}
{{watched-word-uploader
id="watched-word-uploader"
uploading=uploading
actionKey=actionNameKey
done=(action "uploadComplete")}}
{{d-button
class="watched-word-test"

View File

@ -20,6 +20,8 @@ const REPLACEMENTS = {
"d-drop-collapsed": "caret-right",
"d-unliked": "far-heart",
"d-liked": "heart",
"d-post-share": "link",
"d-topic-share": "link",
"notification.mentioned": "at",
"notification.group_mentioned": "users",
"notification.quoted": "quote-right",

View File

@ -11,7 +11,7 @@ export default function decoratorAlias(fn, errorMessage) {
enumerable: desc.enumerable,
configurable: desc.configurable,
writable: desc.writable,
initializer: function () {
initializer() {
let value = extractValue(desc);
return fn.apply(null, params.concat(value));
},

View File

@ -50,7 +50,7 @@ export function readOnly(target, name, desc) {
writable: false,
enumerable: desc.enumerable,
configurable: desc.configurable,
initializer: function () {
initializer() {
let value = extractValue(desc);
return value.readOnly();
},

View File

@ -6,7 +6,7 @@ export default function handleDescriptor(target, key, desc, params = []) {
enumerable: desc.enumerable,
configurable: desc.configurable,
writeable: desc.writeable,
initializer: function () {
initializer() {
let computedDescriptor;
if (desc.writable) {

View File

@ -5,7 +5,7 @@ function handleDescriptor(target, property, desc, fn, params = []) {
enumerable: desc.enumerable,
configurable: desc.configurable,
writable: desc.writable,
initializer: function () {
initializer() {
return fn(...params);
},
};

View File

@ -1,8 +1,8 @@
import Component from "@ember/component";
import UploadMixin from "discourse/mixins/upload";
import UppyUploadMixin from "discourse/mixins/uppy-upload";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend(UploadMixin, {
export default Component.extend(UppyUploadMixin, {
type: "avatar",
tagName: "span",
imageIsNotASquare: false,

View File

@ -19,7 +19,7 @@ export default Component.extend({
},
@observes("topicList.[]")
_topicListChanged: function () {
_topicListChanged() {
this._initFromTopicList(this.topicList);
},

View File

@ -7,7 +7,7 @@ import I18n from "I18n";
import { Promise } from "rsvp";
import { action } from "@ember/object";
import bootbox from "bootbox";
import showModal from "discourse/lib/show-modal";
import { openBookmarkModal } from "discourse/controllers/bookmark";
export default Component.extend({
classNames: ["bookmark-list-wrapper"],
@ -19,6 +19,11 @@ export default Component.extend({
bookmark
.destroy()
.then(() => {
this.appEvents.trigger(
"bookmarks:changed",
null,
bookmark.attachedTo()
);
this._removeBookmarkFromList(bookmark);
resolve(true);
})
@ -51,17 +56,19 @@ export default Component.extend({
@action
editBookmark(bookmark) {
let controller = showModal("bookmark", {
model: {
postId: bookmark.post_id,
id: bookmark.id,
reminderAt: bookmark.reminder_at,
name: bookmark.name,
openBookmarkModal(bookmark, {
onAfterSave: (savedData) => {
this.appEvents.trigger(
"bookmarks:changed",
savedData,
bookmark.attachedTo()
);
this.reload();
},
onAfterDelete: () => {
this.reload();
},
title: "post.bookmarks.edit",
modalClass: "bookmark-with-reminder",
});
controller.set("afterSave", this.reload);
},
@action

View File

@ -17,7 +17,7 @@ import { TIME_SHORTCUT_TYPES } from "discourse/lib/time-shortcut";
import { action } from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import bootbox from "bootbox";
import discourseComputed, { on } from "discourse-common/utils/decorators";
import discourseComputed, { bind, on } from "discourse-common/utils/decorators";
import { formattedReminderTime } from "discourse/lib/bookmark";
import { and, notEmpty } from "@ember/object/computed";
import { popupAjaxError } from "discourse/lib/ajax-error";
@ -65,7 +65,7 @@ export default Component.extend({
_itsatrap: new ItsATrap(),
});
this.registerOnCloseHandler(this._onModalClose.bind(this));
this.registerOnCloseHandler(this._onModalClose);
this._loadBookmarkOptions();
this._bindKeyboardShortcuts();
@ -201,6 +201,7 @@ export default Component.extend({
post_id: this.model.postId,
id: this.model.id || response.id,
name: this.model.name,
topic_id: this.model.topicId,
});
},
@ -238,12 +239,15 @@ export default Component.extend({
}
},
_onModalClose(initiatedByCloseButton) {
@bind
_onModalClose(closeOpts) {
// we want to close without saving if the user already saved
// manually or deleted the bookmark, as well as when the modal
// is just closed with the X button
this._closeWithoutSaving =
this._closeWithoutSaving || initiatedByCloseButton;
this._closeWithoutSaving ||
closeOpts.initiatedByCloseButton ||
closeOpts.initiatedByESC;
if (!this._closeWithoutSaving && !this._savingBookmarkManually) {
this._saveBookmark().catch((e) => this._handleSaveError(e));

View File

@ -207,7 +207,6 @@ export default Component.extend(KeyEnterEscape, {
willDestroyElement() {
this._super(...arguments);
this.appEvents.off("composer:resize", this, this.resize);
if (this._visualViewportResizing()) {
window.visualViewport.removeEventListener("resize", this.viewportResize);
}

View File

@ -38,6 +38,18 @@ import { loadOneboxes } from "discourse/lib/load-oneboxes";
import putCursorAtEnd from "discourse/lib/put-cursor-at-end";
import userSearch from "discourse/lib/user-search";
// original string `![image|foo=bar|690x220, 50%|bar=baz](upload://1TjaobgKObzpU7xRMw2HuUc87vO.png "image title")`
// group 1 `image|foo=bar`
// group 2 `690x220`
// group 3 `, 50%`
// group 4 '|bar=baz'
// group 5 'upload://1TjaobgKObzpU7xRMw2HuUc87vO.png "image title"'
// Notes:
// Group 3 is optional. group 4 can match images with or without a markdown title.
// All matches are whitespace tolerant as long it's still valid markdown.
// If the image is inside a code block, we'll ignore it `(?!(.*`))`.
const IMAGE_MARKDOWN_REGEX = /!\[(.*?)\|(\d{1,4}x\d{1,4})(,\s*\d{1,3}%)?(.*?)\]\((upload:\/\/.*?)\)(?!(.*`))/g;
const REBUILD_SCROLL_MAP_EVENTS = ["composer:resized", "composer:typed-reply"];
let uploadHandlers = [];
@ -48,7 +60,14 @@ export function addComposerUploadHandler(extensions, method) {
});
}
export function cleanUpComposerUploadHandler() {
uploadHandlers = [];
// we cannot set this to uploadHandlers = [] because that messes with
// the references to the original array that the component has. this only
// really affects tests, but without doing this you could addComposerUploadHandler
// in a beforeEach function in a test but then it's not adding to the
// existing reference that the component has, because an earlier test ran
// cleanUpComposerUploadHandler and lost it. setting the length to 0 empties
// the array but keeps the reference
uploadHandlers.length = 0;
}
let uploadProcessorQueue = [];
@ -499,41 +518,40 @@ export default Component.extend(ComposerUpload, {
$input.stop(true).animate({ scrollTop }, 100, "linear");
},
_renderUnseenMentions($preview, unseen) {
_renderUnseenMentions(preview, unseen) {
// 'Create a New Topic' scenario is not supported (per conversation with codinghorror)
// https://meta.discourse.org/t/taking-another-1-7-release-task/51986/7
fetchUnseenMentions(unseen, this.get("composer.topic.id")).then(() => {
linkSeenMentions($preview, this.siteSettings);
this._warnMentionedGroups($preview);
this._warnCannotSeeMention($preview);
linkSeenMentions(preview, this.siteSettings);
this._warnMentionedGroups(preview);
this._warnCannotSeeMention(preview);
});
},
_renderUnseenHashtags($preview) {
const unseen = linkSeenHashtags($preview);
_renderUnseenHashtags(preview) {
const unseen = linkSeenHashtags(preview);
if (unseen.length > 0) {
fetchUnseenHashtags(unseen).then(() => {
linkSeenHashtags($preview);
linkSeenHashtags(preview);
});
}
},
_warnMentionedGroups($preview) {
_warnMentionedGroups(preview) {
schedule("afterRender", () => {
let found = this.warnedGroupMentions || [];
$preview.find(".mention-group.notify").each((idx, e) => {
if (this._isInQuote(e)) {
preview?.querySelectorAll(".mention-group.notify")?.forEach((mention) => {
if (this._isInQuote(mention)) {
return;
}
const $e = $(e);
let name = $e.data("name");
let name = mention.dataset.name;
if (found.indexOf(name) === -1) {
this.groupsMentioned([
{
name: name,
user_count: $e.data("mentionable-user-count"),
max_mentions: $e.data("max-mentions"),
name,
user_count: mention.dataset.mentionableUserCount,
max_mentions: mention.dataset.maxMentions,
},
]);
found.push(name);
@ -544,7 +562,7 @@ export default Component.extend(ComposerUpload, {
});
},
_warnCannotSeeMention($preview) {
_warnCannotSeeMention(preview) {
const composerDraftKey = this.get("composer.draftKey");
if (composerDraftKey === Composer.NEW_PRIVATE_MESSAGE_KEY) {
@ -554,9 +572,8 @@ export default Component.extend(ComposerUpload, {
schedule("afterRender", () => {
let found = this.warnedCannotSeeMentions || [];
$preview.find(".mention.cannot-see").each((idx, e) => {
const $e = $(e);
let name = $e.data("name");
preview?.querySelectorAll(".mention.cannot-see")?.forEach((mention) => {
let name = mention.dataset.name;
if (found.indexOf(name) === -1) {
// add a delay to allow for typing, so you don't open the warning right away
@ -565,8 +582,9 @@ export default Component.extend(ComposerUpload, {
this,
() => {
if (
$preview.find('.mention.cannot-see[data-name="' + name + '"]')
.length > 0
preview?.querySelectorAll(
`.mention.cannot-see[data-name="${name}"]`
)?.length > 0
) {
this.cannotSeeMention([{ name }]);
found.push(name);
@ -582,24 +600,15 @@ export default Component.extend(ComposerUpload, {
},
_registerImageScaleButtonClick($preview) {
// original string `![image|foo=bar|690x220, 50%|bar=baz](upload://1TjaobgKObzpU7xRMw2HuUc87vO.png "image title")`
// group 1 `image|foo=bar`
// group 2 `690x220`
// group 3 `, 50%`
// group 4 '|bar=baz'
// group 5 'upload://1TjaobgKObzpU7xRMw2HuUc87vO.png "image title"'
// Notes:
// Group 3 is optional. group 4 can match images with or without a markdown title.
// All matches are whitespace tolerant as long it's still valid markdown.
// If the image is inside a code block, we'll ignore it `(?!(.*`))`.
const imageScaleRegex = /!\[(.*?)\|(\d{1,4}x\d{1,4})(,\s*\d{1,3}%)?(.*?)\]\((upload:\/\/.*?)\)(?!(.*`))/g;
$preview.off("click", ".scale-btn").on("click", ".scale-btn", (e) => {
const index = parseInt($(e.target).parent().attr("data-image-index"), 10);
const index = parseInt(
$(e.target).closest(".button-wrapper").attr("data-image-index"),
10
);
const scale = e.target.attributes["data-scale"].value;
const matchingPlaceholder = this.get("composer.reply").match(
imageScaleRegex
IMAGE_MARKDOWN_REGEX
);
if (matchingPlaceholder) {
@ -607,7 +616,7 @@ export default Component.extend(ComposerUpload, {
if (match) {
const replacement = match.replace(
imageScaleRegex,
IMAGE_MARKDOWN_REGEX,
`![$1|$2, ${scale}%$4]($5)`
);
@ -615,7 +624,7 @@ export default Component.extend(ComposerUpload, {
"composer:replace-text",
matchingPlaceholder[index],
replacement,
{ regex: imageScaleRegex, index }
{ regex: IMAGE_MARKDOWN_REGEX, index }
);
}
}
@ -625,6 +634,58 @@ export default Component.extend(ComposerUpload, {
});
},
_registerImageAltTextButtonClick($preview) {
$preview
.off("click", ".alt-text-edit-btn")
.on("click", ".alt-text-edit-btn", (e) => {
const parentContainer = $(e.target).closest(
".alt-text-readonly-container"
);
const altText = parentContainer.find(".alt-text");
const correspondingInput = parentContainer.find(".alt-text-input");
$(e.target).hide();
altText.hide();
correspondingInput.val(altText.text());
correspondingInput.show();
e.preventDefault();
});
$preview
.off("keypress", ".alt-text-input")
.on("keypress", ".alt-text-input", (e) => {
if (e.key === "[" || e.key === "]") {
e.preventDefault();
}
if (e.key === "Enter") {
const index = parseInt(
$(e.target).closest(".button-wrapper").attr("data-image-index"),
10
);
const matchingPlaceholder = this.get("composer.reply").match(
IMAGE_MARKDOWN_REGEX
);
const match = matchingPlaceholder[index];
const replacement = match.replace(
IMAGE_MARKDOWN_REGEX,
`![${$(e.target).val()}|$2$3$4]($5)`
);
this.appEvents.trigger("composer:replace-text", match, replacement);
const parentContainer = $(e.target).closest(
".alt-text-readonly-container"
);
const altText = parentContainer.find(".alt-text");
const altTextButton = parentContainer.find(".alt-text-edit-btn");
altText.show();
altTextButton.show();
$(e.target).hide();
}
});
},
@on("willDestroyElement")
_composerClosed() {
this._unbindMobileUploadButton();
@ -688,6 +749,14 @@ export default Component.extend(ComposerUpload, {
}
},
_findMatchingUploadHandler(fileName) {
return this.uploadHandlers.find((handler) => {
const ext = handler.extensions.join("|");
const regex = new RegExp(`\\.(${ext})$`, "i");
return regex.test(fileName);
});
},
actions: {
importQuote(toolbarEvent) {
this.importQuote(toolbarEvent);
@ -732,26 +801,29 @@ export default Component.extend(ComposerUpload, {
});
},
previewUpdated($preview) {
previewUpdated(preview) {
// cache jquery objects for functions still using jquery
const $preview = $(preview);
// Paint mentions
const unseenMentions = linkSeenMentions($preview, this.siteSettings);
const unseenMentions = linkSeenMentions(preview, this.siteSettings);
if (unseenMentions.length) {
discourseDebounce(
this,
this._renderUnseenMentions,
$preview,
preview,
unseenMentions,
450
);
}
this._warnMentionedGroups($preview);
this._warnCannotSeeMention($preview);
this._warnMentionedGroups(preview);
this._warnCannotSeeMention(preview);
// Paint category and tag hashtags
const unseenHashtags = linkSeenHashtags($preview);
const unseenHashtags = linkSeenHashtags(preview);
if (unseenHashtags.length > 0) {
discourseDebounce(this, this._renderUnseenHashtags, $preview, 450);
discourseDebounce(this, this._renderUnseenHashtags, preview, 450);
}
// Paint oneboxes
@ -765,7 +837,7 @@ export default Component.extend(ComposerUpload, {
}
const paintedCount = loadOneboxes(
$preview[0],
preview,
ajax,
this.get("composer.topic.id"),
this.get("composer.category.id"),
@ -781,7 +853,7 @@ export default Component.extend(ComposerUpload, {
discourseDebounce(this, paintFunc, 450);
// Short upload urls need resolution
resolveAllShortUrls(ajax, this.siteSettings, $preview[0]);
resolveAllShortUrls(ajax, this.siteSettings, preview);
if (this._enableAdvancedEditorPreviewSync()) {
this._syncScroll(
@ -792,8 +864,9 @@ export default Component.extend(ComposerUpload, {
}
this._registerImageScaleButtonClick($preview);
this._registerImageAltTextButtonClick($preview);
this.trigger("previewRefreshed", $preview[0]);
this.trigger("previewRefreshed", preview);
this.afterRefresh($preview);
},
},

View File

@ -83,7 +83,7 @@ export default Component.extend({
!topic.archived &&
!topic.closed &&
!topic.deleted,
topic: topic,
topic,
});
},

View File

@ -319,7 +319,7 @@ export default Component.extend(TextareaTextManipulation, {
}
if (isTesting()) {
this.element.addEventListener("paste", this.paste.bind(this));
this.element.addEventListener("paste", this.paste);
}
},
@ -341,6 +341,8 @@ export default Component.extend(TextareaTextManipulation, {
if (isTesting()) {
this.element.removeEventListener("paste", this.paste);
}
this._cachedCookFunction = null;
},
@discourseComputed()
@ -394,8 +396,8 @@ export default Component.extend(TextareaTextManipulation, {
const cookedElement = document.createElement("div");
cookedElement.innerHTML = cooked;
linkSeenHashtags($(cookedElement));
linkSeenMentions($(cookedElement), this.siteSettings);
linkSeenHashtags(cookedElement);
linkSeenMentions(cookedElement, this.siteSettings);
resolveCachedShortUrls(this.siteSettings, cookedElement);
loadOneboxes(
cookedElement,
@ -427,7 +429,7 @@ export default Component.extend(TextareaTextManipulation, {
}
if (this.previewUpdated) {
this.previewUpdated($(preview));
this.previewUpdated(preview);
}
});
});

View File

@ -10,7 +10,7 @@ export default Component.extend({
this._super(...arguments);
this._modalAlertElement = document.getElementById("modal-alert");
if (this._modalAlertElement) {
this._modalAlertElement.innerHTML = "";
this._clearFlash();
}
let fixedParent = $(this.element).closest(".d-modal.fixed-modal");

View File

@ -1,9 +1,9 @@
/* global Pikaday:true */
import discourseComputed, { on } from "discourse-common/utils/decorators";
import Component from "@ember/component";
import I18n from "I18n";
import { Promise } from "rsvp";
import { action } from "@ember/object";
/* global Pikaday:true */
import loadScript from "discourse/lib/load-script";
import { schedule } from "@ember/runloop";
@ -144,9 +144,16 @@ export default Component.extend({
}
},
@discourseComputed()
placeholder() {
return I18n.t("dates.placeholder");
@discourseComputed("_placeholder")
placeholder: {
get(_placeholder) {
return _placeholder || I18n.t("dates.placeholder");
},
set(value) {
this.set("_placeholder", value);
return value;
},
},
_opts() {

View File

@ -39,7 +39,7 @@ export default Component.extend({
});
},
_addToCollection: function () {
_addToCollection() {
this.panels.addObject(this.tabClassName);
},
@ -50,7 +50,7 @@ export default Component.extend({
},
actions: {
select: function () {
select() {
this.set("selectedTab", this.tab);
if (!this.newCategory) {

View File

@ -8,7 +8,7 @@ export default buildCategoryPanel("topic-template", {
showInsertLinkButton: false,
@observes("activeTab")
_activeTabChanged: function () {
_activeTabChanged() {
if (this.activeTab) {
schedule("afterRender", () =>
this.element.querySelector(".d-editor-input").focus()

View File

@ -1,12 +1,12 @@
import Component from "@ember/component";
import UploadMixin from "discourse/mixins/upload";
import UppyUploadMixin from "discourse/mixins/uppy-upload";
import { action } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators";
import { notEmpty } from "@ember/object/computed";
const DEFAULT_GROUP = "default";
export default Component.extend(UploadMixin, {
export default Component.extend(UppyUploadMixin, {
type: "emoji",
uploadUrl: "/admin/customize/emojis",
hasName: notEmpty("name"),
@ -15,10 +15,10 @@ export default Component.extend(UploadMixin, {
emojiGroups: null,
newEmojiGroups: null,
tagName: null,
preventDirectS3Uploads: true,
didReceiveAttrs() {
this._super(...arguments);
this.set("newEmojiGroups", this.emojiGroups);
},
@ -27,10 +27,6 @@ export default Component.extend(UploadMixin, {
return !this.hasName || this.uploading;
},
uploadOptions() {
return { sequentialUploads: true };
},
@action
createEmojiGroup(group) {
this.setProperties({

View File

@ -7,7 +7,7 @@ export default Component.extend({
@on("didInsertElement")
@observes("highlight")
_highlightOnInsert: function () {
_highlightOnInsert() {
const term = this.highlight;
highlightSearch(this.element, term);
},

View File

@ -351,12 +351,11 @@ export default Component.extend({
if (this.isInviteeGroup) {
return this.inviteModel
.createGroupInvite(this.invitee.trim())
.then((data) => {
.then(() => {
model.setProperties({ saving: false, finished: true });
this.get("inviteModel.details.allowed_groups").pushObject(
EmberObject.create(data.group)
);
this.appEvents.trigger("post-stream:refresh");
this.inviteModel.reload().then(() => {
this.appEvents.trigger("post-stream:refresh");
});
})
.catch(onerror);
} else {

View File

@ -1,8 +1,9 @@
import Component from "@ember/component";
import { action } from "@ember/object";
import { empty } from "@ember/object/computed";
import { bind, default as computed } from "discourse-common/utils/decorators";
import computed, { bind } from "discourse-common/utils/decorators";
import I18n from "I18n";
import bootbox from "bootbox";
export default Component.extend({
classNames: ["pick-files-button"],

View File

@ -4,7 +4,7 @@ import I18n from "I18n";
import { ajax } from "discourse/lib/ajax";
import bootbox from "bootbox";
import { dasherize } from "@ember/string";
import discourseComputed from "discourse-common/utils/decorators";
import discourseComputed, { bind } from "discourse-common/utils/decorators";
import optionalService from "discourse/lib/optional-service";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { set } from "@ember/object";
@ -113,6 +113,7 @@ export default Component.extend({
return _components[type];
},
@bind
_performConfirmed(action) {
let reviewable = this.reviewable;
@ -264,7 +265,7 @@ export default Component.extend({
title: "review.reject_reason.title",
model: this.reviewable,
}).setProperties({
performConfirmed: this._performConfirmed.bind(this),
performConfirmed: this._performConfirmed,
action,
});
} else if (customModal) {
@ -272,7 +273,7 @@ export default Component.extend({
title: `review.${customModal}.title`,
model: this.reviewable,
}).setProperties({
performConfirmed: this._performConfirmed.bind(this),
performConfirmed: this._performConfirmed,
action,
});
} else {

View File

@ -77,7 +77,12 @@ export default MountWidget.extend({
if (this.isDestroyed || this.isDestroying) {
return;
}
if (isWorkaroundActive()) {
if (
isWorkaroundActive() ||
document.webkitFullscreenElement ||
document.fullscreenElement
) {
return;
}

View File

@ -1,8 +1,9 @@
import Component from "@ember/component";
export default Component.extend({
tagName: "",
actions: {
share: function (source) {
share(source) {
this.action(source);
},
},

View File

@ -37,12 +37,12 @@ export default Component.extend({
const inboxFilter = suggestedGroupName ? "group" : "user";
const unreadCount = this.pmTopicTrackingState.lookupCount("unread", {
inboxFilter: inboxFilter,
inboxFilter,
groupName: suggestedGroupName,
});
const newCount = this.pmTopicTrackingState.lookupCount("new", {
inboxFilter: inboxFilter,
inboxFilter,
groupName: suggestedGroupName,
});
@ -54,7 +54,7 @@ export default Component.extend({
BOTH: hasBoth,
UNREAD: unreadCount,
NEW: newCount,
username: username,
username,
groupName: suggestedGroupName,
groupLink: this._groupLink(username, suggestedGroupName),
basePath: getURL(""),

View File

@ -1,7 +1,9 @@
import { alias, and, or } from "@ember/object/computed";
import { computed } from "@ember/object";
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
import { getTopicFooterButtons } from "discourse/lib/register-topic-footer-button";
import { getTopicFooterDropdowns } from "discourse/lib/register-topic-footer-dropdown";
export default Component.extend({
elementId: "topic-footer-buttons",
@ -18,17 +20,25 @@ export default Component.extend({
return this.siteSettings.enable_personal_messages && isPM;
},
buttons: getTopicFooterButtons(),
inlineButtons: getTopicFooterButtons(),
inlineDropdowns: getTopicFooterDropdowns(),
@discourseComputed("buttons.[]")
inlineButtons(buttons) {
return buttons.filter((button) => !button.dropdown);
},
inlineActionables: computed(
"inlineButtons.[]",
"inlineDropdowns.[]",
function () {
return this.inlineButtons
.filterBy("dropdown", false)
.concat(this.inlineDropdowns)
.sortBy("priority")
.reverse();
}
),
// topic.assigned_to_user is for backward plugin support
@discourseComputed("buttons.[]", "topic.assigned_to_user")
dropdownButtons(buttons) {
return buttons.filter((button) => button.dropdown);
@discourseComputed("inlineButtons.[]", "topic.assigned_to_user")
dropdownButtons(inlineButtons) {
return inlineButtons.filter((button) => button.dropdown);
},
@discourseComputed("topic.isPrivateMessage")

View File

@ -159,16 +159,16 @@ export default Component.extend({
return classes.join(" ");
},
hasLikes: function () {
hasLikes() {
return this.get("topic.like_count") > 0;
},
hasOpLikes: function () {
hasOpLikes() {
return this.get("topic.op_like_count") > 0;
},
@discourseComputed
expandPinned: function () {
expandPinned() {
const pinned = this.get("topic.pinned");
if (!pinned) {
return false;

View File

@ -13,6 +13,7 @@ const MIN_WIDTH_TIMELINE = 924,
MIN_HEIGHT_TIMELINE = 325;
export default Component.extend(PanEvents, {
classNameBindings: ["info.topicProgressExpanded:topic-progress-expanded"],
composerOpen: null,
info: null,
isPanning: false,

View File

@ -1,13 +1,14 @@
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import discourseComputed, { bind } from "discourse-common/utils/decorators";
import Component from "@ember/component";
import I18n from "I18n";
import { alias } from "@ember/object/computed";
import { scheduleOnce } from "@ember/runloop";
import { later, scheduleOnce } from "@ember/runloop";
export default Component.extend({
elementId: "topic-progress-wrapper",
classNameBindings: ["docked"],
classNameBindings: ["docked", "withTransitions"],
docked: false,
withTransitions: null,
progressPosition: null,
postStream: alias("topic.postStream"),
_streamPercentage: null,
@ -68,128 +69,119 @@ export default Component.extend({
return readPos < stream.length - 1 && readPos > position;
},
@observes("postStream.stream.[]")
_updateBar() {
scheduleOnce("afterRender", this, this._updateProgressBar);
},
_topicScrolled(event) {
if (this.docked) {
this.set("progressPosition", this.get("postStream.filteredPostsCount"));
this._streamPercentage = 1.0;
this.setProperties({
progressPosition: this.get("postStream.filteredPostsCount"),
_streamPercentage: 100,
});
} else {
this.set("progressPosition", event.postIndex);
this._streamPercentage = event.percent;
this.setProperties({
progressPosition: event.postIndex,
_streamPercentage: (event.percent * 100).toFixed(2),
});
}
},
this._updateBar();
@discourseComputed("_streamPercentage")
progressStyle(_streamPercentage) {
return `--progress-bg-width: ${_streamPercentage || 0}%`;
},
didInsertElement() {
this._super(...arguments);
this.appEvents
.on("composer:will-open", this, this._dock)
.on("composer:resized", this, this._dock)
.on("composer:closed", this, this._dock)
.on("topic:scrolled", this, this._dock)
.on("composer:resized", this, this._composerEvent)
.on("topic:current-post-scrolled", this, this._topicScrolled);
const prevEvent = this.prevEvent;
if (prevEvent) {
scheduleOnce("afterRender", this, this._topicScrolled, prevEvent);
} else {
scheduleOnce("afterRender", this, this._updateProgressBar);
if (this.prevEvent) {
scheduleOnce("afterRender", this, this._topicScrolled, this.prevEvent);
}
scheduleOnce("afterRender", this, this._dock);
scheduleOnce("afterRender", this, this._startObserver);
// start CSS transitions a tiny bit later
// to avoid jumpiness on initial topic load
later(this._addCssTransitions, 500);
},
willDestroyElement() {
this._super(...arguments);
this._topicBottomObserver?.disconnect();
this.appEvents
.off("composer:will-open", this, this._dock)
.off("composer:resized", this, this._dock)
.off("composer:closed", this, this._dock)
.off("topic:scrolled", this, this._dock)
.off("composer:resized", this, this._composerEvent)
.off("topic:current-post-scrolled", this, this._topicScrolled);
},
_updateProgressBar() {
if (this.isDestroyed || this.isDestroying) {
@bind
_addCssTransitions() {
if (this.isDestroying || this.isDestroyed) {
return;
}
this.set("withTransitions", true);
},
const $topicProgress = $(this.element.querySelector("#topic-progress"));
// speeds up stuff, bypass jquery slowness and extra checks
if (!this._totalWidth) {
this._totalWidth = $topicProgress[0].offsetWidth;
}
// Only show percentage once we have one
if (!this._streamPercentage) {
return;
}
const totalWidth = this._totalWidth;
const progressWidth = (this._streamPercentage || 0) * totalWidth;
const borderSize = progressWidth === totalWidth ? "0px" : "1px";
const $bg = $topicProgress.find(".bg");
if ($bg.length === 0) {
const style = `border-right-width: ${borderSize}; width: ${progressWidth}px`;
$topicProgress.append(`<div class='bg' style="${style}">&nbsp;</div>`);
} else {
$bg.css("border-right-width", borderSize).width(progressWidth - 2);
_startObserver() {
if ("IntersectionObserver" in window) {
this._topicBottomObserver = this._setupObserver();
this._topicBottomObserver.observe(
document.querySelector("#topic-bottom")
);
}
},
_dock() {
const $wrapper = $(this.element);
if (!$wrapper || $wrapper.length === 0) {
return;
}
_setupObserver() {
// minimum 50px here ensures element is not docked when
// scrolling down quickly, it causes post stream refresh loop
// on Android
const bottomIntersectionMargin =
document.querySelector("#reply-control")?.clientHeight || 50;
const $html = $("html");
const offset = window.pageYOffset || $html.scrollTop();
const maximumOffset = $("#topic-bottom").offset().top;
const windowHeight = $(window).height();
let composerHeight = $("#reply-control").height() || 0;
const isDocked = offset >= maximumOffset - windowHeight + composerHeight;
let bottom = $("body").height() - maximumOffset;
const $iPadFooterNav = $(".footer-nav-ipad .footer-nav");
if ($iPadFooterNav && $iPadFooterNav.length > 0) {
bottom += $iPadFooterNav.outerHeight();
}
const draftComposerHeight = 40;
if (composerHeight > 0) {
const $iPhoneFooterNav = $(".footer-nav-visible .footer-nav");
const $replyDraft = $("#reply-control.draft");
if ($iPhoneFooterNav.outerHeight() && $replyDraft.outerHeight()) {
composerHeight =
$replyDraft.outerHeight() + $iPhoneFooterNav.outerHeight();
}
$wrapper.css("bottom", isDocked ? bottom : composerHeight);
} else {
$wrapper.css("bottom", isDocked ? bottom : "");
}
this.set("docked", isDocked);
$wrapper.css(
"margin-bottom",
!isDocked && composerHeight > draftComposerHeight ? "0px" : ""
);
this.appEvents.trigger("topic-progress:docked-status-changed", {
docked: isDocked,
element: this.element,
return new IntersectionObserver(this._intersectionHandler, {
threshold: 1,
rootMargin: `0px 0px -${bottomIntersectionMargin}px 0px`,
});
},
_composerEvent() {
// reinitializing needed to account for composer height
// might be no longer necessary if IntersectionObserver API supports dynamic rootMargin
// see https://github.com/w3c/IntersectionObserver/issues/428
if ("IntersectionObserver" in window) {
this._topicBottomObserver?.disconnect();
this._startObserver();
}
},
@bind
_intersectionHandler(entries) {
if (!this.element || this.isDestroying || this.isDestroyed) {
return;
}
if (entries[0].isIntersecting === true) {
this.set("docked", true);
} else {
if (entries[0].boundingClientRect.top > 0) {
this.set("docked", false);
const wrapper = document.querySelector("#topic-progress-wrapper");
const composerH =
document.querySelector("#reply-control")?.clientHeight || 0;
if (composerH === 0) {
const filteredPostsHeight =
document.querySelector(".posts-filtered-notice")?.clientHeight || 0;
filteredPostsHeight === 0
? wrapper.style.removeProperty("bottom")
: wrapper.style.setProperty("bottom", `${filteredPostsHeight}px`);
} else {
wrapper.style.setProperty("bottom", `${composerH}px`);
}
}
}
},
click(e) {
if ($(e.target).closest("#topic-progress").length) {
if (e.target.closest("#topic-progress")) {
this.send("toggleExpansion");
}
},

View File

@ -5,7 +5,7 @@ export default Component.extend({
tagName: "span",
@observes("selected")
selectionChanged: function () {
selectionChanged() {
const selected = this.selected;
const list = this.selectedList;
const id = this.selectedId;

View File

@ -0,0 +1,25 @@
import Component from "@ember/component";
import I18n from "I18n";
import UppyUploadMixin from "discourse/mixins/uppy-upload";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend(UppyUploadMixin, {
tagName: "span",
type: "backup",
useMultipartUploadsIfAvailable: true,
@discourseComputed("uploading", "uploadProgress")
uploadButtonText(uploading, progress) {
return uploading
? I18n.t("admin.backups.upload.uploading_progress", { progress })
: I18n.t("admin.backups.upload.label");
},
validateUploadedFilesOptions() {
return { skipValidation: true };
},
uploadDone() {
this.done();
},
});

View File

@ -31,7 +31,7 @@ export default Component.extend(LoadMore, {
classNames: ["user-stream"],
@observes("stream.user.id")
_scrollTopOnModelChange: function () {
_scrollTopOnModelChange() {
schedule("afterRender", () => $(document).scrollTop(0));
},

View File

@ -1,6 +1,62 @@
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { action } from "@ember/object";
import { Promise } from "rsvp";
import showModal from "discourse/lib/show-modal";
export function openBookmarkModal(
bookmark,
callbacks = {
onCloseWithoutSaving: null,
onAfterSave: null,
onAfterDelete: null,
}
) {
return new Promise((resolve) => {
const modalTitle = () => {
if (bookmark.for_topic) {
return bookmark.id
? "post.bookmarks.edit_for_topic"
: "post.bookmarks.create_for_topic";
}
return bookmark.id ? "post.bookmarks.edit" : "post.bookmarks.create";
};
let modalController = showModal("bookmark", {
model: {
postId: bookmark.post_id,
topicId: bookmark.topic_id,
id: bookmark.id,
reminderAt: bookmark.reminder_at,
autoDeletePreference: bookmark.auto_delete_preference,
name: bookmark.name,
forTopic: bookmark.for_topic,
},
title: modalTitle(),
modalClass: "bookmark-with-reminder",
});
modalController.setProperties({
onCloseWithoutSaving: () => {
if (callbacks.onCloseWithoutSaving) {
callbacks.onCloseWithoutSaving();
}
resolve();
},
afterSave: (savedData) => {
let resolveData;
if (callbacks.onAfterSave) {
resolveData = callbacks.onAfterSave(savedData);
}
resolve(resolveData);
},
afterDelete: (topicBookmarked, bookmarkId) => {
if (callbacks.onAfterDelete) {
callbacks.onAfterDelete(topicBookmarked, bookmarkId);
}
resolve();
},
});
});
}
export default Controller.extend(ModalFunctionality, {
onShow() {
@ -21,7 +77,7 @@ export default Controller.extend(ModalFunctionality, {
*/
onClose(opts = {}) {
if (this.onCloseHandler) {
this.onCloseHandler(opts.initiatedByCloseButton);
this.onCloseHandler(opts);
}
},
});

View File

@ -50,7 +50,7 @@ export default Controller.extend(ModalFunctionality, {
);
},
() => {
this.flash(I18n.t("topic.change_owner.error"), "alert-error");
this.flash(I18n.t("topic.change_owner.error"), "error");
this.set("saving", false);
}
);

View File

@ -54,7 +54,7 @@ export default Controller.extend(ModalFunctionality, {
next(() => DiscourseURL.routeTo(topic.url));
})
.catch(() =>
this.flash(I18n.t("topic.change_timestamp.error"), "alert-error")
this.flash(I18n.t("topic.change_timestamp.error"), "error")
)
.finally(() => this.set("saving", false));

View File

@ -192,10 +192,13 @@ export default Controller.extend({
@discourseComputed("model.canEditTitle", "model.creatingPrivateMessage")
canEditTags(canEditTitle, creatingPrivateMessage) {
if (creatingPrivateMessage && (this.site.mobileView || !this.isStaffUser)) {
return false;
}
return (
this.site.can_tag_topics &&
canEditTitle &&
!creatingPrivateMessage &&
(!this.get("model.topic.isPrivateMessage") || this.site.can_tag_pms)
);
},
@ -502,6 +505,11 @@ export default Controller.extend({
$links.each((idx, l) => {
const href = l.href;
if (href && href.length) {
// skip links added by watched words
if (l.dataset.word !== undefined) {
return true;
}
// skip links in quotes and oneboxes
for (let element = l; element; element = element.parentElement) {
if (
@ -632,7 +640,9 @@ export default Controller.extend({
},
save(ignore, event) {
this.save(false, { jump: !(event && event.shiftKey) });
this.save(false, {
jump: !event?.shiftKey && !this.skipJumpOnSave,
});
},
displayEditReason() {
@ -665,17 +675,19 @@ export default Controller.extend({
groups.forEach((group) => {
let body;
const groupLink = getURL(`/g/${group.name}/members`);
const maxMentions = parseInt(group.max_mentions, 10);
const userCount = parseInt(group.user_count, 10);
if (group.max_mentions < group.user_count) {
if (maxMentions < userCount) {
body = I18n.t("composer.group_mentioned_limit", {
group: `@${group.name}`,
count: group.max_mentions,
count: maxMentions,
group_link: groupLink,
});
} else if (group.user_count > 0) {
body = I18n.t("composer.group_mentioned", {
group: `@${group.name}`,
count: group.user_count,
count: userCount,
group_link: groupLink,
});
}
@ -956,6 +968,7 @@ export default Controller.extend({
@param {Number} [opts.prioritizedCategoryId]
@param {String} [opts.draftSequence]
@param {Boolean} [opts.skipDraftCheck]
@param {Boolean} [opts.skipJumpOnSave] Option to skip navigating to the post when saved in this composer session
**/
open(opts) {
opts = opts || {};
@ -982,6 +995,8 @@ export default Controller.extend({
skipAutoSave: true,
});
this.set("skipJumpOnSave", !!opts.skipJumpOnSave);
// Scope the categories drop down to the category we opened the composer with.
if (opts.categoryId && !opts.disableScopedCategory) {
const category = this.site.categories.findBy("id", opts.categoryId);

View File

@ -260,7 +260,7 @@ export default Controller.extend(
},
@observes("emailValidation", "accountEmail")
prefillUsername: function () {
prefillUsername() {
if (this.prefilledUsername) {
// If username field has been filled automatically, and email field just changed,
// then remove the username.

View File

@ -29,7 +29,7 @@ export default Controller.extend(ModalFunctionality, {
.destroy(this.currentUser)
.then(() => this.send("closeModal"))
.catch(() => {
this.flash(I18n.t("post.controls.delete_topic_error"), "alert-error");
this.flash(I18n.t("post.controls.delete_topic_error"), "error");
this.set("deletingTopic", false);
});

View File

@ -23,7 +23,7 @@ export default Controller.extend({
loadedAllItems: not("discoveryTopics.model.canLoadMore"),
@observes("loadedAllItems")
_showFooter: function () {
_showFooter() {
this.set("application.showFooter", this.loadedAllItems);
},

View File

@ -74,6 +74,7 @@ const controllerOpts = {
// router and ember throws an error due to missing `handlerInfos`.
// Lesson learned: Don't call `loading` yourself.
this.set("discovery.loading", true);
this.set("model.canLoadMore", true);
this.topicTrackingState.resetTracking();

View File

@ -185,7 +185,7 @@ export default Controller.extend(ModalFunctionality, {
) {
this.flash(
I18n.t("topic.topic_status_update.time_frame_required"),
"alert-error"
"error"
);
return;
}
@ -195,19 +195,13 @@ export default Controller.extend(ModalFunctionality, {
!this.get("topicTimer.updateTime")
) {
if (this.get("topicTimer.duration_minutes") <= 0) {
this.flash(
I18n.t("topic.topic_status_update.min_duration"),
"alert-error"
);
this.flash(I18n.t("topic.topic_status_update.min_duration"), "error");
return;
}
// cannot be more than 20 years
if (this.get("topicTimer.duration_minutes") > 20 * 365 * 1440) {
this.flash(
I18n.t("topic.topic_status_update.max_duration"),
"alert-error"
);
this.flash(I18n.t("topic.topic_status_update.max_duration"), "error");
return;
}
}

View File

@ -37,7 +37,7 @@ export default Controller.extend({
ajax({
url: `/session/email-login/${this.model.token}`,
type: "POST",
data: data,
data,
})
.then((result) => {
if (result.success) {

View File

@ -258,8 +258,9 @@ export default Controller.extend(ModalFunctionality, {
let params = this.get("selected.is_custom_flag")
? { message: this.message }
: {};
if (opts) {
params = $.extend(params, opts);
params = Object.assign(params, opts);
}
this.appEvents.trigger(

View File

@ -5,6 +5,7 @@ import { getWebauthnCredential } from "discourse/lib/webauthn";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { SECOND_FACTOR_METHODS } from "discourse/models/user";
import I18n from "I18n";
import bootbox from "bootbox";
export default Controller.extend(ModalFunctionality, {
showSecondFactor: false,

View File

@ -48,8 +48,8 @@ export default Controller.extend(ModalFunctionality, GrantBadgeController, {
UserBadge.findByUsername(this.get("post.username")),
]).then(([allBadges, userBadges]) => {
this.setProperties({
allBadges: allBadges,
userBadges: userBadges,
allBadges,
userBadges,
loading: false,
});
});

View File

@ -13,7 +13,7 @@ export default Controller.extend(ModalFunctionality, {
if (!this.ignoredUntil || !this.ignoredUsername) {
this.flash(
I18n.t("user.user_notifications.ignore_duration_time_frame_required"),
"alert-error"
"error"
);
return;
}

View File

@ -11,7 +11,7 @@ export default Controller.extend(ModalFunctionality, {
if (!this.ignoredUntil) {
this.flash(
I18n.t("user.user_notifications.ignore_duration_time_frame_required"),
"alert-error"
"error"
);
return;
}

View File

@ -83,7 +83,7 @@ export default Controller.extend(
@discourseComputed("email")
yourEmailMessage(email) {
return I18n.t("invites.your_email", { email: email });
return I18n.t("invites.your_email", { email });
},
@discourseComputed

View File

@ -273,9 +273,7 @@ export default Controller.extend(ModalFunctionality, {
}
this.set("loggingIn", true);
loginMethod
.doLogin({ signup: signup })
.catch(() => this.set("loggingIn", false));
loginMethod.doLogin({ signup }).catch(() => this.set("loggingIn", false));
},
createAccount() {

View File

@ -121,7 +121,7 @@ export default Controller.extend(PasswordValidation, {
this.setProperties({
securityKeyRequired: true,
password: null,
errorMessage: errorMessage,
errorMessage,
});
}
);

View File

@ -73,7 +73,7 @@ export default Controller.extend(ModalFunctionality, {
name: this.model.username_lower,
},
pubKeyCredParams: this.supported_algorithms.map((alg) => {
return { type: "public-key", alg: alg };
return { type: "public-key", alg };
}),
excludeCredentials: this.existing_active_credential_ids.map(
(credentialId) => {

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