Merge branch 'main' into unique-notifications

This commit is contained in:
Jorge Manrubia
2025-11-13 09:38:01 +01:00
26 changed files with 153 additions and 64 deletions
+51 -2
View File
@@ -45,8 +45,9 @@
min-block-size: 20lh;
}
@media (max-width: 639px) {
--bubble-size: 3rem;
/* Hide on small, fat-finger touch devices */
@media (hover: none) and (pointer: coarse) and (max-width: 519px) {
display: none;
}
}
@@ -730,4 +731,52 @@
}
}
}
/* Mobile columns
/* -------------------------------------------------------------------------- */
.mobile-card-columns {
--column-gap: 8px;
--column-padding-expanded: calc(var(--column-gap) * 2);
--column-width-collapsed: 40px;
--progress-increment: var(--progress-max-width) / var(--progress-max-cards);
--progress-max-cards: 20;
--progress-max-width: 100%;
padding-inline: 3vw;
/* Hide on larger devices with cursors */
@media (hover: hover) and (min-width: 520px) {
display: none;
}
.cards.is-collapsed {
inline-size: auto;
display: block;
padding-block: 0.5ch;
.cards__expander {
flex-direction: row;
inline-size: auto;
&:before {
block-size: 1px;
inline-size: 100%;
inset: 50% 0 auto;
translaate: 0 -50%;
}
&:after {
block-size: var(--column-width-collapsed);
inline-size: calc(var(--column-width-collapsed) + var(--card-count) * var(--progress-increment));
margin-inline-start: 0;
max-inline-size: var(--progress-max-width);
}
}
.cards__expander-title {
writing-mode: revert;
}
}
}
}
@@ -10,29 +10,16 @@ class Cards::Comments::ReactionsController < ApplicationController
end
def create
reaction = @comment.reactions.create!(params.expect(reaction: :content))
broadcast_create(reaction)
redirect_to card_comment_reactions_path(@card, @comment)
@reaction = @comment.reactions.create!(params.expect(reaction: :content))
end
def destroy
reaction = @comment.reactions.find(params[:id])
reaction.destroy
broadcast_remove(reaction)
@reaction = @comment.reactions.find(params[:id])
@reaction.destroy
end
private
def set_comment
@comment = @card.comments.find(params[:comment_id])
end
def broadcast_create(reaction)
reaction.broadcast_append_to @card, target: [ @comment, :reactions ], partial: "cards/comments/reactions/reaction"
end
def broadcast_remove(reaction)
reaction.broadcast_remove_to @card
end
end
@@ -8,7 +8,6 @@ class Cards::TaggingsController < ApplicationController
def create
@card.toggle_tag_with sanitized_tag_title_param
render_card_replacement
end
private
+1 -1
View File
@@ -5,6 +5,6 @@ class My::MenusController < ApplicationController
@tags = Tag.all.alphabetically
@users = User.active.alphabetically
# fresh_when etag: [ @user_filtering, Current.session ]
fresh_when etag: [ @filters, @boards, @tags, @users ]
end
end
+1 -1
View File
@@ -30,7 +30,7 @@ module FiltersHelper
tag.dialog class: "margin-block-start-half popup panel flex-column align-start gap-half fill-white shadow txt-small", data: {
action: "turbo:before-cache@document->dialog#close keydown->navigable-list#navigate filter:changed->navigable-list#reset toggle->filter#filter",
aria: { label: label, aria_description: label },
controller: "filter navigable-list",
controller: "navigable-list",
dialog_target: "dialog",
navigable_list_focus_on_selection_value: false,
navigable_list_actionable_items_value: true
@@ -5,7 +5,8 @@ import { limitHeightToViewport } from "helpers/sizing_helpers"
export default class extends Controller {
static targets = [ "dialog" ]
static values = {
modal: { type: Boolean, default: false }
modal: { type: Boolean, default: false },
sizing: { type: Boolean, default: true }
}
connect() {
@@ -22,7 +23,7 @@ export default class extends Controller {
orient(this.dialogTarget)
}
limitHeightToViewport(this.dialogTarget, true)
limitHeightToViewport(this.dialogTarget, this.sizingValue)
this.#loadLazyFrames()
this.dialogTarget.setAttribute("aria-hidden", "false")
@@ -43,6 +44,7 @@ export default class extends Controller {
this.dialogTarget.blur()
orient(this.dialogTarget, false)
limitHeightToViewport(this.dialogTarget, false)
this.dispatch("close")
}
closeOnClickOutside({ target }) {
@@ -20,4 +20,9 @@ export default class extends Controller {
this.dispatch("changed")
}
clearInput() {
if (!this.hasInputTarget) return
this.inputTarget.value = ""
}
}
+1
View File
@@ -26,5 +26,6 @@
<%= render "boards/show/filtered_cards", page: @page %>
<% else %>
<%= render "boards/show/columns", page: @page, board: @board %>
<%= render "boards/show/mobile_columns", page: @page, board: @board %>
<% end %>
<% end %>
-1
View File
@@ -25,4 +25,3 @@
<%= render "boards/show/menu/columns", board: board %>
</div>
<% end %>
@@ -0,0 +1,39 @@
<section class="mobile-card-columns">
<%= link_to board_columns_not_now_path(board), class: "cards cards--on-deck is-collapsed", style: "--card-color: var(--color-card-complete);", data: { turbo_frame: "_top" } do %>
<div class="cards__expander btn btn--plain" style="--card-count: <%= board.cards.postponed.count %>">
<span class="cards__expander-count"><%= board.cards.postponed.count %></span>
<h2 class="cards__expander-title">
Not Now
</h2>
</div>
<% end %>
<%= link_to board_columns_stream_path(board), class: "cards cards--considering is-collapsed", data: { turbo_frame: "_top" } do %>
<div class="cards__expander btn btn--plain" style="--card-count: <%= board.cards.awaiting_triage.count %>">
<span class="cards__expander-count"><%= board.cards.awaiting_triage.count %></span>
<h2 class="cards__expander-title">
Maybe?
</h2>
</div>
<% end %>
<% board.columns.sorted.each do |column| %>
<%= link_to board_column_path(column.board, column), class: "cards cards--doing is-collapsed", style: "--card-color: #{column.color}", data: { turbo_frame: "_top" } do %>
<div class="cards__expander btn btn--plain" style="--card-count: <%= column.cards.active.count %>">
<span class="cards__expander-count"><%= column.cards.active.count %></span>
<h2 class="cards__expander-title">
<%= column.name %>
</h2>
</div>
<% end %>
<% end %>
<%= link_to board_columns_closed_path(board), class: "cards cards--on-deck is-collapsed", style: "--card-color: var(--color-card-complete);", data: { turbo_frame: "_top" } do %>
<div class="cards__expander btn btn--plain" style="--card-count: <%= board.cards.closed.count %>">
<span class="cards__expander-count"><%= board.cards.closed.count %></span>
<h2 class="cards__expander-title">
Done
</h2>
</div>
<% end %>
</section>
+1 -1
View File
@@ -17,7 +17,7 @@
<span class="flex-inline align-center gap">
<%= form.button class: "comment__submit btn btn--reversed",
data: { form_target: "submit" }, disabled: true do %>
<span>Post</span>
<span>Post this comment</span>
<% end %>
<span class="hide-on-touch txt-subtle">
<kbd class="txt-x-small"><%= hotkey_label([ "ctrl", "enter" ]) %></kbd>
@@ -0,0 +1,3 @@
<%= turbo_stream.replace([ @comment, :reacting ]) do %>
<%= render "cards/comments/reactions/reactions", comment: @comment.reload %>
<% end %>
@@ -0,0 +1 @@
<%= turbo_stream.remove @reaction %>
@@ -1,13 +1,19 @@
<div class="card-perma__notch card-perma__notch--bottom">
<%= button_to card_publish_path(card), name: "creation_type", value: "add", class: "btn",
form: { data: { controller: "form" } },
data: { form_target: "submit", controller: "clicker", action: "keydown.ctrl+enter@document->clicker#click keydown.meta+enter@document->clicker#click" } do %>
<span>Create card</span>
<% end %>
<div class="flex flex-column gap-half">
<%= button_to card_publish_path(card), name: "creation_type", value: "add", class: "btn",
form: { data: { controller: "form" } },
data: { form_target: "submit", controller: "clicker", action: "keydown.ctrl+enter@document->clicker#click keydown.meta+enter@document->clicker#click" } do %>
<span>Create card</span>
<% end %>
<kbd class="txt-x-small txt-subtle center"><%= hotkey_label([ "ctrl", "enter" ]) %></kbd>
</div>
<%= button_to card_publish_path(card), method: :post, class: "btn btn--reversed", name: "creation_type", value: "add_another",
title: "Create and add another (ctrl+shift+enter)", form: { data: { controller: "form" } },
data: { form_target: "submit", controller: "clicker", action: "keydown.ctrl+shift+enter@document->clicker#click keydown.meta+shift+enter@document->clicker#click" } do %>
<span>Create and add another</span>
<% end %>
<div class="flex flex-column gap-half">
<%= button_to card_publish_path(card), method: :post, class: "btn btn--reversed", name: "creation_type", value: "add_another",
title: "Create and add another (ctrl+shift+enter)", form: { data: { controller: "form" } },
data: { form_target: "submit", controller: "clicker", action: "keydown.ctrl+shift+enter@document->clicker#click keydown.meta+shift+enter@document->clicker#click" } do %>
<span>Create and add another</span>
<% end %>
<kbd class="txt-x-small txt-subtle center"><%= hotkey_label([ "ctrl", "shift", "enter" ]) %></kbd>
</div>
</div>
@@ -0,0 +1 @@
<%= turbo_stream.replace([ @card, :tags ], partial: "cards/display/perma/tags", method: "morph", locals: { card: @card.reload }) %>
+2 -2
View File
@@ -5,8 +5,8 @@
data: { controller: "form", turbo_frame: "cards_container", turbo_action: "advance" } do |form| %>
<%= tag.div class: "flex-inline position-relative quick-filter",
data: {
controller: "dialog multi-selection-combobox",
action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside turbo:morph@window->multi-selection-combobox#refresh",
controller: "dialog filter multi-selection-combobox",
action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside turbo:morph@window->multi-selection-combobox#refresh dialog:close@document->filter#clearInput",
filter_show: user_filtering.show_boards?,
multi_selection_combobox_no_selection_label_value: user_filtering.selected_boards_label } do %>
<button type="button" class="btn borderless btn--plain events__filter-select" data-action="click->dialog#toggle:stop">
@@ -1,8 +1,8 @@
<% filter = user_filtering.filter %>
<%= tag.div class: "quick-filter",
data: {
controller: "dialog multi-selection-combobox",
action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside",
controller: "dialog filter multi-selection-combobox",
action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside dialog:close@document->filter#clearInput",
filter_show: user_filtering.show_assignees?,
multi_selection_combobox_no_selection_label_value: "Assigned to…",
multi_selection_combobox_label_prefix_value: "Assigned to" } do %>
+2 -2
View File
@@ -2,8 +2,8 @@
<%= tag.div class: "quick-filter",
data: {
controller: "dialog multi-selection-combobox",
action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside",
controller: "dialog filter multi-selection-combobox",
action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside dialog:close->filter#clearInput",
filter_show: user_filtering.show_boards?,
multi_selection_combobox_no_selection_label_value: "Board…",
multi_selection_combobox_label_prefix_value: "" } do %>
+2 -2
View File
@@ -2,8 +2,8 @@
<%= tag.div class: "quick-filter",
data: {
controller: "dialog multi-selection-combobox",
action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside",
controller: "dialog filter multi-selection-combobox",
action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside dialog:close@document->filter#clearInput",
filter_show: user_filtering.show_closers?,
multi_selection_combobox_no_selection_label_value: "Closed by…",
multi_selection_combobox_label_prefix_value: "Closed by" } do %>
@@ -2,8 +2,8 @@
<%= tag.div class: "quick-filter",
data: {
controller: "dialog multi-selection-combobox",
action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside",
controller: "dialog filter multi-selection-combobox",
action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside dialog:close@document->filter#clearInput",
filter_show: user_filtering.show_creators?,
multi_selection_combobox_no_selection_label_value: "Added by…",
multi_selection_combobox_label_prefix_value: "Added by" } do %>
@@ -1,8 +1,8 @@
<% filter = user_filtering.filter %>
<%= tag.div class: class_names("quick-filter"),
data: { controller: "dialog combobox",
action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside",
data: { controller: "dialog filter combobox",
action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside dialog:close@document->filter#clearInput",
filter_show: user_filtering.show_indexed_by?,
combobox_default_value_value: "all",
combobox_default_label_value: "Status…",
@@ -2,8 +2,8 @@
<%= tag.div class: class_names("quick-filter"),
data: {
controller: "dialog combobox",
action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside",
controller: "dialog filter combobox",
action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside dialog:close@document->filter#clearInput",
filter_show: user_filtering.show_sorted_by?,
combobox_default_value_value: "latest",
combobox_with_default_class: "quick-filter--with-default" } do %>
+2 -2
View File
@@ -2,8 +2,8 @@
<%= tag.div class: "quick-filter",
data: {
controller: "dialog multi-selection-combobox",
action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside",
controller: "dialog filter multi-selection-combobox",
action: "keydown.esc->dialog#close click@document->dialog#closeOnClickOutside dialog:close@document->filter#clearInput",
filter_show: user_filtering.show_tags?,
multi_selection_combobox_no_selection_label_value: "Tagged…" } do %>
<button type="button" class="btn input input--select flex-inline txt-x-small" data-action="click->dialog#toggle:stop">
+1 -1
View File
@@ -1,6 +1,6 @@
<%= turbo_stream_from Current.user, :notifications %>
<section class="tray tray--notifications" data-controller="dialog badge" data-badge-unread-class="unread" data-action="turbo:render#update">
<section class="tray tray--notifications" data-controller="dialog badge" data-badge-unread-class="unread" data-action="turbo:render#update" data-dialog-sizing-value="false">
<dialog class="tray__dialog"
data-controller="navigable-list"
data-dialog-target="dialog"
@@ -8,20 +8,17 @@ class Cards::Comments::ReactionsControllerTest < ActionDispatch::IntegrationTest
end
test "create" do
assert_turbo_stream_broadcasts @card, count: 1 do
assert_difference -> { @comment.reactions.count }, 1 do
post card_comment_reactions_path(@comment.card, @comment, format: :turbo_stream), params: { reaction: { content: "Great work!" } }
assert_redirected_to card_comment_reactions_path(@comment.card, @comment)
end
assert_difference -> { @comment.reactions.count }, 1 do
post card_comment_reactions_path(@comment.card, @comment, format: :turbo_stream), params: { reaction: { content: "Great work!" } }
assert_turbo_stream action: :replace, target: dom_id(@comment, :reacting)
end
end
test "destroy" do
assert_turbo_stream_broadcasts @card, count: 1 do
assert_difference -> { @comment.reactions.count }, -1 do
delete card_comment_reaction_path(@comment.card, @comment, reactions(:kevin), format: :turbo_stream)
assert_response :success
end
reaction = reactions(:kevin)
assert_difference -> { @comment.reactions.count }, -1 do
delete card_comment_reaction_path(@comment.card, @comment, reaction, format: :turbo_stream)
assert_turbo_stream action: :remove, target: dom_id(reaction)
end
end
end
@@ -13,14 +13,14 @@ class Cards::TaggingsControllerTest < ActionDispatch::IntegrationTest
test "toggle tag on" do
assert_changes "cards(:logo).tagged_with?(tags(:mobile))", from: false, to: true do
post card_taggings_path(cards(:logo)), params: { tag_title: tags(:mobile).title }, as: :turbo_stream
assert_card_container_rerendered(cards(:logo))
assert_turbo_stream action: :replace, target: dom_id(cards(:logo), :tags)
end
end
test "toggle tag off" do
assert_changes "cards(:logo).tagged_with?(tags(:web))", from: true, to: false do
post card_taggings_path(cards(:logo)), params: { tag_title: tags(:web).title }, as: :turbo_stream
assert_card_container_rerendered(cards(:logo))
assert_turbo_stream action: :replace, target: dom_id(cards(:logo), :tags)
end
end
end