Version bump

This commit is contained in:
Neil Lalonde 2019-09-04 11:18:06 -04:00
commit 33b93124d6
961 changed files with 28625 additions and 10041 deletions

5
.gitignore vendored
View File

@ -46,7 +46,7 @@ bootsnap-compile-cache/
# Ignore plugins except for the bundled ones.
/plugins/*
!/plugins/lazyYT/
!/plugins/lazy-yt/
!/plugins/poll/
!/plugins/discourse-details/
!/plugins/discourse-nginx-performance-report
@ -127,3 +127,6 @@ vendor/bundle/*
# Vagrant
.vagrant
# ignore auto-generated plugin js assets
/app/assets/javascripts/plugins/*

View File

@ -7,7 +7,7 @@ AllCops:
- 'vendor/**/*'
- 'node_modules/**/*'
- 'public/**/*'
- 'plugins/**/*'
- 'plugins/**/gems/**/*'
# Prefer &&/|| over and/or.
Style/AndOr:

View File

@ -60,7 +60,7 @@ before_install:
- git clone --depth=1 https://github.com/discourse/discourse-chat-integration.git plugins/discourse-chat-integration
- git clone --depth=1 https://github.com/discourse/discourse-assign.git plugins/discourse-assign
- git clone --depth=1 https://github.com/discourse/discourse-patreon.git plugins/discourse-patreon
- git clone --depth=1 https://github.com/discourse/discourse-staff-notes.git plugins/discourse-staff-notes
- git clone --depth=1 https://github.com/discourse/discourse-user-notes.git plugins/discourse-user-notes
- git clone --depth=1 https://github.com/discourse/discourse-group-tracker
- export PATH=$HOME/.yarn/bin:$PATH

View File

@ -46,12 +46,12 @@ gem 'redis-namespace'
gem 'active_model_serializers', '~> 0.8.3'
gem 'onebox', '1.9.2'
gem 'onebox', '1.9.12'
gem 'http_accept_language', '~>2.0.5', require: false
gem 'ember-rails', '0.18.5'
gem 'discourse-ember-source', '~> 3.8.0'
gem 'discourse-ember-source', '~> 3.10.0'
gem 'ember-handlebars-template', '0.8.0'
gem 'barber'
@ -74,10 +74,12 @@ gem 'unf', require: false
gem 'email_reply_trimmer', '~> 0.1'
# Forked until https://github.com/toy/image_optim/pull/162 is merged
# https://github.com/discourse/image_optim
gem 'discourse_image_optim', require: 'image_optim'
gem 'multi_json'
gem 'mustache'
gem 'nokogiri'
gem 'css_parser', require: false
gem 'omniauth'
gem 'omniauth-openid'
@ -203,9 +205,12 @@ gem "sassc-rails"
gem 'rotp'
gem 'rqrcode'
gem 'rubyzip', require: false
gem 'sshkey', require: false
gem 'rchardet', require: false
gem 'lz4-ruby', require: false, platform: :mri
if ENV["IMPORT"] == "1"
gem 'mysql2'

View File

@ -88,10 +88,12 @@ GEM
crack (0.4.3)
safe_yaml (~> 1.0.0)
crass (1.0.4)
css_parser (1.7.0)
addressable
debug_inspector (0.0.3)
diff-lcs (1.3)
diffy (3.3.0)
discourse-ember-source (3.8.0.1)
discourse-ember-source (3.10.0.1)
discourse_image_optim (0.26.2)
exifr (~> 1.2, >= 1.2.2)
fspath (~> 3.0)
@ -172,11 +174,12 @@ GEM
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
lru_redux (1.1.0)
lz4-ruby (0.3.3)
mail (2.7.1)
mini_mime (>= 0.1.1)
maxminddb (0.1.22)
memory_profiler (0.9.13)
message_bus (2.2.0)
message_bus (2.2.2)
rack (>= 1.1.3)
metaclass (0.0.4)
method_source (0.9.2)
@ -184,7 +187,7 @@ GEM
mini_portile2 (2.4.0)
mini_racer (0.2.6)
libv8 (>= 6.9.411)
mini_scheduler (0.11.0)
mini_scheduler (0.12.1)
sidekiq
mini_sql (0.2.2)
mini_suffix (0.3.0)
@ -199,7 +202,7 @@ GEM
multi_xml (0.6.0)
multipart-post (2.1.1)
mustache (1.1.0)
nokogiri (1.10.3)
nokogiri (1.10.4)
mini_portile2 (~> 2.4.0)
nokogumbo (2.0.1)
nokogiri (~> 1.8, >= 1.8.4)
@ -238,7 +241,7 @@ GEM
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
rack
onebox (1.9.2)
onebox (1.9.12)
htmlentities (~> 4.3)
moneta (~> 1.0)
multi_json (~> 1.11)
@ -272,7 +275,7 @@ GEM
rack-openid (1.3.1)
rack (>= 1.1.0)
ruby-openid (>= 2.1.8)
rack-protection (2.0.5)
rack-protection (2.0.7)
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
@ -351,6 +354,7 @@ GEM
guess_html_encoding (>= 0.0.4)
nokogiri (>= 1.6.0)
ruby_dep (1.5.0)
rubyzip (1.2.3)
safe_yaml (1.0.5)
sanitize (5.0.0)
crass (~> 1.0.2)
@ -437,8 +441,9 @@ DEPENDENCIES
certified
colored2
cppjieba_rb
css_parser
diffy
discourse-ember-source (~> 3.8.0)
discourse-ember-source (~> 3.10.0)
discourse_image_optim
email_reply_trimmer (~> 0.1)
ember-handlebars-template (= 0.8.0)
@ -463,6 +468,7 @@ DEPENDENCIES
logstash-logger
logster
lru_redux
lz4-ruby
mail
maxminddb
memory_profiler
@ -487,7 +493,7 @@ DEPENDENCIES
omniauth-oauth2
omniauth-openid
omniauth-twitter
onebox (= 1.9.2)
onebox (= 1.9.12)
openid-redis-store
parallel_tests
pg
@ -516,6 +522,7 @@ DEPENDENCIES
rubocop
ruby-prof
ruby-readability
rubyzip
sanitize
sassc
sassc-rails

View File

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

View File

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

View File

