예정된 헤더 변경 – 테마 및 플러그인 준비

최근에 Discourse 헤더를 레거시 ‘위젯’ 렌더링 시스템에서 최신 Glimmer 컴포넌트로 업데이트하는 작업을 진행해 왔습니다. 이 변경 사항은 이제 glimmer header mode 사이트 설정을 통해 Discourse 코어에서 사용할 수 있습니다.

:timer_clock: 대략적인 타임라인

(매우 대략적인 추정치이며, 양방향으로 변경될 수 있음)

2024년 1분기:

  • :white_check_mark: 코어 구현 완료 및 Meta에서 활성화

  • :white_check_mark: 업그레이드 가이드 게시; 콘솔 비추천(deprecation) 메시지 활성화

  • :white_check_mark: 모든 공식 및 제3자 플러그인/테마 업데이트 작업 시작

2024년 2분기:

  • :white_check_mark: 새 헤더 구현을 기본적으로 활성화하기 시작

  • :white_check_mark: 공식 및 제3자 테마/플러그인 업그레이드 준비 완료

  • :white_check_mark: 남은 이슈에 대해 관리자 경고 배너가 트리거되는 비추천 메시지 시작

2024년 3분기:

  • :white_check_mark: 더 넓은 가시성을 위한 공지 토픽 게시: Preparing your community for behind-the-scenes header changes

  • :white_check_mark: 2024년 8월 5일 주 (v3.4.0.beta1): 모든 사이트에서 기본적으로 새 헤더 활성화. 관리자는 ‘glimmer header mode’ 사이트 설정을 전환하여 이전 헤더로 되돌릴 수 있습니다.

  • :white_check_mark: 2024년 9월 2일 주: 기능 플래그 및 레거시 코드 최종 제거

:eyes: 나에게 어떤 의미가 있는가?

플러그인이나 테마가 헤더를 커스터마이징하기 위해 ‘위젯’ API를 사용한다면, 새 헤더와의 호환성을 위해 업데이트가 필요합니다.

:person_tipping_hand: 새 헤더를 어떻게 사용해 볼 수 있는가?

최신 버전의 Discourse에서는 모든 테마/플러그인이 호환되는 경우 새 헤더가 자동으로 활성화됩니다.

테마/플러그인이 호환되지 않는 경우, 레거시 헤더가 계속 사용되며 기존 비추천 메시지와 함께 콘솔에 경고가 출력됩니다. 또한 UI에서 관리자에게 경고 배너가 표시됩니다.

이 자동 시스템이 예상대로 작동하지 않는 극히 드문 경우, glimmer header mode 사이트 설정을 통해 이 '자동 기능 플래그’를 일시적으로 오버라이드할 수 있습니다. 그렇게 할 경우, 이 토픽에서 이유를 알려 주시기 바랍니다.

:technologist: 플러그인/테마를 업데이트해야 하는가?

커스터마이징이 업데이트가 필요한지 확인하려면, 다음 위젯 중 하나에 대해 decorateWidget, changeWidgetSetting, reopenWidget 또는 attachWidgetAction을 사용하는지 확인하십시오:

  • header
  • site-header
  • header-contents
  • header-buttons
  • user-status-bubble
  • sidebar-toggle
  • header-icons
  • header-topic-info
  • header-notifications
  • home-logo
  • user-dropdown

또는 다음 플러그인 API 메서드 중 하나를 사용하는지 확인하십시오:

  • addToHeaderIcons
  • addHeaderPanel

이 모든 것들은 이제 콘솔에 비추천 메시지가 출력되도록 합니다. 비추천 ID는 다음과 같습니다:

  • discourse.add-header-panel
  • discourse.header-widget-overrides

:warning: 인스턴스에서 테마를 하나 이상 사용하는 경우, 모든 테마를 확인하십시오.

관리자 공지

2024년 6월 20일부터, 위 비추천 항목에 대한 관리자 공지를 활성화했습니다.

