工具栏菜单按钮是一种工具栏按钮,点击后会打开一个菜单。此菜单还可包含子菜单。这对于将原本需要多个按钮的操作分组非常有用。它还可以用于减少视觉杂乱并节省 UI 空间,因为菜单栏菜单项和一些工具栏按钮可以移动到工具栏菜单按钮中。理论上,所有菜单栏菜单项都可以移动到工具栏菜单按钮中,从而使编辑器能够在完全没有菜单栏的情况下使用。

例如:表格插件的 table 工具栏按钮打开的菜单类似于菜单栏中的“表格”菜单。

选项

名称值要求说明

fetch

(success: (menu) => void, fetchContext) => void

必填

一个接受回调函数和 fetchContext 对象的函数。回调函数必须传入按钮下拉菜单的选项列表。fetchContext 对象提供了选择哪些项应传递给回调函数所需的信息。有关 fetchContext 的更多详细信息,请参阅“可搜索菜单按钮”。

text

string

可选

未找到图标时要显示的文本。

icon

string

可选

要显示的图标的名称。必须对应于图标:在 图标包中、在 自定义图标包中,或通过 addIcon API添加的图标。

search

boolean or object

可选

如果不为 false,则向菜单添加搜索字段。有关更多详细信息,请参阅“可搜索菜单按钮”。

tooltip

string

可选

按钮工具提示的文本。

onSetup

(api) => (api) => void

可选

默认值:() => () => {} - 按钮渲染时调用的函数。有关详细信息,请参阅:使用 onSetup。

context

string

可选

默认值:mode:design - context 属性根据编辑器的当前状态动态启用或禁用按钮。有关详细信息,请参阅:Context。

API

名称值说明

isEnabled

() => boolean

检查按钮是否已启用。

setEnabled

(state: boolean) => void

设置按钮的启用状态。

setText

(text: string) => void

设置要显示的文本标签。

setIcon

(icon: string) => void

设置按钮的图标。

菜单按钮示例与说明

以下是一个简单的工具栏菜单按钮示例:

HTML

<textarea id="custom-toolbar-menu-button">
  <p><img style="display: block; margin-left: auto; margin-right: auto;" title="Tiny Logo" src="https://www.tiny.cloud/docs/tinymce/latest/_images/logos/android-chrome-256x256.png" alt="TinyMCE Logo" width="128" height="128"></p>
  <h2 style="text-align: center;">Welcome to the TinyMCE editor demo!</h2>
  <p>Select a menu item from the listbox above and it will insert contents into the editor at the caret position.</p>

  <h2>Got questions or need help?</h2>
  <ul>
    <li>Our <a href="https://www.tiny.cloud/docs/tinymce/8/">documentation</a> is a great resource for learning how to configure TinyMCE.</li>
    <li>Have a specific question? Try the <a href="https://stackoverflow.com/questions/tagged/tinymce" target="_blank" rel="noopener"><code>tinymce</code> tag at Stack Overflow</a>.</li>
    <li>We also offer enterprise grade support as part of <a href="https://www.tiny.cloud/pricing">TinyMCE premium plans</a>.</li>
  </ul>

  <h2>Found a bug?</h2>
  <p>If you think you have found a bug please create an issue on the <a href="https://github.com/tinymce/tinymce/issues">GitHub repo</a> to report it to the developers.</p>

  <h2>Finally ...</h2>
  <p>Need file uploads in your app? Consider using <a href="https://www.tiny.cloud/docs/tinymce/latest/uploadcare/" target="_blank" rel="noopener noreferrer">Uploadcare</a> with TinyMCE for a fast, modern upload experience.</p>
  <p>Thanks for supporting TinyMCE! We hope it helps you and your users create great content.
    <br>All the best from the TinyMCE team.</p>
</textarea>

JS

