# 예정된 게시글 메뉴 변경 - 테마와 플러그인 준비 방법

**URL:** https://meta.discourse.org/t/upcoming-post-menu-changes-how-to-prepare-themes-and-plugins/341014
**Category:** Development
**Tags:** dev-news
**Created:** [12월 12, 2024, 9:27오후 UTC](https://meta.discourse.org/t/upcoming-post-menu-changes-how-to-prepare-themes-and-plugins/341014 "2024-12-12T21:27:39Z")
**Posts on this page:** 1
**Showing post:** 1

<div class="post-metadata">

### Author: ![saquetim](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/saquetim/32/116400_2.png) [@saquetim](https://meta.discourse.org/u/saquetim)
#### Post date: [12월 12, 2024, 9:27오후 UTC](https://meta.discourse.org/t/upcoming-post-menu-changes-how-to-prepare-themes-and-plugins/341014/1 "2024-12-12T21:27:39Z")

</div>

Discourse 코드베이스를 지속적으로 개선하기 위한 노력의 일환으로, 레거시 “위젯” 렌더링 시스템의 사용을 제거하고 Glimmer 컴포넌트로 대체하고 있습니다.

최근에 [게시물 메뉴를 현대화](https://github.com/discourse/discourse/pull/28670)했으며, 이제 `glimmer_post_menu_mode` 설정을 통해 Discourse에서 사용할 수 있습니다.

이 설정은 세 가지 값을 허용합니다:

- `disabled`: 레거시 “위젯” 시스템 사용
- `auto`: 현재 플러그인과 테마의 호환성을 감지합니다. 호환되지 않는 항목이 있으면 레거시 시스템을 사용하고, 그렇지 않으면 새 메뉴를 사용합니다.
- `enabled`: 새 메뉴를 사용합니다. 호환되지 않는 플러그인이나 테마가 있는 경우 사이트가 손상될 수 있습니다.

공식 플러그인은 이미 새 메뉴와 호환되도록 업데이트되었지만, 여전히 새 메뉴와 호환되지 않는 서드파티 플러그인, 테마 또는 테마 컴포넌트가 있는 경우 업그레이드가 필요합니다.

> 브라우저 콘솔에 호환성 문제의 원인을 식별하는 경고가 출력됩니다.

## ⏲ 롤아웃 타임라인

_이는 변경될 수 있는 대략적인 추정치입니다_

## 2024년 4분기:

- ✅ 코어 구현 완료
- ✅ 공식 플러그인 업데이트
- ✅ Meta에서 활성화
- ✅ `glimmer_post_menu_mode` 기본값이 `auto`로 설정; 콘솔 비권장(deprecation) 메시지 활성화
- ✅ 업그레이드 조언 게시

## 2025년 1분기:

- ✅ 서드파티 플러그인과 테마 업데이트 필요
- ✅ 비권장 메시지 시작, 남은 문제에 대해 관리자 경고 배너 트리거
- ✅ 새 게시물 메뉴 기본 활성화

## 2025년 2분기

- **✅ 4월 1일** - 기능 플래그 설정 및 레거시 코드 제거

## 👀 나에게 어떤 의미가 되나요?

플러그인이나 테마에서 게시물 메뉴를 커스터마이징하기 위해 ‘위젯’ API를 사용하는 경우, 새 버전과 호환되도록 업데이트해야 합니다.

### 💁 새 게시물 메뉴를 어떻게 시험해 볼 수 있나요?

최신 버전의 Discourse에서는 호환되지 않는 플러그인이나 테마가 없으면 새 게시물 메뉴가 활성화됩니다.

호환되지 않는 확장 기능이 설치된 경우, 관리자로 `enabled`로 설정을 변경하여 새 메뉴 사용을 강제할 수 있습니다. 설치된 커스터마이징에 따라 사이트가 손상될 수 있으므로 신중하게 사용하세요.

이 자동 시스템이 예상대로 작동하지 않는 드문 경우, 위의 설정을 사용하여 이 '자동 기능 플래그’를 일시적으로 오버라이드할 수 있습니다. 이 작업이 필요한 경우 이 주제에서 알려주세요.

### 🧑‍💻 플러그인과 테마를 업데이트해야 하나요?\n

아래와 같은 커스터마이징을 수행하는 플러그인이나 테마가 있는 경우 업데이트가 필요합니다:

- 다음 위젯에서 `decorateWidget`, `changeWidgetSetting`, `reopenWidget` 또는 `attachWidgetAction`을 사용하는 경우:

- 다음 API 메서드 중 하나를 사용하는 경우:

> 💡 위의 커스터마이징 중 하나를 수행하는 확장 기능이 있는 경우, 주제 페이지에 액세스할 때 업그레이드가 필요한 플러그인 또는 컴포넌트를 식별하는 경고가 콘솔에 출력됩니다.
> 
> 비권장 ID는: `discourse.post-menu-widget-overrides`

> ⚠ **인스턴스에서 여러 테마를 사용하는 경우, 경고는 활성 플러그인과 현재 사용 중인 테마 및 테마 컴포넌트에 대해서만 출력되므로 모든 테마를 확인하시기 바랍니다.**

## 대체 방안은 무엇인가요?

게시물 메뉴를 커스터마이징하기 위한 새로운 API로 값 변환기 [`post-menu-buttons`](https://github.com/discourse/discourse/blob/b8325f2190a8c0a9022405c219faeac6f0f98ca5/app/assets/javascripts/discourse/app/components/post/menu.gjs#L228)를 도입했습니다.

이 값 변환기는 버튼의 추가, 교체, 제거 또는 재배열을 허용하는 DAG 객체를 제공합니다. 또한 메뉴와 연관된 게시물, 표시되는 게시물의 상태, 항목의 더 쉬운 배치에 사용할 수 있는 버튼 키와 같은 컨텍스트 정보를 제공합니다.

DAG API는 `.add` 및 `.replace`와 같이 새 버튼 정의가 필요한 경우 Ember 컴포넌트를 수신하도록 기대합니다.

각 커스터마이징은 다르지만, 가장 일반적인 사용 사례에 대한 지침은 다음과 같습니다:

### addPostMenuButton

변경 전:

```js
withPluginApi("1.34.0", (api) => {
  api.addPostMenuButton("solved", (attrs) => {
    if (attrs.can_accept_answer) {
      const isOp = currentUser?.id === attrs.topicCreatedById;
      return {
        action: "acceptAnswer",
        icon: "far-check-square",
        className: "unaccepted",
        title: "solved.accept_answer",
        label: isOp ? "solved.solution" : null,
        position: attrs.topic_accepted_answer ? "second-last-hidden" : "first",
      };
    }
  });
});

```

변경 후:

> 아래 예제는 Ember의 [Template Tag Format (gjs)](https://guides.emberjs.com/release/components/template-tag-format/)를 사용합니다.

```js
// components/solved-accept-answer-button.gjs
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";

export default class SolvedAcceptAnswerButton extends Component {
  // 버튼이 즉시 표시될지, '더 보기' 버튼 뒤에 숨겨질지 나타냅니다
  static hidden(args) { 
    return args.post.topic_accepted_answer;
  }

  ...

  <template>
    <DButton
      class="post-action-menu__solved-unaccepted unaccepted"
      ...attributes
      @action={{this.acceptAnswer}}
      @icon="far-check-square"
      @label={{if this.showLabel "solved.solution"}}
      @title="solved.accept_answer"
    />
  </template>
}

// initializer.js
import SolvedAcceptAnswerButton from "../components/solved-accept-answer-button";

...
withPluginApi("1.34.0", (api) => {
  api.registerValueTransformer(
    "post-menu-buttons",
    ({
      value: dag, 
      context: {
        post,
        firstButtonKey, // 첫 번째 버튼의 키
        secondLastHiddenButtonKey, // 두 번째로 마지막 숨겨진 버튼의 키
        lastHiddenButtonKey, // 마지막 숨겨진 버튼의 키
      },
    }) => {
        dag.add(
          "solved",
          SolvedAcceptAnswerButton,
          post.topic_accepted_answer
            ? {
                before: lastHiddenButtonKey,
                after: secondLastHiddenButtonKey,
              }
            : {
                before: [
                  "assign", // assign 플러그인이 추가한 버튼
                  firstButtonKey,
                ],
              }
        );
    }
  );
});

```

> 💡 **버튼 스타일링**
> 
> 위의 예제와 같이 컴포넌트에 `...attributes`를 포함하는 것이 좋습니다.
> 
> 컴포넌트 `DButton` 또는 `DMenu`의 사용과 결합하면 보일러플레이트 클래스를 처리하여 버튼이 게시물 메뉴의 다른 버튼과 동일한 형식을 따르도록 보장합니다.
> 
> 추가적인 형식은 사용자 정의 클래스를 사용하여 지정할 수 있습니다.

### replacePostMenuButton

- 변경 전:

```js
withPluginApi("1.34.0", (api) => {
  api.replacePostMenuButton("like", {
    name: "discourse-reactions-actions",
    buildAttrs: (widget) => {
      return { post: widget.findAncestorModel() };
    },
    shouldRender: (widget) => {
      const post = widget.findAncestorModel();
      return post && !post.deleted_at;
    },
  });
});

```

- 변경 후:

```js
import ReactionsActionButton from "../components/discourse-reactions-actions-button";

...

withPluginApi("1.34.0", (api) => {
  api.registerValueTransformer(
    "post-menu-buttons",
    ({ value: dag, context: { buttonKeys } }) => {
      // ReactionsActionButton은 새로운 버튼 컴포넌트입니다
      dag.replace(buttonKeys.LIKE, ReactionsActionButton);
    }
  );
});

```

### removePostMenuButton

- 변경 전:

```js
withPluginApi("1.34.0", (api) => {
  api.removePostMenuButton('like', (attrs, state, siteSettings, settings, currentUser) => {
    if (attrs.post_number === 1) {
      return true;
    }
  });
});

```

- 변경 후:

```js
withPluginApi("1.34.0", (api) => {
  api.registerValueTransformer(
    "post-menu-buttons",
    ({ value: dag, context: { post, buttonKeys } }) => {
      if (post.post_number === 1) {
        dag.delete(buttonKeys.LIKE);
      }
    }
  );
});

```

## 🆘 다른 커스터마이징은 어떻게 하나요?

도입한 새 API를 사용하여 커스터마이징을 달성할 수 없는 경우, 토론을 위해 새로운 개발 주제를 생성하여 알려주세요.

## ✨ 저는 플러그인/테마 개발자입니다. 전환 기간 동안 구버전과 새 게시물 메뉴를 모두 지원하는 테마/플러그인을 어떻게 업데이트하나요?

플러그인에서 구버전과 새 버전의 게시물 메뉴를 모두 지원하기 위해 아래 패턴을 사용했습니다:

```js
function customizePostMenu(api) {
  const transformerRegistered = api.registerValueTransformer(
    "post-menu-buttons",
    ({ value: dag, context }) => {
      // 새 게시물 메뉴 커스터마이징
      ...
    }
  );

  const silencedKey =
    transformerRegistered && "discourse.post-menu-widget-overrides";

  withSilencedDeprecations(silencedKey, () => customizeWidgetPostMenu(api));
}

function customizeWidgetPostMenu(api) {
  // 여기 레거시 "위젯" 코드 커스터마이징
  ...
}

export default {
  name: "my-plugin",

  initialize(container) {
    withPluginApi("1.34.0", customizePostMenu);
  }
};

```

## ⭐ 더 많은 예제

새 API 사용법에 대한 예제는 공식 플러그인을 확인해 보세요:

- [`discourse-ai`](https://github.com/discourse/discourse-ai/blob/6ce14a778b02969196545ab7bcc09cad539c1ede/assets/javascripts/initializers/ai-bot-replies.js#L86)
- [`discourse-assign`](https://github.com/discourse/discourse-assign/blob/d2454e91d10b3deb4390f3a167e40d78fcc543a6/assets/javascripts/discourse/initializers/extend-for-assigns.js#L739)
- [`discourse-category-experts`](https://github.com/discourse/discourse-category-experts/blob/e21adb85639a19f3a96d30ac61ed11fcc177e709/assets/javascripts/discourse/initializers/category-experts-post-decorator.js#L56)
- [`discourse-post-voting`](https://github.com/discourse/discourse-post-voting/blob/5eeb8e56b7df8097293fadfcdc9d8ed058afe110/assets/javascripts/discourse/initializers/post-voting-edits.gjs#L167)
- [`discourse-jira`](https://github.com/discourse/discourse-jira/blob/6a342f98de7111231496337be735afdb305a250d/assets/javascripts/discourse/initializers/add-discourse-jira-button.js#L62)
- [`discourse-reactions`](https://github.com/discourse/discourse-reactions/blob/433a07a5e426a63b8e189e41470b3eec21ca750a/assets/javascripts/discourse/initializers/discourse-reactions.gjs#L176)
- [`discourse-shared-edits`](https://github.com/discourse/discourse-shared-edits/blob/d2ca0b892b1c5613b477a327c059430bf67e66d2/assets/javascripts/discourse/initializers/shared-edits-init.js#L123)
- [`discourse-solved`](https://github.com/discourse/discourse-solved/blob/34435397254ddd37e19c012b5f881c8fdcc780e4/assets/javascripts/discourse/initializers/extend-for-solved-button.js#L86)
- [`discourse-translator`](https://github.com/discourse/discourse-translator/blob/fdba17222577552b40a9584d680b9d84d8ce3d8a/assets/javascripts/discourse/initializers/extend-for-translate-button.js#L22)

---

_[View the full topic](https://meta.discourse.org/t/upcoming-post-menu-changes-how-to-prepare-themes-and-plugins/341014)._
