diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index 953f09b72..9d80d143d 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -101,7 +101,7 @@ jobs: - name: Attest image provenance (per-arch) if: github.event_name != 'pull_request' - uses: actions/attest-build-provenance@v3.2.0 + uses: actions/attest-build-provenance@v4.1.0 with: subject-name: ${{ steps.vars.outputs.canonical }} subject-digest: ${{ steps.build.outputs.digest }} diff --git a/Gemfile b/Gemfile index 17e7496c0..da66e1473 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,7 @@ gem "rails", github: "rails/rails", branch: "main" gem "importmap-rails" gem "propshaft" gem "stimulus-rails" -gem "turbo-rails" +gem "turbo-rails", github: "hotwired/turbo-rails", branch: "offline-cache" # Deployment and drivers gem "bootsnap", require: false diff --git a/Gemfile.lock b/Gemfile.lock index 4e3653aeb..8db6817dc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -11,6 +11,15 @@ GIT specs: useragent (0.16.11) +GIT + remote: https://github.com/hotwired/turbo-rails.git + revision: 93ce5bc461cd71af1797e79fcd7e09a19242d080 + branch: offline-cache + specs: + turbo-rails (2.0.23) + actionpack (>= 7.1.0) + railties (>= 7.1.0) + GIT remote: https://github.com/rails/rails.git revision: 12e24eaf2f0a9613e015653f013dd131317d9bf5 @@ -158,7 +167,7 @@ GEM bindex (0.8.1) bootsnap (1.23.0) msgpack (~> 1.2) - brakeman (8.0.2) + brakeman (8.0.4) racc builder (3.3.0) bundler-audit (0.9.3) @@ -461,9 +470,6 @@ GEM trilogy (2.10.0) bigdecimal tsort (0.2.0) - turbo-rails (2.0.23) - actionpack (>= 7.1.0) - railties (>= 7.1.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (3.2.0) @@ -539,7 +545,7 @@ DEPENDENCIES stimulus-rails thruster trilogy (~> 2.10) - turbo-rails + turbo-rails! useragent! vcr web-console! diff --git a/Gemfile.saas b/Gemfile.saas index 39aa49a71..384f5b4b7 100644 --- a/Gemfile.saas +++ b/Gemfile.saas @@ -5,7 +5,6 @@ git_source(:bc) { |repo| "https://github.com/basecamp/#{repo}" } gem "activeresource", require: "active_resource" gem "actionpack-xml_parser" # needed by queenbee for XML request body parsing -gem "stripe", "~> 18.0" gem "queenbee", bc: "queenbee-plugin" gem "fizzy-saas", path: "saas" gem "console1984", bc: "console1984" diff --git a/Gemfile.saas.lock b/Gemfile.saas.lock index 0e35effac..6f9b0d088 100644 --- a/Gemfile.saas.lock +++ b/Gemfile.saas.lock @@ -60,6 +60,15 @@ GIT rails (>= 6.1) yabeda (~> 0.6) +GIT + remote: https://github.com/hotwired/turbo-rails.git + revision: 93ce5bc461cd71af1797e79fcd7e09a19242d080 + branch: offline-cache + specs: + turbo-rails (2.0.23) + actionpack (>= 7.1.0) + railties (>= 7.1.0) + GIT remote: https://github.com/rails/rails.git revision: 12e24eaf2f0a9613e015653f013dd131317d9bf5 @@ -248,7 +257,7 @@ GEM bindex (0.8.1) bootsnap (1.23.0) msgpack (~> 1.2) - brakeman (8.0.2) + brakeman (8.0.4) racc builder (3.3.0) bundler-audit (0.9.3) @@ -622,7 +631,6 @@ GEM stimulus-rails (1.3.4) railties (>= 6.0.0) stringio (3.2.0) - stripe (18.0.1) thor (1.5.0) thruster (0.1.18) thruster (0.1.18-aarch64-linux) @@ -633,9 +641,6 @@ GEM trilogy (2.10.0) bigdecimal tsort (0.2.0) - turbo-rails (2.0.23) - actionpack (>= 7.1.0) - railties (>= 7.1.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (3.2.0) @@ -749,10 +754,9 @@ DEPENDENCIES sqlite3 (>= 2.0) stackprof stimulus-rails - stripe (~> 18.0) thruster trilogy (~> 2.10) - turbo-rails + turbo-rails! useragent! vcr web-console! diff --git a/app/assets/stylesheets/card-perma.css b/app/assets/stylesheets/card-perma.css index 755dd8f9a..6d966d0ae 100644 --- a/app/assets/stylesheets/card-perma.css +++ b/app/assets/stylesheets/card-perma.css @@ -146,37 +146,74 @@ @media (max-width: 639px) { --meta-spacer-block: 0.75ch; - inline-size: 100%; - grid-template-areas: - "avatars-author text-added" - "avatars-author text-author" - "text-updated text-updated" - "text-assignees text-assignees" - "avatars-assignees avatars-assignees"; - grid-template-columns: auto 1fr; + min-inline-size: 0; + gap: calc(var(--meta-spacer-block) / 2); + display: flex; + flex-wrap: wrap; .card__meta-text { border: 0; padding: 0; } - .card__meta-text + .card__meta-text { + .card__meta-avatars--author { + --btn-size: 1.5em; + + display: initial; + margin-inline-end: unset; + order: 3; + } + + .card__meta-text--added { + inline-size: 100%; + order: 1; + } + + .card__meta-text--author { + order: 2; + } + + .card__meta-text--updated { border-block-start: var(--card-border); - margin-block-start: var(--meta-spacer-block); + inline-size: 100%; + margin-block-start: calc(var(--meta-spacer-block) * 0.5); + order: 4; padding-block-start: var(--meta-spacer-block); } + .card__meta-text--assignees { + margin-block-start: calc(var(--meta-spacer-block) * 3); + order: 6; + white-space: unset !important; + } + .card__meta-avatars--assignees { - margin-inline: 0; - margin-block-start: var(--meta-spacer-block); + margin-inline: 0 var(--meta-spacer-inline); + margin-block-start: calc(var(--meta-spacer-block) * 3); + order: 5; + + .avatar { + display: grid; + } } - .card__meta-avatars--author { - display: initial; - } + &:has(.card__meta-avatars--assignees .avatar) { + .card__meta-text--assignees { + order: 5; + } - .card__meta-avatars--assignees .avatar { - display: grid; + .card__meta-avatars--assignees { + margin-block-start: var(--meta-spacer-block); + order: 6; + } + } + } + } + + &:has(.card__closed) .card__meta { + @media (max-width: 639px) { + .card__meta-avatars--assignees { + display: none; } } } @@ -241,10 +278,16 @@ @media (max-width: 639px) { display: grid; font-size: var(--text-x-small); + gap: 1ch 0; grid-template-columns: 1fr auto; grid-template-areas: "meta bg-zoom" "meta reactions"; + + &:not(:has(.reaction)), + &:has(.card__background) { + column-gap: 2ch; + } } } diff --git a/app/controllers/account/entropies_controller.rb b/app/controllers/account/entropies_controller.rb index 9876e99d8..44c9debe5 100644 --- a/app/controllers/account/entropies_controller.rb +++ b/app/controllers/account/entropies_controller.rb @@ -1,13 +1,22 @@ class Account::EntropiesController < ApplicationController + wrap_parameters :entropy, include: [ :auto_postpone_period_in_days ] + before_action :ensure_admin def update - Current.account.entropy.update!(entropy_params) - redirect_to account_settings_path, notice: "Account updated" + @account = Current.account + @account.entropy.update!(entropy_params) + + respond_to do |format| + format.html { redirect_to account_settings_path, notice: "Account updated" } + format.json { render "account/settings/show", status: :ok } + end + rescue ActiveRecord::RecordInvalid + head :unprocessable_entity end private def entropy_params - params.expect(entropy: [ :auto_postpone_period ]) + params.expect(entropy: [ :auto_postpone_period_in_days ]) end end diff --git a/app/controllers/account/exports_controller.rb b/app/controllers/account/exports_controller.rb index dbe0a11c1..32fb5bd42 100644 --- a/app/controllers/account/exports_controller.rb +++ b/app/controllers/account/exports_controller.rb @@ -6,11 +6,20 @@ class Account::ExportsController < ApplicationController CURRENT_EXPORT_LIMIT = 10 def show + respond_to do |format| + format.html + format.json { @export ? render(:show) : head(:not_found) } + end end def create - Current.account.exports.create!(user: Current.user).build_later - redirect_to account_settings_path, notice: "Export started. You'll receive an email when it's ready." + @export = Current.account.exports.create!(user: Current.user) + @export.build_later + + respond_to do |format| + format.html { redirect_to account_settings_path, notice: "Export started. You'll receive an email when it's ready." } + format.json { render :show, status: :created } + end end private @@ -23,6 +32,7 @@ class Account::ExportsController < ApplicationController end def set_export - @export = Current.account.exports.completed.find_by(id: params[:id], user: Current.user) + scope = request.format.json? ? Current.account.exports : Current.account.exports.completed + @export = scope.find_by(id: params[:id], user: Current.user) end end diff --git a/app/controllers/account/join_codes_controller.rb b/app/controllers/account/join_codes_controller.rb index 217ee603c..e7306308b 100644 --- a/app/controllers/account/join_codes_controller.rb +++ b/app/controllers/account/join_codes_controller.rb @@ -1,4 +1,6 @@ class Account::JoinCodesController < ApplicationController + wrap_parameters :account_join_code, include: %i[ usage_limit ] + before_action :set_join_code before_action :ensure_admin, only: %i[ update destroy ] @@ -10,15 +12,25 @@ class Account::JoinCodesController < ApplicationController def update if @join_code.update(join_code_params) - redirect_to account_join_code_path + respond_to do |format| + format.html { redirect_to account_join_code_path } + format.json { head :no_content } + end else - render :edit, status: :unprocessable_entity + respond_to do |format| + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @join_code.errors, status: :unprocessable_entity } + end end end def destroy @join_code.reset - redirect_to account_join_code_path + + respond_to do |format| + format.html { redirect_to account_join_code_path } + format.json { head :no_content } + end end private diff --git a/app/controllers/account/settings_controller.rb b/app/controllers/account/settings_controller.rb index 27f47ab99..e4b949070 100644 --- a/app/controllers/account/settings_controller.rb +++ b/app/controllers/account/settings_controller.rb @@ -1,14 +1,23 @@ class Account::SettingsController < ApplicationController + wrap_parameters :account, include: %i[ name ] + before_action :ensure_admin, only: :update before_action :set_account def show - @users = @account.users.active.alphabetically.includes(:identity) + respond_to do |format| + format.html { @users = @account.users.active.alphabetically.includes(:identity) } + format.json + end end def update @account.update!(account_params) - redirect_to account_settings_path + + respond_to do |format| + format.html { redirect_to account_settings_path } + format.json { head :no_content } + end end private diff --git a/app/controllers/boards/columns_controller.rb b/app/controllers/boards/columns_controller.rb index 4b71e7f3e..d0bd13c55 100644 --- a/app/controllers/boards/columns_controller.rb +++ b/app/controllers/boards/columns_controller.rb @@ -1,4 +1,6 @@ class Boards::ColumnsController < ApplicationController + wrap_parameters :column, include: %i[ name color ] + include BoardScoped before_action :set_column, only: %i[ show update destroy ] @@ -18,7 +20,7 @@ class Boards::ColumnsController < ApplicationController respond_to do |format| format.turbo_stream - format.json { head :created, location: board_column_path(@board, @column, format: :json) } + format.json { render :show, status: :created, location: board_column_path(@board, @column, format: :json) } end end diff --git a/app/controllers/boards/entropies_controller.rb b/app/controllers/boards/entropies_controller.rb index e42631eaf..89e262545 100644 --- a/app/controllers/boards/entropies_controller.rb +++ b/app/controllers/boards/entropies_controller.rb @@ -1,14 +1,23 @@ class Boards::EntropiesController < ApplicationController + wrap_parameters :board, include: [ :auto_postpone_period_in_days ] + include BoardScoped before_action :ensure_permission_to_admin_board def update @board.update!(entropy_params) + + respond_to do |format| + format.turbo_stream + format.json { render "boards/show", status: :ok } + end + rescue ActiveRecord::RecordInvalid + head :unprocessable_entity end private def entropy_params - params.expect(board: [ :auto_postpone_period ]) + params.expect(board: [ :auto_postpone_period_in_days ]) end end diff --git a/app/controllers/boards/involvements_controller.rb b/app/controllers/boards/involvements_controller.rb index a904f2989..271b50262 100644 --- a/app/controllers/boards/involvements_controller.rb +++ b/app/controllers/boards/involvements_controller.rb @@ -3,5 +3,11 @@ class Boards::InvolvementsController < ApplicationController def update @board.access_for(Current.user).update!(involvement: params[:involvement]) + + respond_to do |format| + format.html + format.turbo_stream + format.json { head :no_content } + end end end diff --git a/app/controllers/boards_controller.rb b/app/controllers/boards_controller.rb index 604938deb..3d5485f1f 100644 --- a/app/controllers/boards_controller.rb +++ b/app/controllers/boards_controller.rb @@ -1,4 +1,6 @@ class BoardsController < ApplicationController + wrap_parameters :board, include: %i[ name all_access auto_postpone_period_in_days public_description ] + include FilterScoped before_action :set_board, except: %i[ index new create ] @@ -26,7 +28,7 @@ class BoardsController < ApplicationController respond_to do |format| format.html { redirect_to board_path(@board) } - format.json { head :created, location: board_path(@board, format: :json) } + format.json { render :show, status: :created, location: board_path(@board, format: :json) } end end @@ -88,7 +90,7 @@ class BoardsController < ApplicationController end def board_params - params.expect(board: [ :name, :all_access, :auto_postpone_period, :public_description ]) + params.expect(board: [ :name, :all_access, :auto_postpone_period_in_days, :public_description ]) end def grantees diff --git a/app/controllers/cards/comments/reactions_controller.rb b/app/controllers/cards/comments/reactions_controller.rb index 0c822360e..d6b202bb3 100644 --- a/app/controllers/cards/comments/reactions_controller.rb +++ b/app/controllers/cards/comments/reactions_controller.rb @@ -1,4 +1,6 @@ class Cards::Comments::ReactionsController < ApplicationController + wrap_parameters :reaction, include: %i[ content ] + include CardScoped before_action :set_comment @@ -22,7 +24,7 @@ class Cards::Comments::ReactionsController < ApplicationController respond_to do |format| format.turbo_stream { render "reactions/create" } - format.json { head :created } + format.json { render "reactions/show", status: :created } end end diff --git a/app/controllers/cards/comments_controller.rb b/app/controllers/cards/comments_controller.rb index f2b318813..7aed33c8c 100644 --- a/app/controllers/cards/comments_controller.rb +++ b/app/controllers/cards/comments_controller.rb @@ -1,4 +1,5 @@ class Cards::CommentsController < ApplicationController + wrap_parameters :comment, include: %i[ body created_at ] include CardScoped before_action :set_comment, only: %i[ show edit update destroy ] @@ -14,7 +15,7 @@ class Cards::CommentsController < ApplicationController respond_to do |format| format.turbo_stream - format.json { head :created, location: card_comment_path(@card, @comment, format: :json) } + format.json { render :show, status: :created, location: card_comment_path(@card, @comment, format: :json) } end end diff --git a/app/controllers/cards/publishes_controller.rb b/app/controllers/cards/publishes_controller.rb index a0378eec3..a544e6979 100644 --- a/app/controllers/cards/publishes_controller.rb +++ b/app/controllers/cards/publishes_controller.rb @@ -4,11 +4,17 @@ class Cards::PublishesController < ApplicationController def create @card.publish - if add_another_param? - card = @board.cards.create!(status: :drafted) - redirect_to card_draft_path(card), notice: "Card added" - else - redirect_to @card.board + respond_to do |format| + format.html do + if add_another_param? + card = @board.cards.create!(status: :drafted) + redirect_to card_draft_path(card), notice: "Card added" + else + redirect_to @card.board + end + end + + format.json { head :created } end end diff --git a/app/controllers/cards/reactions_controller.rb b/app/controllers/cards/reactions_controller.rb index 4105a4121..aedb4194f 100644 --- a/app/controllers/cards/reactions_controller.rb +++ b/app/controllers/cards/reactions_controller.rb @@ -1,4 +1,6 @@ class Cards::ReactionsController < ApplicationController + wrap_parameters :reaction, include: %i[ content ] + include CardScoped before_action :set_reactable @@ -21,7 +23,7 @@ class Cards::ReactionsController < ApplicationController respond_to do |format| format.turbo_stream { render "reactions/create" } - format.json { head :created } + format.json { render "reactions/show", status: :created } end end diff --git a/app/controllers/cards/readings_controller.rb b/app/controllers/cards/readings_controller.rb index 75fdbac19..4b71272f8 100644 --- a/app/controllers/cards/readings_controller.rb +++ b/app/controllers/cards/readings_controller.rb @@ -4,11 +4,21 @@ class Cards::ReadingsController < ApplicationController def create @notification = @card.read_by(Current.user) record_board_access + + respond_to do |format| + format.turbo_stream + format.json { head :created } + end end def destroy @notification = @card.unread_by(Current.user) record_board_access + + respond_to do |format| + format.turbo_stream + format.json { head :no_content } + end end private diff --git a/app/controllers/cards/steps_controller.rb b/app/controllers/cards/steps_controller.rb index 0b788fe36..4843da232 100644 --- a/app/controllers/cards/steps_controller.rb +++ b/app/controllers/cards/steps_controller.rb @@ -1,14 +1,20 @@ class Cards::StepsController < ApplicationController + wrap_parameters :step, include: %i[ content completed ] + include CardScoped before_action :set_step, only: %i[ show edit update destroy ] + def index + fresh_when etag: @card.steps + end + def create @step = @card.steps.create!(step_params) respond_to do |format| format.turbo_stream - format.json { head :created, location: card_step_path(@card, @step, format: :json) } + format.json { render :show, status: :created, location: card_step_path(@card, @step, format: :json) } end end diff --git a/app/controllers/cards_controller.rb b/app/controllers/cards_controller.rb index e007527b8..015ecda1f 100644 --- a/app/controllers/cards_controller.rb +++ b/app/controllers/cards_controller.rb @@ -1,4 +1,6 @@ class CardsController < ApplicationController + wrap_parameters :card, include: %i[ title description image created_at last_active_at ] + include FilterScoped before_action :set_board, only: %i[ create ] @@ -18,8 +20,8 @@ class CardsController < ApplicationController end format.json do - card = @board.cards.create! card_params.merge(creator: Current.user, status: "published") - head :created, location: card_path(card, format: :json) + @card = @board.cards.create! card_params.merge(creator: Current.user, status: "published") + render :show, status: :created, location: card_path(@card, format: :json) end end end diff --git a/app/controllers/columns/left_positions_controller.rb b/app/controllers/columns/left_positions_controller.rb index 7161c093f..78e0c0c80 100644 --- a/app/controllers/columns/left_positions_controller.rb +++ b/app/controllers/columns/left_positions_controller.rb @@ -4,5 +4,10 @@ class Columns::LeftPositionsController < ApplicationController def create @left_column = @column.left_column @column.move_left + + respond_to do |format| + format.turbo_stream + format.json { head :created } + end end end diff --git a/app/controllers/columns/right_positions_controller.rb b/app/controllers/columns/right_positions_controller.rb index d43beb662..5d9cc37f9 100644 --- a/app/controllers/columns/right_positions_controller.rb +++ b/app/controllers/columns/right_positions_controller.rb @@ -4,5 +4,10 @@ class Columns::RightPositionsController < ApplicationController def create @right_column = @column.right_column @column.move_right + + respond_to do |format| + format.turbo_stream + format.json { head :created } + end end end diff --git a/app/controllers/concerns/set_platform.rb b/app/controllers/concerns/set_platform.rb index 54e77d517..66de85f12 100644 --- a/app/controllers/concerns/set_platform.rb +++ b/app/controllers/concerns/set_platform.rb @@ -7,6 +7,6 @@ module SetPlatform private def platform - @platform ||= ApplicationPlatform.new(request.user_agent) + @platform ||= ApplicationPlatform.new(cookies[:x_user_agent].presence || request.user_agent) end end diff --git a/app/controllers/my/access_tokens_controller.rb b/app/controllers/my/access_tokens_controller.rb index 82cd93842..dc7e9826a 100644 --- a/app/controllers/my/access_tokens_controller.rb +++ b/app/controllers/my/access_tokens_controller.rb @@ -1,4 +1,8 @@ class My::AccessTokensController < ApplicationController + wrap_parameters :access_token, include: %i[ description permission ] + + skip_before_action :require_account + def index @access_tokens = my_access_tokens.order(created_at: :desc) end @@ -24,14 +28,19 @@ class My::AccessTokensController < ApplicationController format.json do render status: :created, json: \ - { token: access_token.token, description: access_token.description, permission: access_token.permission } + { id: access_token.id, token: access_token.token, description: access_token.description, + permission: access_token.permission, created_at: access_token.created_at.utc } end end end def destroy my_access_tokens.find(params[:id]).destroy! - redirect_to my_access_tokens_path + + respond_to do |format| + format.html { redirect_to my_access_tokens_path } + format.json { head :no_content } + end end private diff --git a/app/controllers/notifications/settings_controller.rb b/app/controllers/notifications/settings_controller.rb index c4aab7f69..a7ae3d8a6 100644 --- a/app/controllers/notifications/settings_controller.rb +++ b/app/controllers/notifications/settings_controller.rb @@ -1,4 +1,6 @@ class Notifications::SettingsController < ApplicationController + wrap_parameters :user_settings, include: %i[ bundle_email_frequency ] + before_action :set_settings def show @@ -7,7 +9,11 @@ class Notifications::SettingsController < ApplicationController def update @settings.update!(settings_params) - redirect_to notifications_settings_path, notice: "Settings updated" + + respond_to do |format| + format.html { redirect_to notifications_settings_path, notice: "Settings updated" } + format.json { head :no_content } + end end private diff --git a/app/controllers/searches_controller.rb b/app/controllers/searches_controller.rb index b1ec5a0d2..70dfa4d4d 100644 --- a/app/controllers/searches_controller.rb +++ b/app/controllers/searches_controller.rb @@ -5,10 +5,22 @@ class SearchesController < ApplicationController @query = params[:q].blank? ? nil : params[:q] if card = Current.user.accessible_cards.find_by_id(@query) - @card = card + respond_to do |format| + format.html { @card = card } + format.json { set_page_and_extract_portion_from Current.user.accessible_cards.where(id: card.id) } + end else - set_page_and_extract_portion_from Current.user.search(@query) - @recent_search_queries = Current.user.search_queries.order(updated_at: :desc).limit(10) + respond_to do |format| + format.html do + set_page_and_extract_portion_from Current.user.search(@query) + @recent_search_queries = Current.user.search_queries.order(updated_at: :desc).limit(10) + end + + format.json do + set_page_and_extract_portion_from \ + Current.user.accessible_cards.mentioning(@query, user: Current.user).distinct.latest.preloaded + end + end end end end diff --git a/app/controllers/signups/completions_controller.rb b/app/controllers/signups/completions_controller.rb index de3d81b63..5c2d9568c 100644 --- a/app/controllers/signups/completions_controller.rb +++ b/app/controllers/signups/completions_controller.rb @@ -1,4 +1,6 @@ class Signups::CompletionsController < ApplicationController + wrap_parameters :signup, include: %i[ full_name ] + layout "public" disallow_account_scope diff --git a/app/controllers/signups_controller.rb b/app/controllers/signups_controller.rb index 56614158f..cf43724f1 100644 --- a/app/controllers/signups_controller.rb +++ b/app/controllers/signups_controller.rb @@ -1,4 +1,6 @@ class SignupsController < ApplicationController + wrap_parameters :signup, include: %i[ email_address ] + disallow_account_scope allow_unauthenticated_access rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_signup_path, alert: "Try again later." } diff --git a/app/controllers/users/avatars_controller.rb b/app/controllers/users/avatars_controller.rb index 55d8f06dd..544398331 100644 --- a/app/controllers/users/avatars_controller.rb +++ b/app/controllers/users/avatars_controller.rb @@ -16,7 +16,11 @@ class Users::AvatarsController < ApplicationController def destroy @user.avatar.destroy - redirect_to @user + + respond_to do |format| + format.html { redirect_to @user } + format.json { head :no_content } + end end private diff --git a/app/controllers/users/joins_controller.rb b/app/controllers/users/joins_controller.rb index 6baa888ae..402dd34a3 100644 --- a/app/controllers/users/joins_controller.rb +++ b/app/controllers/users/joins_controller.rb @@ -1,4 +1,6 @@ class Users::JoinsController < ApplicationController + wrap_parameters :user, include: %i[ name avatar ] + layout "public" def new @@ -6,7 +8,11 @@ class Users::JoinsController < ApplicationController def create Current.user.update!(user_params) - redirect_to landing_path + + respond_to do |format| + format.html { redirect_to landing_path } + format.json { head :no_content } + end end private diff --git a/app/controllers/users/push_subscriptions_controller.rb b/app/controllers/users/push_subscriptions_controller.rb index b511a6b19..c52b89541 100644 --- a/app/controllers/users/push_subscriptions_controller.rb +++ b/app/controllers/users/push_subscriptions_controller.rb @@ -1,16 +1,27 @@ class Users::PushSubscriptionsController < ApplicationController + wrap_parameters :push_subscription, include: %i[ endpoint p256dh_key auth_key ] + before_action :set_push_subscriptions def index end def create - @push_subscriptions.create_with(user_agent: request.user_agent).create_or_find_by!(push_subscription_params) + subscription = @push_subscriptions.create_with(user_agent: request.user_agent).create_or_find_by!(push_subscription_params) + + respond_to do |format| + format.html { head :no_content } + format.json { head :created } + end end def destroy @push_subscriptions.destroy_by(id: params[:id]) - redirect_to user_push_subscriptions_url + + respond_to do |format| + format.html { redirect_to user_push_subscriptions_url } + format.json { head :no_content } + end end private diff --git a/app/controllers/users/roles_controller.rb b/app/controllers/users/roles_controller.rb index 711308d13..61c3262ed 100644 --- a/app/controllers/users/roles_controller.rb +++ b/app/controllers/users/roles_controller.rb @@ -1,10 +1,16 @@ class Users::RolesController < ApplicationController + wrap_parameters :user, include: %i[ role ] + before_action :set_user before_action :ensure_permission_to_administer_user def update @user.update!(role_params) - redirect_to account_settings_path + + respond_to do |format| + format.html { redirect_to account_settings_path } + format.json { head :no_content } + end end private diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 7062b85e5..fdc528d3e 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,4 +1,6 @@ class UsersController < ApplicationController + wrap_parameters :user, include: %i[ name avatar ] + before_action :set_user, except: %i[ index ] before_action :ensure_permission_to_change_user, only: %i[ update destroy ] diff --git a/app/controllers/webhooks/activations_controller.rb b/app/controllers/webhooks/activations_controller.rb index 82166043e..2991be51a 100644 --- a/app/controllers/webhooks/activations_controller.rb +++ b/app/controllers/webhooks/activations_controller.rb @@ -4,9 +4,12 @@ class Webhooks::ActivationsController < ApplicationController before_action :ensure_admin def create - webhook = @board.webhooks.find(params[:webhook_id]) - webhook.activate + @webhook = @board.webhooks.find(params[:webhook_id]) + @webhook.activate - redirect_to webhook + respond_to do |format| + format.html { redirect_to @webhook } + format.json { render "webhooks/show", status: :created } + end end end diff --git a/app/controllers/webhooks_controller.rb b/app/controllers/webhooks_controller.rb index 7d79c4587..ab29badaa 100644 --- a/app/controllers/webhooks_controller.rb +++ b/app/controllers/webhooks_controller.rb @@ -1,4 +1,6 @@ class WebhooksController < ApplicationController + wrap_parameters :webhook, include: %i[ name url subscribed_actions ] + include BoardScoped before_action :ensure_admin @@ -16,21 +18,45 @@ class WebhooksController < ApplicationController end def create - webhook = @board.webhooks.create!(webhook_params) - redirect_to webhook + @webhook = @board.webhooks.new(webhook_params) + + if @webhook.save + respond_to do |format| + format.html { redirect_to @webhook } + format.json { render :show, status: :created, location: board_webhook_url(@webhook.board, @webhook, format: :json) } + end + else + respond_to do |format| + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @webhook.errors, status: :unprocessable_entity } + end + end end def edit end def update - @webhook.update!(webhook_params.except(:url)) - redirect_to @webhook + if @webhook.update(webhook_params.except(:url)) + respond_to do |format| + format.html { redirect_to @webhook } + format.json { render :show } + end + else + respond_to do |format| + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @webhook.errors, status: :unprocessable_entity } + end + end end def destroy @webhook.destroy! - redirect_to board_webhooks_path + + respond_to do |format| + format.html { redirect_to board_webhooks_path } + format.json { head :no_content } + end end private diff --git a/app/helpers/entropy_helper.rb b/app/helpers/entropy_helper.rb index 4329dab88..7257ba481 100644 --- a/app/helpers/entropy_helper.rb +++ b/app/helpers/entropy_helper.rb @@ -1,8 +1,4 @@ module EntropyHelper - def entropy_auto_close_options - [ 3, 7, 30, 90, 365, 11 ] - end - def entropy_bubble_options_for(card) { daysBeforeReminder: card.entropy.days_before_reminder, diff --git a/app/javascript/controllers/clear_offline_cache_controller.js b/app/javascript/controllers/clear_offline_cache_controller.js new file mode 100644 index 000000000..7caa2e89b --- /dev/null +++ b/app/javascript/controllers/clear_offline_cache_controller.js @@ -0,0 +1,8 @@ +import { Controller } from "@hotwired/stimulus" +import { Turbo } from "@hotwired/turbo-rails" + +export default class extends Controller { + clearCache() { + Turbo.offline.clearCache() + } +} diff --git a/app/javascript/controllers/magic_link_controller.js b/app/javascript/controllers/magic_link_controller.js index 3e2e4618c..8217af5aa 100644 --- a/app/javascript/controllers/magic_link_controller.js +++ b/app/javascript/controllers/magic_link_controller.js @@ -15,7 +15,7 @@ export default class extends Controller { submit() { if (this.inputTarget.disabled) return - this.element.submit() + this.element.requestSubmit() this.inputTarget.disabled = true } } diff --git a/app/javascript/controllers/upload_preview_controller.js b/app/javascript/controllers/upload_preview_controller.js index d7b0bd478..7e25d50a3 100644 --- a/app/javascript/controllers/upload_preview_controller.js +++ b/app/javascript/controllers/upload_preview_controller.js @@ -15,7 +15,7 @@ export default class extends Controller { } #showFileName() { - this.fileNameTarget.innerHTML = this.#file.name + this.fileNameTarget.innerText = this.#file.name this.fileNameTarget.removeAttribute("hidden") this.placeholderTarget.setAttribute("hidden", true) } diff --git a/app/javascript/initializers/index.js b/app/javascript/initializers/index.js index 90ef36a26..1d16ef205 100644 --- a/app/javascript/initializers/index.js +++ b/app/javascript/initializers/index.js @@ -1,2 +1,3 @@ import "initializers/current" import "initializers/bridge/bridge_element" +import "initializers/offline" diff --git a/app/javascript/initializers/offline.js b/app/javascript/initializers/offline.js new file mode 100644 index 000000000..877ffa0ef --- /dev/null +++ b/app/javascript/initializers/offline.js @@ -0,0 +1,9 @@ +import { Turbo } from "@hotwired/turbo-rails" + +if (Current.user) { + Turbo.offline.start("/service-worker.js", { + scope: "/", + native: true, + preload: /\/assets\// + }) +} diff --git a/app/models/account/data_transfer/active_storage/blob_record_set.rb b/app/models/account/data_transfer/active_storage/blob_record_set.rb index 1637768f3..0c4a553c1 100644 --- a/app/models/account/data_transfer/active_storage/blob_record_set.rb +++ b/app/models/account/data_transfer/active_storage/blob_record_set.rb @@ -13,6 +13,7 @@ class Account::DataTransfer::ActiveStorage::BlobRecordSet < Account::DataTransfe data = load(file) data.slice(*attributes).merge( "account_id" => account.id, + "key" => ::ActiveStorage::Blob.generate_unique_secure_token(length: ::ActiveStorage::Blob::MINIMUM_TOKEN_LENGTH), "service_name" => ::ActiveStorage::Blob.service.name ) end diff --git a/app/models/account/data_transfer/active_storage/file_record_set.rb b/app/models/account/data_transfer/active_storage/file_record_set.rb index 15d359353..421a2e520 100644 --- a/app/models/account/data_transfer/active_storage/file_record_set.rb +++ b/app/models/account/data_transfer/active_storage/file_record_set.rb @@ -22,9 +22,12 @@ class Account::DataTransfer::ActiveStorage::FileRecordSet < Account::DataTransfe def import_batch(files) files.each do |file| - key = File.basename(file) - blob = ::ActiveStorage::Blob.find_by(key: key, account: account) - next unless blob + old_key = file.delete_prefix("storage/") + blob_id = old_key_to_blob_id[old_key] + raise IntegrityError, "Storage file #{old_key} has no matching blob metadata in export" unless blob_id + + blob = ::ActiveStorage::Blob.find_by(id: blob_id, account: account) + raise IntegrityError, "Blob #{blob_id} not found for storage key #{old_key}" unless blob zip.read(file) do |stream| blob.upload(stream) @@ -32,12 +35,31 @@ class Account::DataTransfer::ActiveStorage::FileRecordSet < Account::DataTransfe end end - def check_record(file_path) - key = File.basename(file_path) + def old_key_to_blob_id + @old_key_to_blob_id ||= build_old_key_to_blob_id + end - unless zip.exists?("data/active_storage_blobs/#{key}.json") || ::ActiveStorage::Blob.exists?(key: key, account: account) - # File exists without corresponding blob record - could be orphaned or blob not yet imported - # We allow this since blob metadata is imported before files + def build_old_key_to_blob_id + zip.glob("data/active_storage_blobs/*.json").each_with_object({}) do |file, map| + data = load(file) + old_key = data["key"] + if map.key?(old_key) + raise IntegrityError, "Duplicate blob key in export: #{old_key}" + end + map[old_key] = data["id"] + end + end + + def with_zip(zip) + @old_key_to_blob_id = nil + super + end + + def check_record(file_path) + old_key = file_path.delete_prefix("storage/") + + unless old_key_to_blob_id.key?(old_key) + raise IntegrityError, "Storage file #{old_key} has no matching blob metadata in export" end end end diff --git a/app/models/account/data_transfer/manifest.rb b/app/models/account/data_transfer/manifest.rb index 2159926d5..9de76f9bf 100644 --- a/app/models/account/data_transfer/manifest.rb +++ b/app/models/account/data_transfer/manifest.rb @@ -26,20 +26,42 @@ class Account::DataTransfer::Manifest [ Account::DataTransfer::AccountRecordSet.new(account), Account::DataTransfer::UserRecordSet.new(account), - *build_record_sets(::User::Settings, ::Tag, ::Board, ::Column), + *build_record_sets( + ::User::Settings, + ::Tag, + ::Board, + ::Column + ), Account::DataTransfer::EntropyRecordSet.new(account), *build_record_sets( - ::Board::Publication, ::Webhook, ::Access, ::Card, ::Comment, ::Step, - ::Assignment, ::Tagging, ::Closure, ::Card::Goldness, ::Card::NotNow, - ::Card::ActivitySpike, ::Watch, ::Pin, ::Reaction, ::Mention, - ::Filter, ::Webhook::DelinquencyTracker, ::Event, - ::Notification, ::Notification::Bundle, ::Webhook::Delivery + ::Board::Publication, + ::Webhook, + ::Access, + ::Card, + ::Comment, + ::Step, + ::Assignment, + ::Tagging, + ::Closure, + ::Card::Goldness, + ::Card::NotNow, + ::Card::ActivitySpike, + ::Watch, + ::Pin, + ::Reaction, + ::Mention, + ::Filter, + ::Webhook::DelinquencyTracker, + ::Event, + ::Notification, + ::Notification::Bundle, + ::Webhook::Delivery ), Account::DataTransfer::ActiveStorage::BlobRecordSet.new(account), *build_record_sets(::ActiveStorage::Attachment), Account::DataTransfer::ActionText::RichTextRecordSet.new(account), Account::DataTransfer::ActiveStorage::FileRecordSet.new(account) - ] + ].then { set_importable_model_names(it) } end def build_record_sets(*models) @@ -47,4 +69,10 @@ class Account::DataTransfer::Manifest Account::DataTransfer::RecordSet.new(account: account, model: model) end end + + def set_importable_model_names(record_sets) + model_names = record_sets.filter_map { |record_set| record_set.model&.name } + record_sets.each { |record_set| record_set.importable_model_names = model_names } + record_sets + end end diff --git a/app/models/account/data_transfer/record_set.rb b/app/models/account/data_transfer/record_set.rb index 8eeafbe10..2fba9402d 100644 --- a/app/models/account/data_transfer/record_set.rb +++ b/app/models/account/data_transfer/record_set.rb @@ -4,12 +4,14 @@ class Account::DataTransfer::RecordSet IMPORT_BATCH_SIZE = 100 + attr_accessor :importable_model_names attr_reader :account, :model, :attributes - def initialize(account:, model:, attributes: nil) + def initialize(account:, model:, attributes: nil, importable_model_names: nil) @account = account @model = model @attributes = (attributes || model.column_names).map(&:to_s) + @importable_model_names = importable_model_names || [ model.name ] end def export(to:, start: nil) @@ -113,7 +115,7 @@ class Account::DataTransfer::RecordSet def check_association_doesnt_exist(data, association, associated_id) if association.polymorphic? type_column = association.foreign_type.to_s - associated_class = data[type_column].constantize + associated_class = verify_model_type(data[type_column]) else associated_class = association.klass end @@ -123,6 +125,14 @@ class Account::DataTransfer::RecordSet end end + def verify_model_type(type_name) + if importable_model_names.include?(type_name) + type_name.constantize + else + raise IntegrityError, "Unrecognized model type: #{type_name}" + end + end + def skip_to(file_list, last_id) index = file_list.index(last_id) diff --git a/app/models/board/auto_postponing.rb b/app/models/board/auto_postponing.rb index 4911c5e33..da53692b3 100644 --- a/app/models/board/auto_postponing.rb +++ b/app/models/board/auto_postponing.rb @@ -6,9 +6,7 @@ module Board::AutoPostponing end private - DEFAULT_AUTO_POSTPONE_PERIOD = 30.days - def set_default_auto_postpone_period - self.auto_postpone_period ||= DEFAULT_AUTO_POSTPONE_PERIOD unless attribute_present?(:auto_postpone_period) + self.auto_postpone_period ||= Entropy::DEFAULT_AUTO_POSTPONE_PERIOD_IN_DAYS.days unless attribute_present?(:auto_postpone_period) end end diff --git a/app/models/board/entropic.rb b/app/models/board/entropic.rb index beee4883b..d3e48d6ea 100644 --- a/app/models/board/entropic.rb +++ b/app/models/board/entropic.rb @@ -2,7 +2,7 @@ module Board::Entropic extend ActiveSupport::Concern included do - delegate :auto_postpone_period, to: :entropy + delegate :auto_postpone_period, :auto_postpone_period_in_days, to: :entropy has_one :entropy, as: :container, dependent: :destroy end @@ -12,6 +12,10 @@ module Board::Entropic def auto_postpone_period=(new_value) entropy ||= association(:entropy).reader || self.build_entropy - entropy.update auto_postpone_period: new_value + entropy.update! auto_postpone_period: new_value + end + + def auto_postpone_period_in_days=(value) + self.auto_postpone_period = value.to_i.days.to_i end end diff --git a/app/models/board/publication.rb b/app/models/board/publication.rb index bd480eecc..20dcc40da 100644 --- a/app/models/board/publication.rb +++ b/app/models/board/publication.rb @@ -1,6 +1,6 @@ class Board::Publication < ApplicationRecord belongs_to :account, default: -> { board.account } - belongs_to :board + belongs_to :board, touch: true has_secure_token :key end diff --git a/app/models/entropy.rb b/app/models/entropy.rb index 9a93ba4e8..c896fc494 100644 --- a/app/models/entropy.rb +++ b/app/models/entropy.rb @@ -1,6 +1,35 @@ class Entropy < ApplicationRecord + DEFAULT_AUTO_POSTPONE_PERIOD_IN_DAYS = 30 + AUTO_POSTPONE_PERIODS_IN_DAYS = [ 3, 7, 30, 90, 365, 11 ].freeze + AUTO_POSTPONE_PERIODS_IN_SECONDS = AUTO_POSTPONE_PERIODS_IN_DAYS.map { |n| n.day.in_seconds }.freeze + belongs_to :account, default: -> { container.account } belongs_to :container, polymorphic: true + validates :auto_postpone_period, inclusion: { in: AUTO_POSTPONE_PERIODS_IN_SECONDS } + after_commit -> { container.cards.touch_all if container } + + def auto_postpone_period_in_days + days = auto_postpone_period / 1.day.to_i + + if days.in?(AUTO_POSTPONE_PERIODS_IN_DAYS) + days + else + default_auto_postpone_period_in_days + end + end + + def auto_postpone_period_in_days=(new_value) + self.auto_postpone_period = new_value.to_i.days.to_i + end + + private + def default_auto_postpone_period_in_days + if container.is_a?(Board) + container.account.entropy.auto_postpone_period_in_days + else + DEFAULT_AUTO_POSTPONE_PERIOD_IN_DAYS + end + end end diff --git a/app/models/user/named.rb b/app/models/user/named.rb index d052a79ad..804e516ee 100644 --- a/app/models/user/named.rb +++ b/app/models/user/named.rb @@ -20,6 +20,6 @@ module User::Named def familiar_name names = name.split return name if names.length <= 1 - "#{names.first} #{names[1..].map { |n| "#{n[0]}." }.join}" + "#{names.first}\u00A0#{names[1..].map { |n| "#{n[0]}." }.join}" end end diff --git a/app/models/zip_file.rb b/app/models/zip_file.rb index dc5b7ee8d..3415f5879 100644 --- a/app/models/zip_file.rb +++ b/app/models/zip_file.rb @@ -84,7 +84,8 @@ class ZipFile def read_from_s3(blob) url = blob.url(expires_in: 6.hour) - remote_io = RemoteIO.new(url) + ssl_verify_peer = blob.service.client.client.config.ssl_verify_peer + remote_io = RemoteIO.new(url, ssl_verify_peer: ssl_verify_peer) reader = Reader.new(remote_io) yield reader end diff --git a/app/models/zip_file/remote_io.rb b/app/models/zip_file/remote_io.rb index 1cb00831a..59329d811 100644 --- a/app/models/zip_file/remote_io.rb +++ b/app/models/zip_file/remote_io.rb @@ -1,4 +1,9 @@ class ZipFile::RemoteIO < ZipKit::RemoteIO + def initialize(url, ssl_verify_peer: true) + super(url) + @ssl_verify_peer = ssl_verify_peer + end + protected def request_range(range) with_http do |http| @@ -37,8 +42,7 @@ class ZipFile::RemoteIO < ZipKit::RemoteIO def with_http http = Net::HTTP.new(@uri.hostname, @uri.port) http.use_ssl = @uri.scheme == "https" - # FIXME: Disable SSL verification for now to avoid issues with our self-signed certificates for PureStorage - http.verify_mode = OpenSSL::SSL::VERIFY_NONE + http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless @ssl_verify_peer http.start { yield http } end end diff --git a/app/views/account/exports/show.json.jbuilder b/app/views/account/exports/show.json.jbuilder new file mode 100644 index 000000000..2bd9e3d60 --- /dev/null +++ b/app/views/account/exports/show.json.jbuilder @@ -0,0 +1,6 @@ +json.(@export, :id, :status) +json.created_at @export.created_at.utc + +if @export.completed? && @export.file.attached? + json.download_url rails_blob_url(@export.file, disposition: "attachment") +end diff --git a/app/views/account/join_codes/show.json.jbuilder b/app/views/account/join_codes/show.json.jbuilder new file mode 100644 index 000000000..559e7b6c2 --- /dev/null +++ b/app/views/account/join_codes/show.json.jbuilder @@ -0,0 +1,3 @@ +json.(@join_code, :code, :usage_count, :usage_limit) +json.url join_url(code: @join_code.code, script_name: Current.account.slug) +json.active !!@join_code.active? diff --git a/app/views/account/settings/show.html.erb b/app/views/account/settings/show.html.erb index 91bb85aff..fdb118e57 100644 --- a/app/views/account/settings/show.html.erb +++ b/app/views/account/settings/show.html.erb @@ -21,5 +21,3 @@ <%= render "account/settings/cancellation" %> - -<%= render "account/settings/subscription_panel" if Fizzy.saas? %> diff --git a/app/views/account/settings/show.json.jbuilder b/app/views/account/settings/show.json.jbuilder new file mode 100644 index 000000000..55b623f9d --- /dev/null +++ b/app/views/account/settings/show.json.jbuilder @@ -0,0 +1,3 @@ +json.(@account, :id, :name, :cards_count) +json.created_at @account.created_at.utc +json.auto_postpone_period_in_days @account.entropy.auto_postpone_period_in_days diff --git a/app/views/boards/_board.json.jbuilder b/app/views/boards/_board.json.jbuilder index 7c85a1dc5..0eeff1896 100644 --- a/app/views/boards/_board.json.jbuilder +++ b/app/views/boards/_board.json.jbuilder @@ -1,6 +1,7 @@ json.cache! board do json.(board, :id, :name, :all_access) json.created_at board.created_at.utc + json.auto_postpone_period_in_days board.auto_postpone_period_in_days json.url board_url(board) json.creator board.creator, partial: "users/user", as: :user diff --git a/app/views/boards/columns/closeds/show.json.jbuilder b/app/views/boards/columns/closeds/show.json.jbuilder new file mode 100644 index 000000000..c1dc1dff1 --- /dev/null +++ b/app/views/boards/columns/closeds/show.json.jbuilder @@ -0,0 +1 @@ +json.array! @page.records, partial: "cards/card", as: :card diff --git a/app/views/boards/columns/not_nows/show.json.jbuilder b/app/views/boards/columns/not_nows/show.json.jbuilder new file mode 100644 index 000000000..c1dc1dff1 --- /dev/null +++ b/app/views/boards/columns/not_nows/show.json.jbuilder @@ -0,0 +1 @@ +json.array! @page.records, partial: "cards/card", as: :card diff --git a/app/views/boards/columns/streams/show.json.jbuilder b/app/views/boards/columns/streams/show.json.jbuilder new file mode 100644 index 000000000..c1dc1dff1 --- /dev/null +++ b/app/views/boards/columns/streams/show.json.jbuilder @@ -0,0 +1 @@ +json.array! @page.records, partial: "cards/card", as: :card diff --git a/app/views/boards/show/_stream.html.erb b/app/views/boards/show/_stream.html.erb index b43309100..f66fb8fa6 100644 --- a/app/views/boards/show/_stream.html.erb +++ b/app/views/boards/show/_stream.html.erb @@ -12,7 +12,7 @@
<% if page.used? %> - <%= with_automatic_pagination "maybe", @page do %> + <%= with_automatic_pagination "maybe", page do %> <%= render "cards/display/previews", cards: page.records, draggable: true %> <% end %> <% end %> diff --git a/app/views/cards/closures/create.turbo_stream.erb b/app/views/cards/closures/create.turbo_stream.erb index c4898e471..5b76efe7b 100644 --- a/app/views/cards/closures/create.turbo_stream.erb +++ b/app/views/cards/closures/create.turbo_stream.erb @@ -3,7 +3,7 @@ <% if @source_column %> <%= turbo_stream.replace(dom_id(@source_column), partial: "boards/show/column", method: :morph, locals: { column: @source_column }) %> <% elsif @was_in_stream %> - <%= turbo_stream.replace("the-stream", partial: "boards/show/stream", method: :morph, locals: { board: @card.board, page: @page }) %> + <%= turbo_stream.replace("maybe", partial: "boards/show/stream", method: :morph, locals: { board: @card.board, page: @page }) %> <% end %> <%= turbo_stream.replace([ @card, :card_container ], partial: "cards/container", method: :morph, locals: { card: @card.reload }) %> diff --git a/app/views/cards/closures/destroy.turbo_stream.erb b/app/views/cards/closures/destroy.turbo_stream.erb index 8df408979..31a9e41c3 100644 --- a/app/views/cards/closures/destroy.turbo_stream.erb +++ b/app/views/cards/closures/destroy.turbo_stream.erb @@ -3,7 +3,7 @@ <% if @card.column %> <%= turbo_stream.replace(dom_id(@card.column), partial: "boards/show/column", method: :morph, locals: { column: @card.column }) %> <% elsif @card.awaiting_triage? %> - <%= turbo_stream.replace("the-stream", partial: "boards/show/stream", method: :morph, locals: { board: @card.board, page: @page }) %> + <%= turbo_stream.replace("maybe", partial: "boards/show/stream", method: :morph, locals: { board: @card.board, page: @page }) %> <% end %> <%= turbo_stream.replace([ @card, :card_container ], partial: "cards/container", method: :morph, locals: { card: @card.reload }) %> diff --git a/app/views/cards/container/footer/_create.html.erb b/app/views/cards/container/footer/_create.html.erb index b55cae344..648beffd1 100644 --- a/app/views/cards/container/footer/_create.html.erb +++ b/app/views/cards/container/footer/_create.html.erb @@ -13,7 +13,5 @@ Create and add another <% end %>
- - <%= render "cards/container/footer/saas/near_notice" if Fizzy.saas? %> diff --git a/app/views/cards/drafts/_container.html.erb b/app/views/cards/drafts/_container.html.erb index fa7134b55..9ffc04711 100644 --- a/app/views/cards/drafts/_container.html.erb +++ b/app/views/cards/drafts/_container.html.erb @@ -26,9 +26,5 @@ <% end %> - <% if Fizzy.saas? %> - <%= render "cards/container/footer/saas/create", card: card %> - <% else %> - <%= render "cards/container/footer/create", card: card %> - <% end %> + <%= render "cards/container/footer/create", card: card %> diff --git a/app/views/cards/not_nows/create.turbo_stream.erb b/app/views/cards/not_nows/create.turbo_stream.erb index 9128580e2..945d86ab2 100644 --- a/app/views/cards/not_nows/create.turbo_stream.erb +++ b/app/views/cards/not_nows/create.turbo_stream.erb @@ -3,7 +3,7 @@ <% if @source_column %> <%= turbo_stream.replace(dom_id(@source_column), partial: "boards/show/column", method: :morph, locals: { column: @source_column }) %> <% elsif @was_in_stream %> - <%= turbo_stream.replace("the-stream", partial: "boards/show/stream", method: :morph, locals: { board: @card.board, page: @page }) %> + <%= turbo_stream.replace("maybe", partial: "boards/show/stream", method: :morph, locals: { board: @card.board, page: @page }) %> <% end %> <%= turbo_stream.replace([ @card, :card_container ], partial: "cards/container", method: :morph, locals: { card: @card.reload }) %> diff --git a/app/views/cards/steps/index.json.jbuilder b/app/views/cards/steps/index.json.jbuilder new file mode 100644 index 000000000..087790175 --- /dev/null +++ b/app/views/cards/steps/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @card.steps, partial: "cards/steps/step", as: :step diff --git a/app/views/entropy/_auto_close.html.erb b/app/views/entropy/_auto_close.html.erb index 6ccb5d205..a2e3fe0ab 100644 --- a/app/views/entropy/_auto_close.html.erb +++ b/app/views/entropy/_auto_close.html.erb @@ -6,8 +6,9 @@ <%= form_with model: model, url: url, data: { controller: "form" } do |form| %> <%= render "entropy/knob", form: form, - name: :auto_postpone_period, - knob_options: entropy_auto_close_options, + name: :auto_postpone_period_in_days, + current_value: model.auto_postpone_period_in_days, + knob_options: Entropy::AUTO_POSTPONE_PERIODS_IN_DAYS, label: "Days until auto-close", disabled: disabled %> <% end %> diff --git a/app/views/entropy/_knob.html.erb b/app/views/entropy/_knob.html.erb index 69bbb5fb0..ba17c6728 100644 --- a/app/views/entropy/_knob.html.erb +++ b/app/views/entropy/_knob.html.erb @@ -1,6 +1,5 @@ <% - period = form.object.send(name) - current_index = knob_options.index(period.to_i / 1.day) + current_index = knob_options.index(current_value) || knob_options.index(Entropy::DEFAULT_AUTO_POSTPONE_PERIOD_IN_DAYS) %>
@@ -8,7 +7,7 @@ <% knob_options.each_with_index do |value, index| %>