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
|