Merge branch 'main' into consolidate-cards

* main: (31 commits)
  Style drafted cards
  Considering needs to include drafted cards or you'll never be able to see them
  This item makes no sense until it's saved
  Tighten engagement switch design, only display it for published cards
  Needed to come first
  In order of appearance
  Schema movement -- is this because the dumper doesnt sort deterministically? cc @flavorjones
  Move comments under cards
  Fix indention
  Refresh timers when morphing happens
  Extract helper for card articles, use classes to determine when to show golden status
  Only consider golden those cards in "doing"
  Fix recently completed order for good!
  Place recently closed cards first
  Monkey patch a prepend_order method
  Rename inconsistency: go with goldness
  Touch
  Use gild/ungild
  Format
  Fix test
  ...
This commit is contained in:
Andy Smith
2025-04-11 12:43:05 -05:00
52 changed files with 458 additions and 214 deletions
+1
View File
@@ -0,0 +1 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m15.58 16.8-3.58-2.3-3.58 2.3 1.08-4.12-3.29-2.68 4.25-.26 1.54-3.94 1.54 3.94 4.25.26-3.29 2.68m5.5-.68c0-1.11.9-2 2-2v-4c0-1.11-.9-2-2-2h-16a2 2 0 0 0 -2 2v4c1.11 0 2 .9 2 2a2 2 0 0 1 -2 2v4a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-4a2 2 0 0 1 -2-2z"/></svg>

After

Width:  |  Height:  |  Size: 320 B

-1
View File
@@ -1 +0,0 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m12 18.5 7.5 4.5-2-8.5 6.6-5.7-8.7-.8-3.4-8-3.4 8-8.7.8 6.6 5.7-2 8.5z"/></svg>

Before

Width:  |  Height:  |  Size: 148 B

