Merge pull request #386 from basecamp/rename-bubbles-2
Rename bubbles (take 2)
This commit is contained in:
|
Before Width: | Height: | Size: 802 B After Width: | Height: | Size: 802 B |
|
Before Width: | Height: | Size: 547 B After Width: | Height: | Size: 547 B |
|
Before Width: | Height: | Size: 894 B After Width: | Height: | Size: 894 B |
@@ -38,7 +38,7 @@
|
||||
}
|
||||
|
||||
@keyframes wobble {
|
||||
0% { transform: rotate(calc(var(--bubble-rotate) + 90deg)); }
|
||||
0% { transform: rotate(calc(var(--card-rotate) + 90deg)); }
|
||||
15% { border-radius: 66% 34% 72% 28% / 39% 63% 37% 61%; }
|
||||
25% { border-radius: 55% 47% 62% 40% / 58% 50% 52% 44%; }
|
||||
33% { border-radius: 46% 54% 61% 39% / 50% 51% 49% 50%; }
|
||||
@@ -47,7 +47,7 @@
|
||||
}
|
||||
|
||||
@keyframes wobble-alt {
|
||||
0% { transform: rotate(calc(var(--bubble-rotate) + -90deg)); }
|
||||
0% { transform: rotate(calc(var(--card-rotate) + -90deg)); }
|
||||
12% { border-radius: 66% 34% 72% 28% / 39% 63% 37% 61%; }
|
||||
25% { border-radius: 53% 45% 60% 38% / 56% 48% 50% 42%; }
|
||||
33% { border-radius: 55% 47% 62% 40% / 58% 50% 52% 44%; }
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.bubble {
|
||||
.something-to-rename {
|
||||
--bubble-background: radial-gradient(circle at center, color(from var(--bubble-color) srgb r g b / 0.25), color(from var(--bubble-color) srgb r g b / 0.05));
|
||||
--bubble-border-width: 0.2rem;
|
||||
--hover-size: 0;
|
||||
@@ -110,15 +110,15 @@
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.bubbles .bubble:not(.popped):hover & {
|
||||
.bubbles .something-to-rename:not(.popped):hover & {
|
||||
transform: translate(0, -1rem);
|
||||
}
|
||||
|
||||
.bubbles .bubble:not(.popped):hover & + & {
|
||||
.bubbles .something-to-rename:not(.popped):hover & + & {
|
||||
transform: translate(1rem, -1rem);
|
||||
}
|
||||
|
||||
.bubbles .bubble:not(.popped):hover & + & + & {
|
||||
.bubbles .something-to-rename:not(.popped):hover & + & + & {
|
||||
transform: translate(1.5rem, -0.5rem);;
|
||||
}
|
||||
}
|
||||
@@ -176,7 +176,7 @@
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.bubbles .bubble:not(.popped):hover & {
|
||||
.bubbles .something-to-rename:not(.popped):hover & {
|
||||
transform: translate(-0.5rem, -2rem);
|
||||
}
|
||||
}
|
||||
@@ -195,22 +195,22 @@
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.bubbles .bubble:not(.popped):hover & {
|
||||
.bubbles .something-to-rename:not(.popped):hover & {
|
||||
transform: rotate(calc(var(--rotation) + var(--bubble-shift) * 1)) translate(0rem, 0.5rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.bubbles .bubble:not(.popped):hover & {
|
||||
.bubbles .something-to-rename:not(.popped):hover & {
|
||||
transform: translate(1rem, 1rem);
|
||||
}
|
||||
|
||||
.bubbles .bubble:not(.popped):hover & + & {
|
||||
.bubbles .something-to-rename:not(.popped):hover & + & {
|
||||
transform: translate(0, -1rem);
|
||||
}
|
||||
|
||||
.bubbles .bubble:not(.popped):hover & + & + & {
|
||||
.bubbles .something-to-rename:not(.popped):hover & + & + & {
|
||||
transform: translate(-1rem, -1rem);
|
||||
}
|
||||
}
|
||||
@@ -227,7 +227,7 @@
|
||||
transition: 0.2s ease;
|
||||
|
||||
@media (hover: hover) {
|
||||
.bubbles .bubble:not(.popped):hover & {
|
||||
.bubbles .something-to-rename:not(.popped):hover & {
|
||||
transform: rotate(calc(var(--bubble-rotate) + var(--bubble-shift))) scale(0.9);
|
||||
}
|
||||
}
|
||||
@@ -273,7 +273,7 @@
|
||||
margin-inline: var(--tray-size);
|
||||
}
|
||||
|
||||
.bubble {
|
||||
.something-to-rename {
|
||||
order: 0;
|
||||
}
|
||||
|
||||
@@ -379,22 +379,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
--input-color: var(--bubble-color);
|
||||
--input-border-radius: 0;
|
||||
/*textarea {*/
|
||||
/* --input-color: var(--bubble-color);*/
|
||||
/* --input-border-radius: 0;*/
|
||||
|
||||
font-size: 9cqi;
|
||||
font-weight: 800;
|
||||
line-height: 1.2;
|
||||
/* font-size: 9cqi;*/
|
||||
/* font-weight: 800;*/
|
||||
/* line-height: 1.2;*/
|
||||
|
||||
&::selection {
|
||||
background-color: var(--bubble-color);
|
||||
color: var(--color-ink-reversed);
|
||||
}
|
||||
}
|
||||
/* &::selection {*/
|
||||
/* background-color: var(--bubble-color);*/
|
||||
/* color: var(--color-ink-reversed);*/
|
||||
/* }*/
|
||||
/*}*/
|
||||
|
||||
@media (hover: hover) {
|
||||
.bubble:has(.bubble__image img:not([src=""])):hover & {
|
||||
.something-to-rename:has(.bubble__image img:not([src=""])):hover & {
|
||||
color: var(--color-ink-reversed);
|
||||
text-shadow: 0 0 0.2em var(--bubble-color), 0 0 0.2em var(--bubble-color), 0 0 0.2em var(--bubble-color);
|
||||
position: relative;
|
||||
@@ -422,7 +422,7 @@
|
||||
--bubble-border-color: color-mix(in srgb, var(--bubble-color) 30%, var(--color-bg));
|
||||
}
|
||||
|
||||
.bubble & {
|
||||
.something-to-rename & {
|
||||
--bubble-size: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,8 +78,8 @@
|
||||
--border-size: 0;
|
||||
|
||||
aspect-ratio: 2/0.95;
|
||||
background-color: color-mix(in srgb, var(--bubble-color) 4%, var(--color-bg));
|
||||
color: color-mix(in srgb, var(--bubble-color) 40%, var(--color-ink));
|
||||
background-color: color-mix(in srgb, var(--card-color) 4%, var(--color-bg));
|
||||
color: color-mix(in srgb, var(--card-color) 40%, var(--color-ink));
|
||||
max-inline-size: 75ch;
|
||||
z-index: 1;
|
||||
|
||||
@@ -113,9 +113,9 @@
|
||||
|
||||
.card__actions-container {
|
||||
--border-radius: 1em;
|
||||
|
||||
|
||||
background-color: var(--color-bg);
|
||||
color: color-mix(in srgb, var(--bubble-color) 40%, var(--color-ink));
|
||||
color: color-mix(in srgb, var(--card-color) 40%, var(--color-ink));
|
||||
margin-block: calc(var(--block-space) * -1.66) calc(var(--block-space) * 1.66);
|
||||
padding: calc(var(--block-space) * 0.66) calc(var(--inline-space) * 1.5);
|
||||
position: relative;
|
||||
@@ -126,18 +126,18 @@
|
||||
}
|
||||
|
||||
.btn:not(.popup__item, .btn--plain) {
|
||||
--btn-background: var(--bubble-color);
|
||||
--btn-background: var(--card-color);
|
||||
--btn-color: var(--color-ink-reversed);
|
||||
}
|
||||
|
||||
.btn--plain {
|
||||
--btn-color: var(--bubble-color);
|
||||
--btn-color: var(--card-color);
|
||||
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.bubble__pop-message {
|
||||
color: var(--bubble-color);
|
||||
.card__closure-message {
|
||||
color: var(--card-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
}
|
||||
|
||||
.switch__input:checked + .switch__btn {
|
||||
background-color: var(--bubble-color) !important;
|
||||
background-color: var(--card-color) !important;
|
||||
}
|
||||
|
||||
strong {
|
||||
@@ -203,14 +203,14 @@
|
||||
padding-block: var(--block-space);
|
||||
}
|
||||
|
||||
.card__bucket {
|
||||
background-color: var(--bubble-color);
|
||||
.card__collection {
|
||||
background-color: var(--card-color);
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.5ch;
|
||||
padding: var(--block-space-half) var(--inline-space-double);
|
||||
}
|
||||
|
||||
.card-bucket__filter {
|
||||
.card-collection__filter {
|
||||
> button {
|
||||
--hover-size: 0;
|
||||
}
|
||||
@@ -244,7 +244,7 @@
|
||||
--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: color-mix(in srgb, var(--bubble-color) 33%, var(--color-bg));
|
||||
background-color: color-mix(in srgb, var(--card-color) 33%, var(--color-bg));
|
||||
border-radius: 0.2em;
|
||||
display: inline-flex;
|
||||
margin-block: calc(var(--block-space) * -1) calc(var(--block-space) * -0.33);
|
||||
@@ -389,7 +389,7 @@
|
||||
}
|
||||
|
||||
.card__meta {
|
||||
--border-color: var(--bubble-color);
|
||||
--border-color: var(--card-color);
|
||||
--border-size: 1px;
|
||||
--row-gap: 0.25em;
|
||||
|
||||
@@ -405,7 +405,7 @@
|
||||
}
|
||||
|
||||
.card__move-button {
|
||||
--btn-background: color-mix(in srgb, var(--bubble-color) 40%, var(--color-ink));
|
||||
--btn-background: color-mix(in srgb, var(--card-color) 40%, var(--color-ink));
|
||||
|
||||
margin-block-start: calc(var(--block-space) * 1.2);
|
||||
margin-inline-end: calc(var(--inline-space) * -4);
|
||||
@@ -417,12 +417,12 @@
|
||||
}
|
||||
|
||||
.card__tag {
|
||||
color: var(--bubble-color);
|
||||
color: var(--card-color);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.card__tags {
|
||||
color: var(--bubble-color);
|
||||
color: var(--card-color);
|
||||
}
|
||||
|
||||
.card__title {
|
||||
@@ -498,10 +498,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.card__popped {
|
||||
.card__closed {
|
||||
align-items: center;
|
||||
aspect-ratio: 7/4;
|
||||
border: var(--block-space-half) solid var(--bubble-color);
|
||||
border: var(--block-space-half) solid var(--card-color);
|
||||
position: absolute;
|
||||
inset: auto var(--block-space) var(--block-space-double) auto;
|
||||
justify-content: space-between;
|
||||
@@ -510,18 +510,18 @@
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.card__popped-by {
|
||||
.card__closed-by {
|
||||
border-block-end: 1px dashed currentcolor;
|
||||
}
|
||||
|
||||
.card__popped-title {
|
||||
.card__closed-title {
|
||||
color: var(--color-ink-reversed);
|
||||
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(--card-color),
|
||||
2px -2px 0 var(--card-color),
|
||||
-2px 2px 0 var(--card-color),
|
||||
2px 2px 0 var(--card-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@layer components {
|
||||
.bucket {
|
||||
.collection {
|
||||
inline-size: min(35ch, 40%);
|
||||
line-height: 1.2;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.buckets {
|
||||
.collections {
|
||||
--gap: 2cqi;
|
||||
--hover-size: 0;
|
||||
--column-gap: var(--gap);
|
||||
@@ -20,12 +20,12 @@
|
||||
padding: var(--gap);
|
||||
}
|
||||
|
||||
.bucket__button {
|
||||
.collection__button {
|
||||
position: absolute;
|
||||
inset: calc((var(--btn-size) * -1) - 2cqi) 1cqi auto auto;
|
||||
}
|
||||
|
||||
.bucket__bubbles {
|
||||
.collection__cards {
|
||||
--border-radius: 1.5em;
|
||||
|
||||
margin-block: 0;
|
||||
@@ -33,7 +33,7 @@
|
||||
padding: 1cqi;
|
||||
}
|
||||
|
||||
.bubbles__container {
|
||||
.cards__container {
|
||||
aspect-ratio: 18 / 9;
|
||||
container-type: inline-size;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
.color-picker {
|
||||
--panel-border-color: var(--bubble-color);
|
||||
--panel-border-color: var(--card-color);
|
||||
--panel-border-radius: 2em;
|
||||
--panel-border-size: 2px;
|
||||
--panel-padding: 0.5em 0.7em;
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
}
|
||||
|
||||
.event--related {
|
||||
--hover-color: var(--bubble-color);
|
||||
--hover-color: var(--card-color);
|
||||
--hover-size: 0.15rem;
|
||||
|
||||
box-shadow: 0 0 0 var(--hover-size) var(--hover-color);
|
||||
@@ -138,7 +138,7 @@
|
||||
|
||||
.event__icon {
|
||||
aspect-ratio: 1;
|
||||
background-color: var(--bubble-color);
|
||||
background-color: var(--card-color);
|
||||
block-size: 1.7em;
|
||||
border-radius: 1.7em;
|
||||
inline-size: 1.7em;
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
.icon--bolt { --svg: url("bolt.svg "); }
|
||||
.icon--bookmark-outline { --svg: url("bookmark-outline.svg "); }
|
||||
.icon--bookmark { --svg: url("bookmark.svg "); }
|
||||
.icon--bubble-add { --svg: url("bubble-add.svg "); }
|
||||
.icon--bubbles { --svg: url("bubbles.svg "); }
|
||||
.icon--card-add { --svg: url("card-add.svg "); }
|
||||
.icon--cards { --svg: url("cards.svg "); }
|
||||
.icon--calendar-add { --svg: url("calendar-add.svg "); }
|
||||
.icon--calendar { --svg: url("calendar.svg "); }
|
||||
.icon--camera { --svg: url("camera.svg "); }
|
||||
@@ -65,7 +65,7 @@
|
||||
.icon--picture-double { --svg: url("picture-double.svg "); }
|
||||
.icon--picture-remove { --svg: url("picture-remove.svg "); }
|
||||
.icon--pinned { --svg: url("pinned.svg "); }
|
||||
.icon--pop { --svg: url("pop.svg "); }
|
||||
.icon--closure { --svg: url("closure.svg "); }
|
||||
.icon--qr-code { --svg: url("qr-code.svg "); }
|
||||
.icon--reaction { --svg: url("reaction.svg "); }
|
||||
.icon--refresh { --svg: url("refresh.svg "); }
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.pin__bucket {
|
||||
background-color: var(--bubble-color);
|
||||
.pin__collection {
|
||||
background-color: var(--card-color);
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.3ch;
|
||||
padding: 0.3em 0.6em 0.3em 1.5em;
|
||||
@@ -45,7 +45,7 @@
|
||||
}
|
||||
|
||||
.pin__tags {
|
||||
color: var(--bubble-color);
|
||||
color: var(--card-color);
|
||||
}
|
||||
|
||||
.pin__title {
|
||||
|
||||
@@ -11,35 +11,35 @@
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
background-color: color(from var(--bubble-color) srgb r g b / 0.15);
|
||||
background-color: color(from var(--card-color) srgb r g b / 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.bubbles-list & {
|
||||
.cards-list & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.workflow-stage--current {
|
||||
--btn-background: var(--bubble-color);
|
||||
--btn-background: var(--card-color);
|
||||
|
||||
color: var(--color-ink-reversed);
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
background-color: color(from var(--bubble-color) srgb r g b / 0.65);
|
||||
background-color: color(from var(--card-color) srgb r g b / 0.65);
|
||||
}
|
||||
}
|
||||
|
||||
.ticket & {
|
||||
--btn-background: var(--color-ink);
|
||||
|
||||
background: var(--bubble-color);
|
||||
background: var(--card-color);
|
||||
border-radius: 0.2em;
|
||||
color: var(--color-ink-reversed);
|
||||
}
|
||||
|
||||
.bubbles-list & {
|
||||
.cards-list & {
|
||||
display: flex;
|
||||
background: transparent;
|
||||
border-radius: 0.2em;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
class Bubbles::AssignmentsController < ApplicationController
|
||||
include BubbleScoped
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
def create
|
||||
@bubble.toggle_assignment @bucket.users.active.find(params[:assignee_id])
|
||||
end
|
||||
end
|
||||
@@ -1,12 +0,0 @@
|
||||
class Bubbles::BoostsController < ApplicationController
|
||||
include BubbleScoped
|
||||
|
||||
def create
|
||||
count = if params[:boost_count].to_i == @bubble.boosts_count
|
||||
@bubble.boosts_count + 1
|
||||
else
|
||||
params[:boost_count].to_i
|
||||
end
|
||||
@bubble.boost!(count)
|
||||
end
|
||||
end
|
||||
@@ -1,13 +0,0 @@
|
||||
class Bubbles::EngagementsController < ApplicationController
|
||||
include BubbleScoped
|
||||
|
||||
def create
|
||||
@bubble.engage
|
||||
redirect_to @bubble
|
||||
end
|
||||
|
||||
def destroy
|
||||
@bubble.reconsider
|
||||
redirect_to @bubble
|
||||
end
|
||||
end
|
||||
@@ -1,8 +0,0 @@
|
||||
class Bubbles::ImagesController < ApplicationController
|
||||
include BubbleScoped
|
||||
|
||||
def destroy
|
||||
@bubble.image.purge_later
|
||||
redirect_to @bubble
|
||||
end
|
||||
end
|
||||
@@ -1,13 +0,0 @@
|
||||
class Bubbles::PopsController < ApplicationController
|
||||
include BubbleScoped
|
||||
|
||||
def create
|
||||
@bubble.pop!(user: Current.user, reason: params[:reason])
|
||||
redirect_to @bubble
|
||||
end
|
||||
|
||||
def destroy
|
||||
@bubble.unpop
|
||||
redirect_to @bubble
|
||||
end
|
||||
end
|
||||
@@ -1,8 +0,0 @@
|
||||
class Bubbles::PublishesController < ApplicationController
|
||||
include BubbleScoped
|
||||
|
||||
def create
|
||||
@bubble.publish
|
||||
redirect_to @bubble
|
||||
end
|
||||
end
|
||||
@@ -1,13 +0,0 @@
|
||||
class Bubbles::ReadingsController < ApplicationController
|
||||
include BubbleScoped
|
||||
|
||||
def create
|
||||
mark_bubble_notifications_read
|
||||
@notifications = Current.user.notifications.unread.ordered.limit(20)
|
||||
end
|
||||
|
||||
private
|
||||
def mark_bubble_notifications_read
|
||||
Current.user.notifications.unread.where(bubble: @bubble).read_all
|
||||
end
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
class Bubbles::RecoversController < ApplicationController
|
||||
include BubbleScoped
|
||||
|
||||
def create
|
||||
redirect_to @bubble.recover_abandoned_creation
|
||||
end
|
||||
end
|
||||
@@ -1,11 +0,0 @@
|
||||
class Bubbles::StagingsController < ApplicationController
|
||||
include BubbleScoped
|
||||
|
||||
def create
|
||||
if params[:stage_id].present?
|
||||
@bubble.toggle_stage Current.account.stages.find(params[:stage_id])
|
||||
else
|
||||
@bubble.update!(stage: nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,13 +0,0 @@
|
||||
class Bubbles::WatchesController < ApplicationController
|
||||
include BubbleScoped
|
||||
|
||||
def create
|
||||
@bubble.watch_by Current.user
|
||||
redirect_to bubble_watch_path(@bubble)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@bubble.unwatch_by Current.user
|
||||
redirect_to bubble_watch_path(@bubble)
|
||||
end
|
||||
end
|
||||
@@ -1,71 +0,0 @@
|
||||
class BubblesController < ApplicationController
|
||||
include BucketScoped
|
||||
|
||||
skip_before_action :set_bucket, only: :index
|
||||
|
||||
before_action :set_filter, only: :index
|
||||
before_action :set_bubble, only: %i[ show edit update destroy ]
|
||||
before_action :handle_display_count, only: :index
|
||||
|
||||
DISPLAY_COUNT_OPTIONS = [ 6, 12, 18, 24 ].freeze
|
||||
DEFAULT_DISPLAY_COUNT = 6
|
||||
RECENTLY_POPPED_LIMIT = 100
|
||||
|
||||
def index
|
||||
@considering_bubbles = @filter.bubbles.considering.load_async
|
||||
@doing_bubbles = @filter.bubbles.doing.load_async
|
||||
@popped_bubbles = @filter.with(indexed_by: "popped").bubbles.recently_popped_first.limit(RECENTLY_POPPED_LIMIT).load_async
|
||||
end
|
||||
|
||||
def create
|
||||
redirect_to @bucket.bubbles.create!
|
||||
end
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def destroy
|
||||
@bubble.destroy!
|
||||
redirect_to bubbles_path(bucket_ids: [ @bubble.bucket ]), notice: deleted_notice
|
||||
end
|
||||
|
||||
def update
|
||||
@bubble.update! bubble_params
|
||||
redirect_to @bubble
|
||||
end
|
||||
|
||||
private
|
||||
DEFAULT_PARAMS = { indexed_by: "newest" }
|
||||
|
||||
def set_filter
|
||||
@filter = Current.user.filters.from_params params.reverse_merge(**DEFAULT_PARAMS).permit(*Filter::PERMITTED_PARAMS)
|
||||
end
|
||||
|
||||
def set_bubble
|
||||
@bubble = @bucket.bubbles.find params[:id]
|
||||
end
|
||||
|
||||
def bubble_params
|
||||
params.expect(bubble: [ :status, :title, :color, :due_on, :image, :draft_comment, tag_ids: [] ])
|
||||
end
|
||||
|
||||
def deleted_notice
|
||||
"Bubble deleted" unless @bubble.creating?
|
||||
end
|
||||
|
||||
def handle_display_count
|
||||
if params[:set_display_count].present?
|
||||
cookies[:display_count] = params[:set_display_count]
|
||||
redirect_to bubbles_path(
|
||||
params.permit(*Filter::PERMITTED_PARAMS, :bucket_ids).except(:set_display_count)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def display_count
|
||||
(cookies[:display_count] || DEFAULT_DISPLAY_COUNT).to_i
|
||||
end
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
class Buckets::InvolvementsController < ApplicationController
|
||||
include BucketScoped
|
||||
|
||||
def update
|
||||
@bucket.access_for(Current.user).update!(involvement: params[:involvement])
|
||||
end
|
||||
end
|
||||
@@ -1,16 +0,0 @@
|
||||
class Buckets::WorkflowsController < ApplicationController
|
||||
include BucketScoped
|
||||
|
||||
before_action :set_workflow
|
||||
|
||||
def update
|
||||
@bucket.update! workflow: @workflow
|
||||
|
||||
redirect_to bubbles_path(bucket_ids: [ @bucket ])
|
||||
end
|
||||
|
||||
private
|
||||
def set_workflow
|
||||
@workflow = Current.account.workflows.find(params.expect(bucket: [ :workflow_id ]).require(:workflow_id))
|
||||
end
|
||||
end
|
||||
@@ -1,50 +0,0 @@
|
||||
class BucketsController < ApplicationController
|
||||
before_action :set_bucket, except: %i[ new create ]
|
||||
|
||||
def new
|
||||
@bucket = Current.account.buckets.build
|
||||
end
|
||||
|
||||
def create
|
||||
@bucket = Current.account.buckets.create! bucket_params
|
||||
redirect_to bubbles_path(bucket_ids: [ @bucket ])
|
||||
end
|
||||
|
||||
def edit
|
||||
selected_user_ids = @bucket.users.pluck :id
|
||||
@selected_users, @unselected_users = User.active.alphabetically.partition { |user| selected_user_ids.include? user.id }
|
||||
end
|
||||
|
||||
def update
|
||||
@bucket.update! bucket_params
|
||||
@bucket.accesses.revise granted: grantees, revoked: revokees
|
||||
|
||||
redirect_to bubbles_path(bucket_ids: [ @bucket ])
|
||||
end
|
||||
|
||||
def destroy
|
||||
@bucket.destroy
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
private
|
||||
def set_bucket
|
||||
@bucket = Current.user.buckets.find params[:id]
|
||||
end
|
||||
|
||||
def bucket_params
|
||||
params.expect(bucket: [ :name, :all_access ]).with_defaults(all_access: true)
|
||||
end
|
||||
|
||||
def grantees
|
||||
Current.account.users.active.where id: grantee_ids
|
||||
end
|
||||
|
||||
def revokees
|
||||
@bucket.users.where.not id: grantee_ids
|
||||
end
|
||||
|
||||
def grantee_ids
|
||||
params.fetch :user_ids, []
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
class Cards::AssignmentsController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
def create
|
||||
@card.toggle_assignment @collection.users.active.find(params[:assignee_id])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
class Cards::BoostsController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
def create
|
||||
count = if params[:boost_count].to_i == @card.boosts_count
|
||||
@card.boosts_count + 1
|
||||
else
|
||||
params[:boost_count].to_i
|
||||
end
|
||||
@card.boost!(count)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
class Cards::ClosuresController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
def create
|
||||
@card.close(user: Current.user, reason: params[:reason])
|
||||
redirect_to @card
|
||||
end
|
||||
|
||||
def destroy
|
||||
@card.reopen
|
||||
redirect_to @card
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
class Cards::EngagementsController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
def create
|
||||
@card.engage
|
||||
redirect_to @card
|
||||
end
|
||||
|
||||
def destroy
|
||||
@card.reconsider
|
||||
redirect_to @card
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
class Cards::ImagesController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
def destroy
|
||||
@card.image.purge_later
|
||||
redirect_to @card
|
||||
end
|
||||
end
|
||||
+6
-6
@@ -1,21 +1,21 @@
|
||||
class Bubbles::PinsController < ApplicationController
|
||||
include BubbleScoped
|
||||
class Cards::PinsController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
def create
|
||||
pin = @bubble.pin_by Current.user
|
||||
pin = @card.pin_by Current.user
|
||||
|
||||
broadcast_my_new pin
|
||||
redirect_to bubble_pin_path(@bubble)
|
||||
redirect_to card_pin_path(@card)
|
||||
end
|
||||
|
||||
def destroy
|
||||
pin = @bubble.unpin_by Current.user
|
||||
pin = @card.unpin_by Current.user
|
||||
|
||||
broadcast_my_removed pin
|
||||
redirect_to bubble_pin_path(@bubble)
|
||||
redirect_to card_pin_path(@card)
|
||||
end
|
||||
|
||||
private
|
||||
@@ -0,0 +1,8 @@
|
||||
class Cards::PublishesController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
def create
|
||||
@card.publish
|
||||
redirect_to @card
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
class Cards::ReadingsController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
def create
|
||||
mark_card_notifications_read
|
||||
@notifications = Current.user.notifications.unread.ordered.limit(20)
|
||||
end
|
||||
|
||||
private
|
||||
def mark_card_notifications_read
|
||||
Current.user.notifications.unread.where(card: @card).read_all
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
class Cards::RecoversController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
def create
|
||||
redirect_to @card.recover_abandoned_creation
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
class Cards::StagingsController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
def create
|
||||
if params[:stage_id].present?
|
||||
@card.toggle_stage Current.account.stages.find(params[:stage_id])
|
||||
else
|
||||
@card.update!(stage: nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
class Bubbles::TaggingsController < ApplicationController
|
||||
include BubbleScoped
|
||||
class Cards::TaggingsController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
def new
|
||||
@tags = Current.account.tags.alphabetically
|
||||
end
|
||||
|
||||
def create
|
||||
@bubble.toggle_tag_with sanitized_tag_title_param
|
||||
@card.toggle_tag_with sanitized_tag_title_param
|
||||
end
|
||||
|
||||
private
|
||||
@@ -0,0 +1,13 @@
|
||||
class Cards::WatchesController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
def create
|
||||
@card.watch_by Current.user
|
||||
redirect_to card_watch_path(@card)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@card.unwatch_by Current.user
|
||||
redirect_to card_watch_path(@card)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,71 @@
|
||||
class CardsController < ApplicationController
|
||||
include CollectionScoped
|
||||
|
||||
skip_before_action :set_collection, only: :index
|
||||
|
||||
before_action :set_filter, only: :index
|
||||
before_action :set_card, only: %i[ show edit update destroy ]
|
||||
before_action :handle_display_count, only: :index
|
||||
|
||||
DISPLAY_COUNT_OPTIONS = [ 6, 12, 18, 24 ].freeze
|
||||
DEFAULT_DISPLAY_COUNT = 6
|
||||
RECENTLY_CLOSED_LIMIT = 100
|
||||
|
||||
def index
|
||||
@considering_cards = @filter.cards.considering.load_async
|
||||
@doing_cards = @filter.cards.doing.load_async
|
||||
@closed_cards = @filter.with(indexed_by: "closed").cards.recently_closed_first.limit(RECENTLY_CLOSED_LIMIT).load_async
|
||||
end
|
||||
|
||||
def create
|
||||
redirect_to @collection.cards.create!
|
||||
end
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def destroy
|
||||
@card.destroy!
|
||||
redirect_to cards_path(collection_ids: [ @card.collection ]), notice: deleted_notice
|
||||
end
|
||||
|
||||
def update
|
||||
@card.update! card_params
|
||||
redirect_to @card
|
||||
end
|
||||
|
||||
private
|
||||
DEFAULT_PARAMS = { indexed_by: "newest" }
|
||||
|
||||
def set_filter
|
||||
@filter = Current.user.filters.from_params params.reverse_merge(**DEFAULT_PARAMS).permit(*Filter::PERMITTED_PARAMS)
|
||||
end
|
||||
|
||||
def set_card
|
||||
@card = @collection.cards.find params[:id]
|
||||
end
|
||||
|
||||
def card_params
|
||||
params.expect(card: [ :status, :title, :color, :due_on, :image, :draft_comment, tag_ids: [] ])
|
||||
end
|
||||
|
||||
def deleted_notice
|
||||
"Card deleted" unless @card.creating?
|
||||
end
|
||||
|
||||
def handle_display_count
|
||||
if params[:set_display_count].present?
|
||||
cookies[:display_count] = params[:set_display_count]
|
||||
redirect_to cards_path(
|
||||
params.permit(*Filter::PERMITTED_PARAMS, :collection_ids).except(:set_display_count)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def display_count
|
||||
(cookies[:display_count] || DEFAULT_DISPLAY_COUNT).to_i
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
class Collections::InvolvementsController < ApplicationController
|
||||
include CollectionScoped
|
||||
|
||||
def update
|
||||
@collection.access_for(Current.user).update!(involvement: params[:involvement])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
class Collections::WorkflowsController < ApplicationController
|
||||
include CollectionScoped
|
||||
|
||||
before_action :set_workflow
|
||||
|
||||
def update
|
||||
@collection.update! workflow: @workflow
|
||||
|
||||
redirect_to cards_path(collection_ids: [ @collection ])
|
||||
end
|
||||
|
||||
private
|
||||
def set_workflow
|
||||
@workflow = Current.account.workflows.find(params.expect(collection: [ :workflow_id ]).require(:workflow_id))
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
class CollectionsController < ApplicationController
|
||||
before_action :set_collection, except: %i[ new create ]
|
||||
|
||||
def new
|
||||
@collection = Current.account.collections.build
|
||||
end
|
||||
|
||||
def create
|
||||
@collection = Current.account.collections.create! collection_params
|
||||
redirect_to cards_path(collection_ids: [ @collection ])
|
||||
end
|
||||
|
||||
def edit
|
||||
selected_user_ids = @collection.users.pluck :id
|
||||
@selected_users, @unselected_users = User.active.alphabetically.partition { |user| selected_user_ids.include? user.id }
|
||||
end
|
||||
|
||||
def update
|
||||
@collection.update! collection_params
|
||||
@collection.accesses.revise granted: grantees, revoked: revokees
|
||||
|
||||
redirect_to cards_path(collection_ids: [ @collection ])
|
||||
end
|
||||
|
||||
def destroy
|
||||
@collection.destroy
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
private
|
||||
def set_collection
|
||||
@collection = Current.user.collections.find params[:id]
|
||||
end
|
||||
|
||||
def collection_params
|
||||
params.expect(collection: [ :name, :all_access ]).with_defaults(all_access: true)
|
||||
end
|
||||
|
||||
def grantees
|
||||
Current.account.users.active.where id: grantee_ids
|
||||
end
|
||||
|
||||
def revokees
|
||||
@collection.users.where.not id: grantee_ids
|
||||
end
|
||||
|
||||
def grantee_ids
|
||||
params.fetch :user_ids, []
|
||||
end
|
||||
end
|
||||
@@ -12,7 +12,7 @@ class Comments::ReactionsController < ApplicationController
|
||||
@reaction = @comment.reactions.create!(reaction_params)
|
||||
|
||||
broadcast_create
|
||||
redirect_to bucket_bubble_comment_reactions_url(@comment.bubble.bucket, @comment.bubble, @comment)
|
||||
redirect_to collection_card_comment_reactions_url(@comment.card.collection, @comment.card, @comment)
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
class CommentsController < ApplicationController
|
||||
include BubbleScoped
|
||||
include CardScoped
|
||||
before_action :set_comment, only: [ :show, :edit, :update, :destroy ]
|
||||
before_action :require_own_comment, only: [ :edit, :update, :destroy ]
|
||||
|
||||
def create
|
||||
@bubble.capture new_comment
|
||||
@card.capture new_comment
|
||||
end
|
||||
|
||||
def show
|
||||
@@ -19,7 +19,7 @@ class CommentsController < ApplicationController
|
||||
|
||||
def destroy
|
||||
@comment.destroy
|
||||
redirect_to @bubble
|
||||
redirect_to @card
|
||||
end
|
||||
|
||||
private
|
||||
@@ -33,7 +33,7 @@ class CommentsController < ApplicationController
|
||||
|
||||
def set_comment
|
||||
@comment = Comment.joins(:message)
|
||||
.where(messages: { bubble_id: @bubble.id })
|
||||
.where(messages: { card_id: @card.id })
|
||||
.find(params[:id])
|
||||
end
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
module BubbleScoped
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :set_bubble, :set_bucket
|
||||
end
|
||||
|
||||
private
|
||||
def set_bubble
|
||||
@bubble = Current.user.accessible_bubbles.find(params[:bubble_id])
|
||||
end
|
||||
|
||||
def set_bucket
|
||||
@bucket = Current.user.buckets.find(@bubble.bucket_id)
|
||||
end
|
||||
end
|
||||
@@ -1,24 +0,0 @@
|
||||
module BucketFilterable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :set_bucket_filter
|
||||
end
|
||||
|
||||
private
|
||||
def set_bucket_filter
|
||||
params[:bucket_ids] ||= cookies[:bucket_filter]&.split(",") unless params[:clear_filter]
|
||||
end
|
||||
|
||||
def bucket_filter
|
||||
params[:bucket_ids].presence || Current.user.bucket_ids
|
||||
end
|
||||
|
||||
def update_bucket_filter
|
||||
if params[:clear_filter]
|
||||
cookies.delete(:bucket_filter)
|
||||
elsif params[:bucket_ids].present?
|
||||
cookies[:bucket_filter] = params[:bucket_ids].join(",")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,12 +0,0 @@
|
||||
module BucketScoped
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :set_bucket
|
||||
end
|
||||
|
||||
private
|
||||
def set_bucket
|
||||
@bucket = Current.user.buckets.find(params[:bucket_id])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
module CardScoped
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :set_card, :set_collection
|
||||
end
|
||||
|
||||
private
|
||||
def set_card
|
||||
@card = Current.user.accessible_cards.find(params[:card_id])
|
||||
end
|
||||
|
||||
def set_collection
|
||||
@collection = Current.user.collections.find(@card.collection_id)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
module CollectionFilterable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :set_collection_filter
|
||||
end
|
||||
|
||||
private
|
||||
def set_collection_filter
|
||||
params[:collection_ids] ||= cookies[:collection_filter]&.split(",") unless params[:clear_filter]
|
||||
end
|
||||
|
||||
def collection_filter
|
||||
params[:collection_ids].presence || Current.user.collection_ids
|
||||
end
|
||||
|
||||
def update_collection_filter
|
||||
if params[:clear_filter]
|
||||
cookies.delete(:collection_filter)
|
||||
elsif params[:collection_ids].present?
|
||||
cookies[:collection_filter] = params[:collection_ids].join(",")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
module CollectionScoped
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :set_collection
|
||||
end
|
||||
|
||||
private
|
||||
def set_collection
|
||||
@collection = Current.user.collections.find(params[:collection_id])
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,7 @@
|
||||
module EventsTimeline
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include BucketFilterable
|
||||
include CollectionFilterable
|
||||
|
||||
included do
|
||||
before_action :set_activity_day
|
||||
@@ -23,7 +23,7 @@ module EventsTimeline
|
||||
group_by { |event| [ event.created_at.hour, helpers.event_column(event) ] }.
|
||||
map { |hour_col, events|
|
||||
[ hour_col,
|
||||
events.uniq { |e| e.action == "boosted" ? [ e.creator_id, e.bubble_id ] : e.id }
|
||||
events.uniq { |e| e.action == "boosted" ? [ e.creator_id, e.card_id ] : e.id }
|
||||
]
|
||||
}
|
||||
end
|
||||
@@ -33,12 +33,12 @@ module EventsTimeline
|
||||
end
|
||||
|
||||
def user_events
|
||||
Event.where(bubble: user_bubbles, creator: Current.account.users)
|
||||
Event.where(card: user_cards, creator: Current.account.users)
|
||||
end
|
||||
|
||||
def user_bubbles
|
||||
Current.user.accessible_bubbles
|
||||
def user_cards
|
||||
Current.user.accessible_cards
|
||||
.published_or_drafted_by(Current.user)
|
||||
.where(bucket_id: bucket_filter)
|
||||
.where(collection_id: collection_filter)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,9 +2,9 @@ class EventsController < ApplicationController
|
||||
include EventsTimeline
|
||||
|
||||
def index
|
||||
update_bucket_filter
|
||||
update_collection_filter
|
||||
|
||||
@buckets = Current.user.buckets.alphabetically
|
||||
@collections = Current.user.collections.alphabetically
|
||||
@filters = Current.user.filters.all
|
||||
|
||||
@events = events_for_activity_day
|
||||
|
||||
@@ -3,7 +3,7 @@ class FiltersController < ApplicationController
|
||||
|
||||
def create
|
||||
@filter = Current.user.filters.remember filter_params
|
||||
redirect_to bubbles_path(@filter.as_params)
|
||||
redirect_to cards_path(@filter.as_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@@ -24,7 +24,7 @@ class FiltersController < ApplicationController
|
||||
if request.referer == root_url
|
||||
redirect_to root_path
|
||||
else
|
||||
redirect_to bubbles_path(@filter.as_params)
|
||||
redirect_to cards_path(@filter.as_params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class My::PinsController < ApplicationController
|
||||
def index
|
||||
@pins = Current.user.pins.includes(:bubble).ordered.limit(20)
|
||||
@pins = Current.user.pins.includes(:card).ordered.limit(20)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module Notifications
|
||||
class SettingsController < ApplicationController
|
||||
def show
|
||||
@buckets = Current.user.buckets.alphabetically
|
||||
@collections = Current.user.collections.alphabetically
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class TerminalsController < ApplicationController
|
||||
def show
|
||||
@events = Event.where(bubble: user_bubbles, creator: Current.user).chronologically.reverse_order.limit(20)
|
||||
@events = Event.where(card: user_cards, creator: Current.user).chronologically.reverse_order.limit(20)
|
||||
end
|
||||
|
||||
def edit
|
||||
@@ -8,7 +8,7 @@ class TerminalsController < ApplicationController
|
||||
end
|
||||
|
||||
private
|
||||
def user_bubbles
|
||||
Current.user.accessible_bubbles
|
||||
def user_cards
|
||||
Current.user.accessible_cards
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
module AccessesHelper
|
||||
def access_menu_tag(bucket, &)
|
||||
tag.menu class: [ "flex flex-column gap margin-none pad txt-medium", { "toggler--toggled": bucket.all_access? } ], data: {
|
||||
def access_menu_tag(collection, &)
|
||||
tag.menu class: [ "flex flex-column gap margin-none pad txt-medium", { "toggler--toggled": collection.all_access? } ], data: {
|
||||
controller: "filter toggle-class",
|
||||
filter_active_class: "filter--active", filter_selected_class: "selected",
|
||||
toggle_class_toggle_class: "toggler--toggled" }, &
|
||||
end
|
||||
|
||||
def access_toggles_for(users, selected:)
|
||||
render partial: "buckets/access_toggle",
|
||||
render partial: "collections/access_toggle",
|
||||
collection: users, as: :user,
|
||||
locals: { selected: selected },
|
||||
cached: ->(user) { [ user, selected ] }
|
||||
end
|
||||
|
||||
def access_involvement_advance_button(bucket, user)
|
||||
access = bucket.access_for(user)
|
||||
def access_involvement_advance_button(collection, user)
|
||||
access = collection.access_for(user)
|
||||
|
||||
turbo_frame_tag dom_id(bucket, :involvement_button) do
|
||||
button_to bucket_involvement_path(bucket), method: :put,
|
||||
aria: { labelledby: dom_id(bucket, :involvement_label) },
|
||||
turbo_frame_tag dom_id(collection, :involvement_button) do
|
||||
button_to collection_involvement_path(collection), method: :put,
|
||||
aria: { labelledby: dom_id(collection, :involvement_label) },
|
||||
class: [ "btn", { "btn--reversed": access.involvement == "watching" || access.involvement == "everything" } ],
|
||||
params: { involvement: next_involvement(access.involvement) },
|
||||
title: involvement_access_label(bucket, access.involvement) do
|
||||
title: involvement_access_label(collection, access.involvement) do
|
||||
icon_tag("notification-bell-#{access.involvement.dasherize}") +
|
||||
tag.span(involvement_access_label(bucket, access.involvement), class: "for-screen-reader", id: dom_id(bucket, :involvement_label))
|
||||
tag.span(involvement_access_label(collection, access.involvement), class: "for-screen-reader", id: dom_id(collection, :involvement_label))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -34,14 +34,14 @@ module AccessesHelper
|
||||
order[(order.index(involvement.to_s) + 1) % order.size]
|
||||
end
|
||||
|
||||
def involvement_access_label(bucket, involvement)
|
||||
def involvement_access_label(collection, involvement)
|
||||
case involvement
|
||||
when "access_only"
|
||||
"Notifications are off for #{bucket.name}"
|
||||
"Notifications are off for #{collection.name}"
|
||||
when "everything"
|
||||
"Notifying me about everything in #{bucket.name}"
|
||||
"Notifying me about everything in #{collection.name}"
|
||||
when "watching"
|
||||
"Notifying me only about @mentions and new items in #{bucket.name}"
|
||||
"Notifying me only about @mentions and new items in #{collection.name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
module BubblesHelper
|
||||
BUBBLE_ROTATION = %w[ 75 60 45 35 25 5 ]
|
||||
|
||||
def bubble_title(bubble)
|
||||
bubble.title.presence || "Untitled"
|
||||
end
|
||||
|
||||
def bubble_rotation(bubble)
|
||||
value = BUBBLE_ROTATION[Zlib.crc32(bubble.to_param) % BUBBLE_ROTATION.size]
|
||||
|
||||
"--bubble-rotate: #{value}deg;"
|
||||
end
|
||||
|
||||
def display_count_options
|
||||
BubblesController::DISPLAY_COUNT_OPTIONS.map do |count|
|
||||
{
|
||||
value: count,
|
||||
label: count,
|
||||
selected: @display_count == count,
|
||||
id: "display-count-#{count}"
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
module CardsHelper
|
||||
CARD_ROTATION = %w[ 75 60 45 35 25 5 ]
|
||||
|
||||
def card_title(card)
|
||||
card.title.presence || "Untitled"
|
||||
end
|
||||
|
||||
def card_rotation(card)
|
||||
value = CARD_ROTATION[Zlib.crc32(card.to_param) % CARD_ROTATION.size]
|
||||
|
||||
"--card-rotate: #{value}deg;"
|
||||
end
|
||||
|
||||
def display_count_options
|
||||
CardsController::DISPLAY_COUNT_OPTIONS.map do |count|
|
||||
{
|
||||
value: count,
|
||||
label: count,
|
||||
selected: @display_count == count,
|
||||
id: "display-count-#{count}"
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,8 +4,8 @@ module CommentsHelper
|
||||
data: { creator_id: comment.creator_id, created_by_current_user_target: "creation" }, &
|
||||
end
|
||||
|
||||
def new_comment_placeholder(bubble)
|
||||
if bubble.creator == Current.user && bubble.messages.comments.empty?
|
||||
def new_comment_placeholder(card)
|
||||
if card.creator == Current.user && card.messages.comments.empty?
|
||||
"Next, add some notes, context, pictures, or video about this…"
|
||||
else
|
||||
"Type your comment…"
|
||||
|
||||
@@ -12,7 +12,7 @@ module EventsHelper
|
||||
|
||||
def event_column(event)
|
||||
case event.action
|
||||
when "popped"
|
||||
when "closed"
|
||||
3
|
||||
when "published"
|
||||
1
|
||||
@@ -45,15 +45,15 @@ module EventsHelper
|
||||
start_time = day.beginning_of_day
|
||||
end_time = day.end_of_day
|
||||
|
||||
accessible_events = Event.joins(bubble: :bucket)
|
||||
.merge(Current.user.buckets)
|
||||
accessible_events = Event.joins(card: :collection)
|
||||
.merge(Current.user.collections)
|
||||
.where(created_at: start_time..end_time)
|
||||
.where(bubbles: { bucket_id: params[:bucket_ids].presence || Current.user.bucket_ids })
|
||||
.where(cards: { collection_id: params[:collection_ids].presence || Current.user.collection_ids })
|
||||
|
||||
headers = {
|
||||
"Added" => accessible_events.where(action: "published").count,
|
||||
"Updated" => nil,
|
||||
"Closed" => accessible_events.where(action: "popped").count
|
||||
"Closed" => accessible_events.where(action: "closed").count
|
||||
}
|
||||
|
||||
headers.map do |header, count|
|
||||
@@ -66,32 +66,32 @@ module EventsHelper
|
||||
case event.action
|
||||
when "assigned"
|
||||
if event.assignees.include?(Current.user)
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } will handle <span style='color: var(--bubble-color)'>#{ bubble_title(event.bubble) }</span>".html_safe
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } will handle <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
|
||||
else
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } assigned #{ event.assignees.pluck(:name).to_sentence } to <span style='color: var(--bubble-color)'>#{ bubble_title(event.bubble) }</span>".html_safe
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } assigned #{ event.assignees.pluck(:name).to_sentence } to <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
|
||||
end
|
||||
when "unassigned"
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } unassigned #{ event.assignees.pluck(:name).to_sentence } from <span style='color: var(--bubble-color)'>#{ bubble_title(event.bubble) }</span>".html_safe
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } unassigned #{ event.assignees.pluck(:name).to_sentence } from <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
|
||||
when "boosted"
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } boosted <span style='color: var(--bubble-color)'>#{ bubble_title(event.bubble) }</span>".html_safe
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } boosted <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
|
||||
when "commented"
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } commented on <span style='color: var(--bubble-color)'>#{ bubble_title(event.bubble) }</span>".html_safe
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } commented on <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
|
||||
when "published"
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } added <span style='color: var(--bubble-color)'>#{ bubble_title(event.bubble) }</span>".html_safe
|
||||
when "popped"
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } closed <span style='color: var(--bubble-color)'>#{ bubble_title(event.bubble) }</span>".html_safe
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } added <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
|
||||
when "closed"
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } closed <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
|
||||
when "staged"
|
||||
"#{event.creator == Current.user ? "You" : event.creator.name} changed the stage to #{event.stage_name} on <span style='color: var(--bubble-color)'>#{ bubble_title(event.bubble) }</span>".html_safe
|
||||
"#{event.creator == Current.user ? "You" : event.creator.name} changed the stage to #{event.stage_name} on <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
|
||||
when "unstaged"
|
||||
"#{event.creator == Current.user ? "You" : event.creator.name} removed <span style='color: var(--bubble-color)'>#{ bubble_title(event.bubble) }</span> from the #{event.stage_name} stage".html_safe
|
||||
"#{event.creator == Current.user ? "You" : event.creator.name} removed <span style='color: var(--card-color)'>#{ card_title(event.card) }</span> from the #{event.stage_name} stage".html_safe
|
||||
when "due_date_added"
|
||||
"#{event.creator == Current.user ? "You" : event.creator.name} set the date to #{event.particulars.dig('particulars', 'due_date').to_date.strftime('%B %-d')} on <span style='color: var(--bubble-color)'>#{ bubble_title(event.bubble) }</span>".html_safe
|
||||
"#{event.creator == Current.user ? "You" : event.creator.name} set the date to #{event.particulars.dig('particulars', 'due_date').to_date.strftime('%B %-d')} on <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
|
||||
when "due_date_changed"
|
||||
"#{event.creator == Current.user ? "You" : event.creator.name} changed the date to #{event.particulars.dig('particulars', 'due_date').to_date.strftime('%B %-d')} on <span style='color: var(--bubble-color)'>#{ bubble_title(event.bubble) }</span>".html_safe
|
||||
"#{event.creator == Current.user ? "You" : event.creator.name} changed the date to #{event.particulars.dig('particulars', 'due_date').to_date.strftime('%B %-d')} on <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
|
||||
when "due_date_removed"
|
||||
"#{event.creator == Current.user ? "You" : event.creator.name} removed the date on <span style='color: var(--bubble-color)'>#{ bubble_title(event.bubble) }</span>"
|
||||
"#{event.creator == Current.user ? "You" : event.creator.name} removed the date on <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>"
|
||||
when "title_changed"
|
||||
"#{event.creator == Current.user ? "You" : event.creator.name} renamed on <span style='color: var(--bubble-color)'>#{ bubble_title(event.bubble) }</span> (was: '#{event.particulars.dig('particulars', 'old_title')})'".html_safe
|
||||
"#{event.creator == Current.user ? "You" : event.creator.name} renamed on <span style='color: var(--card-color)'>#{ card_title(event.card) }</span> (was: '#{event.particulars.dig('particulars', 'old_title')})'".html_safe
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
module FiltersHelper
|
||||
def filter_title(filter)
|
||||
if filter.buckets.none?
|
||||
if filter.collections.none?
|
||||
"All collections"
|
||||
elsif filter.buckets.one?
|
||||
filter.buckets.first.name
|
||||
elsif filter.collections.one?
|
||||
filter.collections.first.name
|
||||
else
|
||||
filter.buckets.map(&:name).to_sentence
|
||||
filter.collections.map(&:name).to_sentence
|
||||
end
|
||||
end
|
||||
|
||||
def filter_chip_tag(text, params)
|
||||
link_to bubbles_path(params), class: "btn txt-small btn--remove fill-selected" do
|
||||
link_to cards_path(params), class: "btn txt-small btn--remove fill-selected" do
|
||||
concat tag.span(text)
|
||||
concat icon_tag("close")
|
||||
end
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
module MessagesHelper
|
||||
def messages_tag(bubble, &)
|
||||
turbo_frame_tag dom_id(bubble, :messages),
|
||||
def messages_tag(card, &)
|
||||
turbo_frame_tag dom_id(card, :messages),
|
||||
class: "comments gap center",
|
||||
style: "--bubble-color: <%= bubble.color %>",
|
||||
style: "--card-color: <%= card.color %>",
|
||||
role: "group", aria: { label: "Messages" },
|
||||
data: {
|
||||
controller: "created-by-current-user",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module NotificationsHelper
|
||||
def notification_title(notification)
|
||||
title = bubble_title(notification.bubble)
|
||||
title = card_title(notification.card)
|
||||
|
||||
if notification.resource.is_a? Comment
|
||||
"RE: " + title
|
||||
@@ -15,7 +15,7 @@ module NotificationsHelper
|
||||
name = notification.creator.name
|
||||
|
||||
case notification_event_action(notification)
|
||||
when "popped" then "Popped by #{name}"
|
||||
when "closed" then "Closed by #{name}"
|
||||
when "published" then "Added by #{name}"
|
||||
else name
|
||||
end
|
||||
@@ -59,6 +59,6 @@ module NotificationsHelper
|
||||
end
|
||||
|
||||
def notification_is_for_initial_assignement?(notification)
|
||||
notification.event.action == "published" && notification.bubble.assigned_to?(notification.user)
|
||||
notification.event.action == "published" && notification.card.assigned_to?(notification.user)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@ module TranslationsHelper
|
||||
user_name: { "🇺🇸": "Enter your name", "🇪🇸": "Introduce tu nombre", "🇫🇷": "Entrez votre nom", "🇮🇳": "अपना नाम दर्ज करें", "🇩🇪": "Geben Sie Ihren Namen ein", "🇧🇷": "Insira seu nome" },
|
||||
email_address: { "🇺🇸": "Enter your email address", "🇪🇸": "Introduce tu correo electrónico", "🇫🇷": "Entrez votre adresse courriel", "🇮🇳": "अपना ईमेल पता दर्ज करें", "🇩🇪": "Geben Sie Ihre E-Mail-Adresse ein", "🇧🇷": "Insira seu endereço de email" },
|
||||
password: { "🇺🇸": "Enter your password", "🇪🇸": "Introduce tu contraseña", "🇫🇷": "Saisissez votre mot de passe", "🇮🇳": "अपना पासवर्ड दर्ज करें", "🇩🇪": "Geben Sie Ihr Passwort ein", "🇧🇷": "Insira sua senha" },
|
||||
bucket_name: { "🇺🇸": "Give it a name", "🇪🇸": "Dale un nombre", "🇫🇷": "Donnez lui un nom", "🇮🇳": "इसे एक नाम दें", "🇩🇪": "Gib ihm einen Namen", "🇧🇷": "Dê-lhe um nome" }
|
||||
collection_name: { "🇺🇸": "Give it a name", "🇪🇸": "Dale un nombre", "🇫🇷": "Donnez lui un nom", "🇮🇳": "इसे एक नाम दें", "🇩🇪": "Gib ihm einen Namen", "🇧🇷": "Dê-lhe um nome" }
|
||||
}
|
||||
|
||||
def translations_for(translation_key)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module WorkflowsHelper
|
||||
def button_to_set_stage(bubble, stage)
|
||||
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 } ],
|
||||
def button_to_set_stage(card, stage)
|
||||
button_to card_stagings_path(card, stage_id: stage),
|
||||
method: :post, class: [ "btn full-width justify-start workflow-stage txt-uppercase", { "workflow-stage--current": stage == card.stage } ],
|
||||
form_class: "flex align-center gap-half",
|
||||
data: { turbo_frame: "_top" } do
|
||||
tag.span class: "overflow-ellipsis" do
|
||||
|
||||
+7
-7
@@ -3,7 +3,7 @@ import { Controller } from "@hotwired/stimulus"
|
||||
const SIZES = [ "one", "two", "three", "four", "five" ]
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = [ "bubble" ]
|
||||
static targets = [ "card" ]
|
||||
|
||||
connect() {
|
||||
this.resize()
|
||||
@@ -12,19 +12,19 @@ export default class extends Controller {
|
||||
resize() {
|
||||
const [ min, max ] = this.#getScoreRange()
|
||||
|
||||
this.bubbleTargets.forEach(bubble => {
|
||||
const score = this.#currentBubbleScore(bubble)
|
||||
this.cardTargets.forEach(card => {
|
||||
const score = this.#currentCardScore(card)
|
||||
const idx = Math.round((score - min) / (max - min) * (SIZES.length - 1))
|
||||
|
||||
bubble.style.setProperty("--bubble-size", `var(--bubble-size-${SIZES[idx]})`)
|
||||
card.style.setProperty("--card-size", `var(--card-size-${SIZES[idx]})`)
|
||||
})
|
||||
}
|
||||
|
||||
#getScoreRange() {
|
||||
var min = 0, max = 1;
|
||||
|
||||
this.bubbleTargets.forEach(bubble => {
|
||||
const score = this.#currentBubbleScore(bubble)
|
||||
this.cardTargets.forEach(card => {
|
||||
const score = this.#currentCardScore(card)
|
||||
|
||||
min = Math.min(min, score)
|
||||
max = Math.max(max, score)
|
||||
@@ -33,7 +33,7 @@ export default class extends Controller {
|
||||
return [ min, max ]
|
||||
}
|
||||
|
||||
#currentBubbleScore(el) {
|
||||
#currentCardScore(el) {
|
||||
const score = el.dataset.activityScore
|
||||
const scoreAt = el.dataset.activityScoreAt
|
||||
const daysAgo = (Date.now() / 1000 - scoreAt) / (60 * 60 * 24)
|
||||
@@ -47,7 +47,7 @@ export default class extends Controller {
|
||||
#triggerChangeEvent(newValue) {
|
||||
if (this.inputTarget.tagName === "HOUSE-MD") {
|
||||
this.inputTarget.dispatchEvent(new CustomEvent('house-md:change', {
|
||||
bubbles: true,
|
||||
cards: true,
|
||||
detail: {
|
||||
previousContent: '',
|
||||
newContent: newValue
|
||||
|
||||
@@ -12,7 +12,7 @@ export default class extends Controller {
|
||||
|
||||
for (const file of files) {
|
||||
const uploadEvent = new CustomEvent("house-md:before-upload", {
|
||||
bubbles: true,
|
||||
cards: true,
|
||||
detail: { file },
|
||||
cancelable: true
|
||||
})
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
class Bubble::AutoPopAllDueJob < ApplicationJob
|
||||
class Card::AutoCloseAllDueJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
Bubble.auto_pop_all_due
|
||||
Card.auto_close_all_due
|
||||
end
|
||||
end
|
||||
end
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
class Bubble::AutoReconsiderAllStagnatedJob < ApplicationJob
|
||||
class Card::AutoReconsiderAllStagnatedJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
Bubble.auto_reconsider_all_stagnated
|
||||
Card.auto_reconsider_all_stagnated
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,7 +3,7 @@ class RemoveAbandonedCreationsJob < ApplicationJob
|
||||
|
||||
def perform
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
Bubble.remove_abandoned_creations
|
||||
Card.remove_abandoned_creations
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Access < ApplicationRecord
|
||||
belongs_to :bucket
|
||||
belongs_to :collection
|
||||
belongs_to :user
|
||||
|
||||
enum :involvement, %i[ access_only watching everything ].index_by(&:itself)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
class Account < ApplicationRecord
|
||||
include PopReasons, Joinable
|
||||
include ClosureReasons, Joinable
|
||||
|
||||
has_many :buckets, dependent: :destroy
|
||||
has_many :bubbles, through: :buckets
|
||||
has_many :collections, dependent: :destroy
|
||||
has_many :cards, through: :collections
|
||||
|
||||
has_many :users, dependent: :destroy do
|
||||
def system
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module Account::PopReasons
|
||||
module Account::ClosureReasons
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
DEFAULT_LABELS = [
|
||||
@@ -11,19 +11,19 @@ module Account::PopReasons
|
||||
FALLBACK_LABEL = "Done"
|
||||
|
||||
included do
|
||||
has_many :pop_reasons, dependent: :destroy, class_name: "Pop::Reason" do
|
||||
has_many :closure_reasons, dependent: :destroy, class_name: "Closure::Reason" do
|
||||
def labels
|
||||
pluck(:label).presence || [ FALLBACK_LABEL ]
|
||||
end
|
||||
end
|
||||
|
||||
after_create :create_default_pop_reasons
|
||||
after_create :create_default_closure_reasons
|
||||
end
|
||||
|
||||
private
|
||||
def create_default_pop_reasons
|
||||
def create_default_closure_reasons
|
||||
DEFAULT_LABELS.each do |label|
|
||||
pop_reasons.create! label: label
|
||||
closure_reasons.create! label: label
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
class Assignment < ApplicationRecord
|
||||
belongs_to :bubble, touch: true
|
||||
belongs_to :card, touch: true
|
||||
|
||||
belongs_to :assignee, class_name: "User"
|
||||
belongs_to :assigner, class_name: "User"
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
class Bubble::Engagement < ApplicationRecord
|
||||
belongs_to :bubble, class_name: "::Bubble", touch: true
|
||||
end
|
||||
@@ -1,56 +0,0 @@
|
||||
module Bubble::Poppable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
AUTO_POP_AFTER = 30.days
|
||||
|
||||
included do
|
||||
has_one :pop, dependent: :destroy
|
||||
|
||||
scope :popped, -> { joins(:pop) }
|
||||
scope :active, -> { where.missing(:pop) }
|
||||
|
||||
scope :recently_popped_first, -> { popped.order("pops.created_at": :desc) }
|
||||
scope :due_to_be_popped, -> { considering.where(last_active_at: ..AUTO_POP_AFTER.ago) }
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def auto_pop_all_due
|
||||
due_to_be_popped.find_each do |bubble|
|
||||
bubble.pop!(user: bubble.bucket.account.users.system, reason: "Closed")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def auto_pop_at
|
||||
last_active_at + AUTO_POP_AFTER if last_active_at
|
||||
end
|
||||
|
||||
def popped?
|
||||
pop.present?
|
||||
end
|
||||
|
||||
def active?
|
||||
!popped?
|
||||
end
|
||||
|
||||
def popped_by
|
||||
pop&.user
|
||||
end
|
||||
|
||||
def popped_at
|
||||
pop&.created_at
|
||||
end
|
||||
|
||||
def pop!(user: Current.user, reason: Account::PopReasons::FALLBACK_LABEL)
|
||||
unless popped?
|
||||
transaction do
|
||||
create_pop! user: user, reason: reason
|
||||
track_event :popped, creator: user
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unpop
|
||||
pop&.destroy
|
||||
end
|
||||
end
|
||||
@@ -1,21 +0,0 @@
|
||||
class Bucket < ApplicationRecord
|
||||
include Accessible, Broadcastable, Filterable
|
||||
|
||||
belongs_to :account
|
||||
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
||||
belongs_to :workflow, optional: true
|
||||
|
||||
has_many :bubbles, dependent: :destroy
|
||||
has_many :tags, -> { distinct }, through: :bubbles
|
||||
|
||||
validates_presence_of :name
|
||||
|
||||
after_save :update_bubbles_workflow, if: :saved_change_to_workflow_id?
|
||||
|
||||
scope :alphabetically, -> { order("lower(name)") }
|
||||
|
||||
private
|
||||
def update_bubbles_workflow
|
||||
bubbles.update_all(stage_id: workflow&.stages&.first&.id)
|
||||
end
|
||||
end
|
||||
@@ -1,8 +0,0 @@
|
||||
module Bucket::Broadcastable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
broadcasts_refreshes
|
||||
broadcasts_refreshes_to ->(bucket) { [ bucket.account, :buckets ] }
|
||||
end
|
||||
end
|
||||
@@ -1,8 +1,8 @@
|
||||
class Bubble < ApplicationRecord
|
||||
class Card < ApplicationRecord
|
||||
include Assignable, Boostable, Colored, Commentable, Engageable, Eventable,
|
||||
Messages, Notifiable, Pinnable, Poppable, Scorable, Searchable, Staged, Statuses, Taggable, Watchable
|
||||
Messages, Notifiable, Pinnable, Closeable, Scorable, Searchable, Staged, Statuses, Taggable, Watchable
|
||||
|
||||
belongs_to :bucket, touch: true
|
||||
belongs_to :collection, touch: true
|
||||
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
||||
|
||||
has_many :notifications, dependent: :destroy
|
||||
@@ -15,7 +15,7 @@ class Bubble < ApplicationRecord
|
||||
|
||||
scope :reverse_chronologically, -> { order created_at: :desc, id: :desc }
|
||||
scope :chronologically, -> { order created_at: :asc, id: :asc }
|
||||
scope :in_bucket, ->(bucket) { where bucket: bucket }
|
||||
scope :in_collection, ->(collection) { where collection: collection }
|
||||
|
||||
scope :indexed_by, ->(index) do
|
||||
case index
|
||||
@@ -25,12 +25,12 @@ class Bubble < ApplicationRecord
|
||||
when "newest" then reverse_chronologically
|
||||
when "oldest" then chronologically
|
||||
when "stalled" then ordered_by_staleness
|
||||
when "popped" then popped
|
||||
when "closed" then closed
|
||||
end
|
||||
end
|
||||
|
||||
def cache_key
|
||||
[ super, bucket&.name ].compact.join("/")
|
||||
[ super, collection&.name ].compact.join("/")
|
||||
end
|
||||
|
||||
private
|
||||
@@ -56,7 +56,7 @@ class Bubble < ApplicationRecord
|
||||
end
|
||||
|
||||
def assign_initial_stage
|
||||
if workflow_stage = bucket.workflow&.stages&.first
|
||||
if workflow_stage = collection.workflow&.stages&.first
|
||||
self.stage = workflow_stage
|
||||
save! touch: false
|
||||
track_event :staged, stage_id: workflow_stage.id, stage_name: workflow_stage.name
|
||||
@@ -1,4 +1,4 @@
|
||||
module Bubble::Assignable
|
||||
module Card::Assignable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
@@ -1,4 +1,4 @@
|
||||
module Bubble::Boostable
|
||||
module Card::Boostable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
@@ -0,0 +1,56 @@
|
||||
module Card::Closeable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
AUTO_CLOSURE_AFTER = 30.days
|
||||
|
||||
included do
|
||||
has_one :closure, dependent: :destroy
|
||||
|
||||
scope :closed, -> { joins(:closure) }
|
||||
scope :active, -> { where.missing(:closure) }
|
||||
|
||||
scope :recently_closed_first, -> { closed.order("closures.created_at": :desc) }
|
||||
scope :due_to_be_closed, -> { considering.where(last_active_at: ..AUTO_CLOSURE_AFTER.ago) }
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def auto_close_all_due
|
||||
due_to_be_closed.find_each do |card|
|
||||
card.close(user: card.collection.account.users.system, reason: "Closed")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def auto_close_at
|
||||
last_active_at + AUTO_CLOSURE_AFTER if last_active_at
|
||||
end
|
||||
|
||||
def closed?
|
||||
closure.present?
|
||||
end
|
||||
|
||||
def active?
|
||||
!closed?
|
||||
end
|
||||
|
||||
def closed_by
|
||||
closure&.user
|
||||
end
|
||||
|
||||
def closed_at
|
||||
closure&.created_at
|
||||
end
|
||||
|
||||
def close(user: Current.user, reason: Account::ClosureReasons::FALLBACK_LABEL)
|
||||
unless closed?
|
||||
transaction do
|
||||
create_closure! user: user, reason: reason
|
||||
track_event :closed, creator: user
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def reopen
|
||||
closure&.destroy
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
module Bubble::Colored
|
||||
module Card::Colored
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
COLORS = %w[ #b7462b #ff63a8 #eb7a32 #6ac967 #2c6da8 #663251 ]
|
||||
@@ -1,4 +1,4 @@
|
||||
module Bubble::Commentable
|
||||
module Card::Commentable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
@@ -1,10 +1,10 @@
|
||||
module Bubble::Engageable
|
||||
module Card::Engageable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
STAGNATED_AFTER = 30.days
|
||||
|
||||
included do
|
||||
has_one :engagement, dependent: :destroy, class_name: "Bubble::Engagement"
|
||||
has_one :engagement, dependent: :destroy, class_name: "Card::Engagement"
|
||||
|
||||
scope :doing, -> { published.active.joins(:engagement) }
|
||||
scope :considering, -> { published.active.where.missing(:engagement) }
|
||||
@@ -14,8 +14,8 @@ module Bubble::Engageable
|
||||
|
||||
class_methods do
|
||||
def auto_reconsider_all_stagnated
|
||||
stagnated.find_each do |bubble|
|
||||
bubble.reconsider
|
||||
stagnated.find_each do |card|
|
||||
card.reconsider
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -35,7 +35,7 @@ module Bubble::Engageable
|
||||
def engage
|
||||
unless doing?
|
||||
transaction do
|
||||
unpop
|
||||
reopen
|
||||
create_engagement!
|
||||
end
|
||||
end
|
||||
@@ -43,7 +43,7 @@ module Bubble::Engageable
|
||||
|
||||
def reconsider
|
||||
transaction do
|
||||
unpop
|
||||
reopen
|
||||
engagement&.destroy
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,3 @@
|
||||
class Card::Engagement < ApplicationRecord
|
||||
belongs_to :card, class_name: "::Card", touch: true
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
module Bubble::Eventable
|
||||
module Card::Eventable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
@@ -13,7 +13,7 @@ module Bubble::Eventable
|
||||
private
|
||||
def track_event(action, creator: Current.user, **particulars)
|
||||
if published?
|
||||
event = find_or_capture_event_summary.events.create! action: action, creator: creator, bubble: self, particulars: particulars
|
||||
event = find_or_capture_event_summary.events.create! action: action, creator: creator, card: self, particulars: particulars
|
||||
event.generate_notifications_later
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
module Bubble::Messages
|
||||
module Card::Messages
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
@@ -1,4 +1,4 @@
|
||||
module Bubble::Pinnable
|
||||
module Card::Pinnable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
@@ -1,4 +1,4 @@
|
||||
module Bubble::Scorable
|
||||
module Card::Scorable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
REFERENCE_DATE = Time.utc(2025, 1, 1)
|
||||
@@ -37,7 +37,7 @@ module Bubble::Scorable
|
||||
def event_weight(event)
|
||||
case
|
||||
when event.boosted? then 1
|
||||
when event.comment&.first_by_author_on_bubble? then 20
|
||||
when event.comment&.first_by_author_on_card? then 20
|
||||
when event.comment&.follows_comment_by_another_author? then 15
|
||||
when event.commented? then 10
|
||||
else 0
|
||||
@@ -46,7 +46,7 @@ module Bubble::Scorable
|
||||
|
||||
def event_score_reference(score, activity_at)
|
||||
# The reference score is used to make the activity score comparable
|
||||
# across different bubbles, since it represents the bubble's activity
|
||||
# across different cards, since it represents the card's activity
|
||||
# level at a consistent point in time.
|
||||
#
|
||||
# We store this as log2 to tame the huge/tiny numbers we'd otherwise get
|
||||
@@ -1,17 +1,17 @@
|
||||
module Bubble::Searchable
|
||||
module Card::Searchable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include ::Searchable
|
||||
|
||||
searchable_by :title, using: :bubbles_search_index
|
||||
searchable_by :title, using: :cards_search_index
|
||||
|
||||
scope :mentioning, ->(query) do
|
||||
if query = sanitize_query_syntax(query)
|
||||
bubbles = Bubble.search(query).select(:id).to_sql
|
||||
cards = Card.search(query).select(:id).to_sql
|
||||
comments = Comment.search(query).select(:id).to_sql
|
||||
|
||||
left_joins(:messages).where("bubbles.id in (#{bubbles}) or messages.messageable_id in (#{comments})").distinct
|
||||
left_joins(:messages).where("cards.id in (#{cards}) or messages.messageable_id in (#{comments})").distinct
|
||||
else
|
||||
none
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
module Bubble::Staged
|
||||
module Card::Staged
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
@@ -1,4 +1,4 @@
|
||||
module Bubble::Statuses
|
||||
module Card::Statuses
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
@@ -9,7 +9,7 @@ module Bubble::Statuses
|
||||
|
||||
class_methods do
|
||||
def remove_abandoned_creations
|
||||
Bubble.creating.where(updated_at: ..1.day.ago).destroy_all
|
||||
Card.creating.where(updated_at: ..1.day.ago).destroy_all
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,8 +18,8 @@ module Bubble::Statuses
|
||||
end
|
||||
|
||||
def recover_abandoned_creation
|
||||
abandoned_creations.last.tap do |bubble|
|
||||
Bubble.creating.where(creator: creator).excluding(bubble).destroy_all
|
||||
abandoned_creations.last.tap do |card|
|
||||
Card.creating.where(creator: creator).excluding(card).destroy_all
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,6 +32,6 @@ module Bubble::Statuses
|
||||
|
||||
private
|
||||
def abandoned_creations
|
||||
Bubble.creating.where(creator: creator).where("created_at != updated_at").excluding(self)
|
||||
Card.creating.where(creator: creator).where("created_at != updated_at").excluding(self)
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
module Bubble::Taggable
|
||||
module Card::Taggable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
@@ -9,7 +9,7 @@ module Bubble::Taggable
|
||||
end
|
||||
|
||||
def toggle_tag_with(title)
|
||||
tag = bucket.account.tags.find_or_create_by!(title: title)
|
||||
tag = collection.account.tags.find_or_create_by!(title: title)
|
||||
|
||||
transaction do
|
||||
if tagged_with?(tag)
|
||||
@@ -1,4 +1,4 @@
|
||||
module Bubble::Watchable
|
||||
module Card::Watchable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
@@ -22,7 +22,7 @@ module Bubble::Watchable
|
||||
|
||||
def watchers_and_subscribers(include_only_watching: false)
|
||||
involvements = include_only_watching ? [ :watching, :everything ] : :everything
|
||||
subscribers = bucket.users.where(accesses: { involvement: involvements })
|
||||
subscribers = collection.users.where(accesses: { involvement: involvements })
|
||||
|
||||
User.where(id: subscribers.pluck(:id) +
|
||||
watches.watching.pluck(:user_id) - watches.not_watching.pluck(:user_id))
|
||||
@@ -0,0 +1,4 @@
|
||||
class Closure < ApplicationRecord
|
||||
belongs_to :card, touch: true
|
||||
belongs_to :user, optional: true
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user