slackblocks 2.8.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -2
- data/lib/slackblocks/generated/limits.rb +1 -1
- data/lib/slackblocks/generated/models.rb +5 -5
- data/lib/slackblocks/validator.rb +53 -2
- data/lib/slackblocks/value.rb +6 -0
- data/lib/slackblocks/version.rb +2 -2
- data/sig/slackblocks/generated.rbs +10 -8
- data/sig/slackblocks.rbs +2 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2854904b3e2e3ecbdf99f451ce5fa572336b2f9ed1946d18b9a5f6089d6ea889
|
|
4
|
+
data.tar.gz: 2fc7f9e644c71a5554b4095e2896374a9be3d8cd7366d7c700a9bbf4899fc604
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e63f0c5e302a0ee9841fcd18cf143cb9995ab9a6314063efc5f8111a792423dec1c8dbdec3503602155823b71257d37a856953f973bf1e3d2a5c6a5eddec618
|
|
7
|
+
data.tar.gz: 3a30a4e84adb15803679cc90b2a7d1b1fc24d687b6a56e558f5278414067e962bc55782c6efdcb75324192ed5be45e2d113ef7bd129443e6352bdfdf0ba5e7c3
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.1.0] — 2026-10-28
|
|
4
|
+
|
|
5
|
+
- Coordinate this release with the new Haskell implementation and ten-language documentation. Existing runtime support remains unchanged.
|
|
6
|
+
|
|
7
|
+
## [3.0.0] — 2026-10-24
|
|
8
|
+
|
|
9
|
+
- Add native button `agent_prompt` and `agent_prompt_display` fields, Unicode prompt-length validation and the display/prompt dependency (spec 1.4.0). Document interaction acknowledgement regardless of Slackbot AI availability. Existing button calls remain compatible.
|
|
10
|
+
|
|
11
|
+
- Release the coordinated 3.0.0 API across all nine implementations; supported language/runtime minimums remain unchanged.
|
|
12
|
+
- Breaking: number-input bounds are strings in stored values and getters; numeric construction conveniences remain. See the migration guide before upgrading.
|
|
13
|
+
- Freeze the 2.8.0 documentation and fix shared language/version selector artwork and arrows across documentation versions.
|
|
14
|
+
|
|
15
|
+
- SB-006: reject messages with more than two data visualization blocks after composition, including attachment charts. The rule does not impose a two-chart limit on views.
|
|
16
|
+
|
|
17
|
+
- SB-003: reject explicitly supplied `focus_on_load` on external multi-selects, including false. Omit this deprecated parameter; other selectors retain focus support.
|
|
18
|
+
- Reject negative external-query thresholds, empty supplied static options/groups and malformed date/time strings, matching the retained Slack structural probes. Date shape checks do not add a calendar-validity guarantee; empty image alt text remains accepted (SB-013).
|
|
19
|
+
|
|
20
|
+
- Reject a plain-text input whose supplied `min_length` exceeds `max_length` (`out-of-range`); equal, zero-minimum and omitted bounds remain supported (SB-013).
|
|
21
|
+
|
|
22
|
+
- Correct number-input bounds to string wire values, retain native numeric convenience arguments, and reject nonfinite/inverted bounds (SB-001, spec 1.3.0). Bound getters now return strings; reflection/signature consumers must migrate.
|
|
23
|
+
|
|
24
|
+
- Clarify that `submit_disabled`, including explicit false, is configuration-modal-only; ordinary modal examples omit it (SB-015).
|
|
25
|
+
|
|
3
26
|
## [2.8.0] — 2026-10-18
|
|
4
27
|
|
|
5
28
|
- Coordinate the first Elixir implementation release; existing language support remains unchanged.
|
|
@@ -8,8 +31,6 @@
|
|
|
8
31
|
|
|
9
32
|
- Add the PHP implementation to the coordinated release and documentation.
|
|
10
33
|
|
|
11
|
-
## [Unreleased]
|
|
12
|
-
|
|
13
34
|
## [2.6.0] — 2026-10-09
|
|
14
35
|
|
|
15
36
|
- Keep application metadata outside message-wide Block Kit text totals.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Generated from spec/limits.json. Do not edit by hand.
|
|
2
2
|
module Slackblocks
|
|
3
3
|
module Limits
|
|
4
|
-
VALUES = {"action_id.max_length" => 255, "actions.elements.max_items" => 25, "alert.text.max_length" => 200, "block_id.max_length" => 255, "button.accessibility_label.max_length" => 75, "button.text.max_length" => 75, "button.url.max_length" => 3000, "button.value.max_length" => 2000, "card.actions.max_items" => 3, "card.body.max_length" => 200, "card.subtext.max_length" => 200, "card.subtitle.max_length" => 150, "card.title.max_length" => 150, "carousel.elements.max_items" => 10, "carousel.elements.min_items" => 1, "checkboxes.options.max_items" => 10, "checkboxes.options.min_items" => 1, "confirmation.confirm.max_length" => 30, "confirmation.deny.max_length" => 30, "confirmation.text.max_length" => 300, "confirmation.title.max_length" => 100, "container.child_blocks.max_items" => 10, "container.child_blocks.min_items" => 1, "container.subtitle.max_length" => 150, "container.title.max_length" => 150, "context.elements.max_items" => 10, "context_actions.elements.max_items" => 5, "conversation_filter.include.min_items" => 1, "data_table.cell_text.min_length" => 1, "data_table.columns.max_items" => 20, "data_table.columns.min_items" => 1, "data_table.content.max_length" => 20000, "data_table.page_size.max" => 100, "data_table.page_size.min" => 1, "data_table.row_header_column_index.min" => 0, "data_table.rows.max_items" => 201, "data_table.rows.min_items" => 2, "data_table.total_content.max_length" => 20000, "data_visualization.axis_label.max_length" => 50, "data_visualization.categories.max_items" => 20, "data_visualization.categories.min_items" => 1, "data_visualization.category_label.max_length" => 20, "data_visualization.data.max_items" => 20, "data_visualization.data.min_items" => 1, "data_visualization.point_label.max_length" => 20, "data_visualization.segment.label.max_length" => 20, "data_visualization.segment.value.exclusive_min" => 0, "data_visualization.segments.max_items" => 12, "data_visualization.segments.min_items" => 1, "data_visualization.series.max_items" => 12, "data_visualization.series.min_items" => 1, "data_visualization.series_name.max_length" => 20, "data_visualization.title.max_length" => 50, "date_picker.placeholder.max_length" => 150, "dispatch_action_configuration.trigger_actions_on.max_items" => 2, "dispatch_action_configuration.trigger_actions_on.min_items" => 1, "email_input.placeholder.max_length" => 150, "feedback_button.accessibility_label.max_length" => 75, "feedback_button.text.max_length" => 75, "feedback_button.value.max_length" => 2000, "file_input.max_files.max" => 10, "file_input.max_files.min" => 1, "header.text.max_length" => 150, "icon_button.accessibility_label.max_length" => 75, "icon_button.value.max_length" => 2000, "icon_button.visible_to_user_ids.max_items" => 10, "image.alt_text.max_length" => 2000, "image.image_url.max_length" => 3000, "image.title.max_length" => 2000, "image_element.alt_text.max_length" => 2000, "image_element.image_url.max_length" => 3000, "input.hint.max_length" => 2000, "input.label.max_length" => 2000, "markdown.text.max_length" => 12000, "markdown.total_text.max_length" => 12000, "message.attachments.max_items" => 100, "message.blocks.max_items" => 50, "message.channel.min_length" => 1, "multi_select.max_selected_items.min" => 1, "number_input.placeholder.max_length" => 150, "option.description.max_length" => 75, "option.text.max_length" => 75, "option.url.max_length" => 3000, "option.value.max_length" => 150, "option_group.label.max_length" => 75, "option_group.options.max_items" => 100, "option_group.options.min_items" => 1, "overflow.options.max_items" => 5, "overflow.options.min_items" => 1, "plain_text_input.max_length.max" => 3000, "plain_text_input.max_length.min" => 1, "plain_text_input.min_length.max" => 3000, "plain_text_input.min_length.min" => 0, "plain_text_input.placeholder.max_length" => 150, "plan.tasks.max_items" => 50, "radio_buttons.options.max_items" => 10, "radio_buttons.options.min_items" => 1, "rich_text_input.max_lines.max" => 100, "rich_text_input.max_lines.min" => 1, "rich_text_input.min_lines.max" => 100, "rich_text_input.min_lines.min" => 1, "rich_text_input.placeholder.max_length" => 150, "rich_text_list.border.max" => 1, "rich_text_list.border.min" => 0, "rich_text_list.indent.max" => 8, "rich_text_list.indent.min" => 0, "rich_text_list.offset.min" => 0, "rich_text_preformatted.border.max" => 1, "rich_text_preformatted.border.min" => 0, "rich_text_quote.border.max" => 1, "rich_text_quote.border.min" => 0, "section.fields.item_max_length" => 2000, "section.fields.max_items" => 10, "section.text.max_length" => 3000, "select.option_groups.max_items" => 100, "select.options.max_items" => 100, "select.placeholder.max_length" => 150, "table.column_settings.max_items" => 20, "table.columns.max_items" => 20, "table.rows.max_items" => 100, "text.max_length" => 3000, "text.min_length" => 1, "time_picker.placeholder.max_length" => 150, "url_input.placeholder.max_length" => 150, "video.alt_text.max_length" => 2000, "video.author_name.max_length" => 50, "video.description.max_length" => 200, "video.provider_icon_url.max_length" => 2000, "video.provider_name.max_length" => 50, "video.thumbnail_url.max_length" => 3000, "video.title.max_length" => 200, "video.title_url.max_length" => 2000, "video.video_url.max_length" => 3000, "view.blocks.max_items" => 100, "view.callback_id.max_length" => 255, "view.close.max_length" => 24, "view.external_id.max_length" => 255, "view.private_metadata.max_length" => 3000, "view.submit.max_length" => 24, "view.title.max_length" => 24, "workflow_button.accessibility_label.max_length" => 75, "workflow_button.text.max_length" => 75}.freeze
|
|
4
|
+
VALUES = {"action_id.max_length" => 255, "actions.elements.max_items" => 25, "alert.text.max_length" => 200, "block_id.max_length" => 255, "button.accessibility_label.max_length" => 75, "button.agent_prompt.max_length" => 4000, "button.text.max_length" => 75, "button.url.max_length" => 3000, "button.value.max_length" => 2000, "card.actions.max_items" => 3, "card.body.max_length" => 200, "card.subtext.max_length" => 200, "card.subtitle.max_length" => 150, "card.title.max_length" => 150, "carousel.elements.max_items" => 10, "carousel.elements.min_items" => 1, "checkboxes.options.max_items" => 10, "checkboxes.options.min_items" => 1, "confirmation.confirm.max_length" => 30, "confirmation.deny.max_length" => 30, "confirmation.text.max_length" => 300, "confirmation.title.max_length" => 100, "container.child_blocks.max_items" => 10, "container.child_blocks.min_items" => 1, "container.subtitle.max_length" => 150, "container.title.max_length" => 150, "context.elements.max_items" => 10, "context_actions.elements.max_items" => 5, "conversation_filter.include.min_items" => 1, "data_table.cell_text.min_length" => 1, "data_table.columns.max_items" => 20, "data_table.columns.min_items" => 1, "data_table.content.max_length" => 20000, "data_table.page_size.max" => 100, "data_table.page_size.min" => 1, "data_table.row_header_column_index.min" => 0, "data_table.rows.max_items" => 201, "data_table.rows.min_items" => 2, "data_table.total_content.max_length" => 20000, "data_visualization.axis_label.max_length" => 50, "data_visualization.categories.max_items" => 20, "data_visualization.categories.min_items" => 1, "data_visualization.category_label.max_length" => 20, "data_visualization.data.max_items" => 20, "data_visualization.data.min_items" => 1, "data_visualization.point_label.max_length" => 20, "data_visualization.segment.label.max_length" => 20, "data_visualization.segment.value.exclusive_min" => 0, "data_visualization.segments.max_items" => 12, "data_visualization.segments.min_items" => 1, "data_visualization.series.max_items" => 12, "data_visualization.series.min_items" => 1, "data_visualization.series_name.max_length" => 20, "data_visualization.title.max_length" => 50, "date_picker.placeholder.max_length" => 150, "dispatch_action_configuration.trigger_actions_on.max_items" => 2, "dispatch_action_configuration.trigger_actions_on.min_items" => 1, "email_input.placeholder.max_length" => 150, "feedback_button.accessibility_label.max_length" => 75, "feedback_button.text.max_length" => 75, "feedback_button.value.max_length" => 2000, "file_input.max_files.max" => 10, "file_input.max_files.min" => 1, "header.text.max_length" => 150, "icon_button.accessibility_label.max_length" => 75, "icon_button.value.max_length" => 2000, "icon_button.visible_to_user_ids.max_items" => 10, "image.alt_text.max_length" => 2000, "image.image_url.max_length" => 3000, "image.title.max_length" => 2000, "image_element.alt_text.max_length" => 2000, "image_element.image_url.max_length" => 3000, "input.hint.max_length" => 2000, "input.label.max_length" => 2000, "markdown.text.max_length" => 12000, "markdown.total_text.max_length" => 12000, "message.attachments.max_items" => 100, "message.blocks.max_items" => 50, "message.channel.min_length" => 1, "message.data_visualizations.max_items" => 2, "multi_select.max_selected_items.min" => 1, "number_input.placeholder.max_length" => 150, "option.description.max_length" => 75, "option.text.max_length" => 75, "option.url.max_length" => 3000, "option.value.max_length" => 150, "option_group.label.max_length" => 75, "option_group.options.max_items" => 100, "option_group.options.min_items" => 1, "overflow.options.max_items" => 5, "overflow.options.min_items" => 1, "plain_text_input.max_length.max" => 3000, "plain_text_input.max_length.min" => 1, "plain_text_input.min_length.max" => 3000, "plain_text_input.min_length.min" => 0, "plain_text_input.placeholder.max_length" => 150, "plan.tasks.max_items" => 50, "radio_buttons.options.max_items" => 10, "radio_buttons.options.min_items" => 1, "rich_text_input.max_lines.max" => 100, "rich_text_input.max_lines.min" => 1, "rich_text_input.min_lines.max" => 100, "rich_text_input.min_lines.min" => 1, "rich_text_input.placeholder.max_length" => 150, "rich_text_list.border.max" => 1, "rich_text_list.border.min" => 0, "rich_text_list.indent.max" => 8, "rich_text_list.indent.min" => 0, "rich_text_list.offset.min" => 0, "rich_text_preformatted.border.max" => 1, "rich_text_preformatted.border.min" => 0, "rich_text_quote.border.max" => 1, "rich_text_quote.border.min" => 0, "section.fields.item_max_length" => 2000, "section.fields.max_items" => 10, "section.text.max_length" => 3000, "select.min_query_length.min" => 0, "select.option_groups.max_items" => 100, "select.option_groups.min_items" => 1, "select.options.max_items" => 100, "select.options.min_items" => 1, "select.placeholder.max_length" => 150, "table.column_settings.max_items" => 20, "table.columns.max_items" => 20, "table.rows.max_items" => 100, "text.max_length" => 3000, "text.min_length" => 1, "time_picker.placeholder.max_length" => 150, "url_input.placeholder.max_length" => 150, "video.alt_text.max_length" => 2000, "video.author_name.max_length" => 50, "video.description.max_length" => 200, "video.provider_icon_url.max_length" => 2000, "video.provider_name.max_length" => 50, "video.thumbnail_url.max_length" => 3000, "video.title.max_length" => 200, "video.title_url.max_length" => 2000, "video.video_url.max_length" => 3000, "view.blocks.max_items" => 100, "view.callback_id.max_length" => 255, "view.close.max_length" => 24, "view.external_id.max_length" => 255, "view.private_metadata.max_length" => 3000, "view.submit.max_length" => 24, "view.title.max_length" => 24, "workflow_button.accessibility_label.max_length" => 75, "workflow_button.text.max_length" => 75}.freeze
|
|
5
5
|
def self.[](path)
|
|
6
6
|
VALUES.fetch(path)
|
|
7
7
|
end
|
|
@@ -295,10 +295,10 @@ module Slackblocks
|
|
|
295
295
|
class ButtonElement
|
|
296
296
|
include Value
|
|
297
297
|
include Element
|
|
298
|
-
attr_reader :text, :action_id, :url, :value, :style, :confirm, :accessibility_label, :additional_fields
|
|
298
|
+
attr_reader :text, :action_id, :url, :value, :style, :confirm, :accessibility_label, :agent_prompt, :agent_prompt_display, :additional_fields
|
|
299
299
|
end
|
|
300
300
|
ButtonElement.const_set(:WIRE_TYPE, "button")
|
|
301
|
-
ButtonElement.const_set(:FIELD_SPECS, Value.deep_freeze([{"name" => "text", "wire" => "text", "kind" => "text", "target" => "PlainText", "coerce" => "plain_text", "flags" => nil, "limits" => "button.text", "required" => true}, {"name" => "action_id", "wire" => "action_id", "kind" => "string", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => "action_id", "required" => false}, {"name" => "url", "wire" => "url", "kind" => "string", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => "button.url", "required" => false}, {"name" => "value", "wire" => "value", "kind" => "string", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => "button.value", "required" => false}, {"name" => "style", "wire" => "style", "kind" => "enum", "target" => "ButtonStyle", "coerce" => nil, "flags" => nil, "limits" => nil, "required" => false}, {"name" => "confirm", "wire" => "confirm", "kind" => "object", "target" => "Confirmation", "coerce" => nil, "flags" => nil, "limits" => nil, "required" => false}, {"name" => "accessibility_label", "wire" => "accessibility_label", "kind" => "string", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => "button.accessibility_label", "required" => false}]))
|
|
301
|
+
ButtonElement.const_set(:FIELD_SPECS, Value.deep_freeze([{"name" => "text", "wire" => "text", "kind" => "text", "target" => "PlainText", "coerce" => "plain_text", "flags" => nil, "limits" => "button.text", "required" => true}, {"name" => "action_id", "wire" => "action_id", "kind" => "string", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => "action_id", "required" => false}, {"name" => "url", "wire" => "url", "kind" => "string", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => "button.url", "required" => false}, {"name" => "value", "wire" => "value", "kind" => "string", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => "button.value", "required" => false}, {"name" => "style", "wire" => "style", "kind" => "enum", "target" => "ButtonStyle", "coerce" => nil, "flags" => nil, "limits" => nil, "required" => false}, {"name" => "confirm", "wire" => "confirm", "kind" => "object", "target" => "Confirmation", "coerce" => nil, "flags" => nil, "limits" => nil, "required" => false}, {"name" => "accessibility_label", "wire" => "accessibility_label", "kind" => "string", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => "button.accessibility_label", "required" => false}, {"name" => "agent_prompt", "wire" => "agent_prompt", "kind" => "string", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => "button.agent_prompt", "required" => false}, {"name" => "agent_prompt_display", "wire" => "agent_prompt_display", "kind" => "string", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => nil, "required" => false}]))
|
|
302
302
|
ButtonElement.const_set(:DEFAULTS, Value.deep_freeze({}))
|
|
303
303
|
ButtonElement.singleton_class.prepend(KeywordOnly)
|
|
304
304
|
|
|
@@ -397,7 +397,7 @@ module Slackblocks
|
|
|
397
397
|
attr_reader :action_id, :min_query_length, :initial_options, :confirm, :max_selected_items, :focus_on_load, :placeholder, :additional_fields
|
|
398
398
|
end
|
|
399
399
|
ExternalMultiSelectElement.const_set(:WIRE_TYPE, "multi_external_select")
|
|
400
|
-
ExternalMultiSelectElement.const_set(:FIELD_SPECS, Value.deep_freeze([{"name" => "action_id", "wire" => "action_id", "kind" => "string", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => "action_id", "required" => false}, {"name" => "min_query_length", "wire" => "min_query_length", "kind" => "int", "target" => nil, "coerce" => nil, "flags" => nil, "limits" =>
|
|
400
|
+
ExternalMultiSelectElement.const_set(:FIELD_SPECS, Value.deep_freeze([{"name" => "action_id", "wire" => "action_id", "kind" => "string", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => "action_id", "required" => false}, {"name" => "min_query_length", "wire" => "min_query_length", "kind" => "int", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => "select.min_query_length", "required" => false}, {"name" => "initial_options", "wire" => "initial_options", "kind" => "list", "target" => "Option", "coerce" => nil, "flags" => nil, "limits" => nil, "required" => false}, {"name" => "confirm", "wire" => "confirm", "kind" => "object", "target" => "Confirmation", "coerce" => nil, "flags" => nil, "limits" => nil, "required" => false}, {"name" => "max_selected_items", "wire" => "max_selected_items", "kind" => "int", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => "multi_select.max_selected_items", "required" => false}, {"name" => "focus_on_load", "wire" => "focus_on_load", "kind" => "boolean", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => nil, "required" => false}, {"name" => "placeholder", "wire" => "placeholder", "kind" => "text", "target" => "PlainText", "coerce" => "plain_text", "flags" => nil, "limits" => "select.placeholder", "required" => false}]))
|
|
401
401
|
ExternalMultiSelectElement.const_set(:DEFAULTS, Value.deep_freeze({}))
|
|
402
402
|
ExternalMultiSelectElement.singleton_class.prepend(KeywordOnly)
|
|
403
403
|
|
|
@@ -408,7 +408,7 @@ module Slackblocks
|
|
|
408
408
|
attr_reader :action_id, :min_query_length, :initial_option, :confirm, :focus_on_load, :placeholder, :additional_fields
|
|
409
409
|
end
|
|
410
410
|
ExternalSelectElement.const_set(:WIRE_TYPE, "external_select")
|
|
411
|
-
ExternalSelectElement.const_set(:FIELD_SPECS, Value.deep_freeze([{"name" => "action_id", "wire" => "action_id", "kind" => "string", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => "action_id", "required" => false}, {"name" => "min_query_length", "wire" => "min_query_length", "kind" => "int", "target" => nil, "coerce" => nil, "flags" => nil, "limits" =>
|
|
411
|
+
ExternalSelectElement.const_set(:FIELD_SPECS, Value.deep_freeze([{"name" => "action_id", "wire" => "action_id", "kind" => "string", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => "action_id", "required" => false}, {"name" => "min_query_length", "wire" => "min_query_length", "kind" => "int", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => "select.min_query_length", "required" => false}, {"name" => "initial_option", "wire" => "initial_option", "kind" => "object", "target" => "Option", "coerce" => nil, "flags" => nil, "limits" => nil, "required" => false}, {"name" => "confirm", "wire" => "confirm", "kind" => "object", "target" => "Confirmation", "coerce" => nil, "flags" => nil, "limits" => nil, "required" => false}, {"name" => "focus_on_load", "wire" => "focus_on_load", "kind" => "boolean", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => nil, "required" => false}, {"name" => "placeholder", "wire" => "placeholder", "kind" => "text", "target" => "PlainText", "coerce" => "plain_text", "flags" => nil, "limits" => "select.placeholder", "required" => false}]))
|
|
412
412
|
ExternalSelectElement.const_set(:DEFAULTS, Value.deep_freeze({}))
|
|
413
413
|
ExternalSelectElement.singleton_class.prepend(KeywordOnly)
|
|
414
414
|
|
|
@@ -463,7 +463,7 @@ module Slackblocks
|
|
|
463
463
|
attr_reader :action_id, :is_decimal_allowed, :initial_value, :min_value, :max_value, :dispatch_action_config, :focus_on_load, :placeholder, :additional_fields
|
|
464
464
|
end
|
|
465
465
|
NumberInputElement.const_set(:WIRE_TYPE, "number_input")
|
|
466
|
-
NumberInputElement.const_set(:FIELD_SPECS, Value.deep_freeze([{"name" => "action_id", "wire" => "action_id", "kind" => "string", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => "action_id", "required" => false}, {"name" => "is_decimal_allowed", "wire" => "is_decimal_allowed", "kind" => "boolean", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => nil, "required" => true}, {"name" => "initial_value", "wire" => "initial_value", "kind" => "string", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => nil, "required" => false}, {"name" => "min_value", "wire" => "min_value", "kind" => "
|
|
466
|
+
NumberInputElement.const_set(:FIELD_SPECS, Value.deep_freeze([{"name" => "action_id", "wire" => "action_id", "kind" => "string", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => "action_id", "required" => false}, {"name" => "is_decimal_allowed", "wire" => "is_decimal_allowed", "kind" => "boolean", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => nil, "required" => true}, {"name" => "initial_value", "wire" => "initial_value", "kind" => "string", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => nil, "required" => false}, {"name" => "min_value", "wire" => "min_value", "kind" => "string", "target" => nil, "coerce" => "decimal_string", "flags" => nil, "limits" => nil, "required" => false}, {"name" => "max_value", "wire" => "max_value", "kind" => "string", "target" => nil, "coerce" => "decimal_string", "flags" => nil, "limits" => nil, "required" => false}, {"name" => "dispatch_action_config", "wire" => "dispatch_action_config", "kind" => "object", "target" => "DispatchActionConfiguration", "coerce" => nil, "flags" => nil, "limits" => nil, "required" => false}, {"name" => "focus_on_load", "wire" => "focus_on_load", "kind" => "boolean", "target" => nil, "coerce" => nil, "flags" => nil, "limits" => nil, "required" => false}, {"name" => "placeholder", "wire" => "placeholder", "kind" => "text", "target" => "PlainText", "coerce" => "plain_text", "flags" => nil, "limits" => "number_input.placeholder", "required" => false}]))
|
|
467
467
|
NumberInputElement.const_set(:DEFAULTS, Value.deep_freeze({}))
|
|
468
468
|
NumberInputElement.singleton_class.prepend(KeywordOnly)
|
|
469
469
|
|
|
@@ -40,6 +40,12 @@ module Slackblocks
|
|
|
40
40
|
|
|
41
41
|
def self.special(klass, wire, path)
|
|
42
42
|
case klass.name.split("::").last
|
|
43
|
+
when "ButtonElement"
|
|
44
|
+
if wire.key?("agent_prompt_display") && !wire.key?("agent_prompt")
|
|
45
|
+
fail!("missing-required", "#{path}.agent_prompt", "required when agent_prompt_display is supplied")
|
|
46
|
+
end
|
|
47
|
+
when "ExternalMultiSelectElement"
|
|
48
|
+
fail!("invalid-usage", "#{path}.focus_on_load", "unsupported for external multi-select menus; omit this field") if wire.key?("focus_on_load")
|
|
43
49
|
when "SlackIcon"
|
|
44
50
|
unless Vocabulary::ICON_NAMES.include?(wire["name"])
|
|
45
51
|
fail!("type-mismatch", "#{path}.name", "unknown Slack icon")
|
|
@@ -99,8 +105,24 @@ module Slackblocks
|
|
|
99
105
|
end
|
|
100
106
|
when "DispatchActionConfiguration"
|
|
101
107
|
fail!("missing-required", "#{path}.trigger_actions_on", "required") unless wire.key?("trigger_actions_on")
|
|
108
|
+
when "DatePickerElement"
|
|
109
|
+
if wire.key?("initial_date") && !wire["initial_date"].match?(/\A[0-9]{4}-[0-9]{2}-[0-9]{2}\z/)
|
|
110
|
+
fail!("type-mismatch", "#{path}.initial_date", "expected YYYY-MM-DD")
|
|
111
|
+
end
|
|
112
|
+
when "TimePickerElement"
|
|
113
|
+
if wire.key?("initial_time") && !wire["initial_time"].match?(/\A([01][0-9]|2[0-3]):[0-5][0-9]\z/)
|
|
114
|
+
fail!("type-mismatch", "#{path}.initial_time", "expected HH:mm in 24-hour time")
|
|
115
|
+
end
|
|
116
|
+
when "PlainTextInputElement"
|
|
117
|
+
if wire["min_length"] && wire["max_length"] && wire["min_length"] > wire["max_length"]
|
|
118
|
+
fail!("out-of-range", "#{path}.min_length", "cannot exceed max_length")
|
|
119
|
+
end
|
|
102
120
|
when "NumberInputElement"
|
|
103
|
-
|
|
121
|
+
minimum, maximum = %w[min_value max_value].map do |field|
|
|
122
|
+
text = wire[field]
|
|
123
|
+
decimal_bound(text, "#{path}.#{field}") unless text.nil?
|
|
124
|
+
end
|
|
125
|
+
if minimum && maximum && compare_decimal_bounds(minimum, maximum).positive?
|
|
104
126
|
fail!("out-of-range", path, "min_value exceeds max_value")
|
|
105
127
|
end
|
|
106
128
|
when "DataTableBlock", "TableBlock"
|
|
@@ -131,6 +153,32 @@ module Slackblocks
|
|
|
131
153
|
end
|
|
132
154
|
end
|
|
133
155
|
|
|
156
|
+
# Compare the significand and decimal rank, without rounding or expanding an exponent.
|
|
157
|
+
def self.decimal_bound(text, path)
|
|
158
|
+
unless text.match?(/\A[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?\z/)
|
|
159
|
+
fail!("type-mismatch", path, "expected a numeric string")
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
parts = text.sub(/\A[+-]/, "").split(/[eE]/)
|
|
163
|
+
mantissa = parts.fetch(0).split(".", -1)
|
|
164
|
+
fraction = mantissa.fetch(1, "")
|
|
165
|
+
digits = (mantissa.fetch(0) + fraction).sub(/\A0+/, "")
|
|
166
|
+
return [0, 0, ""] if digits.empty?
|
|
167
|
+
|
|
168
|
+
rank = Integer(parts.fetch(1, "0"), 10) + digits.length - fraction.length
|
|
169
|
+
[text.start_with?("-") ? -1 : 1, rank, digits.sub(/0+\z/, "")]
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def self.compare_decimal_bounds(left, right)
|
|
173
|
+
sign = left[0] <=> right[0]
|
|
174
|
+
return sign unless sign.zero?
|
|
175
|
+
return 0 if left[0].zero?
|
|
176
|
+
|
|
177
|
+
rank = left[1] <=> right[1]
|
|
178
|
+
width = [left[2].length, right[2].length].max
|
|
179
|
+
left[0] * (rank.zero? ? left[2].ljust(width, "0") <=> right[2].ljust(width, "0") : rank)
|
|
180
|
+
end
|
|
181
|
+
|
|
134
182
|
def self.surface(blocks, name, path)
|
|
135
183
|
allowed = Vocabulary::SURFACE_BLOCK_TYPES.fetch(name)
|
|
136
184
|
blocks.each_with_index do |block, index|
|
|
@@ -158,9 +206,10 @@ module Slackblocks
|
|
|
158
206
|
|
|
159
207
|
def self.message(wire, path)
|
|
160
208
|
surface(wire["blocks"] || [], "message", "#{path}.blocks")
|
|
161
|
-
totals = {"markdown" => 0, "data_table" => 0}
|
|
209
|
+
totals = {"markdown" => 0, "data_table" => 0, "data_visualization" => 0}
|
|
162
210
|
tally(wire["blocks"], totals)
|
|
163
211
|
(wire["attachments"] || []).each { |attachment| tally(attachment["blocks"], totals) }
|
|
212
|
+
fail!("length-exceeded", path, "a message may contain at most two data visualization blocks") if totals["data_visualization"] > limit("message.data_visualizations.max_items")
|
|
164
213
|
if totals["markdown"] > limit("markdown.total_text.max_length")
|
|
165
214
|
fail!("length-exceeded", path, "message markdown total exceeded")
|
|
166
215
|
end
|
|
@@ -174,6 +223,8 @@ module Slackblocks
|
|
|
174
223
|
when Hash
|
|
175
224
|
if node["type"] == "markdown"
|
|
176
225
|
totals["markdown"] += node.fetch("text").length
|
|
226
|
+
elsif node["type"] == "data_visualization"
|
|
227
|
+
totals["data_visualization"] += 1
|
|
177
228
|
elsif node["type"] == "data_table"
|
|
178
229
|
totals["data_table"] += characters(node.fetch("rows"))
|
|
179
230
|
elsif node["type"] == "container"
|
data/lib/slackblocks/value.rb
CHANGED
|
@@ -88,6 +88,12 @@ module Slackblocks
|
|
|
88
88
|
def self.normalize_field(field, value, path)
|
|
89
89
|
kind = field.fetch("kind")
|
|
90
90
|
target = field["target"] && Slackblocks.const_get(field["target"], false)
|
|
91
|
+
if field["coerce"] == "decimal_string"
|
|
92
|
+
unless value.is_a?(String) || value.is_a?(Integer) || value.instance_of?(Float)
|
|
93
|
+
fail!("type-mismatch", path, "expected a numeric string or number")
|
|
94
|
+
end
|
|
95
|
+
return string(value.to_s, path)
|
|
96
|
+
end
|
|
91
97
|
case kind
|
|
92
98
|
when "string"
|
|
93
99
|
string(value, path)
|
data/lib/slackblocks/version.rb
CHANGED
|
@@ -367,10 +367,12 @@ module Slackblocks
|
|
|
367
367
|
attr_reader style: (:primary | :danger)?
|
|
368
368
|
attr_reader confirm: (Confirmation)?
|
|
369
369
|
attr_reader accessibility_label: (String)?
|
|
370
|
+
attr_reader agent_prompt: (String)?
|
|
371
|
+
attr_reader agent_prompt_display: (String)?
|
|
370
372
|
attr_reader additional_fields: Hash[String, untyped]
|
|
371
|
-
def initialize: (text: String | PlainText, ?action_id: (String)?, ?url: (String)?, ?value: (String)?, ?style: (:primary | :danger)?, ?confirm: (Confirmation)?, ?accessibility_label: (String)?, ?additional_fields: Hash[String | Symbol, untyped]?) -> void
|
|
372
|
-
def self.new: (text: String | PlainText, ?action_id: (String)?, ?url: (String)?, ?value: (String)?, ?style: (:primary | :danger)?, ?confirm: (Confirmation)?, ?accessibility_label: (String)?, ?additional_fields: Hash[String | Symbol, untyped]?) -> ButtonElement
|
|
373
|
-
def self.[]: (text: String | PlainText, ?action_id: (String)?, ?url: (String)?, ?value: (String)?, ?style: (:primary | :danger)?, ?confirm: (Confirmation)?, ?accessibility_label: (String)?, ?additional_fields: Hash[String | Symbol, untyped]?) -> ButtonElement
|
|
373
|
+
def initialize: (text: String | PlainText, ?action_id: (String)?, ?url: (String)?, ?value: (String)?, ?style: (:primary | :danger)?, ?confirm: (Confirmation)?, ?accessibility_label: (String)?, ?agent_prompt: (String)?, ?agent_prompt_display: (String)?, ?additional_fields: Hash[String | Symbol, untyped]?) -> void
|
|
374
|
+
def self.new: (text: String | PlainText, ?action_id: (String)?, ?url: (String)?, ?value: (String)?, ?style: (:primary | :danger)?, ?confirm: (Confirmation)?, ?accessibility_label: (String)?, ?agent_prompt: (String)?, ?agent_prompt_display: (String)?, ?additional_fields: Hash[String | Symbol, untyped]?) -> ButtonElement
|
|
375
|
+
def self.[]: (text: String | PlainText, ?action_id: (String)?, ?url: (String)?, ?value: (String)?, ?style: (:primary | :danger)?, ?confirm: (Confirmation)?, ?accessibility_label: (String)?, ?agent_prompt: (String)?, ?agent_prompt_display: (String)?, ?additional_fields: Hash[String | Symbol, untyped]?) -> ButtonElement
|
|
374
376
|
end
|
|
375
377
|
|
|
376
378
|
class ChannelMultiSelectElement
|
|
@@ -596,15 +598,15 @@ module Slackblocks
|
|
|
596
598
|
attr_reader action_id: (String)?
|
|
597
599
|
attr_reader is_decimal_allowed: bool
|
|
598
600
|
attr_reader initial_value: (String)?
|
|
599
|
-
attr_reader min_value: (
|
|
600
|
-
attr_reader max_value: (
|
|
601
|
+
attr_reader min_value: (String)?
|
|
602
|
+
attr_reader max_value: (String)?
|
|
601
603
|
attr_reader dispatch_action_config: (DispatchActionConfiguration)?
|
|
602
604
|
attr_reader focus_on_load: (bool)?
|
|
603
605
|
attr_reader placeholder: (PlainText)?
|
|
604
606
|
attr_reader additional_fields: Hash[String, untyped]
|
|
605
|
-
def initialize: (is_decimal_allowed: bool, ?action_id: (String)?, ?initial_value: (String)?, ?min_value: (Integer | Float)?, ?max_value: (Integer | Float)?, ?dispatch_action_config: (DispatchActionConfiguration)?, ?focus_on_load: (bool)?, ?placeholder: (String | PlainText)?, ?additional_fields: Hash[String | Symbol, untyped]?) -> void
|
|
606
|
-
def self.new: (is_decimal_allowed: bool, ?action_id: (String)?, ?initial_value: (String)?, ?min_value: (Integer | Float)?, ?max_value: (Integer | Float)?, ?dispatch_action_config: (DispatchActionConfiguration)?, ?focus_on_load: (bool)?, ?placeholder: (String | PlainText)?, ?additional_fields: Hash[String | Symbol, untyped]?) -> NumberInputElement
|
|
607
|
-
def self.[]: (is_decimal_allowed: bool, ?action_id: (String)?, ?initial_value: (String)?, ?min_value: (Integer | Float)?, ?max_value: (Integer | Float)?, ?dispatch_action_config: (DispatchActionConfiguration)?, ?focus_on_load: (bool)?, ?placeholder: (String | PlainText)?, ?additional_fields: Hash[String | Symbol, untyped]?) -> NumberInputElement
|
|
607
|
+
def initialize: (is_decimal_allowed: bool, ?action_id: (String)?, ?initial_value: (String)?, ?min_value: (String | Integer | Float)?, ?max_value: (String | Integer | Float)?, ?dispatch_action_config: (DispatchActionConfiguration)?, ?focus_on_load: (bool)?, ?placeholder: (String | PlainText)?, ?additional_fields: Hash[String | Symbol, untyped]?) -> void
|
|
608
|
+
def self.new: (is_decimal_allowed: bool, ?action_id: (String)?, ?initial_value: (String)?, ?min_value: (String | Integer | Float)?, ?max_value: (String | Integer | Float)?, ?dispatch_action_config: (DispatchActionConfiguration)?, ?focus_on_load: (bool)?, ?placeholder: (String | PlainText)?, ?additional_fields: Hash[String | Symbol, untyped]?) -> NumberInputElement
|
|
609
|
+
def self.[]: (is_decimal_allowed: bool, ?action_id: (String)?, ?initial_value: (String)?, ?min_value: (String | Integer | Float)?, ?max_value: (String | Integer | Float)?, ?dispatch_action_config: (DispatchActionConfiguration)?, ?focus_on_load: (bool)?, ?placeholder: (String | PlainText)?, ?additional_fields: Hash[String | Symbol, untyped]?) -> NumberInputElement
|
|
608
610
|
end
|
|
609
611
|
|
|
610
612
|
class OverflowElement
|
data/sig/slackblocks.rbs
CHANGED
|
@@ -45,6 +45,8 @@ module Slackblocks
|
|
|
45
45
|
def self.text_limit: (untyped value, String key, String path) -> untyped
|
|
46
46
|
def self.max_items: (untyped items, String key, String path) -> untyped
|
|
47
47
|
def self.special: (untyped klass, Hash[String, untyped] wire, String path) -> untyped
|
|
48
|
+
def self.decimal_bound: (String text, String path) -> [Integer, Integer, String]
|
|
49
|
+
def self.compare_decimal_bounds: ([Integer, Integer, String] left, [Integer, Integer, String] right) -> Integer
|
|
48
50
|
def self.surface: (Array[untyped] blocks, String name, String path) -> untyped
|
|
49
51
|
def self.standalone_task: (untyped value, String path) -> untyped
|
|
50
52
|
def self.message: (Hash[String, untyped] wire, String path) -> untyped
|