Skip to content

Commit 4e8c1de

Browse files
committed
Implement load draft
1 parent ff0de6d commit 4e8c1de

File tree

3 files changed

+204
-3
lines changed

3 files changed

+204
-3
lines changed

projects/stream-chat-angular/src/lib/channel.service.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1138,7 +1138,10 @@ export class ChannelService {
11381138
* Selects or deselects the current message to quote reply to
11391139
* @param message The message to select, if called with `undefined`, it deselects the message
11401140
*/
1141-
selectMessageToQuote(message: StreamMessage | undefined) {
1141+
selectMessageToQuote(message: StreamMessage | undefined | MessageResponse) {
1142+
if (message && !this.isStreamMessage(message)) {
1143+
message = this.transformToStreamMessage(message);
1144+
}
11421145
this.messageToQuoteSubject.next(message);
11431146
}
11441147

projects/stream-chat-angular/src/lib/message-input/message-input.component.spec.ts

Lines changed: 167 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@ import {
1010
import { By } from '@angular/platform-browser';
1111
import { TranslateModule } from '@ngx-translate/core';
1212
import { BehaviorSubject, Subject, of } from 'rxjs';
13-
import { Attachment, Channel, UserResponse } from 'stream-chat';
13+
import {
14+
Attachment,
15+
Channel,
16+
ChannelResponse,
17+
DraftMessage,
18+
MessageResponse,
19+
UserResponse,
20+
} from 'stream-chat';
1421
import { AttachmentService } from '../attachment.service';
1522
import { ChannelService } from '../channel.service';
1623
import { ChatClientService } from '../chat-client.service';
@@ -1306,6 +1313,7 @@ fdescribe('MessageInputComponent', () => {
13061313

13071314
it(`shouldn't emit if in edit mode`, () => {
13081315
component.message = mockMessage();
1316+
component.ngOnChanges({ message: {} as any as SimpleChange });
13091317
fixture.detectChanges();
13101318
const messageDraftSpy = jasmine.createSpy();
13111319
component.messageDraftChange.subscribe(messageDraftSpy);
@@ -1340,4 +1348,162 @@ fdescribe('MessageInputComponent', () => {
13401348
expect(messageDraftSpy).not.toHaveBeenCalled();
13411349
});
13421350
});
1351+
1352+
describe('load draft', () => {
1353+
it(`shouldn't load draft if draft's channel id doesn't match the active channel id`, () => {
1354+
const channel = mockActiveChannel$.getValue();
1355+
fixture.detectChanges();
1356+
component.loadDraft({
1357+
channel: { cid: `not${channel.cid}` } as any as ChannelResponse,
1358+
message: {
1359+
text: 'Hello, world!',
1360+
} as any as DraftMessage,
1361+
channel_cid: 'messaging:123',
1362+
created_at: new Date().toISOString(),
1363+
});
1364+
fixture.detectChanges();
1365+
1366+
expect(component.textareaValue).not.toBe('Hello, world!');
1367+
});
1368+
1369+
it(`shouldn't load draft if draft's parent id doesn't match the active parent message id`, () => {
1370+
const channel = mockActiveChannel$.getValue();
1371+
const parentMessageId = 'parentMessageId';
1372+
mockActiveParentMessageId$.next(parentMessageId);
1373+
fixture.detectChanges();
1374+
component.loadDraft({
1375+
channel: { cid: `not${channel.cid}` } as any as ChannelResponse,
1376+
message: {
1377+
text: 'Hello, world!',
1378+
parent_id: 'not' + parentMessageId,
1379+
} as any as DraftMessage,
1380+
channel_cid: 'messaging:123',
1381+
created_at: new Date().toISOString(),
1382+
});
1383+
fixture.detectChanges();
1384+
1385+
expect(component.textareaValue).not.toBe('Hello, world!');
1386+
});
1387+
1388+
it(`shouldn't load draft if in edit mode`, () => {
1389+
const channel = mockActiveChannel$.getValue();
1390+
const messageToEdit = mockMessage();
1391+
messageToEdit.text = 'This message is being edited';
1392+
component.message = messageToEdit;
1393+
component.ngOnChanges({ message: {} as any as SimpleChange });
1394+
fixture.detectChanges();
1395+
component.loadDraft({
1396+
channel: { cid: channel.cid } as any as ChannelResponse,
1397+
message: {
1398+
text: 'Hello, world!',
1399+
} as any as DraftMessage,
1400+
channel_cid: 'messaging:123',
1401+
created_at: new Date().toISOString(),
1402+
});
1403+
fixture.detectChanges();
1404+
1405+
expect(component.textareaValue).toBe(messageToEdit.text);
1406+
});
1407+
1408+
it(`should select message to quote if quoted message is set`, () => {
1409+
const channel = mockActiveChannel$.getValue();
1410+
const mockQuotedMessage = mockMessage();
1411+
mockQuotedMessage.id = 'quotedMessageId';
1412+
selectMessageToQuoteSpy.calls.reset();
1413+
1414+
component.loadDraft({
1415+
channel: { cid: channel.cid } as any as ChannelResponse,
1416+
message: {
1417+
text: 'Hello, world!',
1418+
} as any as DraftMessage,
1419+
channel_cid: 'messaging:123',
1420+
created_at: new Date().toISOString(),
1421+
quoted_message: mockQuotedMessage as any as MessageResponse,
1422+
});
1423+
fixture.detectChanges();
1424+
1425+
expect(selectMessageToQuoteSpy).toHaveBeenCalledOnceWith(
1426+
mockQuotedMessage
1427+
);
1428+
});
1429+
1430+
it(`should deselect message to quote if draft doesn't contain quoted message`, () => {
1431+
mockMessageToQuote$.next(mockMessage());
1432+
const channel = mockActiveChannel$.getValue();
1433+
1434+
component.loadDraft({
1435+
channel: { cid: channel.cid } as any as ChannelResponse,
1436+
message: {
1437+
text: 'Hello, world!',
1438+
} as any as DraftMessage,
1439+
channel_cid: 'messaging:123',
1440+
created_at: new Date().toISOString(),
1441+
quoted_message: undefined,
1442+
});
1443+
fixture.detectChanges();
1444+
1445+
expect(selectMessageToQuoteSpy).toHaveBeenCalledOnceWith(undefined);
1446+
});
1447+
1448+
it(`should set all fields from draft`, () => {
1449+
const channel = mockActiveChannel$.getValue();
1450+
const draft = {
1451+
channel: { cid: channel.cid } as any as ChannelResponse,
1452+
message: {
1453+
text: 'Hello, world!',
1454+
mentioned_users: ['user1', 'user2'],
1455+
poll_id: 'poll1',
1456+
attachments: [{ type: 'file', url: 'url' }],
1457+
} as any as DraftMessage,
1458+
channel_cid: 'messaging:123',
1459+
created_at: new Date().toISOString(),
1460+
};
1461+
attachmentService.createFromAttachments.calls.reset();
1462+
1463+
component.loadDraft(draft);
1464+
fixture.detectChanges();
1465+
1466+
expect(component.textareaValue).toBe('Hello, world!');
1467+
expect(component.mentionedUsers).toEqual([
1468+
{ id: 'user1' },
1469+
{ id: 'user2' },
1470+
]);
1471+
expect(component['pollId']).toBe('poll1');
1472+
expect(attachmentService.createFromAttachments).toHaveBeenCalledOnceWith([
1473+
{ type: 'file', url: 'url' },
1474+
]);
1475+
});
1476+
1477+
it(`shouldn't emit message draft when loading a draft (avoid infinite loop)`, async () => {
1478+
const channel = mockActiveChannel$.getValue();
1479+
attachmentService.createFromAttachments.and.callFake(() => {
1480+
attachmentService.attachmentUploads$.next([
1481+
{
1482+
type: 'file',
1483+
state: 'success',
1484+
url: 'url',
1485+
file: { name: 'file.pdf', type: 'application/pdf' } as File,
1486+
} as AttachmentUpload,
1487+
]);
1488+
});
1489+
const draft = {
1490+
channel: { cid: channel.cid } as any as ChannelResponse,
1491+
message: {
1492+
text: 'Hello, world!',
1493+
mentioned_users: ['user1', 'user2'],
1494+
poll_id: 'poll1',
1495+
attachments: [{ type: 'file', url: 'url' }],
1496+
} as any as DraftMessage,
1497+
channel_cid: 'messaging:123',
1498+
created_at: new Date().toISOString(),
1499+
};
1500+
const spy = jasmine.createSpy();
1501+
component.messageDraftChange.subscribe(spy);
1502+
spy.calls.reset();
1503+
component.loadDraft(draft);
1504+
fixture.detectChanges();
1505+
1506+
expect(spy).not.toHaveBeenCalled();
1507+
});
1508+
});
13431509
});

projects/stream-chat-angular/src/lib/message-input/message-input.component.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
Attachment,
2727
Channel,
2828
DraftMessagePayload,
29+
DraftResponse,
2930
UserResponse,
3031
} from 'stream-chat';
3132
import { AttachmentService } from '../attachment.service';
@@ -130,7 +131,7 @@ export class MessageInputComponent
130131
*
131132
* To save and fetch message drafts, you can use the [Stream message drafts API](https://getstream.io/chat/docs/javascript/drafts/).
132133
*
133-
* Message draft only works for new messages, nothing is emitted when input is in edit mode.
134+
* Message draft only works for new messages, nothing is emitted when input is in edit mode (if `message` input is set).
134135
*/
135136
@Output() readonly messageDraftChange = new EventEmitter<
136137
DraftMessagePayload | undefined
@@ -178,6 +179,7 @@ export class MessageInputComponent
178179
private pollId: string | undefined;
179180
private isChannelChangeResetInProgress = false;
180181
private isSendingMessage = false;
182+
private isLoadingDraft = false;
181183

182184
constructor(
183185
private channelService: ChannelService,
@@ -598,6 +600,7 @@ export class MessageInputComponent
598600

599601
updateMessageDraft() {
600602
if (
603+
this.isLoadingDraft ||
601604
this.isSendingMessage ||
602605
this.isChannelChangeResetInProgress ||
603606
this.isUpdate
@@ -641,6 +644,35 @@ export class MessageInputComponent
641644
return !!this.message;
642645
}
643646

647+
/**
648+
*
649+
* @param draft DraftResponse to load into the message input.
650+
* - Draft messages are only supported for new messages, input is ignored in edit mode (if `message` input is set).
651+
* - If channel id doesn't match the active channel id, the draft is ignored.
652+
* - If a thread message is loaded, and the input isn't it thread mode or parent ids don't match, the draft is ignored.
653+
* @returns
654+
*/
655+
loadDraft(draft: DraftResponse) {
656+
if (
657+
this.channel?.cid !== draft.channel?.cid ||
658+
draft?.message?.parent_id !== this.parentMessageId ||
659+
this.isUpdate
660+
) {
661+
return;
662+
}
663+
this.isLoadingDraft = true;
664+
this.channelService.selectMessageToQuote(draft.quoted_message);
665+
666+
this.textareaValue = draft.message?.text || '';
667+
this.mentionedUsers =
668+
draft?.message?.mentioned_users?.map((id) => ({ id })) || [];
669+
this.pollId = draft?.message?.poll_id;
670+
this.attachmentService.createFromAttachments(
671+
draft?.message?.attachments || []
672+
);
673+
this.isLoadingDraft = false;
674+
}
675+
644676
private deleteUpload(upload: AttachmentUpload) {
645677
if (this.isUpdate) {
646678
// Delay delete to avoid modal detecting this click as outside click

0 commit comments

Comments
 (0)