페이지의 플러그인 아웃렛에 hbs 템플릿 삽입: user-preferences-interface에서 user-preferences-account로

안녕하세요, 사용자가 다크 모드와 라이트 모드 중 원하는 것을 선택할 수 있도록 user-preferences-account에 추가하려고 합니다. 아래 코드를 사용하여 플러그인 아웃렛에 넣으려 했는데, 선택 항목으로 표시되지 않고 코드가 작동하지 않습니다. 어떻게 하면 여기에 올바르게 추가할 수 있는지 조언해 주실 수 있을까요? 저에게 매우 중요한 문제입니다.

네, 감사합니다.

{{#if showColorSchemeSelector}}
  <fieldset class="control-group color-scheme">
    <legend class="control-label">{{i18n "user.color_scheme"}}</legend>
    <div class="control-subgroup light-color-scheme">
      {{#if showDarkColorSchemeSelector}}
        <div class="instructions">{{i18n "user.color_schemes.regular" }}</div>
      {{/if}}
      <div class="controls">
        {{combo-box
          content=userSelectableColorSchemes
          value=selectedColorSchemeId
          onChange=(action "loadColorScheme")
          options=(hash
            translatedNone=selectedColorSchemeNoneLabel
            autoInsertNoneItem=showColorSchemeNoneItem
          )
        }}
      </div>
    </div>
    {{#if showDarkColorSchemeSelector}}
      <div class="control-subgroup dark-color-scheme">
        <div class="instructions">{{i18n "user.color_schemes.dark" }}</div>
        <div class="controls">
          {{combo-box
            content=userSelectableDarkColorSchemes
            value=selectedDarkColorSchemeId
            onChange=(action "loadDarkColorScheme")}}
        </div>
      </div>

      <div class="instructions">
        {{i18n "user.color_schemes.dark_instructions" }}
      </div>
    {{/if}}

    {{#if previewingColorScheme}}
      {{#if previewingColorScheme}}
        {{d-button action=(action "undoColorSchemePreview") label="user.color_schemes.undo" icon="undo" class="btn-default btn-small undo-preview"}}
      {{/if}}

      <div class="controls color-scheme-checkbox">
        {{preference-checkbox labelKey="user.color_scheme_default_on_all_devices" checked=makeColorSchemeDefault}}
      </div>
    {{/if}}
  </fieldset>
{{/if}}

이 코드를 여기에 붙여넣어 올바르게 작동하게 하려고 합니다.


<script type="text/x-handlebars" data-template-name="/connectors/user-preferences-account/terajsok-custom-user-preferences"> </script>

이런 개입을 어떻게 하는지 아시는 분이 계신가요?

안녕하세요,

네, 이 방식으로는 작동하지 않을 것 같습니다. Developing Discourse Themes & Theme Components 링크를 읽어보시는 것을 권장합니다.

제 이해가 맞다면, 이 섹션을 인터페이스 페이지에서 :arrow_down_small:

계정 페이지 여기로 :arrow_down_small:

이동하려는 것이신 것 같습니다.

네, 바로 제가 하고 싶었던 것입니다. 그 부분 도와주실 수 있을까요? 추천해 주신 주제는 이미 읽어 보았지만, 이 특정 경우에는 도움이 되지 않았습니다.

모든 사용자 설정을 하나의 페이지로 이동하는 것이 가능한가요? 이전 버전의 Discourse에서는 이렇게 작동했던 것 같습니다.

이것이 작동하지 않을 수 있는 이유에 대해 일부는 이미 여기서 다뤘습니다:

컨트롤러나 컴포넌트 JS를 고려하지 않으면 이것이 작동할 것이라고 기대할 수 없습니다.

라우트 간에 인터페이스 템플릿 요소만 이동하는 것은 다음 요소를 고려하지 않는 한 작동하지 않을 가능성이 높습니다:

  • 계산된 속성(computed properties)
  • 액션(actions)
  • 직렬화된 데이터(serialised data)
  • 등

안녕하세요, Juraj 팀원입니다.

네, 기존 플러그인을 편집하여 사용자 인터페이스 설정을 포함하는 방식으로 이미 확인했습니다. (hbs 템플릿과 js 컨트롤러 포함). 원하지 않는 설정만 표시하도록 하는 새로운 사용자 설정 페이지를 만드는 것이 유일한 해결책인 것 같습니다. 기존 함수를 가져와서 우리가 만든 사용자 정의 설정 페이지와 함께 작동하도록 하려면 어떻게 해야 하나요?

필요한 모델이 통과되는 한 플러그인 아웃렛을 사용하는 것이 좋습니다.

해당 아웃렛 내에서 소스에서 필요한 모든 JS 요소를 복제해야 합니다.

플러그인 아웃렛 내에서 컨트롤러를 설정하는 JS의 예시는 다음과 같습니다:

컴포넌트 내에서 모든 작업을 수행하면 이러한 다소 비표준적인 컨트롤러 정의 방식을 피할 수 있습니다. 예를 들어 다음과 같이요:

답변해 주셔서 감사합니다. 하지만 현재 저희는 다음과 같은 상태입니다:

<script type="text/x-handlebars" data-template-name="/connectors/user-preferences-account/tg-custom-prefs">

<fieldset class="control-group color-scheme">
    <legend class="control-label">{{i18n "user.color_scheme"}}</legend>
    <div class="control-subgroup light-color-scheme">
      <div class="controls">
        <ComboBox @content={{this.userSelectableColorSchemes}} @value={{this.selectedColorSchemeId}} @onChange={{action "loadColorScheme"}} @options={{hash
            translatedNone=this.selectedColorSchemeNoneLabel
            autoInsertNoneItem=this.showColorSchemeNoneItem
          }} />
      </div>
    </div>
</fieldset>
</script>

이것은 interface.hbs 템플릿에서 복사한 코드입니다. 이 컴포넌트 스크립트에 JS를 삽입할 수 없습니다. setupComponent 함수를 어디에 넣어야 하나요?

링크된 플러그인의 파일 구조를 복사하세요. (테마 컴포넌트를 사용하는 경우 최신 표준에 맞게 작성된 예제 테마 컴포넌트를 사용할 수 있습니다.)

이러한 스크립트 태그를 사용하지 말고, 적절한 JavaScript 파일 구조를 사용하세요. 더 전문적이고(또는 논리적으로) 유지보수가 더 쉽습니다.

대략적으로 말하자면, 다음 파일들로 테마 컴포넌트를 만들었습니다.

/about.json
/javascripts/discourse/connectors/user-preferences-account/component-test.hbs
/javascripts/discourse/connectors/user-preferences-account/component-test.js.es6

관련된 hbs 코드를 템플릿에 붙여넣고, 사용자 설정 - 인터페이스 컨트롤러에서 자바스크립트 코드를 전체적으로 복사했습니다. 태그에 직접 붙여넣었을 때와 동일한 결과를 얻고 있습니다. 예제 JS가 플러그인 아웃렛 내에서 컨트롤러를 설정한다는 말씀이 무슨 의미인지 이해하지 못하는 것 같습니다.

가능하다면 코드를 GitHub에 push하고 공유하는 것을 권장합니다.

이것은 오타인 것 같으며, .js 또는 .js.es6이어야 합니다.

용어를 사용할 때 조금 더 주의하세요. Theme Component 또는 Plugin 중 하나입니다.

죄송합니다, 네, js.es6를 말하는 것이었습니다. 네, 테마 컴포넌트입니다.

저는 해당 테마 컴포넌트를 GitHub에 업로드하지 않았습니다.

/about.json:

{
  "name": "Component Test",
  "component": true,
  "license_url": null,
  "about_url": null,
  "authors": null,
  "theme_version": null,
  "minimum_discourse_version": null,
  "maximum_discourse_version": null,
  "assets": {
  },
  "color_schemes": {
  },
  "modifiers": {
  },
  "learn_more": "https://meta.discourse.org/t/beginners-guide-to-using-discourse-themes/91966"
}

/javascripts/discourse/connectors/user-preferences-account/component-test.hbs

<fieldset class="control-group color-scheme">
    <legend class="control-label">{{i18n "user.color_scheme"}}</legend>
    <div class="control-subgroup light-color-scheme">
      <div class="controls">
        <ComboBox @content={{this.userSelectableColorSchemes}} @value={{this.selectedColorSchemeId}} @onChange={{action "loadColorScheme"}} @options={{hash
            translatedNone=this.selectedColorSchemeNoneLabel
            autoInsertNoneItem=this.showColorSchemeNoneItem
          }} />
      </div>
    </div>
</fieldset>

/javascripts/discourse/connectors/user-preferences-account/component-test.js.es6

import Controller, { inject as controller } from "@ember/controller";
import Session from "discourse/models/session";
import { setDefaultHomepage } from "discourse/lib/utilities";
import {
  listColorSchemes,
  loadColorSchemeStylesheet,
  updateColorSchemeCookie,
} from "discourse/lib/color-scheme-picker";
import { listThemes, setLocalTheme } from "discourse/lib/theme-selector";
import { not, reads } from "@ember/object/computed";
import I18n from "I18n";
import { computed } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { reload } from "discourse/helpers/page-reloader";
import { propertyEqual } from "discourse/lib/computed";

const USER_HOMES = {
  1: "latest",
  2: "categories",
  3: "unread",
  4: "new",
  5: "top",
  6: "bookmarks",
  7: "unseen",
};

const TEXT_SIZES = ["smallest", "smaller", "normal", "larger", "largest"];
const TITLE_COUNT_MODES = ["notifications", "contextual"];

export default Controller.extend({
  currentThemeId: -1,
  previewingColorScheme: false,
  selectedDarkColorSchemeId: null,
  preferencesController: controller("preferences"),
  makeColorSchemeDefault: true,
  canPreviewColorScheme: propertyEqual("model.id", "currentUser.id"),

  init() {
    this._super(...arguments);

    this.set("selectedDarkColorSchemeId", this.session.userDarkSchemeId);
  },

  @discourseComputed("makeThemeDefault")
  saveAttrNames(makeThemeDefault) {
    let attrs = [
      "locale",
      "external_links_in_new_tab",
      "dynamic_favicon",
      "enable_quoting",
      "enable_defer",
      "automatically_unpin_topics",
      "allow_private_messages",
      "enable_allowed_pm_users",
      "homepage_id",
      "hide_profile_and_presence",
      "text_size",
      "title_count_mode",
      "skip_new_user_tips",
      "color_scheme_id",
      "dark_scheme_id",
    ];

    if (makeThemeDefault) {
      attrs.push("theme_ids");
    }

    return attrs;
  },

  @discourseComputed()
  availableLocales() {
    return JSON.parse(this.siteSettings.available_locales);
  },

  @discourseComputed
  defaultDarkSchemeId() {
    return this.siteSettings.default_dark_mode_color_scheme_id;
  },

  @discourseComputed
  textSizes() {
    return TEXT_SIZES.map((value) => {
      return { name: I18n.t(`user.text_size.${value}`), value };
    });
  },

  homepageId: computed(
    "model.user_option.homepage_id",
    "userSelectableHome.[]",
    function () {
      return (
        this.model.user_option.homepage_id ||
        this.userSelectableHome.firstObject.value
      );
    }
  ),

  @discourseComputed
  titleCountModes() {
    return TITLE_COUNT_MODES.map((value) => {
      return { name: I18n.t(`user.title_count_mode.${value}`), value };
    });
  },

  @discourseComputed
  userSelectableThemes() {
    return listThemes(this.site);
  },

  @discourseComputed("userSelectableThemes")
  showThemeSelector(themes) {
    return themes && themes.length > 1;
  },

  @discourseComputed("themeId")
  themeIdChanged(themeId) {
    if (this.currentThemeId === -1) {
      this.set("currentThemeId", themeId);
      return false;
    } else {
      return this.currentThemeId !== themeId;
    }
  },

  @discourseComputed
  userSelectableColorSchemes() {
    return listColorSchemes(this.site);
  },

  showColorSchemeSelector: reads("userSelectableColorSchemes.length"),
  selectedColorSchemeNoneLabel: I18n.t(
    "user.color_schemes.default_description"
  ),

  @discourseComputed(
    "userSelectableThemes",
    "userSelectableColorSchemes",
    "themeId"
  )
  currentSchemeCanBeSelected(userThemes, userColorSchemes, themeId) {
    if (!userThemes || !themeId) {
      return false;
    }

    const theme = userThemes.findBy("id", themeId);
    if (!theme) {
      return false;
    }

    return userColorSchemes.findBy("id", theme.color_scheme_id);
  },

  showColorSchemeNoneItem: not("currentSchemeCanBeSelected"),

  @discourseComputed("model.user_option.theme_ids", "themeId")
  showThemeSetDefault(userOptionThemes, selectedTheme) {
    return !userOptionThemes || userOptionThemes[0] !== selectedTheme;
  },

  @discourseComputed("model.user_option.text_size", "textSize")
  showTextSetDefault(userOptionTextSize, selectedTextSize) {
    return userOptionTextSize !== selectedTextSize;
  },

  homeChanged() {
    const siteHome = this.siteSettings.top_menu.split("|")[0].split(",")[0];
    const userHome = USER_HOMES[this.get("model.user_option.homepage_id")];

    setDefaultHomepage(userHome || siteHome);
  },

  @discourseComputed()
  userSelectableHome() {
    let homeValues = {};
    Object.keys(USER_HOMES).forEach((newValue) => {
      const newKey = USER_HOMES[newValue];
      homeValues[newKey] = newValue;
    });

    let result = [];
    this.siteSettings.top_menu.split("|").forEach((m) => {
      let id = homeValues[m];
      if (id) {
        result.push({ name: I18n.t(`filters.${m}.title`), value: Number(id) });
      }
    });
    return result;
  },

  @discourseComputed
  showDarkModeToggle() {
    return this.defaultDarkSchemeId > 0 && !this.showDarkColorSchemeSelector;
  },

  @discourseComputed
  userSelectableDarkColorSchemes() {
    return listColorSchemes(this.site, {
      darkOnly: true,
    });
  },

  @discourseComputed("userSelectableDarkColorSchemes")
  showDarkColorSchemeSelector(darkSchemes) {
    // when a default dark scheme is set
    // dropdown has two items (disable / use site default)
    // but we show a checkbox in that case
    const minToShow = this.defaultDarkSchemeId > 0 ? 2 : 1;
    return darkSchemes && darkSchemes.length > minToShow;
  },

  enableDarkMode: computed({
    set(key, value) {
      return value;
    },
    get() {
      return this.get("model.user_option.dark_scheme_id") === -1 ? false : true;
    },
  }),

  selectedColorSchemeId: computed({
    set(key, value) {
      return value;
    },
    get() {
      if (!this.session.userColorSchemeId) {
        return;
      }

      const theme = this.userSelectableThemes?.findBy("id", this.themeId);

      // we don't want to display the numeric ID of a scheme
      // when it is set by the theme but not marked as user selectable
      if (
        theme?.color_scheme_id === this.session.userColorSchemeId &&
        !this.userSelectableColorSchemes.findBy(
          "id",
          this.session.userColorSchemeId
        )
      ) {
        return;
      } else {
        return this.session.userColorSchemeId;
      }
    },
  }),

  actions: {
    save() {
      this.set("saved", false);
      const makeThemeDefault = this.makeThemeDefault;
      if (makeThemeDefault) {
        this.set("model.user_option.theme_ids", [this.themeId]);
      }

      const makeTextSizeDefault = this.makeTextSizeDefault;
      if (makeTextSizeDefault) {
        this.set("model.user_option.text_size", this.textSize);
      }

      if (!this.showColorSchemeSelector) {
        this.set("model.user_option.color_scheme_id", null);
      } else if (this.makeColorSchemeDefault) {
        this.set(
          "model.user_option.color_scheme_id",
          this.selectedColorSchemeId
        );
      }

      if (this.showDarkModeToggle) {
        this.set(
          "model.user_option.dark_scheme_id",
          this.enableDarkMode ? null : -1
        );
      } else {
        // if chosen dark scheme matches site dark scheme, no need to store
        if (
          this.defaultDarkSchemeId > 0 &&
          this.selectedDarkColorSchemeId === this.defaultDarkSchemeId
        ) {
          this.set("model.user_option.dark_scheme_id", null);
        } else {
          this.set(
            "model.user_option.dark_scheme_id",
            this.selectedDarkColorSchemeId
          );
        }
      }

      return this.model
        .save(this.saveAttrNames)
        .then(() => {
          this.set("saved", true);

          if (makeThemeDefault) {
            setLocalTheme([]);
          } else {
            setLocalTheme(
              [this.themeId],
              this.get("model.user_option.theme_key_seq")
            );
          }
          if (makeTextSizeDefault) {
            this.model.updateTextSizeCookie(null);
          } else {
            this.model.updateTextSizeCookie(this.textSize);
          }

          if (this.makeColorSchemeDefault) {
            updateColorSchemeCookie(null);
            updateColorSchemeCookie(null, { dark: true });
          } else {
            updateColorSchemeCookie(this.selectedColorSchemeId);

            if (
              this.defaultDarkSchemeId > 0 &&
              this.selectedDarkColorSchemeId === this.defaultDarkSchemeId
            ) {
              updateColorSchemeCookie(null, { dark: true });
            } else {
              updateColorSchemeCookie(this.selectedDarkColorSchemeId, {
                dark: true,
              });
            }
          }

          this.homeChanged();

          if (this.themeId !== this.currentThemeId) {
            reload();
          }
        })
        .catch(popupAjaxError);
    },

    selectTextSize(newSize) {
      const classList = document.documentElement.classList;

      TEXT_SIZES.forEach((name) => {
        const className = `text-size-${name}`;
        if (newSize === name) {
          classList.add(className);
        } else {
          classList.remove(className);
        }
      });

      // Force refresh when leaving this screen
      this.session.requiresRefresh = true;
      this.set("textSize", newSize);
    },

    loadColorScheme(colorSchemeId) {
      this.setProperties({
        selectedColorSchemeId: colorSchemeId,
        previewingColorScheme: this.canPreviewColorScheme,
      });

      if (!this.canPreviewColorScheme) {
        return;
      }

      if (colorSchemeId < 0) {
        const defaultTheme = this.userSelectableThemes.findBy(
          "id",
          this.themeId
        );

        if (defaultTheme && defaultTheme.color_scheme_id) {
          colorSchemeId = defaultTheme.color_scheme_id;
        }
      }
      loadColorSchemeStylesheet(colorSchemeId, this.themeId);
      if (this.selectedDarkColorSchemeId === -1) {
        // set this same scheme for dark mode preview when dark scheme is disabled
        loadColorSchemeStylesheet(colorSchemeId, this.themeId, true);
      }
    },

    loadDarkColorScheme(colorSchemeId) {
      this.setProperties({
        selectedDarkColorSchemeId: colorSchemeId,
        previewingColorScheme: this.canPreviewColorScheme,
      });

      if (!this.canPreviewColorScheme) {
        return;
      }

      if (colorSchemeId === -1) {
        // load preview of regular scheme when dark scheme is disabled
        loadColorSchemeStylesheet(
          this.selectedColorSchemeId,
          this.themeId,
          true
        );
        Session.currentProp("darkModeAvailable", false);
      } else {
        loadColorSchemeStylesheet(colorSchemeId, this.themeId, true);
        Session.currentProp("darkModeAvailable", true);
      }
    },

    undoColorSchemePreview() {
      this.setProperties({
        selectedColorSchemeId: this.session.userColorSchemeId,
        selectedDarkColorSchemeId: this.session.userDarkSchemeId,
        previewingColorScheme: false,
      });
      const darkStylesheet = document.querySelector("link#cs-preview-dark"),
        lightStylesheet = document.querySelector("link#cs-preview-light");
      if (darkStylesheet) {
        darkStylesheet.remove();
      }

      if (lightStylesheet) {
        lightStylesheet.remove();
      }
    },
  },
});

제가 가진 것은 전부이며, 원본 파일에서 그대로 복사한 것입니다.

가장 먼저 확인해야 할 것: 콘솔에 오류가 있는지?

친구여, "그대로 복사"하는 방식은 많은 경우 작동하지 않을 것입니다. 무엇을 하고 있는지 이해하고 그에 맞게 작업해야 합니다.

export default Controller.extend({

이것이 바로 해당 사례입니다. 이 특정 컨텍스트에서는 작동하지 않을 가능성이 높습니다.

제가 제안했듯이, 제가 공유한 예제 코드를 확인해 주세요.

다음과 같은 형태가 더 적합합니다:

export default {
  setupComponent(args, component) {

이는 커넥터(connector)이기 때문입니다.

도와주셔서 감사합니다. 아니요, 콘솔에서 에러가 보이지 않아요. 코드가 어디에 부족한지 확인할 수 있어서 항상 가장 먼저 콘솔을 확인하거든요.

제시해 주신 두 함수만으로 자바스크립트를 교체했는데, 예제에서 무엇을 가져와야 할지 모르겠어요. 말씀드린 것처럼, 콘솔에서 에러가 보이지 않아서 무엇을 구현해야 할지 확신이 서지 않아요.