line-message-builder 0.8.0 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c49876bd79a2b1e98639a5e709a41736ebc11e8c7b5bf811ca478c52df762183
4
- data.tar.gz: 65ceed1d2fbef231161bc7eb131269354875c39b202da96bb6fcd42edce4bf86
3
+ metadata.gz: e0b31fce5c00d5f47ade90df479d30b79361f73e3e08852e10f4f90996de5bb9
4
+ data.tar.gz: 6a72a291942bd3f7448f5a294da49dd3321424d5dad057745590e4b96b1a1abb
5
5
  SHA512:
6
- metadata.gz: bf2111f3470856dc021330c2fa73e3275842596d858e636d198f3dd8dcea7bddf9a60a252e4a80eb5e6297126b0ec3afeeaf143181ef565db06af9cf558c93c6
7
- data.tar.gz: e98ec6e6696268c00f599de55e6b6acb09067c2370c6fc5357f8d1ee7f74b9b6e7d2c9b59a527ae5bc806ca6c685163311648b3a78641bd028d8ba6b71dc3bf1
6
+ metadata.gz: bb358c2a18903395e07bfc878d3befe2048ad23c5d68b4e5cd23e17e1914bb300f7a692858a68f9c71daa3630b1f30761ab5fc603887296c1d1cffd4964b7414
7
+ data.tar.gz: 342237372588844319052e9be35b1014024f5f93552f99699137f19a5abf7f341bd03eda306f4f2c5d181c50f257f47fdf5f0a7899cc2093312a91d4cd42667f
@@ -1 +1 @@
1
- {".":"0.8.0"}
1
+ {".":"0.9.0"}
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.0](https://github.com/elct9620/line-message-builder/compare/v0.8.0...v0.9.0) (2025-06-03)
4
+
5
+
6
+ ### Features
7
+
8
+ * add flex separator matcher and tests for flex message components ([0d34bc9](https://github.com/elct9620/line-message-builder/commit/0d34bc9fcfeef9d2ca519548f4a83a3de046eb33))
9
+ * add Flex::Span message builder for LINE messaging API ([a8377e9](https://github.com/elct9620/line-message-builder/commit/a8377e93861fa286e95a69d3cb3c9897c1992044))
10
+ * add have_line_flex_span matcher and comprehensive tests for span component ([b9072cd](https://github.com/elct9620/line-message-builder/commit/b9072cd3e4c56c9346bb3d6cf98ee11374029807))
11
+ * add Separator builder for Flex messages ([accb2aa](https://github.com/elct9620/line-message-builder/commit/accb2aa41f9a016445f377f97e93f085734764c4))
12
+ * add separator method to Flex::Box for adding separator elements ([4125ae1](https://github.com/elct9620/line-message-builder/commit/4125ae15fd5666431ec864121cf40d3c4fb0f56f))
13
+ * add Span component for styled text in Flex Messages ([a458d1d](https://github.com/elct9620/line-message-builder/commit/a458d1d2473e0859bc7e1c81254888a8c8fde9a7))
14
+ * add span method to flex box builder and remove related specs ([af8dc8f](https://github.com/elct9620/line-message-builder/commit/af8dc8f9f4b8c8a7a12c15db23a50331dfc102cb))
15
+ * add support for spans within flex text components ([90ab15d](https://github.com/elct9620/line-message-builder/commit/90ab15df7044d15308d23ef864a69f84d71e28e7))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * add Flex::Separator component and require it in flex.rb ([4152c10](https://github.com/elct9620/line-message-builder/commit/4152c1024a7634b526f94fa4e76a518a6ff0787c))
21
+ * ensure matcher finds flex separator in all nested containers ([d289ea1](https://github.com/elct9620/line-message-builder/commit/d289ea178e17d4ca00b796a9f666c527982d82f6))
22
+ * remove duplicate Separator class definition in flex separator.rb ([2c6b6d0](https://github.com/elct9620/line-message-builder/commit/2c6b6d063a2da97a0781591a697cf3034cb135f6))
23
+
3
24
  ## [0.8.0](https://github.com/elct9620/line-message-builder/compare/v0.7.0...v0.8.0) (2025-05-24)
4
25
 
5
26
 
data/CONVENTIONS.md CHANGED
@@ -16,6 +16,42 @@ This gem is designed to provide DSL (Domain Specific Language) for building LINE
16
16
 
17
17
  Only use comments for RDoc documentation. Do not use comments to explain anything in the code. The code should be self-explanatory. If you find yourself needing to write a comment to explain something, consider refactoring the code instead.
18
18
 
19
+ ## DSL
20
+
21
+ When using the `Line::Message::Builder` DSL, following is recommended:
22
+
23
+ ```ruby
24
+ Line::Message::Builder.with do
25
+ flex alt_text: "Hello, World!" do
26
+ bubble do
27
+ header do
28
+ text "Welcome to LINE Messaging API"
29
+ end
30
+ body do
31
+ text "This is a sample message."
32
+ end
33
+ end
34
+ end
35
+ end
36
+ ```
37
+
38
+ DO NOT use `do |container|` syntax as following:
39
+
40
+ ```ruby
41
+ Line::Message::Builder.with do |builder|
42
+ builder.flex alt_text: "Hello, World!" do
43
+ builder.bubble do
44
+ builder.header do
45
+ builder.text "Welcome to LINE Messaging API"
46
+ end
47
+ builder.body do
48
+ builder.text "This is a sample message."
49
+ end
50
+ end
51
+ end
52
+ end
53
+ ```
54
+
19
55
  ## RSpec
20
56
 
21
57
  - Write feature tests instead of unit tests, use `Line::Message::Builder` as test subject to verify the behavior of the DSL.
data/README.md CHANGED
@@ -12,7 +12,7 @@ Build LINE messages using DSL (Domain Specific Language) in Ruby.
12
12
  - Build LINE messages using DSL
13
13
  - Validation of properties
14
14
  - RSpec matchers for testing
15
- - LINE Bot SDK v2 support (WIP)
15
+ - LINE Bot SDK v2 support (Experimental)
16
16
 
17
17
  ## Installation
18
18
 
@@ -43,14 +43,22 @@ You can get it from [http://aotoki.me/line-message-builder/llm.txt](http://aotok
43
43
 
44
44
  ```ruby
45
45
  builder = Line::MessageBuilder::Builder.with do
46
- text "Hello, world!"
46
+ text "Hello, world!"
47
47
  end
48
48
 
49
49
  pp builder.build
50
50
  # => [{ type: "text", text: "Hello, world!" }]
51
51
 
52
52
  puts builder.to_json
53
- # => {"type":"text","text":"Hello, world!"}
53
+ # => "[{\"type\":\"text",\"text\":\"Hello, world!\"}"
54
+ ```
55
+
56
+ To use with [line-bot-sdk-ruby](https://github.com/line/line-bot-sdk-ruby) v2, you can set mode to `sdkv2`:
57
+
58
+ ```ruby
59
+ builder = Line::MessageBuilder::Builder.with(mode: :sdkv2) do
60
+ text "Hello, world!"
61
+ end
54
62
  ```
55
63
 
56
64
  ### Context
@@ -59,18 +67,18 @@ The context can make `Builder` to access additional methods and variables.
59
67
 
60
68
  ```ruby
61
69
  context = OpenStruct.new(
62
- name: "John Doe",
70
+ name: "John Doe",
63
71
  )
64
72
 
65
73
  builder = Line::MessageBuilder::Builder.with(context) do
66
- text "Hello, #{name}!"
74
+ text "Hello, #{name}!"
67
75
  end
68
76
 
69
77
  pp builder.build
70
78
  # => [{ type: "text", text: "Hello, John Doe!" }]
71
79
 
72
80
  puts builder.to_json
73
- # => {"type":"text","text":"Hello, John Doe!"}
81
+ # => "[{\"type\":\"text\",\"text\":\"Hello, John Doe!\"}"
74
82
  ```
75
83
 
76
84
  For Rails, you can use `view_context` to make `Builder` to access Rails helpers.
@@ -78,12 +86,12 @@ For Rails, you can use `view_context` to make `Builder` to access Rails helpers.
78
86
  ```ruby
79
87
  # app/controllers/line_controller.rb
80
88
  builder = Line::MessageBuilder::Builder.with(view_context) do
81
- text "Anything you want?" do
82
- quick_reply do
83
- message "Yes", label: "Yes", image_url: image_url("yes.png")
84
- message "No", label: "No", image_url: image_url("no.png")
85
- end
89
+ text "Anything you want?" do
90
+ quick_reply do
91
+ message "Yes", label: "Yes", image_url: image_url("yes.png")
92
+ message "No", label: "No", image_url: image_url("no.png")
86
93
  end
94
+ end
87
95
  end
88
96
  ```
89
97
 
@@ -92,18 +100,18 @@ If not in controller, you can create a `ActionView::Base` instance and pass it t
92
100
  ```ruby
93
101
  # app/presenters/line_presenter.rb
94
102
  context = ActionView::Base.new(
95
- ActionController::Base.view_paths,
96
- {},
97
- ActionController::Base.new,
103
+ ActionController::Base.view_paths,
104
+ {},
105
+ ActionController::Base.new,
98
106
  )
99
107
 
100
108
  builder = Line::MessageBuilder::Builder.with(context) do
101
- text "Anything you want?" do
102
- quick_reply do
103
- message "Yes", label: "Yes", image_url: image_url("yes.png")
104
- message "No", label: "No", image_url: image_url("no.png")
105
- end
109
+ text "Anything you want?" do
110
+ quick_reply do
111
+ message "Yes", label: "Yes", image_url: image_url("yes.png")
112
+ message "No", label: "No", image_url: image_url("no.png")
106
113
  end
114
+ end
107
115
  end
108
116
  ```
109
117
 
@@ -171,6 +179,7 @@ end
171
179
  | `have_line_flex_image` | Match a flex message with image |
172
180
  | `have_line_flex_button` | Match a flex message with button |
173
181
  | `have_line_flex_box` | Match a flex message with box |
182
+ | `have_line_flex_separator` | Match a flex message with separator |
174
183
 
175
184
 
176
185
  Add `line/message/rspec` to your `spec_helper.rb` or `rails_helper.rb`:
@@ -183,7 +192,7 @@ Include `Line::Message::RSpec::Matchers` in your RSpec configuration:
183
192
 
184
193
  ```ruby
185
194
  RSpec.configure do |config|
186
- config.include Line::Message::RSpec::Matchers
195
+ config.include Line::Message::RSpec::Matchers
187
196
  end
188
197
  ```
189
198
 
@@ -191,10 +200,10 @@ Then the matchers are available in your specs:
191
200
 
192
201
  ```ruby
193
202
  let(:builder) do
194
- Line::MessageBuilder::Builder.with do
195
- text "Hello, world!"
196
- text "Nice to meet you!"
197
- end
203
+ Line::MessageBuilder::Builder.with do
204
+ text "Hello, world!"
205
+ text "Nice to meet you!"
206
+ end
198
207
  end
199
208
 
200
209
  subject { builder.build }
@@ -207,12 +216,13 @@ The matchers can work with webmock `a_request`:
207
216
 
208
217
  ```ruby
209
218
  it "reply with message" do
210
- expect(a_request(:post, "https://api.line.me/v2/bot/message/reply")
211
- .with(
212
- body: hash_including({
213
- messages: have_line_text_message(/Hello, world!/),
214
- },
215
- ))).to have_been_made.once
219
+ expect(a_request(:post, "https://api.line.me/v2/bot/message/reply")
220
+ .with(
221
+ body: hash_including({
222
+ messages: have_line_text_message(/Hello, world!/),
223
+ })
224
+ )
225
+ ).to have_been_made.once
216
226
  end
217
227
  ```
218
228
 
@@ -271,8 +281,8 @@ end
271
281
  | Video | ❌ |
272
282
  | Icon | ❌ |
273
283
  | Text | 🚧 |
274
- | Span | |
275
- | Separator | |
284
+ | Span | |
285
+ | Separator | |
276
286
  | Filler | ❌ Deprecated |
277
287
 
278
288
  ## Development
@@ -15,13 +15,13 @@ module Line
15
15
  # in quick replies or other interactive message components.
16
16
  #
17
17
  # @example Creating a message action for a quick reply button
18
- # Line::Message::Builder.with do |root|
19
- # root.text "Select your favorite food:"
20
- # root.quick_reply do |qr|
18
+ # Line::Message::Builder.with do
19
+ # text "Select your favorite food:"
20
+ # quick_reply do
21
21
  # # When this button is tapped, the user sends "Pizza"
22
- # qr.button action: :message, label: "Pizza", text: "Pizza"
22
+ # button action: :message, label: "Pizza", text: "Pizza"
23
23
  # # When this button is tapped, the user sends "Sushi"
24
- # qr.button action: :message, label: "Sushi", text: "Sushi"
24
+ # button action: :message, label: "Sushi", text: "Sushi"
25
25
  # end
26
26
  # end
27
27
  #
@@ -16,13 +16,13 @@ module Line
16
16
  # a different message than the data payload.
17
17
  #
18
18
  # @example Creating a postback action for a quick reply button
19
- # Line::Message::Builder.with do |root|
20
- # root.text "What do you want to do?"
21
- # root.quick_reply do |qr|
22
- # qr.button action: :postback,
23
- # label: "Track Order",
24
- # data: "action=track_order&order_id=123",
25
- # display_text: "I want to track my order."
19
+ # Line::Message::Builder.with do
20
+ # text "What do you want to do?"
21
+ # quick_reply do
22
+ # button action: :postback,
23
+ # label: "Track Order",
24
+ # data: "action=track_order&order_id=123",
25
+ # display_text: "I want to track my order."
26
26
  # end
27
27
  # end
28
28
  #
@@ -16,11 +16,11 @@ module Line
16
16
  # Each message added to the container can also have its own quick reply.
17
17
  #
18
18
  # @example Building multiple messages
19
- # message_payload = Line::Message::Builder.with do |root|
20
- # root.text "Hello, this is the first message!"
21
- # root.flex alt_text: "This is a Flex Message" do |flex_builder|
22
- # flex_builder.bubble do |bubble|
23
- # bubble.body do |body|
19
+ # message_payload = Line::Message::Builder.with do
20
+ # text "Hello, this is the first message!"
21
+ # flex alt_text: "This is a Flex Message" do
22
+ # bubble do
23
+ # body do
24
24
  # body.text "This is a Flex Message body."
25
25
  # end
26
26
  # end
@@ -91,10 +91,10 @@ module Line
91
91
  # is typically within Flex::Builder).
92
92
  #
93
93
  # @example
94
- # root.flex alt_text: "Important information" do |fb|
95
- # fb.bubble do |bubble|
96
- # bubble.header { |h| h.text "Header" }
97
- # bubble.body { |b| b.text "Body" }
94
+ # flex alt_text: "Important information" do
95
+ # bubble do
96
+ # header { text "Header" }
97
+ # body { text "Body" }
98
98
  # end
99
99
  # end
100
100
  def flex(**options, &)
@@ -30,20 +30,16 @@ module Line
30
30
  # and data into the message building process.
31
31
  #
32
32
  # @example Using a custom context
33
- # class MyHelpers
33
+ # class MyContext
34
34
  # def current_user_name
35
35
  # "Alice"
36
36
  # end
37
37
  # end
38
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."
39
+ # context = MyContext.new
40
+ # Line::Message::Builder.with(context) do
41
+ # # `current_user_name` is resolved from `context` by Context
42
+ # text "Hello, #{current_user_name}!"
47
43
  # end
48
44
  class Context
49
45
  # @!attribute assigns
@@ -19,14 +19,14 @@ module Line
19
19
  # box area tappable.
20
20
  #
21
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"
22
+ # Line::Message::Builder.with do
23
+ # flex alt_text: "Box example" do
24
+ # bubble do
25
+ # body do
26
+ # layout :horizontal
27
+ # spacing :md
28
+ # text "Item 1"
29
+ # text "Item 2"
30
30
  # end
31
31
  # end
32
32
  # end
@@ -135,9 +135,21 @@ module Line
135
135
  #
136
136
  # @param text [String] The text content.
137
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).
138
+ # @param block [Proc, nil] An optional block for the text component. This can be used
139
+ # to define an action for the text or to add {Span} components within the text.
139
140
  # @return [Flex::Text] The newly created Text object.
140
- def text(text, **options, &)
141
+ # @example Simple text component
142
+ # text "Hello, World!"
143
+ # @example Text with an action
144
+ # text "Click me" do
145
+ # message "Action", text: "You clicked me!"
146
+ # end
147
+ # @example Text with spans
148
+ # text "This has " do
149
+ # span "styled", color: "#FF0000"
150
+ # span " parts"
151
+ # end
152
+ def text(text = nil, **options, &)
141
153
  @contents << Flex::Text.new(text, context: context, **options, &)
142
154
  end
143
155
 
@@ -160,6 +172,18 @@ module Line
160
172
  @contents << Flex::Image.new(url, context: context, **options, &)
161
173
  end
162
174
 
175
+ # Adds a Flex {Separator} component to this box's contents.
176
+ #
177
+ # A separator is a simple component that draws a horizontal line,
178
+ # creating a visual division between other components in a container.
179
+ #
180
+ # @param options [Hash] Options for the separator component. See {Separator}.
181
+ # @param block [Proc, nil] An optional block for advanced separator configuration.
182
+ # @return [Flex::Separator] The newly created Separator object.
183
+ def separator(**options, &)
184
+ @contents << Flex::Separator.new(context: context, **options, &)
185
+ end
186
+
163
187
  def to_h
164
188
  raise RequiredError, "layout is required" if layout.nil?
165
189
 
@@ -11,11 +11,11 @@ module Line
11
11
  # for each item in a {Carousel} container.
12
12
  #
13
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!"
14
+ # Line::Message::Builder.with do
15
+ # flex alt_text: "Simple Bubble" do
16
+ # bubble do
17
+ # body do
18
+ # text "Hello, this is a bubble!"
19
19
  # end
20
20
  # end
21
21
  # end
@@ -14,16 +14,16 @@ module Line
14
14
  # It can also have a `quickReply` attached to it.
15
15
  #
16
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
17
+ # Line::Message::Builder.with do
18
+ # flex alt_text: "My Product" do
19
+ # flex_builder.bubble size: :giga do
20
+ # hero_image "https://example.com/product.jpg"
21
+ # body do
22
+ # text "Product Name", weight: :bold, size: :xl
23
23
  # end
24
24
  # end
25
- # flex_builder.quick_reply do |qr|
26
- # qr.button action: :message, label: "Learn More", text: "Tell me more"
25
+ # quick_reply do
26
+ # button action: :message, label: "Learn More", text: "Tell me more"
27
27
  # end
28
28
  # end
29
29
  # end
@@ -14,12 +14,12 @@ module Line
14
14
  # An action is mandatory for a button component.
15
15
  #
16
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
17
+ # Line::Message::Builder.with do
18
+ # flex alt_text: "Button Example" do
19
+ # bubble do
20
+ # body do
21
+ # button style: :primary, height: :sm do
22
+ # message "Buy Now", label: "Buy" # Action definition
23
23
  # end
24
24
  # end
25
25
  # end
@@ -13,16 +13,16 @@ module Line
13
13
  # articles, or options, in a compact and interactive way.
14
14
  #
15
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" }
16
+ # Line::Message::Builder.with do
17
+ # flex alt_text: "Product Showcase" do
18
+ # carousel do
19
+ # bubble size: :mega do
20
+ # hero_image "https://example.com/product1.jpg"
21
+ # body { text "Product 1" }
22
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" }
23
+ # bubble size: :mega do
24
+ # hero_image "https://example.com/product2.jpg"
25
+ # body { text "Product 2" }
26
26
  # end
27
27
  # end
28
28
  # end
@@ -13,15 +13,14 @@ module Line
13
13
  # {Actionable#action action} to make it tappable.
14
14
  #
15
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"
16
+ # Line::Message::Builder.with do
17
+ # flex alt_text: "Image Example" do
18
+ # bubble do
19
+ # body do
20
+ # image "https://example.com/image.png",
21
+ # aspect_ratio: "16:9",
22
+ # aspect_mode: :cover,
23
+ # size: :full
25
24
  # end
26
25
  # end
27
26
  # end
@@ -13,19 +13,24 @@ module Line
13
13
  # @example Defining and using a partial
14
14
  # # Define a reusable partial
15
15
  # class MyButtonPartial < Line::Message::Builder::Flex::Partial
16
- # def call(label:, data:)
16
+ # def call
17
17
  # # `button` method is available from the context (e.g., a Box)
18
- # button style: :primary do |btn|
19
- # btn.postback data, label: label
18
+ # button style: :primary do
19
+ # # `data` and `label` are available from `context.assigns`
20
+ # postback data, label: label
20
21
  # end
21
22
  # end
22
23
  # end
23
24
  #
24
25
  # # 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 ...
26
+ # Line::Message::Builder.with do
27
+ # flex do
28
+ # bubble do
29
+ # body do
30
+ # partial! MyButtonPartial, label: "Action", data: "action=do_something"
31
+ # end
32
+ # end
33
+ # end
29
34
  # end
30
35
  #
31
36
  # @see Partial
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module Builder
6
+ module Flex
7
+ # Represents a "separator" component in a LINE Flex Message.
8
+ #
9
+ # Separator components are used to create a visual separation between components
10
+ # within a container. They draw a horizontal line that helps organize the layout
11
+ # and improve readability.
12
+ #
13
+ # @example Creating a separator within a box
14
+ # Line::Message::Builder.with do
15
+ # flex alt_text: "Separator Example" do
16
+ # bubble do
17
+ # body do
18
+ # text "Section 1"
19
+ # separator
20
+ # text "Section 2"
21
+ # end
22
+ # end
23
+ # end
24
+ # end
25
+ #
26
+ # @see https://developers.line.biz/en/reference/messaging-api/#separator
27
+ class Separator < Line::Message::Builder::Base
28
+ # Converts the separator component to a hash representation compatible with
29
+ # the LINE Messaging API.
30
+ #
31
+ # @return [Hash] A hash containing the separator component's type.
32
+ def to_h
33
+ {
34
+ type: "separator"
35
+ }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module Builder
6
+ module Flex
7
+ # Represents a "span" component in a LINE Flex Message.
8
+ #
9
+ # Span components are used within a Text component to apply different styling
10
+ # to specific portions of text. They offer various styling options, including
11
+ # `size`, `weight`, `color`, and `decoration`. Unlike Text components, spans
12
+ # cannot have actions attached to them.
13
+ #
14
+ # @example Creating a text component with spans
15
+ # Line::Message::Builder.with do
16
+ # flex alt_text: "Span Example" do
17
+ # bubble do
18
+ # body do
19
+ # text do
20
+ # span "Hello, ", color: "#FF0000"
21
+ # span "Flex ", weight: :bold
22
+ # span "World!", size: :xl, decoration: :underline
23
+ # end
24
+ # end
25
+ # end
26
+ # end
27
+ # end
28
+ #
29
+ # @see https://developers.line.biz/en/reference/messaging-api/#span
30
+ # @see Size::Shared For common `size` keywords (e.g., `:xl`, `:sm`).
31
+ class Span < Line::Message::Builder::Base
32
+ include Size::Shared # Adds `size` option (e.g., :sm, :md, :xl).
33
+
34
+ # @!attribute [r] text
35
+ # @return [String] The actual text content to be displayed.
36
+ # This is a required attribute.
37
+ attr_reader :text
38
+
39
+ # Specifies the color of the text.
40
+ # @!method color(value)
41
+ # @param value [String, nil] Hexadecimal color code (e.g., `"#RRGGBB"`, `"#RRGGBBAA"`).
42
+ # @return [String, nil] The current text color.
43
+ option :color, default: nil
44
+
45
+ # Specifies the weight of the text.
46
+ # @!method weight(value)
47
+ # @param value [Symbol, String, nil] Text weight. Valid values are `:regular` and `:bold`.
48
+ # @return [Symbol, String, nil] The current text weight.
49
+ option :weight, default: nil, validator: Validators::Enum.new(:regular, :bold)
50
+
51
+ # Specifies the decoration of the text.
52
+ # @!method decoration(value)
53
+ # @param value [Symbol, String, nil] Text decoration.
54
+ # Valid values are `:none`, `:underline`, and `:line-through`.
55
+ # @return [Symbol, String, nil] The current text decoration.
56
+ option :decoration, default: nil, validator: Validators::Enum.new(:none, :underline, :"line-through")
57
+
58
+ # Initializes a new Flex Message Span component.
59
+ #
60
+ # @param text_content [String] The text to display. This is required.
61
+ # @param context [Object, nil] An optional context for the builder.
62
+ # @param options [Hash] A hash of options to set instance variables
63
+ # (e.g., `:color`, `:weight`, `:decoration`, and options from included modules).
64
+ # @param block [Proc, nil] An optional block, typically not used for spans.
65
+ # @raise [ArgumentError] if `text_content` is nil (though the more specific
66
+ # `RequiredError` is raised in `to_h`).
67
+ def initialize(text_content, context: nil, **options, &)
68
+ @text = text_content # The text content is mandatory.
69
+
70
+ super(context: context, **options, &) # Sets options and evals block
71
+ end
72
+
73
+ # Sets weight to bold.
74
+ #
75
+ # @return [Symbol] Returns `:bold`.
76
+ def bold!
77
+ weight(:bold)
78
+ end
79
+
80
+ # Sets decoration to underline.
81
+ #
82
+ # @return [Symbol] Returns `:underline`.
83
+ def underline!
84
+ decoration(:underline)
85
+ end
86
+
87
+ # Sets decoration to line-through.
88
+ #
89
+ # @return [Symbol] Returns `:line-through`.
90
+ def line_through!
91
+ decoration(:"line-through")
92
+ end
93
+
94
+ def to_h
95
+ raise RequiredError, "text content is required for a span component" if text.nil?
96
+
97
+ {
98
+ type: "span",
99
+ text: text,
100
+ color: color,
101
+ size: size,
102
+ weight: weight,
103
+ decoration: decoration
104
+ }.compact
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -12,16 +12,35 @@ module Line
12
12
  # `line_spacing`, and more. A text component can also have an
13
13
  # {Actionable#action action} to make it tappable.
14
14
  #
15
+ # Text components also support embedded {Span} components, which allow parts of
16
+ # the text to have different styling. Spans can be added to a text component
17
+ # using the {#span} method within the Text component block.
18
+ #
15
19
  # @example Creating a text component within a box
16
- # Line::Message::Builder.with do |root|
17
- # root.flex alt_text: "Text Example" do |flex|
18
- # flex.bubble do |bubble|
19
- # bubble.body do |body_box|
20
- # body_box.text "Hello, Flex World!",
21
- # size: :xl,
22
- # color: "#FF0000",
23
- # wrap: true do |txt_action|
24
- # txt_action.message "More info", text: "Tell me more about text"
20
+ # Line::Message::Builder.with do
21
+ # flex alt_text: "Text Example" do
22
+ # bubble do
23
+ # body do
24
+ # text "Hello, Flex World!",
25
+ # size: :xl,
26
+ # color: "#FF0000",
27
+ # wrap: true do
28
+ # message "More info", text: "Tell me more about text"
29
+ # end
30
+ # end
31
+ # end
32
+ # end
33
+ # end
34
+ #
35
+ # @example Creating a text component with spans
36
+ # Line::Message::Builder.with do
37
+ # flex alt_text: "Span Example" do
38
+ # bubble do
39
+ # body do
40
+ # text "This message has styled spans:" do
41
+ # span "Red", color: "#FF0000"
42
+ # span " and ", size: :sm
43
+ # span "Bold", weight: :bold
25
44
  # end
26
45
  # end
27
46
  # end
@@ -50,7 +69,7 @@ module Line
50
69
  # @!attribute [r] text
51
70
  # @return [String] The actual text content to be displayed.
52
71
  # This is a required attribute.
53
- attr_reader :text
72
+ attr_reader :text, :contents
54
73
 
55
74
  # Specifies whether the text should wrap or be truncated if it exceeds
56
75
  # the component's width.
@@ -82,8 +101,9 @@ module Line
82
101
  # {Actionable#action action} for the text.
83
102
  # @raise [ArgumentError] if `text_content` is nil (though the more specific
84
103
  # `RequiredError` is raised in `to_h`).
85
- def initialize(text_content, context: nil, **options, &)
104
+ def initialize(text_content = nil, context: nil, **options, &)
86
105
  @text = text_content # The text content is mandatory.
106
+ @contents = [] # Initialize contents for spans, if any.
87
107
 
88
108
  super(context: context, **options, &) # Sets options and evals block (for action).
89
109
  end
@@ -99,8 +119,30 @@ module Line
99
119
  wrap(true) # Use the setter generated by `option`
100
120
  end
101
121
 
122
+ # Adds a {Span} component to this text component's contents.
123
+ #
124
+ # Spans allow different styling for parts of the text, such as color,
125
+ # weight, size, and decoration.
126
+ #
127
+ # @param text_content [String] The span text content.
128
+ # @param options [Hash] Options for the span. See {Span#initialize}.
129
+ # @param block [Proc, nil] An optional block for advanced span configuration.
130
+ # @return [Flex::Span] The newly created Span object.
131
+ # @example
132
+ # text "Message with spans:" do
133
+ # span "Important", color: "#FF0000", weight: :bold
134
+ # span " normal text"
135
+ # end
136
+ def span(text_content, **options, &)
137
+ @contents << Span.new(text_content, context: @context, **options, &)
138
+ end
139
+
140
+ def any_content?
141
+ !contents.empty? || !text.nil?
142
+ end
143
+
102
144
  def to_h
103
- raise RequiredError, "text content is required for a text component" if text.nil?
145
+ raise RequiredError, "text content is required for a text component" unless any_content?
104
146
 
105
147
  return to_sdkv2 if context.sdkv2?
106
148
 
@@ -113,6 +155,7 @@ module Line
113
155
  {
114
156
  type: "text",
115
157
  text: text,
158
+ contents: contents.map(&:to_h), # Convert spans to hash
116
159
  wrap: wrap, # From option
117
160
  color: color, # From option
118
161
  lineSpacing: line_spacing, # From option (maps to API key)
@@ -141,6 +184,7 @@ module Line
141
184
  {
142
185
  type: "text",
143
186
  text: text,
187
+ contents: contents.map(&:to_h), # Convert spans to hash
144
188
  wrap: wrap, # From option
145
189
  color: color, # From option
146
190
  line_spacing: line_spacing, # From option (maps to API key)
@@ -26,10 +26,10 @@ module Line
26
26
  # instantiates and uses {Flex::Builder} to define the Flex Message structure.
27
27
  #
28
28
  # @example How a Flex Message is typically initiated (conceptual)
29
- # Line::Message::Builder.with do |root_container|
29
+ # Line::Message::Builder.with do
30
30
  # # This `flex` call on root_container would utilize Flex::Builder
31
- # root_container.flex(alt_text: "My Flex Message") do |flex_msg_builder|
32
- # flex_msg_builder.bubble do |bubble_builder|
31
+ # flex alt_text: "My Flex Message" do
32
+ # bubble do
33
33
  # # ... define bubble content ...
34
34
  # end
35
35
  # end
@@ -64,6 +64,8 @@ module Line
64
64
  require_relative "flex/text" # Text display
65
65
  require_relative "flex/button" # Interactive button
66
66
  require_relative "flex/image" # Image display
67
+ require_relative "flex/separator" # Visual separator
68
+ require_relative "flex/span" # Text span for styling parts of text
67
69
  end
68
70
  end
69
71
  end
@@ -3,7 +3,7 @@
3
3
  module Line
4
4
  module Message
5
5
  module Builder
6
- VERSION = "0.8.0"
6
+ VERSION = "0.9.0"
7
7
  end
8
8
  end
9
9
  end
@@ -48,8 +48,8 @@ module Line
48
48
  # instance, allowing you to define the message structure using the DSL.
49
49
  # @return [Container] The initialized message container with the defined structure.
50
50
  # @example
51
- # message = Line::Message::Builder.with do |root|
52
- # root.text "Hello, world!"
51
+ # message = Line::Message::Builder.with do
52
+ # text "Hello, world!"
53
53
  # end
54
54
  def with(context = nil, mode: :api, &)
55
55
  Container.new(context: context, mode: mode, &)
@@ -49,6 +49,7 @@ module Line
49
49
  end || @expected.call(content)
50
50
  end
51
51
 
52
+ # Directly check if this is the component we're looking for
52
53
  @expected.call(content)
53
54
  end
54
55
 
@@ -114,6 +115,16 @@ module Line
114
115
  ::RSpec::Matchers::BuiltIn::Include.new({ "url" => url, **options }).matches?(content)
115
116
  end
116
117
  end
118
+
119
+ def have_line_flex_span(text, **options) # rubocop:disable Naming/PredicateName
120
+ options = Utils.stringify_keys!(options, deep: true)
121
+
122
+ HaveFlexComponent.new(expected_desc: "span(#{text.inspect})") do |content|
123
+ next false unless content["type"] == "span"
124
+
125
+ content["text"].match?(text) && ::RSpec::Matchers::BuiltIn::Include.new(options).matches?(content)
126
+ end
127
+ end
117
128
  end
118
129
  end
119
130
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module RSpec
6
+ # :nodoc:
7
+ module Matchers
8
+ def have_line_flex_separator(**options) # rubocop:disable Naming/PredicateName
9
+ options = Utils.stringify_keys!(options, deep: true)
10
+
11
+ HaveFlexComponent.new(expected_desc: "separator(#{options.inspect})") do |content|
12
+ next false unless content["type"] == "separator"
13
+
14
+ options.empty? || ::RSpec::Matchers::BuiltIn::Include.new(options).matches?(content)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -6,3 +6,4 @@ require_relative "matchers/have_quick_reply"
6
6
  require_relative "matchers/have_flex_message"
7
7
  require_relative "matchers/have_flex_bubble"
8
8
  require_relative "matchers/have_flex_component"
9
+ require_relative "matchers/have_flex_separator"
data/llm.txt CHANGED
@@ -18,6 +18,42 @@ end
18
18
  # json_output = messages.to_json
19
19
  ```
20
20
 
21
+ ### Coding Style
22
+
23
+ When using the `Line::Message::Builder` DSL, following is recommended:
24
+
25
+ ```ruby
26
+ Line::Message::Builder.with do
27
+ flex alt_text: "Hello, World!" do
28
+ bubble do
29
+ header do
30
+ text "Welcome to LINE Messaging API"
31
+ end
32
+ body do
33
+ text "This is a sample message."
34
+ end
35
+ end
36
+ end
37
+ end
38
+ ```
39
+
40
+ DO NOT use `do |container|` syntax as following:
41
+
42
+ ```ruby
43
+ Line::Message::Builder.with do |builder|
44
+ builder.flex alt_text: "Hello, World!" do
45
+ builder.bubble do
46
+ builder.header do
47
+ builder.text "Welcome to LINE Messaging API"
48
+ end
49
+ builder.body do
50
+ builder.text "This is a sample message."
51
+ end
52
+ end
53
+ end
54
+ end
55
+ ```
56
+
21
57
  ### Context
22
58
 
23
59
  The builder can accept a context object. Methods called within the builder block will first attempt to resolve against this context object. This allows for dynamic message content based on your application's data.
@@ -258,9 +294,18 @@ Displays text.
258
294
  - `flex`: (Integer, Optional) Flex factor.
259
295
  - `adjust_mode`: (Symbol, Optional) How text adjusts when it overflows. Valid value: `:shrink_to_fit` (reduces font size).
260
296
  - `action`: (Action Object, Optional) Makes the text tappable.
297
+ - `span`: Within a Text component, you can add Span components to style parts of the text differently.
261
298
 
262
299
  ```ruby
300
+ # Simple text component
263
301
  text "Special Offer!", size: :xl, weight: :bold, color: "#FF0000", align: :center, action: postback(data: "offer_details")
302
+
303
+ # Text with spans for different styling of segments
304
+ text "Welcome to our service:" do
305
+ span "Special ", color: "#FF0000"
306
+ span "Offer", weight: :bold
307
+ span "!", decoration: :underline
308
+ end
264
309
  ```
265
310
 
266
311
  #### Button Component
@@ -298,23 +343,59 @@ Displays an image.
298
343
  image "https://example.com/product_image.png", size: :full, aspect_ratio: "1:1", aspect_mode: :cover, action: message(text: "View product")
299
344
  ```
300
345
 
301
- #### Separators and Spacers
346
+ #### Span Component
347
+ Spans are used within a Text component to apply different styling to specific portions of text. They cannot be used directly within boxes or other containers; they must be placed inside a Text component block.
348
+
349
+ - `text`: (String, Required) The text content of the span.
350
+ - Styling:
351
+ - `color`: (String, Optional) Hex color code (e.g., `'#RRGGBB'`, `'#RRGGBBAA'`).
352
+ - `size`: (Symbol or String, Optional) Font size. Valid keywords: `:xxs`, `:xs`, `:sm`, `:md`, `:lg`, `:xl`, `:xxl`, `:3xl`, `:4xl`, `:5xl`. Also accepts pixel values (e.g., `'16px'`).
353
+ - `weight`: (Symbol, Optional) Font weight. Valid values: `:regular`, `:bold`.
354
+ - `decoration`: (Symbol, Optional) Text decoration. Valid values: `:none`, `:underline`, `:line-through`.
355
+
356
+ Spans also support helper methods to easily apply common styles:
357
+ - `bold!`: Sets the weight to `:bold`.
358
+ - `underline!`: Sets the decoration to `:underline`.
359
+ - `line_through!`: Sets the decoration to `:line-through`.
302
360
 
303
- The LINE Flex Message specification includes `separator` and `spacer` component types. This DSL does not provide explicit `separator()` or `spacer()` methods. Instead:
361
+ ```ruby
362
+ # Basic span usage
363
+ text "This message contains:" do
364
+ span "colored", color: "#FF0000"
365
+ span " and "
366
+ span "bold", weight: :bold
367
+ span " text."
368
+ end
304
369
 
305
- - **Separators**: Achieve a visual line by using a `box` component styled to look like a separator. Set its `height` (for horizontal line) or `width` (for vertical line) to a small value (e.g., `"1px"`) and give it a `background_color`.
306
- ```ruby
307
- # Example of a horizontal separator
308
- box layout: :vertical, padding_all: :md do # Outer box for content
309
- text "Content above separator"
310
- box height: "1px", background_color: "#CCCCCC", margin: :md # This is the separator
311
- text "Content below separator"
370
+ # Using helper methods
371
+ text "This message has:" do
372
+ span "underlined" do
373
+ underline!
312
374
  end
313
- ```
314
- - **Spacers**: Create space between components using:
315
- - `spacing` property on a parent `box` container.
316
- - `margin` property on individual components (e.g., `text "Hello", margin: :xl`).
317
- - An empty `box` with a defined `flex` value or `height`/`width` (e.g., `box height: "30px"`).
375
+ span " and "
376
+ span "bold" do
377
+ bold!
378
+ end
379
+ span " text."
380
+ end
381
+ ```
382
+
383
+ #### Separator Component
384
+ A `separator` draws a horizontal line to create visual separation between components.
385
+
386
+ ```ruby
387
+ box layout: :vertical do
388
+ text "Section 1"
389
+ separator
390
+ text "Section 2"
391
+ end
392
+ ```
393
+
394
+ #### Spacers
395
+ Create space between components using:
396
+ - `spacing` property on a parent `box` container.
397
+ - `margin` property on individual components (e.g., `text "Hello", margin: :xl`).
398
+ - An empty `box` with a defined `flex` value or `height`/`width` (e.g., `box height: "30px"`).
318
399
 
319
400
  ### Flex Message Partials
320
401
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: line-message-builder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aotokitsuruya
@@ -42,7 +42,9 @@ files:
42
42
  - lib/line/message/builder/flex/image.rb
43
43
  - lib/line/message/builder/flex/partial.rb
44
44
  - lib/line/message/builder/flex/position.rb
45
+ - lib/line/message/builder/flex/separator.rb
45
46
  - lib/line/message/builder/flex/size.rb
47
+ - lib/line/message/builder/flex/span.rb
46
48
  - lib/line/message/builder/flex/text.rb
47
49
  - lib/line/message/builder/quick_reply.rb
48
50
  - lib/line/message/builder/text.rb
@@ -55,6 +57,7 @@ files:
55
57
  - lib/line/message/rspec/matchers/have_flex_bubble.rb
56
58
  - lib/line/message/rspec/matchers/have_flex_component.rb
57
59
  - lib/line/message/rspec/matchers/have_flex_message.rb
60
+ - lib/line/message/rspec/matchers/have_flex_separator.rb
58
61
  - lib/line/message/rspec/matchers/have_quick_reply.rb
59
62
  - lib/line/message/rspec/matchers/have_text_message.rb
60
63
  - lib/line/message/rspec/matchers/utils.rb