Merge branch 'main' into plain-text-mentions
* main: (48 commits) Improve layout and truncation This is annoying Fix summary Remove index columns Same treatment for the events index New menu filters and navigates Don't test for _Added by_ events Indention @collection is always available when card scoped Style Remove check for _Added by..._ line The placeholder attribute is on the `house-md` element Duh, no before destroy commit, but this is still not right! No long transactions! Don't display published event in threads But feels more essentially part of the User, so list first Not related to the other two Missing controller Style Errant `bubbles` => `cards` replacement ... # Conflicts: # app/models/card.rb # app/models/user.rb # db/schema.rb # db/schema_cache.yml
This commit is contained in:
+9
-9
@@ -223,24 +223,24 @@ GEM
|
||||
net-protocol
|
||||
net-ssh (7.3.0)
|
||||
nio4r (2.7.4)
|
||||
nokogiri (1.18.7)
|
||||
nokogiri (1.18.8)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-aarch64-linux-gnu)
|
||||
nokogiri (1.18.8-aarch64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-aarch64-linux-musl)
|
||||
nokogiri (1.18.8-aarch64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-arm-linux-gnu)
|
||||
nokogiri (1.18.8-arm-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-arm-linux-musl)
|
||||
nokogiri (1.18.8-arm-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-arm64-darwin)
|
||||
nokogiri (1.18.8-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-x86_64-darwin)
|
||||
nokogiri (1.18.8-x86_64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-x86_64-linux-gnu)
|
||||
nokogiri (1.18.8-x86_64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-x86_64-linux-musl)
|
||||
nokogiri (1.18.8-x86_64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
ostruct (0.6.1)
|
||||
parallel (1.26.3)
|
||||
|
||||
@@ -116,6 +116,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.card__content {
|
||||
flex: 2 1 auto;
|
||||
}
|
||||
|
||||
.card__title {
|
||||
--hover-size: 0;
|
||||
--input-border-radius: 0;
|
||||
@@ -123,7 +127,6 @@
|
||||
--lines: 3;
|
||||
|
||||
color: inherit;
|
||||
flex: 2 1 75%;
|
||||
font-size: var(--text-xx-large);
|
||||
font-weight: 900;
|
||||
line-height: 1.2;
|
||||
@@ -148,10 +151,11 @@
|
||||
|
||||
.card__stages {
|
||||
display: flex;
|
||||
flex: 1 1 25%;
|
||||
flex: 0 1 auto;
|
||||
flex-direction: column;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
justify-self: end;
|
||||
max-inline-size: 20ch;
|
||||
min-inline-size: 15ch;
|
||||
padding-block: var(--block-space-half);
|
||||
}
|
||||
|
||||
@@ -214,10 +218,10 @@
|
||||
flex-direction: column;
|
||||
inset: auto var(--block-space) var(--block-space-double) auto;
|
||||
justify-content: space-between;
|
||||
mix-blend-mode: multiply;
|
||||
padding: var(--block-space-half) calc(var(--inline-space) * 1.5);
|
||||
position: absolute;
|
||||
transform: rotate(-10deg);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.card__closed-by {
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
--column-gap: clamp(var(--inline-space), 3cqmin, calc(var(--inline-space) * 3));
|
||||
--grid-lines: 0.1rem;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 5fr 1fr;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@@ -61,15 +59,10 @@
|
||||
}
|
||||
|
||||
.events__popup {
|
||||
.panel:is(&) {
|
||||
max-block-size: calc(100dvh - (2 * var(--block-space-double)));
|
||||
max-inline-size: calc(100dvw - (2 * var(--block-space-double)));
|
||||
}
|
||||
margin-block-start: var(--block-space-double);
|
||||
|
||||
#header:has(&) {
|
||||
max-inline-size: 100%;
|
||||
.events__index-header:has(&) {
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
@layer components {
|
||||
/* Editor */
|
||||
house-md {
|
||||
--placeholder: attr(placeholder);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
@@ -65,7 +67,7 @@
|
||||
padding: 1rem;
|
||||
|
||||
&.house-md-content-empty::before {
|
||||
content: attr(placeholder);
|
||||
content: var(--placeholder);
|
||||
color: currentColor;
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
|
||||
@@ -94,6 +94,11 @@
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 19.5c-.7 0-1.3-.3-1.7-.8l-9.8-11.1c-.7-.8-.6-1.9.2-2.6.8-.6 1.9-.6 2.5.2l8.6 9.8c0 .1.2.1.4 0l8.6-9.8c.7-.8 1.8-.9 2.6-.2s.9 1.8.2 2.6l-9.8 11.1c-.4.5-1.1.8-1.7.8z' fill='%23fff'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
option {
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
}
|
||||
|
||||
.input--textarea {
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
@layer components {
|
||||
.comments {
|
||||
.markdown-content {
|
||||
:where(h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6) {
|
||||
font-weight: 800;
|
||||
hyphens: auto;
|
||||
@@ -31,6 +31,7 @@
|
||||
--text-xx-large: 2em;
|
||||
|
||||
border: 1px solid var(--color-bg);
|
||||
view-transition-name: unset !important;
|
||||
}
|
||||
|
||||
.card__collection {
|
||||
@@ -42,16 +43,18 @@
|
||||
padding-block-end: 0;
|
||||
}
|
||||
|
||||
.card__link {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.card__title {
|
||||
--lines: 1;
|
||||
|
||||
font-size: var(--text-small);
|
||||
font-weight: bold;
|
||||
min-block-size: 0;
|
||||
}
|
||||
|
||||
.card__title-link {
|
||||
--lines: 1;
|
||||
}
|
||||
|
||||
.card__stages {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,42 @@
|
||||
@layer components {
|
||||
.popup {
|
||||
--panel-border-radius: 0.5em;
|
||||
--panel-padding: 0.4em 0.5em 0.5em;
|
||||
--panel-padding: var(--block-space);
|
||||
--panel-size: auto;
|
||||
|
||||
inline-size: auto;
|
||||
inset: 0 auto auto 0;
|
||||
min-inline-size: 20ch;
|
||||
max-inline-size: 40ch;
|
||||
inset: 0 auto auto 50%;
|
||||
min-inline-size: min(20ch, 90vw);
|
||||
max-inline-size: min(50ch, 90vw);
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
z-index: 2;
|
||||
|
||||
&[open] {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#header:has(&) {
|
||||
max-inline-size: 100%;
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
}
|
||||
}
|
||||
|
||||
.popup__group {
|
||||
background: var(--color-subtle-light);
|
||||
max-inline-size: 100%;
|
||||
padding-inline: var(--inline-space);
|
||||
|
||||
.btn {
|
||||
--btn-background: transparent;
|
||||
}
|
||||
|
||||
@media (any-hover: hover) {
|
||||
&:hover {
|
||||
background: var(--color-selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup__item {
|
||||
@@ -25,17 +48,10 @@
|
||||
--hover-size: 0;
|
||||
--outline-color: transparent;
|
||||
|
||||
flex: 1 1 auto;
|
||||
justify-content: start;
|
||||
|
||||
&::after {
|
||||
border-radius: var(--btn-border-radius);
|
||||
border: var(--btn-border-size) solid var(--btn-border-color, var(--color-subtle));
|
||||
content: "";
|
||||
display: block;
|
||||
inline-size: calc(100% - var(--btn-padding) * 2);
|
||||
block-size: calc(100% - var(--btn-padding) * 2);
|
||||
|
||||
}
|
||||
max-inline-size: 100%;
|
||||
text-align: start;
|
||||
|
||||
@media (any-hover: hover) {
|
||||
&:where(:not(:active):hover) {
|
||||
@@ -78,7 +94,62 @@
|
||||
row-gap: 2px;
|
||||
}
|
||||
|
||||
.popup__new {
|
||||
--btn-icon-size: 1em;
|
||||
|
||||
.icon {
|
||||
margin-inline-end: 0.5em;
|
||||
}
|
||||
|
||||
padding-inline-start: var(--inline-space-half);
|
||||
}
|
||||
|
||||
.popup__title {
|
||||
font-weight: 800;
|
||||
}
|
||||
}
|
||||
|
||||
.popup--animated {
|
||||
--backdrop-speed: 150ms;
|
||||
--panel-size: max-content;
|
||||
--speed: 150ms;
|
||||
|
||||
opacity: 0;
|
||||
transform: scale(0.2) translateX(-50%);
|
||||
transform-origin: top left;
|
||||
transition:
|
||||
display var(--speed) allow-discrete,
|
||||
opacity var(--speed),
|
||||
overlay var(--speed) allow-discrete,
|
||||
transform var(--speed);
|
||||
|
||||
&::backdrop {
|
||||
background-color: var(--color-always-black);
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
transition:
|
||||
display var(--backdrop-speed) allow-discrete,
|
||||
opacity var(--backdrop-speed),
|
||||
overlay var(--backdrop-speed) allow-discrete;
|
||||
}
|
||||
|
||||
&[open] {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateX(-50%);
|
||||
|
||||
&::backdrop {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
@starting-style {
|
||||
&[open] {
|
||||
opacity: 0;
|
||||
transform: scale(0.2) translateX(-50%);
|
||||
}
|
||||
|
||||
&[open]::backdrop {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,13 +27,5 @@
|
||||
background-color: color(from var(--card-color) srgb r g b / 0.65);
|
||||
}
|
||||
}
|
||||
|
||||
.ticket & {
|
||||
--btn-background: var(--color-ink);
|
||||
|
||||
background: var(--card-color);
|
||||
border-radius: 0.2em;
|
||||
color: var(--color-ink-reversed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,6 @@ class Cards::PinsController < ApplicationController
|
||||
end
|
||||
|
||||
def render_pin_button_replacement
|
||||
turbo_stream.replace [ @card, :pin_button ], partial: "cards/pins/pin_button", locals: { card: @card }
|
||||
render turbo_stream: turbo_stream.replace([ @card, :pin_button ], partial: "cards/pins/pin_button", locals: { card: @card })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
class Cards::StagingsController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
before_action :set_stage
|
||||
|
||||
def create
|
||||
if @stage
|
||||
@card.toggle_stage @stage
|
||||
else
|
||||
@card.update!(stage: nil)
|
||||
end
|
||||
|
||||
def update
|
||||
@card.change_stage_to @collection.workflow.stages.find(params[:stage_id])
|
||||
render_card_replacement
|
||||
end
|
||||
|
||||
private
|
||||
def set_stage
|
||||
@stage = Workflow::Stage.find_by(id: params[:stage_id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,7 +15,7 @@ module CardsHelper
|
||||
**options
|
||||
end
|
||||
|
||||
def card_article_tag(card, id: dom_id(card, :ticket), **options, &block)
|
||||
def card_article_tag(card, id: dom_id(card, :article), **options, &block)
|
||||
classes = [
|
||||
options.delete(:class),
|
||||
("card--golden" if card.golden?),
|
||||
|
||||
@@ -2,14 +2,14 @@ module WorkflowsHelper
|
||||
def button_to_set_stage(card, stage)
|
||||
button_to \
|
||||
tag.span(stage.name, class: "overflow-ellipsis"),
|
||||
card_stagings_path(card, stage_id: stage),
|
||||
method: :post,
|
||||
card_staging_path(card, stage_id: stage),
|
||||
method: :put,
|
||||
class: [ "btn justify-start workflow-stage txt-uppercase workflow-stage", { "workflow-stage--current": stage == card.stage } ],
|
||||
form_class: "flex align-center gap-half",
|
||||
data: { turbo_frame: "_top" }
|
||||
end
|
||||
|
||||
def stage_color(stage)
|
||||
stage.color.presence || Colorable::DEFAULT_COLOR
|
||||
stage.color.presence || Card::DEFAULT_COLOR
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,7 +47,7 @@ export default class extends Controller {
|
||||
#triggerChangeEvent(newValue) {
|
||||
if (this.inputTarget.tagName === "HOUSE-MD") {
|
||||
this.inputTarget.dispatchEvent(new CustomEvent('house-md:change', {
|
||||
cards: true,
|
||||
bubbles: true,
|
||||
detail: {
|
||||
previousContent: '',
|
||||
newContent: newValue
|
||||
|
||||
@@ -12,7 +12,7 @@ export default class extends Controller {
|
||||
|
||||
for (const file of files) {
|
||||
const uploadEvent = new CustomEvent("house-md:before-upload", {
|
||||
cards: true,
|
||||
bubbles: true,
|
||||
detail: { file },
|
||||
cancelable: true
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ module Card::Closeable
|
||||
has_one :closure, dependent: :destroy
|
||||
|
||||
scope :closed, -> { joins(:closure) }
|
||||
scope :active, -> { where.missing(:closure) }
|
||||
scope :open, -> { where.missing(:closure) }
|
||||
|
||||
scope :recently_closed_first, -> { closed.order("closures.created_at": :desc) }
|
||||
scope :due_to_be_closed, -> { considering.where(last_active_at: ..AUTO_CLOSE_AFTER.ago) }
|
||||
@@ -29,7 +29,7 @@ module Card::Closeable
|
||||
closure.present?
|
||||
end
|
||||
|
||||
def active?
|
||||
def open?
|
||||
!closed?
|
||||
end
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
module Card::Colored
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
COLORS = %w[ #3b3633 #67695e #eb7a32 #bf7c2b #c09c6f #746b1e #2c6da8 #5d618f #663251 #ff63a8 ]
|
||||
DEFAULT_COLOR = "#2c6da8"
|
||||
|
||||
def color
|
||||
color_from_stage || Colorable::DEFAULT_COLOR
|
||||
color_from_stage || DEFAULT_COLOR
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -6,8 +6,8 @@ module Card::Engageable
|
||||
included do
|
||||
has_one :engagement, dependent: :destroy, class_name: "Card::Engagement"
|
||||
|
||||
scope :considering, -> { published_or_drafted_by(Current.user).active.where.missing(:engagement) }
|
||||
scope :doing, -> { published.active.joins(:engagement) }
|
||||
scope :considering, -> { published_or_drafted_by(Current.user).open.where.missing(:engagement) }
|
||||
scope :doing, -> { published.open.joins(:engagement) }
|
||||
scope :stagnated, -> { doing.where(last_active_at: ..STAGNATED_AFTER.ago) }
|
||||
|
||||
scope :by_engagement_status, ->(status) do
|
||||
@@ -29,11 +29,11 @@ module Card::Engageable
|
||||
end
|
||||
|
||||
def doing?
|
||||
active? && published? && engagement.present?
|
||||
open? && published? && engagement.present?
|
||||
end
|
||||
|
||||
def considering?
|
||||
active? && published? && engagement.blank?
|
||||
open? && published? && engagement.blank?
|
||||
end
|
||||
|
||||
def engage
|
||||
|
||||
@@ -2,10 +2,9 @@ module Card::Golden
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
scope :golden, -> { joins(:goldness) }
|
||||
|
||||
has_one :goldness, dependent: :destroy, class_name: "Card::Goldness"
|
||||
|
||||
scope :golden, -> { joins(:goldness) }
|
||||
scope :with_golden_first, -> { left_outer_joins(:goldness).prepend_order("card_goldnesses.id IS NULL") }
|
||||
end
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ module Card::Staged
|
||||
included do
|
||||
belongs_to :stage, class_name: "Workflow::Stage", optional: true
|
||||
|
||||
after_create :assign_initial_stage
|
||||
before_create :assign_initial_stage
|
||||
|
||||
scope :in_stage, ->(stage) { where stage: stage }
|
||||
end
|
||||
@@ -13,21 +13,15 @@ module Card::Staged
|
||||
stage&.workflow
|
||||
end
|
||||
|
||||
def toggle_stage(stage)
|
||||
new_stage, event = self.stage_id == stage.id ? [ nil, :unstaged ] : [ stage, :staged ]
|
||||
|
||||
def change_stage_to(new_stage)
|
||||
transaction do
|
||||
update! stage: new_stage
|
||||
track_event event, stage_id: stage.id, stage_name: stage.name
|
||||
track_event :staged, stage_id: new_stage.id, stage_name: new_stage.name
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def assign_initial_stage
|
||||
if workflow_stage = collection.workflow&.stages&.first
|
||||
self.stage = workflow_stage
|
||||
save! touch: false
|
||||
track_event :staged, stage_id: workflow_stage.id, stage_name: workflow_stage.name
|
||||
end
|
||||
self.stage = collection.initial_workflow_stage
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
class Collection < ApplicationRecord
|
||||
include Accessible, Broadcastable, Filterable
|
||||
include Accessible, Broadcastable, Filterable, Workflowing
|
||||
|
||||
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
||||
belongs_to :workflow, optional: true
|
||||
|
||||
has_many :cards, dependent: :destroy
|
||||
has_many :tags, -> { distinct }, through: :cards
|
||||
|
||||
after_save :update_cards_workflow, if: :saved_change_to_workflow_id?
|
||||
|
||||
scope :alphabetically, -> { order("lower(name)") }
|
||||
|
||||
private
|
||||
def update_cards_workflow
|
||||
cards.update_all(stage_id: workflow&.stages&.first&.id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
module Collection::Workflowing
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
belongs_to :workflow, optional: true
|
||||
|
||||
after_update_commit :set_all_cards_to_initial_workflow_stage, if: :saved_change_to_workflow_id?
|
||||
end
|
||||
|
||||
def initial_workflow_stage
|
||||
workflow&.stages&.first
|
||||
end
|
||||
|
||||
private
|
||||
def set_all_cards_to_initial_workflow_stage
|
||||
cards.update_all(stage_id: initial_workflow_stage&.id)
|
||||
end
|
||||
end
|
||||
@@ -1,10 +0,0 @@
|
||||
module Colorable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
COLORS = %w[ #3b3633 #67695e #eb7a32 #bf7c2b #c09c6f #746b1e #2c6da8 #5d618f #663251 #ff63a8 ]
|
||||
DEFAULT_COLOR = "#2c6da8"
|
||||
|
||||
included do
|
||||
attribute :color, default: DEFAULT_COLOR
|
||||
end
|
||||
end
|
||||
@@ -9,7 +9,9 @@ module Filterable
|
||||
end
|
||||
|
||||
private
|
||||
# FIXME: This is too inefficient to have part of a destroy transaction.
|
||||
# Need to find a way to use a job or a single query.
|
||||
def remove_from_filters
|
||||
filters.each { |filter| filter.resource_removed self }
|
||||
filters.each { it.resource_removed self }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,8 +13,6 @@ class EventSummary < ApplicationRecord
|
||||
|
||||
def summarize(event)
|
||||
case event.action
|
||||
when "published"
|
||||
"Added by #{event.creator.name}."
|
||||
when "assigned"
|
||||
"Assigned to #{event.assignees.pluck(:name).to_sentence}."
|
||||
when "unassigned"
|
||||
|
||||
@@ -19,7 +19,7 @@ class Filter < ApplicationRecord
|
||||
def cards
|
||||
@cards ||= begin
|
||||
result = creator.accessible_cards.indexed_by(indexed_by)
|
||||
result = result.active unless indexed_by.closed?
|
||||
result = result.open unless indexed_by.closed?
|
||||
result = result.by_engagement_status(engagement_status) if engagement_status.present?
|
||||
result = result.unassigned if assignment_status.unassigned?
|
||||
result = result.assigned_to(assignees.ids) if assignees.present?
|
||||
|
||||
+6
-6
@@ -1,5 +1,7 @@
|
||||
class User < ApplicationRecord
|
||||
include Accessor, Assignee, Avatar, Mentionable, Named, Role, Transferable
|
||||
include Accessor, Assignee, Named, Role, Transferable
|
||||
|
||||
has_one_attached :avatar
|
||||
|
||||
has_many :sessions, dependent: :destroy
|
||||
has_secure_password validations: false
|
||||
@@ -16,11 +18,9 @@ class User < ApplicationRecord
|
||||
normalizes :email_address, with: ->(value) { value.strip.downcase }
|
||||
|
||||
def deactivate
|
||||
transaction do
|
||||
sessions.delete_all
|
||||
accesses.destroy_all
|
||||
update! active: false, email_address: deactived_email_address
|
||||
end
|
||||
sessions.delete_all
|
||||
accesses.destroy_all
|
||||
update! active: false, email_address: deactived_email_address
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
module User::Avatar
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_one_attached :avatar
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def from_avatar_token(sid)
|
||||
find_signed!(sid, purpose: :avatar)
|
||||
end
|
||||
end
|
||||
|
||||
def avatar_token
|
||||
signed_id(purpose: :avatar)
|
||||
end
|
||||
end
|
||||
@@ -7,7 +7,6 @@ class Workflow < ApplicationRecord
|
||||
|
||||
private
|
||||
def create_default_stages
|
||||
Workflow::Stage.insert_all \
|
||||
DEFAULT_STAGES.collect { |default_stage_name| { workflow_id: id, name: default_stage_name } }
|
||||
Workflow::Stage.insert_all(DEFAULT_STAGES.map { { name: it, workflow_id: id } })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<%= render "cards/display/perma/tags", card: card %>
|
||||
</header>
|
||||
|
||||
<div class="card__body flex gap full-width flex-item-grow">
|
||||
<div class="card__body flex gap full-width flex-item-grow justify-space-between">
|
||||
<%= render "cards/container/title", card: card %>
|
||||
<%= render "cards/stagings/stages", card: card if card.doing? %>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<%= turbo_frame_tag @card, :assignment do %>
|
||||
<strong class="popup__title margin-block-start-half pad-inline-half txt-nowrap">Assign this to…</strong>
|
||||
<strong class="popup__title pad-inline-half txt-nowrap">Assign this to…</strong>
|
||||
|
||||
<div class="flex flex-column full-width popup__list">
|
||||
<% @card.collection.users.active.alphabetically.each do |user| %>
|
||||
<% @collection.users.active.alphabetically.each do |user| %>
|
||||
<%= button_to card_assignments_path(@card, params: { assignee_id: user.id }), method: :post, class: "btn popup__item full-width" do %>
|
||||
<span class="overflow-ellipsis"><%= user.name %></span>
|
||||
<%= icon_tag "check", size: 18, class: "checked flex-item-justify-end" if @card.assignees.include?(user) %>
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
<% end %>
|
||||
|
||||
<%= 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 %>
|
||||
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>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="comment__body txt-align-start">
|
||||
<div class="comment__body markdown-content txt-align-start">
|
||||
<%= sanitize comment.body_html %>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</figure>
|
||||
|
||||
<div class="comment__content flex-inline flex-column full-width">
|
||||
<div class="comment__body txt-align-start">
|
||||
<div class="comment__body markdown-content txt-align-start">
|
||||
<%= 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}",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<%= 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">
|
||||
<div class="comment__body markdown-content txt-align-start">
|
||||
<%= 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) %>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<% if card.published? %>
|
||||
<%= turbo_frame_tag card, :edit do %>
|
||||
<div class="full-width">
|
||||
<div class="card__content">
|
||||
<h1 class="card__title flex align-start gap-half">
|
||||
<%= link_to card.title, edit_collection_card_path(card.collection, card), class: "card__title-link" %>
|
||||
</h1>
|
||||
<p class="card__description margin-block-half">
|
||||
<div class="card__description markdown-content margin-block-half">
|
||||
<%= sanitize card.description_html %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= form_with model: card, url: collection_card_path(card.collection, card), id: "card_form", class: "full-width", data: { controller: "auto-save" } do |form| %>
|
||||
<%= form_with model: card, url: collection_card_path(card.collection, card), id: "card_form", class: "card__content", data: { controller: "paste auto-save" } do |form| %>
|
||||
<h1 class="card__title">
|
||||
<%= form.text_area :title, placeholder: "Name it…",
|
||||
class: "input input--textarea full-width borderless txt-align-start",
|
||||
autofocus: card.title.blank?,
|
||||
data: { action: "auto-save#change blur->auto-save#submit keydown.enter->auto-save#submit:prevent keydown.ctrl+enter->auto-save#submit:prevent" } %>
|
||||
</h1>
|
||||
<div class="card__description margin-block-half">
|
||||
<div class="card__description markdown-content margin-block-half">
|
||||
<%= form.markdown_area :description, class: "input input--textarea full-width borderless txt-align-start card-field__description",
|
||||
placeholder: "Add some notes, context, pictures, or video about this…",
|
||||
data: { action: "house-md:change->auto-save#change focusout->auto-save#submit keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel:stop paste->paste#pasteFiles" } %>
|
||||
|
||||
@@ -5,14 +5,16 @@
|
||||
<%= render "cards/display/preview/tags", card: card %>
|
||||
</header>
|
||||
|
||||
<div class="card__body flex gap full-width flex-item-grow">
|
||||
<h1 class="card__title overflow-line-clamp">
|
||||
<%= card.title %>
|
||||
</h1>
|
||||
<div class="card__body flex gap full-width flex-item-grow justify-space-between">
|
||||
<div class="card__content">
|
||||
<h1 class="card__title overflow-line-clamp">
|
||||
<%= card.title %>
|
||||
</h1>
|
||||
|
||||
<%= link_to collection_card_path(card.collection, card), class: "card__link" do %>
|
||||
<span class="for-screen-reader"><%= card.title %></span>
|
||||
<% end %>
|
||||
<%= link_to collection_card_path(card.collection, card), class: "card__link", data: { turbo_frame: "_top" } do %>
|
||||
<span class="for-screen-reader"><%= card.title %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= render "cards/stagings/stages", card: card if card.doing? %>
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
Reported by <strong><%= familiar_name_for(card.creator) %></strong>
|
||||
</span>
|
||||
<span class="card__meta-item card__meta-item--assigned overflow-ellipsis">
|
||||
<%= card.assignees.any? ? card.assignees.map { |assignee| "Assigned to <strong>#{familiar_name_for(assignee)}</strong>" }.to_sentence.html_safe : "Not assigned" %>
|
||||
<%= card.assignees.any? ? "Assigned to" : "Not assigned" %>
|
||||
<%= card.assignees.map { |assignee| "<strong>#{familiar_name_for(assignee)}</strong>" }.to_sentence.html_safe %>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<%= turbo_frame_tag @card, :edit do %>
|
||||
<%= form_with model: @card, url: collection_card_path(@card.collection, @card), class: "full-width", data: { controller: "form paste" } do |form| %>
|
||||
<%= form_with model: @card, url: collection_card_path(@card.collection, @card), class: "card__content", data: { controller: "form paste" } do |form| %>
|
||||
<h1 class="card__title">
|
||||
<%= form.label :title, class: "flex flex-column align-center" do %>
|
||||
<%= form.text_area :title, class: "input input--textarea full-width borderless txt-align-start card-field__title",
|
||||
@@ -8,12 +8,12 @@
|
||||
data: { action: "keydown.enter->form#submit:prevent keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel focus->form#select" } %>
|
||||
<% end %>
|
||||
</h1>
|
||||
<div class="card__description margin-block-half">
|
||||
<div class="card__description markdown-content margin-block-half">
|
||||
<%= form.markdown_area :description, class: "input input--textarea full-width borderless txt-align-start card-field__description",
|
||||
placeholder: "Add some notes, context, pictures, or video about this…",
|
||||
data: { action: "keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel:stop paste->paste#pasteFiles" } %>
|
||||
</div>
|
||||
<%= form.button "Save changes", type: :submit, class: "btn"%>
|
||||
<%= form.button "Save changes", type: :submit, class: "btn" %>
|
||||
<%= link_to "Close editor and discard changes", collection_card_path(@card.collection, @card), data: { form_target: "cancel" }, hidden: true %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
<%= form_with url: cards_path, method: :get, class: "flex flex-column full-width popup__list",
|
||||
<%= form_with url: cards_path, method: :get, class: "flex flex-column max-width popup__list",
|
||||
data: { controller: "form" } do |form| %>
|
||||
<% filter.as_params.except(:collection_ids).each do |key, value| %>
|
||||
<%= filter_hidden_field_tag key, value %>
|
||||
<% end %>
|
||||
|
||||
<%= link_to cards_path(filter.as_params.except(:collection_ids)), class: "btn popup__item" do %>
|
||||
<span class="overflow-ellipsis">All collections</span>
|
||||
<% end %>
|
||||
|
||||
<% Current.user.collections.order(:name).each do |collection| %>
|
||||
<div class="btn popup__item">
|
||||
<%= form.check_box "collection_ids[]", {
|
||||
checked: filter.collections.include?(collection),
|
||||
data: { action: "change->form#submit" },
|
||||
include_hidden: false,
|
||||
}, collection.id %>
|
||||
<div class="popup__group flex align-center">
|
||||
<div class="btn txt-xx-small flex-item-no-shrink">
|
||||
<%= form.check_box "collection_ids[]", {
|
||||
checked: filter.collections.include?(collection),
|
||||
data: { action: "change->form#submit" },
|
||||
include_hidden: false,
|
||||
}, collection.id %>
|
||||
|
||||
<%= form.label "collection_ids[]", collection.name, for: dom_id(collection, :filter), class: "overflow-ellipsis" %>
|
||||
<%= icon_tag "check", size: 18, class: "checked flex-item-justify-end" %>
|
||||
<span class="for-screen-reader">
|
||||
Toggle filter for <%= collection.name %>
|
||||
</span>
|
||||
<%= icon_tag "check", size: 18, class: "checked" %>
|
||||
</div>
|
||||
|
||||
<%= link_to cards_path(collection_ids: [ collection ]), class: "btn popup__item min-width" do %>
|
||||
<span class="overflow-ellipsis"><%= collection.name %></span>
|
||||
<% end %>
|
||||
|
||||
<span class="translucent flex-item-no-shrink"> ›</span>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -5,15 +5,24 @@
|
||||
<div class="btn btn--placeholder flex-item-justify-start"></div>
|
||||
|
||||
<header class="flex flex-inline flex-column center flex-shrink">
|
||||
<div class="card-filter--collection flex-inline center position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<div class="card-filter--collection flex-inline center position-relative" data-controller="dialog" data-action="click@document->dialog#closeOnClickOutside">
|
||||
<button class="txt-align-center input input--select borderless center txt-medium" data-action="click->dialog#open:stop">
|
||||
<h1 class="txt-large overflow-ellipsis margin-none">
|
||||
<%= filter_title filter %>
|
||||
</h1>
|
||||
</button>
|
||||
|
||||
<dialog class="events__popup popup panel flex-column align-start gap-half fill-white shadow" data-dialog-target="dialog" data-action="turbo:before-cache@document->dialog#close">
|
||||
<strong class="popup__title margin-block-start-half pad-inline-half">In Collection</strong>
|
||||
<dialog class="popup popup--animated panel flex-column align-start gap-half fill-white shadow" data-dialog-target="dialog" data-action="keydown.esc->dialog#close:stop turbo:before-cache@document->dialog#close">
|
||||
<div class="flex align-center full-width gap-half">
|
||||
<span class="btn borderless" style="pointer-events: none; --icon-size: 1.3em; --btn-padding: 0 0.6em 0 0.3em;">
|
||||
<%= icon_tag "filter" %>
|
||||
</span>
|
||||
<strong class="popup__title txt-medium">Collections</strong>
|
||||
<button class="btn borderless txt-x-small flex-item-justify-end" data-action="click->dialog#close" >
|
||||
<%= icon_tag "remove-med" %>
|
||||
<span class="for-screen-reader">Close</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<%= render "cards/index/collections_filter", filter: filter %>
|
||||
</dialog>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<%= turbo_frame_tag @card, :tagging do %>
|
||||
<dialog class="popup panel flex-column align-start justify-start fill-white shadow" data-dialog-target="dialog">
|
||||
<strong class="popup__title margin-block-half pad-inline-half">Tag this…</strong>
|
||||
<strong class="popup__title margin-block-end-half pad-inline-half">Tag this…</strong>
|
||||
|
||||
<%= form_with url: card_taggings_path(@card), class: "flex gap-half align-center full-width pad-inline-half margin-block-end" do |form| %>
|
||||
<%= form.text_field :tag_title, placeholder: "New tag name...", class: "input txt-small full-width" %>
|
||||
<%= form.text_field :tag_title, placeholder: "New tag name...", class: "input txt-small full-width", autocomplete: "off" %>
|
||||
<%= form.button "Add a tag…", type: "submit", class: "btn txt-small" do %>
|
||||
<%= icon_tag "add" %>
|
||||
<span class="for-screen-reader">Add a tag</span>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<% presenter = CollectionFilterPresenter.new(@collections, params, cookies) %>
|
||||
<div class="position-relative max-width margin-block-end-half"
|
||||
<div class="max-width margin-block-end-half"
|
||||
data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="events__filter-btn input input--select flex-inline center min-width max-width txt-small" data-action="click->dialog#open:stop">
|
||||
<span class="overflow-ellipsis center">
|
||||
@@ -7,22 +7,45 @@
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<dialog class="events__popup popup panel flex-column full-width align-start gap-half fill-white shadow"
|
||||
style="z-index: 5; inset: 0 0 auto auto;" data-dialog-target="dialog" data-action="turbo:before-cache@document->dialog#close">
|
||||
<strong class="popup__title margin-block-start-half pad-inline-half txt-nowrap">Filter by Collection…</strong>
|
||||
<dialog class="events__popup popup popup--animated panel flex-column full-width align-start gap-half fill-white shadow"
|
||||
style="z-index: 5;" data-dialog-target="dialog" data-action="turbo:before-cache@document->dialog#close">
|
||||
<div class="flex align-center full-width gap-half">
|
||||
<span class="btn borderless" style="pointer-events: none; --icon-size: 1.3em; --btn-padding: 0 0.6em 0 0.3em;">
|
||||
<%= icon_tag "filter" %>
|
||||
</span>
|
||||
<strong class="popup__title txt-medium">Collections</strong>
|
||||
</div>
|
||||
|
||||
<%= form_with url: events_path, method: :get, class: "flex flex-column full-width popup__list",
|
||||
data: { controller: "form" } do |form| %>
|
||||
<%= link_to "Show everything", events_path(clear_filter: true), class: "btn popup__item txt-nowrap" %>
|
||||
<% @collections.each do |collection| %>
|
||||
<div class="btn popup__item">
|
||||
<%= form.check_box "collection_ids[]", {
|
||||
checked: (params[:collection_ids] || cookies[:collection_filter]&.split(","))&.include?(collection.id.to_s),
|
||||
data: { action: "change->form#submit" },
|
||||
include_hidden: false,
|
||||
}, collection.id %>
|
||||
<%#= link_to "Show everything", events_path(clear_filter: true), class: "btn popup__item txt-nowrap" %>
|
||||
<div class="popup__group flex align-center full-width gap-half overflow-ellipsis">
|
||||
<%= link_to new_collection_path, class: "btn popup__new popup__item", style: "view-transition-name: new-collection" do %>
|
||||
<%= icon_tag "add" %>
|
||||
<span>Add a new collection</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<hr class="full-width separator--horizontal" style="--border-color: transparent;">
|
||||
|
||||
<%= form.label "collection_ids[]", collection.name, for: dom_id(collection, :filter), class: "overflow-ellipsis" %>
|
||||
<%= icon_tag "check", class: "checked flex-item-justify-end" %>
|
||||
<% @collections.each do |collection| %>
|
||||
<div class="popup__group flex align-center full-width gap-half overflow-ellipsis">
|
||||
<div class="btn txt-xx-small">
|
||||
<%= form.check_box "collection_ids[]", {
|
||||
checked: (params[:collection_ids] || cookies[:collection_filter]&.split(","))&.include?(collection.id.to_s),
|
||||
data: { action: "change->form#submit" },
|
||||
include_hidden: false,
|
||||
}, collection.id %>
|
||||
|
||||
<span class="for-screen-reader">
|
||||
Toggle filter for <%= collection.name %>
|
||||
</span>
|
||||
<%= icon_tag "check", size: 18, class: "checked flex-item-justify-end" %>
|
||||
</div>
|
||||
|
||||
<%= link_to cards_path(collection_ids: [ collection ]), class: "btn popup__item flex-item-grow justify-space-between" do %>
|
||||
<span class="overflow-ellipsis flex-item-grow"><%= collection.name %></span>
|
||||
<span class="translucent"> ›</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -17,36 +17,10 @@
|
||||
<% end %>
|
||||
|
||||
<div class="events__container align-start gap">
|
||||
<div class="txt-align-start">
|
||||
<h2 class="txt-normal txt-uppercase txt-nowrap">Custom views</h2>
|
||||
<div style="border-block-start: 1px solid var(--color-subtle); padding-block-start: var(--block-space-half);">
|
||||
<% @filters.each do |filter| %>
|
||||
<h3 class="txt-small margin-block-end-half txt-tight-lines">
|
||||
<%= link_to filter.summary, cards_path(**filter.as_params) %>
|
||||
</h3>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-column">
|
||||
<div id="activity" class="events__index flex flex-column">
|
||||
<%= render "events/day", activity_day: @activity_day, next_day: @next_day, events: @events %>
|
||||
</div>
|
||||
<%= event_next_page_link(@next_day) %>
|
||||
</div>
|
||||
|
||||
<div class="txt-align-start">
|
||||
<div class="flex align-start gap min-width" >
|
||||
<h2 class="txt-normal txt-uppercase overflow-ellipsis">Collections</h2>
|
||||
<%= link_to new_collection_path, class: "btn txt-xx-small flex-item-justify-end flex-item-no-shrink", style: "view-transition-name: new-collection" do %>
|
||||
<%= icon_tag "add" %>
|
||||
<span class="for-screen-reader">New</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div style="border-block-start: 1px solid var(--color-subtle); padding-block-start: var(--block-space-half);">
|
||||
<% @collections.each do |collection| %>
|
||||
<h3 class="txt-small margin-block-end-half txt-tight-lines"><%= link_to collection.name, cards_path(collection_ids: [ collection ]) %></h3>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<dialog class="color-picker panel fill-white shadow gap-half" data-dialog-target="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<%= form_with model: stage, url: workflow_stage_path(stage.workflow, stage), class: "flex gap-half", data: { controller: "form", action: "change->form#submit" } do |form| %>
|
||||
<% Colorable::COLORS.each do |color| %>
|
||||
<% Card::COLORS.each do |color| %>
|
||||
<span class="color-picker__color">
|
||||
<label class="btn btn--circle" style="--btn-background: <%= color %>" title="<%= color %>">
|
||||
<%= form.radio_button :color, color %>
|
||||
|
||||
@@ -1,31 +1,14 @@
|
||||
#!/usr/bin/env ruby
|
||||
require "fileutils"
|
||||
|
||||
APP_ROOT = File.expand_path("..", __dir__)
|
||||
def system!(*args) system(*args, exception: true) end
|
||||
|
||||
def system!(*args)
|
||||
system(*args, exception: true)
|
||||
end
|
||||
puts "== Installing dependencies =="
|
||||
system("gem install bundler --conservative")
|
||||
system("bundle check") || system!("bundle install")
|
||||
|
||||
FileUtils.chdir APP_ROOT do
|
||||
# This script is a way to set up or update your development environment automatically.
|
||||
# This script is idempotent, so that you can run it at any time and get an expectable outcome.
|
||||
# Add necessary setup steps to this file.
|
||||
puts "\n== Preparing database =="
|
||||
system! "bin/rails db:prepare"
|
||||
system! "bin/rails db:reset" if ARGV.include?("--reset")
|
||||
|
||||
puts "== Installing dependencies =="
|
||||
system("gem install bundler --conservative")
|
||||
system("bundle check") || system!("bundle install")
|
||||
|
||||
puts "\n== Preparing database =="
|
||||
system! "bin/rails db:prepare"
|
||||
system! "bin/rails db:reset" if ARGV.include?("--reset")
|
||||
|
||||
puts "\n== Removing old logs and tempfiles =="
|
||||
system! "bin/rails log:clear tmp:clear"
|
||||
|
||||
unless ARGV.include?("--skip-server")
|
||||
puts "\n== Starting development server =="
|
||||
STDOUT.flush # flush the output before exec(2) so that it displays
|
||||
exec "bin/dev"
|
||||
end
|
||||
end
|
||||
puts "\n== Removing old logs and tempfiles =="
|
||||
system! "bin/rails log:clear tmp:clear"
|
||||
|
||||
+2
-2
@@ -26,17 +26,17 @@ Rails.application.routes.draw do
|
||||
resources :cards do
|
||||
scope module: :cards do
|
||||
resource :engagement
|
||||
resource :goldness
|
||||
resource :image
|
||||
resource :pin
|
||||
resource :closure
|
||||
resource :publish
|
||||
resource :reading
|
||||
resource :recover
|
||||
resource :staging
|
||||
resource :watch
|
||||
resource :goldness
|
||||
|
||||
resources :assignments
|
||||
resources :stagings
|
||||
resources :taggings
|
||||
|
||||
resources :comments do
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class RemoveDueDateFromEvents < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
remove_column :events, :due_date
|
||||
end
|
||||
end
|
||||
+64
-10
@@ -3,19 +3,73 @@ create_tenant "honcho"
|
||||
david = create_first_run "David Heinemeier Hansson", "david@37signals.com"
|
||||
login_as david
|
||||
|
||||
jz = create_user "Jason Zimdars", "jz@37signals.com"
|
||||
jason = create_user "Jason Zimdars", "jz@37signals.com"
|
||||
kevin = create_user "Kevin Mcconnell", "kevin@37signals.com"
|
||||
sarah = create_user "Sarah Johnson", "sarah@37signals.com"
|
||||
mike = create_user "Mike Peterson", "mike@37signals.com"
|
||||
|
||||
create_collection("Mucho", access_to: [ jz, kevin ]).tap do |fizzy|
|
||||
create_card("Prepare MUCHO sign-up page", description: "We need to do this before the launch.", collection: fizzy)
|
||||
# Array of authors for random assignment
|
||||
authors = [ david, jason, kevin, sarah, mike ]
|
||||
|
||||
create_card("Prepare MUCHO sign-up page", description: "We need to do this before the launch.", collection: fizzy).tap do |card|
|
||||
card.toggle_assignment(kevin)
|
||||
card.engage
|
||||
end
|
||||
# Card titles for reuse across collections
|
||||
card_titles = [
|
||||
"Implement authentication",
|
||||
"Design landing page",
|
||||
"Set up database",
|
||||
"Create API endpoints",
|
||||
"Write unit tests",
|
||||
"Optimize performance",
|
||||
"Add user profiles",
|
||||
"Implement search",
|
||||
"Create admin panel",
|
||||
"Set up CI/CD",
|
||||
"Design logo",
|
||||
"Create documentation",
|
||||
"Add payment system",
|
||||
"Implement notifications",
|
||||
"Set up analytics",
|
||||
"Create mobile layout",
|
||||
"Add social sharing",
|
||||
"Implement caching",
|
||||
"Set up monitoring",
|
||||
"Create error handling"
|
||||
]
|
||||
|
||||
create_card("Plain MUCHO text mentions", description: "We'll support plain text mentions first.", collection: fizzy).tap do |card|
|
||||
card.toggle_assignment(david)
|
||||
card.close
|
||||
# Create 10 collections
|
||||
collections = [
|
||||
"Project Launch",
|
||||
"Frontend Dev",
|
||||
"Backend Dev",
|
||||
"Design System",
|
||||
"Testing Suite",
|
||||
"Performance",
|
||||
"User Experience",
|
||||
"API Development",
|
||||
"DevOps",
|
||||
"Documentation"
|
||||
]
|
||||
|
||||
collections.each_with_index do |collection_name, index|
|
||||
create_collection(collection_name, access_to: authors.sample(3)).tap do |collection|
|
||||
# Create 20 unique cards for each collection
|
||||
card_titles.each do |title|
|
||||
create_card(title,
|
||||
description: "#{title} for #{collection_name} phase #{index + 1}.",
|
||||
collection: collection
|
||||
).tap do |card|
|
||||
# Randomly assign to 1-2 authors
|
||||
card.toggle_assignment(authors.sample)
|
||||
card.toggle_assignment(authors.sample) if rand > 0.5
|
||||
|
||||
# Randomly set card state
|
||||
case rand(3)
|
||||
when 0
|
||||
card.engage
|
||||
when 1
|
||||
card.close
|
||||
# 2 remains open
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
require "test_helper"
|
||||
|
||||
class Cards::StagingsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in_as :kevin
|
||||
end
|
||||
|
||||
test "update" do
|
||||
put card_staging_path(cards(:logo)), params: { stage_id: workflow_stages(:qa_in_progress).id }, as: :turbo_stream
|
||||
assert_card_container_rerendered(cards(:logo))
|
||||
end
|
||||
end
|
||||
Vendored
+4
@@ -1,6 +1,7 @@
|
||||
logo:
|
||||
collection: writebook
|
||||
creator: david
|
||||
stage: qa_triage
|
||||
title: The logo isn't big enough
|
||||
due_on: <%= 3.days.from_now %>
|
||||
created_at: <%= 1.week.ago %>
|
||||
@@ -10,6 +11,7 @@ logo:
|
||||
layout:
|
||||
collection: writebook
|
||||
creator: david
|
||||
stage: qa_triage
|
||||
title: Layout is broken
|
||||
created_at: <%= 1.week.ago %>
|
||||
status: published
|
||||
@@ -18,6 +20,7 @@ layout:
|
||||
text:
|
||||
collection: writebook
|
||||
creator: kevin
|
||||
stage: qa_triage
|
||||
title: The text is too small
|
||||
created_at: <%= 1.week.ago %>
|
||||
status: published
|
||||
@@ -27,6 +30,7 @@ text:
|
||||
shipping:
|
||||
collection: writebook
|
||||
creator: kevin
|
||||
stage: qa_triage
|
||||
title: We need to ship the app
|
||||
created_at: <%= 1.week.ago %>
|
||||
status: published
|
||||
|
||||
@@ -25,19 +25,5 @@ class CardMessagesTest < ActionDispatch::IntegrationTest
|
||||
assert_predicate card.messages.last, :event_summary?
|
||||
assert_equal 1, card.messages.last.event_summary.events.count
|
||||
assert_equal "assigned", card.messages.last.messageable.events.last.action
|
||||
|
||||
# Stage it
|
||||
post card_stagings_path(card), params: { stage_id: workflow_stages(:qa_triage).id }
|
||||
assert_equal 3, card.messages.count
|
||||
assert_predicate card.messages.last, :event_summary?
|
||||
assert_equal 2, card.messages.last.event_summary.events.count
|
||||
assert_equal "staged", card.messages.last.messageable.events.last.action
|
||||
|
||||
# Unstage it
|
||||
post card_stagings_path(card), params: { stage_id: workflow_stages(:qa_triage).id }
|
||||
assert_equal 3, card.messages.count
|
||||
assert_predicate card.messages.last, :event_summary?
|
||||
assert_equal 3, card.messages.last.event_summary.events.count
|
||||
assert_equal "unstaged", card.messages.last.messageable.events.last.action
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@ require "test_helper"
|
||||
class Card::CloseableTest < ActiveSupport::TestCase
|
||||
test "closed scope" do
|
||||
assert_equal [ cards(:shipping) ], Card.closed
|
||||
assert_not_includes Card.active, cards(:shipping)
|
||||
assert_not_includes Card.open, cards(:shipping)
|
||||
end
|
||||
|
||||
test "popping" do
|
||||
|
||||
@@ -2,11 +2,11 @@ require "test_helper"
|
||||
|
||||
class Card::ColoredTest < ActiveSupport::TestCase
|
||||
test "use default color no stage" do
|
||||
assert_equal Colorable::DEFAULT_COLOR, cards(:logo).color
|
||||
assert_equal Card::DEFAULT_COLOR, cards(:logo).color
|
||||
end
|
||||
|
||||
test "use default color not in doing stage" do
|
||||
assert_equal Colorable::DEFAULT_COLOR, cards(:text).color
|
||||
assert_equal Card::DEFAULT_COLOR, cards(:text).color
|
||||
end
|
||||
|
||||
test "infer color from stage when in doing stage" do
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
require "test_helper"
|
||||
|
||||
class Card::StagedTest < ActiveSupport::TestCase
|
||||
setup { Current.session = sessions(:david) }
|
||||
|
||||
test "change stage" do
|
||||
assert_difference -> { Event.where(action: :staged).count }, +1 do
|
||||
cards(:logo).change_stage_to(workflow_stages(:qa_in_progress))
|
||||
assert_equal workflow_stages(:qa_in_progress), cards(:logo).reload.stage
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -70,8 +70,8 @@ class CardTest < ActiveSupport::TestCase
|
||||
assert_equal [ cards(:shipping) ], Card.closed
|
||||
end
|
||||
|
||||
test "active" do
|
||||
assert_equal cards(:logo, :layout, :text), Card.active
|
||||
test "open" do
|
||||
assert_equal cards(:logo, :layout, :text), Card.open
|
||||
end
|
||||
|
||||
test "unassigned" do
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
require "test_helper"
|
||||
|
||||
class Collection::WorkflowingTest < ActiveSupport::TestCase
|
||||
test "change all card stages when changing workflow" do
|
||||
collections(:writebook).update! workflow: workflows(:on_call)
|
||||
assert_equal [ collections(:writebook).initial_workflow_stage ], collections(:writebook).cards.reload.collect(&:stage).uniq
|
||||
end
|
||||
|
||||
test "remove stage from cards if workflow is removed" do
|
||||
collections(:writebook).update! workflow: nil
|
||||
assert_equal [], collections(:writebook).cards.reload.collect(&:stage).compact
|
||||
end
|
||||
end
|
||||
@@ -2,8 +2,7 @@ require "test_helper"
|
||||
|
||||
class EventSummaryTest < ActiveSupport::TestCase
|
||||
test "body" do
|
||||
assert_equal "Added by David. Assigned to JZ.", event_summaries(:logo_initial_activity).body
|
||||
assert_equal " Assigned to JZ.", event_summaries(:logo_initial_activity).body
|
||||
assert_equal "Assigned to Kevin.", event_summaries(:logo_second_activity).body
|
||||
assert_equal "Added by Kevin.", event_summaries(:text_initial_activity).body
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,11 +5,11 @@ class FilterTest < ActiveSupport::TestCase
|
||||
Current.set session: sessions(:david) do
|
||||
@new_collection = Collection.create! name: "Inaccessible Collection"
|
||||
@new_card = @new_collection.cards.create!
|
||||
@new_card.update!(stage: workflow_stages(:qa_triage))
|
||||
@new_card.update!(stage: workflow_stages(:qa_on_hold))
|
||||
|
||||
cards(:layout).capture Comment.new(body: "I hate haggis")
|
||||
cards(:logo).capture Comment.new(body: "I love haggis")
|
||||
cards(:logo).update(stage: workflow_stages(:qa_triage))
|
||||
cards(:logo).update(stage: workflow_stages(:qa_on_hold))
|
||||
end
|
||||
|
||||
assert_not_includes users(:kevin).filters.new.cards, @new_card
|
||||
@@ -17,7 +17,7 @@ class FilterTest < ActiveSupport::TestCase
|
||||
filter = users(:david).filters.new creator_ids: [ users(:david).id ], tag_ids: [ tags(:mobile).id ]
|
||||
assert_equal [ cards(:layout) ], filter.cards
|
||||
|
||||
filter = users(:david).filters.new stage_ids: [ workflow_stages(:qa_triage).id ]
|
||||
filter = users(:david).filters.new stage_ids: [ workflow_stages(:qa_on_hold).id ]
|
||||
assert_equal [ cards(:logo), @new_card ], filter.cards
|
||||
|
||||
filter = users(:david).filters.new assignment_status: "unassigned", collection_ids: [ @new_collection.id ]
|
||||
|
||||
Reference in New Issue
Block a user