mirror of
https://github.com/dream-num/univer.git
synced 2026-08-28 23:01:30 +08:00
feat(docs-find-replace): add find/replace plugin for Univer Docs (#7346)
This commit is contained in:
@@ -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:*",
|
||||
|
||||
@@ -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);
|
||||
|
||||
+1
-1
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
@@ -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. <developer@univer.ai>",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
+136
@@ -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> = {}): 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();
|
||||
});
|
||||
});
|
||||
+10
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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<IDocsReplaceCommandParams, IReplaceAllResult> = {
|
||||
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<DocumentDataModel>(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 };
|
||||
},
|
||||
};
|
||||
+51
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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> = {}): 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 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)),
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -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<typeof import('@univerjs/docs-ui')>();
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -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<ITextRange> {
|
||||
provider: typeof DOCS_FIND_REPLACE_PROVIDER;
|
||||
range: ITextRange;
|
||||
replaceable: boolean;
|
||||
}
|
||||
|
||||
export class DocsFindModel extends FindModel {
|
||||
private readonly _matchesUpdate$ = new Subject<IDocFindMatch[]>();
|
||||
override readonly matchesUpdate$ = this._matchesUpdate$.asObservable();
|
||||
private readonly _activelyChangingMatch$ = new Subject<IDocFindMatch>();
|
||||
override readonly activelyChangingMatch$ = this._activelyChangingMatch$.asObservable();
|
||||
private _matches: IDocFindMatch[] = [];
|
||||
private _position = -1;
|
||||
private _query: Nullable<IFindQuery> = 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<IDocFindMatch> {
|
||||
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<boolean> {
|
||||
const current = this.currentMatch;
|
||||
if (!current?.replaceable || !this._query) return false;
|
||||
const result = await this._commandService.executeCommand<IDocsReplaceCommandParams, IReplaceAllResult>(DocsReplaceCommand.id, {
|
||||
unitId: this.unitId,
|
||||
query: this._query,
|
||||
replaceString,
|
||||
range: current.range,
|
||||
});
|
||||
return result?.success === 1;
|
||||
}
|
||||
|
||||
override async replaceAll(replaceString: string): Promise<IReplaceAllResult> {
|
||||
if (!this._query) return { success: 0, failure: 0 };
|
||||
return await this._commandService.executeCommand<IDocsReplaceCommandParams, IReplaceAllResult>(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<Documents>;
|
||||
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 = [];
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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<DocsFindModel> = 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<DocsFindModel[]> {
|
||||
this.terminate();
|
||||
const doc = this._instanceService.getCurrentUnitOfType<DocumentDataModel>(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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@univerjs-infra/shared/tsconfigs/base",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import createConfig from '@univerjs-infra/shared/vitest';
|
||||
|
||||
export default createConfig();
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Workbook>(focused, UniverInstanceType.UNIVER_SHEET)) {
|
||||
if (!focused || focused !== sessionUnitId) {
|
||||
this.closePanel();
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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: 'البحث حسب الصف',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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: 'جستجو بر اساس ردیف',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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: '行ごとに検索',
|
||||
|
||||
@@ -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: '행 기준 검색',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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: 'Искать по строкам',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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': '当前子表',
|
||||
|
||||
@@ -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: '按列搜尋',
|
||||
|
||||
@@ -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: '按列搜尋',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<unknown>();
|
||||
focusedUnitId: string | null = 'u1';
|
||||
readonly focused$ = new Subject<string | null>();
|
||||
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<FindModel[]> {
|
||||
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]);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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<FindModel[]>;
|
||||
terminate(): void;
|
||||
}
|
||||
@@ -116,6 +127,7 @@ export interface IFindReplaceService {
|
||||
readonly replaceables$: Observable<IReplaceableMatch[]>;
|
||||
|
||||
readonly focusSignal$: Observable<void>;
|
||||
readonly providerCapabilities$: Observable<Nullable<IFindReplaceProviderCapabilities>>;
|
||||
|
||||
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<IFindReplaceStat
|
||||
if (typeof statusUpdate.inputtingFindString !== 'undefined') return true;
|
||||
if (typeof statusUpdate.findDirection !== 'undefined') return true;
|
||||
if (typeof statusUpdate.matchesTheWholeCell !== 'undefined') return true;
|
||||
if (typeof statusUpdate.matchesTheWholeWord !== 'undefined') return true;
|
||||
if (typeof statusUpdate.caseSensitive !== 'undefined') return true;
|
||||
if (typeof statusUpdate.findScope !== 'undefined') return true;
|
||||
if (typeof statusUpdate.findBy !== 'undefined') return true;
|
||||
@@ -293,6 +308,7 @@ export class FindReplaceModel extends Disposable {
|
||||
replaceRevealed: this._state.replaceRevealed,
|
||||
caseSensitive: this._state.caseSensitive,
|
||||
matchesTheWholeCell: this._state.matchesTheWholeCell,
|
||||
matchesTheWholeWord: this._state.matchesTheWholeWord,
|
||||
})))
|
||||
).flat());
|
||||
|
||||
@@ -520,6 +536,7 @@ export interface IFindReplaceState {
|
||||
|
||||
caseSensitive: boolean;
|
||||
matchesTheWholeCell: boolean;
|
||||
matchesTheWholeWord: boolean;
|
||||
findDirection: FindDirection;
|
||||
findScope: FindScope;
|
||||
findBy: FindBy;
|
||||
@@ -537,6 +554,7 @@ export function createInitFindReplaceState(): IFindReplaceState {
|
||||
matchesCount: 0,
|
||||
matchesPosition: 0,
|
||||
matchesTheWholeCell: false,
|
||||
matchesTheWholeWord: false,
|
||||
replaceRevealed: false,
|
||||
replaceString: '',
|
||||
revealed: true,
|
||||
@@ -567,6 +585,7 @@ export class FindReplaceState {
|
||||
private _matchesCount = 0;
|
||||
private _caseSensitive = true;
|
||||
private _matchesTheWholeCell = false;
|
||||
private _matchesTheWholeWord = false;
|
||||
private _findDirection = FindDirection.ROW;
|
||||
private _findScope = FindScope.SUBUNIT;
|
||||
private _findBy = FindBy.VALUE;
|
||||
@@ -581,6 +600,7 @@ export class FindReplaceState {
|
||||
get replaceString(): string { return this._replaceString; }
|
||||
get caseSensitive(): boolean { return this._caseSensitive; }
|
||||
get matchesTheWholeCell(): boolean { return this._matchesTheWholeCell; }
|
||||
get matchesTheWholeWord(): boolean { return this._matchesTheWholeWord; }
|
||||
get findDirection(): FindDirection { return this._findDirection; }
|
||||
get findScope(): FindScope { return this._findScope; }
|
||||
get findBy(): FindBy { return this._findBy; }
|
||||
@@ -657,6 +677,12 @@ export class FindReplaceState {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (typeof changes.matchesTheWholeWord !== 'undefined' && changes.matchesTheWholeWord !== this._matchesTheWholeWord) {
|
||||
this._matchesTheWholeWord = changes.matchesTheWholeWord;
|
||||
changedState.matchesTheWholeWord = changes.matchesTheWholeWord;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (typeof changes.inputtingFindString !== 'undefined' && changes.inputtingFindString !== this._inputtingFindString) {
|
||||
this._inputtingFindString = changes.inputtingFindString;
|
||||
changedState.inputtingFindString = changes.inputtingFindString;
|
||||
@@ -681,6 +707,7 @@ export class FindReplaceState {
|
||||
matchesCount: this._matchesCount,
|
||||
matchesPosition: this._matchesPosition,
|
||||
matchesTheWholeCell: this._matchesTheWholeCell,
|
||||
matchesTheWholeWord: this._matchesTheWholeWord,
|
||||
replaceRevealed: this._replaceRevealed,
|
||||
revealed: this._revealed,
|
||||
});
|
||||
@@ -709,13 +736,23 @@ export class FindReplaceService extends Disposable implements IFindReplaceServic
|
||||
private readonly _focusSignal$ = new Subject<void>();
|
||||
readonly focusSignal$ = this._focusSignal$.asObservable();
|
||||
|
||||
private readonly _providerCapabilities$ = new BehaviorSubject<Nullable<IFindReplaceProviderCapabilities>>(null);
|
||||
readonly providerCapabilities$ = this._providerCapabilities$.asObservable();
|
||||
private _activeProvider: Nullable<IFindReplaceProvider> = 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<IFindReplaceProvider> {
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<unknown>) {
|
||||
function useFindInputFocus(findReplaceService: IFindReplaceService, ref: Parameters<typeof useImperativeHandle>[0]) {
|
||||
const focus = useCallback(() => {
|
||||
(document.querySelector('.univer-find-input input') as Nullable<HTMLInputElement>)?.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)}
|
||||
/>
|
||||
</FormLayout>
|
||||
<FormLayout label={localeService.t<LocaleKey>('find-replace.dialog.find-direction.title')}>
|
||||
<Select value={findDirection} options={findDirectionOptions} onChange={onChangeFindDirection} />
|
||||
</FormLayout>
|
||||
<FormDualColumnLayout>
|
||||
<>
|
||||
<FormLayout label={localeService.t<LocaleKey>('find-replace.dialog.find-scope.title')}>
|
||||
<Select value={findScope} options={findScopeOptions} onChange={onChangeFindScope} />
|
||||
</FormLayout>
|
||||
<FormLayout label={localeService.t<LocaleKey>('find-replace.dialog.find-by.title')}>
|
||||
<Select value={findBy} options={findByOptions} onChange={onChangeFindBy} />
|
||||
</FormLayout>
|
||||
</>
|
||||
</FormDualColumnLayout>
|
||||
<FormDualColumnLayout>
|
||||
<>
|
||||
<FormLayout>
|
||||
<Checkbox
|
||||
checked={caseSensitive}
|
||||
onChange={(checked) => {
|
||||
findReplaceService.changeCaseSensitive(checked as boolean);
|
||||
}}
|
||||
>
|
||||
{localeService.t<LocaleKey>('find-replace.dialog.case-sensitive')}
|
||||
</Checkbox>
|
||||
</FormLayout>
|
||||
<FormLayout>
|
||||
<Checkbox
|
||||
checked={matchesTheWholeCell}
|
||||
onChange={(checked) => {
|
||||
findReplaceService.changeMatchesTheWholeCell(checked as boolean);
|
||||
}}
|
||||
>
|
||||
{localeService.t<LocaleKey>('find-replace.dialog.match-the-whole-cell')}
|
||||
</Checkbox>
|
||||
</FormLayout>
|
||||
</>
|
||||
</FormDualColumnLayout>
|
||||
{capabilities?.findDirection && (
|
||||
<FormLayout label={localeService.t<LocaleKey>('find-replace.dialog.find-direction.title')}>
|
||||
<Select value={findDirection} options={findDirectionOptions} onChange={onChangeFindDirection} />
|
||||
</FormLayout>
|
||||
)}
|
||||
{(capabilities?.findScope || capabilities?.findBy) && (
|
||||
<FormDualColumnLayout>
|
||||
<>
|
||||
{capabilities.findScope && (
|
||||
<FormLayout label={localeService.t<LocaleKey>('find-replace.dialog.find-scope.title')}>
|
||||
<Select value={findScope} options={findScopeOptions} onChange={onChangeFindScope} />
|
||||
</FormLayout>
|
||||
)}
|
||||
{capabilities.findBy && (
|
||||
<FormLayout label={localeService.t<LocaleKey>('find-replace.dialog.find-by.title')}>
|
||||
<Select value={findBy} options={findByOptions} onChange={onChangeFindBy} />
|
||||
</FormLayout>
|
||||
)}
|
||||
</>
|
||||
</FormDualColumnLayout>
|
||||
)}
|
||||
{(capabilities?.caseSensitive || capabilities?.matchesTheWholeCell || capabilities?.matchesTheWholeWord) && (
|
||||
<FormDualColumnLayout>
|
||||
<>
|
||||
{capabilities.caseSensitive && (
|
||||
<FormLayout>
|
||||
<Checkbox
|
||||
checked={caseSensitive}
|
||||
onChange={(checked) => {
|
||||
findReplaceService.changeCaseSensitive(checked as boolean);
|
||||
if (findCompleted) findReplaceService.find();
|
||||
}}
|
||||
>
|
||||
{localeService.t<LocaleKey>('find-replace.dialog.case-sensitive')}
|
||||
</Checkbox>
|
||||
</FormLayout>
|
||||
)}
|
||||
{capabilities.matchesTheWholeCell && (
|
||||
<FormLayout>
|
||||
<Checkbox checked={matchesTheWholeCell} onChange={(checked) => findReplaceService.changeMatchesTheWholeCell(checked as boolean)}>
|
||||
{localeService.t<LocaleKey>('find-replace.dialog.match-the-whole-cell')}
|
||||
</Checkbox>
|
||||
</FormLayout>
|
||||
)}
|
||||
{capabilities.matchesTheWholeWord && (
|
||||
<FormLayout>
|
||||
<Checkbox
|
||||
checked={matchesTheWholeWord}
|
||||
onChange={(checked) => {
|
||||
findReplaceService.changeMatchesTheWholeWord(checked as boolean);
|
||||
if (findCompleted) findReplaceService.find();
|
||||
}}
|
||||
>
|
||||
{localeService.t<LocaleKey>('find-replace.dialog.match-the-whole-word')}
|
||||
</Checkbox>
|
||||
</FormLayout>
|
||||
)}
|
||||
</>
|
||||
</FormDualColumnLayout>
|
||||
)}
|
||||
<div className="univer-mt-6 univer-flex univer-justify-between">
|
||||
<Button variant="primary" onClick={onClickFindButton} disabled={findDisabled}>{localeService.t<LocaleKey>('find-replace.dialog.find')}</Button>
|
||||
<span className="univer-inline-flex univer-gap-2">
|
||||
@@ -266,12 +289,11 @@ export function FindReplaceDialog() {
|
||||
|
||||
const dialogContainerRef = useRef<HTMLDivElement>(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<ISubFormRef>(null);
|
||||
|
||||
@@ -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<IFindReplaceService['registerFindReplaceProvider']>[0];
|
||||
type FindQuery = Parameters<FindProvider['find']>[0];
|
||||
type FindCapabilities = FindProvider['capabilities'];
|
||||
type FindMoveParams = Parameters<FindModel['moveToNextMatch']>[0];
|
||||
type FindMatch = NonNullable<ReturnType<FindModel['moveToNextMatch']>> & { range: ICellRange };
|
||||
type ReplaceAllResult = Awaited<ReturnType<FindModel['replaceAll']>>;
|
||||
|
||||
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<IFindMatch<ICellRange>[]>([]);
|
||||
private readonly _activelyChangingMatch$ = new Subject<IFindMatch<ICellRange>>();
|
||||
private _matches: IFindMatch<ICellRange>[] = [];
|
||||
private readonly _matchesUpdate$ = new BehaviorSubject<FindMatch[]>([]);
|
||||
private readonly _activelyChangingMatch$ = new Subject<FindMatch>();
|
||||
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<ICellRange>[] {
|
||||
override getMatches(): FindMatch[] {
|
||||
return this._matches;
|
||||
}
|
||||
|
||||
override moveToNextMatch(params?: IFindMoveParams): IFindMatch<ICellRange> | 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<ICellRange> | 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<IReplaceAllResult> {
|
||||
override async replaceAll(replaceString: string): Promise<ReplaceAllResult> {
|
||||
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<ICellRange> {
|
||||
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<FindModel[]> {
|
||||
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<FindModel[]> {
|
||||
this.queries.push({ ...query });
|
||||
return [new TextFindModel(this.rows, query)];
|
||||
}
|
||||
@@ -224,19 +239,19 @@ class TestLayoutService {
|
||||
this._focused = true;
|
||||
}
|
||||
|
||||
registerFocusHandler(): IDisposable {
|
||||
registerFocusHandler(): ReturnType<typeof toDisposable> {
|
||||
return toDisposable(() => undefined);
|
||||
}
|
||||
|
||||
registerRootContainerElement(): IDisposable {
|
||||
registerRootContainerElement(): ReturnType<typeof toDisposable> {
|
||||
return toDisposable(() => undefined);
|
||||
}
|
||||
|
||||
registerContentElement(): IDisposable {
|
||||
registerContentElement(): ReturnType<typeof toDisposable> {
|
||||
return toDisposable(() => undefined);
|
||||
}
|
||||
|
||||
registerContainerElement(): IDisposable {
|
||||
registerContainerElement(): ReturnType<typeof toDisposable> {
|
||||
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<ReturnType<typeof createTestBed>['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<typeof createRoot> | 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<HTMLInputElement>('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<IFindMatch> {
|
||||
let value: Nullable<IFindMatch> = 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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
+54
-45
@@ -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<IWorkbookData, Workbook>(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, {
|
||||
|
||||
+2
-24
@@ -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 }]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
+3
-28
@@ -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 };
|
||||
|
||||
+5
-29
@@ -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 {
|
||||
|
||||
@@ -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<IWorkbookData, UnitModel<IWorkbookData>>(UniverInstanceType.UNIVER_SHEET, workbookData || getTestWorkbookDataDemo());
|
||||
const univerInstanceService = injector.get(IUniverInstanceService);
|
||||
univerInstanceService.focusUnit('test');
|
||||
|
||||
@@ -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<IWorkbookData, UnitModel<IWorkbookData>>(UniverInstanceType.UNIVER_SHEET, workbookData || getTestWorkbookDataDemo());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -25,6 +25,7 @@ describe('hitCell', () => {
|
||||
findString: 'abc',
|
||||
caseSensitive: false,
|
||||
matchesTheWholeCell: false,
|
||||
matchesTheWholeWord: false,
|
||||
findDirection: FindDirection.ROW,
|
||||
findScope: FindScope.SUBUNIT,
|
||||
findBy: FindBy.VALUE,
|
||||
|
||||
+1
@@ -70,6 +70,7 @@ describe('SheetFindModel navigation', () => {
|
||||
findString: 'a',
|
||||
caseSensitive: false,
|
||||
matchesTheWholeCell: false,
|
||||
matchesTheWholeWord: false,
|
||||
findDirection: FindDirection.ROW,
|
||||
findScope: FindScope.SUBUNIT,
|
||||
findBy: FindBy.VALUE,
|
||||
|
||||
+1
@@ -45,6 +45,7 @@ const baseQuery: IFindQuery = {
|
||||
findString: 'alpha',
|
||||
caseSensitive: false,
|
||||
matchesTheWholeCell: false,
|
||||
matchesTheWholeWord: false,
|
||||
findDirection: FindDirection.ROW,
|
||||
findScope: FindScope.SUBUNIT,
|
||||
findBy: FindBy.VALUE,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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<SheetFindModel[]> {
|
||||
this._terminate();
|
||||
|
||||
|
||||
@@ -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<FindModel[]> {
|
||||
return Promise.resolve([this._injector.createInstance(TestWorkbookFindModel, query)]);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user