Skip to content

Commit 7bdb40f

Browse files
author
Filip Maj
committed
partway through msg attachments redo
1 parent badc73e commit 7bdb40f

File tree

2 files changed

+135
-71
lines changed

2 files changed

+135
-71
lines changed

packages/types/src/message-attachments.ts

Lines changed: 116 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,46 @@
11
import { AnyBlock } from './block-kit/blocks';
22
import { PlainTextElement } from './block-kit/composition-objects';
33

4-
// TODO: breaking changes, use discriminated union for `fallback`, `text` and `block` properties, maybe LegacyAttachment
5-
// vs. BlocksAttachment? as per https://api.slack.com/reference/messaging/attachments#legacy_fields
6-
// "these fields are optional if you're including blocks as above. If you aren't, one of fallback or text are required"
7-
// also further nested discriminated union types that could be helpful here:
8-
// - LegacyAttachmentWithAuthor: if author_name is present, then author_icon and author_link are optional fields
9-
// - LegacyAttachmentWithFooter: if footer is present, then footer_icon is an optional field
10-
// - image_url and thumb_url cannot be used together
114
/**
125
* Add {@link https://api.slack.com/messaging/composing/layouts#attachments secondary attachments} to your messages in Slack.
136
* Message attachments are considered a legacy part of messaging functionality. They are not deprecated per se, but they may change in the future, in ways that reduce their visibility or utility. We recommend moving to Block Kit instead. Read more about {@link https://api.slack.com/messaging/composing/layouts#when-to-use-attachments when to use message attachments}.
147
* @see {@link https://api.slack.com/reference/messaging/attachments Secondary message attachments reference documentation}
158
*/
16-
export type MessageAttachment = LegacyAttachment | BlocksAttachment;
9+
export type MessageAttachment = (LegacyAttachment | BlocksAttachment) & LegacyAttachmentOptionalFields;
1710

