@@ -0,0 +1 @@
|
||||
<svg enable-background="new 0 0 24 24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m15.9 19.5-11-11c-.3.8-.4 1.7-.4 2.6v4.9c0 .8-.7 1.5-1.5 1.5-.6 0-1 .4-1 1s.4 1 1 1z"/><path d="m9.5 21.2v.3c0 1.4 1.1 2.5 2.5 2.5s2.5-1.1 2.5-2.5c0-.1 0-.2 0-.3s-.1-.2-.2-.2h-4.5c-.2 0-.3.1-.3.2z"/><path d="m23.7 23.7c.4-.4.4-1 0-1.4l-2.8-2.8h.1c.6 0 1-.4 1-1s-.4-1-1-1c-.8 0-1.5-.7-1.5-1.5v-4.9c.1-3.9-2.6-7.4-6.5-8.1v-2c0-.6-.4-1-1-1s-1 .4-1 1v2.1c-1.6.3-3.1 1-4.3 2.3l-5-5c-.4-.4-1-.4-1.4 0-.4.4-.4 1 0 1.4l22 22c.2.2.4.3.7.3.3-.1.5-.2.7-.4z"/></svg>
|
||||
|
After Width: | Height: | Size: 557 B |
@@ -1 +0,0 @@
|
||||
<svg viewBox="0 0 310.61 281.75" xmlns="http://www.w3.org/2000/svg"><path d="m20.47 271.92c-1.6-6.97 2.82-15.44 9.97-17.4 12.8-3.61 24.89 6.78 37.67 6.31 15.88-.37 21.55-17.47 30.42-27.85 24.86-29.76 43.09-15.99 55.66-29.43 6.11-6.89 6.02-17.7 11.34-25.03 3.56-5.2 10.53-8.05 16.55-5.64 4.98 1.84 8.18 6.54 11.3 10.68 1.82 2.38 3.85 4.63 6.2 5.91 4.23 2.49 9.06.6 10.31-3.89 3.96-14.02-15.89-22.54-12.55-34.08 1.29-3.43 4.96-4.99 9.06-4.91 6.05.12 11.44 3.32 17.61 4.46 28.76 4.66 13.73-34.72 14.62-50.46.19-27.14 15.28-45.38 34.38-62.66 5.49-5.16 13.69-10.19 21.22-7.22 5.82 2.17 8.96 9.87 10.6 16.13 5.62 22.06 9.09 44.93 1.11 66.37-20.23 50.47-56.78 95.13-102.65 123.97-46.23 27.85-101.79 42.33-155.42 44.54-9.31.01-24.05 1.27-27.35-9.61l-.04-.17z" fill="#fff"/><g stroke="#000" stroke-miterlimit="10" stroke-width="6"><circle cx="109.95" cy="235.27" fill="#fff" r="15.98"/><circle cx="159.65" cy="148.41" fill="none" r="15.53"/><circle cx="205.67" cy="113.68" fill="none" r="11.29"/><circle cx="200.45" cy="216.88" fill="#fff" r="12.28"/><circle cx="69.15" cy="187.8" fill="none" r="11.44"/><circle cx="103.6" cy="205.59" fill="#fff" r="6.35"/><circle cx="64.07" cy="232.27" fill="#fff" r="6.35"/><circle cx="153.29" cy="219.28" fill="#fff" r="6.35"/><circle cx="261.43" cy="128.79" fill="#fff" r="10.02"/><circle cx="222.19" cy="140.93" fill="#fff" r="5.22"/><circle cx="236.73" cy="187.8" fill="#fff" r="5.22"/><circle cx="292.7" cy="61.47" fill="#fff" r="5.22"/><circle cx="252.54" cy="61.47" fill="none" r="15.81"/><circle cx="292.7" cy="88.64" fill="#fff" r="11.36"/><path d="m13.03 265.59c-10.1-3.27-13.54-18.51-5.82-25.79 5.26-4.97 13.31-5.68 19.21-9.86s8.92-11.25 11.38-18.04c4.51-12.46 8.08-25.25 10.69-38.24.84-4.17 1.59-8.42 3.48-12.24 4.88-9.87 17.2-14.91 27.98-12.64s19.6 10.92 23.56 21.2c2.26 5.87 3.12 12.28 6.03 17.87s8.87 10.38 15.03 9.11c6.49-1.34 10.06-8.97 9.26-15.55s-4.75-12.3-8.56-17.73-7.72-11.18-8.43-17.78c-.99-9.28 4.93-18.37 12.98-23.09s17.82-5.68 27.12-4.9c6.21.52 13.48 1.4 17.73-3.15 5.15-5.52 1.88-14.38 2.65-21.89 1.32-12.95 15.25-21.16 28.21-22.37 2.29-.21 4.65-.29 6.78-1.16 6.09-2.48 8.01-10.2 7.84-16.78s-1.52-13.51 1.23-19.49c4.42-9.63 17.06-12.01 27.62-11.13s21.63 3.68 31.42-.37" fill="none"/><circle cx="288.05" cy="19.59" fill="#fff" r="16.59"/><circle cx="18.98" cy="262.62" fill="#fff" r="15.98"/><circle cx="205.67" cy="67.25" fill="#fff" r="10.02"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -1 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m10 20v-6h4v6h5v-8h3l-10-9-10 9h3v8z"/></svg>
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m12 3 8 6v12h-5v-7h-6v7h-5v-12z"/></svg>
|
||||
|
Before Width: | Height: | Size: 114 B After Width: | Height: | Size: 109 B |
@@ -0,0 +1 @@
|
||||
<svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><g fill="#000"><path d="m1.50197 7.275 5.764-5.765c.57744-.579297 1.51515-.580803 2.09445-.00337.07782.07757.14676.16355.20555.25637l4.10003 6.443c1.8579-.44233 3.8147-.15604 5.468.8.7059.4121.944 1.3184.5319 2.0243-.0641.1098-.142.2108-.2319.3007l-8.105 8.1c-.3257.3218-.7805.4772-1.235.422-.45519-.0584-.85766-.3243-1.09003-.72-.96021-1.6515-1.24675-3.6096-.8-5.467l-6.443-4.1c-.68979-.43852-.893482-1.3532-.45496-2.04299.05793-.09112.12565-.17562.20196-.25201z"/><path d="m16.54 14.428 5.94 5.94c.5856.5857.5856 1.5352 0 2.121-.5896.5764-1.5315.5764-2.121 0l-5.94-5.94c-.5755-.5959-.559-1.5455.0368-2.121.5813-.5614 1.5029-.5614 2.0842 0z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 749 B |
@@ -1 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m23 10c0-1.11-.9-2-2-2h-6.32l.96-4.57c.02-.1.03-.21.03-.32 0-.41-.17-.79-.44-1.06l-1.06-1.05-6.58 6.58c-.37.37-.59.87-.59 1.42v10a2 2 0 0 0 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73zm-22 11h4v-12h-4z"/></svg>
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m10.2 11.2h-2.1c-.2 0-.4-.2-.4-.4v-2.1c0-.6-.5-1.1-1.1-1.1s-1.1.5-1.1 1.1v2.1c0 .2-.2.4-.4.4h-2.1c-.6 0-1.1.5-1.1 1.1s.5 1.1 1.1 1.1h2.1c.2 0 .4.2.4.4v2.1c0 .6.5 1.1 1.1 1.1s1.1-.5 1.1-1.1v-2.1c0-.2.2-.4.4-.4h2.1c.6 0 1.1-.5 1.1-1.1s-.5-1.1-1.1-1.1z"/><rect height="16.6" rx="1.4" width="2.7" x="16.2" y="3.4"/><rect height="8.3" rx="1.4" transform="matrix(0 1 -1 0 36.3 .9)" width="2.7" x="16.3" y="14.4"/><rect height="6.6" rx="1.4" transform="matrix(.70710678 .70710678 -.70710678 .70710678 9 -9.5)" width="2.7" x="14.6" y="2.8"/></svg>
|
||||
|
Before Width: | Height: | Size: 297 B After Width: | Height: | Size: 608 B |
@@ -0,0 +1 @@
|
||||
<svg enable-background="new 0 0 24 24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m22.5 20.4-5.4-5.4 3-3c.2-.1.3-.3.4-.5.7-1.2.3-2.7-.9-3.4-1.7-1-3.6-1.3-5.5-1l-3.7-5.9c-.1-.2-.2-.3-.3-.4-.5-.5-1.1-.7-1.8-.7s-1.3.2-1.7.7l-5.8 5.8c-.1.1-.2.2-.3.4-.7 1.2-.4 2.7.8 3.4l5.9 3.7c-.3 1.9.1 3.8 1 5.5.4.7 1.1 1.1 1.8 1.2h.3c.7 0 1.3-.3 1.8-.7l3-3 5.3 5.3c.6.6 1.5.6 2.1 0 .6-.5.6-1.4 0-2zm-11.9-1.7c-.1.1-.3.2-.4.1-.1 0-.3-.1-.4-.2-.8-1.4-1.1-3.1-.7-4.7l.2-.7-7-4.5c-.2-.1-.3-.4-.1-.6 0 0 0-.1.1-.1l5.7-5.8c.1-.1.3-.1.3-.1.1 0 .2 0 .3.1 0 0 0 0 .1.1l4.5 7 .7-.2c1.6-.4 3.3-.1 4.7.7.1.1.2.2.2.3v.4s0 .1-.1.1z"/></svg>
|
||||
|
After Width: | Height: | Size: 630 B |
@@ -17,6 +17,10 @@
|
||||
max-inline-size: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
+ .avatar {
|
||||
margin-inline-start: -0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar__group {
|
||||
|
||||
@@ -62,16 +62,6 @@
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.bubble__container {
|
||||
inline-size: min(67ch, 75vw);
|
||||
max-inline-size: 100dvw;
|
||||
|
||||
@media (max-width: 70ch) {
|
||||
inline-size: min(67ch, 100dvw);
|
||||
}
|
||||
}
|
||||
|
||||
.bubble__detail {
|
||||
--bubble-border-color: color-mix(in srgb, var(--bubble-color) 30%, var(--color-bg));
|
||||
--bubble-border-width: 0.15em;
|
||||
@@ -101,51 +91,6 @@
|
||||
color: var(--bubble-color);
|
||||
}
|
||||
|
||||
&.bubble__boosts {
|
||||
--rotation: 45deg;
|
||||
|
||||
aspect-ratio: 5/7;
|
||||
border-radius: var(--bubble-shape);
|
||||
inline-size: min-content;
|
||||
inset: 70cqi 5cqi auto auto;
|
||||
min-inline-size: 4.5ch;
|
||||
padding: 0;
|
||||
transform: rotate(var(--rotation));
|
||||
|
||||
|
||||
.boost__form {
|
||||
transform: rotate(calc(var(--rotation) * -1));
|
||||
}
|
||||
|
||||
.boost__btn {
|
||||
--btn-background: var(--bubble-color);
|
||||
--btn-border-radius: 50%;
|
||||
--btn-color: var(--color-ink-reversed);
|
||||
--btn-size: 0.5em;
|
||||
--btn-padding: 0.3em 0.5em 0.4em;
|
||||
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.boost__input {
|
||||
color: var(--bubble-color);
|
||||
font-size: inherit;
|
||||
font-weight: 800;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
|
||||
@supports (field-sizing: content) {
|
||||
field-sizing: content;
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.bubbles .bubble:not(.popped):hover & {
|
||||
transform: rotate(calc(var(--rotation) + var(--bubble-shift) * 1)) translate(0rem, 1rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble__assignee {
|
||||
font-size: 4.5cqi;
|
||||
inset: 8em auto auto -1.7em;
|
||||
@@ -321,8 +266,14 @@
|
||||
--divider-offset: 1.2em;
|
||||
--hover-size: 0;
|
||||
|
||||
font-size: var(--text-medium);
|
||||
max-inline-size: 80ch;
|
||||
container-type: inline-size;
|
||||
font-size: 1.4cqi;
|
||||
inline-size: min(90ch, 100dvw);
|
||||
max-inline-size: 100dvw;
|
||||
|
||||
@media (max-width: 70ch) {
|
||||
inline-size: 100%;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
order: 0;
|
||||
@@ -337,7 +288,7 @@
|
||||
margin-inline: var(--divider-offset);
|
||||
|
||||
li {
|
||||
border-radius: 0.6em;
|
||||
border-radius: 0;
|
||||
list-style: none;
|
||||
padding: 0.5em var(--inline-space);
|
||||
position: relative;
|
||||
@@ -350,7 +301,6 @@
|
||||
&:hover {
|
||||
background-color: color(from var(--bubble-color) srgb r g b / 0.15);
|
||||
border: 0;
|
||||
border-radius: 0.6em;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -408,8 +358,9 @@
|
||||
}
|
||||
|
||||
.bubble__title-text {
|
||||
font-weight: 600;
|
||||
font-weight: 700;
|
||||
flex-grow: 1;
|
||||
letter-spacing: -0.015em;
|
||||
white-space: nowrap;
|
||||
|
||||
.drafted & {
|
||||
@@ -550,11 +501,6 @@
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.btn {
|
||||
--btn-background: var(--bubble-color);
|
||||
--btn-color: var(--color-ink-reversed);
|
||||
}
|
||||
|
||||
.btn--plain {
|
||||
--btn-color: var(--bubble-color);
|
||||
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
.cards {
|
||||
--column-gap: 2cqi;
|
||||
--row-gap: 2cqi;
|
||||
|
||||
font-size: 0.9cqi;
|
||||
display: grid;
|
||||
background-color: var(--color-container);
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, calc(100%/2 - var(--column-gap) * 2)), 1fr));
|
||||
justify-items: center;
|
||||
margin-block: 0;
|
||||
padding: 3cqi;
|
||||
view-transition-name: cards-container;
|
||||
|
||||
> * {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.card {
|
||||
max-inline-size: 50cqi;
|
||||
}
|
||||
|
||||
@media (min-width: 70ch) {
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, calc(100%/3 - var(--column-gap) * 2)), 1fr));
|
||||
font-size: 0.7cqi;
|
||||
|
||||
.card {
|
||||
max-inline-size: 33cqi;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
--block-space: 1em;
|
||||
--column-gap: 0.5em;
|
||||
--inline-space: 0.5em;
|
||||
--row-gap: 0.5em;
|
||||
|
||||
aspect-ratio: 15/7;
|
||||
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));
|
||||
font-size: 1em;
|
||||
max-inline-size: 100ch;
|
||||
z-index: 1;
|
||||
|
||||
.card__container & {
|
||||
inline-size: 60cqi;
|
||||
|
||||
@media (max-width: 70ch) {
|
||||
inline-size: 100dvw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card__actions {
|
||||
position: absolute;
|
||||
inset: 1.4em auto auto -4em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.card__actions--right {
|
||||
inset: 1.4em -3.5em auto auto;
|
||||
}
|
||||
|
||||
.card__actions-container {
|
||||
background-color: var(--color-bg);
|
||||
margin-block: -1em 1em;
|
||||
padding: 1em 2em;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
|
||||
.btn {
|
||||
--btn-background: var(--bubble-color);
|
||||
--btn-color: var(--color-ink-reversed);
|
||||
}
|
||||
|
||||
.btn--plain {
|
||||
--btn-color: var(--bubble-color);
|
||||
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.bubble__pop-message {
|
||||
color: var(--bubble-color);
|
||||
}
|
||||
}
|
||||
|
||||
.card__actions-wrapper {
|
||||
font-size: 1.33cqi;
|
||||
}
|
||||
|
||||
.cards .card__assignees:not(:has(.avatar)) {
|
||||
.txt-uppercase {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.card__background {
|
||||
inset: 0;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
|
||||
img {
|
||||
block-size: 100%;
|
||||
inline-size: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
opacity: 0.2;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.card:hover & {
|
||||
img {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card__body {
|
||||
padding-inline: 1em;
|
||||
}
|
||||
|
||||
.card__body-header {
|
||||
flex: 2 1 75%;
|
||||
padding-block: 1em;
|
||||
}
|
||||
|
||||
.card__bucket {
|
||||
background-color: var(--bubble-color);
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.5ch;
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
.card__container {
|
||||
background-color: var(--color-container);
|
||||
display: inline-block;
|
||||
font-size: 1.33cqi;
|
||||
padding: 2cqi 3cqi 3cqi;
|
||||
}
|
||||
|
||||
.card__container--pointing {
|
||||
--arrow-size: 20em;
|
||||
--aspect-ratio: 7;
|
||||
|
||||
position: relative;
|
||||
margin-block-end: calc(var(--arrow-size) / var(--aspect-ratio));
|
||||
|
||||
&::after {
|
||||
aspect-ratio: var(--aspect-ratio);
|
||||
background: var(--color-container);
|
||||
clip-path: polygon(50% 100%, 100% 0, 0 0);
|
||||
inline-size: var(--arrow-size);
|
||||
content: "";
|
||||
display: block;
|
||||
inset-block-start: 99%;
|
||||
inset-inline-start: 50%;
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.card__delete-btn {
|
||||
.cards & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.card__footer {
|
||||
margin-block-end: 1em;
|
||||
padding-inline-end: 1em;
|
||||
}
|
||||
|
||||
.card__header {
|
||||
padding-inline-end: 0.7em;
|
||||
}
|
||||
|
||||
.card__hide-on-index {
|
||||
.cards & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.card__link {
|
||||
--hover-size: 0;
|
||||
|
||||
content: "";
|
||||
inset: 0;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
|
||||
.cards & {
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.card__meta {
|
||||
--border-color: var(--bubble-color);
|
||||
--row-gap: 0.25em;
|
||||
|
||||
font-size: 0.9em;
|
||||
margin-inline-start: 1em;
|
||||
|
||||
strong {
|
||||
font-size: 1.1em;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.separator {
|
||||
height: calc(2lh + 2ex);
|
||||
}
|
||||
}
|
||||
|
||||
.card__people {
|
||||
margin-inline-start: 1em;
|
||||
|
||||
span {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
.card__stages {
|
||||
flex: 1 1 25%;
|
||||
font-size: 0.8em;
|
||||
padding-block: 1em;
|
||||
}
|
||||
|
||||
.card__tags {
|
||||
color: var(--bubble-color);
|
||||
}
|
||||
|
||||
.card__tag {
|
||||
color: var(--bubble-color);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.card__title {
|
||||
--hover-size: 0;
|
||||
--input-border-radius: 0;
|
||||
|
||||
font-size: 2.5em;
|
||||
font-weight: 900;
|
||||
line-height: 1.1;
|
||||
min-block-size: 3lh;
|
||||
text-wrap: balance;
|
||||
|
||||
.cards & {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.card__title-field {
|
||||
&:is(textarea) {
|
||||
|
||||
&::placeholder {
|
||||
color: inherit;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card__title-link {
|
||||
--lines: 3;
|
||||
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.card-count__selector {
|
||||
font-size: 1cqi;
|
||||
inset: 0.5cqi 0.5cqi auto auto;
|
||||
position: absolute;
|
||||
text-align: start;
|
||||
|
||||
.popup {
|
||||
inset: 1em 1em auto auto;
|
||||
}
|
||||
}
|
||||
|
||||
.input.boost__input {
|
||||
--input-border-radius: 0;
|
||||
--input-border-size: 0;
|
||||
--input-padding: 0;
|
||||
|
||||
color: inherit;
|
||||
font-size: 1.1em !important;
|
||||
font-weight: inherit;
|
||||
inline-size: min-content;
|
||||
max-inline-size: 3ch;
|
||||
min-inline-size: 1ch;
|
||||
|
||||
@supports (field-sizing: content) {
|
||||
field-sizing: content;
|
||||
}
|
||||
}
|
||||
|
||||
.card__popped {
|
||||
align-items: center;
|
||||
aspect-ratio: 7/4;
|
||||
border: 0.4rem solid var(--bubble-color);
|
||||
position: absolute;
|
||||
inset: auto 2em 2em auto;
|
||||
justify-content: space-between;
|
||||
padding: 0.5em 1em;
|
||||
transform: rotate(-10deg);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.card__popped-title {
|
||||
color: var(--color-ink-reversed);
|
||||
font-size: 1.5em;
|
||||
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);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
:root {
|
||||
/* Named color values */
|
||||
--lch-black: 0% 0 0;
|
||||
--lch-black: 27.13% 0.1035 284.77;
|
||||
--lch-white: 100% 0 0;
|
||||
--lch-gray-light: 96% 0.005 96;
|
||||
--lch-gray: 92% 0.005 96;
|
||||
@@ -12,9 +12,10 @@
|
||||
--lch-red: 51% 0.2 31;
|
||||
--lch-green: 65.59% 0.234 142.49;
|
||||
--lch-green-light: 95% 0.03 142.49;
|
||||
--lch-yellow: 96.01% 0.0348 81.29;
|
||||
--lch-yellow: 92.62% 0.1 91.5;
|
||||
--lch-always-black: 0% 0 0;
|
||||
--lch-always-white: 100% 0 0;
|
||||
--lch-light-blue: 84.94% 0.048 216.62;
|
||||
|
||||
/* Abstractions */
|
||||
--color-negative: oklch(var(--lch-red));
|
||||
@@ -33,6 +34,7 @@
|
||||
--color-marker: oklch(var(--lch-orange));
|
||||
--color-always-black: oklch(var(--lch-always-black));
|
||||
--color-always-white: oklch(var(--lch-always-white));
|
||||
--color-container: oklch(var(--lch-light-blue));
|
||||
|
||||
/* Redefine named color values for dark mode */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@@ -47,7 +49,8 @@
|
||||
--lch-red: 73.8% 0.184 29.18;
|
||||
--lch-green: 75% 0.21 141.89;
|
||||
--lch-green-light: 28.11% 0.02 142.49;
|
||||
--lch-yellow: 35.29% 0.0493 84.59;
|
||||
--lch-yellow: 40.9% 0.06 88.9;
|
||||
--lch-light-blue: 20.62% 0.048 216.62;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,3 +91,17 @@
|
||||
--btn-size: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.color-picker__button {
|
||||
font-size: 0.75em;
|
||||
|
||||
.cards & {
|
||||
display: none;
|
||||
}
|
||||
|
||||
+ .panel {
|
||||
--panel-size: auto;
|
||||
|
||||
inset: 0 0 auto auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
.comments {
|
||||
--avatar-size: 2.33em;
|
||||
--comment-padding-block: 0.65lh;
|
||||
--comment-padding-block: 0.25lh;
|
||||
--comment-padding-inline: 3ch;
|
||||
|
||||
max-inline-size: 120ch;
|
||||
font-size: 1.33cqi;
|
||||
inline-size: min(67ch, 100dvw);
|
||||
max-inline-size: 100dvw;
|
||||
row-gap: var(--comment-padding-block);
|
||||
|
||||
@media (max-width: 70ch) {
|
||||
inline-size: 100%;
|
||||
}
|
||||
|
||||
> :first-child {
|
||||
margin-block-start: var(--comment-padding-block);
|
||||
/* margin-block-start: var(--comment-padding-block); */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,14 +21,10 @@
|
||||
margin-block: calc(var(--comment-padding-block) * 0.3);
|
||||
margin-inline: 0 auto;
|
||||
position: relative;
|
||||
|
||||
&:not(.comment--new) {
|
||||
max-inline-size: 66ch;
|
||||
}
|
||||
}
|
||||
|
||||
.comment--new {
|
||||
background-color: var(--color-subtle-light);
|
||||
border-block: 1px solid var(--color-subtle-dark);
|
||||
}
|
||||
|
||||
.comment__avatar {
|
||||
@@ -30,7 +32,7 @@
|
||||
margin-inline: auto calc(var(--comment-padding-inline) * -0.75);
|
||||
z-index: 0;
|
||||
|
||||
.comment--mine & {
|
||||
.comment--mine_ & {
|
||||
margin-inline: calc(var(--comment-padding-inline) * -0.75) auto;
|
||||
}
|
||||
}
|
||||
@@ -56,11 +58,10 @@
|
||||
}
|
||||
|
||||
.comment__content {
|
||||
background-color: var(--color-subtle-light);
|
||||
margin-inline: auto var(--avatar-size);
|
||||
margin-inline: auto 0 auto var(--avatar-size);
|
||||
padding: var(--comment-padding-block) var(--comment-padding-inline);
|
||||
|
||||
.comment--mine & {
|
||||
|
||||
.comment--mine_ & {
|
||||
margin-inline: var(--avatar-size) auto;
|
||||
}
|
||||
|
||||
@@ -71,12 +72,11 @@
|
||||
|
||||
.comment__input {
|
||||
--input-border-size: 0;
|
||||
--input-padding: 0;
|
||||
|
||||
line-height: inherit;
|
||||
min-block-size: calc(9lh + (2 * var(--comment-padding-block)));
|
||||
min-inline-size: 100%;
|
||||
padding-block: var(--comment-padding-block);
|
||||
padding-inline: calc(var(--comment-padding-inline) + calc((1lh - 1ex) / 2));
|
||||
|
||||
@supports (field-sizing: content) {
|
||||
field-sizing: content;
|
||||
@@ -84,7 +84,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.comment--mine {
|
||||
.comment--mine_ {
|
||||
flex-direction: row-reverse;
|
||||
margin-inline: auto 0;
|
||||
|
||||
@@ -93,6 +93,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.comment__author {
|
||||
border-block-end: 1px solid var(--color-subtle-dark);
|
||||
margin-block-end: 0.3lh;
|
||||
}
|
||||
|
||||
.comment__submit {
|
||||
inset: auto 0.5em 0.5em auto;
|
||||
position: absolute;
|
||||
@@ -100,13 +105,11 @@
|
||||
}
|
||||
|
||||
.comment__timestamp {
|
||||
color: var(--color-ink);
|
||||
opacity: 0.5;
|
||||
color: var(--color-subtle-dark);
|
||||
}
|
||||
|
||||
.event-summary {
|
||||
color: var(--bubble-color);
|
||||
font-size: 0.875em;
|
||||
font-weight: normal;
|
||||
gap: 0.5ch;
|
||||
opacity: 0.7;
|
||||
|
||||
@@ -1,28 +1,46 @@
|
||||
.events {
|
||||
--grid-lines: 0.3em;
|
||||
|
||||
border-block-start: 1px solid var(--color-subtle);
|
||||
display: grid;
|
||||
gap: 0 var(--grid-lines);
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
padding-block-start: var(--block-space);
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
&:has(.event--related) {
|
||||
.event:not(.event--related) {
|
||||
box-shadow: none;
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
.events__container {
|
||||
display: grid;
|
||||
grid-template-columns: 5fr 2fr;
|
||||
--column-gap: clamp(var(--inline-space), 3cqmin, calc(var(--inline-space) * 3));
|
||||
}
|
||||
|
||||
.events__day-header {
|
||||
background-color: var(--color-bg);
|
||||
block-size: var(--grid-lines);
|
||||
display: block;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.events__day-header-content {
|
||||
background-color: var(--color-bg);
|
||||
border-radius: 2em;
|
||||
display: inline-flex;
|
||||
inset: calc(var(--grid-lines) - 1em) auto -1em;
|
||||
padding: 0.3em 1.3em 0.3em;
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.events__index {
|
||||
--row-gap: calc(4 * var(--block-space));
|
||||
--grid-lines: 0.2em;
|
||||
--row-gap: var(--block-space);
|
||||
}
|
||||
|
||||
.events--none {
|
||||
.events + & {
|
||||
padding-block-start: calc(4 * var(--block-space));
|
||||
}
|
||||
background-color: var(--color-container);
|
||||
border-block-start: var(--grid-lines) solid var(--color-bg);
|
||||
padding-block: 3em;
|
||||
margin: auto var(--grid-lines) calc(var(--grid-lines) * -1) auto;
|
||||
}
|
||||
|
||||
.events__popup {
|
||||
@@ -40,6 +58,7 @@
|
||||
|
||||
.event {
|
||||
--column-gap: 0.7ch;
|
||||
--panel-border-color: transparent;
|
||||
--panel-border-radius: 0.5em;
|
||||
--panel-padding: 0.6em 1.2em 0.6em 0.6em;
|
||||
--panel-size: auto;
|
||||
@@ -50,31 +69,13 @@
|
||||
}
|
||||
|
||||
.event--related {
|
||||
--hover-color: var(--bubble-color);
|
||||
|
||||
z-index: 2;
|
||||
|
||||
box-shadow:
|
||||
0 0 0 1px oklch(var(--lch-always-black) / 0.02),
|
||||
0 .2em 1.6em -0.8em oklch(var(--lch-always-black) / 0.2),
|
||||
0 .4em 2.4em -1em oklch(var(--lch-always-black) / 0.3),
|
||||
0 .4em .8em -1.2em oklch(var(--lch-always-black) / 0.4),
|
||||
0 .8em 1.2em -1.6em oklch(var(--lch-always-black) / 0.5),
|
||||
0 1.2em 1.6em -2em oklch(var(--lch-always-black) / 0.6),
|
||||
0 0 0.3em 0.3em var(--bubble-color) !important;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
box-shadow:
|
||||
0 0 0 1px oklch(var(--lch-always-black) / 0.42),
|
||||
0 .2em 1.6em -0.8em oklch(var(--lch-always-black) / 0.6),
|
||||
0 .4em 2.4em -1em oklch(var(--lch-always-black) / 0.7),
|
||||
0 .4em .8em -1.2em oklch(var(--lch-always-black) / 0.8),
|
||||
0 .8em 1.2em -1.6em oklch(var(--lch-always-black) / 0.9),
|
||||
0 1.2em 1.6em -2em oklch(var(--lch-always-black) / 1),
|
||||
0 0 0.3em 0.3em var(--bubble-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.event__grid-item {
|
||||
background-color: var(--color-selected);
|
||||
background-color: var(--color-container);
|
||||
block-size: 100%;
|
||||
border-radius: 0;
|
||||
display: flex;
|
||||
@@ -84,8 +85,10 @@
|
||||
.event__grid-column-title {
|
||||
--z: 3;
|
||||
|
||||
background-color: var(--color-bg);
|
||||
padding-block: var(--grid-lines);
|
||||
background-color: var(--color-container);
|
||||
font-size: 0.9em;
|
||||
padding: 2em 0 1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.event__timestamp {
|
||||
@@ -103,10 +106,10 @@
|
||||
.event__wrapper {
|
||||
align-content: end;
|
||||
display: grid;
|
||||
gap: var(--grid-lines);
|
||||
gap: calc(var(--grid-lines) * 2);
|
||||
justify-items: center;
|
||||
margin: var(--grid-lines);
|
||||
padding: calc(var(--grid-lines) / 2);
|
||||
padding: var(--grid-lines);
|
||||
|
||||
.event {
|
||||
grid-column-start: unset !important;
|
||||
|
||||
@@ -13,7 +13,7 @@ house-md {
|
||||
/* Toolbar */
|
||||
house-md-toolbar {
|
||||
background-color: inherit;
|
||||
border-block-end: 1px solid var(--color-subtle);
|
||||
border-block-end: 1px solid var(--color-subtle-dark);
|
||||
border-radius: 0;
|
||||
color: currentColor;
|
||||
display: inline-flex;
|
||||
|
||||
@@ -95,18 +95,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
.input--textara {
|
||||
.input--textarea {
|
||||
--input-padding: 0;
|
||||
|
||||
line-height: inherit;
|
||||
min-block-size: calc(4lh + (2 * var(--input-padding)));
|
||||
min-block-size: calc(3lh + (2 * var(--input-padding)));
|
||||
min-inline-size: 100%;
|
||||
padding-block: var(--input-padding);
|
||||
padding-inline: calc(var(--input-padding) + calc((1lh - 1ex) / 2));
|
||||
|
||||
@supports (field-sizing: content) {
|
||||
field-sizing: content;
|
||||
max-block-size: calc(5lh + (2 * var(--input-padding)));
|
||||
max-block-size: calc(3lh + (2 * var(--input-padding)));
|
||||
min-block-size: calc(1lh + (2 * var(--input-padding)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,6 @@ body {
|
||||
text-justify: distribute;
|
||||
widows: 2;
|
||||
}
|
||||
|
||||
&:has(.bubble__perma) {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr minmax(min-content, auto) 1fr;
|
||||
gap: var(--inline-space);
|
||||
}
|
||||
}
|
||||
|
||||
:where(#footer) {
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
.pin-tray {
|
||||
--size: 38ch;
|
||||
--height: calc(5ch + 1lh + var(--block-space));
|
||||
|
||||
block-size: var(--height);
|
||||
inline-size: var(--size);
|
||||
inset: auto auto var(--block-space-half) var(--inline-space);
|
||||
padding: var(--block-space-half) var(--inline-space);
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
|
||||
.pin {
|
||||
--offset: calc((var(--position) - 1) * (var(--block-space) * -1));
|
||||
--position: 1;
|
||||
--scale: calc(1 - var(--position) / 30);
|
||||
--z: calc(6 - var(--position));
|
||||
|
||||
inset: auto auto 0;
|
||||
inline-size: var(--size);
|
||||
outline: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
scale: var(--scale);
|
||||
transform: translateY(var(--offset));
|
||||
transform-origin: center;
|
||||
transition: scale 0.2s ease-out, transform 0.2s ease-out;
|
||||
z-index: var(--z);
|
||||
|
||||
&:nth-child(1) { --position: 1; }
|
||||
&:nth-child(2) { --position: 2; }
|
||||
&:nth-child(3) { --position: 3; }
|
||||
&:nth-child(4) { --position: 4; }
|
||||
&:nth-child(5) { --position: 5; }
|
||||
&:nth-child(6) { --position: 6; }
|
||||
|
||||
&:nth-child(1n + 7) { display: none; }
|
||||
|
||||
.pin-tray[open] & {
|
||||
--scale: 1;
|
||||
--offset: calc((100% + var(--block-space-half)) * (var(--position) * -1));
|
||||
|
||||
margin-block-end: calc(var(--block-space) * -1);
|
||||
pointer-events: unset;
|
||||
}
|
||||
|
||||
.pin-tray:has(.pin:only-child) & {
|
||||
pointer-events: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pin-tray__actions {
|
||||
opacity: 0;
|
||||
scale: calc(1 - 1 / 30);
|
||||
transform: translateY(100%);
|
||||
transition: opacity 0.2s ease-out, scale 0.2s ease-out, transform 0.2s ease-out;
|
||||
|
||||
.pin-tray[open] & {
|
||||
opacity: 1;
|
||||
scale: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.pin-tray:has(.pin) & {
|
||||
.btn img {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.pin-tray:not(:has(.pin)) & {
|
||||
opacity: 1;
|
||||
scale: 1;
|
||||
transform: none;
|
||||
transition: none;
|
||||
|
||||
.pin-tray__all_action {
|
||||
--btn-border-radius: 50%;
|
||||
--btn-padding: 0;
|
||||
|
||||
aspect-ratio: 1;
|
||||
block-size: var(--btn-size);
|
||||
display: grid;
|
||||
inline-size: var(--btn-size);
|
||||
place-items: center;
|
||||
|
||||
> * {
|
||||
grid-area: 1/1;
|
||||
}
|
||||
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.pin-tray__read_action {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pin__content {
|
||||
color: var(--color-ink);
|
||||
border: 1px solid;
|
||||
|
||||
.notificiations-list--read & {
|
||||
border: 1px solid var(--color-subtle);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.pin__title {
|
||||
padding-inline-end: calc(var(--inline-space-half) + 1.2em);
|
||||
}
|
||||
|
||||
.pin-tray__expander {
|
||||
--hover-size: 0;
|
||||
--outline-size: 0;
|
||||
|
||||
cursor: pointer;
|
||||
inset: 0;
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
|
||||
.pin-tray[open] &,
|
||||
.pin-tray:not(:has(.pin)) &,
|
||||
.pin-tray:has(.pin:only-child) & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.pin-tray__overflow {
|
||||
--offset: calc((var(--position) - 1) * (var(--block-space) * -1));
|
||||
--position: 7;
|
||||
--scale: calc(1 - var(--position) / 30);
|
||||
--z: calc(6 - var(--position));
|
||||
|
||||
block-size: var(--height);
|
||||
display: none;
|
||||
inset: auto auto 0;
|
||||
inline-size: var(--size);
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
scale: var(--scale);
|
||||
transform: translateY(var(--offset));
|
||||
transition: scale 0.2s ease-out, transform 0.2s ease-out;
|
||||
z-index: var(--z);
|
||||
|
||||
.pin-tray:has(.pin:nth-child(7)) & {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.pin-tray[open] & {
|
||||
--offset: calc((100% + var(--block-space-half)) * (var(--position) * -1));
|
||||
--scale: 1;
|
||||
|
||||
pointer-events: unset;
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,11 @@
|
||||
--panel-border-radius: 0.5em;
|
||||
--panel-padding: 0.4em 0.5em 0.5em;
|
||||
--panel-size: auto;
|
||||
|
||||
|
||||
inline-size: auto;
|
||||
inset: 0 auto auto 0;
|
||||
max-inline-size: 30ch;
|
||||
min-inline-size: 20ch;
|
||||
max-inline-size: 40ch;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.search {
|
||||
min-inline-size: 38ch;
|
||||
}
|
||||
@@ -19,3 +19,12 @@
|
||||
inline-size: 18ch;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-picker__button {
|
||||
font-size: 0.7em;
|
||||
margin-inline-start: 0.5em;
|
||||
|
||||
+ .panel {
|
||||
--panel-size: 18ch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
.align-start { align-items: start; }
|
||||
.align-end { align-items: end; }
|
||||
|
||||
.align-self-end { align-self: end; }
|
||||
.align-self-start { align-self: start; }
|
||||
|
||||
.contain { contain: inline-size; }
|
||||
@@ -166,6 +167,7 @@
|
||||
.fill-white { background-color: var(--color-ink-reversed); }
|
||||
.fill-shade { background-color: var(--color-subtle-light); }
|
||||
.fill-selected { background-color: var(--color-selected); }
|
||||
.fill-highlight { background-color: var(--color-highlight); }
|
||||
.fill-transparent { background-color: transparent; }
|
||||
|
||||
.translucent { opacity: var(--opacity, 0.5); }
|
||||
@@ -219,6 +221,7 @@
|
||||
|
||||
/* Separators */
|
||||
.separator {
|
||||
block-size: 100%;
|
||||
border-block: 0;
|
||||
border-inline-end: 0;
|
||||
border-inline-start: var(--border-size, 1px) var(--border-style, solid) var(--border-color, currentColor);
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
.workflow-stage {
|
||||
--btn-border-radius: 0.5em;
|
||||
--btn-background: transparent;
|
||||
--btn-padding: 0.2em 0.5em;
|
||||
--btn-border-size: 0;
|
||||
--btn-border-radius: 0.2em;
|
||||
--hover-size: 0;
|
||||
|
||||
color: inherit;
|
||||
text-transform: uppercase;
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
background-color: color(from var(--bubble-color) srgb r g b / 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.bubbles-list & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.workflow-stage--current {
|
||||
@@ -19,6 +29,23 @@
|
||||
background-color: color(from var(--bubble-color) srgb r g b / 0.65);
|
||||
}
|
||||
}
|
||||
|
||||
.ticket & {
|
||||
--btn-background: var(--color-ink);
|
||||
|
||||
background: var(--bubble-color);
|
||||
border-radius: 0.2em;
|
||||
color: var(--color-ink-reversed);
|
||||
}
|
||||
|
||||
.bubbles-list & {
|
||||
display: flex;
|
||||
background: transparent;
|
||||
border-radius: 0.2em;
|
||||
color: var(--color-ink);
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.workflow__popup {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class Bubbles::Pins::TraysController < ApplicationController
|
||||
def show
|
||||
@pins = Current.user.pins.includes(:bubble).ordered.limit(20)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
class Bubbles::PinsController < ApplicationController
|
||||
include BubbleScoped, BucketScoped
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
def create
|
||||
@bubble.set_pinned(Current.user, true)
|
||||
redirect_to bucket_bubble_pin_path(@bucket, @bubble)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@bubble.set_pinned(Current.user, false)
|
||||
redirect_to bucket_bubble_pin_path(@bucket, @bubble)
|
||||
end
|
||||
end
|
||||
@@ -5,9 +5,14 @@ class BubblesController < ApplicationController
|
||||
|
||||
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
|
||||
|
||||
def index
|
||||
@bubbles = @filter.bubbles.published_or_drafted_by(Current.user)
|
||||
@display_count = display_count
|
||||
end
|
||||
|
||||
def create
|
||||
@@ -46,4 +51,17 @@ class BubblesController < ApplicationController
|
||||
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
|
||||
|
||||
@@ -6,6 +6,7 @@ class EventsController < ApplicationController
|
||||
update_bucket_filter
|
||||
@buckets = Current.user.buckets.alphabetically
|
||||
@events = events_by_hour_and_column
|
||||
@filters = Current.user.filters.all
|
||||
@next_day = latest_event_before_today&.created_at
|
||||
end
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class WorkflowsController < ApplicationController
|
||||
def create
|
||||
@workflow = Current.account.workflows.create! workflow_params
|
||||
# FIXME: this should definitely change.
|
||||
%w[ Triage WIP On-hold ].each { |name| @workflow.stages.create! name: name }
|
||||
[ "Maybe?", "Not now", "Done" ].each { |name| @workflow.stages.create! name: name }
|
||||
redirect_to workflows_path
|
||||
end
|
||||
|
||||
|
||||
@@ -10,4 +10,15 @@ module BubblesHelper
|
||||
|
||||
"--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
|
||||
|
||||
@@ -6,7 +6,7 @@ module CommentsHelper
|
||||
|
||||
def new_comment_placeholder(bubble)
|
||||
if bubble.creator == Current.user && bubble.messages.comments.empty?
|
||||
"Add some notes…"
|
||||
"Next, add some notes, context, pictures, or video about this…"
|
||||
else
|
||||
"Type your comment…"
|
||||
end
|
||||
|
||||
@@ -13,10 +13,8 @@ module EventsHelper
|
||||
def event_column(event)
|
||||
case event.action
|
||||
when "popped"
|
||||
4
|
||||
when "published"
|
||||
3
|
||||
when "commented"
|
||||
when "published"
|
||||
2
|
||||
else
|
||||
1
|
||||
@@ -53,15 +51,14 @@ module EventsHelper
|
||||
.where(bubbles: { bucket_id: params[:bucket_ids].presence || Current.user.bucket_ids })
|
||||
|
||||
headers = {
|
||||
"Touched" => nil,
|
||||
"Discussed" => nil,
|
||||
"Updated" => nil,
|
||||
"Added" => accessible_events.where(action: "published").count,
|
||||
"Popped" => accessible_events.where(action: "popped").joins(:creator).merge(User.without_system).count
|
||||
}
|
||||
|
||||
headers.map do |header, count|
|
||||
title = count&.positive? ? "#{header} (#{count})" : header
|
||||
content_tag(:h3, title, class: "event__grid-column-title margin-block-end-half position-sticky")
|
||||
content_tag(:h3, title, class: "event__grid-column-title position-sticky")
|
||||
end.join.html_safe
|
||||
end
|
||||
|
||||
@@ -69,30 +66,30 @@ module EventsHelper
|
||||
case event.action
|
||||
when "assigned"
|
||||
if event.assignees.include?(Current.user)
|
||||
"#{ event.creator.name } will handle <span class='txt-link'>#{ event.bubble.title }</span>".html_safe
|
||||
"#{ event.creator.name } will handle <span style='color: var(--bubble-color)'>#{ event.bubble.title }</span>".html_safe
|
||||
else
|
||||
"#{ event.creator.name } assigned #{ event.assignees.pluck(:name).to_sentence } to <span class='txt-link'>#{ event.bubble.title }</span>".html_safe
|
||||
"#{ event.creator.name } assigned #{ event.assignees.pluck(:name).to_sentence } to <span style='color: var(--bubble-color)'>#{ event.bubble.title }</span>".html_safe
|
||||
end
|
||||
when "unassigned"
|
||||
"#{ event.creator.name } unassigned #{ event.assignees.pluck(:name).to_sentence } from <span class='txt-link'>#{ event.bubble.title }</span>".html_safe
|
||||
"#{ event.creator.name } unassigned #{ event.assignees.pluck(:name).to_sentence } from <span style='color: var(--bubble-color)'>#{ event.bubble.title }</span>".html_safe
|
||||
when "boosted"
|
||||
"#{ event.creator.name } boosted <span class='txt-link'>#{ event.bubble.title }</span>".html_safe
|
||||
"#{ event.creator.name } boosted <span style='color: var(--bubble-color)'>#{ event.bubble.title }</span>".html_safe
|
||||
when "commented"
|
||||
"#{ event.creator.name } commented on <span class='txt-link'>#{ event.bubble.title }</span>".html_safe
|
||||
"#{ event.creator.name } commented on <span style='color: var(--bubble-color)'>#{ event.bubble.title }</span>".html_safe
|
||||
when "published"
|
||||
"#{ event.creator.name } added <span class='txt-link'>#{ event.bubble.title }</span>".html_safe
|
||||
"#{ event.creator.name } added <span style='color: var(--bubble-color)'>#{ event.bubble.title }</span>".html_safe
|
||||
when "popped"
|
||||
"#{ event.creator.name } popped <span class='txt-link'>#{ event.bubble.title }</span>".html_safe
|
||||
"#{ event.creator.name } popped <span style='color: var(--bubble-color)'>#{ event.bubble.title }</span>".html_safe
|
||||
when "staged"
|
||||
"#{event.creator.name} changed the stage to #{event.stage_name} on <span class='txt-link'>#{ event.bubble.title }</span>".html_safe
|
||||
"#{event.creator.name} changed the stage to #{event.stage_name} on <span style='color: var(--bubble-color)'>#{ event.bubble.title }</span>".html_safe
|
||||
when "due_date_added"
|
||||
"#{event.creator.name} set the date to #{event.particulars.dig('particulars', 'due_date').to_date.strftime('%B %-d')} on <span class='txt-link'>#{ event.bubble.title }</span>".html_safe
|
||||
"#{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)'>#{ event.bubble.title }</span>".html_safe
|
||||
when "due_date_changed"
|
||||
"#{event.creator.name} changed the date to #{event.particulars.dig('particulars', 'due_date').to_date.strftime('%B %-d')} on <span class='txt-link'>#{ event.bubble.title }</span>".html_safe
|
||||
"#{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)'>#{ event.bubble.title }</span>".html_safe
|
||||
when "due_date_removed"
|
||||
"#{event.creator.name} removed the date on <span class='txt-link'>#{ event.bubble.title }</span>"
|
||||
"#{event.creator.name} removed the date on <span style='color: var(--bubble-color)'>#{ event.bubble.title }</span>"
|
||||
when "title_changed"
|
||||
"#{event.creator.name} renamed on <span class='txt-link'>#{ event.bubble.title }</span> (was: '#{event.particulars.dig('particulars', 'old_title')})'".html_safe
|
||||
"#{event.creator.name} renamed on <span style='color: var(--bubble-color)'>#{ event.bubble.title }</span> (was: '#{event.particulars.dig('particulars', 'old_title')})'".html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,7 +5,7 @@ module WorkflowsHelper
|
||||
|
||||
def button_to_set_stage(bubble, stage)
|
||||
button_to bucket_bubble_stagings_path(bubble.bucket, bubble, stage_id: stage),
|
||||
method: :post, class: [ "btn full-width justify-start borderless workflow-stage", { "workflow-stage--current": stage == bubble.stage } ],
|
||||
method: :post, class: [ "btn full-width justify-start workflow-stage txt-uppercase", { "workflow-stage--current": stage == bubble.stage } ],
|
||||
form_class: "flex align-center gap-half",
|
||||
data: { turbo_frame: "_top" } do
|
||||
tag.span class: "overflow-ellipsis" do
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class Bubble < ApplicationRecord
|
||||
include Assignable, Boostable, Colored, Commentable, Eventable,
|
||||
Messages, Notifiable, Poppable, Scorable, Searchable, Staged, Statuses, Taggable, Watchable
|
||||
Messages, Notifiable, Pinnable, Poppable, Scorable, Searchable, Staged, Statuses, Taggable, Watchable
|
||||
|
||||
belongs_to :bucket, touch: true
|
||||
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
||||
@@ -11,6 +11,7 @@ class Bubble < ApplicationRecord
|
||||
|
||||
after_save :track_due_date_change, if: :saved_change_to_due_on?
|
||||
after_save :track_title_change, if: :saved_change_to_title?
|
||||
after_create :assign_initial_stage
|
||||
|
||||
scope :reverse_chronologically, -> { order created_at: :desc, id: :desc }
|
||||
scope :chronologically, -> { order created_at: :asc, id: :asc }
|
||||
@@ -48,4 +49,12 @@ class Bubble < ApplicationRecord
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def assign_initial_stage
|
||||
if workflow_stage = bucket.account.workflows.first&.stages&.first
|
||||
self.stage = workflow_stage
|
||||
save! touch: false
|
||||
track_event :staged, stage_id: workflow_stage.id, stage_name: workflow_stage.name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
module Bubble::Colored
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
COLORS = %w[ #BF1B1B #ED3F1C #ED8008 #7C956B #266ec3 #3B3633 ]
|
||||
COLORS = %w[ #b7462b #ff63a8 #eb7a32 #6ac967 #2c6da8 #663251 ]
|
||||
|
||||
included do
|
||||
attribute :color, default: "#266ec3"
|
||||
attribute :color, default: "#2c6da8"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
module Bubble::Pinnable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :pins, dependent: :destroy
|
||||
end
|
||||
|
||||
def pinned_by?(user)
|
||||
pins.exists?(user: user)
|
||||
end
|
||||
|
||||
def set_pinned(user, pinned)
|
||||
if pinned
|
||||
pins.find_or_create_by!(user: user)
|
||||
else
|
||||
pins.find_by(user: user)&.destroy
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -9,5 +9,15 @@ class Bucket < ApplicationRecord
|
||||
|
||||
validates_presence_of :name
|
||||
|
||||
after_create :ensure_workflow_exists
|
||||
|
||||
scope :alphabetically, -> { order(name: :asc) }
|
||||
|
||||
private
|
||||
def ensure_workflow_exists
|
||||
unless account.workflows.exists?
|
||||
workflow = account.workflows.create!(name: "Default Workflow")
|
||||
[ "Maybe?", "Not now", "Done" ].each { |name| workflow.stages.create!(name: name) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
module Filter::Params
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
PERMITTED_PARAMS = %i[
|
||||
indexed_by assignment_status bucket_ids creator_ids
|
||||
assignee_ids stage_ids tag_ids terms display_count
|
||||
].freeze
|
||||
|
||||
def as_params
|
||||
self.class.normalize_params(
|
||||
indexed_by: indexed_by,
|
||||
assignment_status: assignment_status,
|
||||
bucket_ids: buckets.ids,
|
||||
creator_ids: creators.ids,
|
||||
assignee_ids: assignees.ids,
|
||||
stage_ids: stages.ids,
|
||||
tag_ids: tags.ids,
|
||||
terms: terms,
|
||||
display_count: display_count
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
class Pin < ApplicationRecord
|
||||
belongs_to :bubble
|
||||
belongs_to :user
|
||||
|
||||
scope :ordered, -> { order(created_at: :desc) }
|
||||
|
||||
after_create_commit -> { broadcast_prepend_later_to [ user, :pins ],
|
||||
target: "pins",
|
||||
partial: "bubbles/pins/pin"
|
||||
}
|
||||
after_destroy_commit -> { broadcast_remove_to [ user, :pins ] }
|
||||
end
|
||||
@@ -23,6 +23,9 @@ class User < ApplicationRecord
|
||||
|
||||
has_one_attached :avatar
|
||||
|
||||
has_many :pins, dependent: :destroy
|
||||
has_many :pinned_bubbles, through: :pins, source: :bubble
|
||||
|
||||
normalizes :email_address, with: ->(value) { value.strip.downcase }
|
||||
|
||||
after_create_commit :grant_access_to_buckets
|
||||
|
||||
@@ -2,20 +2,16 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<nav class="align-start">
|
||||
<%= link_to root_path, class: "btn borderless flex-item-justify-start" do %>
|
||||
<%= link_to root_path, class: "btn flex-item-justify-start", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<%= image_tag "arrow-left.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Go back</span>
|
||||
<% end %>
|
||||
|
||||
<header class="flex flex-column center">
|
||||
<div class="flex align-center gap-half justify-center">
|
||||
<%= link_to "Collections", root_path %>
|
||||
<%= link_to "Activity", events_path %>
|
||||
</div>
|
||||
<h1 class="txt-x-large"><%= @page_title %></h1>
|
||||
</header>
|
||||
|
||||
<%= link_to workflows_path, class: "btn flex-item-justify-end borderless" do %>
|
||||
<%= link_to workflows_path, class: "btn flex-item-justify-end" do %>
|
||||
<%= image_tag "bolt.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Workflows</span>
|
||||
<% end %>
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
<%= tag.div class: "bubble__detail bubble__boosts",
|
||||
role: "group", aria: { label: "Boosts" },
|
||||
data: {
|
||||
controller: "animation toggle-class",
|
||||
animation_play_class: "boosting",
|
||||
toggle_class_toggle_class: "boosting",
|
||||
action: "animationend->toggle-class#toggle" },
|
||||
hidden: !bubble.boosts_count.positive? do %>
|
||||
<%= form_with url: bucket_bubble_boosts_path(bubble.bucket, bubble),
|
||||
class: "boost__form",
|
||||
data: { action: "toggle-class#toggle" } do %>
|
||||
<%= text_field_tag :boost_count, bubble.boosts_count, min: 1, class: "input borderless boost__input", autocomplete: "off", aria: { label: "Boost count" } %>
|
||||
<%= tag.button "+", class: "btn btn--plain boost__btn", type: :submit, aria: { label: "Boost" } %>
|
||||
<%= form_with url: bucket_bubble_boosts_path(bubble.bucket, bubble), class: "pad-inline flex align-center gap" do %>
|
||||
<span class="overflow-ellipsis">
|
||||
<strong><%= text_field_tag :boost_count, bubble.boosts_count, min: 1, class: "boost__input input", autocomplete: "off" %></strong>
|
||||
<span><%= bubble.boosts_count == 1 ? "boost" : "boosts" %></span>
|
||||
</span>
|
||||
<%= tag.button class: "btn", type: :submit, style: "font-size: 0.5em" do %>
|
||||
<%= image_tag "add.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Boost</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<% bubble.assignees.each do |assignee| %>
|
||||
<article class="bubble__detail bubble__assignee">
|
||||
<div class="btn btn--plain position-relative fill-transparent">
|
||||
<span class="assignee__name" aria-hidden="true"><span><%= assignee.name %></span></span>
|
||||
<%= avatar_image_tag assignee, loading: :lazy, class: "avatar flex-item-no-shrink", aria: { hidden: true } %>
|
||||
<span class="for-screen-reader">Assigned to <%= assignee.name %></span>
|
||||
</div>
|
||||
</article>
|
||||
<% end %>
|
||||
@@ -20,7 +20,7 @@
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= form_with model: bubble, url: bucket_bubble_path(bubble.bucket, bubble), id: "bubble_form", data: { controller: "auto-save" } do |form| %>
|
||||
<%= form.text_area :title, placeholder: "Name it…", class: "input input--textara txt-align-center full-width borderless bubble__title-rendered", autofocus: bubble.title.blank?, data: { action: "auto-save#change blur->auto-save#submit keydown.enter->auto-save#submit:prevent keydown.ctrl+enter->auto-save#submit:prevent" } %>
|
||||
<%= form.text_area :title, placeholder: "Name it…", class: "input full-width borderless txt-align-start card__title-field", autofocus: bubble.title.blank?, data: { action: "auto-save#change blur->auto-save#submit keydown.enter->auto-save#submit:prevent keydown.ctrl+enter->auto-save#submit:prevent" } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<% cache bubble do %>
|
||||
<article class="card flex flex-column position-relative txt-align-start full-width"
|
||||
style="--bubble-color: <%= bubble.color %>; view-transition-name: <%= dom_id(bubble, :ticket) %>;" id="<%= dom_id(bubble, :ticket) %>">
|
||||
<header class="card__header flex align-center gap min-width">
|
||||
<%= link_to bubble.bucket.name, bubbles_path(bucket_ids: [ bubble.bucket ]),
|
||||
class: "card__bucket txt-uppercase overflow-ellipsis txt-reversed" %>
|
||||
|
||||
<%= render "bubbles/tags", bubble: bubble %>
|
||||
</header>
|
||||
|
||||
<div class="card__body flex gap full-width flex-item-grow">
|
||||
<div class="card__body-header">
|
||||
<h1 class="card__title">
|
||||
<% if bubble.published? %>
|
||||
<%= turbo_frame_tag bubble, :edit do %>
|
||||
<%= link_to bubble_title(bubble), edit_bucket_bubble_path(bubble.bucket, bubble), class: "card__title-link overflow-line-clamp" %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= form_with model: bubble, url: bucket_bubble_path(bubble.bucket, bubble), id: "bubble_form", data: { controller: "auto-save" } do |form| %>
|
||||
<%= form.text_area :title, placeholder: "Name it…",
|
||||
class: "input input--textarea full-width borderless txt-align-start #{ "fill-highlight" if bubble.creating? }",
|
||||
autofocus: bubble.title.blank?,
|
||||
data: { action: "auto-save#change blur->auto-save#submit keydown.enter->auto-save#submit:prevent keydown.ctrl+enter->auto-save#submit:prevent" } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</h1>
|
||||
<%= link_to bucket_bubble_path(bubble.bucket, bubble), class: "card__link" do %>
|
||||
<span class="for-screen-reader"><%= bubble.title %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= turbo_frame_tag dom_id(bubble, :stage_picker), src: new_bucket_bubble_stage_picker_path(bubble.bucket, bubble) %>
|
||||
</div>
|
||||
|
||||
<footer class="card__footer full-width flex align-center gap">
|
||||
<%= render "bubbles/people", bubble: bubble %>
|
||||
<%= render "bubbles/meta", bubble: bubble %>
|
||||
</footer>
|
||||
|
||||
<div class="card__background">
|
||||
<%= image_tag bubble.image.presence || "", size: 120, data: { upload_preview_target: "image" } %>
|
||||
</div>
|
||||
|
||||
<% if bubble.popped? %>
|
||||
<div class="card__popped flex flex-column">
|
||||
<span class="card__popped-title txt-uppercase">Completed</span>
|
||||
<strong><%= bubble.popped_at.strftime("%b %d, %Y") %></strong>
|
||||
<span>by <span style="border-block-end: 1px dashed var(--color-ink);"><%= bubble.popped_by.name %></span></span>
|
||||
</div>
|
||||
<% end %>
|
||||
</article>
|
||||
<% end %>
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close">
|
||||
<button class="btn full-width justify-start borderless" data-action="dialog#open:stop">
|
||||
<button class="btn" data-action="dialog#open:stop">
|
||||
<%= image_tag "art.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span>Color</span>
|
||||
<span class="for-screen-reader">Change color</span>
|
||||
</button>
|
||||
|
||||
<dialog class="color-picker panel fill-white shadow gap-half" data-dialog-target="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<div class="card-count__selector" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="btn borderless btn--circle"
|
||||
data-action="click->dialog#open:stop"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded="false"
|
||||
aria-controls="display-count-dialog">
|
||||
<span><%= display_count %></span>
|
||||
</button>
|
||||
|
||||
<dialog id="display-count-dialog"
|
||||
class="popup panel flex-column align-start justify-start fill-white shadow"
|
||||
data-dialog-target="dialog"
|
||||
aria-label="Select number of tickets to display">
|
||||
<% display_count_options.each do |option| %>
|
||||
<div class="btn fill-transparent popup__item full-width">
|
||||
<%= form_tag request.path, method: :get, class: "full-width" do %>
|
||||
<% filter.as_params.each do |key, value| %>
|
||||
<% if value.is_a?(Array) %>
|
||||
<% value.each do |v| %>
|
||||
<%= filter_hidden_field_tag key, v %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= filter_hidden_field_tag key, value %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= hidden_field_tag :set_display_count, option[:value] %>
|
||||
<input type="checkbox"
|
||||
id="<%= option[:id] %>"
|
||||
<%= "checked" if option[:selected] %>
|
||||
onChange="this.form.submit()"
|
||||
aria-label="<%= option[:label] %>">
|
||||
<label for="<%= option[:id] %>" class="overflow-ellipsis"><%= option[:label] %></label>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</dialog>
|
||||
</div>
|
||||
@@ -1,14 +1,5 @@
|
||||
<div class="flex flex-column gap-half align-center" data-controller="dialog filter-form"
|
||||
data-action="keydown.esc->dialog#close:stop click@document->dialog#closeOnClickOutside turbo:before-cache@document->dialog#close">
|
||||
<h1 class="txt-x-large margin-none-block-end txt-tight-lines">
|
||||
<% if filter.buckets.none? %>
|
||||
All collections
|
||||
<% elsif filter.buckets.one? %>
|
||||
<%= filter.buckets.first.name %>
|
||||
<% else %>
|
||||
<%= filter.buckets.map(&:name).to_sentence %>
|
||||
<% end %>
|
||||
</h1>
|
||||
|
||||
<%= render "filters/settings", filter: filter %>
|
||||
<%= render "filters/dialog", filter: filter %>
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
<div class="bubble__image">
|
||||
<%= image_tag bubble.image.presence || "", data: { upload_preview_target: "image" }, aria: { hidden: true } %>
|
||||
</div>
|
||||
<% if bubble.image.attached? %>
|
||||
<%= button_to bucket_bubble_image_path(bubble.bucket, bubble), method: :delete, class: "btn" do %>
|
||||
<%= image_tag "picture-remove.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Clear background image</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= form_with model: bubble, url: bucket_bubble_path(bubble.bucket, bubble), data: { controller: "form" } do |form| %>
|
||||
<label class="btn">
|
||||
<%= image_tag "picture-add.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Add background image</span>
|
||||
<%= form.file_field :image, class: "input",
|
||||
accept: "image/png, image/jpeg, image/jpg, image/webp",
|
||||
data: { action: "upload-preview#previewImage form#submit", upload_preview_target: "input" },
|
||||
hidden: true %>
|
||||
</label>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -3,4 +3,11 @@
|
||||
<%# Template Dependency: event_summaries/event_summary %>
|
||||
<%= render bubble.messages, cached: true %>
|
||||
<%= render "comments/new", bubble: bubble %>
|
||||
|
||||
<div class="flex flex-column gap-half justify-start full-width txt-align-start">
|
||||
<span>Watching this…</span>
|
||||
<div class="flex align-center flex-wrap gap-half max-width">
|
||||
<%= render partial: "bubbles/watches/watcher", collection: bubble.watchers_and_subscribers.sorted_with_user_first(Current.user) %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<div class="card__meta flex txt-tight-lines txt-uppercase">
|
||||
<div class="flex flex-column gap txt-align-end">
|
||||
<span class="pad-inline overflow-ellipsis"><%= bubble.creating? ? "Added" : "Updated" %>
|
||||
<%= days = (Date.current - bubble.updated_at.to_date).to_i
|
||||
case days
|
||||
when 0 then "<strong>Today</strong>".html_safe
|
||||
when 1 then "<strong>Yesterday</strong>".html_safe
|
||||
else "<strong>#{days}</strong> days ago".html_safe
|
||||
end %>
|
||||
</span>
|
||||
|
||||
<hr class="full-width separator--horizontal unpad margin-none card__hide-on-index">
|
||||
|
||||
<% if bubble.creating? %>
|
||||
<span class="pad-inline overflow-ellipsis card__hide-on-index">Expires
|
||||
<%= days = (bubble.auto_pop_at.to_date - Date.current).to_i
|
||||
case days
|
||||
when 0 then "<strong>Today</strong>".html_safe
|
||||
else "in <strong>#{days}</strong> days".html_safe
|
||||
end %>
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="pad-inline overflow-ellipsis card__hide-on-index">Added
|
||||
<%= days = (Date.current - bubble.created_at.to_date).to_i
|
||||
case days
|
||||
when 0 then "<strong>Today</strong>".html_safe
|
||||
when 1 then "<strong>Yesterday</strong>".html_safe
|
||||
else "<strong>#{days}</strong> days ago".html_safe
|
||||
end %>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<hr class="separator margin-none unpad card__hide-on-index">
|
||||
|
||||
<div class="flex flex-column gap txt-align-start card__hide-on-index">
|
||||
<span class="pad-inline overflow-ellipsis">
|
||||
<strong><%= bubble.comments_count %></strong> <%= bubble.comments_count == 1 ? "comment" : "comments" %>
|
||||
</span>
|
||||
|
||||
<hr class="full-width separator--horizontal unpad margin-none">
|
||||
|
||||
<%= render "bubbles/boosts", bubble: bubble %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,17 @@
|
||||
<div class="card__people flex gap">
|
||||
<div class="flex flex-column align-center">
|
||||
<span class="txt-uppercase">Reported</span>
|
||||
<%= avatar_tag bubble.creator, loading: :lazy, class: "avatar" %>
|
||||
</div>
|
||||
|
||||
<div class="card__assignees flex flex-column align-center">
|
||||
<span class="txt-uppercase">Assigned</span>
|
||||
<div class="flex">
|
||||
<% bubble.assignees.each do |assignee| %>
|
||||
<%= avatar_tag assignee, loading: :lazy, class: "avatar" %>
|
||||
<% end %>
|
||||
|
||||
<%= render "bubbles/sidebar/assignment", bubble: bubble, users: bubble.bucket.users.active %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,16 +1,15 @@
|
||||
<div class="bubble__actions-container center border-radius pad flex-inline align-center justify-center gap pad-inline-double" style="--bubble-color: <%= @bubble.color %>">
|
||||
<div class="card__actions-container center border-radius flex-inline align-center justify-center gap" style="--bubble-color: <%= @bubble.color %>">
|
||||
<% if bubble.popped? %>
|
||||
<span class="bubble__pop-message">Popped by <%= bubble.popped_by.name %> on <%= local_datetime_tag(bubble.popped_at, style: :shortdate) %>.</span>
|
||||
<span class="card__pop-message">Completed by <%= bubble.popped_by.name %> on <%= local_datetime_tag(bubble.popped_at, style: :shortdate) %>.</span>
|
||||
<%= button_to bucket_bubble_pop_path(bubble.bucket, bubble), method: :delete, class: "btn btn--plain borderless fill-transparent" do %>
|
||||
<span>Un-pop</span>
|
||||
<span>Un-do</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= button_to bucket_bubble_pop_path(bubble.bucket, bubble), class: "btn borderless" do %>
|
||||
<%= image_tag "pop.svg", aria: { hidden: true }, size: 24, class: "colorize--white" %>
|
||||
<span>Pop</span>
|
||||
<span>Mark as done</span>
|
||||
<% end %>
|
||||
<span class="bubble__pop-message">
|
||||
Auto-pops <%= local_datetime_tag(bubble.auto_pop_at, style: :indays) %>.
|
||||
<span class="card__pop-message">
|
||||
Auto-closes <%= local_datetime_tag(bubble.auto_pop_at, style: :indays) %>.
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<%= button_to bucket_bubble_publish_path(bubble.bucket, bubble), class: "btn txt-small btn--link" do %>
|
||||
<%= button_to bucket_bubble_publish_path(bubble.bucket, bubble), class: "btn txt-small btn--link", style: "--btn-background: #{ bubble.color }" do %>
|
||||
<span>Post to project</span>
|
||||
<% end %>
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<% bubble.tags.each do |tag| %>
|
||||
<%= link_to bubbles_path(bucket_ids: [ bubble.bucket ], tag_ids: [ tag.id ]),
|
||||
class: "bubble__tag btn btn--plain fill-transparent min-width", style: "color: #{bubble.color}", data: { turbo_frame: "_top" } do %>
|
||||
<span class="overflow-ellipsis"><%= tag.hashtag %></span>
|
||||
<div class="card__tags flex align-center flex-item-grow">
|
||||
<% if bubble.tags.any? || bubble.creating? %>
|
||||
<span># </span>
|
||||
<% bubble.tags.each_with_index do |tag, index| %>
|
||||
<%= link_to bubbles_path(bucket_ids: [ bubble.bucket ], tag_ids: [ tag.id ]),
|
||||
class: "card__tag btn btn--plain min-width txt-uppercase fill-transparent", data: { turbo_frame: "_top" } do %>
|
||||
<span class="overflow-ellipsis"><%= tag.title %></span>
|
||||
<% end %><%= ", " unless index == bubble.tags.size - 1 %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= render "bubbles/sidebar/tag", bubble: bubble, tags: Current.account.tags %>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
<%= turbo_frame_tag @bubble, :edit do %>
|
||||
<%= form_with model: @bubble, url: bucket_bubble_path(@bubble.bucket, @bubble), class: "flex flex-column justify-center gap full-width", data: { controller: "form" } do |form| %>
|
||||
<%= form.label :title, class: "flex flex-column justify-center align-center" do %>
|
||||
<%= form.text_area :title, class: "input input--textara txt-align-center full-width borderless bubble__title-rendered",
|
||||
<%= form_with model: @bubble, url: bucket_bubble_path(@bubble.bucket, @bubble), class: "flex flex-column gap full-width", data: { controller: "form" } do |form| %>
|
||||
<%= form.label :title, class: "flex flex-column align-center" do %>
|
||||
<%= form.text_area :title, class: "input input--textarea full-width borderless txt-align-start card__title-field",
|
||||
required: true, autofocus: true, placeholder: "Name it…",
|
||||
rows: 3,
|
||||
data: { action: "keydown.enter->form#submit:prevent keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel focus->form#select" },
|
||||
style: "color: var(--spat-color); --input-border-radius: 0" %>
|
||||
<% end %>
|
||||
<%= form.button "Save", class: "btn btn--reversed txt-small center", type: :submit, hidden: true do %>
|
||||
<%= image_tag "check.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Save</span>
|
||||
<% end %>
|
||||
<%= form.button "Save", type: :submit, hidden: true %>
|
||||
<%= link_to "Close editor and discard changes", bucket_bubble_path(@bubble.bucket, @bubble), data: { form_target: "cancel" }, hidden: true %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -5,35 +5,41 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<nav class="align-start">
|
||||
<% if bucket = @filter.buckets.first %>
|
||||
<%= link_to edit_bucket_path(bucket), class: "btn borderless flex-item-justify-start" do %>
|
||||
<%= image_tag "settings.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Settings for <%= bucket.name %></span>
|
||||
<% end %>
|
||||
<%= link_to root_path, class: "btn", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<%= image_tag "home.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Home</span>
|
||||
<% end %>
|
||||
|
||||
<header class="flex flex-column center">
|
||||
<div class="flex align-center gap-half justify-center">
|
||||
<%= link_to "Collections", root_path %>
|
||||
<%= link_to "Activity", events_path %>
|
||||
</div>
|
||||
<div class="btn btn--placeholder flex-item-justify-start"></div>
|
||||
|
||||
<header class="search flex flex-inline flex-column center flex-shrink txt--large">
|
||||
<input type="search" placeholder="Type names, tags, phrases to find something…" class="input input--search">
|
||||
<%= render "bubbles/filters", filter: @filter %>
|
||||
</header>
|
||||
|
||||
<% if bucket = @filter.buckets.first %>
|
||||
<%= button_to bucket_bubbles_path(bucket), method: :post, class: "btn borderless", form_class: "flex-item-justify-end" do %>
|
||||
<%= link_to edit_bucket_path(bucket), class: "btn flex-item-justify-end" do %>
|
||||
<%= image_tag "settings.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Settings for <%= bucket.name %></span>
|
||||
<% end %>
|
||||
|
||||
<%= button_to bucket_bubbles_path(bucket), method: :post, class: "btn" do %>
|
||||
<%= image_tag "add.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Create a new bubble</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="btn btn--placeholder flex-item-justify-end"></div>
|
||||
<div class="btn btn--placeholder"></div>
|
||||
<% end %>
|
||||
</nav>
|
||||
<% end %>
|
||||
|
||||
<section class="bubbles flex-inline flex-wrap gap justify-center align-end" style="view-transition-name: bubbles_<%= @filter.id %>" data-controller="bubble-size" data-action="turbo:morph@window->bubble-size#resize">
|
||||
<section class="cards gap position-relative" style="view-transition-name: cards-container; data-controller="bubble-size" data-action="turbo:morph@window->bubble-size#resize">
|
||||
<% if @bubbles.any? %>
|
||||
<%= render partial: "bubbles/bubble", collection: @bubbles.limit(10), cached: true %>
|
||||
<%= render "display_count_selector", display_count: @display_count, filter: @filter %>
|
||||
<%= render partial: "bubbles/card", collection: @bubbles.limit(@display_count), as: :bubble, cached: true %>
|
||||
<% else %>
|
||||
<p><strong>Nothing here.</strong></p>
|
||||
<p class="txt-medium"><strong>Nothing here.</strong></p>
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
@@ -45,9 +51,9 @@
|
||||
data-divider-installed-class="bubbles-list__divider--installed"
|
||||
data-divider-dragging-class="dragging"
|
||||
data-action="turbo:morph@document->divider#install dragstart->divider#configureDrag dragenter->divider#acceptDrop dragover->divider#acceptDrop dragover->divider#moveDivider drop->divider#drop">
|
||||
<%= render partial: "bubbles/list/bubble", collection: @bubbles, cached: true %>
|
||||
<%= render "bubbles/list/divider", filter: @filter %>
|
||||
<%= render partial: "bubbles/list/bubble", collection: @bubbles.offset(@display_count), as: :bubble, cached: true %>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<%= render "notifications/tray" %>
|
||||
<%= render "bubbles/pins/tray" %>
|
||||
|
||||
@@ -34,18 +34,42 @@
|
||||
</div>
|
||||
|
||||
<hr class="separator--horizontal flex-item-grow" aria-hidden="true" style="--border-color: var(--color-subtle-dark); --border-style: dotted" />
|
||||
|
||||
<% if bubble.due_on.present? %>
|
||||
<strong class="txt-nowrap txt-small" style="color: <%= bubble.color %>">
|
||||
<div class="for-screen-reader">Due on <%= local_datetime_tag bubble.due_on, style: :shortdate %></div>
|
||||
<div aria-hidden="true"><%= local_datetime_tag bubble.due_on, style: :shortdate %></div>
|
||||
</strong>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-half txt-small full-width min-width">
|
||||
<span class="overflow-ellipsis"><%= "Added by #{bubble.creator.name} on" %> <%= local_datetime_tag bubble.created_at, style: :shortdate %></span>
|
||||
<span class="flex-item-no-shrink"><%= render "bubbles/tags", bubble: bubble %></span>
|
||||
<div class="flex gap-half full-width min-width align-center" style="font-size: 0.8em;">
|
||||
<span class="overflow-ellipsis"><%= "Added to #{bubble.bucket.name} by #{bubble.creator.name}" %> <%=
|
||||
days = (Date.current - bubble.created_at.to_date).to_i
|
||||
case days
|
||||
when 0 then "today"
|
||||
when 1 then "yesterday"
|
||||
else "#{days} days ago".html_safe
|
||||
end
|
||||
%></span>
|
||||
|
||||
<% if bubble.tags.any? %>
|
||||
<div class="flex flex-item-no-shrink">
|
||||
<div>
|
||||
<span>#</span>
|
||||
<% bubble.tags.each_with_index do |tag, index| %>
|
||||
<%= link_to bubbles_path(bucket_ids: [ bubble.bucket ], tag_ids: [ tag.id ]),
|
||||
class: "bubble__tag btn btn--plain fill-transparent min-width", style: "font-weight: 700;", data: { turbo_frame: "_top" } do %>
|
||||
<span class="overflow-ellipsis"><%= tag.title %></span>
|
||||
<% end %><%= ", " unless index == bubble.tags.size - 1 %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if bubble.stage %>
|
||||
<div class="flex flex-column max-width min-width flex-item-no-shrink">
|
||||
<% workflow = Current.account.workflows.first %>
|
||||
<div class="flex flex-column txt-uppercase border" style="font-size: 0.8em; --border-color: currentColor; padding: 0.1em 0.4em;">
|
||||
<% workflow.stages.each do |stage| %>
|
||||
<%= button_to_set_stage bubble, stage %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<% cache pin do %>
|
||||
<%= tag.div id: dom_id(pin), class: "pin flex position-relative" do %>
|
||||
<%= link_to bucket_bubble_path(pin.bubble.bucket, pin.bubble),
|
||||
class: "pin__content pad pad-inline-double shadow fill-white flex align-start txt-align-start gap flex-item-grow max-width",
|
||||
style: "border-color: #{ pin.bubble.color }",
|
||||
data: { action: "click->dialog#close", turbo_frame: "_top" } do %>
|
||||
<div class="flex flex-column txt-tight-lines min-width flex-item-grow">
|
||||
<strong class="overflow-ellipsis pin__title"><%= pin.bubble.title %></strong>
|
||||
<div class="overflow-ellipsis" style="color: <%= pin.bubble.color %>">
|
||||
<%= pin.bubble.bucket.name %>
|
||||
<%= "· #" if pin.bubble.tags.any? %>
|
||||
<%= pin.bubble.tags.map(&:title).join(", ") %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,24 @@
|
||||
<%= turbo_stream_from Current.user, :pins %>
|
||||
|
||||
<%= tag.dialog id: "pin-tray", class: "pin-tray flex flex-column gap unpad borderless justify-end fill-transparent",
|
||||
data: { controller: "dialog", turbo_permanent: true, dialog_modal_value: false, dialog_target: "dialog", action: "keydown.esc->dialog#close:stop click@document->dialog#closeOnClickOutside" } do %>
|
||||
<%= turbo_frame_tag "pins", src: bubbles_pins_tray_path %>
|
||||
|
||||
<%= link_to bubbles_pins_path, class: "pin-tray__overflow border-radius" do %>
|
||||
<div class="pin__content border-radius pad shadow fill-white flex align-center txt-align-center gap full-width">
|
||||
<div class="flex flex-column txt-tight-lines min-width flex-item-grow">
|
||||
<strong>+ More…</strong>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="pin-tray__actions flex align-center gap-half">
|
||||
<%= tag.button type: "button", class: "pin-tray__all_action btn flex-item-justify-start shadow", data: { action: "click->dialog#close"} do %>
|
||||
<%= image_tag "pinned.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span>See all pins</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<button class="pin-tray__expander borderless unpad fill-transparent" data-action="dialog#toggle" aria-label="Expand pins stack">
|
||||
</button>
|
||||
<% end %>
|
||||
@@ -0,0 +1,13 @@
|
||||
<%= turbo_frame_tag dom_id(@bubble, :pin) do %>
|
||||
<% if @bubble.pinned_by? Current.user %>
|
||||
<%= button_to bucket_bubble_pin_path(@bubble.bucket, @bubble), method: :delete, class: "btn btn--reversed" do %>
|
||||
<%= image_tag "pinned.svg", size: 16, aria: { hidden: true } %>
|
||||
<span class="for-screen-reader">Un-pin this card</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= button_to bucket_bubble_pin_path(@bubble.bucket, @bubble), class: "btn" do %>
|
||||
<%= image_tag "unpinned.svg", size: 16, aria: { hidden: true } %>
|
||||
<span class="for-screen-reader">Pin this card</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,3 @@
|
||||
<%= turbo_frame_tag "pins" do %>
|
||||
<%= render collection: @pins, partial: "bubbles/pins/pin" %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,3 @@
|
||||
<%= turbo_frame_tag "pins" do %>
|
||||
<%= render collection: @pins, partial: "bubbles/pins/pin" %>
|
||||
<% end %>
|
||||
@@ -2,130 +2,95 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<% if @bubble.creating? && @bubble.can_recover_abandoned_creation? %>
|
||||
<div class="position-sticky flex align-center gap-half fill-selected justify-center border-block margin-block-end"
|
||||
style="--border-color: var(--color-selected-dark); --border-style: dashed; view-transition-name: draft-banner;">
|
||||
<div class="fill-selected position-sticky flex align-center gap-half justify-center border-block margin-block-end"
|
||||
style="view-transition-name: draft-banner; --bubble-color: <%= @bubble.color %>;">
|
||||
You have an unsaved work. Would you like to continue where you left off?
|
||||
<%= button_to bucket_bubble_recover_path(@bubble.bucket, @bubble), class: "btn txt-small btn--link", data: { turbo_action: "replace" } do %>
|
||||
<%= button_to bucket_bubble_recover_path(@bubble.bucket, @bubble), class: "btn btn--reversed", data: { turbo_action: "replace" } do %>
|
||||
<span>Restore</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if @bubble.drafted? %>
|
||||
<div class="position-sticky flex align-center gap-half fill-selected justify-center border-block margin-block-end"
|
||||
style="--border-color: var(--color-selected-dark); --border-style: dashed; view-transition-name: draft-banner;">
|
||||
<div class="fill-selected position-sticky flex align-center gap-half fill-highlight justify-center border-block margin-block-end"
|
||||
style="view-transition-name: draft-banner;">
|
||||
This is a draft, it’s only visible to you.
|
||||
<%= render "bubbles/publish", bubble: @bubble %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<nav class="align-start max-width">
|
||||
<header class="flex flex-column center max-width">
|
||||
<div class="flex align-center gap-half justify-center">
|
||||
<%= link_to "Collections", root_path %>
|
||||
<%= link_to "Activity", events_path %>
|
||||
</div>
|
||||
<h1 class="txt-large txt-align-center min-width flex align-center gap-half margin-block-end justify-center">
|
||||
<%= link_to @bucket.name, bubbles_path(bucket_ids: [ @bucket ]), data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } %>
|
||||
</h1>
|
||||
<%= link_to root_path, class: "btn flex-item-justify-start", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<%= image_tag "home.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Home</span>
|
||||
<% end %>
|
||||
|
||||
<header class="search flex flex-inline flex-column center flex-shrink txt--large">
|
||||
<input type="search" placeholder="Type names, tags, phrases to find something…" class="input input--search">
|
||||
</header>
|
||||
|
||||
<div class="btn btn--placeholder flex-item-justify-end"></div>
|
||||
</nav>
|
||||
<% end %>
|
||||
|
||||
<aside class="flex align-start justify-start max-width" style="--bubble-color: <%= @bubble.color %>">
|
||||
<div class="flex flex-column" style="row-gap: 3px;">
|
||||
<%= render "bubbles/color", bubble: @bubble %>
|
||||
<div data-controller="beacon" data-beacon-url-value="<%= bucket_bubble_readings_url(@bubble.bucket, @bubble) %>">
|
||||
<div class="card__container position-relative margin-block-start <%= "card__container--pointing" unless @bubble.published? %>">
|
||||
<%= render "bubbles/card", bubble: @bubble %>
|
||||
|
||||
<%= form_with model: @bubble, url: bucket_bubble_path(@bubble.bucket, @bubble), data: { controller: "form" } do |form| %>
|
||||
<label class="btn full-width justify-start borderless">
|
||||
<%= image_tag "calendar-add.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span>Date</span>
|
||||
<%= form.date_field :due_on, class: "input input--hidden", data: { action: "change->form#submit click->form#showPicker" } %>
|
||||
</label>
|
||||
<% end %>
|
||||
|
||||
<%= render "bubbles/sidebar/assignment", bubble: @bubble, users: @bucket.users.active %>
|
||||
<%= render "bubbles/sidebar/tag", bubble: @bubble, tags: Current.account.tags %>
|
||||
|
||||
<% if @bubble.image.attached? %>
|
||||
<%= button_to bucket_bubble_image_path(@bubble.bucket, @bubble), method: :delete, class: "btn full-width justify-start borderless" do %>
|
||||
<%= image_tag "picture-add.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span>Clear background image</span>
|
||||
<div class="card__actions flex flex-column gap-half">
|
||||
<%= render "bubbles/color", bubble: @bubble %>
|
||||
<%= render "bubbles/image", bubble: @bubble %>
|
||||
<%= button_to bucket_bubble_path(@bubble.bucket, @bubble),
|
||||
method: :delete,
|
||||
class: "btn",
|
||||
data: { turbo_confirm: "Are you sure you want to delete this?" } do %>
|
||||
<%= image_tag "trash.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Delete</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= form_with model: @bubble, url: bucket_bubble_path(@bubble.bucket, @bubble), data: { controller: "form" } do |form| %>
|
||||
<label class="btn full-width justify-start borderless min-width">
|
||||
<%= image_tag "picture-add.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="overflow-ellipsis" aria-hidden="true">Background</span>
|
||||
<%= form.file_field :image, class: "input visually-hidden",
|
||||
accept: "image/png, image/jpeg, image/jpg, image/webp",
|
||||
aria: { label: "Upload background image" },
|
||||
data: { action: "upload-preview#previewImage form#submit", upload_preview_target: "input" } %>
|
||||
</label>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= button_to bucket_bubble_boosts_path(@bubble.bucket, @bubble), class: "btn full-width justify-start borderless", data: { turbo_frame: dom_id(@bubble, :boosts) } do %>
|
||||
<%= image_tag "thumb-up.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="overflow-ellipsis">Boost</span>
|
||||
<% end %>
|
||||
|
||||
<% unless @bubble.drafted? %>
|
||||
<button class="btn full-width justify-start borderless min-width">
|
||||
<%= image_tag "picture-double.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="overflow-ellipsis">I’ve seen this</span>
|
||||
</button>
|
||||
<% end %>
|
||||
|
||||
|
||||
<%= button_to bucket_bubble_path(@bubble.bucket, @bubble),
|
||||
method: :delete,
|
||||
class: "btn full-width justify-start borderless min-width",
|
||||
data: { turbo_confirm: "Are you sure you want to delete this bubble?" } do %>
|
||||
<%= image_tag "trash.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="overflow-ellipsis">Delete</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="bubble__container" data-controller="beacon" data-beacon-url-value="<%= bucket_bubble_readings_url(@bubble.bucket, @bubble) %>">
|
||||
<div class="bubble__perma flex justify-center center">
|
||||
<%= render "bubbles/bubble", bubble: @bubble %>
|
||||
</div>
|
||||
|
||||
<% unless @bubble.drafted? || @bubble.creating? %>
|
||||
<%= render "bubbles/pop_toggle", bubble: @bubble %>
|
||||
<% end %>
|
||||
|
||||
<div style="margin-block-start: calc(var(--block-space) * -1);">
|
||||
|
||||
<% if @bubble.published? %>
|
||||
<%= render "bubbles/messages", bubble: @bubble %>
|
||||
<% else %>
|
||||
<div class="comments">
|
||||
<div class="comment comment--new border-radius flex align-start full-width margin-block-double">
|
||||
<form data-controller="outlet-auto-save paste" data-outlet-auto-save-auto-save-outlet="#bubble_form" class="flex flex-column gap full-width" data-action="paste->paste#pasteFiles">
|
||||
<%= tag.house_md @bubble.draft_comment, name: "bubble[draft_comment]", class: "input comment__input", form: "bubble_form", placeholder: new_comment_placeholder(@bubble),
|
||||
data: { action: "house-md:change->outlet-auto-save#change focusout->outlet-auto-save#submit", uploads_url: uploads_url(format: "json") } %>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card__actions card__actions--right flex flex-column gap-half">
|
||||
<%= turbo_frame_tag dom_id(@bubble, :watch), src: bucket_bubble_watch_path(@bubble.bucket, @bubble) %>
|
||||
<%= turbo_frame_tag dom_id(@bubble, :pin), src: bucket_bubble_pin_path(@bubble.bucket, @bubble) %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% unless @bubble.drafted? || @bubble.creating? %>
|
||||
<div class="card__actions-wrapper">
|
||||
<%= render "bubbles/pop_toggle", bubble: @bubble %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if @bubble.published? %>
|
||||
<%= turbo_frame_tag dom_id(@bubble, :watch), src: bucket_bubble_watch_path(@bubble.bucket, @bubble) %>
|
||||
<%= render "bubbles/messages", bubble: @bubble %>
|
||||
<% else %>
|
||||
<div class="comments align-center center borderless margin-block-start flex flex-column gap-half">
|
||||
<div class="comment comment--new flex align-start full-width">
|
||||
<form data-controller="outlet-auto-save paste"
|
||||
class="flex flex-column gap full-width"
|
||||
data-outlet-auto-save-auto-save-outlet="#bubble_form"
|
||||
data-action="paste->paste#pasteFiles">
|
||||
<%= tag.house_md @bubble.draft_comment, name: "bubble[draft_comment]", class: "input comment__input",
|
||||
form: "bubble_form",
|
||||
placeholder: new_comment_placeholder(@bubble),
|
||||
data: { action: "house-md:change->outlet-auto-save#change focusout->outlet-auto-save#submit", uploads_url: uploads_url(format: "json") } %>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if @bubble.creating? %>
|
||||
<div class="bubble__actions-container center border-radius pad flex align-center justify-space-between gap pad-inline-double" style="--bubble-color: <%= @bubble.color %>;">
|
||||
<%= button_to "Save as draft", bucket_bubble_path(@bubble.bucket, @bubble), name: "bubble[status]", value: "drafted", method: :put, class: "btn btn--plain borderless fill-transparent" %>
|
||||
<%= button_to "Create bubble", bucket_bubble_publish_path(@bubble.bucket, @bubble), class: "btn btn--reversed" %>
|
||||
<div class="comments center pad-block flex align-center gap">
|
||||
<%= button_to "Create card", bucket_bubble_publish_path(@bubble.bucket, @bubble), class: "btn btn--reversed" %>
|
||||
<%= button_to "Save as draft", bucket_bubble_path(@bubble.bucket, @bubble), name: "bubble[status]", value: "drafted", method: :put, class: "btn" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex align-start justify-end">
|
||||
<%= turbo_frame_tag dom_id(@bubble, :stage_picker), src: new_bucket_bubble_stage_picker_path(@bubble.bucket, @bubble) %>
|
||||
</div>
|
||||
<%= link_to "Go back", bubbles_path(bucket_ids: [ @bubble.bucket ]), class: "btn", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" }, hidden: true %>
|
||||
|
||||
<%= render "notifications/tray" %>
|
||||
<%= render "bubbles/pins/tray" %>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<div class="position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="btn full-width justify-start borderless" data-action="click->dialog#open:stop">
|
||||
<%= image_tag "person.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span>Assign</span>
|
||||
<button class="btn card__hide-on-index <%= bubble.creating? || bubble.drafted? ? "fill-highlight" : "fill-transparent" %>" data-action="click->dialog#open:stop">
|
||||
<%= image_tag "add.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Assign</span>
|
||||
</button>
|
||||
|
||||
<dialog class="popup panel flex-column align-start gap-half fill-white shadow" data-dialog-target="dialog">
|
||||
<strong class="popup__title margin-block-start-half pad-inline-half">Assign this to…</strong>
|
||||
<strong class="popup__title margin-block-start-half pad-inline-half txt-nowrap">Assign this to…</strong>
|
||||
|
||||
<%= form_with url: bucket_bubble_assignment_toggles_path(bubble.bucket, bubble), class: "flex flex-column full-width popup__list", data: { controller: "form" } do |form| %>
|
||||
<% users.sort_by(&:name).each do |user| %>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="btn full-width justify-start borderless" data-action="click->dialog#open:stop">
|
||||
<button class="tag-picker__button btn card__hide-on-index <%= bubble.creating? || bubble.drafted? ? "fill-highlight" : "fill-transparent" %>" data-action="click->dialog#open:stop">
|
||||
<%= image_tag "tag.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span>Tag</span>
|
||||
<span class="for-screen-reader">Add a tag</span>
|
||||
</button>
|
||||
|
||||
<dialog class="popup panel flex-column align-start justify-start fill-white shadow" data-dialog-target="dialog">
|
||||
|
||||
@@ -1,32 +1,9 @@
|
||||
<%= turbo_frame_tag dom_id(@bubble, :stage_picker) do %>
|
||||
<% if @selected_workflow %>
|
||||
<aside class="position-relative" style="--bubble-color: <%= @bubble.color %>"
|
||||
data-controller="dialog" data-action="keydown.esc->dialog#close:stop click@document->dialog#closeOnClickOutside">
|
||||
<div class="flex align-center justify-start gap-half">
|
||||
<button class="btn txt-small borderless" style="margin-inline-start: calc((var(--btn-size) + var(--inline-space)) * -0.5);" data-action="click->dialog#toggle" data-dialog-modal-value="true">
|
||||
<%= image_tag "bolt.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Choose a workflow</span>
|
||||
</button>
|
||||
<strong class="txt-medium"><%= @selected_workflow.name %></strong>
|
||||
<span class="btn btn--placeholder" aria-hidden="true"></span>
|
||||
</div>
|
||||
|
||||
<dialog class="workflow__popup panel fill-white shadow" data-dialog-target="dialog">
|
||||
<menu class="list-style-none unpad margin-none">
|
||||
<% @workflows.each do |workflow| %>
|
||||
<li><%= link_to_stage_picker @bubble, workflow %></li>
|
||||
<% end %>
|
||||
</menu>
|
||||
</dialog>
|
||||
|
||||
<div class="workflow">
|
||||
<hr class="separator--horizontal" aria-hidden="true" style="--border-color: var(--color-subtle)" />
|
||||
<div class="flex flex-column">
|
||||
<% @selected_workflow.stages.each do |stage| %>
|
||||
<%= button_to_set_stage @bubble, stage %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="card__stages flex flex-column max-width min-width">
|
||||
<% @selected_workflow.stages.each do |stage| %>
|
||||
<%= button_to_set_stage @bubble, stage %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -1,32 +1,13 @@
|
||||
<%= turbo_frame_tag dom_id(@bubble, :watch) do %>
|
||||
<div class="flex flex-column align-start gap-half">
|
||||
<div class="flex flex-column align-start">
|
||||
<h3 class="txt-medium txt-tight-lines">Watchers</h3>
|
||||
<div><%= pluralize @watchers.count, "person" %> are being notified when someone comments on this bubble.</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-half max-width txt-small">
|
||||
<%= render partial: "bubbles/watches/watcher", collection: @watchers %>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-column align-start margin-block-start">
|
||||
<% if @bubble.watched_by? Current.user %>
|
||||
<strong>You are watching this bubble</strong>
|
||||
<div>You will be notified when someone comments.</div>
|
||||
<div class="margin-block-half">
|
||||
<%= button_to bucket_bubble_watch_path(@bubble.bucket, @bubble), method: :delete, class: "btn" do %>
|
||||
<span>Stop watching</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<strong>You’re not watching this bubble</strong>
|
||||
<div>You won’t be notified when someone comments.</div>
|
||||
<div class="margin-block-half">
|
||||
<%= button_to bucket_bubble_watch_path(@bubble.bucket, @bubble), class: "btn" do %>
|
||||
<span">Watch this</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if @bubble.watched_by? Current.user %>
|
||||
<%= button_to bucket_bubble_watch_path(@bubble.bucket, @bubble), method: :delete, class: "btn btn--reversed" do %>
|
||||
<%= image_tag "bell-off.svg", size: 16, aria: { hidden: true } %>
|
||||
<span class="for-screen-reader">Stop watching</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<%= button_to bucket_bubble_watch_path(@bubble.bucket, @bubble), class: "btn" do %>
|
||||
<%= image_tag "bell.svg", size: 16, aria: { hidden: true } %>
|
||||
<span class="for-screen-reader">Watch this</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<nav>
|
||||
<%= link_to buckets_path, class: "btn flex-item-justify-start", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<%= link_to bubbles_path(bucket_ids: [ @bbucket ]), class: "btn flex-item-justify-start", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<%= image_tag "arrow-left.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Go back</span>
|
||||
<% end %>
|
||||
@@ -72,6 +72,6 @@
|
||||
<span class="for-screen-reader">Save</span>
|
||||
</button>
|
||||
|
||||
<%= link_to "Cancel and go back", buckets_path, data: { form_target: "cancel", turbo_frame: "_top" }, hidden: true %>
|
||||
<%= link_to "Cancel and go back", bubbles_path(bucket_ids: [ @bucket ]), data: { form_target: "cancel", turbo_frame: "_top" }, hidden: true %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<header class="flex flex-column center">
|
||||
<div class="flex align-center gap-half justify-center">
|
||||
<%= link_to "Collections", root_path %>
|
||||
<%= link_to "Activity", events_path %>
|
||||
<%= link_to "Activity", root_path %>
|
||||
</div>
|
||||
<h1 class="txt-x-large"><%= @page_title %></h1>
|
||||
</header>
|
||||
@@ -30,3 +30,4 @@
|
||||
</ul>
|
||||
|
||||
<%= render "notifications/tray" %>
|
||||
<%= render "bubbles/pins/tray" %>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<nav>
|
||||
<%= link_to buckets_path, class: "btn flex-item-justify-start", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<%= link_to root_path, class: "btn flex-item-justify-start", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<%= image_tag "arrow-left.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Go back</span>
|
||||
<% end %>
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<div class="panel shadow center" style="--panel-size: 55ch; view-transition-name: new-bucket">
|
||||
<%= form_with model: @bucket, class: "flex flex-column gap txt-large", data: { controller: "form" } do |form| %>
|
||||
<%= image_tag "bubbles.svg", aria: { hidden: "true" }, size: 150, class: "colorize--black center" %>
|
||||
<h2 class="txt-x-large">Create a new collection</h2>
|
||||
<label class="flex align-center gap">
|
||||
<%= form.text_field :name, required: true, class: "input full-width", autofocus: true, placeholder: "Give it a name…", data: { action: "keydown.esc@document->form#cancel" } %>
|
||||
</label>
|
||||
@@ -20,6 +20,6 @@
|
||||
<%= image_tag "check.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Save</span>
|
||||
</button>
|
||||
<%= link_to "Cancel and go back", buckets_path, data: { form_target: "cancel", turbo_frame: "_top" }, hidden: true %>
|
||||
<%= link_to "Cancel and go back", root_path, data: { form_target: "cancel", turbo_frame: "_top" }, hidden: true %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
<% cache comment do %>
|
||||
<%= turbo_frame_tag dom_id(comment) do %>
|
||||
<div class="comment__content flex-inline flex-column full-width border-radius">
|
||||
<div class="comment__content flex-inline flex-column full-width">
|
||||
<div class="comment__author flex align-center gap-half">
|
||||
<h3 contents>
|
||||
<strong><%= link_to comment.creator.name, user_path(comment.creator), class: "txt-ink btn btn--plain fill-transparent", data: { turbo_frame: "_top" } %></strong>
|
||||
<%= link_to bucket_bubble_path(comment.bubble.bucket, comment.bubble, anchor: "comment_#{comment.id}"), class: "txt-undecorated" do %>
|
||||
<%= local_datetime_tag comment.created_at, style: :shortdate, class: "comment__timestamp" %>
|
||||
<% end %>
|
||||
</h3>
|
||||
<strong><%= link_to comment.creator.name, user_path(comment.creator), class: "txt-ink btn btn--plain fill-transparent", data: { turbo_frame: "_top" } %></strong>
|
||||
<%= link_to bucket_bubble_path(comment.bubble.bucket, comment.bubble, anchor: "comment_#{comment.id}"), class: "txt-undecorated" do %>
|
||||
<%= local_datetime_tag comment.created_at, style: :shortdate, class: "comment__timestamp" %>
|
||||
<% end %>
|
||||
|
||||
<%= link_to edit_bucket_bubble_comment_path(comment.bubble.bucket, comment.bubble, comment), role: "button",
|
||||
class: "btn txt-small btn--plain fill-transparent comment__edit",
|
||||
style: "font-size: 0.4em; opacity: 0.5;" do %>
|
||||
<%= link_to edit_bucket_bubble_comment_path(comment.bubble.bucket, comment.bubble, comment), class: "btn txt-small btn--plain fill-transparent comment__edit", style: "font-size: 0.4em; opacity: 0.5;" do %>
|
||||
<%= image_tag "menu-dots-horizontal.svg", class: "icon colorize--black" %>
|
||||
<span class="for-screen-reader">Edit</span>
|
||||
<% end %>
|
||||
@@ -23,4 +19,4 @@
|
||||
<%= render "comments/reactions/reactions", comment: comment %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="comment comment--new border-radius flex align-start full-width margin-block-end-double">
|
||||
<div class="comment comment--new flex align-start full-width margin-block-end-double">
|
||||
<div class="flex flex-column full-width">
|
||||
<div class="position-relative">
|
||||
<%= form_with model: Comment.new, url: bucket_bubble_comments_path(bubble.bucket, bubble), class: "flex flex-column gap full-width",
|
||||
|
||||
@@ -5,16 +5,14 @@
|
||||
data-reaction-delete-reacter-id-value="<%= reaction.reacter.id %>"
|
||||
data-controller="reaction-delete" data-reaction-delete-perform-class="reaction--deleting"
|
||||
data-reaction-delete-reveal-class="expanded" data-reaction-delete-reacter-id-value="<%= reaction.reacter.id %>">
|
||||
<figure class="reaction__avatar margin-none flex-item-no-shrink" aria-hidden="true">
|
||||
<%= avatar_tag reaction.reacter, hidden_for_screen_reader: true, loading: :lazy %>
|
||||
<figure class="reaction__avatar margin-none flex-item-no-shrink">
|
||||
<%= avatar_tag reaction.reacter, aria: { label: "#{reaction.reacter.name} reacted #{reaction.content}" }, loading: :lazy %>
|
||||
</figure>
|
||||
|
||||
<%= tag.span reaction.content, class: [ "txt-small", { "txt-medium": reaction.all_emoji? } ],
|
||||
data: {
|
||||
action: "click->reaction-delete#reveal keydown.enter->reaction-delete#reveal:prevent",
|
||||
reaction_delete_target: "content" } do %>
|
||||
<span class="for-screen-reader"><%= "#{reaction.reacter.name} reacted #{reaction.content}" %></span>
|
||||
<% end %>
|
||||
<%= tag.span reaction.content, role: "button",
|
||||
class: [ "txt-small", { "txt-medium": reaction.all_emoji? } ],
|
||||
data: { action: "click->reaction-delete#reveal keydown.enter->reaction-delete#reveal:prevent", reaction_delete_target: "content" } %>
|
||||
|
||||
<%= button_to bucket_bubble_comment_reaction_path(comment.bubble.bucket, comment.bubble, comment, reaction),
|
||||
method: :delete,
|
||||
class: "btn btn--negative flex-item-justify-end reaction__delete",
|
||||
@@ -23,4 +21,5 @@
|
||||
<span class="for-screen-reader">Delete this reaction</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<span id="delete_reaction_accessible_label" class="for-screen-reader">Press enter to delete this reaction</span>
|
||||
<% end %>
|
||||
@@ -1,7 +1,9 @@
|
||||
<div class="events--day" data-controller="related-element"
|
||||
data-related-element-highlight-class="event--related">
|
||||
<% if events.any? %>
|
||||
<h2 class="txt-large margin-block-end-half"><%= event_day_title(activity_day) %></h2>
|
||||
<h2 class="txt-medium events__day-header center">
|
||||
<span class="events__day-header-content"><%= event_day_title(activity_day) %><span>
|
||||
</h2>
|
||||
|
||||
<div class="center events">
|
||||
<%= render_column_headers(activity_day) %>
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
<% if earliest.present? %>
|
||||
<% if earliest == latest %>
|
||||
<div class="events--none txt-align-center translucent">
|
||||
<div class="events--none txt-align-center">
|
||||
<h2 class="txt-medium margin-none"><%= event_day_title(earliest) %></h2>
|
||||
<p class="margin-none">No activity</p>
|
||||
</div>
|
||||
<% elsif earliest < latest %>
|
||||
<div class="events--none txt-align-center translucent">
|
||||
<div class="events--none txt-align-center">
|
||||
<h2 class="txt-medium margin-none"><%= event_day_title(earliest) %> – <%= event_day_title(latest) %></h2>
|
||||
<p class="margin-none">No activity for <%= (latest - earliest).seconds.in_days.round + 1 %> days</p>
|
||||
</div>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<p class="events--none txt-align-center translucent">No more activity.</p>
|
||||
<div class="events--none txt-align-center">
|
||||
<p class="margin-none">No more activity.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -2,19 +2,17 @@
|
||||
<% link = event.action == "commented" ? bucket_bubble_path(bubble.bucket, bubble, anchor: dom_id(event.comment)) : bucket_bubble_path(bubble.bucket, bubble) %>
|
||||
|
||||
<%= link_to link,
|
||||
class: "event panel shadow center center-block flex-inline align-start justify-start gap position-relative",
|
||||
style: "--bubble-color: #{ bubble.color }; #{ bubble_rotation(bubble) }",
|
||||
data: { controller: "animation",
|
||||
related_element_target: "related",
|
||||
class: "event panel center center-block flex-inline align-start justify-start gap position-relative",
|
||||
style: "--bubble-color: #{ bubble.color }; background-color: color-mix(in srgb, var(--bubble-color) 10%, var(--color-bg));
|
||||
color: color-mix(in srgb, var(--bubble-color) 40%, var(--color-ink));",
|
||||
data: { related_element_target: "related",
|
||||
related_element_group_value: bubble.id,
|
||||
animation_play_class: "bubble--wobble",
|
||||
animation_play_on_load_value: "true",
|
||||
action: "mouseover->animation#play mouseover->related-element#highlight mouseout->related-element#unhighlight" } do %>
|
||||
action: "mouseover->related-element#highlight mouseout->related-element#unhighlight" } do %>
|
||||
<div class="avatar txt-x-small flex-item-no-shrink">
|
||||
<%= avatar_image_tag(event.creator) %>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-column min-width txt-small align-start txt-align-start">
|
||||
<div class="flex flex-column min-width txt-small align-start txt-align-start" style="color: color-mix(in srgb, var(--bubble-color) 40%, var(--color-ink));">
|
||||
<strong class="event__title txt-break overflow-line-clamp txt-tight-lines">
|
||||
<%= event_action_sentence(event) %>
|
||||
</strong>
|
||||
@@ -25,6 +23,6 @@
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<span class="event__bucket translucent"><%= bubble.bucket.name %></span>
|
||||
<span class="event__bucket"><%= bubble.bucket.name %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
<% presenter = BucketFilterPresenter.new(@buckets, params, cookies) %>
|
||||
<div class="position-relative margin-block-start-half margin-block-end center max-width"
|
||||
<div class="position-relative max-width"
|
||||
data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="input input--select flex-inline min-width max-width" data-action="click->dialog#open:stop">
|
||||
<button class="input input--select flex-inline min-width max-width txt-small" data-action="click->dialog#open:stop">
|
||||
<span class="overflow-ellipsis">
|
||||
<%= presenter.filter_text %>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<dialog class="events__popup popup panel flex-column full-width align-start gap-half fill-white shadow" data-dialog-target="dialog" data-action="turbo:before-cache@document->dialog#close">
|
||||
<strong class="popup__title margin-block-start-half pad-inline-half">Filter by projects…</strong>
|
||||
<dialog class="events__popup popup panel flex-column full-width align-start gap-half fill-white shadow"
|
||||
style="z-index: 5; inset: 0 0 auto auto;" data-dialog-target="dialog" data-action="turbo:before-cache@document->dialog#close">
|
||||
<strong class="popup__title margin-block-start-half pad-inline-half txt-nowrap">Filter by Collection…</strong>
|
||||
<%= form_with url: events_path, method: :get, class: "flex flex-column full-width popup__list",
|
||||
data: { controller: "form" } do |form| %>
|
||||
<%= link_to "Show everything", events_path(clear_filter: true), class: "btn popup__item" %>
|
||||
<%= link_to "Show everything", events_path(clear_filter: true), class: "btn popup__item txt-nowrap" %>
|
||||
<% @buckets.each do |bucket| %>
|
||||
<div class="btn popup__item">
|
||||
<%= form.check_box "bucket_ids[]", {
|
||||
|
||||
@@ -1,20 +1,50 @@
|
||||
<% @page_title = "Latest Activity" %>
|
||||
<% @page_title = "Home" %>
|
||||
|
||||
<% content_for :header do %>
|
||||
<nav class="align-start">
|
||||
<header class="flex-inline flex-column center max-width">
|
||||
<div class="flex align-center gap-half justify-center">
|
||||
<%= link_to "Collections", root_path %>
|
||||
<%= link_to "Activity", events_path %>
|
||||
</div>
|
||||
<h1 class="txt-x-large"><%= @page_title %></h1>
|
||||
<%= render "events/filter" %>
|
||||
</header>
|
||||
<div class="btn btn--placeholder flex-item-justify-start"></div>
|
||||
<%= link_to account_users_path, class: "btn flex-item-justify-end" do %>
|
||||
<%= image_tag "settings.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Account settings</span>
|
||||
<% end %>
|
||||
</nav>
|
||||
<% end %>
|
||||
|
||||
<div id="activity" class="events__index flex flex-column gap">
|
||||
<%= render "events/day", activity_day: @activity_day, next_day: @next_day, events: @events %>
|
||||
<div class="events__container align-start gap">
|
||||
<div class="flex flex-column">
|
||||
<div class="flex align-center gap justify-space-between margin-block-end-half" style="padding-inline: calc(0.3em + 0.6em);">
|
||||
<h2 class="txt-large txt-nowrap">Latest activity</h2>
|
||||
<%= render "events/filter" %>
|
||||
</div>
|
||||
<div id="activity" class="events__index flex flex-column">
|
||||
<%= render "events/day", activity_day: @activity_day, next_day: @next_day, events: @events %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="txt-align-start">
|
||||
<h2 class="txt-large txt-nowrap" style="margin-block-end: 0.5em;">Custom views</h2>
|
||||
<div style="border-block-start: 1px solid var(--color-subtle); padding-block-start: var(--block-space-half);">
|
||||
<% @filters.each do |filter| %>
|
||||
<h3 class="txt-medium margin-block-end-half txt-tight-lines">
|
||||
<%= link_to filter.summary,bubbles_path(**filter.as_params) %>
|
||||
</h3>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex align-center gap min-width margin-block-start-double margin-block-end-half" >
|
||||
<h2 class="txt-large overflow-ellipsis">Collections</h2>
|
||||
<%= link_to new_bucket_path, class: "btn txt-small flex-item-justify-end flex-item-no-shrink", style: "view-transition-name: new-bucket" do %>
|
||||
<%= image_tag "add.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">New</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div style="border-block-start: 1px solid var(--color-subtle); padding-block-start: var(--block-space-half);">
|
||||
<% @buckets.each do |bucket| %>
|
||||
<h3 class="txt-medium margin-block-end-half txt-tight-lines"><%= link_to bucket.name, bubbles_path(bucket_ids: [ bucket ]) %></h3>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= event_next_page_link(@next_day) %>
|
||||
|
||||
@@ -1,54 +1,53 @@
|
||||
<div class="quick-filter position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="btn input input--select flex-inline txt-small" data-action="click->dialog#open:stop">
|
||||
<span class="overflow-ellipsis">
|
||||
<% if filter.assignment_status.unassigned? %>
|
||||
No one
|
||||
<% elsif filter.assignees.any? %>
|
||||
<%= filter.assignees.map(&:name).to_sentence %>
|
||||
<% else %>
|
||||
Assigned to…
|
||||
<% end %>
|
||||
</span>
|
||||
</button>
|
||||
<% if filter.assignees.any? %>
|
||||
<div class="quick-filter position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="btn input input--select flex-inline txt-small" data-action="click->dialog#open:stop">
|
||||
<span class="overflow-ellipsis">
|
||||
<% if filter.assignment_status.unassigned? %>
|
||||
No one
|
||||
<% elsif filter.assignees.any? %>
|
||||
<%= filter.assignees.map(&:name).to_sentence %>
|
||||
<% else %>
|
||||
Assigned to…
|
||||
<% end %>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<dialog class="events__popup popup panel flex-column align-start gap-half fill-white shadow"
|
||||
aria-label="Assigned to…" aria-description="Assigned to…"
|
||||
data-dialog-target="dialog" data-action="turbo:before-cache@document->dialog#close">
|
||||
<strong class="popup__title margin-block-start-half pad-inline-half overflow-ellipsis">Assigned to…</strong>
|
||||
<%= form_with url: bubbles_path, method: :get, class: "flex flex-column full-width popup__list",
|
||||
data: { controller: "form" } do |form| %>
|
||||
<% filter.as_params.except(:assignee_ids, :assignment_status).each do |key, value| %>
|
||||
<%= filter_hidden_field_tag key, value %>
|
||||
<% end %>
|
||||
<dialog class="events__popup popup panel flex-column align-start gap-half fill-white shadow" data-dialog-target="dialog" data-action="turbo:before-cache@document->dialog#close">
|
||||
<strong class="popup__title margin-block-start-half pad-inline-half overflow-ellipsis">Assigned to…</strong>
|
||||
<%= form_with url: bubbles_path, method: :get, class: "flex flex-column full-width popup__list",
|
||||
data: { controller: "form" } do |form| %>
|
||||
<% filter.as_params.except(:assignee_ids, :assignment_status).each do |key, value| %>
|
||||
<%= filter_hidden_field_tag key, value %>
|
||||
<% end %>
|
||||
|
||||
<%= link_to bubbles_path(filter.as_params.except(:assignee_ids, :assignment_status)), class: "btn popup__item" do %>
|
||||
<span class="overflow-ellipsis">Clear all</span>
|
||||
<% end %>
|
||||
|
||||
<%= link_to bubbles_path(filter.as_params.except(:assignee_ids, :assignment_status)), class: "btn popup__item" do %>
|
||||
<span class="overflow-ellipsis">Clear all</span>
|
||||
<% end %>
|
||||
|
||||
<div class="btn popup__item">
|
||||
<%= form.check_box :assignment_status, {
|
||||
checked: filter.assignment_status.unassigned?,
|
||||
data: { action: "change->form#submit" },
|
||||
include_hidden: false,
|
||||
}, "unassigned" %>
|
||||
|
||||
<%= form.label :assignment_status, "No one", class: "overflow-ellipsis" %>
|
||||
<%= image_tag "check.svg", aria: { hidden: true }, size: 18, class: "checked flex-item-justify-end" %>
|
||||
</div>
|
||||
|
||||
<% Current.account.users.active.order(:name).each do |user| %>
|
||||
<div class="btn popup__item">
|
||||
<%= form.check_box :assignee_ids, {
|
||||
multiple: true,
|
||||
checked: filter.assignees.include?(user),
|
||||
<%= form.check_box "assignment_status", {
|
||||
checked: filter.assignment_status.unassigned?,
|
||||
data: { action: "change->form#submit" },
|
||||
include_hidden: false,
|
||||
}, user.id %>
|
||||
}, "unassigned" %>
|
||||
|
||||
<%= form.label :assignee_ids, user.name, for: form.field_id(:assignee_ids, user.id), class: "overflow-ellipsis" %>
|
||||
<%= form.label :assignment_status, "No one", class: "overflow-ellipsis" %>
|
||||
<%= image_tag "check.svg", aria: { hidden: true }, size: 18, class: "checked flex-item-justify-end" %>
|
||||
</div>
|
||||
|
||||
<% Current.account.users.active.order(:name).each do |user| %>
|
||||
<div class="btn popup__item">
|
||||
<%= form.check_box "assignee_ids[]", {
|
||||
checked: filter.assignees.include?(user),
|
||||
data: { action: "change->form#submit" },
|
||||
include_hidden: false,
|
||||
}, user.id %>
|
||||
|
||||
<%= form.label "assignee_ids[]", user.name, for: dom_id(user, :filter), class: "overflow-ellipsis" %>
|
||||
<%= image_tag "check.svg", aria: { hidden: true }, size: 18, class: "checked flex-item-justify-end" %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</dialog>
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -1,43 +1,34 @@
|
||||
<% if filter.buckets.many? %>
|
||||
<div class="quick-filter position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="btn input input--select flex-inline txt-small" data-action="click->dialog#open:stop">
|
||||
<span class="overflow-ellipsis">
|
||||
<% if filter.buckets.any? %>
|
||||
<%= filter.buckets.map(&:name).to_sentence %>
|
||||
<% else %>
|
||||
In collection
|
||||
<% end %>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<dialog class="events__popup popup panel flex-column align-start gap-half fill-white shadow"
|
||||
aria-label="In collection" aria-description="In collection"
|
||||
data-dialog-target="dialog" data-action="turbo:before-cache@document->dialog#close">
|
||||
<strong class="popup__title margin-block-start-half pad-inline-half">In collection</strong>
|
||||
<%= form_with url: bubbles_path, method: :get, class: "flex flex-column full-width popup__list",
|
||||
data: { controller: "form" } do |form| %>
|
||||
<% filter.as_params.except(:bucket_ids).each do |key, value| %>
|
||||
<%= filter_hidden_field_tag key, value %>
|
||||
<% end %>
|
||||
|
||||
<%= link_to bubbles_path(filter.as_params.except(:bucket_ids)), class: "btn popup__item" do %>
|
||||
<span class="overflow-ellipsis">Clear all</span>
|
||||
<% end %>
|
||||
|
||||
<% filter.buckets.order(:name).each do |bucket| %>
|
||||
<div class="btn popup__item">
|
||||
<%= form.check_box :bucket_ids, {
|
||||
multiple: true,
|
||||
checked: filter.buckets.include?(bucket),
|
||||
data: { action: "change->form#submit" },
|
||||
include_hidden: false,
|
||||
}, bucket.id %>
|
||||
|
||||
<%= form.label :bucket_ids, bucket.name, for: form.field_id(:bucket_ids, bucket.id), class: "overflow-ellipsis" %>
|
||||
<%= image_tag "check.svg", aria: { hidden: true }, size: 18, class: "checked flex-item-justify-end" %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="quick-filter position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="btn input input--select flex-inline txt-small" data-action="click->dialog#open:stop">
|
||||
<span class="overflow-ellipsis">
|
||||
<% if filter.buckets.any? %>
|
||||
<%= filter.buckets.map(&:name).to_sentence %>
|
||||
<% else %>
|
||||
In Collection…
|
||||
<% end %>
|
||||
</dialog>
|
||||
</div>
|
||||
<% end %>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<dialog class="events__popup popup panel flex-column align-start gap-half fill-white shadow" data-dialog-target="dialog" data-action="turbo:before-cache@document->dialog#close">
|
||||
<strong class="popup__title margin-block-start-half pad-inline-half">In Collection</strong>
|
||||
<%= form_with url: bubbles_path, method: :get, class: "flex flex-column full-width popup__list",
|
||||
data: { controller: "form" } do |form| %>
|
||||
<% filter.as_params.except(:bucket_ids).each do |key, value| %>
|
||||
<%= filter_hidden_field_tag key, value %>
|
||||
<% end %>
|
||||
|
||||
<% Current.user.buckets.order(:name).each do |bucket| %>
|
||||
<div class="btn popup__item">
|
||||
<%= form.check_box "bucket_ids[]", {
|
||||
checked: filter.buckets.include?(bucket),
|
||||
data: { action: "change->form#submit" },
|
||||
include_hidden: false,
|
||||
}, bucket.id %>
|
||||
|
||||
<%= form.label "bucket_ids[]", bucket.name, for: dom_id(bucket, :filter), class: "overflow-ellipsis" %>
|
||||
<%= image_tag "check.svg", aria: { hidden: true }, size: 18, class: "checked flex-item-justify-end" %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</dialog>
|
||||
</div>
|
||||
@@ -37,45 +37,59 @@
|
||||
<% end %>
|
||||
|
||||
<div class="filter__columns flex gap">
|
||||
<div class="filter__menu" role="group" aria-label="Sort by">
|
||||
<div class="filter__label"><strong>Sort by</strong></div>
|
||||
<div>
|
||||
<label class="btn filter__button">
|
||||
<%= radio_button_tag "indexed_by", filter.default_indexed_by, filter.default_indexed_by?, class: "visually-hidden" %>
|
||||
<menu class="filter__menu">
|
||||
<li class="filter__label"><strong>In Collection</strong></li>
|
||||
|
||||
<li>
|
||||
<%= button_tag type: :button, class: "btn filter__button", data: { action: "filter-form#clearCategory", filter_form_name_param: "bucket_ids[]" } do %>
|
||||
<span>All Collections</span>
|
||||
<% end %>
|
||||
</li>
|
||||
<% Current.user.buckets.order(:name).each do |bucket| %>
|
||||
<li>
|
||||
<%= label_tag "bucket_ids_#{bucket.id}", class: "btn filter__button" do %>
|
||||
<%= check_box_tag "bucket_ids[]", bucket.id, filter.buckets.include?(bucket), id: "bucket_ids_#{bucket.id}", hidden: true %>
|
||||
<span><%= bucket.name %></span>
|
||||
<%= image_tag "close.svg", aria: { hidden: true }, size: 24 %>
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
</menu>
|
||||
|
||||
<menu class="filter__menu">
|
||||
<li class="filter__label"><strong>Sort by</strong></li>
|
||||
<li>
|
||||
<%= label_tag "indexed_by_#{filter.default_indexed_by}", class: "btn filter__button" do %>
|
||||
<%= radio_button_tag "indexed_by", filter.default_indexed_by, filter.default_indexed_by?, hidden: true %>
|
||||
<span>Most active</span>
|
||||
<%= image_tag "close.svg", aria: { hidden: true }, size: 24 %>
|
||||
</label>
|
||||
</div>
|
||||
<% end %>
|
||||
</li>
|
||||
|
||||
<% Filter::INDEXES.each do |index| %>
|
||||
<div>
|
||||
<li>
|
||||
<label class="btn filter__button">
|
||||
<%= radio_button_tag "indexed_by", index, filter.indexed_by == index, class: "visually-hidden" %>
|
||||
<span><%= index.humanize %></span>
|
||||
<%= image_tag "close.svg", aria: { hidden: true }, size: 24 %>
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
<% end %>
|
||||
</div>
|
||||
</menu>
|
||||
|
||||
<div class="filter__menu" role="group" aria-label="In Collection">
|
||||
<div class="filter__label"><strong>In Collection</strong></div>
|
||||
|
||||
<div>
|
||||
<%= button_tag type: :button, class: "btn filter__button", data: { action: "filter-form#clearCategory", filter_form_name_param: "bucket_ids[]" } do %>
|
||||
<span>All Collections</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% Current.user.buckets.order(:name).each do |bucket| %>
|
||||
<div>
|
||||
<label class="btn filter__button">
|
||||
<%= check_box_tag "bucket_ids[]", bucket.id, filter.buckets.include?(bucket), class: "visually-hidden" %>
|
||||
<span><%= bucket.name %></span>
|
||||
<menu class="filter__menu">
|
||||
<li class="filter__label"><strong>In Stage</strong></li>
|
||||
<% workflow = Current.account.workflows.first %>
|
||||
<% workflow.stages.each do |stage| %>
|
||||
<li>
|
||||
<%= label_tag "stage_ids_#{stage.id}", class: "btn filter__button" do %>
|
||||
<%= check_box_tag "stage_ids[]", stage.id, filter.stages.include?(stage), id: "stage_ids_#{stage.id}", hidden: true %>
|
||||
<span><%= stage.name %></span>
|
||||
<%= image_tag "close.svg", aria: { hidden: true }, size: 24 %>
|
||||
</label>
|
||||
</div>
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
</div>
|
||||
</menu>
|
||||
|
||||
<div class="filter__menu" role="group" aria-label="Tagged">
|
||||
<div class="filter__label"><strong>Tagged</strong></div>
|
||||
@@ -143,24 +157,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="filter__menu" role="group" aria-label="In Stage">
|
||||
<div class="filter__label"><strong>In Stage</strong></div>
|
||||
<% Current.account.workflows.order(:name).each do |workflow| %>
|
||||
<div class="filter__label"><strong><%= workflow.name %></strong></div>
|
||||
|
||||
<% workflow.stages.order(:name).each do |stage| %>
|
||||
<div>
|
||||
<label class="btn filter__button">
|
||||
<%= check_box_tag "stage_ids[]", stage.id, filter.stages.include?(stage), class: "visually-hidden" %>
|
||||
<span><%= stage.name %></span>
|
||||
<%= image_tag "close.svg", aria: { hidden: true }, size: 24 %>
|
||||
</label>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</menu>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<div class="filters flex-inline align-center gap-half">
|
||||
<div class="filters flex-inline align-center gap-half margin-block">
|
||||
<button class="btn btn--plain txt-small" data-action="click->dialog#toggle">
|
||||
<%= image_tag "filter.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Filter</span>
|
||||
</button>
|
||||
|
||||
<div class="flex-inline center align-center gap-half">
|
||||
<%= render "filters/indexed_by", filter: filter %>
|
||||
<%= render "filters/buckets", filter: filter %>
|
||||
<%= render "filters/indexed_by", filter: filter %>
|
||||
<%= render "filters/stages", filter: filter %>
|
||||
<%= render "filters/tags", filter: filter %>
|
||||
<%= render "filters/assignees", filter: filter %>
|
||||
<%= render "filters/creators", filter: filter %>
|
||||
<%= render "filters/stages", filter: filter %>
|
||||
|
||||
<% filter.terms.each do |term| %>
|
||||
<%= filter_chip_tag %Q("#{term}"), filter.as_params_without(:terms, term) %>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<% if Current.account.workflows.any? && filter.stages.any? %>
|
||||
<% if Current.account.workflows.any? %>
|
||||
<div class="quick-filter position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="btn input input--select flex-inline txt-small" data-action="click->dialog#open:stop">
|
||||
<span class="overflow-ellipsis">
|
||||
@@ -19,15 +19,9 @@
|
||||
<% filter.as_params.except(:stage_ids).each do |key, value| %>
|
||||
<%= filter_hidden_field_tag key, value %>
|
||||
<% end %>
|
||||
|
||||
<%= link_to bubbles_path(filter.as_params.except(:stage_ids)), class: "btn popup__item" do %>
|
||||
<span class="overflow-ellipsis">Clear all</span>
|
||||
<% end %>
|
||||
|
||||
<% Current.account.workflows.order(:name).each do |workflow| %>
|
||||
<strong class="popup__title margin-block-start-half pad-inline-half flex overflow-ellipsis"><%= workflow.name %></strong>
|
||||
|
||||
<% workflow.stages.order(:name).each do |stage| %>
|
||||
|
||||
<% workflow = Current.account.workflows.first %>
|
||||
<% workflow.stages.each do |stage| %>
|
||||
<div class="btn popup__item">
|
||||
<%= form.check_box :stage_ids, {
|
||||
multiple: true,
|
||||
@@ -39,7 +33,6 @@
|
||||
<%= form.label :stage_ids, stage.name, for: form.field_id(:stage_ids, stage.id), class: "overflow-ellipsis" %>
|
||||
<%= image_tag "check.svg", aria: { hidden: true }, size: 18, class: "checked flex-item-justify-end" %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</dialog>
|
||||
|
||||
@@ -1,41 +1,40 @@
|
||||
<div class="quick-filter position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="btn input input--select flex-inline txt-small" data-action="click->dialog#open:stop">
|
||||
<span class="overflow-ellipsis">
|
||||
<% if filter.tags.any? %>
|
||||
<%= filter.tags.map(&:hashtag).to_sentence %>
|
||||
<% else %>
|
||||
Tagged…
|
||||
<% end %>
|
||||
</span>
|
||||
</button>
|
||||
<% if filter.tags.any? %>
|
||||
<div class="quick-filter position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="btn input input--select flex-inline txt-small" data-action="click->dialog#open:stop">
|
||||
<span class="overflow-ellipsis">
|
||||
<% if filter.tags.any? %>
|
||||
<%= filter.tags.map(&:hashtag).to_sentence %>
|
||||
<% else %>
|
||||
Tagged…
|
||||
<% end %>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<dialog class="events__popup popup panel flex-column align-start gap-half fill-white shadow"
|
||||
aria-label="Tagged…" aria-description="Tagged…"
|
||||
data-dialog-target="dialog" data-action="turbo:before-cache@document->dialog#close">
|
||||
<strong class="popup__title margin-block-start-half pad-inline-half overflow-ellipsis">Tagged…</strong>
|
||||
<%= form_with url: bubbles_path, method: :get, class: "flex flex-column full-width popup__list",
|
||||
data: { controller: "form" } do |form| %>
|
||||
<% filter.as_params.except(:tag_ids).each do |key, value| %>
|
||||
<%= filter_hidden_field_tag key, value %>
|
||||
<% end %>
|
||||
<dialog class="events__popup popup panel flex-column align-start gap-half fill-white shadow" data-dialog-target="dialog" data-action="turbo:before-cache@document->dialog#close">
|
||||
<strong class="popup__title margin-block-start-half pad-inline-half overflow-ellipsis">Tagged…</strong>
|
||||
<%= form_with url: bubbles_path, method: :get, class: "flex flex-column full-width popup__list",
|
||||
data: { controller: "form" } do |form| %>
|
||||
<% filter.as_params.except(:tag_ids).each do |key, value| %>
|
||||
<%= filter_hidden_field_tag key, value %>
|
||||
<% end %>
|
||||
|
||||
<%= link_to bubbles_path(filter.as_params.except(:tag_ids)), class: "btn popup__item" do %>
|
||||
<span class="overflow-ellipsis">Clear all</span>
|
||||
<% end %>
|
||||
|
||||
<% Current.account.tags.order(:title).each do |tag| %>
|
||||
<div class="btn popup__item">
|
||||
<%= form.check_box "tag_ids[]", {
|
||||
checked: filter.tags.include?(tag),
|
||||
data: { action: "change->form#submit" },
|
||||
include_hidden: false,
|
||||
}, tag.id %>
|
||||
|
||||
<%= link_to bubbles_path(filter.as_params.except(:tag_ids)), class: "btn popup__item" do %>
|
||||
<span class="overflow-ellipsis">Clear all</span>
|
||||
<%= form.label "tag_ids[]", tag.hashtag, for: dom_id(tag, :filter), class: "overflow-ellipsis" %>
|
||||
<%= image_tag "check.svg", aria: { hidden: true }, size: 18, class: "checked flex-item-justify-end" %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% Current.account.tags.order(:title).each do |tag| %>
|
||||
<div class="btn popup__item">
|
||||
<%= form.check_box :tag_ids, {
|
||||
multiple: true,
|
||||
checked: filter.tags.include?(tag),
|
||||
data: { action: "change->form#submit" },
|
||||
include_hidden: false,
|
||||
}, tag.id %>
|
||||
|
||||
<%= form.label :tag_ids, tag.hashtag, for: form.field_id(:tag_ids, tag.id), class: "overflow-ellipsis" %>
|
||||
<%= image_tag "check.svg", aria: { hidden: true }, size: 18, class: "checked flex-item-justify-end" %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</dialog>
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -1,14 +1,7 @@
|
||||
<% @page_title = "Set up Fizzy" %>
|
||||
|
||||
<div class="panel shadow center margin-block-double <%= "shake" if flash[:alert] %>" style="--panel-size: 55ch;">
|
||||
<div class="bubble center" style="--bubble-size: 12cqi; --bubble-color: var(--color-link);">
|
||||
<span class="bubble__shape"></span>
|
||||
<span class="bubble__detail bubble__date"> </span>
|
||||
<span class="bubble__detail bubble__boosts"> </span>
|
||||
<span class="bubble__detail bubble__assignee"> </span>
|
||||
</div>
|
||||
|
||||
<h1 class="margin-block-start-half margin-block-end-double">Fizzy</h1>
|
||||
<h1 class="txt-xx-large margin-block-end-double">Fizzy</h1>
|
||||
|
||||
<%= form_with model: User.new, url: first_run_path, class: "flex flex-column gap txt-large" do |form| %>
|
||||
<div class="flex align-center gap">
|
||||
|
||||
@@ -2,16 +2,14 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<nav class="align-start">
|
||||
<div class="btn btn--placeholder flex-item-justify-start"></div>
|
||||
<header class="flex flex-column center margin-block-end">
|
||||
<div class="flex align-center gap-half justify-center">
|
||||
<%= link_to "Collections", root_path %>
|
||||
<%= link_to "Activity", events_path %>
|
||||
</div>
|
||||
<h1 class="txt-x-large"><%= @page_title %></h1>
|
||||
</header>
|
||||
<%= link_to root_path, class: "btn flex-item-justify-start", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<%= image_tag "home.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Home</span>
|
||||
<% end %>
|
||||
|
||||
<%= link_to notifications_settings_path, class: "btn borderless flex-item-justify-end" do %>
|
||||
<h1 class="txt-x-large"><%= @page_title %></h1>
|
||||
|
||||
<%= link_to notifications_settings_path, class: "btn flex-item-justify-end" do %>
|
||||
<%= image_tag "settings.svg", size: 20 %>
|
||||
<span class="for-screen-reader">Notification settings</span>
|
||||
<% end %>
|
||||
|
||||
@@ -7,13 +7,7 @@
|
||||
<span class="for-screen-reader">Go back</span>
|
||||
<% end %>
|
||||
|
||||
<header class="flex flex-column center margin-block-end">
|
||||
<div class="flex align-center gap-half justify-center">
|
||||
<%= link_to "Collections", root_path %>
|
||||
<%= link_to "Activity", events_path %>
|
||||
</div>
|
||||
<h1 class="txt-x-large"><%= @page_title %></h1>
|
||||
</header>
|
||||
<h1 class="txt-x-large"><%= @page_title %></h1>
|
||||
|
||||
<div class="btn btn--placeholder flex-item-justify-end"></div>
|
||||
</nav>
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
<% @page_title = "Sign in" %>
|
||||
|
||||
<div class="panel shadow center margin-block-double <%= "shake" if flash[:alert] %>" style="--panel-size: 55ch;">
|
||||
<div class="bubble center" style="--bubble-size: 12cqi; --bubble-color: var(--color-link);">
|
||||
<span class="bubble__shape"></span>
|
||||
<span class="bubble__detail bubble__date"> </span>
|
||||
<span class="bubble__detail bubble__boosts"> </span>
|
||||
<span class="bubble__detail bubble__assignee"> </span>
|
||||
</div>
|
||||
<h1 class="txt-xx-large margin-block-end-double">Fizzy</h1>
|
||||
|
||||
<h1 class="margin-block-start-half margin-block-end-double">Fizzy</h1>
|
||||
|
||||
<%= form_with url: session_path, class: "flex flex-column gap txt-large" do |form| %>
|
||||
<%= form_with url: session_path, class: "flex flex-column gap txt-large", data: { turbo: false } do |form| %>
|
||||
<div class="flex align-center gap">
|
||||
<%= translation_button :email_address %>
|
||||
<label class="flex align-center gap input input--actor">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Rails.application.routes.draw do
|
||||
root "buckets#index"
|
||||
root "events#index"
|
||||
|
||||
resource :account do
|
||||
scope module: :accounts do
|
||||
@@ -52,6 +52,7 @@ Rails.application.routes.draw do
|
||||
resource :stage_picker
|
||||
resources :stagings
|
||||
resource :watch
|
||||
resource :pin, only: [ :show, :create, :destroy ]
|
||||
end
|
||||
|
||||
namespace :assignments, as: :assignment do
|
||||
@@ -92,4 +93,11 @@ Rails.application.routes.draw do
|
||||
post "join/:join_code", to: "users#create"
|
||||
get "up", to: "rails/health#show", as: :rails_health_check
|
||||
get "manifest" => "rails/pwa#manifest", as: :pwa_manifest
|
||||
|
||||
namespace :bubbles do
|
||||
namespace :pins do
|
||||
resource :tray, only: :show
|
||||
end
|
||||
resources :pins, only: :index
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
ActiveRecord::Schema[8.1].define(version: 1) do
|
||||
create_table "solid_cable_messages", force: :cascade do |t|
|
||||
t.binary "channel", limit: 1024, null: false
|
||||
t.binary "payload", limit: 536870912, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.integer "channel_hash", limit: 8, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.binary "payload", limit: 536870912, null: false
|
||||
t.index ["channel"], name: "index_solid_cable_messages_on_channel"
|
||||
t.index ["channel_hash"], name: "index_solid_cable_messages_on_channel_hash"
|
||||
t.index ["created_at"], name: "index_solid_cable_messages_on_created_at"
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
|
||||
ActiveRecord::Schema[8.1].define(version: 1) do
|
||||
create_table "solid_cache_entries", force: :cascade do |t|
|
||||
t.binary "key", limit: 1024, null: false
|
||||
t.binary "value", limit: 536870912, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.integer "key_hash", limit: 8, null: false
|
||||
t.integer "byte_size", limit: 4, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.binary "key", limit: 1024, null: false
|
||||
t.integer "key_hash", limit: 8, null: false
|
||||
t.binary "value", limit: 536870912, null: false
|
||||
t.index ["byte_size"], name: "index_solid_cache_entries_on_byte_size"
|
||||
t.index ["key_hash", "byte_size"], name: "index_solid_cache_entries_on_key_hash_and_byte_size"
|
||||
t.index ["key_hash"], name: "index_solid_cache_entries_on_key_hash", unique: true
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
class CreatePins < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
create_table :pins do |t|
|
||||
t.references :bubble, null: false, foreign_key: true
|
||||
t.references :user, null: false, foreign_key: true
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :pins, [ :bubble_id, :user_id ], unique: true
|
||||
end
|
||||
end
|
||||
@@ -12,42 +12,42 @@
|
||||
|
||||
ActiveRecord::Schema[8.1].define(version: 1) do
|
||||
create_table "solid_queue_blocked_executions", force: :cascade do |t|
|
||||
t.bigint "job_id", null: false
|
||||
t.string "queue_name", null: false
|
||||
t.integer "priority", default: 0, null: false
|
||||
t.string "concurrency_key", null: false
|
||||
t.datetime "expires_at", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "expires_at", null: false
|
||||
t.bigint "job_id", null: false
|
||||
t.integer "priority", default: 0, null: false
|
||||
t.string "queue_name", null: false
|
||||
t.index ["concurrency_key", "priority", "job_id"], name: "index_solid_queue_blocked_executions_for_release"
|
||||
t.index ["expires_at", "concurrency_key"], name: "index_solid_queue_blocked_executions_for_maintenance"
|
||||
t.index ["job_id"], name: "index_solid_queue_blocked_executions_on_job_id", unique: true
|
||||
end
|
||||
|
||||
create_table "solid_queue_claimed_executions", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.bigint "job_id", null: false
|
||||
t.bigint "process_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.index ["job_id"], name: "index_solid_queue_claimed_executions_on_job_id", unique: true
|
||||
t.index ["process_id", "job_id"], name: "index_solid_queue_claimed_executions_on_process_id_and_job_id"
|
||||
end
|
||||
|
||||
create_table "solid_queue_failed_executions", force: :cascade do |t|
|
||||
t.bigint "job_id", null: false
|
||||
t.text "error"
|
||||
t.datetime "created_at", null: false
|
||||
t.text "error"
|
||||
t.bigint "job_id", null: false
|
||||
t.index ["job_id"], name: "index_solid_queue_failed_executions_on_job_id", unique: true
|
||||
end
|
||||
|
||||
create_table "solid_queue_jobs", force: :cascade do |t|
|
||||
t.string "queue_name", null: false
|
||||
t.string "class_name", null: false
|
||||
t.text "arguments"
|
||||
t.integer "priority", default: 0, null: false
|
||||
t.string "active_job_id"
|
||||
t.datetime "scheduled_at"
|
||||
t.datetime "finished_at"
|
||||
t.text "arguments"
|
||||
t.string "class_name", null: false
|
||||
t.string "concurrency_key"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "finished_at"
|
||||
t.integer "priority", default: 0, null: false
|
||||
t.string "queue_name", null: false
|
||||
t.datetime "scheduled_at"
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["active_job_id"], name: "index_solid_queue_jobs_on_active_job_id"
|
||||
t.index ["class_name"], name: "index_solid_queue_jobs_on_class_name"
|
||||
@@ -57,76 +57,76 @@ ActiveRecord::Schema[8.1].define(version: 1) do
|
||||
end
|
||||
|
||||
create_table "solid_queue_pauses", force: :cascade do |t|
|
||||
t.string "queue_name", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.string "queue_name", null: false
|
||||
t.index ["queue_name"], name: "index_solid_queue_pauses_on_queue_name", unique: true
|
||||
end
|
||||
|
||||
create_table "solid_queue_processes", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.string "hostname"
|
||||
t.string "kind", null: false
|
||||
t.datetime "last_heartbeat_at", null: false
|
||||
t.bigint "supervisor_id"
|
||||
t.integer "pid", null: false
|
||||
t.string "hostname"
|
||||
t.text "metadata"
|
||||
t.datetime "created_at", null: false
|
||||
t.string "name", null: false
|
||||
t.integer "pid", null: false
|
||||
t.bigint "supervisor_id"
|
||||
t.index ["last_heartbeat_at"], name: "index_solid_queue_processes_on_last_heartbeat_at"
|
||||
t.index ["name", "supervisor_id"], name: "index_solid_queue_processes_on_name_and_supervisor_id", unique: true
|
||||
t.index ["supervisor_id"], name: "index_solid_queue_processes_on_supervisor_id"
|
||||
end
|
||||
|
||||
create_table "solid_queue_ready_executions", force: :cascade do |t|
|
||||
t.bigint "job_id", null: false
|
||||
t.string "queue_name", null: false
|
||||
t.integer "priority", default: 0, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.bigint "job_id", null: false
|
||||
t.integer "priority", default: 0, null: false
|
||||
t.string "queue_name", null: false
|
||||
t.index ["job_id"], name: "index_solid_queue_ready_executions_on_job_id", unique: true
|
||||
t.index ["priority", "job_id"], name: "index_solid_queue_poll_all"
|
||||
t.index ["queue_name", "priority", "job_id"], name: "index_solid_queue_poll_by_queue"
|
||||
end
|
||||
|
||||
create_table "solid_queue_recurring_executions", force: :cascade do |t|
|
||||
t.bigint "job_id", null: false
|
||||
t.string "task_key", null: false
|
||||
t.datetime "run_at", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.bigint "job_id", null: false
|
||||
t.datetime "run_at", null: false
|
||||
t.string "task_key", null: false
|
||||
t.index ["job_id"], name: "index_solid_queue_recurring_executions_on_job_id", unique: true
|
||||
t.index ["task_key", "run_at"], name: "index_solid_queue_recurring_executions_on_task_key_and_run_at", unique: true
|
||||
end
|
||||
|
||||
create_table "solid_queue_recurring_tasks", force: :cascade do |t|
|
||||
t.string "key", null: false
|
||||
t.string "schedule", null: false
|
||||
t.string "command", limit: 2048
|
||||
t.string "class_name"
|
||||
t.text "arguments"
|
||||
t.string "queue_name"
|
||||
t.integer "priority", default: 0
|
||||
t.boolean "static", default: true, null: false
|
||||
t.text "description"
|
||||
t.string "class_name"
|
||||
t.string "command", limit: 2048
|
||||
t.datetime "created_at", null: false
|
||||
t.text "description"
|
||||
t.string "key", null: false
|
||||
t.integer "priority", default: 0
|
||||
t.string "queue_name"
|
||||
t.string "schedule", null: false
|
||||
t.boolean "static", default: true, null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["key"], name: "index_solid_queue_recurring_tasks_on_key", unique: true
|
||||
t.index ["static"], name: "index_solid_queue_recurring_tasks_on_static"
|
||||
end
|
||||
|
||||
create_table "solid_queue_scheduled_executions", force: :cascade do |t|
|
||||
t.bigint "job_id", null: false
|
||||
t.string "queue_name", null: false
|
||||
t.integer "priority", default: 0, null: false
|
||||
t.datetime "scheduled_at", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.bigint "job_id", null: false
|
||||
t.integer "priority", default: 0, null: false
|
||||
t.string "queue_name", null: false
|
||||
t.datetime "scheduled_at", null: false
|
||||
t.index ["job_id"], name: "index_solid_queue_scheduled_executions_on_job_id", unique: true
|
||||
t.index ["scheduled_at", "priority", "job_id"], name: "index_solid_queue_dispatch_all"
|
||||
end
|
||||
|
||||
create_table "solid_queue_semaphores", force: :cascade do |t|
|
||||
t.string "key", null: false
|
||||
t.integer "value", default: 1, null: false
|
||||
t.datetime "expires_at", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "expires_at", null: false
|
||||
t.string "key", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "value", default: 1, null: false
|
||||
t.index ["expires_at"], name: "index_solid_queue_semaphores_on_expires_at"
|
||||
t.index ["key", "value"], name: "index_solid_queue_semaphores_on_key_and_value"
|
||||
t.index ["key"], name: "index_solid_queue_semaphores_on_key", unique: true
|
||||
|
||||
@@ -10,42 +10,42 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.1].define(version: 2025_03_04_140641) do
|
||||
ActiveRecord::Schema[8.1].define(version: 2025_03_18_005138) do
|
||||
create_table "accesses", force: :cascade do |t|
|
||||
t.integer "bucket_id", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.index ["bucket_id", "user_id"], name: "index_accesses_on_bucket_id_and_user_id", unique: true
|
||||
t.index ["bucket_id"], name: "index_accesses_on_bucket_id"
|
||||
t.index ["user_id"], name: "index_accesses_on_user_id"
|
||||
end
|
||||
|
||||
create_table "accounts", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "join_code"
|
||||
t.string "name", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["join_code"], name: "index_accounts_on_join_code", unique: true
|
||||
t.index ["name"], name: "index_accounts_on_name", unique: true
|
||||
end
|
||||
|
||||
create_table "action_text_markdowns", force: :cascade do |t|
|
||||
t.string "record_type", null: false
|
||||
t.integer "record_id", null: false
|
||||
t.string "name", null: false
|
||||
t.text "content", default: "", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.string "name", null: false
|
||||
t.integer "record_id", null: false
|
||||
t.string "record_type", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["record_type", "record_id"], name: "index_action_text_markdowns_on_record"
|
||||
end
|
||||
|
||||
create_table "active_storage_attachments", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "record_type", null: false
|
||||
t.bigint "record_id", null: false
|
||||
t.bigint "blob_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.string "name", null: false
|
||||
t.bigint "record_id", null: false
|
||||
t.string "record_type", null: false
|
||||
t.string "slug"
|
||||
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
|
||||
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
|
||||
@@ -53,14 +53,14 @@ ActiveRecord::Schema[8.1].define(version: 2025_03_04_140641) do
|
||||
end
|
||||
|
||||
create_table "active_storage_blobs", force: :cascade do |t|
|
||||
t.string "key", null: false
|
||||
t.string "filename", null: false
|
||||
t.string "content_type"
|
||||
t.text "metadata"
|
||||
t.string "service_name", null: false
|
||||
t.bigint "byte_size", null: false
|
||||
t.string "checksum"
|
||||
t.string "content_type"
|
||||
t.datetime "created_at", null: false
|
||||
t.string "filename", null: false
|
||||
t.string "key", null: false
|
||||
t.text "metadata"
|
||||
t.string "service_name", null: false
|
||||
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
|
||||
end
|
||||
|
||||
@@ -71,45 +71,45 @@ ActiveRecord::Schema[8.1].define(version: 2025_03_04_140641) do
|
||||
end
|
||||
|
||||
create_table "assignees_filters", id: false, force: :cascade do |t|
|
||||
t.integer "filter_id", null: false
|
||||
t.integer "assignee_id", null: false
|
||||
t.integer "filter_id", null: false
|
||||
t.index ["assignee_id"], name: "index_assignees_filters_on_assignee_id"
|
||||
t.index ["filter_id"], name: "index_assignees_filters_on_filter_id"
|
||||
end
|
||||
|
||||
create_table "assigners_filters", id: false, force: :cascade do |t|
|
||||
t.integer "filter_id", null: false
|
||||
t.integer "assigner_id", null: false
|
||||
t.integer "filter_id", null: false
|
||||
t.index ["assigner_id"], name: "index_assigners_filters_on_assigner_id"
|
||||
t.index ["filter_id"], name: "index_assigners_filters_on_filter_id"
|
||||
end
|
||||
|
||||
create_table "assignments", force: :cascade do |t|
|
||||
t.integer "assignee_id", null: false
|
||||
t.integer "assigner_id", null: false
|
||||
t.integer "bubble_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "assigner_id", null: false
|
||||
t.index ["assignee_id", "bubble_id"], name: "index_assignments_on_assignee_id_and_bubble_id", unique: true
|
||||
t.index ["bubble_id"], name: "index_assignments_on_bubble_id"
|
||||
end
|
||||
|
||||
create_table "bubbles", force: :cascade do |t|
|
||||
t.string "title"
|
||||
t.string "color"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "creator_id", null: false
|
||||
t.date "due_on"
|
||||
t.integer "bucket_id", null: false
|
||||
t.integer "boosts_count", default: 0, null: false
|
||||
t.integer "stage_id"
|
||||
t.integer "comments_count", default: 0, null: false
|
||||
t.float "activity_score", default: 0.0, null: false
|
||||
t.text "status", default: "creating", null: false
|
||||
t.datetime "auto_pop_at", null: false
|
||||
t.datetime "activity_score_at"
|
||||
t.float "activity_score_order", default: 0.0, null: false
|
||||
t.datetime "auto_pop_at", null: false
|
||||
t.integer "boosts_count", default: 0, null: false
|
||||
t.integer "bucket_id", null: false
|
||||
t.string "color"
|
||||
t.integer "comments_count", default: 0, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.integer "creator_id", null: false
|
||||
t.date "due_on"
|
||||
t.integer "stage_id"
|
||||
t.text "status", default: "creating", null: false
|
||||
t.string "title"
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["activity_score_order"], name: "index_bubbles_on_activity_score_order", order: :desc
|
||||
t.index ["auto_pop_at"], name: "index_bubbles_on_auto_pop_at"
|
||||
t.index ["bucket_id"], name: "index_bubbles_on_bucket_id"
|
||||
@@ -118,31 +118,31 @@ ActiveRecord::Schema[8.1].define(version: 2025_03_04_140641) do
|
||||
|
||||
create_table "buckets", force: :cascade do |t|
|
||||
t.integer "account_id", null: false
|
||||
t.boolean "all_access", default: false, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.integer "creator_id", null: false
|
||||
t.string "name", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.boolean "all_access", default: false, null: false
|
||||
t.index ["account_id"], name: "index_buckets_on_account_id"
|
||||
t.index ["creator_id"], name: "index_buckets_on_creator_id"
|
||||
end
|
||||
|
||||
create_table "buckets_filters", id: false, force: :cascade do |t|
|
||||
t.integer "filter_id", null: false
|
||||
t.integer "bucket_id", null: false
|
||||
t.integer "filter_id", null: false
|
||||
t.index ["bucket_id"], name: "index_buckets_filters_on_bucket_id"
|
||||
t.index ["filter_id"], name: "index_buckets_filters_on_filter_id"
|
||||
end
|
||||
|
||||
create_table "comments", force: :cascade do |t|
|
||||
t.integer "creator_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.integer "creator_id", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "creators_filters", id: false, force: :cascade do |t|
|
||||
t.integer "filter_id", null: false
|
||||
t.integer "creator_id", null: false
|
||||
t.integer "filter_id", null: false
|
||||
t.index ["creator_id"], name: "index_creators_filters_on_creator_id"
|
||||
t.index ["filter_id"], name: "index_creators_filters_on_filter_id"
|
||||
end
|
||||
@@ -153,25 +153,25 @@ ActiveRecord::Schema[8.1].define(version: 2025_03_04_140641) do
|
||||
end
|
||||
|
||||
create_table "events", force: :cascade do |t|
|
||||
t.integer "creator_id", null: false
|
||||
t.json "particulars", default: {}
|
||||
t.string "action", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "summary_id", null: false
|
||||
t.integer "bubble_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.integer "creator_id", null: false
|
||||
t.date "due_date"
|
||||
t.json "particulars", default: {}
|
||||
t.integer "summary_id", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["bubble_id"], name: "index_events_on_bubble_id"
|
||||
t.index ["creator_id"], name: "index_events_on_creator_id"
|
||||
t.index ["summary_id", "action"], name: "index_events_on_summary_id_and_action"
|
||||
end
|
||||
|
||||
create_table "filters", force: :cascade do |t|
|
||||
t.integer "creator_id", null: false
|
||||
t.string "params_digest", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "creator_id", null: false
|
||||
t.json "fields", default: {}, null: false
|
||||
t.string "params_digest", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["creator_id", "params_digest"], name: "index_filters_on_creator_id_and_params_digest", unique: true
|
||||
end
|
||||
|
||||
@@ -191,23 +191,23 @@ ActiveRecord::Schema[8.1].define(version: 2025_03_04_140641) do
|
||||
|
||||
create_table "messages", force: :cascade do |t|
|
||||
t.integer "bubble_id", null: false
|
||||
t.string "messageable_type", null: false
|
||||
t.integer "messageable_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.integer "messageable_id", null: false
|
||||
t.string "messageable_type", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["bubble_id"], name: "index_messages_on_bubble_id"
|
||||
t.index ["messageable_type", "messageable_id"], name: "index_messages_on_messageable", unique: true
|
||||
end
|
||||
|
||||
create_table "notifications", force: :cascade do |t|
|
||||
t.integer "user_id", null: false
|
||||
t.integer "event_id", null: false
|
||||
t.integer "bubble_id", null: false
|
||||
t.string "resource_type", null: false
|
||||
t.integer "resource_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "event_id", null: false
|
||||
t.datetime "read_at"
|
||||
t.integer "resource_id", null: false
|
||||
t.string "resource_type", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.index ["bubble_id"], name: "index_notifications_on_bubble_id"
|
||||
t.index ["event_id"], name: "index_notifications_on_event_id"
|
||||
t.index ["resource_type", "resource_id"], name: "index_notifications_on_resource"
|
||||
@@ -215,40 +215,50 @@ ActiveRecord::Schema[8.1].define(version: 2025_03_04_140641) do
|
||||
t.index ["user_id"], name: "index_notifications_on_user_id"
|
||||
end
|
||||
|
||||
create_table "pops", force: :cascade do |t|
|
||||
create_table "pins", force: :cascade do |t|
|
||||
t.integer "bubble_id", null: false
|
||||
t.integer "user_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.index ["bubble_id", "user_id"], name: "index_pins_on_bubble_id_and_user_id", unique: true
|
||||
t.index ["bubble_id"], name: "index_pins_on_bubble_id"
|
||||
t.index ["user_id"], name: "index_pins_on_user_id"
|
||||
end
|
||||
|
||||
create_table "pops", force: :cascade do |t|
|
||||
t.integer "bubble_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "user_id"
|
||||
t.index ["bubble_id"], name: "index_pops_on_bubble_id", unique: true
|
||||
t.index ["user_id"], name: "index_pops_on_user_id"
|
||||
end
|
||||
|
||||
create_table "reactions", force: :cascade do |t|
|
||||
t.integer "comment_id", null: false
|
||||
t.integer "reacter_id", null: false
|
||||
t.string "content", limit: 16, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.integer "reacter_id", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["comment_id"], name: "index_reactions_on_comment_id"
|
||||
t.index ["reacter_id"], name: "index_reactions_on_reacter_id"
|
||||
end
|
||||
|
||||
create_table "sessions", force: :cascade do |t|
|
||||
t.integer "user_id", null: false
|
||||
t.string "ip_address"
|
||||
t.string "user_agent"
|
||||
t.datetime "created_at", null: false
|
||||
t.string "ip_address"
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "user_agent"
|
||||
t.integer "user_id", null: false
|
||||
t.index ["user_id"], name: "index_sessions_on_user_id"
|
||||
end
|
||||
|
||||
create_table "subscriptions", force: :cascade do |t|
|
||||
t.string "subscribable_type", null: false
|
||||
t.integer "subscribable_id", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.integer "subscribable_id", null: false
|
||||
t.string "subscribable_type", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.index ["subscribable_type", "subscribable_id", "user_id"], name: "idx_on_subscribable_type_subscribable_id_user_id_81936d569b", unique: true
|
||||
t.index ["subscribable_type", "subscribable_id"], name: "index_subscriptions_on_subscribable"
|
||||
t.index ["user_id"], name: "index_subscriptions_on_user_id"
|
||||
@@ -256,57 +266,57 @@ ActiveRecord::Schema[8.1].define(version: 2025_03_04_140641) do
|
||||
|
||||
create_table "taggings", force: :cascade do |t|
|
||||
t.integer "bubble_id", null: false
|
||||
t.integer "tag_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.integer "tag_id", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["bubble_id", "tag_id"], name: "index_taggings_on_bubble_id_and_tag_id", unique: true
|
||||
t.index ["tag_id"], name: "index_taggings_on_tag_id"
|
||||
end
|
||||
|
||||
create_table "tags", force: :cascade do |t|
|
||||
t.string "title"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "account_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.string "title"
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["account_id"], name: "index_tags_on_account_id"
|
||||
end
|
||||
|
||||
create_table "users", force: :cascade do |t|
|
||||
t.integer "account_id", null: false
|
||||
t.string "name", null: false
|
||||
t.string "email_address"
|
||||
t.string "password_digest"
|
||||
t.boolean "active", default: true, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "email_address"
|
||||
t.string "name", null: false
|
||||
t.string "password_digest"
|
||||
t.string "role", default: "member", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["account_id"], name: "index_users_on_account_id"
|
||||
t.index ["email_address"], name: "index_users_on_email_address", unique: true
|
||||
t.index ["role"], name: "index_users_on_role"
|
||||
end
|
||||
|
||||
create_table "watches", force: :cascade do |t|
|
||||
t.integer "user_id", null: false
|
||||
t.integer "bubble_id", null: false
|
||||
t.boolean "watching", default: true, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.boolean "watching", default: true, null: false
|
||||
t.index ["bubble_id"], name: "index_watches_on_bubble_id"
|
||||
t.index ["user_id"], name: "index_watches_on_user_id"
|
||||
end
|
||||
|
||||
create_table "workflow_stages", force: :cascade do |t|
|
||||
t.integer "workflow_id", null: false
|
||||
t.string "name", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.string "name", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "workflow_id", null: false
|
||||
t.index ["workflow_id"], name: "index_workflow_stages_on_workflow_id"
|
||||
end
|
||||
|
||||
create_table "workflows", force: :cascade do |t|
|
||||
t.integer "account_id", null: false
|
||||
t.string "name", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.string "name", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["account_id"], name: "index_workflows_on_account_id"
|
||||
end
|
||||
@@ -320,6 +330,8 @@ ActiveRecord::Schema[8.1].define(version: 2025_03_04_140641) do
|
||||
add_foreign_key "notifications", "bubbles"
|
||||
add_foreign_key "notifications", "events"
|
||||
add_foreign_key "notifications", "users"
|
||||
add_foreign_key "pins", "bubbles"
|
||||
add_foreign_key "pins", "users"
|
||||
add_foreign_key "pops", "bubbles"
|
||||
add_foreign_key "pops", "users"
|
||||
add_foreign_key "sessions", "users"
|
||||
|
||||
@@ -708,6 +708,12 @@ columns:
|
||||
default_function:
|
||||
collation:
|
||||
comment:
|
||||
pins:
|
||||
- *5
|
||||
- *25
|
||||
- *30
|
||||
- *6
|
||||
- *7
|
||||
pops:
|
||||
- *5
|
||||
- *25
|
||||
@@ -946,6 +952,7 @@ primary_keys:
|
||||
filters_tags:
|
||||
messages: id
|
||||
notifications: id
|
||||
pins: id
|
||||
pops: id
|
||||
reactions: id
|
||||
schema_migrations: version
|
||||
@@ -980,6 +987,7 @@ data_sources:
|
||||
filters_tags: true
|
||||
messages: true
|
||||
notifications: true
|
||||
pins: true
|
||||
pops: true
|
||||
reactions: true
|
||||
schema_migrations: true
|
||||
@@ -1282,22 +1290,6 @@ indexes:
|
||||
comment:
|
||||
valid: true
|
||||
bubbles:
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
table: bubbles
|
||||
name: index_bubbles_on_activity_score_order
|
||||
unique: false
|
||||
columns:
|
||||
- activity_score_order
|
||||
lengths: {}
|
||||
orders: :desc
|
||||
opclasses: {}
|
||||
where:
|
||||
type:
|
||||
using:
|
||||
include:
|
||||
nulls_not_distinct:
|
||||
comment:
|
||||
valid: true
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
table: bubbles
|
||||
name: index_bubbles_on_stage_id
|
||||
@@ -1346,6 +1338,22 @@ indexes:
|
||||
nulls_not_distinct:
|
||||
comment:
|
||||
valid: true
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
table: bubbles
|
||||
name: index_bubbles_on_activity_score_order
|
||||
unique: false
|
||||
columns:
|
||||
- activity_score_order
|
||||
lengths: {}
|
||||
orders: :desc
|
||||
opclasses: {}
|
||||
where:
|
||||
type:
|
||||
using:
|
||||
include:
|
||||
nulls_not_distinct:
|
||||
comment:
|
||||
valid: true
|
||||
buckets:
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
table: buckets
|
||||
@@ -1701,6 +1709,56 @@ indexes:
|
||||
nulls_not_distinct:
|
||||
comment:
|
||||
valid: true
|
||||
pins:
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
table: pins
|
||||
name: index_pins_on_bubble_id_and_user_id
|
||||
unique: true
|
||||
columns:
|
||||
- bubble_id
|
||||
- user_id
|
||||
lengths: {}
|
||||
orders: {}
|
||||
opclasses: {}
|
||||
where:
|
||||
type:
|
||||
using:
|
||||
include:
|
||||
nulls_not_distinct:
|
||||
comment:
|
||||
valid: true
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
table: pins
|
||||
name: index_pins_on_user_id
|
||||
unique: false
|
||||
columns:
|
||||
- user_id
|
||||
lengths: {}
|
||||
orders: {}
|
||||
opclasses: {}
|
||||
where:
|
||||
type:
|
||||
using:
|
||||
include:
|
||||
nulls_not_distinct:
|
||||
comment:
|
||||
valid: true
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
table: pins
|
||||
name: index_pins_on_bubble_id
|
||||
unique: false
|
||||
columns:
|
||||
- bubble_id
|
||||
lengths: {}
|
||||
orders: {}
|
||||
opclasses: {}
|
||||
where:
|
||||
type:
|
||||
using:
|
||||
include:
|
||||
nulls_not_distinct:
|
||||
comment:
|
||||
valid: true
|
||||
pops:
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
table: pops
|
||||
@@ -2004,4 +2062,4 @@ indexes:
|
||||
nulls_not_distinct:
|
||||
comment:
|
||||
valid: true
|
||||
version: 20250304140641
|
||||
version: 20250318005138
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
require "test_helper"
|
||||
|
||||
class Bubbles::PinsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in_as :kevin
|
||||
end
|
||||
|
||||
test "create" do
|
||||
assert_changes -> { bubbles(:layout).pinned_by?(users(:kevin)) }, from: false, to: true do
|
||||
post bucket_bubble_pin_url(buckets(:writebook), bubbles(:layout))
|
||||
end
|
||||
|
||||
assert_redirected_to bucket_bubble_pin_url(buckets(:writebook), bubbles(:layout))
|
||||
end
|
||||
|
||||
test "destroy" do
|
||||
assert_changes -> { bubbles(:shipping).pinned_by?(users(:kevin)) }, from: true, to: false do
|
||||
delete bucket_bubble_pin_url(buckets(:writebook), bubbles(:shipping))
|
||||
end
|
||||
|
||||
assert_redirected_to bucket_bubble_pin_url(buckets(:writebook), bubbles(:shipping))
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
logo_kevin:
|
||||
bubble: logo
|
||||
user: kevin
|
||||
|
||||
shipping_kevin:
|
||||
bubble: shipping
|
||||
user: kevin
|
||||
@@ -1,11 +1,11 @@
|
||||
qa_triage:
|
||||
name: Triage
|
||||
qa_maybe:
|
||||
name: Maybe?
|
||||
workflow: qa
|
||||
|
||||
qa_in_progress:
|
||||
name: In Progress
|
||||
qa_not_now:
|
||||
name: Not now
|
||||
workflow: qa
|
||||
|
||||
qa_ready_for_review:
|
||||
name: Ready for Review
|
||||
qa_done:
|
||||
name: Done
|
||||
workflow: qa
|
||||
|
||||
@@ -34,14 +34,14 @@ class BubbleMessagesTest < ActionDispatch::IntegrationTest
|
||||
assert_equal "assigned", bubble.messages.last.messageable.events.last.action
|
||||
|
||||
# Stage it
|
||||
post bucket_bubble_stagings_url(buckets(:writebook), bubble), params: { stage_id: workflow_stages(:qa_triage).id }
|
||||
post bucket_bubble_stagings_url(buckets(:writebook), bubble), params: { stage_id: workflow_stages(:qa_maybe).id }
|
||||
assert_equal 3, bubble.messages.count
|
||||
assert_predicate bubble.messages.last, :event_summary?
|
||||
assert_equal 2, bubble.messages.last.event_summary.events.count
|
||||
assert_equal "staged", bubble.messages.last.messageable.events.last.action
|
||||
|
||||
# Unstage it
|
||||
post bucket_bubble_stagings_url(buckets(:writebook), bubble), params: { stage_id: workflow_stages(:qa_triage).id }
|
||||
post bucket_bubble_stagings_url(buckets(:writebook), bubble), params: { stage_id: workflow_stages(:qa_maybe).id }
|
||||
assert_equal 3, bubble.messages.count
|
||||
assert_predicate bubble.messages.last, :event_summary?
|
||||
assert_equal 3, bubble.messages.last.event_summary.events.count
|
||||
|
||||
@@ -8,7 +8,7 @@ class FilterTest < ActiveSupport::TestCase
|
||||
|
||||
bubbles(:layout).capture Comment.new(body: "I hate haggis")
|
||||
bubbles(:logo).capture Comment.new(body: "I love haggis")
|
||||
bubbles(:logo).update(stage: workflow_stages(:qa_triage))
|
||||
bubbles(:logo).update(stage: workflow_stages(:qa_maybe))
|
||||
end
|
||||
|
||||
assert_not_includes users(:kevin).filters.new.bubbles, @new_bubble
|
||||
@@ -19,8 +19,8 @@ class FilterTest < ActiveSupport::TestCase
|
||||
filter = users(:david).filters.new creator_ids: [ users(:david).id ], tag_ids: [ tags(:mobile).id ]
|
||||
assert_equal [ bubbles(:layout) ], filter.bubbles
|
||||
|
||||
filter = users(:david).filters.new stage_ids: [ workflow_stages(:qa_triage).id ]
|
||||
assert_equal [ bubbles(:logo) ], filter.bubbles
|
||||
filter = users(:david).filters.new stage_ids: [ workflow_stages(:qa_maybe).id ]
|
||||
assert_equal [ bubbles(:logo), @new_bubble ], filter.bubbles
|
||||
|
||||
filter = users(:david).filters.new assignment_status: "unassigned", bucket_ids: [ @new_bucket.id ]
|
||||
assert_equal [ @new_bubble ], filter.bubbles
|
||||
@@ -105,8 +105,8 @@ class FilterTest < ActiveSupport::TestCase
|
||||
test "summary" do
|
||||
assert_equal "Most discussed, tagged #Mobile, and assigned to JZ ", filters(:jz_assignments).summary
|
||||
|
||||
filters(:jz_assignments).update!(stages: workflow_stages(:qa_triage, :qa_in_progress))
|
||||
assert_equal "Most discussed, tagged #Mobile, assigned to JZ, and staged in Triage or In Progress ", filters(:jz_assignments).summary
|
||||
filters(:jz_assignments).update!(stages: workflow_stages(:qa_maybe, :qa_not_now))
|
||||
assert_equal "Most discussed, tagged #Mobile, assigned to JZ, and staged in Maybe? or Not now ", filters(:jz_assignments).summary
|
||||
|
||||
filters(:jz_assignments).update!(stages: [], assignees: [], tags: [], buckets: [ buckets(:writebook) ])
|
||||
assert_equal "Most discussed in Writebook", filters(:jz_assignments).summary
|
||||
|
||||