mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-08-28 17:42:50 +09:00
Ensure backwards compatibility with Trix
This commit is contained in:
@@ -88,6 +88,25 @@ lexxy-toolbar {
|
||||
.lexxy-editor__toolbar-button.lexxy-editor__toolbar-group-end::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Dropdown panels: a solid surface with a uniform radius. Lexxy derives
|
||||
the panel's radius and gap from --lexxy-toolbar-gap, which Campfire
|
||||
repurposes for button spacing */
|
||||
.lexxy-editor__toolbar-dropdown [data-dropdown-panel] {
|
||||
--lexxy-color-canvas: var(--color-bg);
|
||||
--lexxy-color-ink-lightest: var(--color-message-bg);
|
||||
--lexxy-color-selected: var(--color-selected);
|
||||
--lexxy-color-selected-hover: var(--color-message-bg);
|
||||
|
||||
border: 1px solid var(--color-border-dark);
|
||||
border-radius: 0.5em;
|
||||
gap: 0.25em;
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
.lexxy-editor__toolbar-dropdown > .lexxy-editor__toolbar-button[aria-expanded="true"] {
|
||||
border-radius: var(--lexxy-radius);
|
||||
}
|
||||
}
|
||||
|
||||
/* Attachment markup contains formatting whitespace that must not turn into
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
class ContentFilters::RemoveSoloUnfurledLinkText < ActionText::Content::Filter
|
||||
TWITTER_DOMAINS = %w[ x.com twitter.com ]
|
||||
TWITTER_DOMAIN_MAPPING = { "x.com" => "twitter.com" }
|
||||
|
||||
def applicable?
|
||||
normalize_tweet_url(solo_unfurled_url) == normalize_tweet_url(content.to_plain_text)
|
||||
end
|
||||
@@ -12,9 +15,6 @@ class ContentFilters::RemoveSoloUnfurledLinkText < ActionText::Content::Filter
|
||||
end
|
||||
|
||||
private
|
||||
TWITTER_DOMAINS = %w[ x.com twitter.com ]
|
||||
TWITTER_DOMAIN_MAPPING = { "x.com" => "twitter.com" }
|
||||
|
||||
def solo_unfurled_url
|
||||
ActionText::Attachment::OpengraphEmbed.from_node(unfurled_links.first)&.href if unfurled_links.size == 1
|
||||
end
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
class ContentFilters::SanitizeTags < ActionText::Content::Filter
|
||||
ALLOWED_TAGS = %w[ a abbr acronym address b big blockquote br cite code dd del dfn div dl dt em h1 h2 h3 h4 h5 h6 hr i ins kbd li ol
|
||||
p pre samp small span strong sub sup time tt ul var ] + ContentFilters::EDITOR_FORMATTING_TAGS +
|
||||
[ ActionText::Attachment.tag_name, "figure", "figcaption" ]
|
||||
|
||||
def applicable?
|
||||
true
|
||||
end
|
||||
@@ -8,10 +12,6 @@ class ContentFilters::SanitizeTags < ActionText::Content::Filter
|
||||
end
|
||||
|
||||
private
|
||||
ALLOWED_TAGS = %w[ a abbr acronym address b big blockquote br cite code dd del dfn div dl dt em h1 h2 h3 h4 h5 h6 hr i ins kbd li ol
|
||||
p pre samp small span strong sub sup time tt ul var ] + ContentFilters::EDITOR_FORMATTING_TAGS +
|
||||
[ ActionText::Attachment.tag_name, "figure", "figcaption" ]
|
||||
|
||||
def not_allowed_tags_css_selector
|
||||
ALLOWED_TAGS.map { |tag| ":not(#{tag})" }.join("")
|
||||
end
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
module RichTextHelper
|
||||
LEGACY_EMBED_SELECTOR = "action-text-attachment[content-type='#{ActionText::Attachment::OpengraphEmbed::OPENGRAPH_EMBED_CONTENT_TYPE}'][href]"
|
||||
|
||||
def rich_text_data_actions
|
||||
# submitByKeyboard runs in the capture phase so it can submit on Enter
|
||||
# before the editor turns the keystroke into a newline
|
||||
@@ -9,4 +11,17 @@ module RichTextHelper
|
||||
tag.lexxy_prompt trigger: "@", name: "mention", src: autocompletable_users_path(room_id: room.id),
|
||||
"remote-filtering": true, "empty-results": "No matches"
|
||||
end
|
||||
|
||||
# Trix-era opengraph embeds carry their details as node attributes, which
|
||||
# the editor doesn't round-trip. Rendering them into the content attribute
|
||||
# lets the editor preserve them like any embed it created itself.
|
||||
def editable_body(message)
|
||||
fragment = ActionText::Fragment.wrap(message.body.body_before_type_cast)
|
||||
|
||||
transformed = fragment.replace(LEGACY_EMBED_SELECTOR) do |node|
|
||||
node.tap { |n| n["content"] = render_action_text_attachment(ActionText::Attachment.from_node(n)) }
|
||||
end
|
||||
|
||||
ActionText::RichText.new(body: transformed.to_html)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
controller: "form", action: "lexxy:file-accept->form#preventAttachment keydown.esc->form#cancel keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent" } do |form| %>
|
||||
<div class="full-width input input--actor min-width fill-white">
|
||||
<%= form.rich_text_area :body,
|
||||
value: editable_body(@message),
|
||||
rows: 1,
|
||||
class: "input lexxy-content",
|
||||
aria: { multiline: "true", label: "Edit message" },
|
||||
|
||||
@@ -33,12 +33,13 @@ class ActionText::Attachment::OpengraphEmbed
|
||||
|
||||
def attributes_from_content(content)
|
||||
fragment = Nokogiri::HTML.fragment(content)
|
||||
link = fragment.at_css(".og-embed__title a")
|
||||
title = fragment.at_css(".og-embed__title")
|
||||
link = title&.at_css("a")
|
||||
|
||||
{
|
||||
href: link&.[]("href"),
|
||||
url: fragment.at_css(".og-embed__image img")&.[]("src"),
|
||||
filename: link&.text&.strip,
|
||||
filename: (link || title)&.text&.strip,
|
||||
description: fragment.at_css(".og-embed__description")&.text&.strip
|
||||
}
|
||||
end
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
require "test_helper"
|
||||
|
||||
class RichTextHelperTest < ActionView::TestCase
|
||||
include ActionText::ContentHelper
|
||||
test "editable_body renders legacy opengraph embeds into the content attribute" do
|
||||
body = %(<div>https://example.com/ <action-text-attachment content-type="application/vnd.actiontext.opengraph-embed" url="https://example.com/image.png" href="https://example.com/" filename="Example title" caption="Example description"></action-text-attachment></div>)
|
||||
message = Message.create! room: rooms(:pets), body: body, client_message_id: "0017", creator: users(:jason)
|
||||
|
||||
node = editable_body(message).body.fragment.find_all("action-text-attachment").first
|
||||
content = Nokogiri::HTML.fragment(node["content"])
|
||||
|
||||
assert_equal "Example title", content.at_css(".og-embed__title a").text.strip
|
||||
assert_equal "https://example.com/", content.at_css(".og-embed__title a")["href"]
|
||||
assert_equal "Example description", content.at_css(".og-embed__description").text.strip
|
||||
assert_equal "https://example.com/image.png", content.at_css(".og-embed__image img")["src"]
|
||||
end
|
||||
|
||||
test "editable_body leaves bodies without legacy embeds unchanged" do
|
||||
message = Message.create! room: rooms(:pets), body: "<p>Plain text</p>", client_message_id: "0018", creator: users(:jason)
|
||||
|
||||
assert_equal message.body.body.to_html, editable_body(message).body.to_html
|
||||
end
|
||||
end
|
||||
@@ -68,6 +68,24 @@ class ComposerTest < ApplicationSystemTestCase
|
||||
assert_equal [ users(:jason) ], message.reload.mentionees
|
||||
end
|
||||
|
||||
test "editing a legacy trix message keeps its mention and embed" do
|
||||
body = %(<div>Hey #{mention_attachment_for(:jason)} check <action-text-attachment content-type="application/vnd.actiontext.opengraph-embed" url="https://example.com/image.png" href="https://example.com/" filename="Example title" caption="Example description"></action-text-attachment></div>)
|
||||
message = Message.create! room: rooms(:designers), body: body, client_message_id: "legacy", creator: users(:jz)
|
||||
|
||||
join_room rooms(:designers)
|
||||
|
||||
within_message message do
|
||||
reveal_message_actions
|
||||
find(".message__edit-btn").click
|
||||
assert_edit_editor_text "Jason"
|
||||
click_on "Save changes"
|
||||
end
|
||||
|
||||
assert_selector last_message_selector(".mention"), text: "Jason"
|
||||
assert_selector last_message_selector(%(.og-embed__title a[href="https://example.com/"])), text: "Example title"
|
||||
assert_equal [ users(:jason) ], message.reload.mentionees
|
||||
end
|
||||
|
||||
test "replying quotes the original message with attribution" do
|
||||
within_message messages(:third) do
|
||||
reveal_message_actions
|
||||
|
||||
Reference in New Issue
Block a user