在编辑器底部添加投票按钮

@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 只有在触发了 onPopupMenuAction 动作(即工具栏中打开操作列表的图标,如隐藏详情、模糊Spoiler、创建投票)后,才会触发 showPollBuilder 动作(这是打开投票构建器的动作)。」

是否有人愿意接手解决这个问题?:slight_smile:
(再次感谢 Steven 提供的慷慨帮助!)

您可能无需覆盖整个 composer 模板。请改用 composer-fields-below 插槽。

我没忍住,稍微钻了一下兔子洞。哈哈,结果两个小时就这样过去了。


你需要像这样添加 handlebars 文件来显示按钮。


然后,你需要一些类似于投票插件中的操作:

我们可以向投票构建器模态框传入一个“伪造”的工具栏事件,但问题在于你还需要复制编辑器组件中的逻辑:

_getSelected_addText 部分。我很想知道是否有某种 API 可以从“外部”以编程方式向编辑器的文本区域添加文本。

这原本是计划好的,但为了测试方便,我直接重写了 composer 模板,因为插件出口可能需要一些调整才能适配他的主题。

不过,我的初衷是先让代码跑通,之后再进行调整,结果却卡在编码环节了。

没有,据我所知,我们目前没有在任何方法来实现这一点。

哦,好的,明白了。干得漂亮!:slight_smile:

这实际上比看起来要棘手(挪动一个按钮能有多难,对吧?:))

我简单尝试了一下,发现最整洁的方法是:将编辑器工具栏分配给一个控制器属性,这样你就可以在 outlet 中访问它。

如果你试图重新实现工具栏的行为,就会遇到 @spirobel 提到的那些直接问题,而且随着文本解析逻辑的调整,未来很可能还会遇到其他问题。

基本原则是尽量在最高抽象层级上使用功能,也就是说,我们需要的是实际编辑器(composer editor)的工具栏……那就直接获取它。

例如:

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 中,我们从 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)

附:我刚在 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: 这也是一个选项。

哇,我无语了,Angus……这太不可思议了,你真是个天才,小伙子!你激励我从今天开始学习开发,就像 Johani 一年前激励我学习 CSS 一样。谢谢你,Angus,简直太棒了。:trophy: