Merge branch 'main' into update-public-boards
This commit is contained in:
@@ -400,6 +400,10 @@
|
||||
box-shadow: none !important;
|
||||
color: color-mix(in srgb, var(--card-color) 70%, var(--color-canvas));
|
||||
|
||||
.cards--considering & {
|
||||
background-color: var(--card-bg-color) !important;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: var(--text-small);
|
||||
overflow: hidden;
|
||||
|
||||
@@ -179,17 +179,20 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
& ~ .btn {
|
||||
--btn-background: var(--card-color);
|
||||
--btn-color: var(--color-ink-inverted);
|
||||
}
|
||||
}
|
||||
|
||||
.card-field__description {
|
||||
lexxy-toolbar {
|
||||
border-block-start: 1px solid var(--color-ink-light);
|
||||
margin-block-start: var(--block-space-half) !important;
|
||||
}
|
||||
|
||||
& ~ .btn.btn--reversed {
|
||||
--btn-background: var(--card-color);
|
||||
--btn-color: var(--color-ink-inverted);
|
||||
}
|
||||
|
||||
& ~ .btn {
|
||||
--btn-border-color: var(--card-color);
|
||||
--btn-color: var(--card-color);
|
||||
}
|
||||
}
|
||||
|
||||
.card__stages {
|
||||
|
||||
@@ -6,12 +6,22 @@
|
||||
color: var(--color-ink);
|
||||
inline-size: var(--panel-size, 60ch);
|
||||
max-inline-size: 100%;
|
||||
padding: var(--panel-padding, calc(var(--block-space) * 2));
|
||||
padding: var(--panel-padding, var(--block-space));
|
||||
|
||||
@media (min-width: 640px) {
|
||||
--panel-size: 100%;
|
||||
|
||||
padding: var(--panel-padding, var(--block-space-double));
|
||||
}
|
||||
}
|
||||
|
||||
.panel--centered {
|
||||
--panel-border-size: 0;
|
||||
--panel-size: 42ch;
|
||||
--panel-size: 100%;
|
||||
|
||||
@media (min-width: 640px) {
|
||||
--panel-size: 42ch;
|
||||
}
|
||||
|
||||
#main:has(&) {
|
||||
display: grid;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@layer components {
|
||||
[data-controller^="tooltip"] {
|
||||
[data-controller~="tooltip"] {
|
||||
--tooltip-delay: 750ms;
|
||||
--tooltip-duration: 150ms;
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
class Account::EntropiesController < ApplicationController
|
||||
before_action :ensure_admin
|
||||
|
||||
def update
|
||||
Account.sole.entropy.update!(entropy_params)
|
||||
redirect_to account_settings_path, notice: "Account updated"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
class Account::JoinCodesController < ApplicationController
|
||||
before_action :set_join_code
|
||||
before_action :ensure_admin, only: %i[ update destroy ]
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
class Boards::EntropiesController < ApplicationController
|
||||
include BoardScoped
|
||||
|
||||
before_action :ensure_permission_to_admin_board
|
||||
|
||||
def update
|
||||
@board.entropy.update!(entropy_params)
|
||||
end
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
class Boards::PublicationsController < ApplicationController
|
||||
include BoardScoped
|
||||
|
||||
before_action :ensure_permission_to_admin_board
|
||||
|
||||
def create
|
||||
@board.publish
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ class BoardsController < ApplicationController
|
||||
include FilterScoped
|
||||
|
||||
before_action :set_board, except: %i[ new create ]
|
||||
before_action :ensure_permission_to_change_accesses, only: %i[ update ]
|
||||
before_action :ensure_permission_to_admin_board, only: %i[ update ]
|
||||
|
||||
def show
|
||||
if @filter.used?(ignore_boards: true)
|
||||
@@ -48,20 +48,12 @@ class BoardsController < ApplicationController
|
||||
@board = Current.user.boards.find params[:id]
|
||||
end
|
||||
|
||||
def ensure_permission_to_change_accesses
|
||||
if trying_to_change_accesses? && !Current.user.can_administer_board?(@board)
|
||||
def ensure_permission_to_admin_board
|
||||
unless Current.user.can_administer_board?(@board)
|
||||
head :forbidden
|
||||
end
|
||||
end
|
||||
|
||||
def trying_to_change_accesses?
|
||||
all_access_changed? || grantees_changed?
|
||||
end
|
||||
|
||||
def all_access_changed?
|
||||
params[:board]&.key?(:all_access)
|
||||
end
|
||||
|
||||
def grantees_changed?
|
||||
params.key?(:user_ids)
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@ class Cards::AssignmentsController < ApplicationController
|
||||
|
||||
def new
|
||||
@users = @board.users.active.alphabetically
|
||||
fresh_when @users
|
||||
fresh_when etag: [ @users, @card.assignees ]
|
||||
end
|
||||
|
||||
def create
|
||||
|
||||
@@ -3,7 +3,7 @@ class Cards::TaggingsController < ApplicationController
|
||||
|
||||
def new
|
||||
@tags = Tag.all.alphabetically
|
||||
fresh_when @tags
|
||||
fresh_when etag: [ @tags, @card.tags ]
|
||||
end
|
||||
|
||||
def create
|
||||
|
||||
@@ -9,4 +9,10 @@ module BoardScoped
|
||||
def set_board
|
||||
@board = Current.user.boards.find(params[:board_id])
|
||||
end
|
||||
|
||||
def ensure_permission_to_admin_board
|
||||
unless Current.user.can_administer_board?(@board)
|
||||
head :forbidden
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
class Memberships::EmailAddressesController < ApplicationController
|
||||
require_untenanted_access
|
||||
|
||||
layout "public"
|
||||
|
||||
before_action :set_membership
|
||||
rate_limit to: 5, within: 1.hour, only: :create
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
module BoardsHelper
|
||||
def link_back_to_board(board)
|
||||
link_to board, class: "btn borderless txt-medium",
|
||||
data: { controller: "hotkey", action: "keydown.b@document->hotkey#click click->turbo-navigation#backIfSamePath" } do
|
||||
tag.span ("←" + tag.strong(board.name, class: "overflow-ellipsis") + tag.kbd("b", class: "txt-x-small margin-inline-start hide-on-touch")).html_safe
|
||||
data: { controller: "hotkey", action: "keydown.left@document->hotkey#click click->turbo-navigation#backIfSamePath" } do
|
||||
tag.span(tag.strong("Back to #{board.name}", class: "overflow-ellipsis") + tag.kbd("←", class: "txt-x-small margin-inline-start hide-on-touch"), class: "flex align-center").html_safe
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module ClipboardHelper
|
||||
def button_to_copy_to_clipboard(url, &)
|
||||
tag.button class: "btn", data: {
|
||||
controller: "copy-to-clipboard", action: "copy-to-clipboard#copy",
|
||||
controller: "copy-to-clipboard tooltip", action: "copy-to-clipboard#copy",
|
||||
copy_to_clipboard_success_class: "btn--success", copy_to_clipboard_content_value: url
|
||||
}, &
|
||||
end
|
||||
|
||||
@@ -18,7 +18,7 @@ module User::Role
|
||||
admin? || other == self
|
||||
end
|
||||
|
||||
def can_administer?(other = nil)
|
||||
def can_administer?(other)
|
||||
admin? && other != self
|
||||
end
|
||||
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to account_join_code_path, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">← <strong>Invite link</strong></span>
|
||||
<%= link_to account_join_code_path, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.left@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis flex align-center">
|
||||
<strong>Back to Invite link</strong>
|
||||
<kbd class="txt-x-small margin-inline-start hide-on-touch">←</kbd>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to account_settings_path, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">← <strong>Account Settings</strong></span>
|
||||
<%= link_to account_settings_path, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.left@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis flex align-center">
|
||||
<strong>Back to Account Settings</strong>
|
||||
<kbd class="txt-x-small margin-inline-start hide-on-touch">←</kbd>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -18,7 +21,7 @@
|
||||
<div class="flex align-center gap-half">
|
||||
<input type="text" class="input flex-item-grow" value="<%= url %>" readonly>
|
||||
|
||||
<% if Current.user.can_administer? %>
|
||||
<% if Current.user.admin? %>
|
||||
<%= button_to account_join_code_path, method: :delete, class: "btn btn--circle txt-small", data: {
|
||||
turbo_confirm: "Are you sure you want to generate a new link? The previous code will stop working." } do %>
|
||||
<%= icon_tag "refresh" %>
|
||||
@@ -62,7 +65,7 @@
|
||||
This code has been used <%= @join_code.usage_count %>/<%= @join_code.usage_limit %> times
|
||||
(<%= @join_code.active? ? @join_code.usage_limit - @join_code.usage_count : "none" %> remaining)
|
||||
|
||||
<% if Current.user.can_administer? %>
|
||||
<% if Current.user.admin? %>
|
||||
<%= link_to edit_account_join_code_path, class: @join_code.active? ? "txt-link" : "txt-negative txt-underline" do %>
|
||||
<span>Change limit</span>
|
||||
<% end %>
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
<p class="margin-none-block-start">Fizzy doesn’t let stale cards stick around forever. Cards automatically close as “Not Now” without activity for specific period of time. <em>This is the default, global setting — you can override it on each board.</em></p>
|
||||
</header>
|
||||
|
||||
<%= render "entropy/auto_close", model: account.entropy, url: account_entropy_path, disabled: !Current.user.can_administer? %>
|
||||
<%= render "entropy/auto_close", model: account.entropy, url: account_entropy_path, disabled: !Current.user.admin? %>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<%= form_with model: account, url: account_settings_path, method: :put, scope: :account, data: { controller: "form" }, class: "flex gap-half" do |form| %>
|
||||
<strong class="full-width"><%= form.text_field :name, required: true, class: "input input--transparent full-width txt-medium", placeholder: "Account name…", data: { action: "input->form#disableSubmitWhenInvalid" }, readonly: !Current.user.can_administer? %></strong>
|
||||
<strong class="full-width"><%= form.text_field :name, required: true, class: "input input--transparent full-width txt-medium", placeholder: "Account name…", data: { action: "input->form#disableSubmitWhenInvalid" }, readonly: !Current.user.admin? %></strong>
|
||||
|
||||
<% if Current.user.can_administer? %>
|
||||
<% if Current.user.admin? %>
|
||||
<%= form.button class: "btn btn--circle btn--link txt-medium", data: { form_target: "submit" }, disabled: form.object do %>
|
||||
<%= icon_tag "arrow-right" %>
|
||||
<span class="for-screen-reader">Save changes</span>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<% content_for :header do %>
|
||||
<h1 class="header__title">
|
||||
<%= @page_title %>
|
||||
<% unless Current.user.can_administer?%>
|
||||
<% unless Current.user.admin? %>
|
||||
<div class="txt-normal font-weight-normal">Only admins can change these settings</div>
|
||||
<% end %>
|
||||
</h1>
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to @board, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">
|
||||
←
|
||||
<strong><%= @board.name %></strong>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= link_back_to_board(@board) %>
|
||||
</div>
|
||||
|
||||
<h1 class="header__title divider divider--fade full-width">
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to @board, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">
|
||||
←
|
||||
<strong><%= @board.name %></strong>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= link_back_to_board(@board) %>
|
||||
</div>
|
||||
|
||||
<h1 class="header__title divider divider--fade full-width">
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to @column.board, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">
|
||||
←
|
||||
<strong><%= @column.board.name %></strong>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= link_back_to_board(@column.board) %>
|
||||
</div>
|
||||
|
||||
<h1 class="header__title divider divider--fade full-width">
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to @board, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">
|
||||
←
|
||||
<strong><%= @board.name %></strong>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= link_back_to_board(@board) %>
|
||||
</div>
|
||||
|
||||
<h1 class="header__title divider divider--fade full-width">
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to @board, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">
|
||||
←
|
||||
<strong><%= @board.name %></strong>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= link_back_to_board(@board) %>
|
||||
</div>
|
||||
|
||||
<h1 class="header__title">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<div class="cards__new-column position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside" data-turbo-permanent>
|
||||
<button class="btn btn--circle txt-x-small borderless" data-action="click->dialog#open:stop" aria-label="Add a new column, etc.">
|
||||
<button class="btn btn--circle txt-x-small borderless" data-controller="tooltip" data-action="click->dialog#open:stop">
|
||||
<%= icon_tag "add", class: "translucent" %>
|
||||
<span class="for-screen-reader">Add a column</span>
|
||||
</button>
|
||||
<dialog class="popup panel flex-column gap-half fill-white shadow txt-small margin-block-double" data-dialog-target="dialog">
|
||||
<%= render "boards/show/menu/column_form", board: board, column: Column.new, label: "Add column" %>
|
||||
|
||||
@@ -13,13 +13,16 @@
|
||||
data: { local_save_target: "input", action: "lexxy:change->form#disableSubmitWhenInvalid lexxy:change->local-save#save turbo:morph-element->local-save#restoreContent" } do %>
|
||||
<%= general_prompts(@card.board) %>
|
||||
<% end %>
|
||||
<%= form.button class: "comment__submit btn btn--reversed flex-item-justify-start",
|
||||
data: { form_target: "submit" }, disabled: true do %>
|
||||
<span>Post this comment</span>
|
||||
<span class="hide-on-touch">
|
||||
|
||||
<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>
|
||||
<% end %>
|
||||
<span class="hide-on-touch txt-subtle">
|
||||
<kbd class="txt-x-small"><%= hotkey_label([ "ctrl", "enter" ]) %></kbd>
|
||||
</span>
|
||||
<% end %>
|
||||
</span>
|
||||
<span data-form-target="cancel" hidden></span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= link_to "Edit", edit_card_path(card), class: "btn btn--reversed fit-content", style: "--btn-background: #{card.color}" %>
|
||||
<%= link_to edit_card_path(card), class: "btn fit-content txt-small", data: { controller: "hotkey", action: "keydown.e@document->hotkey#click" } do %>
|
||||
<span>Edit</span>
|
||||
<kbd class="txt-x-small hide-on-touch">e</kbd>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= form_with model: card, id: "card_form", data: { controller: "autoresize auto-save" } do |form| %>
|
||||
@@ -23,7 +26,7 @@
|
||||
<% end %>
|
||||
</h1>
|
||||
|
||||
<%= form.rich_textarea :description, class: "card-field__description rich-text-content",
|
||||
<%= form.rich_textarea :description, class: "card__description rich-text-content",
|
||||
placeholder: "Add some notes, context, pictures, or video about this…",
|
||||
data: { action: "lexxy:change->auto-save#change focusout->auto-save#submit" } do %>
|
||||
<%= general_prompts(card.board) %>
|
||||
|
||||
@@ -9,13 +9,16 @@
|
||||
<% end %>
|
||||
</h1>
|
||||
|
||||
<%= form.rich_textarea :description, class: "card-field__description rich-text-content",
|
||||
<%= form.rich_textarea :description, class: "card__description rich-text-content",
|
||||
placeholder: "Add some notes, context, pictures, or video about this…",
|
||||
data: { local_save_target: "input", action: "lexxy:change->local-save#save turbo:morph-element->local-save#restoreContent keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel:stop" } do %>
|
||||
<%= general_prompts(@card.board) %>
|
||||
<% end %>
|
||||
|
||||
<%= form.button "Save changes", type: :submit, class: "btn btn--reversed fit-content", style: "--btn-background: #{@card.color}" %>
|
||||
<%= form.button type: :submit, class: "btn btn--reversed fit-content txt-small", data: { form_target: "submit" } do %>
|
||||
<span>Save changes</span>
|
||||
<kbd class="txt-x-small hide-on-touch"><%= hotkey_label([ "ctrl", "enter" ]) %></kdb>
|
||||
<% end %>
|
||||
<%= link_to "Close editor and discard changes", @card, data: { form_target: "cancel" }, hidden: true %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<div id="filter-settings-save-toggle">
|
||||
<% if filter.persisted? %>
|
||||
<%= button_to filter_path(filter), method: :delete, class: "btn txt-x-small btn--reversed", form_class: "inline" do %>
|
||||
<%= button_to filter_path(filter), method: :delete, class: "btn txt-x-small btn--reversed", data: { controller: "tooltip" }, form_class: "inline" do %>
|
||||
<%= icon_tag "bookmark-outline" %>
|
||||
<span class="for-screen-reader">Remove saved filter</span>
|
||||
<span class="for-screen-reader">Delete custom view</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= button_to filters_path(filter.as_params), method: :post, class: "btn txt-x-small", form_class: "inline" do %>
|
||||
<%= button_to filters_path(filter.as_params), method: :post, class: "btn txt-x-small", data: { controller: "tooltip" }, form_class: "inline" do %>
|
||||
<%= icon_tag "bookmark-outline" %>
|
||||
<span class="for-screen-reader">Save filter</span>
|
||||
<span class="for-screen-reader">Save custom view</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="filters__manage gap-half">
|
||||
<%= render "filters/filter_toggle", filter: filter %>
|
||||
|
||||
<%= link_to no_filtering_url, class: "btn btn--remove txt-x-small", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click"} do %>
|
||||
<%= link_to no_filtering_url, class: "btn btn--remove txt-x-small", data: { controller: "hotkey tooltip", action: "keydown.esc@document->hotkey#click"} do %>
|
||||
<%= icon_tag "close" %>
|
||||
<span class="for-screen-reader">Clear all</span>
|
||||
<% end %>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<% filter = user_filtering.filter %>
|
||||
|
||||
<% if user_filtering.expanded? %>
|
||||
<button type="button" class="btn txt-x-small btn--reversed" aria-selected="true" aria-label="Close filter options" data-action="toggle-class#toggle toggle-enable#toggle">
|
||||
<button type="button" class="btn txt-x-small btn--reversed" aria-selected="true" data-action="toggle-class#toggle toggle-enable#toggle" data-controller="tooltip">
|
||||
<%= icon_tag "filter" %>
|
||||
<span class="for-screen-reader">Close filter options</span>
|
||||
</button>
|
||||
<% else %>
|
||||
<button type="button" class="btn txt-x-small filter-toggle" aria-label="Expand filter options" data-action="toggle-class#toggle toggle-enable#toggle">
|
||||
<button type="button" class="btn txt-x-small filter-toggle" data-action="toggle-class#toggle toggle-enable#toggle" data-controller="tooltip">
|
||||
<%= icon_tag "filter" %>
|
||||
<span class="for-screen-reader">Expand filter options</span>
|
||||
</button>
|
||||
<% end %>
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
<% @page_title = "Confirm your new email address" %>
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__title">
|
||||
<%= link_to edit_user_path(@membership.user, script_name: "/#{@tenant}"), class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">← <strong>My profile</strong></span>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to edit_user_path(@membership.user, script_name: "/#{@tenant}"), class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.left@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis flex align-center">
|
||||
<strong>Back to My profile</strong>
|
||||
<kbd class="txt-x-small margin-inline-start hide-on-touch">←</kbd>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="panel shadow center flex flex-column gap-half" style="--panel-size: 45ch;">
|
||||
<div class="panel panel--centered flex flex-column gap-half">
|
||||
<h1 class="txt-x-large font-weight-black margin-none">Check your email</h1>
|
||||
<p class="margin-none">We just sent an email to <strong><%= params[:email_address] %></strong></p>
|
||||
<p class="margin-none-block-start">Hit the link in the email to confirm this is the email address you want to use with Fizzy going forward.</p>
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
<% @page_title = "Change your email" %>
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__title">
|
||||
<%= link_to edit_user_path(@membership.user, script_name: "/#{@membership.tenant}"), class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">← <strong>My profile</strong></span>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to edit_user_path(@membership.user, script_name: "/#{@membership.tenant}"), class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.left@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis flex align-center">
|
||||
<strong>Back to My profile</strong>
|
||||
<kbd class="txt-x-small margin-inline-start hide-on-touch">←</kbd>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="panel shadow center flex flex-column gap" style="--panel-size: 45ch;">
|
||||
<header>
|
||||
<h1 class="txt-x-large font-weight-black margin-none">
|
||||
<%= @page_title %>
|
||||
</h1>
|
||||
<p class="margin-none">Enter your new email address, then hit the link to confirm it</p>
|
||||
</header>
|
||||
<div class="panel panel--centered flex flex-column gap-half">
|
||||
<h1 class="txt-x-large font-weight-black margin-none">
|
||||
<%= @page_title %>
|
||||
</h1>
|
||||
|
||||
<%= form_with url: email_addresses_path(membership_id: @membership.id), method: :post, class: "flex flex-column gap", data: { controller: "form", turbo: false } do |form| %>
|
||||
<div class="flex align-center gap">
|
||||
<%= form_with url: email_addresses_path(membership_id: @membership.id), method: :post, class: "flex flex-column gap-half", data: { controller: "form", turbo: false } do |form| %>
|
||||
<div class="flex align-center gap-haf">
|
||||
<label class="flex align-center gap input input--actor">
|
||||
<%= form.email_field :email_address, class: "input full-width", autocomplete: "email", placeholder: "New email address", autofocus: true, required: true %>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<p class="margin-none">Enter your new email address, then hit the link to confirm it</p>
|
||||
|
||||
<button type="submit" class="btn btn--link center" data-form-target="submit">
|
||||
<span>Continue</span>
|
||||
<%= icon_tag "arrow-right" %>
|
||||
|
||||
@@ -17,9 +17,6 @@
|
||||
<%= filter_hotkey_link "Home", root_path, 1, "home" %>
|
||||
<%= filter_hotkey_link "Assigned to me", cards_path(assignee_ids: [Current.user.id]), 2, "clipboard" %>
|
||||
<%= filter_hotkey_link "Added by me", cards_path(creator_ids: [Current.user.id]), 3, "person-add" %>
|
||||
<%= filter_hotkey_link "Notifications", notifications_path, 4, "bell" %>
|
||||
<%= filter_hotkey_link "My Profile", user_path(Current.user), 5, "person" %>
|
||||
<%= filter_hotkey_link "Settings", account_settings_path, 6, "settings" %>
|
||||
</div>
|
||||
|
||||
<%= yield %>
|
||||
|
||||
@@ -1,20 +1,5 @@
|
||||
<%= turbo_frame_tag "my_menu", target: "_top" do %>
|
||||
<%= render "my/menus/jump" do %>
|
||||
<%= collapsible_nav_section "Saved filters", id: "my-filters" do %>
|
||||
<%= form_with url: cards_path, method: :get, data: { controller: "form" } do |form| %>
|
||||
<li class="popup__item overflow-ellipsis" data-navigable-list-target="item" data-filter-target="item" id="filter-custom-create">
|
||||
<%= icon_tag "bookmark", class: "popup__icon" %>
|
||||
<%= link_to cards_path(expand_all: true), class: "popup__btn btn" do %>
|
||||
<span>Add a filter</span>
|
||||
<% end %>
|
||||
</li>
|
||||
|
||||
<% @filters.each do |filter| %>
|
||||
<%= my_menu_filter_item(filter) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= collapsible_nav_section "Boards" do %>
|
||||
<li class="popup__item" data-filter-target="item" data-navigable-list-target="item">
|
||||
<%= icon_tag "add", class: "popup__icon" %>
|
||||
@@ -28,6 +13,21 @@
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= collapsible_nav_section "Custom views", id: "my-filters" do %>
|
||||
<%= form_with url: cards_path, method: :get, data: { controller: "form" } do |form| %>
|
||||
<li class="popup__item overflow-ellipsis" data-navigable-list-target="item" data-filter-target="item" id="filter-custom-create">
|
||||
<%= icon_tag "bookmark", class: "popup__icon" %>
|
||||
<%= link_to cards_path(expand_all: true), class: "popup__btn btn" do %>
|
||||
<span>Create a custom view</span>
|
||||
<% end %>
|
||||
</li>
|
||||
|
||||
<% @filters.each do |filter| %>
|
||||
<%= my_menu_filter_item(filter) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= collapsible_nav_section "Tags" do %>
|
||||
<% @tags.each do |tag| %>
|
||||
<%= my_menu_tag_item(tag) %>
|
||||
@@ -47,10 +47,10 @@
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= collapsible_nav_section "Jump to…" do %>
|
||||
<%= collapsible_nav_section "Settings" do %>
|
||||
<%= filter_place_menu_item account_settings_path, "Account Settings", "settings" %>
|
||||
<%= filter_place_menu_item user_path(Current.user), "My Profile", "person" %>
|
||||
<%= filter_place_menu_item notifications_path, "Notifications", "bell" %>
|
||||
<%= filter_place_menu_item notifications_path, "All notifications", "bell" %>
|
||||
<%= filter_place_menu_item notifications_settings_path, "Notification Settings", "settings" %>
|
||||
|
||||
<%= tag.li class: "popup__item", data: { filter_target: "item", navigable_list_target: "item" } do %>
|
||||
@@ -65,6 +65,6 @@
|
||||
<% end %>
|
||||
|
||||
<footer class="nav__footer">
|
||||
<strong><%= link_to "Fizzy", "https://www.fizzy.do", class: "txt-ink", target: "_blank" %></strong> is designed, built, and backed by <strong class="txt-nowrap"><%= icon_tag "37signals", class: "v-align-middle", target: "_blank" %> <a href="https://37signals.com" target="_blank" class="txt-ink">37signals</a></strong>
|
||||
<strong><%= link_to "Fizzy™", "https://www.fizzy.do", class: "txt-ink", target: "_blank" %></strong> is designed, built, and backed by <strong class="txt-nowrap"><%= icon_tag "37signals", class: "v-align-middle", target: "_blank" %> <a href="https://37signals.com" target="_blank" class="txt-ink">37signals™</a></strong>
|
||||
</footer>
|
||||
<% end %>
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to published_board_url(@card.board), class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.left@document->hotkey#click" } do %>
|
||||
<span class="flex align-center">
|
||||
<strong class="overflow-ellipsis">Back to <%= @card.board.name %></strong>
|
||||
<kbd class="txt-x-small hide-on-touch margin-inline-start">←</kbd>
|
||||
<%= link_to published_board_url(@card.board), class: "btn borderless txt-medium", style: "view-transistion-name: card-board-title;", data: { controller: "hotkey", action: "keydown.left@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis flex align-center">
|
||||
<strong>Back to <%= @card.board.name %></strong>
|
||||
<kbd class="txt-x-small margin-inline-start hide-on-touch">←</kbd>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="txt-align-center center margin-block-double txt-subtle txt-small">
|
||||
<strong><%= link_to "Fizzy", "https://www.fizzy.do", class: "txt-subtle" %></strong> is designed, built, and backed by <strong class="txt-nowrap"><%= icon_tag "37signals", class: "v-align-middle" %> <a href="https://37signals.com" target="_blank" class="txt-subtle">37signals</a></strong>.
|
||||
<div class="pad-inline txt-align-center center margin-block-double txt-subtle txt-small">
|
||||
<strong><%= link_to "Fizzy™", "https://www.fizzy.do", class: "txt-subtle" %></strong> is designed, built, and backed by <strong class="txt-nowrap"><%= icon_tag "37signals", class: "v-align-middle" %> <a href="https://37signals.com" target="_blank" class="txt-subtle">37signals™</a></strong>.
|
||||
Need help? <%= mail_to "support@fizzy.do", "Send us an email", class: "txt-link" %>
|
||||
</div>
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="panel panel--centered flex flex-column gap-half <%= "shake" if flash[:alert] %>">
|
||||
<header>
|
||||
<h1 class="txt-x-large font-weight-black margin-none"><%= @page_title %></h1>
|
||||
<p class="margin-none-block-start txt-medium">Then enter the verfifcation code included in the email below:</p>
|
||||
<p class="margin-none-block-start txt-medium">Then enter the verification code included in the email below:</p>
|
||||
</header>
|
||||
|
||||
<%= form_with url: session_magic_link_path, method: :post, html: { data: { controller: token_list("form", "auto-submit" => params[:code].present?)} } do |form| %>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<% @page_title = "Enter your email" %>
|
||||
|
||||
<div class="panel panel--centered flex flex-column gap-half" style="--panel-size: 65ch;">
|
||||
<div class="panel panel--centered flex flex-column gap-half">
|
||||
<h1 class="txt-x-large font-weight-black margin-block-end">Let’s get you in to Fizzy</h1>
|
||||
|
||||
<%= form_with url: session_path, class: "flex flex-column gap txt-medium" do |form| %>
|
||||
<%= form_with url: session_path, class: "flex flex-column gap-half txt-medium" do |form| %>
|
||||
<div class="flex align-center gap">
|
||||
<label class="flex align-center gap input input--actor">
|
||||
<%= form.email_field :email_address, required: true, class: "input txt-large full-width", autofocus: true, autocomplete: "username", placeholder: "Enter your email address…" %>
|
||||
|
||||
@@ -12,18 +12,18 @@
|
||||
|
||||
<div class="flex align-center gap">
|
||||
<div data-controller="dialog" data-dialog-modal-value="true" class="flex-inline">
|
||||
<%= tag.button class: "btn", data: { action: "dialog#open" } do %>
|
||||
<%= tag.button class: "btn", data: { action: "dialog#open", controller: "tooltip" } do %>
|
||||
<%= icon_tag "qr-code" %>
|
||||
<span class="for-screen-reader">Show auto-login QR code</span>
|
||||
<span class="for-screen-reader">Display auto-login QR code</span>
|
||||
<% end %>
|
||||
|
||||
<dialog class="dialog panel shadow" data-dialog-target="dialog">
|
||||
<%= qr_code_image(url) %>
|
||||
|
||||
<form method="dialog" class="margin-block-start flex justify-center">
|
||||
<button class="btn">
|
||||
<button class="btn" data-controller="tooltip">
|
||||
<%= icon_tag "remove" %>
|
||||
<span class="for-screen-reader">Close</span>
|
||||
<span class="for-screen-reader">Close dialog</span>
|
||||
</button>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
<% @page_title = "Edit your profile" %>
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to user_path(@user), class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.left@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis flex align-center">
|
||||
<strong>Back to profile</strong>
|
||||
<kbd class="txt-x-small margin-inline-start hide-on-touch">←</kbd>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="panel shadow center" style="--panel-size: 45ch;">
|
||||
<div class="flex flex-column gap txt-medium">
|
||||
<%= form_with model: @user, method: :patch, class: "flex flex-column gap", data: { controller: "form upload-preview" } do |form| %>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<section class="panel shadow txt-align-center" style="--panel-size: 45ch;">
|
||||
<div class="flex flex-column gap position-relative">
|
||||
<% if Current.user == @user %>
|
||||
<%= link_to edit_user_path(@user), class: "user-edit-link btn" do %>
|
||||
<%= link_to edit_user_path(@user), class: "user-edit-link btn", data: { controller: "tooltip" } do %>
|
||||
<%= icon_tag "pencil" %>
|
||||
<span class="for-screen-reader">Edit</span>
|
||||
<span class="for-screen-reader">Edit profile</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
</div>
|
||||
|
||||
<div class="flex-inline center justify-center flex-wrap gap">
|
||||
<%= link_to "Which cards are assigned to #{me_or_you}?",
|
||||
<%= link_to "Which cards are assigned to #{me_or_you}?",
|
||||
cards_path(assignee_ids: [ @user.id ], sorted_by: "newest"), class: "btn", data: { turbo_frame: "_top" } %>
|
||||
<%= link_to "Which cards were added by #{me_or_you}?",
|
||||
<%= link_to "Which cards were added by #{me_or_you}?",
|
||||
cards_path(creator_ids: [ @user.id ], sorted_by: "newest"), class: "btn", data: { turbo_frame: "_top" } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to @webhook, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">
|
||||
←
|
||||
<strong><%= @page_title %></strong>
|
||||
<%= link_to @webhook, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.left@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis flex align-center">
|
||||
<strong>Back to <%= @page_title %></strong>
|
||||
<kbd class="txt-x-small margin-inline-start hide-on-touch">←</kbd>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -2,35 +2,24 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to @board, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">
|
||||
←
|
||||
<strong><%= @board.name %></strong>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= link_back_to_board(@board) %>
|
||||
</div>
|
||||
|
||||
<h1 class="header__title"><%= @page_title %></h1>
|
||||
|
||||
<div class="header__actions header__actions--end">
|
||||
<%= link_to new_board_webhook_path, class: "btn" do %>
|
||||
<%= icon_tag "add" %>
|
||||
<span class="for-screen-reader">Create a new webhook</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= tag.section class: "panel shadow center webhooks" do %>
|
||||
<% if @page.records.any? %>
|
||||
<ul class="flex flex-column align-start gap margin-none unpad webhooks-list">
|
||||
<ul class="flex flex-column align-start gap margin-none-block-start unpad webhooks-list">
|
||||
<%= render partial: "webhooks/webhook", collection: @page.records %>
|
||||
</ul>
|
||||
<% else %>
|
||||
<p>Webhooks can notify another application when something happens in this Fizzy board. You’ll choose which events to subscribe to and provide a URL to receive the data.</p>
|
||||
<p>For example, you could create a webhook that posts to a Campfire chat in Basecamp when new cards are added to Fizzy.</p>
|
||||
<%= link_to new_board_webhook_path, class: "btn btn--link" do %>
|
||||
<%= icon_tag "add" %>
|
||||
<span>Set up a webhook</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= link_to new_board_webhook_path, class: "btn btn--link" do %>
|
||||
<%= icon_tag "add" %>
|
||||
<span>Set up a new webhook</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to board_webhooks_path, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">
|
||||
←
|
||||
<strong>Webhooks</strong>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= link_back_to_board(@board) %>
|
||||
</div>
|
||||
|
||||
<h1 class="header__title"><%= @page_title %></h1>
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to board_webhooks_path, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">
|
||||
←
|
||||
<strong>Webhooks</strong>
|
||||
<%= link_to board_webhooks_path, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.left@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis flex align-center">
|
||||
<strong>Back to Webhooks</strong>
|
||||
<kbd class="txt-x-small margin-inline-start hide-on-touch">←</kbd>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -12,4 +12,11 @@ class Account::EntropiesControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
assert_redirected_to account_settings_path
|
||||
end
|
||||
|
||||
test "update requires admin" do
|
||||
logout_and_sign_in_as :david
|
||||
|
||||
put account_entropy_path, params: { entropy: { auto_postpone_period: 1.day } }
|
||||
assert_response :forbidden
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,4 +23,18 @@ class Account::JoinCodesControllerTest < ActionDispatch::IntegrationTest
|
||||
assert_equal 5, Account::JoinCode.sole.usage_limit
|
||||
assert_redirected_to account_join_code_path
|
||||
end
|
||||
|
||||
test "update requires admin" do
|
||||
logout_and_sign_in_as :david
|
||||
|
||||
put account_join_code_path, params: { account_join_code: { usage_limit: 5 } }
|
||||
assert_response :forbidden
|
||||
end
|
||||
|
||||
test "destroy requires admin" do
|
||||
logout_and_sign_in_as :david
|
||||
|
||||
delete account_join_code_path
|
||||
assert_response :forbidden
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,4 +15,11 @@ class Account::SettingsControllerTest < ActionDispatch::IntegrationTest
|
||||
assert_equal "New Account Name", Account.sole.name
|
||||
assert_redirected_to account_settings_path
|
||||
end
|
||||
|
||||
test "update requires admin" do
|
||||
logout_and_sign_in_as :david
|
||||
|
||||
put account_settings_path, params: { account: { name: "New Account Name" } }
|
||||
assert_response :forbidden
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,4 +13,15 @@ class Boards::EntropiesControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
assert_turbo_stream action: :replace, target: dom_id(@board, :entropy)
|
||||
end
|
||||
|
||||
test "update requires board admin permission" do
|
||||
logout_and_sign_in_as :jz
|
||||
|
||||
original_period = @board.entropy.auto_postpone_period
|
||||
|
||||
put board_entropy_path(@board, format: :turbo_stream), params: { board: { auto_postpone_period: 1.day } }
|
||||
|
||||
assert_response :forbidden
|
||||
assert_equal original_period, @board.entropy.reload.auto_postpone_period
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,9 +21,32 @@ class Boards::PublicationsControllerTest < ActionDispatch::IntegrationTest
|
||||
assert @board.published?
|
||||
|
||||
assert_changes -> { @board.reload.published? }, from: true, to: false do
|
||||
delete board_publication_path(@board, format: :turbo_streamn)
|
||||
delete board_publication_path(@board, format: :turbo_stream)
|
||||
end
|
||||
|
||||
assert_turbo_stream action: :replace, target: dom_id(@board, :publication)
|
||||
end
|
||||
|
||||
test "publish requires board admin permission" do
|
||||
logout_and_sign_in_as :jz
|
||||
|
||||
assert_not @board.published?
|
||||
|
||||
post board_publication_path(@board, format: :turbo_stream)
|
||||
|
||||
assert_response :forbidden
|
||||
assert_not @board.reload.published?
|
||||
end
|
||||
|
||||
test "unpublish requires board admin permission" do
|
||||
logout_and_sign_in_as :jz
|
||||
|
||||
@board.publish
|
||||
assert @board.published?
|
||||
|
||||
delete board_publication_path(@board, format: :turbo_stream)
|
||||
|
||||
assert_response :forbidden
|
||||
assert @board.reload.published?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -122,4 +122,18 @@ class BoardsControllerTest < ActionDispatch::IntegrationTest
|
||||
assert_response :forbidden
|
||||
assert_equal original_users, board.reload.users.sort
|
||||
end
|
||||
|
||||
test "non-admin cannot change board name on board they don't own" do
|
||||
logout_and_sign_in_as :jz
|
||||
|
||||
board = boards(:writebook)
|
||||
original_name = board.name
|
||||
|
||||
patch board_path(board), params: {
|
||||
board: { name: "Hacked Board Name" }
|
||||
}
|
||||
|
||||
assert_response :forbidden
|
||||
assert_equal original_name, board.reload.name
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,7 +27,7 @@ class SmokeTest < ApplicationSystemTestCase
|
||||
assert_selector "figcaption input[placeholder='moon.jpg']"
|
||||
end
|
||||
|
||||
click_on "Post this comment"
|
||||
click_on "Post"
|
||||
|
||||
within("action-text-attachment") do
|
||||
assert_selector "a img[src*='/rails/active_storage']"
|
||||
|
||||
Reference in New Issue
Block a user