Module: Chats::EngineHelper
- Defined in:
- app/helpers/chats/engine_helper.rb
Overview
View helpers, available BOTH inside the engine’s own views and in the HOST app’s views (mixed into ActionView via the engine’s on_load hook, the same pattern the moderate gem uses for ‘report_link`).
Instance Method Summary collapse
-
#chat_button_to(other, about: nil, label: nil, **html_options) ⇒ Object
The “message this person” affordance for host pages — a listing, a profile, an order.
-
#chats_conversation_avatar(conversation, viewer) ⇒ Object
The avatar shown on an inbox row: the counterpart’s (direct) or an initials disc from the group name.
-
#chats_messager_avatar(messager, css_class: "chats-avatar", loading: "eager") ⇒ Object
An avatar for any messager: whatever ‘config.messager_avatar` returns (URL / ActiveStorage attachment / variant) or an initials placeholder.
-
#chats_preview_for(conversation, viewer) ⇒ Object
The inbox-row preview line for a conversation’s latest message.
-
#chats_routes ⇒ Object
Engine URL helpers that work from EVERY render context — this is more subtle than it looks, and the reason the broadcast partials use it:.
-
#chats_styles ⇒ Object
The gem’s bundled stylesheet (CSS-variable themed — see chats.css).
-
#chats_timestamp(time) ⇒ Object
WhatsApp-style compact timestamps, deliberately numeric so they need no date-name translations (many apps don’t bundle rails-i18n; the gem must not require it): today → “14:05”, this week-ish → “9/6”, older → “9/6/25”.
-
#chats_unread_badge(messager = chats_viewer) ⇒ Object
A live unread-conversations badge, embeddable on ANY page (nav bars, tab docks).
Instance Method Details
#chat_button_to(other, about: nil, label: nil, **html_options) ⇒ Object
The “message this person” affordance for host pages — a listing, a profile, an order. Renders nothing when there’s no viewer, the viewer IS the target, or policy/blocks forbid the pair, so it’s always safe to drop into a page unconditionally:
<%= chat_button_to @driver, about: @ride, label: "Chat with driver" %>
The recipient/subject travel as SIGNED GlobalIDs (purpose-scoped, minted here, verified in Chats::ConversationsController#create), so the endpoint never trusts raw polymorphic params. ‘expires_in: nil` because these buttons sit on long-lived pages — the default 1-month sgid expiry would quietly break stale tabs.
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'app/helpers/chats/engine_helper.rb', line 20 def (other, about: nil, label: nil, **) viewer = chats_viewer return if viewer.nil? || other.nil? || viewer == other return unless Chats.(viewer, other) params = { recipient_sgid: other.to_sgid(expires_in: nil, for: :chats_recipient).to_s } params[:subject_sgid] = about.to_sgid(expires_in: nil, for: :chats_subject).to_s if about ( label || I18n.t("chats.buttons.chat"), chats_routes.conversations_path, params: params, method: :post, ** ) end |
#chats_conversation_avatar(conversation, viewer) ⇒ Object
The avatar shown on an inbox row: the counterpart’s (direct) or an initials disc from the group name.
118 119 120 121 122 123 124 125 126 127 |
# File 'app/helpers/chats/engine_helper.rb', line 118 def chats_conversation_avatar(conversation, viewer) if conversation.direct? other = conversation.other_participants(viewer).first (other&.) else initials = conversation.title_for(viewer).split.first(2).map { |word| word[0] }.join.upcase tag.span(initials.presence || "👥", class: "chats-avatar chats-avatar--initials chats-avatar--group", "aria-hidden": true) end end |
#chats_messager_avatar(messager, css_class: "chats-avatar", loading: "eager") ⇒ Object
An avatar for any messager: whatever ‘config.messager_avatar` returns (URL / ActiveStorage attachment / variant) or an initials placeholder.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'app/helpers/chats/engine_helper.rb', line 52 def (, css_class: "chats-avatar", loading: "eager") name = Chats.display_name_for().presence || "?" source = begin avatar = Chats.avatar_for() # An ActiveStorage attachment that isn't attached renders as a broken # image — treat it as "no avatar" instead. avatar.respond_to?(:attached?) && !avatar.attached? ? nil : avatar end if source image_tag chats_avatar_image_source(source), alt: name, class: css_class, loading: loading else initials = name.split.first(2).map { |word| word[0] }.join.upcase tag.span(initials, class: "#{css_class} chats-avatar--initials", "aria-hidden": true) end end |
#chats_preview_for(conversation, viewer) ⇒ Object
The inbox-row preview line for a conversation’s latest message.
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'app/helpers/chats/engine_helper.rb', line 86 def chats_preview_for(conversation, viewer) = conversation. return I18n.t("chats.inbox.no_messages") if .nil? text = if .deleted? I18n.t("chats.message.deleted") elsif .body.present? .body.truncate(90) elsif . "📷 #{I18n.t("chats.message.attachment")}" else "" end # Tombstones read as a statement ("Message deleted") — never prefixed. # Otherwise: "You:" for own messages, and in GROUPS the sender's first # name (WhatsApp-style), since "who said it" is ambiguous there. System # messages (no sender) stay bare. prefix = if .deleted? nil elsif .sent_by?(viewer) I18n.t("chats.inbox.you_prefix") elsif conversation.group? && .sender "#{Chats.display_name_for(.sender).split.first}:" end [prefix, text].compact.join(" ") end |
#chats_routes ⇒ Object
Engine URL helpers that work from EVERY render context — this is more subtle than it looks, and the reason the broadcast partials use it:
* host views & the broadcast renderer (Turbo broadcasts render through
the host's ApplicationController renderer — no engine request, no
SCRIPT_NAME): the mounted proxy (`chats.`) carries the mount prefix
baked in at mount time, so URLs come out right with no request.
* engine views during requests: the engine controller inherits from
the host's ApplicationController, so the proxy is available there
too (and bare helpers would also work — the proxy just works
everywhere).
* no mount at all (bare view tests): fall back to the engine's own
url_helpers (prefix-less, but nothing better exists without a mount).
NOTE: assumes the default mount name (‘mount Chats::Engine => “/x”` auto-names the proxy `chats`). Hosts using `as: :something_else` should override this helper.
153 154 155 |
# File 'app/helpers/chats/engine_helper.rb', line 153 def chats_routes respond_to?(:chats) ? chats : Chats::Engine.routes.url_helpers end |
#chats_styles ⇒ Object
The gem’s bundled stylesheet (CSS-variable themed — see chats.css). Called from the engine’s own views; hosts that eject + restyle the views with their own framework simply don’t include it.
132 133 134 |
# File 'app/helpers/chats/engine_helper.rb', line 132 def chats_styles stylesheet_link_tag "chats", "data-turbo-track": "reload" end |
#chats_timestamp(time) ⇒ Object
WhatsApp-style compact timestamps, deliberately numeric so they need no date-name translations (many apps don’t bundle rails-i18n; the gem must not require it): today → “14:05”, this week-ish → “9/6”, older → “9/6/25”.
72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'app/helpers/chats/engine_helper.rb', line 72 def (time) return "" if time.nil? local = time.in_time_zone if local.today? local.strftime("%H:%M") elsif local.year == Time.current.year local.strftime("%-d/%-m") else local.strftime("%-d/%-m/%y") end end |
#chats_unread_badge(messager = chats_viewer) ⇒ Object
A live unread-conversations badge, embeddable on ANY page (nav bars, tab docks). Subscribes to the messager’s badge stream so it updates in real time without refreshing the page it sits on (see Chats::Broadcasts for why badges get their own stream).
41 42 43 44 45 46 47 48 |
# File 'app/helpers/chats/engine_helper.rb', line 41 def chats_unread_badge( = chats_viewer) return if .nil? safe_join([ turbo_stream_from(, :chats_badge), render(partial: "chats/shared/unread_badge", locals: { count: .unread_chats_count }) ]) end |