slack-block-kit 0.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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +4 -0
  6. data/README.md +62 -0
  7. data/Rakefile +6 -0
  8. data/bin/console +27 -0
  9. data/bin/setup +8 -0
  10. data/examples/slack_template_00.rb +46 -0
  11. data/examples/slack_template_01.rb +22 -0
  12. data/examples/slack_template_02.rb +28 -0
  13. data/examples/slack_template_03.rb +57 -0
  14. data/examples/slack_template_04.rb +29 -0
  15. data/examples/slack_template_05.rb +12 -0
  16. data/examples/slack_template_06.rb +53 -0
  17. data/examples/slack_template_07.rb +74 -0
  18. data/examples/slack_template_08.rb +77 -0
  19. data/examples/slack_template_09.rb +53 -0
  20. data/lib/slack-block-kit.rb +1 -0
  21. data/lib/slack/block_kit.rb +38 -0
  22. data/lib/slack/block_kit/block.rb +41 -0
  23. data/lib/slack/block_kit/block/actions_block.rb +35 -0
  24. data/lib/slack/block_kit/block/context_block.rb +30 -0
  25. data/lib/slack/block_kit/block/divider_block.rb +17 -0
  26. data/lib/slack/block_kit/block/image_block.rb +44 -0
  27. data/lib/slack/block_kit/block/section_block.rb +55 -0
  28. data/lib/slack/block_kit/composition_objects/confirmation_dialog.rb +59 -0
  29. data/lib/slack/block_kit/composition_objects/filter.rb +38 -0
  30. data/lib/slack/block_kit/composition_objects/option.rb +57 -0
  31. data/lib/slack/block_kit/composition_objects/option_group.rb +34 -0
  32. data/lib/slack/block_kit/composition_objects/text.rb +61 -0
  33. data/lib/slack/block_kit/element.rb +41 -0
  34. data/lib/slack/block_kit/element/button_element.rb +48 -0
  35. data/lib/slack/block_kit/element/channels_select_element.rb +31 -0
  36. data/lib/slack/block_kit/element/conversations_select_element.rb +43 -0
  37. data/lib/slack/block_kit/element/date_picker_element.rb +56 -0
  38. data/lib/slack/block_kit/element/external_select_element.rb +38 -0
  39. data/lib/slack/block_kit/element/image_element.rb +26 -0
  40. data/lib/slack/block_kit/element/multi_channels_select_element.rb +38 -0
  41. data/lib/slack/block_kit/element/multi_conversations_select_element.rb +38 -0
  42. data/lib/slack/block_kit/element/multi_external_select_element.rb +38 -0
  43. data/lib/slack/block_kit/element/multi_static_select_element.rb +38 -0
  44. data/lib/slack/block_kit/element/multi_users_select_element.rb +39 -0
  45. data/lib/slack/block_kit/element/overflow_element.rb +42 -0
  46. data/lib/slack/block_kit/element/select_element.rb +45 -0
  47. data/lib/slack/block_kit/element/static_select_element.rb +46 -0
  48. data/lib/slack/block_kit/element/users_select_element.rb +24 -0
  49. data/lib/slack/block_kit/execution_context.rb +64 -0
  50. data/lib/slack/block_kit/refinements/hash_compact.rb +15 -0
  51. data/lib/slack/block_kit/type_restricted_array.rb +21 -0
  52. data/lib/slack/block_kit/version.rb +5 -0
  53. data/slack-block-kit.gemspec +29 -0
  54. metadata +166 -0
