mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
REFACTOR: Support bundling our admin section as an ember addon
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import I18n from "I18n";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import { popupAjaxError } 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, {
|
||||
reason: null,
|
||||
message: null,
|
||||
postEdit: null,
|
||||
postAction: null,
|
||||
user: null,
|
||||
postId: null,
|
||||
successCallback: null,
|
||||
confirmClose: false,
|
||||
|
||||
resetModal() {
|
||||
this.setProperties({
|
||||
reason: null,
|
||||
message: null,
|
||||
loadingUser: true,
|
||||
postId: null,
|
||||
postEdit: null,
|
||||
postAction: "delete",
|
||||
before: null,
|
||||
successCallback: null,
|
||||
confirmClose: false,
|
||||
});
|
||||
},
|
||||
|
||||
beforeClose() {
|
||||
// prompt a confirmation if we have unsaved content
|
||||
if (
|
||||
!this.confirmClose &&
|
||||
((this.reason && this.reason.length > 1) ||
|
||||
(this.message && this.message.length > 1))
|
||||
) {
|
||||
this.send("hideModal");
|
||||
bootbox.confirm(I18n.t("admin.user.confirm_cancel_penalty"), (result) => {
|
||||
if (result) {
|
||||
next(() => {
|
||||
this.set("confirmClose", true);
|
||||
this.send("closeModal");
|
||||
});
|
||||
} else {
|
||||
next(() => this.send("reopenModal"));
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
penalize(cb) {
|
||||
let before = this.before;
|
||||
let promise = before ? before() : Promise.resolve();
|
||||
|
||||
return promise
|
||||
.then(() => cb())
|
||||
.then((result) => {
|
||||
this.set("confirmClose", true);
|
||||
this.send("closeModal");
|
||||
let callback = this.successCallback;
|
||||
if (callback) {
|
||||
callback(result);
|
||||
}
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
import Mixin from "@ember/object/mixin";
|
||||
|
||||
export default Mixin.create({
|
||||
queryParams: ["period"],
|
||||
period: "monthly",
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.availablePeriods = ["yearly", "quarterly", "monthly", "weekly"];
|
||||
},
|
||||
|
||||
@discourseComputed("period")
|
||||
startDate(period) {
|
||||
let fullDay = moment().locale("en").utc().subtract(1, "day");
|
||||
|
||||
switch (period) {
|
||||
case "yearly":
|
||||
return fullDay.subtract(1, "year").startOf("day");
|
||||
break;
|
||||
case "quarterly":
|
||||
return fullDay.subtract(3, "month").startOf("day");
|
||||
break;
|
||||
case "weekly":
|
||||
return fullDay.subtract(1, "week").startOf("day");
|
||||
break;
|
||||
case "monthly":
|
||||
return fullDay.subtract(1, "month").startOf("day");
|
||||
break;
|
||||
default:
|
||||
return fullDay.subtract(1, "month").startOf("day");
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed()
|
||||
lastWeek() {
|
||||
return moment().locale("en").utc().endOf("day").subtract(1, "week");
|
||||
},
|
||||
|
||||
@discourseComputed()
|
||||
lastMonth() {
|
||||
return moment().locale("en").utc().startOf("day").subtract(1, "month");
|
||||
},
|
||||
|
||||
@discourseComputed()
|
||||
endDate() {
|
||||
return moment().locale("en").utc().subtract(1, "day").endOf("day");
|
||||
},
|
||||
|
||||
@discourseComputed()
|
||||
today() {
|
||||
return moment().locale("en").utc().endOf("day");
|
||||
},
|
||||
|
||||
actions: {
|
||||
changePeriod(period) {
|
||||
DiscourseURL.routeTo(this._reportsForPeriodURL(period));
|
||||
},
|
||||
},
|
||||
});
|
||||
264
app/assets/javascripts/admin/addon/mixins/setting-component.js
Normal file
264
app/assets/javascripts/admin/addon/mixins/setting-component.js
Normal file
@@ -0,0 +1,264 @@
|
||||
import I18n from "I18n";
|
||||
import { warn } from "@ember/debug";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { alias, oneWay } from "@ember/object/computed";
|
||||
import { categoryLinkHTML } from "discourse/helpers/category-link";
|
||||
import { on } from "@ember/object/evented";
|
||||
import Mixin from "@ember/object/mixin";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import { Promise } from "rsvp";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
|
||||
const CUSTOM_TYPES = [
|
||||
"bool",
|
||||
"enum",
|
||||
"list",
|
||||
"url_list",
|
||||
"host_list",
|
||||
"category_list",
|
||||
"value_list",
|
||||
"category",
|
||||
"uploaded_image_list",
|
||||
"compact_list",
|
||||
"secret_list",
|
||||
"upload",
|
||||
"group_list",
|
||||
"tag_list",
|
||||
"color",
|
||||
"simple_list",
|
||||
];
|
||||
|
||||
const AUTO_REFRESH_ON_SAVE = ["logo", "logo_small", "large_icon"];
|
||||
|
||||
function splitPipes(str) {
|
||||
if (typeof str === "string") {
|
||||
return str.split("|").filter(Boolean);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export default Mixin.create({
|
||||
classNameBindings: [":row", ":setting", "overridden", "typeClass"],
|
||||
content: alias("setting"),
|
||||
validationMessage: null,
|
||||
isSecret: oneWay("setting.secret"),
|
||||
|
||||
@discourseComputed("buffered.value", "setting.value")
|
||||
dirty(bufferVal, settingVal) {
|
||||
if (bufferVal === null || bufferVal === undefined) {
|
||||
bufferVal = "";
|
||||
}
|
||||
if (settingVal === null || settingVal === undefined) {
|
||||
settingVal = "";
|
||||
}
|
||||
|
||||
return bufferVal.toString() !== settingVal.toString();
|
||||
},
|
||||
|
||||
@discourseComputed("setting", "buffered.value")
|
||||
preview(setting, value) {
|
||||
// A bit hacky, but allows us to use helpers
|
||||
if (setting.get("setting") === "category_style") {
|
||||
let category = this.site.get("categories.firstObject");
|
||||
if (category) {
|
||||
return categoryLinkHTML(category, {
|
||||
categoryStyle: value,
|
||||
});
|
||||
}
|
||||
}
|
||||
let preview = setting.get("preview");
|
||||
if (preview) {
|
||||
return htmlSafe(
|
||||
"<div class='preview'>" +
|
||||
preview.replace(/\{\{value\}\}/g, value) +
|
||||
"</div>"
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("componentType")
|
||||
typeClass(componentType) {
|
||||
return componentType.replace(/\_/g, "-");
|
||||
},
|
||||
|
||||
@discourseComputed("setting.setting", "setting.label")
|
||||
settingName(setting, label) {
|
||||
return label || setting.replace(/\_/g, " ");
|
||||
},
|
||||
|
||||
@discourseComputed("type")
|
||||
componentType(type) {
|
||||
return CUSTOM_TYPES.indexOf(type) !== -1 ? type : "string";
|
||||
},
|
||||
|
||||
@discourseComputed("setting")
|
||||
type(setting) {
|
||||
if (setting.type === "list" && setting.list_type) {
|
||||
return `${setting.list_type}_list`;
|
||||
}
|
||||
|
||||
return setting.type;
|
||||
},
|
||||
|
||||
@discourseComputed("typeClass")
|
||||
componentName(typeClass) {
|
||||
return "site-settings/" + typeClass;
|
||||
},
|
||||
|
||||
@discourseComputed("setting.anyValue")
|
||||
allowAny(anyValue) {
|
||||
return anyValue !== false;
|
||||
},
|
||||
|
||||
@discourseComputed("setting.default", "buffered.value")
|
||||
overridden(settingDefault, bufferedValue) {
|
||||
return settingDefault !== bufferedValue;
|
||||
},
|
||||
|
||||
@discourseComputed("buffered.value")
|
||||
bufferedValues: splitPipes,
|
||||
|
||||
@discourseComputed("setting.defaultValues")
|
||||
defaultValues: splitPipes,
|
||||
|
||||
@discourseComputed("defaultValues", "bufferedValues")
|
||||
defaultIsAvailable(defaultValues, bufferedValues) {
|
||||
return (
|
||||
defaultValues &&
|
||||
defaultValues.length > 0 &&
|
||||
!defaultValues.every((value) => bufferedValues.includes(value))
|
||||
);
|
||||
},
|
||||
|
||||
_watchEnterKey: on("didInsertElement", function () {
|
||||
$(this.element).on(
|
||||
"keydown.setting-enter",
|
||||
".input-setting-string",
|
||||
(e) => {
|
||||
if (e.keyCode === 13) {
|
||||
// enter key
|
||||
this.send("save");
|
||||
}
|
||||
}
|
||||
);
|
||||
}),
|
||||
|
||||
_removeBindings: on("willDestroyElement", function () {
|
||||
$(this.element).off("keydown.setting-enter");
|
||||
}),
|
||||
|
||||
_save() {
|
||||
warn("You should define a `_save` method", {
|
||||
id: "discourse.setting-component.missing-save",
|
||||
});
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
actions: {
|
||||
update() {
|
||||
const defaultUserPreferences = [
|
||||
"default_email_digest_frequency",
|
||||
"default_include_tl0_in_digests",
|
||||
"default_email_level",
|
||||
"default_email_messages_level",
|
||||
"default_email_mailing_list_mode",
|
||||
"default_email_mailing_list_mode_frequency",
|
||||
"default_email_previous_replies",
|
||||
"default_email_in_reply_to",
|
||||
"default_other_new_topic_duration_minutes",
|
||||
"default_other_auto_track_topics_after_msecs",
|
||||
"default_other_notification_level_when_replying",
|
||||
"default_other_external_links_in_new_tab",
|
||||
"default_other_enable_quoting",
|
||||
"default_other_enable_defer",
|
||||
"default_other_dynamic_favicon",
|
||||
"default_other_like_notification_frequency",
|
||||
"default_other_skip_new_user_tips",
|
||||
"default_topics_automatic_unpin",
|
||||
"default_categories_watching",
|
||||
"default_categories_tracking",
|
||||
"default_categories_muted",
|
||||
"default_categories_watching_first_post",
|
||||
"default_categories_regular",
|
||||
"default_tags_watching",
|
||||
"default_tags_tracking",
|
||||
"default_tags_muted",
|
||||
"default_tags_watching_first_post",
|
||||
"default_text_size",
|
||||
"default_title_count_mode",
|
||||
];
|
||||
const key = this.buffered.get("setting");
|
||||
|
||||
if (defaultUserPreferences.includes(key)) {
|
||||
const data = {};
|
||||
data[key] = this.buffered.get("value");
|
||||
|
||||
ajax(`/admin/site_settings/${key}/user_count.json`, {
|
||||
type: "PUT",
|
||||
data,
|
||||
}).then((result) => {
|
||||
const count = result.user_count;
|
||||
|
||||
if (count > 0) {
|
||||
const controller = showModal("site-setting-default-categories", {
|
||||
model: {
|
||||
count: result.user_count,
|
||||
key: key.replace(/_/g, " "),
|
||||
},
|
||||
admin: true,
|
||||
});
|
||||
|
||||
controller.set("onClose", () => {
|
||||
this.updateExistingUsers = controller.updateExistingUsers;
|
||||
this.send("save");
|
||||
});
|
||||
} else {
|
||||
this.send("save");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.send("save");
|
||||
}
|
||||
},
|
||||
|
||||
save() {
|
||||
this._save()
|
||||
.then(() => {
|
||||
this.set("validationMessage", null);
|
||||
this.commitBuffer();
|
||||
if (AUTO_REFRESH_ON_SAVE.includes(this.setting.setting)) {
|
||||
this.afterSave();
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
|
||||
this.set("validationMessage", e.jqXHR.responseJSON.errors[0]);
|
||||
} else {
|
||||
this.set("validationMessage", I18n.t("generic_error"));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
cancel() {
|
||||
this.rollbackBuffer();
|
||||
},
|
||||
|
||||
resetDefault() {
|
||||
this.set("buffered.value", this.get("setting.default"));
|
||||
},
|
||||
|
||||
toggleSecret() {
|
||||
this.toggleProperty("isSecret");
|
||||
},
|
||||
|
||||
setDefaultValues() {
|
||||
this.set(
|
||||
"buffered.value",
|
||||
this.bufferedValues.concat(this.defaultValues).uniq().join("|")
|
||||
);
|
||||
return false;
|
||||
},
|
||||
},
|
||||
});
|
||||
69
app/assets/javascripts/admin/addon/mixins/setting-object.js
Normal file
69
app/assets/javascripts/admin/addon/mixins/setting-object.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import I18n from "I18n";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { computed } from "@ember/object";
|
||||
import Mixin from "@ember/object/mixin";
|
||||
import { isPresent } from "@ember/utils";
|
||||
|
||||
export default Mixin.create({
|
||||
@discourseComputed("value", "default")
|
||||
overridden(val, defaultVal) {
|
||||
if (val === null) {
|
||||
val = "";
|
||||
}
|
||||
if (defaultVal === null) {
|
||||
defaultVal = "";
|
||||
}
|
||||
|
||||
return val.toString() !== defaultVal.toString();
|
||||
},
|
||||
|
||||
computedValueProperty: computed(
|
||||
"valueProperty",
|
||||
"validValues.[]",
|
||||
function () {
|
||||
if (isPresent(this.valueProperty)) {
|
||||
return this.valueProperty;
|
||||
}
|
||||
|
||||
if (isPresent(this.validValues.get("firstObject.value"))) {
|
||||
return "value";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
computedNameProperty: computed("nameProperty", "validValues.[]", function () {
|
||||
if (isPresent(this.nameProperty)) {
|
||||
return this.nameProperty;
|
||||
}
|
||||
|
||||
if (isPresent(this.validValues.get("firstObject.name"))) {
|
||||
return "name";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
|
||||
@discourseComputed("valid_values")
|
||||
validValues(validValues) {
|
||||
const vals = [],
|
||||
translateNames = this.translate_names;
|
||||
|
||||
validValues.forEach((v) => {
|
||||
if (v.name && v.name.length > 0 && translateNames) {
|
||||
vals.addObject({ name: I18n.t(v.name), value: v.value });
|
||||
} else {
|
||||
vals.addObject(v);
|
||||
}
|
||||
});
|
||||
return vals;
|
||||
},
|
||||
|
||||
@discourseComputed("valid_values")
|
||||
allowsNone(validValues) {
|
||||
if (validValues && validValues.indexOf("") >= 0) {
|
||||
return "admin.settings.none";
|
||||
}
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user