Like @tshenry pointed out, what you’re looking for is decorateCooked
For basic changes, something like this works:
api.decorateCooked($elem => $elem.css("background", "yellow"));
If you need something more complicated, you have two choices, you can either use this syntax:
api.decorateCooked($elem => {
// 1
console.log("foo");
// 2
$elem.css("background", "yellow");
// 3
console.log("bar");
});
or create your own jQuery plugins like so:
$.fn.doSomething = function() {
// 1
console.log("foo");
// 2
$(this).css("background", "yellow");
// 3
console.log("bar");
// you have to add this at the end for chainability
return this;
};
api.decorateCooked($elem => $elem.doSomething());
Do note that decorateCooked
runs every time
1- a post is loaded (yes, that means once for every post in the stream).
2- the composer preview is updated - this can be very expensive if you don’t watch out.
What that means is that you have to be mindful and only fire scripts on the posts you want to target.
So, if you don’t want the changes to happen in the composer you would add the onlyStream
option. You would do that with something like this:
$.fn.doSomething = function() {
// do your work
return this;
};
api.decorateCooked($elem => $elem.doSomething(), { onlyStream: true });
This also ensures that your script ignores other places where the post might appear like the user post-stream.
And if you only want to target posts that contain specific elements you can use something like this
$.fn.doSomething = function() {
const targetElement = $(this).children("[data-theme-test]").length;
if (!targetElement) return;
// do your work
return this;
};
api.decorateCooked($elem => $elem.doSomething(), { onlyStream: true });
and that would ensure that your script only fires on the posts that contain the element you want to target and also ignore the composer preview.
If you have external scripts you want to load in order to do some work, you can use loadScript
and you can do that with something like this:
const loadScript = require("discourse/lib/load-script").default;
$.fn.doSomething = function() {
const targetElement = $(this).children("[data-theme-test]").length;
if (!targetElement) return;
loadScript(
"//cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.1.1/iframeResizer.min.js"
).then(() => {
// do your work
});
return this;
};
api.decorateCooked($elem => $elem.doSomething(), { onlyStream: true });
Do note that if that’s the case, you will also need to watch out for CSP issues
Finally, if you need to transverse the dom, you might need a little bit of Ember. By default, everything in cooked
div should be available for jQuery. However, say you want to target one of the parents of cooked, well you can use something like this:
const loadScript = require("discourse/lib/load-script").default;
$.fn.doSomething = function() {
const targetElement = $(this).children("[data-theme-test]").length;
if (!targetElement) return;
Ember.run.scheduleOnce("afterRender", () => {
loadScript(
"//cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.1.1/iframeResizer.min.js"
).then(() => {
// do your work
});
});
return this;
};
api.decorateCooked($elem => $elem.doSomething(), { onlyStream: true });
and that will allow you to fire a script after everything renders and after you load an external script while ignoring posts you don’t want to target and ignoring the composer preview.
Let me know if you need any clarification.