DEV: Convert PluginOutlet wrapper to a Glimmer component
PluginConnector remains a Classic Component, so this commit does not require any changes from plugin/theme developers. Two shims are introduced for backwards compatibility: - The component variable passed to shouldRender is replaced with a helperContext instance which includes all the common injections (the new PluginOutlet component instance does not have any of these) - A custom component manager is introduced so that parentView continues to work. Using parentView was never really intended as an API, so it's now deprecated and will print a warning to the console. Users should switch to using the outlet's explicit arguments, or data from a service (e.g. the Router service).
This commit is contained in:
parent
96a6bb69b5
commit
30025a96f3
|
@ -1,8 +1,19 @@
|
|||
import Component from "@ember/component";
|
||||
import GlimmerComponentWithDeprecatedParentView from "discourse/components/glimmer-component-with-deprecated-parent-view";
|
||||
import ClassicComponent from "@ember/component";
|
||||
|
||||
import {
|
||||
buildArgsWithDeprecations,
|
||||
renderedConnectorsFor,
|
||||
} from "discourse/lib/plugin-connectors";
|
||||
import { helperContext } from "discourse-common/lib/helpers";
|
||||
import deprecated from "discourse-common/lib/deprecated";
|
||||
import { get } from "@ember/object";
|
||||
import { cached } from "@glimmer/tracking";
|
||||
|
||||
const PARENT_VIEW_DEPRECATION_MSG =
|
||||
"parentView should not be used within plugin outlets. Use the available outlet arguments, or inject a service which can provide the context you need.";
|
||||
const GET_DEPRECATION_MSG =
|
||||
"Plugin outlet context is no longer an EmberObject - using `get()` is deprecated.";
|
||||
|
||||
/**
|
||||
A plugin outlet is an extension point for templates where other templates can
|
||||
|
@ -13,7 +24,7 @@ import {
|
|||
If your handlebars template has:
|
||||
|
||||
```handlebars
|
||||
{{plugin-outlet name="evil-trout"}}
|
||||
<PluginOutlet @name="evil-trout" />
|
||||
```
|
||||
|
||||
Then any handlebars files you create in the `connectors/evil-trout` directory
|
||||
|
@ -29,26 +40,74 @@ import {
|
|||
|
||||
Will insert <b>Hello World</b> at that point in the template.
|
||||
|
||||
## Disabling
|
||||
|
||||
If a plugin returns a disabled status, the outlets will not be wired up for it.
|
||||
The list of disabled plugins is returned via the `Site` singleton.
|
||||
|
||||
**/
|
||||
export default Component.extend({
|
||||
tagName: "",
|
||||
connectorTagName: "",
|
||||
connectors: null,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
const name = this.name;
|
||||
if (name) {
|
||||
const args = buildArgsWithDeprecations(
|
||||
this.args || {},
|
||||
this.deprecatedArgs || {}
|
||||
);
|
||||
this.set("connectors", renderedConnectorsFor(name, args, this));
|
||||
export default class PluginOutletComponent extends GlimmerComponentWithDeprecatedParentView {
|
||||
context = {
|
||||
...helperContext(),
|
||||
get parentView() {
|
||||
return this.parentView;
|
||||
},
|
||||
get() {
|
||||
deprecated(GET_DEPRECATION_MSG, {
|
||||
id: "discourse.plugin-outlet-context-get",
|
||||
});
|
||||
return get(this, ...arguments);
|
||||
},
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
this.connectors = renderedConnectorsFor(
|
||||
this.args.name,
|
||||
this.outletArgsWithDeprecations,
|
||||
this.context
|
||||
);
|
||||
}
|
||||
|
||||
// Traditionally, pluginOutlets had an argument named 'args'. However, that name is reserved
|
||||
// in recent versions of ember so we need to migrate to outletArgs
|
||||
@cached
|
||||
get outletArgs() {
|
||||
return this.args.outletArgs || this.args.args || {};
|
||||
}
|
||||
|
||||
@cached
|
||||
get outletArgsWithDeprecations() {
|
||||
if (!this.args.deprecatedArgs) {
|
||||
return this.outletArgs;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return buildArgsWithDeprecations(
|
||||
this.outletArgs,
|
||||
this.args.deprecatedArgs || {}
|
||||
);
|
||||
}
|
||||
|
||||
get parentView() {
|
||||
deprecated(`${PARENT_VIEW_DEPRECATION_MSG} (outlet: ${this.args.name})`, {
|
||||
id: "discourse.plugin-outlet-parent-view",
|
||||
});
|
||||
return this._parentView;
|
||||
}
|
||||
set parentView(value) {
|
||||
this._parentView = value;
|
||||
}
|
||||
|
||||
// Older plugin outlets have a `tagName` which we need to preserve for backwards-compatibility
|
||||
get wrapperComponent() {
|
||||
return PluginOutletWithTagNameWrapper;
|
||||
}
|
||||
}
|
||||
|
||||
class PluginOutletWithTagNameWrapper extends ClassicComponent {
|
||||
// Overridden parentView to make this wrapper 'transparent'
|
||||
// Calling this will trigger the deprecation notice in PluginOutletComponent
|
||||
get parentView() {
|
||||
return this._parentView.parentView;
|
||||
}
|
||||
set parentView(value) {
|
||||
this._parentView = value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,28 @@
|
|||
{{#each this.connectors as |c|}}
|
||||
<PluginConnector
|
||||
@connector={{c}}
|
||||
@args={{this.args}}
|
||||
@deprecatedArgs={{this.deprecatedArgs}}
|
||||
@class={{c.classNames}}
|
||||
@tagName={{this.connectorTagName}}
|
||||
/>
|
||||
{{/each}}
|
||||
{{#if @tagName}}
|
||||
{{!
|
||||
Older outlets have a wrapper tagName. RFC0389 proposes an interface for dynamic tag names, which we may want to use in future.
|
||||
But for now, this classic component wrapper takes care of the tagName.
|
||||
}}
|
||||
<this.wrapperComponent @tagName={{@tagName}}>
|
||||
{{#each this.connectors as |c|}}
|
||||
<PluginConnector
|
||||
@connector={{c}}
|
||||
@args={{this.outletArgs}}
|
||||
@deprecatedArgs={{@deprecatedArgs}}
|
||||
@class={{c.classNames}}
|
||||
@tagName={{or @connectorTagName ""}}
|
||||
/>
|
||||
{{/each}}
|
||||
</this.wrapperComponent>
|
||||
{{else}}
|
||||
{{! The modern path: no wrapper element = no classic component }}
|
||||
{{#each this.connectors as |c|}}
|
||||
<PluginConnector
|
||||
@connector={{c}}
|
||||
@args={{this.outletArgs}}
|
||||
@deprecatedArgs={{@deprecatedArgs}}
|
||||
@class={{c.classNames}}
|
||||
@tagName={{or @connectorTagName ""}}
|
||||
/>
|
||||
{{/each}}
|
||||
{{/if}}
|
|
@ -0,0 +1,29 @@
|
|||
import { acceptance, query } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import { test } from "qunit";
|
||||
import { visit } from "@ember/test-helpers";
|
||||
import { withSilencedDeprecationsAsync } from "discourse-common/lib/deprecated";
|
||||
import { registerTemporaryModule } from "discourse/tests/helpers/temporary-module-helper";
|
||||
|
||||
acceptance("Plugin Outlet - Deprecated parentView", function (needs) {
|
||||
needs.hooks.beforeEach(function () {
|
||||
registerTemporaryModule(
|
||||
"discourse/templates/connectors/user-profile-primary/hello",
|
||||
hbs`<span class='hello-username'>{{parentView.parentView.class}}</span>`
|
||||
);
|
||||
});
|
||||
|
||||
test("Can access parentview", async function (assert) {
|
||||
await withSilencedDeprecationsAsync(
|
||||
"discourse.plugin-outlet-parent-view",
|
||||
async () => {
|
||||
await visit("/u/eviltrout");
|
||||
assert.strictEqual(
|
||||
query(".hello-username").innerText,
|
||||
"user-main",
|
||||
"it renders a value from parentView.parentView"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue