Version bump

This commit is contained in:
Neil Lalonde 2020-11-19 13:59:15 -05:00
commit ccc2c940bf
No known key found for this signature in database
GPG Key ID: FF871CA9037D0A91
845 changed files with 35550 additions and 20271 deletions

38
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,38 @@
version: 2
updates:
- package-ecosystem: bundler
directory: "/"
schedule:
interval: daily
time: "08:00"
timezone: Australia/Sydney
open-pull-requests-limit: 10
versioning-strategy: lockfile-only
allow:
- dependency-type: direct
- dependency-type: indirect
ignore:
- dependency-name: aws-partitions
versions:
- "> 1.329.0"
- "< 2"
- dependency-name: aws-sdk-core
versions:
- "> 3.99.1"
- "< 4"
- dependency-name: aws-sdk-kms
versions:
- "> 1.31.0"
- "< 2"
- dependency-name: aws-sdk-s3
versions:
- "> 1.66.0"
- "< 2"
- dependency-name: aws-sdk-sns
versions:
- "> 1.25.1"
- "< 2"
- dependency-name: aws-sigv4
versions:
- "> 1.2.0"
- "< 2"

6
.gitignore vendored
View File

@ -137,6 +137,12 @@ node_modules
# ignore generated api documentation files
openapi/*
# ignore VSCode config files
.vscode
# ignore direnv
.envrc
# ember-cli generated
dist

View File

@ -45,34 +45,34 @@ GEM
rake (>= 10.4, < 14.0)
ast (2.4.1)
aws-eventstream (1.1.0)
aws-partitions (1.329.0)
aws-sdk-core (3.99.1)
aws-partitions (1.390.0)
aws-sdk-core (3.109.2)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.31.0)
aws-sdk-core (~> 3, >= 3.71.0)
aws-sdk-kms (1.39.0)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.66.0)
aws-sdk-core (~> 3, >= 3.96.1)
aws-sdk-s3 (1.83.2)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
aws-sdk-sns (1.25.1)
aws-sdk-core (~> 3, >= 3.99.0)
aws-sdk-sns (1.35.0)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.2.0)
aws-sigv4 (1.2.2)
aws-eventstream (~> 1, >= 1.0.2)
barber (0.12.2)
ember-source (>= 1.0, < 3.1)
execjs (>= 1.2, < 3)
better_errors (2.8.3)
better_errors (2.9.1)
coderay (>= 1.0.0)
erubi (>= 1.0.0)
rack (>= 0.9.0)
binding_of_caller (0.8.0)
debug_inspector (>= 0.0.1)
bootsnap (1.4.9)
bootsnap (1.5.1)
msgpack (~> 1.0)
builder (3.2.4)
bullet (6.1.0)
@ -120,7 +120,7 @@ GEM
barber (>= 0.11.0)
sprockets (>= 3.3, < 4.1)
ember-source (2.18.2)
erubi (1.9.0)
erubi (1.10.0)
excon (0.78.0)
execjs (2.7.0)
exifr (1.3.9)
@ -160,7 +160,7 @@ GEM
jwt (2.2.2)
kgio (2.11.3)
libv8 (8.4.255.0)
listen (3.2.1)
listen (3.3.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
lograge (0.11.2)
@ -213,7 +213,7 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
oj (3.10.15)
oj (3.10.16)
omniauth (1.9.1)
hashie (>= 3.4.6)
rack (>= 1.6.2, < 3)
@ -235,7 +235,7 @@ GEM
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
rack
onebox (2.1.4)
onebox (2.1.6)
addressable (~> 2.7.0)
htmlentities (~> 4.3)
multi_json (~> 1.11)
@ -244,7 +244,7 @@ GEM
sanitize
openssl-signature_algorithm (1.0.0)
optimist (3.0.1)
parallel (1.19.2)
parallel (1.20.0)
parallel_tests (3.3.0)
parallel
parser (2.7.2.0)
@ -275,7 +275,7 @@ GEM
nokogiri (>= 1.6)
rails-html-sanitizer (1.3.0)
loofah (~> 2.3)
rails_failover (0.5.7)
rails_failover (0.6.0)
activerecord (~> 6.0)
railties (~> 6.0)
rails_multisite (2.5.0)
@ -311,21 +311,21 @@ GEM
chunky_png (~> 1.0)
rqrcode_core (~> 0.1)
rqrcode_core (0.1.2)
rspec (3.9.0)
rspec-core (~> 3.9.0)
rspec-expectations (~> 3.9.0)
rspec-mocks (~> 3.9.0)
rspec-core (3.9.3)
rspec-support (~> 3.9.3)
rspec-expectations (3.9.4)
rspec (3.10.0)
rspec-core (~> 3.10.0)
rspec-expectations (~> 3.10.0)
rspec-mocks (~> 3.10.0)
rspec-core (3.10.0)
rspec-support (~> 3.10.0)
rspec-expectations (3.10.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-support (~> 3.10.0)
rspec-html-matchers (0.9.4)
nokogiri (~> 1)
rspec (>= 3.0.0.a, < 4)
rspec-mocks (3.9.1)
rspec-mocks (3.10.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-support (~> 3.10.0)
rspec-rails (4.0.1)
actionpack (>= 4.2)
activesupport (>= 4.2)
@ -334,29 +334,30 @@ GEM
rspec-expectations (~> 3.9)
rspec-mocks (~> 3.9)
rspec-support (~> 3.9)
rspec-support (3.9.4)
rspec-support (3.10.0)
rswag-specs (2.3.1)
activesupport (>= 3.1, < 7.0)
json-schema (~> 2.2)
railties (>= 3.1, < 7.0)
rtlit (0.0.5)
rubocop (1.1.0)
rubocop (1.3.1)
parallel (~> 1.10)
parser (>= 2.7.1.5)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8)
rexml
rubocop-ast (>= 1.0.1)
rubocop-ast (>= 1.1.1)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0)
rubocop-ast (1.1.0)
rubocop-ast (1.1.1)
parser (>= 2.7.1.5)
rubocop-discourse (2.4.0)
rubocop-discourse (2.4.1)
rubocop (>= 1.1.0)
rubocop-rspec (>= 2.0.0.pre)
rubocop-rspec (2.0.0.pre)
rubocop-rspec (>= 2.0.0)
rubocop-rspec (2.0.0)
rubocop (~> 1.0)
ruby-prof (1.4.1)
rubocop-ast (>= 1.1.0)
ruby-prof (1.4.2)
ruby-progressbar (1.10.1)
ruby-readability (0.7.0)
guess_html_encoding (>= 0.0.4)
@ -402,7 +403,7 @@ GEM
thor (1.0.1)
thread_safe (0.3.6)
tilt (2.0.10)
tzinfo (1.2.7)
tzinfo (1.2.8)
thread_safe (~> 0.1)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
@ -414,11 +415,11 @@ GEM
kgio (~> 2.6)
raindrops (~> 0.7)
uniform_notifier (1.13.0)
webmock (3.9.3)
webmock (3.10.0)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webpush (1.0.0)
webpush (1.1.0)
hkdf (~> 0.2)
jwt (~> 2.0)
xorcist (1.1.2)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 882 B

After

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 984 B

After

Width:  |  Height:  |  Size: 898 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 895 B

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 947 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 613 B

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 845 B

After

Width:  |  Height:  |  Size: 759 B

View File

@ -2,6 +2,7 @@ import Component from "@ember/component";
import BufferedContent from "discourse/mixins/buffered-content";
import SiteSetting from "admin/models/site-setting";
import SettingComponent from "admin/mixins/setting-component";
import { readOnly } from "@ember/object/computed";
export default Component.extend(BufferedContent, SettingComponent, {
updateExistingUsers: null,
@ -12,4 +13,6 @@ export default Component.extend(BufferedContent, SettingComponent, {
updateExistingUsers: this.updateExistingUsers,
});
},
staffLogFilter: readOnly("setting.staffLogFilter"),
});

View File

@ -1,4 +1,52 @@
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n";
import { equal } from "@ember/object/computed";
import { action } from "@ember/object";
const CUSTOM_REASON_KEY = "custom";
export default Component.extend({
tagName: "",
selectedReason: CUSTOM_REASON_KEY,
customReason: "",
reasonKeys: [
"not_listening_to_staff",
"consuming_staff_time",
"combatative",
"in_wrong_place",
"no_constructive_purpose",
CUSTOM_REASON_KEY,
],
isCustomReason: equal("selectedReason", CUSTOM_REASON_KEY),
@discourseComputed("reasonKeys")
reasons(keys) {
return keys.map((key) => {
return { id: key, name: I18n.t(`admin.user.suspend_reasons.${key}`) };
});
},
@action
setSelectedReason(value) {
this.set("selectedReason", value);
this.setReason();
},
@action
setCustomReason(value) {
this.set("customReason", value);
this.setReason();
},
setReason() {
if (this.isCustomReason) {
this.set("reason", this.customReason);
} else {
this.set(
"reason",
I18n.t(`admin.user.suspend_reasons.${this.selectedReason}`)
);
}
},
});

View File

@ -35,6 +35,11 @@ export default Controller.extend({
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() {
const descriptions = [];
@ -304,6 +309,10 @@ export default Controller.extend({
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);

View File

@ -4,7 +4,7 @@ import Controller from "@ember/controller";
import { popupAjaxError } from "discourse/lib/ajax-error";
import bootbox from "bootbox";
const MAX_FIELDS = 20;
const MAX_FIELDS = 30;
export default Controller.extend({
fieldTypes: null,

View File

@ -19,6 +19,7 @@ export default Controller.extend(CanCheckEmails, {
customGroupIdsBuffer: null,
availableGroups: null,
userTitleValue: null,
ssoExternalEmail: null,
showBadges: setting("enable_badges"),
hasLockedTrustLevel: notEmpty("model.manual_locked_trust_level"),
@ -339,5 +340,15 @@ export default Controller.extend(CanCheckEmails, {
}
);
},
checkSsoEmail() {
return ajax(userPath(`${this.model.username_lower}/sso-email.json`), {
data: { context: window.location.pathname },
}).then((result) => {
if (result) {
this.set("ssoExternalEmail", result.email);
}
});
},
},
});

View File

@ -1,12 +1,13 @@
import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { extractError } from "discourse/lib/ajax-error";
import Mixin from "@ember/object/mixin";
import { next } from "@ember/runloop";
import { Promise } from "rsvp";
import bootbox from "bootbox";
export default Mixin.create(ModalFunctionality, {
errorMessage: null,
reason: null,
message: null,
postEdit: null,
@ -18,6 +19,7 @@ export default Mixin.create(ModalFunctionality, {
resetModal() {
this.setProperties({
errorMessage: null,
reason: null,
message: null,
loadingUser: true,
@ -66,6 +68,8 @@ export default Mixin.create(ModalFunctionality, {
callback(result);
}
})
.catch(popupAjaxError);
.catch((error) => {
this.set("errorMessage", extractError(error));
});
},
});

View File

@ -352,28 +352,17 @@ const AdminUser = User.extend({
type: "PUT",
})
.then((result) => this.setProperties(result.unsilence))
.catch((e) => {
const error = I18n.t("admin.user.unsilence_failed", {
error: this._formatError(e),
});
bootbox.alert(error);
})
.finally(() => this.set("silencingUser", false));
},
silence(data) {
this.set("silencingUser", true);
return ajax(`/admin/users/${this.id}/silence`, {
type: "PUT",
data,
})
.then((result) => this.setProperties(result.silence))
.catch((e) => {
const error = I18n.t("admin.user.silence_failed", {
error: this._formatError(e),
});
bootbox.alert(error);
})
.finally(() => this.set("silencingUser", false));
},

View File

@ -20,7 +20,10 @@ const ColorScheme = EmberObject.extend({
},
startTrackingChanges() {
this.set("originals", { name: this.name });
this.set("originals", {
name: this.name,
user_selectable: this.user_selectable,
});
},
schemeJson() {
@ -46,14 +49,22 @@ const ColorScheme = EmberObject.extend({
return newScheme;
},
@discourseComputed("name", "colors.@each.changed", "saving")
changed(name) {
@discourseComputed(
"name",
"user_selectable",
"colors.@each.changed",
"saving"
)
changed(name, user_selectable) {
if (!this.originals) {
return false;
}
if (this.originals.name !== name) {
return true;
}
if (this.originals.user_selectable !== user_selectable) {
return true;
}
if (this.colors.any((c) => c.get("changed"))) {
return true;
}
@ -80,9 +91,9 @@ const ColorScheme = EmberObject.extend({
this.setProperties({ savingStatus: I18n.t("saving"), saving: true });
const data = {};
if (!opts || !opts.enabledOnly) {
data.name = this.name;
data.user_selectable = this.user_selectable;
data.base_scheme_id = this.base_scheme_id;
data.colors = [];
this.colors.forEach((c) => {

View File

@ -2,8 +2,21 @@ import I18n from "I18n";
import { ajax } from "discourse/lib/ajax";
import Setting from "admin/mixins/setting-object";
import EmberObject from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators";
const SiteSetting = EmberObject.extend(Setting, {});
const SiteSetting = EmberObject.extend(Setting, {
@discourseComputed("setting")
staffLogFilter(setting) {
if (!setting) {
return;
}
return {
subject: setting,
action_name: "change_site_setting",
};
},
});
SiteSetting.reopenClass({
findAll() {

View File

@ -5,11 +5,6 @@ import { or, gt } from "@ember/object/computed";
import RestModel from "discourse/models/rest";
import discourseComputed from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { ajax } from "discourse/lib/ajax";
import { escapeExpression } from "discourse/lib/utilities";
import highlightSyntax from "discourse/lib/highlight-syntax";
import { url } from "discourse/lib/computed";
import bootbox from "bootbox";
const THEME_UPLOAD_VAR = 2;
const FIELDS_IDS = [0, 1, 5];
@ -23,7 +18,6 @@ const Theme = RestModel.extend({
isPendingUpdates: gt("remote_theme.commits_behind", 0),
hasEditedFields: gt("editedFields.length", 0),
hasParents: gt("parent_themes.length", 0),
diffLocalChangesUrl: url("id", "/admin/themes/%@/diff_local_changes"),
@discourseComputed("theme_fields.[]")
targets() {
@ -292,37 +286,9 @@ const Theme = RestModel.extend({
},
updateToLatest() {
return ajax(this.diffLocalChangesUrl).then((json) => {
if (json && json.error) {
bootbox.alert(
I18n.t("generic_error_with_reason", {
error: json.error,
})
);
} else if (json && json.diff) {
bootbox.confirm(
I18n.t("admin.customize.theme.update_confirm") +
`<pre><code class="diff">${escapeExpression(
json.diff
)}</code></pre>`,
I18n.t("cancel"),
I18n.t("admin.customize.theme.update_confirm_yes"),
(result) => {
if (result) {
return this.save({ remote_update: true }).then(() =>
this.set("changed", false)
);
}
}
);
// TODO: Models shouldn't be updating the DOM
highlightSyntax(undefined, this.siteSettings, this.session);
} else {
return this.save({ remote_update: true }).then(() =>
this.set("changed", false)
);
}
});
return this.save({ remote_update: true }).then(() =>
this.set("changed", false)
);
},
changed: false,

View File

@ -28,6 +28,10 @@ export default Route.extend({
const fields = wrapper.model
.get("fields")
[wrapper.target].map((f) => f.name);
if (wrapper.model.remote_theme) {
this.transitionTo("adminCustomizeThemes.index");
return;
}
if (!fields.includes(wrapper.field_name)) {
this.transitionTo(
"adminCustomizeThemes.edit",

View File

@ -1,5 +1,16 @@
<div class="setting-label">
<h3>{{settingName}}</h3>
<h3>
{{#if staffLogFilter}}
{{settingName}}
{{#link-to "adminLogs.staffActionLogs" (query-params filters=staffLogFilter) title=(i18n "admin.settings.history")}}
<span class="history-icon">
{{d-icon "history"}}
</span>
{{/link-to}}
{{else}}
{{settingName}}
{{/if}}
</h3>
{{#if defaultIsAvailable}}
<a href onClick={{action "setDefaultValues"}}>{{setting.setDefaultValuesLabel}}</a>
{{/if}}

View File

@ -8,10 +8,20 @@
{{/if}}
</div>
</label>
{{text-field
value=reason
<label>
{{i18n "admin.user.suspend_reason_title"}}
</label>
{{combo-box
content=reasons
value=selectedReason
class="suspend-reason"
placeholderKey="admin.user.suspend_reason_placeholder"}}
onChange=(action setSelectedReason)}}
{{#if isCustomReason}}
{{text-field
value=customReason
class="suspend-reason"
onChange=(action setCustomReason)}}
{{/if}}
</div>
<label>

View File

@ -41,7 +41,14 @@
<br>
<div class="admin-controls">
{{inline-edit-checkbox action=(action "applyUserSelectable") labelKey="admin.customize.theme.color_scheme_user_selectable" checked=model.user_selectable}}
{{#if model.theme_id}}
{{inline-edit-checkbox action=(action "applyUserSelectable") labelKey="admin.customize.theme.color_scheme_user_selectable" checked=model.user_selectable}}
{{else}}
<label>
{{input type="checkbox" checked=model.user_selectable}}
{{i18n "admin.customize.theme.color_scheme_user_selectable"}}
</label>
{{/if}}
</div>
{{#if colors.length}}

View File

@ -1,4 +1,5 @@
<div class="show-current-style">
{{plugin-outlet name="admin-customize-themes-show-top" args=(hash theme=model)}}
<div class="title">
{{#if editingName}}
{{text-field value=model.name autofocus="true"}}
@ -59,7 +60,12 @@
{{#if sourceIsHttp}}
<a class="remote-url" href={{remoteThemeLink}}>{{i18n "admin.customize.theme.source_url"}}{{d-icon "link"}}</a>
{{else}}
<div class="remote-url"><code>{{model.remote_theme.remote_url}}</code></div>
<div class="remote-url">
<code>{{model.remote_theme.remote_url}}</code>
{{#if model.remote_theme.branch}}
(<code>{{model.remote_theme.branch}}</code>)
{{/if}}
</div>
{{/if}}
{{/if}}
{{#if model.remote_theme.about_url}}
@ -128,12 +134,17 @@
{{/if}}
</div>
{{#unless model.component}}
{{#if showCheckboxes}}
<div class="control-unit">
{{inline-edit-checkbox action=(action "applyDefault") labelKey="admin.customize.theme.is_default" checked=model.default}}
{{inline-edit-checkbox action=(action "applyUserSelectable") labelKey="admin.customize.theme.user_selectable" checked=model.user_selectable}}
{{#unless model.component}}
{{inline-edit-checkbox action=(action "applyDefault") labelKey="admin.customize.theme.is_default" checked=model.default}}
{{inline-edit-checkbox action=(action "applyUserSelectable") labelKey="admin.customize.theme.user_selectable" checked=model.user_selectable}}
{{/unless}}
{{#if model.remote_theme}}
{{inline-edit-checkbox action=(action "applyAutoUpdateable") labelKey="admin.customize.theme.auto_update" checked=model.auto_update}}
{{/if}}
</div>
{{/unless}}
{{/if}}
{{#unless model.component}}
{{#d-section class="form-horizontal theme settings control-unit"}}
@ -186,45 +197,47 @@
{{/d-section}}
{{/if}}
<div class="control-unit">
<div class="mini-title">{{i18n "admin.customize.theme.css_html"}}</div>
{{#if model.hasEditedFields}}
<div class="description">{{i18n "admin.customize.theme.custom_sections"}}</div>
<ul>
{{#each editedFieldsFormatted as |field|}}
<li>{{field}}</li>
{{/each}}
</ul>
{{else}}
<div class="description">
{{i18n "admin.customize.theme.edit_css_html_help"}}
</div>
{{/if}}
{{#unless model.remote_theme}}
<div class="control-unit">
<div class="mini-title">{{i18n "admin.customize.theme.css_html"}}</div>
{{#if model.hasEditedFields}}
<div class="description">{{i18n "admin.customize.theme.custom_sections"}}</div>
<ul>
{{#each editedFieldsFormatted as |field|}}
<li>{{field}}</li>
{{/each}}
</ul>
{{else}}
<div class="description">
{{i18n "admin.customize.theme.edit_css_html_help"}}
</div>
{{/if}}
{{d-button
class="btn-default edit"
action=(action "editTheme")
label="admin.customize.theme.edit_css_html"}}
</div>
{{d-button
class="btn-default edit"
action=(action "editTheme")
label="admin.customize.theme.edit_css_html"}}
</div>
<div class="control-unit">
<div class="mini-title">{{i18n "admin.customize.theme.uploads"}}</div>
{{#if model.uploads}}
<ul class="removable-list">
{{#each model.uploads as |upload|}}
<li>
<span class="col">${{upload.name}}: <a href={{upload.url}} rel="noopener noreferrer" target="_blank">{{upload.filename}}</a></span>
<span class="col">
{{d-button action=(action "removeUpload") actionParam=upload class="second btn-default btn-default cancel-edit" icon="times"}}
</span>
</li>
{{/each}}
</ul>
{{else}}
<div class="description">{{i18n "admin.customize.theme.no_uploads"}}</div>
{{/if}}
{{d-button action=(action "addUploadModal") class="btn-default" icon="plus" label="admin.customize.theme.add"}}
</div>
<div class="control-unit">
<div class="mini-title">{{i18n "admin.customize.theme.uploads"}}</div>
{{#if model.uploads}}
<ul class="removable-list">
{{#each model.uploads as |upload|}}
<li>
<span class="col">${{upload.name}}: <a href={{upload.url}} rel="noopener noreferrer" target="_blank">{{upload.filename}}</a></span>
<span class="col">
{{d-button action=(action "removeUpload") actionParam=upload class="second btn-default btn-default cancel-edit" icon="times"}}
</span>
</li>
{{/each}}
</ul>
{{else}}
<div class="description">{{i18n "admin.customize.theme.no_uploads"}}</div>
{{/if}}
{{d-button action=(action "addUploadModal") class="btn-default" icon="plus" label="admin.customize.theme.add"}}
</div>
{{/unless}}
{{#if extraFiles.length}}
<div class="control-unit">

View File

@ -27,7 +27,7 @@
&mdash;
{{/if}}
</td>
<td><a href="mailto:{{l.to_address}}">{{l.to_address}}</a></td>
<td class="email-address"><a href="mailto:{{l.to_address}}">{{l.to_address}}</a></td>
{{#if l.has_bounce_key}}
<td><a href {{action "showIncomingEmail" l.id}}>{{l.email_type}}</a></td>
{{else}}

View File

@ -29,7 +29,7 @@
&mdash;
{{/if}}
</td>
<td><a href="mailto:{{l.to_address}}">{{l.to_address}}</a></td>
<td class="email-address"><a href="mailto:{{l.to_address}}">{{l.to_address}}</a></td>
<td>{{l.email_type}}</td>
<td>
{{#if l.post_url}}

View File

@ -1,6 +1,10 @@
{{#d-modal-body title="admin.user.silence_modal_title"}}
{{#conditional-loading-spinner condition=loadingUser}}
{{#if errorMessage}}
<div class="alert alert-error">{{errorMessage}}</div>
{{/if}}
<div class="until-controls">
<label>
{{future-date-input

View File

@ -1,6 +1,10 @@
{{#d-modal-body title="admin.user.suspend_modal_title"}}
{{#conditional-loading-spinner condition=loadingUser}}
{{#if errorMessage}}
<div class="alert alert-error">{{errorMessage}}</div>
{{/if}}
{{#if user.canSuspend}}
<div class="until-controls">
<label>

View File

@ -671,10 +671,19 @@
<div class="field">{{i18n "admin.user.sso.external_name"}}</div>
<div class="value">{{sso.external_name}}</div>
</div>
{{#if sso.external_email}}
{{#if canAdminCheckEmails}}
<div class="display-row">
<div class="field">{{i18n "admin.user.sso.external_email"}}</div>
<div class="value">{{sso.external_email}}</div>
{{#if ssoExternalEmail}}
<div class="value">{{ssoExternalEmail}}</div>
{{else}}
{{d-button
class="btn-default"
action=(action "checkSsoEmail")
actionParam=model icon="far-envelope"
label="admin.users.check_email.text"
title="admin.users.check_email.title"}}
{{/if}}
</div>
{{/if}}
<div class="display-row">

View File

@ -0,0 +1,75 @@
//browser-update.org notification script, <browser-update.org>
//Copyright (c) 2007-2009, MIT Style License <browser-update.org/LICENSE.txt>
(function () {
var $buo = function () {
// Sometimes we have to resort to parsing the user agent string. :(
if (navigator && navigator.userAgent) {
var ua = navigator.userAgent;
// we don't ask Googlebot to update their browser
if (
ua.indexOf("Googlebot") >= 0 ||
ua.indexOf("Mediapartners") >= 0 ||
ua.indexOf("AdsBot") >= 0
) {
return;
}
}
if (!window.unsupportedBrowser) {
return;
}
document.getElementsByTagName("body")[0].className += " crawler";
var mainElement = document.getElementById("main");
var noscriptElements = document.getElementsByTagName("noscript");
// find the element with the "data-path" attribute set
for (var i = 0; i < noscriptElements.length; ++i) {
if (noscriptElements[i].getAttribute("data-path")) {
// noscriptElements[i].innerHTML contains encoded HTML
if (noscriptElements[i].childNodes.length > 0) {
mainElement.innerHTML = noscriptElements[i].childNodes[0].nodeValue;
break;
}
}
}
// retrieve localized browser upgrade text
var t = I18n.t("browser_update"); // eslint-disable-line no-undef
if (t.indexOf(".browser_update]") !== -1) {
// very old browsers might fail to load even translations
t =
'Unfortunately, <a href="https://www.discourse.org/faq/#browser">your browser is too old to work on this site</a>. Please <a href="https://browsehappy.com">upgrade your browser</a> to view rich content, log in and reply.';
}
// create the notification div HTML
var div = document.createElement("div");
div.className = "buorg";
div.innerHTML = "<div>" + t + "</div>";
// create the notification div stylesheet
var sheet = document.createElement("style");
var style =
".buorg {position:absolute; z-index:111111; width:100%; top:0px; left:0px; background:#FDF2AB; text-align:left; font-family: sans-serif; color:#000; font-size: 14px;} .buorg div {padding: 8px;} .buorg a, .buorg a:visited {color:#E25600; text-decoration: underline;} @media print { .buorg { display: none !important; } }";
// insert the div and stylesheet into the DOM
document.body.appendChild(div); // put it last in the DOM so Googlebot doesn't include it in search excerpts
document.getElementsByTagName("head")[0].appendChild(sheet);
try {
sheet.innerText = style;
sheet.innerHTML = style;
} catch (e) {
try {
sheet.styleSheet.cssText = style;
} catch (ex) {
return;
}
}
// shift the body down to make room for our notification div
document.body.style.marginTop = div.clientHeight + "px";
};
$bu = $buo(); // eslint-disable-line no-undef
})(this);

View File

@ -52,6 +52,24 @@ export function buildResolver(baseName) {
return "service:app-events";
}
for (const [key, value] of Object.entries({
"controller:discovery.categoryWithID": "controller:discovery.category",
"controller:discovery.parentCategory": "controller:discovery.category",
"controller:tags-show": "controller:tag-show",
"controller:tags.show": "controller:tag.show",
"controller:tagsShow": "controller:tagShow",
"route:discovery.categoryWithID": "route:discovery.category",
"route:discovery.parentCategory": "route:discovery.category",
"route:tags-show": "route:tag-show",
"route:tags.show": "route:tag.show",
"route:tagsShow": "route:tagShow",
})) {
if (fullName === key) {
deprecated(`${key} was replaced with ${value}`, { since: "2.6.0" });
return value;
}
}
const split = fullName.split(":");
if (split.length > 1) {
const appBase = `${baseName}/${split[0]}s/`;

View File

@ -0,0 +1,7 @@
import RESTAdapter from "discourse/adapters/rest";
export default RESTAdapter.extend({
pathFor(store, type, id) {
return id ? `/tag/${id}` : `/tags`;
},
});

View File

@ -313,6 +313,10 @@ export default Component.extend({
this.appEvents.on("composer:replace-text", this, "_replaceText");
}
this._mouseTrap = mouseTrap;
if (isTesting()) {
this.element.addEventListener("paste", this.paste.bind(this));
}
},
_insertBlock(text) {
@ -336,6 +340,10 @@ export default Component.extend({
mouseTrap.unbind(sc)
);
$(this.element.querySelector(".d-editor-preview")).off("click.preview");
if (isTesting()) {
this.element.removeEventListener("paste", this.paste);
}
},
@discourseComputed()
@ -870,7 +878,7 @@ export default Component.extend({
},
paste(e) {
if (!$(".d-editor-input").is(":focus")) {
if (!$(".d-editor-input").is(":focus") && !isTesting()) {
return;
}
@ -894,7 +902,7 @@ export default Component.extend({
!isInlinePasting &&
!isCodeBlock
) {
plainText = plainText.trim().replace(/\r/g, "");
plainText = plainText.replace(/\r/g, "");
const table = this._extractTable(plainText);
if (table) {
this.appEvents.trigger("composer:insert-text", table);

View File

@ -10,6 +10,13 @@ export default Component.extend(FilterModeMixin, {
tagName: "",
// Should be a `readOnly` instead but some themes/plugins still pass
// the `categories` property into this component
@discourseComputed("site.categoriesList")
categories(categoriesList) {
return categoriesList;
},
@discourseComputed("category")
showCategoryNotifications(category) {
return category && this.currentUser;
@ -25,14 +32,20 @@ export default Component.extend(FilterModeMixin, {
@discourseComputed(
"createTopicDisabled",
"hasDraft",
"categoryReadOnlyBanner"
"categoryReadOnlyBanner",
"canCreateTopicOnTag",
"tag.id"
)
createTopicButtonDisabled(
createTopicDisabled,
hasDraft,
categoryReadOnlyBanner
categoryReadOnlyBanner,
canCreateTopicOnTag,
tagId
) {
if (categoryReadOnlyBanner && !hasDraft) {
if (tagId && !canCreateTopicOnTag) {
return true;
} else if (categoryReadOnlyBanner && !hasDraft) {
return false;
}
return createTopicDisabled;
@ -47,11 +60,6 @@ export default Component.extend(FilterModeMixin, {
}
},
@discourseComputed()
categories() {
return this.site.get("categoriesList");
},
@discourseComputed("hasDraft")
createTopicLabel(hasDraft) {
return hasDraft ? "topic.open_draft" : "topic.create";
@ -60,14 +68,20 @@ export default Component.extend(FilterModeMixin, {
@discourseComputed("category.can_edit")
showCategoryEdit: (canEdit) => canEdit,
@discourseComputed("filterType", "category", "noSubcategories")
navItems(filterType, category, noSubcategories) {
@discourseComputed("additionalTags", "category", "tag.id")
showToggleInfo(additionalTags, category, tagId) {
return !additionalTags && !category && tagId !== "none";
},
@discourseComputed("filterType", "category", "noSubcategories", "tag.id")
navItems(filterType, category, noSubcategories, tagId) {
const currentRouteQueryParams = this.get("router.currentRoute.queryParams");
return NavItem.buildList(category, {
filterType,
noSubcategories,
currentRouteQueryParams,
tagId,
siteSettings: this.siteSettings,
});
},

View File

@ -2,6 +2,6 @@ import Component from "@ember/component";
export default Component.extend({
didInsertElement() {
this._super(...arguments);
$(this.element.querySelector("input")).select().focus();
$(this.element.querySelector(".invite-link-input")).select().focus();
},
});

View File

@ -19,9 +19,19 @@ export default Component.extend({
editing: false,
_updates: null,
@discourseComputed("reviewable.type")
customClass(type) {
return type.dasherize();
@discourseComputed(
"reviewable.type",
"siteSettings.blur_tl0_flagged_posts_media",
"reviewable.target_created_by_trust_level"
)
customClasses(type, blurEnabled, trustLevel) {
let classes = type.dasherize();
if (blurEnabled && trustLevel === 0) {
classes = `${classes} blur-images`;
}
return classes;
},
@discourseComputed(

View File

@ -99,14 +99,18 @@ export default Component.extend(bufferedProperty("model"), {
},
setPermissionsGroups(groupIds) {
let permissions = {};
let updatedPermissions = Object.assign(
{},
this.buffered.get("permissions")
);
this.allGroups.forEach((group) => {
if (groupIds.includes(group.id)) {
permissions[group.name] = PermissionType.FULL;
updatedPermissions[group.name] = PermissionType.FULL;
}
});
this.buffered.set("permissions", permissions);
this.buffered.set("permissions", updatedPermissions);
},
save() {

View File

@ -0,0 +1,4 @@
import Component from "@ember/component";
export default Component.extend({
tagName: "",
});

View File

@ -3,6 +3,7 @@ import MountWidget from "discourse/components/mount-widget";
import Docking from "discourse/mixins/docking";
import { observes } from "discourse-common/utils/decorators";
import optionalService from "discourse/lib/optional-service";
import outletHeights from "discourse/lib/header-outlet-height";
const headerPadding = () => {
let topPadding = parseInt($("#main-outlet").css("padding-top"), 10) + 3;
@ -67,13 +68,16 @@ export default MountWidget.extend(Docking, {
const prev = this.dockAt;
const posTop = headerPadding() + info.offset();
const pos = posTop + timelineHeight;
const pos = posTop + timelineHeight - outletHeights();
this.dockBottom = false;
if (posTop < topicTop) {
this.dockAt = parseInt(topicTop, 10);
} else if (pos > topicBottom + footerHeight) {
this.dockAt = parseInt(topicBottom - timelineHeight + footerHeight, 10);
this.dockAt = parseInt(
topicBottom - timelineHeight + footerHeight + outletHeights(),
10
);
this.dockBottom = true;
if (this.dockAt < 0) {
this.dockAt = 0;

View File

@ -1,16 +1,22 @@
import Component from "@ember/component";
import { propertyEqual } from "discourse/lib/computed";
import { computed } from "@ember/object";
import { actionDescription } from "discourse/widgets/post-small-action";
export default Component.extend({
classNameBindings: [
":user-stream-item",
":item", // DEPRECATED: 'item' class
"item.hidden",
"hidden",
"item.deleted:deleted",
"moderatorAction",
],
hidden: computed("item.hidden", function () {
return (
this.get("item.hidden") && !(this.currentUser && this.currentUser.staff)
);
}),
moderatorAction: propertyEqual(
"item.post_type",
"site.post_types.moderator_action"

View File

@ -1,3 +1,4 @@
import I18n from "I18n";
import { schedule } from "@ember/runloop";
import Component from "@ember/component";
import LoadMore from "discourse/mixins/load-more";
@ -9,6 +10,7 @@ import { popupAjaxError } from "discourse/lib/ajax-error";
import { getOwner } from "discourse-common/lib/get-owner";
import { observes } from "discourse-common/utils/decorators";
import { on } from "@ember/object/evented";
import bootbox from "bootbox";
export default Component.extend(LoadMore, {
_initialize: on("init", function () {
@ -94,13 +96,22 @@ export default Component.extend(LoadMore, {
removeDraft(draft) {
const stream = this.stream;
Draft.clear(draft.draft_key, draft.sequence)
.then(() => {
stream.remove(draft);
})
.catch((error) => {
popupAjaxError(error);
});
bootbox.confirm(
I18n.t("drafts.remove_confirmation"),
I18n.t("no_value"),
I18n.t("yes_value"),
(confirmed) => {
if (confirmed) {
Draft.clear(draft.draft_key, draft.sequence)
.then(() => {
stream.remove(draft);
})
.catch((error) => {
popupAjaxError(error);
});
}
}
);
},
loadMore() {

View File

@ -1,63 +0,0 @@
import discourseComputed from "discourse-common/utils/decorators";
import { isEmpty } from "@ember/utils";
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { cookAsync } from "discourse/lib/text";
export default Controller.extend(ModalFunctionality, {
post: null,
resolve: null,
reject: null,
notice: null,
saving: false,
@discourseComputed("saving", "notice")
disabled(saving, notice) {
return saving || isEmpty(notice);
},
onShow() {
this.setProperties({
notice: "",
saving: false,
});
},
onClose() {
const reject = this.reject;
if (reject) {
reject();
}
},
actions: {
setNotice() {
this.set("saving", true);
const post = this.post;
const resolve = this.resolve;
const reject = this.reject;
const notice = this.notice;
// Let `updatePostField` handle state.
this.setProperties({ resolve: null, reject: null });
post
.updatePostField("notice", notice)
.then(() => cookAsync(notice, { features: { onebox: false } }))
.then((cookedNotice) => {
post.setProperties({
notice_type: "custom",
notice_args: cookedNotice.string,
});
resolve();
this.send("closeModal");
})
.catch(() => {
reject();
this.send("closeModal");
});
},
},
});

View File

@ -0,0 +1,58 @@
import Controller from "@ember/controller";
import { action } from "@ember/object";
import { isEmpty } from "@ember/utils";
import discourseComputed from "discourse-common/utils/decorators";
import { cookAsync } from "discourse/lib/text";
import ModalFunctionality from "discourse/mixins/modal-functionality";
export default Controller.extend(ModalFunctionality, {
post: null,
resolve: null,
reject: null,
notice: null,
saving: false,
@discourseComputed("saving", "notice")
disabled(saving, notice) {
return saving || isEmpty(notice);
},
onShow() {
this.setProperties({ notice: "", saving: false });
},
onClose() {
if (this.reject) {
this.reject();
}
},
@action
setNotice(notice) {
const { resolve, reject } = this;
this.setProperties({ saving: true, resolve: null, reject: null });
this.model
.updatePostField("notice", notice)
.then(() => {
if (notice) {
return cookAsync(notice, { features: { onebox: false } });
}
})
.then((cooked) =>
this.model.set(
"notice",
cooked
? {
type: "custom",
raw: notice,
cooked: cooked.string,
}
: null
)
)
.then(resolve, reject)
.finally(() => this.send("closeModal"));
},
});

View File

@ -28,6 +28,7 @@ import { isTesting } from "discourse-common/config/environment";
import EmberObject, { computed, action } from "@ember/object";
import deprecated from "discourse-common/lib/deprecated";
import bootbox from "bootbox";
import showModal from "discourse/lib/show-modal";
import {
cannotPostAgain,
durationTextFromSeconds,
@ -1097,46 +1098,36 @@ export default Controller.extend({
cancel(this._saveDraftDebounce);
}
const keyPrefix =
this.model.action === "edit" ? "post.abandon_edit" : "post.abandon";
let promise = new Promise((resolve, reject) => {
if (this.get("model.hasMetaData") || this.get("model.replyDirty")) {
bootbox.dialog(I18n.t(keyPrefix + ".confirm"), [
{
label: differentDraft
? I18n.t(keyPrefix + ".no_save_draft")
: I18n.t(keyPrefix + ".no_value"),
callback: () => {
// cancel composer without destroying draft on new draft context
if (differentDraft) {
const controller = showModal("discard-draft", {
model: this.model,
modalClass: "discard-draft-modal",
title: "post.abandon.title",
});
controller.setProperties({
differentDraft,
onDestroyDraft: () => {
this.destroyDraft()
.then(() => {
this.model.clearState();
this.close();
})
.finally(() => {
resolve();
}
});
},
onSaveDraft: () => {
// cancel composer without destroying draft on new draft context
if (differentDraft) {
this.model.clearState();
this.close();
resolve();
}
reject();
},
reject();
},
{
label: I18n.t(keyPrefix + ".yes_value"),
class: "btn-danger",
callback: (result) => {
if (result) {
this.destroyDraft()
.then(() => {
this.model.clearState();
this.close();
})
.finally(() => {
resolve();
});
} else {
resolve();
}
},
},
]);
});
} else {
// it is possible there is some sort of crazy draft with no body ... just give up on it
this.destroyDraft()

View File

@ -0,0 +1,40 @@
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import discourseComputed from "discourse-common/utils/decorators";
export default Controller.extend(ModalFunctionality, {
differentDraft: null,
@discourseComputed()
keyPrefix() {
return this.model.action === "edit" ? "post.abandon_edit" : "post.abandon";
},
@discourseComputed("keyPrefix")
descriptionKey(keyPrefix) {
return `${keyPrefix}.confirm`;
},
@discourseComputed("keyPrefix")
discardKey(keyPrefix) {
return `${keyPrefix}.yes_value`;
},
@discourseComputed("keyPrefix", "differentDraft")
saveKey(keyPrefix, differentDraft) {
return differentDraft
? `${keyPrefix}.no_save_draft`
: `${keyPrefix}.no_value`;
},
actions: {
_destroyDraft() {
this.onDestroyDraft();
this.send("closeModal");
},
_saveDraft() {
this.onSaveDraft();
this.send("closeModal");
},
},
});

View File

@ -1,4 +1,4 @@
import { alias, not } from "@ember/object/computed";
import { alias, not, equal } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller";
import DiscourseURL from "discourse/lib/url";
import Category from "discourse/models/category";
@ -10,6 +10,10 @@ export default Controller.extend({
navigationCategory: controller("navigation/category"),
application: controller(),
router: service(),
viewingCategoriesList: equal(
"router.currentRouteName",
"discovery.categories"
),
loading: false,

View File

@ -5,6 +5,8 @@ import bootbox from "bootbox";
import { extractError } from "discourse/lib/ajax-error";
import DiscourseURL from "discourse/lib/url";
import { readOnly } from "@ember/object/computed";
import PermissionType from "discourse/models/permission-type";
import { NotificationLevels } from "discourse/lib/notification-levels";
export default Controller.extend({
selectedTab: "general",
@ -93,8 +95,11 @@ export default Controller.extend({
model.setProperties({
slug: result.category.slug,
id: result.category.id,
createdCategory: true,
can_edit: result.category.can_edit,
permission: PermissionType.FULL,
notification_level: NotificationLevels.REGULAR,
});
this.site.updateCategory(model);
}
})
.catch((error) => {
@ -111,15 +116,17 @@ export default Controller.extend({
I18n.t("yes_value"),
(result) => {
if (result) {
this.model.destroy().then(
() => {
this.model
.destroy()
.then(() => {
this.transitionToRoute("discovery.categories");
},
() => {
})
.catch(() => {
this.displayErrors([I18n.t("category.delete_error")]);
})
.finally(() => {
this.set("deleting", false);
}
);
});
} else {
this.set("deleting", false);
}
@ -132,15 +139,7 @@ export default Controller.extend({
},
goBack() {
if (this.model.createdCategory) {
DiscourseURL.redirectTo(this.model.url);
} else {
DiscourseURL.routeTo(this.model.url);
}
},
toggleMenu() {
this.toggleProperty("expandedMenu");
DiscourseURL.routeTo(this.model.url);
},
},
});

View File

@ -9,7 +9,6 @@ export default Controller.extend({
this.saveAttrNames = [
"muted_usernames",
"ignored_usernames",
"new_topic_duration_minutes",
"auto_track_topics_after_msecs",
"notification_level_when_replying",

View File

@ -43,7 +43,6 @@ export default Controller.extend({
this.saveAttrNames = [
"muted_usernames",
"ignored_usernames",
"allowed_pm_usernames",
"enable_allowed_pm_users",
];

View File

@ -26,7 +26,7 @@ export default Controller.extend(ModalFunctionality, BufferedContent, {
this.send("closeModal");
if (result.responseJson.tag) {
this.transitionToRoute("tags.show", result.responseJson.tag.id);
this.transitionToRoute("tag.show", result.responseJson.tag.id);
} else {
this.flash(extractError(result.responseJson.errors[0]), "error");
}

View File

@ -1,6 +1,8 @@
import Controller from "@ember/controller";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n";
export default Controller.extend({
saving: false,
@ -9,7 +11,7 @@ export default Controller.extend({
actions: {
save() {
let priorities = {};
this.get("settings.reviewable_score_types").forEach((st) => {
this.scoreTypes.forEach((st) => {
priorities[st.id] = parseFloat(st.reviewable_priority);
});
@ -25,4 +27,15 @@ export default Controller.extend({
.finally(() => this.set("saving", false));
},
},
@discourseComputed("settings.reviewable_score_types")
scoreTypes(types) {
const username = I18n.t("review.example_username");
return types.map((type) =>
Object.assign({}, type, {
title: type.title.replace("%{username}", username),
})
);
},
});

View File

@ -28,8 +28,6 @@ export default Controller.extend(BulkTopicSelection, FilterModeMixin, {
q: null,
showInfo: false,
categories: alias("site.categoriesList"),
@discourseComputed("list", "list.draft")
createTopicLabel(list, listDraft) {
return listDraft ? "topic.open_draft" : "topic.create";
@ -73,11 +71,6 @@ export default Controller.extend(BulkTopicSelection, FilterModeMixin, {
return this.siteSettings.show_filter_by_tag;
},
@discourseComputed("additionalTags", "category", "tag.id")
showToggleInfo(additionalTags, category, tagId) {
return !additionalTags && !category && tagId !== "none";
},
loadMoreTopics() {
return this.list.loadMore();
},

View File

@ -3,7 +3,6 @@ import { empty, alias } from "@ember/object/computed";
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import Topic from "discourse/models/topic";
import Category from "discourse/models/category";
import bootbox from "bootbox";
import { Promise } from "rsvp";
@ -241,11 +240,10 @@ export default Controller.extend(ModalFunctionality, {
changeCategory() {
const categoryId = parseInt(this.newCategoryId, 10) || 0;
const category = Category.findById(categoryId);
this.perform({ type: "change_category", category_id: categoryId }).then(
(topics) => {
topics.forEach((t) => t.set("category", category));
topics.forEach((t) => t.set("category_id", categoryId));
(this.refreshClosure || identity)();
this.send("closeModal");
}

View File

@ -857,22 +857,17 @@ export default Controller.extend(bufferedProperty("model"), {
this.send("showGrantBadgeModal");
},
addNotice(post) {
changeNotice(post) {
return new Promise(function (resolve, reject) {
const modal = showModal("add-post-notice");
modal.setProperties({ post, resolve, reject });
const modal = showModal("change-post-notice", { model: post });
modal.setProperties({
resolve,
reject,
notice: post.notice ? post.notice.raw : "",
});
});
},
removeNotice(post) {
return post.updatePostField("notice", null).then(() =>
post.setProperties({
notice_type: null,
notice_args: null,
})
);
},
toggleParticipant(user) {
this.get("model.postStream")
.toggleParticipant(user.get("username"))
@ -1424,6 +1419,12 @@ export default Controller.extend(bufferedProperty("model"), {
.then(() => refresh({ id: data.id }));
break;
}
case "destroyed": {
postStream
.triggerDestroyedPost(data.id)
.then(() => refresh({ id: data.id }));
break;
}
case "recovered": {
postStream
.triggerRecoveredPost(data.id)

View File

@ -9,6 +9,6 @@ export default Controller.extend({
init() {
this._super(...arguments);
this.badgeSortOrder = ["badge.badge_type.sort_order", "badge.name"];
this.badgeSortOrder = ["badge.badge_type.sort_order:desc", "badge.name"];
},
});

View File

@ -36,8 +36,11 @@ export default {
return;
}
// Also use Universal Analytics if it is present
if (typeof window.ga !== "undefined") {
// Use Universal Analytics v3 if it is present
if (
typeof window.ga !== "undefined" &&
typeof window.gtag === "undefined"
) {
appEvents.on("page:changed", (data) => {
if (!data.replacedOnlyQueryParams) {
window.ga("send", "pageview", { page: data.url, title: data.title });
@ -45,7 +48,19 @@ export default {
});
}
// And Google Tag Manager too
// And Universal Analytics v4 if we're upgraded
if (typeof window.gtag !== "undefined") {
appEvents.on("page:changed", (data) => {
if (!data.replacedOnlyQueryParams) {
window.gtag("event", "page_view", {
page_location: data.url,
page_title: data.title,
});
}
});
}
// Google Tag Manager too
if (typeof window.dataLayer !== "undefined") {
appEvents.on("page:changed", (data) => {
if (!data.replacedOnlyQueryParams) {

View File

@ -16,7 +16,7 @@ export default {
},
updateAppBackground() {
later(() => {
const header = document.querySelectorAll(".d-header")[0];
const header = document.querySelector(".d-header-wrap .d-header");
if (header) {
const styles = window.getComputedStyle(header);
postRNWebviewMessage("headerBg", styles.backgroundColor);

View File

@ -0,0 +1,22 @@
export default function () {
const outletSelector = [
".above-site-header-outlet",
".below-site-header-outlet",
];
// If these outlets have height they impact timeline and usercard positioning
let outletHeights = 0;
outletSelector.forEach(function (outletClass) {
if (document.querySelector(outletClass)) {
let outlets = document.querySelectorAll(outletClass);
outlets.forEach((outlet) => {
if (outlet.offsetHeight) {
outletHeights += parseInt(outlet.offsetHeight, 10);
}
});
}
});
return outletHeights;
}

View File

@ -118,6 +118,8 @@ export default {
},
teardown() {
this.keyTrapper.reset();
this.keyTrapper = null;
this.container = null;
},

View File

@ -1,30 +1,16 @@
import getURL from "discourse-common/lib/get-url";
import { isEmpty } from "@ember/utils";
import { findAll } from "discourse/models/login-method";
import { helperContext } from "discourse-common/lib/helpers";
export default function logout() {
export default function logout({ redirect } = {}) {
const ctx = helperContext();
let siteSettings = ctx.siteSettings;
let keyValueStore = ctx.keyValueStore;
keyValueStore.abandonLocal();
const redirect = siteSettings.logout_redirect;
if (!isEmpty(redirect)) {
window.location.href = redirect;
return;
}
const sso = siteSettings.enable_sso;
const oneAuthenticator =
!siteSettings.enable_local_logins && findAll().length === 1;
if (siteSettings.login_required && (sso || oneAuthenticator)) {
// In this situation visiting most URLs will start the auth process again
// Go to the `/login` page to avoid an immediate redirect
window.location.href = getURL("/login");
return;
}
window.location.href = getURL("/");
}

View File

@ -7,7 +7,14 @@ export function minimumOffset() {
iPadNav = document.querySelector(".footer-nav-ipad .footer-nav"),
iPadNavHeight = iPadNav ? iPadNav.offsetHeight : 0;
return header ? header.offsetHeight + iPadNavHeight : 0;
// if the header has a positive offset from the top of the window, we need to include the offset
// this covers cases where a site has a custom header above d-header (covers fixed and unfixed)
const headerWrap = document.querySelector(".d-header-wrap"),
headerWrapOffset = headerWrap.getBoundingClientRect();
return header
? header.offsetHeight + headerWrapOffset.top + iPadNavHeight
: 0;
}
export default function offsetCalculator() {

View File

@ -654,6 +654,7 @@ function trimUnwanted(html) {
const body = html.match(/<body[^>]*>([\s\S]*?)<\/body>/);
html = body ? body[1] : html;
html = html.replace(/\r|\n|&nbsp;/g, " ");
html = html.replace(/\u00A0/g, " "); // trim no-break space
let match;
while ((match = html.match(/<[^\s>]+[^>]*>\s{2,}<[^\s>]+[^>]*>/))) {

View File

@ -140,12 +140,10 @@ export default function transformPost(
postAtts.topicUrl = topic.get("url");
postAtts.isSaving = post.isSaving;
if (post.notice_type) {
postAtts.noticeType = post.notice_type;
if (postAtts.noticeType === "custom") {
postAtts.noticeMessage = post.notice_args;
} else if (postAtts.noticeType === "returning_user") {
postAtts.noticeTime = new Date(post.notice_args);
if (post.notice) {
postAtts.notice = post.notice;
if (postAtts.notice.type === "returning_user") {
postAtts.notice.lastPostedAt = new Date(post.notice.last_posted_at);
}
}

View File

@ -0,0 +1,67 @@
// This file's code is based on Favcount by Chris Hunt, Copyright 2013 Chris Hunt, MIT License
function renderIcon(canvas, img, count) {
count = Math.round(count);
if (isNaN(count) || count < 1) {
count = "";
} else if (count < 10) {
count = " " + count;
} else if (count > 99) {
count = "99";
}
// Scale canvas elements based on favicon size
let multiplier = img.width / 16;
let fontSize = multiplier * 11;
let xOffset = multiplier;
let shadow = multiplier * 2;
canvas.height = canvas.width = img.width;
let ctx = canvas.getContext("2d");
ctx.font = `bold ${fontSize}px Helvetica, Arial, sans-serif`;
if (count) {
ctx.globalAlpha = 0.4;
}
ctx.drawImage(img, 0, 0);
ctx.globalAlpha = 1.0;
// Draw white drop shadow
ctx.shadowColor = "#FFF";
ctx.shadowBlur = shadow;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
// Draw white border
ctx.fillStyle = "#FFF";
ctx.fillText(count, xOffset, fontSize);
ctx.fillText(count, xOffset + multiplier, fontSize);
ctx.fillText(count, xOffset, fontSize + multiplier);
ctx.fillText(count, xOffset + multiplier, fontSize + multiplier);
// Draw black count
ctx.fillStyle = "#000";
ctx.fillText(count, xOffset + multiplier / 2.0, fontSize + multiplier / 2.0);
// Replace favicon with new favicon
let newFavicon = document.createElement("link");
newFavicon.rel = "icon";
newFavicon.href = canvas.toDataURL("image/png");
let favicon = document.querySelector("link[rel=icon]");
let head = document.querySelector("head");
if (favicon) {
head.removeChild(favicon);
}
head.appendChild(newFavicon);
}
export default function tabCount(url, count) {
let canvas = document.createElement("canvas");
if (canvas.getContext) {
let img = document.createElement("img");
img.crossOrigin = "anonymous";
img.onload = () => renderIcon(canvas, img, count);
img.src = url;
}
}

View File

@ -5,6 +5,7 @@ import afterTransition from "discourse/lib/after-transition";
import DiscourseURL from "discourse/lib/url";
import Mixin from "@ember/object/mixin";
import { escapeExpression } from "discourse/lib/utilities";
import headerOutletHeights from "discourse/lib/header-outlet-height";
import { inject as service } from "@ember/service";
export default Mixin.create({
@ -211,6 +212,10 @@ export default Mixin.create({
}
}
position.top -= this._calculateTopOffset(
$("#main-outlet").offset(),
headerOutletHeights()
);
if (isFixed) {
position.top -= $("html").scrollTop();
//if content is fixed and will be cut off on the bottom, display it above...
@ -259,6 +264,13 @@ export default Mixin.create({
});
},
// some plugins/themes modify the page layout and may
// need to override this calculation for the card to
// position correctly
_calculateTopOffset(mainOutletOffset, outletHeights) {
return mainOutletOffset.top - outletHeights;
},
_hide() {
if (!this.visible) {
$(this.element).css({ left: -9999, top: -9999 });

View File

@ -43,11 +43,7 @@ CategoryList.reopenClass({
}
if (c.topics) {
c.topics = c.topics.map((t) => {
const topic = Topic.create(t);
topic.set("category", c);
return topic;
});
c.topics = c.topics.map((t) => Topic.create(t));
}
switch (statPeriod) {

View File

@ -1127,26 +1127,22 @@ const Composer = RestModel.extend({
if (this.canEditTitle) {
// Save title and/or post body
if (!this.title && !this.reply) {
if (isEmpty(this.title) && isEmpty(this.reply)) {
return Promise.resolve();
}
if (
this.title &&
this.titleLengthValid &&
this.reply &&
this.replyLength < this.siteSettings.min_post_length
) {
// Do not save when both title and reply's length are too small
if (!this.titleLengthValid && this.replyLength < this.minimumPostLength) {
return Promise.resolve();
}
} else {
// Do not save when there is no reply
if (!this.reply) {
if (isEmpty(this.reply)) {
return Promise.resolve();
}
// Do not save when the reply's length is too small
if (this.replyLength < this.siteSettings.min_post_length) {
if (this.replyLength < this.minimumPostLength) {
return Promise.resolve();
}
}

View File

@ -48,7 +48,7 @@ const NavItem = EmberObject.extend({
}, this);
if (customHref) {
return customHref;
return getURL(customHref);
}
const context = { category, noSubcategories, tagId };
@ -122,7 +122,12 @@ NavItem.reopenClass({
if (context.tagId && Site.currentProp("filters").includes(filterType)) {
includesTagContext = true;
path += "/tags";
if (context.category) {
path += "/tags";
} else {
path += "/tag";
}
}
if (context.category) {
@ -245,6 +250,10 @@ NavItem.reopenClass({
item.init(item, category, args);
}
if (item.href) {
item.href = getURL(item.href);
}
const before = item.before;
if (before) {
let i = 0;

View File

@ -737,6 +737,12 @@ export default RestModel.extend({
return Promise.resolve();
},
triggerDestroyedPost(postId) {
const existing = this._identityMap[postId];
this.removePosts([existing]);
return Promise.resolve();
},
triggerChangedPost(postId, updatedAt, opts) {
opts = opts || {};

View File

@ -5,7 +5,6 @@ import { ajax } from "discourse/lib/ajax";
import RestModel from "discourse/models/rest";
import { getOwner } from "discourse-common/lib/get-owner";
import { Promise } from "rsvp";
import Category from "discourse/models/category";
import Session from "discourse/models/session";
import { isEmpty } from "@ember/utils";
import User from "discourse/models/user";
@ -156,12 +155,10 @@ TopicList.reopenClass({
// Stitch together our side loaded data
const categories = Category.list(),
users = extractByKey(result.users, User),
groups = extractByKey(result.primary_groups, EmberObject);
const users = extractByKey(result.users, User);
const groups = extractByKey(result.primary_groups, EmberObject);
return result.topic_list[listKey].map((t) => {
t.category = categories.findBy("id", t.category_id);
t.posters.forEach((p) => {
p.user = users[p.user_id];
p.extraClasses = p.extras;

View File

@ -6,6 +6,7 @@ import PreloadStore from "discourse/lib/preload-store";
import Category from "discourse/models/category";
import User from "discourse/models/user";
import { deepEqual } from "discourse-common/lib/object";
import DiscourseURL from "discourse/lib/url";
function isNew(topic) {
return (
@ -148,6 +149,17 @@ const TopicTrackingState = EmberObject.extend({
}
tracker.incrementMessageCount();
});
this.messageBus.subscribe("/destroy", (msg) => {
tracker.incrementMessageCount();
const currentRoute = DiscourseURL.router.currentRoute.parent;
if (
currentRoute.name === "topic" &&
parseInt(currentRoute.params.id, 10) === msg.topic_id
) {
DiscourseURL.redirectTo("/");
}
});
},
mutedTopics() {

View File

@ -13,10 +13,7 @@ import { emojiUnescape } from "discourse/lib/text";
import PreloadStore from "discourse/lib/preload-store";
import { userPath } from "discourse/lib/url";
import { fancyTitle } from "discourse/lib/topic-fancy-title";
import discourseComputed, {
observes,
on,
} from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import Category from "discourse/models/category";
import Session from "discourse/models/session";
import { Promise } from "rsvp";
@ -227,10 +224,9 @@ const Topic = RestModel.extend({
return { type: "topic", id };
},
@on("init")
@observes("category_id")
_categoryIdChanged() {
this.set("category", Category.findById(this.category_id));
@discourseComputed("category_id")
category(categoryId) {
return Category.findById(categoryId);
},
categoryClass: fmt("category.fullSlug", "category-%@"),

View File

@ -13,7 +13,7 @@ import UserStream from "discourse/models/user-stream";
import UserPostsStream from "discourse/models/user-posts-stream";
import Singleton from "discourse/mixins/singleton";
import { longDate } from "discourse/lib/formatter";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
import Badge from "discourse/models/badge";
import UserBadge from "discourse/models/user-badge";
import UserActionStat from "discourse/models/user-action-stat";
@ -729,41 +729,29 @@ const User = RestModel.extend({
});
},
@observes("muted_category_ids")
updateMutedCategories() {
this.set("mutedCategories", Category.findByIds(this.muted_category_ids));
@discourseComputed("muted_category_ids")
mutedCategories(mutedCategoryIds) {
return Category.findByIds(mutedCategoryIds);
},
@observes("regular_category_ids")
updateRegularCategories() {
this.set(
"regularCategories",
Category.findByIds(this.regular_category_ids)
);
@discourseComputed("regular_category_ids")
regularCategories(regularCategoryIds) {
return Category.findByIds(regularCategoryIds);
},
@observes("tracked_category_ids")
updateTrackedCategories() {
this.set(
"trackedCategories",
Category.findByIds(this.tracked_category_ids)
);
@discourseComputed("tracked_category_ids")
trackedCategories(trackedCategoryIds) {
return Category.findByIds(trackedCategoryIds);
},
@observes("watched_category_ids")
updateWatchedCategories() {
this.set(
"watchedCategories",
Category.findByIds(this.watched_category_ids)
);
@discourseComputed("watched_category_ids")
watchedCategories(watchedCategoryIds) {
return Category.findByIds(watchedCategoryIds);
},
@observes("watched_first_post_category_ids")
updateWatchedFirstPostCategories() {
this.set(
"watchedFirstPostCategories",
Category.findByIds(this.watched_first_post_category_ids)
);
@discourseComputed("watched_first_post_category_ids")
watchedFirstPostCategories(wachedFirstPostCategoryIds) {
return Category.findByIds(wachedFirstPostCategoryIds);
},
@discourseComputed("can_delete_account")

View File

@ -1,7 +1,7 @@
import buildCategoryRoute from "discourse/routes/build-category-route";
import buildTopicRoute from "discourse/routes/build-topic-route";
import DiscoverySortableController from "discourse/controllers/discovery-sortable";
import TagsShowRoute from "discourse/routes/tags-show";
import TagShowRoute from "discourse/routes/tag-show";
import Site from "discourse/models/site";
import User from "discourse/models/user";
@ -11,20 +11,16 @@ export default {
initialize(registry, app) {
app.DiscoveryCategoryController = DiscoverySortableController.extend();
app.DiscoveryParentCategoryController = DiscoverySortableController.extend();
app.DiscoveryCategoryNoneController = DiscoverySortableController.extend();
app.DiscoveryCategoryAllController = DiscoverySortableController.extend();
app.DiscoveryCategoryWithIDController = DiscoverySortableController.extend();
app.DiscoveryCategoryRoute = buildCategoryRoute("default");
app.DiscoveryParentCategoryRoute = buildCategoryRoute("default");
app.DiscoveryCategoryNoneRoute = buildCategoryRoute("default", {
no_subcategories: true,
});
app.DiscoveryCategoryAllRoute = buildCategoryRoute("default", {
no_subcategories: false,
});
app.DiscoveryCategoryWithIDRoute = buildCategoryRoute("default");
const site = Site.current();
site.get("filters").forEach((filter) => {
@ -35,9 +31,6 @@ export default {
app[
`Discovery${filterCapitalized}CategoryController`
] = DiscoverySortableController.extend();
app[
`Discovery${filterCapitalized}ParentCategoryController`
] = DiscoverySortableController.extend();
app[
`Discovery${filterCapitalized}CategoryNoneController`
] = DiscoverySortableController.extend();
@ -48,9 +41,6 @@ export default {
app[`Discovery${filterCapitalized}CategoryRoute`] = buildCategoryRoute(
filter
);
app[
`Discovery${filterCapitalized}ParentCategoryRoute`
] = buildCategoryRoute(filter);
app[
`Discovery${filterCapitalized}CategoryNoneRoute`
] = buildCategoryRoute(filter, { no_subcategories: true });
@ -74,9 +64,6 @@ export default {
app[
`DiscoveryTop${periodCapitalized}CategoryController`
] = DiscoverySortableController.extend();
app[
`DiscoveryTop${periodCapitalized}ParentCategoryController`
] = DiscoverySortableController.extend();
app[
`DiscoveryTop${periodCapitalized}CategoryNoneController`
] = DiscoverySortableController.extend();
@ -86,38 +73,26 @@ export default {
app[`DiscoveryTop${periodCapitalized}CategoryRoute`] = buildCategoryRoute(
"top/" + period
);
app[
`DiscoveryTop${periodCapitalized}ParentCategoryRoute`
] = buildCategoryRoute("top/" + period);
app[
`DiscoveryTop${periodCapitalized}CategoryNoneRoute`
] = buildCategoryRoute("top/" + period, { no_subcategories: true });
});
app["TagsShowCategoryRoute"] = TagsShowRoute.extend();
app["TagsShowCategoryNoneRoute"] = TagsShowRoute.extend({
app["TagsShowCategoryRoute"] = TagShowRoute.extend();
app["TagsShowCategoryNoneRoute"] = TagShowRoute.extend({
noSubcategories: true,
});
app["TagsShowParentCategoryRoute"] = TagsShowRoute.extend();
app["TagShowRoute"] = TagsShowRoute;
site.get("filters").forEach(function (filter) {
app["TagsShow" + filter.capitalize() + "Route"] = TagsShowRoute.extend({
navMode: filter,
});
app["TagShow" + filter.capitalize() + "Route"] = TagsShowRoute.extend({
app["TagShow" + filter.capitalize() + "Route"] = TagShowRoute.extend({
navMode: filter,
});
app[
"TagsShowCategory" + filter.capitalize() + "Route"
] = TagsShowRoute.extend({ navMode: filter });
] = TagShowRoute.extend({ navMode: filter });
app[
"TagsShowNoneCategory" + filter.capitalize() + "Route"
] = TagsShowRoute.extend({ navMode: filter, noSubcategories: true });
app[
"TagsShowParentCategory" + filter.capitalize() + "Route"
] = TagsShowRoute.extend({ navMode: filter });
] = TagShowRoute.extend({ navMode: filter, noSubcategories: true });
});
},
};

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