diff --git a/packages/docs/fluent-editor/demos/custom-toolbar.vue b/packages/docs/fluent-editor/demos/custom-toolbar.vue index 9c6859f..31e623d 100644 --- a/packages/docs/fluent-editor/demos/custom-toolbar.vue +++ b/packages/docs/fluent-editor/demos/custom-toolbar.vue @@ -23,7 +23,8 @@ const TOOLBAR_CONFIG = [ [{ header: [1, 2, 3, 4, 5, 6, false] }, { font: ['songti', 'yahei', 'kaiti', 'heiti', 'lishu', 'mono', 'arial', 'arialblack', 'comic', 'impact', 'times'] }, { size: ['12px', '14px', '16px', '18px', '20px', '24px', '32px', '36px', '48px', '72px'] }], ['bold', 'italic', 'strike', 'underline'], [{ color: [] }, { background: [] }], - [{ align: [] }, { list: 'ordered' }, { list: 'bullet' }, { list: 'check' }], + [{ align: '' }, { align: 'center' }, { align: 'right' }, { align: 'justify' }], + [{ list: 'ordered' }, { list: 'bullet' }, { list: 'check' }], [{ script: 'sub' }, { script: 'super' }], [{ indent: '-1' }, { indent: '+1' }], [{ direction: 'rtl' }], diff --git a/packages/fluent-editor/package.json b/packages/fluent-editor/package.json index 0033e6d..2ad3920 100644 --- a/packages/fluent-editor/package.json +++ b/packages/fluent-editor/package.json @@ -34,7 +34,8 @@ }, "dependencies": { "lodash-es": "^4.17.15", - "quill": "^2.0.0" + "quill": "^2.0.0", + "quill-toolbar-tip": "^0.0.4" }, "devDependencies": { "@types/jest": "^26.0.23", diff --git a/packages/fluent-editor/src/assets/common.scss b/packages/fluent-editor/src/assets/common.scss index b40c395..bbffa33 100644 --- a/packages/fluent-editor/src/assets/common.scss +++ b/packages/fluent-editor/src/assets/common.scss @@ -1,6 +1,4 @@ -// @import './style/theme/color'; - -$defaultFontSize: 14px; +@use './common/config.scss' as *; @mixin ellipsis($width) { width: $width; @@ -278,7 +276,6 @@ $defaultFontSize: 14px; } } -$namespace: fe; @function getCssVar($name) { @return var(--#{$namespace}-#{$name}); } diff --git a/packages/fluent-editor/src/assets/common/config.scss b/packages/fluent-editor/src/assets/common/config.scss new file mode 100644 index 0000000..0cac671 --- /dev/null +++ b/packages/fluent-editor/src/assets/common/config.scss @@ -0,0 +1,4 @@ +$defaultFontSize: 14px; +$namespace: fe; +$fullscreenZIndex-normal: 50; +$fullscreenZIndex-full: 51; diff --git a/packages/fluent-editor/src/assets/editor.scss b/packages/fluent-editor/src/assets/editor.scss index 1c115f9..3c0a1cd 100644 --- a/packages/fluent-editor/src/assets/editor.scss +++ b/packages/fluent-editor/src/assets/editor.scss @@ -1,3 +1,4 @@ +@use './common/config.scss' as *; @import './common'; @import './tasklist'; @import './better-table'; diff --git a/packages/fluent-editor/src/assets/fullscreen.scss b/packages/fluent-editor/src/assets/fullscreen.scss index 029f5af..cbe8ce1 100644 --- a/packages/fluent-editor/src/assets/fullscreen.scss +++ b/packages/fluent-editor/src/assets/fullscreen.scss @@ -1,3 +1,5 @@ +@use './common/config.scss' as *; + :-webkit-full-screen { background-color: white !important; } @@ -23,7 +25,7 @@ &.ql { &-toolbar, &-container { - @include setCssVar(fullscreen-z-index, 50); + @include setCssVar(fullscreen-z-index, $fullscreenZIndex-normal); position: fixed; width: 100%; @@ -32,7 +34,7 @@ z-index: getCssVar(fullscreen-z-index); } &-toolbar { - @include setCssVar(fullscreen-container-z-index, calc(getCssVar(fullscreen-z-index) + 1)); + @include setCssVar(fullscreen-container-z-index, $fullscreenZIndex-full); top: 0; z-index: getCssVar(fullscreen-container-z-index); diff --git a/packages/fluent-editor/src/assets/toolbar.scss b/packages/fluent-editor/src/assets/toolbar.scss index 261f9c2..a1d5151 100644 --- a/packages/fluent-editor/src/assets/toolbar.scss +++ b/packages/fluent-editor/src/assets/toolbar.scss @@ -1,4 +1,19 @@ +@use './common/config.scss' as *; @import './common'; +@import 'quill-toolbar-tip/dist/index.css'; + +.toolbar-tip__tooltip { + @include setCssVar(text-color, #ffffff); + @include setCssVar(bg-color, #303133); + + z-index: $fullscreenZIndex-full; + color: getCssVar(text-color); + background-color: getCssVar(bg-color); +} +.dark .toolbar-tip__tooltip { + @include setCssVar(text-color, #141414); + @include setCssVar(bg-color, #f5f5f5); +} $iconHeight: 18px; // 工具栏按钮高度 $iconPadding: 7.5px; // 工具栏按钮左/右间距 diff --git a/packages/fluent-editor/src/config/i18n/en-us.ts b/packages/fluent-editor/src/config/i18n/en-us.ts index 7c5e215..6d37fca 100644 --- a/packages/fluent-editor/src/config/i18n/en-us.ts +++ b/packages/fluent-editor/src/config/i18n/en-us.ts @@ -1,7 +1,4 @@ export const EN_US = { - 'undo': 'Undo', - 'redo': 'Redo', - 'clean': 'Clear Formatting', 'header': 'Paragraph Formatting', 'normal': 'Normal', @@ -12,8 +9,6 @@ export const EN_US = { 'h5': 'Heading 5', 'h6': 'Heading 6', - 'font': 'Font', - 'size': 'Size', 'lineheight': 'Line Height', 'songti': 'SimSun', 'yahei': 'Microsoft Yahei', @@ -21,36 +16,13 @@ export const EN_US = { 'heiti': 'SimHei', 'lishu': 'LiSu', - 'bold': 'Bold', - 'italic': 'Italic', - 'underline': 'Underline', - 'strike': 'Strikethrough', - - 'color': 'Font Color', - 'background': 'Background Color', - - 'orderedlist': 'Ordered List', - 'bulletlist': 'Unordered List', - 'checklist': 'Task List', - 'left': 'Left', 'center': 'Centered', 'right': 'Right', - 'image': 'Image', - 'file': 'File', - 'table': 'Table', - 'link': 'Hyperlink', - - 'code': 'Inline Code', 'codeblock': 'Code Block', - 'blockquote': 'Quote', - 'superscript': 'Superscript', - 'subscript': 'Subscript', 'globallink': 'Link', - 'emoji': 'Emoji', - 'fullscreen': 'Full Screen', 'exit-fullscreen': 'Exit Full Screen', 'help': 'Help', 'more': 'More', @@ -103,4 +75,50 @@ export const EN_US = { 'screenshot': 'Screenshot', 'uploading': 'Uploading...', 'sub-title-bg-color': 'Background Color', + + // blot tip name + 'emoji': 'Emoji', + 'fullscreen': 'Full Screen', + 'blockquote': 'Quote', + 'undo': 'Undo', + 'redo': 'Redo', + 'clean': 'Clear Formatting', + 'bold': 'Bold', + 'italic': 'Italic', + 'underline': 'Underline', + 'strike': 'Strikethrough', + 'image': 'Image', + 'file': 'File', + 'table': 'Table', + 'link': 'Hyperlink', + 'code': 'Inline Code', + 'better-table': 'Table', + 'code-block': 'Code Block', + 'formula': 'Formula', + 'format-painter': 'Format Painter', + 'video': 'Video', + 'color': 'Font Color', + 'background': 'Background Color', + 'font': 'Font', + 'size': 'Size', + 'list-ordered': 'Ordered List', + 'list-bullet': 'Unordered List', + 'list-check': 'Task List', + 'align-left': 'Left aligned', + 'align-center': 'Center aligned', + 'align-right': 'Right aligned', + 'align-justify': 'Justify aligned', + 'direction-ltr': 'Text Direction Left To Right', + 'direction-rtl': 'Text Direction Right To Left', + 'indent--1': 'Minus Indent', + 'indent-+1': 'Add Indent', + 'script-super': 'Superscript', + 'script-sub': 'Subscript', + 'header-normal': 'Text', + 'header-1': 'Heading 1', + 'header-2': 'Heading 2', + 'header-3': 'Heading 3', + 'header-4': 'Heading 4', + 'header-5': 'Heading 5', + 'header-6': 'Heading 6', } diff --git a/packages/fluent-editor/src/config/i18n/zh-cn.ts b/packages/fluent-editor/src/config/i18n/zh-cn.ts index b89e3cf..0280dc3 100644 --- a/packages/fluent-editor/src/config/i18n/zh-cn.ts +++ b/packages/fluent-editor/src/config/i18n/zh-cn.ts @@ -1,7 +1,4 @@ export const ZH_CN = { - 'undo': '撤销', - 'redo': '重做', - 'clean': '清除格式', 'header': '段落格式', 'normal': '正文', @@ -12,8 +9,6 @@ export const ZH_CN = { 'h5': '标题5', 'h6': '标题6', - 'font': '字体', - 'size': '字号', 'lineheight': '行距', 'songti': '宋体', 'yahei': '微软雅黑', @@ -21,36 +16,13 @@ export const ZH_CN = { 'heiti': '黑体', 'lishu': '隶书', - 'bold': '粗体', - 'italic': '斜体', - 'underline': '下划线', - 'strike': '删除线', - - 'color': '字体颜色', - 'background': '背景色', - - 'orderedlist': '有序列表', - 'bulletlist': '无序列表', - 'checklist': '任务列表', - 'left': '左对齐', 'center': '居中对齐', 'right': '右对齐', - 'image': '图片', - 'file': '文件', - 'table': '表格', - 'link': '超链接', - - 'code': '行内代码', 'codeblock': '代码块', - 'blockquote': '引用', - 'superscript': '上标', - 'subscript': '下标', 'globallink': '全局链接', - 'emoji': '表情', - 'fullscreen': '全屏', 'exit-fullscreen': '退出全屏', 'help': '帮助', 'more': '更多', @@ -101,4 +73,50 @@ export const ZH_CN = { 'screenshot': '截图', 'uploading': '上传中...', 'sub-title-bg-color': '背景颜色', + + // blot tip name + 'emoji': '表情', + 'fullscreen': '全屏', + 'blockquote': '引用', + 'undo': '撤销', + 'redo': '重做', + 'clean': '清除格式', + 'bold': '粗体', + 'italic': '斜体', + 'underline': '下划线', + 'strike': '删除线', + 'image': '图片', + 'file': '文件', + 'table': '表格', + 'link': '超链接', + 'code': '行内代码', + 'better-table': '表格', + 'code-block': '代码块', + 'formula': '公式', + 'format-painter': '格式刷', + 'video': '视频', + 'color': '字体颜色', + 'background': '背景色', + 'font': '字体', + 'size': '字号', + 'list-ordered': '有序列表', + 'list-bullet': '无序列表', + 'list-check': '任务列表', + 'align-left': '左对齐', + 'align-center': '居中对齐', + 'align-right': '右对齐', + 'align-justify': '两端对齐', + 'direction-ltr': '文本方向左到右', + 'direction-rtl': '文本方向右到左', + 'indent--1': '减少缩进', + 'indent-+1': '增加缩进', + 'script-super': '上标', + 'script-sub': '下标', + 'header-normal': '正文', + 'header-1': '标题1', + 'header-2': '标题2', + 'header-3': '标题3', + 'header-4': '标题4', + 'header-5': '标题5', + 'header-6': '标题6', } diff --git a/packages/fluent-editor/src/fluent-editor.ts b/packages/fluent-editor/src/fluent-editor.ts index 1fc690b..2da4fb2 100644 --- a/packages/fluent-editor/src/fluent-editor.ts +++ b/packages/fluent-editor/src/fluent-editor.ts @@ -21,7 +21,7 @@ import SoftBreak from './soft-break' // 软回车 import Strike from './strike' // 删除线 import CustomSyntax from './syntax' // 代码块高亮 import BetterTable from './table/better-table' // 表格 -import Toolbar from './toolbar' // 工具栏 +import Toolbar, { ToolbarTip } from './toolbar' // 工具栏 import { isUndefined } from './utils/is' import Video from './video' // 视频 // import GlobalLink from './global-link' // 全局链接 @@ -164,6 +164,7 @@ const registerModules = function () { }, }, }, + [ToolbarTip.moduleName]: true, }, } @@ -184,6 +185,7 @@ const registerModules = function () { // 'modules/quickmenu': QuickMenu,//暂未开发 'modules/syntax': CustomSyntax, 'modules/mathlive': MathliveModule, + [`modules/${ToolbarTip.moduleName}`]: ToolbarTip, 'formats/strike': Strike, 'formats/softBreak': SoftBreak, diff --git a/packages/fluent-editor/src/fullscreen/handler.ts b/packages/fluent-editor/src/fullscreen/handler.ts index 4892752..1467987 100644 --- a/packages/fluent-editor/src/fullscreen/handler.ts +++ b/packages/fluent-editor/src/fullscreen/handler.ts @@ -1,5 +1,6 @@ import type { FluentEditorToolbar } from '../config/types' import { ICONS_CONFIG, namespace } from '../config' +import { ToolbarTip } from '../toolbar' import { lockScroll } from '../utils/scroll-lock' let exitEscHandlerBindToolbar: (e: KeyboardEvent) => void @@ -26,6 +27,10 @@ function intoFullscreen(toolbar: FluentEditorToolbar) { btn.innerHTML = ICONS_CONFIG['fullscreen-exit'] window.addEventListener('resize', resizeHandlerBindToolbar) document.addEventListener('keydown', exitEscHandlerBindToolbar) + const toolbarTipModule = toolbar.quill.getModule(ToolbarTip.moduleName) as ToolbarTip + if (toolbarTipModule) { + toolbarTipModule.hideAllTips() + } } function exitFullscreen(toolbar: FluentEditorToolbar) { toolbar.quill.isFullscreen = false @@ -37,6 +42,10 @@ function exitFullscreen(toolbar: FluentEditorToolbar) { btn.innerHTML = ICONS_CONFIG.fullscreen window.removeEventListener('resize', resizeHandlerBindToolbar) document.removeEventListener('keydown', exitEscHandlerBindToolbar) + const toolbarTipModule = toolbar.quill.getModule(ToolbarTip.moduleName) as ToolbarTip + if (toolbarTipModule) { + toolbarTipModule.hideAllTips() + } } export function fullscreenHandler(this: FluentEditorToolbar) { if (this.quill.isFullscreen) { diff --git a/packages/fluent-editor/src/toolbar/index.ts b/packages/fluent-editor/src/toolbar/index.ts index 04441af..c68cc47 100644 --- a/packages/fluent-editor/src/toolbar/index.ts +++ b/packages/fluent-editor/src/toolbar/index.ts @@ -178,3 +178,4 @@ class BetterToolbar extends Toolbar { } export default BetterToolbar +export * from './toolbar-tip' diff --git a/packages/fluent-editor/src/toolbar/toolbar-tip.ts b/packages/fluent-editor/src/toolbar/toolbar-tip.ts new file mode 100644 index 0000000..bbe322a --- /dev/null +++ b/packages/fluent-editor/src/toolbar/toolbar-tip.ts @@ -0,0 +1,105 @@ +import type { QuillToolbarTipOptions } from 'quill-toolbar-tip' +import type { FluentEditor } from '../fluent-editor' +import QuillToolbarTip from 'quill-toolbar-tip' +import { CHANGE_LANGUAGE_EVENT } from '../config' + +export class ToolbarTip extends QuillToolbarTip { + static moduleName: string = 'toolbar-tip' + constructor(public quill: FluentEditor, options: Partial) { + if (!options?.tipTextMap) { + options.tipTextMap = {} + } + super(quill, options) + + this.quill.on(CHANGE_LANGUAGE_EVENT, () => { + this.destroyAllTips() + this.options = this.resolveOptions(options) + this.createToolbarTip() + }) + } + + resolveOptions(options: Partial): QuillToolbarTipOptions { + const result = super.resolveOptions(options) + const langText = this.quill.options.langText + const btnTips = [ + 'bold', + 'italic', + 'strike', + 'underline', + 'undo', + 'redo', + 'clean', + 'link', + 'blockquote', + 'code', + 'image', + 'file', + 'emoji', + 'video', + 'screenshot', + 'better-table', + 'code-block', + 'formula', + 'format-painter', + ].reduce((map, name) => { + map[name] = langText[name] + return map + }, {} as Record) + const selectTips = [ + 'color', + 'background', + 'font', + 'size', + ].reduce((map, name) => { + map[name] = { + onShow() { + return langText[name] + }, + } + return map + }, {}) + const valueControlTips = [ + 'list', + 'align', + 'script', + 'indent', + 'header', + 'direction', + ].reduce((map, name) => { + map[name] = { + onShow(target: HTMLElement, value: string) { + if (name === 'direction') { + value = target.classList.contains('ql-active') ? 'rtl' : 'ltr' + } + if (!value) { + if (name === 'align') { + value = 'left' + } + else if (name === 'header') { + value = 'normal' + } + } + return langText[`${name}-${value}`] + }, + } + return map + }, {}) + const textMap: QuillToolbarTipOptions['tipTextMap'] = { + ...btnTips, + ...valueControlTips, + ...selectTips, + fullscreen: { + onShow: () => { + return langText[this.quill.isFullscreen ? 'exit-fullscreen' : 'fullscreen'] + }, + }, + } + return { + ...result, + tipTextMap: { + ...textMap, + ...options.tipTextMap, + }, + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82ede21..ddd5a2a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,6 +78,9 @@ importers: quill: specifier: ^2.0.0 version: 2.0.2 + quill-toolbar-tip: + specifier: ^0.0.4 + version: 0.0.4(quill@2.0.2) devDependencies: '@types/jest': specifier: ^26.0.23 @@ -140,12 +143,18 @@ packages: peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' + peerDependenciesMeta: + '@algolia/client-search': + optional: true '@algolia/autocomplete-shared@1.9.3': resolution: {integrity: sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==} peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' + peerDependenciesMeta: + '@algolia/client-search': + optional: true '@algolia/cache-browser-local-storage@4.24.0': resolution: {integrity: sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==} @@ -805,6 +814,9 @@ packages: engines: {node: '>=18'} hasBin: true + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@rollup/pluginutils@5.1.3': resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} engines: {node: '>=14.0.0'} @@ -3667,6 +3679,11 @@ packages: quill-markdown-shortcuts@0.0.10: resolution: {integrity: sha512-2FFFqqo65JgDgAGSer7cFQTCeiSjJF4N8lRGXGv/xjppCxSwj42OnNdGPZ/zeeCxdUY/j1LW4AiSvPQaTIkY2A==} + quill-toolbar-tip@0.0.4: + resolution: {integrity: sha512-p6pT/ERgrwnc1FtTV2IySiUX0FFVKBfhCIaLqiIOB/mHFsU4vVZYnLymjA7rbD7IzG/9E3eAr6iyjgqdlQL6ng==} + peerDependencies: + quill: ^2.0.0 + quill@1.3.7: resolution: {integrity: sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==} @@ -4581,13 +4598,15 @@ snapshots: '@algolia/autocomplete-preset-algolia@1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)': dependencies: '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0) - '@algolia/client-search': 4.24.0 algoliasearch: 4.24.0 + optionalDependencies: + '@algolia/client-search': 4.24.0 '@algolia/autocomplete-shared@1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)': dependencies: - '@algolia/client-search': 4.24.0 algoliasearch: 4.24.0 + optionalDependencies: + '@algolia/client-search': 4.24.0 '@algolia/cache-browser-local-storage@4.24.0': dependencies: @@ -5277,7 +5296,7 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 collect-v8-coverage: 1.0.2 - '@jest/test-sequencer@26.6.3(ts-node@9.1.1(typescript@4.9.5))': + '@jest/test-sequencer@26.6.3': dependencies: '@jest/test-result': 26.6.2 graceful-fs: 4.2.11 @@ -5285,11 +5304,7 @@ snapshots: jest-runner: 26.6.3(ts-node@9.1.1(typescript@4.9.5)) jest-runtime: 26.6.3(ts-node@9.1.1(typescript@4.9.5)) transitivePeerDependencies: - - bufferutil - - canvas - supports-color - - ts-node - - utf-8-validate '@jest/transform@26.6.2': dependencies: @@ -5392,6 +5407,8 @@ snapshots: dependencies: playwright: 1.47.0 + '@popperjs/core@2.11.8': {} + '@rollup/pluginutils@5.1.3(rollup@4.21.3)': dependencies: '@types/estree': 1.0.6 @@ -7559,7 +7576,7 @@ snapshots: jest-config@26.6.3(ts-node@9.1.1(typescript@4.9.5)): dependencies: '@babel/core': 7.25.2 - '@jest/test-sequencer': 26.6.3(ts-node@9.1.1(typescript@4.9.5)) + '@jest/test-sequencer': 26.6.3 '@jest/types': 26.6.2 babel-jest: 26.6.3(@babel/core@7.25.2) chalk: 4.1.2 @@ -8818,6 +8835,11 @@ snapshots: dependencies: quill: 1.3.7 + quill-toolbar-tip@0.0.4(quill@2.0.2): + dependencies: + '@popperjs/core': 2.11.8 + quill: 2.0.2 + quill@1.3.7: dependencies: clone: 2.1.2