工具栏菜单按钮是一种工具栏按钮,点击后会打开一个菜单。此菜单还可包含子菜单。这对于将原本需要多个按钮的操作分组非常有用。它还可以用于减少视觉杂乱并节省 UI 空间,因为菜单栏菜单项和一些工具栏按钮可以移动到工具栏菜单按钮中。理论上,所有菜单栏菜单项都可以移动到工具栏菜单按钮中,从而使编辑器能够在完全没有菜单栏的情况下使用。
例如:表格插件的 table 工具栏按钮打开的菜单类似于菜单栏中的“表格”菜单。
选项
| 名称 | 值 | 要求 | 说明 |
|---|---|---|---|
fetch |
| 必填 | 一个接受回调函数和 |
text | string | 可选 | 未找到图标时要显示的文本。 |
icon | string | 可选 | 要显示的图标的名称。必须对应于图标:在 图标包中、在 自定义图标包中,或通过 |
search | boolean or object | 可选 | 如果不为 false,则向菜单添加搜索字段。有关更多详细信息,请参阅“可搜索菜单按钮”。 |
tooltip | string | 可选 | 按钮工具提示的文本。 |
onSetup |
| 可选 | 默认值: |
context | string | 可选 | 默认值: |
API
| 名称 | 值 | 说明 |
|---|---|---|
isEnabled |
| 检查按钮是否已启用。 |
setEnabled |
| 设置按钮的启用状态。 |
setText |
| 设置要显示的文本标签。 |
setIcon |
| 设置按钮的图标。 |
菜单按钮示例与说明
以下是一个简单的工具栏菜单按钮示例:
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(' <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(' <em>You clicked Sub menu item 1!</em>')
},
{
type: 'menuitem',
text: 'Sub menu item 2',
icon: 'lock',
onAction: () => editor.insertContent(' <em>You clicked Sub menu item 2!</em>')
}
]
},
{
type: 'togglemenuitem',
text: 'Toggle menu item',
onAction: () => {
toggleState = !toggleState;
editor.insertContent(' <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(' <em>You clicked menu item 1!</em>')
},
{
type: 'togglemenuitem',
text: 'Toggle menu item',
onAction: () => {
toggleState = !toggleState;
editor.insertContent(' <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 () => {};。