Merge branch 'main' into btn-dropdown
* main: (21 commits) Migration to migrate URLs after renaming bubbles => cards Remove migration Restore bubbles.css since some styles are used Fix issues with styling card__ components Remove bubbles.css Fix renames that the scripts missed Reorganize templates to remove cards/cards redundancy Rename bubbles => cards Safari sure loves outlines Use a number field, and improve focus styles Fix boosting via number input Embed additional video formats Fix that notifications weren't marked as read Fix tray item position Update House to bring patch for history restoration error Tighten up Restore additional missing routes removed inc0f3ff46d7Retore route removed inc0f3ff46d7Fix visual regression in popup menus Perma cards should not always be 100% width ...
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;
|
||||
|
||||
@@ -112,9 +112,12 @@
|
||||
}
|
||||
|
||||
.card__actions-container {
|
||||
--border-radius: 1em;
|
||||
|
||||
background-color: var(--color-bg);
|
||||
margin-block: calc(var(--block-space) * -1) var(--block-space);
|
||||
padding: var(--block-space) var(--inline-space-double);
|
||||
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;
|
||||
z-index: 0;
|
||||
|
||||
@@ -123,24 +126,25 @@
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
.card__actions-container--top {
|
||||
margin: var(--block-space-half) auto calc(var(--block-space) * -2);
|
||||
padding: var(--block-space) 3ch;
|
||||
min-inline-size: 36ch;
|
||||
margin: calc(var(--block-space) * 1) auto calc(var(--block-space) * -0.33);
|
||||
padding: 0.5em 0.75em;
|
||||
|
||||
#header:has(&) {
|
||||
position: relative;
|
||||
@@ -148,11 +152,19 @@
|
||||
}
|
||||
|
||||
.switch__input:checked + .switch__btn {
|
||||
background-color: var(--bubble-color) !important;
|
||||
background-color: var(--card-color) !important;
|
||||
}
|
||||
|
||||
strong {
|
||||
flex: 1 0 42%;
|
||||
text-align: end;
|
||||
|
||||
&:last-of-type {
|
||||
text-align: start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.cards .card__assignees:not(:has(.avatar)) {
|
||||
.txt-uppercase {
|
||||
display: none;
|
||||
@@ -191,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;
|
||||
}
|
||||
@@ -232,11 +244,11 @@
|
||||
--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;
|
||||
padding-block: var(--padding-block);
|
||||
padding-inline: var(--padding-inline);
|
||||
margin-block: calc(var(--block-space) * -1) calc(var(--block-space) * -0.33);
|
||||
padding: var(--padding-block) var(--padding-inline);
|
||||
|
||||
&:has(.card__star-input:checked) {
|
||||
.card {
|
||||
@@ -377,7 +389,7 @@
|
||||
}
|
||||
|
||||
.card__meta {
|
||||
--border-color: var(--bubble-color);
|
||||
--border-color: var(--card-color);
|
||||
--border-size: 1px;
|
||||
--row-gap: 0.25em;
|
||||
|
||||
@@ -393,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);
|
||||
@@ -405,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 {
|
||||
@@ -456,9 +468,11 @@
|
||||
}
|
||||
|
||||
.input.boost__input {
|
||||
--hover-size: 0;
|
||||
--input-border-radius: 0;
|
||||
--input-border-size: 0;
|
||||
--input-padding: 0;
|
||||
--outline-size: 0;
|
||||
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
@@ -466,17 +480,28 @@
|
||||
inline-size: min-content;
|
||||
max-inline-size: 3ch;
|
||||
min-inline-size: 1ch;
|
||||
outline: none;
|
||||
|
||||
@supports (field-sizing: content) {
|
||||
field-sizing: content;
|
||||
max-inline-size: unset;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: var(--color-highlight);
|
||||
}
|
||||
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
@@ -485,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 "); }
|
||||
@@ -66,7 +66,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 {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
inline-size: var(--tray-size);
|
||||
outline: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
position: absolute !important; /* tmp fix; ideally should remove the position-relative class from tray__item */
|
||||
scale: var(--scale);
|
||||
transform: translateY(var(--offset));
|
||||
transform-origin: center;
|
||||
|
||||
@@ -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))
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user