Iconos de Usuario del Encabezado del Tema Central

Hola, estoy intentando recrear la separación del menú de encabezado con el usuario y las notificaciones similar al tema central en meta, pregunté en el hilo del tema pero no obtuve respuesta.

Si quisiera hacer lo mismo, ¿qué archivos necesitaría editar y es mejor colocar el archivo en mi tema o recrearlo con un componente temático?

1 me gusta

Si inspeccionas una página y revisas la pestaña Fuentes, puedes ver cómo se ha creado.

Esencialmente:

  • El nuevo menú de usuario se crea con un widget, añadido con api.addToHeaderIcons
  • El menú de notificaciones:
    • El avatar se reemplaza con un icono de campana reemplazando su contenido desde el widget header-notifications.
    • El icono de acceso rápido del menú de usuario se oculta con CSS.
5 Me gusta

Gracias por la ayuda como siempre, haré un staging dev para resolverlo. Gracias de nuevo.

1 me gusta

Hola @digitaldominica. Disculpa por no haber revisado el hilo en un tiempo, ya que estoy a punto de lanzar la próxima actualización. Todos tienen comentarios muy valiosos, pero si no me aparto, tenderé a agregarlos a mi lista actual en lugar de guardarlos para la siguiente actualización. :sweat_smile:

@Arkshine tiene razón. Es un método bastante improvisado que funciona dentro de las limitaciones de un tema (gran parte de Central es solo prototipos, ya que soy diseñador primero, segundo y tercero, y desarrollador cuarto).

Proporcionaré una guía paso a paso sobre cómo logré el prototipo aproximado en Central. Pero advertencia, esta solución puede no ser a prueba de futuro si alguna vez hacemos una actualización central del menú de usuario, y puede haber una forma más óptima de crear el componente.


Paso 1: Crear un componente de menú de usuario personalizado

Crea estos archivos:

: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">
          View Profile
        </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>

Por ahora, dejaré el estilo CSS a tu cargo, pero es importante incluir esta parte para el efecto de alternancia:

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

Paso 2: Registrar el componente como un widget

Crea este archivo:

: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 />`)];
  },
});

Paso 3: Añadir el widget a la cabecera, reemplazar el icono de usuario existente con el icono de campana de notificación

Crea este archivo:

: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");
      }
    });
  },
};

El cambio principal fue reemplazar avatarImg(…) con iconNode para usar el icono de notificación en lugar del avatar del usuario editando (o “reabriendo”) el widget existente header-notifications.

El código original de header-notifications como referencia: discourse/app/assets/javascripts/discourse/app/widgets/header.js at 9bc78625af1d54693bc4f1bad3eaa9161ae030b6 · discourse/discourse · GitHub

Y como mencionó @Arkshine, oculté la sección del usuario en el menú de notificaciones con CSS.

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

Hola, he conseguido reemplazar el user avatarImg con el icono de la campana, sin embargo, el siguiente código:

api.addToHeaderIcons("new-user-icon");

Elimina toda la cabecera en lugar de mostrar el avatar del usuario con un menú desplegable.

2 Me gusta

Mi descuido, había un error tipográfico en ese código, debería ser:

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

Porque el widget está registrado como header-user-new en el Paso 2.

3 Me gusta

Gracias, esto funcionó.

Si puedo hacer otra pregunta sobre algo aparte;

¿Cómo alterna la plantilla de lista de temas centrales entre
publicado por el usuario y respondido por el usuario
?

He intentado {{#if topic.replies}} y {{#if topic.posters.[0]}}
pero eso no funciona.

No hay problema. topic.posters.1.user es lo que buscas (la primera respuesta). Si existe, úsalo, de lo contrario, usa el OP (el autor original).

Así es como está estructurado actualmente en 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}}
1 me gusta

Entonces, ¿topic.posters.1.user actúa como autor de la publicación?

Oh no, topic.posters.0.user es el autor, topic.posters.1.user es el primer usuario que responde (si existe). :laughing:

3 Me gusta

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.