tinymce.init({
  selector: 'textarea#custom-toolbar-menu-button',
  height: 500,
  toolbar: 'mybutton',

  setup: (editor) => {
    /* 菜单关闭和打开时菜单项会被重新创建,因此我们需要一个变量来存储切换菜单项的状态。 */
    let toggleState = false;

    /* 示例,添加一个工具栏菜单按钮 */
    editor.ui.registry.addMenuButton('mybutton', {
      text: 'My button',
      fetch: (callback) => {
        const items = [
          {
            type: 'menuitem',
            text: 'Menu item 1',
            onAction: () => editor.insertContent('&nbsp;<em>You clicked menu item 1!</em>')
          },
          {
            type: 'nestedmenuitem',
            text: 'Menu item 2',
            icon: 'user',
            getSubmenuItems: () => [
              {
                type: 'menuitem',
                text: 'Sub menu item 1',
                icon: 'unlock',
                onAction: () => editor.insertContent('&nbsp;<em>You clicked Sub menu item 1!</em>')
              },
              {
                type: 'menuitem',
                text: 'Sub menu item 2',
                icon: 'lock',
                onAction: () => editor.insertContent('&nbsp;<em>You clicked Sub menu item 2!</em>')
              }
            ]
          },
          {
            type: 'togglemenuitem',
            text: 'Toggle menu item',
            onAction: () => {
              toggleState = !toggleState;
              editor.insertContent('&nbsp;<em>You toggled a menuitem ' + (toggleState ? 'on' : 'off') + '</em>');
            },
            onSetup: (api) => {
              api.setActive(toggleState);
              return () => {};
            }
          }
        ];
        callback(items);
      }
    });

  },
  content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:16px }'
});

此示例配置了一个带有标签 My Button 的工具栏菜单按钮,点击时会打开指定的菜单。顶级菜单包含两个菜单项。第一个菜单项在点击时插入内容,第二个菜单项打开一个包含两个菜单项的子菜单,这两个子菜单项在点击时插入内容。

当工具栏菜单按钮的菜单打开时,会调用 fetch 函数。该函数接收一个回调,并传递给它一个要在下拉菜单中渲染的菜单项数组。这允许异步获取菜单项。

可搜索的菜单按钮

按钮的菜单可以配置一个用于搜索的输入字段以及其通常的项目。输入字段的存在由选项中的 search 字段控制。search 字段可以是 boolean 或包含单个可选字符串 placeholder 的 object。默认情况下,search 为 false。如果 search 不为 false,按钮的菜单将包含一个带有任何指定占位符文本的输入字段。当用户在此字段中输入时,fetch 函数将被调用,输入字段中的文本将作为 fetchContext 的一部分传回。fetch 函数负责使用此 fetchContext 来确定要传递给其 success 回调的项目。

fetchContext 是一个包含单个字符串属性 pattern 的对象。如果工具栏菜单按钮没有将 search 配置为激活状态,那么 pattern 字符串将为空。

可搜索菜单按钮示例及解释

以下是一个已配置搜索功能的简单工具栏菜单按钮示例:

HTML

<textarea id="custom-toolbar-menu-button-search">
  <p><img style="display: block; margin-left: auto; margin-right: auto;" title="Tiny Logo" src="https://www.tiny.cloud/docs/tinymce/latest/_images/logos/android-chrome-256x256.png" alt="TinyMCE Logo" width="128" height="128"></p>
  <h2 style="text-align: center;">Welcome to the TinyMCE editor demo!</h2>
  <p>Select a menu item from the listbox above and it will insert contents into the editor at the caret position.</p>

  <h2>Got questions or need help?</h2>
  <ul>
    <li>Our <a href="https://www.tiny.cloud/docs/tinymce/8/">documentation</a> is a great resource for learning how to configure TinyMCE.</li>
    <li>Have a specific question? Try the <a href="https://stackoverflow.com/questions/tagged/tinymce" target="_blank" rel="noopener"><code>tinymce</code> tag at Stack Overflow</a>.</li>
    <li>We also offer enterprise grade support as part of <a href="https://www.tiny.cloud/pricing">TinyMCE premium plans</a>.</li>
  </ul>

  <h2>Found a bug?</h2>
  <p>If you think you have found a bug please create an issue on the <a href="https://github.com/tinymce/tinymce/issues">GitHub repo</a> to report it to the developers.</p>

  <h2>Finally ...</h2>
  <p>Need file uploads in your app? Consider using <a href="https://www.tiny.cloud/docs/tinymce/latest/uploadcare/" target="_blank" rel="noopener noreferrer">Uploadcare</a> with TinyMCE for a fast, modern upload experience.</p>
  <p>Thanks for supporting TinyMCE! We hope it helps you and your users create great content.
    <br>All the best from the TinyMCE team.</p>
