작성 화면 하단에 투표 버튼 추가

@Steven 님이 친절하게 도와주셔서 거의 작동하는 스크립트를 만들어 주셨지만, 버튼을 탭해도 아무 반응이 없습니다:

<script type='text/x-handlebars' data-template-name='composer'>
{{#composer-body composer=model
                 showPreview=showPreview
                 openIfDraft=(action "openIfDraft")
                 typed=(action "typed")
                 cancelled=(action "cancelled")
                 save=(action "save")}}
  <div class="grippie"></div>
  {{#if visible}}
      {{composer-messages composer=model
                          messageCount=messageCount
                          addLinkLookup=(action "addLinkLookup")}}
      {{#if model.viewOpenOrFullscreen}}
        <div class="reply-area {{if canEditTags 'with-tags'}}">
          <div class='composer-fields'>
            {{plugin-outlet name="composer-open" args=(hash model=model)}}
            <div class='reply-to'>
              {{#unless model.viewFullscreen}}
                <div class="reply-details">
                  {{composer-action-title
                    model=model
                    openComposer=(action "openComposer")
                    closeComposer=(action "closeComposer")
                    canWhisper=canWhisper
                    tabindex=8}}
                  {{plugin-outlet name="composer-action-after" noTags=true args=(hash model=model)}}

                  {{#unless site.mobileView}}
                    {{#if isWhispering}}
                      <span class='whisper'>{{d-icon "far-eye-slash"}}</span>
                    {{/if}}
                    {{#if model.unlistTopic}}
                      <span class='whisper'>({{i18n 'composer.unlist'}})</span>
                    {{/if}}
                    {{#if model.noBump}}
                      <span class="no-bump">{{d-icon "anchor"}}</span>
                    {{/if}}
                  {{/unless}}

                  {{#if canEdit}}
                    {{#link-to-input onClick=(action "displayEditReason") showInput=showEditReason icon="info-circle" class="display-edit-reason"}}
                      {{text-field value=editReason tabindex="7" id="edit-reason" maxlength="255" placeholderKey="composer.edit_reason_placeholder"}}
                    {{/link-to-input}}
                  {{/if}}
                </div>
              {{/unless}}
              {{composer-toggles composeState=model.composeState
                        toggleComposer=(action "toggle")
                        toggleToolbar=(action "toggleToolbar")
                        toggleFullscreen=(action "fullscreenComposer")}}
            </div>
            {{#unless model.viewFullscreen}}
              {{#if model.canEditTitle}}
                {{#if model.creatingPrivateMessage}}
                  <div class='user-selector'>
                    {{composer-user-selector topicId=topicModel.id
                                             usernames=model.targetRecipients
                                             hasGroups=model.hasTargetGroups
                                             focusTarget=focusTarget
                                             class="users-input"}}
                    {{#if showWarning}}
                      <label class='add-warning'>
                        {{input type="checkbox" checked=model.isWarning tabindex="3"}}
                        {{i18n "composer.add_warning"}}
                      </label>
                    {{/if}}
                  </div>
                {{/if}}

                <div class="title-and-category {{if showPreview 'with-preview'}}">

                  {{composer-title composer=model lastValidatedAt=lastValidatedAt focusTarget=focusTarget}}

                  {{#if model.showCategoryChooser}}
                    <div class="category-input">
                      {{category-chooser
                        value=model.categoryId
                        tabindex="3"
                        onChange=(action (mut model.categoryId))
                        isDisabled=disableCategoryChooser
                        options=(hash
                          scopedCategoryId=scopedCategoryId
                        )
                      }}
                      {{popup-input-tip validation=categoryValidation}}
                    </div>
                  {{/if}}
                  {{#if canEditTags}}
                    {{mini-tag-chooser
                      value=model.tags
                      tabindex=4
                      isDisabled=disableTagsChooser
                      onChange=(action (mut model.tags))
                      options=(hash
                        categoryId=model.categoryId
                        minimum=model.minimumRequiredTags
                      )
                    }}
                    {{popup-input-tip validation=tagValidation}}
                  {{/if}}
                </div>
              {{/if}}

              {{plugin-outlet name="composer-fields" args=(hash model=model)}}
            {{/unless}}

          </div>

          {{composer-editor topic=topic
                            composer=model
                            lastValidatedAt=lastValidatedAt
                            canWhisper=canWhisper
                            storeToolbarState=(action "storeToolbarState")
                            onPopupMenuAction=(action "onPopupMenuAction")
                            showUploadModal=(route-action "showUploadSelector")
                            popupMenuOptions=popupMenuOptions
                            draftStatus=model.draftStatus
                            isUploading=isUploading
                            allowUpload=allowUpload
                            uploadIcon=uploadIcon
                            isCancellable=isCancellable
                            uploadProgress=uploadProgress
                            groupsMentioned=(action "groupsMentioned")
                            cannotSeeMention=(action "cannotSeeMention")
                            importQuote=(action "importQuote")
                            togglePreview=(action "togglePreview")
                            showToolbar=showToolbar
                            afterRefresh=(action "afterRefresh")
                            focusTarget=focusTarget}}

          <div class='submit-panel'>
            {{plugin-outlet name="composer-fields-below" args=(hash model=model)}}

            <div class='save-or-cancel'>
              {{#unless model.viewFullscreen}}
                {{composer-save-button action=(action "save")
                                       icon=saveIcon
                                       label=saveLabel
                                       disableSubmit=disableSubmit}}

              {{#if site.mobileView}}
                <a href {{action "cancel"}} class='cancel' tabindex="6" title="{{i18n 'cancel'}}">
                  {{#if canEdit}}
                    {{d-icon "times"}}
                  {{else}}
                    {{d-icon "far-trash-alt"}}
                  {{/if}}
                </a>
              {{else}}
                <a href {{action "cancel"}} class='cancel' tabindex="6" >{{i18n 'cancel'}}</a>
              {{/if}}
            {{/unless}}


              {{#if site.mobileView}}
                {{#if whisperOrUnlistTopic}}
                  <span class='whisper'>
                    {{d-icon "far-eye-slash"}}
                  </span>
                {{/if}}
                {{#if model.noBump}}
                  <span class="no-bump">{{d-icon "anchor"}}</span>
                {{/if}}
              {{/if}}


              {{#if isUploading}}
                <div id="file-uploading">
                  {{loading-spinner size="small"}}<span>{{i18n 'upload_selector.uploading'}} {{uploadProgress}}%</span>
                  {{#if isCancellable}}
                    <a href id="cancel-file-upload" {{action "cancelUpload"}}>{{d-icon "times"}}</a>
                  {{/if}}
                </div>
              {{/if}}
              <div id='draft-status' class="{{if isUploading 'hidden'}}">
                {{#if model.draftSaving}}<div class="spinner small"></div>{{/if}}
                {{#if model.draftSaved}}{{d-icon 'check' class='save-animation'}}{{/if}}
                {{#if model.draftStatus}}
                  <span title="{{model.draftStatus}}">
                    {{#if model.draftConflictUser}}
                      {{avatar model.draftConflictUser imageSize="small"}} {{d-icon 'user-edit'}}
                    {{else}}
                      {{d-icon 'sync-alt'}}
                    {{/if}}
                    {{#unless site.mobileView}}
                      {{model.draftStatus}}
                    {{/unless}}
                  </span>
                {{/if}}
              </div>
            </div>

              {{#if site.mobileView}}
                {{#if allowUpload}}
                  <a class="btn btn-default no-text mobile-file-upload {{if isUploading 'hidden'}}">
                    {{d-icon uploadIcon}}
                  </a>
                {{/if}}

                <a href class="btn btn-default no-text mobile-preview" {{action "showPollBuilder"}}>
                  {{d-icon "chart-bar"}}
                </a>
                
                <a href class="btn btn-default no-text mobile-preview" title="{{i18n 'composer.show_preview'}}" {{action "togglePreview"}}>
                  {{d-icon "desktop"}}
                </a>

                {{#if showPreview}}
                  {{d-button action=(action "togglePreview") class="hide-preview" label="composer.hide_preview"}}
                {{/if}}
              {{else}}
                <a href {{action "togglePreview"}} class='toggle-preview'>{{{toggleText}}}</a>
              {{/if}}

          </div>
        </div>

      {{else}}
        <div class='saving-text'>
          {{#if model.createdPost}}
            {{i18n 'composer.saved'}} <a class='permalink' href="{{unbound createdPost.url}}" {{action "viewNewReply"}}>{{i18n 'composer.view_new_post'}}</a>
          {{else}}
            {{i18n 'composer.saving'}} {{loading-spinner size="small"}}
          {{/if}}
        </div>

        <div class='draft-text'>
          {{#if model.topic}}
            {{d-icon "share"}} {{{draftTitle}}}
          {{else}}
            {{i18n "composer.saved_draft"}}
          {{/if}}
        </div>

        {{composer-toggles composeState=model.composeState
          toggleFullscreen=(action "openIfDraft")
          toggleComposer=(action "toggle")
          toggleToolbar=(action "toggleToolbar")}}

      {{/if}}

  {{/if}}

{{/composer-body}}
</script>

Steven 님의 추측은 다음과 같았습니다: “작동하려면 먼저 컴포저의 투표 버튼을 사용해야 합니다. Discourse가 onPopupMenuAction(도구 모음에서 세부 정보 숨기기, 스포일러 흐림 처리, 투표 만들기 등 동작 목록을 여는 아이콘) 동작을 트리거한 후에야 showPollBuilder(투표 빌더를 여는 동작) 동작을 트리거하기 때문이라고 생각합니다.”

혹시 이 문제를 이어받을 수 있는 분 계신가요? :slight_smile:
(다시 한번 Steven 님의 넉넉한 도움에 감사드립니다!)

전체 composer 템플릿을 오버라이드할 필요가 없을 수 있습니다. 대신 composer-fields-below 아웃렛을 사용하세요.

도저히 참을 수 없어 토끼굴을 좀 파보게 되었네요. 하하, 벌써 2시간이 지나버렸어요.


버튼을 표시하려면 핸들바(Handlebars) 파일을 이렇게 추가해야 합니다.


그리고 다음으로, 투표(poll) 플러그인에 있는 것과 유사한 액션이 필요합니다:

투표 빌더 모달에 “가짜” 툴바 이벤트를 전달할 수는 있지만, 문제는 에디터 컴포넌트 내부의 로직도 재현해야 한다는 점입니다.

_getSelected와 _addText가 포함된 부분 말입니다. "외부"에서 에디터의 텍스트 영역에 텍스트를 프로그래매틱으로 추가할 수 있는 API가 있는지 매우 궁금합니다.

그것이 원래 계획이었지만, 플러그인 아웃렛이 테마에 맞게 조정되려면 몇 가지 수정이 필요할 수 있으므로 테스트 목적으로는 composer 템플릿을 다시 작성하는 것이 더 쉬웠습니다.

하지만 먼저 코드가 동작하도록 만들고 나중에 조정할 생각이었지만, 코딩에 막혀 있었습니다.

AFAIK 현재 이를 수행할 수 있는 기능은 없습니다.

아, 알겠어요. 잘했어요! :slight_smile:

실제로는 보이는 것보다 더 까다로운 문제입니다 (버튼을 이동하는 게 얼마나 어렵겠어요? :)).

이 문제를 잠시 살펴보았는데, 가장 덜 지저분한 방법은 composer 에디터의 툴바를 컨트롤러의 프로퍼티에 할당하여 아웃렛(outlet)에서 접근할 수 있도록 하는 것 같습니다.

툴바의 동작을 재현하려고 하면 @spirobel 님이 언급한 즉각적인 문제에 부딪히게 되며, 텍스트 파싱 로직이 조정될 때 다른 문제들도 발생할 가능성이 큽니다.

기본 원칙은 추상화 수준이 가장 높은 곳에서 기능을 사용하려는 것입니다. 즉, 실제 composer 에디터의 툴바가 필요합니다. 따라서 그 툴바를 가져오겠습니다.

즉,

api.modifyClass("component:d-editor", {
  @on('didInsertElement')
  makeToolbarAccessible() {
    if (this.outletArgs && this.outletArgs.editorType === 'composer') {
      const controller = getOwner(this).lookup('controller:composer');
      controller.set('editorToolbar', this.toolbar);
    }
  }
});

그런 다음 아웃렛에서 composer 컨트롤러로부터 툴바를 가져와 새 버튼을 추가합니다. 기존 투표 버튼을 사용하는 방법이 있을 수 있습니다(그것이 이상적이겠지만), 그것은 popupMenu 로직에 묶여 있어 분리하는 방법을 찾지 못했습니다.

setupComponent(attrs, ctx) {
  const controller = getOwner(this).lookup('controller:composer');
  
  controller.addObserver('editorToolbar', function() {
    if (this._state === 'destroying') return;

    const toolbar = controller.editorToolbar;
          
    toolbar.addButton({
      group: "extras",
      icon: "chart-bar",
      title: "poll.ui_builder.title",
      sendAction: e => {
        controller.send('storeToolbarState', e);
        this.send("showPollBuilder");
      }
    });
    
    const extras = toolbar.groups.find(g => g.group == 'extras');
    const pollButton = extras.buttons.find(b => b.icon == "chart-bar");
        
    ctx.set('pb', pollButton);
  });
}

그리고 템플릿 자체에서는

{{d-button
  type="button"
  action=pb.action
  actionParam=pb
  translatedTitle=pb.title
  icon=pb.icon
  class=pb.className}}

@nexo 이 테마 컴포넌트를 한번 시도해 보세요: discourse-poll-button-bottom.zip (2.5 KB)

p.s. 방금 thepavilion.io에 로드해 보았는데 작동하는 것 같습니다 (모바일 전용).

와, 정말 멋진 해결책이네요. 제 방식은 그거랑 비교하면 좀 심심하긴 해요:

export default {
  actions: {
    showPollBuilderNextToSave() {
$("button[title='Options']").trigger('click')



console.log($("button[title='Options']"))
Ember.run.later((function() {
  $("li[data-value='showPollBuilder']").trigger('click');
    console.log($("li[data-value='showPollBuilder']"))
 
}), 50);

    }
  }
};

저는 그냥 jQuery로 버튼을 클릭하는 방식을 쓰고 있어요 :smiley: 혹시 이 방식이 문제가 될 수 있으니, 옵션 타이틀을 하드코딩하지 않고 첫 번째 셀렉터를 찾는 더 나은 방법이 있을까요?

그래, 잘했네 :+1: 이것도 하나의 옵션이네.

와, 정말 말문이 막혀버렸어, 앵거스.. 이건 정말 믿을 수 없어, 넌 천재야, 친구! 1년 전 요하니가 CSS를 배우도록 나를 격려해줬던 것처럼, 오늘부터 개발을 배우기 시작할 만큼 큰 동기부여가 됐어. 정말 대단해, 앵거스. :trophy: