diff --git a/json-logs/samples/api/views.open.json b/json-logs/samples/api/views.open.json index 7431d33f6..a38a4ac55 100644 --- a/json-logs/samples/api/views.open.json +++ b/json-logs/samples/api/views.open.json @@ -449,7 +449,8 @@ }, "unicode": "" } - ] + ], + "border": 123 }, { "type": "rich_text_section", @@ -1003,7 +1004,8 @@ }, "unicode": "" } - ] + ], + "border": 123 }, { "type": "rich_text_section", @@ -1554,7 +1556,8 @@ }, "unicode": "" } - ] + ], + "border": 123 }, { "type": "rich_text_section", @@ -1691,7 +1694,8 @@ } ] } - ] + ], + "border": 123 }, { "type": "rich_text_section", @@ -2104,7 +2108,8 @@ }, "unicode": "" } - ] + ], + "border": 123 }, { "type": "rich_text_section", diff --git a/json-logs/samples/api/views.publish.json b/json-logs/samples/api/views.publish.json index 7431d33f6..a38a4ac55 100644 --- a/json-logs/samples/api/views.publish.json +++ b/json-logs/samples/api/views.publish.json @@ -449,7 +449,8 @@ }, "unicode": "" } - ] + ], + "border": 123 }, { "type": "rich_text_section", @@ -1003,7 +1004,8 @@ }, "unicode": "" } - ] + ], + "border": 123 }, { "type": "rich_text_section", @@ -1554,7 +1556,8 @@ }, "unicode": "" } - ] + ], + "border": 123 }, { "type": "rich_text_section", @@ -1691,7 +1694,8 @@ } ] } - ] + ], + "border": 123 }, { "type": "rich_text_section", @@ -2104,7 +2108,8 @@ }, "unicode": "" } - ] + ], + "border": 123 }, { "type": "rich_text_section", diff --git a/json-logs/samples/api/views.push.json b/json-logs/samples/api/views.push.json index 7431d33f6..a38a4ac55 100644 --- a/json-logs/samples/api/views.push.json +++ b/json-logs/samples/api/views.push.json @@ -449,7 +449,8 @@ }, "unicode": "" } - ] + ], + "border": 123 }, { "type": "rich_text_section", @@ -1003,7 +1004,8 @@ }, "unicode": "" } - ] + ], + "border": 123 }, { "type": "rich_text_section", @@ -1554,7 +1556,8 @@ }, "unicode": "" } - ] + ], + "border": 123 }, { "type": "rich_text_section", @@ -1691,7 +1694,8 @@ } ] } - ] + ], + "border": 123 }, { "type": "rich_text_section", @@ -2104,7 +2108,8 @@ }, "unicode": "" } - ] + ], + "border": 123 }, { "type": "rich_text_section", diff --git a/json-logs/samples/api/views.update.json b/json-logs/samples/api/views.update.json index 7431d33f6..a38a4ac55 100644 --- a/json-logs/samples/api/views.update.json +++ b/json-logs/samples/api/views.update.json @@ -449,7 +449,8 @@ }, "unicode": "" } - ] + ], + "border": 123 }, { "type": "rich_text_section", @@ -1003,7 +1004,8 @@ }, "unicode": "" } - ] + ], + "border": 123 }, { "type": "rich_text_section", @@ -1554,7 +1556,8 @@ }, "unicode": "" } - ] + ], + "border": 123 }, { "type": "rich_text_section", @@ -1691,7 +1694,8 @@ } ] } - ] + ], + "border": 123 }, { "type": "rich_text_section", @@ -2104,7 +2108,8 @@ }, "unicode": "" } - ] + ], + "border": 123 }, { "type": "rich_text_section", diff --git a/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/RichTextBlockBuilder.kt b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/RichTextBlockBuilder.kt new file mode 100644 index 000000000..8b2ea131f --- /dev/null +++ b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/RichTextBlockBuilder.kt @@ -0,0 +1,43 @@ +package com.slack.api.model.kotlin_extension.block + +import com.slack.api.model.block.RichTextBlock +import com.slack.api.model.kotlin_extension.block.element.container.MultiRichTextElementContainer +import com.slack.api.model.kotlin_extension.block.element.dsl.RichTextElementDsl + +@BlockLayoutBuilder +class RichTextBlockBuilder private constructor( + private val elementsContainer: MultiRichTextElementContainer +) : Builder, RichTextElementDsl by elementsContainer { + private var blockId: String? = null + + constructor() : this(MultiRichTextElementContainer()) + + /** + * A string acting as a unique identifier for a block. You can use this `block_id` when you receive an interaction + * payload to identify the source of the action. If not specified, one will be generated. Maximum length for this + * field is 255 characters. `block_id` should be unique for each message and each iteration of a message. If a + * message is updated, use a new `block_id`. + * + * @see Rich text block documentation + */ + fun blockId(id: String) { + blockId = id + } + + /** + * An array of rich text objects - `rich_text_section`, `rich_text_list`, `rich_text_preformatted`, + * and `rich_text_quote`. + * + * @see Rich text block documentation + */ + fun elements(builder: RichTextElementDsl.() -> Unit) { + elementsContainer.apply(builder) + } + + override fun build(): RichTextBlock { + return RichTextBlock.builder() + .blockId(blockId) + .elements(elementsContainer.underlying) + .build() + } +} diff --git a/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/composition/container/MultiRichTextObjectContainer.kt b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/composition/container/MultiRichTextObjectContainer.kt new file mode 100644 index 000000000..5330cd72a --- /dev/null +++ b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/composition/container/MultiRichTextObjectContainer.kt @@ -0,0 +1,81 @@ +package com.slack.api.model.kotlin_extension.block.composition.container + +import com.slack.api.model.block.element.RichTextElement +import com.slack.api.model.block.element.RichTextSectionElement +import com.slack.api.model.block.element.RichTextSectionElement.LimitedTextStyle +import com.slack.api.model.block.element.RichTextSectionElement.TextStyle +import com.slack.api.model.kotlin_extension.block.composition.dsl.RichTextObjectDsl +import com.slack.api.model.kotlin_extension.block.element.BroadcastRange + +class MultiRichTextObjectContainer : RichTextObjectDsl { + val underlying = mutableListOf() + + override fun broadcast(range: BroadcastRange, style: LimitedTextStyle?) { + underlying += RichTextSectionElement.Broadcast.builder() + .range(range.value) + .style(style) + .build() + } + + override fun color(value: String, style: LimitedTextStyle?) { + underlying += RichTextSectionElement.Color.builder() + .value(value) + .style(style) + .build() + } + + override fun channel(channelId: String, style: LimitedTextStyle?) { + underlying += RichTextSectionElement.Channel.builder() + .channelId(channelId) + .style(style) + .build() + } + + override fun date(timestamp: Int, format: String, style: TextStyle?, url: String?, fallback: String?) { + underlying += RichTextSectionElement.Date.builder() + .timestamp(timestamp) + .format(format) + .style(style) + .url(url) + .fallback(fallback) + .build() + } + + override fun emoji(name: String, skinTone: Int?, style: LimitedTextStyle?) { + underlying += RichTextSectionElement.Emoji.builder() + .name(name) + .skinTone(skinTone) + .style(style) + .build() + } + + override fun link(url: String, text: String?, unsafe: Boolean?, style: TextStyle?) { + underlying += RichTextSectionElement.Link.builder() + .url(url) + .text(text) + .unsafe(unsafe) + .style(style) + .build() + } + + override fun text(text: String, style: TextStyle?) { + underlying += RichTextSectionElement.Text.builder() + .text(text) + .style(style) + .build() + } + + override fun user(userId: String, style: LimitedTextStyle?) { + underlying += RichTextSectionElement.User.builder() + .userId(userId) + .style(style) + .build() + } + + override fun usergroup(usergroupId: String, style: LimitedTextStyle?) { + underlying += RichTextSectionElement.UserGroup.builder() + .usergroupId(usergroupId) + .style(style) + .build() + } +} diff --git a/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/composition/dsl/RichTextObjectDsl.kt b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/composition/dsl/RichTextObjectDsl.kt new file mode 100644 index 000000000..14096623a --- /dev/null +++ b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/composition/dsl/RichTextObjectDsl.kt @@ -0,0 +1,75 @@ +package com.slack.api.model.kotlin_extension.block.composition.dsl + +import com.slack.api.model.block.element.RichTextSectionElement.LimitedTextStyle +import com.slack.api.model.block.element.RichTextSectionElement.TextStyle +import com.slack.api.model.kotlin_extension.block.BlockLayoutBuilder +import com.slack.api.model.kotlin_extension.block.element.BroadcastRange + +@BlockLayoutBuilder +interface RichTextObjectDsl { + + /** + * @param range The range of the broadcast. + * @see Rich text object documentation + */ + fun broadcast(range: BroadcastRange, style: LimitedTextStyle? = null) + + /** + * @param value The hex value for the color. + * @see Rich text object documentation + */ + fun color(value: String, style: LimitedTextStyle? = null) + + /** + * @param channelId The ID of the channel to be mentioned. + * @param style An object of six optional boolean properties that dictate style: bold, italic, strike, highlight, client_highlight, and unlink. + * @see Rich text object documentation + */ + fun channel(channelId: String, style: LimitedTextStyle? = null) + + /** + * @param timestamp A Unix timestamp for the date to be displayed in seconds. + * @param format A template string containing curly-brace-enclosed tokens to substitute your provided timestamp. + * @param url URL to link the entire format string to. + * @param fallback Text to display in place of the date should parsing, formatting or displaying fail. + * @see Rich text object documentation + */ + fun date(timestamp: Int, format: String, style: TextStyle? = null, url: String? = null, fallback: String? = null) + + /** + * @param name The name of the emoji; i.e. "wave" or "wave::skin-tone-2". + * @see Rich text object documentation + */ + fun emoji(name: String, skinTone: Int? = null, style: LimitedTextStyle? = null) + + /** + * @param url The link's url. + * @param text The text shown to the user (instead of the url). If no text is provided, the url is used. + * @param unsafe Indicates whether the link is safe. + * @param style An object containing four boolean properties: bold, italic, strike, and code. + * @see Rich text object documentation + */ + fun link(url: String, text: String? = null, unsafe: Boolean? = null, style: TextStyle? = null) + + /** + * @param text The text shown to the user. + * @param style An object containing four boolean fields, none of which are required: bold, italic, strike, and code. + * @see Rich text object documentation + */ + fun text(text: String, style: TextStyle? = null) + + /** + * @param userId The ID of the user to be mentioned. + * @param style An object of six optional boolean properties that dictate style: bold, italic, strike, highlight, client_highlight, and unlink. + * @see Rich text object documentation + */ + fun user(userId: String, style: LimitedTextStyle? = null) + + /** + * @param usergroupId The ID of the user group to be mentioned. + * @param style An object of six optional boolean properties that dictate style: bold, italic, strike, highlight, client_highlight, and unlink. + * @see Rich text object documentation + */ + fun usergroup(usergroupId: String, style: LimitedTextStyle? = null) + +} diff --git a/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/container/MultiLayoutBlockContainer.kt b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/container/MultiLayoutBlockContainer.kt index 4eb7f83f2..9f35b0c67 100644 --- a/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/container/MultiLayoutBlockContainer.kt +++ b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/container/MultiLayoutBlockContainer.kt @@ -56,6 +56,10 @@ class MultiLayoutBlockContainer : LayoutBlockDsl { underlying += InputBlockBuilder().apply(builder).build() } + override fun richText(builder: RichTextBlockBuilder.() -> Unit) { + underlying += RichTextBlockBuilder().apply(builder).build() + } + override fun video(builder: VideoBlockBuilder.() -> Unit) { underlying += VideoBlockBuilder().apply(builder).build() } @@ -63,4 +67,4 @@ class MultiLayoutBlockContainer : LayoutBlockDsl { override fun shareShortcut(builder: ShareShortcutBlockBuilder.() -> Unit) { underlying += ShareShortcutBlockBuilder().apply(builder).build() } -} \ No newline at end of file +} diff --git a/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/dsl/LayoutBlockDsl.kt b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/dsl/LayoutBlockDsl.kt index 9b8dbea41..32479bf1a 100644 --- a/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/dsl/LayoutBlockDsl.kt +++ b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/dsl/LayoutBlockDsl.kt @@ -73,6 +73,16 @@ interface LayoutBlockDsl { */ fun input(builder: InputBlockBuilder.() -> Unit) + /** + * Displays formatted, structured representation of text. It is also the output of the Slack client's WYSIWYG + * message composer, so all messages sent by end-users will have this format. Use this block to include user-defined + * formatted text in your Block Kit payload. While it is possible to format text with mrkdwn, rich_text is + * strongly preferred and allows greater flexibility. + * + * @see Rich text documentation + */ + fun richText(builder: RichTextBlockBuilder.() -> Unit) + /** * A video block is designed to embed videos in all app surfaces (e.g. link unfurls, messages, modals, App Home) * — anywhere you can put blocks! To use the video block within your app, you must have the links.embed:write scope. diff --git a/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/BroadcastRange.kt b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/BroadcastRange.kt new file mode 100644 index 000000000..11d66dae7 --- /dev/null +++ b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/BroadcastRange.kt @@ -0,0 +1,26 @@ +package com.slack.api.model.kotlin_extension.block.element + +enum class BroadcastRange { + /** + * Notifies only the active members of a channel. + */ + HERE { + override val value = "here" + }, + + /** + * Notifies all members of a channel. + */ + CHANNEL { + override val value = "channel" + }, + + /** + * Notifies every person in the #general channel. + */ + EVERYONE { + override val value = "everyone" + }; + + abstract val value: String +} diff --git a/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/ListStyle.kt b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/ListStyle.kt new file mode 100644 index 000000000..126c23cb7 --- /dev/null +++ b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/ListStyle.kt @@ -0,0 +1,13 @@ +package com.slack.api.model.kotlin_extension.block.element + +enum class ListStyle { + BULLET { + override val value = "bullet" + }, + + ORDERED { + override val value = "ordered" + }; + + abstract val value: String +} diff --git a/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/RichTextListElementBuilder.kt b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/RichTextListElementBuilder.kt new file mode 100644 index 000000000..b2d7052d2 --- /dev/null +++ b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/RichTextListElementBuilder.kt @@ -0,0 +1,75 @@ +package com.slack.api.model.kotlin_extension.block.element + +import com.slack.api.model.block.element.RichTextListElement +import com.slack.api.model.kotlin_extension.block.BlockLayoutBuilder +import com.slack.api.model.kotlin_extension.block.Builder +import com.slack.api.model.kotlin_extension.block.element.container.MultiRichTextSectionElementContainer +import com.slack.api.model.kotlin_extension.block.element.dsl.RichTextListElementDsl + +@BlockLayoutBuilder +class RichTextListElementBuilder private constructor( + private val elementsContainer: MultiRichTextSectionElementContainer +) : Builder, RichTextListElementDsl by elementsContainer { + private var style: String? = null + private var indent: Int? = null + private var offset: Int? = null + private var border: Int? = null + + constructor() : this(MultiRichTextSectionElementContainer()) + + /** + * Either bullet or ordered, the latter meaning a numbered list. + * + * @see Rich text list element documentation + */ + fun style(style: ListStyle) { + this.style = style.value + } + + /** + * An array of rich_text_section objects containing two properties: type, which is "rich_text_section", + * and elements, which is an array of rich text element objects. + * + * @see Rich text list element documentation + */ + fun elements(builder: RichTextListElementDsl.() -> Unit) { + elementsContainer.apply(builder) + } + + /** + * Number of pixels to indent the list. + * + * @see Rich text list element documentation + */ + fun indent(indent: Int) { + this.indent = indent + } + + /** + * Number of pixels to offset the list. + * + * @see Rich text list element documentation + */ + fun offset(offset: Int) { + this.offset = offset + } + + /** + * Number of pixels of border thickness. + * + * @see Rich text list element documentation + */ + fun border(border: Int) { + this.border = border + } + + override fun build(): RichTextListElement { + return RichTextListElement.builder() + .style(style) + .elements(elementsContainer.underlying) + .indent(indent) + .offset(offset) + .border(border) + .build() + } +} diff --git a/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/RichTextPreformattedElementBuilder.kt b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/RichTextPreformattedElementBuilder.kt new file mode 100644 index 000000000..4794c19d6 --- /dev/null +++ b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/RichTextPreformattedElementBuilder.kt @@ -0,0 +1,41 @@ +package com.slack.api.model.kotlin_extension.block.element + +import com.slack.api.model.block.element.RichTextPreformattedElement +import com.slack.api.model.kotlin_extension.block.BlockLayoutBuilder +import com.slack.api.model.kotlin_extension.block.Builder +import com.slack.api.model.kotlin_extension.block.composition.container.MultiRichTextObjectContainer +import com.slack.api.model.kotlin_extension.block.composition.dsl.RichTextObjectDsl + +@BlockLayoutBuilder +class RichTextPreformattedElementBuilder private constructor( + private val elementsContainer: MultiRichTextObjectContainer +) : Builder, RichTextObjectDsl by elementsContainer { + private var border: Int? = null + + constructor() : this(MultiRichTextObjectContainer()) + + /** + * An array of rich text elements. + * + * @see Rich text preformatted element documentation + */ + fun elements(builder: RichTextObjectDsl.() -> Unit) { + elementsContainer.apply(builder) + } + + /** + * Number of pixels of border thickness. + * + * @see Rich text preformatted element documentation + */ + fun border(border: Int) { + this.border = border + } + + override fun build(): RichTextPreformattedElement { + return RichTextPreformattedElement.builder() + .elements(elementsContainer.underlying) + .border(border) + .build() + } +} diff --git a/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/RichTextQuoteElementBuilder.kt b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/RichTextQuoteElementBuilder.kt new file mode 100644 index 000000000..d3aa50b8a --- /dev/null +++ b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/RichTextQuoteElementBuilder.kt @@ -0,0 +1,41 @@ +package com.slack.api.model.kotlin_extension.block.element + +import com.slack.api.model.block.element.RichTextQuoteElement +import com.slack.api.model.kotlin_extension.block.BlockLayoutBuilder +import com.slack.api.model.kotlin_extension.block.Builder +import com.slack.api.model.kotlin_extension.block.composition.container.MultiRichTextObjectContainer +import com.slack.api.model.kotlin_extension.block.composition.dsl.RichTextObjectDsl + +@BlockLayoutBuilder +class RichTextQuoteElementBuilder private constructor( + private val elementsContainer: MultiRichTextObjectContainer +) : Builder, RichTextObjectDsl by elementsContainer { + private var border: Int? = null + + constructor() : this(MultiRichTextObjectContainer()) + + /** + * An array of rich text elements. + * + * @see Rich text quote element documentation + */ + fun elements(builder: RichTextObjectDsl.() -> Unit) { + elementsContainer.apply(builder) + } + + /** + * Number of pixels of border thickness. + * + * @see Rich text quote element documentation + */ + fun border(border: Int) { + this.border = border + } + + override fun build(): RichTextQuoteElement { + return RichTextQuoteElement.builder() + .elements(elementsContainer.underlying) + .border(border) + .build() + } +} diff --git a/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/RichTextSectionElementBuilder.kt b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/RichTextSectionElementBuilder.kt new file mode 100644 index 000000000..0d5795c36 --- /dev/null +++ b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/RichTextSectionElementBuilder.kt @@ -0,0 +1,29 @@ +package com.slack.api.model.kotlin_extension.block.element + +import com.slack.api.model.block.element.RichTextSectionElement +import com.slack.api.model.kotlin_extension.block.BlockLayoutBuilder +import com.slack.api.model.kotlin_extension.block.Builder +import com.slack.api.model.kotlin_extension.block.composition.container.MultiRichTextObjectContainer +import com.slack.api.model.kotlin_extension.block.composition.dsl.RichTextObjectDsl + +@BlockLayoutBuilder +class RichTextSectionElementBuilder private constructor( + private val elementsContainer: MultiRichTextObjectContainer +) : Builder, RichTextObjectDsl by elementsContainer { + constructor() : this(MultiRichTextObjectContainer()) + + /** + * An array of rich text elements. + * + * @see Rich text section element documentation + */ + fun elements(builder: RichTextObjectDsl.() -> Unit) { + elementsContainer.apply(builder) + } + + override fun build(): RichTextSectionElement { + return RichTextSectionElement.builder() + .elements(elementsContainer.underlying) + .build() + } +} diff --git a/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/container/MultiRichTextElementContainer.kt b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/container/MultiRichTextElementContainer.kt new file mode 100644 index 000000000..32851ec02 --- /dev/null +++ b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/container/MultiRichTextElementContainer.kt @@ -0,0 +1,31 @@ +package com.slack.api.model.kotlin_extension.block.element.container + +import com.slack.api.model.block.element.BlockElement +import com.slack.api.model.kotlin_extension.block.element.RichTextListElementBuilder +import com.slack.api.model.kotlin_extension.block.element.RichTextPreformattedElementBuilder +import com.slack.api.model.kotlin_extension.block.element.RichTextQuoteElementBuilder +import com.slack.api.model.kotlin_extension.block.element.RichTextSectionElementBuilder +import com.slack.api.model.kotlin_extension.block.element.dsl.RichTextElementDsl + +/** + * Supports a RichTextElementContainer that can hold one to many rich text elements + */ +class MultiRichTextElementContainer : RichTextElementDsl { + val underlying = mutableListOf() + + override fun richTextSection(builder: RichTextSectionElementBuilder.() -> Unit) { + underlying += RichTextSectionElementBuilder().apply(builder).build() + } + + override fun richTextList(builder: RichTextListElementBuilder.() -> Unit) { + underlying += RichTextListElementBuilder().apply(builder).build() + } + + override fun richTextPreformatted(builder: RichTextPreformattedElementBuilder.() -> Unit) { + underlying += RichTextPreformattedElementBuilder().apply(builder).build() + } + + override fun richTextQuote(builder: RichTextQuoteElementBuilder.() -> Unit) { + underlying += RichTextQuoteElementBuilder().apply(builder).build() + } +} diff --git a/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/container/MultiRichTextSectionElementContainer.kt b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/container/MultiRichTextSectionElementContainer.kt new file mode 100644 index 000000000..1ce8453da --- /dev/null +++ b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/container/MultiRichTextSectionElementContainer.kt @@ -0,0 +1,16 @@ +package com.slack.api.model.kotlin_extension.block.element.container + +import com.slack.api.model.block.element.RichTextElement +import com.slack.api.model.kotlin_extension.block.element.RichTextSectionElementBuilder +import com.slack.api.model.kotlin_extension.block.element.dsl.RichTextListElementDsl + +/** + * Supports a RichTextSectionElementContainer that can hold one to many rich text section elements + */ +class MultiRichTextSectionElementContainer : RichTextListElementDsl { + val underlying = mutableListOf() + + override fun richTextSection(builder: RichTextSectionElementBuilder.() -> Unit) { + underlying += RichTextSectionElementBuilder().apply(builder).build() + } +} diff --git a/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/dsl/RichTextElementDsl.kt b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/dsl/RichTextElementDsl.kt new file mode 100644 index 000000000..8b9517d34 --- /dev/null +++ b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/dsl/RichTextElementDsl.kt @@ -0,0 +1,20 @@ +package com.slack.api.model.kotlin_extension.block.element.dsl + +import com.slack.api.model.kotlin_extension.block.BlockLayoutBuilder +import com.slack.api.model.kotlin_extension.block.element.RichTextListElementBuilder +import com.slack.api.model.kotlin_extension.block.element.RichTextPreformattedElementBuilder +import com.slack.api.model.kotlin_extension.block.element.RichTextQuoteElementBuilder +import com.slack.api.model.kotlin_extension.block.element.RichTextSectionElementBuilder + +@BlockLayoutBuilder +interface RichTextElementDsl { + + fun richTextSection(builder: RichTextSectionElementBuilder.() -> Unit) + + fun richTextList(builder: RichTextListElementBuilder.() -> Unit) + + fun richTextPreformatted(builder: RichTextPreformattedElementBuilder.() -> Unit) + + fun richTextQuote(builder: RichTextQuoteElementBuilder.() -> Unit) + +} diff --git a/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/dsl/RichTextListElementDsl.kt b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/dsl/RichTextListElementDsl.kt new file mode 100644 index 000000000..c7e094cc5 --- /dev/null +++ b/slack-api-model-kotlin-extension/src/main/kotlin/com/slack/api/model/kotlin_extension/block/element/dsl/RichTextListElementDsl.kt @@ -0,0 +1,11 @@ +package com.slack.api.model.kotlin_extension.block.element.dsl + +import com.slack.api.model.kotlin_extension.block.BlockLayoutBuilder +import com.slack.api.model.kotlin_extension.block.element.RichTextSectionElementBuilder + +@BlockLayoutBuilder +interface RichTextListElementDsl { + + fun richTextSection(builder: RichTextSectionElementBuilder.() -> Unit) + +} diff --git a/slack-api-model-kotlin-extension/src/test/kotlin/test_locally/block/RichTextBlockTest.kt b/slack-api-model-kotlin-extension/src/test/kotlin/test_locally/block/RichTextBlockTest.kt new file mode 100644 index 000000000..49551853e --- /dev/null +++ b/slack-api-model-kotlin-extension/src/test/kotlin/test_locally/block/RichTextBlockTest.kt @@ -0,0 +1,580 @@ +package test_locally.block + +import com.google.gson.JsonElement +import com.slack.api.SlackConfig +import com.slack.api.model.block.element.RichTextSectionElement.LimitedTextStyle +import com.slack.api.model.block.element.RichTextSectionElement.TextStyle +import com.slack.api.model.kotlin_extension.block.element.BroadcastRange +import com.slack.api.model.kotlin_extension.block.element.ListStyle +import com.slack.api.model.kotlin_extension.block.withBlocks +import com.slack.api.util.json.GsonFactory +import org.junit.Test +import kotlin.test.assertEquals + +class RichTextBlockTest { + + val gson = GsonFactory.createSnakeCase(SlackConfig().apply { isPrettyResponseLoggingEnabled = true }) // FIXME + + @Test + fun kitchenSink() { + val blocks = withBlocks { + richText { + richTextSection { + text("Check out these different block types with paragraph breaks between them:\n\n") + } + richTextPreformatted { + text("Hello there, I am preformatted block!\n\nI can have multiple paragraph breaks within the block.") + } + richTextSection { + text("\nI am rich text with a paragraph break following preformatted text. \n\nI can have multiple paragraph breaks within the block.\n\n") + } + richTextQuote { + text("I am a basic rich text quote, \n\nI can have multiple paragraph breaks within the block.") + } + richTextSection { + text("\nI am rich text with a paragraph after the quote block\n\n") + } + richTextQuote { + text("I am a basic quote block following rich text") + } + richTextSection { + text("\n") + } + richTextPreformatted { + text("I am more preformatted text following a quote block") + } + richTextSection { + text("\n") + } + richTextQuote { + text("I am a basic quote block following preformatted text") + } + richTextSection { + text("\n") + } + richTextList { + style(ListStyle.BULLET) + richTextSection { + text("list item one") + } + richTextSection { + text("list item two") + } + } + richTextSection { + text("\nI am rich text with a paragraph break after a list") + } + } + } + val original = """ +{ + "blocks": [ + { + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "Check out these different block types with paragraph breaks between them:\n\n" + } + ] + }, + { + "type": "rich_text_preformatted", + "elements": [ + { + "type": "text", + "text": "Hello there, I am preformatted block!\n\nI can have multiple paragraph breaks within the block." + } + ] + }, + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "\nI am rich text with a paragraph break following preformatted text. \n\nI can have multiple paragraph breaks within the block.\n\n" + } + ] + }, + { + "type": "rich_text_quote", + "elements": [ + { + "type": "text", + "text": "I am a basic rich text quote, \n\nI can have multiple paragraph breaks within the block." + } + ] + }, + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "\nI am rich text with a paragraph after the quote block\n\n" + } + ] + }, + { + "type": "rich_text_quote", + "elements": [ + { + "type": "text", + "text": "I am a basic quote block following rich text" + } + ] + }, + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "\n" + } + ] + }, + { + "type": "rich_text_preformatted", + "elements": [ + { + "type": "text", + "text": "I am more preformatted text following a quote block" + } + ] + }, + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "\n" + } + ] + }, + { + "type": "rich_text_quote", + "elements": [ + { + "type": "text", + "text": "I am a basic quote block following preformatted text" + } + ] + }, + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "\n" + } + ] + }, + { + "type": "rich_text_list", + "style": "bullet", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "list item one" + } + ] + }, + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "list item two" + } + ] + } + ] + }, + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "\nI am rich text with a paragraph break after a list" + } + ] + } + ] + } + ] +} + """.trimIndent() + val json = gson.fromJson(original, JsonElement::class.java) + val expected = json.asJsonObject["blocks"] + val actual = gson.toJsonTree(blocks) + assertEquals(expected, actual, "\n" + expected.toString() + "\n" + actual.toString()) + } + + @Test + fun richTextObjects() { + val blocks = withBlocks { + richText { + blockId("rich-text-block") + richTextList { + style(ListStyle.BULLET) + indent(0) + border(0) + richTextSection { + broadcast(BroadcastRange.HERE) + } + richTextSection { + broadcast(BroadcastRange.CHANNEL) + } + richTextSection { + broadcast(BroadcastRange.EVERYONE) + } + } + + richTextList { + style(ListStyle.BULLET) + indent(1) + border(0) + richTextSection { + color("#C0FFEE") + } + } + + richTextList { + style(ListStyle.ORDERED) + indent(0) + border(1) + richTextSection { + channel("C0123456789") + } + richTextSection { + channel("C0123456789", + LimitedTextStyle.builder().bold(true).italic(true).clientHighlight(true).build()) + } + } + + richTextList { + style(ListStyle.BULLET) + offset(0) + richTextSection { + date(1720710212, "{date_num} at {time}", fallback = "timey") + } + } + + richTextSection { + emoji("basketball") + text(" ") + emoji("snowboarder") + text(" ") + emoji("checkered_flag") + } + + richTextQuote { + border(1) + link("https://api.slack.com") + text(" ") + link("https://api.slack.com", "Slack API") + text(" ") + link("https://api.slack.com", unsafe = true) + text(" ") + link("https://api.slack.com", "Slack API", + style = TextStyle.builder().strike(true).code(true).clientHighlight(true).build()) + } + + richTextSection { + user("U12345678") + } + + richTextSection { + usergroup("S0123456789") + } + + richTextPreformatted { + border(1) + text("preformatted") + } + } + } + val original = """ +{ + "blocks": [ + { + "type": "rich_text", + "block_id": "rich-text-block", + "elements": [ + { + "type": "rich_text_list", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "broadcast", + "range": "here" + } + ] + }, + { + "type": "rich_text_section", + "elements": [ + { + "type": "broadcast", + "range": "channel" + } + ] + }, + { + "type": "rich_text_section", + "elements": [ + { + "type": "broadcast", + "range": "everyone" + } + ] + } + ], + "style": "bullet", + "indent": 0, + "border": 0 + }, + { + "type": "rich_text_list", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "color", + "value": "#C0FFEE" + } + ] + } + ], + "style": "bullet", + "indent": 1, + "border": 0 + }, + { + "type": "rich_text_list", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "channel", + "channel_id": "C0123456789" + } + ] + }, + { + "type": "rich_text_section", + "elements": [ + { + "type": "channel", + "channel_id": "C0123456789", + "style": { + "bold": true, + "italic": true, + "strike": false, + "highlight": false, + "client_highlight": true, + "unlink": false + } + } + ] + } + ], + "style": "ordered", + "indent": 0, + "border": 1 + }, + { + "type": "rich_text_list", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "date", + "timestamp": 1720710212, + "format": "{date_num} at {time}", + "fallback": "timey" + } + ] + } + ], + "style": "bullet", + "offset": 0 + }, + { + "type": "rich_text_section", + "elements": [ + { + "type": "emoji", + "name": "basketball" + }, + { + "type": "text", + "text": " " + }, + { + "type": "emoji", + "name": "snowboarder" + }, + { + "type": "text", + "text": " " + }, + { + "type": "emoji", + "name": "checkered_flag" + } + ] + }, + { + "type": "rich_text_quote", + "elements": [ + { + "type": "link", + "url": "https://api.slack.com" + }, + { + "type": "text", + "text": " " + }, + { + "type": "link", + "url": "https://api.slack.com", + "text": "Slack API" + }, + { + "type": "text", + "text": " " + }, + { + "type": "link", + "url": "https://api.slack.com", + "unsafe": true + }, + { + "type": "text", + "text": " " + }, + { + "type": "link", + "url": "https://api.slack.com", + "text": "Slack API", + "style": { + "bold": false, + "italic": false, + "strike": true, + "highlight": false, + "client_highlight": true, + "unlink": false, + "code": true + } + } + ], + "border": 1 + }, + { + "type": "rich_text_section", + "elements": [ + { + "type": "user", + "user_id": "U12345678" + } + ] + }, + { + "type": "rich_text_section", + "elements": [ + { + "type": "usergroup", + "usergroup_id": "S0123456789" + } + ] + }, + { + "type": "rich_text_preformatted", + "elements": [ + { + "type": "text", + "text": "preformatted" + } + ], + "border": 1 + } + ] + } + ] +} + """.trimIndent() + val json = gson.fromJson(original, JsonElement::class.java) + val expected = json.asJsonObject["blocks"] + val actual = gson.toJsonTree(blocks) + assertEquals(expected, actual, "\n" + expected.toString() + "\n" + actual.toString()) + } + + @Test + fun dslWithElements() { + val blocksWithElements = withBlocks { + richText { + elements { + richTextSection { + elements { + text("section") + } + } + richTextList { + style(ListStyle.BULLET) + elements { + richTextSection { + elements { + text("list") + } + } + } + } + richTextPreformatted { + elements { + text("preformatted") + } + } + richTextQuote { + elements { + text("quote") + } + } + } + } + } + val original = withBlocks { + richText { + richTextSection { + text("section") + } + richTextList { + style(ListStyle.BULLET) + richTextSection { + text("list") + } + } + richTextPreformatted { + text("preformatted") + } + richTextQuote { + text("quote") + } + } + } + val expected = gson.toJsonTree(original) + val actual = gson.toJsonTree(blocksWithElements) + assertEquals(expected, actual, "\n" + expected.toString() + "\n" + actual.toString()) + } + +} diff --git a/slack-api-model-kotlin-extension/src/test/kotlin/test_locally/block/validation/BlockBuilderValidationTest.kt b/slack-api-model-kotlin-extension/src/test/kotlin/test_locally/block/validation/BlockBuilderValidationTest.kt index 8abff734f..7a147266a 100644 --- a/slack-api-model-kotlin-extension/src/test/kotlin/test_locally/block/validation/BlockBuilderValidationTest.kt +++ b/slack-api-model-kotlin-extension/src/test/kotlin/test_locally/block/validation/BlockBuilderValidationTest.kt @@ -27,9 +27,14 @@ class BlockBuilderValidationTest { validateMethodNames(InputBlock::class.java, InputBlockBuilder::class.java) } + @Test + fun testRichTextBlockBuilder() { + validateMethodNames(RichTextBlock::class.java, RichTextBlockBuilder::class.java) + } + @Test fun testSectionBlockBuilder() { validateMethodNames(SectionBlock::class.java, SectionBlockBuilder::class.java) } -} \ No newline at end of file +} diff --git a/slack-api-model-kotlin-extension/src/test/kotlin/test_locally/block/validation/BlockElementBuilderValidationTest.kt b/slack-api-model-kotlin-extension/src/test/kotlin/test_locally/block/validation/BlockElementBuilderValidationTest.kt index f595466eb..922bfa62b 100644 --- a/slack-api-model-kotlin-extension/src/test/kotlin/test_locally/block/validation/BlockElementBuilderValidationTest.kt +++ b/slack-api-model-kotlin-extension/src/test/kotlin/test_locally/block/validation/BlockElementBuilderValidationTest.kt @@ -82,6 +82,26 @@ class BlockElementBuilderValidationTest { validateMethodNames(RadioButtonsElement::class.java, RadioButtonsElementBuilder::class.java) } + @Test + fun testRichTextListElementBuilder() { + validateMethodNames(RichTextListElement::class.java, RichTextListElementBuilder::class.java) + } + + @Test + fun testRichTextPreformattedElementBuilder() { + validateMethodNames(RichTextPreformattedElement::class.java, RichTextPreformattedElementBuilder::class.java) + } + + @Test + fun testRichTextQuoteElementBuilder() { + validateMethodNames(RichTextQuoteElement::class.java, RichTextQuoteElementBuilder::class.java) + } + + @Test + fun testRichTextSectionElementBuilder() { + validateMethodNames(RichTextSectionElement::class.java, RichTextSectionElementBuilder::class.java) + } + @Test fun testStaticSelectElementBuilder() { validateMethodNames(StaticSelectElement::class.java, StaticSelectElementBuilder::class.java) @@ -92,4 +112,4 @@ class BlockElementBuilderValidationTest { validateMethodNames(UsersSelectElement::class.java, UsersSelectElementBuilder::class.java) } -} \ No newline at end of file +} diff --git a/slack-api-model/src/main/java/com/slack/api/model/block/element/RichTextQuoteElement.java b/slack-api-model/src/main/java/com/slack/api/model/block/element/RichTextQuoteElement.java index bf58135bf..1b87fc453 100644 --- a/slack-api-model/src/main/java/com/slack/api/model/block/element/RichTextQuoteElement.java +++ b/slack-api-model/src/main/java/com/slack/api/model/block/element/RichTextQuoteElement.java @@ -19,5 +19,6 @@ public class RichTextQuoteElement extends BlockElement implements RichTextElemen private final String type = TYPE; @Builder.Default private List elements = new ArrayList<>(); + private Integer border; }