diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 05bc28d35..000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: CI - -on: - pull_request: - push: - branches: [ main ] - -env: - BUNDLE_GITHUB__COM: x-access-token:${{ secrets.GH_TOKEN }} - -jobs: - scan_ruby: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: .ruby-version - bundler-cache: true - - - name: Scan for security vulnerabilities in Ruby dependencies - run: bin/brakeman --no-pager - - scan_js: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: .ruby-version - bundler-cache: true - - - name: Scan for security vulnerabilities in JavaScript dependencies - run: bin/importmap audit - - lint: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: .ruby-version - bundler-cache: true - - - name: Lint code for consistent style - run: bin/rubocop -f github - - test: - runs-on: ubuntu-latest - - # services: - # redis: - # image: redis - # ports: - # - 6379:6379 - # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 - steps: - - name: Install packages - run: sudo apt-get update && sudo apt-get install --no-install-recommends -y google-chrome-stable curl libjemalloc2 libsqlite3-0 libvips - - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: .ruby-version - bundler-cache: true - - - name: Run tests - env: - RAILS_ENV: test - # REDIS_URL: redis://localhost:6379/0 - run: bin/rails db:setup test test:system - - - name: Keep screenshots from failed system tests - uses: actions/upload-artifact@v4 - if: failure() - with: - name: screenshots - path: ${{ github.workspace }}/tmp/screenshots - if-no-files-found: ignore diff --git a/Gemfile b/Gemfile index 19e7f2d5e..edb02dcf8 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,5 @@ source "https://rubygems.org" git_source(:bc) { |repo| "https://github.com/basecamp/#{repo}" } -ruby file: ".ruby-version" gem "rails", github: "rails/rails", branch: "main" gem "active_record-tenanted", bc: "active_record-tenanted" @@ -36,14 +35,11 @@ gem "rails_structured_logging", bc: "rails-structured-logging" group :development, :test do gem "debug" + gem "bundler-audit", require: false gem "brakeman", require: false gem "rubocop-rails-omakase", require: false end -group :development do - gem "hotwire-spark" -end - group :test do gem "capybara" gem "selenium-webdriver" diff --git a/Gemfile.lock b/Gemfile.lock index 0e1eddfb2..8fbd0f756 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/basecamp/active_record-tenanted - revision: 1bb339a1ca045a240aefacc2bb0b88607935d401 + revision: 8758240be33a6f6f7e7f56486a2e78008c193030 specs: active_record-tenanted (0.1.0) activerecord (>= 8.1.alpha) @@ -18,7 +18,7 @@ GIT GIT remote: https://github.com/rails/rails.git - revision: e0452e80137c396c93465cf98f8eda43026346c7 + revision: 32d96235810e268eb5335a7837b13cda3f8b653d branch: main specs: actioncable (8.1.0.alpha) @@ -128,9 +128,12 @@ GEM bigdecimal (3.1.9) bootsnap (1.18.4) msgpack (~> 1.2) - brakeman (7.0.0) + brakeman (7.0.2) racc builder (3.3.0) + bundler-audit (0.9.2) + bundler (>= 1.2.0, < 3) + thor (~> 1.0) capybara (3.40.0) addressable matrix @@ -154,16 +157,6 @@ GEM erubi (1.13.1) et-orbi (1.2.11) tzinfo - ffi (1.17.1-aarch64-linux-gnu) - ffi (1.17.1-aarch64-linux-musl) - ffi (1.17.1-arm-linux-gnu) - ffi (1.17.1-arm-linux-musl) - ffi (1.17.1-arm64-darwin) - ffi (1.17.1-x86-linux-gnu) - ffi (1.17.1-x86-linux-musl) - ffi (1.17.1-x86_64-darwin) - ffi (1.17.1-x86_64-linux-gnu) - ffi (1.17.1-x86_64-linux-musl) fugit (1.11.1) et-orbi (~> 1, >= 1.2.11) raabro (~> 1.4) @@ -172,10 +165,6 @@ GEM addressable (>= 2.5.0) globalid (1.2.1) activesupport (>= 6.1) - hotwire-spark (0.1.13) - listen - rails (>= 7.0.0) - zeitwerk i18n (1.14.7) concurrent-ruby (~> 1.0) importmap-rails (2.1.0) @@ -204,9 +193,6 @@ GEM zeitwerk (>= 2.6.18, < 3.0) language_server-protocol (3.17.0.4) lint_roller (1.1.0) - listen (3.9.0) - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) logger (1.7.0) loofah (2.24.0) crass (~> 1.0.2) @@ -297,14 +283,11 @@ GEM nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) rainbow (3.1.1) rake (13.2.1) - rb-fsevent (0.11.2) - rb-inotify (0.11.1) - ffi (~> 1.0) rdoc (6.13.1) psych (>= 4.0.0) redcarpet (3.6.1) regexp_parser (2.10.0) - reline (0.6.0) + reline (0.6.1) io-console (~> 0.5) rexml (3.4.1) rouge (4.5.1) @@ -343,7 +326,7 @@ GEM ruby-progressbar (1.13.0) rubyzip (2.4.1) securerandom (0.4.1) - selenium-webdriver (4.30.1) + selenium-webdriver (4.31.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) @@ -438,10 +421,10 @@ DEPENDENCIES bcrypt (~> 3.1.7) bootsnap brakeman + bundler-audit capybara debug geared_pagination (~> 1.2) - hotwire-spark importmap-rails jbuilder kamal @@ -464,8 +447,5 @@ DEPENDENCIES thruster turbo-rails -RUBY VERSION - ruby 3.4.2p28 - BUNDLED WITH 2.5.21 diff --git a/app/assets/images/picture-add.svg b/app/assets/images/picture-add.svg index 610d32901..a64cf38d6 100644 --- a/app/assets/images/picture-add.svg +++ b/app/assets/images/picture-add.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/assets/images/star.svg b/app/assets/images/star.svg new file mode 100644 index 000000000..281a18051 --- /dev/null +++ b/app/assets/images/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/stylesheets/buttons.css b/app/assets/stylesheets/buttons.css index 758b9246f..cd5bd830a 100644 --- a/app/assets/stylesheets/buttons.css +++ b/app/assets/stylesheets/buttons.css @@ -33,10 +33,31 @@ } } - &:where(.avatar) { - --btn-border-radius: var(--avatar-border-radius); - --btn-padding: 0; - --btn-border-size: 0; + /* Circle buttons */ + &.btn--circle, + &[aria-label]:where(:has(.icon)), + &:where(:has(.for-screen-reader):has(.icon)) { + --btn-border-radius: 50%; + --btn-padding: 0; + + aspect-ratio: 1; + block-size: var(--btn-size); + display: grid; + inline-size: var(--btn-size); + place-items: center; + + > * { + grid-area: 1/1; + } + } + + /* With radios and checkboxes */ + &:has(input[type=radio]), + &:has(input[type=checkbox]) { + position: relative; + + :is(input[type=radio]), + :is(input[type=checkbox]) { --hover-size: 0; aspect-ratio: 1; diff --git a/app/assets/stylesheets/cards.css b/app/assets/stylesheets/cards.css index 558b3d9fd..1b9231cae 100644 --- a/app/assets/stylesheets/cards.css +++ b/app/assets/stylesheets/cards.css @@ -73,7 +73,9 @@ } .card { + --border-color: transparent; --border-radius: 0.2em; + --border-size: 0; aspect-ratio: 2/0.95; background-color: color-mix(in srgb, var(--bubble-color) 4%, var(--color-bg)); @@ -84,6 +86,15 @@ .card__container & { inline-size: calc(100dvw - var(--actions-inline-inset) * 2 - var(--btn-size) * 2); } + + .local-time-value { + font-weight: bold; + } + + @media (prefers-color-scheme: dark) { + --border-color: var(--color-subtle-dark); + --border-size: 1px; + } } .card__actions { @@ -127,6 +138,21 @@ } } + .card__actions-container--top { + margin: var(--block-space-half) auto calc(var(--block-space) * -2); + padding: var(--block-space) 3ch; + + #header:has(&) { + position: relative; + z-index: 1; + } + + .switch__input:checked + .switch__btn { + background-color: var(--bubble-color) !important; + } + } + + .cards .card__assignees:not(:has(.avatar)) { .txt-uppercase { display: none; @@ -206,11 +232,17 @@ --padding-inline: calc(var(--block-space-double) + var(--block-space)); --padding-block: var(--block-space-double) calc(var(--block-space-double) + var(--block-space-half)); - background-color: var(--color-container); + background-color: color-mix(in srgb, var(--bubble-color) 33%, var(--color-bg)); border-radius: 0.2em; display: inline-flex; padding-block: var(--padding-block); padding-inline: var(--padding-inline); + + &:has(.card__star-input:checked) { + .card { + outline: 4px solid var(--color-negative); + } + } } .card__container--pointing { @@ -346,12 +378,12 @@ .card__meta { --border-color: var(--bubble-color); + --border-size: 1px; --row-gap: 0.25em; margin-inline-start: var(--inline-space-double); strong { - font-size: 1.1em; font-weight: 900; } @@ -462,9 +494,9 @@ font-size: var(--text-rel-large); font-weight: 900; text-shadow: - -2px -2px 0 var(--bubble-color), + -2px -2px 0 var(--bubble-color), 2px -2px 0 var(--bubble-color), - -2px 2px 0 var(--bubble-color), + -2px 2px 0 var(--bubble-color), 2px 2px 0 var(--bubble-color); } } diff --git a/app/assets/stylesheets/comments.css b/app/assets/stylesheets/comments.css index 1ba711061..2db3016db 100644 --- a/app/assets/stylesheets/comments.css +++ b/app/assets/stylesheets/comments.css @@ -11,7 +11,7 @@ place-items: center; text-align: center; - @media (min-width: 150ch) { + @media (min-width: 160ch) { padding-inline: calc(var(--tray-size) + var(--inline-space) * 1.5); } } diff --git a/app/assets/stylesheets/icons.css b/app/assets/stylesheets/icons.css index 0506fc38a..7937952f3 100644 --- a/app/assets/stylesheets/icons.css +++ b/app/assets/stylesheets/icons.css @@ -76,6 +76,7 @@ .icon--share { --svg: url("share.svg "); } .icon--sort-ascending { --svg: url("sort-ascending.svg "); } .icon--sort-descending { --svg: url("sort-descending.svg "); } + .icon--star { --svg: url("star.svg "); } .icon--tag { --svg: url("tag.svg "); } .icon--temperature { --svg: url("temperature.svg "); } .icon--thumb-up { --svg: url("thumb-up.svg "); } diff --git a/app/assets/stylesheets/layout.css b/app/assets/stylesheets/layout.css index 34290ffc3..c38100393 100644 --- a/app/assets/stylesheets/layout.css +++ b/app/assets/stylesheets/layout.css @@ -37,7 +37,6 @@ view-transition-name: header; > nav { - font-size: var(--text-small); view-transition-name: nav; header { diff --git a/app/controllers/assignments/toggles_controller.rb b/app/controllers/assignments/toggles_controller.rb deleted file mode 100644 index 96d510baa..000000000 --- a/app/controllers/assignments/toggles_controller.rb +++ /dev/null @@ -1,34 +0,0 @@ -class Assignments::TogglesController < ApplicationController - include BubbleScoped, BucketScoped - - def new - render partial: "bubbles/assignment", locals: { bubble: @bubble } - end - - def create - new_assignee_ids = Array(params[:assignee_id]) - current_assignees = @bubble.assignees - - current_assignees.each do |assignee| - @bubble.toggle_assignment(assignee) unless new_assignee_ids.include?(assignee.id.to_s) - end - - new_assignee_ids.each do |id| - assignee = @bucket.users.active.find(id) - @bubble.toggle_assignment(assignee) unless current_assignees.include?(assignee) - end - - @bubble.assignees.reload - - respond_to do |format| - format.turbo_stream do - render turbo_stream: turbo_stream.replace([ @bubble, :assignees ], partial: "bubbles/cards/perma/assignees", locals: { bubble: @bubble }) - end - end - end - - private - def assignee - @bucket.users.active.find params[:assignee_id] - end -end diff --git a/app/controllers/bubbles/assignments_controller.rb b/app/controllers/bubbles/assignments_controller.rb new file mode 100644 index 000000000..7fd9186aa --- /dev/null +++ b/app/controllers/bubbles/assignments_controller.rb @@ -0,0 +1,10 @@ +class Bubbles::AssignmentsController < ApplicationController + include BubbleScoped + + def new + end + + def create + @bubble.toggle_assignment @bucket.users.active.find(params[:assignee_id]) + end +end diff --git a/app/controllers/boosts_controller.rb b/app/controllers/bubbles/boosts_controller.rb similarity index 57% rename from app/controllers/boosts_controller.rb rename to app/controllers/bubbles/boosts_controller.rb index 0a6f34f78..38970c40f 100644 --- a/app/controllers/boosts_controller.rb +++ b/app/controllers/bubbles/boosts_controller.rb @@ -1,5 +1,5 @@ -class BoostsController < ApplicationController - include BubbleScoped, BucketScoped +class Bubbles::BoostsController < ApplicationController + include BubbleScoped def create count = if params[:boost_count].to_i == @bubble.boosts_count @@ -8,9 +8,5 @@ class BoostsController < ApplicationController params[:boost_count].to_i end @bubble.boost!(count) - - respond_to do |format| - format.turbo_stream - end end end diff --git a/app/controllers/bubbles/engagements_controller.rb b/app/controllers/bubbles/engagements_controller.rb index e13d6679c..5599b7381 100644 --- a/app/controllers/bubbles/engagements_controller.rb +++ b/app/controllers/bubbles/engagements_controller.rb @@ -1,5 +1,5 @@ class Bubbles::EngagementsController < ApplicationController - include BubbleScoped, BucketScoped + include BubbleScoped def create @bubble.engage diff --git a/app/controllers/bubbles/images_controller.rb b/app/controllers/bubbles/images_controller.rb index 005eb967d..b7dc7da97 100644 --- a/app/controllers/bubbles/images_controller.rb +++ b/app/controllers/bubbles/images_controller.rb @@ -1,5 +1,5 @@ class Bubbles::ImagesController < ApplicationController - include BubbleScoped, BucketScoped + include BubbleScoped def destroy @bubble.image.purge_later diff --git a/app/controllers/bubbles/pins_controller.rb b/app/controllers/bubbles/pins_controller.rb index a28526dea..d75d2b10d 100644 --- a/app/controllers/bubbles/pins_controller.rb +++ b/app/controllers/bubbles/pins_controller.rb @@ -1,16 +1,29 @@ class Bubbles::PinsController < ApplicationController - include BubbleScoped, BucketScoped + include BubbleScoped def show end def create - @bubble.set_pinned(Current.user, true) - redirect_to bucket_bubble_pin_path(@bucket, @bubble) + pin = @bubble.pin_by Current.user + + broadcast_my_new pin + redirect_to bubble_pin_path(@bubble) end def destroy - @bubble.set_pinned(Current.user, false) - redirect_to bucket_bubble_pin_path(@bucket, @bubble) + pin = @bubble.unpin_by Current.user + + broadcast_my_removed pin + redirect_to bubble_pin_path(@bubble) end + + private + def broadcast_my_new(pin) + pin.broadcast_prepend_later_to [ Current.user, :pins ], target: "pins", partial: "my/pins/pin" + end + + def broadcast_my_removed(pin) + pin.broadcast_remove_to [ Current.user, :pins ] + end end diff --git a/app/controllers/bubbles/pops_controller.rb b/app/controllers/bubbles/pops_controller.rb index 395f241e2..5421428ca 100644 --- a/app/controllers/bubbles/pops_controller.rb +++ b/app/controllers/bubbles/pops_controller.rb @@ -1,5 +1,5 @@ class Bubbles::PopsController < ApplicationController - include BubbleScoped, BucketScoped + include BubbleScoped def create @bubble.pop!(user: Current.user, reason: params[:reason]) diff --git a/app/controllers/bubbles/publishes_controller.rb b/app/controllers/bubbles/publishes_controller.rb index 8064e7c68..bcc75eb27 100644 --- a/app/controllers/bubbles/publishes_controller.rb +++ b/app/controllers/bubbles/publishes_controller.rb @@ -1,5 +1,5 @@ class Bubbles::PublishesController < ApplicationController - include BubbleScoped, BucketScoped + include BubbleScoped def create @bubble.publish diff --git a/app/controllers/readings_controller.rb b/app/controllers/bubbles/readings_controller.rb similarity index 75% rename from app/controllers/readings_controller.rb rename to app/controllers/bubbles/readings_controller.rb index 6482cd7c9..9f99c456d 100644 --- a/app/controllers/readings_controller.rb +++ b/app/controllers/bubbles/readings_controller.rb @@ -1,5 +1,5 @@ -class ReadingsController < ApplicationController - include BubbleScoped, BucketScoped +class Bubbles::ReadingsController < ApplicationController + include BubbleScoped def create mark_bubble_notifications_read diff --git a/app/controllers/bubbles/recovers_controller.rb b/app/controllers/bubbles/recovers_controller.rb index 530592762..c94fd6ccf 100644 --- a/app/controllers/bubbles/recovers_controller.rb +++ b/app/controllers/bubbles/recovers_controller.rb @@ -1,5 +1,5 @@ class Bubbles::RecoversController < ApplicationController - include BubbleScoped, BucketScoped + include BubbleScoped def create redirect_to @bubble.recover_abandoned_creation diff --git a/app/controllers/bubbles/stagings_controller.rb b/app/controllers/bubbles/stagings_controller.rb index 2f2ea68cc..28f7af26e 100644 --- a/app/controllers/bubbles/stagings_controller.rb +++ b/app/controllers/bubbles/stagings_controller.rb @@ -1,5 +1,5 @@ class Bubbles::StagingsController < ApplicationController - include BubbleScoped, BucketScoped + include BubbleScoped def create if params[:stage_id].present? diff --git a/app/controllers/bubbles/taggings_controller.rb b/app/controllers/bubbles/taggings_controller.rb new file mode 100644 index 000000000..156705a98 --- /dev/null +++ b/app/controllers/bubbles/taggings_controller.rb @@ -0,0 +1,16 @@ +class Bubbles::TaggingsController < ApplicationController + include BubbleScoped + + def new + @tags = Current.account.tags.alphabetically + end + + def create + @bubble.toggle_tag_with sanitized_tag_title_param + end + + private + def sanitized_tag_title_param + params.required(:tag_title).strip.gsub(/\A#/, "") + end +end diff --git a/app/controllers/bubbles/watches_controller.rb b/app/controllers/bubbles/watches_controller.rb index cecd292b5..3b21acfa4 100644 --- a/app/controllers/bubbles/watches_controller.rb +++ b/app/controllers/bubbles/watches_controller.rb @@ -1,17 +1,13 @@ class Bubbles::WatchesController < ApplicationController - include BubbleScoped, BucketScoped + include BubbleScoped def create - set_watching_and_redirect(true) + @bubble.watch_by Current.user + redirect_to bubble_watch_path(@bubble) end def destroy - set_watching_and_redirect(false) + @bubble.unwatch_by Current.user + redirect_to bubble_watch_path(@bubble) end - - private - def set_watching_and_redirect(watching) - @bubble.set_watching(Current.user, watching) - redirect_to bucket_bubble_watch_path(@bucket, @bubble) - end end diff --git a/app/controllers/buckets_controller.rb b/app/controllers/buckets_controller.rb index 63357bb92..f4f5a2ff4 100644 --- a/app/controllers/buckets_controller.rb +++ b/app/controllers/buckets_controller.rb @@ -33,7 +33,7 @@ class BucketsController < ApplicationController end def bucket_params - params.expect(bucket: [ :name, :all_access ]).with_defaults(all_access: false) + params.expect(bucket: [ :name, :all_access ]).with_defaults(all_access: true) end def grantees diff --git a/app/controllers/comments/reactions_controller.rb b/app/controllers/comments/reactions_controller.rb index c362f4a2b..36f51f6f6 100644 --- a/app/controllers/comments/reactions_controller.rb +++ b/app/controllers/comments/reactions_controller.rb @@ -24,7 +24,7 @@ class Comments::ReactionsController < ApplicationController private def set_comment - @comment = Comment.find(params[:comment_id]) + @comment = Current.account.comments.find(params[:comment_id]) end def reaction_params diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index c0a9c9d34..4708af0e1 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -1,5 +1,5 @@ class CommentsController < ApplicationController - include BubbleScoped, BucketScoped + include BubbleScoped before_action :set_comment, only: [ :show, :edit, :update, :destroy ] before_action :require_own_comment, only: [ :edit, :update, :destroy ] @@ -38,6 +38,6 @@ class CommentsController < ApplicationController end def require_own_comment - head :forbidden unless @comment.creator.current? + head :forbidden unless Current.user == @comment.creator end end diff --git a/app/controllers/concerns/bubble_scoped.rb b/app/controllers/concerns/bubble_scoped.rb index bf123910c..a3783d9c5 100644 --- a/app/controllers/concerns/bubble_scoped.rb +++ b/app/controllers/concerns/bubble_scoped.rb @@ -2,11 +2,15 @@ module BubbleScoped extend ActiveSupport::Concern included do - before_action :set_bubble + before_action :set_bubble, :set_bucket end private def set_bubble - @bubble = @bucket.bubbles.find(params[:bubble_id]) + @bubble = Current.user.accessible_bubbles.find(params[:bubble_id]) + end + + def set_bucket + @bucket = Current.user.buckets.find(@bubble.bucket_id) end end diff --git a/app/controllers/bubbles/pins/trays_controller.rb b/app/controllers/my/pins_controller.rb similarity index 51% rename from app/controllers/bubbles/pins/trays_controller.rb rename to app/controllers/my/pins_controller.rb index ef79beae2..c7f222d33 100644 --- a/app/controllers/bubbles/pins/trays_controller.rb +++ b/app/controllers/my/pins_controller.rb @@ -1,5 +1,5 @@ -class Bubbles::Pins::TraysController < ApplicationController - def show +class My::PinsController < ApplicationController + def index @pins = Current.user.pins.includes(:bubble).ordered.limit(20) end end diff --git a/app/controllers/taggings/toggles_controller.rb b/app/controllers/taggings/toggles_controller.rb deleted file mode 100644 index d768c4cc7..000000000 --- a/app/controllers/taggings/toggles_controller.rb +++ /dev/null @@ -1,35 +0,0 @@ -class Taggings::TogglesController < ApplicationController - include BubbleScoped, BucketScoped - - def new - render partial: "bubbles/tagging", locals: { bubble: @bubble, tags: Current.account.tags } - end - - def create - if params[:tag_title].present? - sanitized_title = params[:tag_title].strip.gsub(/\A#/, "") - tag = Current.account.tags.find_or_create_by!(title: sanitized_title) - @bubble.toggle_tag(tag) - else - new_tag_ids = Array(params[:tag_id]) - current_tags = @bubble.tags - - current_tags.each do |tag| - @bubble.toggle_tag(tag) unless new_tag_ids.include?(tag.id.to_s) - end - - new_tag_ids.each do |id| - tag = Current.account.tags.find(id) - @bubble.toggle_tag(tag) unless current_tags.include?(tag) - end - end - - @bubble.tags.reload - - respond_to do |format| - format.turbo_stream do - render turbo_stream: turbo_stream.replace([ @bubble, :tags ], partial: "bubbles/cards/perma/tags", locals: { bubble: @bubble }) - end - end - end -end diff --git a/app/controllers/users/avatars_controller.rb b/app/controllers/users/avatars_controller.rb index f700a6825..e48a1f7f2 100644 --- a/app/controllers/users/avatars_controller.rb +++ b/app/controllers/users/avatars_controller.rb @@ -4,7 +4,7 @@ class Users::AvatarsController < ApplicationController before_action :set_user def show - if stale? @user, cache_control: { max_age: 30.minutes, stale_while_revalidate: 1.week } + if stale? @user, cache_control: { max_age: cache_max_age, stale_while_revalidate: 1.week } render_avatar_or_initials end end @@ -15,6 +15,14 @@ class Users::AvatarsController < ApplicationController end private + def cache_max_age + if Current.user == @user + 0 + else + 30.minutes + end + end + def set_user @user = Current.account.users.find(params[:user_id]) end diff --git a/app/helpers/navigation_helper.rb b/app/helpers/navigation_helper.rb index d7c99ec86..d1bb85a81 100644 --- a/app/helpers/navigation_helper.rb +++ b/app/helpers/navigation_helper.rb @@ -6,4 +6,11 @@ module NavigationHelper icon_tag(icon) + tag.span(label, class: "for-screen-reader") end end + + def link_to_back(fallback_path: root_path) + link_to fallback_path, class: "btn flex-item-justify-start", aria: { label: "Go back" }, + data: { controller: "back-navigation hotkey", action: "keydown.esc@document->hotkey#click", back_navigation_fallback_destination_value: fallback_path } do + icon_tag("arrow-left") + end + end end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb new file mode 100644 index 000000000..58b3e12d6 --- /dev/null +++ b/app/helpers/users_helper.rb @@ -0,0 +1,5 @@ +module UsersHelper + def prepend_current_user_to(users_scope) + users_scope.to_a.prepend(Current.user).uniq + end +end diff --git a/app/helpers/workflows_helper.rb b/app/helpers/workflows_helper.rb index dfcd317f4..f78241aa9 100644 --- a/app/helpers/workflows_helper.rb +++ b/app/helpers/workflows_helper.rb @@ -1,12 +1,12 @@ module WorkflowsHelper def button_to_set_stage(bubble, stage) - button_to bucket_bubble_stagings_path(bubble.bucket, bubble, stage_id: stage), - method: :post, class: [ "btn full-width justify-start workflow-stage txt-uppercase", { "workflow-stage--current": stage == bubble.stage } ], - form_class: "flex align-center gap-half", - data: { turbo_frame: "_top" } do - tag.span class: "overflow-ellipsis" do - stage.name - end + button_to bubble_stagings_path(bubble, stage_id: stage), + method: :post, class: [ "btn full-width justify-start workflow-stage txt-uppercase", { "workflow-stage--current": stage == bubble.stage } ], + form_class: "flex align-center gap-half", + data: { turbo_frame: "_top" } do + tag.span class: "overflow-ellipsis" do + stage.name end + end end end diff --git a/app/javascript/controllers/local_time_controller.js b/app/javascript/controllers/local_time_controller.js index 512169949..bdfbe677a 100644 --- a/app/javascript/controllers/local_time_controller.js +++ b/app/javascript/controllers/local_time_controller.js @@ -1,7 +1,8 @@ import { Controller } from "@hotwired/stimulus" +import { differenceInDays } from "helpers/date_helpers" export default class extends Controller { - static targets = [ "time", "date", "datetime", "shortdate", "ago", "indays" ] + static targets = [ "time", "date", "datetime", "shortdate", "ago", "indays", "daysago" ] #timer @@ -11,7 +12,7 @@ export default class extends Controller { this.shortDateFormatter = new Intl.DateTimeFormat(undefined, { month: "short", day: "numeric" }) this.dateTimeFormatter = new Intl.DateTimeFormat(undefined, { timeStyle: "short", dateStyle: "short" }) this.agoFormatter = new AgoFormatter() - this.daysAgoFormatter = new DaysAgoFormatter() + this.daysagoFormatter = new DaysAgoFormatter() this.indaysFormatter = new InDaysFormatter() } @@ -43,14 +44,14 @@ export default class extends Controller { this.#formatTime(this.agoFormatter, target) } - daysAgoTargetConnected(target) { - this.#formatTime(this.daysAgoFormatter, target) - } - indaysTargetConnected(target) { this.#formatTime(this.indaysFormatter, target) } + daysagoTargetConnected(target) { + this.#formatTime(this.daysagoFormatter, target) + } + #refreshRelativeTimes() { this.agoTargets.forEach(target => { this.#formatTime(this.agoFormatter, target) @@ -59,7 +60,7 @@ export default class extends Controller { #formatTime(formatter, target) { const dt = new Date(target.getAttribute("datetime")) - target.textContent = formatter.format(dt) + target.innerHTML = formatter.format(dt) target.title = this.dateTimeFormatter.format(dt) } } @@ -93,38 +94,25 @@ class AgoFormatter { } class DaysAgoFormatter { - format(dt) { - const now = new Date() + format(date) { + const days = differenceInDays(date, new Date()) - const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()) - const startOfGivenDay = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate()) - - const msPerDay = 1000 * 60 * 60 * 24 - const dayDiff = Math.floor((startOfToday - startOfGivenDay) / msPerDay) - - if (dayDiff === 0) return "Today" - if (dayDiff === 1) return "Yesterday" - return `in ${dayDiff} days` + if (days <= 0) return styleableValue("today") + if (days === 1) return styleableValue("yesterday") + return `${styleableValue(days)} days ago` } } class InDaysFormatter { - format(dt) { - const target = this.#beginningOfDay(dt) - const today = this.#beginningOfDay(new Date()) - const days = Math.round((target - today) / (1000 * 60 * 60 * 24)) + format(date) { + const days = differenceInDays(new Date(), date) - if (days <= 0) { - return "today" - } - if (days === 1) { - return "tomorrow" - } - - return `in ${Math.round(days)} days` - } - - #beginningOfDay(dt) { - return new Date(dt.getFullYear(), dt.getMonth(), dt.getDate()) + if (days <= 0) return styleableValue("today") + if (days === 1) return styleableValue("tomorrow") + return `in ${styleableValue(days)} days` } } + +function styleableValue(value) { + return `${value}` +} diff --git a/app/javascript/helpers/date_helpers.js b/app/javascript/helpers/date_helpers.js new file mode 100644 index 000000000..9f3dffc4d --- /dev/null +++ b/app/javascript/helpers/date_helpers.js @@ -0,0 +1,7 @@ +export function differenceInDays(fromDate, toDate) { + return Math.round(Math.abs((beginningOfDay(toDate) - beginningOfDay(fromDate)) / (1000 * 60 * 60 * 24))) +} + +export function beginningOfDay(date) { + return new Date(date.getFullYear(), date.getMonth(), date.getDate()) +} diff --git a/app/models/account.rb b/app/models/account.rb index 1a6e45fde..a2b3c7e34 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -10,6 +10,8 @@ class Account < ApplicationRecord end end + has_many :comments, through: :users + has_many :workflows, dependent: :destroy has_many :stages, through: :workflows, class_name: "Workflow::Stage" diff --git a/app/models/bubble/assignable.rb b/app/models/bubble/assignable.rb index 98ea3d8ea..c0dd36ff4 100644 --- a/app/models/bubble/assignable.rb +++ b/app/models/bubble/assignable.rb @@ -21,7 +21,7 @@ module Bubble::Assignable private def assign(user) assignments.create! assignee: user, assigner: Current.user - set_watching(user, true) + watch_by user track_event :assigned, assignee_ids: [ user.id ] rescue ActiveRecord::RecordNotUnique diff --git a/app/models/bubble/commentable.rb b/app/models/bubble/commentable.rb index 5b8f630f5..ab1c72a74 100644 --- a/app/models/bubble/commentable.rb +++ b/app/models/bubble/commentable.rb @@ -7,7 +7,7 @@ module Bubble::Commentable def comment_created(comment) increment! :comments_count - set_watching comment.creator, true + watch_by comment.creator track_event :commented, comment_id: comment.id rescore diff --git a/app/models/bubble/pinnable.rb b/app/models/bubble/pinnable.rb index 2452fadfe..044125eae 100644 --- a/app/models/bubble/pinnable.rb +++ b/app/models/bubble/pinnable.rb @@ -9,11 +9,11 @@ module Bubble::Pinnable pins.exists?(user: user) end - def set_pinned(user, pinned) - if pinned - pins.find_or_create_by!(user: user) - else - pins.find_by(user: user)&.destroy - end + def pin_by(user) + pins.find_or_create_by!(user: user) + end + + def unpin_by(user) + pins.find_by(user: user).tap { it.destroy } end end diff --git a/app/models/bubble/taggable.rb b/app/models/bubble/taggable.rb index 6b6462e9b..7fa4dee07 100644 --- a/app/models/bubble/taggable.rb +++ b/app/models/bubble/taggable.rb @@ -8,22 +8,19 @@ module Bubble::Taggable scope :tagged_with, ->(tags) { joins(:taggings).where(taggings: { tag: tags }) } end - def toggle_tag(tag) - tagged_with?(tag) ? untag(tag) : tag(tag) + def toggle_tag_with(title) + tag = bucket.account.tags.find_or_create_by!(title: title) + + transaction do + if tagged_with?(tag) + taggings.destroy_by tag: tag + else + taggings.create tag: tag + end + end end def tagged_with?(tag) tags.include? tag end - - private - def tag(tag) - taggings.create! tag: tag - rescue ActiveRecord::RecordNotUnique - # Already tagged - end - - def untag(tag) - taggings.destroy_by tag: tag - end end diff --git a/app/models/bubble/watchable.rb b/app/models/bubble/watchable.rb index 8ad47554d..bc4a82ccf 100644 --- a/app/models/bubble/watchable.rb +++ b/app/models/bubble/watchable.rb @@ -5,15 +5,19 @@ module Bubble::Watchable has_many :watches, dependent: :destroy has_many :watchers, -> { merge(Watch.watching) }, through: :watches, source: :user - after_create :set_watching_for_creator + after_create -> { watch_by creator } end def watched_by?(user) watchers_and_subscribers(include_only_watching: true).include?(user) end - def set_watching(user, watching) - watches.where(user: user).first_or_create.update!(watching: watching) + def watch_by(user) + watches.where(user: user).first_or_create.update!(watching: true) + end + + def unwatch_by(user) + watches.where(user: user).first_or_create.update!(watching: false) end def watchers_and_subscribers(include_only_watching: false) @@ -23,9 +27,4 @@ module Bubble::Watchable User.where(id: subscribers.pluck(:id) + watches.watching.pluck(:user_id) - watches.not_watching.pluck(:user_id)) end - - private - def set_watching_for_creator - set_watching(creator, true) - end end diff --git a/app/models/bucket.rb b/app/models/bucket.rb index 20a7f0336..ce6ec4154 100644 --- a/app/models/bucket.rb +++ b/app/models/bucket.rb @@ -12,7 +12,7 @@ class Bucket < ApplicationRecord after_save :update_bubbles_workflow, if: :saved_change_to_workflow_id? - scope :alphabetically, -> { order(name: :asc) } + scope :alphabetically, -> { order("lower(name)") } private def update_bubbles_workflow diff --git a/app/models/event/particulars.rb b/app/models/event/particulars.rb index 9584bd6d5..a9cec0452 100644 --- a/app/models/event/particulars.rb +++ b/app/models/event/particulars.rb @@ -10,6 +10,6 @@ module Event::Particulars end def comment - @comment ||= Comment.find_by(id: comment_id) + @comment ||= account.comments.find_by(id: comment_id) end end diff --git a/app/models/pin.rb b/app/models/pin.rb index 5bf4635c8..86913023c 100644 --- a/app/models/pin.rb +++ b/app/models/pin.rb @@ -3,10 +3,4 @@ class Pin < ApplicationRecord belongs_to :user scope :ordered, -> { order(created_at: :desc) } - - after_create_commit -> { broadcast_prepend_later_to [ user, :pins ], - target: "pins", - partial: "bubbles/pins/pin" - } - after_destroy_commit -> { broadcast_remove_to [ user, :pins ] } end diff --git a/app/models/tag.rb b/app/models/tag.rb index 51e27c30f..376e21bc0 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -7,6 +7,9 @@ class Tag < ApplicationRecord has_many :bubbles, through: :taggings validates :title, format: { without: /\A#/ } + normalizes :title, with: -> { it.downcase } + + scope :alphabetically, -> { order("lower(title)") } def hashtag "#" + title diff --git a/app/models/user.rb b/app/models/user.rb index 2eb87913f..623111447 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,36 +1,23 @@ class User < ApplicationRecord - include Avatar, Role, Transferable + include Accessor, Assignee, Avatar, Role, Transferable belongs_to :account has_many :sessions, dependent: :destroy has_secure_password validations: false - has_many :accesses, dependent: :destroy - has_many :buckets, through: :accesses - has_many :accessible_bubbles, through: :buckets, source: :bubbles - - has_many :filters, foreign_key: :creator_id, inverse_of: :creator, dependent: :destroy - - has_many :pops, dependent: :nullify - - has_many :assignments, foreign_key: :assignee_id, dependent: :destroy - has_many :assignings, foreign_key: :assigner_id, class_name: "Assignment" - has_many :assigned_bubbles, through: :assignments, source: :bubble + has_many :comments, inverse_of: :creator, dependent: :destroy has_many :notifications, dependent: :destroy - has_one_attached :avatar - + has_many :filters, foreign_key: :creator_id, inverse_of: :creator, dependent: :destroy + has_many :pops, dependent: :nullify has_many :pins, dependent: :destroy has_many :pinned_bubbles, through: :pins, source: :bubble normalizes :email_address, with: ->(value) { value.strip.downcase } - after_create_commit :grant_access_to_buckets - scope :alphabetically, -> { order("lower(name)") } - scope :sorted_with_user_first, ->(user) { order(Arel.sql("users.id != ?, lower(name)", user.id)) } def initials name.to_s.scan(/\b\p{L}/).join.upcase @@ -38,22 +25,14 @@ class User < ApplicationRecord def deactivate transaction do - sessions.destroy_all + sessions.delete_all accesses.destroy_all update! active: false, email_address: deactived_email_address end end - def current? - Current.user == self - end - private def deactived_email_address email_address.sub(/@/, "-deactivated-#{SecureRandom.uuid}@") end - - def grant_access_to_buckets - Access.insert_all account.buckets.all_access.pluck(:id).collect { |bucket_id| { bucket_id: bucket_id, user_id: id } } - end end diff --git a/app/models/user/accessor.rb b/app/models/user/accessor.rb new file mode 100644 index 000000000..5615f9976 --- /dev/null +++ b/app/models/user/accessor.rb @@ -0,0 +1,16 @@ +module User::Accessor + extend ActiveSupport::Concern + + included do + has_many :accesses, dependent: :destroy + has_many :buckets, through: :accesses + has_many :accessible_bubbles, through: :buckets, source: :bubbles + + after_create_commit :grant_access_to_buckets + end + + private + def grant_access_to_buckets + Access.insert_all account.buckets.all_access.pluck(:id).collect { |bucket_id| { bucket_id: bucket_id, user_id: id } } + end +end diff --git a/app/models/user/assignee.rb b/app/models/user/assignee.rb new file mode 100644 index 000000000..730158164 --- /dev/null +++ b/app/models/user/assignee.rb @@ -0,0 +1,9 @@ +module User::Assignee + extend ActiveSupport::Concern + + included do + has_many :assignments, foreign_key: :assignee_id, dependent: :destroy + has_many :assignings, foreign_key: :assigner_id, class_name: "Assignment" + has_many :assigned_bubbles, through: :assignments, source: :bubble + end +end diff --git a/app/views/accounts/users/_user.html.erb b/app/views/accounts/users/_user.html.erb index ebfec287a..e8e57b096 100644 --- a/app/views/accounts/users/_user.html.erb +++ b/app/views/accounts/users/_user.html.erb @@ -1,5 +1,5 @@
- <%= avatar_tag user, loading: :lazy, class: "flex-item-no-shrink" %> + <%= avatar_tag user, class: "flex-item-no-shrink" %> <%= link_to user.name, user_path(user), class: "txt-ink btn btn--plain" %> diff --git a/app/views/bubbles/_assignment.html.erb b/app/views/bubbles/_assignment.html.erb deleted file mode 100644 index 2d6914591..000000000 --- a/app/views/bubbles/_assignment.html.erb +++ /dev/null @@ -1,23 +0,0 @@ -<% cache [ bubble, bubble.bucket ] do %> - <%= turbo_frame_tag bubble, :assignment do %> - - Assign this to… - - <%= form_with url: bucket_bubble_assignment_toggles_path(bubble.bucket, bubble), class: "flex flex-column full-width popup__list", data: { controller: "form" } do |form| %> - <% bubble.bucket.users.active.sort_by(&:name).each do |user| %> - - <% end %> - <% end %> - - <% end %> -<% end %> diff --git a/app/views/bubbles/_bubble.html.erb b/app/views/bubbles/_bubble.html.erb deleted file mode 100644 index d22a7f701..000000000 --- a/app/views/bubbles/_bubble.html.erb +++ /dev/null @@ -1,48 +0,0 @@ -<% cache bubble do %> - <%= tag.div class: [ "bubble", drafted: bubble.drafted?, popped: bubble.popped? ], - style: "view-transition-name: bubble-#{bubble.id}; --bubble-color: #{bubble.color}; #{bubble_rotation(bubble)}", - role: "group", aria: { label: bubble_title(bubble) }, - data: { - bubble_size_target: "bubble", - activity_score: bubble.activity_score, - activity_score_at: bubble.activity_score_at.to_i, - controller: "animation upload-preview", - animation_play_class: "bubble--wobble", - animation_play_on_load_value: true, - action: "mouseover->animation#play" } do %> -
-

- <% if bubble.published? %> - <%= turbo_frame_tag bubble, :edit do %> - <%= link_to bubble_title(bubble), edit_bucket_bubble_path(bubble.bucket, bubble), - class: "txt-undecorated bubble__title-rendered", - role: "button", aria: { label: "Edit name: #{bubble_title(bubble)}" } %> - <% end %> - <% else %> - <%= form_with model: bubble, url: bucket_bubble_path(bubble.bucket, bubble), id: "bubble_form", data: { controller: "auto-save" } do |form| %> - <%= form.text_area :title, placeholder: "Name it…", class: "input full-width borderless txt-align-start card__title-field", autofocus: bubble.title.blank?, data: { action: "auto-save#change blur->auto-save#submit keydown.enter->auto-save#submit:prevent keydown.ctrl+enter->auto-save#submit:prevent" } %> - <% end %> - <% end %> - -
- <%= render "bubbles/tags", bubble: bubble %> -
-

-
- -
- - -

- <%= link_to bucket_bubble_path(bubble.bucket, bubble), class: "bubble__link" do %> - <%= bubble_title(bubble) %> - <% end %> -

- - <%= render "bubbles/assignments", bubble: bubble %> - <%= render "bubbles/boosts", bubble: bubble %> - <%= render "bubbles/comments", bubble: bubble %> - <%= render "bubbles/date", bubble: bubble %> - <%= render "bubbles/image", bubble: bubble %> - <% end %> -<% end %> diff --git a/app/views/bubbles/_image.html.erb b/app/views/bubbles/_image.html.erb index 6c5d4e84c..e038c041f 100644 --- a/app/views/bubbles/_image.html.erb +++ b/app/views/bubbles/_image.html.erb @@ -1,5 +1,5 @@ <% if bubble.image.attached? %> - <%= button_to bucket_bubble_image_path(bubble.bucket, bubble), method: :delete, class: "btn" do %> + <%= button_to bubble_image_path(bubble), method: :delete, class: "btn" do %> <%= icon_tag "picture-remove" %> Clear background image <% end %> diff --git a/app/views/bubbles/_messages.html.erb b/app/views/bubbles/_messages.html.erb index c28e26eae..9ad77ac10 100644 --- a/app/views/bubbles/_messages.html.erb +++ b/app/views/bubbles/_messages.html.erb @@ -1,13 +1,18 @@ <%= messages_tag(bubble) do %> - <%# Template Dependency: comments/comment %> - <%# Template Dependency: event_summaries/event_summary %> - <%= render bubble.messages, cached: true %> + <%= render partial: "messages/message", collection: bubble.messages, cached: true %> <%= render "comments/new", bubble: bubble, cached: true %> +
Subscribers -

