Quite a few Ember-CLI / Upgrade related changes (#11867)

* Quite a few Ember-CLI / Upgrade related changes

They should all be backwards compatible. This is all to help merge our
branches.

* REFACTOR: DRY up username validation

Also avoids overwriting computed properties for compatibility with newer
Ember releases.
This commit is contained in:
Robin Ward 2021-01-29 10:19:54 -05:00 committed by GitHub
parent 0bd0358158
commit 11c812f042
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 105 additions and 129 deletions

View File

@ -2,6 +2,7 @@ import discourseComputed, { observes } from "discourse-common/utils/decorators";
import Component from "@ember/component";
import DiscourseURL from "discourse/lib/url";
import I18n from "I18n";
import { RUNTIME_OPTIONS } from "discourse-common/lib/raw-handlebars-helpers";
import { alias } from "@ember/object/computed";
import { findRawTemplate } from "discourse-common/lib/raw-templates";
import { on } from "@ember/object/evented";
@ -49,7 +50,10 @@ export default Component.extend({
renderTopicListItem() {
const template = findRawTemplate("list/topic-list-item");
if (template) {
this.set("topicListItemContents", template(this).htmlSafe());
this.set(
"topicListItemContents",
template(this, RUNTIME_OPTIONS).htmlSafe()
);
}
},

View File

@ -1,4 +1,5 @@
import discourseComputed, { on } from "discourse-common/utils/decorators";
import BufferedMixin from "ember-buffered-proxy/mixin";
import BufferedProxy from "ember-buffered-proxy/proxy";
import Controller from "@ember/controller";
import EmberObjectProxy from "@ember/object/proxy";
@ -11,14 +12,13 @@ import { sort } from "@ember/object/computed";
export default Controller.extend(ModalFunctionality, Evented, {
init() {
this._super(...arguments);
this.categoriesSorting = ["position"];
},
@discourseComputed("site.categories.[]")
categoriesBuffered(categories) {
const bufProxy = EmberObjectProxy.extend(BufferedProxy);
return categories.map((c) => bufProxy.create({ content: c }));
const bufProxy = EmberObjectProxy.extend(BufferedMixin || BufferedProxy);
return (categories || []).map((c) => bufProxy.create({ content: c }));
},
categoriesOrdered: sort("categoriesBuffered", "categoriesSorting"),

View File

@ -1,6 +1,7 @@
import { helperContext, registerUnbound } from "discourse-common/lib/helpers";
import { findRawTemplate } from "discourse-common/lib/raw-templates";
import { htmlSafe } from "@ember/template";
import { RUNTIME_OPTIONS } from "discourse-common/lib/raw-handlebars-helpers";
function renderRaw(ctx, template, templateName, params) {
params = jQuery.extend({}, params);
@ -21,7 +22,7 @@ function renderRaw(ctx, template, templateName, params) {
}
}
return htmlSafe(template(params));
return htmlSafe(template(params, RUNTIME_OPTIONS));
}
registerUnbound("raw", function (templateName, params) {

View File

@ -63,6 +63,8 @@ export default {
name: "copy-codeblocks",
initialize(container) {
const siteSettings = container.lookup("site-settings:main");
withPluginApi("0.8.7", (api) => {
function _cleanUp() {
Object.values(_copyCodeblocksClickHandlers || {}).forEach((handler) =>
@ -112,7 +114,6 @@ export default {
return;
}
const siteSettings = container.lookup("site-settings:main");
if (!siteSettings.show_copy_button_on_codeblocks) {
return;
}

View File

@ -3,7 +3,7 @@ import bootbox from "bootbox";
export default {
name: "jquery-plugins",
initialize: function () {
initialize() {
// Settings for bootbox
bootbox.animate(false);
bootbox.backdrop(true);

View File

@ -1223,8 +1223,6 @@ class PluginApi {
}
}
let _pluginv01;
// from http://stackoverflow.com/questions/6832596/how-to-compare-software-version-number-using-js-only-number
function cmpVersions(a, b) {
let i, diff;
@ -1244,16 +1242,24 @@ function cmpVersions(a, b) {
function getPluginApi(version) {
version = version.toString();
if (cmpVersions(version, PLUGIN_API_VERSION) <= 0) {
if (!_pluginv01) {
_pluginv01 = new PluginApi(version, getOwner(this));
const owner = getOwner(this);
let pluginApi = owner.lookup("plugin-api:main");
if (!pluginApi) {
pluginApi = new PluginApi(version, owner);
owner.registry.register("plugin-api:main", pluginApi, {
instantiate: false,
});
}
// We are recycling the compatible object, but let's update to the higher version
if (_pluginv01.version < version) {
_pluginv01.version = version;
if (pluginApi.version < version) {
pluginApi.version = version;
}
return _pluginv01;
return pluginApi;
} else {
// eslint-disable-next-line no-console
console.warn(`Plugin API v${version} is not supported`);
@ -1306,7 +1312,3 @@ function decorate(klass, evt, cb, id) {
});
klass.reopen(mixin);
}
export function resetPluginApi() {
_pluginv01 = null;
}

View File

@ -1,3 +1,4 @@
import BufferedMixin from "ember-buffered-proxy/mixin";
import BufferedProxy from "ember-buffered-proxy/proxy";
import EmberObjectProxy from "@ember/object/proxy";
import Mixin from "@ember/object/mixin";
@ -6,7 +7,7 @@ import { computed } from "@ember/object";
export function bufferedProperty(property) {
const mixin = {
buffered: computed(property, function () {
return EmberObjectProxy.extend(BufferedProxy).create({
return EmberObjectProxy.extend(BufferedMixin || BufferedProxy).create({
content: this.get(property),
});
}),

View File

@ -1,5 +1,6 @@
import Mixin from "@ember/object/mixin";
import discourseDebounce from "discourse-common/lib/debounce";
// Small buffer so that very tiny scrolls don't trigger mobile header switch
const MOBILE_SCROLL_TOLERANCE = 5;

View File

@ -49,7 +49,6 @@ const Scrolling = Mixin.create({
let debouncedScrollMethod = () => {
discourseDebounce(this, onScrollMethod, opts.debounce);
};
ScrollingDOMMethods.bindOnScroll(debouncedScrollMethod, opts.name);
} else {
ScrollingDOMMethods.bindOnScroll(onScrollMethod, opts.name);

View File

@ -2,16 +2,30 @@ import EmberObject from "@ember/object";
import I18n from "I18n";
import Mixin from "@ember/object/mixin";
import User from "discourse/models/user";
import discourseComputed from "discourse-common/utils/decorators";
import discourseDebounce from "discourse-common/lib/debounce";
import { isEmpty } from "@ember/utils";
import { observes } from "discourse-common/utils/decorators";
import { setting } from "discourse/lib/computed";
function failedResult(attrs) {
let result = EmberObject.create({
shouldCheck: false,
failed: true,
element: document.querySelector("#new-account-username"),
});
result.setProperties(attrs);
return result;
}
function validResult(attrs) {
let result = EmberObject.create({ ok: true });
result.setProperties(attrs);
return result;
}
export default Mixin.create({
uniqueUsernameValidation: null,
maxUsernameLength: setting("max_username_length"),
minUsernameLength: setting("min_username_length"),
fetchExistingUsername() {
@ -35,125 +49,78 @@ export default Mixin.create({
);
},
@discourseComputed("accountUsername")
basicUsernameValidation(accountUsername) {
const failedAttrs = {
failed: true,
element: document.querySelector("#new-account-username"),
};
this.set("uniqueUsernameValidation", null);
@observes("accountUsername")
triggerValidation() {
let { accountUsername } = this;
if (accountUsername && accountUsername === this.prefilledUsername) {
return EmberObject.create({
ok: true,
reason: I18n.t("user.username.prefilled"),
});
let result = this.basicUsernameValidation(accountUsername);
if (result.shouldCheck) {
this.checkUsernameAvailability();
}
// If blank, fail without a reason
if (isEmpty(accountUsername)) {
return EmberObject.create(
Object.assign(failedAttrs, {
message: I18n.t("user.username.required"),
})
);
}
// If too short
if (accountUsername.length < this.siteSettings.min_username_length) {
return EmberObject.create(
Object.assign(failedAttrs, {
reason: I18n.t("user.username.too_short"),
})
);
}
// If too long
if (accountUsername.length > this.maxUsernameLength) {
return EmberObject.create(
Object.assign(failedAttrs, {
reason: I18n.t("user.username.too_long"),
})
);
}
this.checkUsernameAvailability();
// Let's check it out asynchronously
return EmberObject.create(
Object.assign(failedAttrs, {
reason: I18n.t("user.username.checking"),
})
);
this.set("usernameValidation", result);
},
shouldCheckUsernameAvailability() {
return (
!isEmpty(this.accountUsername) &&
this.accountUsername.length >= this.minUsernameLength
);
basicUsernameValidation(username) {
if (username && username === this.prefilledUsername) {
return validResult({ reason: I18n.t("user.username.prefilled") });
}
if (isEmpty(username)) {
return failedResult({ message: I18n.t("user.username.required") });
}
if (username.length < this.siteSettings.min_username_length) {
return failedResult({ reason: I18n.t("user.username.too_short") });
}
if (username.length > this.maxUsernameLength) {
return failedResult({ reason: I18n.t("user.username.too_long") });
}
return failedResult({
shouldCheck: true,
reason: I18n.t("user.username.checking"),
});
},
checkUsernameAvailability() {
discourseDebounce(
this,
function () {
if (this.shouldCheckUsernameAvailability()) {
return User.checkUsername(
this.accountUsername,
this.accountEmail
).then((result) => {
return User.checkUsername(this.accountUsername, this.accountEmail).then(
(result) => {
this.set("isDeveloper", false);
if (result.available) {
if (result.is_developer) {
this.set("isDeveloper", true);
}
return this.set(
"uniqueUsernameValidation",
EmberObject.create({
ok: true,
reason: I18n.t("user.username.available"),
})
"usernameValidation",
validResult({ reason: I18n.t("user.username.available") })
);
} else {
const failedAttrs = {
failed: true,
element: document.querySelector("#new-account-username"),
};
if (result.suggestion) {
return this.set(
"uniqueUsernameValidation",
EmberObject.create(
Object.assign(failedAttrs, {
reason: I18n.t("user.username.not_available", result),
})
)
"usernameValidation",
failedResult({
reason: I18n.t("user.username.not_available", result),
})
);
} else {
return this.set(
"uniqueUsernameValidation",
EmberObject.create(
Object.assign(failedAttrs, {
reason: result.errors
? result.errors.join(" ")
: I18n.t("user.username.not_available_no_suggestion"),
})
)
"usernameValidation",
failedResult({
reason: result.errors
? result.errors.join(" ")
: I18n.t("user.username.not_available_no_suggestion"),
})
);
}
}
});
}
}
);
},
500
);
},
// Actually wait for the async name check before we're 100% sure we're good to go
@discourseComputed("uniqueUsernameValidation", "basicUsernameValidation")
usernameValidation() {
const basicValidation = this.basicUsernameValidation;
const uniqueUsername = this.uniqueUsernameValidation;
return uniqueUsername ? uniqueUsername : basicValidation;
},
});

View File

@ -21,6 +21,15 @@ export function clearAppEventsCache(container) {
}
export default Service.extend(Evented, {
init() {
this._super(...arguments);
// A hack because we don't make `current user` properly via container in testing mode
if (this.currentUser) {
this.currentUser.appEvents = this;
}
},
on() {
if (arguments.length === 2) {
let [name, fn] = arguments;

View File

@ -28,7 +28,6 @@ import { moduleFor } from "ember-qunit";
import { resetCustomPostMessageCallbacks } from "discourse/controllers/topic";
import { resetDecorators } from "discourse/widgets/widget";
import { resetCache as resetOneboxCache } from "pretty-text/oneboxer";
import { resetPluginApi } from "discourse/lib/plugin-api";
import { resetDecorators as resetPluginOutletDecorators } from "discourse/components/plugin-connector";
import { resetDecorators as resetPostCookedDecorators } from "discourse/widgets/post-cooked";
import { resetTopicTitleDecorators } from "discourse/components/topic-title";
@ -197,7 +196,6 @@ export function acceptance(name, optionsOrCallback) {
clearOutletCache();
clearHTMLCache();
resetPluginApi();
if (siteChanges) {
resetSite(currentSettings(), siteChanges);
@ -231,7 +229,6 @@ export function acceptance(name, optionsOrCallback) {
resetExtraClasses();
clearOutletCache();
clearHTMLCache();
resetPluginApi();
clearRewrites();
initSearchData();
resetDecorators();

View File

@ -8,13 +8,10 @@ discourseModule("Unit | Controller | create-account", function () {
const controller = await this.owner.lookup("controller:create-account");
controller.set("accountUsername", username);
let validation = controller.basicUsernameValidation(username);
assert.ok(validation.failed, "username should be invalid: " + username);
assert.equal(
controller.get("basicUsernameValidation.failed"),
true,
"username should be invalid: " + username
);
assert.equal(
controller.get("basicUsernameValidation.reason"),
validation.reason,
expectedReason,
"username validation reason: " + username + ", " + expectedReason
);
@ -33,13 +30,10 @@ discourseModule("Unit | Controller | create-account", function () {
prefilledUsername: "porkchops",
});
let validation = controller.basicUsernameValidation("porkchops");
assert.ok(validation.ok, "Prefilled username is valid");
assert.equal(
controller.get("basicUsernameValidation.ok"),
true,
"Prefilled username is valid"
);
assert.equal(
controller.get("basicUsernameValidation.reason"),
validation.reason,
I18n.t("user.username.prefilled"),
"Prefilled username is valid"
);