이 날짜 이후에 배포된 인스턴스에서 현재 플러그인, 테마 또는 테마 컴포넌트가 비추천 경고 중 하나를 트리거하는 경우, 다음 메시지는 관리자*에게만 표시됩니다:

이 메시지는 관리자가 영향받는 커스터마이징을 현대화하기 위해 조치가 필요함을 알리기 위한 것입니다: 레거시 코드베이스를 제거할 때까지 이전 커스터마이징은 계속 작동합니다.

:twisted_rightwards_arrows: 대체 방안은 무엇인가?

각 테마/플러그인은 다르지만, 가장 일반적인 사용 사례에 대한 지침은 다음과 같습니다:

addToHeaderIcons

:information_source: 사용자 정의 헤더 아이콘의 경우, 코드를 제거하고 공식 Custom Header Links (Icons) Theme Component를 설치하는 것을 권장합니다. 요구 사항을 충족하지 못하는 경우, 필요한 코드 변경 사항에 대한 세부 정보를 확인하려면 아래를 참조하십시오:

addToHeaderIcons 플러그인 API는 새로운 headerIcons API를 لصالح으로 비추천되었습니다. 이는 헤더에서 아이콘을 추가, 제거 또는 순서를 변경하는 것을 허용하기 위해 존재합니다. 컴포넌트를 전달해야 합니다.

컴포넌트는 다음과 같이 전달할 수 있습니다:

이전 이후
api.addToHeaderIcons(“widget-foo”) api.headerIcons.add(“foo”, FooIcon)
api.decorateWidget(“header-icons:before”, () => return helper.h(“div”, “widget-foo”)) api.headerIcons.add(“foo”, FooIcon, { before: “search” })
api.decorateWidget(“header-icons:after”, () => return helper.h(“div”, “widget-foo”)) api.headerIcons.add(“foo”, FooComponent, { after: “search” })

이 예제는 Ember의 Template Tag Format (gjs)를 사용하여 컴포넌트를 인라인으로 정의하고 headerButtons.add API에 전달합니다:

// .../discourse/api-initializers/add-my-button.gjs

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

export default apiInitializer("1.0", (api) => {
  api.headerIcons.add("some-unique-name", <template>
    <li><DButton class="icon btn-flat" @href="/u" @icon="address-book" /></li>
  </template>);
});

또는 드롭다운의 경우, <DButton 대신 <DMenu를 사용할 수 있습니다:

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

export default apiInitializer("1.0", (api) => {
  api.headerIcons.add("some-unique-name", <template>
    <li>
      <DMenu class="icon btn-flat" @icon="address-book">
        <DButton @translatedLabel="User 1" @href="/u/user1" />
        <DButton @translatedLabel="User 2" @href="/u/user2" />
        <DButton @translatedLabel="User 3" @href="/u/user3" />
      </DMenu>
    </li>
  </template>);
});

업그레이드 커밋 예제:

decorateWidget("header-buttons:*")

:information_source: 사용자 정의 헤더 링크의 경우, 코드를 제거하고 공식 Custom Header Links Theme Component를 설치하는 것을 권장합니다. 요구 사항을 충족하지 못하는 경우, 필요한 코드 변경 사항에 대한 세부 정보를 확인하려면 아래를 참조하십시오:

header-buttons 위젯은 비추천되었으며, headerButtons 플러그인 API를 도입했습니다. 이는 헤더에서 버튼을 추가, 제거 또는 순서를 변경하는 것을 허용하기 위해 존재합니다. 컴포넌트를 전달해야 합니다.

이전 이후
api.decorateWidget(“header-buttons:before”) api.headerButtons(“button-name”, ButtonComponent, { before: “auth” })
api.decorateWidget(“header-buttons:after”) api.headerButtons(“button-name”, ButtonComponent, { after: “auth” })

헤더 위젯에 대한 changeWidgetSetting(...)

:information_source: changeWidgetSetting의 가장 일반적인 사용은 다음 테마 컴포넌트를 사용하여 달성할 수 있습니다:

이 경우 사용 사례에 맞지 않는다면, 계속 읽어보세요…

헤더 위젯의 일부 커스터마이징은 changeWidgetSetting API를 사용했습니다.

위와 같은 커스터마이징에 대한 직접적인 대체 방안은 없지만, Glimmer 컴포넌트 필드의 작동 방식 때문에 Discourse 3.3.0.beta3에서 이러한 일부 사례를 처리하기 위해 새로운 플러그인 API를 도입했습니다.

registerValueTransformer는 소스 코드에서 오버라이드 가능하도록 태그된 값을 오버라이드하는 데 사용할 수 있으며, 이는 플러그인 아웃렛이 작동하는 방식과 유사한 접근 방식입니다.

소스 코드 베이스에서 공통적으로 발견된 사용 사례에 대해 두 가지 변환기를 이미 추가했습니다:

  • home-logo-href: 홈 로고 앵커의 URL을 오버라이드하는 데 사용할 수 있습니다. 예제는 아래 home-logo 섹션을 참조하십시오.

  • header-notifications-avatar-size: 헤더의 사용자 아바타에 가져온 이미지의 크기를 변경하는 데 사용할 수 있습니다. 예제:

아래의 코드:

api.changeWidgetSetting(
  "header-notifications",
  "avatarSize",
  settings.header_avatars_size
);

다음과 같이 변환됩니다:

api.registerValueTransformer(
  "header-notifications-avatar-size",
  () => settings.header_avatars_size
);

이러한 변환기는 Discourse 소스 코드에 추가되어야 합니다. 다른 것이 필요한 경우, 아래에 사용 사례를 게시하여 알려 주시기 바랍니다.

새로운 값 변환기 API에 대한 자세한 내용은 여기에서 찾을 수 있습니다.

home-logo

home-logo:before 또는 home-logo:after 위젯 장식의 대체로 home-logo 플러그인 아웃렛을 도입했습니다. 커넥터 파일에서 자동 __before__after 명명을 사용하여 사용자 정의 콘텐츠를 배치할 위치를 지정할 수 있습니다.

before/after 커넥터 파일 명명에 대한 자세한 내용은 여기에서 찾을 수 있습니다.

이전 이후
api.decorateWidget(“home-logo:before”) 콘텐츠를 /connectors/home-logo__before로 이동
api.decorateWidget(“header-buttons:after”) 콘텐츠를 /connectors/home-logo__after)로 이동

home-logo 앵커 URL 변경:

매우 일반적인 요구 사항은 home-logo가 링크하는 URL을 변경하는 것입니다. 이를 해결하기 위해 home-logo-href 값 변환기를 도입했습니다. 예제:

  • 정적 URL로 링크를 변경하려면

    api.registerValueTransformer("home-logo-href", () => "https://example.com");
    
  • 현재 사용자를 기반으로 동적 URL을 반환하려면

    api.registerValueTransformer("home-logo-href", () => {
      const currentUser = api.getCurrentUser();
      return `https://example.com/${currentUser.username}`;
    });
    
  • 테마-컴포넌트 설정을 기반으로 URL을 반환하려면

    api.registerValueTransformer("home-logo-href", () => {
      return settings.example_logo_url_setting;
    });
    

:sos: 다른 커스터마이징은 어떻게 되는가?

커스터마이징을 CSS, PluginOutlets 또는 우리가 도입한 새로운 API를 사용하여 달성할 수 없는 경우, 논의하기 위해 새로운 Development 토픽을 만들어 알려 주시기 바랍니다.

:sparkles: 새와 구 헤더를 모두 지원하도록 테마/플러그인을 어떻게 업데이트하는가?

이 문서에 나열된 모든 새로운 API와 플러그인 아웃렛은 새 헤더와 구 헤더 모두에서 지원됩니다. 따라서 현재 테마/플러그인에 한 번만 업데이트하면 사용자는 전환에 대비할 수 있습니다.

But how do I define a FooIcon?

In a plugin I tried creating /assets/javascripts/discourse/components/server-link.js (like for other components that I use in an hbs file)