</textarea>

JS

tinymce.init({
  selector: 'textarea#custom-toolbar-menu-button-search',
  height: 500,
  toolbar: 'mybutton',

  setup: (editor) => {
    /* 菜单项在菜单关闭和打开时会被重新创建,因此我们需要
       一个变量来存储切换菜单项的状态。 */
    let toggleState = false;

    /* 示例,添加一个工具栏菜单按钮 */
    editor.ui.registry.addMenuButton('mybutton', {
      text: 'My searchable button',
      search: {
        placeholder: 'Type...'
      },
      fetch: (callback, fetchContext) => {
        if (fetchContext.pattern.length > 0) {
          callback([
            {
              type: 'menuitem',
              text: `You searched for: "${fetchContext.pattern}"`,
              onAction: () => editor.insertContent(`<strong>Inserted selected search result</strong>`)
            }
          ]);
        } else {
          const items = [
            {
              type: 'menuitem',
              text: 'Menu item 1',
              onAction: () => editor.insertContent('&nbsp;<em>You clicked menu item 1!</em>')
            },
            {
              type: 'togglemenuitem',
              text: 'Toggle menu item',
              onAction: () => {
                toggleState = !toggleState;
                editor.insertContent('&nbsp;<em>You toggled a menuitem ' + (toggleState ? 'on' : 'off') + '</em>');
              },
              onSetup: (api) => {
                api.setActive(toggleState);
                return () => {};
              }
            }
          ];
          callback(items);
        }
      }
    });

  },
  content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:16px }'
});

本示例配置了一个带有标签 My searchable button 的工具栏菜单按钮,点击时会打开指定的菜单。由于 search 未设置为 false,菜单中将包含一个搜索输入框。输入框的 placeholder 属性 将为 Type...。

最初,当菜单打开时,搜索输入框为空,并且会以空的 pattern 调用 fetch 函数,作为其 fetchContext。在这种情况下,fetch 会返回一个包含两个项目的数组,以便在下拉菜单中渲染。当用户在输入框中输入内容时,将再次调用 fetch,但这一次,fetchContext 中的 pattern 属性将反映在输入框中输入的值。为了便于说明,本示例随后会返回一个包含此模式的项目(位于项目的文本内)。在更实际的示例中,pattern 可用于过滤传递给回调函数的项目。

在自定义菜单中添加表格选择器

可以使用 fancymenuitem 和 fancytype: 'inserttable' 将内置的表格网格选择器添加到自定义菜单按钮中。当启用 table_grid 时,这将显示与 Table 插件菜单中相同的交互式网格。

onAction 回调会接收一个包含 numRows 和 numColumns 属性的对象。将这些值传递给 mceInsertTable 命令以插入表格。

表格选择器需要 table 插件。

HTML

