DEV: Introduce a value transformer front-end plugin API (#27090)
This commit introduces the `valueTransformer`API to safely override values defined in Discourse. Two new plugin APIs are introduced: - `addValueTransformerName` which allows plugins and theme-components to add a new valid transformer name if they want to provide overridable values; - `registerValueTransformer` to register a transformer to override values. It also introduces the function `applyValueTransformer` which can be imported from `discourse/lib/transformer`. This function marks the desired value as overridable and applies the transformer logic. How does it work? ## Marking a value as overridable: To mark a value as overridable, in Discourse core, first the transformer name must be added to `app/assets/javascripts/discourse/app/lib/transformer/registry.js`. For plugins and theme-components, use the plugin API `addValueTransformerName` instead. Then, in your component or class, use the function `applyValueTransformer` to mark the value as overridable and handle the logic: - example: ```js export default class HomeLogo extends Component { @service session; @service site; ... get href() { return applyValueTransformer("home-logo-href", getURL("/")); } ``` ## Overriding a value in plugins or themes To override a value in plugins, themes, or TCs use the plugin API `registerValueTransformer`: - Example: ```js withPluginApi("1.34.0", (api) => { api.registerValueTransformer("example-transformer", ({ value }) => { return "new-value"; }); }); ```
This commit is contained in:
parent
e5dac3b422
commit
9668592aab
|
@ -6,20 +6,11 @@ import { service } from "@ember/service";
|
||||||
import PluginOutlet from "discourse/components/plugin-outlet";
|
import PluginOutlet from "discourse/components/plugin-outlet";
|
||||||
import concatClass from "discourse/helpers/concat-class";
|
import concatClass from "discourse/helpers/concat-class";
|
||||||
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
||||||
|
import { applyValueTransformer } from "discourse/lib/transformer";
|
||||||
import DiscourseURL from "discourse/lib/url";
|
import DiscourseURL from "discourse/lib/url";
|
||||||
import getURL from "discourse-common/lib/get-url";
|
import getURL from "discourse-common/lib/get-url";
|
||||||
import HomeLogoContents from "./home-logo-contents";
|
import HomeLogoContents from "./home-logo-contents";
|
||||||
|
|
||||||
let hrefCallback;
|
|
||||||
|
|
||||||
export function registerHomeLogoHrefCallback(callback) {
|
|
||||||
hrefCallback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clearHomeLogoHrefCallback() {
|
|
||||||
hrefCallback = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class HomeLogo extends Component {
|
export default class HomeLogo extends Component {
|
||||||
@service session;
|
@service session;
|
||||||
@service site;
|
@service site;
|
||||||
|
@ -28,11 +19,7 @@ export default class HomeLogo extends Component {
|
||||||
darkModeAvailable = this.session.darkModeAvailable;
|
darkModeAvailable = this.session.darkModeAvailable;
|
||||||
|
|
||||||
get href() {
|
get href() {
|
||||||
if (hrefCallback) {
|
return applyValueTransformer("home-logo-href", getURL("/"));
|
||||||
return hrefCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
return getURL("/");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get showMobileLogo() {
|
get showMobileLogo() {
|
||||||
|
|
|
@ -7,17 +7,18 @@ import {
|
||||||
addExtraUserClasses,
|
addExtraUserClasses,
|
||||||
renderAvatar,
|
renderAvatar,
|
||||||
} from "discourse/helpers/user-avatar";
|
} from "discourse/helpers/user-avatar";
|
||||||
|
import { applyValueTransformer } from "discourse/lib/transformer";
|
||||||
import icon from "discourse-common/helpers/d-icon";
|
import icon from "discourse-common/helpers/d-icon";
|
||||||
import i18n from "discourse-common/helpers/i18n";
|
import i18n from "discourse-common/helpers/i18n";
|
||||||
import UserTip from "../../user-tip";
|
import UserTip from "../../user-tip";
|
||||||
import UserStatusBubble from "./user-status-bubble";
|
import UserStatusBubble from "./user-status-bubble";
|
||||||
|
|
||||||
|
const DEFAULT_AVATAR_SIZE = "medium";
|
||||||
|
|
||||||
export default class Notifications extends Component {
|
export default class Notifications extends Component {
|
||||||
@service currentUser;
|
@service currentUser;
|
||||||
@service siteSettings;
|
@service siteSettings;
|
||||||
|
|
||||||
avatarSize = "medium";
|
|
||||||
|
|
||||||
get avatar() {
|
get avatar() {
|
||||||
const avatarAttrs = addExtraUserClasses(this.currentUser, {});
|
const avatarAttrs = addExtraUserClasses(this.currentUser, {});
|
||||||
return htmlSafe(
|
return htmlSafe(
|
||||||
|
@ -32,6 +33,13 @@ export default class Notifications extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get avatarSize() {
|
||||||
|
return applyValueTransformer(
|
||||||
|
"header-notifications-avatar-size",
|
||||||
|
DEFAULT_AVATAR_SIZE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
get _shouldHighlightAvatar() {
|
get _shouldHighlightAvatar() {
|
||||||
return (
|
return (
|
||||||
!this.currentUser.read_first_notification &&
|
!this.currentUser.read_first_notification &&
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { _freezeValidTransformerNames } from "discourse/lib/transformer";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
before: "inject-discourse-objects",
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
_freezeValidTransformerNames();
|
||||||
|
},
|
||||||
|
};
|
|
@ -13,7 +13,6 @@ import { addCategorySortCriteria } from "discourse/components/edit-category-sett
|
||||||
import { forceDropdownForMenuPanels as glimmerForceDropdownForMenuPanels } from "discourse/components/glimmer-site-header";
|
import { forceDropdownForMenuPanels as glimmerForceDropdownForMenuPanels } from "discourse/components/glimmer-site-header";
|
||||||
import { addGlobalNotice } from "discourse/components/global-notice";
|
import { addGlobalNotice } from "discourse/components/global-notice";
|
||||||
import { headerButtonsDAG } from "discourse/components/header";
|
import { headerButtonsDAG } from "discourse/components/header";
|
||||||
import { registerHomeLogoHrefCallback } from "discourse/components/header/home-logo";
|
|
||||||
import { headerIconsDAG } from "discourse/components/header/icons";
|
import { headerIconsDAG } from "discourse/components/header/icons";
|
||||||
import { _addBulkButton } from "discourse/components/modal/topic-bulk-actions";
|
import { _addBulkButton } from "discourse/components/modal/topic-bulk-actions";
|
||||||
import MountWidget, {
|
import MountWidget, {
|
||||||
|
@ -93,6 +92,10 @@ import {
|
||||||
import { registerCustomTagSectionLinkPrefixIcon } from "discourse/lib/sidebar/user/tags-section/base-tag-section-link";
|
import { registerCustomTagSectionLinkPrefixIcon } from "discourse/lib/sidebar/user/tags-section/base-tag-section-link";
|
||||||
import { consolePrefix } from "discourse/lib/source-identifier";
|
import { consolePrefix } from "discourse/lib/source-identifier";
|
||||||
import { includeAttributes } from "discourse/lib/transform-post";
|
import { includeAttributes } from "discourse/lib/transform-post";
|
||||||
|
import {
|
||||||
|
_addTransformerName,
|
||||||
|
_registerTransformer,
|
||||||
|
} from "discourse/lib/transformer";
|
||||||
import { registerUserMenuTab } from "discourse/lib/user-menu/tab";
|
import { registerUserMenuTab } from "discourse/lib/user-menu/tab";
|
||||||
import { replaceFormatter } from "discourse/lib/utilities";
|
import { replaceFormatter } from "discourse/lib/utilities";
|
||||||
import { addCardClickListenerSelector } from "discourse/mixins/card-contents-base";
|
import { addCardClickListenerSelector } from "discourse/mixins/card-contents-base";
|
||||||
|
@ -110,7 +113,6 @@ import { setNewCategoryDefaultColors } from "discourse/routes/new-category";
|
||||||
import { setNotificationsLimit } from "discourse/routes/user-notifications";
|
import { setNotificationsLimit } from "discourse/routes/user-notifications";
|
||||||
import { addComposerSaveErrorCallback } from "discourse/services/composer";
|
import { addComposerSaveErrorCallback } from "discourse/services/composer";
|
||||||
import { attachAdditionalPanel } from "discourse/widgets/header";
|
import { attachAdditionalPanel } from "discourse/widgets/header";
|
||||||
import { registerHomeLogoHrefCallback as registerHomeLogoHrefCallbackOnWidget } from "discourse/widgets/home-logo";
|
|
||||||
import { addPostClassesCallback } from "discourse/widgets/post";
|
import { addPostClassesCallback } from "discourse/widgets/post";
|
||||||
import { addDecorator } from "discourse/widgets/post-cooked";
|
import { addDecorator } from "discourse/widgets/post-cooked";
|
||||||
import {
|
import {
|
||||||
|
@ -153,7 +155,7 @@ import { modifySelectKit } from "select-kit/mixins/plugin-api";
|
||||||
// docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version
|
// docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version
|
||||||
// using the format described at https://keepachangelog.com/en/1.0.0/.
|
// using the format described at https://keepachangelog.com/en/1.0.0/.
|
||||||
|
|
||||||
export const PLUGIN_API_VERSION = "1.33.0";
|
export const PLUGIN_API_VERSION = "1.34.0";
|
||||||
|
|
||||||
const DEPRECATED_HEADER_WIDGETS = [
|
const DEPRECATED_HEADER_WIDGETS = [
|
||||||
"header",
|
"header",
|
||||||
|
@ -328,6 +330,69 @@ class PluginApi {
|
||||||
return klass;
|
return klass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new valid transformer name.
|
||||||
|
*
|
||||||
|
* Use this API to add a new transformer name that can be used in the `registerValueTransformer` API.
|
||||||
|
*
|
||||||
|
* Notice that this API must be used in a pre-initializer, executed before `freeze-valid-transformers`, otherwise it will throw an error:
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* // pre-initializers/my-transformers.js
|
||||||
|
*
|
||||||
|
* export default {
|
||||||
|
* before: "freeze-valid-transformers",
|
||||||
|
*
|
||||||
|
* initialize() {
|
||||||
|
* withPluginApi("1.33.0", (api) => {
|
||||||
|
* api.addValueTransformerName("my-unique-transformer-name");
|
||||||
|
* }),
|
||||||
|
* },
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* @param name the name of the new transformer
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
addValueTransformerName(name) {
|
||||||
|
_addTransformerName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a transformer to override values defined in Discourse.
|
||||||
|
*
|
||||||
|
* Example: return a static value
|
||||||
|
* ```
|
||||||
|
* api.registerValueTransformer("example-transformer", () => "value");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Example: transform the current value
|
||||||
|
* ```
|
||||||
|
* api.registerValueTransformer("example-transformer", ({value}) => value * 10);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Example: transform the current value based on a context property
|
||||||
|
* ```
|
||||||
|
* api.registerValueTransformer("example-transformer", ({value, context}) => {
|
||||||
|
* if (context.property) {
|
||||||
|
* return value * 10;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* return value;
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param {string} transformerName the name of the transformer
|
||||||
|
* @param {function({value, context})} valueCallback callback to be used to transform the value. To avoid potential
|
||||||
|
* errors or unexpected behavior the callback must be a pure function, i.e. return the transform value instead of
|
||||||
|
* mutating the input value, return the same output for the same input and not have any side effects.
|
||||||
|
* @param {*} valueCallback.value the value to be transformed
|
||||||
|
* @param {*} [valueCallback.context] the optional context in which the value is being transformed
|
||||||
|
*/
|
||||||
|
registerValueTransformer(transformerName, valueCallback) {
|
||||||
|
_registerTransformer(transformerName, valueCallback);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If you want to use custom icons in your discourse application,
|
* If you want to use custom icons in your discourse application,
|
||||||
* you can register a renderer that will return an icon in the
|
* you can register a renderer that will return an icon in the
|
||||||
|
@ -2015,8 +2080,7 @@ class PluginApi {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
registerHomeLogoHrefCallback(callback) {
|
registerHomeLogoHrefCallback(callback) {
|
||||||
registerHomeLogoHrefCallback(callback);
|
_registerTransformer("home-logo-href", ({ value }) => callback(value));
|
||||||
registerHomeLogoHrefCallbackOnWidget(callback); // for compatibility with the legacy header
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,226 @@
|
||||||
|
import { VALUE_TRANSFORMERS } from "discourse/lib/transformer/registry";
|
||||||
|
import { isTesting } from "discourse-common/config/environment";
|
||||||
|
|
||||||
|
// add core transformer names
|
||||||
|
const validCoreTransformerNames = new Set(
|
||||||
|
VALUE_TRANSFORMERS.map((name) => name.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
// do not add anything directly to this set, use addValueTransformerName instead
|
||||||
|
const validPluginTransformerNames = new Set();
|
||||||
|
|
||||||
|
const transformersRegistry = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the registry is open for registration.
|
||||||
|
*
|
||||||
|
* When the registry is closed, the system accepts adding new transformer names and throws an error when trying to
|
||||||
|
* register a transformer.
|
||||||
|
*
|
||||||
|
* When the registry is open, the system will throw an error if a transformer name is added and will accept registering
|
||||||
|
* transformers to be applied.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
let registryOpened = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Freezes the valid transformers list and open the registry to accept new transform registrations.
|
||||||
|
*
|
||||||
|
* INTERNAL API: to be used only in `initializers/freeze-valid-transformers`
|
||||||
|
*/
|
||||||
|
export function _freezeValidTransformerNames() {
|
||||||
|
registryOpened = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new valid transformer name.
|
||||||
|
*
|
||||||
|
* INTERNAL API: use pluginApi.addValueTransformerName instead.
|
||||||
|
*
|
||||||
|
* DO NOT USE THIS FUNCTION TO ADD CORE TRANSFORMER NAMES. Instead register them directly in the
|
||||||
|
* validCoreTransformerNames set above.
|
||||||
|
*
|
||||||
|
* @param {string} name the name to register
|
||||||
|
*/
|
||||||
|
export function _addTransformerName(name) {
|
||||||
|
if (registryOpened) {
|
||||||
|
throw new Error(
|
||||||
|
"api.registerValueTransformer was called when the system is no longer accepting new names to be added.\n" +
|
||||||
|
`Move your code to a pre-initializer that runs before "freeze-valid-transformers" to avoid this error.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validCoreTransformerNames.has(name)) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(
|
||||||
|
`api.addValueTransformerName: transformer "${name}" matches an existing core transformer and shouldn't be re-registered using the the API.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validPluginTransformerNames.has(name)) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(
|
||||||
|
`api.addValueTransformerName: transformer "${name}" is already registered.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
validPluginTransformerNames.add(name.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a value transformer.
|
||||||
|
*
|
||||||
|
* INTERNAL API: use pluginApi.registerValueTransformer instead.
|
||||||
|
*
|
||||||
|
* @param {string} transformerName the name of the transformer
|
||||||
|
* @param {function({value, context})} callback callback that will transform the value.
|
||||||
|
*/
|
||||||
|
export function _registerTransformer(transformerName, callback) {
|
||||||
|
if (!registryOpened) {
|
||||||
|
throw new Error(
|
||||||
|
"api.registerValueTransformer was called while the system was still accepting new transformer names to be added.\n" +
|
||||||
|
`Move your code to an initializer or a pre-initializer that runs after "freeze-valid-transformers" to avoid this error.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!transformerExists(transformerName)) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(
|
||||||
|
`api.registerValueTransformer: transformer "${transformerName}" is unknown and will be ignored. ` +
|
||||||
|
"Perhaps you misspelled it?"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof callback !== "function") {
|
||||||
|
throw new Error(
|
||||||
|
"api.registerValueTransformer requires the valueCallback argument to be a function"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingTransformers = transformersRegistry.get(transformerName) || [];
|
||||||
|
|
||||||
|
existingTransformers.push(callback);
|
||||||
|
|
||||||
|
transformersRegistry.set(transformerName, existingTransformers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a transformer to a value
|
||||||
|
*
|
||||||
|
* @param {string} transformerName the name of the transformer applied
|
||||||
|
* @param {*} defaultValue the default value
|
||||||
|
* @param {*} [context] the optional context to pass to the transformer callbacks.
|
||||||
|
*
|
||||||
|
* @returns {*} the transformed value
|
||||||
|
*/
|
||||||
|
export function applyValueTransformer(transformerName, defaultValue, context) {
|
||||||
|
if (!transformerExists(transformerName)) {
|
||||||
|
throw new Error(
|
||||||
|
`applyValueTransformer: transformer name "${transformerName}" does not exist. Did you forget to register it?`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof (context ?? undefined) !== "undefined" &&
|
||||||
|
!(typeof context === "object" && context.constructor === Object)
|
||||||
|
) {
|
||||||
|
throw (
|
||||||
|
`applyValueTransformer("${transformerName}", ...): context must be a simple JS object or nullish.\n` +
|
||||||
|
"Avoid passing complex objects in the context, like for example, component instances or objects that carry " +
|
||||||
|
"mutable state directly. This can induce users to registry transformers with callbacks causing side effects " +
|
||||||
|
"and mutating the context directly. Inevitably, this leads to fragile integrations."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const transformers = transformersRegistry.get(transformerName);
|
||||||
|
if (!transformers) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newValue = defaultValue;
|
||||||
|
|
||||||
|
const transformerPoolSize = transformers.length;
|
||||||
|
for (let i = 0; i < transformerPoolSize; i++) {
|
||||||
|
const valueCallback = transformers[i];
|
||||||
|
newValue = valueCallback({ value: newValue, context });
|
||||||
|
}
|
||||||
|
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a transformer name exists
|
||||||
|
*
|
||||||
|
* @param {string} name the name to check
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function transformerExists(name) {
|
||||||
|
return (
|
||||||
|
validCoreTransformerNames.has(name) || validPluginTransformerNames.has(name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////// Testing helpers
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the initial state of `registryOpened` to allow the correct reset after a test that needs to manually
|
||||||
|
* override the registry opened state finishes running.
|
||||||
|
*
|
||||||
|
* @type {boolean | null}
|
||||||
|
*/
|
||||||
|
let testRegistryOpenedState = null; // initially set to null bto allow testing if it was initialized
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the transformers registry for registration
|
||||||
|
*
|
||||||
|
* USE ONLY FOR TESTING PURPOSES.
|
||||||
|
*/
|
||||||
|
export function acceptNewTransformerNames() {
|
||||||
|
if (!isTesting()) {
|
||||||
|
throw new Error("Use `acceptNewTransformerNames` only in tests.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testRegistryOpenedState === null) {
|
||||||
|
testRegistryOpenedState = registryOpened;
|
||||||
|
}
|
||||||
|
|
||||||
|
registryOpened = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the transformers registry for registration
|
||||||
|
*
|
||||||
|
* USE ONLY FOR TESTING PURPOSES.
|
||||||
|
*/
|
||||||
|
export function acceptTransformerRegistrations() {
|
||||||
|
if (!isTesting()) {
|
||||||
|
throw new Error("Use `acceptTransformerRegistrations` only in tests.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testRegistryOpenedState === null) {
|
||||||
|
testRegistryOpenedState = registryOpened;
|
||||||
|
}
|
||||||
|
|
||||||
|
registryOpened = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the transformers initial state
|
||||||
|
*
|
||||||
|
* USE ONLY FOR TESTING PURPOSES.
|
||||||
|
*/
|
||||||
|
export function resetTransformers() {
|
||||||
|
if (!isTesting()) {
|
||||||
|
throw new Error("Use `resetTransformers` only in tests.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testRegistryOpenedState !== null) {
|
||||||
|
registryOpened = testRegistryOpenedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
validPluginTransformerNames.clear();
|
||||||
|
transformersRegistry.clear();
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export const VALUE_TRANSFORMERS = Object.freeze([
|
||||||
|
// use only lowercase names
|
||||||
|
"header-notifications-avatar-size",
|
||||||
|
"home-logo-href",
|
||||||
|
]);
|
|
@ -1,22 +1,13 @@
|
||||||
// deprecated in favor of components/header/home-logo.gjs
|
// deprecated in favor of components/header/home-logo.gjs
|
||||||
import { h } from "virtual-dom";
|
import { h } from "virtual-dom";
|
||||||
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
||||||
|
import { applyValueTransformer } from "discourse/lib/transformer";
|
||||||
import DiscourseURL from "discourse/lib/url";
|
import DiscourseURL from "discourse/lib/url";
|
||||||
import Session from "discourse/models/session";
|
import Session from "discourse/models/session";
|
||||||
import { createWidget } from "discourse/widgets/widget";
|
import { createWidget } from "discourse/widgets/widget";
|
||||||
import getURL from "discourse-common/lib/get-url";
|
import getURL from "discourse-common/lib/get-url";
|
||||||
import { iconNode } from "discourse-common/lib/icon-library";
|
import { iconNode } from "discourse-common/lib/icon-library";
|
||||||
|
|
||||||
let hrefCallback;
|
|
||||||
|
|
||||||
export function registerHomeLogoHrefCallback(callback) {
|
|
||||||
hrefCallback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clearHomeLogoHrefCallback() {
|
|
||||||
hrefCallback = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createWidget("home-logo", {
|
export default createWidget("home-logo", {
|
||||||
services: ["session"],
|
services: ["session"],
|
||||||
tagName: "div.title",
|
tagName: "div.title",
|
||||||
|
@ -34,11 +25,10 @@ export default createWidget("home-logo", {
|
||||||
href() {
|
href() {
|
||||||
const href = this.settings.href;
|
const href = this.settings.href;
|
||||||
|
|
||||||
if (hrefCallback) {
|
return applyValueTransformer(
|
||||||
return hrefCallback();
|
"home-logo-href",
|
||||||
}
|
typeof href === "function" ? href() : href
|
||||||
|
);
|
||||||
return typeof href === "function" ? href() : href;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
logoUrl(opts = {}) {
|
logoUrl(opts = {}) {
|
||||||
|
|
|
@ -65,6 +65,7 @@ import {
|
||||||
resetHighestReadCache,
|
resetHighestReadCache,
|
||||||
setTopicList,
|
setTopicList,
|
||||||
} from "discourse/lib/topic-list-tracker";
|
} from "discourse/lib/topic-list-tracker";
|
||||||
|
import { resetTransformers } from "discourse/lib/transformer";
|
||||||
import { clearRewrites } from "discourse/lib/url";
|
import { clearRewrites } from "discourse/lib/url";
|
||||||
import { resetUserMenuTabs } from "discourse/lib/user-menu/tab";
|
import { resetUserMenuTabs } from "discourse/lib/user-menu/tab";
|
||||||
import {
|
import {
|
||||||
|
@ -246,6 +247,7 @@ export function testCleanup(container, app) {
|
||||||
clearPopupMenuOptions();
|
clearPopupMenuOptions();
|
||||||
clearAdditionalAdminSidebarSectionLinks();
|
clearAdditionalAdminSidebarSectionLinks();
|
||||||
resetAdminPluginConfigNav();
|
resetAdminPluginConfigNav();
|
||||||
|
resetTransformers();
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanupCssGeneratorTags() {
|
function cleanupCssGeneratorTags() {
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import { getOwner } from "@ember/application";
|
import { getOwner } from "@ember/application";
|
||||||
import { render } from "@ember/test-helpers";
|
import { render } from "@ember/test-helpers";
|
||||||
import { module, test } from "qunit";
|
import { module, test } from "qunit";
|
||||||
import HomeLogo, {
|
import HomeLogo from "discourse/components/header/home-logo";
|
||||||
clearHomeLogoHrefCallback as clearComponentHomeLogoHrefCallback,
|
|
||||||
} from "discourse/components/header/home-logo";
|
|
||||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
import { query } from "discourse/tests/helpers/qunit-helpers";
|
import { query } from "discourse/tests/helpers/qunit-helpers";
|
||||||
import { clearHomeLogoHrefCallback as clearWidgetHomeLogoHrefCallback } from "discourse/widgets/home-logo";
|
|
||||||
|
|
||||||
const bigLogo = "/images/d-logo-sketch.png?test";
|
const bigLogo = "/images/d-logo-sketch.png?test";
|
||||||
const smallLogo = "/images/d-logo-sketch-small.png?test";
|
const smallLogo = "/images/d-logo-sketch-small.png?test";
|
||||||
|
@ -23,8 +20,6 @@ module("Integration | Component | home-logo", function (hooks) {
|
||||||
this.session = getOwner(this).lookup("service:session");
|
this.session = getOwner(this).lookup("service:session");
|
||||||
this.session.set("darkModeAvailable", null);
|
this.session.set("darkModeAvailable", null);
|
||||||
this.session.set("defaultColorSchemeIsDark", null);
|
this.session.set("defaultColorSchemeIsDark", null);
|
||||||
clearWidgetHomeLogoHrefCallback();
|
|
||||||
clearComponentHomeLogoHrefCallback();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("basics", async function (assert) {
|
test("basics", async function (assert) {
|
||||||
|
|
|
@ -2,12 +2,10 @@
|
||||||
import { getOwner } from "@ember/application";
|
import { getOwner } from "@ember/application";
|
||||||
import { render } from "@ember/test-helpers";
|
import { render } from "@ember/test-helpers";
|
||||||
import { module, test } from "qunit";
|
import { module, test } from "qunit";
|
||||||
import { clearHomeLogoHrefCallback as clearComponentHomeLogoHrefCallback } from "discourse/components/header/home-logo";
|
|
||||||
import MountWidget from "discourse/components/mount-widget";
|
import MountWidget from "discourse/components/mount-widget";
|
||||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
import { count, exists, query } from "discourse/tests/helpers/qunit-helpers";
|
import { count, exists, query } from "discourse/tests/helpers/qunit-helpers";
|
||||||
import { clearHomeLogoHrefCallback as clearWidgetHomeLogoHrefCallback } from "discourse/widgets/home-logo";
|
|
||||||
|
|
||||||
const bigLogo = "/images/d-logo-sketch.png?test";
|
const bigLogo = "/images/d-logo-sketch.png?test";
|
||||||
const smallLogo = "/images/d-logo-sketch-small.png?test";
|
const smallLogo = "/images/d-logo-sketch-small.png?test";
|
||||||
|
@ -23,8 +21,6 @@ module("Integration | Component | Widget | home-logo", function (hooks) {
|
||||||
this.session = getOwner(this).lookup("service:session");
|
this.session = getOwner(this).lookup("service:session");
|
||||||
this.session.set("darkModeAvailable", null);
|
this.session.set("darkModeAvailable", null);
|
||||||
this.session.set("defaultColorSchemeIsDark", null);
|
this.session.set("defaultColorSchemeIsDark", null);
|
||||||
clearWidgetHomeLogoHrefCallback();
|
|
||||||
clearComponentHomeLogoHrefCallback();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("basics", async function (assert) {
|
test("basics", async function (assert) {
|
||||||
|
|
|
@ -0,0 +1,410 @@
|
||||||
|
import EmberObject from "@ember/object";
|
||||||
|
import { setupTest } from "ember-qunit";
|
||||||
|
import { module, test } from "qunit";
|
||||||
|
import sinon from "sinon";
|
||||||
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
|
import {
|
||||||
|
acceptNewTransformerNames,
|
||||||
|
acceptTransformerRegistrations,
|
||||||
|
applyValueTransformer,
|
||||||
|
transformerExists,
|
||||||
|
} from "discourse/lib/transformer";
|
||||||
|
|
||||||
|
module("Unit | Utility | transformers", function (hooks) {
|
||||||
|
setupTest(hooks);
|
||||||
|
|
||||||
|
module("pluginApi.addValueTransformerName", function (innerHooks) {
|
||||||
|
innerHooks.beforeEach(function () {
|
||||||
|
this.consoleWarnStub = sinon.stub(console, "warn");
|
||||||
|
});
|
||||||
|
|
||||||
|
innerHooks.afterEach(function () {
|
||||||
|
this.consoleWarnStub.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("raises an exception if the system is already accepting transformers to registered", function (assert) {
|
||||||
|
// there is no need to freeze the list of valid transformers because that happen when the test application is
|
||||||
|
// initialized in `setupTest`
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
withPluginApi("1.34.0", (api) => {
|
||||||
|
api.addValueTransformerName("whatever");
|
||||||
|
}),
|
||||||
|
/was called when the system is no longer accepting new names to be added/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("warns if name is already registered", function (assert) {
|
||||||
|
acceptNewTransformerNames();
|
||||||
|
|
||||||
|
withPluginApi("1.34.0", (api) => {
|
||||||
|
api.addValueTransformerName("home-logo-href"); // existing core transformer
|
||||||
|
|
||||||
|
// testing warning about core transformers
|
||||||
|
assert.strictEqual(
|
||||||
|
this.consoleWarnStub.calledWith(
|
||||||
|
sinon.match(/matches an existing core transformer/)
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
"logs warning to the console about existing core transformer with the same name"
|
||||||
|
);
|
||||||
|
|
||||||
|
// testing warning about plugin transformers
|
||||||
|
this.consoleWarnStub.reset();
|
||||||
|
|
||||||
|
api.addValueTransformerName("new-plugin-transformer"); // first time should go through
|
||||||
|
assert.strictEqual(
|
||||||
|
this.consoleWarnStub.notCalled,
|
||||||
|
true,
|
||||||
|
"did not log warning to the console"
|
||||||
|
);
|
||||||
|
|
||||||
|
api.addValueTransformerName("new-plugin-transformer"); // second time log a warning
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
this.consoleWarnStub.calledWith(sinon.match(/is already registered/)),
|
||||||
|
true,
|
||||||
|
"logs warning to the console about transformer already added with the same name"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("adds a new transformer name", function (assert) {
|
||||||
|
acceptNewTransformerNames();
|
||||||
|
|
||||||
|
withPluginApi("1.34.0", (api) => {
|
||||||
|
assert.strictEqual(
|
||||||
|
transformerExists("a-new-plugin-transformer"),
|
||||||
|
false,
|
||||||
|
"initially the transformer does not exists"
|
||||||
|
);
|
||||||
|
api.addValueTransformerName("a-new-plugin-transformer"); // second time log a warning
|
||||||
|
assert.strictEqual(
|
||||||
|
transformerExists("a-new-plugin-transformer"),
|
||||||
|
true,
|
||||||
|
"the new transformer was added"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module("pluginApi.registerValueTransformer", function (innerHooks) {
|
||||||
|
innerHooks.beforeEach(function () {
|
||||||
|
this.consoleWarnStub = sinon.stub(console, "warn");
|
||||||
|
});
|
||||||
|
|
||||||
|
innerHooks.afterEach(function () {
|
||||||
|
this.consoleWarnStub.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("raises an exception if the application the system is still waiting for transformer names to be registered", function (assert) {
|
||||||
|
acceptNewTransformerNames();
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
withPluginApi("1.34.0", (api) => {
|
||||||
|
api.registerValueTransformer("whatever", () => "foo"); // the name doesn't really matter at this point
|
||||||
|
}),
|
||||||
|
/was called while the system was still accepting new transformer names/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("warns if transformer is unknown", function (assert) {
|
||||||
|
withPluginApi("1.34.0", (api) => {
|
||||||
|
api.registerValueTransformer("whatever", () => "foo");
|
||||||
|
|
||||||
|
// testing warning about core transformers
|
||||||
|
assert.strictEqual(
|
||||||
|
this.consoleWarnStub.calledWith(
|
||||||
|
sinon.match(/is unknown and will be ignored/)
|
||||||
|
),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("raises an exception if the callback parameter is not a function", function (assert) {
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
withPluginApi("1.34.0", (api) => {
|
||||||
|
api.registerValueTransformer("whatever", "foo");
|
||||||
|
}),
|
||||||
|
/requires the valueCallback argument to be a function/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("registering a new transformer works", function (assert) {
|
||||||
|
acceptNewTransformerNames();
|
||||||
|
|
||||||
|
withPluginApi("1.34.0", (api) => {
|
||||||
|
api.addValueTransformerName("test-transformer");
|
||||||
|
acceptTransformerRegistrations();
|
||||||
|
|
||||||
|
const transformerWasRegistered = (name) =>
|
||||||
|
applyValueTransformer(name, false);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
transformerWasRegistered("test-transformer"),
|
||||||
|
false,
|
||||||
|
"value did not change. transformer is not registered yet"
|
||||||
|
);
|
||||||
|
|
||||||
|
api.registerValueTransformer("test-transformer", () => true);
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
transformerWasRegistered("test-transformer"),
|
||||||
|
true,
|
||||||
|
"the transformer was registered successfully. the value did change."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module("applyValueTransformer", function (innerHooks) {
|
||||||
|
innerHooks.beforeEach(function () {
|
||||||
|
acceptNewTransformerNames();
|
||||||
|
|
||||||
|
withPluginApi("1.34.0", (api) => {
|
||||||
|
api.addValueTransformerName("test-value1-transformer");
|
||||||
|
api.addValueTransformerName("test-value2-transformer");
|
||||||
|
});
|
||||||
|
|
||||||
|
acceptTransformerRegistrations();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("raises an exception if the transformer name does not exist", function (assert) {
|
||||||
|
assert.throws(
|
||||||
|
() => applyValueTransformer("whatever", "foo"),
|
||||||
|
/does not exist. Did you forget to register it/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("accepts only simple objects as context", function (assert) {
|
||||||
|
const notThrows = (testCallback) => {
|
||||||
|
try {
|
||||||
|
testCallback();
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
notThrows(() =>
|
||||||
|
applyValueTransformer("test-value1-transformer", "foo")
|
||||||
|
),
|
||||||
|
"it won't throw an error if context is not passed"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
notThrows(() =>
|
||||||
|
applyValueTransformer("test-value1-transformer", "foo", undefined)
|
||||||
|
),
|
||||||
|
"it won't throw an error if context is undefined"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
notThrows(() =>
|
||||||
|
applyValueTransformer("test-value1-transformer", "foo", null)
|
||||||
|
),
|
||||||
|
"it won't throw an error if context is null"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
notThrows(() =>
|
||||||
|
applyValueTransformer("test-value1-transformer", "foo", {
|
||||||
|
pojo: true,
|
||||||
|
property: "foo",
|
||||||
|
})
|
||||||
|
),
|
||||||
|
"it won't throw an error if context is a POJO"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => applyValueTransformer("test-value1-transformer", "foo", ""),
|
||||||
|
/context must be a simple JS object/,
|
||||||
|
"it will throw an error if context is a string"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => applyValueTransformer("test-value1-transformer", "foo", 0),
|
||||||
|
/context must be a simple JS object/,
|
||||||
|
"it will throw an error if context is a number"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => applyValueTransformer("test-value1-transformer", "foo", false),
|
||||||
|
/context must be a simple JS object/,
|
||||||
|
"it will throw an error if context is a boolean value"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
applyValueTransformer(
|
||||||
|
"test-value1-transformer",
|
||||||
|
"foo",
|
||||||
|
() => "function"
|
||||||
|
),
|
||||||
|
/context must be a simple JS object/,
|
||||||
|
"it will throw an error if context is a function"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
applyValueTransformer(
|
||||||
|
"test-value1-transformer",
|
||||||
|
"foo",
|
||||||
|
EmberObject.create({
|
||||||
|
test: true,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
/context must be a simple JS object/,
|
||||||
|
"it will throw an error if context is an Ember object"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
applyValueTransformer(
|
||||||
|
"test-value1-transformer",
|
||||||
|
"foo",
|
||||||
|
EmberObject.create({
|
||||||
|
test: true,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
/context must be a simple JS object/,
|
||||||
|
"it will throw an error if context is an Ember component"
|
||||||
|
);
|
||||||
|
|
||||||
|
class Testable {}
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
applyValueTransformer(
|
||||||
|
"test-value1-transformer",
|
||||||
|
"foo",
|
||||||
|
new Testable()
|
||||||
|
),
|
||||||
|
/context must be a simple JS object/,
|
||||||
|
"it will throw an error if context is an instance of a class"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("applying the transformer works", function (assert) {
|
||||||
|
class Testable {
|
||||||
|
#value;
|
||||||
|
|
||||||
|
constructor(value) {
|
||||||
|
this.#value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get value1() {
|
||||||
|
return applyValueTransformer("test-value1-transformer", this.#value);
|
||||||
|
}
|
||||||
|
|
||||||
|
get value2() {
|
||||||
|
return applyValueTransformer("test-value2-transformer", this.#value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testObject1 = new Testable(1);
|
||||||
|
const testObject2 = new Testable(2);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
[
|
||||||
|
testObject1.value1,
|
||||||
|
testObject1.value2,
|
||||||
|
testObject2.value1,
|
||||||
|
testObject2.value2,
|
||||||
|
],
|
||||||
|
[1, 1, 2, 2],
|
||||||
|
"it returns the default values when there are no transformers registered"
|
||||||
|
);
|
||||||
|
|
||||||
|
withPluginApi("1.34.0", (api) => {
|
||||||
|
api.registerValueTransformer("test-value1-transformer", ({ value }) => {
|
||||||
|
return value * 10;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
[testObject1.value1, testObject2.value1],
|
||||||
|
[10, 20],
|
||||||
|
"when a transformer was registered, it returns the transformed value"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
[testObject1.value2, testObject2.value2],
|
||||||
|
[1, 2],
|
||||||
|
"transformer names without transformers registered are not affected"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the transformer callback can receive an optional context object", function (assert) {
|
||||||
|
let expectedContext = null;
|
||||||
|
|
||||||
|
withPluginApi("1.34.0", (api) => {
|
||||||
|
api.registerValueTransformer(
|
||||||
|
"test-value1-transformer",
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
({ value, context }) => {
|
||||||
|
expectedContext = context; // this function should be pure, but we're using side effects just for the test
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const value = applyValueTransformer("test-value1-transformer", false, {
|
||||||
|
prop1: true,
|
||||||
|
prop2: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(value, true, "the value was transformed");
|
||||||
|
assert.deepEqual(
|
||||||
|
expectedContext,
|
||||||
|
{
|
||||||
|
prop1: true,
|
||||||
|
prop2: false,
|
||||||
|
},
|
||||||
|
"the callback received the expected context"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("multiple transformers registered for the same name will be applied in sequence", function (assert) {
|
||||||
|
class Testable {
|
||||||
|
get sequence() {
|
||||||
|
return applyValueTransformer("test-value1-transformer", ["r"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testObject = new Testable();
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
testObject.sequence,
|
||||||
|
["r"],
|
||||||
|
`initially the sequence contains only the element "r"`
|
||||||
|
);
|
||||||
|
|
||||||
|
withPluginApi("1.34.0", (api) => {
|
||||||
|
api.registerValueTransformer("test-value1-transformer", ({ value }) => {
|
||||||
|
return ["r", ...value];
|
||||||
|
});
|
||||||
|
api.registerValueTransformer("test-value1-transformer", ({ value }) => {
|
||||||
|
return [...value, "e", "c"];
|
||||||
|
});
|
||||||
|
api.registerValueTransformer("test-value1-transformer", ({ value }) => {
|
||||||
|
return ["o", ...value];
|
||||||
|
});
|
||||||
|
api.registerValueTransformer("test-value1-transformer", ({ value }) => {
|
||||||
|
return ["c", ...value, "t"];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
testObject.sequence.join(""),
|
||||||
|
"correct",
|
||||||
|
`the transformers applied in the expected sequence will produce the word "correct"`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -7,6 +7,11 @@ in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.34.0] - 2024-06-06
|
||||||
|
|
||||||
|
- Added `registerValueTransformer` which allows registering a transformer callback to override values defined in Discourse modules
|
||||||
|
- Added `addValueTransformerName` which allows plugins/TCs to register a new transformer to override values defined in their modules
|
||||||
|
|
||||||
## [1.33.0] - 2024-06-06
|
## [1.33.0] - 2024-06-06
|
||||||
|
|
||||||
- Added `addCustomUserFieldValidationCallback` which allows to set a callback to change the validation and user facing message when attempting to save the signup form.
|
- Added `addCustomUserFieldValidationCallback` which allows to set a callback to change the validation and user facing message when attempting to save the signup form.
|
||||||
|
|
Loading…
Reference in New Issue