REFACTOR: Support bundling our admin section as an ember addon

This commit is contained in:
Robin Ward
2020-09-22 14:18:47 -04:00
parent cfb3f4db13
commit ce3fe2f4c4
448 changed files with 130 additions and 29 deletions

View File

@@ -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);
},
});

View File

@@ -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));
},
},
});

View 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;
},
},
});

View 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";
}
},
});