18-
interface BlocksAttachment {
19-
}
20-
interface LegacyAttachment {
11+
// Either `text` or `fallback` is required.
12+
type LegacyAttachment = LegacyAttachmentWithText | LegacyAttachmentWithFallback;
13+
interface LegacyAttachmentWithText {
2114
/**
22-
* @description An array of {@link KnownBlock layout blocks} in the same format
23-
* {@link https://api.slack.com/block-kit/building as described in the building blocks guide}.
15+
* @description The main body text of the attachment. It can be formatted as plain text, or with
16+
* {@link https://api.slack.com/reference/surfaces/formatting#basics `mrkdwn`} by including it in the `mrkdwn_in` field.
17+
* The content will automatically collapse if it contains 700+ characters or 5+ line breaks, and will display
18+
* a "Show more..." link to expand the content.
2419
*/
25-
blocks?: AnyBlock[];
20+
text: string;
21+
}
22+
interface LegacyAttachmentWithFallback {
2623
/**
2724
* @description A plain text summary of the attachment used in clients that
2825
* don't show formatted text (e.g. mobile notifications).
2926
*/
30-
fallback?: string; // either this or text must be defined
31-
/**
32-
* @description Changes the color of the border on the left side of this attachment from the default gray. Can either
33-
* be one of `good` (green), `warning` (yellow), `danger` (red), or any hex color code (eg. `#439FE0`)
34-
*/
35-
color?: 'good' | 'warning' | 'danger' | string;
27+
fallback: string;
28+
}
29+
interface BlocksAttachment {
3630
/**
37-
* @description Text that appears above the message attachment block. It can be formatted as plain text,
38-
* or with {@link https://api.slack.com/reference/surfaces/formatting#basics `mrkdwn`} by including it in the `mrkdwn_in` field.
31+
* @description An array of {@link KnownBlock layout blocks} in the same format
32+
* {@link https://api.slack.com/block-kit/building as described in the building blocks guide}.
3933
*/
40-
pretext?: string;
34+
blocks: AnyBlock[];
35+
}
36+
37+
// author_link and author_icon require author_name
38+
type AttachmentAuthor = AuthorDetails | RequireAuthorName;
39+
interface AuthorDetails {
4140
/**
4241
* @description Small text used to display the author's name.
4342
*/
44-
author_name?: string;
43+
author_name: string;
4544
/**
4645
* @description A valid URL that will hyperlink the `author_name` text. Will only work if `author_name` is present.
4746
*/
@@ -51,35 +50,46 @@ interface LegacyAttachment {
5150
* Will only work if `author_name` is present.
5251
*/
5352
author_icon?: string; // author_name must be present
54-
author_subname?: string; // TODO: not documented in https://api.slack.com/reference/messaging/attachments
55-
/**
56-
* @description Large title text near the top of the attachment.
57-
*/
58-
title?: string;
59-
/**
60-
* @description A valid URL that turns the `title` text into a hyperlink.
61-
*/
62-
title_link?: string; // title must be present
53+
}
54+
interface RequireAuthorName {
55+
author_name?: undefined;
56+
author_link?: never;
57+
author_icon?: never;
58+
}
59+
60+
// footer_icon requires footer
61+
type Footer = FooterDetails | RequireFooter;
62+
interface FooterDetails {
6363
/**
64-
* @description The main body text of the attachment. It can be formatted as plain text, or with
65-
* {@link https://api.slack.com/reference/surfaces/formatting#basics `mrkdwn`} by including it in the `mrkdwn_in` field.
66-
* The content will automatically collapse if it contains 700+ characters or 5+ line breaks, and will display
67-
* a "Show more..." link to expand the content.
64+
* @description Some brief text to help contextualize and identify an attachment. Limited to 300 characters,
65+
* and may be truncated further when displayed to users in environments with limited screen real estate.
6866
*/
69-
text?: string; // either this or fallback must be defined
67+
footer: string;
7068
/**
71-
* @description An array of {@link MessageAttachmentField} that get displayed in a table-like way
72-
* (see {@link https://api.slack.com/reference/messaging/attachments#example this example}).
73-
* For best results, include no more than 2-3 field objects.
69+
* @description A valid URL to an image file that will be displayed beside the `footer` text.
70+
* Will only work if `footer` is present. We'll render what you provide at 16px by 16px.
71+
* It's best to use an image that is similarly sized.
7472
*/
75-
fields?: MessageAttachmentField[];
73+
footer_icon?: string;
74+
}
75+
interface RequireFooter {
76+
footer?: undefined;
77+
footer_icon?: never;
78+
}
79+
80+
// image_url and thumb_url cannot be used together
81+
type Image = ImageUrl | ThumbUrl;
82+
interface ImageUrl {
7683
/**
7784
* @description A valid URL to an image file that will be displayed at the bottom of the attachment.
7885
* We support GIF, JPEG, PNG, and BMP formats.
7986
* Large images will be resized to a maximum width of 360px or a maximum height of 500px, while still
8087
* maintaining the original aspect ratio. Cannot be used with `thumb_url`.
8188
*/
8289
image_url?: string;
90+
thumb_url?: never;
91+
}
92+
interface ThumbUrl {
8393
/**
8494
* @description A valid URL to an image file that will be displayed as a thumbnail on the right side of
8595
* a message attachment. We currently support the following formats: GIF, JPEG, PNG, and BMP.
@@ -88,38 +98,54 @@ interface LegacyAttachment {
8898
* For best results, please use images that are already 75px by 75px.
8999
*/
90100
thumb_url?: string;
101+
image_url?: never;
102+
}
103+
104+
type LegacyAttachmentOptionalFields = Partial<LegacyAttachmentWithText> & Partial<LegacyAttachmentWithFallback> &
105+
Partial<BlocksAttachment> & AttachmentAuthor & Footer & Image & AttachmentActions & {
106+
app_id?: string; // may be present on payloads _sent_ by Slack
107+
bot_id?: string; // may be present on payloads _sent_ by Slack
108+
app_unfurl_url?: string; // may be present on payloads _sent_ by Slack
91109
/**
92-
* @description Some brief text to help contextualize and identify an attachment. Limited to 300 characters,
93-
* and may be truncated further when displayed to users in environments with limited screen real estate.
110+
* @description Changes the color of the border on the left side of this attachment from the default gray. Can either
111+
* be one of `good` (green), `warning` (yellow), `danger` (red), or any hex color code (eg. `#439FE0`)
94112
*/
95-
footer?: string;
113+
color?: 'good' | 'warning' | 'danger' | string;
96114
/**
97-
* @description A valid URL to an image file that will be displayed beside the `footer` text.
98-
* Will only work if `footer` is present. We'll render what you provide at 16px by 16px.
99-
* It's best to use an image that is similarly sized.
115+
* @description An array of {@link MessageAttachmentField} that get displayed in a table-like way
116+
* (see {@link https://api.slack.com/reference/messaging/attachments#example this example}).
117+
* For best results, include no more than 2-3 field objects.
100118
*/
101-
footer_icon?: string;
119+
fields?: MessageAttachmentField[];
120+
is_app_unfurl?: boolean; // TODO: not documented in https://api.slack.com/reference/messaging/attachments
121+
/**
122+
* @description Field names that should be {@link https://api.slack.com/reference/surfaces/formatting#basics formatted by `mrkdwn` syntax}.
123+
* The fields that can be formatted in this way include the names of the `fields` property, or
124+
* the `text` or `pretext` properties.
125+
*/
126+
mrkdwn_in?: ('pretext' | 'text' | 'fields')[]; // TODO: I think `fields` here is wrong? instead they should reference field names from `fields`
127+
/**
128+
* @description Text that appears above the message attachment block. It can be formatted as plain text,
129+
* or with {@link https://api.slack.com/reference/surfaces/formatting#basics `mrkdwn`} by including it in the `mrkdwn_in` field.
130+
*/
131+
pretext?: string;
132+
preview?: MessageAttachmentPreview; // https://api.slack.com/methods/chat.unfurl#markdown
133+
/**
134+
* @description Large title text near the top of the attachment.
135+
*/
136+
title?: string;
137+
/**
138+
* @description A valid URL that turns the `title` text into a hyperlink.
139+
*/
140+
title_link?: string; // title must be present
102141
/**
103142
* @description A Unix timestamp that is used to relate your attachment to a specific time.
104143
* The attachment will display the additional timestamp value as part of the attachment's footer.
105144
* Your message's timestamp will be displayed in varying ways, depending on how far in the past or future it is,
106145
* relative to the present. Form factors, like mobile versus desktop may also transform its rendered appearance.
107146
*/
108147
ts?: string;
109-
actions?: AttachmentAction[]; // TODO: https://api.slack.com/legacy/message-buttons#crafting_your_message
110-
callback_id?: string; // TODO: https://api.slack.com/legacy/message-buttons#crafting_your_message
111-
/**
112-
* @description Field names that should be {@link https://api.slack.com/reference/surfaces/formatting#basics formatted by `mrkdwn` syntax}.
113-
* The fields that can be formatted in this way include the names of the `fields` property, or
114-
* the `text` or `pretext` properties.
115-
*/
116-
mrkdwn_in?: ('pretext' | 'text' | 'fields')[]; // TODO: I think `fields` here is wrong? instead they should reference field names from `fields`
117-
app_unfurl_url?: string; // TODO: not documented in https://api.slack.com/reference/messaging/attachments
118-
is_app_unfurl?: boolean; // TODO: not documented in https://api.slack.com/reference/messaging/attachments
119-
app_id?: string; // TODO: not documented in https://api.slack.com/reference/messaging/attachments
120-
bot_id?: string; // TODO: not documented in https://api.slack.com/reference/messaging/attachments
121-
preview?: MessageAttachmentPreview; // https://api.slack.com/methods/chat.unfurl#markdown TODO: not documented in https://api.slack.com/reference/messaging/attachments, also unclear why this links to chat.unfurl?
122-
}
148+
};
123149

124150
/**
125151
* @description A field object to include in a {@link MessageAttachment}.
@@ -151,23 +177,42 @@ interface MessageAttachmentPreview {
151177
iconUrl?: string;
152178
}
153179

154-
interface AttachmentAction {
180+
// Either specify both callback_id and actions, or neither.
181+
type AttachmentActions = {
182+
/**
183+
* @deprecated Legacy attachments with buttons are deprecated. See {@link https://api.slack.com/messaging/attachments-to-blocks transitioning to blocks}.
184+
* @see {@link https://api.slack.com/legacy/message-buttons#crafting_your_message Legacy "Crafting messages with buttons documentation}
185+
*/
186+
actions: [AttachmentAction, ...AttachmentAction[]];
187+
/**
188+
* @deprecated Legacy attachments with buttons are deprecated. See {@link https://api.slack.com/messaging/attachments-to-blocks transitioning to blocks}.
189+
* @see {@link https://api.slack.com/legacy/message-buttons#crafting_your_message Legacy "Crafting messages with buttons documentation}
190+
*/
191+
callback_id: string;
192+
} | { callback_id?: never; actions?: never; };
193+
type AttachmentAction = AttachmentButtonAction | AttachmentMenuAction;
194+
interface AttachmentBaseAction {
155195
id?: string;
196+
name: string;
197+
text: string;
198+
}
199+
interface AttachmentButtonAction extends AttachmentBaseAction {
200+
type: 'button';
156201
confirm?: Confirmation;
157-
data_source?: 'static' | 'channels' | 'conversations' | 'users' | 'external';
158202
min_query_length?: number;
159-
name?: string;
203+
style?: 'default' | 'primary' | 'danger';
204+
value?: string;
205+
url?: string;
206+
}
207+
interface AttachmentMenuAction extends AttachmentBaseAction {
208+
type: 'select';
209+
data_source?: 'static' | 'channels' | 'conversations' | 'users' | 'external';
160210
options?: OptionField[];
161211
option_groups?: {
162212
text: string
163213
options: OptionField[];
164214
}[];
165215
selected_options?: OptionField[];
166-
style?: 'default' | 'primary' | 'danger';
167-
text: string;
168-
type: 'button' | 'select';
169-
value?: string;
170-
url?: string;
171216
}
172217

