diff --git a/packages/floatingToolbar/README.md b/packages/floatingToolbar/README.md index d276bcf..1da398f 100644 --- a/packages/floatingToolbar/README.md +++ b/packages/floatingToolbar/README.md @@ -2,7 +2,7 @@

floating toolbar plugin for canvas-editor

-## usage +## 基本用法 ```bash npm i @hufe921/canvas-editor-plugin-floating-toolbar --save @@ -15,3 +15,113 @@ import floatingToolbarPlugin from '@hufe921/canvas-editor-plugin-floating-toolba const instance = new Editor() instance.use(floatingToolbarPlugin) ``` + +## 高级用法 + +### 自定义工具栏 + +```javascript +import Editor from '@hufe921/canvas-editor' +import floatingToolbarPlugin from '@hufe921/canvas-editor-plugin-floating-toolbar' + +const instance = new Editor() + +// 配置选项 +const options = { + // 是否显示默认工具栏项,默认为true + showDefaultItems: true, + + // 自定义工具栏项 + customItems: [ + { + key: 'custom-ai-polish', + title: 'AI润色', + icon: '.icon-ai-polish', // CSS类图标(以.开头表示使用CSS类) + callback: editor => { + alert('AI润色功能尚未实现') + } + }, + // 添加分隔符 + { + isDivider: true, + key: 'divider-1' // key对分隔符是可选的 + }, + { + key: 'custom-text-icon', + title: '文本图标', + icon: 'T', // 文本图标(直接使用字符作为图标) + callback: editor => { + // 自定义功能 + } + } + ] +} + +instance.use(floatingToolbarPlugin, options) +``` + +### 图标类型 + +浮动工具栏支持两种类型的图标: + +1. **CSS 类图标** + + - 使用`.className`格式,如:`.icon-align-left` + - 插件会自动将其解析为 CSS 类名并应用到图标元素上 + - 您需要在 CSS 中定义相应的样式,例如: + + ```css + /* 方法1: 使用SVG文件 */ + .icon-ai-polish { + background-image: url('/path/to/icons/ai-polish.svg'); + background-position: center; + background-repeat: no-repeat; + background-size: contain; + } + ``` + +2. **文本图标** + - 直接使用文本字符作为图标,如:`T`、`B`、`I`等 + - 适合简单的图标表示,会居中显示在工具栏按钮中 + +### 分隔符 + +您可以在工具栏中添加分隔符来分隔不同功能的按钮: + +```javascript +{ + isDivider: true, + key: 'divider-name' // 可选,用于标识分隔符 +} +``` + +分隔符会在工具栏中显示为垂直线。 + +## 接口说明 + +### 选项接口 + +```typescript +// 工具栏配置选项 +interface IFloatingToolbarOptions { + customItems?: IToolbarItem[] // 自定义工具栏项 + showDefaultItems?: boolean // 是否显示默认工具栏项 +} + +// 分隔符项 +interface IToolbarDivider { + isDivider: true + key?: string // 可选的标识符 +} + +// 常规工具栏项 +interface IToolbarAction { + key: string // 唯一标识 + title?: string // 悬停提示 + icon?: string // 图标(CSS类名或自定义文本) + callback: (editor: Editor) => void // 点击回调 +} + +// 工具栏项可以是常规项或分隔符 +type IToolbarItem = IToolbarDivider | IToolbarAction +``` diff --git a/packages/floatingToolbar/src/floatingToolbar/icons/ai-polish.svg b/packages/floatingToolbar/src/floatingToolbar/icons/ai-polish.svg new file mode 100644 index 0000000..5138300 --- /dev/null +++ b/packages/floatingToolbar/src/floatingToolbar/icons/ai-polish.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/packages/floatingToolbar/src/floatingToolbar/index.ts b/packages/floatingToolbar/src/floatingToolbar/index.ts index 5f29065..7cabe38 100644 --- a/packages/floatingToolbar/src/floatingToolbar/index.ts +++ b/packages/floatingToolbar/src/floatingToolbar/index.ts @@ -3,9 +3,17 @@ import Pickr from '@simonwep/pickr' import Editor from '@hufe921/canvas-editor' import './style/index.scss' import { ToolbarType } from './enum' -import { IToolbarRegister } from './interface' +import { IFloatingToolbarOptions, IToolbarRegister } from './interface' import { PLUGIN_PREFIX } from './constant' +interface IRangeStyle { + type: string | null + bold: boolean + italic: boolean + underline: boolean + strikeout: boolean +} + function createPickerToolbar( container: HTMLDivElement, toolbarType: ToolbarType, @@ -61,16 +69,18 @@ function createPickerToolbar( }) } -// 工具栏列表 -const toolbarRegisterList: IToolbarRegister[] = [ +// 默认工具栏列表 +const defaultToolbarRegisterList: IToolbarRegister[] = [ { key: ToolbarType.SIZE_ADD, + title: '增大字号', callback(editor) { editor.command.executeSizeAdd() } }, { key: ToolbarType.SIZE_MINUS, + title: '减小字号', callback(editor) { editor.command.executeSizeMinus() } @@ -80,24 +90,28 @@ const toolbarRegisterList: IToolbarRegister[] = [ }, { key: ToolbarType.BOLD, + title: '粗体', callback(editor) { editor.command.executeBold() } }, { key: ToolbarType.ITALIC, + title: '斜体', callback(editor) { editor.command.executeItalic() } }, { key: ToolbarType.UNDERLINE, + title: '下划线', callback(editor) { editor.command.executeUnderline() } }, { key: ToolbarType.STRIKEOUT, + title: '删除线', callback(editor) { editor.command.executeStrikeout() } @@ -106,6 +120,8 @@ const toolbarRegisterList: IToolbarRegister[] = [ isDivider: true }, { + key: ToolbarType.COLOR, + title: '文字颜色', render(container, editor) { createPickerToolbar(container, ToolbarType.COLOR, color => { editor.command.executeColor(color) @@ -113,6 +129,8 @@ const toolbarRegisterList: IToolbarRegister[] = [ } }, { + key: ToolbarType.HIGHLIGHT, + title: '高亮颜色', render(container, editor) { createPickerToolbar(container, ToolbarType.HIGHLIGHT, color => { editor.command.executeHighlight(color) @@ -121,10 +139,43 @@ const toolbarRegisterList: IToolbarRegister[] = [ } ] -function createToolbar(editor: Editor): HTMLDivElement { +function createToolbar( + editor: Editor, + options?: IFloatingToolbarOptions +): HTMLDivElement { const toolbarContainer = document.createElement('div') toolbarContainer.classList.add(`${PLUGIN_PREFIX}-floating-toolbar`) - for (const toolbar of toolbarRegisterList) { + + let toolbarItems: IToolbarRegister[] = [] + + // 决定是否显示默认工具栏 + if (!options || options.showDefaultItems !== false) { + toolbarItems = [...defaultToolbarRegisterList] + } + + // 添加自定义工具栏项 + if (options?.customItems?.length) { + const customRegisterItems: IToolbarRegister[] = options.customItems.map( + item => { + // 处理分隔符情况 + if (item.isDivider) { + return { isDivider: true } + } + + return { + key: item.key, + title: item.title, + icon: item.icon, + callback: item.callback + } + } + ) + + toolbarItems = [...toolbarItems, ...customRegisterItems] + } + + // 创建工具栏项 + for (const toolbar of toolbarItems) { if (toolbar.render) { toolbar.render(toolbarContainer, editor) } else if (toolbar.isDivider) { @@ -132,17 +183,35 @@ function createToolbar(editor: Editor): HTMLDivElement { divider.classList.add(`${PLUGIN_PREFIX}-divider`) toolbarContainer.append(divider) } else { - const { key, callback } = toolbar - const toolbarItem = document.createElement('div') - toolbarItem.classList.add(`${PLUGIN_PREFIX}-${key}`) - const icon = document.createElement('i') - toolbarItem.append(icon) - toolbarItem.onclick = () => { - callback?.(editor) + const { key, callback, title, icon } = toolbar + + if (key && callback) { + const toolbarItem = document.createElement('div') + toolbarItem.classList.add(`${PLUGIN_PREFIX}-${key}`) + + if (title) { + toolbarItem.setAttribute('title', title) + } + + const iconElement = document.createElement('i') + if (icon) { + if (icon.startsWith('.') || icon.startsWith('#')) { + iconElement.className = icon.substring(1) + } else { + iconElement.textContent = icon + } + } + + toolbarItem.append(iconElement) + toolbarItem.onclick = () => { + callback(editor) + } + + toolbarContainer.append(toolbarItem) } - toolbarContainer.append(toolbarItem) } } + return toolbarContainer } @@ -156,14 +225,17 @@ function toggleToolbarItemActive(toolbarItem: HTMLDivElement, active: boolean) { : toolbarItem.classList.remove('active') } -export default function floatingToolbarPlugin(editor: Editor) { +export default function floatingToolbarPlugin( + editor: Editor, + options?: IFloatingToolbarOptions +) { // 创建工具栏 - const toolbarContainer = createToolbar(editor) + const toolbarContainer = createToolbar(editor, options) const editorContainer = editor.command.getContainer() editorContainer.append(toolbarContainer) // 监听选区样式变化 - editor.eventBus.on('rangeStyleChange', rangeStyle => { + editor.eventBus.on('rangeStyleChange', (rangeStyle: IRangeStyle) => { if (rangeStyle.type === null) { toggleToolbarVisible(toolbarContainer, false) return diff --git a/packages/floatingToolbar/src/floatingToolbar/interface/index.ts b/packages/floatingToolbar/src/floatingToolbar/interface/index.ts index fd72012..e26ce2b 100644 --- a/packages/floatingToolbar/src/floatingToolbar/interface/index.ts +++ b/packages/floatingToolbar/src/floatingToolbar/interface/index.ts @@ -1,8 +1,30 @@ import Editor from '@hufe921/canvas-editor' +export interface IToolbarDivider { + isDivider: true + key?: string +} + +export interface IToolbarAction { + key: string + title?: string + icon?: string // CSS类名或自定义图标字符串 + isDivider?: false + callback: (editor: Editor) => void +} + +export type IToolbarItem = IToolbarDivider | IToolbarAction + export interface IToolbarRegister { key?: string isDivider?: boolean + title?: string + icon?: string render?: (container: HTMLDivElement, editor: Editor) => void callback?: (editor: Editor) => void } + +export interface IFloatingToolbarOptions { + customItems?: IToolbarItem[] + showDefaultItems?: boolean +} diff --git a/packages/floatingToolbar/src/floatingToolbar/style/index.scss b/packages/floatingToolbar/src/floatingToolbar/style/index.scss index 5c1184f..f561603 100644 --- a/packages/floatingToolbar/src/floatingToolbar/style/index.scss +++ b/packages/floatingToolbar/src/floatingToolbar/style/index.scss @@ -97,4 +97,38 @@ background: url(../icons/highlight.svg); } } + + // 通用项样式 + .ce-item { + min-width: 20px; + margin: 0 3px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + padding: 2px; + + &:hover, + &.active { + background-color: rgba(25, 55, 88, .07); + } + + i { + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + font-style: normal; + font-size: 14px; + } + } +} + +/* AI润色图标 */ +.icon-ai-polish { + background-image: url(../icons/ai-polish.svg); + background-position: center; + background-repeat: no-repeat; + background-size: contain; } \ No newline at end of file diff --git a/packages/floatingToolbar/src/main.ts b/packages/floatingToolbar/src/main.ts index ae5185d..b11d7c6 100644 --- a/packages/floatingToolbar/src/main.ts +++ b/packages/floatingToolbar/src/main.ts @@ -1,10 +1,48 @@ import Editor from '@hufe921/canvas-editor' import floatingToolbarPlugin from './floatingToolbar' +import { IFloatingToolbarOptions } from './floatingToolbar/interface' window.onload = function () { const container = document.querySelector('#editor')! const instance = new Editor(container, { main: [] }) - instance.use(floatingToolbarPlugin) + + // 定义自定义工具栏配置 + const toolbarOptions: IFloatingToolbarOptions = { + // 是否显示默认工具栏项,默认为true + showDefaultItems: true, + + // 自定义工具栏项 + customItems: [ + // 添加一个分隔符 + { + isDivider: true, + key: 'divider-1' // key对分隔符可选 + }, + { + key: 'custom-ai-polish', + title: 'AI润色', + // 使用CSS类作为图标(以.开头表示类名) + icon: '.icon-ai-polish', + // eslint-disable-next-line @typescript-eslint/no-unused-vars + callback: _editor => { + alert('AI润色功能尚未实现') + } + }, + { + key: 'custom-text-icon', + title: '文本图标示例', + // 使用文本作为图标(不以.或#开头即为文本) + icon: 'T', + // eslint-disable-next-line @typescript-eslint/no-unused-vars + callback: _editor => { + alert('使用文本作为图标示例') + } + } + ] + } + + // 使用带有自定义选项的浮动工具栏插件) + instance.use(floatingToolbarPlugin, toolbarOptions) }