Merge branch 'main' into cascade-layers-2
* main: (104 commits) Default collection permissions to "all access" dep: update active_record-tenanted to 8758240b dep: bump Rails to 32d96235 Test for `Bubbles::RecoversController` Move readings under bubbles and out of double nesting Move bubble stagings out from double nesting Correct alpha ordering Fix indention Move bubble recovery out from double nesting Move bubbles publish out from the double nesting Move bubble pops out from double nesting Move controller to delete bubble image out from double nesting under bucket Revert "Scope searches by user" Scope searches by user Few more lazy loading avatars! Another lazy loading point Remove double nesting under bubbles for engagement Lets just preload all avatars for now No need for an array when there is only one dependency Scope data by account ...
This commit is contained in:
@@ -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
|
||||
@@ -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"
|
||||
|
||||
+9
-29
@@ -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
|
||||
|
||||
@@ -1 +1 @@
|
||||
<svg viewBox="0 0 24 20" xmlns="http://www.w3.org/2000/svg"><path d="m19.1 0c-2.8 0-5 2.2-5 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm-.1 2.5c.3 0 .6.3.6.6v1.2c0 .2 0 .2.2.2h1.2c.3 0 .6.3.6.6s-.3.6-.6.6h-1.2c-.2 0-.2 0-.2.2v1.2c0 .3-.3.6-.6.6s-.6-.3-.6-.6v-1.2c0-.2 0-.2-.2-.2h-1.2c-.3 0-.6-.3-.6-.6s.3-.6.6-.6h1.2c.2 0 .2 0 .2-.2v-1.2c0-.3.3-.6.6-.6z"/><path d="m6.7 10.5c.9 0 1.6-.7 1.6-1.6s-.7-1.6-1.6-1.6-1.6.7-1.6 1.6.7 1.6 1.6 1.6z" fill="none"/><path d="m9.7 16.9h.1l-.1-.1z"/><path d="m19.2 11.6v6.3c0 .2 0 .4-.4.4h-16.8v-13.7c0-.2 0-.4.4-.4h10.1c0-.7.3-1.3.5-1.8h-11.6c-.9.2-1.4.9-1.4 1.8v14c0 1 .8 1.8 1.8 1.8h17.5c1 0 1.8-.8 1.8-1.8v-6.9c-.6.2-1.2.3-1.8.3z"/><path d="m4.8 5.5c-.7 0-1.3.6-1.3 1.3v8.9c0 .4.2.7.5 1l2.6-3.5c.2-.3.6-.5.9-.5s0 0 0 0c.4 0 .7.2.9.5l.9 1.3 2.3-3.5c.3-.4.8-.7 1.3-.7s0 0 0 0c.5 0 1 .3 1.3.7l3.1 5c0-.1 0-.2 0-.3v-4.2c-2.8-.6-4.9-3-5.1-6h-7.6zm1.9 5c-.9 0-1.6-.7-1.6-1.6s.7-1.6 1.6-1.6 1.6.7 1.6 1.6-.7 1.6-1.6 1.6z"/><path d="m5.2 16.9h2.6l1-1.5-1.2-1.7z"/><path d="m10.4 15.8-.7 1 .1.1h1.3 5.1l-3.1-5.1z"/></svg>
|
||||
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19 2c-2.8 0-5 2.2-5 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm-.1 2.5c.3 0 .6.3.6.6v1.2c0 .2 0 .2.2.2h1.2c.3 0 .6.3.6.6s-.3.6-.6.6h-1.2c-.2 0-.2 0-.2.2v1.2c0 .3-.3.6-.6.6s-.6-.3-.6-.6V7.9c0-.2 0-.2-.2-.2h-1.2c-.3 0-.6-.3-.6-.6s.3-.6.6-.6h1.2c.2 0 .2 0 .2-.2V5.1c0-.3.3-.6.6-.6zM9.7 18.9h.1l-.1-.1v.1z" fill="#000"/><path d="M19.2 13.6v6.3c0 .2 0 .4-.4.4H2V6.6c0-.2 0-.4.4-.4h10.1c0-.7.3-1.3.5-1.8H1.4C.5 4.6 0 5.3 0 6.2v14c0 1 .8 1.8 1.8 1.8h17.5c1 0 1.8-.8 1.8-1.8v-6.9c-.6.2-1.2.3-1.8.3h-.1z" fill="#000"/><path d="M4.8 7.5c-.7 0-1.3.6-1.3 1.3v8.9c0 .4.2.7.5 1l2.6-3.5c.2-.3.6-.5.9-.5.4 0 .7.2.9.5l.9 1.3 2.3-3.5c.3-.4.8-.7 1.3-.7s1 .3 1.3.7l3.1 5v-4.5c-2.8-.6-4.9-3-5.1-6H4.6h.2zm1.9 5c-.9 0-1.6-.7-1.6-1.6 0-.9.7-1.6 1.6-1.6.9 0 1.6.7 1.6 1.6 0 .9-.7 1.6-1.6 1.6z" fill="#000"/><path d="M5.2 18.9h2.6l1-1.5-1.2-1.7-2.4 3.2zM10.4 17.8l-.7 1 .1.1H16.2l-3.1-5.1-2.7 4z" fill="#000"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 966 B |
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m12 18.5 7.5 4.5-2-8.5 6.6-5.7-8.7-.8-3.4-8-3.4 8-8.7.8 6.6 5.7-2 8.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 148 B |
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 "); }
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
view-transition-name: header;
|
||||
|
||||
> nav {
|
||||
font-size: var(--text-small);
|
||||
view-transition-name: nav;
|
||||
|
||||
header {
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,5 +1,5 @@
|
||||
class Bubbles::EngagementsController < ApplicationController
|
||||
include BubbleScoped, BucketScoped
|
||||
include BubbleScoped
|
||||
|
||||
def create
|
||||
@bubble.engage
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Bubbles::ImagesController < ApplicationController
|
||||
include BubbleScoped, BucketScoped
|
||||
include BubbleScoped
|
||||
|
||||
def destroy
|
||||
@bubble.image.purge_later
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Bubbles::PopsController < ApplicationController
|
||||
include BubbleScoped, BucketScoped
|
||||
include BubbleScoped
|
||||
|
||||
def create
|
||||
@bubble.pop!(user: Current.user, reason: params[:reason])
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Bubbles::PublishesController < ApplicationController
|
||||
include BubbleScoped, BucketScoped
|
||||
include BubbleScoped
|
||||
|
||||
def create
|
||||
@bubble.publish
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
class ReadingsController < ApplicationController
|
||||
include BubbleScoped, BucketScoped
|
||||
class Bubbles::ReadingsController < ApplicationController
|
||||
include BubbleScoped
|
||||
|
||||
def create
|
||||
mark_bubble_notifications_read
|
||||
@@ -1,5 +1,5 @@
|
||||
class Bubbles::RecoversController < ApplicationController
|
||||
include BubbleScoped, BucketScoped
|
||||
include BubbleScoped
|
||||
|
||||
def create
|
||||
redirect_to @bubble.recover_abandoned_creation
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Bubbles::StagingsController < ApplicationController
|
||||
include BubbleScoped, BucketScoped
|
||||
include BubbleScoped
|
||||
|
||||
def create
|
||||
if params[:stage_id].present?
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
module UsersHelper
|
||||
def prepend_current_user_to(users_scope)
|
||||
users_scope.to_a.prepend(Current.user).uniq
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
|
||||
@@ -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 `<span class="local-time-value">${value}</span>`
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+5
-26
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="flex align-center gap-half pad-inline">
|
||||
<%= avatar_tag user, loading: :lazy, class: "flex-item-no-shrink" %>
|
||||
<%= avatar_tag user, class: "flex-item-no-shrink" %>
|
||||
|
||||
<strong class="overflow-ellipsis">
|
||||
<%= link_to user.name, user_path(user), class: "txt-ink btn btn--plain" %>
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<% cache [ bubble, bubble.bucket ] do %>
|
||||
<%= turbo_frame_tag bubble, :assignment do %>
|
||||
<dialog class="popup panel flex-column align-start gap-half fill-white shadow" data-dialog-target="dialog">
|
||||
<strong class="popup__title margin-block-start-half pad-inline-half txt-nowrap">Assign this to…</strong>
|
||||
|
||||
<%= 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| %>
|
||||
<div class="btn popup__item">
|
||||
<%= form.check_box "assignee_id[]", {
|
||||
checked: bubble.assigned_to?(user),
|
||||
data: { action: "change->form#submit" },
|
||||
id: dom_id(user, :assign),
|
||||
include_hidden: false,
|
||||
}, user.id %>
|
||||
|
||||
<%= form.label "assignee_id[]", user.name, for: dom_id(user, :assign), class: "overflow-ellipsis" %>
|
||||
<%= icon_tag "check", size: 18, class: "checked flex-item-justify-end" %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</dialog>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -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 %>
|
||||
<div>
|
||||
<h1 class="bubble__title" role="presentation">
|
||||
<% 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 %>
|
||||
|
||||
<div class="bubble__tags flex flex-wrap align-end justify-center gap-half txt-tight-lines flex-item-no-shrink pad-inline-double">
|
||||
<%= render "bubbles/tags", bubble: bubble %>
|
||||
</div>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="bubble__shape"></div>
|
||||
<svg class="bubble__splat" viewBox="0 0 934 914" xmlns="http://www.w3.org/2000/svg"><circle cx="485.2" cy="467.2" r="286.5"/><path d="m197.7 219.1c-.2-7.1 5.8-11.7 12.7-13.2 34-7.1 53.5 29.2 81.4 40 39.5 6.4 25.9-54 30.8-77.7 17.2-65.3 78.4 67.2 108.2 55.7 26.8-17.3 25.9-58.8 38.7-86.1 9.6-41.5 30.4-2.6 48.6 10.1 14.2 11.2 31.9 17.9 45.1 29.9 13 11.2 20 29.1 35.8 36.8 20.2 9.1 44.8 2.3 65.2 11.8 18.3 7.2 33.8 22.6 52.5 30.4 23 8.6 42.4 1.8 37.8 35.1 2.7 36.8 38.3 71.9 40.1 108.6-.2 21.1-30.1 21.7-22.4 36.3 22.1 32.5-7.7 51.1 21 93.3 7 43.2-91.2-7.2-14.3 78.2 13.2 13.7 22.3 28.2 4.5 40.2-18.7 18.4-19.5 18.4-45 13.8-15.5-1.2 1.8 16.5-4.6 22.7-21.1 8-6.7 22-14.6 34.9-6 3.2-14.7-2.6-21.1-4-17.2-6.2-11.5 17.5-15.3 25.7-4 7.3-15.6 1.5-22.2-.2-26.7-10.8-55.1-3.1-82.4-.2-14.2 1.1-26 2.2-30.2 18.3-8 20.8-7.5 87.5-41.5 68.5-15.3-7.2-29.8-51.7-45.3-26.4-2.8 3.9-6.4 9.3-11.2 5.9-20.7-27.6-17-71-54.3-25.6-29.6 29.4-32.5 2.5-48.8-20.5-18.9-17.1-48.4-20.5-61.6-44.6-18.5-27.6-35.8-54.6-60.2-77.9-10.5-10.8-60.9-17.2-34.7-35.6 42.5-23.5-50.6-14-19.6-30.8 33.5-11.7 12.7-23.2-10.3-28.1-6.2-1.9-22-3.3-22.7-8.2 2.9-11.2 13.8-26.5 22.6-35.7 38.3-20.1-7-33.5-23.6-48.2-4-4.4 4.8-2.9 5.5-5.8-7.4-11.8-11.8-10-.4-21.8 3.2-7.8-4.9-17.6-3.8-25.5 8.1-21 57.9-6.5 64.2-36.2-3.5-26.1-16.4-50.3 3.5-75 5.9-8.8 22.4-16.8 19.3-28.7-5.7-14.7-26.4-23.7-27.6-40.2z"/><path d="m573 256.7s29.6-24.9 55-64.4 40.3-158 88.1-127.9c44.4 28-27.7 97.3-60.3 143.3-32.6 45.9-14.3 85.7-14.3 85.7l-68.5-36.6z"/><path d="m606.4 674.7s-11.3 37-11.2 84c0 47 51.9 154.6-4.6 155.3-52.5.6-29.6-96.8-27.1-153.1s-34.5-79.7-34.5-79.7z"/><path d="m325.5 622.7s-34 16.1-71.9 44c-37.8 27.9-104.9 118.2-126.8 89.7-20.3-26.5 66.8-72.5 113.1-104.6s51.2-64.9 51.2-64.9l34.4 35.9z"/><path d="m225.7 388.5s-30.8-35.1-75.8-72.7c-45.1-37.5-169.2-98.3-146.9-126.5 20.8-26.1 104.8 63 157.6 109.3 52.9 46.2 90.4 46.9 90.4 46.9l-25.4 43z"/><path d="m327.7 593.7s-47.6 1.4-103.9 17.7c-56.3 16.2-170.6 104.9-187.6 48.8-15.8-52.2 107.7-62.8 175.9-79.7s85.8-61.9 85.8-61.9l29.9 75.1z"/><path d="m443.5 344.2s0-46.7-8.9-104.6c-9-57.9-62-185.6-26.7-192 32.9-5.9 37 116.5 46.2 186.2 9.2 69.6 36.9 95 36.9 95l-47.5 15.5z"/><path d="m433.2 340.9s-6.8-37-19.5-82.8c-12.8-45.8-56.9-146.5-40.8-151.7 15-4.8 34.8 92 49.3 147s31.6 74.9 31.6 74.9l-20.6 12.5z"/><path d="m496.8 298.1s10.8-45.7 13.6-104.2c2.8-58.6-29.9-194.6 12.9-193.4 39.8 1.1 16.5 121.7 11.2 191.8-5.3 70 21.3 100.6 21.3 100.6l-59.1 5.2z"/><path d="m647.8 525s22.1 41.1 57.6 87.8c35.4 46.7 142.8 133.8 114.7 156.2-26.1 20.8-88-84.9-129.1-141.8-41.2-56.9-77.6-66-77.6-66z"/><path d="m658.4 523s23.5 29.3 56.5 63.5 119.7 101.9 108 114.1c-10.9 11.4-74.3-64.4-113.3-105.9s-63.4-50.9-63.4-50.9z"/><path d="m622.8 591s12.2 45.3 37.6 98.2 118.8 157 80.6 176.3c-35.6 17.9-72.4-99.3-101.1-163.4-28.6-64.1-66.6-78.4-66.6-78.4l49.5-32.6z"/><path d="m613.7 441.6s43.3 17.5 100.4 30.7c57.1 13.3 195.4 11.6 188.1 46.8-6.8 32.7-121.9-9.1-189.9-26.6s-101.9-1.2-101.9-1.2l3.3-49.8z"/><path d="m666.2 496.5s38.4 27.1 91.7 51.5 191.7 44.8 174.6 84.1c-15.9 36.5-119.1-30.1-182.2-61.1-63-31-101.3-17.7-101.3-17.7l17.2-56.7z"/><path d="m57.2 500.1c-7.1-1.3-14.4-2-21.5-.7-13.9 2.4-27.3 11.4-31.7 25.2-5 15.2 2.5 30.2 12.9 41.7 7.5 8.5 16.3 16.5 27.6 16.6 19.9-.7 36.3-19.9 37.6-39 1.6-18.3-8.2-36.2-27.2-40.2-3.5-.8-7.2-1.2-10.8-1.5"/><path d="m796.1 362.9c5.4 21.4 22.7 41 44.2 46.3 16.7 4.5 33.7-3 43.2-17.2 24.3-38.2-3-125.5-53.2-116.5-8 1.5-15.8 5.8-21.4 11.6-18.2 19.3-19.3 51.3-12.8 75.6z"/><path d="m255.1 782.9c5.4 21.4 22.7 41 44.2 46.3 16.7 4.5 33.7-3 43.2-17.2 24.3-38.2-3-125.5-53.2-116.5-8 1.5-15.8 5.8-21.4 11.6-18.2 19.3-19.3 51.3-12.8 75.6z"/><path d="m771 224.4c-3.4-.5-6.8-1-10.2-1.1-23.8-1.1-36.1 23.5-16.7 36.4 9.4 6.3 22.2 2.4 26.7-7.7 5.2-11.6-.7-20.3-10.6-26.9"/><path d="m402 812.4c-3.4-.5-6.8-1-10.2-1.1-23.8-1.1-36.1 23.5-16.7 36.4 9.4 6.3 22.2 2.4 26.7-7.7 5.2-11.6-.7-20.3-10.6-26.9"/></svg>
|
||||
|
||||
<h2 contents>
|
||||
<%= link_to bucket_bubble_path(bubble.bucket, bubble), class: "bubble__link" do %>
|
||||
<span class="for-screen-reader"><%= bubble_title(bubble) %></span>
|
||||
<% end %>
|
||||
</h2>
|
||||
|
||||
<%= 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 %>
|
||||
@@ -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" %>
|
||||
<span class="for-screen-reader">Clear background image</span>
|
||||
<% end %>
|
||||
|
||||
@@ -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 %>
|
||||
|
||||
<div class="comments__subscribers flex flex-column margin-block txt-align-start full-width">
|
||||
<strong class="txt-uppercase">Subscribers</strong>
|
||||
<p class="margin-none-block-start margin-block-end-half"><%= pluralize(@bubble.watchers.count, "person") %> will be notified when someone comments on this.</p>
|
||||
|
||||
<p class="margin-none-block-start margin-block-end-half">
|
||||
<%= pluralize(@bubble.watchers.count, "person") %> will be notified when someone comments on this.
|
||||
</p>
|
||||
|
||||
<div class="flex align-center flex-wrap gap-half max-width txt-normal">
|
||||
<%= 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 %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="card__actions-container center border-radius flex-inline align-center justify-center gap" style="--bubble-color: <%= @bubble.color %>">
|
||||
<% if bubble.popped? %>
|
||||
<span class="card__pop-message">Completed by <%= bubble.popped_by.name %> on <%= local_datetime_tag(bubble.popped_at, style: :shortdate) %>.</span>
|
||||
<%= 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 %>
|
||||
<span>Un-do</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
@@ -16,17 +16,10 @@
|
||||
<strong class="popup__title margin-block-start-half pad-inline-half">Close as…</strong>
|
||||
|
||||
<% 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 %>
|
||||
<span class="overflow-ellipsis"><%= label %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% if bubble.doing? %>
|
||||
<hr class="separator--horizontal full-width flex-item-grow" style="--border-color: var(--color-subtle)" aria-hidden="true">
|
||||
<%= button_to bucket_bubble_engagement_path(bubble.bucket, bubble), method: :delete, class: "btn popup__item full-width" do %>
|
||||
<span class="overflow-ellipsis">Move back to Considering</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</dialog>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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 %>
|
||||
<span>Post to project</span>
|
||||
<% end %>
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
<% cache [ bubble, Current.account ] do %>
|
||||
<%= turbo_frame_tag bubble, :tagging do %>
|
||||
<dialog class="popup panel flex-column align-start justify-start fill-white shadow" data-dialog-target="dialog">
|
||||
<strong class="popup__title margin-block-half pad-inline-half">Tag this…</strong>
|
||||
|
||||
<%= 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" %>
|
||||
<span class="for-screen-reader">Add a tag</span>
|
||||
<% 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| %>
|
||||
<div class="btn popup__item">
|
||||
<%= form.check_box "tag_id[]", {
|
||||
checked: bubble.tagged_with?(tag),
|
||||
data: { action: "change->form#submit" },
|
||||
id: dom_id(tag, :tag),
|
||||
include_hidden: false,
|
||||
}, tag.id %>
|
||||
|
||||
<%= form.label "tag_id[]", tag.hashtag, for: dom_id(tag, :tag), class: "overflow-ellipsis" %>
|
||||
<%= icon_tag "check", size: 18, class: "checked flex-item-justify-end" %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</dialog>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -0,0 +1 @@
|
||||
<%= turbo_stream.replace([ @bubble, :assignees ], partial: "bubbles/cards/perma/assignees", locals: { bubble: @bubble }) %>
|
||||
@@ -0,0 +1,14 @@
|
||||
<%= turbo_frame_tag @bubble, :assignment do %>
|
||||
<dialog class="popup panel flex-column align-start gap-half fill-white shadow" data-dialog-target="dialog">
|
||||
<strong class="popup__title margin-block-start-half pad-inline-half txt-nowrap">Assign this to…</strong>
|
||||
|
||||
<div class="flex flex-column full-width popup__list">
|
||||
<% @bubble.bucket.users.active.alphabetically.each do |user| %>
|
||||
<div class="btn popup__item">
|
||||
<%= button_to user.name, bubble_assignments_path(@bubble, params: { assignee_id: user.id }), method: :post %>
|
||||
<%= icon_tag "check", size: 18, class: "checked flex-item-justify-end" if @bubble.assignees.include?(user) %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</dialog>
|
||||
<% end %>
|
||||
@@ -1,10 +1,11 @@
|
||||
<%= form_with url: bucket_bubble_boosts_path(bubble.bucket, bubble), class: "pad-inline flex align-center gap" do %>
|
||||
<div class="pad-inline flex align-center gap">
|
||||
<span class="overflow-ellipsis">
|
||||
<strong><%= text_field_tag :boost_count, bubble.boosts_count, min: 1, class: "boost__input input", autocomplete: "off" %></strong>
|
||||
<span><%= bubble.boosts_count == 1 ? "boost" : "boosts" %></span>
|
||||
</span>
|
||||
<%= 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" %>
|
||||
<span class="for-screen-reader">Boost</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
+1
-1
@@ -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 %>
|
||||
@@ -1,13 +1,15 @@
|
||||
<div class="card card--mini shadow border flex gap position-relative txt-align-start full-width border-radius" style="view-transition-name: <%= dom_id(bubble, :ticket) %>;">
|
||||
<%= avatar_image_tag bubble.creator, loading: :lazy, class: "avatar" %>
|
||||
<%= avatar_image_tag bubble.creator, class: "avatar" %>
|
||||
|
||||
<div class="card__body flex flex-column gap-half full-width flex-item-grow min-width max-width">
|
||||
<h3 class="card__title">
|
||||
<%= bubble_title(bubble) %>
|
||||
</h3>
|
||||
|
||||
<div class="txt-small overflow-ellipsis txt-uppercase"><%= "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) %>
|
||||
</div>
|
||||
|
||||
<div class="card__stuff flex align-center gap">
|
||||
<%= render "bubbles/cards/mini/assignees", bubble: bubble %>
|
||||
<div class="overflow-ellipsis"><%= render "bubbles/cards/mini/tags", bubble: bubble %></div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<% cache [bubble] do %>
|
||||
<article class="card shadow border flex flex-column position-relative txt-align-start border-radius"
|
||||
style="--bubble-color: <%= bubble.color %>; view-transition-name: <%= dom_id(bubble, :ticket) %>;"
|
||||
id="<%= dom_id(bubble, :ticket) %>">
|
||||
<% cache bubble do %>
|
||||
<article class="card shadow border flex flex-column position-relative txt-align-start full-width border-radius"
|
||||
style="--bubble-color: <%= bubble.color %>; view-transition-name: <%= dom_id(bubble, :ticket) %>;"
|
||||
id="<%= dom_id(bubble, :ticket) %>">
|
||||
<header class="card__header flex align-center gap min-width">
|
||||
<%= 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 %>
|
||||
<span class="txt-nowrap">Move to Doing</span>
|
||||
<%= icon_tag "assigned" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<footer class="card__footer full-width flex align-start gap">
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
<%= turbo_frame_tag bubble, :assignees do %>
|
||||
<div class="flex">
|
||||
<% bubble.assignees.each do |assignee| %>
|
||||
<%= avatar_tag assignee, loading: :lazy, class: "avatar" %>
|
||||
<% end %>
|
||||
<div class="flex" id="<%= dom_id(bubble, :assignees) %>">
|
||||
<% bubble.assignees.each do |assignee| %>
|
||||
<%= avatar_tag assignee, class: "avatar" %>
|
||||
<% end %>
|
||||
|
||||
<div class="position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="btn card__hide-on-index <%= bubble.creating? || bubble.drafted? ? "fill-highlight" : "fill-transparent" %>" data-action="click->dialog#open:stop">
|
||||
<%= icon_tag "add" %>
|
||||
<span class="for-screen-reader">Assign</span>
|
||||
</button>
|
||||
<div class="position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="btn card__hide-on-index <%= bubble.creating? || bubble.drafted? ? "fill-highlight" : "fill-transparent" %>" data-action="click->dialog#open:stop">
|
||||
<%= icon_tag "add" %>
|
||||
<span class="for-screen-reader">Assign</span>
|
||||
</button>
|
||||
|
||||
<%= yield %>
|
||||
</div>
|
||||
<%= yield %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<div class="card__meta flex txt-tight-lines txt-uppercase">
|
||||
<div class="flex flex-column gap txt-align-end">
|
||||
<span class="pad-inline overflow-ellipsis"><%= bubble.creating? ? "Added" : "Updated" %>
|
||||
<%= local_datetime_tag(bubble.updated_at, style: :daysAgo) %>
|
||||
<%= local_datetime_tag(bubble.updated_at, style: :daysago) %>
|
||||
</span>
|
||||
|
||||
<hr class="full-width separator--horizontal unpad margin-none">
|
||||
|
||||
<% if bubble.creating? %>
|
||||
<span class="pad-inline overflow-ellipsis">Expires
|
||||
<%= local_datetime_tag(bubble.auto_pop_at, style: :daysAgo) %>
|
||||
<%= local_datetime_tag(bubble.auto_pop_at, style: :indays) %>
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="pad-inline overflow-ellipsis">Added
|
||||
<%= local_datetime_tag(bubble.created_at, style: :daysAgo) %>
|
||||
<%= local_datetime_tag(bubble.created_at, style: :daysago) %>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="card__people flex gap margin-inline-start">
|
||||
<div class="flex flex-column align-center">
|
||||
<span class="txt-uppercase">Reported</span>
|
||||
<%= avatar_tag bubble.creator, loading: :lazy, class: "avatar" %>
|
||||
<%= avatar_tag bubble.creator, class: "avatar" %>
|
||||
</div>
|
||||
|
||||
<div class="card__assignees flex flex-column align-center">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<%= turbo_frame_tag bubble, :assignees do %>
|
||||
<div class="flex">
|
||||
<% bubble.assignees.each do |assignee| %>
|
||||
<%= avatar_tag assignee, loading: :lazy, class: "avatar" %>
|
||||
<%= avatar_tag assignee, class: "avatar" %>
|
||||
<% end %>
|
||||
|
||||
<div class="position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
|
||||
@@ -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 %>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<%= turbo_frame_tag dom_id(bubble, :boosts) do %>
|
||||
<%= render "boosts/boosts", bubble: bubble %>
|
||||
<%= render "bubbles/boosts/boosts", bubble: bubble %>
|
||||
<% end %>
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
<%= turbo_frame_tag bubble, :tags do %>
|
||||
<div class="card__tags flex align-center flex-item-grow min-width">
|
||||
<div class="overflow-ellipsis">
|
||||
<% if bubble.tags.any? || bubble.creating? %>
|
||||
<span># </span>
|
||||
<% 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 %>
|
||||
<span class="overflow-ellipsis"><%= tag.title %></span>
|
||||
<% end %><%= ", " unless index == bubble.tags.size - 1 %>
|
||||
<% end %>
|
||||
<div id="<%= dom_id(bubble, :tags) %>" class="card__tags flex align-center flex-item-grow min-width">
|
||||
<div class="overflow-ellipsis">
|
||||
<% if bubble.tags.any? || bubble.creating? %>
|
||||
<span># </span>
|
||||
<% 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 %>
|
||||
<span class="overflow-ellipsis"><%= tag.title %></span>
|
||||
<% end %><%= ", " unless index == bubble.tags.size - 1 %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="tag-picker__button btn card__hide-on-index <%= bubble.creating? || bubble.drafted? ? "fill-highlight" : "fill-transparent" %>" data-action="click->dialog#open:stop">
|
||||
<%= icon_tag "tag" %>
|
||||
<span class="for-screen-reader">Add a tag</span>
|
||||
</button>
|
||||
|
||||
<%= turbo_frame_tag bubble, :tagging, src: new_bucket_bubble_tagging_toggle_path(bubble.bucket, bubble) %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="tag-picker__button btn card__hide-on-index <%= bubble.creating? || bubble.drafted? ? "fill-highlight" : "fill-transparent" %>" data-action="click->dialog#open:stop">
|
||||
<%= icon_tag "tag" %>
|
||||
<span class="for-screen-reader">Add a tag</span>
|
||||
</button>
|
||||
|
||||
<%= turbo_frame_tag bubble, :tagging, src: new_bubble_tagging_path(bubble) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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" %>
|
||||
<span class="for-screen-reader">Un-pin this card</span>
|
||||
<% end %>
|
||||
<%= button_to bubble_pin_path(@bubble), method: :delete, class: "btn btn--reversed" do %>
|
||||
<%= icon_tag "pinned" %>
|
||||
<span class="for-screen-reader">Un-pin this card</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= button_to bucket_bubble_pin_path(@bubble.bucket, @bubble), class: "btn" do %>
|
||||
<%= icon_tag "unpinned" %>
|
||||
<span class="for-screen-reader">Pin this card</span>
|
||||
<% end %>
|
||||
<%= button_to bubble_pin_path(@bubble), class: "btn" do %>
|
||||
<%= icon_tag "unpinned" %>
|
||||
<span class="for-screen-reader">Pin this card</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<%= turbo_frame_tag "pins" do %>
|
||||
<%= render collection: @pins, partial: "bubbles/pins/pin" %>
|
||||
<% end %>
|
||||
@@ -1,3 +0,0 @@
|
||||
<%= turbo_frame_tag "pins" do %>
|
||||
<%= render collection: @pins, partial: "bubbles/pins/pin" %>
|
||||
<% end %>
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="fill-selected position-sticky flex align-center gap-half justify-center border-block margin-block-end"
|
||||
style="view-transition-name: draft-banner; --bubble-color: <%= @bubble.color %>;">
|
||||
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 %>
|
||||
<span>Restore</span>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -22,21 +22,46 @@
|
||||
<nav class="align-start max-width">
|
||||
<%= link_to_home %>
|
||||
|
||||
<%= link_to "#", class: "btn flex-item-justify-start", data: { controller: "back-navigation hotkey", action: "keydown.esc@document->hotkey#click", back_navigation_fallback_destination_value: bubbles_path(bucket_id: @bubble.bucket) } do %>
|
||||
<%= icon_tag "arrow-left" %>
|
||||
<span class="for-screen-reader">Go Back</span>
|
||||
<% end %>
|
||||
<%= link_to_back fallback_path: bubbles_path(bucket_id: @bubble.bucket) %>
|
||||
|
||||
<div class="card__actions-container card__actions-container--top center border-radius flex-inline align-center justify-center gap" style="--bubble-color: <%= @bubble.color %>;">
|
||||
<span>Considering</span>
|
||||
<% if @bubble.doing? %>
|
||||
<%= form_with url: bubble_engagement_path(@bubble), method: :delete, data: { controller: "form" }, class: "flex" do %>
|
||||
<label class="switch flex-item-no-shrink">
|
||||
<input type="checkbox" class="switch__input" data-action="form#submit" checked>
|
||||
<span class="switch__btn round"></span>
|
||||
<span class="for-screen-reader">Switch to Considering</span>
|
||||
</label>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= form_with url: bubble_engagement_path(@bubble), method: :post, data: { controller: "form" }, class: "flex" do %>
|
||||
<label class="switch flex-item-no-shrink">
|
||||
<input type="checkbox" class="switch__input" data-action="form#submit">
|
||||
<span class="switch__btn round"></span>
|
||||
<span class="for-screen-reader">Switch to Doing</span>
|
||||
</label>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<span>Doing</span>
|
||||
</div>
|
||||
|
||||
<div class="btn btn--placeholder flex-item-justify-end"></div>
|
||||
<div class="btn btn--placeholder"></div>
|
||||
</nav>
|
||||
<% end %>
|
||||
|
||||
<div data-controller="beacon" data-beacon-url-value="<%= bucket_bubble_readings_url(@bubble.bucket, @bubble) %>">
|
||||
<div class="card__container position-relative <%= "card__container--pointing" unless @bubble.published? %>">
|
||||
<div data-controller="beacon" data-beacon-url-value="<%= bubble_reading_url(@bubble) %>">
|
||||
<div class="card__container position-relative <%= "card__container--pointing" unless @bubble.published? %>" style="--bubble-color: <%= @bubble.color %>;">
|
||||
<%= render "bubbles/cards/perma", bubble: @bubble %>
|
||||
|
||||
<div class="card__actions flex flex-column gap-half">
|
||||
<button class="car__star-button btn">
|
||||
<%= icon_tag "star" %>
|
||||
<span class="for-screen-reader">Star this card</span>
|
||||
<input type="checkbox" class="card__star-input">
|
||||
</button>
|
||||
|
||||
<%= 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? %>
|
||||
<div class="card__actions card__actions--right flex flex-column gap-half">
|
||||
<%= 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 %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -69,7 +94,7 @@
|
||||
<div class="comments pad-block">
|
||||
<div class="comment comment--new flex-inline align-start full-width">
|
||||
<figure class="comment__avatar flex-item-no-shrink" aria-hidden="true">
|
||||
<%= avatar_tag Current.user, hidden_for_screen_reader: true, loading: :lazy %>
|
||||
<%= avatar_tag Current.user, hidden_for_screen_reader: true %>
|
||||
</figure>
|
||||
|
||||
<div class="comment__content flex-inline flex-column full-width">
|
||||
@@ -86,7 +111,7 @@
|
||||
|
||||
<% if @bubble.creating? %>
|
||||
<div class="margin-block-start flex align-center gap">
|
||||
<%= 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" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<%= turbo_stream.replace([ @bubble, :tags ], partial: "bubbles/cards/perma/tags", locals: { bubble: @bubble }) %>
|
||||
@@ -0,0 +1,22 @@
|
||||
<%= turbo_frame_tag @bubble, :tagging do %>
|
||||
<dialog class="popup panel flex-column align-start justify-start fill-white shadow" data-dialog-target="dialog">
|
||||
<strong class="popup__title margin-block-half pad-inline-half">Tag this…</strong>
|
||||
|
||||
<%= 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" %>
|
||||
<span class="for-screen-reader">Add a tag</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<div class: "flex flex-column full-width popup__list">
|
||||
<% @tags.each do |tag| %>
|
||||
<div class="btn popup__item">
|
||||
<%= button_to tag.hashtag, bubble_taggings_path(@bubble, params: { tag_title: tag.title }), method: :post %>
|
||||
<%= icon_tag "check", size: 18, class: "checked flex-item-justify-end" if @bubble.tagged_with?(tag) %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</dialog>
|
||||
<% end %>
|
||||
@@ -1,7 +1,4 @@
|
||||
<div class="flex gap-half justify-space-between align-center" style="--btn-icon-size: 1rem">
|
||||
<%= tag.hashtag %>
|
||||
|
||||
<% if bubble.tagged_with?(tag) %>
|
||||
<%= icon_tag "check" %>
|
||||
<% end %>
|
||||
<%= icon_tag "check" if bubble.tagged_with?(tag) %>
|
||||
</div>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<%= avatar_tag watcher, loading: :lazy %>
|
||||
@@ -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" %>
|
||||
<span class="for-screen-reader">Stop watching</span>
|
||||
<% 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" %>
|
||||
<span class="for-screen-reader">Watch this</span>
|
||||
<% end %>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<li class="flex align-center gap-half margin-none unpad" data-value="<%= user.name.downcase %>">
|
||||
<figure class="avatar flex-item-no-shrink">
|
||||
<%= avatar_tag user, loading: :lazy %>
|
||||
<%= avatar_tag user %>
|
||||
</figure>
|
||||
|
||||
<div class="min-width">
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<nav>
|
||||
<%= link_to bubbles_path(bucket_ids: [ @bbucket ]), class: "btn flex-item-justify-start", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<%= icon_tag "arrow-left" %>
|
||||
<span class="for-screen-reader">Go back</span>
|
||||
<% end %>
|
||||
<%= link_to_back fallback_path: bubbles_path(bucket_ids: [ @bbucket ]) %>
|
||||
|
||||
<%= form_with model: @bucket, class: "flex-item-justify-end", method: :delete do |form| %>
|
||||
<%= form.button class: "btn btn--negative", data: { turbo_confirm: "Are you sure you want to delete this?" } do %>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<%= comment_tag comment do %>
|
||||
<figure class="comment__avatar flex-item-no-shrink" aria-hidden="true">
|
||||
<%= avatar_tag comment.creator, hidden_for_screen_reader: true, loading: :lazy %>
|
||||
<%= avatar_tag comment.creator, hidden_for_screen_reader: true %>
|
||||
</figure>
|
||||
|
||||
<%= render "comments/body", comment: comment %>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="comment comment--new flex-inline align-start full-width">
|
||||
<figure class="comment__avatar flex-item-no-shrink" aria-hidden="true">
|
||||
<%= avatar_tag Current.user, hidden_for_screen_reader: true, loading: :lazy %>
|
||||
<%= avatar_tag Current.user, hidden_for_screen_reader: true %>
|
||||
</figure>
|
||||
|
||||
<div class="comment__content flex-inline flex-column full-width">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
data-controller="reaction-delete" data-reaction-delete-perform-class="reaction--deleting"
|
||||
data-reaction-delete-reveal-class="expanded" data-reaction-delete-reacter-id-value="<%= reaction.reacter.id %>">
|
||||
<figure class="reaction__avatar margin-none flex-item-no-shrink">
|
||||
<%= avatar_tag reaction.reacter, aria: { label: "#{reaction.reacter.name} reacted #{reaction.content}" }, loading: :lazy %>
|
||||
<%= avatar_tag reaction.reacter, aria: { label: "#{reaction.reacter.name} reacted #{reaction.content}" } %>
|
||||
</figure>
|
||||
|
||||
<%= tag.span reaction.content, role: "button",
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
<% if Current.user %>
|
||||
<%= render "notifications/tray" %>
|
||||
<%= render "bubbles/pins/tray" %>
|
||||
<%= render "my/pins/tray" %>
|
||||
<%= turbo_frame_tag "terminal", src: terminal_path, data: { turbo_permanent: true } %>
|
||||
<% end %>
|
||||
</footer>
|
||||
|
||||
@@ -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 %>
|
||||
|
||||
@@ -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 %>
|
||||
|
||||
<div class="tray__actions border border-radius">
|
||||
<button class="txt-uppercase btn btn--reversed borderless fill-transparent txt-x-small translucent center" data-action="click->dialog#close">Pinned</button>
|
||||
@@ -10,4 +10,4 @@
|
||||
|
||||
<button class="tray__expander" data-action="dialog#toggle" aria-label="Expand pins stack">
|
||||
</button>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,3 @@
|
||||
<%= turbo_frame_tag "pins" do %>
|
||||
<%= render collection: @pins, partial: "my/pins/pin" %>
|
||||
<% end %>
|
||||
@@ -2,10 +2,7 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<nav class="align-start">
|
||||
<%= link_to notifications_path, class: "btn flex-item-justify-start", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<%= icon_tag "arrow-left" %>
|
||||
<span class="for-screen-reader">Go back</span>
|
||||
<% end %>
|
||||
<%= link_to_back fallback_path: notifications_path %>
|
||||
|
||||
<h1 class="txt-x-large"><%= @page_title %></h1>
|
||||
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<nav>
|
||||
<%= link_to user_path(@user), class: "btn flex-item-justify-start", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<%= icon_tag "arrow-left" %>
|
||||
<span class="for-screen-reader">Back</span>
|
||||
<% end %>
|
||||
<%= link_to_back fallback_path: user_path(@user) %>
|
||||
</nav>
|
||||
<% end %>
|
||||
|
||||
@@ -19,7 +16,7 @@
|
||||
<label class="btn avatar input--file txt-xx-large center fill-white">
|
||||
<%= image_tag user_avatar_path(@user), aria: { hidden: "true" }, class: "avatar", size: 128, data: { upload_preview_target: "image" } %>
|
||||
<%= form.file_field :avatar, id: "file", class: "input", accept: "image/*",
|
||||
data: { upload_preview_target: "input", action: "upload-preview#previewImage" } %>
|
||||
data: { upload_preview_target: "input", action: "upload-preview#previewImage" } %>
|
||||
<span class="for-screen-reader">Profile avatar for <%= @user.name %></span>
|
||||
</label>
|
||||
|
||||
@@ -54,7 +51,7 @@
|
||||
</div>
|
||||
<button type="submit" id="log_in" class="btn btn--reversed center">
|
||||
<%= icon_tag "check" %>
|
||||
<span class="for-screen-reader">Sign in</span>
|
||||
<span class="for-screen-reader">Sign in</span>
|
||||
</button>
|
||||
|
||||
<%= link_to "Cancel and go back", user_path(@user), data: { form_target: "cancel", turbo_frame: "_top" }, hidden: true %>
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<nav>
|
||||
<%= link_to "javascript:window.history.back()", class: "btn flex-item-justify-start", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<%= icon_tag "arrow-left" %>
|
||||
<span class="for-screen-reader">Back</span>
|
||||
<% end %>
|
||||
<%= link_to_back %>
|
||||
|
||||
<% if Current.user == @user %>
|
||||
<%= link_to edit_user_path(@user), class: "btn flex-item-justify-end", data: { turbo_frame: dom_id(@user) } do %>
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<nav>
|
||||
<%= link_to account_users_path, class: "btn flex-item-justify-start", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<%= icon_tag "arrow-left" %>
|
||||
<span class="for-screen-reader">Go back</span>
|
||||
<% end %>
|
||||
<%= link_to_back fallback_path: account_users_path %>
|
||||
|
||||
<span class="btn btn--placeholder"></span>
|
||||
|
||||
<h1 class="txt-large"><%= @page_title %></h1>
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<nav>
|
||||
<%= link_to workflows_path, class: "btn flex-item-justify-start", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<%= icon_tag "arrow-left" %>
|
||||
<span class="for-screen-reader">Go back</span>
|
||||
<% end %>
|
||||
<%= link_to_back fallback_path: workflows_path %>
|
||||
<h1 class="txt-large center"><%= @page_title %></h1>
|
||||
<span class="btn btn--placeholder flex-item-justify-end"></span>
|
||||
</nav>
|
||||
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env ruby
|
||||
require_relative "../config/boot"
|
||||
require "bundler/audit/cli"
|
||||
|
||||
ARGV.concat %w[ --config config/bundler-audit.yml ] if ARGV.empty? || ARGV.include?("check")
|
||||
Bundler::Audit::CLI.start
|
||||
@@ -1,12 +1,6 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require_relative "../config/boot"
|
||||
require "active_support/continuous_integration"
|
||||
|
||||
ActiveSupport::ContinuousIntegration.run do
|
||||
step "Security: Ruby", "bin/brakeman -q"
|
||||
step "Security: JS", "bin/importmap audit"
|
||||
step "Style: Ruby", "bin/rubocop"
|
||||
step "Test: Unit", "bin/rails test"
|
||||
step "Test: System", "bin/rails test:system"
|
||||
end
|
||||
CI = ActiveSupport::ContinuousIntegration
|
||||
require_relative "../config/ci.rb"
|
||||
|
||||
@@ -9,7 +9,7 @@ Bundler.require(*Rails.groups)
|
||||
module Fizzy
|
||||
class Application < Rails::Application
|
||||
# Initialize configuration defaults for originally generated Rails version.
|
||||
config.load_defaults 8.0
|
||||
config.load_defaults 8.1
|
||||
|
||||
# Include the `lib` directory in autoload paths. Use the `ignore:` option
|
||||
# to list subdirectories that don't contain `.rb` files or that shouldn't
|
||||
@@ -23,5 +23,8 @@ module Fizzy
|
||||
#
|
||||
# config.time_zone = "Central Time (US & Canada)"
|
||||
# config.eager_load_paths << Rails.root.join("extras")
|
||||
|
||||
# enable load_async
|
||||
config.active_record.async_query_executor = :global_thread_pool
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Run using bin/ci
|
||||
|
||||
CI.run do
|
||||
step "Setup", "bin/setup"
|
||||
|
||||
step "Style: Ruby", "bin/rubocop"
|
||||
|
||||
step "Security: Gem audit", "bin/bundler-audit check --update"
|
||||
step "Security: Importmap vulnerability audit", "bin/importmap audit"
|
||||
step "Security: Brakeman code analysis", "bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error"
|
||||
|
||||
step "Tests: Rails", "bin/rails test"
|
||||
|
||||
if success?
|
||||
step "Signoff: All systems go. Ready for merge and deploy.", "gh signoff"
|
||||
else
|
||||
failure "Signoff: CI failed. Do not merge or deploy.", "Fix the issues and try again."
|
||||
end
|
||||
end
|
||||
+28
-35
@@ -8,16 +8,24 @@ Rails.application.routes.draw do
|
||||
end
|
||||
end
|
||||
|
||||
resolve "Bubble" do |bubble, options|
|
||||
route_for :bucket_bubble, bubble.bucket, bubble, options
|
||||
resources :bubbles do
|
||||
scope module: :bubbles do
|
||||
resource :engagement
|
||||
resource :image
|
||||
resource :pin
|
||||
resource :pop
|
||||
resource :publish
|
||||
resource :reading
|
||||
resource :recover
|
||||
resource :watch
|
||||
|
||||
resources :assignments
|
||||
resources :boosts
|
||||
resources :stagings
|
||||
resources :taggings
|
||||
end
|
||||
end
|
||||
|
||||
resolve "Comment" do |comment, options|
|
||||
options[:anchor] = ActionView::RecordIdentifier.dom_id(comment)
|
||||
route_for :bucket_bubble, comment.bubble.bucket, comment.bubble, options
|
||||
end
|
||||
|
||||
resources :bubbles
|
||||
resources :notifications, only: :index
|
||||
namespace :notifications do
|
||||
resource :tray, only: :show
|
||||
@@ -40,30 +48,9 @@ Rails.application.routes.draw do
|
||||
end
|
||||
|
||||
resources :bubbles do
|
||||
resources :boosts
|
||||
resources :comments do
|
||||
resources :reactions, module: :comments
|
||||
end
|
||||
resource :readings, only: :create
|
||||
|
||||
scope module: :bubbles do
|
||||
resource :image
|
||||
resource :pop
|
||||
resource :engagement
|
||||
resource :publish
|
||||
resource :recover
|
||||
resources :stagings
|
||||
resource :watch
|
||||
resource :pin, only: [ :show, :create, :destroy ]
|
||||
end
|
||||
|
||||
namespace :assignments, as: :assignment do
|
||||
resources :toggles
|
||||
end
|
||||
|
||||
namespace :taggings, as: :tagging do
|
||||
resources :toggles
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -76,6 +63,10 @@ Rails.application.routes.draw do
|
||||
resource :first_run
|
||||
resources :qr_codes
|
||||
|
||||
namespace :my do
|
||||
resources :pins
|
||||
end
|
||||
|
||||
resource :session do
|
||||
scope module: "sessions" do
|
||||
resources :transfers, only: %i[ show update ]
|
||||
@@ -100,12 +91,14 @@ Rails.application.routes.draw do
|
||||
get "up", to: "rails/health#show", as: :rails_health_check
|
||||
get "manifest" => "rails/pwa#manifest", as: :pwa_manifest
|
||||
|
||||
namespace :bubbles do
|
||||
namespace :pins do
|
||||
resource :tray, only: :show
|
||||
end
|
||||
resources :pins, only: :index
|
||||
resource :terminal, only: [ :show, :edit ]
|
||||
|
||||
resolve "Bubble" do |bubble, options|
|
||||
route_for :bucket_bubble, bubble.bucket, bubble, options
|
||||
end
|
||||
|
||||
resource :terminal, only: [ :show, :edit ]
|
||||
resolve "Comment" do |comment, options|
|
||||
options[:anchor] = ActionView::RecordIdentifier.dom_id(comment)
|
||||
route_for :bucket_bubble, comment.bubble.bucket, comment.bubble, options
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class MakeTagTitlesUniquePerAccount < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
add_index :tags, [ :account_id, :title ], unique: true
|
||||
end
|
||||
end
|
||||
Generated
+3
-2
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.1].define(version: 2025_04_04_133310) do
|
||||
ActiveRecord::Schema[8.1].define(version: 2025_04_07_093421) do
|
||||
create_table "accesses", force: :cascade do |t|
|
||||
t.integer "bucket_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
@@ -113,7 +113,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_04_04_133310) do
|
||||
t.datetime "created_at", null: false
|
||||
t.integer "creator_id", null: false
|
||||
t.date "due_on"
|
||||
t.datetime "last_active_at"
|
||||
t.datetime "last_active_at", null: false
|
||||
t.integer "stage_id"
|
||||
t.text "status", default: "creating", null: false
|
||||
t.string "title"
|
||||
@@ -287,6 +287,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_04_04_133310) do
|
||||
t.datetime "created_at", null: false
|
||||
t.string "title"
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["account_id", "title"], name: "index_tags_on_account_id_and_title", unique: true
|
||||
t.index ["account_id"], name: "index_tags_on_account_id"
|
||||
end
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user