173218
interface OptionField {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { expectAssignable, expectError } from 'tsd';
2+
import { MessageAttachment } from '../src/index';
3+
4+
// -- sad path
5+
expectError<MessageAttachment>({}); // if no blocks, either text or fallback is required.
6+
expectError<MessageAttachment>({ fallback: 'hi', author_link: 'https://slack.com' }); // use of author_link requires author_name.
7+
expectError<MessageAttachment>({ fallback: 'hi', author_icon: 'https://slack.com' }); // use of author_icon requires author_name.
8+
expectError<MessageAttachment>({ fallback: 'hi', footer_icon: 'https://slack.com' }); // use of footer_icon requires footer.
9+
expectError<MessageAttachment>({ fallback: 'hi', thumb_url: 'https://slack.com', image_url: 'https://slack.com' }); // cant use both image_url and thumb_url.
10+
expectError<MessageAttachment>({ fallback: 'hi', callback_id: 'hollah' }); // cant use callback_id without actions.
11+
expectError<MessageAttachment>({ fallback: 'hi', actions: [{ name: 'sup', text: 'sup', type: 'button'}] }); // cant use callback_id without actions.
12+
expectError<MessageAttachment>({ fallback: 'hi', callback_id: 'hi', actions: [] }); // must specify at least one action.
13+
14+
// -- happy path
15+
expectAssignable<MessageAttachment>({ text: 'hi' }); // if no blocks, either text or fallback is required.
16+
expectAssignable<MessageAttachment>({ fallback: 'hi' }); // if no blocks, either text or fallback is required.
17+
expectAssignable<MessageAttachment>({ fallback: 'hi', author_name: 'filmaj', author_icon: 'https://slack.com' }); // use of author_icon requires author_name.
18+
expectAssignable<MessageAttachment>({ fallback: 'hi', author_name: 'filmaj', author_link: 'https://slack.com' }); // use of author_link requires author_name.
19+
expectAssignable<MessageAttachment>({ fallback: 'hi', footer: 'filmaj', footer_icon: 'https://slack.com' }); // use of footer_icon requires footer.

0 commit comments

Comments
 (0)