<%= pluralize(@bubble.watchers.count, "person") %> will be notified when someone comments on this.

+ +

+ <%= pluralize(@bubble.watchers.count, "person") %> will be notified when someone comments on this. +

+
- <%= render partial: "bubbles/watches/watcher", collection: @bubble.watchers.sorted_with_user_first(Current.user) %> + <% prepend_current_user_to(@bubble.watchers.alphabetically).each do |watcher| %> + <%= avatar_tag watcher %> + <% end %>
<% end %> diff --git a/app/views/bubbles/_pop_toggle.html.erb b/app/views/bubbles/_pop_toggle.html.erb index d55fbbdab..3335efdac 100644 --- a/app/views/bubbles/_pop_toggle.html.erb +++ b/app/views/bubbles/_pop_toggle.html.erb @@ -1,7 +1,7 @@
<% if bubble.popped? %> Completed by <%= bubble.popped_by.name %> on <%= local_datetime_tag(bubble.popped_at, style: :shortdate) %>. - <%= button_to bucket_bubble_pop_path(bubble.bucket, bubble), method: :delete, class: "btn btn--plain borderless fill-transparent" do %> + <%= button_to bubble_pop_path(bubble), method: :delete, class: "btn btn--plain borderless fill-transparent" do %> Un-do <% end %> <% else %> @@ -16,17 +16,10 @@ Close as… <% Current.account.pop_reasons.labels.each do |label| %> - <%= button_to bucket_bubble_pop_path(bubble.bucket, bubble, reason: label), class: "btn popup__item full-width", form_class: "full-width" do %> + <%= button_to bubble_pop_path(bubble, reason: label), class: "btn popup__item full-width", form_class: "full-width" do %> <%= label %> <% end %> <% end %> - - <% if bubble.doing? %> - - <%= button_to bucket_bubble_engagement_path(bubble.bucket, bubble), method: :delete, class: "btn popup__item full-width" do %> - Move back to Considering - <% end %> - <% end %>
diff --git a/app/views/bubbles/_publish.html.erb b/app/views/bubbles/_publish.html.erb index 9d01f7b13..044142556 100644 --- a/app/views/bubbles/_publish.html.erb +++ b/app/views/bubbles/_publish.html.erb @@ -1,3 +1,3 @@ -<%= button_to bucket_bubble_publish_path(bubble.bucket, bubble), class: "btn txt-small btn--link", style: "--btn-background: #{ bubble.color }" do %> +<%= button_to bubble_publish_path(bubble), class: "btn txt-small btn--link", style: "--btn-background: #{bubble.color}" do %> Post to project <% end %> diff --git a/app/views/bubbles/_tagging.html.erb b/app/views/bubbles/_tagging.html.erb deleted file mode 100644 index e37696cc2..000000000 --- a/app/views/bubbles/_tagging.html.erb +++ /dev/null @@ -1,31 +0,0 @@ -<% cache [ bubble, Current.account ] do %> - <%= turbo_frame_tag bubble, :tagging do %> - - Tag this… - - <%= form_with url: bucket_bubble_tagging_toggles_path(bubble.bucket, bubble), class: "flex gap-half align-center full-width pad-inline-half margin-block-end" do |form| %> - <%= form.text_field :tag_title, placeholder: "New tag name...", class: "input txt-small full-width" %> - <%= form.button "Add a tag…", type: "submit", class: "btn txt-small" do %> - <%= icon_tag "add" %> - Add a tag - <% end %> - <% end %> - - <%= form_with url: bucket_bubble_tagging_toggles_path(bubble.bucket, bubble), class: "flex flex-column full-width popup__list", data: { controller: "form" } do |form| %> - <% tags.sort_by { |tag| tag.hashtag.downcase }.each do |tag| %> - - <% end %> - <% end %> - - <% end %> -<% end %> diff --git a/app/views/bubbles/assignments/create.turbo_stream.erb b/app/views/bubbles/assignments/create.turbo_stream.erb new file mode 100644 index 000000000..67f41c898 --- /dev/null +++ b/app/views/bubbles/assignments/create.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.replace([ @bubble, :assignees ], partial: "bubbles/cards/perma/assignees", locals: { bubble: @bubble }) %> diff --git a/app/views/bubbles/assignments/new.html.erb b/app/views/bubbles/assignments/new.html.erb new file mode 100644 index 000000000..e130994ae --- /dev/null +++ b/app/views/bubbles/assignments/new.html.erb @@ -0,0 +1,14 @@ +<%= turbo_frame_tag @bubble, :assignment do %> + + Assign this to… + + + +<% end %> diff --git a/app/views/boosts/_boosts.html.erb b/app/views/bubbles/boosts/_boosts.html.erb similarity index 62% rename from app/views/boosts/_boosts.html.erb rename to app/views/bubbles/boosts/_boosts.html.erb index 684ce110e..b741a89fb 100644 --- a/app/views/boosts/_boosts.html.erb +++ b/app/views/bubbles/boosts/_boosts.html.erb @@ -1,10 +1,11 @@ -<%= form_with url: bucket_bubble_boosts_path(bubble.bucket, bubble), class: "pad-inline flex align-center gap" do %> +
<%= text_field_tag :boost_count, bubble.boosts_count, min: 1, class: "boost__input input", autocomplete: "off" %> <%= bubble.boosts_count == 1 ? "boost" : "boosts" %> - <%= tag.button class: "btn", type: :submit, style: "font-size: 0.5em" do %> + + <%= button_to bubble_boosts_path(bubble), class: "btn", type: :submit, style: "font-size: 0.5em" do %> <%= icon_tag "add" %> Boost <% end %> -<% end %> +
diff --git a/app/views/boosts/create.turbo_stream.erb b/app/views/bubbles/boosts/create.turbo_stream.erb similarity index 76% rename from app/views/boosts/create.turbo_stream.erb rename to app/views/bubbles/boosts/create.turbo_stream.erb index e9dcee709..63d73d4a9 100644 --- a/app/views/boosts/create.turbo_stream.erb +++ b/app/views/bubbles/boosts/create.turbo_stream.erb @@ -1,5 +1,5 @@ <%= turbo_stream.update dom_id(@bubble, :boosts) do %> - <%= render "boosts/boosts", bubble: @bubble %> + <%= render "bubbles/boosts/boosts", bubble: @bubble %> <% end %> <%= turbo_stream.replace dom_id(@bubble, :messages) do %> diff --git a/app/views/bubbles/cards/_mini.html.erb b/app/views/bubbles/cards/_mini.html.erb index 234326793..161154023 100644 --- a/app/views/bubbles/cards/_mini.html.erb +++ b/app/views/bubbles/cards/_mini.html.erb @@ -1,13 +1,15 @@
- <%= avatar_image_tag bubble.creator, loading: :lazy, class: "avatar" %> + <%= avatar_image_tag bubble.creator, class: "avatar" %>

