diff --git a/examples/package.json b/examples/package.json index cadb9a114a..71c26fb0be 100644 --- a/examples/package.json +++ b/examples/package.json @@ -26,6 +26,7 @@ "@univerjs/docs": "workspace:*", "@univerjs/docs-drawing": "workspace:*", "@univerjs/docs-drawing-ui": "workspace:*", + "@univerjs/docs-find-replace": "workspace:*", "@univerjs/docs-hyper-link-ui": "workspace:*", "@univerjs/docs-mention-ui": "workspace:*", "@univerjs/docs-quick-insert-ui": "workspace:*", diff --git a/examples/src/docs/main.ts b/examples/src/docs/main.ts index b1d5f38116..f7ce68984c 100644 --- a/examples/src/docs/main.ts +++ b/examples/src/docs/main.ts @@ -3,6 +3,7 @@ import { FUniver } from '@univerjs/core/facade'; import { UniverDebuggerPlugin } from '@univerjs/debugger'; import { UniverDocsPlugin } from '@univerjs/docs'; import { UniverDocsDrawingUIPlugin } from '@univerjs/docs-drawing-ui'; +import { UniverDocsFindReplacePlugin } from '@univerjs/docs-find-replace'; import { UniverDocsHyperLinkUIPlugin } from '@univerjs/docs-hyper-link-ui'; import { UniverDocsMentionUIPlugin } from '@univerjs/docs-mention-ui'; import { UniverDocsQuickInsertUIPlugin } from '@univerjs/docs-quick-insert-ui'; @@ -44,6 +45,7 @@ univer.registerPlugin(UniverDocsUIPlugin, { container: 'univerdoc', }); +univer.registerPlugin(UniverDocsFindReplacePlugin); univer.registerPlugin(UniverDocsDrawingUIPlugin); univer.registerPlugin(UniverDocsThreadCommentUIPlugin); univer.registerPlugin(UniverDocsHyperLinkUIPlugin); diff --git a/package.json b/package.json index f02c05e154..252a35daec 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.3", "husky": "^9.1.7", - "lint-staged": "^17.0.8", + "lint-staged": "^17.2.0", "posthog-node": "^5.46.1", "react": "19.2.8", "react-dom": "19.2.8", diff --git a/packages/action-recorder/src/plugin.ts b/packages/action-recorder/src/plugin.ts index 428a7ad205..dc832ce2af 100644 --- a/packages/action-recorder/src/plugin.ts +++ b/packages/action-recorder/src/plugin.ts @@ -16,7 +16,8 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverActionRecorderConfig } from './config/config'; -import { IConfigService, Inject, Injector, merge, Plugin } from '@univerjs/core'; +import { DependentOn, IConfigService, Inject, Injector, merge, Plugin } from '@univerjs/core'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { ACTION_RECORDER_PLUGIN_CONFIG_KEY, defaultPluginConfig } from './config/config'; import { ActionRecorderController } from './controllers/action-recorder.controller'; @@ -28,6 +29,7 @@ import { ActionReplayService } from './services/replay.service'; * This plugin provides a recorder for user's interactions with Univer, * it only records commands (and some special operations) so that it can be replayed later. */ +@DependentOn(UniverUIPlugin) export class UniverActionRecorderPlugin extends Plugin { static override pluginName = 'UNIVER_ACTION_RECORDER_PLUGIN'; static override packageName = pkg.name; diff --git a/packages/docs-drawing-ui/src/plugin.ts b/packages/docs-drawing-ui/src/plugin.ts index 377a5d6960..8b3337a665 100644 --- a/packages/docs-drawing-ui/src/plugin.ts +++ b/packages/docs-drawing-ui/src/plugin.ts @@ -17,10 +17,12 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverDocsDrawingUIConfig } from './config/config'; import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; +import { UniverDocsPlugin } from '@univerjs/docs'; import { UniverDocsDrawingPlugin } from '@univerjs/docs-drawing'; +import { UniverDocsUIPlugin } from '@univerjs/docs-ui'; import { UniverDrawingPlugin } from '@univerjs/drawing'; import { UniverDrawingUIPlugin } from '@univerjs/drawing-ui'; -import { IRenderManagerService } from '@univerjs/engine-render'; +import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render'; import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { defaultPluginConfig, DOCS_DRAWING_UI_PLUGIN_CONFIG_KEY } from './config/config'; @@ -40,7 +42,15 @@ import { DocDrawingPopupMenuController } from './menu/drawing-popup-menu.control import { DocDrawingFloatingToolbarAdapterService } from './services/doc-drawing-floating-toolbar-adapter.service'; import { DocRefreshDrawingsService } from './services/doc-refresh-drawings.service'; -@DependentOn(UniverDrawingUIPlugin, UniverDrawingPlugin, UniverDocsDrawingPlugin, UniverUIPlugin) +@DependentOn( + UniverDocsPlugin, + UniverDrawingPlugin, + UniverRenderEnginePlugin, + UniverDocsDrawingPlugin, + UniverUIPlugin, + UniverDocsUIPlugin, + UniverDrawingUIPlugin +) export class UniverDocsDrawingUIPlugin extends Plugin { static override type = UniverInstanceType.UNIVER_DOC; static override pluginName = 'DOC_DRAWING_UI_PLUGIN'; diff --git a/packages/docs-drawing/src/plugin.ts b/packages/docs-drawing/src/plugin.ts index 2a78205534..9fdbf1a346 100644 --- a/packages/docs-drawing/src/plugin.ts +++ b/packages/docs-drawing/src/plugin.ts @@ -16,13 +16,16 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverDocsDrawingConfig } from './config/config'; -import { IConfigService, Inject, Injector, merge, Plugin, touchDependencies, UniverInstanceType } from '@univerjs/core'; +import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, touchDependencies, UniverInstanceType } from '@univerjs/core'; +import { UniverDocsPlugin } from '@univerjs/docs'; +import { UniverDrawingPlugin } from '@univerjs/drawing'; import pkg from '../package.json'; import { defaultPluginConfig, DOCS_DRAWING_PLUGIN_CONFIG_KEY } from './config/config'; import { DocDrawingController, DOCS_DRAWING_PLUGIN } from './controllers/doc-drawing.controller'; import { DocDrawingAdapterService, IDocDrawingAdapterService } from './services/doc-drawing-adapter.service'; import { DocDrawingService, IDocDrawingService } from './services/doc-drawing.service'; +@DependentOn(UniverDocsPlugin, UniverDrawingPlugin) export class UniverDocsDrawingPlugin extends Plugin { static override pluginName = DOCS_DRAWING_PLUGIN; static override packageName = pkg.name; diff --git a/packages/docs-find-replace/README.md b/packages/docs-find-replace/README.md new file mode 100644 index 0000000000..bf0e81b8fc --- /dev/null +++ b/packages/docs-find-replace/README.md @@ -0,0 +1,13 @@ +# @univerjs/docs-find-replace + +```bash +pnpm add @univerjs/docs-find-replace +``` + +```ts +import { UniverDocsFindReplacePlugin } from '@univerjs/docs-find-replace'; + +univer.registerPlugin(UniverDocsFindReplacePlugin); +``` + +Phase one searches and replaces literal text in the current document body, including tables. It excludes headers, footers, comments, drawings, regular expressions, formatting search, special characters, result sidebars, presets, and Facade APIs. diff --git a/packages/docs-find-replace/package.json b/packages/docs-find-replace/package.json new file mode 100644 index 0000000000..be70bdb7b8 --- /dev/null +++ b/packages/docs-find-replace/package.json @@ -0,0 +1,84 @@ +{ + "name": "@univerjs/docs-find-replace", + "version": "1.0.0-alpha.7", + "private": false, + "description": "Find and replace support for Univer Docs.", + "author": "DreamNum Co., Ltd. ", + "license": "Apache-2.0", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/univer" + }, + "homepage": "https://univer.ai", + "repository": { + "type": "git", + "url": "https://github.com/dream-num/univer" + }, + "bugs": { + "url": "https://github.com/dream-num/univer/issues" + }, + "keywords": [ + "univer", + "docs", + "find-replace", + "plugin" + ], + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "main": "./src/index.ts", + "types": "./lib/types/index.d.ts", + "publishConfig": { + "access": "public", + "main": "./lib/es/index.js", + "module": "./lib/es/index.js", + "exports": { + ".": { + "import": "./lib/es/index.js", + "require": "./lib/cjs/index.js", + "types": "./lib/types/index.d.ts" + }, + "./*": { + "import": "./lib/es/*", + "require": "./lib/cjs/*", + "types": "./lib/types/index.d.ts" + }, + "./lib/*": "./lib/*" + } + }, + "directories": { + "lib": "lib" + }, + "files": [ + "lib" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "coverage": "vitest run --coverage", + "typecheck": "tsc --noEmit", + "build:bundle": "univer-cli build", + "build:types": "tsc -p tsconfig.node.json", + "build": "pnpm run build:bundle && pnpm run build:types" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "rxjs": ">=7.0.0" + }, + "dependencies": { + "@univerjs/core": "workspace:*", + "@univerjs/docs": "workspace:*", + "@univerjs/docs-ui": "workspace:*", + "@univerjs/engine-render": "workspace:*", + "@univerjs/find-replace": "workspace:*", + "@univerjs/ui": "workspace:*" + }, + "devDependencies": { + "@univerjs-infra/shared": "workspace:*", + "react": "18.3.1", + "rxjs": "^7.8.2", + "typescript": "^6.0.3", + "vitest": "^4.1.10" + } +} diff --git a/packages/docs-find-replace/src/commands/commands/__tests__/docs-replace.command.spec.ts b/packages/docs-find-replace/src/commands/commands/__tests__/docs-replace.command.spec.ts new file mode 100644 index 0000000000..778b9fc9b9 --- /dev/null +++ b/packages/docs-find-replace/src/commands/commands/__tests__/docs-replace.command.spec.ts @@ -0,0 +1,136 @@ +/** + * Copyright 2023-present DreamNum Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ICommand, IDocumentData } from '@univerjs/core'; +import type { IFindQuery } from '@univerjs/find-replace'; +import { BooleanNumber, CustomRangeType, ICommandService, UndoCommandId } from '@univerjs/core'; +import { RichTextEditingMutation } from '@univerjs/docs'; +import { createCommandTestBed } from '@univerjs/docs-ui/commands/commands/__tests__/create-command-test-bed'; +import { FindBy, FindDirection, FindScope } from '@univerjs/find-replace'; +import { describe, expect, it } from 'vitest'; +import { DocsReplaceCommand } from '../docs-replace.command'; + +function docsQuery(findString: string, overrides: Partial = {}): IFindQuery { + return { + findString, + replaceRevealed: true, + caseSensitive: false, + matchesTheWholeWord: false, + matchesTheWholeCell: false, + findDirection: FindDirection.ROW, + findScope: FindScope.SUBUNIT, + findBy: FindBy.VALUE, + ...overrides, + }; +} + +function styledDocument(dataStream: string): IDocumentData { + return { + id: 'test-doc', + body: { + dataStream, + textRuns: [{ st: 0, ed: dataStream.length - 2, ts: { bl: BooleanNumber.TRUE } }], + }, + documentStyle: {}, + }; +} + +function registerCommands(commandService: ICommandService): void { + commandService.registerCommand(RichTextEditingMutation as unknown as ICommand); + commandService.registerCommand(DocsReplaceCommand); +} + +describe('DocsReplaceCommand', () => { + it('replaces all matches in one undoable mutation and keeps the first style', async () => { + const testBed = createCommandTestBed(styledDocument('cat cat\r\n')); + const commandService = testBed.get(ICommandService); + registerCommands(commandService); + + await expect(commandService.executeCommand(DocsReplaceCommand.id, { + unitId: 'test-doc', + query: docsQuery('cat'), + replaceString: 'dog', + })).resolves.toEqual({ success: 2, failure: 0 }); + expect(testBed.doc.getBody()?.dataStream).toBe('dog dog\r\n'); + expect(testBed.doc.getBody()?.textRuns?.[0].ts?.bl).toBe(BooleanNumber.TRUE); + + await commandService.executeCommand(UndoCommandId); + expect(testBed.doc.getBody()?.dataStream).toBe('cat cat\r\n'); + testBed.univer.dispose(); + }); + + it('skips whole entities and rejects a stale current range', async () => { + const testBed = createCommandTestBed({ + id: 'test-doc', + body: { + dataStream: 'cat @cat\r\n', + customRanges: [{ + startIndex: 4, + endIndex: 7, + rangeId: 'mention-1', + rangeType: CustomRangeType.MENTION, + wholeEntity: true, + }], + }, + documentStyle: {}, + }); + const commandService = testBed.get(ICommandService); + registerCommands(commandService); + + await expect(commandService.executeCommand(DocsReplaceCommand.id, { + unitId: 'test-doc', + query: docsQuery('cat'), + replaceString: 'dog', + })).resolves.toEqual({ success: 1, failure: 1 }); + await expect(commandService.executeCommand(DocsReplaceCommand.id, { + unitId: 'test-doc', + query: docsQuery('cat'), + replaceString: 'dog', + range: { startOffset: 20, endOffset: 23, collapsed: false }, + })).resolves.toEqual({ success: 0, failure: 1 }); + testBed.univer.dispose(); + }); + + it('supports deletion', async () => { + const testBed = createCommandTestBed(styledDocument('cat dog\r\n')); + const commandService = testBed.get(ICommandService); + registerCommands(commandService); + + await expect(commandService.executeCommand(DocsReplaceCommand.id, { + unitId: 'test-doc', + query: docsQuery('cat'), + replaceString: '', + })).resolves.toEqual({ success: 1, failure: 0 }); + expect(testBed.doc.getBody()?.dataStream).toBe(' dog\r\n'); + testBed.univer.dispose(); + }); + + it('does not replace text in a disabled document', async () => { + const snapshot = styledDocument('cat\r\n'); + snapshot.disabled = true; + const testBed = createCommandTestBed(snapshot); + const commandService = testBed.get(ICommandService); + registerCommands(commandService); + + await expect(commandService.executeCommand(DocsReplaceCommand.id, { + unitId: 'test-doc', + query: docsQuery('cat'), + replaceString: 'dog', + })).resolves.toEqual({ success: 0, failure: 1 }); + expect(testBed.doc.getBody()?.dataStream).toBe('cat\r\n'); + testBed.univer.dispose(); + }); +}); diff --git a/packages/docs-find-replace/src/commands/commands/__tests__/docs-ui-test-bed.d.ts b/packages/docs-find-replace/src/commands/commands/__tests__/docs-ui-test-bed.d.ts new file mode 100644 index 0000000000..c7b103125b --- /dev/null +++ b/packages/docs-find-replace/src/commands/commands/__tests__/docs-ui-test-bed.d.ts @@ -0,0 +1,10 @@ +declare module '@univerjs/docs-ui/commands/commands/__tests__/create-command-test-bed' { + import type { Dependency, DocumentDataModel, IDocumentData, Injector, Univer } from '@univerjs/core'; + + export function createCommandTestBed(docData?: IDocumentData, dependencies?: Dependency[]): { + univer: Univer; + get: Injector['get']; + doc: DocumentDataModel; + injector: Injector; + }; +} diff --git a/packages/docs-find-replace/src/commands/commands/docs-replace.command.ts b/packages/docs-find-replace/src/commands/commands/docs-replace.command.ts new file mode 100644 index 0000000000..3e29d3aeec --- /dev/null +++ b/packages/docs-find-replace/src/commands/commands/docs-replace.command.ts @@ -0,0 +1,92 @@ +/** + * Copyright 2023-present DreamNum Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { DocumentDataModel, ICommand, IDocumentBody, ITextRange } from '@univerjs/core'; +import type { IFindQuery, IReplaceAllResult } from '@univerjs/find-replace'; +import { + BuildTextUtils, + CommandType, + getRichTextEditPath, + ICommandService, + IUniverInstanceService, + JSONX, + MemoryCursor, + TextX, + UniverInstanceType, +} from '@univerjs/core'; +import { RichTextEditingMutation } from '@univerjs/docs'; +import { findDocRanges } from '../../controllers/utils'; + +export interface IDocsReplaceCommandParams { + unitId: string; + query: IFindQuery; + replaceString: string; + range?: ITextRange; +} + +export const DocsReplaceCommand: ICommand = { + id: 'docs.command.replace', + type: CommandType.COMMAND, + handler: (accessor, params) => { + if (!params) return { success: 0, failure: 0 }; + const instanceService = accessor.get(IUniverInstanceService); + const commandService = accessor.get(ICommandService); + const doc = instanceService.getUnit(params.unitId, UniverInstanceType.UNIVER_DOC); + const body = doc?.getBody(); + if (!doc || !body) return { success: 0, failure: params.range ? 1 : 0 }; + + const matches = findDocRanges(body, params.query, !!doc.getSnapshot().disabled); + const candidates = params.range + ? matches.filter((match) => match.startOffset === params.range!.startOffset && match.endOffset === params.range!.endOffset) + : matches; + const replaceable = candidates.filter((match) => match.replaceable); + const failure = (params.range && candidates.length === 0 ? 1 : candidates.length) - replaceable.length; + if (!replaceable.length) return { success: 0, failure }; + + const textX = new TextX(); + const memoryCursor = new MemoryCursor(); + for (const match of replaceable) { + const slice = doc.sliceBody(match.startOffset, match.endOffset); + const insertBody: IDocumentBody | null = params.replaceString + ? { + dataStream: params.replaceString, + textRuns: slice?.textRuns?.length + ? [{ ...slice.textRuns[0], st: 0, ed: params.replaceString.length }] + : undefined, + customRanges: slice?.customRanges?.length + ? [{ ...slice.customRanges[0], startIndex: 0, endIndex: params.replaceString.length - 1 }] + : undefined, + } + : null; + textX.push(...BuildTextUtils.selection.delete([{ + startOffset: match.startOffset, + endOffset: match.endOffset, + collapsed: false, + }], body, memoryCursor.cursor, insertBody, false)); + memoryCursor.moveCursorTo(match.endOffset); + } + + const actions = JSONX.getInstance().editOp(textX.serialize(), getRichTextEditPath(doc)); + const result = commandService.syncExecuteCommand(RichTextEditingMutation.id, { + unitId: params.unitId, + actions, + textRanges: [], + }); + return result + ? { success: replaceable.length, failure } + : { success: 0, failure: failure + replaceable.length }; + }, +}; diff --git a/packages/docs-find-replace/src/controllers/__tests__/docs-find-replace.controller.spec.ts b/packages/docs-find-replace/src/controllers/__tests__/docs-find-replace.controller.spec.ts new file mode 100644 index 0000000000..9fd51196dd --- /dev/null +++ b/packages/docs-find-replace/src/controllers/__tests__/docs-find-replace.controller.spec.ts @@ -0,0 +1,51 @@ +/** + * Copyright 2023-present DreamNum Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IFindReplaceService } from '@univerjs/find-replace'; +import type { IMenuManagerService } from '@univerjs/ui'; +import { ICommandService, toDisposable } from '@univerjs/core'; +import { createCommandTestBed } from '@univerjs/docs-ui/commands/commands/__tests__/create-command-test-bed'; +import { IFindReplaceService as FindReplaceServiceToken } from '@univerjs/find-replace'; +import { IMenuManagerService as MenuManagerServiceToken } from '@univerjs/ui'; +import { describe, expect, it, vi } from 'vitest'; +import { DocsReplaceCommand } from '../../commands/commands/docs-replace.command'; +import { menuSchema } from '../../menu/schema'; +import { DocsFindReplaceProvider } from '../../services/docs-find-replace.provider'; +import { DocsFindReplaceController } from '../docs-find-replace.controller'; + +describe('DocsFindReplaceController', () => { + it('registers and disposes the provider, command, and menu', () => { + const providerDisposed = vi.fn(); + const registerProvider = vi.fn(() => toDisposable(providerDisposed)); + const mergeMenu = vi.fn(); + const testBed = createCommandTestBed(undefined, [ + [FindReplaceServiceToken, { useValue: { registerFindReplaceProvider: registerProvider } as unknown as IFindReplaceService }], + [MenuManagerServiceToken, { useValue: { mergeMenu } as unknown as IMenuManagerService }], + ]); + testBed.injector.add([DocsFindReplaceProvider]); + const commandService = testBed.get(ICommandService); + const registerCommand = vi.spyOn(commandService, 'registerCommand'); + + const controller = testBed.injector.createInstance(DocsFindReplaceController); + expect(registerProvider).toHaveBeenCalledWith(testBed.injector.get(DocsFindReplaceProvider)); + expect(registerCommand).toHaveBeenCalledWith(DocsReplaceCommand); + expect(mergeMenu).toHaveBeenCalledWith(menuSchema); + + controller.dispose(); + expect(providerDisposed).toHaveBeenCalledOnce(); + testBed.univer.dispose(); + }); +}); diff --git a/packages/docs-find-replace/src/controllers/__tests__/utils.spec.ts b/packages/docs-find-replace/src/controllers/__tests__/utils.spec.ts new file mode 100644 index 0000000000..e9ffc5f4f4 --- /dev/null +++ b/packages/docs-find-replace/src/controllers/__tests__/utils.spec.ts @@ -0,0 +1,77 @@ +/** + * Copyright 2023-present DreamNum Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IDocumentBody } from '@univerjs/core'; +import type { IFindQuery } from '@univerjs/find-replace'; +import { CustomRangeType, DataStreamTreeTokenType } from '@univerjs/core'; +import { FindBy, FindDirection, FindScope } from '@univerjs/find-replace'; +import { describe, expect, it } from 'vitest'; +import { findDocRanges } from '../utils'; + +function query(findString: string, overrides: Partial = {}): IFindQuery { + return { + findString, + replaceRevealed: true, + caseSensitive: false, + matchesTheWholeWord: false, + matchesTheWholeCell: false, + findDirection: FindDirection.ROW, + findScope: FindScope.SUBUNIT, + findBy: FindBy.VALUE, + ...overrides, + }; +} + +describe('findDocRanges', () => { + it('matches literal text and respects case', () => { + const body = { dataStream: 'Apple a.b apple\r\n' } as IDocumentBody; + expect(findDocRanges(body, query('a.b'), false)).toEqual([ + { startOffset: 6, endOffset: 9, replaceable: true }, + ]); + expect(findDocRanges(body, query('apple', { caseSensitive: true }), false)).toEqual([ + { startOffset: 10, endOffset: 15, replaceable: true }, + ]); + }); + + it('uses Docs word segmentation for English and Chinese', () => { + expect(findDocRanges({ dataStream: 'cat scatter\r\n' }, query('cat', { matchesTheWholeWord: true }), false)).toHaveLength(1); + expect(findDocRanges({ dataStream: '中文测试\r\n' }, query('中文', { matchesTheWholeWord: true }), false)).toHaveLength(1); + }); + + it('finds table text and marks whole entities non-replaceable', () => { + const body: IDocumentBody = { + dataStream: `${DataStreamTreeTokenType.TABLE_START}${DataStreamTreeTokenType.TABLE_ROW_START}${DataStreamTreeTokenType.TABLE_CELL_START}target\r\n`, + customRanges: [{ + startIndex: 3, + endIndex: 8, + rangeId: 'mention', + rangeType: CustomRangeType.MENTION, + wholeEntity: true, + }], + }; + + expect(findDocRanges(body, query('target'), false)).toEqual([ + { startOffset: 3, endOffset: 9, replaceable: false }, + ]); + }); + + it('marks every match non-replaceable in a disabled document', () => { + expect(findDocRanges({ dataStream: 'cat cat\r\n' }, query('cat'), true)).toEqual([ + { startOffset: 0, endOffset: 3, replaceable: false }, + { startOffset: 4, endOffset: 7, replaceable: false }, + ]); + }); +}); diff --git a/packages/docs-find-replace/src/controllers/docs-find-replace.controller.ts b/packages/docs-find-replace/src/controllers/docs-find-replace.controller.ts new file mode 100644 index 0000000000..9eeddc215f --- /dev/null +++ b/packages/docs-find-replace/src/controllers/docs-find-replace.controller.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2023-present DreamNum Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Disposable, ICommandService, Inject } from '@univerjs/core'; +import { IFindReplaceService } from '@univerjs/find-replace'; +import { IMenuManagerService } from '@univerjs/ui'; +import { DocsReplaceCommand } from '../commands/commands/docs-replace.command'; +import { menuSchema } from '../menu/schema'; +import { DocsFindReplaceProvider } from '../services/docs-find-replace.provider'; + +export class DocsFindReplaceController extends Disposable { + constructor( + @Inject(DocsFindReplaceProvider) provider: DocsFindReplaceProvider, + @IFindReplaceService findReplaceService: IFindReplaceService, + @ICommandService commandService: ICommandService, + @IMenuManagerService menuManagerService: IMenuManagerService + ) { + super(); + + this.disposeWithMe(provider); + this.disposeWithMe(findReplaceService.registerFindReplaceProvider(provider)); + this.disposeWithMe(commandService.registerCommand(DocsReplaceCommand)); + menuManagerService.mergeMenu(menuSchema); + } +} diff --git a/packages/docs-find-replace/src/controllers/utils.ts b/packages/docs-find-replace/src/controllers/utils.ts new file mode 100644 index 0000000000..f5e04b8c7a --- /dev/null +++ b/packages/docs-find-replace/src/controllers/utils.ts @@ -0,0 +1,49 @@ +/** + * Copyright 2023-present DreamNum Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IDocumentBody } from '@univerjs/core'; +import type { IFindQuery } from '@univerjs/find-replace'; +import { regexp } from '@univerjs/core'; + +export interface IDocFindRange { + startOffset: number; + endOffset: number; + replaceable: boolean; +} + +export function findDocRanges(body: IDocumentBody, query: IFindQuery, disabled: boolean): IDocFindRange[] { + if (!query.findString) return []; + + const expression = regexp.createLiteralRegExp(query.findString, query.caseSensitive ? 'gu' : 'giu'); + const wordRanges = query.matchesTheWholeWord + ? new Set(Array.from(new Intl.Segmenter(undefined, { granularity: 'word' }).segment(body.dataStream)) + .filter((item) => item.isWordLike) + .map((item) => `${item.index}:${item.index + item.segment.length}`)) + : null; + return Array.from(body.dataStream.matchAll(expression)) + .filter((match) => { + return wordRanges?.has(`${match.index}:${match.index + match[0].length}`) ?? true; + }) + .map((match) => { + const startOffset = match.index; + const endOffset = startOffset + match[0].length; + const overlapsWholeEntity = body.customRanges?.some((range) => + range.wholeEntity && range.startIndex < endOffset && range.endIndex + 1 > startOffset + ) ?? false; + + return { startOffset, endOffset, replaceable: !disabled && !overlapsWholeEntity }; + }); +} diff --git a/packages/docs-find-replace/src/index.ts b/packages/docs-find-replace/src/index.ts new file mode 100644 index 0000000000..4149922347 --- /dev/null +++ b/packages/docs-find-replace/src/index.ts @@ -0,0 +1,48 @@ +/** + * Copyright 2023-present DreamNum Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DependentOn, Inject, Injector, Plugin, UniverInstanceType } from '@univerjs/core'; +import { UniverDocsPlugin } from '@univerjs/docs'; +import { UniverDocsUIPlugin } from '@univerjs/docs-ui'; +import { UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverFindReplacePlugin } from '@univerjs/find-replace'; +import pkg from '../package.json'; +import { DocsFindReplaceController } from './controllers/docs-find-replace.controller'; +import { DocsFindReplaceProvider } from './services/docs-find-replace.provider'; + +@DependentOn(UniverDocsPlugin, UniverDocsUIPlugin, UniverRenderEnginePlugin, UniverFindReplacePlugin) +export class UniverDocsFindReplacePlugin extends Plugin { + static override pluginName = 'DOCS_FIND_REPLACE_PLUGIN'; + static override packageName = pkg.name; + static override version = pkg.version; + static override type = UniverInstanceType.UNIVER_DOC; + + constructor( + _config: undefined, + @Inject(Injector) protected override readonly _injector: Injector + ) { + super(); + } + + override onStarting(): void { + this._injector.add([DocsFindReplaceProvider]); + this._injector.add([DocsFindReplaceController]); + } + + override onSteady(): void { + this._injector.get(DocsFindReplaceController); + } +} diff --git a/packages/docs-find-replace/src/menu/find-replace.menu.ts b/packages/docs-find-replace/src/menu/find-replace.menu.ts new file mode 100644 index 0000000000..4b34df56c2 --- /dev/null +++ b/packages/docs-find-replace/src/menu/find-replace.menu.ts @@ -0,0 +1,33 @@ +/** + * Copyright 2023-present DreamNum Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IAccessor } from '@univerjs/core'; +import type { IMenuButtonItem } from '@univerjs/ui'; +import { FOCUSING_DOC, IContextService } from '@univerjs/core'; +import { OpenFindDialogOperation } from '@univerjs/find-replace'; +import { MenuItemType } from '@univerjs/ui'; +import { map } from 'rxjs'; + +export function DocsFindReplaceMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const contextService = accessor.get(IContextService); + return { + id: OpenFindDialogOperation.id, + icon: 'SearchIcon', + tooltip: 'find-replace.toolbar', + type: MenuItemType.BUTTON, + disabled$: contextService.subscribeContextValue$(FOCUSING_DOC).pipe(map((focused) => !focused)), + }; +} diff --git a/packages/docs-find-replace/src/menu/schema.ts b/packages/docs-find-replace/src/menu/schema.ts new file mode 100644 index 0000000000..2a45549742 --- /dev/null +++ b/packages/docs-find-replace/src/menu/schema.ts @@ -0,0 +1,29 @@ +/** + * Copyright 2023-present DreamNum Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { MenuSchemaType } from '@univerjs/ui'; +import { OpenFindDialogOperation } from '@univerjs/find-replace'; +import { RibbonStartGroup } from '@univerjs/ui'; +import { DocsFindReplaceMenuItemFactory } from './find-replace.menu'; + +export const menuSchema: MenuSchemaType = { + [RibbonStartGroup.OTHERS]: { + [OpenFindDialogOperation.id]: { + order: 3, + menuItemFactory: DocsFindReplaceMenuItemFactory, + }, + }, +}; diff --git a/packages/docs-find-replace/src/models/__tests__/docs-find.model.spec.ts b/packages/docs-find-replace/src/models/__tests__/docs-find.model.spec.ts new file mode 100644 index 0000000000..61e63d1a84 --- /dev/null +++ b/packages/docs-find-replace/src/models/__tests__/docs-find.model.spec.ts @@ -0,0 +1,173 @@ +/** + * Copyright 2023-present DreamNum Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ICommand } from '@univerjs/core'; +import type { Documents, DocumentSkeleton, ITextRangeWithStyle } from '@univerjs/engine-render'; +import type { IFindQuery } from '@univerjs/find-replace'; +import { ICommandService } from '@univerjs/core'; +import { DocSelectionManagerService, DocSkeletonManagerService, RichTextEditingMutation } from '@univerjs/docs'; +import { DocBackScrollRenderController, getTextRangeFromCharIndex } from '@univerjs/docs-ui'; +import { createCommandTestBed } from '@univerjs/docs-ui/commands/commands/__tests__/create-command-test-bed'; +import { IRenderManagerService } from '@univerjs/engine-render'; +import { FindBy, FindDirection, FindScope } from '@univerjs/find-replace'; +import { firstValueFrom } from 'rxjs'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { DocsReplaceCommand } from '../../commands/commands/docs-replace.command'; +import { DocsFindModel } from '../docs-find.model'; + +const getTextRangeFromCharIndexMock = vi.hoisted(() => vi.fn()); +vi.mock('@univerjs/docs-ui', async (importOriginal) => { + const actual = await importOriginal(); + return { ...actual, getTextRangeFromCharIndex: getTextRangeFromCharIndexMock }; +}); + +function docsQuery(findString: string): IFindQuery { + return { + findString, + replaceRevealed: true, + caseSensitive: false, + matchesTheWholeWord: false, + matchesTheWholeCell: false, + findDirection: FindDirection.ROW, + findScope: FindScope.SUBUNIT, + findBy: FindBy.VALUE, + }; +} + +function activeRange(startOffset: number, endOffset: number): ITextRangeWithStyle { + return { startOffset, endOffset, collapsed: startOffset === endOffset, isActive: true, segmentId: '' }; +} + +function createModelTestBed(dataStream = 'cat xx cat\r\n') { + const scrollToRange = vi.fn(); + const testBed = createCommandTestBed( + { id: 'test-doc', body: { dataStream }, documentStyle: {} }, + [[DocBackScrollRenderController, { useValue: { scrollToRange } }]] + ); + const commandService = testBed.get(ICommandService); + commandService.registerCommand(RichTextEditingMutation as unknown as ICommand); + commandService.registerCommand(DocsReplaceCommand); + const selectionManager = testBed.get(DocSelectionManagerService); + selectionManager.__TEST_ONLY_setCurrentSelection({ unitId: 'test-doc', subUnitId: 'test-doc' }); + const skeletonManager = testBed.get(DocSkeletonManagerService); + vi.spyOn(skeletonManager, 'getSkeleton').mockReturnValue(null as never); + const model = testBed.injector.createInstance(DocsFindModel, testBed.doc, skeletonManager); + return { ...testBed, commandService, selectionManager, skeletonManager, scrollToRange, model }; +} + +describe('DocsFindModel', () => { + beforeEach(() => { + getTextRangeFromCharIndexMock.mockReset(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('starts from the cursor and honors movement flags', () => { + const { model, selectionManager, univer } = createModelTestBed(); + selectionManager.__TEST_ONLY_add([activeRange(5, 5)]); + const replaceDocRanges = vi.spyOn(selectionManager, 'replaceDocRanges'); + model.start(docsQuery('cat')); + + expect(model.moveToNextMatch({ stayIfOnMatch: true, noFocus: true })?.range.startOffset).toBe(7); + expect(model.moveToNextMatch({ loop: true, noFocus: true })?.range.startOffset).toBe(0); + expect(model.moveToPreviousMatch({ loop: true, noFocus: true })?.range.startOffset).toBe(7); + expect(replaceDocRanges).not.toHaveBeenCalled(); + + model.dispose(); + univer.dispose(); + }); + + it('ignores the cursor when requested and stays on an exact selected match', () => { + const { model, selectionManager, univer } = createModelTestBed(); + selectionManager.__TEST_ONLY_add([activeRange(5, 5)]); + model.start(docsQuery('cat')); + expect(model.moveToNextMatch({ ignoreSelection: true, noFocus: true })?.range.startOffset).toBe(0); + + model.dispose(); + const second = createModelTestBed(); + second.selectionManager.__TEST_ONLY_add([activeRange(0, 3)]); + second.model.start(docsQuery('cat')); + expect(second.model.moveToNextMatch({ stayIfOnMatch: true, noFocus: true })?.range.startOffset).toBe(0); + second.model.dispose(); + second.univer.dispose(); + univer.dispose(); + }); + + it('selects and scrolls to the active match without focusing the document editor', () => { + const { model, selectionManager, scrollToRange, univer } = createModelTestBed(); + const replaceDocRanges = vi.spyOn(selectionManager, 'replaceDocRanges'); + model.start(docsQuery('cat')); + model.moveToNextMatch({ ignoreSelection: true, noFocus: true }); + model.focusSelection(); + + expect(replaceDocRanges).toHaveBeenCalledWith([ + expect.objectContaining({ startOffset: 0, endOffset: 3 }), + ], { unitId: 'test-doc', subUnitId: 'test-doc' }, true, { shouldFocus: false }); + expect(scrollToRange).toHaveBeenCalledWith(expect.objectContaining({ startOffset: 0, endOffset: 3 })); + model.dispose(); + univer.dispose(); + }); + + it('passes a range only for current replacement', async () => { + const { model, commandService, univer } = createModelTestBed(); + const executeCommand = vi.spyOn(commandService, 'executeCommand'); + model.start(docsQuery('cat')); + model.moveToNextMatch({ ignoreSelection: true, noFocus: true }); + + await expect(model.replace('dog')).resolves.toBe(true); + expect(executeCommand).toHaveBeenCalledWith(DocsReplaceCommand.id, expect.objectContaining({ + range: expect.objectContaining({ startOffset: 0, endOffset: 3 }), + })); + await model.replaceAll('dog'); + expect(executeCommand).toHaveBeenLastCalledWith(DocsReplaceCommand.id, expect.not.objectContaining({ range: expect.anything() })); + model.dispose(); + univer.dispose(); + }); + + it('rescans after a rich-text mutation', async () => { + vi.useFakeTimers(); + const { model, univer } = createModelTestBed(); + model.start(docsQuery('cat')); + model.moveToNextMatch({ ignoreSelection: true, noFocus: true }); + const update = firstValueFrom(model.matchesUpdate$); + + await expect(model.replace('dog')).resolves.toBe(true); + await vi.advanceTimersByTimeAsync(250); + expect(await update).toEqual(model.getMatches()); + expect(model.getMatches()).toHaveLength(1); + model.dispose(); + univer.dispose(); + }); + + it('disposes every TextRange highlight', () => { + const { model, skeletonManager, get, univer } = createModelTestBed(); + vi.mocked(skeletonManager.getSkeleton).mockReturnValue({} as DocumentSkeleton); + const render = get(IRenderManagerService).getRenderUnitById('test-doc')!; + (render as unknown as { mainComponent: Documents }).mainComponent = {} as Documents; + const first = { dispose: vi.fn() }; + const second = { dispose: vi.fn() }; + getTextRangeFromCharIndexMock.mockReturnValueOnce(first).mockReturnValueOnce(second); + + model.start(docsQuery('cat')); + expect(getTextRangeFromCharIndex).toHaveBeenCalledTimes(2); + model.dispose(); + expect(first.dispose).toHaveBeenCalledOnce(); + expect(second.dispose).toHaveBeenCalledOnce(); + univer.dispose(); + }); +}); diff --git a/packages/docs-find-replace/src/models/docs-find.model.ts b/packages/docs-find-replace/src/models/docs-find.model.ts new file mode 100644 index 0000000000..5175047f39 --- /dev/null +++ b/packages/docs-find-replace/src/models/docs-find.model.ts @@ -0,0 +1,274 @@ +/** + * Copyright 2023-present DreamNum Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { DocumentDataModel, ITextRange, Nullable } from '@univerjs/core'; +import type { DocSkeletonManagerService } from '@univerjs/docs'; +import type { Documents, ITextSelectionStyle } from '@univerjs/engine-render'; +import type { IFindMatch, IFindMoveParams, IFindQuery, IReplaceAllResult } from '@univerjs/find-replace'; +import type { IDocsReplaceCommandParams } from '../commands/commands/docs-replace.command'; +import { + ColorKit, + fromCallback, + ICommandService, + Inject, + ThemeService, + toDisposable, +} from '@univerjs/core'; +import { DocSelectionManagerService, RichTextEditingMutation } from '@univerjs/docs'; +import { DocBackScrollRenderController, getTextRangeFromCharIndex } from '@univerjs/docs-ui'; +import { IRenderManagerService } from '@univerjs/engine-render'; +import { FindModel } from '@univerjs/find-replace'; +import { debounceTime, filter, Subject } from 'rxjs'; +import { DocsReplaceCommand } from '../commands/commands/docs-replace.command'; +import { findDocRanges } from '../controllers/utils'; + +export const DOCS_FIND_REPLACE_PROVIDER = 'docs-find-replace-provider'; + +export interface IDocFindMatch extends IFindMatch { + provider: typeof DOCS_FIND_REPLACE_PROVIDER; + range: ITextRange; + replaceable: boolean; +} + +export class DocsFindModel extends FindModel { + private readonly _matchesUpdate$ = new Subject(); + override readonly matchesUpdate$ = this._matchesUpdate$.asObservable(); + private readonly _activelyChangingMatch$ = new Subject(); + override readonly activelyChangingMatch$ = this._activelyChangingMatch$.asObservable(); + private _matches: IDocFindMatch[] = []; + private _position = -1; + private _query: Nullable = null; + private _highlights: Array<{ dispose(): void }> = []; + + override readonly unitId: string; + + constructor( + private readonly _doc: DocumentDataModel, + private readonly _skeletonManager: DocSkeletonManagerService, + @Inject(DocSelectionManagerService) private readonly _selectionManager: DocSelectionManagerService, + @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, + @ICommandService private readonly _commandService: ICommandService, + @Inject(ThemeService) private readonly _themeService: ThemeService + ) { + super(); + this.unitId = _doc.getUnitId(); + + this.disposeWithMe(toDisposable(fromCallback(this._commandService.onCommandExecuted.bind(this._commandService)) + .pipe( + filter(([command]) => command.id === RichTextEditingMutation.id && + (command.params as { unitId?: string })?.unitId === this.unitId), + debounceTime(220) + ) + .subscribe(() => this._scan(true)))); + this.disposeWithMe(toDisposable(this._skeletonManager.currentSkeleton$.subscribe(() => this._refreshHighlights()))); + } + + get currentMatch(): Nullable { + return this._position < 0 ? null : this._matches[this._position] ?? null; + } + + override dispose(): void { + super.dispose(); + this._disposeHighlights(); + this._matchesUpdate$.complete(); + this._activelyChangingMatch$.complete(); + } + + override getMatches(): IDocFindMatch[] { + return this._matches; + } + + start(query: IFindQuery): void { + this._query = query; + this._position = -1; + this._scan(false); + } + + override moveToNextMatch(params?: IFindMoveParams): IDocFindMatch | null { + if (!this._matches.length) return null; + if (params?.stayIfOnMatch && this.currentMatch) { + return this._activate(this._position, params.noFocus); + } + + let index: number; + if (params?.ignoreSelection) { + index = 0; + } else if (this._position >= 0) { + index = this._position + 1; + } else { + const selection = this._selectionManager.getActiveTextRange(); + const selectedMatch = params?.stayIfOnMatch ? this._selectionMatchIndex() : -1; + index = selectedMatch >= 0 + ? selectedMatch + : this._matches.findIndex((match) => match.range.startOffset >= (selection?.endOffset ?? 0)); + } + + if (index < 0 || index >= this._matches.length) index = params?.loop ? 0 : -1; + if (index < 0) { + this._position = -1; + this._refreshHighlights(); + return null; + } + return this._activate(index, params?.noFocus); + } + + override moveToPreviousMatch(params?: IFindMoveParams): IDocFindMatch | null { + if (!this._matches.length) return null; + if (params?.stayIfOnMatch && this.currentMatch) { + return this._activate(this._position, params.noFocus); + } + + let index: number; + if (params?.ignoreSelection) { + index = this._matches.length - 1; + } else if (this._position >= 0) { + index = this._position - 1; + } else { + const selection = this._selectionManager.getActiveTextRange(); + const selectedMatch = params?.stayIfOnMatch ? this._selectionMatchIndex() : -1; + index = selectedMatch >= 0 + ? selectedMatch + : this._matches.findLastIndex((match) => match.range.endOffset <= (selection?.startOffset ?? 0)); + } + + if (index < 0) index = params?.loop ? this._matches.length - 1 : -1; + if (index < 0) { + this._position = -1; + this._refreshHighlights(); + return null; + } + return this._activate(index, params?.noFocus); + } + + override async replace(replaceString: string): Promise { + const current = this.currentMatch; + if (!current?.replaceable || !this._query) return false; + const result = await this._commandService.executeCommand(DocsReplaceCommand.id, { + unitId: this.unitId, + query: this._query, + replaceString, + range: current.range, + }); + return result?.success === 1; + } + + override async replaceAll(replaceString: string): Promise { + if (!this._query) return { success: 0, failure: 0 }; + return await this._commandService.executeCommand(DocsReplaceCommand.id, { + unitId: this.unitId, + query: this._query, + replaceString, + }) ?? { success: 0, failure: this._matches.length }; + } + + override focusSelection(): void { + if (this.currentMatch) this._focusMatch(this.currentMatch); + } + + private _scan(emit: boolean): void { + if (!this._query) return; + const body = this._doc.getBody(); + if (!body) { + this._matches = []; + this._position = -1; + this._disposeHighlights(); + if (emit) this._matchesUpdate$.next([]); + return; + } + + const previousStart = this.currentMatch?.range.startOffset; + this._matches = findDocRanges(body, this._query, !!this._doc.getSnapshot().disabled) + .map((range) => ({ + provider: DOCS_FIND_REPLACE_PROVIDER, + unitId: this.unitId, + range: { ...range, collapsed: false }, + replaceable: range.replaceable, + })); + this._position = previousStart == null + ? -1 + : this._matches.findIndex((match) => match.range.startOffset >= previousStart); + this._refreshHighlights(); + if (emit) this._matchesUpdate$.next(this._matches); + } + + private _selectionMatchIndex(): number { + const selection = this._selectionManager.getActiveTextRange(); + if (!selection) return -1; + return this._matches.findIndex((match) => + match.range.startOffset <= selection.startOffset && match.range.endOffset >= selection.endOffset + ); + } + + private _activate(index: number, noFocus = false): IDocFindMatch | null { + const match = this._matches[index]; + if (!match) return null; + this._position = index; + if (!noFocus) this._focusMatch(match); + else this._refreshHighlights(); + return match; + } + + private _focusMatch(match: IDocFindMatch): void { + this._selectionManager.replaceDocRanges([match.range], { + unitId: this.unitId, + subUnitId: this.unitId, + }, true, { shouldFocus: false }); + this._renderManagerService.getRenderUnitById(this.unitId) + ?.with(DocBackScrollRenderController) + .scrollToRange(match.range); + this._activelyChangingMatch$.next(match); + this._refreshHighlights(); + } + + private _refreshHighlights(): void { + this._disposeHighlights(); + const render = this._renderManagerService.getRenderUnitById(this.unitId); + const skeleton = this._skeletonManager.getSkeleton(); + const document = render?.mainComponent as Nullable; + if (!render || !skeleton || !document) return; + + const color = this._themeService.getColorFromTheme('yellow.400'); + const passiveStyle: ITextSelectionStyle = { + strokeWidth: 0, + stroke: 'rgba(0,0,0,0)', + strokeActive: 'rgba(0,0,0,0)', + fill: new ColorKit(color).setAlpha(0.3).toRgbString(), + }; + const activeStyle: ITextSelectionStyle = { + ...passiveStyle, + fill: new ColorKit(color).setAlpha(0.65).toRgbString(), + }; + + this._matches.forEach((match, index) => { + const range = getTextRangeFromCharIndex( + match.range.startOffset, + match.range.endOffset, + render.scene, + document, + skeleton, + index === this._position ? activeStyle : passiveStyle, + '', + -1 + ); + if (range) this._highlights.push(range); + }); + } + + private _disposeHighlights(): void { + this._highlights.forEach((highlight) => highlight.dispose()); + this._highlights = []; + } +} diff --git a/packages/docs-find-replace/src/services/__tests__/docs-find-replace.provider.spec.ts b/packages/docs-find-replace/src/services/__tests__/docs-find-replace.provider.spec.ts new file mode 100644 index 0000000000..0704944ba4 --- /dev/null +++ b/packages/docs-find-replace/src/services/__tests__/docs-find-replace.provider.spec.ts @@ -0,0 +1,61 @@ +/** + * Copyright 2023-present DreamNum Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { UnitModel } from '@univerjs/core'; +import type { IFindQuery } from '@univerjs/find-replace'; +import { UniverInstanceType } from '@univerjs/core'; +import { DocSkeletonManagerService } from '@univerjs/docs'; +import { createCommandTestBed } from '@univerjs/docs-ui/commands/commands/__tests__/create-command-test-bed'; +import { IRenderManagerService } from '@univerjs/engine-render'; +import { FindBy, FindDirection, FindScope } from '@univerjs/find-replace'; +import { describe, expect, it, vi } from 'vitest'; +import { DocsFindReplaceProvider } from '../docs-find-replace.provider'; + +const query: IFindQuery = { + findString: 'cat', + replaceRevealed: true, + caseSensitive: false, + matchesTheWholeWord: false, + matchesTheWholeCell: false, + findDirection: FindDirection.ROW, + findScope: FindScope.SUBUNIT, + findBy: FindBy.VALUE, +}; + +describe('DocsFindReplaceProvider', () => { + it('supports Docs and creates one model for the current document', async () => { + const testBed = createCommandTestBed({ id: 'test-doc', body: { dataStream: 'cat\r\n' }, documentStyle: {} }); + const skeleton = testBed.get(IRenderManagerService).getRenderUnitById('test-doc')!.with(DocSkeletonManagerService); + vi.spyOn(skeleton, 'getSkeleton').mockReturnValue(null as never); + testBed.injector.add([DocsFindReplaceProvider]); + const provider = testBed.injector.get(DocsFindReplaceProvider); + + expect(provider.isSupported(testBed.doc)).toBe(true); + expect(provider.isSupported({ type: UniverInstanceType.UNIVER_SHEET } as UnitModel)).toBe(false); + expect(provider.capabilities).toEqual({ + caseSensitive: true, + matchesTheWholeWord: true, + matchesTheWholeCell: false, + findDirection: false, + findScope: false, + findBy: false, + }); + await expect(provider.find(query)).resolves.toHaveLength(1); + + provider.dispose(); + testBed.univer.dispose(); + }); +}); diff --git a/packages/docs-find-replace/src/services/docs-find-replace.provider.ts b/packages/docs-find-replace/src/services/docs-find-replace.provider.ts new file mode 100644 index 0000000000..47383c7625 --- /dev/null +++ b/packages/docs-find-replace/src/services/docs-find-replace.provider.ts @@ -0,0 +1,69 @@ +/** + * Copyright 2023-present DreamNum Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { DocumentDataModel, Nullable, UnitModel } from '@univerjs/core'; +import type { IFindQuery, IFindReplaceProvider } from '@univerjs/find-replace'; +import { Disposable, Inject, Injector, IUniverInstanceService, UniverInstanceType } from '@univerjs/core'; +import { DocSkeletonManagerService } from '@univerjs/docs'; +import { IRenderManagerService } from '@univerjs/engine-render'; +import { DocsFindModel } from '../models/docs-find.model'; + +export class DocsFindReplaceProvider extends Disposable implements IFindReplaceProvider { + readonly capabilities = { + caseSensitive: true, + matchesTheWholeWord: true, + matchesTheWholeCell: false, + findDirection: false, + findScope: false, + findBy: false, + }; + + private _model: Nullable = null; + + constructor( + @IUniverInstanceService private readonly _instanceService: IUniverInstanceService, + @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, + @Inject(Injector) private readonly _injector: Injector + ) { + super(); + } + + isSupported(unit: UnitModel): boolean { + return unit.type === UniverInstanceType.UNIVER_DOC; + } + + async find(query: IFindQuery): Promise { + this.terminate(); + const doc = this._instanceService.getCurrentUnitOfType(UniverInstanceType.UNIVER_DOC); + if (!doc) return []; + const skeleton = this._renderManagerService.getRenderUnitById(doc.getUnitId())?.with(DocSkeletonManagerService); + if (!skeleton) return []; + + this._model = this._injector.createInstance(DocsFindModel, doc, skeleton); + this._model.start(query); + return [this._model]; + } + + terminate(): void { + this._model?.dispose(); + this._model = null; + } + + override dispose(): void { + this.terminate(); + super.dispose(); + } +} diff --git a/packages/docs-find-replace/tsconfig.json b/packages/docs-find-replace/tsconfig.json new file mode 100644 index 0000000000..d4f9172636 --- /dev/null +++ b/packages/docs-find-replace/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@univerjs-infra/shared/tsconfigs/base", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib/types" + }, + "include": ["src"] +} diff --git a/packages/docs-find-replace/tsconfig.node.json b/packages/docs-find-replace/tsconfig.node.json new file mode 100644 index 0000000000..9b6ab66f1b --- /dev/null +++ b/packages/docs-find-replace/tsconfig.node.json @@ -0,0 +1,12 @@ +{ + "extends": "@univerjs-infra/shared/tsconfigs/node", + "compilerOptions": { + "rootDir": "src", + "declaration": true, + "emitDeclarationOnly": true, + "noEmit": false, + "outDir": "lib/types" + }, + "include": ["src"], + "exclude": ["src/**/__tests__/**", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/packages/docs-find-replace/vitest.config.ts b/packages/docs-find-replace/vitest.config.ts new file mode 100644 index 0000000000..af97c6d7a2 --- /dev/null +++ b/packages/docs-find-replace/vitest.config.ts @@ -0,0 +1,3 @@ +import createConfig from '@univerjs-infra/shared/vitest'; + +export default createConfig(); diff --git a/packages/docs-hyper-link-ui/src/plugin.ts b/packages/docs-hyper-link-ui/src/plugin.ts index c81aabc43c..8d771e0f0d 100644 --- a/packages/docs-hyper-link-ui/src/plugin.ts +++ b/packages/docs-hyper-link-ui/src/plugin.ts @@ -17,8 +17,11 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverDocsHyperLinkUIConfig } from './config/config'; import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; +import { UniverDocsPlugin } from '@univerjs/docs'; import { UniverDocsHyperLinkPlugin } from '@univerjs/docs-hyper-link'; -import { IRenderManagerService } from '@univerjs/engine-render'; +import { UniverDocsUIPlugin } from '@univerjs/docs-ui'; +import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { defaultPluginConfig, DOCS_HYPER_LINK_UI_PLUGIN_CONFIG_KEY } from './config/config'; import { ComponentsController } from './controllers/components.controller'; @@ -29,7 +32,13 @@ import { DocHyperLinkUIController } from './controllers/ui.controller'; import { DocHyperLinkPopupService } from './services/hyper-link-popup.service'; import { DOC_HYPER_LINK_UI_PLUGIN } from './types/const'; -@DependentOn(UniverDocsHyperLinkPlugin) +@DependentOn( + UniverDocsPlugin, + UniverRenderEnginePlugin, + UniverDocsHyperLinkPlugin, + UniverUIPlugin, + UniverDocsUIPlugin +) export class UniverDocsHyperLinkUIPlugin extends Plugin { static override pluginName = DOC_HYPER_LINK_UI_PLUGIN; static override packageName = pkg.name; diff --git a/packages/docs-mention-ui/src/plugin.ts b/packages/docs-mention-ui/src/plugin.ts index 745b8654cb..0f7f3a228e 100644 --- a/packages/docs-mention-ui/src/plugin.ts +++ b/packages/docs-mention-ui/src/plugin.ts @@ -16,7 +16,10 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverDocsMentionUIConfig } from './config/config'; -import { IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; +import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; +import { UniverDocsPlugin } from '@univerjs/docs'; +import { UniverDocsUIPlugin } from '@univerjs/docs-ui'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { defaultPluginConfig, DOCS_MENTION_UI_PLUGIN_CONFIG_KEY } from './config/config'; import { ComponentsController } from './controllers/components.controller'; @@ -26,6 +29,7 @@ import { DocMentionPopupService } from './services/doc-mention-popup.service'; import { DocMentionService } from './services/doc-mention.service'; import { DOC_MENTION_UI_PLUGIN } from './types/const/const'; +@DependentOn(UniverDocsPlugin, UniverUIPlugin, UniverDocsUIPlugin) export class UniverDocsMentionUIPlugin extends Plugin { static override pluginName = DOC_MENTION_UI_PLUGIN; static override packageName = pkg.name; diff --git a/packages/docs-quick-insert-ui/src/plugin.ts b/packages/docs-quick-insert-ui/src/plugin.ts index 036d996c04..b626cd131d 100644 --- a/packages/docs-quick-insert-ui/src/plugin.ts +++ b/packages/docs-quick-insert-ui/src/plugin.ts @@ -17,11 +17,13 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverDocsQuickInsertUIConfig } from './config/config'; import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; +import { UniverDocsPlugin } from '@univerjs/docs'; import { UniverDocsDrawingPlugin } from '@univerjs/docs-drawing'; import { UniverDocsDrawingUIPlugin } from '@univerjs/docs-drawing-ui'; +import { UniverDocsUIPlugin } from '@univerjs/docs-ui'; import { UniverDrawingPlugin } from '@univerjs/drawing'; import { UniverDrawingUIPlugin } from '@univerjs/drawing-ui'; -import { IRenderManagerService } from '@univerjs/engine-render'; +import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render'; import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { defaultPluginConfig, DOCS_QUICK_INSERT_UI_PLUGIN_CONFIG_KEY } from './config/config'; @@ -31,7 +33,16 @@ import { DocQuickInsertUIController } from './controllers/ui.controller'; import { DocQuickInsertMenuController } from './menu/doc-quick-insert-menu.controller'; import { DocQuickInsertPopupService } from './services/doc-quick-insert-popup.service'; -@DependentOn(UniverDrawingUIPlugin, UniverDrawingPlugin, UniverDocsDrawingUIPlugin, UniverDocsDrawingPlugin, UniverUIPlugin) +@DependentOn( + UniverDocsPlugin, + UniverDrawingPlugin, + UniverRenderEnginePlugin, + UniverDocsDrawingPlugin, + UniverUIPlugin, + UniverDocsUIPlugin, + UniverDrawingUIPlugin, + UniverDocsDrawingUIPlugin +) export class UniverDocsQuickInsertUIPlugin extends Plugin { static override type = UniverInstanceType.UNIVER_DOC; static override pluginName = 'DOC_QUICK_INSERT_UI_PLUGIN'; diff --git a/packages/docs-thread-comment-ui/src/plugin.ts b/packages/docs-thread-comment-ui/src/plugin.ts index b53d01773a..609c072090 100644 --- a/packages/docs-thread-comment-ui/src/plugin.ts +++ b/packages/docs-thread-comment-ui/src/plugin.ts @@ -17,8 +17,12 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverDocsThreadCommentUIConfig } from './config/config'; import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; -import { IRenderManagerService } from '@univerjs/engine-render'; +import { UniverDocsPlugin } from '@univerjs/docs'; +import { UniverDocsUIPlugin } from '@univerjs/docs-ui'; +import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverThreadCommentPlugin } from '@univerjs/thread-comment'; import { UniverThreadCommentUIPlugin } from '@univerjs/thread-comment-ui'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { PLUGIN_NAME } from './common/const'; import { defaultPluginConfig, DOCS_THREAD_COMMENT_UI_PLUGIN_CONFIG_KEY } from './config/config'; @@ -28,7 +32,14 @@ import { DocThreadCommentRenderController } from './controllers/render-controlle import { DocThreadCommentUIController } from './controllers/ui.controller'; import { DocThreadCommentService } from './services/doc-thread-comment.service'; -@DependentOn(UniverThreadCommentUIPlugin) +@DependentOn( + UniverDocsPlugin, + UniverThreadCommentPlugin, + UniverRenderEnginePlugin, + UniverUIPlugin, + UniverDocsUIPlugin, + UniverThreadCommentUIPlugin +) export class UniverDocsThreadCommentUIPlugin extends Plugin { static override pluginName = PLUGIN_NAME; static override packageName = pkg.name; diff --git a/packages/docs-ui/src/index.ts b/packages/docs-ui/src/index.ts index 8bfb6eb4fb..231d394be6 100644 --- a/packages/docs-ui/src/index.ts +++ b/packages/docs-ui/src/index.ts @@ -216,8 +216,11 @@ export { convertBodyToHtml } from './services/clipboard/udm-to-html/convertor'; export { DocHtmlExportService } from './services/clipboard/udm-to-html/doc-html-export.service'; export type { DocHtmlExportTransformer } from './services/clipboard/udm-to-html/doc-html-export.service'; export { DocAutoFormatService } from './services/doc-auto-format.service'; -export { DOC_EMBED_INTERACTION_BOUNDARY_OWNER_ATTRIBUTE, IDocEmbedInteractionBoundaryService, IDocEmbedRuntimeFocusCoordinator } from './services/doc-embed-integration.service'; -export type { IDocEmbedInteractionBoundaryService as IDocEmbedInteractionBoundaryServiceType, IDocEmbedRuntimeFocusCoordinator as IDocEmbedRuntimeFocusCoordinatorType } from './services/doc-embed-integration.service'; +export { + DOC_EMBED_INTERACTION_BOUNDARY_OWNER_ATTRIBUTE, + IDocEmbedInteractionBoundaryService, + IDocEmbedRuntimeFocusCoordinator, +} from './services/doc-embed-integration.service'; export { DocEventManagerService, getListMarkerFallbackBound, @@ -245,7 +248,7 @@ export type { IEditorInputConfig } from './services/selection/doc-selection-rend export { DocSelectionRenderService } from './services/selection/doc-selection-render.service'; export type { IDocRange } from './services/selection/range-interface'; export { convertPositionsToRectRanges, RectRange } from './services/selection/rect-range'; -export { getCanvasOffsetByEngine } from './services/selection/selection-utils'; +export { getCanvasOffsetByEngine, getTextRangeFromCharIndex } from './services/selection/selection-utils'; export { getAnchorBounding, getLineBounding, TEXT_RANGE_LAYER_INDEX, TextRange } from './services/selection/text-range'; export { whenDocAndEditorFocused } from './shortcuts/utils'; export { DOC_VERTICAL_PADDING } from './types/const/padding'; diff --git a/packages/docs-ui/src/plugin.ts b/packages/docs-ui/src/plugin.ts index 51bd352c98..1f3469b1ad 100644 --- a/packages/docs-ui/src/plugin.ts +++ b/packages/docs-ui/src/plugin.ts @@ -233,7 +233,7 @@ import { NormalTextHeadingShortcut, } from './shortcuts/heading.shortcut'; -@DependentOn(UniverRenderEnginePlugin, UniverDocsPlugin) +@DependentOn(UniverDocsPlugin, UniverRenderEnginePlugin) export class UniverDocsUIPlugin extends Plugin { static override pluginName = 'DOC_UI_PLUGIN'; static override packageName = pkg.name; diff --git a/packages/drawing-ui/src/plugin.ts b/packages/drawing-ui/src/plugin.ts index 499fc6517c..e991fa7834 100644 --- a/packages/drawing-ui/src/plugin.ts +++ b/packages/drawing-ui/src/plugin.ts @@ -16,7 +16,10 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverDrawingUIConfig } from './config/config'; -import { IConfigService, Inject, Injector, merge, Plugin } from '@univerjs/core'; +import { DependentOn, IConfigService, Inject, Injector, merge, Plugin } from '@univerjs/core'; +import { UniverDrawingPlugin } from '@univerjs/drawing'; +import { UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { defaultPluginConfig, DRAWING_UI_PLUGIN_CONFIG_KEY } from './config/config'; import { ComponentsController } from './controllers/components.controller'; @@ -27,6 +30,7 @@ import { DrawingUIController } from './controllers/ui.controller'; import { DrawingImageClipService } from './services/drawing-image-clip.service'; import { DrawingRenderService } from './services/drawing-render.service'; +@DependentOn(UniverDrawingPlugin, UniverRenderEnginePlugin, UniverUIPlugin) export class UniverDrawingUIPlugin extends Plugin { static override pluginName = 'UNIVER_DRAWING_UI_PLUGIN'; static override packageName = pkg.name; diff --git a/packages/find-replace/src/commands/operations/__tests__/find-replace.operation.spec.ts b/packages/find-replace/src/commands/operations/__tests__/find-replace.operation.spec.ts index 4f3008bba2..347ca32464 100644 --- a/packages/find-replace/src/commands/operations/__tests__/find-replace.operation.spec.ts +++ b/packages/find-replace/src/commands/operations/__tests__/find-replace.operation.spec.ts @@ -30,6 +30,15 @@ import { function createProvider(): IFindReplaceProvider { return { + capabilities: { + caseSensitive: true, + matchesTheWholeWord: false, + matchesTheWholeCell: true, + findDirection: true, + findScope: true, + findBy: true, + }, + isSupported: () => true, find: vi.fn(async () => []), terminate: vi.fn(), }; diff --git a/packages/find-replace/src/controllers/__tests__/find-replace.shortcut.spec.ts b/packages/find-replace/src/controllers/__tests__/find-replace.shortcut.spec.ts index ee999a34c3..a420267765 100644 --- a/packages/find-replace/src/controllers/__tests__/find-replace.shortcut.spec.ts +++ b/packages/find-replace/src/controllers/__tests__/find-replace.shortcut.spec.ts @@ -15,10 +15,11 @@ */ import type { Injector, Univer } from '@univerjs/core'; -import { EDITOR_ACTIVATED, FOCUSING_SHEET, IContextService } from '@univerjs/core'; +import { EDITOR_ACTIVATED, IContextService } from '@univerjs/core'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { createTestBed } from '../../__tests__/create-test-bed'; import { + FIND_REPLACE_AVAILABLE, FIND_REPLACE_DIALOG_FOCUS, FIND_REPLACE_INPUT_FOCUS, FIND_REPLACE_REPLACE_REVEALED, @@ -48,9 +49,9 @@ describe('find-replace.shortcut', () => { univer.dispose(); }); - it('should require sheet focus and editor inactivity for opening find dialog', () => { + it('should require provider availability and editor inactivity for opening find dialog', () => { contextService.setContextValue(FIND_REPLACE_DIALOG_FOCUS, false); - contextService.setContextValue(FOCUSING_SHEET, true); + contextService.setContextValue(FIND_REPLACE_AVAILABLE, true); contextService.setContextValue(EDITOR_ACTIVATED, false); expect(OpenFindDialogShortcutItem.preconditions!(contextService)).toBe(true); @@ -63,7 +64,7 @@ describe('find-replace.shortcut', () => { it('should open replace dialog only when dialog is hidden or replace is not revealed', () => { contextService.setContextValue(FIND_REPLACE_DIALOG_FOCUS, false); contextService.setContextValue(FIND_REPLACE_REPLACE_REVEALED, false); - contextService.setContextValue(FOCUSING_SHEET, true); + contextService.setContextValue(FIND_REPLACE_AVAILABLE, true); contextService.setContextValue(EDITOR_ACTIVATED, false); expect(OpenReplaceDialogShortcutItem.preconditions!(contextService)).toBe(true); @@ -72,6 +73,17 @@ describe('find-replace.shortcut', () => { expect(OpenReplaceDialogShortcutItem.preconditions!(contextService)).toBe(false); }); + it('should disable open shortcuts when no provider is available', () => { + contextService.setContextValue(FIND_REPLACE_DIALOG_FOCUS, false); + contextService.setContextValue(FIND_REPLACE_REPLACE_REVEALED, false); + contextService.setContextValue(FIND_REPLACE_AVAILABLE, false); + contextService.setContextValue(EDITOR_ACTIVATED, false); + + expect(OpenFindDialogShortcutItem.preconditions!(contextService)).toBe(false); + expect(MacOpenFindDialogShortcutItem.preconditions!(contextService)).toBe(false); + expect(OpenReplaceDialogShortcutItem.preconditions!(contextService)).toBe(false); + }); + it('should require input and dialog focus for next/previous actions', () => { contextService.setContextValue(FIND_REPLACE_INPUT_FOCUS, true); contextService.setContextValue(FIND_REPLACE_DIALOG_FOCUS, true); diff --git a/packages/find-replace/src/controllers/find-replace.controller.ts b/packages/find-replace/src/controllers/find-replace.controller.ts index 5786b1571c..e57b345c44 100644 --- a/packages/find-replace/src/controllers/find-replace.controller.ts +++ b/packages/find-replace/src/controllers/find-replace.controller.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { IDisposable, Nullable, Workbook } from '@univerjs/core'; +import type { IDisposable, Nullable } from '@univerjs/core'; import type { LocaleKey } from '../locale/types'; import { ICommandService, @@ -23,7 +23,6 @@ import { LocaleService, RxDisposable, toDisposable, - UniverInstanceType, } from '@univerjs/core'; import { IDialogService, ILayoutService, IMenuManagerService, IShortcutService } from '@univerjs/ui'; import { takeUntil } from 'rxjs'; @@ -114,6 +113,7 @@ export class FindReplaceController extends RxDisposable { } private _openPanel(): void { + const sessionUnitId = this._univerInstanceService.getFocusedUnit()?.getUnitId(); this._dialogService.open({ id: FIND_REPLACE_DIALOG_ID, draggable: true, @@ -130,7 +130,7 @@ export class FindReplaceController extends RxDisposable { this._closingListenerDisposable = toDisposable( this._univerInstanceService.focused$.pipe(takeUntil(this.dispose$)).subscribe((focused) => { - if (!focused || !this._univerInstanceService.getUnit(focused, UniverInstanceType.UNIVER_SHEET)) { + if (!focused || focused !== sessionUnitId) { this.closePanel(); } }) diff --git a/packages/find-replace/src/controllers/find-replace.shortcut.ts b/packages/find-replace/src/controllers/find-replace.shortcut.ts index 89ba5ed253..3c01cabe34 100644 --- a/packages/find-replace/src/controllers/find-replace.shortcut.ts +++ b/packages/find-replace/src/controllers/find-replace.shortcut.ts @@ -16,7 +16,7 @@ import type { IContextService } from '@univerjs/core'; import type { IShortcutItem } from '@univerjs/ui'; -import { EDITOR_ACTIVATED, FOCUSING_SHEET } from '@univerjs/core'; +import { EDITOR_ACTIVATED } from '@univerjs/core'; import { KeyCode, MetaKeys } from '@univerjs/ui'; import { FocusSelectionOperation, @@ -26,6 +26,7 @@ import { OpenReplaceDialogOperation, } from '../commands/operations/find-replace.operation'; import { + FIND_REPLACE_AVAILABLE, FIND_REPLACE_DIALOG_FOCUS, FIND_REPLACE_INPUT_FOCUS, FIND_REPLACE_REPLACE_REVEALED, @@ -45,9 +46,8 @@ function whenFindReplaceInputFocused(contextService: IContextService): boolean { const FIND_REPLACE_SHORTCUT_GROUP = '7_find-replace-shortcuts'; -// Current we only support find replace on sheet. -function whenSheetFocused(contextService: IContextService) { - return contextService.getContextValue(FOCUSING_SHEET); +function whenFindReplaceAvailable(contextService: IContextService): boolean { + return contextService.getContextValue(FIND_REPLACE_AVAILABLE); } function whenEditorNotActivated(contextService: IContextService): boolean { @@ -61,7 +61,7 @@ export const OpenFindDialogShortcutItem: IShortcutItem = { group: FIND_REPLACE_SHORTCUT_GROUP, groupTitle: 'find-replace.shortcut.panel', preconditions(contextService) { - return !whenFindReplaceDialogFocused(contextService) && whenSheetFocused(contextService) && whenEditorNotActivated(contextService); + return !whenFindReplaceDialogFocused(contextService) && whenFindReplaceAvailable(contextService) && whenEditorNotActivated(contextService); }, }; @@ -71,7 +71,7 @@ export const MacOpenFindDialogShortcutItem: IShortcutItem = { binding: KeyCode.F | MetaKeys.CTRL_COMMAND, mac: KeyCode.F | MetaKeys.MAC_CTRL, preconditions(contextService) { - return !whenFindReplaceDialogFocused(contextService) && whenSheetFocused(contextService) && whenEditorNotActivated(contextService); + return !whenFindReplaceDialogFocused(contextService) && whenFindReplaceAvailable(contextService) && whenEditorNotActivated(contextService); }, }; @@ -83,7 +83,7 @@ export const OpenReplaceDialogShortcutItem: IShortcutItem = { group: FIND_REPLACE_SHORTCUT_GROUP, groupTitle: 'find-replace.shortcut.panel', preconditions(contextService) { - return whenSheetFocused(contextService) && whenEditorNotActivated(contextService) && (!whenFindReplaceDialogFocused(contextService) || !whenReplaceRevealed(contextService)); + return whenFindReplaceAvailable(contextService) && whenEditorNotActivated(contextService) && (!whenFindReplaceDialogFocused(contextService) || !whenReplaceRevealed(contextService)); }, }; diff --git a/packages/find-replace/src/locale/ar-SA.ts b/packages/find-replace/src/locale/ar-SA.ts index 99246bd0c9..fecf0d605d 100644 --- a/packages/find-replace/src/locale/ar-SA.ts +++ b/packages/find-replace/src/locale/ar-SA.ts @@ -34,10 +34,11 @@ const locale: typeof enUS = { replace: 'استبدال', 'replace-all': 'استبدال الكل', 'case-sensitive': 'حساس لحالة الأحرف', - 'find-placeholder': 'البحث في هذه الورقة', + 'find-placeholder': 'البحث', 'advanced-finding': 'بحث واستبدال متقدم', 'replace-placeholder': 'أدخل نص الاستبدال', 'match-the-whole-cell': 'تطابق الخلية بأكملها', + 'match-the-whole-word': 'مطابقة الكلمة بالكامل', 'find-direction': { title: 'اتجاه البحث', row: 'البحث حسب الصف', diff --git a/packages/find-replace/src/locale/ca-ES.ts b/packages/find-replace/src/locale/ca-ES.ts index 6cc0abe965..d93f707d32 100644 --- a/packages/find-replace/src/locale/ca-ES.ts +++ b/packages/find-replace/src/locale/ca-ES.ts @@ -34,10 +34,11 @@ const locale: typeof enUS = { replace: 'Reemplaça', 'replace-all': 'Reemplaça-ho tot', 'case-sensitive': 'Distingeix majúscules de minúscules', - 'find-placeholder': 'Cerca en aquest full', + 'find-placeholder': 'Cerca', 'advanced-finding': 'Cerca i reemplaçament avançats', 'replace-placeholder': 'Introduïu la cadena de reemplaçament', 'match-the-whole-cell': 'Coincidir amb la cel·la sencera', + 'match-the-whole-word': 'Coincideix amb la paraula sencera', 'find-direction': { title: 'Direcció de la cerca', row: 'Cerca per fila', diff --git a/packages/find-replace/src/locale/de-DE.ts b/packages/find-replace/src/locale/de-DE.ts index 35bf1e5faf..c5de3d292f 100644 --- a/packages/find-replace/src/locale/de-DE.ts +++ b/packages/find-replace/src/locale/de-DE.ts @@ -34,10 +34,11 @@ const locale: typeof enUS = { replace: 'Ersetzen', 'replace-all': 'Alle ersetzen', 'case-sensitive': 'Groß-/Kleinschreibung beachten', - 'find-placeholder': 'In diesem Blatt suchen', + 'find-placeholder': 'Suchen', 'advanced-finding': 'Erweitertes Suchen & Ersetzen', 'replace-placeholder': 'Ersetzungstext eingeben', 'match-the-whole-cell': 'Ganze Zelle vergleichen', + 'match-the-whole-word': 'Nur ganzes Wort', 'find-direction': { title: 'Suchrichtung', row: 'Nach Zeile suchen', diff --git a/packages/find-replace/src/locale/en-US.ts b/packages/find-replace/src/locale/en-US.ts index 7cbb68eff3..8d6d4ba4f2 100644 --- a/packages/find-replace/src/locale/en-US.ts +++ b/packages/find-replace/src/locale/en-US.ts @@ -32,10 +32,11 @@ const locale = { replace: 'Replace', 'replace-all': 'Replace All', 'case-sensitive': 'Case Sensitive', - 'find-placeholder': 'Find in this Sheet', + 'find-placeholder': 'Find', 'advanced-finding': 'Advanced Searching & Replace', 'replace-placeholder': 'Input Replace String', 'match-the-whole-cell': 'Match the Whole Cell', + 'match-the-whole-word': 'Match Whole Word', 'find-direction': { title: 'Find Direction', row: 'Search by Row', diff --git a/packages/find-replace/src/locale/es-ES.ts b/packages/find-replace/src/locale/es-ES.ts index 12c9dad6b4..4f5a210a5d 100644 --- a/packages/find-replace/src/locale/es-ES.ts +++ b/packages/find-replace/src/locale/es-ES.ts @@ -34,10 +34,11 @@ const locale: typeof enUS = { replace: 'Reemplazar', 'replace-all': 'Reemplazar todo', 'case-sensitive': 'Distinguir mayúsculas y minúsculas', - 'find-placeholder': 'Buscar en esta hoja', + 'find-placeholder': 'Buscar', 'advanced-finding': 'Búsqueda y reemplazo avanzados', 'replace-placeholder': 'Introducir cadena de reemplazo', 'match-the-whole-cell': 'Coincidir con toda la celda', + 'match-the-whole-word': 'Coincidir con la palabra completa', 'find-direction': { title: 'Dirección de búsqueda', row: 'Buscar por fila', diff --git a/packages/find-replace/src/locale/fa-IR.ts b/packages/find-replace/src/locale/fa-IR.ts index 17d5207029..dc48b46947 100644 --- a/packages/find-replace/src/locale/fa-IR.ts +++ b/packages/find-replace/src/locale/fa-IR.ts @@ -34,10 +34,11 @@ const locale: typeof enUS = { replace: 'جایگزینی', 'replace-all': 'جایگزینی همه', 'case-sensitive': 'حساس به حروف بزرگ و کوچک', - 'find-placeholder': 'جستجو در این شیت', + 'find-placeholder': 'جستجو', 'advanced-finding': 'جستجو و جایگزینی پیشرفته', 'replace-placeholder': 'رشته جایگزینی را وارد کنید', 'match-the-whole-cell': 'تطابق کل سلول', + 'match-the-whole-word': 'تطبیق کل کلمه', 'find-direction': { title: 'جهت جستجو', row: 'جستجو بر اساس ردیف', diff --git a/packages/find-replace/src/locale/fr-FR.ts b/packages/find-replace/src/locale/fr-FR.ts index c0c8742c86..0c4b2f65a6 100644 --- a/packages/find-replace/src/locale/fr-FR.ts +++ b/packages/find-replace/src/locale/fr-FR.ts @@ -34,10 +34,11 @@ const locale: typeof enUS = { replace: 'Remplacer', 'replace-all': 'Remplacer tout', 'case-sensitive': 'Sensible à la casse', - 'find-placeholder': 'Rechercher dans cette feuille', + 'find-placeholder': 'Rechercher', 'advanced-finding': 'Recherche et remplacement avancés', 'replace-placeholder': 'Saisir la chaîne de remplacement', 'match-the-whole-cell': 'Correspondance de la cellule entière', + 'match-the-whole-word': 'Mot entier', 'find-direction': { title: 'Direction de la recherche', row: 'Rechercher par ligne', diff --git a/packages/find-replace/src/locale/id-ID.ts b/packages/find-replace/src/locale/id-ID.ts index 7bfc012027..320acea1e6 100644 --- a/packages/find-replace/src/locale/id-ID.ts +++ b/packages/find-replace/src/locale/id-ID.ts @@ -34,10 +34,11 @@ const locale: typeof enUS = { replace: 'Ganti', 'replace-all': 'Ganti Semua', 'case-sensitive': 'Sensitif Huruf', - 'find-placeholder': 'Cari di Lembar ini', + 'find-placeholder': 'Cari', 'advanced-finding': 'Pencarian & Penggantian Lanjutan', 'replace-placeholder': 'Masukkan String Pengganti', 'match-the-whole-cell': 'Cocokkan Seluruh Sel', + 'match-the-whole-word': 'Cocokkan seluruh kata', 'find-direction': { title: 'Arah Cari', row: 'Cari per Baris', diff --git a/packages/find-replace/src/locale/it-IT.ts b/packages/find-replace/src/locale/it-IT.ts index 7d0b61a6c2..b367bd883a 100644 --- a/packages/find-replace/src/locale/it-IT.ts +++ b/packages/find-replace/src/locale/it-IT.ts @@ -34,10 +34,11 @@ const locale: typeof enUS = { replace: 'Sostituisci', 'replace-all': 'Sostituisci tutto', 'case-sensitive': 'Maiuscole/minuscole', - 'find-placeholder': 'Trova in questo foglio', + 'find-placeholder': 'Trova', 'advanced-finding': 'Ricerca e sostituzione avanzate', 'replace-placeholder': 'Inserisci stringa di sostituzione', 'match-the-whole-cell': "Corrispondenza con l'intera cella", + 'match-the-whole-word': 'Trova parola intera', 'find-direction': { title: 'Direzione ricerca', row: 'Cerca per riga', diff --git a/packages/find-replace/src/locale/ja-JP.ts b/packages/find-replace/src/locale/ja-JP.ts index 4c26956126..0313727d77 100644 --- a/packages/find-replace/src/locale/ja-JP.ts +++ b/packages/find-replace/src/locale/ja-JP.ts @@ -34,10 +34,11 @@ const locale: typeof enUS = { replace: '置換', 'replace-all': 'すべて置換', 'case-sensitive': '大文字と小文字を区別', - 'find-placeholder': 'このシート内を検索', + 'find-placeholder': '検索', 'advanced-finding': '詳細検索と置換', 'replace-placeholder': '置換文字列を入力', 'match-the-whole-cell': 'セル全体に一致', + 'match-the-whole-word': '単語単位で検索', 'find-direction': { title: '検索方向', row: '行ごとに検索', diff --git a/packages/find-replace/src/locale/ko-KR.ts b/packages/find-replace/src/locale/ko-KR.ts index 5318cb6dcf..6758de2258 100644 --- a/packages/find-replace/src/locale/ko-KR.ts +++ b/packages/find-replace/src/locale/ko-KR.ts @@ -34,10 +34,11 @@ const locale: typeof enUS = { replace: '바꾸기', 'replace-all': '모두 바꾸기', 'case-sensitive': '대/소문자 구분', - 'find-placeholder': '이 시트에서 찾기', + 'find-placeholder': '찾기', 'advanced-finding': '고급 검색 및 바꾸기', 'replace-placeholder': '바꿀 문자열 입력', 'match-the-whole-cell': '전체 셀 내용 일치', + 'match-the-whole-word': '단어 단위로 일치', 'find-direction': { title: '검색 방향', row: '행 기준 검색', diff --git a/packages/find-replace/src/locale/pl-PL.ts b/packages/find-replace/src/locale/pl-PL.ts index 0571ef84db..5eb0c3cc1c 100644 --- a/packages/find-replace/src/locale/pl-PL.ts +++ b/packages/find-replace/src/locale/pl-PL.ts @@ -34,10 +34,11 @@ const locale: typeof enUS = { replace: 'Zamień', 'replace-all': 'Zamień wszystko', 'case-sensitive': 'Rozróżniaj wielkość liter', - 'find-placeholder': 'Znajdź w tym arkuszu', + 'find-placeholder': 'Znajdź', 'advanced-finding': 'Zaawansowane wyszukiwanie i zastępowanie', 'replace-placeholder': 'Wprowadź ciąg zastępujący', 'match-the-whole-cell': 'Dopasuj całą komórkę', + 'match-the-whole-word': 'Dopasuj całe słowo', 'find-direction': { title: 'Kierunek wyszukiwania', row: 'Szukaj według wierszy', diff --git a/packages/find-replace/src/locale/pt-BR.ts b/packages/find-replace/src/locale/pt-BR.ts index 24984db434..e1d95a22bf 100644 --- a/packages/find-replace/src/locale/pt-BR.ts +++ b/packages/find-replace/src/locale/pt-BR.ts @@ -34,10 +34,11 @@ const locale: typeof enUS = { replace: 'Substituir', 'replace-all': 'Substituir Tudo', 'case-sensitive': 'Diferenciar Maiúsculas/Minúsculas', - 'find-placeholder': 'Localizar nesta Planilha', + 'find-placeholder': 'Localizar', 'advanced-finding': 'Pesquisa e Substituição Avançadas', 'replace-placeholder': 'Digite o Texto de Substituição', 'match-the-whole-cell': 'Corresponder à Célula Inteira', + 'match-the-whole-word': 'Corresponder palavra inteira', 'find-direction': { title: 'Direção da Pesquisa', row: 'Pesquisar por Linha', diff --git a/packages/find-replace/src/locale/ru-RU.ts b/packages/find-replace/src/locale/ru-RU.ts index 674ef88ca0..5fb7bc7d17 100644 --- a/packages/find-replace/src/locale/ru-RU.ts +++ b/packages/find-replace/src/locale/ru-RU.ts @@ -34,10 +34,11 @@ const locale: typeof enUS = { replace: 'Заменить', 'replace-all': 'Заменить все', 'case-sensitive': 'Учитывать регистр', - 'find-placeholder': 'Найти на этом листе', + 'find-placeholder': 'Найти', 'advanced-finding': 'Расширенный поиск и замена', 'replace-placeholder': 'Введите строку для замены', 'match-the-whole-cell': 'Соответствие всей ячейке', + 'match-the-whole-word': 'Только слово целиком', 'find-direction': { title: 'Направление поиска', row: 'Искать по строкам', diff --git a/packages/find-replace/src/locale/sk-SK.ts b/packages/find-replace/src/locale/sk-SK.ts index 8a610559c8..a86392e285 100644 --- a/packages/find-replace/src/locale/sk-SK.ts +++ b/packages/find-replace/src/locale/sk-SK.ts @@ -34,10 +34,11 @@ const locale: typeof enUS = { replace: 'Nahradiť', 'replace-all': 'Nahradiť všetko', 'case-sensitive': 'Rozlišovať veľké a malé písmená', - 'find-placeholder': 'Hľadať v tomto hárku', + 'find-placeholder': 'Hľadať', 'advanced-finding': 'Pokročilé hľadanie a nahradenie', 'replace-placeholder': 'Zadajte reťazec na nahradenie', 'match-the-whole-cell': 'Zhoda s celou bunkou', + 'match-the-whole-word': 'Hľadať iba celé slová', 'find-direction': { title: 'Smer hľadania', row: 'Hľadať podľa riadkov', diff --git a/packages/find-replace/src/locale/vi-VN.ts b/packages/find-replace/src/locale/vi-VN.ts index 7ffbf865fe..95024c01a6 100644 --- a/packages/find-replace/src/locale/vi-VN.ts +++ b/packages/find-replace/src/locale/vi-VN.ts @@ -34,10 +34,11 @@ const locale: typeof enUS = { replace: 'Thay thế', 'replace-all': 'Thay thế Tất cả', 'case-sensitive': 'Phân biệt chữ hoa chữ thường', - 'find-placeholder': 'Tìm trong Trang tính này', + 'find-placeholder': 'Tìm', 'advanced-finding': 'Tìm kiếm & Thay thế Nâng cao', 'replace-placeholder': 'Nhập chuỗi thay thế', 'match-the-whole-cell': 'Khớp toàn bộ ô', + 'match-the-whole-word': 'Khớp toàn bộ từ', 'find-direction': { title: 'Hướng tìm kiếm', row: 'Tìm theo hàng', diff --git a/packages/find-replace/src/locale/zh-CN.ts b/packages/find-replace/src/locale/zh-CN.ts index 7e562bf6b6..85454bb636 100644 --- a/packages/find-replace/src/locale/zh-CN.ts +++ b/packages/find-replace/src/locale/zh-CN.ts @@ -38,6 +38,7 @@ const locale: typeof enUS = { 'replace-placeholder': '输入替换内容', 'case-sensitive': '匹配大小写', 'match-the-whole-cell': '匹配整个单元格', + 'match-the-whole-word': '全词匹配', 'find-scope': { title: '查找范围', 'current-sheet': '当前子表', diff --git a/packages/find-replace/src/locale/zh-HK.ts b/packages/find-replace/src/locale/zh-HK.ts index a315a1c0a3..2e07a02b41 100644 --- a/packages/find-replace/src/locale/zh-HK.ts +++ b/packages/find-replace/src/locale/zh-HK.ts @@ -34,10 +34,11 @@ const locale: typeof enUS = { replace: '取代', 'replace-all': '全部取代', 'case-sensitive': '區分大小寫', - 'find-placeholder': '在此工作表中尋找', + 'find-placeholder': '尋找', 'advanced-finding': '進階搜尋與取代', 'replace-placeholder': '輸入取代字串', 'match-the-whole-cell': '完全符合儲存格內容', + 'match-the-whole-word': '全詞匹配', 'find-direction': { title: '尋找方向', row: '按列搜尋', diff --git a/packages/find-replace/src/locale/zh-TW.ts b/packages/find-replace/src/locale/zh-TW.ts index a315a1c0a3..2e07a02b41 100644 --- a/packages/find-replace/src/locale/zh-TW.ts +++ b/packages/find-replace/src/locale/zh-TW.ts @@ -34,10 +34,11 @@ const locale: typeof enUS = { replace: '取代', 'replace-all': '全部取代', 'case-sensitive': '區分大小寫', - 'find-placeholder': '在此工作表中尋找', + 'find-placeholder': '尋找', 'advanced-finding': '進階搜尋與取代', 'replace-placeholder': '輸入取代字串', 'match-the-whole-cell': '完全符合儲存格內容', + 'match-the-whole-word': '全詞匹配', 'find-direction': { title: '尋找方向', row: '按列搜尋', diff --git a/packages/find-replace/src/plugin.ts b/packages/find-replace/src/plugin.ts index 13a5ad1997..3087c40dd5 100644 --- a/packages/find-replace/src/plugin.ts +++ b/packages/find-replace/src/plugin.ts @@ -16,13 +16,15 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverFindReplaceConfig } from './config/config'; -import { IConfigService, Inject, Injector, merge, Plugin } from '@univerjs/core'; +import { DependentOn, IConfigService, Inject, Injector, merge, Plugin } from '@univerjs/core'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { defaultPluginConfig, FIND_REPLACE_PLUGIN_CONFIG_KEY } from './config/config'; import { ComponentsController } from './controllers/components.controller'; import { FindReplaceController } from './controllers/find-replace.controller'; import { FindReplaceService, IFindReplaceService } from './services/find-replace.service'; +@DependentOn(UniverUIPlugin) export class UniverFindReplacePlugin extends Plugin { static override pluginName = 'UNIVER_FIND_REPLACE_PLUGIN'; static override packageName = pkg.name; diff --git a/packages/find-replace/src/services/__tests__/find-replace.service.spec.ts b/packages/find-replace/src/services/__tests__/find-replace.service.spec.ts index 376a10a51a..7e4720ea79 100644 --- a/packages/find-replace/src/services/__tests__/find-replace.service.spec.ts +++ b/packages/find-replace/src/services/__tests__/find-replace.service.spec.ts @@ -14,18 +14,36 @@ * limitations under the License. */ -import type { IFindMatch, IFindMoveParams, IFindQuery, IReplaceAllResult } from '../find-replace.service'; -import { awaitTime, ICommandService, IContextService, Inject, Injector, IUniverInstanceService } from '@univerjs/core'; -import { Subject } from 'rxjs'; +import type { UnitModel } from '@univerjs/core'; +import type { + IFindMatch, + IFindMoveParams, + IFindQuery, + IFindReplaceProviderCapabilities, + IReplaceAllResult, +} from '../find-replace.service'; +import { + awaitTime, + ICommandService, + IContextService, + Inject, + Injector, + IUniverInstanceService, + UniverInstanceType, +} from '@univerjs/core'; +import { firstValueFrom, Subject } from 'rxjs'; import { describe, expect, it } from 'vitest'; import { FindBy, FindDirection, FindModel, FindReplaceService, FindScope } from '../find-replace.service'; class TestUniverInstanceService { - readonly focused$ = new Subject(); - focusedUnitId: string | null = 'u1'; + readonly focused$ = new Subject(); + focusedUnit = { + getUnitId: () => 'u1', + type: UniverInstanceType.UNIVER_DOC, + } as UnitModel; getFocusedUnit() { - return this.focusedUnitId ? { getUnitId: () => this.focusedUnitId } : null; + return this.focusedUnit; } } @@ -103,6 +121,16 @@ class TestFindModel extends FindModel { } class TestFindReplaceProvider { + capabilities: IFindReplaceProviderCapabilities = { + caseSensitive: true, + matchesTheWholeWord: true, + matchesTheWholeCell: false, + findDirection: false, + findScope: false, + findBy: false, + }; + + supportedType = UniverInstanceType.UNIVER_DOC; readonly queries: IFindQuery[] = []; readonly models: TestFindModel[] = []; modelConfigs: ITestModelConfig[] = []; @@ -110,6 +138,10 @@ class TestFindReplaceProvider { constructor(@Inject(Injector) private readonly _injector: Injector) {} + isSupported(unit: UnitModel): boolean { + return unit.type === this.supportedType; + } + async find(query: IFindQuery): Promise { this.queries.push(query); this.models.splice(0, this.models.length, ...this.modelConfigs.map((config) => @@ -145,6 +177,34 @@ function createService(registerProvider = true) { } describe('FindReplaceService', () => { + it('selects the provider supporting the focused unit', async () => { + const { service, provider, univerInstanceService } = createService(); + univerInstanceService.focusedUnit = { + getUnitId: () => 'doc-1', + type: UniverInstanceType.UNIVER_DOC, + } as UnitModel; + univerInstanceService.focused$.next('doc-1'); + + expect(service.start()).toBe(true); + await expect(firstValueFrom(service.providerCapabilities$)).resolves.toEqual(provider.capabilities); + expect(provider.queries).toHaveLength(0); + + service.terminate(); + service.dispose(); + }); + + it('does not start when no provider supports the focused unit', () => { + const { service, univerInstanceService } = createService(); + univerInstanceService.focusedUnit = { + getUnitId: () => 'slide-1', + type: UniverInstanceType.UNIVER_SLIDE, + } as UnitModel; + univerInstanceService.focused$.next('slide-1'); + + expect(service.start()).toBe(false); + service.dispose(); + }); + it('should start session and move to next match', async () => { const { service, provider } = createService(); const match1: IFindMatch = { provider: 'p', unitId: 'u1', range: { i: 1 }, replaceable: true } as any; @@ -190,6 +250,7 @@ describe('FindReplaceService', () => { expect(provider.queries.at(-1)).toMatchObject({ findString: 'invoice', replaceRevealed: true, + matchesTheWholeWord: false, }); expect(service.getCurrentMatch()).toEqual(match); expect(focusSignals).toEqual([1]); diff --git a/packages/find-replace/src/services/context-keys.ts b/packages/find-replace/src/services/context-keys.ts index c74b846c72..7166970015 100644 --- a/packages/find-replace/src/services/context-keys.ts +++ b/packages/find-replace/src/services/context-keys.ts @@ -25,3 +25,6 @@ export const FIND_REPLACE_DIALOG_FOCUS = 'FIND_REPLACE_DIALOG_FOCUS'; * If the find replace feature is activated and the replace is revealed. */ export const FIND_REPLACE_REPLACE_REVEALED = 'FIND_REPLACE_REPLACE_REVEALED'; + +/** If the focused unit has a find-replace provider. */ +export const FIND_REPLACE_AVAILABLE = 'FIND_REPLACE_AVAILABLE'; diff --git a/packages/find-replace/src/services/find-replace.service.ts b/packages/find-replace/src/services/find-replace.service.ts index 36d92fee01..8e8eb3479d 100644 --- a/packages/find-replace/src/services/find-replace.service.ts +++ b/packages/find-replace/src/services/find-replace.service.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { IDisposable, Nullable } from '@univerjs/core'; +import type { IDisposable, Nullable, UnitModel } from '@univerjs/core'; import type { Observable } from 'rxjs'; import { createIdentifier, @@ -29,7 +29,7 @@ import { } from '@univerjs/core'; import { RENDER_RAW_FORMULA_KEY } from '@univerjs/engine-render'; import { BehaviorSubject, combineLatest, debounceTime, Subject, throttleTime } from 'rxjs'; -import { FIND_REPLACE_REPLACE_REVEALED } from './context-keys'; +import { FIND_REPLACE_AVAILABLE, FIND_REPLACE_REPLACE_REVEALED } from './context-keys'; export type FindProgressFn = () => void; @@ -97,7 +97,18 @@ export abstract class FindModel extends Disposable { /** * A provider should be implemented by a business to provide the find results. */ +export interface IFindReplaceProviderCapabilities { + caseSensitive: boolean; + matchesTheWholeWord: boolean; + matchesTheWholeCell: boolean; + findDirection: boolean; + findScope: boolean; + findBy: boolean; +} + export interface IFindReplaceProvider { + readonly capabilities: IFindReplaceProviderCapabilities; + isSupported(unit: UnitModel): boolean; find(query: IFindQuery): Promise; terminate(): void; } @@ -116,6 +127,7 @@ export interface IFindReplaceService { readonly replaceables$: Observable; readonly focusSignal$: Observable; + readonly providerCapabilities$: Observable>; readonly revealed: boolean; readonly replaceRevealed: boolean; @@ -156,6 +168,7 @@ export interface IFindReplaceService { changeReplaceString(value: string): void; changeCaseSensitive(sensitive: boolean): void; changeMatchesTheWholeCell(wholeCell: boolean): void; + changeMatchesTheWholeWord(wholeWord: boolean): void; changeFindScope(scope: FindScope): void; changeFindDirection(direction: FindDirection): void; changeFindBy(findBy: FindBy): void; @@ -187,6 +200,7 @@ export interface IFindQuery extends Pick< | 'findDirection' | 'findScope' | 'matchesTheWholeCell' + | 'matchesTheWholeWord' > { } /** @@ -198,6 +212,7 @@ function shouldStateUpdateTriggerResearch(statusUpdate: Partial(); readonly focusSignal$ = this._focusSignal$.asObservable(); + private readonly _providerCapabilities$ = new BehaviorSubject>(null); + readonly providerCapabilities$ = this._providerCapabilities$.asObservable(); + private _activeProvider: Nullable = null; + get stateUpdates$() { return this._state.stateUpdates$; } get state$() { return this._state.state$; } get revealed(): boolean { return this._state.revealed; } get replaceRevealed(): boolean { return this._state.replaceRevealed; } - constructor(@Inject(Injector) private readonly _injector: Injector, @IContextService private readonly _contextService: IContextService) { + constructor( + @Inject(Injector) private readonly _injector: Injector, + @IContextService private readonly _contextService: IContextService, + @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService + ) { super(); + + this.disposeWithMe(toDisposable(this._univerInstanceService.focused$.subscribe(() => this._syncActiveProvider()))); } override dispose(): void { @@ -728,6 +765,8 @@ export class FindReplaceService extends Disposable implements IFindReplaceServic this._replaceables$.complete(); this._focusSignal$.complete(); + + this._providerCapabilities$.complete(); } getProviders(): Set { @@ -766,6 +805,10 @@ export class FindReplaceService extends Disposable implements IFindReplaceServic this._state.changeState({ matchesTheWholeCell }); } + changeMatchesTheWholeWord(matchesTheWholeWord: boolean): void { + this._state.changeState({ matchesTheWholeWord }); + } + changeCaseSensitive(caseSensitive: boolean): void { this._state.changeState({ caseSensitive }); } @@ -839,11 +882,11 @@ export class FindReplaceService extends Disposable implements IFindReplaceServic } start(revealReplace = false): boolean { - if (this._providers.size === 0) { + if (!this._activeProvider) { return false; } - this._model = this._injector.createInstance(FindReplaceModel, this._state, this._providers); + this._model = this._injector.createInstance(FindReplaceModel, this._state, new Set([this._activeProvider])); this._modelDisposables = new DisposableCollection(); this._modelDisposables.add(toDisposable(this._model.currentMatch$.subscribe((match) => this._currentMatch$.next(match)))); this._modelDisposables.add(toDisposable(this._model.replaceables$.subscribe((replaceables) => this._replaceables$.next(replaceables)))); @@ -875,7 +918,24 @@ export class FindReplaceService extends Disposable implements IFindReplaceServic registerFindReplaceProvider(provider: IFindReplaceProvider): IDisposable { this._providers.add(provider); - return toDisposable(() => this._providers.delete(provider)); + this._syncActiveProvider(); + return toDisposable(() => { + this._providers.delete(provider); + this._syncActiveProvider(); + }); + } + + private _syncActiveProvider(): void { + const unit = this._univerInstanceService.getFocusedUnit(); + const provider = unit == null + ? null + : Array.from(this._providers).find((candidate) => candidate.isSupported(unit)) ?? null; + if (provider === this._activeProvider) return; + + if (this.revealed) this.terminate(); + this._activeProvider = provider; + this._providerCapabilities$.next(provider?.capabilities ?? null); + this._contextService.setContextValue(FIND_REPLACE_AVAILABLE, provider != null); } private _toggleRevealReplace(revealReplace: boolean): void { diff --git a/packages/find-replace/src/views/dialog/FindReplaceDialog.tsx b/packages/find-replace/src/views/dialog/FindReplaceDialog.tsx index e798abd91d..b7d3b1c7ce 100644 --- a/packages/find-replace/src/views/dialog/FindReplaceDialog.tsx +++ b/packages/find-replace/src/views/dialog/FindReplaceDialog.tsx @@ -14,8 +14,6 @@ * limitations under the License. */ -import type { IDisposable, Nullable } from '@univerjs/core'; -import type { ForwardedRef } from 'react'; import type { LocaleKey } from '../../locale/types'; import { ICommandService, IContextService, LocaleService } from '@univerjs/core'; import { Button, Checkbox, FormDualColumnLayout, FormLayout, Input, MessageType, Select } from '@univerjs/design'; @@ -48,9 +46,9 @@ const FIND_BY_OPTIONS: Array<{ label: LocaleKey; value: FindBy }> = [ { label: 'find-replace.dialog.find-by.formula', value: FindBy.FORMULA }, ]; -function useFindInputFocus(findReplaceService: IFindReplaceService, ref: ForwardedRef) { +function useFindInputFocus(findReplaceService: IFindReplaceService, ref: Parameters[0]) { const focus = useCallback(() => { - (document.querySelector('.univer-find-input input') as Nullable)?.focus(); + (document.querySelector('.univer-find-input input') as HTMLInputElement | null)?.focus(); }, []); const selectHasFocus = useCallback(() => { @@ -119,6 +117,7 @@ export const ReplaceDialog = forwardRef(function ReplaceDialogImpl(_props, ref) const currentMatch = useObservable(findReplaceService.currentMatch$, undefined, true); const replaceables = useObservable(findReplaceService.replaceables$, undefined, true); + const capabilities = useObservable(findReplaceService.providerCapabilities$, null, true); const state = useObservable(findReplaceService.state$, undefined, true); const { matchesCount, @@ -128,6 +127,7 @@ export const ReplaceDialog = forwardRef(function ReplaceDialogImpl(_props, ref) replaceString, caseSensitive, matchesTheWholeCell, + matchesTheWholeWord, findDirection, findScope, findBy, @@ -161,7 +161,7 @@ export const ReplaceDialog = forwardRef(function ReplaceDialogImpl(_props, ref) const onClickReplaceAllButton = useCallback(async () => { await commandService.executeCommand(ReplaceAllMatchesCommand.id); focus(); - }, [commandService]); + }, [commandService, focus]); const onChangeFindDirection = useCallback((findDirection: string) => { findReplaceService.changeFindDirection(findDirection as FindDirection); @@ -209,43 +209,66 @@ export const ReplaceDialog = forwardRef(function ReplaceDialogImpl(_props, ref) onChange={(value) => onReplaceStringChange(value)} /> - ('find-replace.dialog.find-direction.title')}> - - - ('find-replace.dialog.find-by.title')}> - + + )} + {(capabilities?.findScope || capabilities?.findBy) && ( + + <> + {capabilities.findScope && ( + ('find-replace.dialog.find-scope.title')}> + + + )} + + + )} + {(capabilities?.caseSensitive || capabilities?.matchesTheWholeCell || capabilities?.matchesTheWholeWord) && ( + + <> + {capabilities.caseSensitive && ( + + { + findReplaceService.changeCaseSensitive(checked as boolean); + if (findCompleted) findReplaceService.find(); + }} + > + {localeService.t('find-replace.dialog.case-sensitive')} + + + )} + {capabilities.matchesTheWholeCell && ( + + findReplaceService.changeMatchesTheWholeCell(checked as boolean)}> + {localeService.t('find-replace.dialog.match-the-whole-cell')} + + + )} + {capabilities.matchesTheWholeWord && ( + + { + findReplaceService.changeMatchesTheWholeWord(checked as boolean); + if (findCompleted) findReplaceService.find(); + }} + > + {localeService.t('find-replace.dialog.match-the-whole-word')} + + + )} + + + )}
@@ -266,12 +289,11 @@ export function FindReplaceDialog() { const dialogContainerRef = useRef(null); useEffect(() => { - let disposable: IDisposable | undefined; - if (dialogContainerRef.current) { - disposable = layoutService.registerContainerElement(dialogContainerRef.current); - } + const container = dialogContainerRef.current; + if (!container) return; - return () => disposable?.dispose(); + const disposable = layoutService.registerContainerElement(container); + return () => disposable.dispose(); }, [layoutService]); const focusRef = useRef(null); diff --git a/packages/find-replace/src/views/dialog/__tests__/FindReplaceDialog.spec.tsx b/packages/find-replace/src/views/dialog/__tests__/FindReplaceDialog.spec.tsx index d7cbf7bf97..fad87fcd8f 100644 --- a/packages/find-replace/src/views/dialog/__tests__/FindReplaceDialog.spec.tsx +++ b/packages/find-replace/src/views/dialog/__tests__/FindReplaceDialog.spec.tsx @@ -14,17 +14,7 @@ * limitations under the License. */ -import type { IDisposable, Injector, Nullable } from '@univerjs/core'; -import type { Root } from 'react-dom/client'; -import type { TestMessageService } from '../../../__tests__/create-test-bed'; -import type { - IFindMatch, - IFindMoveParams, - IFindQuery, - IFindReplaceState, - IReplaceAllResult, -} from '../../../services/find-replace.service'; -import { awaitTime, ICommandService, toDisposable } from '@univerjs/core'; +import { awaitTime, ICommandService, toDisposable, UniverInstanceType } from '@univerjs/core'; import { ILayoutService, IMessageService, RediContext } from '@univerjs/ui'; import { act } from 'react'; import { createRoot } from 'react-dom/client'; @@ -60,6 +50,18 @@ interface ICellRange { id: string; } +interface IObservedFindState { + matchesCount: number; + matchesPosition: number; +} + +type FindProvider = Parameters[0]; +type FindQuery = Parameters[0]; +type FindCapabilities = FindProvider['capabilities']; +type FindMoveParams = Parameters[0]; +type FindMatch = NonNullable> & { range: ICellRange }; +type ReplaceAllResult = Awaited>; + const INITIAL_ROWS: ICellText[] = [ { id: 'A1', text: 'apple' }, { id: 'A2', text: 'pineapple' }, @@ -73,9 +75,9 @@ const REPLACE_ALL_LABEL = 'find-replace.dialog.replace-all'; const ADVANCED_LABEL = 'find-replace.dialog.advanced-finding'; class TextFindModel extends FindModel { - private readonly _matchesUpdate$ = new BehaviorSubject[]>([]); - private readonly _activelyChangingMatch$ = new Subject>(); - private _matches: IFindMatch[] = []; + private readonly _matchesUpdate$ = new BehaviorSubject([]); + private readonly _activelyChangingMatch$ = new Subject(); + private _matches: FindMatch[] = []; private _position = -1; override readonly unitId = 'test'; @@ -84,7 +86,7 @@ class TextFindModel extends FindModel { constructor( private readonly _rows: ICellText[], - private readonly _query: IFindQuery + private readonly _query: FindQuery ) { super(); this._refreshMatches(); @@ -96,11 +98,11 @@ class TextFindModel extends FindModel { super.dispose(); } - override getMatches(): IFindMatch[] { + override getMatches(): FindMatch[] { return this._matches; } - override moveToNextMatch(params?: IFindMoveParams): IFindMatch | null { + override moveToNextMatch(params?: FindMoveParams): FindMatch | null { if (this._matches.length === 0) { return null; } @@ -121,7 +123,7 @@ class TextFindModel extends FindModel { return this._emitCurrent(); } - override moveToPreviousMatch(params?: IFindMoveParams): IFindMatch | null { + override moveToPreviousMatch(params?: FindMoveParams): FindMatch | null { if (this._matches.length === 0) { return null; } @@ -154,7 +156,7 @@ class TextFindModel extends FindModel { return true; } - override async replaceAll(replaceString: string): Promise { + override async replaceAll(replaceString: string): Promise { const ids = new Set(this._matches.map((match) => match.range.id)); this._rows.forEach((row) => { if (ids.has(row.id)) { @@ -173,7 +175,7 @@ class TextFindModel extends FindModel { } } - private _emitCurrent(): IFindMatch { + private _emitCurrent(): FindMatch { const match = this._matches[this._position]; this._activelyChangingMatch$.next(match); return match; @@ -200,10 +202,23 @@ class TextFindModel extends FindModel { } class TestFindReplaceProvider { - readonly rows = INITIAL_ROWS.map((row) => ({ ...row })); - readonly queries: IFindQuery[] = []; + capabilities: FindCapabilities = { + caseSensitive: true, + matchesTheWholeWord: false, + matchesTheWholeCell: true, + findDirection: true, + findScope: true, + findBy: true, + }; - async find(query: IFindQuery): Promise { + readonly rows = INITIAL_ROWS.map((row) => ({ ...row })); + readonly queries: FindQuery[] = []; + + isSupported(unit: { type: UniverInstanceType }): boolean { + return unit.type === UniverInstanceType.UNIVER_SHEET; + } + + async find(query: FindQuery): Promise { this.queries.push({ ...query }); return [new TextFindModel(this.rows, query)]; } @@ -224,19 +239,19 @@ class TestLayoutService { this._focused = true; } - registerFocusHandler(): IDisposable { + registerFocusHandler(): ReturnType { return toDisposable(() => undefined); } - registerRootContainerElement(): IDisposable { + registerRootContainerElement(): ReturnType { return toDisposable(() => undefined); } - registerContentElement(): IDisposable { + registerContentElement(): ReturnType { return toDisposable(() => undefined); } - registerContainerElement(): IDisposable { + registerContainerElement(): ReturnType { return toDisposable(() => undefined); } @@ -253,7 +268,7 @@ class TestLayoutService { } } -function createDialogTestBed() { +function createDialogTestBed(capabilities?: FindCapabilities) { const testBed = createTestBed(undefined, [ [IFindReplaceService, { useClass: FindReplaceService }], [ILayoutService, { useClass: TestLayoutService as never }], @@ -269,6 +284,7 @@ function createDialogTestBed() { ].forEach((command) => commandService.registerCommand(command)); const provider = testBed.get(TestFindReplaceProvider); + if (capabilities) provider.capabilities = capabilities; const findReplaceService = testBed.get(IFindReplaceService); findReplaceService.registerFindReplaceProvider(provider); @@ -280,7 +296,7 @@ function createDialogTestBed() { }; } -function renderDialog(injector: Injector) { +function renderDialog(injector: ReturnType['univer']['__getInjector']>) { const container = document.createElement('div'); document.body.appendChild(container); const root = createRoot(container); @@ -329,7 +345,7 @@ async function flushSearch() { } describe('FindReplaceDialog', () => { - let root: Root | undefined; + let root: ReturnType | undefined; let container: HTMLElement | undefined; afterEach(() => { @@ -393,6 +409,29 @@ describe('FindReplaceDialog', () => { expect(getInputs(container!)).toHaveLength(2); }); + it('shows only controls supported by the active provider', async () => { + const testBed = createDialogTestBed({ + caseSensitive: true, + matchesTheWholeWord: true, + matchesTheWholeCell: false, + findDirection: false, + findScope: false, + findBy: false, + }); + + await act(async () => { + testBed.commandService.syncExecuteCommand(OpenReplaceDialogOperation.id); + }); + const rendered = renderDialog(testBed.univer.__getInjector()); + root = rendered.root; + container = rendered.container; + + expect(container.textContent).toContain('find-replace.dialog.case-sensitive'); + expect(container.textContent).toContain('find-replace.dialog.match-the-whole-word'); + expect(container.textContent).not.toContain('find-replace.dialog.find-direction.title'); + expect(container.textContent).not.toContain('find-replace.dialog.match-the-whole-cell'); + }); + it('replaces the selected result and then replaces all remaining results', async () => { const testBed = createDialogTestBed(); await act(async () => { @@ -426,7 +465,7 @@ describe('FindReplaceDialog', () => { await flushSearch(); expect(testBed.provider.rows.map((row) => row.text)).toEqual(['orange', 'orange', 'orange', 'banana']); - expect((testBed.get(IMessageService) as TestMessageService).messages.at(-1)?.content).toBe('find-replace.replace.all-success'); + expect((testBed.get(IMessageService) as unknown as { messages: Array<{ content: string }> }).messages.at(-1)?.content).toBe('find-replace.replace.all-success'); }); it('moves to the next result when finding the same replace dialog input again', async () => { @@ -489,6 +528,66 @@ describe('FindReplaceDialog', () => { expect(readCurrentMatch(testBed.findReplaceService)?.range).toEqual({ id: 'A3' }); }); + it('reruns completed searches when case or whole-word matching changes', async () => { + const testBed = createDialogTestBed({ + caseSensitive: true, + matchesTheWholeWord: true, + matchesTheWholeCell: false, + findDirection: false, + findScope: false, + findBy: false, + }); + await act(async () => { + testBed.commandService.syncExecuteCommand(OpenReplaceDialogOperation.id); + }); + + const rendered = renderDialog(testBed.univer.__getInjector()); + root = rendered.root; + container = rendered.container; + const checkboxes = Array.from(container.querySelectorAll('input[type="checkbox"]')); + + await act(async () => { + setInputValue(getInputs(container!)[0], 'apple'); + checkboxes.forEach((checkbox) => checkbox.dispatchEvent(new MouseEvent('click', { bubbles: true }))); + }); + expect(testBed.provider.queries).toHaveLength(0); + + await act(async () => { + getButton(container!, FIND_LABEL).dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + await flushSearch(); + expect(testBed.provider.queries).toHaveLength(1); + + await act(async () => { + checkboxes[0].dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + await flushSearch(); + expect(testBed.provider.queries).toHaveLength(2); + expect(testBed.provider.queries.at(-1)).toMatchObject({ caseSensitive: false, matchesTheWholeWord: true }); + + await act(async () => { + checkboxes[1].dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + await flushSearch(); + expect(testBed.provider.queries).toHaveLength(3); + expect(testBed.provider.queries.at(-1)).toMatchObject({ caseSensitive: false, matchesTheWholeWord: false }); + + await act(async () => { + setInputValue(getInputs(container!)[0], 'kiwi'); + getButton(container!, FIND_LABEL).dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + await flushSearch(); + expect(testBed.provider.queries).toHaveLength(4); + expect(readState(testBed.findReplaceService)).toMatchObject({ matchesCount: 0, matchesPosition: 0 }); + + await act(async () => { + checkboxes[0].dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + await flushSearch(); + expect(testBed.provider.queries).toHaveLength(5); + expect(testBed.provider.queries.at(-1)).toMatchObject({ findString: 'kiwi', caseSensitive: true }); + }); + it('warns and keeps replace actions disabled when a replace dialog search has no results', async () => { const testBed = createDialogTestBed(); await act(async () => { @@ -513,15 +612,15 @@ describe('FindReplaceDialog', () => { }); expect(readState(testBed.findReplaceService)).toMatchObject({ matchesCount: 0, matchesPosition: 0 }); expect(readCurrentMatch(testBed.findReplaceService)).toBeNull(); - expect((testBed.get(IMessageService) as TestMessageService).messages.at(-1)?.content).toBe('find-replace.dialog.no-match'); + expect((testBed.get(IMessageService) as unknown as { messages: Array<{ content: string }> }).messages.at(-1)?.content).toBe('find-replace.dialog.no-match'); expect(getButton(container!, REPLACE_LABEL).disabled).toBe(true); expect(getButton(container!, REPLACE_ALL_LABEL).disabled).toBe(true); expect(testBed.provider.rows.map((row) => row.text)).toEqual(INITIAL_ROWS.map((row) => row.text)); }); }); -function readState(findReplaceService: IFindReplaceService): IFindReplaceState { - let value!: IFindReplaceState; +function readState(findReplaceService: IFindReplaceService): IObservedFindState { + let value!: IObservedFindState; const subscription = findReplaceService.state$.subscribe((state) => { value = state; }); @@ -529,10 +628,10 @@ function readState(findReplaceService: IFindReplaceService): IFindReplaceState { return value; } -function readCurrentMatch(findReplaceService: IFindReplaceService): Nullable { - let value: Nullable = null; +function readCurrentMatch(findReplaceService: IFindReplaceService): FindMatch | null { + let value: FindMatch | null = null; const subscription = findReplaceService.currentMatch$.subscribe((match) => { - value = match; + value = match == null ? null : match as FindMatch; }); subscription.unsubscribe(); return value; diff --git a/packages/sheets-conditional-formatting-ui/src/mobile-plugin.ts b/packages/sheets-conditional-formatting-ui/src/mobile-plugin.ts index 5b198efa2d..fda98b2a1b 100644 --- a/packages/sheets-conditional-formatting-ui/src/mobile-plugin.ts +++ b/packages/sheets-conditional-formatting-ui/src/mobile-plugin.ts @@ -25,7 +25,11 @@ import { Plugin, UniverInstanceType, } from '@univerjs/core'; +import { UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import { SHEET_CONDITIONAL_FORMATTING_PLUGIN, UniverSheetsConditionalFormattingPlugin } from '@univerjs/sheets-conditional-formatting'; +import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula'; +import { UniverSheetsMobileUIPlugin } from '@univerjs/sheets-ui'; import pkg from '../package.json'; import { AddAverageCfCommand } from './commands/commands/add-average-cf.command'; import { AddColorScaleConditionalRuleCommand } from './commands/commands/add-color-scale-cf.command'; @@ -45,7 +49,13 @@ import { ConditionalFormattingI18nController } from './controllers/cf.i18n.contr import { ConditionalFormattingPermissionController } from './controllers/cf.permission.controller'; import { SheetsCfRenderController } from './controllers/cf.render.controller'; -@DependentOn(UniverSheetsConditionalFormattingPlugin) +@DependentOn( + UniverRenderEnginePlugin, + UniverSheetsPlugin, + UniverSheetsFormulaPlugin, + UniverSheetsConditionalFormattingPlugin, + UniverSheetsMobileUIPlugin +) export class UniverSheetsConditionalFormattingMobileUIPlugin extends Plugin { static override pluginName = `${SHEET_CONDITIONAL_FORMATTING_PLUGIN}_MOBILE_UI_PLUGIN`; static override packageName = pkg.name; diff --git a/packages/sheets-conditional-formatting-ui/src/plugin.ts b/packages/sheets-conditional-formatting-ui/src/plugin.ts index fff4336a69..28936c2143 100644 --- a/packages/sheets-conditional-formatting-ui/src/plugin.ts +++ b/packages/sheets-conditional-formatting-ui/src/plugin.ts @@ -27,8 +27,12 @@ import { touchDependencies, UniverInstanceType, } from '@univerjs/core'; +import { UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import { SHEET_CONDITIONAL_FORMATTING_PLUGIN, UniverSheetsConditionalFormattingPlugin } from '@univerjs/sheets-conditional-formatting'; import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula'; +import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { AddAverageCfCommand } from './commands/commands/add-average-cf.command'; import { AddColorScaleConditionalRuleCommand } from './commands/commands/add-color-scale-cf.command'; @@ -56,7 +60,14 @@ import { ConditionalFormattingViewportController } from './controllers/cf.viewpo import { ComponentsController } from './controllers/components.controller'; import { ConditionalFormattingMenuController } from './menu/cf.menu.controller'; -@DependentOn(UniverSheetsConditionalFormattingPlugin, UniverSheetsFormulaPlugin) +@DependentOn( + UniverRenderEnginePlugin, + UniverSheetsPlugin, + UniverUIPlugin, + UniverSheetsFormulaPlugin, + UniverSheetsConditionalFormattingPlugin, + UniverSheetsUIPlugin +) export class UniverSheetsConditionalFormattingUIPlugin extends Plugin { static override pluginName = `${SHEET_CONDITIONAL_FORMATTING_PLUGIN}_UI_PLUGIN`; static override packageName = pkg.name; diff --git a/packages/sheets-conditional-formatting/src/plugin.ts b/packages/sheets-conditional-formatting/src/plugin.ts index 97b73f2988..62a83dd6ed 100644 --- a/packages/sheets-conditional-formatting/src/plugin.ts +++ b/packages/sheets-conditional-formatting/src/plugin.ts @@ -18,6 +18,7 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverSheetsConditionalFormattingConfig } from './config/config'; import { DependentOn, ICommandService, IConfigService, Inject, Injector, merge, Plugin, touchDependencies, UniverInstanceType } from '@univerjs/core'; import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import pkg from '../package.json'; import { SHEET_CONDITIONAL_FORMATTING_PLUGIN } from './base/const'; import { AddCfCommand } from './commands/commands/add-cf.command'; @@ -42,7 +43,7 @@ import { ConditionalFormattingRangeTransformService } from './services/condition import { ConditionalFormattingStyleComposer } from './services/conditional-formatting-style-composer.service'; import { ConditionalFormattingService } from './services/conditional-formatting.service'; -@DependentOn(UniverFormulaEnginePlugin) +@DependentOn(UniverFormulaEnginePlugin, UniverSheetsPlugin) export class UniverSheetsConditionalFormattingPlugin extends Plugin { static override pluginName = SHEET_CONDITIONAL_FORMATTING_PLUGIN; static override packageName = pkg.name; diff --git a/packages/sheets-crosshair-highlight/src/plugin.ts b/packages/sheets-crosshair-highlight/src/plugin.ts index ed6c80c907..bd1ea46348 100644 --- a/packages/sheets-crosshair-highlight/src/plugin.ts +++ b/packages/sheets-crosshair-highlight/src/plugin.ts @@ -16,8 +16,11 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverSheetsCrosshairHighlightConfig } from './config/config'; -import { IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; -import { IRenderManagerService } from '@univerjs/engine-render'; +import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; +import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; +import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { defaultPluginConfig, SHEETS_CROSSHAIR_HIGHLIGHT_PLUGIN_CONFIG_KEY } from './config/config'; import { ComponentsController } from './controllers/components.controller'; @@ -25,6 +28,7 @@ import { SheetsCrosshairHighlightController } from './controllers/crosshair.cont import { SheetsCrosshairHighlightService } from './services/crosshair.service'; import { SheetCrosshairHighlightRenderController } from './views/widgets/crosshair-highlight.render-controller'; +@DependentOn(UniverRenderEnginePlugin, UniverSheetsPlugin, UniverUIPlugin, UniverSheetsUIPlugin) export class UniverSheetsCrosshairHighlightPlugin extends Plugin { static override pluginName: string = 'SHEET_CROSSHAIR_HIGHLIGHT_PLUGIN'; static override packageName = pkg.name; diff --git a/packages/sheets-data-validation-ui/src/mobile-plugin.ts b/packages/sheets-data-validation-ui/src/mobile-plugin.ts index 690daca25e..285f9ddee6 100644 --- a/packages/sheets-data-validation-ui/src/mobile-plugin.ts +++ b/packages/sheets-data-validation-ui/src/mobile-plugin.ts @@ -16,8 +16,13 @@ import type { Dependency, Workbook } from '@univerjs/core'; import type { IUniverSheetsDataValidationUIConfig } from './config/config'; -import { ICommandService, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; -import { IRenderManagerService } from '@univerjs/engine-render'; +import { DependentOn, ICommandService, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; +import { UniverDataValidationPlugin } from '@univerjs/data-validation'; +import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; +import { UniverSheetsDataValidationPlugin } from '@univerjs/sheets-data-validation'; +import { UniverSheetsMobileUIPlugin } from '@univerjs/sheets-ui'; +import { UniverMobileUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { AddSheetDataValidationAndOpenCommand, @@ -43,6 +48,14 @@ import { SheetsDataValidationUIController } from './controllers/ui.controller'; import { DataValidationPanelService } from './services/data-validation-panel.service'; import { DataValidationDropdownManagerService } from './services/dropdown-manager.service'; +@DependentOn( + UniverDataValidationPlugin, + UniverRenderEnginePlugin, + UniverSheetsPlugin, + UniverMobileUIPlugin, + UniverSheetsMobileUIPlugin, + UniverSheetsDataValidationPlugin +) export class UniverSheetsDataValidationMobileUIPlugin extends Plugin { static override pluginName: string = 'SHEET_DATA_VALIDATION_UI_PLUGIN'; static override packageName = pkg.name; diff --git a/packages/sheets-data-validation-ui/src/plugin.ts b/packages/sheets-data-validation-ui/src/plugin.ts index 426f10e9db..2e3689334b 100644 --- a/packages/sheets-data-validation-ui/src/plugin.ts +++ b/packages/sheets-data-validation-ui/src/plugin.ts @@ -17,8 +17,12 @@ import type { Dependency, Workbook } from '@univerjs/core'; import type { IUniverSheetsDataValidationUIConfig } from './config/config'; import { DependentOn, ICommandService, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; -import { IRenderManagerService } from '@univerjs/engine-render'; +import { UniverDataValidationPlugin } from '@univerjs/data-validation'; +import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsDataValidationPlugin } from '@univerjs/sheets-data-validation'; +import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { AddSheetDataValidationAndOpenCommand, @@ -45,7 +49,14 @@ import { SheetsDataValidationUIController } from './controllers/ui.controller'; import { DataValidationPanelService } from './services/data-validation-panel.service'; import { DataValidationDropdownManagerService } from './services/dropdown-manager.service'; -@DependentOn(UniverSheetsDataValidationPlugin) +@DependentOn( + UniverDataValidationPlugin, + UniverRenderEnginePlugin, + UniverSheetsPlugin, + UniverUIPlugin, + UniverSheetsUIPlugin, + UniverSheetsDataValidationPlugin +) export class UniverSheetsDataValidationUIPlugin extends Plugin { static override pluginName: string = 'SHEET_DATA_VALIDATION_UI_PLUGIN'; static override packageName = pkg.name; diff --git a/packages/sheets-data-validation/src/plugin.ts b/packages/sheets-data-validation/src/plugin.ts index a797702227..0203ff8904 100644 --- a/packages/sheets-data-validation/src/plugin.ts +++ b/packages/sheets-data-validation/src/plugin.ts @@ -27,6 +27,8 @@ import { UniverInstanceType, } from '@univerjs/core'; import { UniverDataValidationPlugin } from '@univerjs/data-validation'; +import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula'; import pkg from '../package.json'; import { @@ -52,7 +54,12 @@ import { DataValidationFormulaService } from './services/dv-formula.service'; import { DataValidationListCacheService } from './services/dv-list-cache.service'; import { SheetsDataValidationValidatorService } from './services/dv-validator.service'; -@DependentOn(UniverSheetsFormulaPlugin, UniverDataValidationPlugin) +@DependentOn( + UniverDataValidationPlugin, + UniverFormulaEnginePlugin, + UniverSheetsPlugin, + UniverSheetsFormulaPlugin +) export class UniverSheetsDataValidationPlugin extends Plugin { static override pluginName = DATA_VALIDATION_PLUGIN_NAME; static override packageName = pkg.name; diff --git a/packages/sheets-drawing-ui/src/plugin.ts b/packages/sheets-drawing-ui/src/plugin.ts index ee2d226c49..a71894b788 100644 --- a/packages/sheets-drawing-ui/src/plugin.ts +++ b/packages/sheets-drawing-ui/src/plugin.ts @@ -30,8 +30,11 @@ import { import { UniverDocsDrawingPlugin } from '@univerjs/docs-drawing'; import { UniverDrawingPlugin } from '@univerjs/drawing'; import { UniverDrawingUIPlugin } from '@univerjs/drawing-ui'; -import { IRenderManagerService } from '@univerjs/engine-render'; +import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsDrawingPlugin } from '@univerjs/sheets-drawing'; +import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { defaultPluginConfig, SHEETS_DRAWING_UI_PLUGIN_CONFIG_KEY } from './config/config'; import { ComponentsController } from './controllers/components.controller'; @@ -55,7 +58,16 @@ import { SheetCanvasFloatDomManagerService } from './services/canvas-float-dom-m import { DrawingContextMenuService, IDrawingContextMenuService } from './services/drawing-context-menu.service'; import { SheetDrawingHitTestService } from './services/sheet-drawing-hit-test.service'; -@DependentOn(UniverDrawingPlugin, UniverDocsDrawingPlugin, UniverDrawingUIPlugin, UniverSheetsDrawingPlugin) +@DependentOn( + UniverDrawingPlugin, + UniverRenderEnginePlugin, + UniverDocsDrawingPlugin, + UniverSheetsPlugin, + UniverUIPlugin, + UniverSheetsDrawingPlugin, + UniverDrawingUIPlugin, + UniverSheetsUIPlugin +) export class UniverSheetsDrawingUIPlugin extends Plugin { static override type = UniverInstanceType.UNIVER_SHEET; static override pluginName = 'SHEET_IMAGE_UI_PLUGIN'; diff --git a/packages/sheets-drawing/src/plugin.ts b/packages/sheets-drawing/src/plugin.ts index aefc4374a7..05deec1788 100644 --- a/packages/sheets-drawing/src/plugin.ts +++ b/packages/sheets-drawing/src/plugin.ts @@ -18,6 +18,7 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverSheetsDrawingConfig } from './config/config'; import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; import { UniverDrawingPlugin } from '@univerjs/drawing'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import pkg from '../package.json'; import { defaultPluginConfig, SHEETS_DRAWING_PLUGIN_CONFIG_KEY } from './config/config'; import { SheetDrawingTransformAffectedController } from './controllers/sheet-drawing-transform-affected.controller'; @@ -25,7 +26,7 @@ import { SHEET_DRAWING_PLUGIN, SheetsDrawingLoadController } from './controllers import { SheetDrawingTransformPlanService } from './services/sheet-drawing-transform-plan.service'; import { ISheetDrawingService, SheetDrawingService } from './services/sheet-drawing.service'; -@DependentOn(UniverDrawingPlugin) +@DependentOn(UniverDrawingPlugin, UniverSheetsPlugin) export class UniverSheetsDrawingPlugin extends Plugin { static override pluginName = SHEET_DRAWING_PLUGIN; static override packageName = pkg.name; diff --git a/packages/sheets-filter-ui/package.json b/packages/sheets-filter-ui/package.json index 7576539815..a764e42f45 100644 --- a/packages/sheets-filter-ui/package.json +++ b/packages/sheets-filter-ui/package.json @@ -86,7 +86,6 @@ }, "devDependencies": { "@univerjs-infra/shared": "workspace:*", - "@univerjs/engine-formula": "workspace:*", "postcss": "^8.5.23", "react": "18.3.1", "rxjs": "^7.8.2", diff --git a/packages/sheets-filter-ui/src/commands/commands/__tests__/sheets-filter.command.spec.ts b/packages/sheets-filter-ui/src/commands/commands/__tests__/sheets-filter.command.spec.ts index 29d4f967be..ed30ac2dc8 100644 --- a/packages/sheets-filter-ui/src/commands/commands/__tests__/sheets-filter.command.spec.ts +++ b/packages/sheets-filter-ui/src/commands/commands/__tests__/sheets-filter.command.spec.ts @@ -14,19 +14,52 @@ * limitations under the License. */ -import type { Dependency, IRange, IWorkbookData, Workbook } from '@univerjs/core'; +import type { IDisposable, IRange, IWorkbookData, Workbook } from '@univerjs/core'; import type { ISetRangeValuesCommandParams } from '@univerjs/sheets'; -import type { FilterModel, ISetSheetsFilterRangeMutationParams } from '@univerjs/sheets-filter'; -import type { ISetSheetsFilterCriteriaCommandParams } from '@univerjs/sheets-filter/commands/commands/sheets-filter.command.js'; -import { AuthzIoLocalService, IAuthzIoService, ICommandService, Inject, Injector, IUniverInstanceService, LocaleType, Plugin, RANGE_TYPE, RedoCommand, UndoCommand, Univer, UniverInstanceType } from '@univerjs/core'; -import { ActiveDirtyManagerService, IActiveDirtyManagerService, ISheetRowFilteredService, SheetRowFilteredService } from '@univerjs/engine-formula'; -import { MarkDirtyFilterChangeMutation, RangeProtectionRuleModel, RefRangeService, SetRangeValuesCommand, SetRangeValuesMutation, SheetInterceptorService, SheetRangeThemeModel, SheetsSelectionsService, WorkbookPermissionService, WorksheetPermissionService, WorksheetProtectionPointModel, WorksheetProtectionRuleModel, ZebraCrossingCacheController } from '@univerjs/sheets'; -import { SetSheetsFilterRangeMutation, SheetsFilterService, UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'; -import { ClearSheetsFilterCriteriaCommand, ReCalcSheetsFilterCommand, SetSheetsFilterCriteriaCommand, SmartToggleSheetsFilterCommand } from '@univerjs/sheets-filter/commands/commands/sheets-filter.command.js'; +import { + ICommandService, + Inject, + Injector, + IUniverInstanceService, + LocaleService, + LocaleType, + Plugin, + RANGE_TYPE, + RedoCommand, + toDisposable, + UndoCommand, + Univer, + UniverInstanceType, +} from '@univerjs/core'; +import { SetRangeValuesCommand, SheetsSelectionsService } from '@univerjs/sheets'; +import { + ClearSheetsFilterCriteriaCommand, + ReCalcSheetsFilterCommand, + SetSheetsFilterCriteriaCommand, + SetSheetsFilterRangeMutation, + SheetsFilterService, + SmartToggleSheetsFilterCommand, + UniverSheetsFilterPlugin, +} from '@univerjs/sheets-filter'; import { IMessageService } from '@univerjs/ui'; -import { MockMessageService } from '@univerjs/ui/services/message/__tests__/mock-message.service.js'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +class MockMessageService { + show(): IDisposable { + return toDisposable(() => undefined); + } + + remove(): void {} + + removeAll(): void {} + + setContainer(): void {} + + getContainer(): undefined { + return undefined; + } +} + function testWorkbookDataFactory(): IWorkbookData { return { id: 'test', @@ -89,41 +122,17 @@ function createFilterCommandTestBed() { } override onStarting(): void { - ([ - [RefRangeService], - [SheetInterceptorService], - [SheetsSelectionsService], - [WorksheetPermissionService], - [WorksheetProtectionPointModel], - [WorkbookPermissionService], - [IAuthzIoService, { useClass: AuthzIoLocalService }], - [WorksheetProtectionRuleModel], - [RangeProtectionRuleModel], - [IMessageService, { useClass: MockMessageService }], - [IActiveDirtyManagerService, { useClass: ActiveDirtyManagerService }], - [ISheetRowFilteredService, { useClass: SheetRowFilteredService }], - [SheetRangeThemeModel], - [ZebraCrossingCacheController], - ] as Dependency[]).forEach((d) => this._injector.add(d)); - - this._injector.get(SheetInterceptorService); - this._injector.get(WorkbookPermissionService); - this._injector.get(WorksheetPermissionService); + this._injector.add([IMessageService, { useClass: MockMessageService }]); } } univer.registerPlugin(UniverSheetsFilterPlugin); univer.registerPlugin(SheetsFilterCommandTestPlugin); + get(LocaleService).load({}); univer.createUnit(UniverInstanceType.UNIVER_SHEET, testWorkbookDataFactory()); - const commandService = get(ICommandService); - [ - SetRangeValuesCommand, - SetRangeValuesMutation, - SmartToggleSheetsFilterCommand, - MarkDirtyFilterChangeMutation, - ].forEach((command) => commandService.registerCommand(command)); + get(ICommandService).registerCommand(SmartToggleSheetsFilterCommand); const univerInstanceService = get(IUniverInstanceService); univerInstanceService.focusUnit('test'); @@ -169,7 +178,7 @@ describe('test sheets filter commands', () => { ]); } - function getFilterModel(): FilterModel { + function getFilterModel() { return sheetsFilterService.getFilterModel('test', 'sheet1')!; } @@ -179,7 +188,7 @@ describe('test sheets filter commands', () => { unitId: 'test', subUnitId: 'sheet1', range: { startRow: 0, startColumn: 0, endRow: 5, endColumn: 5 }, - } as ISetSheetsFilterRangeMutationParams)); + })); expect(getFilterModel()).toBeTruthy(); expect(await commandService.executeCommand(SmartToggleSheetsFilterCommand.id)).toBeTruthy(); @@ -225,7 +234,7 @@ describe('test sheets filter commands', () => { subUnitId: 'sheet1', col: 0, criteria: null, - } as ISetSheetsFilterCriteriaCommandParams)).toBeFalsy(); + })).toBeFalsy(); }); it('should set filter criteria works', async () => { @@ -240,7 +249,7 @@ describe('test sheets filter commands', () => { criteria: { filters: { filters: ['1'] }, }, - } as ISetSheetsFilterCriteriaCommandParams)).toBeTruthy(); + })).toBeTruthy(); expect(getFilterModel()!.filteredOutRows).toEqual(new Set([2, 3, 4, 5])); expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); @@ -255,7 +264,7 @@ describe('test sheets filter commands', () => { subUnitId: 'sheet1', col: 0, criteria: null, - } as ISetSheetsFilterCriteriaCommandParams)).toBeTruthy(); + })).toBeTruthy(); expect(getFilterModel()!.filteredOutRows).toEqual(new Set()); expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); @@ -278,7 +287,7 @@ describe('test sheets filter commands', () => { criteria: { filters: { filters: ['1'] }, }, - } as ISetSheetsFilterCriteriaCommandParams)).toBeTruthy(); + })).toBeTruthy(); expect(await commandService.executeCommand(SetSheetsFilterCriteriaCommand.id, { unitId: 'test', subUnitId: 'sheet1', @@ -286,7 +295,7 @@ describe('test sheets filter commands', () => { criteria: { filters: { filters: ['b'] }, }, - } as ISetSheetsFilterCriteriaCommandParams)).toBeTruthy(); + })).toBeTruthy(); expect(getFilterModel()!.filteredOutRows).toEqual(new Set([1, 2, 3, 4, 5])); expect(await commandService.executeCommand(ClearSheetsFilterCriteriaCommand.id, { @@ -315,7 +324,7 @@ describe('test sheets filter commands', () => { criteria: { filters: { filters: ['1'] }, }, - } as ISetSheetsFilterCriteriaCommandParams)).toBeTruthy(); + })).toBeTruthy(); expect(await commandService.executeCommand(SetSheetsFilterCriteriaCommand.id, { unitId: 'test', subUnitId: 'sheet1', @@ -323,7 +332,7 @@ describe('test sheets filter commands', () => { criteria: { filters: { filters: ['b'] }, }, - } as ISetSheetsFilterCriteriaCommandParams)).toBeTruthy(); + })).toBeTruthy(); expect(getFilterModel()!.filteredOutRows).toEqual(new Set([1, 2, 3, 4, 5])); expect(await commandService.executeCommand(SetRangeValuesCommand.id, { diff --git a/packages/sheets-filter-ui/src/commands/operations/__tests__/sheets-filter.operation.spec.ts b/packages/sheets-filter-ui/src/commands/operations/__tests__/sheets-filter.operation.spec.ts index 0f258ab74e..0fa11b564a 100644 --- a/packages/sheets-filter-ui/src/commands/operations/__tests__/sheets-filter.operation.spec.ts +++ b/packages/sheets-filter-ui/src/commands/operations/__tests__/sheets-filter.operation.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { Dependency, IOperation, IWorkbookData, Workbook } from '@univerjs/core'; +import type { IOperation, IWorkbookData, Workbook } from '@univerjs/core'; import type { ISetSheetsFilterRangeMutationParams } from '@univerjs/sheets-filter'; import type { IEditorBridgeServiceVisibleParam } from '@univerjs/sheets-ui'; import { @@ -29,19 +29,6 @@ import { Univer, UniverInstanceType, } from '@univerjs/core'; -import { - ActiveDirtyManagerService, - IActiveDirtyManagerService, - ISheetRowFilteredService, - SheetRowFilteredService, -} from '@univerjs/engine-formula'; -import { - RefRangeService, - SheetInterceptorService, - SheetRangeThemeModel, - SheetsSelectionsService, - ZebraCrossingCacheController, -} from '@univerjs/sheets'; import { FilterBy, SetSheetsFilterRangeMutation, UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { ISheetsFilterPanelService, SheetsFilterPanelService } from '../../../services/sheets-filter-panel.service'; @@ -101,16 +88,7 @@ function createFilterOperationTestBed() { } override onStarting(): void { - ([ - [SheetInterceptorService], - [ISheetsFilterPanelService, { useClass: SheetsFilterPanelService }], - [RefRangeService], - [SheetsSelectionsService], - [SheetRangeThemeModel], - [ZebraCrossingCacheController], - [IActiveDirtyManagerService, { useClass: ActiveDirtyManagerService }], - [ISheetRowFilteredService, { useClass: SheetRowFilteredService }], - ] as Dependency[]).forEach((d) => this._injector.add(d)); + this._injector.add([ISheetsFilterPanelService, { useClass: SheetsFilterPanelService }]); } } diff --git a/packages/sheets-filter-ui/src/menu/__tests__/sheets-filter.menu.spec.ts b/packages/sheets-filter-ui/src/menu/__tests__/sheets-filter.menu.spec.ts index d6bb3dcc90..d4108eb7fa 100644 --- a/packages/sheets-filter-ui/src/menu/__tests__/sheets-filter.menu.spec.ts +++ b/packages/sheets-filter-ui/src/menu/__tests__/sheets-filter.menu.spec.ts @@ -16,15 +16,38 @@ import type { IWorkbookData, Workbook } from '@univerjs/core'; import type { ISetSheetsFilterCriteriaMutationParams, ISetSheetsFilterRangeMutationParams } from '@univerjs/sheets-filter'; -import { AuthzIoLocalService, DisposableCollection, IAuthzIoService, ICommandService, Inject, Injector, LocaleType, Plugin, Univer, UniverInstanceType } from '@univerjs/core'; -import { ActiveDirtyManagerService, IActiveDirtyManagerService, ISheetRowFilteredService, SheetRowFilteredService } from '@univerjs/engine-formula'; -import { ExclusiveRangeService, IExclusiveRangeService, RangeProtectionRuleModel, RefRangeService, SetWorksheetActiveOperation, SheetInterceptorService, SheetRangeThemeModel, SheetsSelectionsService, WorkbookPermissionService, WorksheetPermissionService, WorksheetProtectionPointModel, WorksheetProtectionRuleModel, ZebraCrossingCacheController } from '@univerjs/sheets'; -import { RemoveSheetsFilterMutation, SetSheetsFilterCriteriaMutation, SetSheetsFilterRangeMutation, UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'; -import { SmartToggleSheetsFilterCommand } from '@univerjs/sheets-filter/commands/commands/sheets-filter.command.js'; -import { IMenuManagerService, IPlatformService, IShortcutService, MenuManagerService, PlatformService, ShortcutService } from '@univerjs/ui'; +import { + DisposableCollection, + ICommandService, + Inject, + Injector, + LocaleType, + Plugin, + Univer, + UniverInstanceType, +} from '@univerjs/core'; +import { SetWorksheetActiveOperation } from '@univerjs/sheets'; +import { + RemoveSheetsFilterMutation, + SetSheetsFilterCriteriaMutation, + SetSheetsFilterRangeMutation, + UniverSheetsFilterPlugin, +} from '@univerjs/sheets-filter'; +import { + IMenuManagerService, + IPlatformService, + IShortcutService, + MenuManagerService, + PlatformService, + ShortcutService, +} from '@univerjs/ui'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { CloseFilterPanelOperation, OpenFilterPanelOperation } from '../../commands/operations/sheets-filter.operation'; -import { ClearFilterCriteriaMenuItemFactory, ReCalcFilterMenuItemFactory, SmartToggleFilterMenuItemFactory } from '../sheets-filter.menu'; +import { + ClearFilterCriteriaMenuItemFactory, + ReCalcFilterMenuItemFactory, + SmartToggleFilterMenuItemFactory, +} from '../sheets-filter.menu'; const TEST_WORKBOOK_DATA_DEMO: IWorkbookData = { id: 'test', @@ -66,33 +89,14 @@ function createSheetsFilterMenuTestBed() { override onStarting(): void { const injector = this._injector; injector.add([IPlatformService, { useClass: PlatformService }]); - injector.add([RefRangeService]); - injector.add([SheetsSelectionsService]); injector.add([IShortcutService, { useClass: ShortcutService }]); injector.add([IMenuManagerService, { useClass: MenuManagerService }]); - injector.add([WorksheetPermissionService]); - injector.add([WorksheetProtectionPointModel]); - injector.add([WorkbookPermissionService]); - injector.add([IAuthzIoService, { useClass: AuthzIoLocalService }]); - injector.add([WorksheetProtectionRuleModel]); - injector.add([SheetInterceptorService]); - injector.add([RangeProtectionRuleModel]); - injector.add([SheetRangeThemeModel]); - injector.add([ZebraCrossingCacheController]); - injector.add([IExclusiveRangeService, { useClass: ExclusiveRangeService, deps: [SheetsSelectionsService] }]); - injector.add([IActiveDirtyManagerService, { useClass: ActiveDirtyManagerService }]); - injector.add([ISheetRowFilteredService, { useClass: SheetRowFilteredService }]); const commandService = injector.get(ICommandService); [ - SmartToggleSheetsFilterCommand, OpenFilterPanelOperation, CloseFilterPanelOperation, ].forEach((command) => commandService.registerCommand(command)); - - this._injector.get(SheetInterceptorService); - this._injector.get(WorkbookPermissionService); - this._injector.get(WorksheetPermissionService); } } @@ -121,7 +125,6 @@ describe('test sheet filter menu items', () => { disposableCollection = new DisposableCollection(); commandService = get(ICommandService); - commandService.registerCommand(SetWorksheetActiveOperation); // Active sheet, prevent activeSheet from being initialized to null, causing activeFilterModel$ to also transmit null. expect(commandService.syncExecuteCommand(SetWorksheetActiveOperation.id, { unitId: 'test', subUnitId: 'sheet1' })).toBeTruthy(); diff --git a/packages/sheets-filter-ui/src/mobile-plugin.ts b/packages/sheets-filter-ui/src/mobile-plugin.ts index f9b63062b9..af7f33aedd 100644 --- a/packages/sheets-filter-ui/src/mobile-plugin.ts +++ b/packages/sheets-filter-ui/src/mobile-plugin.ts @@ -17,14 +17,24 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverSheetsFilterUIConfig } from './config/config'; import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; +import { UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'; +import { UniverSheetsMobileUIPlugin } from '@univerjs/sheets-ui'; +import { UniverMobileUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { defaultPluginConfig, SHEETS_FILTER_UI_PLUGIN_CONFIG_KEY } from './config/config'; import { ComponentsController } from './controllers/components.controller'; import { SheetsFilterPermissionController } from './controllers/sheets-filter-permission.controller'; import { SheetsFilterUIMobileController } from './controllers/ui-mobile.controller'; -@DependentOn(UniverSheetsFilterPlugin) +@DependentOn( + UniverRenderEnginePlugin, + UniverSheetsPlugin, + UniverMobileUIPlugin, + UniverSheetsFilterPlugin, + UniverSheetsMobileUIPlugin +) export class UniverSheetsFilterMobileUIPlugin extends Plugin { static override type = UniverInstanceType.UNIVER_SHEET; static override pluginName = 'SHEET_FILTER_UI_PLUGIN'; diff --git a/packages/sheets-filter-ui/src/plugin.ts b/packages/sheets-filter-ui/src/plugin.ts index 533c8971dd..5e10a02705 100644 --- a/packages/sheets-filter-ui/src/plugin.ts +++ b/packages/sheets-filter-ui/src/plugin.ts @@ -27,8 +27,12 @@ import { touchDependencies, UniverInstanceType, } from '@univerjs/core'; +import { UniverRenderEnginePlugin } from '@univerjs/engine-render'; import { IRPCChannelService, toModule } from '@univerjs/rpc'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'; +import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { defaultPluginConfig, SHEETS_FILTER_UI_PLUGIN_CONFIG_KEY } from './config/config'; import { ComponentsController } from './controllers/components.controller'; @@ -43,7 +47,13 @@ import { /** * The plugin for the desktop version of the sheets filter UI. Its type is {@link UniverInstanceType.UNIVER_SHEET}. */ -@DependentOn(UniverSheetsFilterPlugin) +@DependentOn( + UniverRenderEnginePlugin, + UniverSheetsPlugin, + UniverUIPlugin, + UniverSheetsFilterPlugin, + UniverSheetsUIPlugin +) export class UniverSheetsFilterUIPlugin extends Plugin { static override type = UniverInstanceType.UNIVER_SHEET; static override pluginName = 'SHEET_FILTER_UI_PLUGIN'; diff --git a/packages/sheets-filter-ui/src/services/__tests__/sheets-filter-panel.service.spec.ts b/packages/sheets-filter-ui/src/services/__tests__/sheets-filter-panel.service.spec.ts index 861e7fa6bf..67eebd0f6e 100644 --- a/packages/sheets-filter-ui/src/services/__tests__/sheets-filter-panel.service.spec.ts +++ b/packages/sheets-filter-ui/src/services/__tests__/sheets-filter-panel.service.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { Dependency, IOperation, IWorkbookData, Workbook } from '@univerjs/core'; +import type { IOperation, IWorkbookData, Workbook } from '@univerjs/core'; import type { IInsertColMutationParams } from '@univerjs/sheets'; import type { IEditorBridgeServiceVisibleParam } from '@univerjs/sheets-ui'; import type { IOpenFilterPanelOperationParams } from '../../commands/operations/sheets-filter.operation'; @@ -32,21 +32,7 @@ import { Univer, UniverInstanceType, } from '@univerjs/core'; -import { - ActiveDirtyManagerService, - IActiveDirtyManagerService, - ISheetRowFilteredService, - SheetRowFilteredService, -} from '@univerjs/engine-formula'; -import { - InsertColMutation, - MarkDirtyFilterChangeMutation, - RefRangeService, - SheetInterceptorService, - SheetRangeThemeModel, - SheetsSelectionsService, - ZebraCrossingCacheController, -} from '@univerjs/sheets'; +import { InsertColMutation } from '@univerjs/sheets'; import { CustomFilterOperator, FilterBy, SheetsFilterService, UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'; import { afterEach, beforeEach, describe, expect, it, vitest } from 'vitest'; import { @@ -130,16 +116,7 @@ function createSheetsFilterPanelServiceTestBed(workbookData: IWorkbookData) { } override onStarting(): void { - ([ - [RefRangeService], - [SheetsSelectionsService], - [SheetInterceptorService], - [ISheetsFilterPanelService, { useClass: SheetsFilterPanelService }], - [SheetRangeThemeModel], - [ZebraCrossingCacheController], - [IActiveDirtyManagerService, { useClass: ActiveDirtyManagerService }], - [ISheetRowFilteredService, { useClass: SheetRowFilteredService }], - ] as Dependency[]).forEach((d) => this._injector.add(d)); + this._injector.add([ISheetsFilterPanelService, { useClass: SheetsFilterPanelService }]); } } @@ -156,8 +133,6 @@ function createSheetsFilterPanelServiceTestBed(workbookData: IWorkbookData) { OpenFilterPanelOperation, CloseFilterPanelOperation, SetCellEditVisibleOperation, - MarkDirtyFilterChangeMutation, - InsertColMutation, ].forEach((command) => commandService.registerCommand(command)); return { univer, get }; diff --git a/packages/sheets-filter-ui/src/views/components/__tests__/SheetsFilterPanel.spec.tsx b/packages/sheets-filter-ui/src/views/components/__tests__/SheetsFilterPanel.spec.tsx index 30ba8c35d6..54ea6ed80e 100644 --- a/packages/sheets-filter-ui/src/views/components/__tests__/SheetsFilterPanel.spec.tsx +++ b/packages/sheets-filter-ui/src/views/components/__tests__/SheetsFilterPanel.spec.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { Dependency, IDisposable, IOperation, IWorkbookData, Workbook } from '@univerjs/core'; +import type { IDisposable, IOperation, IWorkbookData, Workbook } from '@univerjs/core'; import type { IUniverSheetsFilterConfig } from '@univerjs/sheets-filter'; import type { IEditorBridgeServiceVisibleParam } from '@univerjs/sheets-ui'; import type { Root } from 'react-dom/client'; @@ -34,20 +34,6 @@ import { Univer, UniverInstanceType, } from '@univerjs/core'; -import { - ActiveDirtyManagerService, - IActiveDirtyManagerService, - ISheetRowFilteredService, - SheetRowFilteredService, -} from '@univerjs/engine-formula'; -import { - MarkDirtyFilterChangeMutation, - RefRangeService, - SheetInterceptorService, - SheetRangeThemeModel, - SheetsSelectionsService, - ZebraCrossingCacheController, -} from '@univerjs/sheets'; import { FilterBy, SheetsFilterService, SheetsFilterSyncController, UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'; import { ILayoutService, IMessageService, IUIPartsService, RediContext, UIPartsService } from '@univerjs/ui'; import { act } from 'react'; @@ -149,19 +135,10 @@ function createFilterPanelViewTestBed(workbookData: IWorkbookData, filterConfig? } override onStarting(): void { - ([ - [SheetInterceptorService], - [ISheetsFilterPanelService, { useClass: SheetsFilterPanelService }], - [RefRangeService], - [SheetsSelectionsService], - [SheetRangeThemeModel], - [ZebraCrossingCacheController], - [IActiveDirtyManagerService, { useClass: ActiveDirtyManagerService }], - [ISheetRowFilteredService, { useClass: SheetRowFilteredService }], - [ILayoutService, { useClass: TestLayoutService as never }], - [IMessageService, { useClass: TestMessageService as never }], - [IUIPartsService, { useClass: UIPartsService }], - ] as Dependency[]).forEach((dependency) => this._injector.add(dependency)); + this._injector.add([ISheetsFilterPanelService, { useClass: SheetsFilterPanelService }]); + this._injector.add([ILayoutService, { useClass: TestLayoutService as never }]); + this._injector.add([IMessageService, { useClass: TestMessageService as never }]); + this._injector.add([IUIPartsService, { useClass: UIPartsService }]); } } @@ -184,7 +161,6 @@ function createFilterPanelViewTestBed(workbookData: IWorkbookData, filterConfig? CloseFilterPanelOperation, ChangeFilterByOperation, SetCellEditVisibleOperation, - MarkDirtyFilterChangeMutation, ].forEach((command) => commandService.registerCommand(command)); return { diff --git a/packages/sheets-filter/src/facade/__tests__/create-test-bed.ts b/packages/sheets-filter/src/facade/__tests__/create-test-bed.ts index fde76ea254..6368fefe25 100644 --- a/packages/sheets-filter/src/facade/__tests__/create-test-bed.ts +++ b/packages/sheets-filter/src/facade/__tests__/create-test-bed.ts @@ -14,44 +14,19 @@ * limitations under the License. */ -import type { Dependency, IWorkbookData, UnitModel } from '@univerjs/core'; +import type { Injector, IWorkbookData, UnitModel } from '@univerjs/core'; import { - ICommandService, ILogService, - Inject, - Injector, IUniverInstanceService, LocaleService, LocaleType, LogLevel, - Plugin, set, ThemeService, Univer, UniverInstanceType, } from '@univerjs/core'; import { FUniver } from '@univerjs/core/facade'; -import { - ActiveDirtyManagerService, - DefinedNamesService, - FormulaDataModel, - FunctionService, - IActiveDirtyManagerService, - IDefinedNamesService, - IFunctionService, - ISheetRowFilteredService, - LexerTreeBuilder, -} from '@univerjs/engine-formula'; -import { SheetRowFilteredService } from '@univerjs/engine-formula/services/sheet-row-filtered.service.js'; -import { - MarkDirtyFilterChangeMutation, - RefRangeService, - SheetInterceptorService, - SheetRangeThemeModel, - SheetSkeletonService, - SheetsSelectionsService, - ZebraCrossingCacheController, -} from '@univerjs/sheets'; import { UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'; import enUS from '@univerjs/sheets/locale/en-US'; import zhCN from '@univerjs/sheets/locale/zh-CN'; @@ -113,52 +88,10 @@ export interface ITestBed { injector: Injector; } -export function createFacadeTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]): ITestBed { +export function createFacadeTestBed(workbookData?: IWorkbookData): ITestBed { const univer = new Univer(); const injector = univer.__getInjector(); - class TestPlugin extends Plugin { - static override pluginName = 'test-plugin'; - static override type = UniverInstanceType.UNIVER_SHEET; - - constructor( - _config: undefined, - @Inject(Injector) override readonly _injector: Injector - ) { - super(); - } - - override onStarting(): void { - const injector = this._injector; - injector.add([SheetsSelectionsService]); - injector.add([SheetInterceptorService]); - injector.add([IFunctionService, { useClass: FunctionService }]); - injector.add([SheetSkeletonService]); - injector.add([SheetRangeThemeModel]); - injector.add([ZebraCrossingCacheController]); - injector.add([FormulaDataModel]); - injector.add([LexerTreeBuilder]); - injector.add([RefRangeService]); - injector.add([IDefinedNamesService, { useClass: DefinedNamesService }]); - - // register feature modules - ([ - [IActiveDirtyManagerService, { useClass: ActiveDirtyManagerService }], - [ISheetRowFilteredService, { useClass: SheetRowFilteredService }], - ] as Dependency[]).forEach((d) => { - injector.add(d); - }); - - dependencies?.forEach((d) => injector.add(d)); - - this._injector.get(SheetInterceptorService); - } - - override onReady(): void { - - } - } - // load i18n injector.get(LocaleService).load({ zhCN, enUS }); @@ -170,12 +103,8 @@ export function createFacadeTestBed(workbookData?: IWorkbookData, dependencies?: // register builtin plugins // note that UI plugins are not registered here, because the unit test environment does not have a UI - univer.registerPlugin(TestPlugin); univer.registerPlugin(UniverSheetsFilterPlugin); - const commandService = injector.get(ICommandService); - commandService.registerCommand(MarkDirtyFilterChangeMutation); - const sheet = univer.createUnit>(UniverInstanceType.UNIVER_SHEET, workbookData || getTestWorkbookDataDemo()); const univerInstanceService = injector.get(IUniverInstanceService); univerInstanceService.focusUnit('test'); diff --git a/packages/sheets-filter/src/facade/__tests__/create-worksheet-test-bed.ts b/packages/sheets-filter/src/facade/__tests__/create-worksheet-test-bed.ts index 85e85e23af..8512569b1d 100644 --- a/packages/sheets-filter/src/facade/__tests__/create-worksheet-test-bed.ts +++ b/packages/sheets-filter/src/facade/__tests__/create-worksheet-test-bed.ts @@ -14,42 +14,19 @@ * limitations under the License. */ -import type { Dependency, IWorkbookData, UnitModel } from '@univerjs/core'; +import type { Injector, IWorkbookData, UnitModel } from '@univerjs/core'; import { ILogService, - Inject, - Injector, IUniverInstanceService, LocaleService, LocaleType, LogLevel, - Plugin, set, ThemeService, Univer, UniverInstanceType, } from '@univerjs/core'; import { FUniver } from '@univerjs/core/facade'; -import { - ActiveDirtyManagerService, - DefinedNamesService, - FormulaDataModel, - FunctionService, - IActiveDirtyManagerService, - IDefinedNamesService, - IFunctionService, - ISheetRowFilteredService, - LexerTreeBuilder, -} from '@univerjs/engine-formula'; -import { SheetRowFilteredService } from '@univerjs/engine-formula/services/sheet-row-filtered.service.js'; -import { - RefRangeService, - SheetInterceptorService, - SheetRangeThemeModel, - SheetSkeletonService, - SheetsSelectionsService, - ZebraCrossingCacheController, -} from '@univerjs/sheets'; import { UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'; import enUS from '@univerjs/sheets/locale/en-US'; import zhCN from '@univerjs/sheets/locale/zh-CN'; @@ -105,50 +82,10 @@ export interface ITestBed { injector: Injector; } -export function createWorksheetTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]): ITestBed { +export function createWorksheetTestBed(workbookData?: IWorkbookData): ITestBed { const univer = new Univer(); const injector = univer.__getInjector(); - class TestPlugin extends Plugin { - static override pluginName = 'test-plugin'; - static override type = UniverInstanceType.UNIVER_SHEET; - - constructor( - _config: undefined, - @Inject(Injector) override readonly _injector: Injector - ) { - super(); - } - - override onStarting(): void { - const injector = this._injector; - injector.add([SheetsSelectionsService]); - injector.add([SheetInterceptorService]); - injector.add([IFunctionService, { useClass: FunctionService }]); - injector.add([SheetSkeletonService]); - injector.add([FormulaDataModel]); - injector.add([LexerTreeBuilder]); - injector.add([SheetRangeThemeModel]); - injector.add([ZebraCrossingCacheController]); - injector.add([RefRangeService]); - injector.add([IDefinedNamesService, { useClass: DefinedNamesService }]); - - // register feature modules - ([ - [IActiveDirtyManagerService, { useClass: ActiveDirtyManagerService }], - [ISheetRowFilteredService, { useClass: SheetRowFilteredService }], - ] as Dependency[]).forEach((d) => { - injector.add(d); - }); - - dependencies?.forEach((d) => injector.add(d)); - } - - override onReady(): void { - - } - } - // load i18n injector.get(LocaleService).load({ zhCN, enUS }); @@ -160,7 +97,6 @@ export function createWorksheetTestBed(workbookData?: IWorkbookData, dependencie // register builtin plugins // note that UI plugins are not registered here, because the unit test environment does not have a UI - univer.registerPlugin(TestPlugin); univer.registerPlugin(UniverSheetsFilterPlugin); const sheet = univer.createUnit>(UniverInstanceType.UNIVER_SHEET, workbookData || getTestWorkbookDataDemo()); diff --git a/packages/sheets-filter/src/plugin.ts b/packages/sheets-filter/src/plugin.ts index 839968dae9..9116990a4c 100644 --- a/packages/sheets-filter/src/plugin.ts +++ b/packages/sheets-filter/src/plugin.ts @@ -16,7 +16,9 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverSheetsFilterConfig } from './config/config'; -import { IConfigService, Inject, Injector, merge, Plugin, touchDependencies, UniverInstanceType } from '@univerjs/core'; +import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, touchDependencies, UniverInstanceType } from '@univerjs/core'; +import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import pkg from '../package.json'; import { defaultPluginConfig, SHEETS_FILTER_PLUGIN_CONFIG_KEY } from './config/config'; import { SheetsFilterSyncController } from './controllers/sheets-filter-sync.controller'; @@ -24,6 +26,7 @@ import { SheetsFilterController } from './controllers/sheets-filter.controller'; import { SheetsFilterFormulaService } from './services/sheet-filter-formula.service'; import { SHEET_FILTER_SNAPSHOT_ID, SheetsFilterService } from './services/sheet-filter.service'; +@DependentOn(UniverFormulaEnginePlugin, UniverSheetsPlugin) export class UniverSheetsFilterPlugin extends Plugin { static override type = UniverInstanceType.UNIVER_SHEET; static override pluginName = SHEET_FILTER_SNAPSHOT_ID; diff --git a/packages/sheets-find-replace/src/controllers/__tests__/hit-cell.spec.ts b/packages/sheets-find-replace/src/controllers/__tests__/hit-cell.spec.ts index 5208bfd39b..612e9cac6b 100644 --- a/packages/sheets-find-replace/src/controllers/__tests__/hit-cell.spec.ts +++ b/packages/sheets-find-replace/src/controllers/__tests__/hit-cell.spec.ts @@ -25,6 +25,7 @@ describe('hitCell', () => { findString: 'abc', caseSensitive: false, matchesTheWholeCell: false, + matchesTheWholeWord: false, findDirection: FindDirection.ROW, findScope: FindScope.SUBUNIT, findBy: FindBy.VALUE, diff --git a/packages/sheets-find-replace/src/controllers/__tests__/sheet-find-model.navigation.spec.ts b/packages/sheets-find-replace/src/controllers/__tests__/sheet-find-model.navigation.spec.ts index 577b8da297..ffb982e167 100644 --- a/packages/sheets-find-replace/src/controllers/__tests__/sheet-find-model.navigation.spec.ts +++ b/packages/sheets-find-replace/src/controllers/__tests__/sheet-find-model.navigation.spec.ts @@ -70,6 +70,7 @@ describe('SheetFindModel navigation', () => { findString: 'a', caseSensitive: false, matchesTheWholeCell: false, + matchesTheWholeWord: false, findDirection: FindDirection.ROW, findScope: FindScope.SUBUNIT, findBy: FindBy.VALUE, diff --git a/packages/sheets-find-replace/src/controllers/__tests__/sheet-find-replace.controller.spec.ts b/packages/sheets-find-replace/src/controllers/__tests__/sheet-find-replace.controller.spec.ts index 22c1cff86b..bec51b5132 100644 --- a/packages/sheets-find-replace/src/controllers/__tests__/sheet-find-replace.controller.spec.ts +++ b/packages/sheets-find-replace/src/controllers/__tests__/sheet-find-replace.controller.spec.ts @@ -45,6 +45,7 @@ const baseQuery: IFindQuery = { findString: 'alpha', caseSensitive: false, matchesTheWholeCell: false, + matchesTheWholeWord: false, findDirection: FindDirection.ROW, findScope: FindScope.SUBUNIT, findBy: FindBy.VALUE, diff --git a/packages/sheets-find-replace/src/controllers/__tests__/utils.spec.ts b/packages/sheets-find-replace/src/controllers/__tests__/utils.spec.ts index df14bc009c..fb9dd5274c 100644 --- a/packages/sheets-find-replace/src/controllers/__tests__/utils.spec.ts +++ b/packages/sheets-find-replace/src/controllers/__tests__/utils.spec.ts @@ -238,6 +238,7 @@ describe('test "hitCell" method', () => { findDirection: FindDirection.COLUMN, findScope: FindScope.SUBUNIT, matchesTheWholeCell: false, + matchesTheWholeWord: false, replaceRevealed: false, }; @@ -260,6 +261,7 @@ describe('test "hitCell" method', () => { findDirection: FindDirection.COLUMN, findScope: FindScope.SUBUNIT, matchesTheWholeCell: false, + matchesTheWholeWord: false, replaceRevealed: false, }; diff --git a/packages/sheets-find-replace/src/controllers/sheet-find-replace.controller.ts b/packages/sheets-find-replace/src/controllers/sheet-find-replace.controller.ts index 8256708acc..2b7b52bac9 100644 --- a/packages/sheets-find-replace/src/controllers/sheet-find-replace.controller.ts +++ b/packages/sheets-find-replace/src/controllers/sheet-find-replace.controller.ts @@ -14,8 +14,24 @@ * limitations under the License. */ -import type { ICellData, IDisposable, IObjectMatrixPrimitiveType, IRange, Nullable, Workbook, Worksheet } from '@univerjs/core'; -import type { IFindComplete, IFindMatch, IFindMoveParams, IFindQuery, IFindReplaceProvider, IReplaceAllResult } from '@univerjs/find-replace'; +import type { + ICellData, + IDisposable, + IObjectMatrixPrimitiveType, + IRange, + Nullable, + UnitModel, + Workbook, + Worksheet, +} from '@univerjs/core'; +import type { + IFindComplete, + IFindMatch, + IFindMoveParams, + IFindQuery, + IFindReplaceProvider, + IReplaceAllResult, +} from '@univerjs/find-replace'; import type { ISelectionWithStyle, ISelectRangeCommandParams, @@ -976,6 +992,15 @@ export class SheetFindModel extends FindModel { * It also adds the search results to the search view by highlighting them. */ class SheetsFindReplaceProvider extends Disposable implements IFindReplaceProvider { + readonly capabilities = { + caseSensitive: true, + matchesTheWholeWord: false, + matchesTheWholeCell: true, + findDirection: true, + findScope: true, + findBy: true, + }; + /** * Hold all find results in this kind of univer business instances (Workbooks). */ @@ -989,6 +1014,10 @@ class SheetsFindReplaceProvider extends Disposable implements IFindReplaceProvid super(); } + isSupported(unit: UnitModel): boolean { + return unit.type === UniverInstanceType.UNIVER_SHEET; + } + async find(query: IFindQuery): Promise { this._terminate(); diff --git a/packages/sheets-find-replace/src/facade/__tests__/f-univer.spec.ts b/packages/sheets-find-replace/src/facade/__tests__/f-univer.spec.ts index 99559f6ca5..35a571f544 100644 --- a/packages/sheets-find-replace/src/facade/__tests__/f-univer.spec.ts +++ b/packages/sheets-find-replace/src/facade/__tests__/f-univer.spec.ts @@ -105,8 +105,21 @@ class TestWorkbookFindModel extends FindModel { } class TestWorkbookFindReplaceProvider { + readonly capabilities = { + caseSensitive: true, + matchesTheWholeWord: false, + matchesTheWholeCell: true, + findDirection: true, + findScope: true, + findBy: true, + }; + constructor(@Inject(Injector) private readonly _injector: Injector) {} + isSupported(): boolean { + return true; + } + find(query: IFindQuery): Promise { return Promise.resolve([this._injector.createInstance(TestWorkbookFindModel, query)]); } diff --git a/packages/sheets-find-replace/src/plugin.ts b/packages/sheets-find-replace/src/plugin.ts index 79ca6ece3f..8e956ac9b5 100644 --- a/packages/sheets-find-replace/src/plugin.ts +++ b/packages/sheets-find-replace/src/plugin.ts @@ -17,13 +17,20 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverSheetsFindReplaceConfig } from './config/config'; import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; +import { UniverRenderEnginePlugin } from '@univerjs/engine-render'; import { UniverFindReplacePlugin } from '@univerjs/find-replace'; import { UniverSheetsPlugin } from '@univerjs/sheets'; +import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; import pkg from '../package.json'; import { defaultPluginConfig, SHEETS_FIND_REPLACE_PLUGIN_CONFIG_KEY } from './config/config'; import { SheetsFindReplaceController } from './controllers/sheet-find-replace.controller'; -@DependentOn(UniverSheetsPlugin, UniverSheetsPlugin, UniverFindReplacePlugin) +@DependentOn( + UniverRenderEnginePlugin, + UniverSheetsPlugin, + UniverFindReplacePlugin, + UniverSheetsUIPlugin +) export class UniverSheetsFindReplacePlugin extends Plugin { static override pluginName = 'SHEET_FIND_REPLACE_PLUGIN'; static override packageName = pkg.name; diff --git a/packages/sheets-formula-ui/src/plugin.ts b/packages/sheets-formula-ui/src/plugin.ts index 6930757071..be91ce9114 100644 --- a/packages/sheets-formula-ui/src/plugin.ts +++ b/packages/sheets-formula-ui/src/plugin.ts @@ -27,9 +27,12 @@ import { touchDependencies, UniverInstanceType, } from '@univerjs/core'; -import { IRenderManagerService } from '@univerjs/engine-render'; +import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; +import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula'; -import { BuiltInUIPart, connectInjector, IUIPartsService } from '@univerjs/ui'; +import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; +import { BuiltInUIPart, connectInjector, IUIPartsService, UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { FORMULA_UI_PLUGIN_NAME } from './common/plugin-name'; import { @@ -52,7 +55,14 @@ import { GlobalRangeSelector } from './views/range-selector/Global'; /** * The configuration of the formula UI plugin. */ -@DependentOn(UniverSheetsFormulaPlugin) +@DependentOn( + UniverFormulaEnginePlugin, + UniverRenderEnginePlugin, + UniverSheetsPlugin, + UniverUIPlugin, + UniverSheetsFormulaPlugin, + UniverSheetsUIPlugin +) export class UniverSheetsFormulaUIPlugin extends Plugin { static override pluginName = FORMULA_UI_PLUGIN_NAME; static override packageName = pkg.name; diff --git a/packages/sheets-hyper-link-ui/src/plugin.ts b/packages/sheets-hyper-link-ui/src/plugin.ts index b4701b5652..652ce44399 100644 --- a/packages/sheets-hyper-link-ui/src/plugin.ts +++ b/packages/sheets-hyper-link-ui/src/plugin.ts @@ -17,9 +17,14 @@ import type { Dependency, Workbook } from '@univerjs/core'; import type { IUniverSheetsHyperLinkUIConfig } from './config/config'; import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; +import { UniverDocsPlugin } from '@univerjs/docs'; import { UniverDocsUIPlugin } from '@univerjs/docs-ui'; -import { IRenderManagerService } from '@univerjs/engine-render'; +import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; +import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsHyperLinkPlugin } from '@univerjs/sheets-hyper-link'; +import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { defaultPluginConfig, SHEETS_HYPER_LINK_UI_PLUGIN_CONFIG_KEY } from './config/config'; import { SheetsHyperLinkAutoFillController } from './controllers/auto-fill.controller'; @@ -35,7 +40,16 @@ import { SheetsHyperLinkResolverService } from './services/resolver.service'; import { SheetsHyperLinkSidePanelService } from './services/side-panel.service'; import { SHEET_HYPER_LINK_UI_PLUGIN } from './types/const'; -@DependentOn(UniverSheetsHyperLinkPlugin, UniverDocsUIPlugin) +@DependentOn( + UniverDocsPlugin, + UniverFormulaEnginePlugin, + UniverRenderEnginePlugin, + UniverSheetsPlugin, + UniverUIPlugin, + UniverDocsUIPlugin, + UniverSheetsHyperLinkPlugin, + UniverSheetsUIPlugin +) export class UniverSheetsHyperLinkUIPlugin extends Plugin { static override pluginName: string = SHEET_HYPER_LINK_UI_PLUGIN; static override packageName = pkg.name; diff --git a/packages/sheets-hyper-link/src/plugin.ts b/packages/sheets-hyper-link/src/plugin.ts index 7e2cb5d5ac..c376890423 100644 --- a/packages/sheets-hyper-link/src/plugin.ts +++ b/packages/sheets-hyper-link/src/plugin.ts @@ -16,6 +16,7 @@ import type { IUniverSheetsHyperLinkConfig } from './config/config'; import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, registerDependencies, touchDependencies, UniverInstanceType } from '@univerjs/core'; +import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; import { UniverSheetsPlugin } from '@univerjs/sheets'; import pkg from '../package.json'; import { defaultPluginConfig, SHEETS_HYPER_LINK_PLUGIN_CONFIG_KEY } from './config/config'; @@ -28,7 +29,7 @@ import { HyperLinkModel } from './models/hyper-link.model'; import { SheetsHyperLinkParserService } from './services/parser.service'; import { SHEET_HYPER_LINK_PLUGIN } from './types/const'; -@DependentOn(UniverSheetsPlugin) +@DependentOn(UniverFormulaEnginePlugin, UniverSheetsPlugin) export class UniverSheetsHyperLinkPlugin extends Plugin { static override pluginName = SHEET_HYPER_LINK_PLUGIN; static override packageName = pkg.name; diff --git a/packages/sheets-note-ui/src/plugin.ts b/packages/sheets-note-ui/src/plugin.ts index 13659574e3..9a0bfdfedd 100644 --- a/packages/sheets-note-ui/src/plugin.ts +++ b/packages/sheets-note-ui/src/plugin.ts @@ -26,7 +26,11 @@ import { touchDependencies, UniverInstanceType, } from '@univerjs/core'; +import { UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsNotePlugin } from '@univerjs/sheets-note'; +import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { defaultPluginConfig, SHEETS_NOTE_UI_PLUGIN_CONFIG_KEY } from './config/config'; import { ComponentsController } from './controllers/components.controller'; @@ -36,7 +40,13 @@ import { SheetsNotePopupController } from './controllers/sheets-note-popup.contr import { SheetsNoteUIController } from './controllers/ui.controller'; import { SheetsNotePopupService } from './services/sheets-note-popup.service'; -@DependentOn(UniverSheetsNotePlugin) +@DependentOn( + UniverRenderEnginePlugin, + UniverSheetsPlugin, + UniverUIPlugin, + UniverSheetsNotePlugin, + UniverSheetsUIPlugin +) export class UniverSheetsNoteUIPlugin extends Plugin { static override pluginName = 'SHEET_NOTE_UI_PLUGIN'; static override packageName = pkg.name; diff --git a/packages/sheets-numfmt-ui/src/plugin.ts b/packages/sheets-numfmt-ui/src/plugin.ts index 910d74b870..c24c1d4d52 100644 --- a/packages/sheets-numfmt-ui/src/plugin.ts +++ b/packages/sheets-numfmt-ui/src/plugin.ts @@ -27,9 +27,11 @@ import { touchDependencies, UniverInstanceType, } from '@univerjs/core'; -import { IRenderManagerService } from '@univerjs/engine-render'; +import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsNumfmtPlugin } from '@univerjs/sheets-numfmt'; import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { defaultPluginConfig, SHEETS_NUMFMT_UI_PLUGIN_CONFIG_KEY } from './config/config'; import { ComponentsController } from './controllers/components.controller'; @@ -40,7 +42,13 @@ import { SheetNumfmtUIController } from './controllers/ui.controller'; import { UserHabitController } from './controllers/user-habit.controller'; import { NumfmtMenuController } from './menu/numfmt.menu.controller'; -@DependentOn(UniverSheetsUIPlugin, UniverSheetsNumfmtPlugin) +@DependentOn( + UniverRenderEnginePlugin, + UniverSheetsPlugin, + UniverUIPlugin, + UniverSheetsNumfmtPlugin, + UniverSheetsUIPlugin +) export class UniverSheetsNumfmtUIPlugin extends Plugin { static override pluginName = 'SHEET_NUMFMT_UI_PLUGIN'; static override packageName = pkg.name; diff --git a/packages/sheets-sort-ui/src/plugin.ts b/packages/sheets-sort-ui/src/plugin.ts index 0deb46d28c..f7954d2ad9 100644 --- a/packages/sheets-sort-ui/src/plugin.ts +++ b/packages/sheets-sort-ui/src/plugin.ts @@ -25,14 +25,17 @@ import { Plugin, UniverInstanceType, } from '@univerjs/core'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsSortPlugin } from '@univerjs/sheets-sort'; +import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { defaultPluginConfig, SHEETS_SORT_UI_PLUGIN_CONFIG_KEY } from './config/config'; import { ComponentsController } from './controllers/components.controller'; import { SheetsSortUIController } from './controllers/ui.controller'; import { SheetsSortUIService } from './services/sheets-sort-ui.service'; -@DependentOn(UniverSheetsSortPlugin) +@DependentOn(UniverSheetsPlugin, UniverUIPlugin, UniverSheetsSortPlugin, UniverSheetsUIPlugin) export class UniverSheetsSortUIPlugin extends Plugin { static override type = UniverInstanceType.UNIVER_SHEET; static override pluginName = 'SHEET_SORT_UI_PLUGIN'; diff --git a/packages/sheets-sort/src/plugin.ts b/packages/sheets-sort/src/plugin.ts index fe8b939491..2787a6077f 100644 --- a/packages/sheets-sort/src/plugin.ts +++ b/packages/sheets-sort/src/plugin.ts @@ -24,7 +24,7 @@ import { defaultPluginConfig, SHEETS_SORT_PLUGIN_CONFIG_KEY } from './config/con import { SheetsSortController } from './controllers/sheets-sort.controller'; import { SheetsSortService } from './services/sheets-sort.service'; -@DependentOn(UniverSheetsPlugin, UniverFormulaEnginePlugin) +@DependentOn(UniverFormulaEnginePlugin, UniverSheetsPlugin) export class UniverSheetsSortPlugin extends Plugin { static override type = UniverInstanceType.UNIVER_SHEET; static override pluginName = 'SHEET_SORT_PLUGIN'; diff --git a/packages/sheets-table-ui/src/plugin.ts b/packages/sheets-table-ui/src/plugin.ts index f5285ac60a..73d6de385e 100644 --- a/packages/sheets-table-ui/src/plugin.ts +++ b/packages/sheets-table-ui/src/plugin.ts @@ -17,8 +17,11 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverSheetsTableUIConfig } from './config/config'; import { DependentOn, ICommandService, IConfigService, Inject, Injector, merge, Plugin, registerDependencies, touchDependencies, UniverInstanceType } from '@univerjs/core'; -import { IRenderManagerService } from '@univerjs/engine-render'; +import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsTablePlugin } from '@univerjs/sheets-table'; +import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { OpenTableFilterPanelOperation } from './commands/operations/open-table-filter-dialog.opration'; import { OpenTableSelectorOperation } from './commands/operations/open-table-selector.operation'; @@ -34,7 +37,13 @@ import { SheetTableThemeUIController } from './controllers/sheet-table-theme-ui. import { SheetTableMenuController } from './menu/sheet-table-menu.controller'; import { SheetsTableUiService } from './services/sheets-table-ui.service'; -@DependentOn(UniverSheetsTablePlugin) +@DependentOn( + UniverRenderEnginePlugin, + UniverSheetsPlugin, + UniverUIPlugin, + UniverSheetsTablePlugin, + UniverSheetsUIPlugin +) export class UniverSheetsTableUIPlugin extends Plugin { static override pluginName = PLUGIN_NAME; static override packageName = pkg.name; diff --git a/packages/sheets-table/src/plugin.ts b/packages/sheets-table/src/plugin.ts index f57d973d40..9026a07267 100644 --- a/packages/sheets-table/src/plugin.ts +++ b/packages/sheets-table/src/plugin.ts @@ -15,7 +15,8 @@ */ import type { IUniverSheetsTableConfig } from './config/config'; -import { ICommandService, IConfigService, Inject, Injector, merge, Plugin, registerDependencies, touchDependencies, UniverInstanceType } from '@univerjs/core'; +import { DependentOn, ICommandService, IConfigService, Inject, Injector, merge, Plugin, registerDependencies, touchDependencies, UniverInstanceType } from '@univerjs/core'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import pkg from '../package.json'; import { AddSheetTableCommand } from './commands/commands/add-sheet-table.command'; import { AddTableThemeCommand } from './commands/commands/add-table-theme.command'; @@ -39,6 +40,7 @@ import { TableFilterController } from './controllers/table-filter.controller'; import { TableManager } from './models/table-manager'; import { SheetTableService } from './services/table.service'; +@DependentOn(UniverSheetsPlugin) export class UniverSheetsTablePlugin extends Plugin { static override pluginName = PLUGIN_NAME; static override packageName = pkg.name; diff --git a/packages/sheets-thread-comment-ui/src/plugin.ts b/packages/sheets-thread-comment-ui/src/plugin.ts index 3e0433ebe1..575c54f3fc 100644 --- a/packages/sheets-thread-comment-ui/src/plugin.ts +++ b/packages/sheets-thread-comment-ui/src/plugin.ts @@ -17,8 +17,13 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverSheetsThreadCommentUIConfig } from './config/config'; import { DependentOn, ICommandService, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; +import { UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsThreadCommentPlugin } from '@univerjs/sheets-thread-comment'; +import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; +import { UniverThreadCommentPlugin } from '@univerjs/thread-comment'; import { UniverThreadCommentUIPlugin } from '@univerjs/thread-comment-ui'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { ShowAddSheetCommentModalOperation, ToggleSheetCommentPanelOperation } from './commands/operations/comment.operation'; import { defaultPluginConfig, SHEETS_THREAD_COMMENT_UI_PLUGIN_CONFIG_KEY } from './config/config'; @@ -32,7 +37,15 @@ import { SheetsThreadCommentController } from './controllers/sheets-thread-comme import { SheetsThreadCommentPopupService } from './services/sheets-thread-comment-popup.service'; import { PLUGIN_NAME } from './types/const'; -@DependentOn(UniverThreadCommentUIPlugin, UniverSheetsThreadCommentPlugin) +@DependentOn( + UniverRenderEnginePlugin, + UniverThreadCommentPlugin, + UniverSheetsPlugin, + UniverUIPlugin, + UniverThreadCommentUIPlugin, + UniverSheetsThreadCommentPlugin, + UniverSheetsUIPlugin +) export class UniverSheetsThreadCommentUIPlugin extends Plugin { static override pluginName = PLUGIN_NAME; static override packageName = pkg.name; diff --git a/packages/sheets-thread-comment/src/plugin.ts b/packages/sheets-thread-comment/src/plugin.ts index 7abf7fffe3..30a2b91a90 100644 --- a/packages/sheets-thread-comment/src/plugin.ts +++ b/packages/sheets-thread-comment/src/plugin.ts @@ -27,6 +27,7 @@ import { touchDependencies, UniverInstanceType, } from '@univerjs/core'; +import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverThreadCommentPlugin } from '@univerjs/thread-comment'; import pkg from '../package.json'; import { defaultPluginConfig, SHEETS_THREAD_COMMENT_PLUGIN_CONFIG_KEY } from './config/config'; @@ -35,7 +36,7 @@ import { SheetsThreadCommentResourceController } from './controllers/sheets-thre import { SheetsThreadCommentModel } from './models/sheets-thread-comment.model'; import { SHEET_THREAD_COMMENT_BASE } from './types/const'; -@DependentOn(UniverThreadCommentPlugin) +@DependentOn(UniverThreadCommentPlugin, UniverSheetsPlugin) export class UniverSheetsThreadCommentPlugin extends Plugin { static override pluginName = SHEET_THREAD_COMMENT_BASE; static override packageName = pkg.name; diff --git a/packages/sheets-ui/src/mobile-plugin.ts b/packages/sheets-ui/src/mobile-plugin.ts index a06658d8c8..95cdbadebe 100644 --- a/packages/sheets-ui/src/mobile-plugin.ts +++ b/packages/sheets-ui/src/mobile-plugin.ts @@ -30,7 +30,10 @@ import { touchDependencies, UniverInstanceType, } from '@univerjs/core'; -import { IRenderManagerService } from '@univerjs/engine-render'; +import { UniverDocsPlugin } from '@univerjs/docs'; +import { UniverDocsUIPlugin } from '@univerjs/docs-ui'; +import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; +import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render'; import { IRefSelectionsService, RefSelectionsService, UniverSheetsPlugin } from '@univerjs/sheets'; import { UI_PLUGIN_CONFIG_KEY, UniverMobileUIPlugin } from '@univerjs/ui'; import { filter } from 'rxjs/operators'; @@ -126,7 +129,14 @@ import { SheetsRenderService } from './services/sheets-render.service'; import { ShortcutExperienceService } from './services/shortcut-experience.service'; import { IStatusBarService, StatusBarService } from './services/status-bar.service'; -@DependentOn(UniverSheetsPlugin, UniverMobileUIPlugin) +@DependentOn( + UniverDocsPlugin, + UniverFormulaEnginePlugin, + UniverRenderEnginePlugin, + UniverSheetsPlugin, + UniverMobileUIPlugin, + UniverDocsUIPlugin +) export class UniverSheetsMobileUIPlugin extends Plugin { static override pluginName = 'SHEET_UI_PLUGIN'; static override packageName = pkg.name; diff --git a/packages/sheets-ui/src/plugin.ts b/packages/sheets-ui/src/plugin.ts index 67ac43a24a..0e3f1ac68d 100644 --- a/packages/sheets-ui/src/plugin.ts +++ b/packages/sheets-ui/src/plugin.ts @@ -30,9 +30,12 @@ import { touchDependencies, UniverInstanceType, } from '@univerjs/core'; -import { IRenderManagerService } from '@univerjs/engine-render'; +import { UniverDocsPlugin } from '@univerjs/docs'; +import { UniverDocsUIPlugin } from '@univerjs/docs-ui'; +import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; +import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render'; import { IRefSelectionsService, RefSelectionsService, UniverSheetsPlugin } from '@univerjs/sheets'; -import { UI_PLUGIN_CONFIG_KEY } from '@univerjs/ui'; +import { UI_PLUGIN_CONFIG_KEY, UniverUIPlugin } from '@univerjs/ui'; import { filter } from 'rxjs/operators'; import pkg from '../package.json'; import { defaultPluginConfig, SHEETS_UI_PLUGIN_CONFIG_KEY } from './config/config'; @@ -123,7 +126,14 @@ import { SheetsRenderService } from './services/sheets-render.service'; import { ShortcutExperienceService } from './services/shortcut-experience.service'; import { IStatusBarService, StatusBarService } from './services/status-bar.service'; -@DependentOn(UniverSheetsPlugin) +@DependentOn( + UniverDocsPlugin, + UniverFormulaEnginePlugin, + UniverRenderEnginePlugin, + UniverSheetsPlugin, + UniverUIPlugin, + UniverDocsUIPlugin +) export class UniverSheetsUIPlugin extends Plugin { static override pluginName = 'SHEET_UI_PLUGIN'; static override packageName = pkg.name; diff --git a/packages/slides-ui/src/plugin.ts b/packages/slides-ui/src/plugin.ts index 90c2f465d5..4b1071f502 100644 --- a/packages/slides-ui/src/plugin.ts +++ b/packages/slides-ui/src/plugin.ts @@ -18,6 +18,7 @@ import type { Dependency } from '@univerjs/core'; import type { SlideDataModel } from '@univerjs/slides'; import type { IUniverSlidesUIConfig } from './config/config'; import { + DependentOn, IConfigService, Inject, Injector, @@ -27,7 +28,12 @@ import { Plugin, UniverInstanceType, } from '@univerjs/core'; -import { IRenderManagerService } from '@univerjs/engine-render'; +import { UniverDocsPlugin } from '@univerjs/docs'; +import { UniverDocsUIPlugin } from '@univerjs/docs-ui'; +import { UniverDrawingPlugin } from '@univerjs/drawing'; +import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverSlidesPlugin } from '@univerjs/slides'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { defaultPluginConfig, SLIDES_UI_PLUGIN_CONFIG_KEY } from './config/config'; import { CanvasView } from './controllers/canvas-view'; @@ -42,6 +48,14 @@ import { ISlideEditorManagerService, SlideEditorManagerService } from './service import { SlideCanvasPopMangerService } from './services/slide-popup-manager.service'; import { SlideRenderService } from './services/slide-render.service'; +@DependentOn( + UniverDocsPlugin, + UniverDrawingPlugin, + UniverRenderEnginePlugin, + UniverSlidesPlugin, + UniverUIPlugin, + UniverDocsUIPlugin +) export class UniverSlidesUIPlugin extends Plugin { static override pluginName = 'UNIVER_SLIDES_UI_PLUGIN'; static override packageName = pkg.name; diff --git a/packages/slides/src/plugin.ts b/packages/slides/src/plugin.ts index 98f717fdb3..70b33c1caa 100644 --- a/packages/slides/src/plugin.ts +++ b/packages/slides/src/plugin.ts @@ -16,8 +16,8 @@ import type { Dependency } from '@univerjs/core'; import type { Engine } from '@univerjs/engine-render'; -import { IConfigService, Inject, Injector, IUniverInstanceService, merge, Plugin, UniverInstanceType } from '@univerjs/core'; -import { IRenderingEngine } from '@univerjs/engine-render'; +import { DependentOn, IConfigService, Inject, Injector, IUniverInstanceService, merge, Plugin, UniverInstanceType } from '@univerjs/core'; +import { IRenderingEngine, UniverRenderEnginePlugin } from '@univerjs/engine-render'; import pkg from '../package.json'; import { defaultPluginConfig, SLIDES_PLUGIN_CONFIG_KEY } from './config/config'; import { SlideDataModel } from './data-model/slide-data-model'; @@ -26,6 +26,7 @@ import { SlideDataModel } from './data-model/slide-data-model'; export interface IUniverSlidesConfig {} +@DependentOn(UniverRenderEnginePlugin) export class UniverSlidesPlugin extends Plugin { static override pluginName = 'UNIVER_SLIDES_PLUGIN'; static override packageName = pkg.name; diff --git a/packages/thread-comment-ui/src/plugin.ts b/packages/thread-comment-ui/src/plugin.ts index e7dc1df4d3..20fa5d2618 100644 --- a/packages/thread-comment-ui/src/plugin.ts +++ b/packages/thread-comment-ui/src/plugin.ts @@ -17,14 +17,16 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverThreadCommentUIConfig } from './config/config'; import { DependentOn, ICommandService, IConfigService, Inject, Injector, merge, mergeOverrideWithDependencies, Plugin, UniverInstanceType } from '@univerjs/core'; +import { UniverDocsUIPlugin } from '@univerjs/docs-ui'; import { UniverThreadCommentPlugin } from '@univerjs/thread-comment'; +import { UniverUIPlugin } from '@univerjs/ui'; import pkg from '../package.json'; import { SetActiveCommentOperation } from './commands/operations/comment.operations'; import { defaultPluginConfig, THREAD_COMMENT_UI_PLUGIN_CONFIG_KEY } from './config/config'; import { ThreadCommentPanelService } from './services/thread-comment-panel.service'; import { PLUGIN_NAME } from './types/const'; -@DependentOn(UniverThreadCommentPlugin) +@DependentOn(UniverThreadCommentPlugin, UniverUIPlugin, UniverDocsUIPlugin) export class UniverThreadCommentUIPlugin extends Plugin { static override pluginName = PLUGIN_NAME; static override packageName = pkg.name; diff --git a/packages/watermark/src/plugin.ts b/packages/watermark/src/plugin.ts index f7a901d351..3a74c31d16 100644 --- a/packages/watermark/src/plugin.ts +++ b/packages/watermark/src/plugin.ts @@ -17,14 +17,15 @@ import type { Dependency } from '@univerjs/core'; import type { IWatermarkConfigWithType } from '@univerjs/engine-render'; import type { IUniverWatermarkConfig } from './config/config'; -import { IConfigService, ILocalStorageService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; -import { IRenderManagerService, IWatermarkTypeEnum, UNIVER_WATERMARK_STORAGE_KEY } from '@univerjs/engine-render'; +import { DependentOn, IConfigService, ILocalStorageService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core'; +import { IRenderManagerService, IWatermarkTypeEnum, UNIVER_WATERMARK_STORAGE_KEY, UniverRenderEnginePlugin } from '@univerjs/engine-render'; import pkg from '../package.json'; import { WatermarkImageBaseConfig, WatermarkTextBaseConfig, WatermarkUserInfoBaseConfig } from './common/const'; import { defaultPluginConfig, WATERMARK_PLUGIN_CONFIG_KEY } from './config/config'; import { WatermarkRenderController } from './controllers/watermark.render.controller'; import { WatermarkService } from './services/watermark.service'; +@DependentOn(UniverRenderEnginePlugin) export class UniverWatermarkPlugin extends Plugin { static override pluginName = 'UNIVER_WATERMARK_PLUGIN'; static override packageName = pkg.name; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c6f981ed82..53d8988fff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -70,8 +70,8 @@ importers: specifier: ^9.1.7 version: 9.1.7 lint-staged: - specifier: ^17.0.8 - version: 17.0.8 + specifier: ^17.2.0 + version: 17.2.0 posthog-node: specifier: ^5.46.1 version: 5.46.1(rxjs@7.8.2) @@ -472,6 +472,9 @@ importers: '@univerjs/docs-drawing-ui': specifier: workspace:* version: link:../packages/docs-drawing-ui + '@univerjs/docs-find-replace': + specifier: workspace:* + version: link:../packages/docs-find-replace '@univerjs/docs-hyper-link-ui': specifier: workspace:* version: link:../packages/docs-hyper-link-ui @@ -1027,6 +1030,43 @@ importers: specifier: ^4.1.10 version: 4.1.10(@types/node@26.1.1)(@vitest/coverage-istanbul@4.1.10)(happy-dom@20.11.1)(jsdom@29.1.1)(vite@8.0.11(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.87.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + packages/docs-find-replace: + dependencies: + '@univerjs/core': + specifier: workspace:* + version: link:../core + '@univerjs/docs': + specifier: workspace:* + version: link:../docs + '@univerjs/docs-ui': + specifier: workspace:* + version: link:../docs-ui + '@univerjs/engine-render': + specifier: workspace:* + version: link:../engine-render + '@univerjs/find-replace': + specifier: workspace:* + version: link:../find-replace + '@univerjs/ui': + specifier: workspace:* + version: link:../ui + devDependencies: + '@univerjs-infra/shared': + specifier: workspace:* + version: link:../../common/shared + react: + specifier: 19.2.8 + version: 19.2.8 + rxjs: + specifier: ^7.8.2 + version: 7.8.2 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + vitest: + specifier: ^4.1.10 + version: 4.1.10(@types/node@26.1.1)(@vitest/coverage-istanbul@4.1.10)(happy-dom@20.11.1)(jsdom@29.1.1)(vite@8.0.11(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.87.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + packages/docs-hyper-link: dependencies: '@univerjs/core': @@ -1966,9 +2006,6 @@ importers: '@univerjs-infra/shared': specifier: workspace:* version: link:../../common/shared - '@univerjs/engine-formula': - specifier: workspace:* - version: link:../engine-formula postcss: specifier: ^8.5.23 version: 8.5.23 @@ -7152,10 +7189,6 @@ packages: ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} - ansi-escapes@7.3.0: - resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} - engines: {node: '>=18'} - ansi-html-community@0.0.8: resolution: {integrity: sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==} engines: {'0': node >= 0.8.0} @@ -7558,14 +7591,6 @@ packages: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} - cli-cursor@5.0.0: - resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} - engines: {node: '>=18'} - - cli-truncate@5.2.0: - resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} - engines: {node: '>=20'} - clipboardy@3.0.0: resolution: {integrity: sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -8023,10 +8048,6 @@ packages: resolution: {integrity: sha512-pxP8eL2SwwaTRi/KHYwLYXinDs7gL3jxFcBYmEdYfZmZXbaVDvdppd0XBU8qVz03rDfKZMXg1omHCbsJjZrMsw==} engines: {node: '>=20'} - environment@1.1.0: - resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} - engines: {node: '>=18'} - error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -8444,9 +8465,6 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - eventemitter3@5.0.4: - resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} - events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -8935,10 +8953,6 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-fullwidth-code-point@5.1.0: - resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} - engines: {node: '>=18'} - is-generator-function@1.1.0: resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} engines: {node: '>= 0.4'} @@ -9353,15 +9367,11 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - lint-staged@17.0.8: - resolution: {integrity: sha512-B2P/d+jVW0UXOQ0MVMLrB/9ydA1P+zz6jYfdrbbEd9ur3S2rcbduFWKiUCC02Sm5hbC8nrm7y24WuYMG54HfxA==} + lint-staged@17.2.0: + resolution: {integrity: sha512-FchGnFe4i4B1C/a35SPU9bNGPEHSC1+1iV0plLjzBmKVe9klZrlRfSgK6Cw4VeHyqOXbJUXP0vON61uRftNQ0A==} engines: {node: '>=22.22.1'} hasBin: true - listr2@10.2.1: - resolution: {integrity: sha512-7I5knELsJKTUjXG+A6BkKAiGkW1i25fNa/xlUl9hFtk15WbE9jndA89xu5FzQKrY5llajE1hfZZFMILXkDHk/Q==} - engines: {node: '>=22.13.0'} - lit-element@4.2.2: resolution: {integrity: sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==} @@ -9400,10 +9410,6 @@ packages: lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} - log-update@6.1.0: - resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} - engines: {node: '>=18'} - long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} @@ -9640,10 +9646,6 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - mimic-function@5.0.1: - resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} - engines: {node: '>=18'} - min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -9826,10 +9828,6 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - onetime@7.0.0: - resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} - engines: {node: '>=18'} - open@10.2.0: resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} engines: {node: '>=18'} @@ -10582,10 +10580,6 @@ packages: resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true - restore-cursor@5.1.0: - resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} - engines: {node: '>=18'} - retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} @@ -10594,9 +10588,6 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rfdc@1.4.1: - resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -10770,14 +10761,6 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - slice-ansi@7.1.2: - resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} - engines: {node: '>=18'} - - slice-ansi@8.0.0: - resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} - engines: {node: '>=20'} - sonner@2.0.7: resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} peerDependencies: @@ -10866,10 +10849,6 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} - string-width@8.2.1: - resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} - engines: {node: '>=20'} - string.prototype.matchall@4.0.12: resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} engines: {node: '>= 0.4'} @@ -11572,10 +11551,6 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - wrap-ansi@10.0.0: - resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} - engines: {node: '>=20'} - wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -14932,10 +14907,6 @@ snapshots: dependencies: string-width: 4.2.3 - ansi-escapes@7.3.0: - dependencies: - environment: 1.1.0 - ansi-html-community@0.0.8: {} ansi-regex@5.0.1: {} @@ -15315,15 +15286,6 @@ snapshots: cli-boxes@3.0.0: {} - cli-cursor@5.0.0: - dependencies: - restore-cursor: 5.1.0 - - cli-truncate@5.2.0: - dependencies: - slice-ansi: 8.0.0 - string-width: 8.2.1 - clipboardy@3.0.0: dependencies: arch: 2.2.0 @@ -15749,8 +15711,6 @@ snapshots: dependencies: is-safe-filename: 0.1.1 - environment@1.1.0: {} - error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -16404,8 +16364,6 @@ snapshots: esutils@2.0.3: {} - eventemitter3@5.0.4: {} - events@3.3.0: {} execa@5.1.1: @@ -16899,10 +16857,6 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-fullwidth-code-point@5.1.0: - dependencies: - get-east-asian-width: 1.6.0 - is-generator-function@1.1.0: dependencies: call-bound: 1.0.4 @@ -17291,23 +17245,14 @@ snapshots: lines-and-columns@1.2.4: {} - lint-staged@17.0.8: + lint-staged@17.2.0: dependencies: - listr2: 10.2.1 - picomatch: 4.0.4 + picomatch: 4.0.5 string-argv: 0.3.2 tinyexec: 1.2.4 optionalDependencies: yaml: 2.9.0 - listr2@10.2.1: - dependencies: - cli-truncate: 5.2.0 - eventemitter3: 5.0.4 - log-update: 6.1.0 - rfdc: 1.4.1 - wrap-ansi: 10.0.0 - lit-element@4.2.2: dependencies: '@lit-labs/ssr-dom-shim': 1.5.0 @@ -17348,14 +17293,6 @@ snapshots: lodash@4.18.1: {} - log-update@6.1.0: - dependencies: - ansi-escapes: 7.3.0 - cli-cursor: 5.0.0 - slice-ansi: 7.1.2 - strip-ansi: 7.2.0 - wrap-ansi: 9.0.2 - long@5.3.2: {} longest-streak@3.1.0: {} @@ -17782,8 +17719,6 @@ snapshots: mimic-fn@2.1.0: {} - mimic-function@5.0.1: {} - min-indent@1.0.1: {} minimatch@10.2.5: @@ -17948,10 +17883,6 @@ snapshots: dependencies: mimic-fn: 2.1.0 - onetime@7.0.0: - dependencies: - mimic-function: 5.0.1 - open@10.2.0: dependencies: default-browser: 5.5.0 @@ -18862,17 +18793,10 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - restore-cursor@5.1.0: - dependencies: - onetime: 7.0.0 - signal-exit: 4.1.0 - retry@0.13.1: {} reusify@1.1.0: {} - rfdc@1.4.1: {} - rimraf@3.0.2: dependencies: glob: 7.2.3 @@ -19155,16 +19079,6 @@ snapshots: slash@3.0.0: {} - slice-ansi@7.1.2: - dependencies: - ansi-styles: 6.2.3 - is-fullwidth-code-point: 5.1.0 - - slice-ansi@8.0.0: - dependencies: - ansi-styles: 6.2.3 - is-fullwidth-code-point: 5.1.0 - sonner@2.0.7(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: react: 19.2.8 @@ -19258,11 +19172,6 @@ snapshots: get-east-asian-width: 1.6.0 strip-ansi: 7.2.0 - string-width@8.2.1: - dependencies: - get-east-asian-width: 1.6.0 - strip-ansi: 7.2.0 - string.prototype.matchall@4.0.12: dependencies: call-bind: 1.0.8 @@ -20030,12 +19939,6 @@ snapshots: word-wrap@1.2.5: {} - wrap-ansi@10.0.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 8.2.1 - strip-ansi: 7.2.0 - wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0