From 495e1cfa314989ba4f39055741065b598e3b4e78 Mon Sep 17 00:00:00 2001 From: Antoine GROLIER Date: Sat, 27 Jun 2026 19:11:43 +0200 Subject: [PATCH 1/4] Add missing card properties --- .../Examples/Blocks_Card.json | 5 ++ Slack.NetStandard/Messages/Blocks/Card.cs | 6 ++ .../Messages/Blocks/SlackIcon.cs | 76 +++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 Slack.NetStandard/Messages/Blocks/SlackIcon.cs diff --git a/Slack.NetStandard.Tests/Examples/Blocks_Card.json b/Slack.NetStandard.Tests/Examples/Blocks_Card.json index e16b858..2e63bb7 100644 --- a/Slack.NetStandard.Tests/Examples/Blocks_Card.json +++ b/Slack.NetStandard.Tests/Examples/Blocks_Card.json @@ -25,6 +25,11 @@ "text": "Please enjoy each card equally.", "verbatim": false }, + "subtext": { + "type": "mrkdwn", + "text": "This is a card footer.", + "verbatim": false + }, "actions": [ { "type": "button", diff --git a/Slack.NetStandard/Messages/Blocks/Card.cs b/Slack.NetStandard/Messages/Blocks/Card.cs index 1a160ca..13d2ee6 100644 --- a/Slack.NetStandard/Messages/Blocks/Card.cs +++ b/Slack.NetStandard/Messages/Blocks/Card.cs @@ -34,7 +34,13 @@ public Card(TextObject title) [JsonProperty("body", NullValueHandling = NullValueHandling.Ignore)] public TextObject Body { get; set; } + + [JsonProperty("slack_icon", NullValueHandling = NullValueHandling.Ignore)] + public SlackIcon SlackIcon { get; set; } + [JsonProperty("subtext", NullValueHandling = NullValueHandling.Ignore)] + public TextObject Subtext { get; set; } + [JsonProperty("actions", NullValueHandling = NullValueHandling.Ignore)] public IList Actions { get; set; } = new List(); diff --git a/Slack.NetStandard/Messages/Blocks/SlackIcon.cs b/Slack.NetStandard/Messages/Blocks/SlackIcon.cs new file mode 100644 index 0000000..83335ec --- /dev/null +++ b/Slack.NetStandard/Messages/Blocks/SlackIcon.cs @@ -0,0 +1,76 @@ +using System.Runtime.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Slack.NetStandard.Messages.Blocks +{ + public class SlackIcon + { + public const string SlackIconType = "slack_icon"; + + [JsonProperty("type")] + public string Type => SlackIconType; + + [JsonProperty("icon_name")] + public SlackIconName IconName { get; set; } + } + + [JsonConverter(typeof(StringEnumConverter))] + public enum SlackIconName + { + [EnumMember(Value = "archive")] Archive, + [EnumMember(Value = "book")] Book, + [EnumMember(Value = "bookmark")] Bookmark, + [EnumMember(Value = "bot")] Bot, + [EnumMember(Value = "bug")] Bug, + [EnumMember(Value = "calendar")] Calendar, + [EnumMember(Value = "call")] Call, + [EnumMember(Value = "caret-left")] CaretLeft, + [EnumMember(Value = "caret-right")] CaretRight, + [EnumMember(Value = "check")] Check, + [EnumMember(Value = "clipboard")] Clipboard, + [EnumMember(Value = "code")] Code, + [EnumMember(Value = "comment")] Comment, + [EnumMember(Value = "compass")] Compass, + [EnumMember(Value = "copy")] Copy, + [EnumMember(Value = "cube")] Cube, + [EnumMember(Value = "download")] Download, + [EnumMember(Value = "edit")] Edit, + [EnumMember(Value = "email")] Email, + [EnumMember(Value = "eye-closed")] EyeClosed, + [EnumMember(Value = "eye-open")] EyeOpen, + [EnumMember(Value = "file")] File, + [EnumMember(Value = "flag")] Flag, + [EnumMember(Value = "folder")] Folder, + [EnumMember(Value = "gear")] Gear, + [EnumMember(Value = "globe")] Globe, + [EnumMember(Value = "heart")] Heart, + [EnumMember(Value = "help")] Help, + [EnumMember(Value = "image")] Image, + [EnumMember(Value = "info")] Info, + [EnumMember(Value = "key")] Key, + [EnumMember(Value = "lightbulb")] Lightbulb, + [EnumMember(Value = "link")] Link, + [EnumMember(Value = "map")] Map, + [EnumMember(Value = "mobile")] Mobile, + [EnumMember(Value = "new-window")] NewWindow, + [EnumMember(Value = "pin")] Pin, + [EnumMember(Value = "plus")] Plus, + [EnumMember(Value = "refine")] Refine, + [EnumMember(Value = "refresh")] Refresh, + [EnumMember(Value = "rocket")] Rocket, + [EnumMember(Value = "save")] Save, + [EnumMember(Value = "screen")] Screen, + [EnumMember(Value = "share")] Share, + [EnumMember(Value = "sparkle")] Sparkle, + [EnumMember(Value = "star")] Star, + [EnumMember(Value = "star-filled")] StarFilled, + [EnumMember(Value = "tag")] Tag, + [EnumMember(Value = "thumbs-down")] ThumbsDown, + [EnumMember(Value = "thumbs-up")] ThumbsUp, + [EnumMember(Value = "trash")] Trash, + [EnumMember(Value = "upload")] Upload, + [EnumMember(Value = "user")] User, + [EnumMember(Value = "warning")] Warning + } +} From 6a1c96ebadf3c59976bfc1894bb1f0c63bd1db91 Mon Sep 17 00:00:00 2001 From: Antoine GROLIER Date: Fri, 3 Jul 2026 13:40:54 +0200 Subject: [PATCH 2/4] Add Container block --- Slack.NetStandard.Tests/BlockTests.cs | 7 ++- .../Examples/Blocks_Container.json | 37 ++++++++++++ .../Slack.NetStandard.Tests.csproj | 5 +- .../JsonConverters/MessageBlockConverter.cs | 3 +- .../Messages/Blocks/Container.cs | 59 +++++++++++++++++++ 5 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 Slack.NetStandard.Tests/Examples/Blocks_Container.json create mode 100644 Slack.NetStandard/Messages/Blocks/Container.cs diff --git a/Slack.NetStandard.Tests/BlockTests.cs b/Slack.NetStandard.Tests/BlockTests.cs index d950e8d..9356246 100644 --- a/Slack.NetStandard.Tests/BlockTests.cs +++ b/Slack.NetStandard.Tests/BlockTests.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Slack.NetStandard.Messages.Blocks; using Slack.NetStandard.Messages.Elements; @@ -46,6 +46,11 @@ public void Call() Utility.AssertSubType("Blocks_Call.json"); } + [Fact] + public void Container() + { + Utility.AssertSubType("Blocks_Container.json"); + } [Fact] public void RadioButtons() { diff --git a/Slack.NetStandard.Tests/Examples/Blocks_Container.json b/Slack.NetStandard.Tests/Examples/Blocks_Container.json new file mode 100644 index 0000000..ef1df05 --- /dev/null +++ b/Slack.NetStandard.Tests/Examples/Blocks_Container.json @@ -0,0 +1,37 @@ +{ + "type": "container", + "block_id": "bkb_container_standard", + "title": { + "type": "plain_text", + "text": "Standard Width Container" + }, + "subtitle": { + "type": "plain_text", + "text": "Best sub title ever" + }, + "width": "standard", + "child_blocks": [ + { + "type": "rich_text", + "block_id": "rt-simple", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "This is a simple rich text block inside a container." + } + ] + } + ] + } + ], + "icon": { + "type": "image", + "image_url": "https://picsum.photos/36/36", + "alt_text": "Icon" + }, + "is_collapsible": false, + "default_collapsed": true +} \ No newline at end of file diff --git a/Slack.NetStandard.Tests/Slack.NetStandard.Tests.csproj b/Slack.NetStandard.Tests/Slack.NetStandard.Tests.csproj index 720e292..3e3e277 100644 --- a/Slack.NetStandard.Tests/Slack.NetStandard.Tests.csproj +++ b/Slack.NetStandard.Tests/Slack.NetStandard.Tests.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -863,6 +863,9 @@ PreserveNewest + + PreserveNewest + diff --git a/Slack.NetStandard/JsonConverters/MessageBlockConverter.cs b/Slack.NetStandard/JsonConverters/MessageBlockConverter.cs index 9260377..78e9f6b 100644 --- a/Slack.NetStandard/JsonConverters/MessageBlockConverter.cs +++ b/Slack.NetStandard/JsonConverters/MessageBlockConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -65,6 +65,7 @@ public override IMessageBlock ReadJson(JsonReader reader, Type objectType, IMess {nameof(Alert).ToLower(), typeof(Alert)}, {nameof(Card).ToLower(), typeof(Card)}, {nameof(Carousel).ToLower(), typeof(Carousel)}, + {nameof(Container).ToLower(), typeof(Container)}, {DataTable.MessageBlockType, typeof(DataTable) }, {DataVisualization.MessageBlockType, typeof(DataVisualization) } }; diff --git a/Slack.NetStandard/Messages/Blocks/Container.cs b/Slack.NetStandard/Messages/Blocks/Container.cs new file mode 100644 index 0000000..5613786 --- /dev/null +++ b/Slack.NetStandard/Messages/Blocks/Container.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Slack.NetStandard.Messages.Blocks +{ + public class Container : IMessageBlock + { + public const string MessageBlockType = "container"; + [JsonProperty("type")] public string Type => MessageBlockType; + + public Container() { } + + public Container(string title, params IMessageBlock[] childBlocks) + { + Title = new PlainText(title); + ChildBlocks = childBlocks.ToList(); + } + + [JsonProperty("title", NullValueHandling = NullValueHandling.Ignore)] + public PlainText Title { get; set; } + + [JsonProperty("subtitle", NullValueHandling = NullValueHandling.Ignore)] + public TextObject Subtitle { get; set; } + + [JsonProperty("width", NullValueHandling = NullValueHandling.Ignore)] + public SlackContainerWidth? Width { get; set; } + + + [JsonProperty("block_id", NullValueHandling = NullValueHandling.Ignore)] + public string BlockId { get; set; } + + [JsonProperty("child_blocks", NullValueHandling = NullValueHandling.Ignore)] + public List ChildBlocks { get; set; } = new List(); + + public bool ShouldSerializeChildBlocks() => ChildBlocks?.Any() ?? false; + + [JsonProperty("icon", NullValueHandling = NullValueHandling.Ignore)] + public Image Icon { get; set; } + + [JsonProperty("is_collapsible", NullValueHandling = NullValueHandling.Ignore)] + public bool? IsCollapsible { get; set; } + + [JsonProperty("default_collapsed", NullValueHandling = NullValueHandling.Ignore)] + public bool? DefaultCollapsed { get; set; } + + } + + [JsonConverter(typeof(StringEnumConverter))] + public enum SlackContainerWidth + { + [EnumMember(Value = "narrow")] Narrow, + [EnumMember(Value = "standard")] Standard, + [EnumMember(Value = "wide")] Wide, + [EnumMember(Value = "full")] Full + } +} From c0f6ef865b8cfeab975c593106e90f2b81df1efc Mon Sep 17 00:00:00 2001 From: Antoine GROLIER Date: Fri, 3 Jul 2026 13:41:19 +0200 Subject: [PATCH 3/4] Add Callout --- Slack.NetStandard.Tests/BlockTests.cs | 11 ++++- .../Examples/Blocks_Callout.json | 45 +++++++++++++++++ .../Slack.NetStandard.Tests.csproj | 5 +- .../JsonConverters/MessageBlockConverter.cs | 3 +- Slack.NetStandard/Messages/Blocks/Callout.cs | 48 +++++++++++++++++++ 5 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 Slack.NetStandard.Tests/Examples/Blocks_Callout.json create mode 100644 Slack.NetStandard/Messages/Blocks/Callout.cs diff --git a/Slack.NetStandard.Tests/BlockTests.cs b/Slack.NetStandard.Tests/BlockTests.cs index 9356246..12f6ad3 100644 --- a/Slack.NetStandard.Tests/BlockTests.cs +++ b/Slack.NetStandard.Tests/BlockTests.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Slack.NetStandard.Messages.Blocks; using Slack.NetStandard.Messages.Elements; @@ -45,12 +45,19 @@ public void Call() { Utility.AssertSubType("Blocks_Call.json"); } - + [Fact] public void Container() { Utility.AssertSubType("Blocks_Container.json"); } + + [Fact] + public void Callout() + { + Utility.AssertSubType("Blocks_Callout.json"); + } + [Fact] public void RadioButtons() { diff --git a/Slack.NetStandard.Tests/Examples/Blocks_Callout.json b/Slack.NetStandard.Tests/Examples/Blocks_Callout.json new file mode 100644 index 0000000..9557ce5 --- /dev/null +++ b/Slack.NetStandard.Tests/Examples/Blocks_Callout.json @@ -0,0 +1,45 @@ +{ + "type": "callout", + "block_id": "container-callout-1", + "background_color": "blue", + "child_blocks": [ + { + "type": "rich_text", + "block_id": "container-callout-rt-1", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "This callout is nested inside a container block." + } + ] + } + ] + }, + { + "type": "divider", + "block_id": "container-callout-div-1" + }, + { + "type": "section", + "block_id": "container-callout-section-1", + "text": { + "type": "mrkdwn", + "text": "*Tip:* Callouts can hold rich text, sections, dividers, and images." + } + }, + { + "type": "image", + "block_id": "container-callout-img-1", + "image_url": "https://images.pexels.com/photos/257532/pexels-photo-257532.jpeg", + "alt_text": "A sample landscape image" + }, + { + "type": "contact_card", + "block_id": "container-callout-contact-1", + "contact_user_id": "U0123456789" + } + ] +} \ No newline at end of file diff --git a/Slack.NetStandard.Tests/Slack.NetStandard.Tests.csproj b/Slack.NetStandard.Tests/Slack.NetStandard.Tests.csproj index 3e3e277..facc86d 100644 --- a/Slack.NetStandard.Tests/Slack.NetStandard.Tests.csproj +++ b/Slack.NetStandard.Tests/Slack.NetStandard.Tests.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -866,6 +866,9 @@ PreserveNewest + + PreserveNewest + diff --git a/Slack.NetStandard/JsonConverters/MessageBlockConverter.cs b/Slack.NetStandard/JsonConverters/MessageBlockConverter.cs index 78e9f6b..9b368a4 100644 --- a/Slack.NetStandard/JsonConverters/MessageBlockConverter.cs +++ b/Slack.NetStandard/JsonConverters/MessageBlockConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -66,6 +66,7 @@ public override IMessageBlock ReadJson(JsonReader reader, Type objectType, IMess {nameof(Card).ToLower(), typeof(Card)}, {nameof(Carousel).ToLower(), typeof(Carousel)}, {nameof(Container).ToLower(), typeof(Container)}, + {nameof(Callout).ToLower(), typeof(Callout)}, {DataTable.MessageBlockType, typeof(DataTable) }, {DataVisualization.MessageBlockType, typeof(DataVisualization) } }; diff --git a/Slack.NetStandard/Messages/Blocks/Callout.cs b/Slack.NetStandard/Messages/Blocks/Callout.cs new file mode 100644 index 0000000..1f1ef6a --- /dev/null +++ b/Slack.NetStandard/Messages/Blocks/Callout.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Slack.NetStandard.Messages.Blocks +{ + public class Callout : IMessageBlock + { + public const string MessageBlockType = "callout"; + [JsonProperty("type")] public string Type => MessageBlockType; + + public Callout() { } + + public Callout(CalloutColor backgroundColor, params IMessageBlock[] childBlocks) + { + BackgroundColor = backgroundColor; + ChildBlocks = childBlocks.ToList(); + } + + [JsonProperty("background_color", NullValueHandling = NullValueHandling.Ignore)] + public CalloutColor? BackgroundColor { get; set; } + + [JsonProperty("block_id", NullValueHandling = NullValueHandling.Ignore)] + public string BlockId { get; set; } + + [JsonProperty("child_blocks", NullValueHandling = NullValueHandling.Ignore)] + public List ChildBlocks { get; set; } = new List(); + + public bool ShouldSerializeChildBlocks() => ChildBlocks?.Any() ?? false; + + } + + [JsonConverter(typeof(StringEnumConverter))] + public enum CalloutColor + { + [EnumMember(Value = "indigo")] Indigo, + [EnumMember(Value = "blue")] Blue, + [EnumMember(Value = "jade")] Jade, + [EnumMember(Value = "pink")] Pink, + [EnumMember(Value = "gray")] Gray, + [EnumMember(Value = "purple")] Purple, + [EnumMember(Value = "orange")] Orange, + [EnumMember(Value = "brown")] Brown, + [EnumMember(Value = "green")] Green + } +} From b7444757dcb22c88d2ea402ef54a16ebd6b14541 Mon Sep 17 00:00:00 2001 From: Antoine GROLIER Date: Mon, 6 Jul 2026 11:42:27 +0200 Subject: [PATCH 4/4] Adding Header level (1 to 4, nullable) Slack doc : https://docs.slack.dev/reference/block-kit/blocks/header-block --- Slack.NetStandard.Tests/Examples/Blocks_Header.json | 3 ++- Slack.NetStandard/Messages/Blocks/Header.cs | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Slack.NetStandard.Tests/Examples/Blocks_Header.json b/Slack.NetStandard.Tests/Examples/Blocks_Header.json index 4d269ff..1657305 100644 --- a/Slack.NetStandard.Tests/Examples/Blocks_Header.json +++ b/Slack.NetStandard.Tests/Examples/Blocks_Header.json @@ -3,5 +3,6 @@ "text": { "type": "plain_text", "text": "Budget Performance" - } + }, + "level": 2 } \ No newline at end of file diff --git a/Slack.NetStandard/Messages/Blocks/Header.cs b/Slack.NetStandard/Messages/Blocks/Header.cs index 25c60d6..73bdaf1 100644 --- a/Slack.NetStandard/Messages/Blocks/Header.cs +++ b/Slack.NetStandard/Messages/Blocks/Header.cs @@ -26,5 +26,8 @@ public Header(PlainText text) [JsonProperty("block_id", NullValueHandling = NullValueHandling.Ignore)] public string BlockId { get; set; } + + [JsonProperty("level", NullValueHandling = NullValueHandling.Ignore)] + public int? Level { get; set; } } }