Theme Developer Tutorial: 6. Using the JS API

In the last couple of chapters, we’ve explored how to use the JavaScript API to render content into outlets. renderInOutlet is the most commonly-used API, but there are a ton more! In this chapter we’ll try out a few of them, and show you how to discover more.

Common API methods

getCurrentUser()

api.getCurrentUser() will return information about the current user, or null if nobody is logged in. This can be used for all sorts of things, including per-group logic, or rendering a user’s username into the UI.

import { apiInitializer } from "discourse/lib/api";

export default apiInitializer((api) => {
  console.log("Current user is", api.getCurrentUser());
});

headerIcons

api.headerIcons will allow you to add, remove and re-arrange icons in the header. For example, to add a new icon before the search icon, you’d do something like

import DButton from "discourse/components/d-button";
import { apiInitializer } from "discourse/lib/api";

export default apiInitializer((api) => {
  const sayHello = () => {
    alert("Hello, world!");
  };
  api.headerIcons.add(
    "my-unique-icon-name",
    <template>
      <li>
        <DButton
          @action={{sayHello}}
          @icon="wand-magic"
          class="icon btn-flat"
        />
      </li>
    </template>,
    { before: "search" }
  );
});

replaceIcon()

api.replaceIcon(source, destination);

With this method, you can easily replace any Discourse icon with another. For example, we have a theme component that replaces the heart icon for like with a thumbs-up icon

decorateCookedElement()

api.decorateCookedElement() allows you to customize the rendered content of Discourse posts. This can be used for anything from simple formatting changes, all the way up to advanced integrated UIs like the built-in ‘poll’ plugin.

The API should be passed a callback function which will be run for every post when it’s rendered to the screen. The first argument to the callback will be the post’s root HTML element, and the second will be a helper.

A simple example which appends content to every post would look like:

api.decorateCookedElement((element, helper) => {
  const myNewParagraph = document.createElement("p");
  myNewParagraph.textContent = "Hello, this is appended to every post!";
  element.appendChild(myNewParagraph);
});

Or for a more advanced UI, you can render a glimmer component into a post. For example, to render the counter component we authored earlier into every post, you could do something like this:

import { apiInitializer } from "discourse/lib/api";
import CustomWelcomeBanner from "../components/custom-welcome-banner";

export default apiInitializer((api) => {
  api.decorateCookedElement((element, helper) => {
    const counterWrapper = helper.renderGlimmer(
      "div.my-counter",
      CustomWelcomeBanner
    );
    element.appendChild(counterWrapper);
  });
});

helper.getPost() will return the current post, and can be used to build conditional logic into these decorateCookedElement callbacks. console.log the post to see what’s available.

registerValueTransformer()

api.registerValueTransformer allows you to inject logic into predefined parts of the Discourse JavaScript application. For example, you can add a "home-logo-href" transformer to link the logo to example.com:

api.registerValueTransformer("home-logo-href", () => "https://example.com");

For more information on Transformers, check out the dedicated guide

Finding more JS API methods

All the available APIs are listed in the plugin-api.gjs source code in Discourse core, along with a short description and examples.

That’s it for this chapter, and almost the end of the tutorial. Let’s wrap things up in the conclusion.


This document is version controlled - suggest changes on github.

3 个赞

我想只影响特定类别中的帖子。我找不到一种方法来判断一个元素是否属于给定类别。我曾想过可以使用 element.parentElement 并一直向上追溯到 body,但这似乎不起作用。我之前解决这个问题的时候,意识到如果用户属于某个特定组,我就可以知道是否要更改内容,所以可以使用 currentUser,但现在,我正在这样做:

if (!cooked.innerHTML.includes(newFormText)) {
      console.log("fixSubmittedForm no new form text");
      return;
    }

搜索每个帖子的所有内容来决定是否要更改它,这似乎不好,对吧?这是一个托管客户,否则我将通过插件以多种方式解决了这个问题。

1 个赞

在上面 decorateCookedElement 的示例中,您会看到回调函数有两个参数:element 和 helper。

如果您执行 helper.getModel(),您将可以访问 post 模型,该模型包含所有帖子/主题/类别信息。

对于某些示例,我建议在 all-the-* 中搜索“getModel()”。

2 个赞

哦。叹气。恐怕这已经不是你第一次告诉我同样的事情了。

不过,这样应该就行了!非常感谢。

我会尽量不再问了!

编辑:也许这次我学到了!

async function fixSubmittedForm(element, helper, category_id) {
  if (helper.getModel().topic.category_id !== category_id) {
    return;
  }
 .....
3 个赞