@@ -0,0 +1,77 @@
1
+ [ SectionBlock[text: Text[mrkdwn: ":mag: Search results for #{Bold['Cata']}"]],
2
+ DividerBlock[],
3
+ SectionBlock[
4
+ text: Text[mrkdwn: [
5
+ Bold[Link['fakeLink.toYourApp.com', 'Use Case Catalogue']],
6
+ "Use Case Catalogue for the following departments/roles..."
7
+ ]],
8
+ accessory: StaticSelectElement[
9
+ placeholder: Text[plain_text: 'Manage', emoji: true],
10
+ options: [
11
+ Option[text: Text[plain_text: 'Edit it', emoji: true], value: 'value-0'],
12
+ Option[text: Text[plain_text: 'Read it', emoji: true], value: 'value-1'],
13
+ Option[text: Text[plain_text: 'Save it', emoji: true], value: 'value-2']
14
+ ]
15
+ ]
16
+ ],
17
+ SectionBlock[
18
+ text: Text[mrkdwn: [
19
+ Bold[Link['fakeLink.toYourApp.com', 'Customer Support - Workflow Diagram Catalogue']],
20
+ "This resource was put together by members of..."
21
+ ]],
22
+ accessory: StaticSelectElement[
23
+ placeholder: Text[plain_text: 'Manage', emoji: true],
24
+ options: [
25
+ Option[text: Text[plain_text: 'Manage it', emoji: true], value: 'value-0'],
26
+ Option[text: Text[plain_text: 'Read it', emoji: true], value: 'value-1'],
27
+ Option[text: Text[plain_text: 'Save it', emoji: true], value: 'value-2']
28
+ ]
29
+ ]
30
+ ],
31
+ SectionBlock[
32
+ text: Text[mrkdwn: [
33
+ Bold[Link['fakeLink.toYourApp.com', 'Self-Serve Learning Options Catalogue']],
34
+ "See the learning and development options we..."
35
+ ]],
36
+ accessory: StaticSelectElement[
37
+ placeholder: Text[plain_text: 'Manage', emoji: true],
38
+ options: [
39
+ Option[text: Text[plain_text: 'Manage it', emoji: true], value: 'value-0'],
40
+ Option[text: Text[plain_text: 'Read it', emoji: true], value: 'value-1'],
41
+ Option[text: Text[plain_text: 'Save it', emoji: true], value: 'value-2']
42
+ ]
43
+ ]
44
+ ],
45
+ SectionBlock[
46
+ text: Text[mrkdwn: [
47
+ Bold[Link['fakeLink.toYourApp.com', 'Use Case Catalogue - CF Presentation - [June 12, 2018]']],
48
+ "This is presentation will continue to be updated as..."
49
+ ]],
50
+ accessory: StaticSelectElement[
51
+ placeholder: Text[plain_text: 'Manage', emoji: true],
52
+ options: [
53
+ Option[text: Text[plain_text: 'Manage it', emoji: true], value: 'value-0'],
54
+ Option[text: Text[plain_text: 'Read it', emoji: true], value: 'value-1'],
55
+ Option[text: Text[plain_text: 'Save it', emoji: true], value: 'value-2']
56
+ ]
57
+ ]
58
+ ],
59
+ SectionBlock[
60
+ text: Text[mrkdwn: [
61
+ Bold[Link['fakeLink.toYourApp.com', 'Comprehensive Benefits Catalogue - 2019']],
62
+ "Information about all the benfits we offer is..."
63
+ ]],
64
+ accessory: StaticSelectElement[
65
+ placeholder: Text[plain_text: 'Manage', emoji: true],
66
+ options: [
67
+ Option[text: Text[plain_text: 'Manage it', emoji: true], value: 'value-0'],
68
+ Option[text: Text[plain_text: 'Read it', emoji: true], value: 'value-1'],
69
+ Option[text: Text[plain_text: 'Save it', emoji: true], value: 'value-2']
70
+ ]
71
+ ]
72
+ ],
73
+ DividerBlock[],
74
+ ActionsBlock[elements: [
75
+ ButtonElement[text: Text[plain_text: 'Next 5 Results', emoji: true], value: 'click_me_123']
76
+ ]]
77
+ ]
@@ -0,0 +1,53 @@
1
+ [ SectionBlock[text: Text[mrkdwn: ":newspaper: #{Bold['Paper Company Newsletter']} :newspaper:"]],
2
+ ContextBlock[elements: [Text[mrkdwn: "#{Bold['November 12, 2019']} | Sales Team Announcements"]]],
3
+ DividerBlock[],
4
+ SectionBlock[text: Text[mrkdwn: " :loud_sound: #{Bold['IN CASE YOU MISSED IT']} :loud_sound:"]],
5
+ SectionBlock[
6
+ text: Text[mrkdwn: "Replay our screening of #{Bold['Threat Level Midnight']} and pick up a copy of the DVD to give to your customers at the front desk."],
7
+ accessory: ButtonElement[text: Text[plain_text: 'Watch Now', emoji: true]]
8
+ ],
9
+ SectionBlock[
10
+ text: Text[mrkdwn: [
11
+ "The *2019 Dundies* happened. ",
12
+ "Awards were given, heroes were recognized. ",
13
+ "Check out *#dundies-2019* to see who won awards."
14
+ ]]
15
+ ],
16
+ DividerBlock[],
17
+ SectionBlock[text: Text[mrkdwn: ":calendar: | #{Bold['UPCOMING EVENTS']} | :calendar: "]],
18
+ SectionBlock[
19
+ text: Text[mrkdwn: "#{Code['11/20-11/22']} #{Bold['Beet the Competition']} #{Italic[' annual retreat at Schrute Farms']}"],
20
+ accessory: ButtonElement[text: Text[plain_text: 'RSVP', emoji: true]]
21
+ ],
22
+ SectionBlock[
23
+ text: Text[mrkdwn: "#{Code['12/01']} #{Bold["Toby's Going Away Party"]} at #{Italic['Benihana']}"],
24
+ accessory: ButtonElement[text: Text[plain_text: 'Learn More', emoji: true]]
25
+ ],
26
+ SectionBlock[
27
+ text: Text[mrkdwn: "#{Code['11/13']} :pretzel: #{Bold['Pretzel Day']} :pretzel: at #{Italic['Scranton Office']}"],
28
+ accessory: ButtonElement[text: Text[plain_text: 'RSVP', emoji: true]]
29
+ ],
30
+ DividerBlock[],
31
+ SectionBlock[text: Text[mrkdwn: ":calendar: | #{Bold['PAST EVENTS']} | :calendar: "]],
32
+ SectionBlock[
33
+ text: Text[mrkdwn: "#{Code['10/21']} #{Bold['Conference Room Meeting']}"],
34
+ accessory: ButtonElement[text: Text[plain_text: 'Watch Recording', emoji: true]]
35
+ ],
36
+ DividerBlock[],
37
+ SectionBlock[text: Text[mrkdwn: Bold['FOR YOUR INFORMATION']]],
38
+ SectionBlock[text: Text[mrkdwn: ":printer: #{Bold['Sabre Printers']} are no longer catching on fire! The newest version of our printers are safe to use. Make sure to tell your customers today.", verbatim: false]],
39
+ DividerBlock[],
40
+ SectionBlock[text: Text[mrkdwn: [
41
+ "Please join me in welcoming our 3 #{Bold['new hires']} to the Paper Company family! ",
42
+ nil,
43
+ " #{Bold['Robert California']}, CEO ",
44
+ nil,
45
+ " #{Bold['Ryan Howard']}, Temp ",
46
+ nil,
47
+ " #{Bold['Erin Hannon']}, Receptionist "
48
+ ]]],
49
+ DividerBlock[],
50
+ ContextBlock[elements: [
51
+ Text[mrkdwn: ":pushpin: Do you have something to include in the newsletter? Here's #{Bold['how to submit content']}."]
52
+ ]]
53
+ ]
@@ -0,0 +1 @@
1
+ require_relative 'slack/block_kit'
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'block_kit/type_restricted_array'
4
+ require_relative 'block_kit/refinements/hash_compact'
5
+
6
+ require_relative 'block_kit/block'
7
+ require_relative 'block_kit/block/actions_block'
8
+ require_relative 'block_kit/block/context_block'
9
+ require_relative 'block_kit/block/divider_block'
10
+ require_relative 'block_kit/block/image_block'
11
+ require_relative 'block_kit/block/section_block'
12
+
13
+ require_relative 'block_kit/composition_objects/confirmation_dialog'
14
+ require_relative 'block_kit/composition_objects/filter'
15
+ require_relative 'block_kit/composition_objects/option'
16
+ require_relative 'block_kit/composition_objects/option_group'
17
+ require_relative 'block_kit/composition_objects/text'
18
+
19
+ require_relative 'block_kit/element'
20
+ require_relative 'block_kit/element/button_element'
21
+ require_relative 'block_kit/element/date_picker_element'
22
+ require_relative 'block_kit/element/image_element'
23
+ require_relative 'block_kit/element/overflow_element'
24
+
25
+ require_relative 'block_kit/element/select_element'
26
+ require_relative 'block_kit/element/channels_select_element'
27
+ require_relative 'block_kit/element/conversations_select_element'
28
+ require_relative 'block_kit/element/external_select_element'
29
+ require_relative 'block_kit/element/static_select_element'
30
+ require_relative 'block_kit/element/users_select_element'
31
+
32
+ require_relative 'block_kit/element/multi_channels_select_element'
33
+ require_relative 'block_kit/element/multi_conversations_select_element'
34
+ require_relative 'block_kit/element/multi_external_select_element'
35
+ require_relative 'block_kit/element/multi_static_select_element'
36
+ require_relative 'block_kit/element/multi_users_select_element'
37
+
38
+ require_relative 'block_kit/execution_context'
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slack
4
+ module BlockKit
5
+ class Block
6
+ attr_reader :block_id
7
+
8
+ def self.populate(hash, object)
9
+ object.block_id = hash[:block_id] if hash.key?(:block_id)
10
+ raise ArgumentError, "invalid #{name}" unless object.valid?
11
+ end
12
+
13
+ def self.[](hash)
14
+ new.tap { |obj| populate(hash, obj) }
15
+ end
16
+
17
+ def block_id=(obj)
18
+ raise TypeError, 'block_id must be a string' unless obj.respond_to?(:to_str)
19
+ raise RangeError, 'block_id must be max 255 characters' unless obj.size <= 255
20
+
21
+ @block_id = obj.to_s
22
+ end
23
+
24
+ def type
25
+ @type ||= self.class.name
26
+ .split('::')
27
+ .last.chomp('Block')
28
+ .gsub(/([a-z])([A-Z])/, '\1_\2').downcase
29
+ end
30
+
31
+ def valid?
32
+ true
33
+ end
34
+
35
+ def to_h
36
+ { type: type,
37
+ block_id: block_id }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slack
4
+ module BlockKit
5
+ class Block
6
+ class ActionsBlock < Block
7
+ using Refinements::HashCompact
8
+ attr_accessor :elements
9
+
10
+ def self.populate(hash, object)
11
+ hash[:elements].each(&object.elements.method(:<<))
12
+ end
13
+
14
+ def initialize
15
+ @elements = TypeRestrictedArray.new(
16
+ Element::ButtonElement,
17
+ Element::SelectElement,
18
+ Element::OverflowElement,
19
+ Element::DatePickerElement
20
+ )
21
+ end
22
+
23
+ def valid?
24
+ !@elements.empty? && @elements.size <= 5
25
+ end
26
+
27
+ def to_h
28
+ super.merge(
29
+ elements: elements.map(&:to_h)
30
+ ).compact
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slack
4
+ module BlockKit
5
+ class Block
6
+ class ContextBlock < Block
7
+ using Refinements::HashCompact
8
+ attr_accessor :elements
9
+
10
+ def self.populate(hash, object)
11
+ hash[:elements].each(&object.elements.method(:<<))
12
+ end
13
+
14
+ def initialize
15
+ @elements = TypeRestrictedArray.new(Element, CompositionObjects::Text)
16
+ end
17
+
18
+ def valid?
19
+ !@elements.empty?
20
+ end
21
+
22
+ def to_h
23
+ super.merge(
24
+ elements: elements.map(&:to_h)
25
+ ).compact
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slack
4
+ module BlockKit
5
+ class Block
6
+ class DividerBlock < Block
7
+ def self.[](hash = {})
8
+ new.tap { |obj| populate(hash, obj) }
9
+ end
10
+
11
+ def to_h
12
+ super.compact
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slack
4
+ module BlockKit
5
+ class Block
6
+ class ImageBlock < Block
7
+ using Refinements::HashCompact
8
+ attr_accessor :image_url, :alt_text, :title
9
+
10
+ def self.populate(hash, object)
11
+ object.image_url = hash.fetch(:image_url)
12
+ object.alt_text = hash.fetch(:alt_text)
13
+ object.title = hash[:title] if hash.key?(:title)
14
+ end
15
+
16
+ def title=(obj)
17
+ raise TypeError, 'title must be a Text Object' unless obj.is_a?(CompositionObjects::Text)
18
+
19
+ @title = obj
20
+ end
21
+
22
+ def image_url=(url)
23
+ raise RangeError, 'image_url is max 3000 characters' unless url.size <= 3000
24
+
25
+ @image_url = url
26
+ end
27
+
28
+ def alt_text=(text)
29
+ raise RangeError, 'alt_text is max 2000 characters' unless text.size <= 2000
30
+
31
+ @alt_text = text
32
+ end
33
+
34
+ def to_h
35
+ super.merge(
36
+ image_url: image_url,
37
+ alt_text: alt_text,
38
+ title: title&.to_h
39
+ ).compact
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slack
4
+ module BlockKit
5
+ class Block
6
+ class SectionBlock < Block
7
+ using Refinements::HashCompact
8
+ attr_reader :text, :fields, :accessory
9
+
10
+ def self.populate(hash, object)
11
+ object.accessory = hash[:accessory] if hash.key?(:accessory)
12
+ if hash.key?(:text) then object.text = hash[:text]
13
+ elsif hash.key?(:fields) then hash[:fields].each(&object.fields.method(:<<))
14
+ end
15
+ end
16
+
17
+ def initialize
18
+ @fields = TypeRestrictedArray.new(CompositionObjects::Text)
19
+ end
20
+
21
+ # Either text or fields must exist and be non-empty.
22
+ def valid?
23
+ if @text.nil? || @text.empty? then !@fields.empty?
24
+ else !@text&.empty?
25
+ end
26
+ end
27
+
28
+ def text=(obj)
29
+ raise TypeError, 'text must be a Text Object' unless obj.is_a?(CompositionObjects::Text)
30
+
31
+ @text = obj
32
+ end
33
+
34
+ def accessory=(elem)
35
+ raise TypeError, 'accessory must be a block element' unless elem.is_a?(Element)
36
+
37
+ @accessory = elem
38
+ end
39
+
40
+ def to_h
41
+ if text && text.text.size > 3000
42
+ raise RangeError, 'text in SectionBlock has max 3000 characters'
43
+ end
44
+
45
+ super.merge(
46
+ block_id: block_id,
47
+ text: text&.to_h,
48
+ fields: fields.map(&:to_h),
49
+ accessory: accessory&.to_h
50
+ ).compact
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slack
4
+ module BlockKit
5
+ module CompositionObjects
6
+ class ConfirmationDialog
7
+ using Refinements::HashCompact
8
+ attr_reader :title, :text, :confirm, :deny
9
+
10
+ def self.[](hash)
11
+ new.tap do |object|
12
+ object.title = hash.fetch(:title)
13
+ object.text = hash.fetch(:text)
14
+ object.confirm = hash.fetch(:confirm)
15
+ object.deny = hash.fetch(:deny)
16
+ end
17
+ end
18
+
19
+ def title=(obj)
20
+ raise TypeError, 'title must be a Text Object' unless obj.is_a?(Text)
21
+ raise TypeError, 'title must be plain_text' unless obj.type == :plain_text
22
+ raise RangeError, 'title is max 100 characters' unless obj.text.size <= 100
23
+
24
+ @title = obj
25
+ end
26
+
27
+ def text=(obj)
28
+ raise TypeError, 'text must be a Text Object' unless obj.is_a?(Text)
29
+ raise RangeError, 'text is max 300 characters' unless obj.text.size <= 300
30
+
31
+ @text = obj
32
+ end
33
+
34
+ def confirm=(obj)
35
+ raise TypeError, 'confirm must be a Text Object' unless obj.is_a?(Text)
36
+ raise TypeError, 'confirm must be plain_text' unless obj.type == :plain_text
37
+ raise RangeError, 'confirm is max 30 characters' unless obj.text.size <= 30
38
+
39
+ @confirm = obj
40
+ end
41
+
42
+ def deny=(obj)
43
+ raise TypeError, 'deny must be a Text Object' unless obj.is_a?(Text)
44
+ raise TypeError, 'deny must be plain_text' unless obj.type == :plain_text
45
+ raise RangeError, 'deny is max 30 characters' unless obj.text.size <= 30
46
+
47
+ @deny = obj
48
+ end
49
+
50
+ def to_h
51
+ { title: title.to_h,
52
+ text: text.to_h,
53
+ confirm: confirm.to_h,
54
+ deny: deny.to_h }.compact
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end