DEV: Introduce API for rendering Glimmer inside posts (#20140)

`helper.renderGlimmer` will return an HTML element which can be added to a post's `cooked`

Example usage:
```
import { hbs } from "ember-cli-htmlbars";

api.decorateCookedElement((cooked, helper) => {
  const glimmerElement = helper.renderGlimmer(
    "div.my-wrapper-class",
    hbs`<DButton @icon={{@data.param}} @translatedLabel="Hello world from Glimmer Component"/>`,
    { param: "user-plus" }
  );
  cooked.appendChild(glimmerElement);
}, { onlyStream: true, id: "my-id" });
```

See `widgets/render-glimmer.js` for more detailed usage information.
This commit is contained in:
David Taylor 2023-02-02 16:25:57 +00:00 committed by GitHub
parent adbf69c300
commit ca2b2d034f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 98 additions and 0 deletions

View File

@ -2,6 +2,7 @@ import Connector from "discourse/widgets/connector";
import PostCooked from "discourse/widgets/post-cooked";
import RawHtml from "discourse/widgets/raw-html";
import { h } from "virtual-dom";
import RenderGlimmer from "discourse/widgets/render-glimmer";
class DecoratorHelper {
constructor(widget, attrs, state) {
@ -106,6 +107,44 @@ class DecoratorHelper {
connect(details) {
return new Connector(this.widget, details);
}
/**
* Returns an element containing a rendered glimmer template. For full usage instructions,
* see `widgets/render-glimmer.js`.
*
* Example usage:
*
* ```
* import { hbs } from "ember-cli-htmlbars";
*
* api.decorateCookedElement((cooked, helper) => {
* const glimmerElement = helper.renderGlimmer(
* "div.my-wrapper-class",
* hbs`<DButton @icon={{@data.param}} @translatedLabel="Hello world from Glimmer Component"/>`,
* { param: "user-plus" }
* );
* cooked.appendChild(glimmerElement);
* }, { onlyStream: true, id: "my-id" });
* ```
*
*/
renderGlimmer(tagName, template, data) {
if (!this.widget.postContentsDestroyCallbacks) {
throw "renderGlimmer can only be used in the context of a post";
}
const renderGlimmer = new RenderGlimmer(
this.widget,
tagName,
template,
data
);
renderGlimmer.init();
this.widget.postContentsDestroyCallbacks.push(
renderGlimmer.destroy.bind(renderGlimmer)
);
return renderGlimmer.element;
}
}
DecoratorHelper.prototype.h = h;

View File

@ -629,6 +629,14 @@ createWidget("post-contents", {
controller.setProperties({ topic, post });
});
},
init() {
this.postContentsDestroyCallbacks = [];
},
destroy() {
this.postContentsDestroyCallbacks.forEach((c) => c());
},
});
createWidget("post-notice", {

View File

@ -0,0 +1,51 @@
import Component from "@glimmer/component";
import { hbs } from "ember-cli-htmlbars";
import { setComponentTemplate } from "@ember/component";
import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
import { visit } from "@ember/test-helpers";
import { withPluginApi } from "discourse/lib/plugin-api";
acceptance("Acceptance | decorateCookedElement", function () {
test("decorator with renderGlimmer works", async function (assert) {
class DemoComponent extends Component {
static eventLog = [];
constructor() {
DemoComponent.eventLog.push("created");
return super(...arguments);
}
willDestroy() {
DemoComponent.eventLog.push("willDestroy");
}
}
setComponentTemplate(
hbs`<span class='glimmer-component-content'>Hello world</span>`,
DemoComponent
);
withPluginApi(0, (api) => {
api.decorateCookedElement((cooked, helper) => {
if (helper.getModel().post_number !== 1) {
return;
}
cooked.appendChild(
helper.renderGlimmer(
"div.glimmer-wrapper",
hbs`<@data.component />`,
{ component: DemoComponent }
)
);
});
});
await visit("/t/internationalization-localization/280");
assert.dom("div.glimmer-wrapper").exists();
assert.dom("span.glimmer-component-content").exists();
assert.deepEqual(DemoComponent.eventLog, ["created"]);
await visit("/");
assert.deepEqual(DemoComponent.eventLog, ["created", "willDestroy"]);
});
});