<textarea id="custom-table-picker-menu-button">
  <p><img style="display: block; margin-left: auto; margin-right: auto;" title="Tiny Logo" src="https://www.tiny.cloud/docs/tinymce/latest/_images/logos/android-chrome-256x256.png" alt="TinyMCE Logo" width="128" height="128"></p>
  <h1 style="text-align: center; font-size: 1.75rem;">Tables for the workflow</h1>
  <p style="text-align: center; color: #64748b;">Click the table button above. Hover the grid, pick the size, click to insert. No dialogs.</p>

  <table style="border-collapse: collapse; width: 100%; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.08);" border="0">
    <thead>
      <tr style="background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); color: white;">
        <th scope="col" style="padding: 12px 16px; text-align: left; font-weight: 600;">Contact</th>
        <th scope="col" style="padding: 12px 16px; text-align: left; font-weight: 600;">Company</th>
        <th scope="col" style="padding: 12px 16px; text-align: left; font-weight: 600;">Status</th>
      </tr>
    </thead>
    <tbody>
      <tr style="background: #f8fafc; border-bottom: 1px solid #e2e8f0;"><td style="padding: 12px 16px;"><strong>Sarah Chen</strong></td><td style="padding: 12px 16px;">Acme Corp</td><td style="padding: 12px 16px;">Active</td></tr>
      <tr style="background: white; border-bottom: 1px solid #e2e8f0;"><td style="padding: 12px 16px;"><strong>Marcus Webb</strong></td><td style="padding: 12px 16px;">TechStart</td><td style="padding: 12px 16px;">Follow-up</td></tr>
      <tr style="background: #f8fafc;"><td style="padding: 12px 16px;"><strong>Elena Rodriguez</strong></td><td style="padding: 12px 16px;">Global Inc</td><td style="padding: 12px 16px;">New</td></tr>
    </tbody>
  </table>

  <p style="text-align: center; margin-top: 1.5rem;">Add a table anywhere.</p>
</textarea>

JS

tinymce.init({
  selector: 'textarea#custom-table-picker-menu-button',
  height: 600,
  plugins: 'table',
  toolbar: 'inserttable',
  menubar: false,

  setup: (editor) => {
    editor.ui.registry.addMenuButton('inserttable', {
      icon: 'table',
      tooltip: 'Insert table',
      fetch: (callback) => {
        callback([
          {
            type: 'fancymenuitem',
            fancytype: 'inserttable',
            onAction: (data) => {
              editor.execCommand('mceInsertTable', false, {
                rows: data.numRows,
                columns: data.numColumns,
                options: { headerRows: 1 }
              });
            }
          }
        ]);
      }
    });
  }
});

示例:添加表格选择菜单按钮

tinymce.init({
  selector: 'textarea',
  plugins: 'table',
  toolbar: 'inserttable',
  menubar: false,

  setup: (editor) => {
    editor.ui.registry.addMenuButton('inserttable', {
      icon: 'table',
      tooltip: 'Insert table',
      fetch: (callback) => {
        callback([
          {
            type: 'fancymenuitem',
            fancytype: 'inserttable',
            onAction: (data) => {
              editor.execCommand('mceInsertTable', false, {
                rows: data.numRows,
                columns: data.numColumns,
                options: { headerRows: 1 }
              });
            }
          }
        ]);
      }
    });
  }
});

使用 onSetup

onSetup 接受一个函数,该函数接收组件的 API。此函数应返回一个回调,该回调在传入组件 API 后不返回任何内容。这是因为 onSetup 在组件渲染时运行,而 onSetup 返回的回调在组件销毁时执行。从 onSetup 返回的函数本质上是 onTeardown 处理程序,可用于解绑事件和回调。

为了清楚起见,代码中的 onSetup 可能如下所示:

onSetup: (api) => {
  // 在组件创建时运行
  // 配置组件或绑定事件监听器

  return (api) => {
    // 在组件销毁时运行
    // 解绑事件监听器或清理资源
  };
};

要将回调函数绑定到编辑器事件,请使用 editor.on(eventName, callback)。要解绑事件监听器,请使用 editor.off(eventName, callback)。任何事件监听器都应在销毁回调中解绑。唯一不需要解绑的编辑器事件是 init,例如 editor.on('init', callback)。

  • 传递给 editor.off() 的回调函数应与传递给 editor.on() 的函数相同。例如,如果在按钮创建时将一个 editorEventCallback 函数绑定到 NodeChange 事件,onSetup 应返回 (api) => editor.off('NodeChange', editorEventCallback)。

  • 如果 onSetup 没有注册任何事件监听器或仅监听 init 事件,onSetup 可以返回一个空函数,例如 return () => {};。