block-kit 1.0.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 +7 -0
- data/.rspec +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +183 -0
- data/Rakefile +8 -0
- data/lib/block_kit/base.rb +190 -0
- data/lib/block_kit/blocks.rb +50 -0
- data/lib/block_kit/composition/confirmation_dialog.rb +48 -0
- data/lib/block_kit/composition/conversation_filter.rb +42 -0
- data/lib/block_kit/composition/dispatch_action_config.rb +33 -0
- data/lib/block_kit/composition/input_parameter.rb +19 -0
- data/lib/block_kit/composition/mrkdwn.rb +15 -0
- data/lib/block_kit/composition/option.rb +41 -0
- data/lib/block_kit/composition/option_group.rb +27 -0
- data/lib/block_kit/composition/overflow_option.rb +21 -0
- data/lib/block_kit/composition/plain_text.rb +15 -0
- data/lib/block_kit/composition/slack_file.rb +36 -0
- data/lib/block_kit/composition/text.rb +29 -0
- data/lib/block_kit/composition/trigger.rb +33 -0
- data/lib/block_kit/composition/workflow.rb +19 -0
- data/lib/block_kit/composition.rb +19 -0
- data/lib/block_kit/concerns/confirmable.rb +21 -0
- data/lib/block_kit/concerns/conversation_selection.rb +26 -0
- data/lib/block_kit/concerns/dispatchable.rb +24 -0
- data/lib/block_kit/concerns/dsl_generation.rb +76 -0
- data/lib/block_kit/concerns/external.rb +19 -0
- data/lib/block_kit/concerns/focusable_on_load.rb +17 -0
- data/lib/block_kit/concerns/has_initial_option.rb +23 -0
- data/lib/block_kit/concerns/has_initial_options.rb +23 -0
- data/lib/block_kit/concerns/has_option_groups.rb +37 -0
- data/lib/block_kit/concerns/has_options.rb +28 -0
- data/lib/block_kit/concerns/has_placeholder.rb +21 -0
- data/lib/block_kit/concerns/has_rich_text_elements.rb +83 -0
- data/lib/block_kit/concerns/plain_text_emoji_assignment.rb +39 -0
- data/lib/block_kit/concerns.rb +18 -0
- data/lib/block_kit/elements/base.rb +23 -0
- data/lib/block_kit/elements/base_button.rb +45 -0
- data/lib/block_kit/elements/button.rb +27 -0
- data/lib/block_kit/elements/channels_select.rb +22 -0
- data/lib/block_kit/elements/checkboxes.rb +14 -0
- data/lib/block_kit/elements/conversations_select.rb +24 -0
- data/lib/block_kit/elements/date_picker.rb +21 -0
- data/lib/block_kit/elements/datetime_picker.rb +21 -0
- data/lib/block_kit/elements/email_text_input.rb +24 -0
- data/lib/block_kit/elements/external_select.rb +21 -0
- data/lib/block_kit/elements/file_input.rb +33 -0
- data/lib/block_kit/elements/image.rb +53 -0
- data/lib/block_kit/elements/multi_channels_select.rb +31 -0
- data/lib/block_kit/elements/multi_conversations_select.rb +33 -0
- data/lib/block_kit/elements/multi_external_select.rb +21 -0
- data/lib/block_kit/elements/multi_select.rb +21 -0
- data/lib/block_kit/elements/multi_static_select.rb +12 -0
- data/lib/block_kit/elements/multi_users_select.rb +31 -0
- data/lib/block_kit/elements/number_input.rb +52 -0
- data/lib/block_kit/elements/overflow.rb +23 -0
- data/lib/block_kit/elements/plain_text_input.rb +66 -0
- data/lib/block_kit/elements/radio_buttons.rb +14 -0
- data/lib/block_kit/elements/rich_text_input.rb +26 -0
- data/lib/block_kit/elements/select.rb +18 -0
- data/lib/block_kit/elements/static_select.rb +12 -0
- data/lib/block_kit/elements/time_picker.rb +43 -0
- data/lib/block_kit/elements/url_text_input.rb +24 -0
- data/lib/block_kit/elements/users_select.rb +17 -0
- data/lib/block_kit/elements/workflow_button.rb +18 -0
- data/lib/block_kit/elements.rb +36 -0
- data/lib/block_kit/fixers/associated.rb +27 -0
- data/lib/block_kit/fixers/base.rb +30 -0
- data/lib/block_kit/fixers/null_value.rb +42 -0
- data/lib/block_kit/fixers/truncate.rb +35 -0
- data/lib/block_kit/fixers.rb +11 -0
- data/lib/block_kit/layout/actions.rb +84 -0
- data/lib/block_kit/layout/base.rb +23 -0
- data/lib/block_kit/layout/context.rb +48 -0
- data/lib/block_kit/layout/divider.rb +9 -0
- data/lib/block_kit/layout/file.rb +19 -0
- data/lib/block_kit/layout/header.rb +22 -0
- data/lib/block_kit/layout/image.rb +61 -0
- data/lib/block_kit/layout/input.rb +119 -0
- data/lib/block_kit/layout/markdown.rb +19 -0
- data/lib/block_kit/layout/rich_text/elements/broadcast.rb +22 -0
- data/lib/block_kit/layout/rich_text/elements/channel.rb +21 -0
- data/lib/block_kit/layout/rich_text/elements/color.rb +20 -0
- data/lib/block_kit/layout/rich_text/elements/date.rb +34 -0
- data/lib/block_kit/layout/rich_text/elements/emoji.rb +27 -0
- data/lib/block_kit/layout/rich_text/elements/link.rb +28 -0
- data/lib/block_kit/layout/rich_text/elements/mention_style.rb +27 -0
- data/lib/block_kit/layout/rich_text/elements/text.rb +23 -0
- data/lib/block_kit/layout/rich_text/elements/text_style.rb +23 -0
- data/lib/block_kit/layout/rich_text/elements/user.rb +21 -0
- data/lib/block_kit/layout/rich_text/elements/usergroup.rb +21 -0
- data/lib/block_kit/layout/rich_text/elements.rb +35 -0
- data/lib/block_kit/layout/rich_text/list.rb +41 -0
- data/lib/block_kit/layout/rich_text/preformatted.rb +19 -0
- data/lib/block_kit/layout/rich_text/quote.rb +19 -0
- data/lib/block_kit/layout/rich_text/section.rb +11 -0
- data/lib/block_kit/layout/rich_text.rb +53 -0
- data/lib/block_kit/layout/section.rb +152 -0
- data/lib/block_kit/layout/video.rb +71 -0
- data/lib/block_kit/layout.rb +35 -0
- data/lib/block_kit/surfaces/base.rb +173 -0
- data/lib/block_kit/surfaces/home.rb +40 -0
- data/lib/block_kit/surfaces/message.rb +140 -0
- data/lib/block_kit/surfaces/modal.rb +74 -0
- data/lib/block_kit/surfaces.rb +11 -0
- data/lib/block_kit/typed_array.rb +114 -0
- data/lib/block_kit/typed_set.rb +78 -0
- data/lib/block_kit/types/array.rb +45 -0
- data/lib/block_kit/types/blocks.rb +37 -0
- data/lib/block_kit/types/generic.rb +45 -0
- data/lib/block_kit/types/option.rb +48 -0
- data/lib/block_kit/types/set.rb +35 -0
- data/lib/block_kit/types/text.rb +80 -0
- data/lib/block_kit/types.rb +17 -0
- data/lib/block_kit/validators/array_inclusion_validator.rb +55 -0
- data/lib/block_kit/validators/associated_validator.rb +61 -0
- data/lib/block_kit/validators.rb +8 -0
- data/lib/block_kit/version.rb +5 -0
- data/lib/block_kit.rb +38 -0
- metadata +190 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockKit
|
|
4
|
+
module Elements
|
|
5
|
+
class NumberInput < Base
|
|
6
|
+
self.type = :number_input
|
|
7
|
+
|
|
8
|
+
include Concerns::Dispatchable
|
|
9
|
+
include Concerns::FocusableOnLoad
|
|
10
|
+
include Concerns::HasPlaceholder
|
|
11
|
+
include Concerns::PlainTextEmojiAssignment.new(:placeholder)
|
|
12
|
+
|
|
13
|
+
attribute :is_decimal_allowed, :boolean
|
|
14
|
+
attribute :min_value, :decimal
|
|
15
|
+
attribute :max_value, :decimal
|
|
16
|
+
attribute :initial_value, :decimal
|
|
17
|
+
|
|
18
|
+
validates :min_value, numericality: {less_than_or_equal_to: :max_value}, allow_nil: true, if: :max_value
|
|
19
|
+
validates :max_value, numericality: {greater_than_or_equal_to: :min_value}, allow_nil: true, if: :min_value
|
|
20
|
+
validates :initial_value, numericality: {greater_than_or_equal_to: :min_value}, allow_nil: true, if: :min_value
|
|
21
|
+
validates :initial_value, numericality: {less_than_or_equal_to: :max_value}, allow_nil: true, if: :max_value
|
|
22
|
+
|
|
23
|
+
fixes :initial_value, null_value: {error_types: [:greater_than_or_equal_to, :less_than_or_equal_to]}
|
|
24
|
+
|
|
25
|
+
def is_decimal_allowed?
|
|
26
|
+
!!is_decimal_allowed
|
|
27
|
+
end
|
|
28
|
+
alias_method :decimal_allowed?, :is_decimal_allowed?
|
|
29
|
+
|
|
30
|
+
def min_value
|
|
31
|
+
decimal_allowed? ? super : super&.to_i
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def max_value
|
|
35
|
+
decimal_allowed? ? super : super&.to_i
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def initial_value
|
|
39
|
+
decimal_allowed? ? super : super&.to_i
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def as_json(*)
|
|
43
|
+
super.merge(
|
|
44
|
+
is_decimal_allowed: is_decimal_allowed,
|
|
45
|
+
initial_value: initial_value.respond_to?(:to_digits) ? initial_value.to_digits : initial_value&.to_i,
|
|
46
|
+
min_value: min_value.respond_to?(:to_digits) ? min_value.to_digits : min_value&.to_i,
|
|
47
|
+
max_value: max_value.respond_to?(:to_digits) ? max_value.to_digits : max_value&.to_i
|
|
48
|
+
).compact
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockKit
|
|
4
|
+
module Elements
|
|
5
|
+
class Overflow < Base
|
|
6
|
+
self.type = :overflow
|
|
7
|
+
|
|
8
|
+
MAX_OPTIONS = 5
|
|
9
|
+
|
|
10
|
+
include Concerns::Confirmable
|
|
11
|
+
|
|
12
|
+
attribute :options, Types::Array.of(Composition::OverflowOption)
|
|
13
|
+
validates :options, presence: true, length: {maximum: MAX_OPTIONS, message: "is too long (maximum is %{count} options)"}, "block_kit/validators/associated": true
|
|
14
|
+
fixes :options, truncate: {maximum: MAX_OPTIONS, dangerous: true}, associated: true
|
|
15
|
+
|
|
16
|
+
dsl_method :options, as: :option, required_fields: [:text, :value], yields: false
|
|
17
|
+
|
|
18
|
+
def as_json(*)
|
|
19
|
+
super.merge(options: options&.map(&:as_json))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/object/inclusion"
|
|
4
|
+
require "uri"
|
|
5
|
+
|
|
6
|
+
module BlockKit
|
|
7
|
+
module Elements
|
|
8
|
+
class PlainTextInput < Base
|
|
9
|
+
self.type = :plain_text_input
|
|
10
|
+
|
|
11
|
+
include Concerns::Dispatchable
|
|
12
|
+
include Concerns::FocusableOnLoad
|
|
13
|
+
include Concerns::HasPlaceholder
|
|
14
|
+
include Concerns::PlainTextEmojiAssignment.new(:placeholder)
|
|
15
|
+
|
|
16
|
+
attribute :initial_value, :string
|
|
17
|
+
attribute :min_length, :integer
|
|
18
|
+
attribute :max_length, :integer
|
|
19
|
+
attribute :multiline, :boolean
|
|
20
|
+
|
|
21
|
+
validates :min_length, presence: true, numericality: {only_integer: true}, allow_nil: true
|
|
22
|
+
validates :max_length, presence: true, numericality: {only_integer: true}, allow_nil: true
|
|
23
|
+
validate :min_and_max_lengths_are_valid
|
|
24
|
+
|
|
25
|
+
validates :initial_value, presence: true, length: {
|
|
26
|
+
minimum: ->(input) { input.min_length || 0 },
|
|
27
|
+
maximum: ->(input) { input.max_length || 3000 }
|
|
28
|
+
}, allow_nil: true
|
|
29
|
+
|
|
30
|
+
fixes :initial_value,
|
|
31
|
+
truncate: {maximum: ->(input) { input.max_length || 3000 }},
|
|
32
|
+
null_value: {error_types: [:blank, :too_short]}
|
|
33
|
+
|
|
34
|
+
def as_json(*)
|
|
35
|
+
super.merge(
|
|
36
|
+
initial_value: initial_value,
|
|
37
|
+
min_length: min_length,
|
|
38
|
+
max_length: max_length,
|
|
39
|
+
multiline: multiline
|
|
40
|
+
).compact
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# These validations are more complex than, say, the `number_input` validations due to
|
|
46
|
+
# the fact that `min_length` can be 0 but `max_length` must be at least 1.
|
|
47
|
+
def min_and_max_lengths_are_valid
|
|
48
|
+
if min_length && max_length
|
|
49
|
+
if min_length > max_length
|
|
50
|
+
errors.add(:min_length, "must be less than or equal to max_length")
|
|
51
|
+
elsif max_length < min_length
|
|
52
|
+
errors.add(:max_length, "must be greater than or equal to min_length")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if min_length && !min_length.in?(0..3000)
|
|
57
|
+
errors.add(:min_length, "must be in 0..3000")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
if max_length && !max_length.in?(1..3000)
|
|
61
|
+
errors.add(:max_length, "must be in 1..3000")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockKit
|
|
4
|
+
module Elements
|
|
5
|
+
class RadioButtons < Base
|
|
6
|
+
self.type = :radio_buttons
|
|
7
|
+
|
|
8
|
+
include Concerns::Confirmable
|
|
9
|
+
include Concerns::HasOptions.new(limit: 10)
|
|
10
|
+
include Concerns::HasInitialOption
|
|
11
|
+
include Concerns::FocusableOnLoad
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module BlockKit
|
|
6
|
+
module Elements
|
|
7
|
+
class RichTextInput < Base
|
|
8
|
+
self.type = :rich_text_input
|
|
9
|
+
|
|
10
|
+
include Concerns::Dispatchable
|
|
11
|
+
include Concerns::FocusableOnLoad
|
|
12
|
+
include Concerns::HasPlaceholder
|
|
13
|
+
include Concerns::PlainTextEmojiAssignment.new(:placeholder)
|
|
14
|
+
|
|
15
|
+
attribute :initial_value, Types::Generic.of_type(Layout::RichText)
|
|
16
|
+
validates :initial_value, "block_kit/validators/associated": true, allow_nil: true
|
|
17
|
+
fixes :initial_value, associated: true
|
|
18
|
+
|
|
19
|
+
dsl_method :initial_value
|
|
20
|
+
|
|
21
|
+
def as_json(*)
|
|
22
|
+
super.merge(initial_value: initial_value&.as_json).compact
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockKit
|
|
4
|
+
module Elements
|
|
5
|
+
class Select < Base
|
|
6
|
+
include Concerns::Confirmable
|
|
7
|
+
include Concerns::FocusableOnLoad
|
|
8
|
+
include Concerns::HasPlaceholder
|
|
9
|
+
include Concerns::PlainTextEmojiAssignment.new(:placeholder)
|
|
10
|
+
|
|
11
|
+
def initialize(attributes = {})
|
|
12
|
+
raise NotImplementedError, "#{self.class} is an abstract class and can't be instantiated." if instance_of?(Select)
|
|
13
|
+
|
|
14
|
+
super
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockKit
|
|
4
|
+
module Elements
|
|
5
|
+
class StaticSelect < Select
|
|
6
|
+
self.type = :static_select
|
|
7
|
+
|
|
8
|
+
include Concerns::HasOptionGroups.new(limit: 100, options_limit: 100)
|
|
9
|
+
include Concerns::HasInitialOption
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockKit
|
|
4
|
+
module Elements
|
|
5
|
+
class TimePicker < Base
|
|
6
|
+
self.type = :timepicker
|
|
7
|
+
|
|
8
|
+
include Concerns::Confirmable
|
|
9
|
+
include Concerns::FocusableOnLoad
|
|
10
|
+
include Concerns::HasPlaceholder
|
|
11
|
+
include Concerns::PlainTextEmojiAssignment.new(:placeholder)
|
|
12
|
+
|
|
13
|
+
attribute :initial_time, :time, precision: 0
|
|
14
|
+
attribute :timezone, :string
|
|
15
|
+
|
|
16
|
+
validate :timezone_is_valid
|
|
17
|
+
fixes :timezone, null_value: {error_types: [:blank, :invalid]}
|
|
18
|
+
|
|
19
|
+
def timezone
|
|
20
|
+
ActiveSupport::TimeZone[super] if super
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def as_json(*)
|
|
24
|
+
super.merge(
|
|
25
|
+
initial_time: initial_time&.strftime("%H:%M"),
|
|
26
|
+
timezone: timezone&.name
|
|
27
|
+
).compact
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def timezone_is_valid
|
|
33
|
+
return if attributes["timezone"].nil?
|
|
34
|
+
|
|
35
|
+
if attributes["timezone"].blank?
|
|
36
|
+
errors.add(:timezone, :blank)
|
|
37
|
+
else
|
|
38
|
+
errors.add(:timezone, :invalid, message: "is not a valid timezone") unless ActiveSupport::TimeZone[attributes["timezone"]]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module BlockKit
|
|
6
|
+
module Elements
|
|
7
|
+
class URLTextInput < Base
|
|
8
|
+
self.type = :url_text_input
|
|
9
|
+
|
|
10
|
+
include Concerns::Dispatchable
|
|
11
|
+
include Concerns::FocusableOnLoad
|
|
12
|
+
include Concerns::HasPlaceholder
|
|
13
|
+
include Concerns::PlainTextEmojiAssignment.new(:placeholder)
|
|
14
|
+
|
|
15
|
+
attribute :initial_value, :string
|
|
16
|
+
validates :initial_value, presence: true, format: {with: URI::DEFAULT_PARSER.make_regexp, message: "is not a valid URI", allow_blank: true}, allow_nil: true
|
|
17
|
+
fixes :initial_value, null_value: {error_types: [:blank, :invalid]}
|
|
18
|
+
|
|
19
|
+
def as_json(*)
|
|
20
|
+
super.merge(initial_value: initial_value).compact
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockKit
|
|
4
|
+
module Elements
|
|
5
|
+
class UsersSelect < Select
|
|
6
|
+
self.type = :users_select
|
|
7
|
+
|
|
8
|
+
attribute :initial_user, :string
|
|
9
|
+
validates :initial_user, presence: true, allow_nil: true
|
|
10
|
+
fixes :initial_user, null_value: {error_types: [:blank]}
|
|
11
|
+
|
|
12
|
+
def as_json(*)
|
|
13
|
+
super.merge(initial_user: initial_user).compact
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockKit
|
|
4
|
+
module Elements
|
|
5
|
+
class WorkflowButton < BaseButton
|
|
6
|
+
self.type = :workflow_button
|
|
7
|
+
|
|
8
|
+
attribute :workflow, Types::Generic.of_type(Composition::Workflow)
|
|
9
|
+
validates :workflow, presence: true, "block_kit/validators/associated": true
|
|
10
|
+
|
|
11
|
+
dsl_method :workflow
|
|
12
|
+
|
|
13
|
+
def as_json(*)
|
|
14
|
+
super.merge(workflow: workflow&.as_json).compact
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockKit
|
|
4
|
+
module Elements
|
|
5
|
+
autoload :Base, "block_kit/elements/base"
|
|
6
|
+
autoload :BaseButton, "block_kit/elements/base_button"
|
|
7
|
+
autoload :Select, "block_kit/elements/select"
|
|
8
|
+
autoload :MultiSelect, "block_kit/elements/multi_select"
|
|
9
|
+
autoload :NumberInput, "block_kit/elements/number_input"
|
|
10
|
+
|
|
11
|
+
autoload :Button, "block_kit/elements/button"
|
|
12
|
+
autoload :ChannelsSelect, "block_kit/elements/channels_select"
|
|
13
|
+
autoload :Checkboxes, "block_kit/elements/checkboxes"
|
|
14
|
+
autoload :ConversationsSelect, "block_kit/elements/conversations_select"
|
|
15
|
+
autoload :DatePicker, "block_kit/elements/date_picker"
|
|
16
|
+
autoload :DatetimePicker, "block_kit/elements/datetime_picker"
|
|
17
|
+
autoload :EmailTextInput, "block_kit/elements/email_text_input"
|
|
18
|
+
autoload :ExternalSelect, "block_kit/elements/external_select"
|
|
19
|
+
autoload :FileInput, "block_kit/elements/file_input"
|
|
20
|
+
autoload :Image, "block_kit/elements/image"
|
|
21
|
+
autoload :MultiChannelsSelect, "block_kit/elements/multi_channels_select"
|
|
22
|
+
autoload :MultiConversationsSelect, "block_kit/elements/multi_conversations_select"
|
|
23
|
+
autoload :MultiExternalSelect, "block_kit/elements/multi_external_select"
|
|
24
|
+
autoload :MultiStaticSelect, "block_kit/elements/multi_static_select"
|
|
25
|
+
autoload :MultiUsersSelect, "block_kit/elements/multi_users_select"
|
|
26
|
+
autoload :Overflow, "block_kit/elements/overflow"
|
|
27
|
+
autoload :PlainTextInput, "block_kit/elements/plain_text_input"
|
|
28
|
+
autoload :RadioButtons, "block_kit/elements/radio_buttons"
|
|
29
|
+
autoload :RichTextInput, "block_kit/elements/rich_text_input"
|
|
30
|
+
autoload :StaticSelect, "block_kit/elements/static_select"
|
|
31
|
+
autoload :TimePicker, "block_kit/elements/time_picker"
|
|
32
|
+
autoload :URLTextInput, "block_kit/elements/url_text_input"
|
|
33
|
+
autoload :UsersSelect, "block_kit/elements/users_select"
|
|
34
|
+
autoload :WorkflowButton, "block_kit/elements/workflow_button"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockKit
|
|
4
|
+
module Fixers
|
|
5
|
+
class Associated < Base
|
|
6
|
+
def fix(model, fixing_dangerously: false)
|
|
7
|
+
model.validate
|
|
8
|
+
errors = errors_for(model)
|
|
9
|
+
|
|
10
|
+
return unless errors.any?
|
|
11
|
+
|
|
12
|
+
associated = model.public_send(attribute)
|
|
13
|
+
|
|
14
|
+
if associated.is_a?(Enumerable)
|
|
15
|
+
error = errors.find { |e| e.type == :invalid }
|
|
16
|
+
return unless error
|
|
17
|
+
|
|
18
|
+
error.options[:invalid_values].each do |associated|
|
|
19
|
+
associated.fix_validation_errors(dangerous: fixing_dangerously)
|
|
20
|
+
end
|
|
21
|
+
else
|
|
22
|
+
associated&.fix_validation_errors(dangerous: fixing_dangerously)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockKit
|
|
4
|
+
module Fixers
|
|
5
|
+
class Base
|
|
6
|
+
include ActiveSupport::Callbacks
|
|
7
|
+
|
|
8
|
+
def initialize(attribute:, **options)
|
|
9
|
+
@attribute = attribute.to_sym
|
|
10
|
+
@dangerous = options.delete(:dangerous)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def fix(model, fixing_dangerously: false)
|
|
14
|
+
raise NotImplementedError, "#{self.class} must implement `fix'"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def dangerous?
|
|
18
|
+
!!@dangerous
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
attr_reader :attribute
|
|
24
|
+
|
|
25
|
+
def errors_for(model)
|
|
26
|
+
Array(model.errors.group_by_attribute[attribute])
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/object/inclusion"
|
|
4
|
+
|
|
5
|
+
module BlockKit
|
|
6
|
+
module Fixers
|
|
7
|
+
class NullValue < Base
|
|
8
|
+
def initialize(attribute:, **options)
|
|
9
|
+
super
|
|
10
|
+
|
|
11
|
+
@error_types = options.delete(:error_types) { [:blank] }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def fix(model, fixing_dangerously: false)
|
|
15
|
+
return if dangerous? && !fixing_dangerously
|
|
16
|
+
|
|
17
|
+
model.validate
|
|
18
|
+
errors = errors_for(model)
|
|
19
|
+
|
|
20
|
+
return unless errors.any? { |e| error_types.include?(e.type) }
|
|
21
|
+
|
|
22
|
+
value = model.attributes[attribute.to_s]
|
|
23
|
+
|
|
24
|
+
# First, check if the attribute is an array and if we have an `inclusion` error.
|
|
25
|
+
# If so, we'll remove invalid values from the array. If the new value is empty
|
|
26
|
+
# and this fixer is configured to handle `blank` errors, we'll nullify it.
|
|
27
|
+
if value.is_a?(Enumerable) && (error = errors.find { |e| e.type == :inclusion })
|
|
28
|
+
new_value = value - error.options[:invalid_values]
|
|
29
|
+
new_value = new_value.presence if error_types.include?(:blank)
|
|
30
|
+
|
|
31
|
+
model.assign_attributes(attribute => new_value)
|
|
32
|
+
else
|
|
33
|
+
model.assign_attributes(attribute => nil)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
attr_reader :error_types
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockKit
|
|
4
|
+
module Fixers
|
|
5
|
+
class Truncate < Base
|
|
6
|
+
def initialize(attribute:, **options)
|
|
7
|
+
super
|
|
8
|
+
|
|
9
|
+
@maximum = options.delete(:maximum)
|
|
10
|
+
@omission = options.delete(:omission)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def fix(model, fixing_dangerously: false)
|
|
14
|
+
return if dangerous? && !fixing_dangerously
|
|
15
|
+
|
|
16
|
+
model.validate
|
|
17
|
+
errors = errors_for(model)
|
|
18
|
+
|
|
19
|
+
return unless errors.any? { |e| e.type == :too_long }
|
|
20
|
+
|
|
21
|
+
value = model.attributes[attribute.to_s]
|
|
22
|
+
maximum = @maximum.call(model) if @maximum.is_a?(Proc)
|
|
23
|
+
maximum ||= @maximum
|
|
24
|
+
|
|
25
|
+
new_value = if value.is_a?(Enumerable)
|
|
26
|
+
value.first(maximum)
|
|
27
|
+
else
|
|
28
|
+
value.truncate(maximum, omission: @omission)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
model.assign_attributes(attribute => new_value)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockKit
|
|
4
|
+
module Fixers
|
|
5
|
+
autoload :Base, "block_kit/fixers/base"
|
|
6
|
+
|
|
7
|
+
autoload :Associated, "block_kit/fixers/associated"
|
|
8
|
+
autoload :NullValue, "block_kit/fixers/null_value"
|
|
9
|
+
autoload :Truncate, "block_kit/fixers/truncate"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockKit
|
|
4
|
+
module Layout
|
|
5
|
+
class Actions < Base
|
|
6
|
+
self.type = :actions
|
|
7
|
+
|
|
8
|
+
MAX_ELEMENTS = 25
|
|
9
|
+
SUPPORTED_ELEMENTS = [
|
|
10
|
+
Elements::Button,
|
|
11
|
+
Elements::ChannelsSelect,
|
|
12
|
+
Elements::Checkboxes,
|
|
13
|
+
Elements::ConversationsSelect,
|
|
14
|
+
Elements::DatePicker,
|
|
15
|
+
Elements::DatetimePicker,
|
|
16
|
+
Elements::ExternalSelect,
|
|
17
|
+
Elements::MultiChannelsSelect,
|
|
18
|
+
Elements::MultiConversationsSelect,
|
|
19
|
+
Elements::MultiExternalSelect,
|
|
20
|
+
Elements::MultiStaticSelect,
|
|
21
|
+
Elements::MultiUsersSelect,
|
|
22
|
+
Elements::Overflow,
|
|
23
|
+
Elements::RadioButtons,
|
|
24
|
+
Elements::RichTextInput,
|
|
25
|
+
Elements::StaticSelect,
|
|
26
|
+
Elements::TimePicker,
|
|
27
|
+
Elements::UsersSelect,
|
|
28
|
+
Elements::WorkflowButton
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
def initialize(attributes = {})
|
|
32
|
+
attributes = attributes.with_indifferent_access
|
|
33
|
+
attributes[:elements] ||= []
|
|
34
|
+
|
|
35
|
+
super
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
attribute :elements, Types::Array.of(Types::Blocks.new(*SUPPORTED_ELEMENTS))
|
|
39
|
+
validates :elements, presence: true, length: {maximum: MAX_ELEMENTS, message: "is too long (maximum is %{count} elements)"}, "block_kit/validators/associated": true
|
|
40
|
+
fixes :elements, truncate: {maximum: MAX_ELEMENTS, dangerous: true}, associated: true
|
|
41
|
+
|
|
42
|
+
dsl_method :elements, as: :button, type: Elements::Button, required_fields: [:text]
|
|
43
|
+
dsl_method :elements, as: :channels_select, type: Elements::ChannelsSelect
|
|
44
|
+
dsl_method :elements, as: :checkboxes, type: Elements::Checkboxes
|
|
45
|
+
dsl_method :elements, as: :conversations_select, type: Elements::ConversationsSelect
|
|
46
|
+
dsl_method :elements, as: :datepicker, type: Elements::DatePicker
|
|
47
|
+
dsl_method :elements, as: :datetimepicker, type: Elements::DatetimePicker
|
|
48
|
+
dsl_method :elements, as: :external_select, type: Elements::ExternalSelect
|
|
49
|
+
dsl_method :elements, as: :multi_channels_select, type: Elements::MultiChannelsSelect
|
|
50
|
+
dsl_method :elements, as: :multi_conversations_select, type: Elements::MultiConversationsSelect
|
|
51
|
+
dsl_method :elements, as: :multi_external_select, type: Elements::MultiExternalSelect
|
|
52
|
+
dsl_method :elements, as: :multi_static_select, type: Elements::MultiStaticSelect, mutually_exclusive_fields: [:options, :option_groups]
|
|
53
|
+
dsl_method :elements, as: :multi_users_select, type: Elements::MultiUsersSelect
|
|
54
|
+
dsl_method :elements, as: :overflow, type: Elements::Overflow
|
|
55
|
+
dsl_method :elements, as: :radio_buttons, type: Elements::RadioButtons
|
|
56
|
+
dsl_method :elements, as: :rich_text_input, type: Elements::RichTextInput
|
|
57
|
+
dsl_method :elements, as: :static_select, type: Elements::StaticSelect, mutually_exclusive_fields: [:options, :option_groups]
|
|
58
|
+
dsl_method :elements, as: :timepicker, type: Elements::TimePicker
|
|
59
|
+
dsl_method :elements, as: :users_select, type: Elements::UsersSelect
|
|
60
|
+
dsl_method :elements, as: :workflow_button, type: Elements::WorkflowButton, required_fields: [:text]
|
|
61
|
+
|
|
62
|
+
alias_method :channel_select, :channels_select
|
|
63
|
+
alias_method :conversation_select, :conversations_select
|
|
64
|
+
alias_method :date_picker, :datepicker
|
|
65
|
+
alias_method :datetime_picker, :datetimepicker
|
|
66
|
+
alias_method :multi_channel_select, :multi_channels_select
|
|
67
|
+
alias_method :multi_conversation_select, :multi_conversations_select
|
|
68
|
+
alias_method :multi_user_select, :multi_users_select
|
|
69
|
+
alias_method :overflow_menu, :overflow
|
|
70
|
+
alias_method :time_picker, :timepicker
|
|
71
|
+
alias_method :user_select, :users_select
|
|
72
|
+
|
|
73
|
+
def append(element)
|
|
74
|
+
elements << element
|
|
75
|
+
|
|
76
|
+
self
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def as_json(*)
|
|
80
|
+
super.merge(elements: elements&.map(&:as_json)).compact
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockKit
|
|
4
|
+
module Layout
|
|
5
|
+
class Base < BlockKit::Base
|
|
6
|
+
MAX_BLOCK_ID_LENGTH = 255
|
|
7
|
+
|
|
8
|
+
attribute :block_id, :string
|
|
9
|
+
validates :block_id, presence: true, length: {maximum: MAX_BLOCK_ID_LENGTH}, allow_nil: true
|
|
10
|
+
fixes :block_id, truncate: {maximum: MAX_BLOCK_ID_LENGTH, dangerous: true, omission: ""}, null_value: {error_types: [:blank]}
|
|
11
|
+
|
|
12
|
+
def initialize(attributes = {})
|
|
13
|
+
raise NotImplementedError, "#{self.class} is an abstract class and can't be instantiated." if instance_of?(Base)
|
|
14
|
+
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def as_json(*)
|
|
19
|
+
super.merge(block_id: block_id).compact
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockKit
|
|
4
|
+
module Layout
|
|
5
|
+
class Context < Base
|
|
6
|
+
self.type = :context
|
|
7
|
+
|
|
8
|
+
MAX_ELEMENTS = 10
|
|
9
|
+
SUPPORTED_ELEMENTS = [
|
|
10
|
+
Elements::Image,
|
|
11
|
+
Composition::Mrkdwn,
|
|
12
|
+
Composition::PlainText
|
|
13
|
+
].freeze
|
|
14
|
+
|
|
15
|
+
attribute :elements, Types::Array.of(Types::Blocks.new(*SUPPORTED_ELEMENTS))
|
|
16
|
+
validates :elements, presence: true, length: {maximum: MAX_ELEMENTS, message: "is too long (maximum is %{count} elements)"}, "block_kit/validators/associated": true
|
|
17
|
+
fixes :elements, truncate: {maximum: MAX_ELEMENTS, dangerous: true}, associated: true
|
|
18
|
+
|
|
19
|
+
dsl_method :elements, as: :mrkdwn, type: Composition::Mrkdwn, required_fields: [:text], yields: false
|
|
20
|
+
dsl_method :elements, as: :plain_text, type: Composition::PlainText, required_fields: [:text], yields: false
|
|
21
|
+
|
|
22
|
+
def initialize(attributes = {})
|
|
23
|
+
attributes = attributes.with_indifferent_access
|
|
24
|
+
attributes[:elements] ||= []
|
|
25
|
+
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def image(alt_text:, image_url: nil, slack_file: nil)
|
|
30
|
+
if (image_url.nil? && slack_file.nil?) || (image_url && slack_file)
|
|
31
|
+
raise ArgumentError, "Must provide either image_url or slack_file, but not both."
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
append(Elements::Image.new(alt_text: alt_text, image_url: image_url, slack_file: slack_file))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def append(element)
|
|
38
|
+
elements << element
|
|
39
|
+
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def as_json(*)
|
|
44
|
+
super.merge(elements: elements&.map(&:as_json)).compact
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|