+2
View File
@@ -42,6 +42,7 @@
--lch-blue: 54% 0.15 255;
--lch-blue-light: 95% 0.03 255;
--lch-blue-dark: 80% 0.08 255;
--lch-golden: 83% 0.1128 72.43;
--lch-orange: 70% 0.2 44;
--lch-red: 51% 0.2 31;
--lch-green: 65.59% 0.234 142.49;
@@ -69,6 +70,7 @@
--color-always-black: oklch(var(--lch-always-black));
--color-always-white: oklch(var(--lch-always-white));
--color-container: oklch(var(--lch-light-blue));
--color-golden: oklch(var(--lch-golden));
@media (prefers-color-scheme: dark) {
--lch-black: 100% 0 0;
+33 -1
View File
@@ -109,7 +109,7 @@
display: none;
}
}
&:has(input:checked) {
--btn-background: var(--color-ink);
--btn-color: var(--color-ink-reversed);
@@ -188,5 +188,37 @@
animation: zoom-fade 300ms ease-out;
}
}
.btn__group {
.btn {
--btn-border-radius: 0;
inline-size: 100%;
justify-content: center;
}
form {
flex: 1 1 0%;
}
:first-of-type .btn {
border-bottom-left-radius: 2em;
border-inline-end: 0;
border-top-left-radius: 2em;
flex: 1 0 50%;
padding-inline-end: 0.8em;
}
:last-of-type .btn {
border-bottom-right-radius: 2em;
border-inline-start: 0;
border-top-right-radius: 2em;
flex: 1 0 50%;
padding-inline-start: 0.8em;
}
span {
inline-size: 100%;
}
}
}
+11
View File
@@ -28,6 +28,17 @@
}
}
.card--golden.card--doing {
--border-color: var(--color-bg) !important;
background-color: var(--color-bg);
background-image: linear-gradient(90deg, oklch(var(--lch-golden) / 0.5) 0%, oklch(var(--lch-golden) / 0.1) 50%, oklch(var(--lch-golden) / 0.75) 100%);
box-shadow:
0 0 0.5em 0.2em var(--color-golden),
0 0 1em 0.4em var(--color-golden) !important;
outline: 1px solid var(--color-golden);
}
.card__background {
inset: 0;
position: absolute;
+1 -1
View File
@@ -38,6 +38,7 @@
.icon--everyone { --svg: url("everyone.svg "); }
.icon--filter { --svg: url("filter.svg "); }
.icon--globe { --svg: url("globe.svg "); }
.icon--golden-ticket { --svg: url("golden-ticket.svg "); }
.icon--home { --svg: url("home.svg "); }
.icon--logout { --svg: url("logout.svg "); }
.icon--menu { --svg: url("menu.svg "); }
@@ -61,7 +62,6 @@
.icon--rename { --svg: url("rename.svg "); }
.icon--settings { --svg: url("settings.svg "); }
.icon--share { --svg: url("share.svg "); }
.icon--star { --svg: url("star.svg "); }
.icon--tag { --svg: url("tag.svg "); }
.icon--thumb-up { --svg: url("thumb-up.svg "); }
.icon--trash { --svg: url("trash.svg "); }
@@ -1,4 +1,4 @@
class Comments::ReactionsController < ApplicationController
class Cards::Comments::ReactionsController < ApplicationController
before_action :set_comment
def index
@@ -12,7 +12,7 @@ class Comments::ReactionsController < ApplicationController
@reaction = @comment.reactions.create!(reaction_params)
broadcast_create
redirect_to collection_card_comment_reactions_url(@comment.card.collection, @comment.card, @comment)
redirect_to card_comment_reactions_url(@comment.card, @comment)
end
def destroy
@@ -33,7 +33,7 @@ class Comments::ReactionsController < ApplicationController
def broadcast_create
@reaction.broadcast_append_to @reaction.comment, :comments,
target: "reactions_comment_#{@comment.id}", partial: "comments/reactions/reaction", locals: { comment: @comment }
target: "reactions_comment_#{@comment.id}", partial: "cards/comments/reactions/reaction", locals: { comment: @comment }
end
def broadcast_remove
@@ -1,4 +1,4 @@
class CommentsController < ApplicationController
class Cards::CommentsController < ApplicationController
include CardScoped
before_action :set_comment, only: [ :show, :edit, :update, :destroy ]
before_action :require_own_comment, only: [ :edit, :update, :destroy ]
@@ -0,0 +1,13 @@
class Cards::GoldnessesController < ApplicationController
include CardScoped
def create
@card.gild
redirect_to @card
end
def destroy
@card.ungild
redirect_to @card
end
end
+4 -2
View File
@@ -11,7 +11,7 @@ class CardsController < ApplicationController
def index
@considering = page_and_filter_for @filter.with(engagement_status: "considering", indexed_by: "latest"), per_page: PAGE_SIZE
@doing = page_and_filter_for @filter.with(engagement_status: "doing", indexed_by: "latest"), per_page: PAGE_SIZE
@closed = page_and_filter_for @filter.with(indexed_by: "closed"), per_page: PAGE_SIZE
@closed = page_and_filter_for(@filter.with(indexed_by: "closed"), per_page: PAGE_SIZE) { |cards| cards.recently_closed_first }
end
def create
@@ -40,8 +40,10 @@ class CardsController < ApplicationController
end
def page_and_filter_for(filter, per_page: nil)
cards = block_given? ? yield(filter.cards) : filter.cards
OpenStruct.new \
page: GearedPagination::Recordset.new(filter.cards, per_page:).page(1),
page: GearedPagination::Recordset.new(cards, per_page:).page(1),
filter: filter
end
+15
View File
@@ -26,4 +26,19 @@ module CardsHelper
class: "btn txt-small",
**options
end
def card_article_tag(card, id: dom_id(card, :ticket), **options, &block)
classes = [
options.delete(:class),
("card--golden" if card.golden?),
("card--doing" if card.doing?)
].compact.join(" ")
tag.article \
id: id,
style: "--card-color: #{card.color}; view-transition-name: #{id}",
class: classes,
**options,
&block
end
end
@@ -24,6 +24,14 @@ export default class extends Controller {
clearInterval(this.#timer)
}
refreshAll() {
this.constructor.targets.forEach(targetName => {
this.targets.findAll(targetName).forEach(target => {
this.#formatTime(this[`${targetName}Formatter`], target)
})
})
}
timeTargetConnected(target) {
this.#formatTime(this.timeFormatter, target)
}
+4 -3
View File
@@ -1,6 +1,7 @@
class Card < ApplicationRecord
include Assignable, Boostable, Colored, Commentable, Engageable, Eventable,
Messages, Notifiable, Pinnable, Closeable, Scorable, Searchable, Staged, Statuses, Taggable, Watchable
include Assignable, Boostable, Colored, Commentable, Engageable, Eventable, Golden,
Messages, Notifiable, Pinnable, Closeable, Scorable, Searchable, Staged,
Statuses, Taggable, Watchable
belongs_to :collection, touch: true
belongs_to :creator, class_name: "User", default: -> { Current.user }
@@ -34,7 +35,7 @@ class Card < ApplicationRecord
scope :by_engagement_status, ->(status) do
case status.to_s
when "considering" then considering
when "doing" then doing
when "doing" then doing.with_golden_first
end
end
+1 -1
View File
@@ -7,7 +7,7 @@ module Card::Engageable
has_one :engagement, dependent: :destroy, class_name: "Card::Engagement"
scope :doing, -> { published.active.joins(:engagement) }
scope :considering, -> { published.active.where.missing(:engagement) }
scope :considering, -> { published_or_drafted_by(Current.user).active.where.missing(:engagement) }
scope :stagnated, -> { doing.where(last_active_at: ..STAGNATED_AFTER.ago) }
end
+23
View File
@@ -0,0 +1,23 @@
module Card::Golden
extend ActiveSupport::Concern
included do
scope :golden, -> { joins(:goldness) }
has_one :goldness, dependent: :destroy, class_name: "Card::Goldness"
scope :with_golden_first, -> { left_outer_joins(:goldness).prepend_order("card_goldnesses.id IS NULL") }
end
def golden?
goldness.present?
end
def gild
create_goldness! unless golden?
end
def ungild
goldness&.destroy
end
end
+3
View File
@@ -0,0 +1,3 @@
class Card::Goldness < ApplicationRecord
belongs_to :card, touch: true
end
+18 -15
View File
@@ -18,23 +18,26 @@ class Comment < ApplicationRecord
card_comments.many? && card_comments_prior.last&.creator != creator
end
private
def to_partial_path
"cards/#{super}"
end
def cleanup_events
# Delete events that reference through event_summary
if message&.event_summary.present?
Event.where(summary: message.event_summary).destroy_all
private
def cleanup_events
# Delete events that reference through event_summary
if message&.event_summary.present?
Event.where(summary: message.event_summary).destroy_all
end
# Delete events that reference directly in particulars
Event.where(particulars: { comment_id: id }).destroy_all
end
# Delete events that reference directly in particulars
Event.where(particulars: { comment_id: id }).destroy_all
end
def card_comments_prior
card_comments.where(created_at: ...created_at)
end
def card_comments_prior
card_comments.where(created_at: ...created_at)
end
def card_comments
Comment.joins(:message).where(messages: { card: card })
end
def card_comments
Comment.joins(:message).where(messages: { card: card })
end
end
+11
View File
@@ -0,0 +1,11 @@
<% if card.golden? %>
<%= button_to card_goldness_path(card), method: :delete, class: "btn btn--reversed" do %>
<%= icon_tag "golden-ticket" %>
<span class="for-screen-reader">Demote to normal</span>
<% end %>
<% else %>
<%= button_to card_goldness_path(card), class: "btn" do %>
<%= icon_tag "golden-ticket" %>
<span class="for-screen-reader">Promote to golden</span>
<% end %>
<% end %>
+1 -1
View File
@@ -1,6 +1,6 @@
<%= messages_tag(card) do %>
<%= render partial: "messages/message", collection: card.messages, cached: true %>
<%= render "comments/new", card: card, cached: true %>
<%= render "cards/comments/new", card: card, cached: true %>
<div class="comments__subscribers flex flex-column margin-block txt-align-start full-width">
<strong class="txt-uppercase">Subscribers</strong>
@@ -6,11 +6,11 @@
<%= link_to comment.creator.name, user_path(comment.creator), class: "txt-ink btn btn--plain fill-transparent", data: { turbo_frame: "_top" } %>
</strong>
<%= link_to collection_card_path(comment.card.collection, comment.card, anchor: "comment_#{comment.id}"), class: "txt-undecorated txt-uppercase" do %>
<%= link_to card_path(comment.card, anchor: "comment_#{comment.id}"), class: "txt-undecorated txt-uppercase" do %>
<%= local_datetime_tag comment.created_at, style: :shortdate, class: "txt-ink translucent" %>
<% end %>
<%= link_to edit_collection_card_comment_path(comment.card.collection, comment.card, comment),
<%= link_to edit_card_comment_path(comment.card, comment),
class: "comment__edit btn btn--plain txt-xx-small fill-transparent translucent flex-item-justify-end" do %>
<%= icon_tag "menu-dots-horizontal" %>
<span class="for-screen-reader">Edit this comment</span>
@@ -20,7 +20,7 @@
<%= sanitize comment.body_html %>
</div>
<%= render "comments/reactions/reactions", comment: comment %>
<%= render "cards/comments/reactions/reactions", comment: comment %>
</div>
<% end %>
<% end %>
@@ -3,5 +3,5 @@
<%= avatar_tag comment.creator, hidden_for_screen_reader: true %>
</figure>
<%= render "comments/body", comment: comment %>
<%= render "cards/comments/body", comment: comment %>
<% end %>
@@ -5,7 +5,7 @@
<div class="comment__content flex-inline flex-column full-width">
<div class="comment__body txt-align-start">
<%= form_with model: Comment.new, url: collection_card_comments_path(card.collection, card), class: "flex flex-column gap full-width",
<%= form_with model: Comment.new, url: card_comments_path(card), class: "flex flex-column gap full-width",
data: { controller: "form paste local-save",
local_save_key_value: "comment-#{card.id}",
action: "turbo:submit-end->local-save#submit keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel:stop paste->paste#pasteFiles" } do |form| %>
@@ -1,14 +1,14 @@
<%= turbo_frame_tag dom_id(@comment) do %>
<div class="comment__content comment--edit flex-inline flex-column full-width">
<div class="comment__body txt-align-start">
<%= form_with model: [@card.collection, @card, @comment], class: "flex flex-column gap full-width",
<%= form_with model: [ @card, @comment ], class: "flex flex-column gap full-width",
data: { controller: "form paste", action: "keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel:stop paste->paste#pasteFiles" } do |form| %>
<%= form.markdown_area :body, class: "input comment__input", required: true, autofocus: true, placeholder: new_comment_placeholder(@card) %>
<div class="flex gap-half justify-start">
<%= form.button class: "btn btn--reversed", type: :submit do %>
<span>Save changes</span>
<% end %>
<%= link_to collection_card_comment_path(@card.collection, @card, @comment), class: "btn", data: { form_target: "cancel" } do %>
<%= link_to card_comment_path(@card, @comment), class: "btn", data: { form_target: "cancel" } do %>
<span">Cancel</span>
<% end %>
@@ -20,7 +20,7 @@
</div>
<% end %>
<%= form_with url: collection_card_comment_path(@card.collection, @card, @comment), method: :delete, id: dom_id(@comment, :delete_form), data: { turbo_frame: "_top" } %>
<%= form_with url: card_comment_path(@card, @comment), method: :delete, id: dom_id(@comment, :delete_form), data: { turbo_frame: "_top" } %>
</div>
</div>
<% end %>
@@ -13,7 +13,7 @@
class: [ "txt-small", { "txt-medium": reaction.all_emoji? } ],
data: { action: "click->reaction-delete#reveal keydown.enter->reaction-delete#reveal:prevent", reaction_delete_target: "content" } %>
<%= button_to collection_card_comment_reaction_path(comment.card.collection, comment.card, comment, reaction),
<%= button_to card_comment_reaction_path(comment.card, comment, reaction),
method: :delete,
class: "btn btn--negative flex-item-justify-end reaction__delete",
data: { action: "reaction-delete#perform", reaction_delete_target: "button" } do %>
@@ -2,12 +2,12 @@
<%= turbo_stream_from comment, :comments %>
<div class="reactions flex flex-wrap align-center gap full-width">
<div class="flex-inline flex-wrap gap" id="<%= dom_id(comment, :reactions) %>" data-turbo-streaming-target="container">
<%= render partial: "comments/reactions/reaction", collection: comment.reactions.ordered, locals: { comment: comment }, cached: true %>
<%= render partial: "cards/comments/reactions/reaction", collection: comment.reactions.ordered, locals: { comment: comment }, cached: true %>
</div>
<%= turbo_frame_tag comment, :new_reaction do %>
<div class="flex-inline" data-controller="soft-keyboard">
<%= link_to new_collection_card_comment_reaction_path(comment.card.collection, comment.card, comment), role: "button",
<%= link_to new_card_comment_reaction_path(comment.card, comment), role: "button",
class: "btn reaction__action", action: "soft-keyboard#open" do %>
<%= icon_tag "reaction" %>
<span class="for-screen-reader">Add a reaction</span>
@@ -0,0 +1 @@
<%= render "cards/comments/reactions/reactions", comment: @comment %>
@@ -1,5 +1,5 @@
<%= turbo_frame_tag dom_id(@comment, :new_reaction) do %>
<%= form_with url: collection_card_comment_reactions_path(@comment.card.collection, @comment.card, @comment),
<%= form_with url: card_comment_reactions_path(@comment.card, @comment),
class: "reaction reaction__form flex-inline postion--relative max-width align-center fill-white gap expanded",
html: { aria: { label: "New reaction" } },
data: { controller: "form", turbo_frame: dom_id(@comment, :reacting), action: "keydown.esc->form#cancel" } do |form| %>
@@ -17,7 +17,7 @@
<span class="for-screen-reader">Submit</span>
<% end %>
<%= link_to collection_card_comment_reactions_path(@comment.card.collection, @comment.card, @comment), role: "button",
<%= link_to card_comment_reactions_path(@comment.card, @comment), role: "button",
data: { turbo_frame: dom_id(@comment, :reactions), form_target: "cancel" }, class: "btn btn--negative" do %>
<%= icon_tag "minus" %>
<span class="for-screen-reader">Cancel</span>
+1
View File
@@ -0,0 +1 @@
<%= render "cards/comments/body", comment: @comment %>
+1
View File
@@ -0,0 +1 @@
<%= render "cards/comments/body", comment: @comment %>
+2 -2
View File
@@ -1,5 +1,5 @@
<% cache card do %>
<article class="card" style="--card-color: <%= card.color %>; view-transition-name: <%= dom_id(card, :ticket) %>;" id="<%= dom_id(card, :ticket) %>">
<%= card_article_tag card, class: "card" do %>
<header class="card__header flex align-center gap min-width">
<%= link_to card.collection.name, cards_path(collection_ids: [card.collection]),
class: "card__collection txt-uppercase overflow-ellipsis txt-reversed" %>
@@ -35,5 +35,5 @@
</footer>
<%= render "cards/display/common/background", card: card %>
</article>
<% end %>
<% end %>
+2 -4
View File
@@ -1,7 +1,5 @@
<% cache [ card ] do %>
<article class="card"
style="--card-color: <%= card.color %>; view-transition-name: <%= dom_id(card, :ticket) %>;"
id="<%= dom_id(card, :ticket) %>">
<%= card_article_tag card, class: "card" do %>
<header class="card__header flex align-center gap min-width">
<span class="card__collection txt-uppercase overflow-ellipsis txt-reversed"><%= card.collection.name %></span>
<%= render "cards/display/preview/tags", card: card %>
@@ -24,5 +22,5 @@
</footer>
<%= render "cards/display/common/background", card: card %>
</article>
<% end %>
<% end %>
@@ -4,10 +4,17 @@
<div class="card__meta-grid">
<span class="card__meta-item card__meta-item--added overflow-ellipsis">
Added <%= local_datetime_tag(card.created_at, style: :daysago) %>
<% if card.drafted? %>
<strong>(Draft)</strong>
<% end %>
</span>
<span class="card__meta-item card__meta-item--updated overflow-ellipsis">
<%= card.creating? ? "Created" : "Updated" %>
<%= local_datetime_tag(card.updated_at, style: :daysago) %>
<% if card.creating? %>
&nbsp;
<% else %>
Updated
<%= local_datetime_tag(card.updated_at, style: :daysago) %>
<% end %>
</span>
<span class="card__meta-item card__meta-item--reported overflow-ellipsis">
Reported by <strong><%= familiar_name_for(card.creator) %></strong>
+1 -5
View File
@@ -31,11 +31,7 @@
<%= render "cards/doing_toggle", card: @card %>
<aside class="card-perma__actions card-perma__actions--left" role="toolbar">
<button class="btn">
<%= icon_tag "star" %>
<span class="for-screen-reader">Star this card</span>
<input type="checkbox" class="card-perma__star-input">
</button>
<%= render "cards/golden_toggle", card: @card if @card.doing? %>
<%= render "cards/image", card: @card %>
<%= button_to collection_card_path(@card.collection, @card),
method: :delete,
@@ -1 +0,0 @@
<%= render "comments/reactions/reactions", comment: @comment %>
-1
View File
@@ -1 +0,0 @@
<%= render "comments/body", comment: @comment %>
-1
View File
@@ -1 +0,0 @@
<%= render "comments/body", comment: @comment %>
+1 -1
View File
@@ -25,7 +25,7 @@
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
</head>
<body data-controller="lightbox local-time timezone-cookie">
<body data-controller="lightbox local-time timezone-cookie" data-action="turbo:morph@window->local-time#refreshAll">
<header id="header">
<a href="#main-content" class="skip-navigation btn">Skip to main content</a>
<%= yield :header %>
+1 -1
View File
@@ -1,5 +1,5 @@
<% cache message do %>
<%# Template Dependency: comments/comment %>
<%# Template Dependency: cards/comments/comment %>
<%# Template Dependency: event_summaries/event_summary %>
<%= render partial: message.messageable.to_partial_path, object: message.messageable %>
<% end %>
+30 -24
View File
@@ -8,6 +8,16 @@ Rails.application.routes.draw do
end
end
resources :collections do
scope module: :collections do
resource :subscriptions
resource :workflow, only: :update
resource :involvement
end
resources :cards
end
namespace :cards do
resources :previews
end
@@ -22,14 +32,20 @@ Rails.application.routes.draw do
resource :reading
resource :recover
resource :watch
resource :goldness
resources :assignments
resources :boosts
resources :stagings
resources :taggings
resources :comments do
resources :reactions, module: :comments
end
end
end
resources :notifications, only: :index
namespace :notifications do
resource :tray, only: :show
@@ -44,19 +60,7 @@ Rails.application.routes.draw do
end
end
resources :collections do
scope module: :collections do
resource :subscriptions
resource :workflow, only: :update
resource :involvement
end
resources :cards do
resources :comments do
resources :reactions, module: :comments
end
end
end
resources :filters
resources :events, only: :index
namespace :events do
@@ -67,12 +71,15 @@ Rails.application.routes.draw do
resources :stages, module: :workflows
end
resources :filters
resources :qr_codes
resources :uploads, only: :create
get "/u/*slug" => "uploads#show", as: :upload
namespace :my do
resources :pins
end
resource :terminal, only: [ :show, :edit ]
resources :qr_codes
get "join/:join_code", to: "users#new", as: :join
post "join/:join_code", to: "users#create"
resource :session do
scope module: "sessions" do
@@ -86,8 +93,10 @@ Rails.application.routes.draw do
end
end
get "join/:join_code", to: "users#new", as: :join
post "join/:join_code", to: "users#create"
namespace :my do
resources :pins
end
resolve "Card" do |card, options|
route_for :collection_card, card.collection, card, options
@@ -98,13 +107,10 @@ Rails.application.routes.draw do
route_for :collection_card, comment.card.collection, comment.card, options
end
get "up", to: "rails/health#show", as: :rails_health_check
get "manifest" => "rails/pwa#manifest", as: :pwa_manifest
resources :uploads, only: :create
get "/u/*slug" => "uploads#show", as: :upload
resource :terminal, only: [ :show, :edit ]
root "events#index"
end
@@ -0,0 +1,9 @@
class CreateCardGoldnesses < ActiveRecord::Migration[8.1]
def change
create_table :card_goldnesses do |t|
t.references :card, null: false, foreign_key: true, index: { unique: true }
t.timestamps
end
end
end
Generated
+9 -1
View File
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2025_04_09_201424) do
ActiveRecord::Schema[8.1].define(version: 2025_04_10_141409) do
create_table "accesses", force: :cascade do |t|
t.integer "collection_id", null: false
t.datetime "created_at", null: false
@@ -102,6 +102,13 @@ ActiveRecord::Schema[8.1].define(version: 2025_04_09_201424) do
t.index ["card_id"], name: "index_card_engagements_on_card_id"
end
create_table "card_goldnesses", force: :cascade do |t|
t.integer "card_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["card_id"], name: "index_card_goldnesses_on_card_id", unique: true
end
create_table "cards", force: :cascade do |t|
t.float "activity_score", default: 0.0, null: false
t.datetime "activity_score_at"
@@ -333,6 +340,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_04_09_201424) do
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "card_goldnesses", "cards"
add_foreign_key "cards", "workflow_stages", column: "stage_id"
add_foreign_key "closures", "cards"
add_foreign_key "closures", "users"
+96 -72
View File
@@ -21,7 +21,7 @@ columns:
default_function:
collation:
comment:
- &23 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- &24 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: collection_id
cast_type: *1
@@ -50,26 +50,6 @@ columns:
default_function:
collation:
comment:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: involvement
cast_type: &7 !ruby/object:ActiveModel::Type::String
true: t
false: f
precision:
scale:
limit:
sql_type_metadata: &8 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: varchar
type: :string
limit:
precision:
scale:
'null': false
default: watching
default_function:
collation:
comment:
- &9 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: updated_at
@@ -90,6 +70,26 @@ columns:
default_function:
collation:
comment:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: involvement
cast_type: &7 !ruby/object:ActiveModel::Type::String
true: t
false: f
precision:
scale:
limit:
sql_type_metadata: &8 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: varchar
type: :string
limit:
precision:
scale:
'null': false
default: watching
default_function:
collation:
comment:
accounts:
- *5
- *6
@@ -341,7 +341,7 @@ columns:
- *5
- *19
- *20
- &24 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- &21 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: card_id
cast_type: *1
@@ -367,16 +367,21 @@ columns:
comment:
- *6
- *9
card_goldnesses:
- *5
- *21
- *6
- *9
cards:
- *5
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: activity_score
cast_type: &21 !ruby/object:ActiveModel::Type::Float
cast_type: &22 !ruby/object:ActiveModel::Type::Float
precision:
scale:
limit:
sql_type_metadata: &22 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type_metadata: &23 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: float
type: :float
limit:
@@ -400,8 +405,8 @@ columns:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: activity_score_order
cast_type: *21
sql_type_metadata: *22
cast_type: *22
sql_type_metadata: *23
'null': false
default: 0.0
default_function:
@@ -417,7 +422,7 @@ columns:
default_function:
collation:
comment:
- *23
- *24
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: comments_count
@@ -458,16 +463,6 @@ columns:
default_function:
collation:
comment:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: last_active_at
cast_type: *3
sql_type_metadata: *4
'null': false
default:
default_function:
collation:
comment:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: stage_id
@@ -499,6 +494,16 @@ columns:
collation:
comment:
- *9
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: last_active_at
cast_type: *3
sql_type_metadata: *4
'null': false
default:
default_function:
collation:
comment:
closure_reasons:
- *5
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
@@ -511,7 +516,6 @@ columns:
default_function:
collation:
comment:
- *6
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: label
@@ -522,21 +526,12 @@ columns:
default_function:
collation:
comment:
- *6
- *9
closures:
- *5
- *24
- *21
- *6
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: reason
cast_type: *7
sql_type_metadata: *8
'null': false
default:
default_function:
collation:
comment:
- *9
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
@@ -548,6 +543,16 @@ columns:
default_function:
collation:
comment:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: reason
cast_type: *7
sql_type_metadata: *8
'null': false
default:
default_function:
collation:
comment:
collections:
- *5
- &32 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
@@ -593,7 +598,7 @@ columns:
collation:
comment:
collections_filters:
- *23
- *24
- *18
comments:
- *5
@@ -619,7 +624,7 @@ columns:
default_function:
collation:
comment:
- *24
- *21
- *6
- *25
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
@@ -712,7 +717,7 @@ columns:
comment:
messages:
- *5
- *24
- *21
- *6
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
@@ -737,7 +742,7 @@ columns:
- *9
notifications:
- *5
- *24
- *21
- *6
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
@@ -783,7 +788,7 @@ columns:
- *30
pins:
- *5
- *24
- *21
- *6
- *9
- *30
@@ -869,7 +874,7 @@ columns:
- *30
taggings:
- *5
- *24
- *21
- *6
- *31
- *9
@@ -927,7 +932,7 @@ columns:
- *9
watches:
- *5
- *24
- *21
- *6
- *9
- *30
@@ -984,6 +989,7 @@ primary_keys:
assigners_filters:
assignments: id
card_engagements: id
card_goldnesses: id
cards: id
closure_reasons: id
closures: id
@@ -1020,6 +1026,7 @@ data_sources:
assigners_filters: true
assignments: true
card_engagements: true
card_goldnesses: true
cards: true
closure_reasons: true
closures: true
@@ -1351,6 +1358,23 @@ indexes:
nulls_not_distinct:
comment:
valid: true
card_goldnesses:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: card_goldnesses
name: index_card_goldnesses_on_card_id
unique: true
columns:
- card_id
lengths: {}
orders: {}
opclasses: {}
where:
type:
using:
include:
nulls_not_distinct:
comment:
valid: true
cards:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: cards
@@ -1386,11 +1410,10 @@ indexes:
valid: true
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: cards
name: index_cards_on_last_active_at_and_status
name: index_cards_on_stage_id
unique: false
columns:
- last_active_at
- status
- stage_id
lengths: {}
orders: {}
opclasses: {}
@@ -1403,10 +1426,11 @@ indexes:
valid: true
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: cards
name: index_cards_on_stage_id
name: index_cards_on_last_active_at_and_status
unique: false
columns:
- stage_id
- last_active_at
- status
lengths: {}
orders: {}
opclasses: {}
@@ -1437,10 +1461,11 @@ indexes:
closures:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: closures
name: index_closures_on_card_id
unique: true
name: index_closures_on_card_id_and_created_at
unique: false
columns:
- card_id
- created_at
lengths: {}
orders: {}
opclasses: {}
@@ -1453,11 +1478,10 @@ indexes:
valid: true
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: closures
name: index_closures_on_card_id_and_created_at
unique: false
name: index_closures_on_card_id
unique: true
columns:
- card_id
- created_at
lengths: {}
orders: {}
opclasses: {}
@@ -1993,10 +2017,11 @@ indexes:
tags:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: tags
name: index_tags_on_account_id
unique: false
name: index_tags_on_account_id_and_title
unique: true
columns:
- account_id
- title
lengths: {}
orders: {}
opclasses: {}
@@ -2009,11 +2034,10 @@ indexes:
valid: true
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: tags
name: index_tags_on_account_id_and_title
unique: true
name: index_tags_on_account_id
unique: false
columns:
- account_id
- title
lengths: {}
orders: {}
opclasses: {}
@@ -2140,4 +2164,4 @@ indexes:
nulls_not_distinct:
comment:
valid: true
version: 20250409201424
version: 20250410141409
+16
View File
@@ -0,0 +1,16 @@
module ActiveRecordRelationPrependOrder
extend ActiveSupport::Concern
included do
def prepend_order(*args)
new_orders = args.flatten.map { |arg| arg.is_a?(String) ? arg : arg.to_sql }
spawn.tap do |relation|
relation.order_values = new_orders + order_values
end
end
end
end
ActiveRecord::Relation.include(ActiveRecordRelationPrependOrder)
ActiveRecord::AssociationRelation.include(ActiveRecordRelationPrependOrder)
@@ -0,0 +1,26 @@
require "test_helper"
class Cards::Comments::ReactionsControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in_as :jz
@comment = comments(:logo_agreement_jz)
end
test "create" do
assert_turbo_stream_broadcasts [ @comment, :comments ], count: 1 do
assert_difference -> { @comment.reactions.count }, 1 do
post card_comment_reactions_url(@comment.card, @comment, format: :turbo_stream), params: { reaction: { content: "Great work!" } }
assert_redirected_to card_comment_reactions_url(@comment.card, @comment)
end
end
end
test "destroy" do
assert_turbo_stream_broadcasts [ @comment, :comments ], count: 1 do
assert_difference -> { @comment.reactions.count }, -1 do
delete card_comment_reaction_url(@comment.card, @comment, reactions(:kevin), format: :turbo_stream)
assert_response :success
end
end
end
end
@@ -0,0 +1,30 @@
require "test_helper"
class Cards::CommentsControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in_as :kevin
end
test "create" do
assert_difference "cards(:logo).messages.comments.count", +1 do
post card_comments_url(cards(:logo), params: { comment: { body: "Agreed." } })
end
assert_response :success
end
test "update" do
put card_comment_url(cards(:logo), comments(:logo_agreement_kevin)), params: { comment: { body: "I've changed my mind" } }
assert_response :success
assert_equal "I've changed my mind", comments(:logo_agreement_kevin).reload.body.content
end
test "update another user's comment" do
assert_no_changes "comments(:logo_agreement_jz).body.content" do
put card_comment_url(cards(:logo), comments(:logo_agreement_jz)), params: { comment: { body: "I've changed my mind" } }
end
assert_response :forbidden
end
end
@@ -0,0 +1,27 @@
require "test_helper"
class Cards::GoldnessesControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in_as :kevin
end
test "create" do
card = cards(:text)
assert_changes -> { card.reload.golden? }, from: false, to: true do
post card_goldness_url(card)
end
assert_redirected_to collection_card_url(card.collection, card)
end
test "destroy" do
card = cards(:logo)
assert_changes -> { card.reload.golden? }, from: true, to: false do
delete card_goldness_url(card)
end
assert_redirected_to collection_card_url(card.collection, card)
end
end
@@ -1,30 +0,0 @@
require "test_helper"
class CommentsControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in_as :kevin
end
test "create" do
assert_difference "cards(:logo).messages.comments.count", +1 do
post collection_card_comments_url(collections(:writebook), cards(:logo), params: { comment: { body: "Agreed." } })
end
assert_response :success
end
test "update" do
put collection_card_comment_url(collections(:writebook), cards(:logo), comments(:logo_agreement_kevin)), params: { comment: { body: "I've changed my mind" } }
assert_response :success
assert_equal "I've changed my mind", comments(:logo_agreement_kevin).reload.body.content
end
test "update another user's comment" do
assert_no_changes "comments(:logo_agreement_jz).body.content" do
put collection_card_comment_url(collections(:writebook), cards(:logo), comments(:logo_agreement_jz)), params: { comment: { body: "I've changed my mind" } }
end
assert_response :forbidden
end
end
@@ -1,26 +0,0 @@
require "test_helper"
class Comments::ReactionsControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in_as :jz
@comment = comments(:logo_agreement_jz)
end
test "create" do
assert_turbo_stream_broadcasts [ @comment, :comments ], count: 1 do
assert_difference -> { @comment.reactions.count }, 1 do
post collection_card_comment_reactions_url(@comment.card.collection, @comment.card, @comment, format: :turbo_stream), params: { reaction: { content: "Great work!" } }
assert_redirected_to collection_card_comment_reactions_url(@comment.card.collection, @comment.card, @comment)
end
end
end
test "destroy" do
assert_turbo_stream_broadcasts [ @comment, :comments ], count: 1 do
assert_difference -> { @comment.reactions.count }, -1 do
delete collection_card_comment_reaction_url(@comment.card.collection, @comment.card, @comment, reactions(:kevin), format: :turbo_stream)
assert_response :success
end
end
end
end
+1 -1
View File
@@ -1,2 +1,2 @@
logo_engagement:
logo:
card: logo
+2
View File
@@ -0,0 +1,2 @@
logo:
card: logo
+27
View File
@@ -0,0 +1,27 @@
require "test_helper"
class Card::GoldenTest < ActiveSupport::TestCase
setup do
Current.session = sessions(:david)
end
test "check whether a card is golden" do
assert cards(:logo).golden?
assert_not cards(:text).golden?
end
test "promote and demote from golden" do
assert_changes -> { cards(:text).reload.golden? }, to: true do
cards(:text).gild
end
assert_changes -> { cards(:logo).reload.golden? }, to: false do
cards(:logo).ungild
end
end
test "scopes" do
assert_includes Card.golden, cards(:logo)
assert_not_includes Card.golden, cards(:text)
end
end