line-message-builder 0.7.0 → 0.8.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/.document +3 -0
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +14 -0
- data/README.md +8 -0
- data/lib/line/message/builder/actions/message.rb +67 -1
- data/lib/line/message/builder/actions/postback.rb +71 -1
- data/lib/line/message/builder/actions.rb +18 -1
- data/lib/line/message/builder/base.rb +113 -3
- data/lib/line/message/builder/container.rb +125 -5
- data/lib/line/message/builder/context.rb +115 -3
- data/lib/line/message/builder/flex/actionable.rb +46 -1
- data/lib/line/message/builder/flex/box.rb +166 -14
- data/lib/line/message/builder/flex/bubble.rb +96 -8
- data/lib/line/message/builder/flex/builder.rb +74 -8
- data/lib/line/message/builder/flex/button.rb +104 -17
- data/lib/line/message/builder/flex/carousel.rb +71 -8
- data/lib/line/message/builder/flex/image.rb +106 -20
- data/lib/line/message/builder/flex/partial.rb +89 -9
- data/lib/line/message/builder/flex/position.rb +122 -12
- data/lib/line/message/builder/flex/size.rb +65 -7
- data/lib/line/message/builder/flex/text.rb +131 -27
- data/lib/line/message/builder/flex.rb +54 -12
- data/lib/line/message/builder/quick_reply.rb +16 -4
- data/lib/line/message/builder/text.rb +12 -1
- data/lib/line/message/builder/version.rb +1 -1
- data/lib/line/message/builder.rb +33 -3
- data/lib/line/message/rspec.rb +14 -0
- data/llm.txt +356 -0
- metadata +3 -1
@@ -4,32 +4,98 @@ module Line
|
|
4
4
|
module Message
|
5
5
|
module Builder
|
6
6
|
module Flex
|
7
|
-
# The Builder class is
|
7
|
+
# The `Builder` class is the main entry point for constructing a complete
|
8
|
+
# LINE Flex Message. A Flex Message is a highly customizable message type
|
9
|
+
# that can contain one main content element, which is either a single
|
10
|
+
# {Bubble} or a {Carousel} of bubbles.
|
11
|
+
#
|
12
|
+
# This builder requires `alt_text` (alternative text) for accessibility and
|
13
|
+
# for display on devices or LINE versions that cannot render Flex Messages.
|
14
|
+
# It can also have a `quickReply` attached to it.
|
15
|
+
#
|
16
|
+
# @example Creating a Flex Message with a single bubble
|
17
|
+
# Line::Message::Builder.with do |root|
|
18
|
+
# root.flex alt_text: "My Product" do |flex_builder| # flex_builder is an instance of Flex::Builder
|
19
|
+
# flex_builder.bubble size: :giga do |bubble_content|
|
20
|
+
# bubble_content.hero_image "https://example.com/product.jpg"
|
21
|
+
# bubble_content.body do |b|
|
22
|
+
# b.text "Product Name", weight: :bold, size: :xl
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
# flex_builder.quick_reply do |qr|
|
26
|
+
# qr.button action: :message, label: "Learn More", text: "Tell me more"
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# @see https://developers.line.biz/en/reference/messaging-api/#flex-messages
|
32
|
+
# @see Bubble
|
33
|
+
# @see Carousel
|
34
|
+
# @see QuickReply
|
8
35
|
class Builder < Line::Message::Builder::Base
|
36
|
+
# Alternative text for the Flex Message. Displayed on devices that
|
37
|
+
# cannot render Flex Messages or used for accessibility.
|
38
|
+
# This is a required field for a valid Flex Message.
|
39
|
+
# @!method alt_text(value)
|
40
|
+
# @param value [String, nil] The alternative text. Max 400 characters.
|
41
|
+
# @return [String, nil] The current alternative text.
|
9
42
|
option :alt_text, default: nil
|
10
43
|
|
44
|
+
# Initializes a new Flex Message Builder.
|
45
|
+
# The provided block is instance-eval'd, allowing DSL methods like
|
46
|
+
# {#bubble} or {#carousel} to define the main content.
|
47
|
+
#
|
48
|
+
# @param context [Object, nil] An optional context for the builder.
|
49
|
+
# @param options [Hash] A hash of options to set instance variables (e.g., `:alt_text`).
|
50
|
+
# @param block [Proc, nil] A block to define the content (bubble or carousel)
|
51
|
+
# of this Flex Message.
|
11
52
|
def initialize(context: nil, **options, &)
|
12
|
-
@contents = nil
|
53
|
+
@contents = nil # Will hold either a Bubble or Carousel instance
|
13
54
|
|
14
|
-
super
|
55
|
+
super # Calls Base#initialize, sets options (like @alt_text), and evals block
|
15
56
|
end
|
16
57
|
|
58
|
+
# Defines the content of this Flex Message as a single {Bubble}.
|
59
|
+
# If called, this will overwrite any previously defined `carousel` content.
|
60
|
+
#
|
61
|
+
# @param options [Hash] Options for the Bubble. See {Bubble#initialize}.
|
62
|
+
# @param block [Proc] A block to define the sections of the Bubble.
|
63
|
+
# @return [Flex::Bubble] The newly created Bubble object.
|
17
64
|
def bubble(**options, &)
|
18
65
|
@contents = Line::Message::Builder::Flex::Bubble.new(context: context, **options, &)
|
19
66
|
end
|
20
67
|
|
68
|
+
# Defines the content of this Flex Message as a {Carousel} of bubbles.
|
69
|
+
# If called, this will overwrite any previously defined `bubble` content.
|
70
|
+
#
|
71
|
+
# @param options [Hash] Options for the Carousel. See {Carousel#initialize}.
|
72
|
+
# @param block [Proc] A block to define the bubbles within the Carousel.
|
73
|
+
# @return [Flex::Carousel] The newly created Carousel object.
|
21
74
|
def carousel(**options, &)
|
22
75
|
@contents = Line::Message::Builder::Flex::Carousel.new(context: context, **options, &)
|
23
76
|
end
|
24
77
|
|
25
|
-
|
26
|
-
|
78
|
+
private
|
79
|
+
|
80
|
+
def to_api
|
81
|
+
raise Error, "Flex Message contents (bubble or carousel) must be defined." if @contents.nil?
|
82
|
+
|
83
|
+
{
|
84
|
+
type: "flex",
|
85
|
+
altText: alt_text, # From option
|
86
|
+
contents: @contents.to_h, # Serializes the Bubble or Carousel
|
87
|
+
quickReply: @quick_reply&.to_h # From Base class's quick_reply method
|
88
|
+
}.compact
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_sdkv2
|
92
|
+
raise Error, "Flex Message contents (bubble or carousel) must be defined." if @contents.nil?
|
27
93
|
|
28
94
|
{
|
29
95
|
type: "flex",
|
30
|
-
|
31
|
-
contents: @contents.to_h,
|
32
|
-
|
96
|
+
alt_text: alt_text, # From option
|
97
|
+
contents: @contents.to_h, # Serializes the Bubble or Carousel
|
98
|
+
quick_reply: @quick_reply&.to_h # From Base class's quick_reply method
|
33
99
|
}.compact
|
34
100
|
end
|
35
101
|
end
|
@@ -4,30 +4,87 @@ module Line
|
|
4
4
|
module Message
|
5
5
|
module Builder
|
6
6
|
module Flex
|
7
|
-
#
|
7
|
+
# Represents a "button" component in a LINE Flex Message.
|
8
|
+
#
|
9
|
+
# Buttons are interactive elements that users can tap to trigger an {Actionable#action action}
|
10
|
+
# (e.g., open a URL, send a message, or trigger a postback). They have
|
11
|
+
# various styling options, including `style` (primary, secondary, link) and
|
12
|
+
# `height`.
|
13
|
+
#
|
14
|
+
# An action is mandatory for a button component.
|
15
|
+
#
|
16
|
+
# @example Creating a button that sends a message
|
17
|
+
# Line::Message::Builder.with do |root|
|
18
|
+
# root.flex alt_text: "Button Example" do |flex|
|
19
|
+
# flex.bubble do |bubble|
|
20
|
+
# bubble.body do |body_box|
|
21
|
+
# body_box.button style: :primary, height: :sm do |btn|
|
22
|
+
# btn.message "Buy Now", label: "Buy" # Action definition
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# @see https://developers.line.biz/en/reference/messaging-api/#button
|
30
|
+
# @see Actionable For defining the button's action (mandatory).
|
31
|
+
# @see Position::Vertical For `gravity` property.
|
32
|
+
# @see Position::Padding For padding properties.
|
33
|
+
# @see Position::Margin For margin property.
|
34
|
+
# @see Position::Offset For offset properties.
|
35
|
+
# @see Size::Flex For flex sizing property.
|
36
|
+
# @see Size::AdjustMode For `adjust_mode` property.
|
8
37
|
class Button < Line::Message::Builder::Base
|
9
|
-
include Actionable
|
10
|
-
include Position::Vertical
|
11
|
-
include Position::Padding
|
12
|
-
include Position::Margin
|
13
|
-
include Position::Offset
|
14
|
-
include Size::Flex
|
15
|
-
include Size::AdjustMode
|
38
|
+
include Actionable # Defines the action performed when the button is tapped.
|
39
|
+
include Position::Vertical # Adds `gravity` option for vertical alignment.
|
40
|
+
include Position::Padding # Adds padding options.
|
41
|
+
include Position::Margin # Adds `margin` option.
|
42
|
+
include Position::Offset # Adds offset options.
|
43
|
+
include Size::Flex # Adds `flex` option for sizing within a parent box.
|
44
|
+
include Size::AdjustMode # Adds `adjust_mode` option.
|
16
45
|
|
46
|
+
# Specifies the style of the button.
|
47
|
+
# @!method style(value)
|
48
|
+
# @param value [Symbol, String] Button style.
|
49
|
+
# Can be `:primary`, `:secondary`, `:link` (default).
|
50
|
+
# String values should match these keywords.
|
51
|
+
# @return [Symbol, String] The current button style.
|
17
52
|
option :style, default: :link
|
53
|
+
|
54
|
+
# Specifies the height of the button.
|
55
|
+
# @!method height(value)
|
56
|
+
# @param value [Symbol, String] Button height.
|
57
|
+
# Can be `:sm` (small) or `:md` (medium, default).
|
58
|
+
# @return [Symbol, String] The current button height.
|
18
59
|
option :height, default: :md, validator: Validators::Enum.new(:sm, :md)
|
19
60
|
|
20
|
-
|
21
|
-
|
61
|
+
# Initializes a new Flex Message Button component.
|
62
|
+
# The provided block is instance-eval'd, primarily used to define the
|
63
|
+
# button's action using methods from the {Actionable} module (e.g., `message "label"`).
|
64
|
+
#
|
65
|
+
# @param context [Object, nil] An optional context for the builder.
|
66
|
+
# @param options [Hash] A hash of options to set instance variables
|
67
|
+
# (e.g., `:style`, `:height`, and options from included modules).
|
68
|
+
# @param block [Proc, nil] A block to define the button's action and other properties.
|
69
|
+
# This block is where you should call an action method like `message` or `postback`.
|
70
|
+
def initialize(context: nil, **options, &)
|
71
|
+
super # Calls Base#initialize, sets options, and evals block (which should define the action)
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def to_api # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
77
|
+
raise RequiredError, "action is required for a button" if action.nil?
|
22
78
|
|
23
79
|
{
|
24
80
|
type: "button",
|
25
|
-
action: action.to_h,
|
26
|
-
|
27
|
-
#
|
28
|
-
|
81
|
+
action: action.to_h, # From Actionable module
|
82
|
+
style: style, # From option
|
83
|
+
height: height, # From option
|
84
|
+
# Position::Vertical
|
85
|
+
gravity: gravity,
|
29
86
|
# Position::Padding
|
30
|
-
paddingAll: padding,
|
87
|
+
paddingAll: padding || padding_all,
|
31
88
|
paddingTop: padding_top,
|
32
89
|
paddingBottom: padding_bottom,
|
33
90
|
paddingStart: padding_start,
|
@@ -43,8 +100,38 @@ module Line
|
|
43
100
|
# Size::Flex
|
44
101
|
flex: flex,
|
45
102
|
# Size::AdjustMode
|
46
|
-
adjustMode: adjust_mode
|
47
|
-
|
103
|
+
adjustMode: adjust_mode
|
104
|
+
}.compact
|
105
|
+
end
|
106
|
+
|
107
|
+
def to_sdkv2 # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
108
|
+
raise RequiredError, "action is required for a button" if action.nil?
|
109
|
+
|
110
|
+
{
|
111
|
+
type: "button",
|
112
|
+
action: action.to_h, # From Actionable module
|
113
|
+
style: style, # From option
|
114
|
+
height: height, # From option
|
115
|
+
# Position::Vertical
|
116
|
+
gravity: gravity,
|
117
|
+
# Position::Padding
|
118
|
+
padding_all: padding || padding_all,
|
119
|
+
padding_top: padding_top,
|
120
|
+
padding_bottom: padding_bottom,
|
121
|
+
padding_start: padding_start,
|
122
|
+
padding_end: padding_end,
|
123
|
+
# Position::Margin
|
124
|
+
margin: margin,
|
125
|
+
# Position::Offset
|
126
|
+
position: position,
|
127
|
+
offset_top: offset_top,
|
128
|
+
offset_bottom: offset_bottom,
|
129
|
+
offset_start: offset_start,
|
130
|
+
offset_end: offset_end,
|
131
|
+
# Size::Flex
|
132
|
+
flex: flex,
|
133
|
+
# Size::AdjustMode
|
134
|
+
adjust_mode: adjust_mode
|
48
135
|
}.compact
|
49
136
|
end
|
50
137
|
end
|
@@ -4,28 +4,91 @@ module Line
|
|
4
4
|
module Message
|
5
5
|
module Builder
|
6
6
|
module Flex
|
7
|
-
#
|
7
|
+
# Represents a "carousel" container in a LINE Flex Message.
|
8
|
+
# A carousel is a horizontally scrollable sequence of {Bubble} components.
|
9
|
+
# Each bubble in the carousel is a distinct message unit. Users can swipe
|
10
|
+
# left or right to view the different bubbles.
|
11
|
+
#
|
12
|
+
# Carousels are ideal for presenting multiple items, such as products,
|
13
|
+
# articles, or options, in a compact and interactive way.
|
14
|
+
#
|
15
|
+
# @example Creating a carousel with two bubbles
|
16
|
+
# Line::Message::Builder.with do |root|
|
17
|
+
# root.flex alt_text: "Product Showcase" do |flex_builder|
|
18
|
+
# flex_builder.carousel do |carousel_container| # carousel_container is an instance of Flex::Carousel
|
19
|
+
# carousel_container.bubble size: :mega do |bubble1|
|
20
|
+
# bubble1.hero_image "https://example.com/product1.jpg"
|
21
|
+
# bubble1.body { |b| b.text "Product 1" }
|
22
|
+
# end
|
23
|
+
# carousel_container.bubble size: :mega do |bubble2|
|
24
|
+
# bubble2.hero_image "https://example.com/product2.jpg"
|
25
|
+
# bubble2.body { |b| b.text "Product 2" }
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# @see https://developers.line.biz/en/reference/messaging-api/#carousel
|
32
|
+
# @see Bubble For the structure of individual items within the carousel.
|
33
|
+
# @see HasPartial While included, direct usage on Carousel itself is limited;
|
34
|
+
# partials are more commonly used within the individual bubbles.
|
8
35
|
class Carousel < Line::Message::Builder::Base
|
9
|
-
include HasPartial
|
36
|
+
include HasPartial # Allows including predefined partial component sets (more relevant for bubbles within).
|
10
37
|
|
38
|
+
# @!attribute [r] contents
|
39
|
+
# @return [Array<Flex::Bubble>] An array holding the {Bubble} components
|
40
|
+
# that form the items of this carousel.
|
41
|
+
attr_reader :contents
|
42
|
+
|
43
|
+
# Initializes a new Flex Message Carousel container.
|
44
|
+
# The provided block is instance-eval'd, allowing DSL methods like
|
45
|
+
# {#bubble} to be called to add bubbles to the carousel.
|
46
|
+
#
|
47
|
+
# @param context [Object, nil] An optional context for the builder.
|
48
|
+
# @param options [Hash] A hash of options (currently none specific to Carousel itself,
|
49
|
+
# but available for future extensions or via `Base`).
|
50
|
+
# @param block [Proc, nil] A block to define the bubbles within this carousel.
|
11
51
|
def initialize(context: nil, **options, &)
|
12
|
-
@contents = []
|
52
|
+
@contents = [] # Holds an array of Bubble objects
|
13
53
|
|
14
|
-
super
|
54
|
+
super # Calls Base#initialize, sets options, and evals block
|
15
55
|
end
|
16
56
|
|
57
|
+
# Adds a new {Bubble} to this carousel's contents.
|
58
|
+
# Each call to this method appends another bubble to the horizontal sequence.
|
59
|
+
#
|
60
|
+
# @param options [Hash] Options for the Bubble. See {Bubble#initialize}.
|
61
|
+
# @param block [Proc] A block to define the sections and content of the Bubble.
|
62
|
+
# @return [Flex::Bubble] The newly created Bubble object that was added to the carousel.
|
17
63
|
def bubble(**options, &)
|
64
|
+
# The maximum number of bubbles is validated in `to_h` as per LINE API limits.
|
18
65
|
@contents << Line::Message::Builder::Flex::Bubble.new(context: context, **options, &)
|
19
66
|
end
|
20
67
|
|
21
68
|
def to_h
|
22
|
-
raise RequiredError, "contents
|
23
|
-
|
69
|
+
raise RequiredError, "Carousel contents must have at least 1 bubble." if @contents.empty?
|
70
|
+
# LINE API as of 2023-10-10 allows up to 12 bubbles in a carousel.
|
71
|
+
raise ValidationError, "Carousel contents can have at most 12 bubbles." if @contents.size > 12
|
72
|
+
|
73
|
+
return to_sdkv2 if context.sdkv2?
|
74
|
+
|
75
|
+
to_api
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def to_api
|
81
|
+
{
|
82
|
+
type: "carousel",
|
83
|
+
contents: @contents.map(&:to_h) # Serializes each Bubble in the array
|
84
|
+
}.compact # compact is likely unnecessary here as contents is always present.
|
85
|
+
end
|
24
86
|
|
87
|
+
def to_sdkv2
|
25
88
|
{
|
26
89
|
type: "carousel",
|
27
|
-
contents: @contents.map(&:to_h)
|
28
|
-
}.compact
|
90
|
+
contents: @contents.map(&:to_h) # Serializes each Bubble in the array
|
91
|
+
}.compact # compact is likely unnecessary here as contents is always present.
|
29
92
|
end
|
30
93
|
end
|
31
94
|
end
|
@@ -4,36 +4,93 @@ module Line
|
|
4
4
|
module Message
|
5
5
|
module Builder
|
6
6
|
module Flex
|
7
|
-
#
|
7
|
+
# Represents an "image" component in a LINE Flex Message.
|
8
|
+
#
|
9
|
+
# Images are specified by a URL and can be included in various parts of a
|
10
|
+
# Flex Message, such as a box, a bubble's hero section, etc. They offer
|
11
|
+
# several properties to control their appearance, including `size`,
|
12
|
+
# `aspect_ratio`, and `aspect_mode`. An image can also have an
|
13
|
+
# {Actionable#action action} to make it tappable.
|
14
|
+
#
|
15
|
+
# @example Creating an image component within a box
|
16
|
+
# Line::Message::Builder.with do |root|
|
17
|
+
# root.flex alt_text: "Image Example" do |flex|
|
18
|
+
# flex.bubble do |bubble|
|
19
|
+
# bubble.body do |body_box|
|
20
|
+
# body_box.image "https://example.com/image.png",
|
21
|
+
# aspect_ratio: "16:9",
|
22
|
+
# aspect_mode: :cover,
|
23
|
+
# size: :full do |img_action|
|
24
|
+
# img_action.message "View Details", text: "Show details for image"
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# @see https://developers.line.biz/en/reference/messaging-api/#image
|
32
|
+
# @see Actionable For making the image tappable.
|
33
|
+
# @see Position::Horizontal For `align` property.
|
34
|
+
# @see Position::Vertical For `gravity` property.
|
35
|
+
# @see Position::Margin For `margin` property.
|
36
|
+
# @see Position::Offset For offset properties.
|
37
|
+
# @see Size::Flex For `flex` sizing property.
|
38
|
+
# @see Size::Image For `size` (image specific keywords), `aspect_ratio`, `aspect_mode`.
|
8
39
|
class Image < Line::Message::Builder::Base
|
9
|
-
include Actionable
|
10
|
-
include Position::Horizontal
|
11
|
-
include Position::Vertical
|
12
|
-
include Position::Margin
|
13
|
-
include Position::Offset
|
14
|
-
include Size::Flex
|
15
|
-
include Size::Image
|
40
|
+
include Actionable # Enables defining an action for the image.
|
41
|
+
include Position::Horizontal # Adds `align` option for horizontal alignment.
|
42
|
+
include Position::Vertical # Adds `gravity` option for vertical alignment.
|
43
|
+
include Position::Margin # Adds `margin` option.
|
44
|
+
include Position::Offset # Adds offset options.
|
45
|
+
include Size::Flex # Adds `flex` option for sizing within a parent box.
|
46
|
+
include Size::Image # Adds image-specific sizing options like `size`, `aspect_ratio`, `aspect_mode`.
|
16
47
|
|
48
|
+
# @!attribute [r] url
|
49
|
+
# @return [String] The URL of the image. Must be HTTPS.
|
50
|
+
# This is a required attribute.
|
17
51
|
attr_reader :url
|
18
52
|
|
53
|
+
# Specifies the aspect ratio of the image (width:height).
|
54
|
+
# E.g., "1:1", "16:9", "20:13". Default is "1:1".
|
55
|
+
# @!method aspect_ratio(value)
|
56
|
+
# @param value [String, nil] The aspect ratio string.
|
57
|
+
# @return [String, nil] The current aspect ratio.
|
19
58
|
option :aspect_ratio, default: nil
|
20
|
-
option :aspect_mode, default: nil
|
21
59
|
|
60
|
+
# Specifies how the image should be displayed within the area defined by `aspect_ratio`.
|
61
|
+
# @!method aspect_mode(value)
|
62
|
+
# @param value [Symbol, String, nil] Aspect mode. Can be `:cover` (default)
|
63
|
+
# or `:fit`.
|
64
|
+
# @return [Symbol, String, nil] The current aspect mode.
|
65
|
+
option :aspect_mode, default: nil # :cover, :fit
|
66
|
+
|
67
|
+
# Initializes a new Flex Message Image component.
|
68
|
+
#
|
69
|
+
# @param url [String] The HTTPS URL of the image. This is required.
|
70
|
+
# @param context [Object, nil] An optional context for the builder.
|
71
|
+
# @param options [Hash] A hash of options to set instance variables
|
72
|
+
# (e.g., `:aspect_ratio`, `:aspect_mode`, `:size`, and options from included modules).
|
73
|
+
# @param block [Proc, nil] An optional block, typically used to define an
|
74
|
+
# {Actionable#action action} for the image.
|
75
|
+
# @raise [ArgumentError] if `url` is nil (though the more specific `RequiredError`
|
76
|
+
# is raised in `to_h`).
|
22
77
|
def initialize(url, context: nil, **options, &)
|
23
|
-
@url = url
|
78
|
+
@url = url # The image URL is mandatory.
|
24
79
|
|
25
|
-
super(context: context, **options, &)
|
80
|
+
super(context: context, **options, &) # Sets options and evals block (for action).
|
26
81
|
end
|
27
82
|
|
28
|
-
|
29
|
-
|
83
|
+
private
|
84
|
+
|
85
|
+
def to_api # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
86
|
+
raise RequiredError, "url is required for an image component" if url.nil?
|
30
87
|
|
31
88
|
{
|
32
89
|
type: "image",
|
33
90
|
url: url,
|
34
|
-
# Position
|
35
|
-
align: align,
|
36
|
-
gravity: gravity,
|
91
|
+
# Position::Horizontal & Position::Vertical
|
92
|
+
align: align, # From Position::Horizontal
|
93
|
+
gravity: gravity, # From Position::Vertical
|
37
94
|
# Position::Margin
|
38
95
|
margin: margin,
|
39
96
|
# Position::Offset
|
@@ -44,11 +101,40 @@ module Line
|
|
44
101
|
offsetEnd: offset_end,
|
45
102
|
# Size::Flex
|
46
103
|
flex: flex,
|
47
|
-
# Size::Image
|
104
|
+
# Size::Image (includes aspect_ratio, aspect_mode, and specific image size keywords)
|
105
|
+
size: size,
|
106
|
+
aspectRatio: aspect_ratio, # From option
|
107
|
+
aspectMode: aspect_mode, # From option
|
108
|
+
# Actionable
|
109
|
+
action: action&.to_h # From Actionable module
|
110
|
+
}.compact
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_sdkv2 # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
114
|
+
raise RequiredError, "url is required for an image component" if url.nil?
|
115
|
+
|
116
|
+
{
|
117
|
+
type: "image",
|
118
|
+
url: url,
|
119
|
+
# Position::Horizontal & Position::Vertical
|
120
|
+
align: align, # From Position::Horizontal
|
121
|
+
gravity: gravity, # From Position::Vertical
|
122
|
+
# Position::Margin
|
123
|
+
margin: margin,
|
124
|
+
# Position::Offset
|
125
|
+
position: position,
|
126
|
+
offset_top: offset_top,
|
127
|
+
offset_bottom: offset_bottom,
|
128
|
+
offset_start: offset_start,
|
129
|
+
offset_end: offset_end,
|
130
|
+
# Size::Flex
|
131
|
+
flex: flex,
|
132
|
+
# Size::Image (includes aspect_ratio, aspect_mode, and specific image size keywords)
|
48
133
|
size: size,
|
49
|
-
|
50
|
-
|
51
|
-
|
134
|
+
aspect_ratio: aspect_ratio, # From option
|
135
|
+
aspect_mode: aspect_mode, # From option
|
136
|
+
# Actionable
|
137
|
+
action: action&.to_h # From Actionable module
|
52
138
|
}.compact
|
53
139
|
end
|
54
140
|
end
|
@@ -4,32 +4,112 @@ module Line
|
|
4
4
|
module Message
|
5
5
|
module Builder
|
6
6
|
module Flex
|
7
|
-
#
|
7
|
+
# The `HasPartial` module provides functionality for Flex Message components
|
8
|
+
# (like {Box}, {Bubble}) to render reusable "partials". Partials are defined
|
9
|
+
# by creating classes that inherit from {Partial}.
|
10
|
+
#
|
11
|
+
# Including this module into a component class gives it the {#partial!} method.
|
12
|
+
#
|
13
|
+
# @example Defining and using a partial
|
14
|
+
# # Define a reusable partial
|
15
|
+
# class MyButtonPartial < Line::Message::Builder::Flex::Partial
|
16
|
+
# def call(label:, data:)
|
17
|
+
# # `button` method is available from the context (e.g., a Box)
|
18
|
+
# button style: :primary do |btn|
|
19
|
+
# btn.postback data, label: label
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# # Use the partial within a Box component
|
25
|
+
# a_box_component.instance_eval do
|
26
|
+
# # ... other box content ...
|
27
|
+
# partial! MyButtonPartial, label: "Action", data: "action=do_something"
|
28
|
+
# # ... other box content ...
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# @see Partial
|
8
32
|
module HasPartial
|
9
|
-
|
10
|
-
|
33
|
+
# Renders a given {Partial} class within the current component's context.
|
34
|
+
#
|
35
|
+
# This method temporarily makes the provided `assigns` available to the
|
36
|
+
# partial through the `context.assigns` mechanism. After the partial's
|
37
|
+
# `call` method completes, the original `context.assigns` are restored.
|
38
|
+
#
|
39
|
+
# @param partial_class [Class] The class of the partial to render. Must be a
|
40
|
+
# subclass of {Partial}.
|
41
|
+
# @param assigns [Hash] A hash of key-value pairs that will be made
|
42
|
+
# available as `assigns` within the partial's `call` method.
|
43
|
+
# @return [void]
|
44
|
+
# @raise [ArgumentError] if `partial_class` is not a subclass of {Partial}.
|
45
|
+
def partial!(partial_class, **assigns)
|
46
|
+
unless partial_class < Partial
|
47
|
+
raise ArgumentError,
|
48
|
+
"Argument must be a Line::Message::Builder::Flex::Partial class"
|
49
|
+
end
|
11
50
|
|
12
|
-
|
51
|
+
original_assigns = context.assigns
|
13
52
|
context.assigns = assigns
|
14
|
-
|
15
|
-
context
|
53
|
+
# `self` here is the component instance (e.g., Box, Bubble) that includes HasPartial.
|
54
|
+
# This component instance becomes the `@context` for the Partial instance.
|
55
|
+
partial_class.new(context: self).call
|
56
|
+
context.assigns = original_assigns
|
16
57
|
end
|
17
58
|
end
|
18
59
|
|
19
|
-
# The
|
60
|
+
# The `Partial` class is an abstract base for creating reusable snippets
|
61
|
+
# of Flex Message content. To define a partial, create a new class that
|
62
|
+
# inherits from `Partial` and implements the {#call} instance method.
|
63
|
+
#
|
64
|
+
# Within the `call` method, you can use the standard Flex Message builder
|
65
|
+
# DSL methods (e.g., `text`, `box`, `button`) as if you were directly inside
|
66
|
+
# the component where the partial is being rendered. This is possible because
|
67
|
+
# the `Partial` instance delegates unknown method calls to the component
|
68
|
+
# instance that is rendering it (passed as `context` during initialization).
|
69
|
+
#
|
70
|
+
# @abstract Subclass and implement {#call} to create a concrete partial.
|
71
|
+
# @see HasPartial For how to render partials.
|
20
72
|
class Partial
|
73
|
+
# Initializes a new Partial instance.
|
74
|
+
# This is typically called by the {HasPartial#partial!} method.
|
75
|
+
#
|
76
|
+
# @param context [Object] The component instance (e.g., {Box}, {Bubble})
|
77
|
+
# within which this partial is being rendered. This context provides
|
78
|
+
# the DSL methods (like `text`, `box`) used in the partial's `call` method.
|
21
79
|
def initialize(context:)
|
22
|
-
@context = context
|
80
|
+
@context = context # The component (e.g., Box, Bubble) rendering this partial
|
23
81
|
end
|
24
82
|
|
83
|
+
# This method must be implemented by subclasses to define the content of
|
84
|
+
# the partial. Inside this method, use the Flex Message builder DSL methods
|
85
|
+
# (e.g., `text "Hello"`, `button { ... }`) which will be delegated to the
|
86
|
+
# rendering context.
|
87
|
+
#
|
88
|
+
# Any arguments passed to `partial!` as `assigns` are available via
|
89
|
+
# `context.assigns` or directly if the rendering context's `method_missing`
|
90
|
+
# handles `assigns` lookup (as {Line::Message::Builder::Context} does).
|
91
|
+
#
|
92
|
+
# @param ... [Object] Arguments passed from the `partial!` call's `assigns`
|
93
|
+
# can be explicitly defined as parameters here, or accessed via `context.assigns`.
|
94
|
+
# @raise [NotImplementedError] if a subclass does not implement this method.
|
25
95
|
def call(*)
|
26
|
-
raise NotImplementedError,
|
96
|
+
raise NotImplementedError,
|
97
|
+
"The #{self.class.name} class must implement the #call method to define its content."
|
27
98
|
end
|
28
99
|
|
100
|
+
# @!visibility private
|
101
|
+
# Part of Ruby's dynamic method dispatch. It's overridden here to declare
|
102
|
+
# that instances of `Partial` can respond to methods to which the
|
103
|
+
# wrapped `@context` object responds.
|
29
104
|
def respond_to_missing?(method_name, include_private = false)
|
30
105
|
@context.respond_to?(method_name, include_private) || super
|
31
106
|
end
|
32
107
|
|
108
|
+
# @!visibility private
|
109
|
+
# Handles calls to methods not explicitly defined on the `Partial` class
|
110
|
+
# by delegating them to the wrapped `@context` object (the rendering component).
|
111
|
+
# This allows the `call` method of a partial to use DSL methods like `text`,
|
112
|
+
# `box`, `button`, etc., as if they were defined directly in the partial.
|
33
113
|
def method_missing(method_name, ...)
|
34
114
|
if @context.respond_to?(method_name)
|
35
115
|
@context.public_send(method_name, ...)
|