Project

General

Profile

Feature #43259 » 0004-wiki-toolbar-allow-plugins-to-add-buttons.patch

Florian Walchshofer, 2026-10-30 21:58

View differences:

app/javascript/controllers/wiki_toolbar_controller.js
13 13
// textarea's position on connect, so that existing views only need to call
14 14
// wikitoolbar_for. The markup inserted by each button is defined by the text
15 15
// formatter in app/javascript/wiki_formatting/<format>.js, which is loaded on
16
// connect.
16
// connect. Buttons added by plugins are handled by listening to the
17
// wiki-toolbar:button event.
17 18
export default class extends Controller {
18 19
  static targets = ['editor', 'preview', 'editTab', 'previewTab', 'toolbar', 'precodeMenu', 'tableMenu']
19 20
  static values = {
......
52 53

  
53 54
  async apply({ params: { button } }) {
54 55
    await this.syntaxLoaded
55
    this.syntax?.[button]?.(this)
56
    const action = this.syntax?.[button]
57
    if (action) {
58
      action(this)
59
    } else {
60
      this.dispatch('button', { detail: { button, editor: this } })
61
    }
56 62
  }
57 63

  
58 64
  async insertPrecode({ params: { lang } }) {
lib/redmine/wiki_toolbar.rb
23 23
  # The toolbar only describes the layout: which buttons are displayed and in
24 24
  # which groups. What each button inserts in the text is defined for each text
25 25
  # formatter in app/javascript/wiki_formatting/<format>.js.
26
  #
27
  # Plugins can add a button by extending #button_groups and #labels (and
28
  # #shortcuts) of a text formatter toolbar. The button is handled in JavaScript
29
  # by listening to the wiki-toolbar:button event, which gives the name of the
30
  # button and the editor:
31
  #
32
  #   document.addEventListener('wiki-toolbar:button', ({ detail: { button, editor } }) => {
33
  #     if (button === 'mark') editor.wrap('%{background:yellow}', '%')
34
  #   })
26 35
  class WikiToolbar
27 36
    LABELS = {
28 37
      'strong' => :label_wiki_toolbar_strong,
......
85 94
      raise NotImplementedError
86 95
    end
87 96

  
97
    # Labels of the buttons
98
    def labels
99
      LABELS
100
    end
101

  
102
    # Keys used with Ctrl (Cmd on macOS) to run the buttons
103
    def shortcuts
104
      SHORTCUTS
105
    end
106

  
88 107
    def render(field_id, preview_url)
89 108
      tag.div(class: 'jstBlock', hidden: true,
90 109
              data: {
......
140 159
    end
141 160

  
142 161
    def render_button(name)
143
      title = l(LABELS.fetch(name))
162
      title = l(labels.fetch(name))
144 163
      data =
145 164
        if MENUS.include?(name)
146 165
          {action: 'wiki-toolbar#toggleMenu', wiki_toolbar_menu_param: name}
......
149 168
        else
150 169
          {action: 'wiki-toolbar#apply', wiki_toolbar_button_param: name}
151 170
        end
152
      data[:wiki_toolbar_shortcut] = SHORTCUTS[name] if SHORTCUTS[name]
171
      data[:wiki_toolbar_shortcut] = shortcuts[name] if shortcuts[name]
153 172

  
154 173
      tag.button(tag.span(title), type: 'button', class: "jstb_#{name}", title: title, tabindex: 200, data: data)
155 174
    end
test/system/wiki_toolbar_test.rb
75 75
    end
76 76
  end
77 77

  
78
  def test_button_added_by_a_plugin_should_trigger_an_event
79
    log_user('jsmith', 'jsmith')
80
    visit '/projects/ecookbook/issues/new'
81

  
82
    within 'div.jstBlock' do
83
      assert_selector 'button.jstb_strong'
84
    end
85
    page.execute_script(<<~JS)
86
      document.addEventListener('wiki-toolbar:button', ({ detail: { button, editor } }) => {
87
        if (button === 'mark') editor.wrap('[', ']')
88
      })
89
      const button = document.querySelector('.jstb_strong').cloneNode(true)
90
      button.className = 'jstb_mark'
91
      button.dataset.wikiToolbarButtonParam = 'mark'
92
      document.querySelector('.jstElements').append(button)
93
    JS
94
    fill_in 'Description', :with => 'text'
95
    select_text('issue_description', 0, 4)
96
    find('button.jstb_mark').click
97

  
98
    assert_equal '[text]', find('#issue_description').value
99
  end
100

  
78 101
  def test_insert_table_using_common_mark
79 102
    with_settings :text_formatting => 'common_mark' do
80 103
      log_user('jsmith', 'jsmith')
test/unit/lib/redmine/wiki_toolbar_test.rb
64 64
    end
65 65
  end
66 66

  
67
  def test_button_added_by_a_plugin
68
    toolbar_class = Class.new(Redmine::WikiFormatting::Textile::WikiToolbar) do
69
      def button_groups
70
        super + [%w[mark]]
71
      end
72

  
73
      def labels
74
        super.merge('mark' => :button_add)
75
      end
76

  
77
      def shortcuts
78
        super.merge('mark' => 'm')
79
      end
80
    end
81

  
82
    html = toolbar_class.new(self).render('issue_description', '/preview')
83

  
84
    assert_select_in html, 'button.jstb_mark[title=Add][data-action=?][data-wiki-toolbar-button-param=mark][data-wiki-toolbar-shortcut=m]',
85
                     'wiki-toolbar#apply'
86
  end
87

  
67 88
  def test_textile_buttons
68 89
    with_settings :text_formatting => 'textile' do
69 90
      html = wikitoolbar_for('issue_description')
(8-8/8)