중앙 테마 헤더 사용자 아이콘

안녕하세요, 메타의 중앙 테마와 유사하게 헤더 메뉴에서 사용자 정보와 알림을 분리하는 것을 재현하려고 하고 있습니다. 테마 스레드에서 질문을 했지만 답변을 받지 못했습니다.

동일한 작업을 하려면 어떤 파일을 수정해야 하며, 파일을 내 테마에 배치하는 것이 더 나은지, 아니면 테마 컴포넌트로 재현하는 것이 더 나은지 궁금합니다.

페이지를 검사하고 소스(Sources) 탭을 확인하면 페이지가 어떻게 만들어졌는지 살펴볼 수 있습니다.

대략적인 내용은 다음과 같습니다:

  • 새 사용자 메뉴는 api.addToHeaderIcons로 추가된 위젯으로 만들어집니다.
  • 알림 메뉴:
    • header-notifications 위젯의 내용을 교체하여 아바타를 벨 아이콘으로 대체합니다.
    • CSS를 사용하여 사용자 메뉴의 빠른 접근 아이콘을 숨깁니다.

항상 도움 주셔서 감사합니다. 스테이징 개발 환경을 만들어서 해결해 보겠습니다. 다시 한번 감사합니다.

안녕하세요 @digitaldominica! 다음 업데이트를 곧 출시할 예정이라서 잠시 동안 스레드를 확인하지 못해 죄송합니다. 모든 분들이 매우 가치 있는 피드백을 주시는데, 제가 시선을 돌리지 않으면 현재 작업 목록에 추가하는 습관이 생기기 때문에 업데이트 이후를 위해 남겨두려 합니다. :sweat_smile:

@Arkshine 님이 말씀하신 대로, 이는 테마의 제약 사항 내에서 작동하는 꽤 해킹적인(hacky) 방법입니다 (Central의 대부분은 제가 개발자보다 디자이너가 우선이므로 프로토타이핑 단계에 불과합니다).

Central에서 거친 프로토타입을 어떻게 구현했는지 단계별 가이드를 드리겠습니다. 다만 주의할 점은, 사용자 메뉴에 대한 코어 업데이트가 이루어질 경우 이 솔루션이 미래에 적합하지 않을 수 있으며, 이 컴포넌트를 만드는 더 최적화된 방법이 있을 수 있다는 것입니다.


1단계: 사용자 정의 사용자 메뉴 컴포넌트 생성

다음 파일들을 생성하세요—

:page_facing_up: /javascripts/discourse/components/header-user-new.js
:page_facing_up: /javascripts/discourse/components/header-user-new.hbs

import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";

export default class HeaderUserNew extends Component {
  @service currentUser;
  @tracked isActive = false;

  constructor() {
    super(...arguments);
    this.handleDocumentClick = this.handleDocumentClick.bind(this);
  }

  @action
  toggleDropdown() {
    this.isActive = !this.isActive;

    if (this.isActive) {
      setTimeout(() => {
        document.addEventListener("click", this.handleDocumentClick);
      }, 0);
    } else {
      document.removeEventListener("click", this.handleDocumentClick);
    }
  }

  handleDocumentClick(event) {
    const dropdown = document.querySelector(".header-user-new__menu");
    const isClickInside = dropdown.contains(event.target);

    if (!isClickInside) {
      this.isActive = false;
      document.removeEventListener("click", this.handleDocumentClick);
    }
  }

  willDestroy() {
    super.willDestroy();
    document.removeEventListener("click", this.handleDocumentClick);
  }
}
<div class={{concatClass "header-user-new" (if isActive "active")}}>
  <button class="header-user-new__button" type="button" {{on "click" this.toggleDropdown}}>
    {{avatar currentUser}}
  </button>

  <div class="header-user-new__menu">
    <LinkTo class="header-user-new__profile" @route="user.summary" @model={{this.currentUser}}>
      {{avatar currentUser}}
      <div class="header-user-new__profile-info">
        <span class="header-user-new__profile-name">
          {{#if currentUser.name}}
            {{currentUser.name}}
          {{else}}
            {{currentUser.username}}
          {{/if}}
        </span>
        <span class="header-user-new__profile-view">
          프로필 보기
        </span>
      </div>
    </LinkTo>

    <ul>
      <li>
        <LinkTo @route="userActivity.bookmarks" @model={{this.currentUser}}>
          {{d-icon "bookmark"}}
          <span>
            {{i18n "js.user.bookmarks"}}
          </span>
        </LinkTo>
      </li>
      <li>
        <LinkTo @route="preferences" @model={{this.currentUser}}>
          {{d-icon "cog"}}
          <span>
            {{i18n "user.preferences"}}
          </span>
        </LinkTo>
      </li>
      <li>
        <DButton @action={{route-action "logout"}}>
          {{d-icon "sign-out-alt"}}
          <span>
            {{i18n "user.log_out"}}
          </span>
        </DButton>
      </li>
    </ul>
  </div>
</div>

지금은 CSS 스타일링은 여러분이 직접 하시기로 하겠습니다만, 토글 효과를 위해 이 부분은 포함하는 것이 중요합니다—

.header-user-new {
  &.active {
    .header-user-new__menu {
      display: flex;
    }
  }
  .header-user-new__menu {
    display: none;
  }
}

2단계: 컴포넌트를 위젯으로 등록

다음 파일을 생성하세요—

:page_facing_up: /javascripts/discourse/widgets/header-user-new.js

import { hbs } from "ember-cli-htmlbars";
import RenderGlimmer from "discourse/widgets/render-glimmer";
import { createWidget } from "discourse/widgets/widget";

export default createWidget("header-user-new", {
  tagName: "li.header-dropdown-toggle.header-user-new",

  html() {
    return [new RenderGlimmer(this, "div", hbs`<HeaderUserNew />`)];
  },
});

3단계: 헤더에 위젯 추가, 기존 사용자 아이콘을 알림 벨 아이콘으로 교체

다음 파일을 생성하세요—

:page_facing_up: /javascripts/discourse/initializers/header-edit.js

import { h } from "virtual-dom";
import { withPluginApi } from "discourse/lib/plugin-api";
import { iconNode } from "discourse-common/lib/icon-library";
import I18n from "discourse-i18n";

export default {
  initialize() {
    withPluginApi("0.8", (api) => {

      api.reopenWidget("header-notifications", {
        html(attrs) {
          const { user } = attrs;

          let avatarAttrs = {
            template: user.get("avatar_template"),
            username: user.get("username"),
          };

          if (this.siteSettings.enable_names) {
            avatarAttrs.name = user.get("name");
          }

          const contents = [h("div", iconNode("bell"))];

          if (this.currentUser.status) {
            contents.push(
              this.attach("user-status-bubble", this.currentUser.status)
            );
          }

          if (user.isInDoNotDisturb()) {
            contents.push(h("div.do-not-disturb-background", iconNode("moon")));
          } else {
            if (user.new_personal_messages_notifications_count) {
              contents.push(
                this.attach("link", {
                  action: attrs.action,
                  className: "badge-notification with-icon new-pms",
                  icon: "envelope",
                  omitSpan: true,
                  title: "notifications.tooltip.new_message_notification",
                  titleOptions: {
                    count: user.new_personal_messages_notifications_count,
                  },
                  attributes: {
                    "aria-label": I18n.t(
                      "notifications.tooltip.new_message_notification",
                      {
                        count: user.new_personal_messages_notifications_count,
                      }
                    ),
                  },
                })
              );
            } else if (user.unseen_reviewable_count) {
              contents.push(
                this.attach("link", {
                  action: attrs.action,
                  className: "badge-notification with-icon new-reviewables",
                  icon: "flag",
                  omitSpan: true,
                  title: "notifications.tooltip.new_reviewable",
                  titleOptions: { count: user.unseen_reviewable_count },
                  attributes: {
                    "aria-label": I18n.t(
                      "notifications.tooltip.new_reviewable",
                      {
                        count: user.unseen_reviewable_count,
                      }
                    ),
                  },
                })
              );
            } else if (user.all_unread_notifications_count) {
              contents.push(
                this.attach("link", {
                  action: attrs.action,
                  className: "badge-notification unread-notifications",
                  rawLabel: user.all_unread_notifications_count,
                  omitSpan: true,
                  title: "notifications.tooltip.regular",
                  titleOptions: { count: user.all_unread_notifications_count },
                  attributes: {
                    "aria-label": I18n.t("user.notifications"),
                  },
                })
              );
            }
          }
          return contents;
        },
      });
      
      const currentUser = api.container.lookup("service:current-user");
        if (currentUser !== null) {
          api.addToHeaderIcons("header-user-new");
        }
    });
  },
};

주요 변경 사항은 기존 header-notifications 위젯을 수정(또는 “재개” reopen)하여 사용자 아바타 대신 알림 아이콘을 사용하도록 avatarImg(…)를 iconNode로 교체한 것입니다.

참고용 기존 header-notifications 코드: discourse/app/assets/javascripts/discourse/app/widgets/header.js at 9bc78625af1d54693bc4f1bad3eaa9161ae030b6 · discourse/discourse · GitHub

그리고 @Arkshine 님이 언급하신 대로 CSS를 사용하여 알림 메뉴의 사용자 섹션을 숨겼습니다.

.d-header .panel {
  .user-menu.revamped .bottom-tabs, #user-menu-button-profile {
    display: none;
  }
}

안녕하세요, 사용자 avatarImg를 벨 아이콘으로 대체하는 데는 성공했지만,

api.addToHeaderIcons(“new-user-icon”);

을 호출하면 헤더 전체가 삭제되는 문제가 발생합니다. 사용자 아바타와 드롭다운이 표시되어야 하는데 말이죠.

제 실수였습니다. 해당 코드에 오타가 있었는데, 이렇게 되어야 합니다.

api.addToHeaderIcons(“header-user-new”);

2단계에서 위젯이 header-user-new로 등록되었기 때문입니다.

고맙습니다. 이 방법으로 해결되었습니다.

혹시 다른 주제에 대해 질문해도 될까요?

중앙 토픽 목록 템플릿에서 사용자가 게시한 토픽과 사용자가 답글을 단 토픽을 어떻게 구분하여 표시하나요?

{{#if topic.replies}}와 {{#if topic.posters.[0}}}를 시도해 보았지만 작동하지 않습니다.

문제가 아닙니다! 찾으시는 것은 topic.posters.1.user입니다 (첫 번째 답변). 존재한다면 이를 사용하고, 그렇지 않으면 OP(최초 게시자)로 기본값을 설정하세요.

현재 Central에서는 다음과 같이 구조화되어 있습니다—

{{#if topic.posters.1.user}}
  <span class="tli__last-reply">
    {{d-icon "reply"}}
    <a href="{{topic.lastPoster.user.userPath}}" data-user-card="{{topic.lastPoster.user.username}}">
      {{~topic.lastPoster.user.username~}}
    </a>
    {{theme-i18n "topic_list_item.replied"}}
    <a href={{topic.lastPostUrl}}>
      {{format-date topic.bumpedAt format="medium" leaveAgo="true"}}
    </a>
  </span>
{{else}}
  <span class="tli__last-reply">
    {{d-icon "m-post_add"}}
    <a href="{{topic.posters.0.user.userPath}}" data-user-card="{{topic.posters.0.user.username}}">
      {{~topic.posters.0.user.username~}}
    </a>
    posted
    <a href={{topic.lastPostUrl}}>
      {{format-date topic.bumpedAt format="medium" leaveAgo="true"}}
    </a>
  </span>
{{/if}}

음, 그러면 topic.posters.1.user가 게시물 작성자로 작동하는 건가요?

아니, topic.posters.0.user가 작성자이고, topic.posters.1.user는 첫 번째로 답글을 단 사용자(존재하는 경우)입니다. :laughing: