Skip to content

Commit b68e0a2

Browse files
marker-daomarker dao ®
andauthored
HtmlEditor: Process TextTransform command execution
Co-authored-by: marker dao ® <[email protected]>
1 parent 0896bba commit b68e0a2

File tree

19 files changed

+1388
-309
lines changed

19 files changed

+1388
-309
lines changed

packages/devextreme-scss/scss/widgets/base/_mixins.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,5 +134,15 @@
134134
height: $indicator-size;
135135
}
136136

137+
@mixin dx-pending-indicator-centered($indicator-size) {
138+
pointer-events: none;
139+
position: absolute;
140+
top: 50%;
141+
left: 50%;
142+
width: $indicator-size;
143+
height: $indicator-size;
144+
transform: translate(-50%, -50%);
145+
}
146+
137147
$overlay-zindex: 1000;
138148
$max-integer: 2147483647;

packages/devextreme-scss/scss/widgets/fluent/htmlEditor/_index.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
@use "../common/sizes" as *;
1515
@use "../fileUploader/sizes" as *;
1616
@use "../popup/sizes" as *;
17+
@use "../../base/mixins" as *;
1718
@use "../../base/htmlEditor";
1819

1920
// adduse
@@ -315,3 +316,7 @@
315316
padding: $fluent-aidialog-content-padding;
316317
gap: $fluent-aidialog-content-gap;
317318
}
319+
320+
.dx-aidialog .dx-pending-indicator {
321+
@include dx-pending-indicator-centered($fluent-invalid-badge-size);
322+
}

packages/devextreme-scss/scss/widgets/generic/htmlEditor/_index.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
@use "../fileUploader/sizes" as *;
1212
@use "../toolbar/sizes" as *;
1313
@use "../common/mixins" as *;
14+
@use "../common/sizes" as *;
15+
@use "../../base/mixins" as *;
1416
@use "../../base/htmlEditor";
1517

1618
// adduse
@@ -316,4 +318,8 @@
316318
.dx-button {
317319
min-width: auto;
318320
}
321+
322+
.dx-pending-indicator {
323+
@include dx-pending-indicator-centered($generic-pending-indicator-size);
324+
}
319325
}

packages/devextreme-scss/scss/widgets/material/common/_mixins.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
@use "../colors" as *;
44
@use "sizes" as *;
55
@use "../sizes" as *;
6+
@use "../common/sizes" as *;
67
@use "../../base/mixins" as *;
78
@use "../../base/validation" as *;
89
@use "../list/sizes" as *;

packages/devextreme-scss/scss/widgets/material/htmlEditor/_index.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
@use "../textEditor/colors" as *;
1111
@use "../toolbar/sizes" as *;
1212
@use "../common/mixins" as *;
13+
@use "../common/sizes" as *;
1314
@use "../fileUploader/sizes" as *;
1415
@use "../popup/sizes" as *;
16+
@use "../../base/mixins" as *;
1517
@use "../../base/htmlEditor";
1618

1719
// adduse
@@ -366,4 +368,8 @@
366368
padding: $material-aidialog-content-padding;
367369
gap: $material-aidialog-content-gap;
368370
}
371+
372+
.dx-pending-indicator {
373+
@include dx-pending-indicator-centered($material-invalid-badge-size);
374+
}
369375
}

packages/devextreme/js/__internal/core/ai_integration/core/ai_integration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export class AIIntegration implements IAIIntegration {
161161
}
162162

163163
public shorten(
164-
params: SummarizeCommandParams,
164+
params: ShortenCommandParams,
165165
callbacks: RequestCallbacks<ShortenCommandResult>,
166166
): () => void {
167167
return this.executeCommand(

packages/devextreme/js/__internal/core/ai_integration/core/request_manager.test.ts

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
it,
66
jest,
77
} from '@jest/globals';
8-
import type { AIProvider, Prompt } from '@js/common/ai-integration';
8+
import type { AIProvider, Prompt, RequestParams } from '@js/common/ai-integration';
99
import { ERROR_MESSAGES, RequestManager } from '@ts/core/ai_integration/core/request_manager';
1010
import { Provider } from '@ts/core/ai_integration/test_utils/provider_mock';
1111

@@ -113,8 +113,8 @@ describe('RequestManager', () => {
113113
});
114114
});
115115

116-
it('should return the abort function that returned from sendRequest', () => {
117-
const abort = (): void => {};
116+
it('should call the abort function that returned from sendRequest', () => {
117+
const abort = jest.fn();
118118

119119
const sendRequestSpy = jest.spyOn(provider, 'sendRequest');
120120

@@ -125,7 +125,9 @@ describe('RequestManager', () => {
125125

126126
const abortRequest = requestManager.sendRequest({ user: 'user' }, {});
127127

128-
expect(abortRequest).toBe(abort);
128+
abortRequest();
129+
130+
expect(abort).toHaveBeenCalledTimes(1);
129131
});
130132

131133
it('should work correctly with no definition of callbacks', () => {
@@ -139,5 +141,72 @@ describe('RequestManager', () => {
139141
requestManager.sendRequest({ user: 'test' }, { onChunk: () => {} });
140142
}).not.toThrow();
141143
});
144+
145+
describe('if abort is called', () => {
146+
it('should not forward chunks', () => {
147+
const onChunkSpy = jest.fn();
148+
149+
let capturedParams = undefined as unknown as RequestParams;
150+
151+
jest.spyOn(provider, 'sendRequest').mockImplementation((params) => {
152+
capturedParams = params;
153+
154+
return {
155+
promise: Promise.resolve(''),
156+
abort: (): void => {},
157+
};
158+
});
159+
160+
const abort = requestManager.sendRequest({ user: 'test' }, { onChunk: onChunkSpy });
161+
162+
abort();
163+
164+
capturedParams?.onChunk?.('chunk');
165+
166+
expect(onChunkSpy).not.toHaveBeenCalled();
167+
});
168+
169+
it('should not call onComplete', async () => {
170+
let resolvePromise: (response: string) => void = () => {};
171+
172+
const promise = new Promise<string>((resolve) => { resolvePromise = resolve; });
173+
174+
jest.spyOn(provider, 'sendRequest').mockReturnValue({
175+
promise,
176+
abort: (): void => {},
177+
});
178+
179+
const onCompleteSpy = jest.fn();
180+
const abort = requestManager.sendRequest({ user: 'user' }, { onComplete: onCompleteSpy });
181+
182+
abort();
183+
resolvePromise('resolve');
184+
185+
await promise;
186+
187+
expect(onCompleteSpy).not.toHaveBeenCalled();
188+
});
189+
190+
it('should not call onError', async () => {
191+
let rejectPromise: (e: Error) => void = () => {};
192+
193+
const promise = new Promise<string>((_, reject) => { rejectPromise = reject; });
194+
195+
jest.spyOn(provider, 'sendRequest').mockReturnValue({
196+
promise,
197+
abort: (): void => {},
198+
});
199+
200+
const onErrorSpy = jest.fn();
201+
const abort = requestManager.sendRequest({ user: 'user' }, { onError: onErrorSpy });
202+
203+
abort();
204+
rejectPromise(new Error('error'));
205+
206+
await new Promise(process.nextTick);
207+
208+
expect(onErrorSpy).not.toHaveBeenCalled();
209+
});
210+
});
142211
});
143212
});

packages/devextreme/js/__internal/core/ai_integration/core/request_manager.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,23 @@ export class RequestManager {
1919

2020
public sendRequest(prompt: Prompt, callbacks: RequestManagerCallbacks): () => void {
2121
if (typeof this.provider.sendRequest === 'function') {
22+
let aborted = false;
23+
2224
const params: RequestParams = {
2325
prompt,
24-
onChunk: (chunk: string): void => { callbacks?.onChunk?.(chunk); },
26+
onChunk: (chunk: string): void => { if (!aborted) { callbacks?.onChunk?.(chunk); } },
2527
};
2628

27-
const { promise, abort } = this.provider.sendRequest(params);
29+
const { promise, abort: abortRequest } = this.provider.sendRequest(params);
2830

2931
promise
30-
.then((response) => { callbacks?.onComplete?.(response); })
31-
.catch((e) => { callbacks?.onError?.(e); });
32+
.then((response) => { if (!aborted) { callbacks?.onComplete?.(response); } })
33+
.catch((e) => { if (!aborted) { callbacks?.onError?.(e); } });
34+
35+
const abort = (): void => {
36+
aborted = true;
37+
abortRequest?.();
38+
};
3239

3340
return abort;
3441
}

packages/devextreme/js/__internal/ui/html_editor/modules/m_toolbar.ts

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -380,10 +380,12 @@ if (Quill) {
380380
text?: string,
381381
commandOptions?: string[],
382382
) {
383-
const options = commandOptions ?? getDefaultOptionsByCommand(command)?.map(capitalize);
383+
const options = commandOptions?.map(capitalize)
384+
?? getDefaultOptionsByCommand(command)?.map(capitalize);
384385

385-
return {
386+
const item = {
386387
id: command,
388+
name: command,
387389
text: text ?? defaultCommandNames[command],
388390
items: options?.map((option) => ({
389391
id: option,
@@ -392,31 +394,52 @@ if (Quill) {
392394
options: options?.map(capitalize),
393395
})),
394396
};
397+
398+
return item;
395399
}
396400

397401
private _buildMenuItems(commands: AIToolbarItem['commands']) {
398-
return commands?.map((command) => {
402+
let customCommandIndex = 0;
403+
404+
const items = commands?.map((command) => {
399405
if (typeof command === 'object') {
400406
if (command.name === 'custom') {
401-
return {
402-
id: 'custom',
407+
const id = `custom${customCommandIndex}`;
408+
const { prompt, options } = command as AICustomCommand;
409+
const capitalized = options?.map(capitalize);
410+
411+
const item = {
412+
id,
413+
name: 'custom',
403414
text: command.text,
404-
items: command.options?.map((option) => ({
405-
parentCommand: 'custom',
406-
id: option,
407-
text: option,
408-
options: command.options.map(capitalize),
409-
prompt,
410-
})),
411-
prompt: (command as AICustomCommand).prompt,
415+
items: command.options?.map((rawOptionName: string) => {
416+
const option = capitalize(rawOptionName);
417+
418+
const result = {
419+
parentCommand: id,
420+
id: option,
421+
text: option,
422+
options: capitalized,
423+
prompt,
424+
};
425+
426+
return result;
427+
}),
428+
prompt,
412429
};
430+
431+
customCommandIndex += 1;
432+
433+
return item;
413434
}
414435

415436
return this._createCommandMenuItem(command.name, command.text, command.options);
416437
}
417438

418439
return this._createCommandMenuItem(command);
419440
});
441+
442+
return items;
420443
}
421444

422445
_prepareAIMenuItemConfig(item: AIToolbarItem) {

0 commit comments

Comments
 (0)