Skip to content

Commit fa8daef

Browse files
authored
Merge pull request #357 from GetStream/pin-messages
Pin messages
2 parents 5bef0d2 + 2b6a4ce commit fa8daef

29 files changed

+1653
-276
lines changed
30.7 KB
Loading
199 KB
Loading
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
---
2+
id: pin-messages
3+
title: Pin messages
4+
---
5+
6+
import PinActionScreenshot from "../assets/pin-action-screenshot.png";
7+
import PinnedMessageScreenshot from "../assets/pinned-message-screenshot.png";
8+
9+
Pinning messages can be a useful feature inside your chat application, `stream-chat-angular` supports this feature, but you have to provide the UI template for the pinned messages. This guide shows you how you can set up this functionality.
10+
11+
## Pin and unpin actions
12+
13+
The default message action box contains the pin/unpin action, this action marks a message as pinned/unpinned.
14+
15+
<img src={PinActionScreenshot} width="500" />
16+
17+
:::note
18+
If you don't see the action, you might have [authorize the pin action](https://getstream.io/chat/docs/javascript/chat_permission_policies/?language=javascript).
19+
:::
20+
21+
## Pinned messages stream
22+
23+
The [`activeChannelPinnedMessages$`](../services/ChannelService.mdx/#activechannelpinnedmessages) stream of the [`ChannelService`](../services/ChannelService.mdx) emits the list of currently pinned messages inside the active channel.
24+
25+
```typescript
26+
this.channelService.activeChannelPinnedMessages$.subscribe(console.log);
27+
```
28+
29+
- The initial value is retrived from the [channel query response](https://getstream.io/chat/docs/javascript/query_channels/?language=javascript&q=pin#channelstate-response)
30+
- After that, all pin and unpin actions are reflected in the emitted list
31+
32+
## Pinned messages UI
33+
34+
Once we know the list of pinned messages, we should display them somewhere users can easily spot them. You can display pinned messages anywhere you want to, however a common place could be the top/bottom of the message list, this example will display pinned messages at the bottom of the message list.
35+
36+
You have to provide the template for the pinned messages:
37+
38+
```html
39+
<stream-notification-list></stream-notification-list>
40+
<div
41+
style="padding: 8px; background: #e1f5fe"
42+
*ngFor="
43+
let message of channelService.activeChannelPinnedMessages$ | async
44+
"
45+
>
46+
{{ message.text }}
47+
</div>
48+
<stream-message-input></stream-message-input>
49+
```
50+
51+
The `message` variable has [`StreamMessage`](../types/stream-message.mdx) type, so you can access all fields defined there inside your template.
52+
53+
<img src={PinnedMessageScreenshot} width="500" />
54+
55+
## Jump to a pinned message
56+
57+
Let's add a click event handler to the pinned message, and jump to the message (works for channel and thread messages as well):
58+
59+
```typescript
60+
jumpToMessage(message: StreamMessage) {
61+
this.channelService.jumpToMessage(message.id, message.parent_id);
62+
}
63+
```
64+
65+
```html
66+
<div
67+
style="padding: 8px; background: #e1f5fe"
68+
*ngFor="
69+
let message of channelService.activeChannelPinnedMessages$ | async
70+
"
71+
(click)="jumpToMessage(message)"
72+
>
73+
{{ message.text }}
74+
</div>
75+
```

docusaurus/docs/Angular/components/AttachmentListComponent.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export class CustomMessageComponent {
3636

3737
## Customization
3838

39+
You can use the [`AttachmentConfigurationService`](../services/AttachmentConfigurationService.mdx) to override certain attributes of attachments.
40+
3941
You can provide your own attachment list component by the [`CustomTemplatesService`](../services/CustomTemplatesService.mdx)
4042

4143
[//]: # "Start of generated content"

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@
117117
"@ngx-translate/core": "^13.0.0",
118118
"@ngx-translate/http-loader": "^6.0.0",
119119
"@popperjs/core": "^2.11.5",
120-
"@stream-io/stream-chat-css": "3.0.0-theming2.6",
120+
"@stream-io/stream-chat-css": "3.0.0-theming2.8",
121121
"@stream-io/transliterate": "^1.5.2",
122122
"angular-mentions": "^1.4.0",
123123
"dayjs": "^1.10.7",

projects/stream-chat-angular/src/assets/i18n/en.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const en = {
3737
'Message has been successfully flagged':
3838
'Message has been successfully flagged',
3939
'Message pinned': 'Message pinned',
40+
'Message unpinned': 'Message unpinned',
4041
Mute: 'Mute',
4142
New: 'New',
4243
'New Messages!': 'New Messages!',
@@ -95,6 +96,7 @@ export const en = {
9596
"You can't send thread replies in this channel":
9697
"You can't send thread replies in this channel",
9798
'Unsupported file type: {{type}}': 'Unsupported file type: {{type}}',
99+
'Message not found': 'Message not found',
98100
'No chats here yet…': 'No chats here yet…',
99101
'user is typing': '{{ user }} is typing',
100102
'users are typing': '{{ users }} are typing',
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import { TestBed } from '@angular/core/testing';
2+
import { Attachment } from 'stream-chat';
3+
import { AttachmentConfigurationService } from './attachment-configuration.service';
4+
5+
describe('AttachmentConfigurationService', () => {
6+
let service: AttachmentConfigurationService;
7+
8+
beforeEach(() => {
9+
service = TestBed.inject(AttachmentConfigurationService);
10+
});
11+
12+
it('should provide the correct configuration for image attachments', () => {
13+
let attachment: Attachment = {
14+
img_url: 'url/to/img',
15+
thumb_url: 'different/url',
16+
};
17+
18+
expect(
19+
service.getImageAttachmentConfiguration(attachment, 'single')
20+
).toEqual({
21+
url: 'url/to/img',
22+
height: '300px',
23+
width: '',
24+
});
25+
26+
attachment = {
27+
thumb_url: 'url/to/img',
28+
};
29+
30+
expect(
31+
service.getImageAttachmentConfiguration(attachment, 'single')
32+
).toEqual({
33+
url: 'url/to/img',
34+
height: '300px',
35+
width: '',
36+
});
37+
38+
attachment = {
39+
image_url: 'url/to/img',
40+
};
41+
42+
expect(
43+
service.getImageAttachmentConfiguration(attachment, 'single')
44+
).toEqual({
45+
url: 'url/to/img',
46+
height: '300px',
47+
width: '',
48+
});
49+
});
50+
51+
it('should call #customImageAttachmentConfigurationHandler, if provided', () => {
52+
const spy = jasmine.createSpy();
53+
service.customImageAttachmentConfigurationHandler = spy;
54+
const attachment: Attachment = {
55+
img_url: 'url/to/img',
56+
thumb_url: 'different/url',
57+
};
58+
service.getImageAttachmentConfiguration(attachment, 'carousel');
59+
60+
expect(spy).toHaveBeenCalledWith(attachment, 'carousel');
61+
});
62+
63+
it('should provide the correct configuration for gallery image attachments', () => {
64+
const attachment: Attachment = {
65+
img_url: 'url/to/img',
66+
};
67+
68+
expect(
69+
service.getImageAttachmentConfiguration(attachment, 'gallery')
70+
).toEqual({
71+
url: 'url/to/img',
72+
height: '',
73+
width: '',
74+
});
75+
});
76+
77+
it('should provide the correct configuration for image attachments inside the carousel', () => {
78+
const attachment: Attachment = {
79+
img_url: 'url/to/img',
80+
};
81+
82+
expect(
83+
service.getImageAttachmentConfiguration(attachment, 'carousel')
84+
).toEqual({
85+
url: 'url/to/img',
86+
height: '',
87+
width: '',
88+
});
89+
});
90+
91+
it('should provide the correct configuration for video attachments', () => {
92+
const attachment: Attachment = {
93+
asset_url: 'url/to/video',
94+
};
95+
96+
expect(service.getVideoAttachmentConfiguration(attachment)).toEqual({
97+
url: 'url/to/video',
98+
height: '100%',
99+
width: '100%',
100+
});
101+
});
102+
103+
it('should call #customVideoAttachmentConfigurationHandler, if provided', () => {
104+
const spy = jasmine.createSpy();
105+
service.customVideoAttachmentConfigurationHandler = spy;
106+
const attachment: Attachment = {
107+
img_url: 'url/to/video',
108+
thumb_url: 'different/url',
109+
};
110+
service.getVideoAttachmentConfiguration(attachment);
111+
112+
expect(spy).toHaveBeenCalledWith(attachment);
113+
});
114+
115+
it('should provide the correct configuration for GIFs', () => {
116+
let attachment: Attachment = {
117+
image_url: 'link/to/GIF',
118+
};
119+
120+
expect(service.getGiphyAttachmentConfiguration(attachment)).toEqual({
121+
url: 'link/to/GIF',
122+
height: '300px',
123+
width: '',
124+
});
125+
126+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
127+
attachment = {
128+
image_url: 'link/to/GIF',
129+
giphy: {
130+
fixed_height_downsampled: {
131+
height: '200',
132+
width: '400',
133+
url: 'link/to/smaller/GIF',
134+
},
135+
} as any,
136+
};
137+
/* eslint-enable @typescript-eslint/no-unsafe-assignment */
138+
139+
expect(service.getGiphyAttachmentConfiguration(attachment)).toEqual({
140+
url: 'link/to/smaller/GIF',
141+
height: '200px',
142+
width: '400px',
143+
});
144+
});
145+
146+
it('should call #customGiphyAttachmentConfigurationHandler, if provided', () => {
147+
const spy = jasmine.createSpy();
148+
service.customGiphyAttachmentConfigurationHandler = spy;
149+
150+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
151+
const attachment = {
152+
image_url: 'link/to/GIF',
153+
giphy: {
154+
fixed_height_downsampled: {
155+
height: '200',
156+
width: '400',
157+
url: 'link/to/smaller/GIF',
158+
},
159+
} as any,
160+
};
161+
/* eslint-enable @typescript-eslint/no-unsafe-assignment */
162+
163+
service.getGiphyAttachmentConfiguration(attachment);
164+
165+
expect(spy).toHaveBeenCalledWith(attachment);
166+
});
167+
168+
it('should provide correct configuration for scraped images', () => {
169+
let attachment: Attachment = {
170+
image_url: 'url/to/img',
171+
thumb_url: 'different/url',
172+
};
173+
174+
expect(service.getScrapedImageAttachmentConfiguration(attachment)).toEqual({
175+
url: 'url/to/img',
176+
width: '',
177+
height: '',
178+
});
179+
180+
attachment = {
181+
thumb_url: 'different/url',
182+
};
183+
184+
expect(service.getScrapedImageAttachmentConfiguration(attachment)).toEqual({
185+
url: 'different/url',
186+
width: '',
187+
height: '',
188+
});
189+
});
190+
191+
it('should call #customScrapedImageAttachmentConfigurationHandler, if provided', () => {
192+
const spy = jasmine.createSpy();
193+
service.customScrapedImageAttachmentConfigurationHandler = spy;
194+
const attachment = {
195+
image_url: 'link/to/url',
196+
};
197+
service.getScrapedImageAttachmentConfiguration(attachment);
198+
199+
expect(spy).toHaveBeenCalledWith(attachment);
200+
});
201+
});

0 commit comments

Comments
 (0)