import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";

export default Component.extend({
// does something have to go here?
});

And a assets/javascripts/discourse/templates/components/server-link.hbs with
this is a link (I figure I can make it be a link if I get this “Hello, world” working)

The above example has const IconWithDropdown = ... but where would that go? I tried putting it in an initializer (where the api.decorateWidget had been) but it doesn’t look like valid javascript to me or ember, best I can tell.

Before I had a headerlinks array and would

        headerLinks.push(
          h(
            `li.headerLink.no-servers`,
            h("a", anchorAttributes, I18n.t("pfaffmanager.no_servers_title"))
          )
        );

to add the links I wanted. I think if I can get

      api.headerIcons.add("foo", ServerLink, { before: "search" });

to work then I can just put that in the loop that built that array.

OMG. So glimmer components go in assets/javascripts/discourse/component and embrer components go in assets/javascripts/discourse/components?!?!

I now have a server-link.gjs

import Component from "@ember/component";
export default class ServerLink extends Component {
  // Required argument for the URL
  url = null;
  // Optional argument for the link text
  text = 'asdf';
  click() {
    console.log('ServerLink clicked!',this);

  }
  // Template for the component
  <template>
    {{log "my template" this}}
    LINK!
    <a href={{this.url}}>{{this.text}}</a>
  </template>
}

and in my initializer this:

      api.headerIcons.add("foo", ServerLink, { param: "url, yo", before: "search" });

Now I have something in the header.

But how do I pass stuff to ServerLink? I need to call it several times with different URLs and different text-to-click. I can’t seem to see the stuff in that {} in the component.

And you wan’t really put javascript in before the <template>, as my console.log("") won’t parse!

I also tried doing:

      const x = new ServerLink({
        url: "mylink",
        text: "my-text",
        name: 'Bob',
        message: 'Generated from JavaScript',
      });

and then passing x instead of ServerLink, but still no joy.

Do you mean you want multiple buttons in the headers with different icons/text/URLs or the same button, but depending on the context, the text/URL can change?

Yes, you are in a class—you would declare variables, functions, or templates there!

Yes. The links change for different users. The old code linked through an array of servers and pushed them into this array:

            headerLinks.push(
              h(
                `li.headerLink${deviceClass}${newClass}`,
                h("a", anchorAttributes, linkText)
              )
            );

And then did this:

      // api.decorateWidget("header-buttons:before", (helper) => {
      //   return helper.h("ul.pfaffmanager-header-links", headerLinks);
      // });

So then I had up to 3 links that got added to the header, each linking to a separate server URL.

Aha. Now I get it.

No don’t worry - the convention is still /components/ :sweat_smile:

(Technically, you can define and pass around gjs components however you like, so you can pick whatever directory name you like. But we’re sticking to /components/.)

Yeah that’s a fair question - I’ll work on writing up some ‘from scratch’ docs on how to add icons to the header so we have a better reference point.

In the meantime though, you might like to take a look at the discourse-icon-header-links update for inspiration. The cool thing about using GJS is that you can define components anywhere, and they get access to variables in the local scope.

So, if you rename your initializer to be .gjs, you can do stuff like

servers.forEach((server) => {
  api.headerIcons.add(`server-${server.id}`, <template>
    <li><DButton @translatedLabel={{server.name}} @icon="server" /></li>
  </template>);
});

Or you can define a component earlier in the same file, and use it like

class ServerButton extends Component {
  get icon(){
    // some logic to decide the icon
  }
  <template>
    <li><DButton @translatedLabel={{@server.name}} @icon={{this.icon}} /></li>
  </template>
}

...

servers.forEach((server) => {
  api.headerIcons.add(`server-${server.id}`, <template>
    <ServerButton @server={{server}} />
  </template>);
});

Or you could move the iteration inside the template (useful if the list of servers is a TrackedArray which may change at runtime!)

