기본 그룹 페이지를 /messages로 변경

안녕하세요,

/g 페이지에서 그룹에 접근할 때, 해당 그룹이 가리키는 URL을 /g/group-slug/members 대신 /g/group-slug/messages로 설정할 수 있는 방법이 있나요?

그룹 메시지에 사용되는 그룹이 여러 개 있습니다. 이렇게 하면 사용자들이 자신의 그룹 메시지에 도달하기까지 클릭을 한 번 덜 할 수 있어 도움이 될 것입니다. 그룹 멤버 목록을 보는 것보다 이 기능이 우리에게 더 중요합니다.

직접 링크를 통해 이걸 할 수 있다는 것은 알고 있지만, 유용하게 동적으로 사용자 중심으로 구성된 /g?type=my 페이지에서 이렇게 하고 싶습니다.

이것들은 일반적인 링크가 아닙니다. link-to Ember 컴포넌트로 처리되며, 라우팅이 일부 관여합니다.

가장 쉬운 방법은 해당 요소의 클릭을 가로채서 그곳에서 작업을 수행하는 것입니다. 이는 테마의 common > header에 추가됩니다.

<script
  type="text/x-handlebars"
  data-template-name="/connectors/group-index-box-after/link-to-group-inbox"
></script>

<script type="text/discourse-plugin" version="0.8">
  const DiscourseURL = require("discourse/lib/url").default;
  api.registerConnectorClass('group-index-box-after', 'link-to-group-inbox', {
    setupComponent(args) {
      const group = args.model;

       if (group.is_group_user && group.has_messages) {
         const groupLink = document.querySelector(`.group-box.${group.name}`);
         const newHref = `/g/${group.name}/messages`;

         // updates browser title and handle new tab
         groupLink.href = newHref;

         // redirect the link
         groupLink.addEventListener("click", (event) => {
            DiscourseURL.routeTo(newHref);
            event.preventDefault();
         }, {once: true})
       }
     }
  });
</script>

사용자가 그룹의 멤버이고 그룹에 메시지가 있는 경우, 수신함으로 리다이렉트됩니다. 그렇지 않으면 기본 /members 페이지로 폴백됩니다.

와 - 정말 놀랍네요!!! 완벽하게 작동합니다. 감사합니다.


나중에…

죄송하지만 후속 문제로 조금만 도움을 요청합니다!

몇몇 그룹은 메시지가 없는데도 여전히 /g/group-slug/messages가 표시됩니다.

자세히 살펴보니 과거에 메시지가 있었으나 주제(Topic)로 변환되었거나 삭제된 경우였습니다. 데이터 탐색기(Data Explorer)에서 확인해 보니 Groups 테이블에서 해당 그룹들의 has_messages 값이 True로 설정되어 있었습니다.

Rails를 통해 해당 그룹들의 값을 변경해 보았지만 원하는 결과를 얻지 못했습니다:

[2] pry(main)> Group.find_by_name(“group-slug”).has_messages
=> true
[3] pry(main)> Group.find_by_name(“group-slug”).has_messages = false
=> false
[4] pry(main)> Group.find_by_name(“group-slug”).has_messages
=> true

그룹을 삭제하고 다시 만드는 것(이건 좀 꺼려지는데) 외에 다른 제안이 있을까요?

이 한 줄만 Rails 콘솔에서 실행하면 됩니다. 모든 그룹이 업데이트되어야 합니다.

Group.refresh_has_messages!

이 방법으로 거의 모든 문제가 해결되었지만, 두 개만 남았습니다. 감사합니다. 그 두 개는 삭제하고 다시 만들기로 할 것 같습니다.

이 작업을 수행하면서 원인을 발견했는데, 이는 #contribute:bug에 해당하는 사소한 문제입니다.

그룹 개인 메시지를 주제로 변환할 때, 초대된 사용자의 정보가 유지됩니다. 주제가 다시 원래 형태로 변환될 경우 이는 유용하지만, Group.refresh_has_messages!가 실행될 때 해당 사용자들이 잘못 계산됩니다.

@멘션된 그룹을 클릭했을 때 나오는 링크, 그리고 그 링크를 통해 그룹으로 이동할 때의 링크도 가로챌 수 있을까요? URL을 사용하지 않고 그룹에 도달하는 방법은 이 두 가지뿐이라고 생각합니다.

제가 지금 원하는 것(탐욕스럽죠?)은 실제로 그룹으로 연결되는 모든 링크가 당신의 코드가 /g 페이지의 링크처럼 동작하도록 만드는 것입니다.

@Johani, 이건 가능한가요? 여기저기 살펴봤는데 제 역량 범위를 훨씬 넘어섭니다.

네, 테마/컴포넌트의 헤더 탭에서 다음과 같은 방식으로 시도해 볼 수 있습니다. 따라 해 보시려면 스니펫에 몇 가지 주석을 남겨 두었습니다. 사용하실 준비가 되면 해당 주석은 삭제해도 됩니다.

같은 규칙이 적용됩니다. 사용자가 해당 그룹의 멤버이고 그룹에 메시지가 있으면 Discourse는 그룹 받은 편지함으로 이동합니다. 그렇지 않으면 사용자는 멤버 디렉터리로 이동하게 됩니다.

<script type="text/discourse-plugin" version="0.8">
  // Part 1: modify the path for the links in the group cards. This ensure that the link
  // title reflects where it leads and handles opening the link a new tab.
  const discourseComputed = require("discourse-common/utils/decorators").default;
  
  api.modifyClass("component:group-card-contents", {
    // The groupPath property is used to generate the href for the group links
    // in the group card. Let's modify it
    @discourseComputed("group")
    groupPath(group) {
      // get the default value from core
      let groupURL = this._super(...arguments);

      // if the user matches our requirment, modify the url so it goes to the inbox
      if (group.is_group_user && group.has_messages) {
        groupURL = `${groupURL}/messages/`;
      }

      // return either the default or modified value
      return groupURL;
    }
  });

  // Part 2: modify in-app routing. This ensures that regular in-app navigation is 
  // handled correctly and takes the user to the group inbox if they meet the 
  // requirements
  
  const DiscourseURL = require("discourse/lib/url").default;
  const { groupPath } = require("discourse/lib/url");
  const { action } = require("@ember/object");

  api.modifyClass("controller:user-card", {

    // showGroup here is an action that belongs to the user-card controller. 
    // This is what gets called when you click on the group name / avatar inside group cards
    @action
    showGroup(group) {
      // call super to make sure code from core is loaded first
      this._super(...arguments);

      // group path is a built-in Discourse url helper function
      let groupURL = groupPath(group.name);

      // if the user matches the requirments, modify the url
      if (group.is_group_user && group.has_messages) {
        groupURL = `${groupURL}/messages/`;
      }

      // call routeTo() with either the default or the modified groupURL 
      DiscourseURL.routeTo(groupURL);
    }
  });
</script>

정말 대단해요! 카드가 사용되는 모든 경우에 완벽하게 작동합니다.

이제 아직 방향이 지정되지 않은 링크 유형이 하나 남아 있습니다. 바로 그룹 개인 메시지(GPM) 상태에서의 그룹 링크입니다. 이는 실제로 그룹 메시지(GM)에서 그룹(따라서 받은 편지함)으로 이동할 때 매우 유용합니다.

이 부분도 함께 처리할 수 있을까요?

(참고로, 작업이 끝나면 이를 TC로 패키징하고 다른 사람들이 쉽게 접근할 수 있도록 별도 토픽을 만들겠습니다.)

해당 링크들은 약간 다른 직렬화기를 사용합니다. 거기서는 그룹 데이터 전체가 필요하지 않기 때문입니다.

그래도 초기 페이지 로드 시 현재 사용자의 그룹 중 필터링된 목록을 저장해 두었다가, 멤버십 확인 시 참조 자료로 사용할 수 있습니다. 이 정보는 이미 초기 페이로드에 포함되어 있으므로, 추가 요청이나 그에 따른 오버헤드가 전혀 없습니다. 다음과 같은 방식입니다.

<script type="text/discourse-plugin" version="0.8">
  const user = api.getCurrentUser();

  if (!user) {
    return;
  }

  const userGroups = user.groups.map(group => group.name);

  api.reopenWidget("pm-map-user-group", {
    transform(attrs) {

      const group = attrs.group;
      const isGroupUser = Object.values(userGroups).includes(group.name);

      // {href: "/g/foo"};
      let groupURL = this._super(...arguments);

      if (isGroupUser && group.has_messages) {
        groupURL.href = `${groupURL.href}/messages/`;
      }

      return groupURL;
    }
  });
</script>

이 스니펫은 별도의 스크립트 태그 안에 유지하고, 다른 스니펫들과 함께 헤더에 추가하세요. 사용자가 로그인되어 있지 않을 경우 빠르게 처리를 중단하기 때문에 분리해서 두는 것이 좋습니다.

:partying_face: 도착했습니다! 모바일에서도 완벽하게 작동합니다.

Joe, 놀라운 끈기 감사합니다. 약속을 지키기 위해 Customization > Theme component 작업을 시작해야겠네요, 그렇지?

시간이 흘러…
여기 있습니다:

이것을 완전히 마무리하려면 도움이 필요한 한 가지가 더 있습니다. 이렇게 부탁드리는 것이 좀 무례한 것 같다는 점은 잘 알고 있습니다.

이제 Pavilion 위젯을 수정하여 이 동작도 지원하도록 하려고 합니다. 말씀하신 세 가지 멋진 스니펫의 간단한 로직을 활용하려는 건데요. 제 JavaScript 실력 부족으로 인해 어떻게 해도 작동하지 않습니다. 수정하려는 코드는 다음과 같습니다:

createWidget('layouts-group-link', {
  tagName: 'li.layouts-group-link',
  buildKey: (attrs) => `layouts-group-link-${attrs.id}`,

  getGroupTitle(group) {
    return h('span.group-title', group.name);
  },

  isOwner(group) {
    if (group.owner) {
      return h('span.group-owner-icon', iconNode('shield-alt'));
    }
  },

  html(attrs) {
    const contents = [this.getGroupTitle(attrs), this.isOwner(attrs)];
    return contents;
  },

  click() {
      DiscourseURL.routeTo(`/g/${this.attrs.name}/messages`);
  },
});

아래의 코드를 어떻게 적용해야 할까요? 마지막 click(){etc} 부분에 제가 생각할 수 있는 모든 방법을 시도해 보았지만 실패했습니다. 결국 링크가 완전히 깨져 버립니다.

      if (group.is_group_user && group.has_messages) {
        groupURL = `${groupURL}/messages/`;
      }

잊어주세요!!! @keegan이 해결했어요:

https://github.com/paviliondev/layouts-groups-widget/commit/9f2a9995ad41972e93347d2403f7e0960bff4c90

'메시지 유령(message ghosts)'이 있는 일부 그룹에서는 이 방법으로 작동하지 않습니다. 이러한 그룹은 다음 쿼리로 식별할 수 있습니다(그룹 상호작용 설정에서 메시징이 제한되어 있는 경우):

SELECT id, name, has_messages, messageable_level
From groups
Where messageable_level < 10
AND messageable_level <> 3
AND has_messages = true
ORDER by id

groups 테이블을 해킹하여 이 값도 강제로 초기화할 수 있습니다(물론 Rails 콘솔에서, 먼저 백업하세요!).

 Group.where(id: XXX).update_all(has_messages: false)

이것이 작동하지 않거나 되돌아간다면, topic_groups와 topic_allowed_groups 테이블도 정리해야 할 수 있습니다:

 TopicGroup.where(group_id: XXX).destroy_all
 TopicAllowedGroup.where(group_id: XXX).destroy_all