Ensure the tags dropdown is not subject to the cached card fragment

- Rearrange the _tags and _tag partial, so they are properly nested concerns
- Add turbo frames for _tags and _tag
- Load _tag in a separate request, so it's not cached with the card
- Cache _tag using the bubble and the account
- New model behavior: touch Account when tags are added or removed
- New controller action: "new" for taggings toggles, which renders the _tag partial
- Updating taggings responds with a turbo stream response that updates the _tags frame

This is very much a parallel change to what was done with assignments
in #317.
This commit is contained in:
Mike Dalessio
2025-03-24 16:43:59 -04:00
parent 506f74144b
commit 5ae87d8140
5 changed files with 70 additions and 46 deletions
+13 -1
View File
@@ -1,6 +1,10 @@
class Taggings::TogglesController < ApplicationController
include BubbleScoped, BucketScoped
def new
render partial: "bubbles/sidebar/tag", locals: { bubble: @bubble, tags: Current.account.tags }
end
def create
if params[:tag_title].present?
sanitized_title = params[:tag_title].strip.gsub(/\A#/, "")
@@ -20,6 +24,14 @@ class Taggings::TogglesController < ApplicationController
end
end
redirect_to @bubble
@bubble.tags.reload
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.replace([ @bubble, :tags ],
partial: "bubbles/tags",
locals: { bubble: @bubble })
end
end
end
end
+20 -11
View File
@@ -1,13 +1,22 @@
<div class="card__tags flex align-center flex-item-grow">
<% if bubble.tags.any? || bubble.creating? %>
<span># </span>
<% bubble.tags.each_with_index do |tag, index| %>
<%= link_to bubbles_path(bucket_ids: [ bubble.bucket ], tag_ids: [ tag.id ]),
class: "card__tag btn btn--plain min-width txt-uppercase fill-transparent", data: { turbo_frame: "_top" } do %>
<span class="overflow-ellipsis"><%= tag.title %></span>
<% end %><%= ", " unless index == bubble.tags.size - 1 %>
<%= turbo_frame_tag bubble, :tags do %>
<div class="card__tags flex align-center flex-item-grow">
<% if bubble.tags.any? || bubble.creating? %>
<span># </span>
<% bubble.tags.each_with_index do |tag, index| %>
<%= link_to bubbles_path(bucket_ids: [ bubble.bucket ], tag_ids: [ tag.id ]),
class: "card__tag btn btn--plain min-width txt-uppercase fill-transparent", data: { turbo_frame: "_top" } do %>
<span class="overflow-ellipsis"><%= tag.title %></span>
<% end %><%= ", " unless index == bubble.tags.size - 1 %>
<% end %>
<% end %>
<% end %>
<%= render "bubbles/sidebar/tag", bubble: bubble, tags: Current.account.tags %>
</div>
<div class="position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
<button class="tag-picker__button btn card__hide-on-index <%= bubble.creating? || bubble.drafted? ? "fill-highlight" : "fill-transparent" %>" data-action="click->dialog#open:stop">
<%= image_tag "tag.svg", aria: { hidden: true }, size: 24 %>
<span class="for-screen-reader">Add a tag</span>
</button>
<%= turbo_frame_tag bubble, :tag, src: new_bucket_bubble_tagging_toggle_path(bubble.bucket, bubble) %>
</div>
</div>
<% end %>
@@ -1,4 +1,4 @@
<% cache [bubble, bubble.bucket] do %>
<% cache [ bubble, bubble.bucket ] do %>
<%= turbo_frame_tag bubble, :assignment do %>
<dialog class="popup panel flex-column align-start gap-half fill-white shadow" data-dialog-target="dialog">
<strong class="popup__title margin-block-start-half pad-inline-half txt-nowrap">Assign this to…</strong>
+26 -29
View File
@@ -1,34 +1,31 @@
<div class="position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
<button class="tag-picker__button btn card__hide-on-index <%= bubble.creating? || bubble.drafted? ? "fill-highlight" : "fill-transparent" %>" data-action="click->dialog#open:stop">
<%= image_tag "tag.svg", aria: { hidden: true }, size: 24 %>
<span class="for-screen-reader">Add a tag</span>
</button>
<% cache [ bubble, Current.account ] do %>
<%= turbo_frame_tag bubble, :tag do %>
<dialog class="popup panel flex-column align-start justify-start fill-white shadow" data-dialog-target="dialog">
<strong class="popup__title margin-block-half pad-inline-half">Tag this…</strong>
<dialog class="popup panel flex-column align-start justify-start fill-white shadow" data-dialog-target="dialog">
<strong class="popup__title margin-block-half pad-inline-half">Tag this…</strong>
<%= form_with url: bucket_bubble_tagging_toggles_path(bubble.bucket, bubble), class: "flex gap-half align-center full-width pad-inline-half margin-block-end" do |form| %>
<%= form.text_field :tag_title, placeholder: "New tag name...", class: "input txt-small full-width" %>
<%= form.button "Add a tag…", type: "submit", class: "btn txt-small" do %>
<%= image_tag "add.svg", aria: { hidden: true }, size: 24 %>
<span class="for-screen-reader">Add a tag</span>
<%= form_with url: bucket_bubble_tagging_toggles_path(bubble.bucket, bubble), class: "flex gap-half align-center full-width pad-inline-half margin-block-end" do |form| %>
<%= form.text_field :tag_title, placeholder: "New tag name...", class: "input txt-small full-width" %>
<%= form.button "Add a tag…", type: "submit", class: "btn txt-small" do %>
<%= image_tag "add.svg", aria: { hidden: true }, size: 24 %>
<span class="for-screen-reader">Add a tag</span>
<% end %>
<% end %>
<% end %>
<%= form_with url: bucket_bubble_tagging_toggles_path(bubble.bucket, bubble), class: "flex flex-column full-width popup__list", data: { controller: "form" } do |form| %>
<% tags.sort_by { |tag| tag.hashtag.downcase }.each do |tag| %>
<div class="btn popup__item">
<%= form.check_box "tag_id[]", {
checked: bubble.tagged_with?(tag),
data: { action: "change->form#submit" },
id: dom_id(tag, :tag),
include_hidden: false,
}, tag.id %>
<%= form_with url: bucket_bubble_tagging_toggles_path(bubble.bucket, bubble), class: "flex flex-column full-width popup__list", data: { controller: "form" } do |form| %>
<% tags.sort_by { |tag| tag.hashtag.downcase }.each do |tag| %>
<div class="btn popup__item">
<%= form.check_box "tag_id[]", {
checked: bubble.tagged_with?(tag),
data: { action: "change->form#submit" },
id: dom_id(tag, :tag),
include_hidden: false,
}, tag.id %>
<%= form.label "tag_id[]", tag.hashtag, for: dom_id(tag, :tag), class: "overflow-ellipsis" %>
<%= image_tag "check.svg", aria: { hidden: true }, size: 18, class: "checked flex-item-justify-end" %>
</div>
<%= form.label "tag_id[]", tag.hashtag, for: dom_id(tag, :tag), class: "overflow-ellipsis" %>
<%= image_tag "check.svg", aria: { hidden: true }, size: 18, class: "checked flex-item-justify-end" %>
</div>
<% end %>
<% end %>
<% end %>
</dialog>
</div>
</dialog>
<% end %>
<% end %>
@@ -5,17 +5,23 @@ class Taggings::TogglesControllerTest < ActionDispatch::IntegrationTest
sign_in_as :kevin
end
test "new" do
get new_bucket_bubble_tagging_toggle_url(buckets(:writebook), bubbles(:logo))
assert_response :success
end
test "create" do
assert_changes "bubbles(:logo).tagged_with?(tags(:mobile))", from: false, to: true do
post bucket_bubble_tagging_toggles_url(buckets(:writebook), bubbles(:logo)), params: { tag_id: tags(:mobile).id }
post bucket_bubble_tagging_toggles_url(buckets(:writebook), bubbles(:logo)), params: { tag_id: tags(:mobile).id }, as: :turbo_stream
end
assert_redirected_to bubbles(:logo)
assert_response :success
assert_changes "bubbles(:logo).tagged_with?(tags(:web))", from: false, to: true do
assert_changes "bubbles(:logo).tagged_with?(tags(:mobile))", from: true, to: false do
post bucket_bubble_tagging_toggles_url(buckets(:writebook), bubbles(:logo)), params: { tag_id: tags(:web).id }
post bucket_bubble_tagging_toggles_url(buckets(:writebook), bubbles(:logo)), params: { tag_id: tags(:web).id }, as: :turbo_stream
end
end
assert_redirected_to bubbles(:logo)
assert_response :success
end
end