<%= bubble_title(bubble) %>

+
<%= "Added to #{bubble.bucket.name} by #{bubble.creator.name}" %> - <%= local_datetime_tag(bubble.created_at, style: :daysAgo) %> + <%= local_datetime_tag(bubble.created_at, style: :daysago) %>
+
<%= render "bubbles/cards/mini/assignees", bubble: bubble %>
<%= render "bubbles/cards/mini/tags", bubble: bubble %>
diff --git a/app/views/bubbles/cards/_perma.html.erb b/app/views/bubbles/cards/_perma.html.erb index c04fe9f62..4dd1dc684 100644 --- a/app/views/bubbles/cards/_perma.html.erb +++ b/app/views/bubbles/cards/_perma.html.erb @@ -1,7 +1,7 @@ -<% cache [bubble] do %> -
+<% cache bubble do %> +
<%= link_to bubble.bucket.name, bubbles_path(bucket_ids: [bubble.bucket]), class: "card__bucket txt-uppercase overflow-ellipsis txt-reversed" %> @@ -32,13 +32,6 @@ <%= render "bubbles/stagings/stages", bubble: bubble %> <% end %> <% end %> - - <% if bubble.considering? %> - <%= button_to bucket_bubble_engagement_path(bubble.bucket, bubble), method: :post, class: "card__move-button card__hide-on-index btn btn--reversed" do %> - Move to Doing - <%= icon_tag "assigned" %> - <% end %> - <% end %>
diff --git a/app/views/bubbles/cards/common/_assignees.html.erb b/app/views/bubbles/cards/common/_assignees.html.erb index 70cefba5d..5286fe816 100644 --- a/app/views/bubbles/cards/common/_assignees.html.erb +++ b/app/views/bubbles/cards/common/_assignees.html.erb @@ -1,16 +1,14 @@ -<%= turbo_frame_tag bubble, :assignees do %> -
- <% bubble.assignees.each do |assignee| %> - <%= avatar_tag assignee, loading: :lazy, class: "avatar" %> - <% end %> +
+ <% bubble.assignees.each do |assignee| %> + <%= avatar_tag assignee, class: "avatar" %> + <% end %> -
- +
+ - <%= yield %> -
+ <%= yield %>
-<% end %> +
diff --git a/app/views/bubbles/cards/common/_meta.html.erb b/app/views/bubbles/cards/common/_meta.html.erb index 59f51c8a5..3efe7970d 100644 --- a/app/views/bubbles/cards/common/_meta.html.erb +++ b/app/views/bubbles/cards/common/_meta.html.erb @@ -1,18 +1,18 @@
<%= bubble.creating? ? "Added" : "Updated" %> - <%= local_datetime_tag(bubble.updated_at, style: :daysAgo) %> + <%= local_datetime_tag(bubble.updated_at, style: :daysago) %>
<% if bubble.creating? %> Expires - <%= local_datetime_tag(bubble.auto_pop_at, style: :daysAgo) %> + <%= local_datetime_tag(bubble.auto_pop_at, style: :indays) %> <% else %> Added - <%= local_datetime_tag(bubble.created_at, style: :daysAgo) %> + <%= local_datetime_tag(bubble.created_at, style: :daysago) %> <% end %>
diff --git a/app/views/bubbles/cards/common/_people.html.erb b/app/views/bubbles/cards/common/_people.html.erb index 6790894ad..ea4645be5 100644 --- a/app/views/bubbles/cards/common/_people.html.erb +++ b/app/views/bubbles/cards/common/_people.html.erb @@ -1,7 +1,7 @@
Reported - <%= avatar_tag bubble.creator, loading: :lazy, class: "avatar" %> + <%= avatar_tag bubble.creator, class: "avatar" %>
diff --git a/app/views/bubbles/cards/mini/_assignees.html.erb b/app/views/bubbles/cards/mini/_assignees.html.erb index 7fa0214e4..750894f53 100644 --- a/app/views/bubbles/cards/mini/_assignees.html.erb +++ b/app/views/bubbles/cards/mini/_assignees.html.erb @@ -1,7 +1,7 @@ <%= turbo_frame_tag bubble, :assignees do %>
<% bubble.assignees.each do |assignee| %> - <%= avatar_tag assignee, loading: :lazy, class: "avatar" %> + <%= avatar_tag assignee, class: "avatar" %> <% end %>
diff --git a/app/views/bubbles/cards/perma/_assignees.html.erb b/app/views/bubbles/cards/perma/_assignees.html.erb index b9ef3d6db..8733e969e 100644 --- a/app/views/bubbles/cards/perma/_assignees.html.erb +++ b/app/views/bubbles/cards/perma/_assignees.html.erb @@ -1,3 +1,3 @@ <%= render "bubbles/cards/common/assignees", bubble: bubble do %> - <%= turbo_frame_tag bubble, :assignment, src: new_bucket_bubble_assignment_toggle_path(bubble.bucket, bubble) %> + <%= turbo_frame_tag bubble, :assignment, src: new_bubble_assignment_path(bubble) %> <% end %> diff --git a/app/views/bubbles/cards/perma/_boosts.html.erb b/app/views/bubbles/cards/perma/_boosts.html.erb index a465a7a53..10c18c79e 100644 --- a/app/views/bubbles/cards/perma/_boosts.html.erb +++ b/app/views/bubbles/cards/perma/_boosts.html.erb @@ -1,3 +1,3 @@ <%= turbo_frame_tag dom_id(bubble, :boosts) do %> - <%= render "boosts/boosts", bubble: bubble %> + <%= render "bubbles/boosts/boosts", bubble: bubble %> <% end %> diff --git a/app/views/bubbles/cards/perma/_tags.html.erb b/app/views/bubbles/cards/perma/_tags.html.erb index 9903f9af5..1f51d64db 100644 --- a/app/views/bubbles/cards/perma/_tags.html.erb +++ b/app/views/bubbles/cards/perma/_tags.html.erb @@ -1,24 +1,22 @@ -<%= turbo_frame_tag bubble, :tags do %> -
-
- <% if bubble.tags.any? || bubble.creating? %> - # - <% bubble.tags.each_with_index do |tag, index| %> - <%= link_to bubbles_path(bucket_ids: [ bubble.bucket ], tag_ids: [ tag.id ]), - class: "card__tag btn btn--plain min-width txt-uppercase fill-transparent", data: { turbo_frame: "_top" } do %> - <%= tag.title %> - <% end %><%= ", " unless index == bubble.tags.size - 1 %> - <% end %> +
+
+ <% if bubble.tags.any? || bubble.creating? %> + # + <% bubble.tags.each_with_index do |tag, index| %> + <%= link_to bubbles_path(bucket_ids: [ bubble.bucket ], tag_ids: [ tag.id ]), + class: "card__tag btn btn--plain min-width txt-uppercase fill-transparent" do %> + <%= tag.title %> + <% end %><%= ", " unless index == bubble.tags.size - 1 %> + <% end %> <% end %> -
- -
- - - <%= turbo_frame_tag bubble, :tagging, src: new_bucket_bubble_tagging_toggle_path(bubble.bucket, bubble) %> -
-<% end %> + +
+ + + <%= turbo_frame_tag bubble, :tagging, src: new_bubble_tagging_path(bubble) %> +
+
diff --git a/app/views/bubbles/pins/show.html.erb b/app/views/bubbles/pins/show.html.erb index 9039cc92f..5a8852a03 100644 --- a/app/views/bubbles/pins/show.html.erb +++ b/app/views/bubbles/pins/show.html.erb @@ -1,13 +1,13 @@ <%= turbo_frame_tag dom_id(@bubble, :pin) do %> <% if @bubble.pinned_by? Current.user %> - <%= button_to bucket_bubble_pin_path(@bubble.bucket, @bubble), method: :delete, class: "btn btn--reversed" do %> - <%= icon_tag "pinned" %> - Un-pin this card - <% end %> + <%= button_to bubble_pin_path(@bubble), method: :delete, class: "btn btn--reversed" do %> + <%= icon_tag "pinned" %> + Un-pin this card + <% end %> <% else %> - <%= button_to bucket_bubble_pin_path(@bubble.bucket, @bubble), class: "btn" do %> - <%= icon_tag "unpinned" %> - Pin this card - <% end %> + <%= button_to bubble_pin_path(@bubble), class: "btn" do %> + <%= icon_tag "unpinned" %> + Pin this card + <% end %> <% end %> <% end %> diff --git a/app/views/bubbles/pins/trays/show.html.erb b/app/views/bubbles/pins/trays/show.html.erb deleted file mode 100644 index 1e155e3c5..000000000 --- a/app/views/bubbles/pins/trays/show.html.erb +++ /dev/null @@ -1,3 +0,0 @@ -<%= turbo_frame_tag "pins" do %> - <%= render collection: @pins, partial: "bubbles/pins/pin" %> -<% end %> \ No newline at end of file diff --git a/app/views/bubbles/pins_trays/show.html.erb b/app/views/bubbles/pins_trays/show.html.erb deleted file mode 100644 index 1e155e3c5..000000000 --- a/app/views/bubbles/pins_trays/show.html.erb +++ /dev/null @@ -1,3 +0,0 @@ -<%= turbo_frame_tag "pins" do %> - <%= render collection: @pins, partial: "bubbles/pins/pin" %> -<% end %> \ No newline at end of file diff --git a/app/views/bubbles/show.html.erb b/app/views/bubbles/show.html.erb index 1d83162c9..ec41a139d 100644 --- a/app/views/bubbles/show.html.erb +++ b/app/views/bubbles/show.html.erb @@ -5,7 +5,7 @@
You have an unsaved work. Would you like to continue where you left off? - <%= button_to bucket_bubble_recover_path(@bubble.bucket, @bubble), class: "btn btn--reversed", data: { turbo_action: "replace" } do %> + <%= button_to bubble_recover_path(@bubble), class: "btn btn--reversed", data: { turbo_action: "replace" } do %> Restore <% end %>
@@ -22,21 +22,46 @@ <% end %> -
-
"> +
+
" style="--bubble-color: <%= @bubble.color %>;"> <%= render "bubbles/cards/perma", bubble: @bubble %>
+ + <%= render "bubbles/color", bubble: @bubble %> <%= render "bubbles/image", bubble: @bubble %> <%= button_to bucket_bubble_path(@bubble.bucket, @bubble), @@ -51,8 +76,8 @@ <% if @bubble.published? %>
- <%= turbo_frame_tag dom_id(@bubble, :watch), src: bucket_bubble_watch_path(@bubble.bucket, @bubble), refresh: :morph %> - <%= turbo_frame_tag dom_id(@bubble, :pin), src: bucket_bubble_pin_path(@bubble.bucket, @bubble), refresh: :morph %> + <%= turbo_frame_tag dom_id(@bubble, :watch), src: bubble_watch_path(@bubble), refresh: :morph %> + <%= turbo_frame_tag dom_id(@bubble, :pin), src: bubble_pin_path(@bubble), refresh: :morph %>
<% end %>
@@ -69,7 +94,7 @@
@@ -86,7 +111,7 @@ <% if @bubble.creating? %>
- <%= button_to "Create card", bucket_bubble_publish_path(@bubble.bucket, @bubble), class: "btn btn--reversed" %> + <%= button_to "Create card", bubble_publish_path(@bubble), class: "btn btn--reversed" %> <%= button_to "Save as draft", bucket_bubble_path(@bubble.bucket, @bubble), name: "bubble[status]", value: "drafted", method: :put, class: "btn" %>
<% end %> diff --git a/app/views/bubbles/taggings/create.turbo_stream.erb b/app/views/bubbles/taggings/create.turbo_stream.erb new file mode 100644 index 000000000..b261c7f0b --- /dev/null +++ b/app/views/bubbles/taggings/create.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.replace([ @bubble, :tags ], partial: "bubbles/cards/perma/tags", locals: { bubble: @bubble }) %> diff --git a/app/views/bubbles/taggings/new.html.erb b/app/views/bubbles/taggings/new.html.erb new file mode 100644 index 000000000..28ba578f7 --- /dev/null +++ b/app/views/bubbles/taggings/new.html.erb @@ -0,0 +1,22 @@ +<%= turbo_frame_tag @bubble, :tagging do %> + + Tag this… + + <%= form_with url: bubble_taggings_path(@bubble), class: "flex gap-half align-center full-width pad-inline-half margin-block-end" do |form| %> + <%= form.text_field :tag_title, placeholder: "New tag name...", class: "input txt-small full-width" %> + <%= form.button "Add a tag…", type: "submit", class: "btn txt-small" do %> + <%= icon_tag "add" %> + Add a tag + <% end %> + <% end %> + +
+ <% @tags.each do |tag| %> + + <% end %> +
+
+<% end %> diff --git a/app/views/bubbles/tags/_listbox_option.html.erb b/app/views/bubbles/tags/_listbox_option.html.erb index cea797bf3..712b94022 100644 --- a/app/views/bubbles/tags/_listbox_option.html.erb +++ b/app/views/bubbles/tags/_listbox_option.html.erb @@ -1,7 +1,4 @@
<%= tag.hashtag %> - - <% if bubble.tagged_with?(tag) %> - <%= icon_tag "check" %> - <% end %> + <%= icon_tag "check" if bubble.tagged_with?(tag) %>
diff --git a/app/views/bubbles/watches/_watcher.html.erb b/app/views/bubbles/watches/_watcher.html.erb deleted file mode 100644 index 35a1d3f91..000000000 --- a/app/views/bubbles/watches/_watcher.html.erb +++ /dev/null @@ -1 +0,0 @@ -<%= avatar_tag watcher, loading: :lazy %> diff --git a/app/views/bubbles/watches/show.html.erb b/app/views/bubbles/watches/show.html.erb index 435cc030f..bd68ab801 100644 --- a/app/views/bubbles/watches/show.html.erb +++ b/app/views/bubbles/watches/show.html.erb @@ -1,11 +1,11 @@ <%= turbo_frame_tag dom_id(@bubble, :watch) do %> <% if @bubble.watched_by? Current.user %> - <%= button_to bucket_bubble_watch_path(@bubble.bucket, @bubble), method: :delete, class: "btn btn--reversed" do %> + <%= button_to bubble_watch_path(@bubble), method: :delete, class: "btn btn--reversed" do %> <%= icon_tag "bell" %> Stop watching <% end %> <% else %> - <%= button_to bucket_bubble_watch_path(@bubble.bucket, @bubble), class: "btn" do %> + <%= button_to bubble_watch_path(@bubble), class: "btn" do %> <%= icon_tag "bell-off" %> Watch this <% end %> diff --git a/app/views/buckets/_access_toggle.erb b/app/views/buckets/_access_toggle.erb index b45067866..aee7bb248 100644 --- a/app/views/buckets/_access_toggle.erb +++ b/app/views/buckets/_access_toggle.erb @@ -1,6 +1,6 @@
  • - <%= avatar_tag user, loading: :lazy %> + <%= avatar_tag user %>
    diff --git a/app/views/buckets/edit.html.erb b/app/views/buckets/edit.html.erb index f5610adbd..c1a04ee59 100644 --- a/app/views/buckets/edit.html.erb +++ b/app/views/buckets/edit.html.erb @@ -2,10 +2,7 @@ <% content_for :header do %>
  • diff --git a/app/views/messages/_message.html.erb b/app/views/messages/_message.html.erb index cd12237f6..1a3b8e401 100644 --- a/app/views/messages/_message.html.erb +++ b/app/views/messages/_message.html.erb @@ -1 +1,5 @@ -<%= render message.messageable %> +<% cache message do %> + <%# Template Dependency: comments/comment %> + <%# Template Dependency: event_summaries/event_summary %> + <%= render partial: message.messageable.to_partial_path, object: message.messageable %> +<% end %> diff --git a/app/views/bubbles/pins/_pin.html.erb b/app/views/my/pins/_pin.html.erb similarity index 100% rename from app/views/bubbles/pins/_pin.html.erb rename to app/views/my/pins/_pin.html.erb diff --git a/app/views/bubbles/pins/_tray.html.erb b/app/views/my/pins/_tray.html.erb similarity index 89% rename from app/views/bubbles/pins/_tray.html.erb rename to app/views/my/pins/_tray.html.erb index 7551f3781..70c1d5a16 100644 --- a/app/views/bubbles/pins/_tray.html.erb +++ b/app/views/my/pins/_tray.html.erb @@ -2,7 +2,7 @@ <%= tag.dialog id: "pin-tray", class: "tray pin-tray", data: { controller: "dialog", turbo_permanent: true, dialog_modal_value: false, dialog_target: "dialog", action: "keydown.esc->dialog#close:stop click@document->dialog#closeOnClickOutside" } do %> - <%= turbo_frame_tag "pins", src: bubbles_pins_tray_path %> + <%= turbo_frame_tag "pins", src: my_pins_path %>
    @@ -10,4 +10,4 @@ -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/my/pins/index.html.erb b/app/views/my/pins/index.html.erb new file mode 100644 index 000000000..d4c176587 --- /dev/null +++ b/app/views/my/pins/index.html.erb @@ -0,0 +1,3 @@ +<%= turbo_frame_tag "pins" do %> + <%= render collection: @pins, partial: "my/pins/pin" %> +<% end %> diff --git a/app/views/notifications/settings/show.html.erb b/app/views/notifications/settings/show.html.erb index a1a1cb9b5..b1733a492 100644 --- a/app/views/notifications/settings/show.html.erb +++ b/app/views/notifications/settings/show.html.erb @@ -2,10 +2,7 @@ <% content_for :header do %>
    <%= link_to "Cancel and go back", user_path(@user), data: { form_target: "cancel", turbo_frame: "_top" }, hidden: true %> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 18aaadd5d..c78100427 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -2,10 +2,7 @@ <% content_for :header do %>