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
@@ -3,27 +3,139 @@
|
|
3
3
|
module Line
|
4
4
|
module Message
|
5
5
|
module Builder
|
6
|
-
# The
|
6
|
+
# The `Context` class is a crucial component of the `Line::Message::Builder`
|
7
|
+
# DSL, enabling a flexible and dynamic environment for message construction.
|
8
|
+
# It acts as a wrapper around an optional user-provided context object and
|
9
|
+
# manages a separate hash of "assigns" (local variables for the DSL).
|
10
|
+
#
|
11
|
+
# The primary purposes of the `Context` are:
|
12
|
+
# 1. **To provide access to helper methods**: If a user passes a context object
|
13
|
+
# (e.g., a Rails view context, a presenter, or any custom object) during
|
14
|
+
# builder initialization (`Line::Message::Builder.with(my_helper_object)`),
|
15
|
+
# methods defined on `my_helper_object` become directly callable within
|
16
|
+
# the DSL blocks.
|
17
|
+
# 2. **To allow local data storage (`assigns`)**: The `assigns` hash allows
|
18
|
+
# for setting and retrieving temporary data that can be shared across
|
19
|
+
# different parts of a complex message construction block, without needing
|
20
|
+
# to pass it explicitly or pollute the user-provided context.
|
21
|
+
#
|
22
|
+
# Method calls within the DSL that are not defined on the builder objects
|
23
|
+
# themselves are resolved by `Context` in the following order:
|
24
|
+
# - First, it checks if the method name corresponds to a key in the `assigns` hash.
|
25
|
+
# - If not found in `assigns`, it checks if the wrapped user-context object
|
26
|
+
# responds to the method.
|
27
|
+
# - If neither, the call proceeds up the normal Ruby method lookup chain.
|
28
|
+
#
|
29
|
+
# This mechanism allows for a clean and powerful way to integrate external logic
|
30
|
+
# and data into the message building process.
|
31
|
+
#
|
32
|
+
# @example Using a custom context
|
33
|
+
# class MyHelpers
|
34
|
+
# def current_user_name
|
35
|
+
# "Alice"
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# helpers = MyHelpers.new
|
40
|
+
# Line::Message::Builder.with(helpers) do |root|
|
41
|
+
# # `current_user_name` is resolved from `helpers` by Context
|
42
|
+
# root.text "Hello, #{current_user_name}!"
|
43
|
+
#
|
44
|
+
# # Using assigns
|
45
|
+
# assigns[:item_count] = 5
|
46
|
+
# root.text "You have #{assigns[:item_count]} items."
|
47
|
+
# end
|
7
48
|
class Context
|
49
|
+
# @!attribute assigns
|
50
|
+
# A hash for storing arbitrary data that can be accessed within the
|
51
|
+
# builder DSL. This is useful for temporary variables or shared state
|
52
|
+
# during message construction.
|
53
|
+
# @return [Hash] The hash of assigned values.
|
54
|
+
# @example
|
55
|
+
# context.assigns[:user_id] = 123
|
56
|
+
# puts context.assigns[:user_id] # => 123
|
8
57
|
attr_accessor :assigns
|
9
58
|
|
10
|
-
|
59
|
+
# @!attribute [r] mode
|
60
|
+
# The mode in which the builder is operating. This affects how messages
|
61
|
+
# are formatted in the final output.
|
62
|
+
# @return [Symbol] Either `:api` for direct LINE Messaging API format
|
63
|
+
# or `:sdkv2` for LINE Bot SDK v2 compatible format.
|
64
|
+
attr_reader :mode
|
65
|
+
|
66
|
+
# Initializes a new Context object.
|
67
|
+
#
|
68
|
+
# @param context [Object, nil] An optional object whose methods will be made
|
69
|
+
# available within the DSL. If `nil`, only `assigns` and standard
|
70
|
+
# builder methods will be available.
|
71
|
+
# @param mode [Symbol] The mode of the context, which can be `:api` (default)
|
72
|
+
# for direct LINE Messaging API format or `:sdkv2` for LINE Bot SDK v2
|
73
|
+
# compatible format.
|
74
|
+
def initialize(context, mode: :api)
|
11
75
|
@context = context
|
12
76
|
@assigns = {}
|
77
|
+
@mode = mode
|
13
78
|
end
|
14
79
|
|
80
|
+
# Part of Ruby's dynamic method dispatch. It's overridden here to declare
|
81
|
+
# that instances of `Context` can respond to methods that are either:
|
82
|
+
# 1. Keys in the `@assigns` hash.
|
83
|
+
# 2. Methods to which the wrapped `@context` object responds.
|
84
|
+
#
|
85
|
+
# This ensures that `respond_to?` behaves consistently with how
|
86
|
+
# `method_missing` resolves method calls.
|
87
|
+
#
|
88
|
+
# @param method_name [Symbol] The name of the method being queried.
|
89
|
+
# @param include_private [Boolean] Whether to include private methods in
|
90
|
+
# the check.
|
91
|
+
# @return [Boolean] `true` if the context can handle the method,
|
92
|
+
# `false` otherwise.
|
93
|
+
# @!visibility private
|
15
94
|
def respond_to_missing?(method_name, include_private = false)
|
16
95
|
@assigns.key?(method_name) ||
|
17
|
-
@context.respond_to?(method_name, include_private) ||
|
96
|
+
@context.respond_to?(method_name, include_private) || # Check @context directly
|
18
97
|
super
|
19
98
|
end
|
20
99
|
|
100
|
+
# Handles calls to methods not explicitly defined on the `Context` class.
|
101
|
+
# The resolution order is:
|
102
|
+
# 1. If `method_name` is a key in the `@assigns` hash, its value is returned.
|
103
|
+
# 2. If the wrapped `@context` object responds to `method_name`, the call
|
104
|
+
# is delegated to `@context`.
|
105
|
+
# 3. Otherwise, `super` is called, allowing the standard Ruby method
|
106
|
+
# lookup to continue (which will likely result in a `NoMethodError`
|
107
|
+
# if the method is truly undefined).
|
108
|
+
#
|
109
|
+
# This is the core mechanism that allows DSL blocks to seamlessly access
|
110
|
+
# data from `assigns` or methods from the user-provided context.
|
111
|
+
#
|
112
|
+
# @param method_name [Symbol] The name of the invoked method.
|
113
|
+
# @param ... [Object] Arguments passed to the method.
|
114
|
+
# @return [Object, nil] The value from `@assigns`, the result of the
|
115
|
+
# delegated call to `@context`, or raises `NoMethodError` via `super`.
|
116
|
+
# @raise [NoMethodError] If the method is not found in `assigns` or
|
117
|
+
# on the wrapped context.
|
118
|
+
# @!visibility private
|
21
119
|
def method_missing(method_name, ...)
|
22
120
|
return @assigns[method_name] if @assigns.key?(method_name)
|
121
|
+
# Check @context directly
|
23
122
|
return @context.public_send(method_name, ...) if @context.respond_to?(method_name)
|
24
123
|
|
25
124
|
super
|
26
125
|
end
|
126
|
+
|
127
|
+
# Checks if the current mode is set to SDK v2 compatibility.
|
128
|
+
#
|
129
|
+
# @return [Boolean] `true` if the mode is `:sdkv2`, `false` otherwise.
|
130
|
+
# @example
|
131
|
+
# if context.sdkv2?
|
132
|
+
# # Format message for LINE Bot SDK v2
|
133
|
+
# else
|
134
|
+
# # Format message for direct API use
|
135
|
+
# end
|
136
|
+
def sdkv2?
|
137
|
+
mode == :sdkv2
|
138
|
+
end
|
27
139
|
end
|
28
140
|
end
|
29
141
|
end
|
@@ -4,16 +4,61 @@ module Line
|
|
4
4
|
module Message
|
5
5
|
module Builder
|
6
6
|
module Flex
|
7
|
-
# The
|
7
|
+
# The `Actionable` module provides a DSL for defining an action that can be
|
8
|
+
# triggered when a user interacts with certain Flex Message components
|
9
|
+
# (e.g., a {Button} component, or an entire {Bubble} or {Box} component
|
10
|
+
# if it's made tappable).
|
11
|
+
#
|
12
|
+
# When a component includes this module, it gains methods like {#message}
|
13
|
+
# and {#postback} to associate a specific LINE action with itself. The
|
14
|
+
# chosen action is stored in the `action` attribute.
|
15
|
+
#
|
16
|
+
# @!attribute [r] action
|
17
|
+
# @return [Actions::Message, Actions::Postback, nil] The action object
|
18
|
+
# associated with this component. `nil` if no action is defined.
|
19
|
+
#
|
20
|
+
# @see Line::Message::Builder::Actions::Message
|
21
|
+
# @see Line::Message::Builder::Actions::Postback
|
22
|
+
# @see https://developers.line.biz/en/reference/messaging-api/#action-objects
|
8
23
|
module Actionable
|
24
|
+
# @!visibility private
|
25
|
+
# Automatically adds an `attr_reader :action` to the class that includes
|
26
|
+
# this module.
|
27
|
+
# @param base [Class] The class including this module.
|
9
28
|
def self.included(base)
|
10
29
|
base.attr_reader :action
|
11
30
|
end
|
12
31
|
|
32
|
+
# Defines a message action for the component.
|
33
|
+
# When the component is tapped, a message with the given `text` is sent
|
34
|
+
# from the user to the chat.
|
35
|
+
#
|
36
|
+
# @param text [String] The text of the message to send.
|
37
|
+
# @param options [Hash] Additional options for the message action,
|
38
|
+
# such as `:label`. See {Actions::Message#initialize}.
|
39
|
+
# @param block [Proc, nil] An optional block, though not typically used
|
40
|
+
# directly for message actions here.
|
41
|
+
# @return [Actions::Message] The created message action object.
|
42
|
+
#
|
43
|
+
# @example Setting a message action on a button
|
44
|
+
# button_component.message "Hello User!", label: "Send Greeting"
|
13
45
|
def message(text, **options, &)
|
14
46
|
@action = Actions::Message.new(text, context: context, **options, &)
|
15
47
|
end
|
16
48
|
|
49
|
+
# Defines a postback action for the component.
|
50
|
+
# When the component is tapped, a postback event with the given `data`
|
51
|
+
# is sent to the bot's webhook.
|
52
|
+
#
|
53
|
+
# @param data [String] The data payload for the postback event.
|
54
|
+
# @param options [Hash] Additional options for the postback action,
|
55
|
+
# such as `:label` or `:display_text`. See {Actions::Postback#initialize}.
|
56
|
+
# @param block [Proc, nil] An optional block, though not typically used
|
57
|
+
# directly for postback actions here.
|
58
|
+
# @return [Actions::Postback] The created postback action object.
|
59
|
+
#
|
60
|
+
# @example Setting a postback action on a button
|
61
|
+
# button_component.postback "action=buy&item_id=123", label: "Buy Item"
|
17
62
|
def postback(data, **options, &)
|
18
63
|
@action = Actions::Postback.new(data, context: context, **options, &)
|
19
64
|
end
|
@@ -4,66 +4,182 @@ module Line
|
|
4
4
|
module Message
|
5
5
|
module Builder
|
6
6
|
module Flex
|
7
|
-
#
|
7
|
+
# Represents a "box" component in a LINE Flex Message.
|
8
|
+
#
|
9
|
+
# A box is a fundamental layout container that arranges its child components
|
10
|
+
# (`contents`) in a specified direction (`layout`: horizontal, vertical, or
|
11
|
+
# baseline). It can hold various other components like text, images, buttons,
|
12
|
+
# or even nested boxes, allowing for complex layouts.
|
13
|
+
#
|
14
|
+
# Boxes have numerous properties to control their appearance and the
|
15
|
+
# arrangement of their children, such as padding, margin, spacing between
|
16
|
+
# items, justification, and alignment.
|
17
|
+
#
|
18
|
+
# A box can also have an `action` associated with it, making the entire
|
19
|
+
# box area tappable.
|
20
|
+
#
|
21
|
+
# @example Creating a horizontal box with two text components
|
22
|
+
# Line::Message::Builder.with do |root|
|
23
|
+
# root.flex alt_text: "Box example" do |flex|
|
24
|
+
# flex.bubble do |bubble|
|
25
|
+
# bubble.body do |body_box| # body_box is an instance of Flex::Box
|
26
|
+
# body_box.layout :horizontal
|
27
|
+
# body_box.spacing :md
|
28
|
+
# body_box.text "Item 1"
|
29
|
+
# body_box.text "Item 2"
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# @see https://developers.line.biz/en/reference/messaging-api/#box
|
36
|
+
# @see Actionable For making the box tappable.
|
37
|
+
# @see HasPartial For including reusable component groups.
|
38
|
+
# @see Position::Padding For padding properties.
|
39
|
+
# @see Position::Margin For margin properties.
|
40
|
+
# @see Position::Offset For offset properties.
|
41
|
+
# @see Size::Flex For flex sizing property.
|
8
42
|
class Box < Line::Message::Builder::Base
|
9
|
-
include HasPartial
|
10
|
-
include Actionable
|
11
|
-
include Position::Padding
|
12
|
-
include Position::Margin
|
13
|
-
include Position::Offset
|
14
|
-
include Size::Flex
|
43
|
+
include HasPartial # Allows including predefined partial component sets.
|
44
|
+
include Actionable # Enables defining an action for the entire box.
|
45
|
+
include Position::Padding # Adds padding options like `padding_all`, `padding_top`, etc.
|
46
|
+
include Position::Margin # Adds `margin` option.
|
47
|
+
include Position::Offset # Adds offset options like `offset_top`, `offset_start`, etc.
|
48
|
+
include Size::Flex # Adds `flex` option for controlling size within a parent box.
|
15
49
|
|
50
|
+
# @!attribute [r] contents
|
51
|
+
# @return [Array<Base>] An array holding the child components (e.g.,
|
52
|
+
# {Text}, {Image}, nested {Box} instances) of this box.
|
16
53
|
attr_reader :contents
|
17
54
|
|
55
|
+
# Specifies the arrangement of child components.
|
56
|
+
# @!method layout(value)
|
57
|
+
# @param value [Symbol] `:horizontal` (default), `:vertical`, or `:baseline`.
|
58
|
+
# @return [Symbol] The current layout.
|
18
59
|
option :layout, default: :horizontal, validator: Validators::Enum.new(
|
19
60
|
:horizontal, :vertical, :baseline
|
20
61
|
)
|
62
|
+
|
63
|
+
# Horizontal alignment of content in a horizontal box; vertical alignment in a vertical box.
|
64
|
+
# @!method justify_content(value)
|
65
|
+
# @param value [Symbol, nil] E.g., `:flex_start`, `:center`, `:flex_end`,
|
66
|
+
# `:space_between`, `:space_around`, `:space_evenly`.
|
67
|
+
# @return [Symbol, nil] The current justify_content value.
|
21
68
|
option :justify_content, default: nil, validator: Validators::Enum.new(
|
22
69
|
:flex_start, :center, :flex_end, :space_between, :space_around, :space_evenly
|
23
70
|
)
|
71
|
+
|
72
|
+
# Vertical alignment of content in a horizontal box; horizontal alignment in a vertical box.
|
73
|
+
# @!method align_items(value)
|
74
|
+
# @param value [Symbol, nil] E.g., `:flex_start`, `:center`, `:flex_end`.
|
75
|
+
# @return [Symbol, nil] The current align_items value.
|
24
76
|
option :align_items, default: nil, validator: Validators::Enum.new(
|
25
77
|
:flex_start, :center, :flex_end
|
26
78
|
)
|
79
|
+
|
80
|
+
# Specifies the minimum spacing between components.
|
81
|
+
# Not applicable if `layout` is `:baseline` or if the box contains only one component.
|
82
|
+
# @!method spacing(value)
|
83
|
+
# @param value [String, Symbol, nil] E.g., `"md"`, `:lg`, `"10px"`.
|
84
|
+
# Keywords: `:none`, `:xs`, `:sm`, `:md`, `:lg`, `:xl`, `:xxl`.
|
85
|
+
# @return [String, Symbol, nil] The current spacing value.
|
27
86
|
option :spacing, default: nil, validator: Validators::Size.new(:pixel, :keyword)
|
87
|
+
|
88
|
+
# Width of the box. Can be a percentage or pixel value.
|
89
|
+
# @!method width(value)
|
90
|
+
# @param value [String, nil] E.g., `"100px"`, `"50%"`.
|
91
|
+
# @return [String, nil] The current width.
|
28
92
|
option :width, default: nil, validator: Validators::Size.new(:pixel, :percentage)
|
93
|
+
|
94
|
+
# Maximum width of the box.
|
95
|
+
# @!method max_width(value)
|
96
|
+
# @param value [String, nil] E.g., `"100px"`, `"50%"`.
|
97
|
+
# @return [String, nil] The current maximum width.
|
29
98
|
option :max_width, default: nil, validator: Validators::Size.new(:pixel, :percentage)
|
99
|
+
|
100
|
+
# Height of the box.
|
101
|
+
# @!method height(value)
|
102
|
+
# @param value [String, nil] E.g., `"100px"`, `"50%"`.
|
103
|
+
# @return [String, nil] The current height.
|
30
104
|
option :height, default: nil, validator: Validators::Size.new(:pixel, :percentage)
|
105
|
+
|
106
|
+
# Maximum height of the box.
|
107
|
+
# @!method max_height(value)
|
108
|
+
# @param value [String, nil] E.g., `"100px"`, `"50%"`.
|
109
|
+
# @return [String, nil] The current maximum height.
|
31
110
|
option :max_height, default: nil, validator: Validators::Size.new(:pixel, :percentage)
|
32
111
|
|
112
|
+
# Initializes a new Flex Message Box component.
|
113
|
+
# The provided block is instance-eval'd, allowing DSL methods for adding
|
114
|
+
# child components (e.g., {#text}, {#button}, nested {#box}) to be called.
|
115
|
+
#
|
116
|
+
# @param context [Object, nil] An optional context for the builder.
|
117
|
+
# @param options [Hash] A hash of options to set instance variables.
|
118
|
+
# Corresponds to the `option` definitions in this class and included modules.
|
119
|
+
# @param block [Proc, nil] A block to define the contents of this box.
|
33
120
|
def initialize(context: nil, **options, &)
|
34
|
-
@contents = []
|
35
|
-
|
36
|
-
super
|
121
|
+
@contents = [] # Holds child components
|
122
|
+
super # Calls Base#initialize, sets options, and evals block
|
37
123
|
end
|
38
124
|
|
125
|
+
# Adds a nested Flex {Box} component to this box's contents.
|
126
|
+
#
|
127
|
+
# @param options [Hash] Options for the nested box. See {Box#initialize}.
|
128
|
+
# @param block [Proc] A block to define the contents of the nested box.
|
129
|
+
# @return [Flex::Box] The newly created nested Box object.
|
39
130
|
def box(**options, &)
|
40
131
|
@contents << Flex::Box.new(context: context, **options, &)
|
41
132
|
end
|
42
133
|
|
134
|
+
# Adds a Flex {Text} component to this box's contents.
|
135
|
+
#
|
136
|
+
# @param text [String] The text content.
|
137
|
+
# @param options [Hash] Options for the text component. See {Text#initialize}.
|
138
|
+
# @param block [Proc, nil] An optional block for the text component (e.g., for an action).
|
139
|
+
# @return [Flex::Text] The newly created Text object.
|
43
140
|
def text(text, **options, &)
|
44
141
|
@contents << Flex::Text.new(text, context: context, **options, &)
|
45
142
|
end
|
46
143
|
|
144
|
+
# Adds a Flex {Button} component to this box's contents.
|
145
|
+
#
|
146
|
+
# @param options [Hash] Options for the button component. See {Button#initialize}.
|
147
|
+
# @param block [Proc] A block to define the button's action and properties.
|
148
|
+
# @return [Flex::Button] The newly created Button object.
|
47
149
|
def button(**options, &)
|
48
150
|
@contents << Flex::Button.new(context: context, **options, &)
|
49
151
|
end
|
50
152
|
|
153
|
+
# Adds a Flex {Image} component to this box's contents.
|
154
|
+
#
|
155
|
+
# @param url [String] The URL of the image.
|
156
|
+
# @param options [Hash] Options for the image component. See {Image#initialize}.
|
157
|
+
# @param block [Proc, nil] An optional block for the image component (e.g., for an action).
|
158
|
+
# @return [Flex::Image] The newly created Image object.
|
51
159
|
def image(url, **options, &)
|
52
160
|
@contents << Flex::Image.new(url, context: context, **options, &)
|
53
161
|
end
|
54
162
|
|
55
|
-
def to_h
|
163
|
+
def to_h
|
56
164
|
raise RequiredError, "layout is required" if layout.nil?
|
57
165
|
|
166
|
+
return to_sdkv2 if context.sdkv2?
|
167
|
+
|
168
|
+
to_api
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
|
173
|
+
def to_api # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
58
174
|
{
|
59
175
|
type: "box",
|
60
176
|
layout: layout,
|
61
|
-
# Position
|
177
|
+
# Position & Layout
|
62
178
|
justifyContent: justify_content,
|
63
179
|
alignItems: align_items,
|
64
180
|
spacing: spacing,
|
65
181
|
# Position::Padding
|
66
|
-
paddingAll: padding,
|
182
|
+
paddingAll: padding || padding_all,
|
67
183
|
paddingTop: padding_top,
|
68
184
|
paddingBottom: padding_bottom,
|
69
185
|
paddingStart: padding_start,
|
@@ -83,8 +199,44 @@ module Line
|
|
83
199
|
maxHeight: max_height,
|
84
200
|
# Size::Flex
|
85
201
|
flex: flex,
|
202
|
+
# Contents & Action
|
203
|
+
contents: contents.map(&:to_h),
|
204
|
+
action: action&.to_h # From Actionable module
|
205
|
+
}.compact
|
206
|
+
end
|
207
|
+
|
208
|
+
def to_sdkv2 # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
209
|
+
{
|
210
|
+
type: "box",
|
211
|
+
layout: layout,
|
212
|
+
# Position & Layout
|
213
|
+
justify_content: justify_content,
|
214
|
+
align_items: align_items,
|
215
|
+
spacing: spacing,
|
216
|
+
# Position::Padding
|
217
|
+
padding_all: padding || padding_all,
|
218
|
+
padding_top: padding_top,
|
219
|
+
padding_bottom: padding_bottom,
|
220
|
+
padding_start: padding_start,
|
221
|
+
padding_end: padding_end,
|
222
|
+
# Position::Margin
|
223
|
+
margin: margin,
|
224
|
+
# Position::Offset
|
225
|
+
position: position,
|
226
|
+
offset_top: offset_top,
|
227
|
+
offset_bottom: offset_bottom,
|
228
|
+
offset_start: offset_start,
|
229
|
+
offset_end: offset_end,
|
230
|
+
# Size
|
231
|
+
width: width,
|
232
|
+
max_width: max_width,
|
233
|
+
height: height,
|
234
|
+
max_height: max_height,
|
235
|
+
# Size::Flex
|
236
|
+
flex: flex,
|
237
|
+
# Contents & Action
|
86
238
|
contents: contents.map(&:to_h),
|
87
|
-
action: action&.to_h
|
239
|
+
action: action&.to_h # From Actionable module
|
88
240
|
}.compact
|
89
241
|
end
|
90
242
|
end
|
@@ -4,51 +4,139 @@ module Line
|
|
4
4
|
module Message
|
5
5
|
module Builder
|
6
6
|
module Flex
|
7
|
-
#
|
7
|
+
# Represents a "bubble" container in a LINE Flex Message.
|
8
|
+
# A bubble is a self-contained unit of content, structured into optional
|
9
|
+
# sections: header, hero (an image or box), body, and footer.
|
10
|
+
# Bubbles are the fundamental building blocks for single Flex Messages or
|
11
|
+
# for each item in a {Carousel} container.
|
12
|
+
#
|
13
|
+
# @example Creating a simple bubble with a body
|
14
|
+
# Line::Message::Builder.with do |root|
|
15
|
+
# root.flex alt_text: "Simple Bubble" do |flex|
|
16
|
+
# flex.bubble do |bubble|
|
17
|
+
# bubble.body do |body_box|
|
18
|
+
# body_box.text "Hello, this is a bubble!"
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# @see https://developers.line.biz/en/reference/messaging-api/#bubble
|
25
|
+
# @see HasPartial For including reusable component groups within sections.
|
26
|
+
# @see Box For the structure of header, hero (if box), body, and footer.
|
27
|
+
# @see Image For using an image as a hero section.
|
8
28
|
class Bubble < Line::Message::Builder::Base
|
9
|
-
include HasPartial
|
29
|
+
include HasPartial # Allows including predefined partial component sets into sections.
|
10
30
|
|
11
|
-
|
31
|
+
# Specifies the size of the bubble.
|
32
|
+
# @!method size(value)
|
33
|
+
# @param value [Symbol, String, nil] Bubble size. Keywords: `:nano`, `:micro`,
|
34
|
+
# `:kilo`, `:mega`, `:giga`. Pixel/percentage values are not directly
|
35
|
+
# supported for bubble size by LINE; use keywords.
|
36
|
+
# @return [Symbol, String, nil] The current bubble size.
|
37
|
+
option :size, default: nil # E.g., :nano, :micro, :kilo, :mega, :giga
|
38
|
+
|
39
|
+
# Defines custom styles for the bubble and its sections (header, hero, body, footer).
|
40
|
+
# @!method styles(value)
|
41
|
+
# @param value [Hash, nil] A hash defining style overrides.
|
42
|
+
# See LINE API documentation for the structure of the styles object.
|
43
|
+
# @return [Hash, nil] The current styles hash.
|
44
|
+
# @example
|
45
|
+
# bubble.styles(
|
46
|
+
# header: { backgroundColor: "#FF0000" },
|
47
|
+
# body: { separator: true, separatorColor: "#00FF00" }
|
48
|
+
# )
|
12
49
|
option :styles, default: nil
|
13
50
|
|
51
|
+
# Initializes a new Flex Message Bubble container.
|
52
|
+
# The provided block is instance-eval'd, allowing DSL methods for defining
|
53
|
+
# sections (e.g., {#header}, {#body}) to be called.
|
54
|
+
#
|
55
|
+
# @param context [Object, nil] An optional context for the builder.
|
56
|
+
# @param options [Hash] A hash of options to set instance variables
|
57
|
+
# (e.g., `:size`, `:styles`).
|
58
|
+
# @param block [Proc, nil] A block to define the sections of this bubble.
|
14
59
|
def initialize(context: nil, **options, &)
|
15
60
|
@header = nil
|
16
61
|
@hero = nil
|
17
62
|
@body = nil
|
18
63
|
@footer = nil
|
19
64
|
|
20
|
-
super
|
65
|
+
super # Calls Base#initialize, sets options, and evals block
|
21
66
|
end
|
22
67
|
|
68
|
+
# Defines the header section of the bubble using a {Box} component.
|
69
|
+
#
|
70
|
+
# @param options [Hash] Options for the header Box. See {Box#initialize}.
|
71
|
+
# @param block [Proc] A block to define the contents of the header Box.
|
72
|
+
# @return [Flex::Box] The newly created Box object for the header.
|
23
73
|
def header(**options, &)
|
24
74
|
@header = Box.new(**options, context: context, &)
|
25
75
|
end
|
26
76
|
|
77
|
+
# Defines the hero section of the bubble using a {Box} component.
|
78
|
+
# The hero section is typically used for prominent content like a large image or video.
|
79
|
+
#
|
80
|
+
# @param options [Hash] Options for the hero Box. See {Box#initialize}.
|
81
|
+
# @param block [Proc] A block to define the contents of the hero Box.
|
82
|
+
# @return [Flex::Box] The newly created Box object for the hero section.
|
27
83
|
def hero(**options, &)
|
28
84
|
@hero = Box.new(**options, context: context, &)
|
29
85
|
end
|
30
86
|
|
87
|
+
# Defines the hero section of the bubble using an {Image} component.
|
88
|
+
# This is a convenience method for common cases where the hero is a single image.
|
89
|
+
#
|
90
|
+
# @param url [String] The URL of the image.
|
91
|
+
# @param options [Hash] Options for the Image component. See {Image#initialize}.
|
92
|
+
# @param block [Proc, nil] An optional block for the Image component (e.g., for an action).
|
93
|
+
# @return [Flex::Image] The newly created Image object for the hero section.
|
31
94
|
def hero_image(url, **options, &)
|
32
95
|
@hero = Image.new(url, **options, context: context, &)
|
33
96
|
end
|
34
97
|
|
98
|
+
# Defines the body section of the bubble using a {Box} component.
|
99
|
+
# This is the main content area of the bubble.
|
100
|
+
#
|
101
|
+
# @param options [Hash] Options for the body Box. See {Box#initialize}.
|
102
|
+
# @param block [Proc] A block to define the contents of the body Box.
|
103
|
+
# @return [Flex::Box] The newly created Box object for the body.
|
35
104
|
def body(**options, &)
|
36
105
|
@body = Box.new(**options, context: context, &)
|
37
106
|
end
|
38
107
|
|
108
|
+
# Defines the footer section of the bubble using a {Box} component.
|
109
|
+
#
|
110
|
+
# @param options [Hash] Options for the footer Box. See {Box#initialize}.
|
111
|
+
# @param block [Proc] A block to define the contents of the footer Box.
|
112
|
+
# @return [Flex::Box] The newly created Box object for the footer.
|
39
113
|
def footer(**options, &)
|
40
114
|
@footer = Box.new(**options, context: context, &)
|
41
115
|
end
|
42
116
|
|
43
|
-
|
117
|
+
private
|
118
|
+
|
119
|
+
def to_api
|
120
|
+
{
|
121
|
+
type: "bubble",
|
122
|
+
size: size, # From option
|
123
|
+
styles: styles, # From option
|
124
|
+
header: @header&.to_h,
|
125
|
+
hero: @hero&.to_h,
|
126
|
+
body: @body&.to_h,
|
127
|
+
footer: @footer&.to_h
|
128
|
+
}.compact
|
129
|
+
end
|
130
|
+
|
131
|
+
def to_sdkv2
|
44
132
|
{
|
45
133
|
type: "bubble",
|
134
|
+
size: size, # From option
|
135
|
+
styles: styles, # From option
|
46
136
|
header: @header&.to_h,
|
47
137
|
hero: @hero&.to_h,
|
48
138
|
body: @body&.to_h,
|
49
|
-
footer: @footer&.to_h
|
50
|
-
size: size,
|
51
|
-
styles: styles
|
139
|
+
footer: @footer&.to_h
|
52
140
|
}.compact
|
53
141
|
end
|
54
142
|
end
|