api.headerIcons.add("server-buttons", <template>  
  {{#each servers as |server|}}
    <ServerButton @server={{server}} />
  {{/each}}
</template>);

Oh. Hooray. I thought I tried it in components and it didn’t work.

Thanks! I think I can make one of these things work. The link to the header links is a big help. I’m pretty sure that when I wrote my code I had enough sense to look at that very component to figure it out then.

Seeing a glimmer of hope!

Hey @david and @Arkshine ! I did it!

Whaaaaaaaaaaaaaaaaaaat? Just rename it? That’s bananapants. But sure enough. I did it, and now

          servers.filter(Boolean).map((server) => {
            const linkHref = `/pfaffmanager/servers/${server.id}`;
            const linkTitle = `click to configure server ${server.id}`;
            let host = String(server.hostname);
            const linkText = host.replace(
              /www.|community.|forums?.|talk.|discourse./,
              ""
            );
            const serverLink = <template>
              <li class="headerLink">
                <a class="btn-flat" href={{linkHref}} title={{linkTitle}}>
                  {{host}}
                </a>
              </li>
            </template>;
            const beforeIcon = ["chat", "search", "hamburger", "user-menu"];
            api.headerIcons.add(host, serverLink, { before: beforeIcon });
          });

And it’s doing what it did before, which is what I wanted!

Yeah, Now that sounds very cool, and even more of what I want, but those things don’t change that much, so I’m going to call it a day on this. If they change a hostname, they’ll have to reload the page to get the link to change.

I presume you’ll delete my extra cruft here when you update the stuff above (or I might not have been so chatty in a documentation topic. . .)

I’ve updated the OP with some fully-fledged gjs examples, and included a link to the upstream Ember documentation. How’s that look to you @pfaffman? Anything else you think would be worth adding?

It’s better since it has a working example. But I do understand correctly that there are ember components and glimmer components? And if that’s right, you should say that a glimmer component is required, I think?

And maybe link to the glimmer docs about how those work?

It seems like you can have inline components as in your example, and another kind where you assign it to something like a variable in the same file or put it in another file that you put in the components directory and then include? I think all of that may be too much for this topic, but I’d love a dedicated topic about that.

They’re totally interchangeable - you can use a Classic Ember Component, or a Glimmer Component. And for either of those, you can choose to author them using the old-style .js/.hbs format, or the new-style .gjs format.

I’ll see if I can work in some links to the Ember documentation :+1:

:mega: Today we merged this change, which will automatically enable the new header implementation for sites with compatible themes/plugins.

If your themes/plugins are not compatible, then the legacy header will still be used, and a warning will be printed to the console alongside the existing deprecation messages. In the near future, this console warning will be upgraded to a warning banner in the UI.

In the unlikely event that this automatic change causes issues, you can temporarily override this ‘automatic feature flag’ via the glimmer header mode site setting. If you do that, please let us know the reason in this topic.

I wasn’t looking to make any changes but the depreciation notices tell me otherwise,

So there’s a choice and perhaps an easy way to just keep the status quo?

or

What would I be missing to elect to try and maintain an old header, I don’t understand what the new one means, I see group settings, custom tailoring to different groups is intriguing, but what can be made custom?

This is what I found today,

I’m no guru or wiz with these changes, they take time and I don’t do them often enough to really desire to learn the techniques the users here seem to easily comprehension/know

I somewhat begrudge having to do them in the first place, but before I scream like an old man who ran out of pudding I’d like to know, what why and where is this going?

I do this for a living and and I still find this javascript stuff far from easy.

I’m an old man and I feel your pain.

It’s just progress, I’m afraid. This ember upgrade broke a bunch of things and it’s not over yet.

You “asked for it” when you did that customization. I bet in the past five years you’ve gotten a new phone or laptop.

If I were you (and you were like me, without the full time discourse gig), I’d post in Marketplace. If I were me, I’d likely not respond for under $300, but there’s a reasonable chance that someone else will for $100 or $200. I’d guess that it won’t break again for another 5 years or more.

I think the hamburger theme selector you can get rid of and use the sidebar.

Nice honest reply, appreciate it but not much to work with, perhaps there’s more to come (I hope)

I didn’t even know it was java we were dealing in here :man_shrugging:

Don’t want anyone taking your pudding either :face_with_hand_over_mouth:

Sure, but what is the desired goal, this software dabbles in so many things I wonder who sees what end?

Is this simply needed from the ember upgrade?

I don’t know why ember was done either but if it works why fix it, I’m sure there’s a long deep explanation that all leads to the future of things but is there not a true vision to be shared?

I visit other forums that use very aged software, personally I see discourse as much better than any of them, but they don’t seem to suffer in comparison, they have the same growth issues, most are personality vs software IMO, too many old timers who lost their pudding, I’m wondering, is there a future IOT that will literally make all those forums obsolete where they won’t work at all and discourse is aware and prepping?

There’s more of that honesty you provide :grin: true enough, and I was more eager to learn, more ambitious, felt the worthwhile more, been beat up run over and left for dead since then

ok you’re on, I’ll take that bet and as you’ve already lost you help me out with this, we’ll be friends.

Then you’d of likely quit a long time ago, the beat up run over and left for dead was a bit of a euphemistic description, its just me left at the wheel, I guess anyone else is in some panel somewhere trying to fix the hyper-drive, IDK as I don’t often communicate with others, we have no funding, we (FULL30) were de-platformed from social media and discourse as well, I wonder how many other paying customers discourse willingly cut loose or how many others were deemed so offensive in their beliefs discourse publicly put money out against them?

Yet while I speak the truth I don’t take offense, live and let live, I know there’s a future coming, what I don’t know is why I’m still here and still trying, but I am, so, I’ll keep trying, like AA, just for today :hugs:

But it was all the rage when I employed it :expressionless:

The sidebar (here) can be closed with a hamburger menu, there’s not much difference in function, it opens and closes a navigation window, but mine can’t easily be saved?

Yes, I’d love and prefer to pay someone to clean up my customized code and make things work nice, and I’d happily pay, I enjoy employing others, sharing the wealth, when I grow up I want to be a philanthropist, but today I need a philanthropist :innocent: and again appreciate any help others can offer.

The other way to play it is to ask your community for help, stop doing whatever the customization was, start a new topic that shares your code am ask for help. I’ve gotten lots of help in such matters recently.

Unfortunately not. The ability to stick to the ‘old header’ is just a temporary thing during the transition period. Soon, the new header will be the only option.

Yup! We’re always happy to help with questions in Development. Plus, sharing the code and the solutions publicly creates a useful resource for others.

phew, my community is more in tune to other issues

Surely I could share here but then it goes the opposite, what coder finds interest in helping us?

The irony, coding may well be the firearms of the future, may well cause far more death and destruction as well, I digress

Very well, that means what exactly for me, create one user group, public and not logged in perhaps?

These group settings, I perceive they are based on trust levels vs actual different groups, like a hunting group and a fishing group?

First I need to understand the goal, and where my efforts as a lone ranger will effect the most bang for my buck, saving time and saving the custom feel for my forum.


I don’t wish to derail anyone’s thread, if its deemed this should be its own thread I’m fine with it

but how will there ever be a true cohesive relationship when people feel the need to remove something that offends them?

It takes patience to understand others, the link removed, it showed a missing logo after but not while posting, another header issue to discuss?

It was a post on my forum written by a man I believe well into his 80’s, I could ask for sure but he refuses to talk to me, do I berate him,ban or shun him?

No, why, because there’s a better way but it means putting up with others and how they think, I find good people in bad areas, good people who look bad, and just the opposite in both.

Exactly, I just found the errors, wish to address them but don’t understand the root cause other than the future is moving on, we need a new header, ok fine, whats the correct course to set my sites on, simple tuning, a full course correction?

Are we discussing only needing these three areas worked on?

I have mixed component usage, started with none then learned they can be beneficial, I never went full component and have a mixed bag.

Here’s my theme for what its worth without the components
discourse-full30-ii.zip (10.1 KB)
I can post those too, some, the modals, are already not working as of recent