diff --git a/Gemfile.lock b/Gemfile.lock index 37b14083c..a9c459db0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) diff --git a/app/assets/stylesheets/cards.css b/app/assets/stylesheets/cards.css index 4be6b06ed..b913db6a9 100644 --- a/app/assets/stylesheets/cards.css +++ b/app/assets/stylesheets/cards.css @@ -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 { diff --git a/app/assets/stylesheets/events.css b/app/assets/stylesheets/events.css index 0b19fe853..219e18292 100644 --- a/app/assets/stylesheets/events.css +++ b/app/assets/stylesheets/events.css @@ -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; } } diff --git a/app/assets/stylesheets/house.css b/app/assets/stylesheets/house.css index e1ce84325..66dbdddf1 100644 --- a/app/assets/stylesheets/house.css +++ b/app/assets/stylesheets/house.css @@ -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; diff --git a/app/assets/stylesheets/inputs.css b/app/assets/stylesheets/inputs.css index 5e3ba772c..35854aac6 100644 --- a/app/assets/stylesheets/inputs.css +++ b/app/assets/stylesheets/inputs.css @@ -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 { diff --git a/app/assets/stylesheets/comment-content.css b/app/assets/stylesheets/markdown-content.css similarity index 98% rename from app/assets/stylesheets/comment-content.css rename to app/assets/stylesheets/markdown-content.css index 9944ed45f..9d094aade 100644 --- a/app/assets/stylesheets/comment-content.css +++ b/app/assets/stylesheets/markdown-content.css @@ -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; diff --git a/app/assets/stylesheets/pins.css b/app/assets/stylesheets/pins.css index e42f2d3e7..50da7657e 100644 --- a/app/assets/stylesheets/pins.css +++ b/app/assets/stylesheets/pins.css @@ -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; } diff --git a/app/assets/stylesheets/popup.css b/app/assets/stylesheets/popup.css index 5c8ec1b95..b9987e12c 100644 --- a/app/assets/stylesheets/popup.css +++ b/app/assets/stylesheets/popup.css @@ -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; + } + } +} diff --git a/app/assets/stylesheets/workflows.css b/app/assets/stylesheets/workflows.css index d69dbfe3b..21e36b9c6 100644 --- a/app/assets/stylesheets/workflows.css +++ b/app/assets/stylesheets/workflows.css @@ -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); - } } } diff --git a/app/controllers/cards/pins_controller.rb b/app/controllers/cards/pins_controller.rb index a4b49433f..6ac118543 100644 --- a/app/controllers/cards/pins_controller.rb +++ b/app/controllers/cards/pins_controller.rb @@ -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 diff --git a/app/controllers/cards/stagings_controller.rb b/app/controllers/cards/stagings_controller.rb index 102c17599..714427ecf 100644 --- a/app/controllers/cards/stagings_controller.rb +++ b/app/controllers/cards/stagings_controller.rb @@ -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 diff --git a/app/helpers/cards_helper.rb b/app/helpers/cards_helper.rb index 31c370479..27bdd2595 100644 --- a/app/helpers/cards_helper.rb +++ b/app/helpers/cards_helper.rb @@ -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?), diff --git a/app/helpers/workflows_helper.rb b/app/helpers/workflows_helper.rb index 1bbed5c32..560e1aebf 100644 --- a/app/helpers/workflows_helper.rb +++ b/app/helpers/workflows_helper.rb @@ -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 diff --git a/app/javascript/controllers/local_save_controller.js b/app/javascript/controllers/local_save_controller.js index c0c5892aa..5d9b89a72 100644 --- a/app/javascript/controllers/local_save_controller.js +++ b/app/javascript/controllers/local_save_controller.js @@ -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 diff --git a/app/javascript/controllers/paste_controller.js b/app/javascript/controllers/paste_controller.js index 41fc77d2c..79db7d7db 100644 --- a/app/javascript/controllers/paste_controller.js +++ b/app/javascript/controllers/paste_controller.js @@ -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 }) diff --git a/app/models/card/closeable.rb b/app/models/card/closeable.rb index ce0a63e33..64945af0c 100644 --- a/app/models/card/closeable.rb +++ b/app/models/card/closeable.rb @@ -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 diff --git a/app/models/card/colored.rb b/app/models/card/colored.rb index 99e934a94..9a16ee4a4 100644 --- a/app/models/card/colored.rb +++ b/app/models/card/colored.rb @@ -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 diff --git a/app/models/card/engageable.rb b/app/models/card/engageable.rb index 78dbe0ebe..6671bc5a1 100644 --- a/app/models/card/engageable.rb +++ b/app/models/card/engageable.rb @@ -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 diff --git a/app/models/card/golden.rb b/app/models/card/golden.rb index 759179415..4f2f52a60 100644 --- a/app/models/card/golden.rb +++ b/app/models/card/golden.rb @@ -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 diff --git a/app/models/card/staged.rb b/app/models/card/staged.rb index de853b818..f8036d3e3 100644 --- a/app/models/card/staged.rb +++ b/app/models/card/staged.rb @@ -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 diff --git a/app/models/collection.rb b/app/models/collection.rb index 5f4a75051..236208c16 100644 --- a/app/models/collection.rb +++ b/app/models/collection.rb @@ -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 diff --git a/app/models/collection/workflowing.rb b/app/models/collection/workflowing.rb new file mode 100644 index 000000000..658087fe2 --- /dev/null +++ b/app/models/collection/workflowing.rb @@ -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 diff --git a/app/models/colorable.rb b/app/models/colorable.rb deleted file mode 100644 index 4fba4556d..000000000 --- a/app/models/colorable.rb +++ /dev/null @@ -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 diff --git a/app/models/concerns/filterable.rb b/app/models/concerns/filterable.rb index b3cb07c46..85a33a015 100644 --- a/app/models/concerns/filterable.rb +++ b/app/models/concerns/filterable.rb @@ -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 diff --git a/app/models/event_summary.rb b/app/models/event_summary.rb index fcfaabd83..241d3cc3c 100644 --- a/app/models/event_summary.rb +++ b/app/models/event_summary.rb @@ -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" diff --git a/app/models/filter.rb b/app/models/filter.rb index b2957bfd5..e5ce39383 100644 --- a/app/models/filter.rb +++ b/app/models/filter.rb @@ -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? diff --git a/app/models/user.rb b/app/models/user.rb index c09455bfb..833c41580 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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 diff --git a/app/models/user/avatar.rb b/app/models/user/avatar.rb deleted file mode 100644 index a9d2ad12e..000000000 --- a/app/models/user/avatar.rb +++ /dev/null @@ -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 diff --git a/app/models/workflow.rb b/app/models/workflow.rb index b1e1fdb22..3174289dd 100644 --- a/app/models/workflow.rb +++ b/app/models/workflow.rb @@ -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 diff --git a/app/views/cards/_container.html.erb b/app/views/cards/_container.html.erb index 034106e59..f64ff7e91 100644 --- a/app/views/cards/_container.html.erb +++ b/app/views/cards/_container.html.erb @@ -19,7 +19,7 @@ <%= render "cards/display/perma/tags", card: card %> -
<%= link_to card.title, edit_collection_card_path(card.collection, card), class: "card__title-link" %>
-+
<%= 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" } %>
-- <%= card.title %> -
++ <%= card.title %> +
- <%= link_to collection_card_path(card.collection, card), class: "card__link" do %> - <%= card.title %> - <% end %> + <%= link_to collection_card_path(card.collection, card), class: "card__link", data: { turbo_frame: "_top" } do %> + <%= card.title %> + <% end %> +<%= 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 %>
-