Merge branch 'main' into consolidate-cards

* main: (45 commits)
  Fix tests
  Rework min card design and meta section for all cards
  Move `title` to the `img` element so we get hover in both cases
  Remove sorting filter UI
  Sort Considering and Doing by `updated_at`
  Restore comment action icon
  Fix dot escaping container
  Seen cards should be flat
  Ensure overflow cards are hidden
  Arrow needs to match derived color
  Dialog target needs to be present
  This isn't necessary in the mini card template
  Revert "The button and dialog need not render in previews"
  Paginate collection cards
  Add a rails helper for the new custom stream action
  Add test
  Reflect picked color automatically with a custom stream action
  Simplify conditions to pick up a color
  Reuse existing constant
  Update fixtures and test
  ...
This commit is contained in:
Andy Smith
2025-04-10 15:12:38 -05:00
113 changed files with 354 additions and 1092 deletions
+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3,22V8H7V22H3M10,22V2H14V22H10M17,22V14H21V22H17Z" /></svg>
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m3 22v-14h4v14zm7 0v-20h4v20zm7 0v-8h4v8z"/></svg>

Before

Width:  |  Height:  |  Size: 129 B

After

Width:  |  Height:  |  Size: 119 B

-1
View File
@@ -1 +0,0 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m23.119 20-9.347-17.85a2 2 0 0 0 -3.543 0l-9.348 17.85a2 2 0 0 0 1.772 2.928h18.694a2 2 0 0 0 1.772-2.928zm-12.119-11.577a1 1 0 0 1 2 0v6a1 1 0 1 1 -2 0zm1.05 11.51h-.028a1.528 1.528 0 0 1 -1.522-1.47 1.476 1.476 0 0 1 1.448-1.53h.028a1.527 1.527 0 0 1 1.524 1.467 1.475 1.475 0 0 1 -1.45 1.533z"/></svg>

Before

Width:  |  Height:  |  Size: 373 B

-1
View File
@@ -1 +0,0 @@
<svg viewBox="0 0 990 990" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h990v990h-990z" fill="none"/><path d="m391.65 879.47c-110.52-15.95-212.21-91.86-255.92-191.23-66.78-143.65-41.62-347.61 48.08-481.17 368.33-516.3 1252.97 520.2 451.03 660.78-44.07 8.84-88.98 13.49-133.01 15.68-36.69 2-73.37 1.91-109.99-4.03z"/></svg>

Before

Width:  |  Height:  |  Size: 325 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>calendar-add-1</title><path d="M9.569,16.5H2.5A.5.5,0,0,1,2,16V7.5A.5.5,0,0,1,2.5,7H16a.5.5,0,0,1,.5.5V9.57a7.281,7.281,0,0,1,2,0V3a1,1,0,0,0-1-1H15a.25.25,0,0,1-.25-.25v-1a.75.75,0,1,0-1.5,0V4.5a.75.75,0,1,1-1.5,0v-2a.5.5,0,0,0-.5-.5H7a.25.25,0,0,1-.25-.25v-1a.75.75,0,0,0-1.5,0V4.5a.75.75,0,1,1-1.5,0v-2a.5.5,0,0,0-.5-.5H1A1,1,0,0,0,0,3V16.5a2,2,0,0,0,2,2H9.569a7.281,7.281,0,0,1,0-2Z"/><path d="M17.5,11A6.5,6.5,0,1,0,24,17.5,6.508,6.508,0,0,0,17.5,11Zm.75,9a.75.75,0,1,1-1.5,0V18.5a.25.25,0,0,0-.25-.25H15a.75.75,0,0,1,0-1.5h1.5a.25.25,0,0,0,.25-.25V15a.75.75,0,1,1,1.5,0v1.5a.25.25,0,0,0,.25.25H20a.75.75,0,0,1,0,1.5H18.5a.25.25,0,0,0-.25.25Z"/></svg>

Before

Width:  |  Height:  |  Size: 723 B

-1
View File
@@ -1 +0,0 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m21.5 3h-2.75-.00000001c-.138071-.00000001-.25-.111929-.25-.25v-1.75c0-.552285-.447715-1-1-1s-1 .447715-1 1v4.75c0 .414214-.335786.75-.75.75h-.00000003c-.414214-.00000002-.75-.335786-.75-.75v-2.25c0-.276142-.223858-.5-.5-.5h-6.25-.00000001c-.137682.00000109-.249449-.11132-.25-.249v-1.751-.00000753c0-.552285-.447715-1-1-1s-1 .447715-1 1v.00001506 4.75c0 .414214-.335786.75-.75.75h-.00000003c-.414214-.00000002-.75-.335786-.75-.75v-2.25001c0-.276142-.223858-.5-.5-.5h-1.5-.00000009c-1.10457.00000005-2 .895431-2 2v17-.0000003c-.00000017 1.10457.89543 2 2 2h19-.00000009c1.10457.00000005 2-.89543 2-2v-17c0-1.10457-.89543-2-2-2zm-.5 19h-18-.00000002c-.276142-.00000001-.5-.223858-.5-.5v-12 .00000008c-.00000004-.276142.223858-.5.5-.5h18-.00000002c.276142-.00000001.5.223858.5.5v12 .00000002c0 .276142-.223858.5-.5.5-.00000001 0-.00000002 0-.00000002 0z"/></svg>

Before

Width:  |  Height:  |  Size: 929 B

-1
View File
@@ -1 +0,0 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m6.5 12c0 .4.3.7.7.7h4s.1 0 .1.1v4c0 .4.3.7.7.7s.7-.3.7-.7v-4s0-.1.1-.1h4c.4 0 .7-.3.7-.7s-.3-.7-.7-.7h-4s-.1 0-.1-.1v-4c0-.4-.3-.7-.7-.7s-.7.3-.7.7v4s0 .1-.1.1h-4c-.4 0-.7.3-.7.7z"/><path d="m23.3 8.4c-1.6-4.3-5.7-7.8-9.8-8.2-1.9-.2-4 .3-5.8 1.3-.8-.8-1.9-1.4-3.2-1.4-2.4.1-4.3 2-4.3 4.5s.9 3 2.1 3.7c-.2.5-.5 1-.7 1.5-3.1 7.3-.7 10.8 1 12.2 1.6 1.3 3.9 2 6.4 2s6.6-1 9.5-3c4.6-3.1 6.3-7.7 4.7-12.5zm-21.1-3.8c0-1.3 1.1-2.4 2.4-2.4s1.1.2 1.5.6c.5.4.8 1.1.8 1.8s0 .1 0 .2c0 .9-.7 1.7-1.5 2-.3.1-.6.2-.9.2-.5 0-.9-.1-1.3-.4-.6-.4-1.1-1.2-1.1-2zm15.3 14.7c-4.4 3-10.6 3.5-13.5 1.1-2.3-1.9-2.4-5.3-.4-9.9.2-.5.5-1.1.7-1.6h.3c2.4 0 4.4-2 4.4-4.4s0-.9-.2-1.3c1.4-.9 3.1-1.3 4.5-1.1 3.3.4 6.8 3.3 8.1 6.9 1.8 5.3-1.7 8.7-3.9 10.2z"/></svg>

Before

Width:  |  Height:  |  Size: 802 B

-1
View File
@@ -1 +0,0 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m1.6 21.7c-1-.8-1.5-2.1-1.4-3.3.2-1.8 1.6-3.6 3.2-4.3 6.4-2.9 9.1 12 .6 8.9-.5-.2-.9-.4-1.4-.6-.4-.2-.7-.4-1-.7z"/><path d="m15.8.9c1-.7 2.3-.9 3.4-.4 1.6.6 2.9 2.3 3.2 4 1 6.7-13.4 5.3-8.3-1.7.3-.4.6-.8.9-1.1s.5-.6.9-.8z"/><path d="m19.7 10c1.8.6 3.2 2.2 3.6 4 .6 2.6-.5 5.9-2.4 7.9-7.9 7.4-19.1-12.9-5.3-12.5.8 0 1.5 0 2.3.2.6 0 1.2.2 1.8.4z"/><path d="m.2 5.7c-.2-1.5.6-3.2 1.8-4.1 1.7-1.3 4.6-1.6 6.7-.9 8.2 3.3-3 18.7-7.5 8.3-.3-.6-.5-1.2-.6-1.8-.1-.5-.3-1-.3-1.5z"/></svg>

Before

Width:  |  Height:  |  Size: 547 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6,16.5L3,19.44V11H6M11,14.66L9.43,13.32L8,14.64V7H11M16,13L13,16V3H16M18.81,12.81L17,11H22V16L20.21,14.21L13,21.36L9.53,18.34L5.75,22H3L9.47,15.66L13,18.64" /></svg>

Before

Width:  |  Height:  |  Size: 235 B

-1
View File
@@ -1 +0,0 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h24v24h-24z"/></clipPath><g clip-path="url(#a)"><path d="m1.5 10.5h1.5c.8 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5h-1.5c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5zm9-9c0-.8.7-1.5 1.5-1.5s1.5.7 1.5 1.5v1.5c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5zm12 12h-1.5c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5h1.5c.8 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5zm-9 9c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5v-1.5c0-.8.7-1.5 1.5-1.5s1.5.7 1.5 1.5zm-7.9-19 1.1 1.1c.6.6.6 1.5 0 2.1s-1.5.6-2.1 0l-1.1-1.1c-.6-.6-.6-1.5 0-2.1s1.5-.6 2.1 0zm12.8 0c.6-.6 1.5-.6 2.1 0s.6 1.5 0 2.1l-1.1 1.1c-.6.6-1.5.6-2.1 0s-.6-1.5 0-2.1zm0 17-1.1-1.1c-.6-.6-.6-1.5 0-2.1s1.5-.6 2.1 0l1.1 1.1c.6.6.6 1.5 0 2.1s-1.5.6-2.1 0zm-12.8 0c-.6.6-1.5.6-2.1 0s-.6-1.5 0-2.1l1.1-1.1c.6-.6 1.5-.6 2.1 0s.6 1.5 0 2.1z" fill-rule="evenodd"/></g></svg>

Before

Width:  |  Height:  |  Size: 894 B

+1 -1
View File
@@ -1 +1 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m12 2.9c-5.4 0-9.8 3.6-9.8 8.1 0 2.2 1 4.2 2.7 5.6l-2 3.9c-.1.2 0 .5.2.6h.4l5.3-2.4c1.1.3 2.2.5 3.3.5 5.4 0 9.8-3.6 9.8-8.1s-4.5-8.2-9.9-8.2z"/></svg>
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m21.2 1.4h-18.4c-.9 0-1.6.7-1.6 1.6v13.1c0 .9.7 1.6 1.6 1.6h4.3l-1.2 5.2c0 .4 0 .9.5.9s.6-.2.6-.2l6.9-6h7.3c.9 0 1.6-.7 1.6-1.6v-13c0-.9-.7-1.6-1.6-1.6z"/></svg>

Before

Width:  |  Height:  |  Size: 219 B

After

Width:  |  Height:  |  Size: 230 B

-1
View File
@@ -1 +0,0 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m14.2 8h2.7c.5 0 1-.4 1-1 0-.2 0-.5-.2-.6l-5-5.6c-.4-.4-1-.4-1.4 0l-4.8 5.6c-.4.4-.3 1 0 1.4.2.2.4.2.6.2h7z"/><path d="m9.8 16h-2.7c-.5 0-1 .4-1 1 0 .2 0 .5.2.6l4.8 5.6c.4.4 1 .4 1.4 0l4.8-5.6c.4-.4.3-1 0-1.4-.2-.2-.4-.2-.6-.2h-2.7s-4.4 0-4.4 0z"/></svg>

Before

Width:  |  Height:  |  Size: 323 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 11h8V3H3m0 18h8v-8H3m10 8h8v-8h-8m0-10v8h8V3"/></svg>

Before

Width:  |  Height:  |  Size: 125 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 20C16.4 20 20 16.4 20 12S16.4 4 12 4 4 7.6 4 12 7.6 20 12 20M12 2C17.5 2 22 6.5 22 12S17.5 22 12 22C6.5 22 2 17.5 2 12C2 6.5 6.5 2 12 2M17 13.9L16.3 15.2L11 12.3V7H12.5V11.4L17 13.9Z" /></svg>

Before

Width:  |  Height:  |  Size: 265 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>hourglass</title><path d="M20.5,22H19V18.5A7.01,7.01,0,0,0,14.6,12,7.011,7.011,0,0,0,19,5.5V2h1.5a1,1,0,0,0,0-2H3.5a1,1,0,0,0,0,2H5V5.5A7.011,7.011,0,0,0,9.4,12,7.01,7.01,0,0,0,5,18.5V22H3.5a1,1,0,0,0,0,2h17a1,1,0,0,0,0-2ZM8.19,6.92a1,1,0,0,1,.89-.544h5.84a1,1,0,0,1,.811,1.584,4.591,4.591,0,0,1-7.462,0A1,1,0,0,1,8.19,6.92ZM7.92,18.63l3.044-2.936a1.544,1.544,0,0,1,2.07,0l0,0,3.043,2.935a1.349,1.349,0,0,1,.269,1.6,1.461,1.461,0,0,1-1.3.771H8.957a1.46,1.46,0,0,1-1.3-.764A1.352,1.352,0,0,1,7.92,18.63Z"/></svg>

Before

Width:  |  Height:  |  Size: 578 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 15V5H5V15H9C9 16.66 10.34 18 12 18C13.66 18 15 16.66 15 15H19M19 3C20.1 3 21 3.9 21 5V19C21 20.1 20.1 21 19 21H5C3.89 21 3 20.1 3 19L3 5C3 3.9 3.89 3 5 3H19M7 13V11H17V13H7M7 9V7H17V9H7Z" /></svg>

Before

Width:  |  Height:  |  Size: 269 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect width="23" height="3" x=".5" y="2.5" rx="1"/><rect width="23" height="3" x=".5" y="10.5" rx="1"/><rect width="23" height="3" x=".5" y="18.5" rx="1"/></svg>

Before

Width:  |  Height:  |  Size: 221 B

+1 -1
View File
@@ -1 +1 @@
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19 2c-2.8 0-5 2.2-5 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm-.1 2.5c.3 0 .6.3.6.6v1.2c0 .2 0 .2.2.2h1.2c.3 0 .6.3.6.6s-.3.6-.6.6h-1.2c-.2 0-.2 0-.2.2v1.2c0 .3-.3.6-.6.6s-.6-.3-.6-.6V7.9c0-.2 0-.2-.2-.2h-1.2c-.3 0-.6-.3-.6-.6s.3-.6.6-.6h1.2c.2 0 .2 0 .2-.2V5.1c0-.3.3-.6.6-.6zM9.7 18.9h.1l-.1-.1v.1z" fill="#000"/><path d="M19.2 13.6v6.3c0 .2 0 .4-.4.4H2V6.6c0-.2 0-.4.4-.4h10.1c0-.7.3-1.3.5-1.8H1.4C.5 4.6 0 5.3 0 6.2v14c0 1 .8 1.8 1.8 1.8h17.5c1 0 1.8-.8 1.8-1.8v-6.9c-.6.2-1.2.3-1.8.3h-.1z" fill="#000"/><path d="M4.8 7.5c-.7 0-1.3.6-1.3 1.3v8.9c0 .4.2.7.5 1l2.6-3.5c.2-.3.6-.5.9-.5.4 0 .7.2.9.5l.9 1.3 2.3-3.5c.3-.4.8-.7 1.3-.7s1 .3 1.3.7l3.1 5v-4.5c-2.8-.6-4.9-3-5.1-6H4.6h.2zm1.9 5c-.9 0-1.6-.7-1.6-1.6 0-.9.7-1.6 1.6-1.6.9 0 1.6.7 1.6 1.6 0 .9-.7 1.6-1.6 1.6z" fill="#000"/><path d="M5.2 18.9h2.6l1-1.5-1.2-1.7-2.4 3.2zM10.4 17.8l-.7 1 .1.1H16.2l-3.1-5.1-2.7 4z" fill="#000"/></svg>
<svg fill="none" height="24" width="24" xmlns="http://www.w3.org/2000/svg"><g fill="#000"><path d="m19 2c-2.8 0-5 2.2-5 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm-.1 2.5c.3 0 .6.3.6.6v1.2c0 .2 0 .2.2.2h1.2c.3 0 .6.3.6.6s-.3.6-.6.6h-1.2c-.2 0-.2 0-.2.2v1.2c0 .3-.3.6-.6.6s-.6-.3-.6-.6v-1.2c0-.2 0-.2-.2-.2h-1.2c-.3 0-.6-.3-.6-.6s.3-.6.6-.6h1.2c.2 0 .2 0 .2-.2v-1.2c0-.3.3-.6.6-.6zm-9.2 14.4h.1l-.1-.1z"/><path d="m19.2 13.6v6.3c0 .2 0 .4-.4.4h-16.8v-13.7c0-.2 0-.4.4-.4h10.1c0-.7.3-1.3.5-1.8h-11.6c-.9.2-1.4.9-1.4 1.8v14c0 1 .8 1.8 1.8 1.8h17.5c1 0 1.8-.8 1.8-1.8v-6.9c-.6.2-1.2.3-1.8.3z"/><path d="m4.8 7.5c-.7 0-1.3.6-1.3 1.3v8.9c0 .4.2.7.5 1l2.6-3.5c.2-.3.6-.5.9-.5.4 0 .7.2.9.5l.9 1.3 2.3-3.5c.3-.4.8-.7 1.3-.7s1 .3 1.3.7l3.1 5v-4.5c-2.8-.6-4.9-3-5.1-6h-7.6zm1.9 5c-.9 0-1.6-.7-1.6-1.6s.7-1.6 1.6-1.6 1.6.7 1.6 1.6-.7 1.6-1.6 1.6z"/><path d="m5.2 18.9h2.6l1-1.5-1.2-1.7zm5.2-1.1-.7 1 .1.1h6.4l-3.1-5.1z"/></g></svg>

Before

Width:  |  Height:  |  Size: 966 B

After

Width:  |  Height:  |  Size: 909 B

-1
View File
@@ -1 +0,0 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m3.25 13.25a1 1 0 0 1 -1-1v-6a1 1 0 0 1 1-1h11a1 1 0 0 1 .98.8.25.25 0 0 0 .245.2h1.525a.25.25 0 0 0 .25-.25v-.75a2 2 0 0 0 -2-2h-13a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h1.75a.25.25 0 0 0 .25-.25v-1.5a.25.25 0 0 0 -.25-.25z"/><path d="m21.25 7.75h-13a2.5 2.5 0 0 0 -2.5 2.5v8a2.5 2.5 0 0 0 2.5 2.5h13a2.5 2.5 0 0 0 2.5-2.5v-8a2.5 2.5 0 0 0 -2.5-2.5zm-13 9.5v-6a1 1 0 0 1 1-1h11a1 1 0 0 1 1 1v6a1 1 0 0 1 -1 1h-11a1 1 0 0 1 -1-1z"/></svg>

Before

Width:  |  Height:  |  Size: 500 B

-1
View File
@@ -1 +0,0 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m20 17h3l-4 4-4-4h3v-14h2zm-12-12c-3.86 0-7 3.13-7 7s3.13 7 7 7c3.86 0 7-3.13 7-7s-3.13-7-7-7m0 2.15c2.67 0 4.85 2.17 4.85 4.85s-2.17 4.85-4.85 4.85-4.85-2.17-4.85-4.85 2.17-4.85 4.85-4.85m-1 1.85v3.69l3.19 1.84.75-1.3-2.44-1.41v-2.82"/></svg>

Before

Width:  |  Height:  |  Size: 312 B

-1
View File
@@ -1 +0,0 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m18 7h-3l4-4 4 4h-3v14h-2zm-10-2c-3.86 0-7 3.13-7 7s3.13 7 7 7c3.86 0 7-3.13 7-7s-3.13-7-7-7m0 2.15c2.67 0 4.85 2.17 4.85 4.85s-2.17 4.85-4.85 4.85-4.85-2.17-4.85-4.85 2.17-4.85 4.85-4.85m-1 1.85v3.69l3.19 1.84.75-1.3-2.44-1.41v-2.82"/></svg>

Before

Width:  |  Height:  |  Size: 311 B

-1
View File
@@ -1 +0,0 @@
<svg id="Bold" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>temperature-thermometer-high</title><path d="M14,4.5a4.5,4.5,0,0,0-9,0v9.438a.248.248,0,0,1-.06.162A5.941,5.941,0,0,0,3.5,18a6,6,0,0,0,12,0,5.941,5.941,0,0,0-1.44-3.9.248.248,0,0,1-.06-.162ZM9.5,22a4,4,0,0,1-2.8-6.856A1,1,0,0,0,7,14.43V4.5a2.5,2.5,0,0,1,5,0v9.93a1,1,0,0,0,.3.714A4,4,0,0,1,9.5,22Z"/><path d="M10.611,16.337a.25.25,0,0,1-.111-.208V5a1,1,0,0,0-2,0V16.129a.25.25,0,0,1-.111.208,2,2,0,1,0,2.222,0Z"/><path d="M19.5,11.5H17a1,1,0,0,0,0,2h2.5a1,1,0,0,0,0-2Z"/><path d="M17,5.5h2.5a1,1,0,0,0,0-2H17a1,1,0,0,0,0,2Z"/><path d="M19.5,7.5H17a1,1,0,0,0,0,2h2.5a1,1,0,0,0,0-2Z"/></svg>

Before

Width:  |  Height:  |  Size: 666 B

-36
View File
@@ -9,16 +9,6 @@
}
/* Keyframes */
@keyframes float-up-left {
0% { transform: translate(10px, 20px); }
100% { transform: translate(0, 0); }
}
@keyframes float-down-right {
0% { transform: translate(-10px, -20px); }
100% { transform: translate(0, 0); }
}
@keyframes react {
0% { transform: scale(0.3); opacity: 0; }
50% { transform: scale(1.15); opacity: 1; }
@@ -36,30 +26,4 @@
50% { transform: translateX(-1rem); }
75% { transform: translateX(1rem); }
}
@keyframes wobble {
0% { transform: rotate(calc(var(--card-rotate) + 90deg)); }
15% { border-radius: 66% 34% 72% 28% / 39% 63% 37% 61%; }
25% { border-radius: 55% 47% 62% 40% / 58% 50% 52% 44%; }
33% { border-radius: 46% 54% 61% 39% / 50% 51% 49% 50%; }
50% { border-radius: 54% 46% 61% 39% / 57% 49% 51% 43%; }
75% { border-radius: 53% 45% 60% 38% / 56% 48% 50% 42%; }
}
@keyframes wobble-alt {
0% { transform: rotate(calc(var(--card-rotate) + -90deg)); }
12% { border-radius: 66% 34% 72% 28% / 39% 63% 37% 61%; }
25% { border-radius: 53% 45% 60% 38% / 56% 48% 50% 42%; }
33% { border-radius: 55% 47% 62% 40% / 58% 50% 52% 44%; }
50% { border-radius: 46% 54% 61% 39% / 50% 51% 49% 50%; }
75% { border-radius: 54% 46% 61% 39% / 57% 49% 51% 43%; }
}
@keyframes wobble-img {
15% { border-radius: 66% 34% 72% 28% / 39% 63% 37% 61%; }
25% { border-radius: 55% 47% 62% 40% / 58% 50% 52% 44%; }
33% { border-radius: 46% 54% 61% 39% / 50% 51% 49% 50%; }
50% { border-radius: 54% 46% 61% 39% / 57% 49% 51% 43%; }
75% { border-radius: 53% 45% 60% 38% / 56% 48% 50% 42%; }
}
}
-36
View File
@@ -24,42 +24,6 @@
}
}
.avatar__group {
--avatar-size: 2.5ch;
block-size: 5ch;
display: grid;
gap: 1px;
grid-template-columns: 1fr 1fr;
grid-template-rows: min-content;
inline-size: 5ch;
place-content: center;
.avatar {
margin: auto;
}
&:where(:has(> :last-child:nth-child(2))) {
--avatar-size: 3.5ch;
> :first-child {
margin-block-end: 1.5ch;
margin-inline-end: -0.75ch;
}
> :last-child {
margin-block-start: 1.5ch;
margin-inline-start: -0.75ch;
}
}
&:where(:has(> :last-child:nth-child(3))) {
> :last-child {
margin-inline: 1.25ch -1.25ch;
}
}
}
.avatar__form {
display: grid;
grid-template-columns: 1fr auto 1fr;
-493
View File
@@ -1,493 +0,0 @@
:root {
--bubble-shape: 54% 46% 61% 39% / 57% 49% 51% 43%;
--bubble-shift: 10deg;
--bubble-size-one: 14cqi;
--bubble-size-two: 16cqi;
--bubble-size-three: 18cqi;
--bubble-size-four: 20cqi;
--bubble-size-five: 22cqi;
}
@layer components {
.bubbles {
--column-gap: 2cqi;
--row-gap: 1cqi;
margin-block: clamp(var(--block-space), 3%, calc(var(--block-space) * 2));
> * {
flex-shrink: 1;
}
}
.something-to-rename {
--bubble-background: radial-gradient(circle at center, color(from var(--bubble-color) srgb r g b / 0.25), color(from var(--bubble-color) srgb r g b / 0.05));
--bubble-border-width: 0.2rem;
--hover-size: 0;
aspect-ratio: 1;
block-size: var(--bubble-size);
color: var(--bubble-color, currentColor);
container-type: inline-size;
display: grid;
place-items: center;
position: relative;
transition: 0.2s ease;
.bubbles & {
&:nth-child(1) { order: 5; }
&:nth-child(2) { order: 3; }
&:nth-child(3) { order: 3; }
&:nth-child(4) { order: 7; }
&:nth-child(5) { order: 9; }
&:nth-child(6) { order: 5; }
&:nth-child(7) { order: 4; }
&:nth-child(8) { order: 9; }
&:nth-child(9) { order: 3; }
&:nth-child(10) { order: 7; }
&:nth-of-type(n+6) {
align-self: start;
}
}
[data-turbo-preview] & { animation: none; }
> * {
grid-area: 1/1;
}
.bubble__perma & {
--bubble-border-width: 0.3rem;
--bubble-size: 40dvh !important;
z-index: 1;
}
}
.bubble__detail {
--bubble-border-color: color-mix(in srgb, var(--bubble-color) 30%, var(--color-bg));
--bubble-border-width: 0.15em;
aspect-ratio: 1;
background: var(--color-bg);
block-size: auto;
border: var(--bubble-border-width) solid var(--bubble-border-color);
border-radius: var(--bubble-shape);
display: flex;
font-size: 5cqi;
font-weight: 800;
line-height: 1;
max-inline-size: 18ch;
padding: 1em;
place-content: center;
place-items: center;
position: absolute;
scale: 1;
text-overflow: ellipsis;
transform: none;
transition: transform 200ms ease-out;
white-space: nowrap;
z-index: 1;
a {
color: var(--bubble-color);
}
&.bubble__assignee {
font-size: 4.5cqi;
inset: 8em auto auto -1.7em;
padding: 0.2em;
+ & {
inset: 4em auto auto -1em;
+ & {
inset: 1em auto auto 2em;
}
}
.avatar {
border-radius: var(--bubble-shape);
}
@media (hover: hover) {
.bubbles .something-to-rename:not(.popped):hover & {
transform: translate(0, -1rem);
}
.bubbles .something-to-rename:not(.popped):hover & + & {
transform: translate(1rem, -1rem);
}
.bubbles .something-to-rename:not(.popped):hover & + & + & {
transform: translate(1.5rem, -0.5rem);;
}
}
.assignee__name {
display: none;
.bubble__perma & {
color: var(--bubble-color);
display: inline-block;
font-size: 0.9em;
font-weight: normal;
inline-size: 15ch;
inset: 0.3em 3.6em auto auto;
position: absolute;
text-align: end;
white-space: normal;
}
}
}
&.bubble__comments {
--arrow-size: 1.5em;
--border-size: var(--bubble-border-width);
aspect-ratio: 9/6;
inset: auto auto 2em -1em;
&::before {
aspect-ratio: 2;
background: var(--bubble-border-color);
clip-path: polygon(50% 100%, 100% 0, 0 0);
inline-size: var(--arrow-size);
content: "";
display: block;
inset-block-end: auto;
inset-block-start: 94%;
inset-inline-start: 66%;
position: absolute;
transform: translateX(-50%) rotate(-20deg);
}
&::after {
aspect-ratio: 2;
background: var(--color-bg);
clip-path: polygon(50% 100%, 100% 0, 0 0);
inline-size: var(--arrow-size);
content: "";
display: block;
inset-block-end: auto;
inset-block-start: calc(94% - var(--border-size) * 1.5);
inset-inline-start: 64%;
position: absolute;
transform: translateX(-50%) rotate(-20deg);
}
@media (hover: hover) {
.bubbles .something-to-rename:not(.popped):hover & {
transform: translate(-0.5rem, -2rem);
}
}
}
&.bubble__date {
--rotation: -45deg;
aspect-ratio: 5/7;
cursor: pointer;
inset: 1cqb 1cqi auto auto;
transform: rotate(var(--rotation));
.bubble__date-text {
transform: rotate(calc(var(--rotation) * -1));
}
@media (hover: hover) {
.bubbles .something-to-rename:not(.popped):hover & {
transform: rotate(calc(var(--rotation) + var(--bubble-shift) * 1)) translate(0rem, 0.5rem);
}
}
}
@media (hover: hover) {
.bubbles .something-to-rename:not(.popped):hover & {
transform: translate(1rem, 1rem);
}
.bubbles .something-to-rename:not(.popped):hover & + & {
transform: translate(0, -1rem);
}
.bubbles .something-to-rename:not(.popped):hover & + & + & {
transform: translate(-1rem, -1rem);
}
}
}
.bubble__image {
border: none;
border-radius: var(--bubble-shape);
block-size: 100cqi;
inline-size: 100cqi;
overflow: hidden;
pointer-events: none;
transform: rotate(var(--bubble-rotate)) scale(0.92);
transition: 0.2s ease;
@media (hover: hover) {
.bubbles .something-to-rename:not(.popped):hover & {
transform: rotate(calc(var(--bubble-rotate) + var(--bubble-shift))) scale(0.9);
}
}
img {
block-size: 100cqi;
inline-size: 100cqi;
max-inline-size: none;
object-fit: cover;
transform: rotate(calc(var(--bubble-rotate) * -1)) scale(1.1);
&:is([src=""]) {
display: none;
}
}
:is(.bubbles, .bubbles-list) .bubble--wobble & {
animation: wobble-img 1200ms normal forwards ease-out;
}
}
.bubble__link {
content: "";
inset: 0;
position: absolute;
z-index: -1;
.bubbles & {
z-index: 1;
}
}
.bubbles-list {
--bubble-border-width: 0.15rem;
--bubble-shift: 15deg;
--bubble-size: 1.8em;
--divider-offset: 1.2em;
--hover-size: 0;
margin-inline: auto;
@media (min-width: 150ch) {
margin-inline: var(--tray-size);
}
.something-to-rename {
order: 0;
}
.bubble__title-link {
color: var(--color-txt);
text-decoration: none;
}
ul {
li {
border-radius: 0;
list-style: none;
padding: 0.5em var(--inline-space);
position: relative;
transition: background-color 200ms ease-out;
}
&:not(.dragging) {
li {
@media (hover: hover) {
&:hover {
background-color: color(from var(--bubble-color) srgb r g b / 0.15);
border: 0;
}
}
}
}
}
.avatar {
--avatar-size: 1.1em;
> * {
font-size: 0.5em;
}
}
}
.bubble__title-text {
font-weight: 700;
flex-grow: 1;
letter-spacing: -0.015em;
white-space: nowrap;
.drafted & {
color: color(from var(--color-ink) srgb r g b / 0.5);
}
&::after {
border-block-end: 1px dotted var(--color-subtle-dark);
content: "";
flex-grow: 1;
margin-block-end: 0.25em;
}
}
.bubble__tags {
--row-gap: 0;
font-size: var(--text-small);
margin-block-start: 0.3cqb;
max-block-size: 4lh;
max-inline-size: 75cqi;
overflow: hidden;
text-overflow: ellipsis;
.bubble__perma & {
font-size: var(--text-medium);
}
}
.bubble__tag {
font-weight: 700;
max-inline-size: 75cqi;
}
.bubble__title {
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;
margin: auto;
max-block-size: 90cqi;
max-inline-size: 70cqi;
.bubbles & {
pointer-events: none;
}
a {
color: var(--splat-color);
&.bubble__title-rendered {
-webkit-line-clamp: var(--lines, 4);
-webkit-box-orient: vertical;
display: -webkit-box;
font-size: 9cqi;
font-weight: 800;
line-height: 1.2;
overflow: hidden;
text-overflow: clip;
white-space: normal;
}
}
/*textarea {*/
/* --input-color: var(--bubble-color);*/
/* --input-border-radius: 0;*/
/* font-size: 9cqi;*/
/* font-weight: 800;*/
/* line-height: 1.2;*/
/* &::selection {*/
/* background-color: var(--bubble-color);*/
/* color: var(--color-ink-reversed);*/
/* }*/
/*}*/
@media (hover: hover) {
.something-to-rename:has(.bubble__image img:not([src=""])):hover & {
color: var(--color-ink-reversed);
text-shadow: 0 0 0.2em var(--bubble-color), 0 0 0.2em var(--bubble-color), 0 0 0.2em var(--bubble-color);
position: relative;
z-index: 1;
}
}
}
.bubble__shape {
background: var(--bubble-background);
block-size: var(--bubble-size, 1.2em);
border-color: var(--bubble-border-color, color-mix(in srgb, var(--bubble-color) 15%, var(--color-bg)));
border-radius: var(--bubble-shape);
border-style: var(--bubble-border-style, solid);
border-width: 1px;
inline-size: var(--bubble-size, 1.2em);
pointer-events: none;
position: relative;
transform: rotate(var(--bubble-rotate));
transition: 200ms ease-out;
z-index: 0;
@media (prefers-color-scheme: dark) {
--bubble-background: radial-gradient(circle at center, color(from var(--bubble-color) srgb r g b / 0.45), color(from var(--bubble-color) srgb r g b / 0.15));
--bubble-border-color: color-mix(in srgb, var(--bubble-color) 30%, var(--color-bg));
}
.something-to-rename & {
--bubble-size: 100%;
}
.drafted & {
--bubble-border-style: dashed;
}
:is(.bubbles, .bubbles-list) .bubble--wobble & {
animation: wobble 1200ms normal forwards ease-out;
}
.bubble--wobble:nth-child(even) & {
animation-name: wobble-alt;
animation-duration: 900ms;
}
.popped & {
display: none;
}
.event &,
.bucket &,
.bubbles-list & {
--bubble-background: color-mix(in srgb, var(--bubble-color) 30%, var(--color-bg));
--bubble-border-color: color-mix(in srgb, var(--bubble-color) 40%, var(--color-bg))
}
}
.bubble__actions-container {
background-color: color-mix(in srgb, var(--bubble-color) 20%, var(--color-bg));
margin-block-start: -0.7em;
position: relative;
z-index: 1;
.btn--plain {
--btn-color: var(--bubble-color);
text-decoration: underline;
}
.bubble__pop-message {
color: var(--bubble-color);
}
}
.bubble__splat {
display: none;
pointer-events: none;
transform: rotate(var(--bubble-rotate));
.popped & {
display: block;
fill: color-mix(in srgb, var(--bubble-color) 20%, var(--color-bg));
position: absolute;
scale: 130%;
z-index: -1;
}
.bubbles-list & {
block-size: var(--bubble-size);
fill: color-mix(in srgb, var(--bubble-color) 35%, var(--color-bg));
inline-size: var(--bubble-size);
position: relative;
scale: 1;
z-index: 0;
}
}
}
-4
View File
@@ -181,10 +181,6 @@
--btn-border-color: var(--color-bg);
}
.btn--small {
font-size: 0.8em;
}
.btn--success {
animation: success 1s ease-out;
-40
View File
@@ -1,40 +0,0 @@
@layer components {
.collection {
inline-size: min(35ch, 40%);
line-height: 1.2;
mark {
background-color: transparent;
color: var(--color-link);
text-decoration: underline;
}
}
.collections {
--gap: 2cqi;
--hover-size: 0;
--column-gap: var(--gap);
--row-gap: var(--gap);
list-style: none;
padding: var(--gap);
}
.collection__button {
position: absolute;
inset: calc((var(--btn-size) * -1) - 2cqi) 1cqi auto auto;
}
.collection__cards {
--border-radius: 1.5em;
margin-block: 0;
max-inline-size: 100%;
padding: 1cqi;
}
.cards__container {
aspect-ratio: 18 / 9;
container-type: inline-size;
}
}
-17
View File
@@ -17,41 +17,28 @@
background: none;
}
.icon--activity { --svg: url("activity.svg "); }
.icon--add { --svg: url("add.svg "); }
.icon--alert { --svg: url("alert.svg "); }
.icon--arrow-left { --svg: url("arrow-left.svg "); }
.icon--arrow-right { --svg: url("arrow-right.svg "); }
.icon--art { --svg: url("art.svg "); }
.icon--assigned { --svg: url("assigned.svg "); }
.icon--bell-off { --svg: url("bell-off.svg "); }
.icon--bell { --svg: url("bell.svg "); }
.icon--blob { --svg: url("blob.svg "); }
.icon--bolt { --svg: url("bolt.svg "); }
.icon--bookmark-outline { --svg: url("bookmark-outline.svg "); }
.icon--bookmark { --svg: url("bookmark.svg "); }
.icon--card-add { --svg: url("card-add.svg "); }
.icon--cards { --svg: url("cards.svg "); }
.icon--calendar-add { --svg: url("calendar-add.svg "); }
.icon--calendar { --svg: url("calendar.svg "); }
.icon--camera { --svg: url("camera.svg "); }
.icon--caret-down { --svg: url("caret-down.svg "); }
.icon--chart { --svg: url("chart.svg "); }
.icon--check { --svg: url("check.svg "); }
.icon--close { --svg: url("close.svg "); }
.icon--comment { --svg: url("comment.svg "); }
.icon--copy-paste { --svg: url("copy-paste.svg "); }
.icon--crown { --svg: url("crown.svg "); }
.icon--drag { --svg: url("drag.svg "); }
.icon--email { --svg: url("email.svg "); }
.icon--everyone { --svg: url("everyone.svg "); }
.icon--filter { --svg: url("filter.svg "); }
.icon--globe { --svg: url("globe.svg "); }
.icon--grid { --svg: url("grid.svg "); }
.icon--history { --svg: url("history.svg "); }
.icon--home { --svg: url("home.svg "); }
.icon--hourglass { --svg: url("hourglass.svg "); }
.icon--inbox { --svg: url("inbox.svg "); }
.icon--logout { --svg: url("logout.svg "); }
.icon--menu { --svg: url("menu.svg "); }
.icon--menu-dots-horizontal { --svg: url("menu-dots-horizontal.svg "); }
@@ -66,7 +53,6 @@
.icon--picture-double { --svg: url("picture-double.svg "); }
.icon--picture-remove { --svg: url("picture-remove.svg "); }
.icon--pinned { --svg: url("pinned.svg "); }
.icon--closure { --svg: url("closure.svg "); }
.icon--qr-code { --svg: url("qr-code.svg "); }
.icon--reaction { --svg: url("reaction.svg "); }
.icon--refresh { --svg: url("refresh.svg "); }
@@ -75,11 +61,8 @@
.icon--rename { --svg: url("rename.svg "); }
.icon--settings { --svg: url("settings.svg "); }
.icon--share { --svg: url("share.svg "); }
.icon--sort-ascending { --svg: url("sort-ascending.svg "); }
.icon--sort-descending { --svg: url("sort-descending.svg "); }
.icon--star { --svg: url("star.svg "); }
.icon--tag { --svg: url("tag.svg "); }
.icon--temperature { --svg: url("temperature.svg "); }
.icon--thumb-up { --svg: url("thumb-up.svg "); }
.icon--trash { --svg: url("trash.svg "); }
.icon--unpinned { --svg: url("unpinned.svg"); }
+5 -1
View File
@@ -1,4 +1,8 @@
@layer components {
.notification {
position: relative;
}
.notification-tray {
inset: auto var(--inline-space) var(--block-space-half) auto;
@@ -48,7 +52,7 @@
.notificiations-list--read & {
--border-color: var(--color-subtle);
box-shadow: none;
box-shadow: none !important;
}
}
+1 -1
View File
@@ -2,8 +2,8 @@
.panel {
background-color: var(--color-bg);
border: var(--panel-border-size, 1px) solid var(--panel-border-color, var(--color-subtle));
color: var(--color-ink);
border-radius: var(--panel-border-radius, 1em);
color: var(--color-ink);
inline-size: var(--panel-size, 60ch);
max-inline-size: 100%;
padding: var(--panel-padding, calc(var(--block-space) * 2));
+15 -15
View File
@@ -1,19 +1,4 @@
@layer components {
.pin-tray {
inset: auto auto var(--block-space-half) var(--inline-space);
.pin {
--z: calc(10 - var(--position));
&:nth-child(7) { --position: 7; }
&:nth-child(8) { --position: 8; }
&:nth-child(9) { --position: 9; }
&:nth-child(10) { --position: 10; }
&:nth-child(1n + 11) { display: none; }
}
}
.pin__collection {
background-color: var(--card-color);
font-weight: 800;
@@ -51,4 +36,19 @@
.pin__title {
margin-block-start: 0.3rem;
}
.pin-tray {
inset: auto auto var(--block-space-half) var(--inline-space);
.pin {
--z: calc(10 - var(--position));
&:nth-child(7) { --position: 7; }
&:nth-child(8) { --position: 8; }
&:nth-child(9) { --position: 9; }
&:nth-child(10) { --position: 10; }
&:nth-child(1n + 11) { display: none; }
}
}
}
-35
View File
@@ -1,35 +0,0 @@
@layer components {
/* Language translations */
.lanuage-list-menu {
--max-width: 40ch;
background-color: var(--color-subtle);
border: 1px solid var(--color-subtle-dark);
border-radius: 0.5em;
inset: auto;
inline-size: max-content;
margin-inline: var(--inline-space);
max-inline-size: 40ch;
overflow: clip;
position: absolute;
z-index: 1;
@media (max-width: 70ch) {
max-inline-size: calc(var(--max-width) - var(--inline-space));
}
.popover-orientation-top & {
inset-block-end: 100%;
}
}
.language-list {
display: grid;
gap: var(--block-space-half) var(--inline-space);
grid-template-rows: min-content;
grid-template-columns: min-content 1fr;
justify-content: start;
margin: 0;
text-align: start;
}
}
+1
View File
@@ -22,6 +22,7 @@
--scale: calc(1 - var(--position) / 30);
--z: calc(6 - var(--position));
display: flex;
inset: auto 0 0;
inline-size: var(--tray-size);
outline: 0;
-29
View File
@@ -14,10 +14,6 @@
background-color: color(from var(--card-color) srgb r g b / 0.15);
}
}
.cards-list & {
display: none;
}
}
.workflow-stage--current {
@@ -38,30 +34,5 @@
border-radius: 0.2em;
color: var(--color-ink-reversed);
}
.cards-list & {
display: flex;
background: transparent;
border-radius: 0.2em;
color: var(--color-ink);
padding: 0;
border: 0;
}
}
.workflow__popup {
--panel-border-radius: 0.5em;
--panel-padding: 0.5em;
--panel-size: 100%;
inline-size: auto !important;
inset: 0 auto auto 0;
margin-block-start: 1lh;
margin-inline-start: calc((var(--panel-padding) + 0.7em) * -1);
min-inline-size: 12ch;
max-inline-size: var(--panel-size) !important;
position: absolute;
transform-origin: top left;
z-index: 1;
}
}
@@ -0,0 +1,8 @@
class Cards::PreviewsController < ApplicationController
include FilterScoped
before_action :set_filter, only: :index
def index
set_page_and_extract_portion_from @filter.cards, per_page: CardsController::PAGE_SIZE
end
end
+13 -29
View File
@@ -1,20 +1,17 @@
require "ostruct"
class CardsController < ApplicationController
include CollectionScoped
include CollectionScoped, FilterScoped
skip_before_action :set_collection, only: :index
before_action :set_filter, only: :index
before_action :set_card, only: %i[ show edit update destroy ]
before_action :handle_display_count, only: :index
DISPLAY_COUNT_OPTIONS = [ 6, 12, 18, 24 ].freeze
DEFAULT_DISPLAY_COUNT = 6
RECENTLY_CLOSED_LIMIT = 100
PAGE_SIZE = 50
def index
@considering_cards = @filter.cards.considering.load_async
@doing_cards = @filter.cards.doing.load_async
@closed_cards = @filter.with(indexed_by: "closed").cards.recently_closed_first.limit(RECENTLY_CLOSED_LIMIT).load_async
@considering = page_and_filter_for @filter.with(engagement_status: "considering", indexed_by: "latest"), per_page: PAGE_SIZE
@doing = page_and_filter_for @filter.with(engagement_status: "doing", indexed_by: "latest"), per_page: PAGE_SIZE
@closed = page_and_filter_for @filter.with(indexed_by: "closed"), per_page: PAGE_SIZE
end
def create
@@ -38,16 +35,16 @@ class CardsController < ApplicationController
end
private
DEFAULT_PARAMS = { indexed_by: "newest" }
def set_filter
@filter = Current.user.filters.from_params params.reverse_merge(**DEFAULT_PARAMS).permit(*Filter::PERMITTED_PARAMS)
end
def set_card
@card = @collection.cards.find params[:id]
end
def page_and_filter_for(filter, per_page: nil)
OpenStruct.new \
page: GearedPagination::Recordset.new(filter.cards, per_page:).page(1),
filter: filter
end
def card_params
params.expect(card: [ :status, :title, :color, :due_on, :image, :draft_comment, tag_ids: [] ])
end
@@ -55,17 +52,4 @@ class CardsController < ApplicationController
def deleted_notice
"Card deleted" unless @card.creating?
end
def handle_display_count
if params[:set_display_count].present?
cookies[:display_count] = params[:set_display_count]
redirect_to cards_path(
params.permit(*Filter::PERMITTED_PARAMS, :collection_ids).except(:set_display_count)
)
end
end
def display_count
(cookies[:display_count] || DEFAULT_DISPLAY_COUNT).to_i
end
end
+14
View File
@@ -0,0 +1,14 @@
module FilterScoped
extend ActiveSupport::Concern
included do
before_action :set_filter, only: :index
end
private
DEFAULT_PARAMS = { indexed_by: "newest" }
def set_filter
@filter = Current.user.filters.from_params params.reverse_merge(**DEFAULT_PARAMS).permit(*Filter::PERMITTED_PARAMS)
end
end
@@ -31,6 +31,6 @@ class Workflows::StagesController < ApplicationController
end
def stage_params
params.expect workflow_stage: [ :name ]
params.require(:workflow_stage).permit(:name, :color)
end
end
+2 -2
View File
@@ -11,7 +11,7 @@ module AvatarsHelper
end
def avatar_tag(user, hidden_for_screen_reader: false, **options)
link_to user_path(user), title: user.name, class: "btn avatar", data: { turbo_frame: "_top" },
link_to user_path(user), class: "btn avatar", data: { turbo_frame: "_top" },
aria: { hidden: hidden_for_screen_reader, label: user.name },
tabindex: hidden_for_screen_reader ? -1 : nil do
avatar_image_tag(user, **options)
@@ -19,6 +19,6 @@ module AvatarsHelper
end
def avatar_image_tag(user, **options)
image_tag user_avatar_path(user), aria: { hidden: "true" }, size: 48, class: ("avatar__photo" if user.avatar.attached?), **options
image_tag user_avatar_path(user), aria: { hidden: "true" }, size: 48, title: user.name, class: ("avatar__photo" if user.avatar.attached?), **options
end
end
+13 -8
View File
@@ -11,14 +11,19 @@ module CardsHelper
"--card-rotate: #{value}deg;"
end
def display_count_options
CardsController::DISPLAY_COUNT_OPTIONS.map do |count|
{
value: count,
label: count,
selected: @display_count == count,
id: "display-count-#{count}"
}
def cards_next_page_link(target, page:, filter:, fetch_on_visible: false, data: {}, **options)
url = cards_previews_path(target: target, page: page.next_param, **filter.as_params)
if fetch_on_visible
data[:controller] = "#{data[:controller]} fetch-on-visible"
data[:fetch_on_visible_url_value] = url
end
link_to "Load more",
url,
id: "#{target}-load-page-#{page.next_param}",
data: { turbo_stream: true, **data },
class: "btn txt-small",
**options
end
end
+1 -1
View File
@@ -22,7 +22,7 @@ module NotificationsHelper
end
def notification_tag(notification, &)
tag.div id: dom_id(notification), class: "notification tray__item border-radius flex txt-normal" do
tag.div id: dom_id(notification), class: "notification tray__item border-radius txt-normal" do
concat(
link_to(notification.resource,
class: "notification__content border-radius shadow fill-white flex align-start txt-align-start gap flex-item-grow max-width border txt-ink",
-29
View File
@@ -1,29 +0,0 @@
module TranslationsHelper
TRANSLATIONS = {
user_name: { "🇺🇸": "Enter your name", "🇪🇸": "Introduce tu nombre", "🇫🇷": "Entrez votre nom", "🇮🇳": "अपना नाम दर्ज करें", "🇩🇪": "Geben Sie Ihren Namen ein", "🇧🇷": "Insira seu nome" },
email_address: { "🇺🇸": "Enter your email address", "🇪🇸": "Introduce tu correo electrónico", "🇫🇷": "Entrez votre adresse courriel", "🇮🇳": "अपना ईमेल पता दर्ज करें", "🇩🇪": "Geben Sie Ihre E-Mail-Adresse ein", "🇧🇷": "Insira seu endereço de email" },
password: { "🇺🇸": "Enter your password", "🇪🇸": "Introduce tu contraseña", "🇫🇷": "Saisissez votre mot de passe", "🇮🇳": "अपना पासवर्ड दर्ज करें", "🇩🇪": "Geben Sie Ihr Passwort ein", "🇧🇷": "Insira sua senha" },
collection_name: { "🇺🇸": "Give it a name", "🇪🇸": "Dale un nombre", "🇫🇷": "Donnez lui un nom", "🇮🇳": "इसे एक नाम दें", "🇩🇪": "Gib ihm einen Namen", "🇧🇷": "Dê-lhe um nome" }
}
def translations_for(translation_key)
tag.dl(class: "language-list") do
TRANSLATIONS[translation_key].map do |language, translation|
concat tag.dt(language)
concat tag.dd(translation, class: "margin-none")
end
end
end
def translation_button(translation_key)
tag.div(class: "position-relative", data: { controller: "popover", action: "keydown.esc->popover#close click@document->popover#closeOnClickOutside", popover_orientation_top_class: "popover-orientation-top" }) do
tag.button(type: "button", class: "btn", tabindex: -1, data: { action: "popover#toggle" }) do
concat image_tag("globe.svg", class: "icon", role: "presentation")
concat tag.span("Translate", class: "for-screen-reader")
end +
tag.dialog(class: "lanuage-list-menu popover shadow", data: { popover_target: "menu" }) do
translations_for(translation_key)
end
end
end
end
+6
View File
@@ -2,4 +2,10 @@ module UsersHelper
def prepend_current_user_to(users_scope)
users_scope.to_a.prepend(Current.user).uniq
end
def familiar_name_for(user)
names = user.name.split
return user.name if names.length == 1
"#{names.first} #{names[1..].map { |n| "#{n[0]}." }.join}"
end
end
+4
View File
@@ -9,4 +9,8 @@ module WorkflowsHelper
end
end
end
def stage_color(stage)
stage.color.presence || Colorable::DEFAULT_COLOR
end
end
@@ -0,0 +1,8 @@
import { Turbo } from "@hotwired/turbo-rails"
Turbo.StreamActions.set_css_variable = function() {
const name = this.getAttribute("name")
const value = this.getAttribute("value")
this.targetElements.forEach(element => element.style.setProperty(name, value))
}
+1
View File
@@ -1 +1,2 @@
import "initializers/current"
import "initializers/custom_stream_actions"
+9
View File
@@ -15,6 +15,7 @@ class Card < ApplicationRecord
scope :reverse_chronologically, -> { order created_at: :desc, id: :desc }
scope :chronologically, -> { order created_at: :asc, id: :asc }
scope :latest, -> { order updated_at: :desc, id: :desc }
scope :in_collection, ->(collection) { where collection: collection }
scope :indexed_by, ->(index) do
@@ -24,11 +25,19 @@ class Card < ApplicationRecord
when "most_boosted" then ordered_by_boosts
when "newest" then reverse_chronologically
when "oldest" then chronologically
when "latest" then latest
when "stalled" then ordered_by_staleness
when "closed" then closed
end
end
scope :by_engagement_status, ->(status) do
case status.to_s
when "considering" then considering
when "doing" then doing
end
end
def cache_key
[ super, collection&.name ].compact.join("/")
end
+3 -3
View File
@@ -1,7 +1,7 @@
module Card::Closeable
extend ActiveSupport::Concern
AUTO_CLOSURE_AFTER = 30.days
AUTO_CLOSE_AFTER = 30.days
included do
has_one :closure, dependent: :destroy
@@ -10,7 +10,7 @@ module Card::Closeable
scope :active, -> { where.missing(:closure) }
scope :recently_closed_first, -> { closed.order("closures.created_at": :desc) }
scope :due_to_be_closed, -> { considering.where(last_active_at: ..AUTO_CLOSURE_AFTER.ago) }
scope :due_to_be_closed, -> { considering.where(last_active_at: ..AUTO_CLOSE_AFTER.ago) }
end
class_methods do
@@ -22,7 +22,7 @@ module Card::Closeable
end
def auto_close_at
last_active_at + AUTO_CLOSURE_AFTER if last_active_at
last_active_at + AUTO_CLOSE_AFTER if last_active_at
end
def closed?
+7 -4
View File
@@ -1,9 +1,12 @@
module Card::Colored
extend ActiveSupport::Concern
COLORS = %w[ #b7462b #ff63a8 #eb7a32 #6ac967 #2c6da8 #663251 ]
included do
attribute :color, default: "#2c6da8"
def color
color_from_stage || Colorable::DEFAULT_COLOR
end
private
def color_from_stage
stage&.color&.presence if doing?
end
end
+10
View File
@@ -0,0 +1,10 @@
module Colorable
extend ActiveSupport::Concern
COLORS = %w[ #3b3633 #67695e #eb7a32 #bf7c2b #c09c6f #746b1e #2c6da8 #5d618f #663251 #ff63a8 ]
DEFAULT_COLOR = "#2c6da8"
included do
attribute :color, default: DEFAULT_COLOR
end
end
-22
View File
@@ -1,22 +0,0 @@
module Filter::Params
extend ActiveSupport::Concern
PERMITTED_PARAMS = %i[
indexed_by assignment_status collection_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,
collection_ids: collections.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
+1
View File
@@ -20,6 +20,7 @@ class Filter < ApplicationRecord
@cards ||= begin
result = creator.accessible_cards.indexed_by(indexed_by)
result = result.active unless indexed_by.closed?
result = result.by_engagement_status(engagement_status) if engagement_status.present?
result = result.unassigned if assignment_status.unassigned?
result = result.assigned_to(assignees.ids) if assignees.present?
result = result.where(creator_id: creators.ids) if creators.present?
+7 -3
View File
@@ -1,13 +1,13 @@
module Filter::Fields
extend ActiveSupport::Concern
INDEXES = %w[ most_discussed most_boosted newest oldest stalled ]
INDEXES = %w[ most_discussed most_boosted newest oldest latest stalled ]
delegate :default_value?, to: :class
class_methods do
def default_values
{ indexed_by: "most_active" }
{ indexed_by: "latest" }
end
def default_value?(key, value)
@@ -16,7 +16,7 @@ module Filter::Fields
end
included do
store_accessor :fields, :assignment_status, :indexed_by, :terms
store_accessor :fields, :assignment_status, :indexed_by, :terms, :engagement_status
def assignment_status
super.to_s.inquiry
@@ -26,6 +26,10 @@ module Filter::Fields
(super || default_indexed_by).inquiry
end
def engagement_status
super&.inquiry
end
def terms
Array(super)
end
+2
View File
@@ -4,6 +4,7 @@ module Filter::Params
PERMITTED_PARAMS = [
:assignment_status,
:indexed_by,
:engagement_status,
assignee_ids: [],
creator_ids: [],
collection_ids: [],
@@ -35,6 +36,7 @@ module Filter::Params
def as_params
{}.tap do |params|
params[:indexed_by] = indexed_by
params[:engagement_status] = engagement_status
params[:assignment_status] = assignment_status
params[:terms] = terms
params[:tag_ids] = tags.ids
+1 -1
View File
@@ -15,7 +15,7 @@
<% end %>
<%# FIXME: Move this Current.user check to a stimulus controller that just checks for admin? or the like we so we can cache user list %>
<%= button_to account_user_path(user), method: :delete, class: "btn btn--small btn--negative flex-item-no-shrink",
<%= button_to account_user_path(user), method: :delete, class: "btn txt-small btn--negative flex-item-no-shrink",
disabled: !Current.user.can_administer?(user),
data: { turbo_confirm: "Are you sure you want to permanently remove this person from the account?" } do %>
<%= icon_tag "minus" %>
@@ -1,37 +0,0 @@
<div class="card-count__selector txt-x-small" 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 +1 @@
<%= turbo_stream.replace([ @card, :assignees ], partial: "cards/display/perma/assignees", locals: { card: @card }) %>
<%= turbo_stream.replace([ @card, :meta ], partial: "cards/display/perma/meta", locals: { card: @card }) %>
+9 -11
View File
@@ -1,14 +1,12 @@
<%= turbo_frame_tag @card, :assignment do %>
<dialog class="popup panel flex-column align-start gap-half fill-white shadow" data-dialog-target="dialog">
<strong class="popup__title margin-block-start-half pad-inline-half txt-nowrap">Assign this to…</strong>
<strong class="popup__title margin-block-start-half pad-inline-half txt-nowrap">Assign this to…</strong>
<div class="flex flex-column full-width popup__list">
<% @card.collection.users.active.alphabetically.each do |user| %>
<%= button_to card_assignments_path(@card, params: { assignee_id: user.id }), method: :post, class: "btn popup__item full-width" do %>
<span class="overflow-ellipsis"><%= user.name %></span>
<%= icon_tag "check", size: 18, class: "checked flex-item-justify-end" if @card.assignees.include?(user) %>
<% end %>
<% end %>
</div>
</dialog>
<div class="flex flex-column full-width popup__list">
<% @card.collection.users.active.alphabetically.each do |user| %>
<%= button_to card_assignments_path(@card, params: { assignee_id: user.id }), method: :post, class: "btn popup__item full-width" do %>
<span class="overflow-ellipsis"><%= user.name %></span>
<%= icon_tag "check", size: 18, class: "checked flex-item-justify-end" if @card.assignees.include?(user) %>
<% end %>
<% end %>
</div>
<% end %>
+13 -12
View File
@@ -1,19 +1,20 @@
<div class="card card--mini shadow border flex gap position-relative txt-align-start full-width border-radius" style="view-transition-name: <%= dom_id(card, :ticket) %>;">
<%= avatar_image_tag card.creator, class: "avatar" %>
<div class="card card--mini border flex flex-column gap position-relative txt-align-start full-width border-radius fill-white" style="--card-color: <%= card.color %>; view-transition-name: <%= dom_id(card, :ticket) %>;">
<header class="card__header flex align-center gap min-width">
<span class="card__collection txt-uppercase overflow-ellipsis txt-reversed">
<%= card.collection.name %>
</span>
<div class="overflow-ellipsis"><%= render "cards/display/mini/tags", card: card %></div>
</header>
<div class="card__body flex flex-column gap-half full-width flex-item-grow min-width max-width">
<h3 class="card__title">
<%= card_title(card) %>
</h3>
<div class="txt-small overflow-ellipsis txt-uppercase"><%= "Added to #{card.collection.name} by #{card.creator.name}" %>
<%= local_datetime_tag(card.created_at, style: :daysago) %>
<div class="flex align-center gap min-width margin-block-end-half">
<h3 class="card__title overflow-ellipsis">
<%= card_title(card) %>
</h3>
</div>
<div class="card__stuff flex align-center gap">
<%= render "cards/display/mini/assignees", card: card %>
<div class="overflow-ellipsis"><%= render "cards/display/mini/tags", card: card %></div>
</div>
<%= render "cards/display/mini/meta", card: card %>
</div>
<%= link_to collection_card_path(card.collection, card), class: "card__link" do %>
-1
View File
@@ -31,7 +31,6 @@
</div>
<footer class="card__footer full-width flex align-start gap">
<%= render "cards/display/perma/people", card: card %>
<%= render "cards/display/perma/meta", card: card %>
</footer>
+1 -2
View File
@@ -19,8 +19,7 @@
<%= render "cards/stagings/stages", card: card if card.doing? %>
</div>
<footer class="card__footer full-width flex align-start gap">
<%= render "cards/display/preview/people", card: card %>
<footer class="card__footer">
<%= render "cards/display/preview/meta", card: card %>
</footer>
@@ -9,6 +9,8 @@
<span class="for-screen-reader">Assign</span>
</button>
<%= yield %>
<dialog class="popup panel flex-column align-start gap-half fill-white shadow" data-dialog-target="dialog">
<%= yield %>
</dialog>
</div>
</div>
+23 -21
View File
@@ -1,31 +1,33 @@
<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"><%= card.creating? ? "Added" : "Updated" %>
<div class="card__meta flex align-center txt-tight-lines txt-uppercase max-width min-width" id="<%= dom_id(card, :meta) %>">
<%= avatar_image_tag card.creator, class: "avatar margin-inline-end" %>
<div class="flex flex-column gap txt-align-start">
<span class="pad-inline overflow-ellipsis">Added
<%= local_datetime_tag(card.created_at, style: :daysago) %>
</span>
<hr class="full-width separator--horizontal unpad margin-none">
<span class="pad-inline overflow-ellipsis">
Reported by <strong><%= familiar_name_for(card.creator) %></strong>
</span>
</div>
<hr class="separator margin-none unpad">
<div class="flex flex-column gap txt-align-start overflow-ellipsis">
<span class="pad-inline overflow-ellipsis"><%= card.creating? ? "Created" : "Updated" %>
<%= local_datetime_tag(card.updated_at, style: :daysago) %>
</span>
<hr class="full-width separator--horizontal unpad margin-none">
<% if card.creating? %>
<span class="pad-inline overflow-ellipsis">Expires
<%= local_datetime_tag(card.auto_close_at, style: :indays) %>
</span>
<% else %>
<span class="pad-inline overflow-ellipsis">Added
<%= local_datetime_tag(card.created_at, style: :daysago) %>
</span>
<% end %>
<span class="pad-inline overflow-ellipsis">
Assigned to <%= card.assignees.any? ? card.assignees.map { |assignee| "<strong>#{familiar_name_for(assignee)}</strong>" }.to_sentence.html_safe : "no one" %>
</span>
</div>
<hr class="separator margin-none unpad">
<div class="flex flex-column gap txt-align-start">
<span class="pad-inline overflow-ellipsis">
<strong><%= card.comments_count %></strong> <%= card.comments_count == 1 ? "comment" : "comments" %>
</span>
<hr class="full-width separator--horizontal unpad margin-none">
<div class="margin-inline-start flex-item-no-shrink">
<%= yield %>
</div>
</div>
@@ -1,12 +0,0 @@
<div class="card__people flex gap">
<div class="flex flex-column align-center">
<span class="card__people-label txt-uppercase">Reported</span>
<%= avatar_tag card.creator, class: "avatar" %>
</div>
<div class="card__assignees flex flex-column align-center">
<span class="card__people-label txt-uppercase">Assigned</span>
<%= yield %>
</div>
</div>
@@ -3,12 +3,5 @@
<% card.assignees.each do |assignee| %>
<%= avatar_tag assignee, class: "avatar" %>
<% end %>
<div class="position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
<button class="btn card__hide-on-index <%= card.creating? || card.drafted? ? "fill-highlight" : "fill-transparent" %>" data-action="click->dialog#open:stop">
<%= icon_tag "add" %>
<span class="for-screen-reader">Assign</span>
</button>
</div>
</div>
<% end %>
@@ -0,0 +1,3 @@
<%= render "cards/display/common/meta", card: card do %>
<%= render "cards/display/mini/assignees", card: card %>
<% end %>
@@ -1,3 +0,0 @@
<%= turbo_frame_tag dom_id(card, :boosts) do %>
<%= render "cards/boosts/boosts", card: card %>
<% end %>
+1 -1
View File
@@ -1,3 +1,3 @@
<%= render "cards/display/common/meta", card: card do %>
<%= render "cards/display/perma/boosts", card: card %>
<%= render "cards/display/perma/assignees", card: card %>
<% end %>
@@ -1,3 +0,0 @@
<%= render "cards/display/common/people", card: card do%>
<%= render "cards/display/perma/assignees", card: card %>
<% end %>
@@ -1,8 +0,0 @@
<%= turbo_frame_tag dom_id(card, :boosts) do %>
<%= tag.div class: "pad-inline flex align-center gap" do %>
<span class="overflow-ellipsis">
<strong><%= card.boosts_count %></strong>
<span><%= card.boosts_count == 1 ? "boost" : "boosts" %></span>
</span>
<% end %>
<% end %>
@@ -1,3 +1,3 @@
<%= render "cards/display/common/meta", card: card do %>
<%= render "cards/display/preview/boosts", card: card %>
<%= render "cards/display/preview/assignees", card: card %>
<% end %>
+3 -3
View File
@@ -7,8 +7,8 @@
<% end %>
<div class="card-columns">
<%= render "cards/index/engagement/considering", cards: @considering_cards %>
<%= render "cards/index/engagement/doing", cards: @doing_cards, filter: @filter %>
<%= render "cards/index/engagement/considering", **@considering.to_h %>
<%= render "cards/index/engagement/doing", **@doing.to_h %>
</div>
<%= render "cards/index/engagement/closed", cards: @closed_cards, filter: @filter %>
<%= render "cards/index/engagement/closed", **@closed.to_h %>
-2
View File
@@ -18,8 +18,6 @@
<%= render "cards/index/collections_filter", filter: filter %>
</dialog>
</div>
<%= render "cards/index/sorting_options", filter: filter %>
</header>
<% if collection = filter.single_collection %>
@@ -1,14 +0,0 @@
<div class="card-filter--sorting flex-inline">
<%= form_with url: cards_path, method: :get do |form| %>
<% filter.as_params.except(:indexed_by).each do |key, value| %>
<%= filter_hidden_field_tag key, value %>
<% end %>
<%= form.hidden_field :indexed_by, value: filter.indexed_by == "oldest" ? "newest" : "oldest" %>
<button class="btn borderless txt-medium">
<%= icon_tag filter.indexed_by == "oldest" ? "sort-ascending" : "sort-descending", size: 24, aria: { hidden: true } %>
<span class="for-screen-reader">Sort by <%= filter.indexed_by == "oldest" ? "newest" : "oldest" %></span>
</button>
<% end %>
</div>
@@ -3,9 +3,13 @@
Recently closed
</h2>
<section class="cards cards--closed">
<% if cards.any? %>
<%= render partial: "cards/display/preview", collection: cards, as: :card, cached: true %>
<section id="closed-cards" class="cards cards--closed">
<% if page.used? %>
<%= render partial: "cards/display/preview", collection: page.records, as: :card, cached: true %>
<% unless page.last? %>
<%= cards_next_page_link "closed-cards", page: page, filter: filter, fetch_on_visible: true %>
<% end %>
<% else %>
<p class="txt-medium translucent">Nothing here</p>
<% end %>
@@ -1,10 +1,14 @@
<section class="cards cards--considering">
<section id="considering-cards" class="cards cards--considering">
<h2 class="txt-align-center flex-inline center txt-uppercase txt-medium margin-none-block">
Considering
</h2>
<% if cards.any? %>
<%= render partial: "cards/display/preview", collection: cards, as: :card, cached: true %>
<% if page.used? %>
<%= render partial: "cards/display/preview", collection: page.records, as: :card, cached: true %>
<% unless page.last? %>
<%= cards_next_page_link "considering-cards", page: page, filter: filter %>
<% end %>
<% else %>
<p class="txt-medium translucent">Nothing here</p>
<% end %>
@@ -1,4 +1,4 @@
<section class="cards cards--doing" style="view-transition-name: cards-container;" data-controller="card-size" data-action="turbo:morph@window->card-size#resize">
<section id="doing-cards" class="cards cards--doing" style="view-transition-name: cards-container;" data-controller="card-size" data-action="turbo:morph@window->card-size#resize">
<% if workflow = filter.single_workflow %>
<%= render "cards/index/workflow_filter", workflow: workflow, filter: filter %>
<% else %>
@@ -7,8 +7,12 @@
</h2>
<% end %>
<% if cards.any? %>
<%= render partial: "cards/display/preview", collection: cards, as: :card, cached: true %>
<% if page.used? %>
<%= render partial: "cards/display/preview", collection: page.records, as: :card, cached: true %>
<% unless page.last? %>
<%= cards_next_page_link "doing-cards", page: page, filter: filter %>
<% end %>
<% else %>
<p class="txt-medium translucent">Nothing here</p>
<% end %>
@@ -0,0 +1,10 @@
<%= turbo_stream.remove "#{params[:target]}-load-page-#{@page.number}" %>
<%= turbo_stream.append params[:target] do %>
<%= render partial: @filter.engagement_status&.considering? ? "cards/display/mini" : "cards/display/preview",
collection: @page.records, as: :card, cached: true %>
<% unless @page.last? %>
<%= cards_next_page_link params[:target], page: @page, filter: @filter, fetch_on_visible: @filter.indexed_by.closed? %>
<% end %>
<% end %>
+1 -2
View File
@@ -28,14 +28,13 @@
<%= render "cards/doing_toggle", card: @card %>
<div data-controller="beacon" data-beacon-url-value="<%= card_reading_url(@card) %>">
<section class="card-perma <%= "card-perma--pointing" unless @card.published? %>" style="--card-color: <%= @card.color %>;">
<section id="<%= dom_id(@card, :card_container) %>" class="card-perma <%= "card-perma--pointing" unless @card.published? %>" style="--card-color: <%= @card.color %>;">
<aside class="card-perma__actions" role="toolbar">
<button class="btn">
<%= icon_tag "star" %>
<span class="for-screen-reader">Star this card</span>
<input type="checkbox" class="card-perma__star-input">
</button>
<%= render "cards/color", card: @card %>
<%= render "cards/image", card: @card %>
<%= button_to collection_card_path(@card.collection, @card),
method: :delete,
@@ -1,3 +1,6 @@
<%= turbo_stream.replace dom_id(@card, "stages") do %>
<%= render partial: "cards/stagings/stages", locals: { card: @card } %>
<% end %>
<%= turbo_stream.set_css_variable dom_id(@card, :card_container), name: "--card-color", value: @card.color %>
<%= turbo_stream.set_css_variable dom_id(@card, :ticket), name: "--card-color", value: @card.color %>
@@ -1,27 +0,0 @@
<li class="collection flex flex-column txt-align-center max-width">
<%= link_to cards_path(collection_ids: [ collection ]),
class: "border border-radius margin-block-end-half cards__container flex justify-center align-center position-relative",
aria: { hidden: true }, tabindex: -1 do %>
<div class="cards collection__cards flex flex-wrap gap justify-center align-end" style="view-transition-name: cards_<%= collection.id %>">
<% collection.cards.active.published_or_drafted_by(Current.user).ordered_by_activity.limit(10).each do |card| %>
<div class="<%= class_names("card", drafted: card.drafted?) %>" style="--card-color: <%= card.color %>; <%= card_rotation(card) %>"
data-card-size-target="card"
data-activity-score="<%= card.activity_score %>"
data-activity-score-at="<%= card.activity_score_at.to_i %>">
<span class="card__shape"></span>
</div>
<% end %>
</div>
<% end %>
<div class="flex align-center justify-center flex-column flex-wrap center gap-half position-relative full-width">
<%= link_to cards_path(collection_ids: [ collection ]), class: "txt-ink flex flex-column" do %>
<strong class="txt-large"><%= collection.name %></strong>
<% end %>
<%= link_to edit_collection_path(collection), class: "btn txt-small collection__button" do %>
<%= icon_tag "settings" %>
<span class="for-screen-reader">Settings for <%= collection.name %></span>
<% end %>
</div>
</li>
-2
View File
@@ -16,8 +16,6 @@
<div class="panel shadow center" style="--panel-size: 55ch;">
<%= form_with model: @collection, class: "flex flex-column gap txt-large", data: { controller: "form" } do |form| %>
<div class="flex align-center gap">
<%= translation_button(:collection_name) %>
<label class="flex-item-grow txt-large">
<strong><%= form.text_field :name, name: "collection[name]", class: "input full-width",
required: true, autofocus: true, placeholder: "Give it a name…",
@@ -8,7 +8,7 @@
<%= turbo_frame_tag comment, :new_reaction do %>
<div class="flex-inline" data-controller="soft-keyboard">
<%= link_to new_collection_card_comment_reaction_path(comment.card.collection, comment.card, comment), role: "button",
class: "txt-small btn reaction__action", action: "soft-keyboard#open" do %>
class: "btn reaction__action", action: "soft-keyboard#open" do %>
<%= icon_tag "reaction" %>
<span class="for-screen-reader">Add a reaction</span>
<% end %>
-1
View File
@@ -173,7 +173,6 @@
<div class="flex align-center txt-align-center justify-space-between gap-half margin-block-start">
<%= tag.button class: "btn", form: :filter_form, formaction: filters_path, formmethod: :post do %>
<%= icon_tag "cards" %>
<span>Save collection</span>
<% end %>
-28
View File
@@ -1,28 +0,0 @@
<% cache_if filter.cacheable?, filter do %>
<li class="collection flex flex-column txt-align-center max-width position-relative">
<%= link_to cards_path(**filter.as_params),
class: "border border-radius margin-block-end-half cards__container flex justify-center align-center position-relative",
aria: { hidden: true }, tabindex: -1 do %>
<div class="cards collection__cards flex flex-wrap gap justify-center align-end" style="view-transition-name: cards_<%= filter.id %>">
<% filter.cards.ordered_by_activity.limit(10).each do |card| %>
<div class="<%= class_names("card", drafted: card.drafted?) %>" style="--card-color: <%= card.color %>; <%= card_rotation(card) %>"
data-card-size-target="card"
data-activity-score="<%= card.activity_score %>"
data-activity-score-at="<%= card.activity_score_at.to_i %>">
<span class="card__shape"></span>
</div>
<% end %>
</div>
<% end %>
<div class="flex align-center justify-center flex-column flex-wrap center gap-half position-relative full-width">
<%= link_to cards_path(**filter.as_params), class: "txt-ink" do %>
<strong class="txt-medium"><%= icon_tag "filter", class: "flex-inline", style: "vertical-align: bottom" %> <%= filter.summary %></strong>
<% end %>
<%= button_to filter_path(filter), method: :delete, class: "btn txt-small btn--negative collection__button", data: { turbo_confirm: "Are you sure you want to delete this filter?" } do %>
<%= icon_tag "minus" %>
<span class="for-screen-reader">Delete</span>
<% end %>
</div>
</li>
<% end %>
-3
View File
@@ -5,7 +5,6 @@
<%= form_with model: User.new, url: first_run_path, class: "flex flex-column gap txt-large" do |form| %>
<div class="flex align-center gap">
<%= translation_button :user_name %>
<label class="flex align-center gap input input--actor txt-large">
<%= form.text_field :name, class: "input", autocomplete: "name", placeholder: "Name", autofocus: true, required: true, data: { "1p-ignore": true } %>
<%= icon_tag "person", class: "txt-large" %>
@@ -13,7 +12,6 @@
</div>
<div class="flex align-center gap">
<%= translation_button :email_address %>
<label class="flex align-center gap input input--actor txt-large">
<%= form.email_field :email_address, class: "input", autocomplete: "username", placeholder: "Email address", required: true %>
<%= icon_tag "email", class: "txt-large" %>
@@ -21,7 +19,6 @@
</div>
<div class="flex align-center gap">
<%= translation_button :password %>
<label class="flex align-center gap input input--actor txt-large">
<%= form.password_field :password, class: "input", autocomplete: "new-password", placeholder: "Password", required: true, maxlength: 72 %>
<%= icon_tag "password", class: "txt-large" %>
+1 -1
View File
@@ -1,5 +1,5 @@
<% cache pin do %>
<%= tag.div id: dom_id(pin), class: "pin tray__item flex txt-normal" do %>
<%= tag.div id: dom_id(pin), class: "pin tray__item txt-normal" do %>
<%= link_to collection_card_path(pin.card.collection, pin.card),
class: "pin__content shadow fill-white flex align-start txt-align-start gap flex-item-grow max-width border border-radius txt-ink overflow-clip",
data: { action: "click->dialog#close", turbo_frame: "_top" },
@@ -1,4 +1,4 @@
<% cache [notification, "2025-03-20"] do %>
<% cache [notification, "2025-04-09"] do %>
<%= notification_tag notification do %>
<div class="avatar txt-x-small flex-item-no-shrink">
<%= avatar_image_tag(notification.creator) %>
-2
View File
@@ -5,7 +5,6 @@
<%= form_with url: session_path, class: "flex flex-column gap txt-large" do |form| %>
<div class="flex align-center gap">
<%= translation_button :email_address %>
<label class="flex align-center gap input input--actor">
<%= form.email_field :email_address, required: true, class: "input full-width", autofocus: true, autocomplete: "username", placeholder: "Enter your email address" %>
<%= icon_tag "email", class: "txt-large" %>
@@ -13,7 +12,6 @@
</div>
<div class="flex align-center gap">
<%= translation_button :password %>
<label class="flex align-center gap input input--actor">
<%= form.password_field :password, required: true, class: "input full-width", autocomplete: "current-password", placeholder: "Enter your password", maxlength: 72 %>
<%= icon_tag "password", class: "txt-large" %>
-3
View File
@@ -29,21 +29,18 @@
</div>
<div class="flex align-center gap">
<%= translation_button :user_name %>
<label class="flex align-center gap input input--actor">
<%= form.text_field :name, class: "input full-width", autocomplete: "name", placeholder: "Name", autofocus: true, required: true, data: { "1p-ignore": true, action: "keydown.esc@document->form#cancel" } %>
<%= icon_tag "person", class: "txt-large" %>
</label>
</div>
<div class="flex align-center gap">
<%= translation_button :email_address %>
<label class="flex align-center gap input input--actor">
<%= form.email_field :email_address, class: "input full-width", autocomplete: "username", placeholder: "Email address", required: true %>
<%= icon_tag "email", class: "txt-large" %>
</label>
</div>
<div class="flex align-center gap">
<%= translation_button :password %>
<label class="flex align-center gap input input--actor">
<%= form.password_field :password, class: "input full-width", autocomplete: "new-password", placeholder: "Password", required: false, maxlength: 72 %>
<%= icon_tag "password", class: "txt-large" %>
-3
View File
@@ -13,21 +13,18 @@
<%= form_with model: @user, url: join_path(@account.join_code), class: "flex flex-column gap" do |form| %>
<div class="flex align-center gap">
<%= translation_button :user_name %>
<label class="flex align-center gap input input--actor">
<%= form.text_field :name, class: "input full-width", autocomplete: "name", placeholder: "Name", autofocus: true, required: true, data: { "1p-ignore": true } %>
<%= icon_tag "person", class: "txt-large" %>
</label>
</div>
<div class="flex align-center gap">
<%= translation_button :email_address %>
<label class="flex align-center gap input input--actor">
<%= form.email_field :email_address, class: "input full-width", autocomplete: "username", placeholder: "Email address", required: true %>
<%= icon_tag "email", class: "txt-large" %>
</label>
</div>
<div class="flex align-center gap">
<%= translation_button :password %>
<label class="flex align-center gap input input--actor">
<%= form.password_field :password, class: "input full-width", autocomplete: "new-password", placeholder: "Password", required: true, maxlength: 72 %>
<%= icon_tag "password", class: "txt-large" %>
+1 -1
View File
@@ -14,7 +14,7 @@
<%= turbo_frame_tag dom_id(workflow, :stages) do %>
<%= render partial: "workflows/stages/stage", collection: workflow.stages %>
<%= link_to new_workflow_stage_path(workflow), class: "btn btn--small" do %>
<%= link_to new_workflow_stage_path(workflow), class: "btn txt-small" do %>
<%= icon_tag "add" %>
<span class="for-screen-reader">New stage</span>
<% end %>
+3 -3
View File
@@ -7,17 +7,17 @@
<%= link_to "Cancel", workflow_path(@workflow), data: { form_target: "cancel", turbo_frame: dom_id(@workflow) }, hidden: true %>
<% end %>
<%= tag.button type: :submit, class: "btn btn--small btn--positive", form: dom_id(@workflow, :form) do %>
<%= tag.button type: :submit, class: "btn txt-small btn--positive", form: dom_id(@workflow, :form) do %>
<%= icon_tag "check" %>
<span class="for-screen-reader">Save</span>
<% end %>
<%= link_to workflow_path(@workflow), class: "btn btn--small", data: { turbo_frame: dom_id(@workflow) } do %>
<%= link_to workflow_path(@workflow), class: "btn txt-small", data: { turbo_frame: dom_id(@workflow) } do %>
<%= icon_tag "remove-med" %>
<span class="for-screen-reader">Cancel</span>
<% end %>
<%= button_to workflow_path(@workflow), method: :delete, class: "btn btn--small btn--negative", data: { turbo_frame: :workflows, turbo_confirm: "Are you sure you want to delete this workflow?" } do %>
<%= button_to workflow_path(@workflow), method: :delete, class: "btn txt-small btn--negative", data: { turbo_frame: :workflows, turbo_confirm: "Are you sure you want to delete this workflow?" } do %>
<%= icon_tag "minus" %>
<span class="for-screen-reader">Delete <%= @workflow.name %></span>
<% end %>
@@ -1,12 +1,12 @@
<div class="position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close">
<button class="btn" data-action="dialog#open:stop">
<button class="btn btn--reversed txt-x-small" data-action="dialog#open:stop" style="--btn-background: <%= stage_color(stage) %>">
<%= icon_tag "art" %>
<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">
<%= form_with model: card, url: collection_card_path(card.collection, card), class: "flex gap-half", data: { controller: "form", action: "change->form#submit" } do |form| %>
<% Card::COLORS.each do |color| %>
<%= form_with model: stage, url: workflow_stage_path(stage.workflow, stage), class: "flex gap-half", data: { controller: "form", action: "change->form#submit" } do |form| %>
<% Colorable::COLORS.each do |color| %>
<span class="color-picker__color">
<label class="btn btn--circle" style="--btn-background: <%= color %>" title="<%= color %>">
<%= form.radio_button :color, color %>
+4 -2
View File
@@ -1,7 +1,9 @@
<div class="flex gap-half align-center justify-space-between">
<div class="flex gap-half align-center">
<%= render "workflows/stages/color", stage: stage %>
<%= turbo_frame_tag stage do %>
<%= link_to edit_workflow_stage_path(stage.workflow, stage) do %>
<strong class="txt-nowrap overflow-ellipsis"><%= stage.name %></strong>
<% end %>
<% end %>
</div>
</div>
+3 -3
View File
@@ -5,18 +5,18 @@
<% end %>
<div class="flex align-center gap-half">
<%= tag.button type: :submit, class: "btn btn--small btn--positive", form: dom_id(@stage, :form) do %>
<%= tag.button type: :submit, class: "btn txt-small btn--positive", form: dom_id(@stage, :form) do %>
<%= icon_tag "check" %>
<span class="for-screen-reader">Save</span>
<% end %>
<%= link_to workflow_path(@workflow), class: "btn btn--small", data: { turbo_frame: dom_id(@workflow) } do %>
<%= link_to workflow_path(@workflow), class: "btn txt-small", data: { turbo_frame: dom_id(@workflow) } do %>
<%= icon_tag "remove-med" %>
<span class="for-screen-reader">Cancel</span>
<% end %>
<% if @stage.workflow.stages.many? %>
<%= button_to workflow_stage_path(@stage.workflow, @stage), method: :delete, class: "btn btn--negative btn--small",
<%= button_to workflow_stage_path(@stage.workflow, @stage), method: :delete, class: "btn btn--negative txt-small",
data: { turbo_frame: dom_id(@workflow), turbo_confirm: "Are you sure you want to delete this stage?" } do %>
<%= icon_tag "minus" %>
<span class="for-screen-reader">Delete <%= @stage.name %></span>
+1 -1
View File
@@ -5,7 +5,7 @@
<%= icon_tag "check" %>
<span class="for-screen-reader">Add stage</span>
<% end %>
<%= link_to workflow_path(@workflow), class: "btn btn--small", data: { form_target: "cancel", controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
<%= link_to workflow_path(@workflow), class: "btn txt-small", data: { form_target: "cancel", controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
<%= icon_tag "remove-med" %>
<span class="for-screen-reader">Cancel</span>
<% end %>
+4
View File
@@ -8,6 +8,10 @@ Rails.application.routes.draw do
end
end
namespace :cards do
resources :previews
end
resources :cards do
scope module: :cards do
resource :engagement
@@ -0,0 +1,5 @@
class AddColorToWorkflowStages < ActiveRecord::Migration[8.1]
def change
add_column :workflow_stages, :color, :string
end
end
@@ -0,0 +1,5 @@
class RemoveColorFromCards < ActiveRecord::Migration[8.1]
def change
remove_column :cards, :color, :string
end
end

Some files were not shown because too many files have changed in this diff Show More