diff --git a/packages/docs/fluent-editor/demos/collaborative-editing.spec.ts b/packages/docs/fluent-editor/demos/collaborative-editing.spec.ts index c465b616..fbe2cfbd 100644 --- a/packages/docs/fluent-editor/demos/collaborative-editing.spec.ts +++ b/packages/docs/fluent-editor/demos/collaborative-editing.spec.ts @@ -93,15 +93,15 @@ test('header collaborative-editing test', async () => { await typeSync(p1, p2, 'Title') const levels = [1, 2, 3, 4, 5, 6] for (const lv of levels) { - await p1.locator('.ql-editor').click() + await p1.locator('.ql-editor').first().click() await selectAll(p1) if (lv <= 2) { - await p1.getByRole('button', { name: 'Normal' }).click() - await p1.getByRole('button', { name: `Heading ${lv}` }).click() + await p1.locator('.ql-toolbar .ql-picker.ql-header').click() + await p1.locator('.ql-toolbar').getByRole('button', { name: `Heading ${lv}` }).click() } else { - await p1.getByRole('button', { name: `Heading ${lv - 1}` }).click() - await p1.getByRole('button', { name: `Heading ${lv}` }).click() + await p1.locator('.ql-toolbar .ql-picker.ql-header').click() + await p1.locator('.ql-toolbar').getByRole('button', { name: `Heading ${lv}` }).click() } await expect.poll(() => headingMatched(p2, lv, 'Title')).toBeTruthy() } @@ -121,7 +121,7 @@ test('size collaborative-editing test', async () => { if (next === current) { continue } - await p1.getByRole('button', { name: current }).click() + await p1.locator('.ql-toolbar .ql-size.ql-picker').click() await p1.getByRole('button', { name: next }).click() const sizeMatch = next.match(/\d+px/) @@ -147,18 +147,18 @@ test('font collaborative-editing test', async () => { await selectAll(p1) await p1.getByRole('button', { name: 'Sans Serif' }).click() - await p1.getByRole('button', { name: 'serif', exact: true }).click() + await p1.getByRole('button', { name: '宋体', exact: true }).click() await expect - .poll(async () => (await p2.locator('.ql-editor span[style*="font-family: serif"]').count()) > 0) + .poll(async () => (await p2.locator('.ql-editor span[style*="font-family: 宋体"]').count()) > 0) .toBeTruthy() }) test('line-height collaborative-editing test', async () => { await typeSync(p1, p2, 'fdsafdsa') - await p1.getByRole('button', { name: '1', exact: true }).click() - await p1.getByRole('button', { name: '1.15' }).click() - await expect.poll(async () => (await p2.locator('.ql-editor p[style*="line-height: 1.15"]').count()) > 0).toBeTruthy() + await p1.locator('.ql-toolbar .ql-picker.ql-line-height').click() + await p1.getByRole('button', { name: '1.5' }).click() + await expect.poll(async () => (await p2.locator('.ql-editor p[style*="line-height: 1.5"]').count()) > 0).toBeTruthy() }) const formatTypes = ['bold', 'italic', 'underline', 'strike'] @@ -287,7 +287,7 @@ test('formula collaborative-editing test', async () => { test('table-up collaborative-editing test', async () => { await p1.locator('.ql-table-up > .ql-picker-label').click() - await p1.locator('div:nth-child(29)').first().click() + await p1.locator('.table-up-select-box__item[data-row="2"][data-col="2"]').first().click() await expect.poll(async () => (await p2.locator('.ql-editor div.ql-table-wrapper').count()) > 0).toBeTruthy() }) diff --git a/packages/docs/fluent-editor/demos/collaborative-editing.vue b/packages/docs/fluent-editor/demos/collaborative-editing.vue index fc528460..145cd303 100644 --- a/packages/docs/fluent-editor/demos/collaborative-editing.vue +++ b/packages/docs/fluent-editor/demos/collaborative-editing.vue @@ -1,6 +1,9 @@ diff --git a/packages/docs/fluent-editor/demos/i18n.vue b/packages/docs/fluent-editor/demos/i18n.vue index 292c57ff..3e2dca63 100644 --- a/packages/docs/fluent-editor/demos/i18n.vue +++ b/packages/docs/fluent-editor/demos/i18n.vue @@ -1,7 +1,7 @@ diff --git a/packages/docs/fluent-editor/demos/mind-map.spec.ts b/packages/docs/fluent-editor/demos/mind-map.spec.ts index 69fc9b68..ee92b0cc 100644 --- a/packages/docs/fluent-editor/demos/mind-map.spec.ts +++ b/packages/docs/fluent-editor/demos/mind-map.spec.ts @@ -31,10 +31,10 @@ test.describe('MindMap.vue', () => { test('should activate mind-map when button is clicked', async ({ page }) => { const mindMapButton = page.locator('.ql-toolbar .ql-mind-map').first() + const mindMapCount = await page.locator('.ql-mind-map-item').count() await expect(mindMapButton).toBeVisible() await mindMapButton.click() await page.waitForTimeout(500) - const editor = page.locator('.ql-editor') - await expect(editor).toBeVisible() + await expect(await page.locator('.ql-mind-map-item').count()).toBe(mindMapCount + 1) }) }) diff --git a/packages/docs/fluent-editor/demos/table-up-shortcut.vue b/packages/docs/fluent-editor/demos/table-up-shortcut.vue index be980fe9..95fc157e 100644 --- a/packages/docs/fluent-editor/demos/table-up-shortcut.vue +++ b/packages/docs/fluent-editor/demos/table-up-shortcut.vue @@ -1,9 +1,12 @@ diff --git a/packages/docs/fluent-editor/docs/demo/i18n.md b/packages/docs/fluent-editor/docs/demo/i18n.md index 08c43dfc..8ef9775a 100644 --- a/packages/docs/fluent-editor/docs/demo/i18n.md +++ b/packages/docs/fluent-editor/docs/demo/i18n.md @@ -6,7 +6,7 @@ Welcome to commit PR for more language support. -可通过函数 `changeLanguage({ lang, langText })` 修改当前语言 +可通过 `i18n` 模块的 `setLocale(lang)` 修改当前语言 @@ -14,8 +14,8 @@ Welcome to commit PR for more language support. 可通过 lang 设置初始语言。 -可通过引入 I18N 模块自行注册语言配置文字,具体字段查看[源码](https://github.com/opentiny/tiny-editor/blob/main/packages/fluent-editor/src/config/i18n/en-us.ts) +可通过在初始化 options 中配置 `i18n.messages` 自行注册语言配置文字,如无需覆盖可参考[源码](https://github.com/opentiny/tiny-editor/blob/main/packages/fluent-editor/src/config/i18n/en-us.ts) -针对直接使用文本作为显示的模块选项,可以使用模板`_i18n"key"`,内部会自动根据当前编辑器语言替换 key 对应的文本 +针对直接使用文本作为显示的模块选项,可以直接传入对应的 key,内部会自动根据当前编辑器语言替换 key 对应的文本 diff --git a/packages/docs/package.json b/packages/docs/package.json index 32bdf3ab..7f53032a 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -27,8 +27,8 @@ "quill-cursors": "^4.0.3", "quill-header-list": "0.0.2", "quill-markdown-shortcuts": "^0.0.10", - "quill-table-up": "^3.3.0", - "quill-toolbar-tip": "^0.0.13", + "quill-table-up": "^3.4.0", + "quill-toolbar-tip": "1.1.0", "simple-mind-map": "0.14.0-fix.1", "simple-mind-map-plugin-themes": "^1.0.1", "vue": "^3.5.13", diff --git a/packages/fluent-editor/package.json b/packages/fluent-editor/package.json index b3bb7700..e6b64a60 100644 --- a/packages/fluent-editor/package.json +++ b/packages/fluent-editor/package.json @@ -36,7 +36,8 @@ "dependencies": { "quill": "^2.0.0", "quill-easy-color": "^0.0.10", - "quill-shortcut-key": "^0.0.5" + "quill-i18n": "^0.2.0", + "quill-shortcut-key": "^0.2.1" }, "devDependencies": { "@emoji-mart/data": "^1.2.1", diff --git a/packages/fluent-editor/src/assets/link.scss b/packages/fluent-editor/src/assets/link.scss index 8f0ca3f6..0a278d15 100644 --- a/packages/fluent-editor/src/assets/link.scss +++ b/packages/fluent-editor/src/assets/link.scss @@ -12,7 +12,7 @@ z-index: 1; &[data-mode='link']::before { - content: none; + content: attr(data-before-title); } &.ql-flip { @@ -117,5 +117,3 @@ } } } -// 编辑器正文 -@include linkTooltip; diff --git a/packages/fluent-editor/src/assets/style.scss b/packages/fluent-editor/src/assets/style.scss index 5972b204..088f864f 100644 --- a/packages/fluent-editor/src/assets/style.scss +++ b/packages/fluent-editor/src/assets/style.scss @@ -44,3 +44,5 @@ // 图片预览 @include custom-image.imagePreview; + +@include linkTooltip; diff --git a/packages/fluent-editor/src/config/editor.config.ts b/packages/fluent-editor/src/config/editor.config.ts index 1be77f6f..8d1edd81 100644 --- a/packages/fluent-editor/src/config/editor.config.ts +++ b/packages/fluent-editor/src/config/editor.config.ts @@ -102,7 +102,7 @@ export const FULL_TOOLBAR = [ ], ['bold', 'italic', 'strike', 'underline', 'divider'], [{ color: [] }, { background: [] }], - [{ align: '' }, { align: 'center' }, { align: 'right' }, { align: 'justify' }], + [{ align: ['', 'center', 'right'] }, { align: '' }, { align: 'center' }, { align: 'right' }, { align: 'justify' }], [{ list: 'ordered' }, { list: 'bullet' }, { list: 'check' }], [{ script: 'sub' }, { script: 'super' }], [{ indent: '-1' }, { indent: '+1' }], diff --git a/packages/fluent-editor/src/config/i18n/en-us.ts b/packages/fluent-editor/src/config/i18n/en-us.ts index bca677f2..b273b421 100644 --- a/packages/fluent-editor/src/config/i18n/en-us.ts +++ b/packages/fluent-editor/src/config/i18n/en-us.ts @@ -74,7 +74,10 @@ export const EN_US = { 'file': 'File', 'mind-map': 'Mind Map', 'flow-chart': 'Flow Chart', - 'link': 'Hyperlink', + 'link': { + '': 'Hyperlink', + 'enter-link': 'Enter link: ', + }, 'code': 'Inline Code', 'table': 'Table', 'table-up': 'Table', diff --git a/packages/fluent-editor/src/config/i18n/zh-cn.ts b/packages/fluent-editor/src/config/i18n/zh-cn.ts index be2ed0b5..c5bb76a8 100644 --- a/packages/fluent-editor/src/config/i18n/zh-cn.ts +++ b/packages/fluent-editor/src/config/i18n/zh-cn.ts @@ -72,7 +72,10 @@ export const ZH_CN = { 'file': '文件', 'mind-map': '思维导图', 'flow-chart': '流程图', - 'link': '超链接', + 'link': { + '': '超链接', + 'enter-link': '输入链接: ', + }, 'divider': '分割线', 'code': '行内代码', 'table': '表格', diff --git a/packages/fluent-editor/src/config/types/editor-modules.interface.ts b/packages/fluent-editor/src/config/types/editor-modules.interface.ts index 5b647648..fece7594 100644 --- a/packages/fluent-editor/src/config/types/editor-modules.interface.ts +++ b/packages/fluent-editor/src/config/types/editor-modules.interface.ts @@ -1,5 +1,6 @@ +import type { I18nOptions } from 'quill-i18n' import type { ToolbarProps } from 'quill/modules/toolbar' -import type { I18NOptions, ICounterOption, MentionOption, ShortCutKeyInputOptions } from '../../modules' +import type { ICounterOption, MentionOption, ShortCutKeyInputOptions } from '../../modules' import type { BlotFormatterOptionsInput } from '../../modules/custom-image/options' import type { FileUploaderOptions } from '../../modules/custom-uploader' import type { EmojiModuleOptions } from '../../modules/emoji' @@ -40,7 +41,7 @@ export interface IEditorModules { 'uploader'?: boolean | Partial 'shortcut-key'?: boolean | Partial 'mention'?: boolean | MentionOption - 'i18n'?: boolean | Partial + 'i18n'?: boolean | Partial 'counter'?: boolean | ICounterOption 'emoji'?: boolean | EmojiModuleOptions 'file'?: boolean diff --git a/packages/fluent-editor/src/core/fluent-editor.ts b/packages/fluent-editor/src/core/fluent-editor.ts index 084ec7f1..af9ef584 100644 --- a/packages/fluent-editor/src/core/fluent-editor.ts +++ b/packages/fluent-editor/src/core/fluent-editor.ts @@ -1,9 +1,23 @@ import type { ExpandedQuillOptions } from 'quill' +import type { I18n, I18nOptions } from 'quill-i18n' import type { IEditorConfig } from '../config/types' import type { FileUploader } from '../modules/custom-uploader' import Quill from 'quill' import { defaultLanguage } from '../config' -import I18N from '../modules/i18n' +import { EN_US } from '../config/i18n/en-us' +import { ZH_CN } from '../config/i18n/zh-cn' +import { merge } from '../utils/merge' + +// Temporary backward-compat syntax support: `_i18n"key"` +// TODO: remove this legacy syntax support in a future major version. +const LEGACY_I18N_KEY_REGEX = /^_i18n(?:"([^"]+)"|'([^']+)'|`([^`]+)`)$/ + +function resolveLegacyI18nKey(name: string): string { + const key = name.trim() + const matched = key.match(LEGACY_I18N_KEY_REGEX) + if (!matched) return name + return matched[1] || matched[2] || matched[3] || name +} class FluentEditor extends Quill { isFullscreen: boolean = false @@ -15,16 +29,35 @@ class FluentEditor extends Quill { } get lang() { - const i18nModule = this.getModule('i18n') as I18N - return i18nModule ? i18nModule.options.lang : defaultLanguage + const i18nModule = this.getModule('i18n') as I18n + return i18nModule ? i18nModule.getLocale() : defaultLanguage } constructor(container: HTMLElement | string, options: IEditorConfig = {}) { + if (!options.modules) options.modules = {} + if (!options.modules.i18n) options.modules.i18n = {} + + const i18nOptions = options.modules.i18n as I18nOptions + const defaultMessages = { + 'en-US': EN_US, + 'zh-CN': ZH_CN, + } + const userMessages = i18nOptions.messages || {} + i18nOptions.messages = merge({}, defaultMessages, userMessages) + + if (!i18nOptions.interpolate) { + i18nOptions.interpolate = (template: string, params: Record) => { + return template.replaceAll(/\{\{([\w]+)\}\}/g, (match, key) => params[key] != null ? String(params[key]) : match) + } + } + super(container, options) } - getLangText(name: string) { - return I18N.parserText(name, this.lang) + getLangText(name: string, params?: Record, defaultValue?: string) { + const i18nModule = this.getModule('i18n') as I18n + if (!i18nModule) return name + return i18nModule.t(resolveLegacyI18nKey(name), params, defaultValue) } } diff --git a/packages/fluent-editor/src/fluent-editor.ts b/packages/fluent-editor/src/fluent-editor.ts index 649dc0cc..2450c137 100644 --- a/packages/fluent-editor/src/fluent-editor.ts +++ b/packages/fluent-editor/src/fluent-editor.ts @@ -1,6 +1,5 @@ +import I18n from 'quill-i18n' import { FontStyle, LineHeightStyle, SizeStyle, TextIndentStyle } from './attributors' -import { EN_US } from './config/i18n/en-us' -import { ZH_CN } from './config/i18n/zh-cn' import FluentEditor from './core/fluent-editor' import { EmojiBlot, SoftBreak, StrikeBlot, Video } from './formats' import { AI } from './modules/ai' // AI @@ -12,7 +11,6 @@ import { DividerBlot } from './modules/divider' // 分割线 import { EmojiModule } from './modules/emoji' import { FileModule } from './modules/file' // 文件 import { FlowChartModule } from './modules/flow-chart' // 流程图 -import I18N from './modules/i18n' import { LinkBlot } from './modules/link' // 超链接 import { MathliveModule } from './modules/mathlive' // latex公式 import { Mention } from './modules/mention' // @提醒 @@ -24,13 +22,6 @@ import { ColorPicker, Picker } from './modules/toolbar/better-picker' import SnowTheme from './themes/snow' import Icons from './ui/icons' -I18N.register( - { - 'en-US': EN_US, - 'zh-CN': ZH_CN, - }, - true, -) FluentEditor.register( { 'attributors/style/font': FontStyle, @@ -52,7 +43,7 @@ FluentEditor.register( 'modules/counter': Counter, 'modules/emoji': EmojiModule, 'modules/file': FileModule, - 'modules/i18n': I18N, + 'modules/i18n': I18n, 'modules/image': BlotFormatter, 'modules/mathlive': MathliveModule, 'modules/ai': AI, diff --git a/packages/fluent-editor/src/modules/counter.ts b/packages/fluent-editor/src/modules/counter.ts index e998edcd..65f2c30f 100644 --- a/packages/fluent-editor/src/modules/counter.ts +++ b/packages/fluent-editor/src/modules/counter.ts @@ -1,7 +1,7 @@ import type { AnyFunction } from '../config' import type FluentEditor from '../fluent-editor' import Quill from 'quill' -import { CHANGE_LANGUAGE_EVENT } from '../config' +import { I18N_LOCALE_CHANGE } from 'quill-i18n' export interface ICounterOption { format?: 'text' | 'html' @@ -19,7 +19,7 @@ export default class Counter { this.options = this.resolveOptions(options) this.container = quill.addContainer('ql-counter') quill.on(Quill.events.TEXT_CHANGE, this.renderCount) - this.quill.emitter.on(CHANGE_LANGUAGE_EVENT, () => { + this.quill.on(I18N_LOCALE_CHANGE, () => { this.options = this.resolveOptions(options) this.renderCount() }) diff --git a/packages/fluent-editor/src/modules/custom-image/index.ts b/packages/fluent-editor/src/modules/custom-image/index.ts index 9d364d98..29baf512 100644 --- a/packages/fluent-editor/src/modules/custom-image/index.ts +++ b/packages/fluent-editor/src/modules/custom-image/index.ts @@ -1,5 +1,5 @@ export * from './actions' export * from './blot-formatter' export * from './image' -export * from './specs' export * from './preview' +export * from './specs' diff --git a/packages/fluent-editor/src/modules/custom-image/preview/preview-modal.ts b/packages/fluent-editor/src/modules/custom-image/preview/preview-modal.ts index ff5a9f97..dc2ac55a 100644 --- a/packages/fluent-editor/src/modules/custom-image/preview/preview-modal.ts +++ b/packages/fluent-editor/src/modules/custom-image/preview/preview-modal.ts @@ -117,7 +117,7 @@ export class ImagePreviewModal { }) // 绑定滚轮缩放事件 - document.addEventListener('wheel', (e) => this.onMouseWheel(e), { passive: false }) + document.addEventListener('wheel', e => this.onMouseWheel(e), { passive: false }) // 阻止模态框内的点击事件冒泡到遮罩层 this.modal.addEventListener('click', (e) => { diff --git a/packages/fluent-editor/src/modules/custom-image/preview/preview.css b/packages/fluent-editor/src/modules/custom-image/preview/preview.css index 643b8a79..3eac5abb 100644 --- a/packages/fluent-editor/src/modules/custom-image/preview/preview.css +++ b/packages/fluent-editor/src/modules/custom-image/preview/preview.css @@ -100,5 +100,3 @@ padding: 10px 16px; } } - - diff --git a/packages/fluent-editor/src/modules/custom-image/specs/custom-image-spec.ts b/packages/fluent-editor/src/modules/custom-image/specs/custom-image-spec.ts index f56a4d74..7c29d108 100644 --- a/packages/fluent-editor/src/modules/custom-image/specs/custom-image-spec.ts +++ b/packages/fluent-editor/src/modules/custom-image/specs/custom-image-spec.ts @@ -136,7 +136,7 @@ export class CustomImageSpec extends ImageSpec { */ onImageDoubleClick = (event: MouseEvent) => { const target = event.target - const imageSrc = target.getAttribute('src') || target.getAttribute('data-image') + const imageSrc = (target as HTMLElement).getAttribute('src') || (target as HTMLElement).getAttribute('data-image') if (imageSrc) { const modal = getImagePreviewModal() diff --git a/packages/fluent-editor/src/modules/custom-uploader.ts b/packages/fluent-editor/src/modules/custom-uploader.ts index d3c77af6..6f94c341 100644 --- a/packages/fluent-editor/src/modules/custom-uploader.ts +++ b/packages/fluent-editor/src/modules/custom-uploader.ts @@ -84,10 +84,10 @@ export class FileUploader extends Uploader { if (Array.isArray(mimetypes)) { return this.filterFromArray(mimetypes, kind) } - const map = mimetypes || {} + const map = (mimetypes || {}) as any const fromKind = map[kind] - if (fromKind?.length) return fromKind - if (map.file?.length && kind !== 'file') return map.file + if (fromKind?.length) return fromKind as string[] + if (map.file?.length && kind !== 'file') return map.file as string[] return [] } @@ -96,7 +96,7 @@ export class FileUploader extends Uploader { if (typeof maxSize === 'number') { return maxSize } - const map = maxSize || {} + const map = (maxSize || {}) as any const fromKind = map[kind] if (typeof fromKind === 'number') return fromKind if (typeof map.file === 'number' && kind !== 'file') return map.file @@ -108,7 +108,7 @@ export class FileUploader extends Uploader { if (typeof multiple === 'boolean') { return multiple } - const map = multiple || {} + const map = (multiple || {}) as any const fromKind = map[kind] if (typeof fromKind === 'boolean') return fromKind if (typeof map.file === 'boolean' && kind !== 'file') return map.file diff --git a/packages/fluent-editor/src/modules/file/modules/file-module.ts b/packages/fluent-editor/src/modules/file/modules/file-module.ts index 56946b93..e7c61da5 100644 --- a/packages/fluent-editor/src/modules/file/modules/file-module.ts +++ b/packages/fluent-editor/src/modules/file/modules/file-module.ts @@ -24,7 +24,7 @@ export class FileModule { event.preventDefault() // 在只读模式下直接下载文件 if (!this.quill.isEnabled()) { - this.downloadFile(fileDom) + this.downloadFile(fileDom as HTMLElement) return } if (this.fileBar) { diff --git a/packages/fluent-editor/src/modules/flow-chart/i18n/index.ts b/packages/fluent-editor/src/modules/flow-chart/i18n/index.ts index e170f812..f6502abb 100644 --- a/packages/fluent-editor/src/modules/flow-chart/i18n/index.ts +++ b/packages/fluent-editor/src/modules/flow-chart/i18n/index.ts @@ -1,9 +1,7 @@ import { FLOW_CHART_EN_US } from './en-us' import { FLOW_CHART_ZH_CN } from './zh-cn' -export function registerFlowChartI18N(I18N: any) { - I18N.register({ - 'en-US': FLOW_CHART_EN_US, - 'zh-CN': FLOW_CHART_ZH_CN, - }, false) +export function registerFlowChartI18N(i18nModule: any) { + i18nModule.addMessages('en-US', FLOW_CHART_EN_US) + i18nModule.addMessages('zh-CN', FLOW_CHART_ZH_CN) } diff --git a/packages/fluent-editor/src/modules/flow-chart/modules/context-menu.ts b/packages/fluent-editor/src/modules/flow-chart/modules/context-menu.ts index e7c013d5..39281755 100644 --- a/packages/fluent-editor/src/modules/flow-chart/modules/context-menu.ts +++ b/packages/fluent-editor/src/modules/flow-chart/modules/context-menu.ts @@ -1,7 +1,6 @@ +import type { I18n } from 'quill-i18n' import type FluentEditor from '../../../core/fluent-editor' import type FlowChartPlaceholderBlot from '../formats/flow-chart-blot' -import { CHANGE_LANGUAGE_EVENT } from '../../../config' -import { I18N } from '../../../modules/i18n' import { registerFlowChartI18N } from '../i18n/index' class FlowChartContextMenuHandler { @@ -12,12 +11,12 @@ class FlowChartContextMenuHandler { } constructor(private quill: FluentEditor, private blot: FlowChartPlaceholderBlot) { - const i18nModule = this.quill.getModule('i18n') as I18N - registerFlowChartI18N(I18N) - this.lang = i18nModule.options.lang + const i18nModule = this.quill.getModule('i18n') as I18n + registerFlowChartI18N(i18nModule) + this.lang = i18nModule.getLocale() this.texts = this.resolveTexts() - this.quill.emitter.on(CHANGE_LANGUAGE_EVENT, (lang: string) => { - this.lang = lang + this.quill.on('i18n-locale-change', (event: { locale: string, oldLocale: string }) => { + this.lang = event.locale this.texts = this.resolveTexts() this.updateContextMenuItems() }) @@ -25,12 +24,12 @@ class FlowChartContextMenuHandler { resolveTexts() { return { - copy: I18N.parserText('flowChart.contextMenu.copy', this.lang), - cut: I18N.parserText('flowChart.contextMenu.cut', this.lang), - paste: I18N.parserText('flowChart.contextMenu.paste', this.lang), - deleteContent: I18N.parserText('flowChart.contextMenu.deleteContent', this.lang), - deleteNode: I18N.parserText('flowChart.contextMenu.deleteNode', this.lang), - deleteEdge: I18N.parserText('flowChart.contextMenu.deleteEdge', this.lang), + copy: this.quill.getLangText('flowChart.contextMenu.copy'), + cut: this.quill.getLangText('flowChart.contextMenu.cut'), + paste: this.quill.getLangText('flowChart.contextMenu.paste'), + deleteContent: this.quill.getLangText('flowChart.contextMenu.deleteContent'), + deleteNode: this.quill.getLangText('flowChart.contextMenu.deleteNode'), + deleteEdge: this.quill.getLangText('flowChart.contextMenu.deleteEdge'), } } diff --git a/packages/fluent-editor/src/modules/flow-chart/modules/control-panel.ts b/packages/fluent-editor/src/modules/flow-chart/modules/control-panel.ts index d0dad98a..8b71361c 100644 --- a/packages/fluent-editor/src/modules/flow-chart/modules/control-panel.ts +++ b/packages/fluent-editor/src/modules/flow-chart/modules/control-panel.ts @@ -1,7 +1,6 @@ +import type { I18n } from 'quill-i18n' import type FluentEditor from '../../../core/fluent-editor' import type FlowChartPlaceholderBlot from '../formats/flow-chart-blot' -import { CHANGE_LANGUAGE_EVENT } from '../../../config' -import { I18N } from '../../../modules/i18n' import { registerFlowChartI18N } from '../i18n' import { backIcon, bezierIcon, contractIcon, fitIcon, forwardIcon, lineIcon, polyLineIcon, screenReduceIcon, screenTypeIcon, zoomInIcon, zoomOutIcon } from '../icons' @@ -13,12 +12,12 @@ class FlowChartControlPanelHandler { } constructor(private quill: FluentEditor, private blot: FlowChartPlaceholderBlot) { - const i18nModule = this.quill.getModule('i18n') as I18N - registerFlowChartI18N(I18N) - this.lang = i18nModule.options.lang + const i18nModule = this.quill.getModule('i18n') as I18n + registerFlowChartI18N(i18nModule) + this.lang = i18nModule.getLocale() this.texts = this.resolveTexts() - this.quill.emitter.on(CHANGE_LANGUAGE_EVENT, (lang: string) => { - this.lang = lang + this.quill.on('i18n-locale-change', (event: { locale: string, oldLocale: string }) => { + this.lang = event.locale this.texts = this.resolveTexts() this.updateControlPanelTexts() }) @@ -26,15 +25,15 @@ class FlowChartControlPanelHandler { resolveTexts() { return { - exportTitle: I18N.parserText('flowChart.controlPanel.exportTitle', this.lang), - zoomOutTitle: I18N.parserText('flowChart.controlPanel.zoomOutTitle', this.lang), - zoomInTitle: I18N.parserText('flowChart.controlPanel.zoomInTitle', this.lang), - fitTitle: I18N.parserText('flowChart.controlPanel.fitTitle', this.lang), - backTitle: I18N.parserText('flowChart.controlPanel.backTitle', this.lang), - forwardTitle: I18N.parserText('flowChart.controlPanel.forwardTitle', this.lang), - setEdgeTypeTitle: I18N.parserText('flowChart.controlPanel.setEdgeTypeTitle', this.lang), - panelStatusTitle: I18N.parserText('flowChart.controlPanel.panelStatusTitle', this.lang), - screenTypeTitle: I18N.parserText('flowChart.controlPanel.screenTypeTitle', this.lang), + exportTitle: this.quill.getLangText('flowChart.controlPanel.exportTitle'), + zoomOutTitle: this.quill.getLangText('flowChart.controlPanel.zoomOutTitle'), + zoomInTitle: this.quill.getLangText('flowChart.controlPanel.zoomInTitle'), + fitTitle: this.quill.getLangText('flowChart.controlPanel.fitTitle'), + backTitle: this.quill.getLangText('flowChart.controlPanel.backTitle'), + forwardTitle: this.quill.getLangText('flowChart.controlPanel.forwardTitle'), + setEdgeTypeTitle: this.quill.getLangText('flowChart.controlPanel.setEdgeTypeTitle'), + panelStatusTitle: this.quill.getLangText('flowChart.controlPanel.panelStatusTitle'), + screenTypeTitle: this.quill.getLangText('flowChart.controlPanel.screenTypeTitle'), } } diff --git a/packages/fluent-editor/src/modules/i18n.ts b/packages/fluent-editor/src/modules/i18n.ts deleted file mode 100644 index 4e10988a..00000000 --- a/packages/fluent-editor/src/modules/i18n.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type FluentEditor from '../fluent-editor' -import { CHANGE_LANGUAGE_EVENT, defaultLanguage } from '../config' -import { isUndefined } from '../utils/is' - -const langs: Record> = {} - -export interface I18NOptions { - lang: string -} -export class I18N { - static register(inputLangs: Record>, isCover: boolean = true) { - for (const lang in inputLangs) { - const texts = inputLangs[lang] - if (isCover) { - langs[lang] = texts - } - else { - if (!langs[lang]) langs[lang] = {} - Object.assign(langs[lang], texts) - } - } - } - - static parserText(text: string, lang: string): string { - const i18nPattern = /^_i18n"([^"]*)"/ - const match = text.match(i18nPattern) - let key = text - if (match) { - key = match[1] - } - return langs[lang]?.[key] || key - } - - options: I18NOptions = { - lang: '', - } - - constructor(public quill: FluentEditor, options: Partial) { - this.options = Object.assign({}, options, this.resolveLanguageOption(options || {})) - // wait until all module registed - Promise.resolve().then(() => this.changeLanguage(this.options, true)) - } - - resolveLanguageOption(options: Partial): I18NOptions { - if (isUndefined(options.lang)) { - options.lang = defaultLanguage - } - if (!(options.lang in langs)) { - console.warn(`The language ${options.lang} is not supported. Use the default language: ${defaultLanguage}`) - options.lang = defaultLanguage - } - return { - lang: options.lang, - } - } - - changeLanguage(options: Partial, force: boolean = false) { - const currentLang = this.options.lang - const langOps = this.resolveLanguageOption(options) - if (langOps.lang === currentLang && !force) return - this.options.lang = langOps.lang - this.quill.emitter.emit(CHANGE_LANGUAGE_EVENT, this.options.lang, langs[langOps.lang]) - } -} - -export default I18N diff --git a/packages/fluent-editor/src/modules/index.ts b/packages/fluent-editor/src/modules/index.ts index d7ea08f6..85f7ae0a 100644 --- a/packages/fluent-editor/src/modules/index.ts +++ b/packages/fluent-editor/src/modules/index.ts @@ -7,7 +7,6 @@ export * from './divider' export * from './emoji' export * from './file' export * from './flow-chart' -export * from './i18n' export * from './link' export * from './mathlive' export * from './mention' @@ -16,3 +15,5 @@ export * from './shortcut-key' export * from './syntax' export * from './table-up' export * from './toolbar' +export * from 'quill-i18n' +export * from 'quill-shortcut-key' diff --git a/packages/fluent-editor/src/modules/link/modules/tooltip.ts b/packages/fluent-editor/src/modules/link/modules/tooltip.ts index 371724ec..2c70f1ad 100644 --- a/packages/fluent-editor/src/modules/link/modules/tooltip.ts +++ b/packages/fluent-editor/src/modules/link/modules/tooltip.ts @@ -1,9 +1,9 @@ import type { Parchment as TypeParchment } from 'quill' import type FluentEditor from '../../../core/fluent-editor' import Quill, { Range } from 'quill' +import { I18N_LOCALE_CHANGE } from 'quill-i18n' import Emitter from 'quill/core/emitter' import { BaseTooltip } from 'quill/themes/base' -import { CHANGE_LANGUAGE_EVENT } from '../../../config' import { hadProtocol, isNullOrUndefined } from '../../../config/editor.utils' import { EN_US } from '../../../config/i18n/en-us' import { debounce } from '../../../utils/debounce' @@ -35,7 +35,7 @@ export class LinkTooltip extends BaseTooltip { LinkBlot.autoProtocol = this.options.autoProtocol this.debouncedHideToolTip = debounce(this.hideToolTip, 300) this.debouncedShowToolTip = debounce(this.showToolTip, 300) - this.quill.emitter.on(CHANGE_LANGUAGE_EVENT, () => { + this.quill.on(I18N_LOCALE_CHANGE, () => { this.setTemplate() }) } @@ -47,8 +47,9 @@ export class LinkTooltip extends BaseTooltip { '', '', ].join('') + this.root.setAttribute('data-before-title', this.quill.getLangText(`link.enter-${this.root.dataset.mode}`)) this.textbox = this.root.querySelector('input[type="text"]') - this.listen() + this.bindRootEvents() } resolveOptions() { @@ -113,16 +114,6 @@ export class LinkTooltip extends BaseTooltip { listen() { super.listen() - this.root.querySelector('a.ql-remove').addEventListener('click', (event) => { - if (!isNullOrUndefined(this.linkRange)) { - const range = this.linkRange - this.restoreFocus() - this.quill.formatText(range, 'link', false, Emitter.sources.API) - delete this.linkRange - } - event.preventDefault() - this.hide() - }) this.quill.root.addEventListener( 'mouseover', @@ -160,20 +151,9 @@ export class LinkTooltip extends BaseTooltip { false, ) + this.bindRootEvents() this.root.addEventListener('mouseleave', this.handleMouseLeave.bind(this), false) - this.root.querySelector('a.ql-preview').addEventListener('click', (event) => { - const link = LinkBlot.sanitize(this.textbox.value) - window.open(link, '_blank') - event.preventDefault() - }) - this.root.querySelector('input[type="text"]').addEventListener('focus', () => { - this.isInputFocus = true - }) - this.root.querySelector('input[type="text"]').addEventListener('blur', () => { - this.isInputFocus = false - this.save() - }) this.quill.on( Emitter.events.SELECTION_CHANGE, (range, _oldRange, source) => { @@ -216,6 +196,35 @@ export class LinkTooltip extends BaseTooltip { ) } + bindRootEvents() { + const removeClickHandler = (event: Event) => { + if (!isNullOrUndefined(this.linkRange)) { + const range = this.linkRange + this.restoreFocus() + this.quill.formatText(range, 'link', false, Emitter.sources.API) + delete this.linkRange + } + event.preventDefault() + this.hide() + } + this.root.querySelector('a.ql-remove').addEventListener('click', removeClickHandler) + const previewClickHandler = (event: Event) => { + const link = LinkBlot.sanitize(this.textbox.value) + window.open(link, '_blank') + event.preventDefault() + } + this.root.querySelector('a.ql-preview').addEventListener('click', previewClickHandler) + const inputFocusHandler = () => { + this.isInputFocus = true + } + this.root.querySelector('input[type="text"]').addEventListener('focus', inputFocusHandler) + const inputBlurHandler = () => { + this.isInputFocus = false + this.save() + } + this.root.querySelector('input[type="text"]').addEventListener('blur', inputBlurHandler) + } + save() { let value = this.textbox.value if (!value) return @@ -321,6 +330,7 @@ export class LinkTooltip extends BaseTooltip { this.textbox.getAttribute(`data-${mode}`) || '', ) this.root.setAttribute('data-mode', mode) + this.root.setAttribute('data-before-title', this.quill.getLangText(`link.enter-${this.root.dataset.mode}`)) } show() { diff --git a/packages/fluent-editor/src/modules/mind-map/i18n/index.ts b/packages/fluent-editor/src/modules/mind-map/i18n/index.ts index 1bdc6e9d..aca54ba7 100644 --- a/packages/fluent-editor/src/modules/mind-map/i18n/index.ts +++ b/packages/fluent-editor/src/modules/mind-map/i18n/index.ts @@ -1,9 +1,7 @@ import { MIND_MAP_EN_US } from './en-us' import { MIND_MAP_ZH_CN } from './zh-cn' -export function registerMindMapI18N(I18N: any) { - I18N.register({ - 'en-US': MIND_MAP_EN_US, - 'zh-CN': MIND_MAP_ZH_CN, - }, false) +export function registerMindMapI18N(i18nModule: any) { + i18nModule.addMessages('en-US', MIND_MAP_EN_US) + i18nModule.addMessages('zh-CN', MIND_MAP_ZH_CN) } diff --git a/packages/fluent-editor/src/modules/mind-map/modules/context-menu.ts b/packages/fluent-editor/src/modules/mind-map/modules/context-menu.ts index 9e7adedb..a9721d3b 100644 --- a/packages/fluent-editor/src/modules/mind-map/modules/context-menu.ts +++ b/packages/fluent-editor/src/modules/mind-map/modules/context-menu.ts @@ -1,7 +1,6 @@ import type FluentEditor from '../../../core/fluent-editor' import type MindMapPlaceholderBlot from '../formats/mind-map-blot' -import { CHANGE_LANGUAGE_EVENT } from '../../../config' -import { I18N } from '../../../modules/i18n' +import { type I18n, I18N_LOCALE_CHANGE } from 'quill-i18n' import { registerMindMapI18N } from '../i18n' class MindMapContextMenuHandler { @@ -12,12 +11,12 @@ class MindMapContextMenuHandler { } constructor(private quill: FluentEditor, private blot: MindMapPlaceholderBlot) { - const i18nModule = this.quill.getModule('i18n') as I18N - registerMindMapI18N(I18N) - this.lang = i18nModule.options.lang + const i18nModule = this.quill.getModule('i18n') as I18n + registerMindMapI18N(i18nModule) + this.lang = i18nModule.getLocale() this.texts = this.resolveTexts() - this.quill.emitter.on(CHANGE_LANGUAGE_EVENT, (lang: string) => { - this.lang = lang + this.quill.on(I18N_LOCALE_CHANGE, (event: { locale: string, oldLocale: string }) => { + this.lang = event.locale this.texts = this.resolveTexts() this.updateContextMenuItems() }) @@ -25,10 +24,12 @@ class MindMapContextMenuHandler { resolveTexts() { return { - copy: I18N.parserText('mindMap.contextMenu.copy', this.lang), - cut: I18N.parserText('mindMap.contextMenu.cut', this.lang), - paste: I18N.parserText('mindMap.contextMenu.paste', this.lang), - delete: I18N.parserText('mindMap.contextMenu.deleteContent', this.lang), + copy: this.quill.getLangText('mindMap.contextMenu.copy'), + cut: this.quill.getLangText('mindMap.contextMenu.cut'), + paste: this.quill.getLangText('mindMap.contextMenu.paste'), + deleteContent: this.quill.getLangText('mindMap.contextMenu.deleteContent'), + deleteNode: this.quill.getLangText('mindMap.contextMenu.deleteNode'), + deleteIcon: this.quill.getLangText('mindMap.contextMenu.deleteIcon'), } } diff --git a/packages/fluent-editor/src/modules/mind-map/modules/control-panel.ts b/packages/fluent-editor/src/modules/mind-map/modules/control-panel.ts index 5ab7e2b9..38b01607 100644 --- a/packages/fluent-editor/src/modules/mind-map/modules/control-panel.ts +++ b/packages/fluent-editor/src/modules/mind-map/modules/control-panel.ts @@ -1,7 +1,6 @@ import type FluentEditor from '../../../core/fluent-editor' import type MindMapPlaceholderBlot from '../formats/mind-map-blot' -import { CHANGE_LANGUAGE_EVENT } from '../../../config' -import { I18N } from '../../../modules/i18n' +import { type I18n, I18N_LOCALE_CHANGE } from 'quill-i18n' import { getAllConfigs } from '../config-utils' import { registerMindMapI18N } from '../i18n' import { backIcon, catalogOrganizationIcon, contractIcon, fishboneIcon, fitIcon, forwardIcon, insertChildNodeIcon, insertIconIcon, insertNodeIcon, insertParentNodeIcon, logicalStructureIcon, mindMapIcon, organizationStructureIcon, removeNodeIcon, screenReduceIcon, screenTypeIcon, setLayoutIcon, timelineIcon, zoomInIcon, zoomOutIcon } from '../icons' @@ -14,12 +13,12 @@ class MindMapControlPanelHandler { } constructor(private quill: FluentEditor, private blot: MindMapPlaceholderBlot) { - const i18nModule = this.quill.getModule('i18n') as I18N - registerMindMapI18N(I18N) - this.lang = i18nModule.options.lang + const i18nModule = this.quill.getModule('i18n') as I18n + registerMindMapI18N(i18nModule) + this.lang = i18nModule.getLocale() this.texts = this.resolveTexts() - this.quill.emitter.on(CHANGE_LANGUAGE_EVENT, (lang: string) => { - this.lang = lang + this.quill.on(I18N_LOCALE_CHANGE, (event: { locale: string, oldLocale: string }) => { + this.lang = event.locale this.texts = this.resolveTexts() this.updateControlPanelTexts() }) @@ -50,10 +49,10 @@ class MindMapControlPanelHandler { return textKeys.reduce((acc, key) => { if (!key.includes('Title')) { - acc[key] = I18N.parserText(`mindMap.layout.${key.replace('Layout', '')}`, this.lang) + acc[key] = this.quill.getLangText(`mindMap.layout.${key.replace('Layout', '')}`) } else { - acc[key] = I18N.parserText(`mindMap.controlPanel.${key}`, this.lang) + acc[key] = this.quill.getLangText(`mindMap.controlPanel.${key}`) } return acc }, {} as Record) diff --git a/packages/fluent-editor/src/modules/shortcut-key/index.ts b/packages/fluent-editor/src/modules/shortcut-key/index.ts index ca5b4a5d..39c458a3 100644 --- a/packages/fluent-editor/src/modules/shortcut-key/index.ts +++ b/packages/fluent-editor/src/modules/shortcut-key/index.ts @@ -4,8 +4,9 @@ import type { Context } from 'quill/modules/keyboard' import type TypeToolbar from 'quill/modules/toolbar' import type FluentEditor from '../../fluent-editor' import Quill from 'quill' +import { I18N_LOCALE_CHANGE } from 'quill-i18n' import QuillShortcutKey, { defaultShortKey, searchAndSort } from 'quill-shortcut-key' -import { CHANGE_LANGUAGE_EVENT } from '../../config' +import { isString } from '../../utils/is' export interface ShortCutKeyCustomOptions { isMenuItemsAdd: boolean } export type ShortCutKeyInputOptions = QuillShortcutKeyInputOptions & ShortCutKeyCustomOptions @@ -15,7 +16,7 @@ export class ShortCutKey extends QuillShortcutKey { constructor(public quill: FluentEditor, options: Partial) { super(quill, options) - this.quill.emitter.on(CHANGE_LANGUAGE_EVENT, () => { + this.quill.on(I18N_LOCALE_CHANGE, () => { this.destroyMenuList() this.options = this.resolveOptions(options) this.menuSorter = searchAndSort.bind(this, this.getAllMenuItems()) as (searchText: string) => Menu @@ -43,7 +44,7 @@ export class ShortCutKey extends QuillShortcutKey { const value = { ...item, } - if (item.title) { + if (item.title && isString(item.title)) { value.title = this.quill.getLangText(item.title) } if (item.type === 'group') { diff --git a/packages/fluent-editor/src/modules/table-up/index.ts b/packages/fluent-editor/src/modules/table-up/index.ts index 5f3afe15..1ed09837 100644 --- a/packages/fluent-editor/src/modules/table-up/index.ts +++ b/packages/fluent-editor/src/modules/table-up/index.ts @@ -3,7 +3,7 @@ import type BaseTheme from 'quill/themes/base' import type Picker from 'quill/ui/picker' import type { Constructor } from '../../config/types' import type FluentEditor from '../../core/fluent-editor' -import { CHANGE_LANGUAGE_EVENT } from '../../config' +import { I18N_LOCALE_CHANGE } from 'quill-i18n' import { isFunction } from '../../utils/is' interface QuillTheme extends BaseTheme { @@ -11,8 +11,8 @@ interface QuillTheme extends BaseTheme { } type QuillThemePicker = (Picker & { options: HTMLElement }) -export function generateTableUp(QuillTableUp: Constructor) { - return class extends QuillTableUp { +export function generateTableUp(QuillTableUp: Constructor) { + return class extends QuillTableUp { constructor(public quill: FluentEditor, options: Partial) { super(quill, options) @@ -25,8 +25,8 @@ export function generateTableUp(QuillTableUp: Constructor) { ]), ) - this.quill.emitter.on(CHANGE_LANGUAGE_EVENT, () => { - this.options.texts = this.resolveTexts(options.texts) + this.quill.on(I18N_LOCALE_CHANGE, () => { + this.refreshUI() const toolbar = this.quill.getModule('toolbar') as Toolbar if (toolbar && (this.quill.theme as QuillTheme).pickers) { const [, select] = (toolbar.controls as [string, HTMLElement][] || []).find(([name]) => name === this.statics.toolName) || [] @@ -44,40 +44,36 @@ export function generateTableUp(QuillTableUp: Constructor) { } }) this.modules = {} - this.initModules() - }) - } - - resolveTexts(options: Record = {}) { - return Object.assign({ - fullCheckboxText: this.quill.getLangText('fullCheckboxText'), - customBtnText: this.quill.getLangText('customBtnText'), - confirmText: this.quill.getLangText('confirmText'), - cancelText: this.quill.getLangText('cancelText'), - rowText: this.quill.getLangText('rowText'), - colText: this.quill.getLangText('colText'), - notPositiveNumberError: this.quill.getLangText('notPositiveNumberError'), - custom: this.quill.getLangText('custom'), - clear: this.quill.getLangText('clear'), - transparent: this.quill.getLangText('transparent'), - perWidthInsufficient: this.quill.getLangText('perWidthInsufficient'), - CopyCell: this.quill.getLangText('CopyCell'), - CutCell: this.quill.getLangText('CutCell'), - InsertTop: this.quill.getLangText('InsertTop'), - InsertRight: this.quill.getLangText('InsertRight'), - InsertBottom: this.quill.getLangText('InsertBottom'), - InsertLeft: this.quill.getLangText('InsertLeft'), - MergeCell: this.quill.getLangText('MergeCell'), - SplitCell: this.quill.getLangText('SplitCell'), - DeleteRow: this.quill.getLangText('DeleteRow'), - DeleteColumn: this.quill.getLangText('DeleteColumn'), - DeleteTable: this.quill.getLangText('DeleteTable'), - BackgroundColor: this.quill.getLangText('BackgroundColor'), - BorderColor: this.quill.getLangText('BorderColor'), - }, Object.entries(options).reduce((pre, [key, value]) => { - pre[key] = this.quill.getLangText(value) - return pre - }, {} as Record)) - } - } -} + this.initModules() + }) + } + + resolveOptions(options: Partial = {}) { + const { texts, ...rest } = options || {} + const resolvedOptions = super.resolveOptions(rest) + resolvedOptions.texts = super.resolveTexts(this.createTextResolver(texts)) + return resolvedOptions + } + + resolveTexts(options: Record | ((key: string) => string) = {}) { + return super.resolveTexts(this.createTextResolver(options)) + } + + createTextResolver(options: Record | ((key: string) => string) = {}) { + const textResolver = isFunction(options) ? options : null + const textMap = textResolver ? {} : options + + return (key: string) => { + if (textResolver) { + const customText = textResolver.call(this, key) + if (customText !== undefined) return customText + } + else if (textMap[key] !== undefined) { + return textMap[key] + } + + return this.quill.getLangText(key) + } + } + } +} diff --git a/packages/fluent-editor/src/modules/toolbar/toolbar-tip.ts b/packages/fluent-editor/src/modules/toolbar/toolbar-tip.ts index 132103a3..a4278113 100644 --- a/packages/fluent-editor/src/modules/toolbar/toolbar-tip.ts +++ b/packages/fluent-editor/src/modules/toolbar/toolbar-tip.ts @@ -1,7 +1,7 @@ import type { Constructor } from '../../config/types' import type FluentEditor from '../../fluent-editor' -import { CHANGE_LANGUAGE_EVENT } from '../../config' -import { isString } from '../../utils/is' +import { I18N_LOCALE_CHANGE } from 'quill-i18n' +import { isObject, isString } from '../../utils/is' export function generateToolbarTip(QuillToolbarTip: Constructor) { return class extends QuillToolbarTip { @@ -11,7 +11,7 @@ export function generateToolbarTip(QuillToolbarTip: Constructor) { } super(quill, options) - this.quill.emitter.on(CHANGE_LANGUAGE_EVENT, () => { + this.quill.on(I18N_LOCALE_CHANGE, () => { this.destroyAllTips() this.options = this.resolveOptions(options) this.createToolbarTip() @@ -127,9 +127,35 @@ export function generateToolbarTip(QuillToolbarTip: Constructor) { }, } const inputTipTextMap = Object.entries(options.tipTextMap).reduce((pre, [key, value]) => { - pre[key] = isString(value) ? this.quill.getLangText(value) : value + if (isString(value)) { + pre[key] = { msg: this.quill.getLangText(value) } + } + else if (value && isObject(value)) { + const isNewConfig = 'values' in value || 'onShow' in value || 'msg' in value || 'content' in value + if (!isNewConfig) { + pre[key] = { + values: Object.fromEntries( + Object.entries(value).map(([k, v]) => [k, isString(v) ? this.quill.getLangText(v as string) : v]), + ), + } + } + else { + const config = { ...value } as any + if (isString(config.msg)) config.msg = this.quill.getLangText(config.msg) + if (isString(config.content)) config.content = this.quill.getLangText(config.content) + if (config.values) { + config.values = Object.fromEntries( + Object.entries(config.values).map(([k, v]) => [k, isString(v) ? this.quill.getLangText(v as string) : v]), + ) + } + pre[key] = config + } + } + else { + pre[key] = value + } return pre - }, {}) + }, {} as Record) return { ...result, tipTextMap: { diff --git a/packages/fluent-editor/src/themes/snow.ts b/packages/fluent-editor/src/themes/snow.ts index c3f291d5..5bcb61b2 100644 --- a/packages/fluent-editor/src/themes/snow.ts +++ b/packages/fluent-editor/src/themes/snow.ts @@ -1,7 +1,8 @@ import type { ThemeOptions } from 'quill/core/theme' import type TypeToolbar from 'quill/modules/toolbar' import type TypeIconPicker from 'quill/ui/icon-picker' -import { CHANGE_LANGUAGE_EVENT, inputFile, isNullOrUndefined } from '../config' +import { I18N_LOCALE_CHANGE } from 'quill-i18n' +import { inputFile, isNullOrUndefined } from '../config' import FluentEditor from '../core/fluent-editor' import { CustomImageSpec } from '../modules/custom-image/specs/custom-image-spec' import { LinkTooltip } from '../modules/link' @@ -162,7 +163,7 @@ class SnowTheme extends OriginSnowTheme { constructor(public quill: FluentEditor, options: ThemeOptions) { super(quill, options) - this.quill.emitter.on(CHANGE_LANGUAGE_EVENT, () => { + this.quill.on(I18N_LOCALE_CHANGE, () => { this.i18nTextToolbar() }) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 56fb5e82..29e50369 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,11 +117,11 @@ importers: specifier: ^0.0.10 version: 0.0.10 quill-table-up: - specifier: ^3.3.0 - version: 3.3.0(quill@2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji)) + specifier: ^3.4.0 + version: 3.4.0(quill@2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji)) quill-toolbar-tip: - specifier: ^0.0.13 - version: 0.0.13(quill@2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji)) + specifier: 1.0.1-beta.1 + version: 1.0.1-beta.1(quill@2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji)) simple-mind-map: specifier: 0.14.0-fix.1 version: 0.14.0-fix.1 @@ -198,9 +198,12 @@ importers: quill-easy-color: specifier: ^0.0.10 version: 0.0.10(quill@2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji)) + quill-i18n: + specifier: ^0.2.0 + version: 0.2.0(quill@2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji)) quill-shortcut-key: - specifier: ^0.0.5 - version: 0.0.5(quill@2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji)) + specifier: ^0.2.0 + version: 0.2.0(quill@2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji)) devDependencies: '@emoji-mart/data': specifier: ^1.2.1 @@ -1211,9 +1214,15 @@ packages: '@floating-ui/core@1.7.3': resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + '@floating-ui/core@1.7.4': + resolution: {integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==} + '@floating-ui/dom@1.7.4': resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + '@floating-ui/dom@1.7.5': + resolution: {integrity: sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==} + '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} @@ -3354,11 +3363,12 @@ packages: glob@11.0.3: resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} engines: {node: 20 || >=22} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} @@ -4873,11 +4883,16 @@ packages: peerDependencies: quill: ^2.0.0 + quill-i18n@0.2.0: + resolution: {integrity: sha512-aWhfqpeOZyqiecfuP6RVa4R+oKSJgnOzmZ3zxtYTldj20DiEuXm3vQOMtMlZGMFbziHt8HIOE8fpCFu7JxEHqw==} + peerDependencies: + quill: ^2.0.0 + quill-markdown-shortcuts@0.0.10: resolution: {integrity: sha512-2FFFqqo65JgDgAGSer7cFQTCeiSjJF4N8lRGXGv/xjppCxSwj42OnNdGPZ/zeeCxdUY/j1LW4AiSvPQaTIkY2A==} - quill-shortcut-key@0.0.5: - resolution: {integrity: sha512-7B1KLgkaN4e1a9+ZtT3Fui3I0bqrdeMW3jQaMYJ3W8dHEi4jg2pIwSKW3cFerqH0iSPnNE3pAhX0y9AeDg4vnA==} + quill-shortcut-key@0.2.0: + resolution: {integrity: sha512-f5KeT3VkVebwd9Cg/EsShvQcX7fVgzuQ+31XwpmaiO0y1MV1fqgZcwy4roMY7SUk57YJxuEhubKrXRHHrKwCFw==} peerDependencies: quill: ^2.0.0 @@ -4886,13 +4901,13 @@ packages: peerDependencies: quill: ^2.0.3 - quill-table-up@3.3.0: - resolution: {integrity: sha512-B/EiCy5eXGgQ24WE1SBk9JW5roJuTENH6ZcnipfZm/AbK9BolEUfWujZXtqqXNs28oNvtJlXyTz5SLH+gn1dXA==} + quill-table-up@3.4.0: + resolution: {integrity: sha512-oU/0tVqyNqvXnCUTi9yZmdBzUU5w8D4rqMbGNvAMMjupglWVAGop7DrFTH09oUpzvz8uaPXx8vn9ETt4DxLhVg==} peerDependencies: quill: ^2.0.3 - quill-toolbar-tip@0.0.13: - resolution: {integrity: sha512-E2fK1C2xUiBulIRabgYBS861gD1M+NSU6ngijchMgdQlYY2y5Bml2mOaDnGwxRmMu9SVx5gu6DlXDe0Fi3nPwg==} + quill-toolbar-tip@1.0.1-beta.1: + resolution: {integrity: sha512-OSYGt1qkAvbsM7U1VeC/UGc2+exUbohi1Zp3e1f0nOaMfxVuVwuneYpQnecT5Jwq3FLKlQRqbRGAUItHviTC7g==} peerDependencies: quill: ^2.0.0 @@ -6795,11 +6810,20 @@ snapshots: dependencies: '@floating-ui/utils': 0.2.10 + '@floating-ui/core@1.7.4': + dependencies: + '@floating-ui/utils': 0.2.10 + '@floating-ui/dom@1.7.4': dependencies: '@floating-ui/core': 1.7.3 '@floating-ui/utils': 0.2.10 + '@floating-ui/dom@1.7.5': + dependencies: + '@floating-ui/core': 1.7.4 + '@floating-ui/utils': 0.2.10 + '@floating-ui/utils@0.2.10': {} '@floating-ui/vue@1.1.9(vue@3.5.22(typescript@5.9.3))': @@ -11253,11 +11277,15 @@ snapshots: dependencies: quill: 2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji) + quill-i18n@0.2.0(quill@2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji)): + dependencies: + quill: 2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji) + quill-markdown-shortcuts@0.0.10: dependencies: quill: 1.3.7 - quill-shortcut-key@0.0.5(quill@2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji)): + quill-shortcut-key@0.2.0(quill@2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji)): dependencies: quill: 2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji) @@ -11266,14 +11294,14 @@ snapshots: '@floating-ui/dom': 1.7.4 quill: 2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji) - quill-table-up@3.3.0(quill@2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji)): + quill-table-up@3.4.0(quill@2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji)): dependencies: - '@floating-ui/dom': 1.7.4 + '@floating-ui/dom': 1.7.5 quill: 2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji) - quill-toolbar-tip@0.0.13(quill@2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji)): + quill-toolbar-tip@1.0.1-beta.1(quill@2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji)): dependencies: - '@floating-ui/dom': 1.7.4 + '@floating-ui/dom': 1.7.5 quill: 2.0.3(patch_hash=dnirvjhpzdmsvdidsclwslxxji) quill@1.3.7: