Files
once-campfire/app/helpers/messages_helper.rb
T

159 lines
6.4 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
module MessagesHelper
# auto_link re-sanitizes with Rails' default safe list, which lacks the
# formatting the rich text editor produces
AUTO_LINK_ALLOWED_TAGS = Rails::HTML5::SafeListSanitizer.allowed_tags + ContentFilters::EDITOR_FORMATTING_TAGS
AUTO_LINK_ALLOWED_ATTRIBUTES = Rails::HTML5::SafeListSanitizer.allowed_attributes + ContentFilters::EDITOR_FORMATTING_ATTRIBUTES
def message_area_tag(room, &)
tag.div id: "message-area", class: "message-area", contents: true, data: {
controller: "messages presence drop-target",
action: [ messages_actions, drop_target_actions, presence_actions ].join(" "),
messages_first_of_day_class: "message--first-of-day",
messages_formatted_class: "message--formatted",
messages_me_class: "message--me",
messages_mentioned_class: "message--mentioned",
messages_threaded_class: "message--threaded",
messages_page_url_value: room_messages_url(room)
}, &
end
def messages_tag(room, &)
tag.div id: dom_id(room, :messages), class: "messages", data: {
controller: "maintain-scroll refresh-room",
action: [ maintain_scroll_actions, refresh_room_actions ].join(" "),
messages_target: "messages",
refresh_room_loaded_at_value: room.updated_at.to_fs(:epoch),
refresh_room_url_value: room_refresh_url(room)
}, &
end
def message_tag(message, &)
message_timestamp_milliseconds = message.created_at.to_fs(:epoch)
tag.div id: dom_id(message),
class: "message #{"message--emoji" if message.plain_text_body.all_emoji?}",
data: {
controller: "reply",
user_id: message.creator_id,
message_id: message.id,
client_message_id: message.client_message_id,
message_timestamp: message_timestamp_milliseconds,
message_updated_at: message.updated_at.to_fs(:epoch),
sort_value: message_timestamp_milliseconds,
messages_target: "message",
search_results_target: "message",
refresh_room_target: "message",
reply_composer_outlet: "#composer"
}, &
rescue Exception => e
Sentry.capture_exception(e, extra: { message: message })
Rails.logger.error "Exception while rendering message #{message.class.name}##{message.id}, failed with: #{e.class} `#{e.message}`"
render "messages/unrenderable"
end
def message_timestamp(message, **attributes)
local_datetime_tag message.created_at, **attributes
end
def message_presentation(message)
case message.content_type
when "attachment"
message_attachment_presentation(message)
when "sound"
message_sound_presentation(message)
else
text_message_presentation(message.body.body)
end
rescue Exception => e
Sentry.capture_exception(e, extra: { message: message })
Rails.logger.error "Exception while generating message representation for #{message.class.name}##{message.id}, failed with: #{e.class} `#{e.message}`"
""
end
# These controls contain URLs and static markup, but no viewer or token state.
# Keep them across database commits; attachment controls still render afresh.
def cache_message_actions(message, &block)
return capture(&block) unless controller.perform_caching && !message.attachment?
key = fragment_name_with_digest([
"message-actions-v1", request.base_url, request.script_name, I18n.locale, message.id, message.room_id
], nil)
FragmentCache.store.fetch(key) { capture(&block) }
end
def message_fragment_cache_key(message)
return message unless controller.perform_caching &&
%i[room creator rich_text_body boosts attachment_attachment].all? { |name| message.association(name).loaded? }
room = message.room
body = message.body.body
return message if room.direct? || message.attachment? || !body || body.to_html.include?("<action-text-attachment") ||
message.boosts.any? { |boost| !boost.association(:booster).loaded? }
# Collection caching preloads these rows before deriving keys. Read their
# actual content: foreign SQL writers need not touch Rails timestamps.
dependencies = [ message.attributes, room.attributes.slice("id", "type", "name"), message.creator.attributes,
body.to_html, message.boosts.sort_by(&:created_at).map { |boost| [ boost.attributes, boost.booster.attributes ] } ]
FragmentCache::ContentKey.new(Digest::SHA256.hexdigest(ActiveSupport::JSON.encode(dependencies)))
end
private
def text_message_presentation(body)
render = -> do
auto_link h(ContentFilters::TextMessagePresentationFilters.apply(body)),
html: { target: "_blank" }, sanitize_options: { tags: AUTO_LINK_ALLOWED_TAGS, attributes: AUTO_LINK_ALLOWED_ATTRIBUTES }
end
# Embedded attachments render database-backed metadata and signed URLs.
# Plain text HTML depends only on its content, even after a foreign edit.
html = body.to_html
if controller.perform_caching && !html.include?("<action-text-attachment") && html.bytesize <= ResponseCache::MAX_ENTRY_BYTES
FragmentCache.store.fetch([ "text-presentation-v1", I18n.locale, Digest::SHA256.hexdigest(html) ]) { render.call }
else
render.call
end
end
def messages_actions
"turbo:before-stream-render@document->messages#beforeStreamRender keydown.up@document->messages#editMyLastMessage"
end
def maintain_scroll_actions
"turbo:before-stream-render@document->maintain-scroll#beforeStreamRender"
end
def refresh_room_actions
"visibilitychange@document->refresh-room#visibilityChanged online@window->refresh-room#online"
end
def presence_actions
"visibilitychange@document->presence#visibilityChanged"
end
def message_attachment_presentation(message)
Messages::AttachmentPresentation.new(message, context: self).render
end
def message_sound_presentation(message)
sound = message.sound
tag.div class: "sound", data: { controller: "sound", action: "messages:play->sound#play", sound_url_value: asset_path(sound.asset_path) } do
play_button + (sound.image ? sound_image_tag(sound.image) : sound.text)
end
end
def play_button
tag.button "🔊", class: "btn btn--plain", data: { action: "sound#play" }
end
def sound_image_tag(image)
image_tag image.asset_path, width: image.width, height: image.height, class: "align--middle"
end
def message_author_title(author)
[ author.name, author.bio ].compact_blank.join(" – ")
end
end