FEATURE: adds a new plugin api to decorate plugin outlets (#8937)

```
api.decoratePluginOutlet(
  "discovery-list-container-top",
  elem => {
    if (elem.classList.contains("foo")) {
      elem.style.backgroundColor = "yellow";
    }
  }
);
```
This commit is contained in:
Joffrey JAFFEUX 2020-02-13 23:44:34 +01:00 committed by GitHub
parent 2136d4b5d5
commit 6405159484
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 115 additions and 1 deletions

View File

@ -2,6 +2,19 @@ import Component from "@ember/component";
import { defineProperty, computed } from "@ember/object";
import deprecated from "discourse-common/lib/deprecated";
import { buildArgsWithDeprecations } from "discourse/lib/plugin-connectors";
import { afterRender } from "discourse-common/utils/decorators";
let _decorators = {};
// Don't call this directly: use `plugin-api/decoratePluginOutlet`
export function addPluginOutletDecorator(outletName, callback) {
_decorators[outletName] = _decorators[outletName] || [];
_decorators[outletName].push(callback);
}
export function resetDecorators() {
_decorators = {};
}
export default Component.extend({
init() {
@ -45,6 +58,19 @@ export default Component.extend({
connectorClass.setupComponent.call(this, merged, this);
},
didReceiveAttrs() {
this._super(...arguments);
this._decoratePluginOutlets();
},
@afterRender
_decoratePluginOutlets() {
(_decorators[this.connector.outletName] || []).forEach(dec =>
dec(this.element, this.args)
);
},
willDestroyElement() {
this._super(...arguments);

View File

@ -1,6 +1,7 @@
import deprecated from "discourse-common/lib/deprecated";
import { iconNode } from "discourse-common/lib/icon-library";
import { addDecorator } from "discourse/widgets/post-cooked";
import { addPluginOutletDecorator } from "discourse/components/plugin-connector";
import ComposerEditor from "discourse/components/composer-editor";
import DiscourseBanner from "discourse/components/discourse-banner";
import { addButton } from "discourse/widgets/post-menu";
@ -51,7 +52,7 @@ import Composer from "discourse/models/composer";
import { on } from "@ember/object/evented";
// If you add any methods to the API ensure you bump up this number
const PLUGIN_API_VERSION = "0.8.37";
const PLUGIN_API_VERSION = "0.8.38";
class PluginApi {
constructor(version, container) {
@ -983,6 +984,29 @@ class PluginApi {
addGlobalNotice(id, text, options) {
addGlobalNotice(id, text, options);
}
/**
* Used for decorating the rendered HTML content of a plugin-outlet after it's been rendered
*
* `callback` will be called when it is time to decorate it.
*
* For example, to add a yellow background to a connector:
*
* ```
* api.decoratePluginOutlet(
* "discovery-list-container-top",
* (elem, args) => {
* if (elem.classList.contains("foo")) {
* elem.style.backgroundColor = "yellow";
* }
* }
* );
* ```
*
**/
decoratePluginOutlet(outletName, callback, opts) {
addPluginOutletDecorator(outletName, callback, opts || {});
}
}
let _pluginv01;

View File

@ -73,6 +73,7 @@ function buildConnectorCache() {
_connectorCache[outletName] = _connectorCache[outletName] || [];
_connectorCache[outletName].push({
outletName,
templateName: resource.replace("javascripts/", ""),
template: Ember.TEMPLATES[resource],
classNames: `${outletName}-outlet ${uniqueName}`,

View File

@ -0,0 +1,61 @@
import { acceptance } from "helpers/qunit-helpers";
import { withPluginApi } from "discourse/lib/plugin-api";
const PREFIX = "javascripts/single-test/connectors";
acceptance("Plugin Outlet - Decorator", {
loggedIn: true,
beforeEach() {
Ember.TEMPLATES[
`${PREFIX}/discovery-list-container-top/foo`
] = Ember.HTMLBars.compile("FOO");
Ember.TEMPLATES[
`${PREFIX}/discovery-list-container-top/bar`
] = Ember.HTMLBars.compile("BAR");
withPluginApi("0.8.38", api => {
api.decoratePluginOutlet(
"discovery-list-container-top",
(elem, args) => {
if (elem.classList.contains("foo")) {
elem.style.backgroundColor = "yellow";
if (args.category) {
elem.classList.add("in-category");
} else {
elem.classList.remove("in-category");
}
}
},
{ id: "yellow-decorator" }
);
});
},
afterEach() {
delete Ember.TEMPLATES[`${PREFIX}/discovery-list-container-top/foo`];
delete Ember.TEMPLATES[`${PREFIX}/discovery-list-container-top/bar`];
}
});
QUnit.test(
"Calls the plugin callback with the rendered outlet",
async assert => {
await visit("/");
const fooConnector = find(".discovery-list-container-top-outlet.foo ")[0];
const barConnector = find(".discovery-list-container-top-outlet.bar ")[0];
assert.ok(exists(fooConnector));
assert.equal(fooConnector.style.backgroundColor, "yellow");
assert.equal(barConnector.style.backgroundColor, "");
await visit("/c/bug");
assert.ok(fooConnector.classList.contains("in-category"));
await visit("/");
assert.notOk(fooConnector.classList.contains("in-category"));
}
);

View File

@ -18,6 +18,7 @@ import { initSearchData } from "discourse/widgets/search-menu";
import { resetDecorators } from "discourse/widgets/widget";
import { resetWidgetCleanCallbacks } from "discourse/components/mount-widget";
import { resetDecorators as resetPostCookedDecorators } from "discourse/widgets/post-cooked";
import { resetDecorators as resetPluginOutletDecorators } from "discourse/components/plugin-connector";
import { resetCache as resetOneboxCache } from "pretty-text/oneboxer";
import { resetCustomPostMessageCallbacks } from "discourse/controllers/topic";
import User from "discourse/models/user";
@ -128,6 +129,7 @@ export function acceptance(name, options) {
initSearchData();
resetDecorators();
resetPostCookedDecorators();
resetPluginOutletDecorators();
resetOneboxCache();
resetCustomPostMessageCallbacks();
Discourse._runInitializer("instanceInitializers", function(