@ -75,7 +75,7 @@ export default Ember.Component.extend({
if (!this.element || this.isDestroying || this.isDestroyed) {
return;
}
const editor = loadedAce.edit(this.$(".ace")[0]);
const editor = loadedAce.edit(this.element.querySelector(".ace"));
editor.setTheme("ace/theme/chrome");
editor.setShowPrintMargin(false);
@ -89,7 +89,7 @@ export default Ember.Component.extend({
editor.$blockScrolling = Infinity;
editor.renderer.setScrollMargin(10, 10);
this.$().data("editor", editor);
this.element.setAttribute("data-editor", editor);
this._editor = editor;
this.changeDisabledState();

View File

@ -18,8 +18,8 @@ export default Ember.Component.extend(
},
_scrollDown() {
const $div = this.$()[0];
$div.scrollTop = $div.scrollHeight;
const div = this.element;
div.scrollTop = div.scrollHeight;
},
@on("init")

View File

@ -5,7 +5,7 @@ export default Ember.Component.extend({
type: "line",
refreshChart() {
const ctx = this.$()[0].getContext("2d");
const ctx = this.element.getContext("2d");
const model = this.model;
const rawData = this.get("model.data");

View File

@ -35,14 +35,17 @@ export default Ember.Component.extend({
_scheduleChartRendering() {
Ember.run.schedule("afterRender", () => {
this._renderChart(this.model, this.$(".chart-canvas"));
this._renderChart(
this.model,
this.element && this.element.querySelector(".chart-canvas")
);
});
},
_renderChart(model, $chartCanvas) {
if (!$chartCanvas || !$chartCanvas.length) return;
_renderChart(model, chartCanvas) {
if (!chartCanvas) return;
const context = $chartCanvas[0].getContext("2d");
const context = chartCanvas.getContext("2d");
const chartData = Ember.makeArray(
model.get("chartData") || model.get("data")
);

View File

@ -33,14 +33,17 @@ export default Ember.Component.extend({
_scheduleChartRendering() {
Ember.run.schedule("afterRender", () => {
this._renderChart(this.model, this.$(".chart-canvas"));
this._renderChart(
this.model,
this.element.querySelector(".chart-canvas")
);
});
},
_renderChart(model, $chartCanvas) {
if (!$chartCanvas || !$chartCanvas.length) return;
_renderChart(model, chartCanvas) {
if (!chartCanvas) return;
const context = $chartCanvas[0].getContext("2d");
const context = chartCanvas.getContext("2d");
const chartData = Ember.makeArray(
model.get("chartData") || model.get("data")

View File

@ -183,6 +183,32 @@ export default Ember.Component.extend({
},
actions: {
onChangeEndDate(date) {
const startDate = moment(this.startDate);
const newEndDate = moment(date).endOf("day");
if (newEndDate.isSameOrAfter(startDate)) {
this.set("endDate", newEndDate.format("YYYY-MM-DD"));
} else {
this.set("endDate", startDate.endOf("day").format("YYYY-MM-DD"));
}
this.send("refreshReport");
},
onChangeStartDate(date) {
const endDate = moment(this.endDate);
const newStartDate = moment(date).startOf("day");
if (newStartDate.isSameOrBefore(endDate)) {
this.set("startDate", newStartDate.format("YYYY-MM-DD"));
} else {
this.set("startDate", endDate.startOf("day").format("YYYY-MM-DD"));
}
this.send("refreshReport");
},
applyFilter(id, value) {
let customFilters = this.get("filters.customFilters") || {};

View File

@ -5,7 +5,7 @@ import { bufferedRender } from "discourse-common/lib/buffered-render";
export default Ember.Component.extend(
bufferedRender({
classes: ["text-muted", "text-danger", "text-successful", "text-muted"],
icons: ["circle-o", "times-circle", "circle", "circle"],
icons: ["far-circle", "times-circle", "circle", "circle"],
@computed("deliveryStatuses", "model.last_delivery_status")
status(deliveryStatuses, lastDeliveryStatus) {

View File

@ -11,10 +11,10 @@ export default Ember.Component.extend({
classNames: ["color-picker"],
hexValueChanged: function() {
var hex = this.hexValue;
let $text = this.$("input.hex-input");
let text = this.element.querySelector("input.hex-input");
if (this.valid) {
$text.attr(
text.setAttribute(
"style",
"color: " +
(this.brightnessValue > 125 ? "black" : "white") +
@ -24,10 +24,12 @@ export default Ember.Component.extend({
);
if (this.pickerLoaded) {
this.$(".picker").spectrum({ color: "#" + this.hexValue });
$(this.element.querySelector(".picker")).spectrum({
color: "#" + this.hexValue
});
}
} else {
$text.attr("style", "");
text.setAttribute("style", "");
}
}.observes("hexValue", "brightnessValue", "valid"),
@ -35,7 +37,7 @@ export default Ember.Component.extend({
loadScript("/javascripts/spectrum.js").then(() => {
loadCSS("/javascripts/spectrum.css").then(() => {
Ember.run.schedule("afterRender", () => {
this.$(".picker")
$(this.element.querySelector(".picker"))
.spectrum({ color: "#" + this.hexValue })
.on("change.spectrum", (me, color) => {
this.set("hexValue", color.toHexString().replace("#", ""));

View File

@ -0,0 +1,45 @@
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend({
editorId: Ember.computed.reads("fieldName"),
@computed("fieldName", "styles.html", "styles.css")
resetDisabled(fieldName) {
return (
this.get(`styles.${fieldName}`) ===
this.get(`styles.default_${fieldName}`)
);
},
@computed("styles", "fieldName")
editorContents: {
get(styles, fieldName) {
return styles[fieldName];
},
set(value, styles, fieldName) {
styles.setField(fieldName, value);
return value;
}
},
actions: {
reset() {
bootbox.confirm(
I18n.t("admin.customize.email_style.reset_confirm", {
fieldName: I18n.t(`admin.customize.email_style.${this.fieldName}`)
}),
I18n.t("no_value"),
I18n.t("yes_value"),
result => {
if (result) {
this.styles.setField(
this.fieldName,
this.styles.get(`default_${this.fieldName}`)
);
this.notifyPropertyChange("editorContents");
}
}
);
}
}
});

View File

@ -14,7 +14,7 @@ export default Ember.Component.extend(bufferedProperty("host"), {
@observes("editing")
_focusOnInput() {
Ember.run.schedule("afterRender", () => {
this.$(".host-name").focus();
this.element.querySelector(".host-name").focus();
});
},

View File

@ -5,6 +5,6 @@ export default Ember.Component.extend({
@on("didInsertElement")
@observes("code")
_refresh: function() {
highlightSyntax(this.$());
highlightSyntax($(this.element));
}
});

View File

@ -23,10 +23,10 @@ export default Ember.Component.extend({
// If we switch to edit mode, jump to the edit textarea
if (postAction === "edit") {
Ember.run.scheduleOnce("afterRender", () => {
let $elem = this.$();
let body = $elem.closest(".modal-body");
let elem = this.element;
let body = elem.closest(".modal-body");
body.scrollTop(body.height());
$elem.find(".post-editor").focus();
elem.querySelector(".post-editor").focus();
});
}
}

View File

@ -19,7 +19,9 @@ export default Ember.Component.extend({
},
focusPermalink() {
Ember.run.schedule("afterRender", () => this.$(".permalink-url").focus());
Ember.run.schedule("afterRender", () =>
this.element.querySelector(".permalink-url").focus()
);
},
actions: {
@ -67,7 +69,7 @@ export default Ember.Component.extend({
this._super(...arguments);
Ember.run.schedule("afterRender", () => {
this.$(".external-url").keydown(e => {
$(this.element.querySelector(".external-url")).keydown(e => {
// enter key
if (e.keyCode === 13) {
this.send("submit");

View File

@ -62,7 +62,7 @@ export default Ember.Component.extend({
this.setProperties({ ip_address: "", formSubmitted: false });
this.action(ScreenedIpAddress.create(result.screened_ip_address));
Ember.run.schedule("afterRender", () =>
this.$(".ip-address-input").focus()
this.element.querySelector(".ip-address-input").focus()
);
})
.catch(e => {
@ -73,7 +73,9 @@ export default Ember.Component.extend({
error: e.jqXHR.responseJSON.errors.join(". ")
})
: I18n.t("generic_error");
bootbox.alert(msg, () => this.$(".ip-address-input").focus());
bootbox.alert(msg, () =>
this.element.querySelector(".ip-address-input").focus()
);
});
}
}
@ -82,7 +84,7 @@ export default Ember.Component.extend({
@on("didInsertElement")
_init() {
Ember.run.schedule("afterRender", () => {
this.$(".ip-address-input").keydown(e => {
$(this.element.querySelector(".ip-address-input")).keydown(e => {
if (e.keyCode === 13) {
this.send("submit");
}

View File

@ -3,6 +3,8 @@ import computed from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend({
@computed()
groupChoices() {
return this.site.get("groups").map(g => g.name);
return this.site.get("groups").map(g => {
return { name: g.name, id: g.id.toString() };
});
}
});

View File

@ -9,11 +9,13 @@ export default Ember.Component.extend({
const term = this._searchTerm();
if (term) {
this.$(".site-text-id, .site-text-value").highlight(term, {
$(
this.element.querySelector(".site-text-id, .site-text-value")
).highlight(term, {
className: "text-highlight"
});
}
this.$(".site-text-value").ellipsis();
$(this.element.querySelector(".site-text-value")).ellipsis();
},
click() {

View File

@ -4,19 +4,23 @@ export default Ember.Component.extend({
classNames: ["table", "staff-actions"],
willDestroyElement() {
this.$().off("click.discourse-staff-logs");
$(this.element).off("click.discourse-staff-logs");
},
didInsertElement() {
this._super(...arguments);
this.$().on("click.discourse-staff-logs", "[data-link-post-id]", e => {
let postId = $(e.target).attr("data-link-post-id");
$(this.element).on(
"click.discourse-staff-logs",
"[data-link-post-id]",
e => {
let postId = $(e.target).attr("data-link-post-id");
this.store.find("post", postId).then(p => {
DiscourseURL.routeTo(p.get("url"));
});
return false;
});
this.store.find("post", postId).then(p => {
DiscourseURL.routeTo(p.get("url"));
});
return false;
}
);
}
});

View File

@ -38,8 +38,8 @@ export default Ember.Component.extend({
},
animate(isInitial) {
const $container = this.$();
const $list = this.$(".components-list");
const $container = $(this.element);
const $list = $(this.element.querySelector(".components-list"));
if ($list.length === 0 || Ember.testing) {
return;
}

View File

@ -64,7 +64,7 @@ export default Ember.Component.extend({
});
this.action(WatchedWord.create(result));
Ember.run.schedule("afterRender", () =>
this.$(".watched-word-input").focus()
this.element.querySelector(".watched-word-input").focus()
);
})
.catch(e => {
@ -75,7 +75,9 @@ export default Ember.Component.extend({
error: e.jqXHR.responseJSON.errors.join(". ")
})
: I18n.t("generic_error");
bootbox.alert(msg, () => this.$(".watched-word-input").focus());
bootbox.alert(msg, () =>
this.element.querySelector(".watched-word-input").focus()
);
});
}
}
@ -84,7 +86,7 @@ export default Ember.Component.extend({
@on("didInsertElement")
_init() {
Ember.run.schedule("afterRender", () => {
this.$(".watched-word-input").keydown(e => {
$(this.element.querySelector(".watched-word-input")).keydown(e => {
if (e.keyCode === 13) {
this.send("submit");
}

View File

@ -2,13 +2,13 @@ import computed from "ember-addons/ember-computed-decorators";
import UploadMixin from "discourse/mixins/upload";
export default Ember.Component.extend(UploadMixin, {
type: "csv",
type: "txt",
classNames: "watched-words-uploader",
uploadUrl: "/admin/logs/watched_words/upload",
addDisabled: Ember.computed.alias("uploading"),
validateUploadedFilesOptions() {
return { csvOnly: true };
return { skipValidation: true };
},
@computed("actionKey")

View File

@ -29,7 +29,7 @@ export default Ember.Controller.extend({
I18n.t("yes_value"),
confirmed => {
if (confirmed) {
Discourse.User.currentProp("hideReadOnlyAlert", true);
this.set("currentUser.hideReadOnlyAlert", true);
this._toggleReadOnlyMode(true);
}
}

View File

@ -0,0 +1,33 @@
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Controller.extend({
@computed("model.isSaving")
saveButtonText(isSaving) {
return isSaving ? I18n.t("saving") : I18n.t("admin.customize.save");
},
@computed("model.changed", "model.isSaving")
saveDisabled(changed, isSaving) {
return !changed || isSaving;
},
actions: {
save() {
if (!this.model.saving) {
this.set("saving", true);
this.model
.update(this.model.getProperties("html", "css"))
.catch(e => {
const msg =
e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors
? I18n.t("admin.customize.email_style.save_error_with_reason", {
error: e.jqXHR.responseJSON.errors.join(". ")
})
: I18n.t("generic_error");
bootbox.alert(msg);
})
.finally(() => this.set("model.changed", false));
}
}
}
});

View File

@ -6,5 +6,11 @@ export default Ember.Controller.extend({
this._super(...arguments);
this.titleSorting = ["title"];
},
actions: {
selectTemplate(template) {
this.transitionToRoute("adminCustomizeEmailTemplates.edit", template);
}
}
});

View File

@ -0,0 +1,45 @@
import { ajax } from "discourse/lib/ajax";
import { bufferedProperty } from "discourse/mixins/buffered-content";
import { propertyEqual } from "discourse/lib/computed";
export default Ember.Controller.extend(bufferedProperty("model"), {
saved: false,
isSaving: false,
saveDisabled: propertyEqual("model.robots_txt", "buffered.robots_txt"),
resetDisbaled: Ember.computed.not("model.overridden"),
actions: {
save() {
this.setProperties({
isSaving: true,
saved: false
});
ajax("robots.json", {
method: "PUT",
data: { robots_txt: this.buffered.get("robots_txt") }
})
.then(data => {
this.commitBuffer();
this.set("saved", true);
this.set("model.overridden", data.overridden);
})
.finally(() => this.set("isSaving", false));
},
reset() {
this.setProperties({
isSaving: true,
saved: false
});
ajax("robots.json", { method: "DELETE" })
.then(data => {
this.buffered.set("robots_txt", data.robots_txt);
this.commitBuffer();
this.set("saved", true);
this.set("model.overridden", false);
})
.finally(() => this.set("isSaving", false));
}
}
});

View File

@ -5,11 +5,11 @@ import Report from "admin/models/report";
import PeriodComputationMixin from "admin/mixins/period-computation";
function staticReport(reportType) {
return function() {
return Ember.computed("reports.[]", function() {
return Ember.makeArray(this.reports).find(
report => report.type === reportType
);
}.property("reports.[]");
});
}
export default Ember.Controller.extend(PeriodComputationMixin, {

View File

@ -1,21 +1,15 @@
import { exportEntity } from "discourse/lib/export-csv";
import { outputExportResult } from "discourse/lib/export-result";
import StaffActionLog from "admin/models/staff-action-log";
import {
default as computed,
on
} from "ember-addons/ember-computed-decorators";
export default Ember.Controller.extend({
loading: false,
filters: null,
userHistoryActions: [],
model: null,
nextPage: 0,
lastPage: null,
filters: null,
filtersExists: Ember.computed.gt("filterCount", 0),
showTable: Ember.computed.gt("model.length", 0),
userHistoryActions: null,
@computed("filters.action_name")
actionFilter(name) {
@ -25,34 +19,21 @@ export default Ember.Controller.extend({
@on("init")
resetFilters() {
this.setProperties({
filters: Ember.Object.create(),
model: [],
nextPage: 0,
lastPage: null
model: Ember.Object.create({ loadingMore: true }),
filters: Ember.Object.create()
});
this.scheduleRefresh();
},
_changeFilters(props) {
this.set("model", Ember.Object.create({ loadingMore: true }));
this.filters.setProperties(props);
this.setProperties({
model: [],
nextPage: 0,
lastPage: null
});
this.scheduleRefresh();
},
_refresh() {
if (this.lastPage && this.nextPage >= this.lastPage) {
return;
}
this.set("loading", true);
const page = this.nextPage;
let filters = this.filters;
let params = { page };
let params = {};
let count = 0;
// Don't send null values
@ -65,32 +46,23 @@ export default Ember.Controller.extend({
});
this.set("filterCount", count);
StaffActionLog.findAll(params)
.then(result => {
this.setProperties({
model: this.model.concat(result.staff_action_logs),
nextPage: page + 1
});
this.store.findAll("staff-action-log", params).then(result => {
this.set("model", result);
if (result.staff_action_logs.length === 0) {
this.set("lastPage", page);
}
if (this.userHistoryActions.length === 0) {
this.set(
"userHistoryActions",
result.user_history_actions
.map(action => ({
id: action.id,
action_id: action.action_id,
name: I18n.t("admin.logs.staff_actions.actions." + action.id),
name_raw: action.id
}))
.sort((a, b) => (a.name > b.name ? 1 : -1))
);
}
})
.finally(() => this.set("loading", false));
if (!this.userHistoryActions) {
this.set(
"userHistoryActions",
result.extras.user_history_actions
.map(action => ({
id: action.id,
action_id: action.action_id,
name: I18n.t("admin.logs.staff_actions.actions." + action.id),
name_raw: action.id
}))
.sort((a, b) => a.name.localeCompare(b.name))
);
}
});
},
scheduleRefresh() {
@ -153,7 +125,7 @@ export default Ember.Controller.extend({
},
loadMore() {
this._refresh();
this.model.loadMore();
}
}
});

View File

@ -134,7 +134,7 @@ export default Ember.Controller.extend(CanCheckEmails, {
return this.model.resetBounceScore();
},
approve() {
return this.model.approve();
return this.model.approve(this.currentUser);
},
deactivate() {
return this.model.deactivate();

View File

@ -1,5 +1,8 @@
import computed from "ember-addons/ember-computed-decorators";
import WatchedWord from "admin/models/watched-word";
import { ajax } from "discourse/lib/ajax";
import { fmt } from "discourse/lib/computed";
import showModal from "discourse/lib/show-modal";
export default Ember.Controller.extend({
actionNameKey: null,
@ -8,6 +11,10 @@ export default Ember.Controller.extend({
"adminWatchedWords.filtered",
"adminWatchedWords.showWords"
),
downloadLink: fmt(
"actionNameKey",
"/admin/logs/watched_words/action/%@/download"
),
findAction(actionName) {
return (this.get("adminWatchedWords.model") || []).findBy(
@ -17,13 +24,13 @@ export default Ember.Controller.extend({
},
@computed("actionNameKey", "adminWatchedWords.model")
filteredContent(actionNameKey) {
if (!actionNameKey) {
return [];
}
currentAction(actionName) {
return this.findAction(actionName);
},
const a = this.findAction(actionNameKey);
return a ? a.words : [];
@computed("currentAction.words.[]", "adminWatchedWords.model")
filteredContent(words) {
return words || [];
},
@computed("actionNameKey")
@ -31,10 +38,9 @@ export default Ember.Controller.extend({
return I18n.t("admin.watched_words.action_descriptions." + actionNameKey);
},
@computed("actionNameKey", "adminWatchedWords.model")
wordCount(actionNameKey) {
const a = this.findAction(actionNameKey);
return a ? a.words.length : 0;
@computed("currentAction.count")
wordCount(count) {
return count || 0;
},
actions: {
@ -62,10 +68,9 @@ export default Ember.Controller.extend({
},
recordRemoved(arg) {
const a = this.findAction(this.actionNameKey);
if (a) {
a.words.removeObject(arg);
a.decrementProperty("count");
if (this.currentAction) {
this.currentAction.words.removeObject(arg);
this.currentAction.decrementProperty("count");
}
},
@ -73,6 +78,40 @@ export default Ember.Controller.extend({
WatchedWord.findAll().then(data => {
this.set("adminWatchedWords.model", data);
});
},
clearAll() {
const actionKey = this.actionNameKey;
bootbox.confirm(
I18n.t(`admin.watched_words.clear_all_confirm_${actionKey}`),
I18n.t("no_value"),
I18n.t("yes_value"),
result => {
if (result) {
ajax(`/admin/logs/watched_words/action/${actionKey}.json`, {
method: "DELETE"
}).then(() => {
const action = this.findAction(actionKey);
if (action) {
action.setProperties({
words: [],
count: 0
});
}
});
}
}
);
},
test() {
WatchedWord.findAll().then(data => {
this.set("adminWatchedWords.model", data);
showModal("admin-watched-word-test", {
admin: true,
model: this.currentAction
});
});
}
}
});

View File

@ -1,7 +1,7 @@
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Controller.extend({
application: Ember.inject.controller(),
router: Ember.inject.service(),
@computed("siteSettings.enable_group_directory")
showGroups(enableGroupDirectory) {
@ -13,7 +13,7 @@ export default Ember.Controller.extend({
return this.currentUser.get("admin") && enableBadges;
},
@computed("application.currentPath")
@computed("router._router.currentPath")
adminContentsClassName(currentPath) {
let cssClasses = currentPath
.split(".")

View File

@ -0,0 +1,11 @@
import { default as computed } from "ember-addons/ember-computed-decorators";
import ModalFunctionality from "discourse/mixins/modal-functionality";
export default Ember.Controller.extend(ModalFunctionality, {
@computed("value", "model.compiledRegularExpression")
matches(value, regexpString) {
if (!value || !regexpString) return;
let censorRegexp = new RegExp(regexpString, "ig");
return value.match(censorRegexp);
}
});

View File

@ -90,7 +90,7 @@ export default Ember.Mixin.create({
},
_watchEnterKey: function() {
this.$().on("keydown.setting-enter", ".input-setting-string", e => {
$(this.element).on("keydown.setting-enter", ".input-setting-string", e => {
if (e.keyCode === 13) {
// enter key
this.send("save");
@ -99,7 +99,7 @@ export default Ember.Mixin.create({
}.on("didInsertElement"),
_removeBindings: function() {
this.$().off("keydown.setting-enter");
$(this.element).off("keydown.setting-enter");
}.on("willDestroyElement"),
_save() {

View File

@ -227,14 +227,14 @@ const AdminUser = Discourse.User.extend({
.catch(popupAjaxError);
},
approve() {
approve(approvedBy) {
return ajax(`/admin/users/${this.id}/approve`, {
type: "PUT"
}).then(() => {
this.setProperties({
can_approve: false,
approved: true,
approved_by: Discourse.User.current()
approved_by: approvedBy
});
});
},

View File

@ -3,7 +3,7 @@ import {
observes,
on
} from "ember-addons/ember-computed-decorators";
import { propertyNotEqual, i18n } from "discourse/lib/computed";
import { propertyNotEqual } from "discourse/lib/computed";
const ColorSchemeColor = Discourse.Model.extend({
@on("init")
@ -42,9 +42,23 @@ const ColorSchemeColor = Discourse.Model.extend({
}
},
translatedName: i18n("name", "admin.customize.colors.%@.name"),
@computed("name")
translatedName(name) {
if (!this.is_advanced) {
return I18n.t(`admin.customize.colors.${name}.name`);
} else {
return name;
}
},
description: i18n("name", "admin.customize.colors.%@.description"),
@computed("name")
description(name) {
if (!this.is_advanced) {
return I18n.t(`admin.customize.colors.${name}.description`);
} else {
return "";
}
},
/**
brightness returns a number between 0 (darkest) to 255 (brightest).

View File

@ -128,7 +128,8 @@ ColorScheme.reopenClass({
return ColorSchemeColor.create({
name: c.name,
hex: c.hex,
default_hex: c.default_hex
default_hex: c.default_hex,
is_advanced: c.is_advanced
});
})
})

View File

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

View File

@ -2,6 +2,7 @@ import computed from "ember-addons/ember-computed-decorators";
import { ajax } from "discourse/lib/ajax";
import AdminUser from "admin/models/admin-user";
import { escapeExpression } from "discourse/lib/utilities";
import RestModel from "discourse/models/rest";
function format(label, value, escape = true) {
return value
@ -9,7 +10,7 @@ function format(label, value, escape = true) {
: "";
}
const StaffActionLog = Discourse.Model.extend({
const StaffActionLog = RestModel.extend({
showFullDetails: false,
@computed("action_name")
@ -80,16 +81,14 @@ const StaffActionLog = Discourse.Model.extend({
});
StaffActionLog.reopenClass({
create(attrs) {
attrs = attrs || {};
if (attrs.acting_user) {
attrs.acting_user = AdminUser.create(attrs.acting_user);
munge(json) {
if (json.acting_user) {
json.acting_user = AdminUser.create(json.acting_user);
}
if (attrs.target_user) {
attrs.target_user = AdminUser.create(attrs.target_user);
if (json.target_user) {
json.target_user = AdminUser.create(json.target_user);
}
return this._super(attrs);
return json;
},
findAll(data) {

View File

@ -42,7 +42,8 @@ WatchedWord.reopenClass({
name: I18n.t("admin.watched_words.actions." + n),
words: actions[n],
count: actions[n].length,
regularExpressions: list.regular_expressions
regularExpressions: list.regular_expressions,
compiledRegularExpression: list.compiled_regular_expressions[n]
});
});
});

View File

@ -0,0 +1,39 @@
export default Ember.Route.extend({
model(params) {
return {
model: this.modelFor("adminCustomizeEmailStyle"),
fieldName: params.field_name
};
},
setupController(controller, model) {
controller.setProperties({
fieldName: model.fieldName,
model: model.model
});
this._shouldAlertUnsavedChanges = true;
},
actions: {
willTransition(transition) {
if (
this.get("controller.model.changed") &&
this._shouldAlertUnsavedChanges &&
transition.intent.name !== this.routeName
) {
transition.abort();
bootbox.confirm(
I18n.t("admin.customize.theme.unsaved_changes_alert"),
I18n.t("admin.customize.theme.discard"),
I18n.t("admin.customize.theme.stay"),
result => {
if (!result) {
this._shouldAlertUnsavedChanges = false;
transition.retry();
}
}
);
}
}
}
});

View File

@ -0,0 +1,9 @@
export default Ember.Route.extend({
model() {
return this.store.find("email-style");
},
redirect() {
this.transitionTo("adminCustomizeEmailStyle.edit", "html");
}
});

View File

@ -0,0 +1,7 @@
import { ajax } from "discourse/lib/ajax";
export default Ember.Route.extend({
model() {
return ajax("/admin/customize/robots");
}
});

View File

@ -86,6 +86,17 @@ export default function() {
this.route("edit", { path: "/:id" });
}
);
this.route("adminCustomizeRobotsTxt", {
path: "/robots",
resetNamespace: true
});
this.route(
"adminCustomizeEmailStyle",
{ path: "/email_style", resetNamespace: true },
function() {
this.route("edit", { path: "/:field_name" });
}
);
}
);

View File

@ -11,7 +11,7 @@ export default Discourse.Route.extend({
},
sendInvites() {
this.transitionTo("userInvited", Discourse.User.current());
this.transitionTo("userInvited", this.currentUser);
},
deleteUser(user) {

View File

@ -130,9 +130,7 @@
</span>
<div class="input">
{{date-picker-past
value=startDate
defaultDate=startDate}}
{{date-input date=startDate onChange=(action "onChangeStartDate")}}
</div>
</div>
@ -142,9 +140,7 @@
</span>
<div class="input">
{{date-picker-past
value=endDate
defaultDate=endDate}}
{{date-input date=endDate onChange=(action "onChangeEndDate")}}
</div>
</div>
{{/if}}

View File

@ -0,0 +1,20 @@
<div class='row'>
<div class='admin-controls'>
<nav>
<ul class='nav nav-pills'>
<li>{{#link-to 'adminCustomizeEmailStyle.edit' 'html' replace=true}}{{i18n 'admin.customize.email_style.html'}}{{/link-to}}</li>
<li>{{#link-to 'adminCustomizeEmailStyle.edit' 'css' replace=true}}{{i18n 'admin.customize.email_style.css'}}{{/link-to}}</li>
</ul>
</nav>
</div>
</div>
{{ace-editor content=editorContents mode=fieldName editorId=editorId}}
<div class='admin-footer'>
<div class='buttons'>
{{#d-button action=(action "reset") disabled=resetDisabled class='btn-default'}}
{{i18n 'admin.customize.email_style.reset'}}
{{/d-button}}
</div>
</div>

View File

@ -1,3 +1,3 @@
{{list-setting settingValue=value choices=groupChoices settingName=setting.setting}}
{{list-setting settingValue=value choices=groupChoices settingName='name'}}
{{setting-validation-message message=validationMessage}}
<div class='desc'>{{{unbound setting.description}}}</div>

View File

@ -1,7 +1,6 @@
<label class="btn btn-default {{if addDisabled 'disabled'}}">
{{d-icon "upload"}}
{{i18n 'admin.watched_words.form.upload'}}
<input class="hidden-upload-field" disabled={{addDisabled}} type="file" accept="text/plain,text/csv" />
<input class="hidden-upload-field" disabled={{addDisabled}} type="file" accept="text/plain" />
</label>
<br/>
<span class="instructions">One word per line</span>
<span class="instructions">{{i18n 'admin.watched_words.one_word_per_line'}}</span>

View File

@ -6,7 +6,7 @@
<button {{action "save"}} disabled={{model.disableSave}} class='btn btn-primary'>{{i18n 'admin.customize.save'}}</button>
{{/unless}}
<button {{action "copy" model}} class='btn btn-default'>{{d-icon "copy"}} {{i18n 'admin.customize.copy'}}</button>
<button {{action "copyToClipboard" model}} class='btn btn-default'>{{d-icon "clipboard"}} {{i18n 'admin.customize.copy_to_clipboard'}}</button>
<button {{action "copyToClipboard" model}} class='btn btn-default'>{{d-icon "far-clipboard"}} {{i18n 'admin.customize.copy_to_clipboard'}}</button>
{{#if model.theme_id}}
{{i18n "admin.customize.theme_owner"}}
{{#link-to "adminCustomizeThemes.show" model.theme_id}}{{model.theme_name}}{{/link-to}}

View File

@ -0,0 +1,9 @@
{{email-styles-editor styles=model fieldName=fieldName}}
<div class='admin-footer'>
<div class='buttons'>
{{#d-button action=(action "save") disabled=saveDisabled class='btn-primary'}}
{{saveButtonText}}
{{/d-button}}
</div>
</div>

View File

@ -0,0 +1,7 @@
<div class='row'>
<h2>{{i18n 'admin.customize.email_style.heading'}}</h2>
<p>{{i18n 'admin.customize.email_style.instructions'}}</p>
</div>
{{outlet}}

View File

@ -1,15 +1,8 @@
<div class='row'>
<div class='content-list'>
<ul>
{{#each sortedTemplates as |et|}}
<li>
{{#link-to 'adminCustomizeEmailTemplates.edit' et}}{{et.title}}{{/link-to}}
</li>
{{/each}}
</ul>
</div>
{{combo-box
content=sortedTemplates
valueAttribute="id"
nameProperty="title"
onSelect=(action "selectTemplate")
}}
<div class='content-editor'>
{{outlet}}
</div>
</div>
{{outlet}}

View File

@ -0,0 +1,20 @@
<div class="robots-txt-edit">
<h3>{{i18n "admin.customize.robots.title"}}</h3>
<p>{{i18n "admin.customize.robots.warning"}}</p>
{{#if model.overridden}}
<div class="overridden">
{{i18n "admin.customize.robots.overridden"}}
</div>
{{/if}}
{{textarea
value=buffered.robots_txt
class="robots-txt-input"}}
{{#save-controls model=this action=(action "save") saved=saved saveDisabled=saveDisabled}}
{{d-button
class="btn-default"
disabled=resetDisbaled
icon="undo"
action=(action "reset")
label="admin.settings.reset"}}
{{/save-controls}}
</div>

View File

@ -3,6 +3,7 @@
{{nav-item route='adminCustomize.colors' label='admin.customize.colors.title'}}
{{nav-item route='adminSiteText' label='admin.site_text.title'}}
{{nav-item route='adminCustomizeEmailTemplates' label='admin.customize.email_templates.title'}}
{{nav-item route='adminCustomizeEmailStyle' label='admin.customize.email_style.title'}}
{{nav-item route='adminUserFields' label='admin.user_fields.title'}}
{{nav-item route='adminEmojis' label='admin.emoji.title'}}
{{nav-item route='adminPermalinks' label='admin.permalink.title'}}

View File

@ -117,13 +117,15 @@
<h4>{{i18n "admin.dashboard.last_updated"}} </h4>
<p>{{format-date model.attributes.updated_at leaveAgo="true"}}</p>
</div>
<div>
<h4>{{i18n "admin.dashboard.discourse_last_updated"}} </h4>
<p>{{format-date model.attributes.discourse_updated_at leaveAgo="true"}}</p>
<a rel="noopener" target="_blank" href={{model.attributes.release_notes_link}} class="btn btn-default">
{{i18n "admin.dashboard.whats_new_in_discourse"}}
</a>
</div>
{{#if model.attributes.discourse_updated_at}}
<div>
<h4>{{i18n "admin.dashboard.discourse_last_updated"}} </h4>
<p>{{format-date model.attributes.discourse_updated_at leaveAgo="true"}}</p>
<a rel="noopener" target="_blank" href={{model.attributes.release_notes_link}} class="btn btn-default">
{{i18n "admin.dashboard.whats_new_in_discourse"}}
</a>
</div>
{{/if}}
</div>
</div>
</div>

View File

@ -6,13 +6,15 @@
{{date-picker-past value=lastSeen id="last-seen"}}
<label>{{i18n 'admin.email.user'}}:</label>
{{user-selector single="true" usernames=username canReceiveUpdates="true"}}
<button class='btn' {{action "refresh"}}>{{i18n 'admin.email.refresh'}}</button>
<button class='btn btn-primary digest-refresh-button' {{action "refresh"}}>{{i18n 'admin.email.refresh'}}</button>
<div class="toggle">
<label>{{i18n 'admin.email.format'}}</label>
{{#if showHtml}}
<span>{{i18n 'admin.email.html'}}</span> | <a href {{action "toggleShowHtml"}}>{{i18n 'admin.email.text'}}</a>
<span>{{i18n 'admin.email.html'}}</span> | <a href
{{action "toggleShowHtml"}}>{{i18n 'admin.email.text'}}</a>
{{else}}
<a href {{action "toggleShowHtml"}}>{{i18n 'admin.email.html'}}</a> | <span>{{i18n 'admin.email.text'}}</span>
<a href {{action "toggleShowHtml"}}>{{i18n 'admin.email.html'}}</a> |
<span>{{i18n 'admin.email.text'}}</span>
{{/if}}
</div>
</div>
@ -20,35 +22,33 @@
{{#conditional-loading-spinner condition=loading}}
<div class="email-preview-digest">
{{#if showSendEmailForm}}
<br/>
<div class="controls">
{{#if sendingEmail}}
{{i18n 'admin.email.sending_test'}}
{{else}}
<label>{{i18n 'admin.email.send_digest_label'}}</label>
{{text-field value=email placeholderKey="admin.email.test_email_address"}}
<button class='btn btn-default' {{action "sendEmail"}} disabled={{sendEmailDisabled}}>{{i18n 'admin.email.send_digest'}}</button>
{{#if sentEmail}}
<span class='result-message'>{{i18n 'admin.email.sent_test'}}</span>
<div class="email-preview-digest">
{{#if showSendEmailForm}}
<div class="controls">
{{#if sendingEmail}}
{{i18n 'admin.email.sending_test'}}
{{else}}
<label>{{i18n 'admin.email.send_digest_label'}}</label>
{{text-field value=email placeholderKey="admin.email.test_email_address"}}
<button class='btn btn-default' {{action "sendEmail"}} disabled={{sendEmailDisabled}}>{{i18n 'admin.email.send_digest'}}</button>
{{#if sentEmail}}
<span class='result-message'>{{i18n 'admin.email.sent_test'}}</span>
{{/if}}
{{/if}}
</div>
{{/if}}
<div class="preview-output">
{{#if showHtml}}
{{#if htmlEmpty}}
<p>{{i18n 'admin.email.no_result'}}</p>
{{else}}
<iframe srcdoc={{model.html_content}} />
{{/if}}
{{else}}
<pre>{{{model.text_content}}}</pre>
{{/if}}
</div>
<br/>
{{/if}}
<div class="preview-output">
{{#if showHtml}}
{{#if htmlEmpty}}
<p>{{i18n 'admin.email.no_result'}}</p>
{{else}}
<iframe srcdoc={{model.html_content}} />
{{/if}}
{{else}}
<pre>{{{model.text_content}}}</pre>
{{/if}}
</div>
</div>
{{/conditional-loading-spinner}}
{{/conditional-loading-spinner}}

View File

@ -39,70 +39,66 @@
{{#staff-actions}}
{{#load-more selector=".staff-logs tr" action=(action "loadMore")}}
{{#if showTable}}
<table class='table staff-logs grid'>
<thead>
<th>{{i18n 'admin.logs.staff_actions.staff_user'}}</th>
<th>{{i18n 'admin.logs.action'}}</th>
<th>{{i18n 'admin.logs.staff_actions.subject'}}</th>
<th>{{i18n 'admin.logs.staff_actions.when'}}</th>
<th>{{i18n 'admin.logs.staff_actions.details'}}</th>
<th>{{i18n 'admin.logs.staff_actions.context'}}</th>
</thead>
<tbody>
{{#each model as |item|}}
<tr class='admin-list-item'>
<td class="staff-users">
<div class="staff-user">
{{#if item.acting_user}}
{{#link-to 'adminUser' item.acting_user}}{{avatar item.acting_user imageSize="tiny"}}{{/link-to}}
<a {{action "filterByStaffUser" item.acting_user}}>{{item.acting_user.username}}</a>
{{else}}
<span class="deleted-user" title="{{i18n 'admin.user.deleted'}}">
{{d-icon "far-trash-alt"}}
</span>
{{#load-more selector=".staff-logs tr" action=(action "loadMore")}}
{{#if model.content}}
<table class='table staff-logs grid'>
<thead>
<th>{{i18n 'admin.logs.staff_actions.staff_user'}}</th>
<th>{{i18n 'admin.logs.action'}}</th>
<th>{{i18n 'admin.logs.staff_actions.subject'}}</th>
<th>{{i18n 'admin.logs.staff_actions.when'}}</th>
<th>{{i18n 'admin.logs.staff_actions.details'}}</th>
<th>{{i18n 'admin.logs.staff_actions.context'}}</th>
</thead>
<tbody>
{{#each model.content as |item|}}
<tr class='admin-list-item'>
<td class="staff-users">
<div class="staff-user">
{{#if item.acting_user}}
{{#link-to 'adminUser' item.acting_user}}{{avatar item.acting_user imageSize="tiny"}}{{/link-to}}
<a {{action "filterByStaffUser" item.acting_user}}>{{item.acting_user.username}}</a>
{{else}}
<span class="deleted-user" title="{{i18n 'admin.user.deleted'}}">
{{d-icon "far-trash-alt"}}
</span>
{{/if}}
</div>
</td>
<td class="col value action">
<a {{action "filterByAction" item}}>{{item.actionName}}</a>
</td>
<td class="col value subject">
<div class="subject">
{{#if item.target_user}}
{{#link-to 'adminUser' item.target_user}}{{avatar item.target_user imageSize="tiny"}}{{/link-to}}
<a {{action "filterByTargetUser" item.target_user}}>{{item.target_user.username}}</a>
{{/if}}
{{#if item.subject}}
<a {{action "filterBySubject" item.subject}} title={{item.subject}}>{{item.subject}}</a>
{{/if}}
</div>
</td>
<td class="col value created-at">{{age-with-tooltip item.created_at}}</td>
<td class="col value details">
{{{item.formattedDetails}}}
{{#if item.useCustomModalForDetails}}
<a {{action "showCustomDetailsModal" item}}>{{d-icon "info-circle"}} {{i18n 'admin.logs.staff_actions.show'}}</a>
{{/if}}
</div>
</td>
<td class="col value action">
<a {{action "filterByAction" item}}>{{item.actionName}}</a>
</td>
<td class="col value subject">
<div class="subject">
{{#if item.target_user}}
{{#link-to 'adminUser' item.target_user}}{{avatar item.target_user imageSize="tiny"}}{{/link-to}}
<a {{action "filterByTargetUser" item.target_user}}>{{item.target_user.username}}</a>
{{/if}}
{{#if item.subject}}
<a {{action "filterBySubject" item.subject}} title={{item.subject}}>{{item.subject}}</a>
{{/if}}
</div>
</td>
<td class="col value created-at">{{age-with-tooltip item.created_at}}</td>
<td class="col value details">
{{{item.formattedDetails}}}
{{#if item.useCustomModalForDetails}}
<a {{action "showCustomDetailsModal" item}}>{{d-icon "info-circle"}} {{i18n 'admin.logs.staff_actions.show'}}</a>
{{/if}}
{{#if item.useModalForDetails}}
<a {{action "showDetailsModal" item}}>{{d-icon "info-circle"}} {{i18n 'admin.logs.staff_actions.show'}}</a>
{{/if}}
</td>
<td class="col value context">{{item.context}}</td>
</tr>
{{/each}}
</tbody>
</table>
{{else}}
{{i18n 'search.no_results'}}
{{/if}}
{{conditional-loading-spinner condition=loading}}
{{/load-more}}
{{#if item.useModalForDetails}}
<a {{action "showDetailsModal" item}}>{{d-icon "info-circle"}} {{i18n 'admin.logs.staff_actions.show'}}</a>
{{/if}}
</td>
<td class="col value context">{{item.context}}</td>
</tr>
{{/each}}
</tbody>
</table>
{{else if model.loadingMore}}
{{conditional-loading-spinner condition=model.loadingMore}}
{{else}}
{{i18n 'search.no_results'}}
{{/if}}
{{/load-more}}
{{/staff-actions}}

View File

@ -44,7 +44,7 @@
{{#if local}}
<div class="inputs">
<input onchange={{action "uploadLocaleFile"}} type="file" id="file-input" accept='.dcstyle.json,application/json,.tar.gz,application/x-gzip'><br>
<input onchange={{action "uploadLocaleFile"}} type="file" id="file-input" accept='.dcstyle.json,application/json,.tar.gz,application/x-gzip,.zip,application/zip'><br>
<span class="description">{{i18n 'admin.customize.theme.import_file_tip'}}</span>
</div>
{{/if}}

View File

@ -0,0 +1,16 @@
{{#d-modal-body rawTitle=(i18n "admin.watched_words.test.modal_title" action=model.name) class="watched-words-test-modal"}}
<p>{{i18n "admin.watched_words.test.description"}}</p>
{{textarea name="test_value" value=value autofocus="autofocus"}}
{{#if matches}}
<p>
{{i18n "admin.watched_words.test.found_matches"}}
<ul>
{{#each matches as |match|}}
<li>{{match}}</li>
{{/each}}
</ul>
</p>
{{else}}
<p>{{i18n "admin.watched_words.test.no_matches"}}</p>
{{/if}}
{{/d-modal-body}}

View File

@ -3,14 +3,24 @@
<p class="about">{{actionDescription}}</p>
<div class="watched-word-controls">
{{watched-word-form
actionKey=actionNameKey
action=(action "recordAdded")
filteredContent=filteredContent
regularExpressions=adminWatchedWords.regularExpressions}}
{{watched-word-form
actionKey=actionNameKey
action=(action "recordAdded")
filteredContent=filteredContent
regularExpressions=adminWatchedWords.regularExpressions}}
{{watched-word-uploader uploading=uploading actionKey=actionNameKey done=(action "uploadComplete")}}
<div class="download-upload-controls">
<div class="download">
{{d-button
class="btn-default download-link"
href=downloadLink
icon="download"
label="admin.watched_words.download"}}
</div>
{{watched-word-uploader uploading=uploading actionKey=actionNameKey done=(action "uploadComplete")}}
</div>
</div>
<div>
<label class="show-words-checkbox">
{{input type="checkbox" checked=adminWatchedWords.showWords disabled=adminWatchedWords.disableShowWords}}
@ -26,3 +36,15 @@
{{i18n 'admin.watched_words.word_count' count=wordCount}}
{{/if}}
</div>
<div class="clear-all-row">
{{d-button
label="admin.watched_words.test.button_label"
icon="far-eye"
action=(action "test")}}
{{d-button
class="btn-danger clear-all"
label="admin.watched_words.clear_all"
icon="trash-alt"
action=(action "clearAll")}}
</div>

View File

@ -25,7 +25,7 @@
<td class='description'>{{webHook.description}}</td>
<td class='controls'>
{{#link-to 'adminWebHooks.show' webHook tagName='button' classNames='btn btn-default no-text'}}{{d-icon 'far-edit'}}{{/link-to}}
{{d-button class="destroy btn-danger" action=(action "destroy") actionParam=webHook icon="remove"}}
{{d-button class="destroy btn-danger" action=(action "destroy") actionParam=webHook icon="times"}}
</td>
</tr>
{{/each}}

View File

@ -35,7 +35,8 @@ const REPLACEMENTS = {
"notification.topic_reminder": "far-clock",
"notification.watching_first_post": "far-dot-circle",
"notification.group_message_summary": "users",
"notification.post_approved": "check"
"notification.post_approved": "check",
"notification.membership_request_accepted": "user-plus"
};
// TODO: use lib/svg_sprite/fa4-renames.json here
@ -579,13 +580,9 @@ function warnIfMissing(id) {
}
function warnIfDeprecated(oldId, newId) {
if (
typeof Discourse !== "undefined" &&
Discourse.Environment === "development" &&
!Ember.testing
) {
deprecated(`Icon "${oldId}" is now "${newId}".`);
}
deprecated(
`Please replace all occurrences of "${oldId}"" with "${newId}". FontAwesome 4.7 icon names are now deprecated and will be removed in the next release.`
);
}
function handleIconId(icon) {

View File

@ -6,13 +6,6 @@ export default Ember.Component.extend(UploadMixin, {
tagName: "span",
imageIsNotASquare: false,
@computed("uploading")
uploadButtonText(uploading) {
return uploading
? I18n.t("uploading")
: I18n.t("user.change_avatar.upload_picture");
},
validateUploadedFilesOptions() {
return { imagesOnly: true };
},

View File

@ -25,9 +25,9 @@ export default Ember.Component.extend({
didRender() {
this._super(...arguments);
const $backupCodes = this.$("#backupCodes");
if ($backupCodes.length) {
$backupCodes.height($backupCodes[0].scrollHeight);
const backupCodes = this.element.querySelector("#backupCodes");
if (backupCodes) {
backupCodes.style.height = backupCodes.scrollHeight;
}
},
@ -49,8 +49,8 @@ export default Ember.Component.extend({
},
_selectAllBackupCodes() {
const $textArea = this.$("#backupCodes");
$textArea[0].focus();
$textArea[0].setSelectionRange(0, this.formattedBackupCodes.length);
const textArea = this.element.querySelector("#backupCodes");
textArea.focus();
textArea.setSelectionRange(0, this.formattedBackupCodes.length);
}
});

View File

@ -35,7 +35,7 @@ export default Ember.Component.extend(UploadMixin, {
},
_init: function() {
const $upload = this.$();
const $upload = $(this.element);
$upload.on("fileuploadadd", (e, data) => {
ajax("/admin/backups/upload_url", {

View File

@ -33,6 +33,51 @@ export default Ember.Component.extend({
}
},
didInsertElement() {
this._super(...arguments);
this.topics.forEach(topic => {
const includeUnreadIndicator =
typeof topic.unread_by_group_member !== "undefined";
if (includeUnreadIndicator) {
const unreadIndicatorChannel = `/private-messages/unread-indicator/${topic.id}`;
this.messageBus.subscribe(unreadIndicatorChannel, data => {
const nodeClassList = document.querySelector(
`.indicator-topic-${data.topic_id}`
).classList;
if (data.show_indicator) {
nodeClassList.remove("read");
} else {
nodeClassList.add("read");
}
});
}
});
},
willDestroyElement() {
this._super(...arguments);
this.topics.forEach(topic => {
const includeUnreadIndicator =
typeof topic.unread_by_group_member !== "undefined";
if (includeUnreadIndicator) {
const unreadIndicatorChannel = `/private-messages/unread-indicator/${topic.id}`;
this.messageBus.unsubscribe(unreadIndicatorChannel);
}
});
},
@computed("topics")
showUnreadIndicator(topics) {
return topics.some(
topic => typeof topic.unread_by_group_member !== "undefined"
);
},
click(e) {
// Mobile basic-topic-list doesn't use the `topic-list-item` view so
// the event for the topic entrance is never wired up.

View File

@ -1,14 +0,0 @@
const CategoryPanelBase = Ember.Component.extend({
classNameBindings: [":modal-tab", "activeTab::invisible"]
});
export default CategoryPanelBase;
export function buildCategoryPanel(tab, extras) {
return CategoryPanelBase.extend(
{
activeTab: Ember.computed.equal("selectedTab", tab)
},
extras || {}
);
}

View File

@ -93,9 +93,9 @@ export default Ember.Component.extend(KeyEnterEscape, {
},
setupComposerResizeEvents() {
const $composer = this.$();
const $grippie = this.$(".grippie");
const $document = Ember.$(document);
const $composer = $(this.element);
const $grippie = $(this.element.querySelector(".grippie"));
const $document = $(document);
let origComposerSize = 0;
let lastMousePos = 0;
@ -105,7 +105,7 @@ export default Ember.Component.extend(KeyEnterEscape, {
const currentMousePos = mouseYPos(event);
let size = origComposerSize + (lastMousePos - currentMousePos);
const winHeight = Ember.$(window).height();
const winHeight = $(window).height();
size = Math.min(size, winHeight - headerHeight());
size = Math.max(size, MIN_COMPOSER_SIZE);
this.movePanels(size);
@ -145,11 +145,11 @@ export default Ember.Component.extend(KeyEnterEscape, {
};
triggerOpen();
afterTransition(this.$(), () => {
afterTransition($(this.element), () => {
resize();
triggerOpen();
});
positioningWorkaround(this.$());
positioningWorkaround($(this.element));
},
willDestroyElement() {

View File

@ -112,7 +112,7 @@ export default Ember.Component.extend({
@observes("focusTarget")
setFocus() {
if (this.focusTarget === "editor") {
this.$("textarea").putCursorAtEnd();
$(this.element.querySelector("textarea")).putCursorAtEnd();
}
},
@ -155,21 +155,29 @@ export default Ember.Component.extend({
};
},
userSearchTerm(term) {
const topicId = this.get("topic.id");
// maybe this is a brand new topic, so grab category from composer
const categoryId =
this.get("topic.category_id") || this.get("composer.categoryId");
return userSearch({
term,
topicId,
categoryId,
includeMentionableGroups: true
});
},
@on("didInsertElement")
_composerEditorInit() {
const topicId = this.get("topic.id");
const $input = this.$(".d-editor-input");
const $preview = this.$(".d-editor-preview-wrapper");
const $input = $(this.element.querySelector(".d-editor-input"));
const $preview = $(this.element.querySelector(".d-editor-preview-wrapper"));
if (this.siteSettings.enable_mentions) {
$input.autocomplete({
template: findRawTemplate("user-selector-autocomplete"),
dataSource: term =>
userSearch({
term,
topicId,
includeMentionableGroups: true
}),
dataSource: term => this.userSearchTerm.call(this, term),
key: "@",
transformComplete: v => v.username || v.name,
afterComplete() {
@ -206,7 +214,7 @@ export default Ember.Component.extend({
!this.get("composer.canEditTitle") &&
(!this.capabilities.isIOS || safariHacksDisabled())
) {
this.$(".d-editor-input").putCursorAtEnd();
$(this.element.querySelector(".d-editor-input")).putCursorAtEnd();
}
this._bindUploadTarget();
@ -237,7 +245,7 @@ export default Ember.Component.extend({
reason = I18n.t("composer.error.post_missing");
} else if (missingReplyCharacters > 0) {
reason = I18n.t("composer.error.post_length", { min: minimumPostLength });
const tl = Discourse.User.currentProp("trust_level");
const tl = this.get("currentUser.trust_level");
if (tl === 0 || tl === 1) {
reason +=
"<br/>" +
@ -345,12 +353,13 @@ export default Ember.Component.extend({
},
_teardownInputPreviewSync() {
[this.$(".d-editor-input"), this.$(".d-editor-preview-wrapper")].forEach(
$element => {
$element.off("mouseenter touchstart");
$element.off("scroll");
}
);
[
$(this.element.querySelector(".d-editor-input")),
$(this.element.querySelector(".d-editor-preview-wrapper"))
].forEach($element => {
$element.off("mouseenter touchstart");
$element.off("scroll");
});
REBUILD_SCROLL_MAP_EVENTS.forEach(event => {
this.appEvents.off(event, this, this._resetShouldBuildScrollMap);
@ -647,14 +656,11 @@ export default Ember.Component.extend({
this._unbindUploadTarget(); // in case it's still bound, let's clean it up first
this._pasted = false;
const $element = this.$();
const csrf = this.session.get("csrfToken");
const $element = $(this.element);
$element.fileupload({
url: Discourse.getURL(
`/uploads.json?client_id=${
this.messageBus.clientId
}&authenticity_token=${encodeURIComponent(csrf)}`
`/uploads.json?client_id=${this.messageBus.clientId}`
),
dataType: "json",
pasteZone: $element
@ -867,7 +873,8 @@ export default Ember.Component.extend({
// wraps previewed upload markdown in a codeblock in its own class to keep a track
// of indexes later on to replace the correct upload placeholder in the composer
if ($preview.find(".codeblock-image").length === 0) {
this.$(".d-editor-preview *")
$(this.element)
.find(".d-editor-preview *")
.contents()
.each(function() {
if (this.nodeType !== 3) return; // TEXT_NODE
@ -890,7 +897,7 @@ export default Ember.Component.extend({
this._validUploads = 0;
$("#reply-control .mobile-file-upload").off("click.uploader");
this.messageBus.unsubscribe("/uploads/composer");
const $uploadTarget = this.$();
const $uploadTarget = $(this.element);
try {
$uploadTarget.fileupload("destroy");
} catch (e) {
@ -925,7 +932,7 @@ export default Ember.Component.extend({
},
showPreview() {
const $preview = this.$(".d-editor-preview-wrapper");
const $preview = $(this.element.querySelector(".d-editor-preview-wrapper"));
this._placeImageScaleButtons($preview);
this.send("togglePreview");
},
@ -1071,7 +1078,7 @@ export default Ember.Component.extend({
if (this._enableAdvancedEditorPreviewSync()) {
this._syncScroll(
this._syncEditorAndPreviewScroll,
this.$(".d-editor-input"),
$(this.element.querySelector(".d-editor-input")),
$preview
);
}

View File

@ -11,7 +11,7 @@ export default Ember.Component.extend({
didInsertElement() {
this._super(...arguments);
this.$().show();
this.element.style.display = "block";
},
actions: {

View File

@ -14,9 +14,9 @@ export default Ember.Component.extend({
didInsertElement() {
this._super(...arguments);
if (this.focusTarget === "title") {
const $input = this.$("input");
const $input = $(this.element.querySelector("input"));
afterTransition(this.$().closest("#reply-control"), () => {
afterTransition($(this.element).closest("#reply-control"), () => {
$input.putCursorAtEnd();
});
}
@ -133,14 +133,14 @@ export default Ember.Component.extend({
.finally(() => {
this.set("composer.loading", false);
Ember.run.schedule("afterRender", () => {
this.$("input").putCursorAtEnd();
$(this.element.querySelector("input")).putCursorAtEnd();
});
});
} else {
this._updatePost(loadOnebox);
this.set("composer.loading", false);
Ember.run.schedule("afterRender", () => {
this.$("input").putCursorAtEnd();
$(this.element.querySelector("input")).putCursorAtEnd();
});
}
}

View File

@ -12,14 +12,14 @@ export default Ember.Component.extend({
this._super(...arguments);
if (this.focusTarget === "usernames") {
this.$("input").putCursorAtEnd();
$(this.element.querySelector("input")).putCursorAtEnd();
}
},
@observes("usernames")
_checkWidth() {
let width = 0;
const $acWrap = this.$().find(".ac-wrap");
const $acWrap = $(this.element).find(".ac-wrap");
const limit = $acWrap.width();
this.set("defaultUsernameCount", 0);
@ -76,7 +76,7 @@ export default Ember.Component.extend({
this.set("showSelector", true);
Ember.run.schedule("afterRender", () => {
this.$()
$(this.element)
.find("input")
.focus();
});
@ -84,7 +84,7 @@ export default Ember.Component.extend({
triggerResize() {
this.appEvents.trigger("composer:resize");
const $this = this.$().find(".ac-wrap");
const $this = $(this.element).find(".ac-wrap");
if ($this.height() >= 150) $this.scrollTop($this.height());
}
}

View File

@ -8,7 +8,7 @@ export default Ember.Component.extend({
this.set("email", $.cookie("email"));
}
this.$().on("keydown.discourse-create-account", e => {
$(this.element).on("keydown.discourse-create-account", e => {
if (!this.disabled && e.keyCode === 13) {
e.preventDefault();
e.stopPropagation();
@ -17,7 +17,7 @@ export default Ember.Component.extend({
}
});
this.$().on("click.dropdown-user-field-label", "[for]", event => {
$(this.element).on("click.dropdown-user-field-label", "[for]", event => {
const $element = $(event.target);
const $target = $(`#${$element.attr("for")}`);
@ -31,7 +31,7 @@ export default Ember.Component.extend({
willDestroyElement() {
this._super(...arguments);
this.$().off("keydown.discourse-create-account");
this.$().off("click.dropdown-user-field-label");
$(this.element).off("keydown.discourse-create-account");
$(this.element).off("click.dropdown-user-field-label");
}
});

View File

@ -32,7 +32,7 @@ export default Ember.Component.extend(UploadMixin, {
},
_init: function() {
const $upload = this.$();
const $upload = $(this.element);
$upload.on("fileuploadadd", (e, data) => {
bootbox.confirm(

View File

@ -7,8 +7,8 @@ export default Ember.Component.extend({
_hiddenChanged() {
if (!this.hidden) {
Ember.run.scheduleOnce("afterRender", () => {
const $modal = this.$();
const $parent = this.$().closest(".d-editor");
const $modal = $(this.element);
const $parent = $(this.element).closest(".d-editor");
const w = $parent.width();
const h = $parent.height();
const dir = $("html").css("direction") === "rtl" ? "right" : "left";
@ -27,7 +27,7 @@ export default Ember.Component.extend({
@on("didInsertElement")
_listenKeys() {
this.$().on("keydown.d-modal", key => {
$(this.element).on("keydown.d-modal", key => {
if (this.hidden) {
return;
}
@ -45,7 +45,7 @@ export default Ember.Component.extend({
@on("willDestroyElement")
_stopListening() {
this.$().off("keydown.d-modal");
$(this.element).off("keydown.d-modal");
},
actions: {

View File

@ -220,6 +220,7 @@ export default Ember.Component.extend({
_mouseTrap: null,
showLink: true,
emojiPickerIsActive: false,
emojiStore: Ember.inject.service("emoji-store"),
@computed("placeholder")
placeholderTranslated(placeholder) {
@ -231,7 +232,7 @@ export default Ember.Component.extend({
this.set("ready", true);
if (this.autofocus) {
this.$("textarea").focus();
this.element.querySelector("textarea").focus();
}
},
@ -244,13 +245,13 @@ export default Ember.Component.extend({
didInsertElement() {
this._super(...arguments);
const $editorInput = this.$(".d-editor-input");
const $editorInput = $(this.element.querySelector(".d-editor-input"));
this._applyEmojiAutocomplete($editorInput);
this._applyCategoryHashtagAutocomplete($editorInput);
Ember.run.scheduleOnce("afterRender", this, this._readyNow);
const mouseTrap = Mousetrap(this.$(".d-editor-input")[0]);
const mouseTrap = Mousetrap(this.element.querySelector(".d-editor-input"));
const shortcuts = this.get("toolbar.shortcuts");
Object.keys(shortcuts).forEach(sc => {
@ -262,28 +263,31 @@ export default Ember.Component.extend({
});
// disable clicking on links in the preview
this.$(".d-editor-preview").on("click.preview", e => {
if (wantsNewWindow(e)) {
return;
$(this.element.querySelector(".d-editor-preview")).on(
"click.preview",
e => {
if (wantsNewWindow(e)) {
return;
}
const $target = $(e.target);
if ($target.is("a.mention")) {
this.appEvents.trigger(
"click.discourse-preview-user-card-mention",
$target
);
}
if ($target.is("a.mention-group")) {
this.appEvents.trigger(
"click.discourse-preview-group-card-mention-group",
$target
);
}
if ($target.is("a")) {
e.preventDefault();
return false;
}
}
const $target = $(e.target);
if ($target.is("a.mention")) {
this.appEvents.trigger(
"click.discourse-preview-user-card-mention",
$target
);
}
if ($target.is("a.mention-group")) {
this.appEvents.trigger(
"click.discourse-preview-group-card-mention-group",
$target
);
}
if ($target.is("a")) {
e.preventDefault();
return false;
}
});
);
if (this.composerEvents) {
this.appEvents.on("composer:insert-block", this, "_insertBlock");
@ -313,7 +317,7 @@ export default Ember.Component.extend({
Object.keys(this.get("toolbar.shortcuts")).forEach(sc =>
mouseTrap.unbind(sc)
);
this.$(".d-editor-preview").off("click.preview");
$(this.element.querySelector(".d-editor-preview")).off("click.preview");
},
@computed
@ -348,7 +352,7 @@ export default Ember.Component.extend({
if (this._state !== "inDOM") {
return;
}
const $preview = this.$(".d-editor-preview");
const $preview = $(this.element.querySelector(".d-editor-preview"));
if ($preview.length === 0) return;
if (this.previewUpdated) {
@ -375,7 +379,7 @@ export default Ember.Component.extend({
_applyCategoryHashtagAutocomplete() {
const siteSettings = this.siteSettings;
this.$(".d-editor-input").autocomplete({
$(this.element.querySelector(".d-editor-input")).autocomplete({
template: findRawTemplate("category-tag-autocomplete"),
key: "#",
afterComplete: () => this._focusTextArea(),
@ -419,6 +423,7 @@ export default Ember.Component.extend({
transformComplete: v => {
if (v.code) {
this.emojiStore.track(v.code);
return `${v.code}:`;
} else {
$editorInput.autocomplete({ cancel: true });
@ -455,7 +460,17 @@ export default Ember.Component.extend({
}
if (term === "") {
return resolve(["slight_smile", "smile", "wink", "sunny", "blush"]);
if (this.emojiStore.favorites.length) {
return resolve(this.emojiStore.favorites.slice(0, 5));
} else {
return resolve([
"slight_smile",
"smile",
"wink",
"sunny",
"blush"
]);
}
}
if (translations[full]) {
@ -500,7 +515,7 @@ export default Ember.Component.extend({
return;
}
const textarea = this.$("textarea.d-editor-input")[0];
const textarea = this.element.querySelector("textarea.d-editor-input");
const value = textarea.value;
let start = textarea.selectionStart;
let end = textarea.selectionEnd;
@ -533,8 +548,8 @@ export default Ember.Component.extend({
_selectText(from, length) {
Ember.run.scheduleOnce("afterRender", () => {
const $textarea = this.$("textarea.d-editor-input");
const textarea = $textarea[0];
const textarea = this.element.querySelector("textarea.d-editor-input");
const $textarea = $(textarea);
const oldScrollPos = $textarea.scrollTop();
if (!this.capabilities.isIOS || safariHacksDisabled()) {
$textarea.focus();
@ -687,7 +702,7 @@ export default Ember.Component.extend({
return;
}
const textarea = this.$("textarea.d-editor-input")[0];
const textarea = this.element.querySelector("textarea.d-editor-input");
// Determine post-replace selection.
const newSelection = determinePostReplaceSelection({
@ -737,7 +752,7 @@ export default Ember.Component.extend({
}
const value = pre + text + post;
const $textarea = this.$("textarea.d-editor-input");
const $textarea = $(this.element.querySelector("textarea.d-editor-input"));
this.set("value", value);
@ -749,7 +764,7 @@ export default Ember.Component.extend({
},
_addText(sel, text, options) {
const $textarea = this.$("textarea.d-editor-input");
const $textarea = $(this.element.querySelector("textarea.d-editor-input"));
if (options && options.ensureSpace) {
if ((sel.pre + "").length > 0) {
@ -870,8 +885,11 @@ export default Ember.Component.extend({
// ensures textarea scroll position is correct
_focusTextArea() {
const $textarea = this.$("textarea.d-editor-input");
Ember.run.scheduleOnce("afterRender", () => $textarea.blur().focus());
const textarea = this.element.querySelector("textarea.d-editor-input");
Ember.run.scheduleOnce("afterRender", () => {
textarea.blur();
textarea.focus();
});
},
actions: {

View File

@ -7,7 +7,7 @@ export default Ember.Component.extend({
this._super(...arguments);
$("#modal-alert").hide();
let fixedParent = this.$().closest(".d-modal.fixed-modal");
let fixedParent = $(this.element).closest(".d-modal.fixed-modal");
if (fixedParent.length) {
this.set("fixed", true);
fixedParent.modal("show");
@ -26,8 +26,12 @@ export default Ember.Component.extend({
},
_afterFirstRender() {
if (!this.site.mobileView && this.autoFocus !== "false") {
this.$("input:first").focus();
if (
!this.site.mobileView &&
this.autoFocus !== "false" &&
this.element.querySelector("input")
) {
this.element.querySelector("input").focus();
}
const maxHeight = this.maxHeight;
@ -35,7 +39,7 @@ export default Ember.Component.extend({
const maxHeightFloat = parseFloat(maxHeight) / 100.0;
if (maxHeightFloat > 0) {
const viewPortHeight = $(window).height();
this.$().css(
$(this.element).css(
"max-height",
Math.floor(maxHeightFloat * viewPortHeight) + "px"
);

View File

@ -66,7 +66,7 @@ export default Ember.Component.extend({
}
if (data.fixed) {
this.$().removeClass("hidden");
this.element.classList.remove("hidden");
}
if (data.title) {

View File

@ -1,6 +1,9 @@
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend({
router: Ember.inject.service(),
persistedQueryParams: null,
tagName: "",
@computed("category")
@ -27,9 +30,25 @@ export default Ember.Component.extend({
if (filterMode.indexOf("top/") === 0) {
filterMode = filterMode.replace("top/", "");
}
let params;
const currentRouteQueryParams = this.get("router.currentRoute.queryParams");
if (this.persistedQueryParams && currentRouteQueryParams) {
const currentKeys = Object.keys(currentRouteQueryParams);
const discoveryKeys = Object.keys(this.persistedQueryParams);
const supportedKeys = currentKeys.filter(
i => discoveryKeys.indexOf(i) > 0
);
params = supportedKeys.reduce((object, key) => {
object[key] = currentRouteQueryParams[key];
return object;
}, {});
}
return Discourse.NavItem.buildList(category, {
filterMode,
noSubcategories
noSubcategories,
persistedQueryParams: params
});
}
});

View File

@ -0,0 +1,101 @@
/* global Pikaday:true */
import loadScript from "discourse/lib/load-script";
import {
default as computed,
on
} from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend({
classNames: ["d-date-input"],
date: null,
_picker: null,
@computed("site.mobileView")
inputType(mobileView) {
return mobileView ? "date" : "text";
},
@on("didInsertElement")
_loadDatePicker() {
const container = this.element.querySelector(`#${this.containerId}`);
if (this.site.mobileView) {
this._loadNativePicker(container);
} else {
this._loadPikadayPicker(container);
}
},
didUpdateAttrs() {
this._super(...arguments);
if (this._picker) {
this._picker.setDate(this.date, true);
}
},
_loadPikadayPicker(container) {
loadScript("/javascripts/pikaday.js").then(() => {
Ember.run.next(() => {
const default_opts = {
field: this.element.querySelector(".date-picker"),
container: container || this.element,
bound: container === null,
format: "LL",
firstDay: 1,
i18n: {
previousMonth: I18n.t("dates.previous_month"),
nextMonth: I18n.t("dates.next_month"),
months: moment.months(),
weekdays: moment.weekdays(),
weekdaysShort: moment.weekdaysShort()
},
onSelect: date => this._handleSelection(date)
};
this._picker = new Pikaday(Object.assign(default_opts, this._opts()));
this._picker.setDate(this.date, true);
});
});
},
_loadNativePicker(container) {
const wrapper = container || this.element;
const picker = wrapper.querySelector("input.date-picker");
picker.onchange = () => this._handleSelection(picker.value);
picker.hide = () => {
/* do nothing for native */
};
picker.destroy = () => {
/* do nothing for native */
};
this._picker = picker;
},
_handleSelection(value) {
if (!this.element || this.isDestroying || this.isDestroyed) return;
this._picker && this._picker.hide();
if (this.onChange) {
this.onChange(moment(value).toDate());
}
},
@on("willDestroyElement")
_destroy() {
if (this._picker) {
this._picker.destroy();
}
this._picker = null;
},
@computed()
placeholder() {
return I18n.t("dates.placeholder");
},
_opts() {
return null;
}
});

View File

@ -41,7 +41,7 @@ export default Ember.Component.extend({
nextMonth: I18n.t("dates.next_month"),
months: moment.months(),
weekdays: moment.weekdays(),
weekdaysShort: moment.weekdaysShort()
weekdaysShort: moment.weekdaysMin()
},
onSelect: date => this._handleSelection(date)
};

View File

@ -0,0 +1,51 @@
export default Ember.Component.extend({
classNames: ["d-date-time-input-range"],
from: null,
to: null,
onChangeTo: null,
onChangeFrom: null,
currentPanel: "from",
showFromTime: true,
showToTime: true,
error: null,
fromPanelActive: Ember.computed.equal("currentPanel", "from"),
toPanelActive: Ember.computed.equal("currentPanel", "to"),
_valid(state) {
if (state.to < state.from) {
return I18n.t("date_time_picker.errors.to_before_from");
}
return true;
},
actions: {
_onChange(options, value) {
if (this.onChange) {
const state = {
from: this.from,
to: this.to
};
const diff = {};
diff[options.prop] = value;
const newState = Object.assign(state, diff);
const validation = this._valid(newState);
if (validation === true) {
this.set("error", null);
this.onChange(newState);
} else {
this.set("error", validation);
}
}
},
onChangePanel(panel) {
this.set("currentPanel", panel);
}
}
});

View File

@ -0,0 +1,35 @@
export default Ember.Component.extend({
classNames: ["d-date-time-input"],
date: null,
showTime: true,
_hours: Ember.computed("date", function() {
return this.date ? this.date.getHours() : null;
}),
_minutes: Ember.computed("date", function() {
return this.date ? this.date.getMinutes() : null;
}),
actions: {
onChangeTime(time) {
if (this.onChange) {
const year = this.date.getFullYear();
const month = this.date.getMonth();
const day = this.date.getDate();
this.onChange(new Date(year, month, day, time.hours, time.minutes));
}
},
onChangeDate(date) {
if (this.onChange) {
const year = date.getFullYear();
const month = date.getMonth();
const day = date.getDate();
this.onChange(
new Date(year, month, day, this._hours || 0, this._minutes || 0)
);
}
}
}
});

View File

@ -102,7 +102,7 @@ export default Ember.Component.extend(
$(window).on("resize.discourse-on-scroll", () => this.scrolled());
this.$().on(
$(this.element).on(
"click.discourse-redirect",
".cooked a, a.track-link",
function(e) {
@ -120,7 +120,10 @@ export default Ember.Component.extend(
$(window).unbind("resize.discourse-on-scroll");
// Unbind link tracking
this.$().off("click.discourse-redirect", ".cooked a, a.track-link");
$(this.element).off(
"click.discourse-redirect",
".cooked a, a.track-link"
);
this.resetExamineDockCache();

View File

@ -22,6 +22,11 @@ const DiscoveryTopicsListComponent = Ember.Component.extend(
}
},
@observes("topicTrackingState.states")
_updateTopics() {
this.topicTrackingState.updateTopics(this.model.topics);
},
@observes("incomingCount")
_updateTitle() {
Discourse.updateContextCount(this.incomingCount);

View File

@ -1,12 +1,33 @@
import { buildCategoryPanel } from "discourse/components/edit-category-panel";
import PermissionType from "discourse/models/permission-type";
import { on } from "ember-addons/ember-computed-decorators";
export default buildCategoryPanel("security", {
editingPermissions: false,
selectedGroup: null,
selectedPermission: null,
showPendingGroupChangesAlert: false,
interactedWithDropdowns: false,
@on("init")
_registerValidator() {
this.registerValidator(() => {
if (
!this.showPendingGroupChangesAlert &&
this.interactedWithDropdowns &&
this.activeTab
) {
this.set("showPendingGroupChangesAlert", true);
return true;
}
});
},
actions: {
onDropdownChange() {
this.set("interactedWithDropdowns", true);
},
editPermissions() {
if (!this.get("category.is_special")) {
this.set("editingPermissions", true);
@ -25,6 +46,10 @@ export default buildCategoryPanel("security", {
"selectedGroup",
this.get("category.availableGroups.firstObject")
);
this.setProperties({
showPendingGroupChangesAlert: false,
interactedWithDropdowns: false
});
},
removePermission(permission) {

View File

@ -27,7 +27,7 @@ export default Ember.Component.extend({
},
_resetModalScrollState() {
const $modalBody = this.$()
const $modalBody = $(this.element)
.parents("#discourse-modal")
.find(".modal-body");
if ($modalBody.length === 1) {

View File

@ -4,7 +4,7 @@ export default buildCategoryPanel("topic-template", {
_activeTabChanged: function() {
if (this.activeTab) {
Ember.run.scheduleOnce("afterRender", () =>
this.$(".d-editor-input").focus()
this.element.querySelector(".d-editor-input").focus()
);
}
}.observes("activeTab")

View File

@ -1,7 +1,7 @@
import { on, observes } from "ember-addons/ember-computed-decorators";
import { findRawTemplate } from "discourse/lib/raw-templates";
import { emojiUrlFor } from "discourse/lib/text";
import KeyValueStore from "discourse/lib/key-value-store";
import {
extendedEmojiList,
isSkinTonableEmoji,
@ -10,21 +10,14 @@ import {
import { safariHacksDisabled } from "discourse/lib/utilities";
const { run } = Ember;
const keyValueStore = new KeyValueStore("discourse_emojis_");
const EMOJI_USAGE = "emojiUsage";
const EMOJI_SELECTED_DIVERSITY = "emojiSelectedDiversity";
const PER_ROW = 11;
const customEmojis = _.keys(extendedEmojiList()).map(code => {
return { code, src: emojiUrlFor(code) };
});
export function resetCache() {
keyValueStore.setObject({ key: EMOJI_USAGE, value: [] });
keyValueStore.setObject({ key: EMOJI_SELECTED_DIVERSITY, value: 1 });
}
export default Ember.Component.extend({
automaticPositioning: true,
emojiStore: Ember.inject.service("emoji-store"),
close() {
this._unbindEvents();
@ -46,11 +39,10 @@ export default Ember.Component.extend({
this.$results = this.$picker.find(".results");
this.$list = this.$picker.find(".list");
this.set(
"selectedDiversity",
keyValueStore.getObject(EMOJI_SELECTED_DIVERSITY) || 1
);
this.set("recentEmojis", keyValueStore.getObject(EMOJI_USAGE) || []);
this.setProperties({
selectedDiversity: this.emojiStore.diversity,
recentEmojis: this.emojiStore.favorites
});
run.scheduleOnce("afterRender", this, function() {
this._bindEvents();
@ -86,20 +78,9 @@ export default Ember.Component.extend({
@on("didInsertElement")
_setup() {
this.$picker = this.$(".emoji-picker");
this.$modal = this.$(".emoji-picker-modal");
this.$picker = $(this.element.querySelector(".emoji-picker"));
this.$modal = $(this.element.querySelector(".emoji-picker-modal"));
this.appEvents.on("emoji-picker:close", this, "_closeEmojiPicker");
if (!keyValueStore.getObject(EMOJI_USAGE)) {
keyValueStore.setObject({ key: EMOJI_USAGE, value: [] });
} else if (_.isPlainObject(keyValueStore.getObject(EMOJI_USAGE))) {
// handle legacy format
keyValueStore.setObject({
key: EMOJI_USAGE,
value: _.keys(keyValueStore.getObject(EMOJI_USAGE))
});
}
},
@on("didUpdateAttrs")
@ -116,10 +97,7 @@ export default Ember.Component.extend({
@observes("selectedDiversity")
selectedDiversityChanged() {
keyValueStore.setObject({
key: EMOJI_SELECTED_DIVERSITY,
value: this.selectedDiversity
});
this.emojiStore.diversity = this.selectedDiversity;
$.each(
this.$list.find(".emoji[data-loaded='1'].diversity"),
@ -228,8 +206,8 @@ export default Ember.Component.extend({
@on("willDestroyElement")
_unbindEvents() {
this.$().off();
this.$(window).off("resize");
$(this.element).off();
$(window).off("resize");
clearInterval(this._refreshInterval);
$("#reply-control").off("div-resizing");
$("html").off("mouseup.emoji-picker");
@ -312,7 +290,7 @@ export default Ember.Component.extend({
},
_bindResizing() {
this.$(window).on("resize", () => {
$(window).on("resize", () => {
run.throttle(this, this._positionPicker, 16);
});
@ -326,7 +304,7 @@ export default Ember.Component.extend({
".section[data-section='recent'] .clear-recent"
);
$recent.on("click", () => {
keyValueStore.setObject({ key: EMOJI_USAGE, value: [] });
this.emojiStore.favorites = [];
this.set("recentEmojis", []);
this._scrollTo(0);
return false;
@ -468,7 +446,7 @@ export default Ember.Component.extend({
_isReplyControlExpanded() {
const verticalSpace =
this.$(window).height() -
$(window).height() -
$(".d-header").height() -
$("#reply-control").height();
@ -480,7 +458,7 @@ export default Ember.Component.extend({
return;
}
let windowWidth = this.$(window).width();
let windowWidth = $(window).width();
const desktopModalePositioning = options => {
let attributes = {
@ -608,12 +586,8 @@ export default Ember.Component.extend({
},
_trackEmojiUsage(code) {
let recent = keyValueStore.getObject(EMOJI_USAGE) || [];
recent = recent.filter(r => r !== code);
recent.unshift(code);
recent.length = Math.min(recent.length, PER_ROW);
keyValueStore.setObject({ key: EMOJI_USAGE, value: recent });
this.set("recentEmojis", recent);
this.emojiStore.track(code);
this.set("recentEmojis", this.emojiStore.favorites.slice(0, PER_ROW));
},
_scrollTo(y) {

View File

@ -5,7 +5,7 @@ export default Ember.TextArea.extend({
@on("didInsertElement")
_startWatching() {
Ember.run.scheduleOnce("afterRender", () => {
this.$().focus();
$(this.element).focus();
autosize(this.element);
});
},
@ -19,6 +19,6 @@ export default Ember.TextArea.extend({
@on("willDestroyElement")
_disableAutosize() {
autosize.destroy(this.$());
autosize.destroy($(this.element));
}
});

View File

@ -3,14 +3,14 @@ import { observes } from "ember-addons/ember-computed-decorators";
// Mostly hacks because `flag.hbs` didn't use `radio-button`
export default Ember.Component.extend({
_selectRadio() {
this.$("input[type='radio']").prop("checked", false);
this.element.querySelector("input[type='radio']").checked = false;
const nameKey = this.nameKey;
if (!nameKey) {
return;
}
this.$("#radio_" + nameKey).prop("checked", "true");
this.element.querySelector("#radio_" + nameKey).checked = "true";
},
@observes("nameKey")

View File

@ -91,7 +91,7 @@ const FooterNavComponent = MountWidget.extend(
// in the header, otherwise, we hide it.
@observes("mobileScrollDirection")
toggleMobileFooter() {
this.$().toggleClass(
$(this.element).toggleClass(
"visible",
this.mobileScrollDirection === null ? true : false
);

View File

@ -1,7 +1,7 @@
export default Ember.Component.extend({
didInsertElement() {
this._super(...arguments);
this.$("input")
$(this.element.querySelector("input"))
.select()
.focus();
}

View File

@ -22,7 +22,7 @@ export default Ember.Component.extend({
let selectedGroups;
let groupNames = this.groupNames;
this.$("input").autocomplete({
$(this.element.querySelector("input")).autocomplete({
allowAny: false,
items: _.isArray(groupNames)
? groupNames

View File

@ -19,12 +19,12 @@ export default Ember.Component.extend({
@computed("model.visibility_level", "model.public_admission")
disableMembershipRequestSetting(visibility_level, publicAdmission) {
visibility_level = parseInt(visibility_level);
return visibility_level !== 0 || publicAdmission;
return ![0, 1].includes(visibility_level) || publicAdmission;
},
@computed("model.visibility_level", "model.allow_membership_requests")
disablePublicSetting(visibility_level, allowMembershipRequests) {
visibility_level = parseInt(visibility_level);
return visibility_level !== 0 || allowMembershipRequests;
return ![0, 1].includes(visibility_level) || allowMembershipRequests;
}
});

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