mirror of
https://github.com/discourse/discourse.git
synced 2026-08-01 09:08:08 -05:00
DEV: Update Ember to 6.6.0 (#34460)
This upgrade does not include any breaking changes for Discourse themes/plugins. Two of the three deprecations in Ember 6 (array prototype extensions, component-template resolution) have already been polyfilled in Discourse. The third (action helper/modifier) is polyfilled in this commit. Performance testing shows a 2-3% improvement in Discourse rendering time, thanks to upstream performance fixes in the glimmer-vm since the regressions in the Ember 5.x series. --------- Co-authored-by: David Taylor <david@taylorhq.com>
This commit is contained in:
co-authored by
David Taylor
parent
2daf45e5e5
commit
06f841da08
@@ -181,7 +181,6 @@ jobs:
|
||||
run: rm -rf public/uploads && cp -r tmp/app-cache/uploads public/uploads
|
||||
|
||||
- name: Create and migrate database
|
||||
if: steps.app-cache.outputs.cache-hit != 'true'
|
||||
env:
|
||||
LOAD_PLUGINS: ${{ (matrix.target == 'plugins' || matrix.target == 'chat') && '1' || '0' }}
|
||||
run: |
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"devDependencies": {
|
||||
"@ember/optional-features": "^2.2.0",
|
||||
"@embroider/test-setup": "^4.0.0",
|
||||
"@glimmer/component": "^1.1.2",
|
||||
"@glimmer/component": "^2.0.0",
|
||||
"@types/jquery": "^3.5.33",
|
||||
"@types/qunit": "^2.19.13",
|
||||
"@types/rsvp": "^4.0.9",
|
||||
@@ -35,7 +35,7 @@
|
||||
"ember-disable-prototype-extensions": "^1.1.3",
|
||||
"ember-load-initializers": "^3.0.1",
|
||||
"ember-resolver": "^13.1.1",
|
||||
"ember-source": "~5.12.0",
|
||||
"ember-source": "~6.6.0",
|
||||
"ember-source-channel-url": "^3.0.0",
|
||||
"loader.js": "^4.7.0",
|
||||
"webpack": "^5.101.3"
|
||||
|
||||
@@ -8,6 +8,7 @@ const fs = require("fs");
|
||||
const concat = require("broccoli-concat");
|
||||
const DiscoursePluginColocatedTemplateProcessor = require("./colocated-template-compiler");
|
||||
const EmberApp = require("ember-cli/lib/broccoli/ember-app");
|
||||
const transformActionSyntax = require("./transform-action-syntax");
|
||||
|
||||
function fixLegacyExtensions(tree) {
|
||||
return new Funnel(tree, {
|
||||
@@ -103,6 +104,28 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
|
||||
setupPreprocessorRegistry(type, registry) {
|
||||
if (type === "self") {
|
||||
const plugin = this._buildActionPlugin();
|
||||
plugin.parallelBabel = {
|
||||
requireFile: __filename,
|
||||
buildUsing: "_buildActionPlugin",
|
||||
params: {},
|
||||
};
|
||||
|
||||
registry.add("htmlbars-ast-plugin", plugin);
|
||||
}
|
||||
},
|
||||
|
||||
_buildActionPlugin() {
|
||||
return {
|
||||
name: "transform-action-syntax",
|
||||
plugin: transformActionSyntax,
|
||||
baseDir: transformActionSyntax.baseDir,
|
||||
cacheKey: transformActionSyntax.cacheKey,
|
||||
};
|
||||
},
|
||||
|
||||
pluginInfos() {
|
||||
const root = path.resolve("../../../../plugins");
|
||||
const pluginDirectories = fs
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// backported from https://github.com/emberjs/ember.js/blob/v5.12.0/packages/ember-template-compiler/lib/plugins/transform-action-syntax.ts
|
||||
|
||||
/**
|
||||
A Glimmer2 AST transformation that replaces all instances of
|
||||
|
||||
```handlebars
|
||||
<button {{action 'foo'}}>
|
||||
<button onblur={{action 'foo'}}>
|
||||
<button onblur={{action (action 'foo') 'bar'}}>
|
||||
```
|
||||
|
||||
with
|
||||
|
||||
```handlebars
|
||||
<button {{action this 'foo'}}>
|
||||
<button onblur={{action this 'foo'}}>
|
||||
<button onblur={{action this (action this 'foo') 'bar'}}>
|
||||
```
|
||||
|
||||
@private
|
||||
@class TransformActionSyntax
|
||||
*/
|
||||
|
||||
function transformActionSyntax({ syntax }) {
|
||||
let { builders: b } = syntax;
|
||||
|
||||
return {
|
||||
name: "transform-action-syntax",
|
||||
|
||||
visitor: {
|
||||
ElementModifierStatement(node) {
|
||||
if (isAction(node)) {
|
||||
insertThisAsFirstParam(node, b);
|
||||
}
|
||||
},
|
||||
|
||||
MustacheStatement(node) {
|
||||
if (isAction(node)) {
|
||||
insertThisAsFirstParam(node, b);
|
||||
}
|
||||
},
|
||||
|
||||
SubExpression(node) {
|
||||
if (isAction(node)) {
|
||||
insertThisAsFirstParam(node, b);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
transformActionSyntax.baseDir = () => __dirname;
|
||||
transformActionSyntax.cacheKey = () => "transform-action-syntax";
|
||||
|
||||
module.exports = transformActionSyntax;
|
||||
|
||||
function isPath(node) {
|
||||
return node.type === "PathExpression";
|
||||
}
|
||||
|
||||
function isAction(node) {
|
||||
return isPath(node.path) && node.path.original === "d-action";
|
||||
}
|
||||
|
||||
function insertThisAsFirstParam(node, builders) {
|
||||
node.params.unshift(builders.path("this"));
|
||||
}
|
||||
@@ -22,7 +22,7 @@
|
||||
"devDependencies": {
|
||||
"@ember/optional-features": "^2.2.0",
|
||||
"@embroider/test-setup": "^4.0.0",
|
||||
"@glimmer/component": "^1.1.2",
|
||||
"@glimmer/component": "^2.0.0",
|
||||
"@glimmer/syntax": "0.93.1",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"ember-cli": "~6.6.0",
|
||||
@@ -32,7 +32,7 @@
|
||||
"ember-disable-prototype-extensions": "^1.1.3",
|
||||
"ember-load-initializers": "^3.0.1",
|
||||
"ember-resolver": "^13.1.1",
|
||||
"ember-source": "~5.12.0",
|
||||
"ember-source": "~6.6.0",
|
||||
"ember-source-channel-url": "^3.0.0",
|
||||
"loader.js": "^4.7.0",
|
||||
"webpack": "^5.101.3"
|
||||
|
||||
@@ -38,7 +38,7 @@ async function loadThemeFromModulePreload(link) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
`Failed to load theme ${link.dataset.themeId} from ${link.href}`,
|
||||
error
|
||||
String(error)
|
||||
);
|
||||
fireThemeErrorEvent({ themeId: link.dataset.themeId, error });
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@ const DEPRECATION_WORKFLOW = [
|
||||
handler: "silence",
|
||||
matchId: "discourse.decorate-widget.hamburger-widget-links",
|
||||
},
|
||||
{
|
||||
handler: "silence",
|
||||
matchId: "deprecate-import-meta-from-ember",
|
||||
},
|
||||
];
|
||||
|
||||
export default DEPRECATION_WORKFLOW;
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import { setInternalHelperManager } from "@glimmer/manager";
|
||||
import {
|
||||
createUnboundRef,
|
||||
isInvokableRef,
|
||||
updateRef,
|
||||
valueForRef,
|
||||
} from "@glimmer/reference";
|
||||
import { get } from "@ember/-internals/metal";
|
||||
import { flaggedInstrument } from "@ember/instrumentation";
|
||||
import { join } from "@ember/runloop";
|
||||
import deprecated from "discourse/lib/deprecated";
|
||||
|
||||
function NOOP(args) {
|
||||
return args;
|
||||
}
|
||||
|
||||
function makeArgsProcessor(valuePathRef, actionArgsRef) {
|
||||
let mergeArgs;
|
||||
|
||||
if (actionArgsRef.length > 0) {
|
||||
mergeArgs = (args) => {
|
||||
return actionArgsRef.map(valueForRef).concat(args);
|
||||
};
|
||||
}
|
||||
|
||||
let readValue;
|
||||
|
||||
if (valuePathRef) {
|
||||
readValue = (args) => {
|
||||
let valuePath = valueForRef(valuePathRef);
|
||||
|
||||
if (valuePath && args.length > 0) {
|
||||
args[0] = get(args[0], valuePath);
|
||||
}
|
||||
|
||||
return args;
|
||||
};
|
||||
}
|
||||
|
||||
if (mergeArgs && readValue) {
|
||||
return (args) => {
|
||||
return readValue(mergeArgs(args));
|
||||
};
|
||||
} else {
|
||||
return mergeArgs || readValue || NOOP;
|
||||
}
|
||||
}
|
||||
|
||||
function makeClosureAction(context, target, action, processArgs) {
|
||||
let self;
|
||||
let fn;
|
||||
|
||||
if (typeof action === "string") {
|
||||
self = target;
|
||||
let value = target.actions?.[action];
|
||||
fn = value;
|
||||
} else if (typeof action === "function") {
|
||||
self = context;
|
||||
fn = action;
|
||||
}
|
||||
|
||||
return (...args) => {
|
||||
let payload = { target: self, args, label: "@glimmer/closure-action" };
|
||||
return flaggedInstrument("interaction.ember-action", payload, () => {
|
||||
return join(self, fn, ...processArgs(args));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function makeDynamicClosureAction(context, targetRef, actionRef, processArgs) {
|
||||
const action = valueForRef(actionRef);
|
||||
|
||||
return (...args) => {
|
||||
return makeClosureAction(
|
||||
context,
|
||||
valueForRef(targetRef),
|
||||
action,
|
||||
processArgs
|
||||
)(...args);
|
||||
};
|
||||
}
|
||||
|
||||
function invokeRef(value) {
|
||||
updateRef(this, value);
|
||||
}
|
||||
|
||||
// a port of ember's builtin action helper
|
||||
export default setInternalHelperManager(({ named, positional }) => {
|
||||
deprecated(
|
||||
`Usage of the \`(action)\` helper is deprecated. Migrate to native functions and function invocation.`,
|
||||
{
|
||||
id: "discourse.template-action",
|
||||
url: "https://deprecations.emberjs.com/id/template-action",
|
||||
}
|
||||
);
|
||||
|
||||
let [context, action, ...restArgs] = positional;
|
||||
|
||||
let target = "target" in named ? named["target"] : context;
|
||||
let processArgs = makeArgsProcessor(
|
||||
("value" in named && named["value"]) || false,
|
||||
restArgs
|
||||
);
|
||||
|
||||
let fn;
|
||||
if (isInvokableRef(action)) {
|
||||
fn = makeClosureAction(action, action, invokeRef, processArgs);
|
||||
} else {
|
||||
fn = makeDynamicClosureAction(
|
||||
valueForRef(context),
|
||||
target,
|
||||
action,
|
||||
processArgs
|
||||
);
|
||||
}
|
||||
|
||||
return createUnboundRef(fn, "(result of an `action` helper)");
|
||||
}, {});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { modifier } from "ember-modifier";
|
||||
import deprecated from "./deprecated";
|
||||
|
||||
/**
|
||||
* Creates a replacement for Ember's built-in `action` modifier that uses
|
||||
@@ -15,6 +16,14 @@ export const actionModifier = modifier(
|
||||
[context, callback, ...args],
|
||||
{ on, bubbles, preventDefault, allowedKeys }
|
||||
) => {
|
||||
deprecated(
|
||||
`Usage of the \`{{action}}\` modifier is deprecated. Migrate to native functions and function invocation.`,
|
||||
{
|
||||
id: "discourse.template-action",
|
||||
url: "https://deprecations.emberjs.com/id/template-action",
|
||||
}
|
||||
);
|
||||
|
||||
const handler = (event) => {
|
||||
let fn;
|
||||
if (typeof callback === "string") {
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
"@embroider/router": "^2.1.8",
|
||||
"@embroider/webpack": "^4.1.0",
|
||||
"@floating-ui/dom": "^1.7.4",
|
||||
"@glimmer/component": "^1.1.2",
|
||||
"@glimmer/component": "^2.0.0",
|
||||
"@glimmer/tracking": "^1.1.2",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@swc/core": "^1.13.5",
|
||||
@@ -123,7 +123,7 @@
|
||||
"ember-load-initializers": "^3.0.1",
|
||||
"ember-modifier": "^4.2.2",
|
||||
"ember-qunit": "^9.0.3",
|
||||
"ember-source": "~5.12.0",
|
||||
"ember-source": "~6.6.0",
|
||||
"ember-template-imports": "^4.3.0",
|
||||
"ember-test-selectors": "^7.1.0",
|
||||
"float-kit": "workspace:1.0.0",
|
||||
|
||||
+106
-61
@@ -123,7 +123,12 @@ module("Integration | Component | plugin-outlet", function (hooks) {
|
||||
});
|
||||
|
||||
test("Renders a template into the outlet", async function (assert) {
|
||||
await render(hbs`<PluginOutlet @name="test-name" />`);
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`<PluginOutlet @name="test-name" />`);
|
||||
}
|
||||
);
|
||||
|
||||
assert
|
||||
.dom(".hello-username")
|
||||
@@ -389,12 +394,17 @@ module("Integration | Component | plugin-outlet", function (hooks) {
|
||||
|
||||
test("Reevaluates shouldRender for argument changes", async function (assert) {
|
||||
this.set("shouldDisplay", false);
|
||||
await render(hbs`
|
||||
<PluginOutlet
|
||||
@name="test-name"
|
||||
@outletArgs={{lazyHash shouldDisplay=this.shouldDisplay}}
|
||||
/>
|
||||
`);
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`
|
||||
<PluginOutlet
|
||||
@name="test-name"
|
||||
@outletArgs={{lazyHash shouldDisplay=this.shouldDisplay}}
|
||||
/>
|
||||
`);
|
||||
}
|
||||
);
|
||||
assert
|
||||
.dom(".conditional-render")
|
||||
.doesNotExist("doesn't render conditional outlet");
|
||||
@@ -405,7 +415,12 @@ module("Integration | Component | plugin-outlet", function (hooks) {
|
||||
});
|
||||
|
||||
test("Reevaluates shouldRender for other autotracked changes", async function (assert) {
|
||||
await render(hbs`<PluginOutlet @name="test-name" />`);
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`<PluginOutlet @name="test-name" />`);
|
||||
}
|
||||
);
|
||||
assert
|
||||
.dom(".conditional-render")
|
||||
.doesNotExist("doesn't render conditional outlet");
|
||||
@@ -416,7 +431,12 @@ module("Integration | Component | plugin-outlet", function (hooks) {
|
||||
});
|
||||
|
||||
test("shouldRender receives an owner argument", async function (assert) {
|
||||
await render(hbs`<PluginOutlet @name="test-name" />`);
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`<PluginOutlet @name="test-name" />`);
|
||||
}
|
||||
);
|
||||
assert
|
||||
.dom(".conditional-render")
|
||||
.doesNotExist("doesn't render conditional outlet");
|
||||
@@ -428,12 +448,17 @@ module("Integration | Component | plugin-outlet", function (hooks) {
|
||||
|
||||
test("Other outlets are not re-rendered", async function (assert) {
|
||||
this.set("shouldDisplay", false);
|
||||
await render(hbs`
|
||||
<PluginOutlet
|
||||
@name="test-name"
|
||||
@outletArgs={{lazyHash shouldDisplay=this.shouldDisplay}}
|
||||
/>
|
||||
`);
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`
|
||||
<PluginOutlet
|
||||
@name="test-name"
|
||||
@outletArgs={{lazyHash shouldDisplay=this.shouldDisplay}}
|
||||
/>
|
||||
`);
|
||||
}
|
||||
);
|
||||
|
||||
find(".hello-username").someUniqueProperty = true;
|
||||
|
||||
@@ -462,15 +487,20 @@ module("Integration | Component | plugin-outlet", function (hooks) {
|
||||
});
|
||||
|
||||
test("deprecated parameters with default message", async function (assert) {
|
||||
await render(
|
||||
<template>
|
||||
<PluginOutlet
|
||||
@name="test-name"
|
||||
@deprecatedArgs={{lazyHash
|
||||
shouldDisplay=(deprecatedOutletArgument value=true)
|
||||
}}
|
||||
/>
|
||||
</template>
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(
|
||||
<template>
|
||||
<PluginOutlet
|
||||
@name="test-name"
|
||||
@deprecatedArgs={{lazyHash
|
||||
shouldDisplay=(deprecatedOutletArgument value=true)
|
||||
}}
|
||||
/>
|
||||
</template>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// deprecated argument still works
|
||||
@@ -490,21 +520,26 @@ module("Integration | Component | plugin-outlet", function (hooks) {
|
||||
});
|
||||
|
||||
test("deprecated parameters with custom deprecation data", async function (assert) {
|
||||
await render(
|
||||
<template>
|
||||
<PluginOutlet
|
||||
@name="test-name"
|
||||
@deprecatedArgs={{lazyHash
|
||||
shouldDisplay=(deprecatedOutletArgument
|
||||
value=true
|
||||
message="The 'shouldDisplay' is deprecated on this test"
|
||||
id="discourse.plugin-connector.deprecated-arg.test"
|
||||
since="3.3.0.beta4-dev"
|
||||
dropFrom="3.4.0"
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</template>
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(
|
||||
<template>
|
||||
<PluginOutlet
|
||||
@name="test-name"
|
||||
@deprecatedArgs={{lazyHash
|
||||
shouldDisplay=(deprecatedOutletArgument
|
||||
value=true
|
||||
message="The 'shouldDisplay' is deprecated on this test"
|
||||
id="discourse.plugin-connector.deprecated-arg.test"
|
||||
since="3.3.0.beta4-dev"
|
||||
dropFrom="3.4.0"
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</template>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// deprecated argument still works
|
||||
@@ -553,18 +588,23 @@ module("Integration | Component | plugin-outlet", function (hooks) {
|
||||
},
|
||||
};
|
||||
|
||||
await render(
|
||||
<template>
|
||||
<PluginOutlet
|
||||
@name="test-name"
|
||||
@deprecatedArgs={{lazyHash
|
||||
shouldDisplay=(deprecatedOutletArgument
|
||||
value=deprecatedData.display
|
||||
silence="discourse.deprecation-that-should-not-be-logged"
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</template>
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(
|
||||
<template>
|
||||
<PluginOutlet
|
||||
@name="test-name"
|
||||
@deprecatedArgs={{lazyHash
|
||||
shouldDisplay=(deprecatedOutletArgument
|
||||
value=deprecatedData.display
|
||||
silence="discourse.deprecation-that-should-not-be-logged"
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</template>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// deprecated argument still works
|
||||
@@ -594,16 +634,21 @@ module("Integration | Component | plugin-outlet", function (hooks) {
|
||||
});
|
||||
|
||||
test("unused arguments", async function (assert) {
|
||||
await render(
|
||||
<template>
|
||||
<PluginOutlet
|
||||
@name="test-name"
|
||||
@outletArgs={{lazyHash shouldDisplay=true}}
|
||||
@deprecatedArgs={{lazyHash
|
||||
argNotUsed=(deprecatedOutletArgument value=true)
|
||||
}}
|
||||
/>
|
||||
</template>
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(
|
||||
<template>
|
||||
<PluginOutlet
|
||||
@name="test-name"
|
||||
@outletArgs={{lazyHash shouldDisplay=true}}
|
||||
@deprecatedArgs={{lazyHash
|
||||
argNotUsed=(deprecatedOutletArgument value=true)
|
||||
}}
|
||||
/>
|
||||
</template>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// deprecated argument still works
|
||||
|
||||
@@ -5,6 +5,7 @@ import { click, doubleClick, render } from "@ember/test-helpers";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import { setupRenderingTest } from "ember-qunit";
|
||||
import { module, test } from "qunit";
|
||||
import { withSilencedDeprecationsAsync } from "discourse/lib/deprecated";
|
||||
|
||||
module("Unit | Lib | ember-action-modifier", function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
@@ -17,9 +18,14 @@ module("Unit | Lib | ember-action-modifier", function (hooks) {
|
||||
childClicked: undefined,
|
||||
});
|
||||
|
||||
await render(hbs`
|
||||
<button id="childButton" {{action this.onChildClick}} />
|
||||
`);
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`
|
||||
<button id="childButton" {{action this.onChildClick}} />
|
||||
`);
|
||||
}
|
||||
);
|
||||
|
||||
await click("#childButton");
|
||||
|
||||
@@ -34,9 +40,14 @@ module("Unit | Lib | ember-action-modifier", function (hooks) {
|
||||
childClicked: undefined,
|
||||
});
|
||||
|
||||
await render(hbs`
|
||||
<button id="childButton" {{action 'onChildClick'}} />
|
||||
`);
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`
|
||||
<button id="childButton" {{action 'onChildClick'}} />
|
||||
`);
|
||||
}
|
||||
);
|
||||
|
||||
await click("#childButton");
|
||||
|
||||
@@ -51,9 +62,14 @@ module("Unit | Lib | ember-action-modifier", function (hooks) {
|
||||
childClicked: undefined,
|
||||
});
|
||||
|
||||
await render(hbs`
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`
|
||||
<button id="childButton" {{action 'onChildClick'}} />
|
||||
`);
|
||||
}
|
||||
);
|
||||
|
||||
await click("#childButton", { ctrlKey: true });
|
||||
|
||||
@@ -68,9 +84,14 @@ module("Unit | Lib | ember-action-modifier", function (hooks) {
|
||||
dblClicked: undefined,
|
||||
});
|
||||
|
||||
await render(hbs`
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`
|
||||
<button id="childButton" {{action this.onDblClick on='dblclick'}} />
|
||||
`);
|
||||
}
|
||||
);
|
||||
|
||||
await doubleClick("#childButton");
|
||||
|
||||
@@ -146,9 +167,14 @@ module("Unit | Lib | ember-action-modifier", function (hooks) {
|
||||
oneClicked: undefined,
|
||||
});
|
||||
|
||||
await render(hbs`
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`
|
||||
<ExampleClassicButton @onDoSomething={{this.onOneClick}} />
|
||||
`);
|
||||
}
|
||||
);
|
||||
|
||||
assert.strictEqual(this.oneClicked, undefined);
|
||||
|
||||
@@ -172,9 +198,14 @@ module("Unit | Lib | ember-action-modifier", function (hooks) {
|
||||
oneClicked: undefined,
|
||||
});
|
||||
|
||||
await render(hbs`
|
||||
<ExampleClassicButtonWithActions @onDoSomething={{this.onOneClick}} />
|
||||
`);
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`
|
||||
<ExampleClassicButtonWithActions @onDoSomething={{this.onOneClick}} />
|
||||
`);
|
||||
}
|
||||
);
|
||||
|
||||
assert.strictEqual(this.oneClicked, undefined);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { click, render } from "@ember/test-helpers";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import { setupRenderingTest } from "ember-qunit";
|
||||
import { module, test } from "qunit";
|
||||
import { withSilencedDeprecationsAsync } from "discourse/lib/deprecated";
|
||||
|
||||
// Configure test-local Classic and Glimmer components that
|
||||
// will be immune from upgrades to actual Discourse components.
|
||||
@@ -210,11 +211,16 @@ module("Unit | Lib | ember-events", function (hooks) {
|
||||
childClicked: undefined,
|
||||
});
|
||||
|
||||
await render(hbs`
|
||||
<ExampleClassicButton id="parentButton" @onClick={{this.onParentClick}}>
|
||||
<button id="childButton" {{action this.onChildClick}} />
|
||||
</ExampleClassicButton>
|
||||
`);
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`
|
||||
<ExampleClassicButton id="parentButton" @onClick={{this.onParentClick}}>
|
||||
<button id="childButton" {{action this.onChildClick}} />
|
||||
</ExampleClassicButton>
|
||||
`);
|
||||
}
|
||||
);
|
||||
|
||||
await click("#childButton");
|
||||
|
||||
@@ -232,11 +238,16 @@ module("Unit | Lib | ember-events", function (hooks) {
|
||||
childClicked: undefined,
|
||||
});
|
||||
|
||||
await render(hbs`
|
||||
<ExampleClassicButton id="parentButton" @onClick={{this.onParentClick}}>
|
||||
<button id="childButton" {{action this.onChildClick bubbles=false}} />
|
||||
</ExampleClassicButton>
|
||||
`);
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`
|
||||
<ExampleClassicButton id="parentButton" @onClick={{this.onParentClick}}>
|
||||
<button id="childButton" {{action this.onChildClick bubbles=false}} />
|
||||
</ExampleClassicButton>
|
||||
`);
|
||||
}
|
||||
);
|
||||
|
||||
await click("#childButton");
|
||||
|
||||
@@ -256,11 +267,16 @@ module("Unit | Lib | ember-events", function (hooks) {
|
||||
childClicked: undefined,
|
||||
});
|
||||
|
||||
await render(hbs`
|
||||
<ExampleGlimmerButton id="parentButton" @onClick={{this.onParentClick}}>
|
||||
<button id="childButton" {{action this.onChildClick}} />
|
||||
</ExampleGlimmerButton>
|
||||
`);
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`
|
||||
<ExampleGlimmerButton id="parentButton" @onClick={{this.onParentClick}}>
|
||||
<button id="childButton" {{action this.onChildClick}} />
|
||||
</ExampleGlimmerButton>
|
||||
`);
|
||||
}
|
||||
);
|
||||
|
||||
await click("#childButton");
|
||||
|
||||
@@ -278,11 +294,16 @@ module("Unit | Lib | ember-events", function (hooks) {
|
||||
childClicked: undefined,
|
||||
});
|
||||
|
||||
await render(hbs`
|
||||
<ExampleGlimmerButton id="parentButton" @onClick={{this.onParentClick}}>
|
||||
<button id="childButton" {{action this.onChildClick bubbles=false}} />
|
||||
</ExampleGlimmerButton>
|
||||
`);
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`
|
||||
<ExampleGlimmerButton id="parentButton" @onClick={{this.onParentClick}}>
|
||||
<button id="childButton" {{action this.onChildClick bubbles=false}} />
|
||||
</ExampleGlimmerButton>
|
||||
`);
|
||||
}
|
||||
);
|
||||
|
||||
await click("#childButton");
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"devDependencies": {
|
||||
"@ember/optional-features": "^2.2.0",
|
||||
"@embroider/test-setup": "^4.0.0",
|
||||
"@glimmer/component": "^1.1.2",
|
||||
"@glimmer/component": "^2.0.0",
|
||||
"@types/jquery": "^3.5.33",
|
||||
"@types/qunit": "^2.19.13",
|
||||
"@types/rsvp": "^4.0.9",
|
||||
@@ -39,7 +39,7 @@
|
||||
"ember-disable-prototype-extensions": "^1.1.3",
|
||||
"ember-load-initializers": "^3.0.1",
|
||||
"ember-resolver": "^13.1.1",
|
||||
"ember-source": "~5.12.0",
|
||||
"ember-source": "~6.6.0",
|
||||
"ember-source-channel-url": "^3.0.0",
|
||||
"loader.js": "^4.7.0",
|
||||
"webpack": "^5.101.3"
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"devDependencies": {
|
||||
"@ember/optional-features": "^2.2.0",
|
||||
"@embroider/test-setup": "^4.0.0",
|
||||
"@glimmer/component": "^1.1.2",
|
||||
"@glimmer/component": "^2.0.0",
|
||||
"@types/jquery": "^3.5.33",
|
||||
"@types/qunit": "^2.19.13",
|
||||
"@types/rsvp": "^4.0.9",
|
||||
@@ -36,7 +36,7 @@
|
||||
"ember-disable-prototype-extensions": "^1.1.3",
|
||||
"ember-load-initializers": "^3.0.1",
|
||||
"ember-resolver": "^13.1.1",
|
||||
"ember-source": "~5.12.0",
|
||||
"ember-source": "~6.6.0",
|
||||
"ember-source-channel-url": "^3.0.0",
|
||||
"loader.js": "^4.7.0",
|
||||
"webpack": "^5.101.3"
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"devDependencies": {
|
||||
"@ember/optional-features": "^2.2.0",
|
||||
"@embroider/test-setup": "^4.0.0",
|
||||
"@glimmer/component": "^1.1.2",
|
||||
"@glimmer/component": "^2.0.0",
|
||||
"@types/jquery": "^3.5.33",
|
||||
"@types/qunit": "^2.19.13",
|
||||
"@types/rsvp": "^4.0.9",
|
||||
@@ -42,7 +42,7 @@
|
||||
"ember-disable-prototype-extensions": "^1.1.3",
|
||||
"ember-load-initializers": "^3.0.1",
|
||||
"ember-resolver": "^13.1.1",
|
||||
"ember-source": "~5.12.0",
|
||||
"ember-source": "~6.6.0",
|
||||
"ember-source-channel-url": "^3.0.0",
|
||||
"loader.js": "^4.7.0",
|
||||
"webpack": "^5.101.3"
|
||||
|
||||
@@ -23,9 +23,10 @@
|
||||
"core-js": "^3.45.1",
|
||||
"decorator-transforms": "^2.3.0",
|
||||
"discourse": "workspace:0.0.0",
|
||||
"discourse-plugins": "workspace:1.0.0",
|
||||
"discourse-widget-hbs": "workspace:1.0.0",
|
||||
"ember-cli-htmlbars": "^6.3.0",
|
||||
"ember-source": "~5.12.0",
|
||||
"ember-source": "~6.6.0",
|
||||
"ember-this-fallback": "^0.4.0",
|
||||
"fastestsmallesttextencoderdecoder": "^1.0.22",
|
||||
"magic-string": "^0.30.18",
|
||||
|
||||
@@ -4,13 +4,14 @@ import { babel, getBabelOutputPlugin } from "@rollup/plugin-babel";
|
||||
import HTMLBarsInlinePrecompile from "babel-plugin-ember-template-compilation";
|
||||
import DecoratorTransforms from "decorator-transforms";
|
||||
import colocatedBabelPlugin from "ember-cli-htmlbars/lib/colocated-babel-plugin";
|
||||
import { precompile } from "ember-source/dist/ember-template-compiler";
|
||||
import EmberThisFallback from "ember-this-fallback";
|
||||
import { memfs } from "memfs";
|
||||
import transformActionSyntax from "discourse-plugins/transform-action-syntax";
|
||||
import { WidgetHbsCompiler } from "discourse-widget-hbs/lib/widget-hbs-compiler";
|
||||
import { browsers } from "../discourse/config/targets";
|
||||
import AddThemeGlobals from "./add-theme-globals";
|
||||
import BabelReplaceImports from "./babel-replace-imports";
|
||||
import { precompile } from "./node_modules/ember-source/dist/ember-template-compiler";
|
||||
import discourseColocation from "./rollup-plugins/discourse-colocation";
|
||||
import discourseExtensionSearch from "./rollup-plugins/discourse-extension-search";
|
||||
import discourseExternalLoader from "./rollup-plugins/discourse-external-loader";
|
||||
@@ -72,6 +73,7 @@ globalThis.rollup = function (modules, opts) {
|
||||
isTheme: true,
|
||||
}).plugin,
|
||||
buildEmberTemplateManipulatorPlugin(opts.themeId),
|
||||
transformActionSyntax,
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
+4
-1
@@ -71,7 +71,7 @@
|
||||
"virtual-dom@2.1.1": "patches/virtual-dom@2.1.1.patch",
|
||||
"licensee@11.1.1": "patches/licensee@11.1.1.patch",
|
||||
"@ember-compat/tracked-built-ins@0.9.1": "patches/@ember-compat__tracked-built-ins@0.9.1.patch",
|
||||
"ember-source@5.12.0": "patches/ember-source@5.12.0.patch"
|
||||
"ember-source@6.6.0": "patches/ember-source@6.6.0.patch"
|
||||
},
|
||||
"peerDependencyRules": {
|
||||
"allowedVersions": {
|
||||
@@ -83,6 +83,9 @@
|
||||
"ignoreMissing": [
|
||||
"webpack"
|
||||
]
|
||||
},
|
||||
"overrides": {
|
||||
"@ember/test-waiters": "4.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
diff --git a/dist/ember-template-compiler.js b/dist/ember-template-compiler.js
|
||||
index 01999af0c4c125af3c713318db7eb85fa492f72d..edadab049d4e5959b4d33ddcbe59bcaec9641e61 100644
|
||||
--- a/dist/ember-template-compiler.js
|
||||
+++ b/dist/ember-template-compiler.js
|
||||
@@ -9485,10 +9485,20 @@ var define, require;
|
||||
return Object.defineProperty(node, "escaped", {
|
||||
enumerable: !1,
|
||||
get() {
|
||||
- return deprecate$1("The escaped property on mustache nodes is deprecated, use trusting instead"), !this.trusting;
|
||||
+ // when upgrading Ember check if this patch is still necessary
|
||||
+ // Ember 5.12 still shipped with the template-compiler generating a ton of warnings but the fix is already commited
|
||||
+ // to glimmervm/syntax
|
||||
+ // See https://github.com/glimmerjs/glimmer-vm/commit/02313376f21a606841b00a7789fd6128a1b292af
|
||||
+ // return deprecate$1("The escaped property on mustache nodes is deprecated, use trusting instead"), !this.trusting;
|
||||
+ return !this.trusting;
|
||||
},
|
||||
set(value) {
|
||||
- deprecate$1("The escaped property on mustache nodes is deprecated, use trusting instead"), this.trusting = !value;
|
||||
+ // when upgrading Ember check if this patch is still necessary
|
||||
+ // Ember 5.12 still shipped with the template-compiler generating a ton of warnings but the fix is already commited
|
||||
+ // to glimmervm/syntax
|
||||
+ // See https://github.com/glimmerjs/glimmer-vm/commit/02313376f21a606841b00a7789fd6128a1b292af
|
||||
+ // deprecate$1("The escaped property on mustache nodes is deprecated, use trusting instead"), this.trusting = !value;
|
||||
+ this.trusting = !value;
|
||||
}
|
||||
}), node;
|
||||
}({
|
||||
diff --git a/dist/packages/@ember/-internals/deprecations/index.js b/dist/packages/@ember/-internals/deprecations/index.js
|
||||
index a6390a7c14f78c4876f97695e057231c1c7ecef0..f62f2d4efbdbf3fb01cd9d1235ac5dc0967a8756 100644
|
||||
--- a/dist/packages/@ember/-internals/deprecations/index.js
|
||||
+++ b/dist/packages/@ember/-internals/deprecations/index.js
|
||||
@@ -142,7 +142,7 @@ function deprecateUntil(message, deprecation) {
|
||||
if (deprecation.isRemoved) {
|
||||
throw new Error(`The API deprecated by ${options.id} was removed in ember-source ${options.until}. The message was: ${message}. Please see ${options.url} for more details.`);
|
||||
}
|
||||
- (isDevelopingApp() && !(deprecation.test) && deprecate(message, deprecation.test, options));
|
||||
+ (true && !(deprecation.test) && deprecate(message, deprecation.test, options));
|
||||
}
|
||||
const {
|
||||
EXTEND_PROTOTYPES
|
||||
diff --git a/dist/packages/@ember/debug/lib/deprecate.js b/dist/packages/@ember/debug/lib/deprecate.js
|
||||
index bd48673cc5a4416b492daded7e1ca4ff5b1a8d8d..9cf7b80e7308dd4b34b5d6a980294de43ce21bd5 100644
|
||||
--- a/dist/packages/@ember/debug/lib/deprecate.js
|
||||
+++ b/dist/packages/@ember/debug/lib/deprecate.js
|
||||
@@ -50,7 +50,7 @@ let missingOptionsDeprecation;
|
||||
let missingOptionsIdDeprecation;
|
||||
let missingOptionDeprecation = () => '';
|
||||
let deprecate = () => {};
|
||||
-if (isDevelopingApp()) {
|
||||
+if (true) {
|
||||
registerHandler = function registerHandler(handler) {
|
||||
registerHandler$1('deprecate', handler);
|
||||
};
|
||||
diff --git a/dist/packages/@ember/debug/lib/handlers.js b/dist/packages/@ember/debug/lib/handlers.js
|
||||
index cd68afe479229ed77b4e4c93759aa90913cdc49c..6e7d71bbeb28f18259fa8afb48ef78279f25c531 100644
|
||||
--- a/dist/packages/@ember/debug/lib/handlers.js
|
||||
+++ b/dist/packages/@ember/debug/lib/handlers.js
|
||||
@@ -3,7 +3,7 @@ import { isDevelopingApp } from '@embroider/macros';
|
||||
let HANDLERS = {};
|
||||
let registerHandler = function registerHandler(_type, _callback) {};
|
||||
let invoke = () => {};
|
||||
-if (isDevelopingApp()) {
|
||||
+if (true) {
|
||||
registerHandler = function registerHandler(type, callback) {
|
||||
let nextHandler = HANDLERS[type] || (() => {});
|
||||
HANDLERS[type] = (message, options) => {
|
||||
@@ -0,0 +1,39 @@
|
||||
diff --git a/dist/packages/@ember/-internals/deprecations/index.js b/dist/packages/@ember/-internals/deprecations/index.js
|
||||
index 894cd5c5e28db30f8798ee828b8364c94d488964..bffb6b1c0d22d3611f8e85414c0f34f0e416e4f4 100644
|
||||
--- a/dist/packages/@ember/-internals/deprecations/index.js
|
||||
+++ b/dist/packages/@ember/-internals/deprecations/index.js
|
||||
@@ -123,7 +123,7 @@ function deprecateUntil(message, deprecation) {
|
||||
if (deprecation.isRemoved) {
|
||||
throw new Error(`The API deprecated by ${options.id} was removed in ember-source ${options.until}. The message was: ${message}. Please see ${options.url} for more details.`);
|
||||
}
|
||||
- (isDevelopingApp() && !(deprecation.test) && deprecate(message, deprecation.test, options));
|
||||
+ (true && !(deprecation.test) && deprecate(message, deprecation.test, options));
|
||||
}
|
||||
const {
|
||||
EXTEND_PROTOTYPES
|
||||
diff --git a/dist/packages/@ember/debug/lib/deprecate.js b/dist/packages/@ember/debug/lib/deprecate.js
|
||||
index 2a40f7cca607ab80beb267d79243fc30f16c92a1..9c15e205f3fb1661b8f1ae397c2338c42a3cc601 100644
|
||||
--- a/dist/packages/@ember/debug/lib/deprecate.js
|
||||
+++ b/dist/packages/@ember/debug/lib/deprecate.js
|
||||
@@ -50,7 +50,7 @@ let missingOptionsDeprecation;
|
||||
let missingOptionsIdDeprecation;
|
||||
let missingOptionDeprecation = () => '';
|
||||
let deprecate = () => {};
|
||||
-if (isDevelopingApp()) {
|
||||
+if (true) {
|
||||
registerHandler = function registerHandler(handler) {
|
||||
registerHandler$1('deprecate', handler);
|
||||
};
|
||||
diff --git a/dist/packages/@ember/debug/lib/handlers.js b/dist/packages/@ember/debug/lib/handlers.js
|
||||
index cd68afe479229ed77b4e4c93759aa90913cdc49c..6e7d71bbeb28f18259fa8afb48ef78279f25c531 100644
|
||||
--- a/dist/packages/@ember/debug/lib/handlers.js
|
||||
+++ b/dist/packages/@ember/debug/lib/handlers.js
|
||||
@@ -3,7 +3,7 @@ import { isDevelopingApp } from '@embroider/macros';
|
||||
let HANDLERS = {};
|
||||
let registerHandler = function registerHandler(_type, _callback) {};
|
||||
let invoke = () => {};
|
||||
-if (isDevelopingApp()) {
|
||||
+if (true) {
|
||||
registerHandler = function registerHandler(type, callback) {
|
||||
let nextHandler = HANDLERS[type] || (() => {});
|
||||
HANDLERS[type] = (message, options) => {
|
||||
@@ -0,0 +1,66 @@
|
||||
// eslint-disable-next-line ember/no-classic-components
|
||||
import Component from "@ember/component";
|
||||
import { action } from "@ember/object";
|
||||
import { click, render } from "@ember/test-helpers";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import { module, test } from "qunit";
|
||||
import { withSilencedDeprecationsAsync } from "discourse/lib/deprecated";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
|
||||
class FooString extends Component {
|
||||
layout = hbs`<button {{on "click" (action "bar" 123)}}>test</button>`;
|
||||
|
||||
@action
|
||||
bar(value) {
|
||||
this.callback(value);
|
||||
}
|
||||
}
|
||||
|
||||
class FooReference extends Component {
|
||||
layout = hbs`<button {{on "click" (action this.bar)}}>test</button>`;
|
||||
|
||||
@action
|
||||
bar() {
|
||||
this.callback();
|
||||
}
|
||||
}
|
||||
|
||||
// This is a core test but has to be in a plugin since the transform
|
||||
// (that injects `this` into actions' params) is applied only to
|
||||
// themes and plugins
|
||||
module("Integration | Helper | action", function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test("string argument", async function (assert) {
|
||||
this.registry.register("component:foo", FooString);
|
||||
this.callback = (value) => {
|
||||
assert.step("called");
|
||||
assert.strictEqual(value, 123);
|
||||
};
|
||||
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`<Foo @callback={{this.callback}} />`);
|
||||
}
|
||||
);
|
||||
|
||||
await click("button");
|
||||
assert.verifySteps(["called"]);
|
||||
});
|
||||
|
||||
test("reference argument", async function (assert) {
|
||||
this.registry.register("component:foo", FooReference);
|
||||
this.callback = () => assert.step("called");
|
||||
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`<Foo @callback={{this.callback}} />`);
|
||||
}
|
||||
);
|
||||
|
||||
await click("button");
|
||||
assert.verifySteps(["called"]);
|
||||
});
|
||||
});
|
||||
Generated
+204
-352
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
// eslint-disable-next-line ember/no-classic-components
|
||||
import Component from "@ember/component";
|
||||
import { action } from "@ember/object";
|
||||
import { click, render } from "@ember/test-helpers";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import { module, test } from "qunit";
|
||||
import { withSilencedDeprecationsAsync } from "discourse/lib/deprecated";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
|
||||
class FooString extends Component {
|
||||
layout = hbs`<button {{on "click" (action "bar" 123)}}>test</button>`;
|
||||
|
||||
@action
|
||||
bar(value) {
|
||||
this.callback(value);
|
||||
}
|
||||
}
|
||||
|
||||
class FooReference extends Component {
|
||||
layout = hbs`<button {{on "click" (action this.bar)}}>test</button>`;
|
||||
|
||||
@action
|
||||
bar() {
|
||||
this.callback();
|
||||
}
|
||||
}
|
||||
|
||||
// This is a core test but has to be in a theme since the transform
|
||||
// (that injects `this` into actions' params) is applied only to
|
||||
// themes and plugins
|
||||
module("Integration | Helper | action", function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test("string argument", async function (assert) {
|
||||
this.registry.register("component:foo", FooString);
|
||||
this.callback = (value) => {
|
||||
assert.step("called");
|
||||
assert.strictEqual(value, 123);
|
||||
};
|
||||
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`<Foo @callback={{this.callback}} />`);
|
||||
}
|
||||
);
|
||||
|
||||
await click("button");
|
||||
assert.verifySteps(["called"]);
|
||||
});
|
||||
|
||||
test("reference argument", async function (assert) {
|
||||
this.registry.register("component:foo", FooReference);
|
||||
this.callback = () => assert.step("called");
|
||||
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.template-action",
|
||||
async () => {
|
||||
await render(hbs`<Foo @callback={{this.callback}} />`);
|
||||
}
|
||||
);
|
||||
|
||||
await click("button");
|
||||
assert.verifySteps(["called"]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user