From 851f13a93439ea75dfa40766934ae744a7365498 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 12 Feb 2026 12:07:40 -0500 Subject: [PATCH] Render inline code in card titles (#2518) * Remove unused marked JS dependency * Remove unused redcarpet dependency * Render inline code in card titles Add card_html_title helper that HTML-escapes input then converts backtick-wrapped text to elements. Apply to card titles in board preview, card detail, public views, and notification emails. Style inline code elements in titles to match description styling. Co-authored-by: Andy Smith --------- Co-authored-by: Andy Smith --- Gemfile | 1 - Gemfile.lock | 2 - Gemfile.saas.lock | 2 - app/assets/stylesheets/cards.css | 9 + app/helpers/html_helper.rb | 6 + .../cards/container/_content_display.html.erb | 2 +- app/views/cards/display/_preview.html.erb | 2 +- .../cards/display/_public_preview.html.erb | 2 +- .../bundle_mailer/notification.html.erb | 2 +- app/views/public/cards/show/_content.html.erb | 2 +- config/importmap.rb | 1 - test/controllers/cards_controller_test.rb | 11 +- test/helpers/html_helper_test.rb | 25 + .../notification/bundle_mailer_test.rb | 10 + vendor/javascript/marked.js | 2583 ----------------- 15 files changed, 63 insertions(+), 2597 deletions(-) delete mode 100644 vendor/javascript/marked.js diff --git a/Gemfile b/Gemfile index ccc4da06a..c524ee3c1 100644 --- a/Gemfile +++ b/Gemfile @@ -25,7 +25,6 @@ gem "trilogy", "~> 2.9" gem "bcrypt", "~> 3.1.7" gem "geared_pagination", "~> 1.2" gem "rqrcode" -gem "redcarpet" gem "rouge" gem "jbuilder" gem "lexxy", bc: "lexxy" diff --git a/Gemfile.lock b/Gemfile.lock index d20d773b7..f937c83e8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -355,7 +355,6 @@ GEM erb psych (>= 4.0.0) tsort - redcarpet (3.6.1) regexp_parser (2.11.3) reline (0.6.3) io-console (~> 0.5) @@ -521,7 +520,6 @@ DEPENDENCIES puma (>= 5.0) rack-mini-profiler rails! - redcarpet rouge rqrcode rubocop-rails-omakase diff --git a/Gemfile.saas.lock b/Gemfile.saas.lock index 84b5cf2b3..4d267e4fd 100644 --- a/Gemfile.saas.lock +++ b/Gemfile.saas.lock @@ -466,7 +466,6 @@ GEM erb psych (>= 4.0.0) tsort - redcarpet (3.6.1) regexp_parser (2.11.3) reline (0.6.3) io-console (~> 0.5) @@ -676,7 +675,6 @@ DEPENDENCIES rack-mini-profiler rails! rails_structured_logging! - redcarpet rouge rqrcode rubocop-rails-omakase diff --git a/app/assets/stylesheets/cards.css b/app/assets/stylesheets/cards.css index f36b4e76f..272ff7b1f 100644 --- a/app/assets/stylesheets/cards.css +++ b/app/assets/stylesheets/cards.css @@ -220,6 +220,15 @@ .card__title-link { color: inherit; } + + code { + background-color: var(--color-canvas); + border: 1px solid var(--color-ink-lighter); + border-radius: 0.25ch; + font-family: var(--font-mono); + font-size: smaller; + padding: 0.1ch 0.25ch; + } } .card__description { diff --git a/app/helpers/html_helper.rb b/app/helpers/html_helper.rb index a3d0bbab3..7b65d34cf 100644 --- a/app/helpers/html_helper.rb +++ b/app/helpers/html_helper.rb @@ -2,4 +2,10 @@ module HtmlHelper def format_html(html) Loofah::HTML5::DocumentFragment.parse(html).scrub!(AutoLinkScrubber.new).to_html.html_safe end + + def card_html_title(card) + return card.title if card.title.blank? + + ERB::Util.html_escape(card.title).gsub(/`([^`]+)`/, '\1').html_safe + end end diff --git a/app/views/cards/container/_content_display.html.erb b/app/views/cards/container/_content_display.html.erb index 46a9a83d1..e99b5784f 100644 --- a/app/views/cards/container/_content_display.html.erb +++ b/app/views/cards/container/_content_display.html.erb @@ -1,5 +1,5 @@

- <%= link_to card.title, edit_card_path(card), class: "card__title-link" %> + <%= link_to card_html_title(card), edit_card_path(card), class: "card__title-link" %>

<% unless card.description.blank? %> diff --git a/app/views/cards/display/_preview.html.erb b/app/views/cards/display/_preview.html.erb index fe8b7c613..f70aa6ade 100644 --- a/app/views/cards/display/_preview.html.erb +++ b/app/views/cards/display/_preview.html.erb @@ -36,7 +36,7 @@

- <%= card.title %> + <%= card_html_title(card) %>

diff --git a/app/views/cards/display/_public_preview.html.erb b/app/views/cards/display/_public_preview.html.erb index 7aa53fed7..0c297df15 100644 --- a/app/views/cards/display/_public_preview.html.erb +++ b/app/views/cards/display/_public_preview.html.erb @@ -17,7 +17,7 @@

- <%= card.title %> + <%= card_html_title(card) %>

diff --git a/app/views/mailers/notification/bundle_mailer/notification.html.erb b/app/views/mailers/notification/bundle_mailer/notification.html.erb index 28ca0be36..1545f8598 100644 --- a/app/views/mailers/notification/bundle_mailer/notification.html.erb +++ b/app/views/mailers/notification/bundle_mailer/notification.html.erb @@ -9,7 +9,7 @@

<%= link_to board.name, board %>

<% board_notifications.group_by(&:card).each do |card, notifications| %> - <%= link_to "##{ card.number } #{ card.title }", card, class: "card__title" %> + <%= link_to card, class: "card__title" do %>#<%= card.number %> <%= card_html_title(card) %><% end %> <%= render partial: "notification/bundle_mailer/notification", collection: notifications, as: :notification %> <% end %> <% end %> diff --git a/app/views/public/cards/show/_content.html.erb b/app/views/public/cards/show/_content.html.erb index 0bb74b3ce..eb3ccd044 100644 --- a/app/views/public/cards/show/_content.html.erb +++ b/app/views/public/cards/show/_content.html.erb @@ -1,6 +1,6 @@

- <%= tag.span card.title, class: "card__title-link" %> + <%= tag.span card_html_title(card), class: "card__title-link" %>

<%= card.description %> diff --git a/config/importmap.rb b/config/importmap.rb index 27dbd7015..5da84683c 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -13,7 +13,6 @@ pin_all_from "app/javascript/initializers", under: "initializers" pin_all_from "app/javascript/bridge/initializers", under: "bridge/initializers" pin_all_from "app/javascript/bridge/helpers", under: "bridge/helpers" pin_all_from "app/javascript/bridge/controllers/bridge", under: "controllers/bridge", to: "bridge/controllers/bridge" -pin "marked" # @15.0.11 pin "lexxy" pin "@rails/activestorage", to: "activestorage.esm.js" pin "@rails/actiontext", to: "actiontext.esm.js" diff --git a/test/controllers/cards_controller_test.rb b/test/controllers/cards_controller_test.rb index 8007657d4..3a97f3166 100644 --- a/test/controllers/cards_controller_test.rb +++ b/test/controllers/cards_controller_test.rb @@ -42,9 +42,14 @@ class CardsControllerTest < ActionDispatch::IntegrationTest assert_redirected_to card_draft_path(card) end - test "show" do - get card_path(cards(:logo)) - assert_response :success + test "show renders inline code in title" do + card = cards(:logo) + card.update_column :title, "Fix the `bug` in production" + + get card_path(card) + assert_select ".card__title-link" do |element| + assert_equal "Fix the bug in production", element.inner_html + end end test "edit" do diff --git a/test/helpers/html_helper_test.rb b/test/helpers/html_helper_test.rb index 1af507ddf..0fd6c268a 100644 --- a/test/helpers/html_helper_test.rb +++ b/test/helpers/html_helper_test.rb @@ -119,4 +119,29 @@ class HtmlHelperTest < ActionView::TestCase assert_no_match(/bug in production", card_html_title(cards(:logo).tap { _1.title = "Fix the `bug` in production" }) + end + + test "card_html_title renders multiple code spans" do + assert_equal "foo and bar", card_html_title(cards(:logo).tap { _1.title = "`foo` and `bar`" }) + end + + test "card_html_title renders code spans without surrounding spaces" do + assert_equal "whataboutthis", card_html_title(cards(:logo).tap { _1.title = "what`about`this" }) + end + + test "card_html_title escapes HTML tags" do + assert_equal "<script>alert(1)</script>", card_html_title(cards(:logo).tap { _1.title = "" }) + end + + test "card_html_title escapes HTML inside backticks" do + assert_equal "<script>", card_html_title(cards(:logo).tap { _1.title = "`