Добавление кнопки опроса в нижнюю часть редактора

@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>

Его предполагаемая логика была следующей: «Это работает только если мы сначала нажмём кнопку «Опрос» в композере. Я думаю, что это связано с тем, что Discourse сработает действие showPollBuilder (именно оно открывает конструктор опросов) только после того, как будет выполнено действие onPopupMenuAction (иконка, открывающая список действий на панели инструментов, например, скрыть детали, размыть спойлер, создать опрос)».

Не найдётся ли кого-нибудь, кто возьмётся за это? :slight_smile:
(Ещё раз огромное спасибо Стивену за такую щедрую помощь!)

Скорее всего, вам не нужно переопределять весь шаблон composer. Вместо этого используйте слот composer-fields-below.

Я не смог удержаться и немного погрузился в кроличью нору. Ха-ха, и вот уже прошло два часа.


Вам нужно добавить файл Handlebars вот так, чтобы отобразить кнопку:

Затем вам понадобится действие, похожее на то, что используется в плагине опросов:

Мы можем передать модальному окну конструктора опросов «фейковое» событие панели инструментов, но проблема в том, что вам также нужно воспроизвести логику в компоненте редактора:

Речь идет о части с _getSelected и _addText. Мне было бы очень интересно узнать, существует ли какой-то API для программатического добавления текста в текстовое поле редактора «извне».

Таков был план: для тестирования было проще переписать шаблон composer, так как плагин-выход, возможно, потребует некоторых доработок, чтобы адаптироваться к его теме.

Но я хотел сначала заставить код работать, а уже потом адаптировать его, однако застрял на этапе написания кода.

Нет, насколько мне известно, у нас сейчас нет ничего для этого.

О, понял. Отличная работа! :slight_smile:

На самом деле это сложнее, чем кажется (ну что тут такого — сдвинуть кнопку вправо? :slight_smile: )

Я немного покопался в этом, и кажется, что наименее запутанный способ — назначить панель инструментов редактора композера свойству контроллера, к которому затем можно получить доступ в outlet.

Если попытаться воссоздать поведение панели инструментов, вы столкнётесь с проблемами, которые уже упомянул @spirobel, но, скорее всего, столкнётесь и с другими проблемами в будущем, когда логика разбора текста будет дорабатываться.

Основной принцип — стараться использовать функциональность на самом высоком уровне абстракции, то есть нам нужна панель инструментов самого редактора композера… так что давайте получим её.

Например:

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);
    }
  }
});

Затем в outlet мы получаем панель инструментов из контроллера композера и добавляем к ней новую кнопку. Возможно, существует способ использовать существующую кнопку опроса (это было бы идеально), но она упакована в логику 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: Возможно, есть более хороший способ найти первый селектор без жесткого кодирования заголовка «Options», на случай, если это станет проблемой.

Да, отлично :+1: Тоже вариант.

Вау, я в шоке, Энгус… Это невероятно, ты гений, дружище! Ты вдохновил меня начать изучать разработку уже сегодня, точно так же, как год назад Йохани вдохновил меня выучить CSS. Спасибо, Энгус, это просто восхитительно. :trophy: