bh 6.3.0 → 6.4.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: 8e8820e04b1b71fbfacf44696b6fec712f77987c217dbedf5c2314867600bf42
4
- data.tar.gz: 761251633d36f3e62dd92d9784c71d44a2306bc6c3358d24b3420f3347cebff6
3
+ metadata.gz: e56ebe3dc708d2c24faa0ce9419a73432bf56a2a89e30c4e43238c17a3212bfc
4
+ data.tar.gz: 787d7c455cded498c4e53ce624eca61981f7cc9a0522d9f743cf748bc95c09b6
5
5
  SHA512:
6
- metadata.gz: 84be37f63354fe89c52d6532cb55a5c2db53127a568c228d58acc53d7d3a0ff3cbb98cad224b7bdd76138a3a02ffcf2b3a338cacda2ec0f12427e26fd10db361
7
- data.tar.gz: bd2d39d1d0377f5e5730f90ad6400edf736b1cc281b63e2a491c8c6cf7bf922e3822a6f1525e5f7ae775f3ff48de3f089f2c526d9a09b992f6c53a875b9d9eb7
6
+ metadata.gz: 84fb271ca162d32df5eb64e4aeb9bff20752391e4c107a51259bcc9995431d5f6c4d0ff0160dfdc4da8deb080aed74197b0c3930f38e08ef2ccdb3984ff877d1
7
+ data.tar.gz: b7601d8739cdb4e21cb98d52aad08681a95f2366e19e7196028bfa8a9e30c47812826dee5a5a405f6862475474c8dd7f087f0a887051d9f0526d8acdf5ad6603
data/CHANGELOG.md CHANGED
@@ -8,6 +8,23 @@ For more information about changelogs, check
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## 6.4.0 - 2026-09-18
12
+
13
+ * [FEATURE] A message carries its `photos`, drawn in the bubble under its words
14
+
15
+ `Bh::Message` takes a list of addresses beside its text. Each is drawn under the words in
16
+ the same bubble, linking to itself in a tab of its own: what fits beside words is rarely
17
+ what somebody wants to look at. A message carrying pictures and no words is a message all
18
+ the same, rather than one still being typed.
19
+
20
+ * [FEATURE] `chat_with` takes a `note:`, said over the thread rather than in it
21
+
22
+ What the conversation is about, or the other way to reach whoever is on the far end of it.
23
+ Over rather than in, because nobody in the conversation said it and a bubble would claim
24
+ somebody had. It holds the top while the thread scrolls under it, and carries the page's
25
+ own background for that reason: a tint would read as somebody's turn, and without one the
26
+ bubbles would show through.
27
+
11
28
  ## 6.3.0 - 2026-09-18
12
29
 
13
30
  * [FEATURE] The nine palettes, served at `/bh/theme/<name>.css`
@@ -7,6 +7,33 @@
7
7
  overflows pushes its first message clean out of the top with no way to scroll back. */
8
8
  .chat > :first-child { margin-block-start: auto }
9
9
 
10
+ /* What the whole thread is about, or the other way to reach whoever is on the far end of
11
+ it: said over the conversation rather than in it, quiet and in the page's own words,
12
+ because nobody in the conversation said it. It holds the top while the thread scrolls
13
+ under it, which is why it carries the page's background and not a band of its own: a
14
+ tint here would read as somebody's turn, and bubbles would show through without one. */
15
+ .chat-note {
16
+ position: sticky;
17
+ inset-block-start: 0;
18
+ z-index: 1;
19
+ background-color: var(--bs-body-bg);
20
+ font-size: var(--bs-font-size-sm);
21
+ color: var(--bs-fg-2);
22
+ margin: 0;
23
+ padding-block: 0.25rem 0.75rem;
24
+ }
25
+
26
+ /* A picture a message carries, under its words and no wider than the bubble holding it. */
27
+ .chat-photo {
28
+ display: block;
29
+ max-width: 100%;
30
+ height: auto;
31
+ border-radius: 0.5rem;
32
+ margin-block-start: 0.5rem;
33
+ }
34
+ /* The first thing in a bubble has the bubble's own padding over it already. */
35
+ .chat-bubble > a:first-child .chat-photo { margin-block-start: 0 }
36
+
10
37
  /* Who said it and when, small, over the bubble it belongs to and on its side. */
11
38
  .chat-aside { font-size: var(--bs-font-size-sm); color: var(--bs-fg-2); margin: 0 0.75rem }
12
39
  .chat-aside-out { align-self: flex-end; margin-inline-end: 1.25rem }
@@ -8,6 +8,7 @@ en:
8
8
  more: '%{first} + %{count} more'
9
9
  no_results: Nothing matches that
10
10
  okay: Okay
11
+ photo: Photo
11
12
  search: Search
12
13
  send: Send
13
14
  typing: Typing
@@ -8,12 +8,16 @@ module Bh
8
8
  include Asides, Asks
9
9
 
10
10
  # `messages` as a thread that opens at its newest and, where `url` is given, a field
11
- # under it that posts the next message there, suggesting `hints` one at a time.
12
- def chat_with(messages:, url: nil, hints: [])
13
- parts = [bh_chat_messages(messages)]
11
+ # under it that posts the next message there, suggesting `hints` one at a time. A
12
+ # `note` stands over the whole thread: what this conversation is about, or the other
13
+ # way to reach whoever is on the far end of it.
14
+ def chat_with(messages:, url: nil, hints: [], note: nil)
15
+ parts = [bh_chat_note(note), bh_chat_messages(messages)]
14
16
  parts.push bh_chat_ask(url, hints) if url
15
17
 
16
- tag.div safe_join(parts, "\n"), class: 'chat-thread', data: { controller: 'thread' }
18
+ said = safe_join parts.compact, "\n"
19
+
20
+ tag.div said, class: 'chat-thread', data: { controller: 'thread' }
17
21
  end
18
22
 
19
23
  private
@@ -30,13 +34,36 @@ module Bh
30
34
  safe_join parts.compact, "\n"
31
35
  end
32
36
 
37
+ # Said over the thread rather than in it, so it is not one of the turns: nobody
38
+ # said this, and a bubble would claim somebody had.
39
+ def bh_chat_note(note)
40
+ tag.p note, class: 'chat-note' if note.present?
41
+ end
42
+
33
43
  def bh_chat_bubble(message)
34
44
  side = Asides::SIDES.fetch message.side
35
- return tag.p message.text, class: "chat-bubble chat-#{side}" if message.text
45
+ said = bh_chat_said message
46
+ return tag.p said, class: "chat-bubble chat-#{side}" if said
36
47
 
37
48
  tag.p safe_join(Array.new(3) { tag.span }), class: "chat-bubble chat-#{side} chat-typing",
38
49
  aria: { label: t('bh.typing') }
39
50
  end
51
+
52
+ # The words and then the pictures, which is the order a phone lays a picture
53
+ # message out in -- and nothing at all where a message has neither yet.
54
+ def bh_chat_said(message)
55
+ pictures = Array(message.photos).map { |photo| bh_chat_photo photo }
56
+
57
+ safe_join([message.text, *pictures].compact_blank, "\n").presence
58
+ end
59
+
60
+ # A picture in the bubble, opening full size in a tab of its own: what fits beside
61
+ # words is rarely what somebody wants to look at.
62
+ def bh_chat_photo(url)
63
+ tag.a href: url, target: :_blank, rel: 'noopener' do
64
+ tag.img src: url, alt: t('bh.photo'), class: 'chat-photo', loading: :lazy
65
+ end
66
+ end
40
67
  end
41
68
  end
42
69
  end
data/lib/bh/message.rb CHANGED
@@ -1,11 +1,14 @@
1
1
  module Bh
2
2
  # One message of a chat, as `chat_with` draws it: which `side` its bubble is on (`:left`
3
- # for the other party, `:right` for the reader), its `text`, who the `sender` was, when it
4
- # was said (`at`), how its `delivery` went (`:delivered`, `:sent`, `:failed`,
5
- # `:undelivered` or nil) and what it `cost` in dollars, where anything did. Any object
6
- # answering the same six is a message too; this is for a host that has the facts and no
7
- # model to hang them on. A message with no text yet is drawn as being typed.
8
- Message = Data.define :side, :text, :sender, :at, :delivery, :cost do
9
- def initialize(side:, text: nil, sender: nil, at: nil, delivery: nil, cost: nil) = super
3
+ # for the other party, `:right` for the reader), its `text`, the `photos` it carries as
4
+ # a list of addresses, who the `sender` was, when it was said (`at`), how its `delivery`
5
+ # went (`:delivered`, `:sent`, `:failed`, `:undelivered` or nil) and what it `cost` in
6
+ # dollars, where anything did. Any object answering the same seven is a message too;
7
+ # this is for a host that has the facts and no model to hang them on. A message with
8
+ # neither words nor pictures yet is drawn as being typed.
9
+ Message = Data.define :side, :text, :photos, :sender, :at, :delivery, :cost do
10
+ def initialize(side:, text: nil, photos: [], sender: nil, at: nil, delivery: nil, cost: nil)
11
+ super
12
+ end
10
13
  end
11
14
  end
data/lib/bh/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Bh
2
2
  # Version of the gem, read by the gemspec and by hosts checking compatibility.
3
- VERSION = '6.3.0'
3
+ VERSION = '6.4.0'
4
4
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bh6",
3
- "version": "6.3.0",
3
+ "version": "6.4.0",
4
4
  "private": true,
5
5
  "description": "Bootstrap 6 in a Rails app: the form builder that dresses every field, the components Bootstrap ships behavior for and no markup, and the Stimulus controllers behind them.",
6
6
  "license": "MIT",