Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RichTextBlock support to Block Kit Kotlin DSL builder #1376

Merged
merged 8 commits into from
Sep 20, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ public static Gson createCamelCase(SlackConfig config) {
.registerTypeAdapter(ContextBlockElement.class, new GsonContextBlockElementFactory(failOnUnknownProps))
.registerTypeAdapter(BlockElement.class, new GsonBlockElementFactory(failOnUnknownProps))
.registerTypeAdapter(RichTextElement.class, new GsonRichTextElementFactory(failOnUnknownProps))
.registerTypeAdapter(MessageChangedEvent.PreviousMessage.class, new GsonMessageChangedEventPreviousMessageFactory(failOnUnknownProps))
KENNYSOFT marked this conversation as resolved.
Show resolved Hide resolved
.registerTypeAdapter(LogsResponse.DetailsChangedValue.class, new GsonAuditLogsDetailsChangedValueFactory(failOnUnknownProps))
.registerTypeAdapter(MessageChangedEvent.PreviousMessage.class, new GsonMessageChangedEventPreviousMessageFactory(failOnUnknownProps))
.registerTypeAdapter(AppWorkflow.StepInputValue.class, new GsonAppWorkflowStepInputValueFactory(failOnUnknownProps))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package com.slack.api.model.kotlin_extension.block
import com.slack.api.model.block.ContextBlock
import com.slack.api.model.kotlin_extension.block.container.MultiContextBlockElementContainer
import com.slack.api.model.kotlin_extension.block.dsl.ContextBlockElementDsl
import com.slack.api.model.kotlin_extension.block.element.dsl.BlockElementDsl

@BlockLayoutBuilder
class ContextBlockBuilder private constructor(
Expand All @@ -27,7 +26,7 @@ class ContextBlockBuilder private constructor(
/**
* An array of image elements and text objects. Maximum number of items is 10.
*
* @see BlockElementDsl for the set of supported interactive element objects
* @see ContextBlockElementDsl for the set of supported element objects
* @see <a href="https://api.slack.com/reference/block-kit/blocks#context">Context block documentation</a>
*/
fun elements(builder: ContextBlockElementDsl.() -> Unit) {
Expand All @@ -40,4 +39,4 @@ class ContextBlockBuilder private constructor(
.elements(elementsContainer.underlying)
.build()
}
}
}
Original file line number Diff line number Diff line change
@@ -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<RichTextBlock>, 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 <a href="https://api.slack.com/reference/block-kit/blocks#rich_text">Rich text block documentation</a>
*/
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 <a href="https://api.slack.com/reference/block-kit/blocks#rich_text">Rich text block documentation</a>
*/
fun elements(builder: RichTextElementDsl.() -> Unit) {
elementsContainer.apply(builder)
}

override fun build(): RichTextBlock {
return RichTextBlock.builder()
.blockId(blockId)
.elements(elementsContainer.underlying)
.build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
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<RichTextElement>()

override fun broadcast(range: BroadcastRange) {
underlying += RichTextSectionElement.Broadcast.builder()
.range(range.value)
.build()
}

override fun color(value: String) {
underlying += RichTextSectionElement.Color.builder()
.value(value)
.build()
}

override fun channel(channelId: String, style: LimitedTextStyle?) {
underlying += RichTextSectionElement.Channel.builder()
.channelId(channelId)
.style(style)
.build()
}

override fun date(timestamp: Int, format: String, url: String?, fallback: String?) {
underlying += RichTextSectionElement.Date.builder()
.timestamp(timestamp)
.format(format)
.url(url)
.fallback(fallback)
.build()
}

override fun emoji(name: String) {
underlying += RichTextSectionElement.Emoji.builder()
.name(name)
.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()
}
}
Original file line number Diff line number Diff line change
@@ -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 <a href="https://api.slack.com/reference/block-kit/blocks#broadcast-element-type">Rich text object documentation</a>
*/
fun broadcast(range: BroadcastRange)
KENNYSOFT marked this conversation as resolved.
Show resolved Hide resolved

/**
* @param value The hex value for the color.
* @see <a href="https://api.slack.com/reference/block-kit/blocks#color-element-type">Rich text object documentation</a>
*/
fun color(value: String)
KENNYSOFT marked this conversation as resolved.
Show resolved Hide resolved

/**
* @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 <a href="https://api.slack.com/reference/block-kit/blocks#channel-element-type">Rich text object documentation</a>
*/
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 <a href="https://api.slack.com/reference/block-kit/blocks#date-element-type">Rich text object documentation</a>
*/
fun date(timestamp: Int, format: String, url: String? = null, fallback: String? = null)
KENNYSOFT marked this conversation as resolved.
Show resolved Hide resolved

/**
* @param name The name of the emoji; i.e. "wave" or "wave::skin-tone-2".
* @see <a href="https://api.slack.com/reference/block-kit/blocks#emoji-element-type">Rich text object documentation</a>
*/
fun emoji(name: String)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

emoji method should accept two patterns:

  • name: String, skinTone: Int? = null, style: TextStyle? = null
  • unicode: String

overloading with string type may not work; if that's the case, skipping unicode here is okay

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://api.slack.com/reference/block-kit/blocks#emoji-element-type

image

I've implemented based on the documents, but yes I've seen that RichTextSectionElement.Emoji has those fields and will take a look for them.

Copy link
Contributor Author

@KENNYSOFT KENNYSOFT Sep 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved in 1be897c

By the way, Kotlin allows for overloading but I think it can be confusing. With this interface:

fun emoji(name: String, skinTone: Int? = null, style: LimitedTextStyle? = null)
fun emoji(unicode: String)

The call emoji("aaa") is directed to the second one, and to use the first method then we should use a named parameter like emoji(name = "aaa").

So I've skipped the Unicode version for now, but also suggesting a different DSL like emojiUnicode("U+AC00") with little confidence.


/**
* @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 <a href="https://api.slack.com/reference/block-kit/blocks#link-element-type">Rich text object documentation</a>
*/
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 <a href="https://api.slack.com/reference/block-kit/blocks#text-element-type">Rich text object documentation</a>
*/
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 <a href="https://api.slack.com/reference/block-kit/blocks#user-element-type">Rich text object documentation</a>
*/
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 <a href="https://api.slack.com/reference/block-kit/blocks#user-group-element-type">Rich text object documentation</a>
*/
fun usergroup(usergroupId: String, style: LimitedTextStyle? = null)

}
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,15 @@ 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()
}

override fun shareShortcut(builder: ShareShortcutBlockBuilder.() -> Unit) {
underlying += ShareShortcutBlockBuilder().apply(builder).build()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="https://api.slack.com/reference/block-kit/blocks#rich_text">Rich text documentation</a>
*/
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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ enum class ButtonStyle {
* only be used for one button within a set.
*/
PRIMARY {
override val value: String? = "primary"
override val value = "primary"
},

/**
* Danger gives buttons a red outline and text, and should be used when the action is destructive. Use danger even
* more sparingly than primary.
*/
DANGER {
override val value: String? = "danger"
override val value = "danger"
};

abstract val value: String?
abstract val value: String
KENNYSOFT marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class RichTextInputElementBuilder : Builder<RichTextInputElement> {
* view_submission payload to identify the value of the input element. Should be unique among all other action_ids
* used elsewhere by your app. Maximum length for this field is 255 characters.*
*
* @see <a href="https://api.slack.com/reference/block-kit/block-elements#input">Plain text input documentation</a>
* @see <a href="https://api.slack.com/reference/block-kit/block-elements#rich_text_input">Rich text input documentation</a>
*/
fun actionId(id: String) {
actionId = id
Expand All @@ -32,7 +32,7 @@ class RichTextInputElementBuilder : Builder<RichTextInputElement> {
*
* The placeholder text shown in the plain-text input. Maximum length for the text in this field is 150 characters.
*
* @see <a href="https://api.slack.com/reference/block-kit/block-elements#input">Plain text input documentation</a>
* @see <a href="https://api.slack.com/reference/block-kit/block-elements#rich_text_input">Rich text input documentation</a>
*/
fun placeholder(text: String, emoji: Boolean? = null) {
placeholder = PlainTextObject(text, emoji)
Expand Down
Loading
Loading