diff --git a/Gemfile.lock b/Gemfile.lock index 4b3ce6e44..0e48985b0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -123,8 +123,8 @@ GEM ast (2.4.3) autotuner (1.1.0) aws-eventstream (1.4.0) - aws-partitions (1.1197.0) - aws-sdk-core (3.240.0) + aws-partitions (1.1203.0) + aws-sdk-core (3.241.3) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) @@ -132,28 +132,28 @@ GEM bigdecimal jmespath (~> 1, >= 1.6.1) logger - aws-sdk-kms (1.118.0) - aws-sdk-core (~> 3, >= 3.239.1) + aws-sdk-kms (1.120.0) + aws-sdk-core (~> 3, >= 3.241.3) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.208.0) - aws-sdk-core (~> 3, >= 3.234.0) + aws-sdk-s3 (1.211.0) + aws-sdk-core (~> 3, >= 3.241.3) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) aws-sigv4 (1.12.1) aws-eventstream (~> 1, >= 1.0.2) base64 (0.3.0) - bcrypt (3.1.20) - bcrypt_pbkdf (1.1.1) + bcrypt (3.1.21) + bcrypt_pbkdf (1.1.2) benchmark (0.5.0) bigdecimal (4.0.1) bindex (0.8.1) - bootsnap (1.19.0) + bootsnap (1.20.1) msgpack (~> 1.2) - brakeman (7.1.1) + brakeman (7.1.2) racc builder (3.3.0) - bundler-audit (0.9.2) - bundler (>= 1.2.0, < 3) + bundler-audit (0.9.3) + bundler (>= 1.2.0) thor (~> 1.0) capybara (3.40.0) addressable @@ -168,23 +168,23 @@ GEM logger (~> 1.5) chunky_png (1.4.0) concurrent-ruby (1.3.6) - connection_pool (2.5.5) + connection_pool (3.0.2) crack (1.0.1) bigdecimal rexml crass (1.0.6) date (3.5.1) - debug (1.11.0) + debug (1.11.1) irb (~> 1.10) reline (>= 0.3.8) - dotenv (3.1.8) + dotenv (3.2.0) drb (2.2.3) ed25519 (1.4.0) erb (6.0.1) erubi (1.13.1) et-orbi (1.4.0) tzinfo - faker (3.5.2) + faker (3.5.3) i18n (>= 1.8.11, < 2) ffi (1.17.2-aarch64-linux-gnu) ffi (1.17.2-aarch64-linux-musl) @@ -224,7 +224,7 @@ GEM json (2.18.0) jwt (3.1.2) base64 - kamal (2.9.0) + kamal (2.10.1) activesupport (>= 7.0) base64 (~> 0.2) bcrypt_pbkdf (~> 1.0) @@ -271,11 +271,11 @@ GEM stimulus-rails turbo-rails mittens (0.3.1) - mocha (2.8.2) + mocha (3.0.1) ruby2_keywords (>= 0.0.5) msgpack (1.8.0) - net-http-persistent (4.0.6) - connection_pool (~> 2.2, >= 2.2.4) + net-http-persistent (4.0.8) + connection_pool (>= 2.2.4, < 4) net-imap (0.6.2) date net-protocol @@ -307,7 +307,7 @@ GEM racc (~> 1.4) nokogiri (1.19.0-x86_64-linux-musl) racc (~> 1.4) - openssl (3.3.2) + openssl (4.0.0) ostruct (0.6.3) parallel (1.27.0) parser (3.3.10.0) @@ -319,7 +319,7 @@ GEM pp (0.6.3) prettyprint prettyprint (0.2.0) - prism (1.6.0) + prism (1.8.0) propshaft (1.3.1) actionpack (>= 7.0.0) activesupport (>= 7.0.0) @@ -360,11 +360,11 @@ GEM reline (0.6.3) io-console (~> 0.5) rexml (3.4.4) - rouge (4.6.1) - rqrcode (3.1.0) + rouge (4.7.0) + rqrcode (3.2.0) chunky_png (~> 1.0) rqrcode_core (~> 2.0) - rqrcode_core (2.0.0) + rqrcode_core (2.1.0) rubocop (1.81.7) json (~> 2.3) language_server-protocol (~> 3.17.0.2) @@ -400,7 +400,7 @@ GEM ruby2_keywords (0.0.5) rubyzip (3.2.2) securerandom (0.4.1) - selenium-webdriver (4.38.0) + selenium-webdriver (4.39.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) @@ -430,7 +430,7 @@ GEM sqlite3 (2.8.0-x86_64-darwin) sqlite3 (2.8.0-x86_64-linux-gnu) sqlite3 (2.8.0-x86_64-linux-musl) - sshkit (1.24.0) + sshkit (1.25.0) base64 logger net-scp (>= 1.1.2) @@ -440,7 +440,7 @@ GEM stimulus-rails (1.3.4) railties (>= 6.0.0) stringio (3.2.0) - thor (1.4.0) + thor (1.5.0) thruster (0.1.17) thruster (0.1.17-aarch64-linux) thruster (0.1.17-arm64-darwin) @@ -458,16 +458,15 @@ GEM unicode-emoji (~> 4.1) unicode-emoji (4.1.0) uri (1.1.1) - vcr (6.3.1) - base64 + vcr (6.4.0) web-console (4.2.1) actionview (>= 6.0.0) activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - web-push (3.0.2) + web-push (3.1.0) jwt (~> 3.0) - openssl (~> 3.0) + openssl (>= 3.0) webmock (3.26.1) addressable (>= 2.8.0) crack (>= 0.3.2) diff --git a/Gemfile.saas.lock b/Gemfile.saas.lock index 98cf833bc..f340b4ae4 100644 --- a/Gemfile.saas.lock +++ b/Gemfile.saas.lock @@ -201,8 +201,8 @@ GEM ast (2.4.3) autotuner (1.1.0) aws-eventstream (1.4.0) - aws-partitions (1.1197.0) - aws-sdk-core (3.240.0) + aws-partitions (1.1203.0) + aws-sdk-core (3.241.3) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) @@ -210,28 +210,28 @@ GEM bigdecimal jmespath (~> 1, >= 1.6.1) logger - aws-sdk-kms (1.118.0) - aws-sdk-core (~> 3, >= 3.239.1) + aws-sdk-kms (1.120.0) + aws-sdk-core (~> 3, >= 3.241.3) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.208.0) - aws-sdk-core (~> 3, >= 3.234.0) + aws-sdk-s3 (1.211.0) + aws-sdk-core (~> 3, >= 3.241.3) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) aws-sigv4 (1.12.1) aws-eventstream (~> 1, >= 1.0.2) base64 (0.3.0) - bcrypt (3.1.20) - bcrypt_pbkdf (1.1.1) + bcrypt (3.1.21) + bcrypt_pbkdf (1.1.2) benchmark (0.5.0) bigdecimal (4.0.1) bindex (0.8.1) - bootsnap (1.19.0) + bootsnap (1.20.1) msgpack (~> 1.2) - brakeman (7.1.1) + brakeman (7.1.2) racc builder (3.3.0) - bundler-audit (0.9.2) - bundler (>= 1.2.0, < 3) + bundler-audit (0.9.3) + bundler (>= 1.2.0) thor (~> 1.0) capybara (3.40.0) addressable @@ -246,16 +246,16 @@ GEM logger (~> 1.5) chunky_png (1.4.0) concurrent-ruby (1.3.6) - connection_pool (2.5.5) + connection_pool (3.0.2) crack (1.0.1) bigdecimal rexml crass (1.0.6) date (3.5.1) - debug (1.11.0) + debug (1.11.1) irb (~> 1.10) reline (>= 0.3.8) - dotenv (3.1.8) + dotenv (3.2.0) drb (2.2.3) dry-initializer (3.2.0) ed25519 (1.4.0) @@ -263,7 +263,7 @@ GEM erubi (1.13.1) et-orbi (1.4.0) tzinfo - faker (3.5.2) + faker (3.5.3) i18n (>= 1.8.11, < 2) ffi (1.17.2-aarch64-linux-gnu) ffi (1.17.2-aarch64-linux-musl) @@ -302,7 +302,7 @@ GEM json (2.18.0) jwt (3.1.2) base64 - kamal (2.9.0) + kamal (2.10.1) activesupport (>= 7.0) base64 (~> 0.2) bcrypt_pbkdf (~> 1.0) @@ -349,11 +349,11 @@ GEM stimulus-rails turbo-rails mittens (0.3.1) - mocha (2.8.2) + mocha (3.0.1) ruby2_keywords (>= 0.0.5) msgpack (1.8.0) - net-http-persistent (4.0.6) - connection_pool (~> 2.2, >= 2.2.4) + net-http-persistent (4.0.8) + connection_pool (>= 2.2.4, < 4) net-imap (0.6.2) date net-protocol @@ -383,7 +383,7 @@ GEM racc (~> 1.4) nokogiri (1.19.0-x86_64-linux-musl) racc (~> 1.4) - openssl (3.3.2) + openssl (4.0.0) ostruct (0.6.3) parallel (1.27.0) parser (3.3.10.0) @@ -395,7 +395,7 @@ GEM pp (0.6.3) prettyprint prettyprint (0.2.0) - prism (1.6.0) + prism (1.8.0) prometheus-client-mmap (1.4.0) base64 bigdecimal @@ -470,11 +470,11 @@ GEM io-console (~> 0.5) rexml (3.4.4) rinku (2.0.6) - rouge (4.6.1) - rqrcode (3.1.0) + rouge (4.7.0) + rqrcode (3.2.0) chunky_png (~> 1.0) rqrcode_core (~> 2.0) - rqrcode_core (2.0.0) + rqrcode_core (2.1.0) rubocop (1.81.7) json (~> 2.3) language_server-protocol (~> 3.17.0.2) @@ -511,7 +511,7 @@ GEM ruby2_keywords (0.0.5) rubyzip (3.2.2) securerandom (0.4.1) - selenium-webdriver (4.38.0) + selenium-webdriver (4.39.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) @@ -549,7 +549,7 @@ GEM sqlite3 (2.8.0-arm64-darwin) sqlite3 (2.8.0-x86_64-linux-gnu) sqlite3 (2.8.0-x86_64-linux-musl) - sshkit (1.24.0) + sshkit (1.25.0) base64 logger net-scp (>= 1.1.2) @@ -560,7 +560,7 @@ GEM railties (>= 6.0.0) stringio (3.2.0) stripe (18.0.1) - thor (1.4.0) + thor (1.5.0) thruster (0.1.17) thruster (0.1.17-aarch64-linux) thruster (0.1.17-arm64-darwin) @@ -577,16 +577,15 @@ GEM unicode-emoji (~> 4.1) unicode-emoji (4.1.0) uri (1.1.1) - vcr (6.3.1) - base64 + vcr (6.4.0) web-console (4.2.1) actionview (>= 6.0.0) activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - web-push (3.0.2) + web-push (3.1.0) jwt (~> 3.0) - openssl (~> 3.0) + openssl (>= 3.0) webmock (3.26.1) addressable (>= 2.8.0) crack (>= 0.3.2) diff --git a/app/assets/stylesheets/blank-slates.css b/app/assets/stylesheets/blank-slates.css index aa221c452..5db13ef7f 100644 --- a/app/assets/stylesheets/blank-slates.css +++ b/app/assets/stylesheets/blank-slates.css @@ -1,46 +1,21 @@ +/* Styles for the blank slate. To manage when they are shown/hidden, do so in context */ + @layer components { - .blank-slate--drag { - box-shadow: none !important; - color: color-mix(in srgb, var(--card-color) 70%, var(--color-canvas)); - - p { - font-size: var(--text-small); - overflow: hidden; - text-align: center; - text-overflow: ellipsis; - white-space: nowrap; - } - - .cards--maybe & { - background-color: var(--card-bg-color) !important; - } - - .cards--grid & { - display: none; - } - } - - .blank-slate--empty { - border: 2px dashed var(--color-ink-light); - border-radius: 1ch; + .blank-slate { + border-radius: 0.5ch; + border: 2px dashed var(--color-ink-lighter); color: var(--color-ink-dark); - margin-block-start: 2dvh; - padding: 1ch 2ch; - rotate: -3deg; font-weight: 500; - - .cards:has(.card) & { - display: none; - } + margin-block-start: 2dvh; + padding: 1.5ch 2ch; + rotate: -3deg; } - .blank-slate--filters { - .cards--grid:has(.card) & { - display: none; - } - } - - .cards__list:has(> :not(.blank-slate)) .card--hide-unless-empty { - display: none; + .blank-slate--drag { + background-color: color-mix(in srgb, transparent, var(--card-color) 5%); + border-color: color-mix(in srgb, transparent, var(--card-color) 10%); + color: color-mix(in srgb, transparent, var(--card-color) 75%); + margin-block-start: 0; + rotate: 0deg; } } diff --git a/app/assets/stylesheets/buttons.css b/app/assets/stylesheets/buttons.css index 07653e7fa..89638fc12 100644 --- a/app/assets/stylesheets/buttons.css +++ b/app/assets/stylesheets/buttons.css @@ -114,7 +114,7 @@ .btn--circle-mobile { --btn-size: 3em; --btn-padding: 0; - --icon-size: 70%; + --icon-size: 75%; aspect-ratio: 1; inline-size: var(--btn-size); diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 6aaacaf8b..2c0c67ef9 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -255,6 +255,27 @@ } } } + + &:has(.card) { + .blank-slate { + display: none; + } + } + + /* Use the default blank-slate on small viewports since drag-and-drop isn't available */ + [data-controller~="drag-and-drop"] & { + @media (max-width: 639px) { + .blank-slate--drag { + display: none; + } + } + + @media (min-width: 640px) { + .blank-slate--default { + display: none; + } + } + } } .cards__new-column { @@ -315,6 +336,10 @@ inline-size: fit-content; margin: 0 0 0 auto; } + + .blank-slate--drag { + display: none; + } } /* Column Elements @@ -731,7 +756,8 @@ .cards--on-deck { --card-color: var(--color-ink-light) !important; - .card { + .card, + .blank-slate { --card-color: var(--color-card-complete) !important; } @@ -748,12 +774,13 @@ .cards--maybe.is-collapsed:has(.bubble:not([hidden])) .cards__transition-container, .cards--doing.is-collapsed:has(.bubble:not([hidden])) .cards__transition-container { --bubble-color: var(--card-color, oklch(var(--lch-blue-medium))); + --bubble-opacity: 75%; --bubble-shape: 54% 46% 61% 39% / 57% 49% 51% 43%; &:before { background: radial-gradient( - color-mix(in srgb, var(--bubble-color) 8%, var(--color-canvas)) 50%, - color-mix(in srgb, var(--bubble-color) 48%, var(--color-canvas)) 100% + color-mix(in srgb, var(--bubble-color) calc(var(--bubble-opacity) / 5), var(--color-canvas)) 50%, + color-mix(in srgb, var(--bubble-color) var(--bubble-opacity), var(--color-canvas)) 100% ); block-size: 1em; border-radius: var(--bubble-shape); @@ -777,6 +804,22 @@ z-index: -1; } } + + @media (max-width: 639px) { + &.cards__expander-title:before { + display: none; + } + } + + html[data-theme="dark"] & { + --bubble-opacity: 100%; + } + + @media (prefers-color-scheme: dark) { + html:not([data-theme]) & { + --bubble-opacity: 100%; + } + } } /* Card column indicators @@ -806,6 +849,7 @@ --btn-background: var(--card-color); color: var(--color-ink-inverted); + opacity: 1 !important; @media (hover: hover) { &:hover { diff --git a/app/assets/stylesheets/card-perma.css b/app/assets/stylesheets/card-perma.css index 240dadb4b..8d1340c23 100644 --- a/app/assets/stylesheets/card-perma.css +++ b/app/assets/stylesheets/card-perma.css @@ -343,5 +343,9 @@ grid-area: closure-message; margin-block-start: 0.5ch; padding-inline: 1ch; + + form { + display: inline; + } } } diff --git a/app/assets/stylesheets/comments.css b/app/assets/stylesheets/comments.css index 2ae664dcd..006cbb985 100644 --- a/app/assets/stylesheets/comments.css +++ b/app/assets/stylesheets/comments.css @@ -4,7 +4,7 @@ --comment-padding-block: var(--block-space-half); --comment-padding-inline: var(--inline-space-double); --comment-max: 70ch; - --reaction-size: 2rem; + --reaction-size: 2.25rem; display: flex; flex-direction: column; @@ -74,10 +74,6 @@ .comments--system-expanded .comment-by-system & { --stripe-color: color-mix(in srgb, var(--card-color) 10%, var(--color-canvas)); } - - .comment__history { - background-color: var(--stripe-color); - } } .reactions { @@ -113,6 +109,13 @@ &:not:has(lexxy-editor) { padding-inline-end: var(--reaction-size); } + + /* Add an empty space so the last line of text doesn't overlap with the reaction button */ + .action-text-content > p:last-child::after { + content: ""; + display: inline-block; + inline-size: var(--reaction-size); + } } .comment__content { @@ -134,13 +137,35 @@ .comment__edit { background-color: var(--color-ink-lightest); + + &:hover { + z-index: 1; + } + } + + .comment__permalink-title { + color: currentColor; + opacity: 0.66; + text-decoration: none; + text-transform: capitalize; + + @media (max-width: 639px) { + line-height: 1.5lh; + } } .comment__history { - background-color: var(--color-ink-lightest); + background-color: transparent; display: none; - inset: calc(var(--comment-padding-inline) / 2.5) calc(var(--comment-padding-inline) / 1.5) auto auto; margin-inline-end: calc(var(--comment-padding-inline) / -2.5); + inset: var(--comment-padding-block) var(--comment-padding-block) auto auto; + translate: 2px -2px; /* Align baseline with time stamp */ position: absolute; + + @media (any-hover: hover) { + &:hover { + background-color: var(--stripe-color); + } + } } .comment-by-system { @@ -158,7 +183,7 @@ display: contents; .comment__history { - display: grid; + display: inline-flex; } } diff --git a/app/assets/stylesheets/golden-effect.css b/app/assets/stylesheets/golden-effect.css index 423286779..c12af8575 100644 --- a/app/assets/stylesheets/golden-effect.css +++ b/app/assets/stylesheets/golden-effect.css @@ -14,5 +14,9 @@ 0 0 0 1px color-mix(in oklch, var(--color-golden) 100%, transparent), 0 0 0.2em 0.2em color-mix(in oklch, var(--color-golden) 25%, transparent), 0 0 1em 0.5em color-mix(in oklch, var(--color-golden) 25%, transparent); + + lexxy-toolbar { + --lexxy-bg-color: transparent; + } } } diff --git a/app/assets/stylesheets/notifications.css b/app/assets/stylesheets/notifications.css index ada7324d2..91ff5750d 100644 --- a/app/assets/stylesheets/notifications.css +++ b/app/assets/stylesheets/notifications.css @@ -30,7 +30,7 @@ } &:has(.card--notification) { - .notifications-list__empty-message { + .notifications-list__blank-slate { display: none; } } diff --git a/app/assets/stylesheets/search.css b/app/assets/stylesheets/search.css index fcd4563ca..5691ac5e5 100644 --- a/app/assets/stylesheets/search.css +++ b/app/assets/stylesheets/search.css @@ -71,7 +71,7 @@ summary { text-align: start; } - .search__empty { + .search__blank-slate { margin-block: 3em; margin-inline: auto; inline-size: fit-content; diff --git a/app/assets/stylesheets/utilities.css b/app/assets/stylesheets/utilities.css index 62304d020..76dedfe12 100644 --- a/app/assets/stylesheets/utilities.css +++ b/app/assets/stylesheets/utilities.css @@ -13,6 +13,7 @@ .txt-align-start { text-align: start; } .txt-align-end { text-align: end; } + .txt-current { color: currentColor; } .txt-ink { color: var(--color-ink); } .txt-reversed { color: var(--color-ink-inverted); } .txt-negative { color: var(--color-negative); } @@ -23,14 +24,16 @@ .txt-underline { text-decoration: underline; } .txt-tight-lines { line-height: 1.2; } .txt-nowrap { white-space: nowrap; } + .txt-balance { text-wrap: balance; } .txt-break { word-break: break-word; } .txt-uppercase { text-transform: uppercase; } .txt-capitalize { text-transform: capitalize; } .txt-capitalize-first-letter::first-letter { text-transform: capitalize; } .txt-link { color: var(--color-link); text-decoration: underline; } - .font-weight-black { font-weight: 900; } .font-weight-normal { font-weight: normal; } + .font-weight-bold { font-weight: bold; } + .font-weight-black { font-weight: 900; } /* Flexbox and Grid */ .justify-end { justify-content: end; } diff --git a/app/controllers/account/cancellations_controller.rb b/app/controllers/account/cancellations_controller.rb new file mode 100644 index 000000000..b6ac82ab4 --- /dev/null +++ b/app/controllers/account/cancellations_controller.rb @@ -0,0 +1,13 @@ +class Account::CancellationsController < ApplicationController + before_action :ensure_owner + + def create + Current.account.cancel + redirect_to session_menu_path(script_name: nil), notice: "Account deleted" + end + + private + def ensure_owner + head :forbidden unless Current.user.owner? + end +end diff --git a/app/controllers/concerns/authorization.rb b/app/controllers/concerns/authorization.rb index f30d518cc..a6456a050 100644 --- a/app/controllers/concerns/authorization.rb +++ b/app/controllers/concerns/authorization.rb @@ -2,7 +2,7 @@ module Authorization extend ActiveSupport::Concern included do - before_action :ensure_can_access_account, if: -> { Current.account.present? && authenticated? } + before_action :ensure_can_access_account, if: :authenticated_account_access? end class_methods do @@ -25,8 +25,12 @@ module Authorization head :forbidden unless Current.identity.staff? end + def authenticated_account_access? + Current.account.present? && authenticated? + end + def ensure_can_access_account - if Current.user.blank? || !Current.user.active? + unless Current.account.active? && Current.user&.active? respond_to do |format| format.html { redirect_to session_menu_path(script_name: nil) } format.json { head :forbidden } diff --git a/app/controllers/my/menus_controller.rb b/app/controllers/my/menus_controller.rb index 32b3da004..24fa2b085 100644 --- a/app/controllers/my/menus_controller.rb +++ b/app/controllers/my/menus_controller.rb @@ -4,7 +4,7 @@ class My::MenusController < ApplicationController @boards = Current.user.boards.ordered_by_recently_accessed @tags = Current.account.tags.all.alphabetically @users = Current.account.users.active.alphabetically - @accounts = Current.identity.accounts + @accounts = Current.identity.accounts.active fresh_when etag: [ @filters, @boards, @tags, @users, @accounts ] end diff --git a/app/controllers/notifications/unsubscribes_controller.rb b/app/controllers/notifications/unsubscribes_controller.rb index f7486d91e..d1842b169 100644 --- a/app/controllers/notifications/unsubscribes_controller.rb +++ b/app/controllers/notifications/unsubscribes_controller.rb @@ -1,6 +1,6 @@ class Notifications::UnsubscribesController < ApplicationController allow_unauthenticated_access - skip_before_action :verify_authenticity_token + skip_forgery_protection before_action :set_user diff --git a/app/controllers/public/base_controller.rb b/app/controllers/public/base_controller.rb index 018b1ab36..503c914e4 100644 --- a/app/controllers/public/base_controller.rb +++ b/app/controllers/public/base_controller.rb @@ -2,6 +2,7 @@ class Public::BaseController < ApplicationController allow_unauthenticated_access before_action :set_board, :set_card, :set_public_cache_expiration + before_action :ensure_board_accessible layout "public" @@ -17,4 +18,8 @@ class Public::BaseController < ApplicationController def set_public_cache_expiration expires_in 30.seconds, public: true end + + def ensure_board_accessible + raise ActionController::RoutingError, "Not Found" if @board&.account&.cancelled? + end end diff --git a/app/controllers/sessions/menus_controller.rb b/app/controllers/sessions/menus_controller.rb index 984e83a43..5fd885c45 100644 --- a/app/controllers/sessions/menus_controller.rb +++ b/app/controllers/sessions/menus_controller.rb @@ -4,7 +4,7 @@ class Sessions::MenusController < ApplicationController layout "public" def show - @accounts = Current.identity.accounts + @accounts = Current.identity.accounts.active if @accounts.one? redirect_to root_url(script_name: @accounts.first.slug) diff --git a/app/helpers/columns_helper.rb b/app/helpers/columns_helper.rb index a92fb2824..b3e98b30e 100644 --- a/app/helpers/columns_helper.rb +++ b/app/helpers/columns_helper.rb @@ -5,6 +5,7 @@ module ColumnsHelper card_triage_path(card, column_id: column), method: :post, class: [ "card__column-name btn", { "card__column-name--current": column == card.column && card.open? } ], + disabled: column == card.column, style: "--column-color: #{column.color}", form_class: "flex gap-half", data: { turbo_frame: "_top", scroll_to_target: column == card.column && card.open? ? "target" : nil } diff --git a/app/javascript/controllers/collapsible_columns_controller.js b/app/javascript/controllers/collapsible_columns_controller.js index 1a9ba4174..e432a054f 100644 --- a/app/javascript/controllers/collapsible_columns_controller.js +++ b/app/javascript/controllers/collapsible_columns_controller.js @@ -14,13 +14,12 @@ export default class extends Controller { } async connect() { - await this.#restoreColumnsDisablingTransitions() - this.#setupIntersectionObserver() - this.mediaQuery = window.matchMedia(this.desktopBreakpointValue) this.handleDesktop = this.#handleDesktop.bind(this) this.mediaQuery.addEventListener("change", this.handleDesktop) - this.handleDesktop(this.mediaQuery) + + await this.#restoreColumnsDisablingTransitions() + this.#setupIntersectionObserver() } disconnect() { @@ -57,6 +56,7 @@ export default class extends Controller { async #restoreColumnsDisablingTransitions() { this.#disableTransitions() this.#restoreColumns() + this.#handleDesktop() await nextFrame() this.#enableTransitions() diff --git a/app/jobs/account/incinerate_due_job.rb b/app/jobs/account/incinerate_due_job.rb new file mode 100644 index 000000000..5bb7b35eb --- /dev/null +++ b/app/jobs/account/incinerate_due_job.rb @@ -0,0 +1,14 @@ +class Account::IncinerateDueJob < ApplicationJob + include ActiveJob::Continuable + + queue_as :incineration + + def perform + step :incineration do |step| + Account.due_for_incineration.find_each do |account| + account.incinerate + step.checkpoint! + end + end + end +end diff --git a/app/jobs/board/clean_inaccessible_data_job.rb b/app/jobs/board/clean_inaccessible_data_job.rb index b2cdff085..5aa1eb0eb 100644 --- a/app/jobs/board/clean_inaccessible_data_job.rb +++ b/app/jobs/board/clean_inaccessible_data_job.rb @@ -1,4 +1,6 @@ class Board::CleanInaccessibleDataJob < ApplicationJob + discard_on ActiveJob::DeserializationError + def perform(user, board) board.clean_inaccessible_data_for(user) end diff --git a/app/jobs/card/activity_spike/detection_job.rb b/app/jobs/card/activity_spike/detection_job.rb index 2f5142d0d..82278e5d7 100644 --- a/app/jobs/card/activity_spike/detection_job.rb +++ b/app/jobs/card/activity_spike/detection_job.rb @@ -1,4 +1,6 @@ class Card::ActivitySpike::DetectionJob < ApplicationJob + discard_on ActiveJob::DeserializationError + def perform(card) card.detect_activity_spikes end diff --git a/app/jobs/card/remove_inaccessible_notifications_job.rb b/app/jobs/card/remove_inaccessible_notifications_job.rb index db7dec4e3..018c6cf8d 100644 --- a/app/jobs/card/remove_inaccessible_notifications_job.rb +++ b/app/jobs/card/remove_inaccessible_notifications_job.rb @@ -1,4 +1,6 @@ class Card::RemoveInaccessibleNotificationsJob < ApplicationJob + discard_on ActiveJob::DeserializationError + def perform(card) card.remove_inaccessible_notifications end diff --git a/app/jobs/event/webhook_dispatch_job.rb b/app/jobs/event/webhook_dispatch_job.rb index 1df418cc8..96f01ba50 100644 --- a/app/jobs/event/webhook_dispatch_job.rb +++ b/app/jobs/event/webhook_dispatch_job.rb @@ -5,6 +5,8 @@ class Event::WebhookDispatchJob < ApplicationJob queue_as :webhooks + discard_on ActiveJob::DeserializationError + def perform(event) step :dispatch do |step| Webhook.active.triggered_by(event).find_each(start: step.cursor) do |webhook| diff --git a/app/jobs/export_account_data_job.rb b/app/jobs/export_account_data_job.rb index 82b5071cb..c0286c736 100644 --- a/app/jobs/export_account_data_job.rb +++ b/app/jobs/export_account_data_job.rb @@ -1,6 +1,8 @@ class ExportAccountDataJob < ApplicationJob queue_as :backend + discard_on ActiveJob::DeserializationError + def perform(export) export.build end diff --git a/app/jobs/mention/create_job.rb b/app/jobs/mention/create_job.rb index 816782754..4376ac493 100644 --- a/app/jobs/mention/create_job.rb +++ b/app/jobs/mention/create_job.rb @@ -1,4 +1,6 @@ class Mention::CreateJob < ApplicationJob + discard_on ActiveJob::DeserializationError + def perform(record, mentioner:) record.create_mentions(mentioner:) end diff --git a/app/jobs/notification/bundle/deliver_all_job.rb b/app/jobs/notification/bundle/deliver_all_job.rb index 79aeaed16..d3e2cb738 100644 --- a/app/jobs/notification/bundle/deliver_all_job.rb +++ b/app/jobs/notification/bundle/deliver_all_job.rb @@ -1,6 +1,8 @@ class Notification::Bundle::DeliverAllJob < ApplicationJob queue_as :backend + discard_on ActiveJob::DeserializationError + def perform Notification::Bundle.deliver_all end diff --git a/app/jobs/notification/bundle/deliver_job.rb b/app/jobs/notification/bundle/deliver_job.rb index 11f6bcda5..b4059a779 100644 --- a/app/jobs/notification/bundle/deliver_job.rb +++ b/app/jobs/notification/bundle/deliver_job.rb @@ -3,6 +3,8 @@ class Notification::Bundle::DeliverJob < ApplicationJob queue_as :backend + discard_on ActiveJob::DeserializationError + def perform(bundle) bundle.deliver end diff --git a/app/jobs/notify_recipients_job.rb b/app/jobs/notify_recipients_job.rb index 6083622fd..bba5898da 100644 --- a/app/jobs/notify_recipients_job.rb +++ b/app/jobs/notify_recipients_job.rb @@ -1,4 +1,6 @@ class NotifyRecipientsJob < ApplicationJob + discard_on ActiveJob::DeserializationError + def perform(notifiable) notifiable.notify_recipients end diff --git a/app/jobs/push_notification_job.rb b/app/jobs/push_notification_job.rb index 124366967..c912e141d 100644 --- a/app/jobs/push_notification_job.rb +++ b/app/jobs/push_notification_job.rb @@ -1,4 +1,6 @@ class PushNotificationJob < ApplicationJob + discard_on ActiveJob::DeserializationError + def perform(notification) NotificationPusher.new(notification).push end diff --git a/app/jobs/storage/reconcile_job.rb b/app/jobs/storage/reconcile_job.rb index cd3bf3803..43574f428 100644 --- a/app/jobs/storage/reconcile_job.rb +++ b/app/jobs/storage/reconcile_job.rb @@ -5,6 +5,7 @@ class Storage::ReconcileJob < ApplicationJob limits_concurrency to: 1, key: ->(owner) { owner } discard_on ActiveJob::DeserializationError + retry_on ReconcileAborted, wait: 1.minute, attempts: 3 def perform(owner) diff --git a/app/jobs/webhook/delivery_job.rb b/app/jobs/webhook/delivery_job.rb index 95102b9d6..a6e324985 100644 --- a/app/jobs/webhook/delivery_job.rb +++ b/app/jobs/webhook/delivery_job.rb @@ -1,6 +1,8 @@ class Webhook::DeliveryJob < ApplicationJob queue_as :webhooks + discard_on ActiveJob::DeserializationError + def perform(delivery) delivery.deliver end diff --git a/app/mailers/account_mailer.rb b/app/mailers/account_mailer.rb new file mode 100644 index 000000000..5cb6616f8 --- /dev/null +++ b/app/mailers/account_mailer.rb @@ -0,0 +1,11 @@ +class AccountMailer < ApplicationMailer + def cancellation(cancellation) + @account = cancellation.account + @user = cancellation.initiated_by + + mail( + to: @user.identity.email_address, + subject: "Your Fizzy account was cancelled" + ) + end +end diff --git a/app/models/account.rb b/app/models/account.rb index f9b2589c0..34b63b688 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -1,7 +1,7 @@ class Account < ApplicationRecord - include Account::Storage, Entropic, MultiTenantable, Seedeable + include Account::Storage, Cancellable, Entropic, Incineratable, MultiTenantable, Seedeable - has_one :join_code + has_one :join_code, dependent: :destroy has_many :users, dependent: :destroy has_many :boards, dependent: :destroy has_many :cards, dependent: :destroy diff --git a/app/models/account/cancellable.rb b/app/models/account/cancellable.rb new file mode 100644 index 000000000..f10624245 --- /dev/null +++ b/app/models/account/cancellable.rb @@ -0,0 +1,46 @@ +module Account::Cancellable + extend ActiveSupport::Concern + + included do + has_one :cancellation, dependent: :destroy + + scope :active, -> { where.missing(:cancellation) } + + define_callbacks :cancel + define_callbacks :reactivate + end + + def cancel(initiated_by: Current.user) + with_lock do + if cancellable? && active? + run_callbacks :cancel do + create_cancellation!(initiated_by: initiated_by) + end + + AccountMailer.cancellation(cancellation).deliver_later + end + end + end + + def reactivate + with_lock do + if cancelled? + run_callbacks :reactivate do + cancellation.destroy + end + end + end + end + + def active? + !cancelled? + end + + def cancelled? + cancellation.present? + end + + def cancellable? + Account.accepting_signups? + end +end diff --git a/app/models/account/cancellation.rb b/app/models/account/cancellation.rb new file mode 100644 index 000000000..ba8e73bed --- /dev/null +++ b/app/models/account/cancellation.rb @@ -0,0 +1,4 @@ +class Account::Cancellation < ApplicationRecord + belongs_to :account + belongs_to :initiated_by, class_name: "User" +end diff --git a/app/models/account/incineratable.rb b/app/models/account/incineratable.rb new file mode 100644 index 000000000..c734c0768 --- /dev/null +++ b/app/models/account/incineratable.rb @@ -0,0 +1,17 @@ +module Account::Incineratable + extend ActiveSupport::Concern + + INCINERATION_GRACE_PERIOD = 30.days + + included do + scope :due_for_incineration, -> { joins(:cancellation).where(account_cancellations: { created_at: ...INCINERATION_GRACE_PERIOD.ago }) } + + define_callbacks :incinerate + end + + def incinerate + run_callbacks :incinerate do + account.destroy + end + end +end diff --git a/app/models/board/accessible.rb b/app/models/board/accessible.rb index 2f668ae4f..e5b86dbe1 100644 --- a/app/models/board/accessible.rb +++ b/app/models/board/accessible.rb @@ -46,6 +46,7 @@ module Board::Accessible mentions_for_user(user).destroy_all notifications_for_user(user).destroy_all watches_for(user).destroy_all + pins_for(user).destroy_all end def watchers @@ -99,4 +100,8 @@ module Board::Accessible def watches_for(user) Watch.where(card: cards, user: user) end + + def pins_for(user) + Pin.where(card: cards, user: user) + end end diff --git a/app/models/notification/bundle.rb b/app/models/notification/bundle.rb index 8617a0f99..fb57a4fd0 100644 --- a/app/models/notification/bundle.rb +++ b/app/models/notification/bundle.rb @@ -74,7 +74,7 @@ class Notification::Bundle < ApplicationRecord end def deliverable? - user.settings.bundling_emails? && notifications.any? + user.settings.bundling_emails? && notifications.any? && account.active? end def overlapping_bundles diff --git a/app/models/notification_pusher.rb b/app/models/notification_pusher.rb index acb5603a1..d6425e561 100644 --- a/app/models/notification_pusher.rb +++ b/app/models/notification_pusher.rb @@ -20,7 +20,8 @@ class NotificationPusher def should_push? notification.user.push_subscriptions.any? && !notification.creator.system? && - notification.user.active? + notification.user.active? && + notification.account.active? end def build_payload diff --git a/app/models/webhook/triggerable.rb b/app/models/webhook/triggerable.rb index 443c801fd..7f7ab5771 100644 --- a/app/models/webhook/triggerable.rb +++ b/app/models/webhook/triggerable.rb @@ -7,6 +7,6 @@ module Webhook::Triggerable end def trigger(event) - deliveries.create!(event: event) + deliveries.create!(event: event) unless account.cancelled? end end diff --git a/app/views/account/settings/_cancellation.html.erb b/app/views/account/settings/_cancellation.html.erb new file mode 100644 index 000000000..5c493edd5 --- /dev/null +++ b/app/views/account/settings/_cancellation.html.erb @@ -0,0 +1,26 @@ +<% if Current.account.cancellable? && Current.user.owner? %> +
+

Cancel account

+

Delete your Fizzy account

+
+ +
+ + + +

Delete your account?

+ + +
+ + <%= button_to "Delete my account", account_cancellation_path, method: :post, class: "btn btn--negative", form: { data: { action: "submit->dialog#close", turbo: false } } %> +
+
+
+<% end %> diff --git a/app/views/account/settings/show.html.erb b/app/views/account/settings/show.html.erb index fc5e799b3..74e274414 100644 --- a/app/views/account/settings/show.html.erb +++ b/app/views/account/settings/show.html.erb @@ -18,6 +18,7 @@
<%= render "account/settings/entropy", account: @account %> <%= render "account/settings/export" %> + <%= render "account/settings/cancellation" %>
diff --git a/app/views/boards/columns/_empty_placeholder.html.erb b/app/views/boards/columns/_empty_placeholder.html.erb index d9da8a896..21e709b84 100644 --- a/app/views/boards/columns/_empty_placeholder.html.erb +++ b/app/views/boards/columns/_empty_placeholder.html.erb @@ -1,4 +1,2 @@ -
-

Drag cards here

-
-
No cards here
+
No cards here
+
Drag cards here
diff --git a/app/views/boards/edit/_auto_close.html.erb b/app/views/boards/edit/_auto_close.html.erb index c32c1c050..8993c132e 100644 --- a/app/views/boards/edit/_auto_close.html.erb +++ b/app/views/boards/edit/_auto_close.html.erb @@ -1,7 +1,7 @@ -<%= turbo_frame_tag @board, :entropy do %> +<%= turbo_frame_tag board, :entropy do %>

Auto close

Fizzy doesn’t let stale cards stick around forever. Cards automatically move to “Not now” if no one updates, comments, or moves a card for…

- <%= render "entropy/auto_close", model: board, url: board_entropy_path(board), disabled: !Current.user.can_administer_board?(@board) %> + <%= render "entropy/auto_close", model: board, url: board_entropy_path(board), disabled: !Current.user.can_administer_board?(board) %>
<% end %> diff --git a/app/views/boards/show/_filtered_cards.html.erb b/app/views/boards/show/_filtered_cards.html.erb index 4e4d8f749..53b25f80f 100644 --- a/app/views/boards/show/_filtered_cards.html.erb +++ b/app/views/boards/show/_filtered_cards.html.erb @@ -3,6 +3,6 @@ <%= with_automatic_pagination :filtered_cards_paginated_container, page do %> <%= render "cards/display/previews", cards: page.records, draggable: true %> <% end %> -
No cards match this filter
+
No cards match this filter
diff --git a/app/views/cards/columns/edit.html.erb b/app/views/cards/columns/edit.html.erb index 586fac20f..162bfbbeb 100644 --- a/app/views/cards/columns/edit.html.erb +++ b/app/views/cards/columns/edit.html.erb @@ -5,6 +5,7 @@ <%= button_to "Not now", card_not_now_path(@card), class: [ "card__column-name btn", { "card__column-name--current": @card.postponed? } ], style: "--column-color: var(--color-card-complete)", + disabled: @card.postponed?, role: "radio", aria: { checked: @card.postponed? }, data: { scroll_to_target: @card.postponed? ? "target" : nil }, @@ -12,7 +13,8 @@ <%= button_to "Maybe?", card_triage_path(@card), method: :delete, class: [ "card__column-name card__column-name--stream btn", { "card__column-name--current": @card.awaiting_triage? } ], - style: "--column-color: var(--color-card-default)", + style: "--column-color: var(--color-card-default)", + disabled: @card.awaiting_triage?, role: "radio", aria: { checked: @card.awaiting_triage? }, data: { scroll_to_target: @card.awaiting_triage? ? "target" : nil }, @@ -25,6 +27,7 @@ <%= button_to "Done", card_closure_path(@card), class: [ "card__column-name btn", { "card__column-name--current": @card.closed? } ], style: "--column-color: var(--color-card-complete)", + disabled: @card.closed?, role: "radio", aria: { checked: @card.closed? }, data: { scroll_to_target: @card.closed? ? "target" : nil }, diff --git a/app/views/cards/comments/_comment.html.erb b/app/views/cards/comments/_comment.html.erb index d693761ea..199bde108 100644 --- a/app/views/cards/comments/_comment.html.erb +++ b/app/views/cards/comments/_comment.html.erb @@ -14,7 +14,7 @@ <%= link_to comment.creator.name, comment.creator, class: "txt-ink btn btn--plain fill-transparent", data: { turbo_frame: "_top" } %> - <%= link_to comment, class: "txt-undecorated txt-ink translucent txt-normal txt-capitalize", data: { turbo_frame: "_top" } do %> + <%= link_to comment, class: "comment__permalink-title", data: { turbo_frame: "_top" } do %> <%= local_datetime_tag comment.created_at, style: :agoorweekday %>, <%= local_datetime_tag comment.created_at, style: :time %> <% end %> diff --git a/app/views/cards/container/_closure.html.erb b/app/views/cards/container/_closure.html.erb index 09b7a4ef4..8aa190c78 100644 --- a/app/views/cards/container/_closure.html.erb +++ b/app/views/cards/container/_closure.html.erb @@ -2,10 +2,12 @@ <% if card.closed? %>
- Completed by <%= card.closed_by.name %> on <%= local_datetime_tag(card.closed_at, style: :shortdate) %>. - <%= button_to card_closure_path(card), method: :delete, class: "btn btn--plain borderless fill-transparent" do %> - Undo - <% end %> + + Completed by <%= card.closed_by.name %> on <%= local_datetime_tag(card.closed_at, style: :shortdate) %>. + <%= button_to card_closure_path(card), method: :delete, class: "btn btn--plain borderless fill-transparent" do %> + Undo + <% end %> +
<% else %> diff --git a/app/views/cards/index.html.erb b/app/views/cards/index.html.erb index a7e4769f5..20bbfac98 100644 --- a/app/views/cards/index.html.erb +++ b/app/views/cards/index.html.erb @@ -23,7 +23,7 @@ <%= with_automatic_pagination :cards_paginated_container, @page do %> <%= render "cards/display/previews", cards: @page.records, draggable: true %> <% end %> -
No cards match this filter
+
No cards match this filter
<% end %> diff --git a/app/views/events/index.html.erb b/app/views/events/index.html.erb index 1279e4dac..5e0744d26 100644 --- a/app/views/events/index.html.erb +++ b/app/views/events/index.html.erb @@ -6,7 +6,7 @@ <% content_for :header do %> <%= render "events/index/add_card_button", user_filtering: @user_filtering %> -

+

<% if @user_filtering.boards.many? %> Activity <%= @user_filtering.filter.boards.any? ? "in" : "across" %> <% else %> diff --git a/app/views/layouts/shared/_colophon.html.erb b/app/views/layouts/shared/_colophon.html.erb new file mode 100644 index 000000000..a6acb8a3b --- /dev/null +++ b/app/views/layouts/shared/_colophon.html.erb @@ -0,0 +1,9 @@ +<%= link_to "https://www.fizzy.do", class: "txt-current font-weight-bold txt-nowrap", target: "_blank", rel: "noopener noreferrer" do %> + <%= icon_tag "fizzy", class: "v-align-middle" %> + Fizzy™ +<% end %> +is designed, built, and backed by +<%= link_to "https://37signals.com", class: "txt-current font-weight-bold txt-nowrap", target: "_blank", rel: "noopener noreferrer" do %> + <%= icon_tag "37signals", class: "v-align-middle" %> + 37signals™ +<% end %> diff --git a/app/views/mailers/account_mailer/cancellation.html.erb b/app/views/mailers/account_mailer/cancellation.html.erb new file mode 100644 index 000000000..831a1cdab --- /dev/null +++ b/app/views/mailers/account_mailer/cancellation.html.erb @@ -0,0 +1,14 @@ +

Your Fizzy account <%= @account.name %> was cancelled.

+ +

What happens now?

+ + + +Changed your mind? +

If you want to cancel this deletion and restore your account, send us an email to support@fizzy.do as soon as possible.

diff --git a/app/views/mailers/account_mailer/cancellation.text.erb b/app/views/mailers/account_mailer/cancellation.text.erb new file mode 100644 index 000000000..3cfaf2689 --- /dev/null +++ b/app/views/mailers/account_mailer/cancellation.text.erb @@ -0,0 +1,13 @@ +Your Fizzy account "<%= @account.name %>" was cancelled. + +WHAT HAPPENS NOW? + +- No one can access the account anymore +<% if @account.try(:subscription) %> +- We won't charge you anymore +<% end %> +- Everything in the account will be deleted in <%= distance_of_time_in_words_to_now(Account::Incineratable::INCINERATION_GRACE_PERIOD.from_now) %> + +CHANGED YOUR MIND? + +If you want to cancel this deletion and restore your account, send us an email to support@fizzy.do as soon as possible. diff --git a/app/views/my/menus/_jump.html.erb b/app/views/my/menus/_jump.html.erb index 1f1ea70c9..f22bedf23 100644 --- a/app/views/my/menus/_jump.html.erb +++ b/app/views/my/menus/_jump.html.erb @@ -26,7 +26,7 @@ <%= yield %> - diff --git a/app/views/my/menus/show.html.erb b/app/views/my/menus/show.html.erb index cb3e6310e..a942c2af7 100644 --- a/app/views/my/menus/show.html.erb +++ b/app/views/my/menus/show.html.erb @@ -9,6 +9,6 @@ <% end %> <% end %> diff --git a/app/views/notifications/index/_unread_notifications.html.erb b/app/views/notifications/index/_unread_notifications.html.erb index b8b9618d1..4890a4822 100644 --- a/app/views/notifications/index/_unread_notifications.html.erb +++ b/app/views/notifications/index/_unread_notifications.html.erb @@ -5,7 +5,7 @@ <%= button_to "Mark all as read", bulk_reading_path, class: "btn txt-small", form: { data: { turbo: false } }, data: { action: "badge#clear" } %> <% else %> -
+
Nothing new for you
<% end %> diff --git a/app/views/public/_footer.html.erb b/app/views/public/_footer.html.erb index a9b5d1853..ab2856821 100644 --- a/app/views/public/_footer.html.erb +++ b/app/views/public/_footer.html.erb @@ -1,3 +1,3 @@
- <%= icon_tag "fizzy", class: "v-align-middle" %> Fizzy™ is designed, built, and backed by <%= icon_tag "37signals", class: "v-align-middle" %> 37signals. + <%= render "layouts/shared/colophon" %>
diff --git a/app/views/public/boards/columns/closeds/show.html.erb b/app/views/public/boards/columns/closeds/show.html.erb index 21977743e..61ce3887d 100644 --- a/app/views/public/boards/columns/closeds/show.html.erb +++ b/app/views/public/boards/columns/closeds/show.html.erb @@ -18,7 +18,7 @@ <%= render "cards/display/public_previews", cards: @page.records %> <% end %> <% else %> -
No cards here
+
No cards here
<% end %>
<% end %> diff --git a/app/views/public/boards/columns/not_nows/show.html.erb b/app/views/public/boards/columns/not_nows/show.html.erb index 786cb7afb..3dad8bcfe 100644 --- a/app/views/public/boards/columns/not_nows/show.html.erb +++ b/app/views/public/boards/columns/not_nows/show.html.erb @@ -18,7 +18,7 @@ <%= render "cards/display/public_previews", cards: @page.records %> <% end %> <% else %> -
No cards here
+
No cards here
<% end %> <% end %> diff --git a/app/views/public/boards/columns/show.html.erb b/app/views/public/boards/columns/show.html.erb index 6f0d2542f..a8a86451b 100644 --- a/app/views/public/boards/columns/show.html.erb +++ b/app/views/public/boards/columns/show.html.erb @@ -18,7 +18,7 @@ <%= render "cards/display/public_previews", cards: @page.records %> <% end %> <% else %> -
No cards here
+
No cards here
<% end %> <% end %> diff --git a/app/views/public/boards/columns/streams/show.html.erb b/app/views/public/boards/columns/streams/show.html.erb index 4df8c2b12..5566a7955 100644 --- a/app/views/public/boards/columns/streams/show.html.erb +++ b/app/views/public/boards/columns/streams/show.html.erb @@ -18,7 +18,7 @@ <%= render "cards/display/public_previews", cards: @page.records %> <% end %> <% else %> -
No cards here
+
No cards here
<% end %> <% end %> diff --git a/app/views/searches/_results.html.erb b/app/views/searches/_results.html.erb index 602878c65..0b622e95d 100644 --- a/app/views/searches/_results.html.erb +++ b/app/views/searches/_results.html.erb @@ -1,7 +1,7 @@