Merge branch 'main' into unique-notifications
@@ -19,9 +19,7 @@ gem "solid_cache", "~> 1.0"
|
||||
gem "solid_queue", "~> 1.2"
|
||||
gem "sqlite3", ">= 2.0"
|
||||
gem "thruster", require: false
|
||||
source "https://e95ae463b12de3f204526a44650f6ae0@gems.stanko.io/private" do
|
||||
gem "beamer-rails", "~> 0.1.0.beta4"
|
||||
end
|
||||
gem "beamer-rails", "~> 0.1.0.beta4"
|
||||
|
||||
# Features
|
||||
gem "bcrypt", "~> 3.1.7"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
GIT
|
||||
remote: https://github.com/basecamp/activerecord-tenanted
|
||||
revision: aaa4a3473eab400c4c8a6ca05a7207578d3ade53
|
||||
revision: e409f75dc2ea1c3355e98feb22b76b3cc0a01fe0
|
||||
specs:
|
||||
activerecord-tenanted (0.5.0)
|
||||
activerecord-tenanted (0.6.0)
|
||||
activerecord (>= 8.1.beta)
|
||||
railties (>= 8.1.beta)
|
||||
zeitwerk
|
||||
@@ -36,7 +36,7 @@ GIT
|
||||
|
||||
GIT
|
||||
remote: https://github.com/rails/rails.git
|
||||
revision: d3d17c297a70d3e255a34693a1fb0074712e9930
|
||||
revision: bc41ad696a76a151db56259938dccf64ef87b501
|
||||
branch: main
|
||||
specs:
|
||||
actioncable (8.2.0.alpha)
|
||||
@@ -143,20 +143,6 @@ PATH
|
||||
rails (>= 8.1.0.beta1)
|
||||
rails_structured_logging
|
||||
|
||||
GEM
|
||||
remote: https://gems.stanko.io/private/
|
||||
specs:
|
||||
beamer-rails (0.1.0.beta4-arm64-darwin)
|
||||
sqlite3 (>= 2.0)
|
||||
beamer-rails (0.1.0.beta4-arm64-linux-gnu)
|
||||
sqlite3 (>= 2.0)
|
||||
beamer-rails (0.1.0.beta4-x86_64-darwin)
|
||||
sqlite3 (>= 2.0)
|
||||
beamer-rails (0.1.0.beta4-x86_64-linux-gnu)
|
||||
sqlite3 (>= 2.0)
|
||||
beamer-rails (0.1.0.beta4-x86_64-linux-musl)
|
||||
sqlite3 (>= 2.0)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
@@ -198,6 +184,16 @@ GEM
|
||||
base64 (0.3.0)
|
||||
bcrypt (3.1.20)
|
||||
bcrypt_pbkdf (1.1.1)
|
||||
beamer-rails (0.1.0.beta4-arm64-darwin)
|
||||
sqlite3 (>= 2.0)
|
||||
beamer-rails (0.1.0.beta4-arm64-linux-gnu)
|
||||
sqlite3 (>= 2.0)
|
||||
beamer-rails (0.1.0.beta4-x86_64-darwin)
|
||||
sqlite3 (>= 2.0)
|
||||
beamer-rails (0.1.0.beta4-x86_64-linux-gnu)
|
||||
sqlite3 (>= 2.0)
|
||||
beamer-rails (0.1.0.beta4-x86_64-linux-musl)
|
||||
sqlite3 (>= 2.0)
|
||||
benchmark (0.5.0)
|
||||
bigdecimal (3.3.1)
|
||||
bindex (0.8.1)
|
||||
@@ -294,7 +290,7 @@ GEM
|
||||
logger (~> 1.6)
|
||||
letter_opener (1.10.0)
|
||||
launchy (>= 2.2, < 4)
|
||||
lexxy (0.1.18.beta)
|
||||
lexxy (0.1.20.beta)
|
||||
rails (>= 8.0.2)
|
||||
lint_roller (1.1.0)
|
||||
logger (1.7.0)
|
||||
@@ -537,7 +533,7 @@ GEM
|
||||
unicode-display_width (3.2.0)
|
||||
unicode-emoji (~> 4.1)
|
||||
unicode-emoji (4.1.0)
|
||||
uri (1.1.0)
|
||||
uri (1.1.1)
|
||||
useragent (0.16.11)
|
||||
vcr (6.3.1)
|
||||
base64
|
||||
@@ -604,7 +600,7 @@ DEPENDENCIES
|
||||
autotuner
|
||||
aws-sdk-s3
|
||||
bcrypt (~> 3.1.7)
|
||||
beamer-rails (~> 0.1.0.beta4)!
|
||||
beamer-rails (~> 0.1.0.beta4)
|
||||
benchmark
|
||||
bootsnap
|
||||
brakeman
|
||||
|
||||
@@ -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="m10.0001 13.0002c.5523 0 1 .4477 1 1v6c0 .5522-.4478.9999-1 1-.55224 0-.99994-.4478-.99999-1v-3.586l-5.29297 5.293c-.3905.3905-1.02353.3904-1.41406 0-.39053-.3905-.39053-1.0236 0-1.4141l5.29297-5.2929h-3.58594c-.55225 0-.99995-.4478-1-1 0-.5523.44771-1 1-1z"/><path d="m20.2931 2.29313c.3905-.39051 1.0235-.39052 1.414 0s.3905 1.02356 0 1.41406l-5.2929 5.29297h3.5859c.5523.00004 1 .44774 1 1.00004 0 .5522-.4478.9999-1 1h-6c-.5522 0-.9999-.4478-1-1v-6.00004c0-.55229.4477-1 1-1 .5523.00004 1 .44774 1 1v3.58593z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 644 B |
@@ -0,0 +1 @@
|
||||
<svg height="30" viewBox="0 0 30 30" width="30" xmlns="http://www.w3.org/2000/svg"><path d="m3 1c1.10457 0 2 .89543 2 2v17c0 .9763-.69986 1.7872-1.625 1.9629v6.6621c0 .2071-.16789.375-.375.375s-.375-.1679-.375-.375v-6.6621c-.92514-.1757-1.625-.9866-1.625-1.9629v-17c0-1.10457.89543-2 2-2zm6 0c1.1046 0 2 .89543 2 2v24c0 1.1046-.8954 2-2 2-1.10457 0-2-.8954-2-2v-24c0-1.10457.89543-2 2-2zm6 0c1.1046 0 2 .89543 2 2v20.5c0 .9763-.6999 1.7872-1.625 1.9629v3.1621c0 .2071-.1679.375-.375.375s-.375-.1679-.375-.375v-3.1621c-.9251-.1757-1.625-.9866-1.625-1.9629v-20.5c0-1.10457.8954-2 2-2zm6 0c1.1046 0 2 .89543 2 2v13.5c0 .9763-.6999 1.7872-1.625 1.9629v10.1621c0 .2071-.1679.375-.375.375s-.375-.1679-.375-.375v-10.1621c-.9251-.1757-1.625-.9866-1.625-1.9629v-13.5c0-1.10457.8954-2 2-2zm6 0c1.1046 0 2 .89543 2 2v17c0 .9763-.6999 1.7872-1.625 1.9629v6.6621c0 .2071-.1679.375-.375.375s-.375-.1679-.375-.375v-6.6621c-.9251-.1757-1.625-.9866-1.625-1.9629v-17c0-1.10457.8954-2 2-2z" fill="currentColor"/></svg>
|
||||
|
After Width: | Height: | Size: 1000 B |
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g clip-rule="evenodd" fill-rule="evenodd"><path d="m8 13c1.65685 0 3 1.3431 3 3v3c0 1.6569-1.34315 3-3 3h-4c-1.60511 0-2.91579-1.2606-2.99609-2.8457l-.00391-.1543v-3c0-1.6569 1.34315-3 3-3zm-4 2c-.55228 0-1 .4477-1 1v3l.00488.1025c.05133.5042.47744.8975.99512.8975h4c.55228 0 1-.4477 1-1v-3c0-.5523-.44772-1-1-1z"/><path d="m20 13c1.6569 0 3 1.3431 3 3v3c0 1.6569-1.3431 3-3 3h-4c-1.6051 0-2.9158-1.2606-2.9961-2.8457l-.0039-.1543v-3c0-1.6569 1.3431-3 3-3zm-4 2c-.5523 0-1 .4477-1 1v3l.0049.1025c.0513.5042.4774.8975.9951.8975h4c.5523 0 1-.4477 1-1v-3c0-.5523-.4477-1-1-1z"/><path d="m8 2c1.65685 0 3 1.34315 3 3v3c0 1.65685-1.34315 3-3 3h-4c-1.60511 0-2.91579-1.26055-2.99609-2.8457l-.00391-.1543v-3c0-1.65685 1.34315-3 3-3zm-4 2c-.55228 0-1 .44772-1 1v3l.00488.10254c.05133.50413.47744.89746.99512.89746h4c.55228 0 1-.44772 1-1v-3c0-.55228-.44772-1-1-1z"/><path d="m20 2c1.6569 0 3 1.34315 3 3v3c0 1.65685-1.3431 3-3 3h-4c-1.6051 0-2.9158-1.26055-2.9961-2.8457l-.0039-.1543v-3c0-1.65685 1.3431-3 3-3zm-4 2c-.5523 0-1 .44772-1 1v3l.0049.10254c.0513.50413.4774.89746.9951.89746h4c.5523 0 1-.44772 1-1v-3c0-.55228-.4477-1-1-1z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -1 +0,0 @@
|
||||
<svg fill="none" height="275" viewBox="0 0 202 275" width="202" xmlns="http://www.w3.org/2000/svg"><path d="m201 258c0 11.159-44.772 16-100 16-55.2285 0-100-4.841-100-16s44.7715-16 100-16c55.228 0 100 4.841 100 16z" fill="#000" opacity=".08"/><path clip-rule="evenodd" d="m144.805 2.24222c2.294-1.843841 5.681-1.617368 7.696.56152 2.014 2.17904 1.917 5.51121-.174 7.57326l-.209.1953-.012.0117c-.009.0075-.022.0181-.039.0332-.035.0309-.087.078-.156.1397-.138.1235-.344.309-.614.5508-.54.484-1.336 1.1979-2.353 2.1152-2.033 1.835-4.952 4.4821-8.487 7.7256-7.073 6.4897-16.602 15.357-26.441 24.8759-2.543 2.4612-4.066 5.7749-4.269 9.2891-.383 6.654-.719 11.235-1.012 17.6885 27.89.2759 50.213 1.9831 62.831 3.1963l2.598.2558.018.002c7.816.8155 13.555 7.8838 12.741 15.4687l.001.001c-3.418 33.6092-7.351 63.0722-10.988 87.6142.005.178.001.359-.012.54-.03.417-.11.819-.228 1.203-3.37 24.477-6.469 42.647-8.652 56.28-2.231 13.914-13.656 24.673-28.256 25.45h-.011c-24.343 1.247-48.8273 1.346-73.2036.126l-2.3589-.122c-14.5946-.782-26.0187-11.539-28.2492-25.453-2.1836-13.632-5.2839-31.801-8.6543-56.276-.1189-.385-.1974-.789-.2278-1.208-.0133-.182-.0178-.363-.013-.542-3.6384-24.542-7.5712-54.004-10.9942-87.6112l.0009-.001c-.814-7.5854 4.9246-14.6542 12.7413-15.4697l.0179-.002c12.5608-1.2701 37.7453-3.3397 69.6834-3.4824.3004-6.6424.644-11.4215 1.0396-18.2793.3592-6.2258 3.0569-12.1147 7.5909-16.5019 9.93-9.6079 19.535-18.5451 26.654-25.0772 3.561-3.2672 6.504-5.93628 8.557-7.78905 1.027-.9262 1.831-1.64876 2.38-2.14063.274-.24594.485-.43526.628-.56249.07-.06313.124-.111.161-.14356.018-.01633.033-.02867.043-.03711.004-.00412.007-.00855.01-.01074l.003-.00195.001-.00098zm-43.801 180.81978c-19.4 0-36.6494.529-49.0474 1.058-5.7553.246-10.4622.491-13.8495.684 3.0876 22.131 5.9039 38.413 7.9281 51.05l.0726.422c1.6067 8.833 8.807 15.28 17.7176 15.757l2.319.12c22.4196 1.123 44.9546 1.111 67.4046.099l4.644-.225c9.05-.486 16.331-7.128 17.781-16.173 2.024-12.637 4.838-28.919 7.925-51.05-3.387-.193-8.094-.438-13.849-.684-12.398-.529-29.647-1.058-49.046-1.058zm-3.9481-99.0874c-31.2286.1604-55.8254 2.1836-68.0558 3.42-1.5771.1659-2.8714 1.698-2.7906 3.2392l.0119.1494.005.0489c3.2299 31.7109 6.9166 59.5089 10.3775 83.0379.0293-.002.0589-.003.0885-.005 3.4785-.201 8.5176-.468 14.7777-.735 11.3621-.485 26.7597-.968 44.1622-1.054-.0045-.387-.0095-.861-.015-1.42-.017-1.739-.0366-4.292-.0487-7.56-.0243-6.536-.0177-13.933.1084-25.385.1704-15.474.5585-32.713 1.3789-53.7364zm11.2131.0235c-.826 21.0519-1.215 38.3339-1.386 53.8319-.125 11.393-.133 18.735-.109 25.226.012 3.245.032 5.777.049 7.495.006.614.011 1.125.016 1.527 17.212.092 32.437.571 43.698 1.052 6.26.267 11.3.534 14.779.735.029.002.057.003.086.005 3.46-23.529 7.146-51.326 10.372-83.037l.005-.0498c.176-1.5891-1.149-3.2156-2.776-3.3886-11.793-1.1922-35.083-3.1168-64.734-3.3965z" fill="#000" fill-rule="evenodd"/><path d="m101.001 183.062c19.505 0 36.849.529 49.315 1.058 5.786.246 10.519.491 13.924.684-3.103 22.131-5.933 38.413-7.968 51.05-1.458 9.045-8.779 15.687-17.879 16.173l-4.669.225c-22.573 1.012-45.231 1.024-67.7731-.099l-2.3317-.12c-8.9591-.477-16.199-6.924-17.8145-15.757l-.073-.422c-2.0352-12.637-4.867-28.919-7.9714-51.05 3.4057-.193 8.1386-.438 13.9252-.684 12.4657-.529 29.8098-1.058 49.3155-1.058z" fill="#68abe9"/><path d="m56.6279 193.649c2.9632-.313 5.6326 1.728 6.0947 4.602l.0376.281.2601 2.344c1.2836 11.298 2.3362 16.481 4.1927 23.957.6454 2.598.9475 4.032 1.2977 5.222.0161.055.0326.106.0475.155.8538.102 2.113.162 4.2422.244 11.6052.444 23.1581.591 34.6066.001 3.073-.159 5.693 2.172 5.854 5.206.16 3.033-2.2 5.621-5.272 5.779-11.9181.615-23.8417.456-35.6199.005-2.1972-.084-4.2851-.159-5.9622-.427-1.7844-.284-3.9749-.903-5.7881-2.707-1.6614-1.653-2.3709-3.704-2.807-5.186-.4338-1.474-.8976-3.572-1.4193-5.673-1.9779-7.965-3.1089-13.606-4.441-25.321l-.269-2.427-.0238-.282c-.1652-2.905 2.0059-5.459 4.9692-5.773z" fill="#fff" opacity=".15"/></svg>
|
||||
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 339 KiB After Width: | Height: | Size: 349 KiB |
|
After Width: | Height: | Size: 210 KiB |
@@ -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="m3 15c.55228 0 1 .4477 1 1v3c0 .5523.44772 1 1 1h3c.55228 0 1 .4477 1 1s-.44772 1-1 1h-3c-1.65685 0-3-1.3431-3-3v-3c0-.5523.44772-1 1-1z"/><path d="m21 15c.5523 0 1 .4477 1 1v3c0 1.6569-1.3431 3-3 3h-3c-.5523 0-1-.4477-1-1s.4477-1 1-1h3c.5523 0 1-.4477 1-1v-3c0-.5523.4477-1 1-1z"/><path d="m8 2c.55228 0 1 .44772 1 1s-.44772 1-1 1h-3c-.55228 0-1 .44772-1 1v3c0 .55228-.44772 1-1 1s-1-.44772-1-1v-3c0-1.65685 1.34315-3 3-3z"/><path d="m19 2c1.6569 0 3 1.34315 3 3v3c0 .55228-.4477 1-1 1s-1-.44772-1-1v-3c0-.55228-.4477-1-1-1h-3c-.5523 0-1-.44772-1-1s.4477-1 1-1z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 694 B |
@@ -89,7 +89,7 @@
|
||||
|
||||
--lch-ink-darkest: 26% 0.05 264;
|
||||
--lch-ink-darker: 40% 0.026 262;
|
||||
--lch-ink-dark: 57% 0.014 260;
|
||||
--lch-ink-dark: 56% 0.014 260;
|
||||
--lch-ink-medium: 66% 0.008 258;
|
||||
--lch-ink-light: 84% 0.005 256;
|
||||
--lch-ink-lighter: 92% 0.003 254;
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[aria-busy] &:disabled {
|
||||
form[aria-busy] &:disabled {
|
||||
position: relative;
|
||||
|
||||
> * {
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
#main:has(.card-columns) {
|
||||
--main-padding: 0;
|
||||
}
|
||||
|
||||
.card-columns {
|
||||
--bubble-size: 3.5rem;
|
||||
--cards-gap: min(1.2cqi, 1.7rem);
|
||||
--column-gap: 8px;
|
||||
@@ -14,10 +18,7 @@
|
||||
--progress-max-cards: 20;
|
||||
--progress-max-height: 50dvh;
|
||||
|
||||
padding-inline: var(--column-gap);
|
||||
}
|
||||
|
||||
.card-columns {
|
||||
-ms-overflow-style: none; /* Hide x-scrollbar on Edge */
|
||||
container-type: inline-size;
|
||||
display: grid;
|
||||
gap: var(--column-gap);
|
||||
@@ -26,7 +27,14 @@
|
||||
max-inline-size: var(--main-width);
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding-block-end: var(--column-width-collapsed);
|
||||
position: relative;
|
||||
scrollbar-width: none; /* Hide x-scrollbar on FF */
|
||||
|
||||
/* Hide x-scrollbar on Chrome/Safari/Opera */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* When it has something expanded */
|
||||
&:has(.card-columns__left .cards:not(.is-collapsed), .card-columns__right .cards:not(.is-collapsed)) {
|
||||
@@ -51,10 +59,12 @@
|
||||
|
||||
.card-columns__left {
|
||||
justify-content: end;
|
||||
padding-inline-start: var(--column-gap);
|
||||
}
|
||||
|
||||
.card-columns__right {
|
||||
justify-content: start;
|
||||
padding-inline-end: var(--column-gap);
|
||||
}
|
||||
|
||||
/* Column
|
||||
@@ -63,20 +73,9 @@
|
||||
.cards {
|
||||
--column-color: color-mix(in srgb, var(--card-color) 15%, var(--color-canvas));
|
||||
|
||||
border-radius: calc(var(--column-width-collapsed) / 2);
|
||||
inline-size: var(--column-width-expanded);
|
||||
position: relative;
|
||||
scroll-snap-align: start;
|
||||
translate: 0;
|
||||
transition: translate var(--column-transition-duration) var(--ease-out-overshoot-subtle);
|
||||
|
||||
.no-transitions & {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&:not(.is-collapsed) {
|
||||
padding-inline: var(--column-padding-expanded);
|
||||
}
|
||||
|
||||
&.is-collapsed {
|
||||
inline-size: var(--column-width-collapsed);
|
||||
@@ -85,12 +84,41 @@
|
||||
.card {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.cards--considering) {
|
||||
translate: 0 var(--column-width-collapsed);
|
||||
&.drag-and-drop__hover-container {
|
||||
--dnd-bg-color: transparent;
|
||||
--dnd-border-color: transparent;
|
||||
|
||||
&.is-off-screen {
|
||||
&:after {
|
||||
content: attr(data-column-name);
|
||||
font-size: var(--text-x-small);
|
||||
font-weight: 500;
|
||||
line-height: var(--column-width-collapsed);
|
||||
padding-inline: 1ch;
|
||||
position: fixed;
|
||||
text-transform: uppercase;
|
||||
top: 0;
|
||||
translate: -50%;
|
||||
}
|
||||
|
||||
&.is-collapsed {
|
||||
&:after {
|
||||
writing-mode: vertical-rl;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.is-collapsed) {
|
||||
&:after {
|
||||
background-color: var(--column-color);
|
||||
inline-size: calc(var(--column-width-expanded) - 4px); /* make room for the dnd border */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (any-hover: hover) {
|
||||
.card:has(.card__background img:not([src=""])):hover .card__background img:not([src=""]) {
|
||||
filter: blur(3px) brightness(1.2);
|
||||
@@ -99,6 +127,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
.cards__transition-container {
|
||||
block-size: 100%;
|
||||
border-radius: calc(var(--column-width-collapsed) / 2);
|
||||
translate: 0;
|
||||
transition: translate var(--column-transition-duration) var(--ease-out-overshoot-subtle);
|
||||
|
||||
.is-collapsed:not(.cards--considering) & {
|
||||
translate: 0 var(--column-width-collapsed);
|
||||
}
|
||||
|
||||
.cards:not(.is-collapsed) & {
|
||||
padding-inline: var(--column-padding-expanded);
|
||||
}
|
||||
|
||||
.drag-and-drop__hover-container & {
|
||||
--dnd-bg-color: var(--column-color);
|
||||
--dnd-border-color: var(--card-color);
|
||||
|
||||
background-color: var(--dnd-bg-color);
|
||||
outline: 2px dashed var(--dnd-border-color);
|
||||
outline-offset: -2px;
|
||||
transition: background-color 200ms;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.no-transitions & {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* The wrapper around the cards used to clip overflow while transitioning.
|
||||
* Also, don't resize cards while transitioning to avoid reflow. */
|
||||
.cards__list {
|
||||
@@ -171,6 +229,46 @@
|
||||
/* Column Elements
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
.cards__header {
|
||||
.cards.is-collapsed & {
|
||||
block-size: 100%;
|
||||
}
|
||||
|
||||
.cards:not(.is-collapsed) & {
|
||||
display: grid;
|
||||
grid-template-areas: "menu expander maximize";
|
||||
grid-template-columns: var(--column-width-collapsed) 1fr var(--column-width-collapsed);
|
||||
margin-block-end: calc(0.5 * var(--cards-gap));
|
||||
}
|
||||
}
|
||||
|
||||
.cards__menu .btn--circle,
|
||||
.cards__maximize-button {
|
||||
--btn-background: transparent;
|
||||
|
||||
block-size: var(--column-width-collapsed);
|
||||
inline-size: var(--column-width-collapsed);
|
||||
opacity: 0;
|
||||
outline-offset: -2px;
|
||||
|
||||
.cards:hover &,
|
||||
&:focus-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.cards.is-collapsed & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cards__menu {
|
||||
z-index: var(--z-popup);
|
||||
}
|
||||
|
||||
.cards__maximize-button {
|
||||
grid-area: maximize;
|
||||
}
|
||||
|
||||
.cards__expander {
|
||||
align-items: center;
|
||||
border-radius: 99rem;
|
||||
@@ -180,6 +278,8 @@
|
||||
font-size: var(--text-x-small);
|
||||
font-weight: 600;
|
||||
gap: 0.5ch;
|
||||
grid-area: expander;
|
||||
outline-offset: -2px;
|
||||
position: relative;
|
||||
text-transform: uppercase;
|
||||
|
||||
@@ -239,7 +339,6 @@
|
||||
|
||||
.cards:not(.is-collapsed) & {
|
||||
inline-size: 100%;
|
||||
margin-block-end: calc(0.5 * var(--cards-gap));
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@@ -247,39 +346,6 @@
|
||||
cursor: unset;
|
||||
filter: none;
|
||||
}
|
||||
|
||||
@media (any-hover: hover) {
|
||||
.btn--circle,
|
||||
~ .btn--circle,
|
||||
~ .cards__expander-menu .btn--circle:not(label) {
|
||||
transition: opacity 300ms ease-in-out;
|
||||
opacity: 0;
|
||||
|
||||
&:focus-visible,
|
||||
.cards:hover & {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn.cards__expander-button {
|
||||
--btn-background: transparent;
|
||||
|
||||
block-size: var(--column-width-collapsed);
|
||||
inline-size: var(--column-width-collapsed);
|
||||
inset: 0 0 auto auto;
|
||||
position: absolute;
|
||||
translate: calc(var(--column-gap) * -2);
|
||||
z-index: var(--z-popup);
|
||||
|
||||
.cards--considering & {
|
||||
translate: none;
|
||||
}
|
||||
|
||||
.cards.is-collapsed & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cards__expander-count {
|
||||
@@ -305,25 +371,28 @@
|
||||
}
|
||||
|
||||
.cards:not(.is-collapsed, .cards--considering) & {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 0.25ch;
|
||||
max-inline-size: calc(100% - var(--column-width-collapsed) * 2);
|
||||
}
|
||||
}
|
||||
|
||||
.cards__expander-menu {
|
||||
inset: 0 auto auto 0;
|
||||
position: absolute;
|
||||
translate: calc(var(--column-gap) * 2);
|
||||
z-index: var(--z-popup);
|
||||
.icon--collapse {
|
||||
--icon-size: 1.15em;
|
||||
|
||||
.btn--circle {
|
||||
--btn-background: transparent;
|
||||
opacity: 0;
|
||||
scale: 1.5;
|
||||
transition: 150ms ease-out;
|
||||
transition-property: opacity, scale;
|
||||
|
||||
block-size: var(--column-width-collapsed);
|
||||
inline-size: var(--column-width-collapsed);
|
||||
}
|
||||
.cards.is-collapsed & {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cards.is-collapsed & {
|
||||
display: none;
|
||||
.cards:not(.is-collapsed) .cards__expander:hover & {
|
||||
opacity: 0.66;
|
||||
scale: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,6 +400,10 @@
|
||||
box-shadow: none !important;
|
||||
color: color-mix(in srgb, var(--card-color) 70%, var(--color-canvas));
|
||||
|
||||
.cards--considering & {
|
||||
background-color: var(--card-bg-color) !important;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: var(--text-small);
|
||||
overflow: hidden;
|
||||
@@ -344,10 +417,6 @@
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.cards--grid &:not(.blank-slate-for-grid) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,8 +519,8 @@
|
||||
|
||||
@media (any-hover: hover) {
|
||||
.card:hover & {
|
||||
opacity: 1;
|
||||
}
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -473,11 +542,9 @@
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
.cards--considering {
|
||||
position: relative;
|
||||
--card-color: oklch(var(--lch-blue-medium));
|
||||
|
||||
@media (min-width: 640px) {
|
||||
padding-inline: var(--cards-gap);
|
||||
}
|
||||
position: relative;
|
||||
|
||||
.card {
|
||||
--avatar-size: 2.75em;
|
||||
@@ -604,24 +671,26 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Surface a mini bubble if there are cards with bubbles inside */
|
||||
.cards--considering:has(.bubble:not([hidden])) .cards__expander-title,
|
||||
.cards--considering:has(.bubble:not([hidden])),
|
||||
.cards--doing.is-collapsed:has(.bubble:not([hidden])) {
|
||||
--bubble-color: var(--card-color, oklch(var(--lch-blue-medium)));
|
||||
--bubble-shape: 54% 46% 61% 39% / 57% 49% 51% 43%;
|
||||
.cards__transition-container {
|
||||
--bubble-color: var(--card-color, oklch(var(--lch-blue-medium)));
|
||||
--bubble-shape: 54% 46% 61% 39% / 57% 49% 51% 43%;
|
||||
|
||||
&:before {
|
||||
background: radial-gradient(
|
||||
color-mix(in srgb, var(--bubble-color) 8%, var(--color-canvas)) 50%,
|
||||
color-mix(in srgb, var(--bubble-color) 48%, var(--color-canvas)) 100%
|
||||
);
|
||||
block-size: 1em;
|
||||
border-radius: var(--bubble-shape);
|
||||
content: "";
|
||||
inline-size: 1em;
|
||||
inset: 0 0 auto auto;
|
||||
position: absolute;
|
||||
translate: 25% -25%;
|
||||
z-index: 1;
|
||||
&:before {
|
||||
background: radial-gradient(
|
||||
color-mix(in srgb, var(--bubble-color) 8%, var(--color-canvas)) 50%,
|
||||
color-mix(in srgb, var(--bubble-color) 48%, var(--color-canvas)) 100%
|
||||
);
|
||||
block-size: 1em;
|
||||
border-radius: var(--bubble-shape);
|
||||
content: "";
|
||||
inline-size: 1em;
|
||||
inset: 0 0 auto auto;
|
||||
position: absolute;
|
||||
translate: 25% -25%;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -240,8 +240,9 @@
|
||||
translate: 0 calc(-1 * var(--half-btn-height));
|
||||
}
|
||||
|
||||
&:has([open]) {
|
||||
z-index: 1;
|
||||
form {
|
||||
background-color: var(--color-canvas);
|
||||
border-radius: 99rem;
|
||||
}
|
||||
|
||||
.btn:not(.popup__btn, .btn--plain, .btn--reversed) {
|
||||
|
||||
@@ -179,17 +179,20 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
& ~ .btn {
|
||||
--btn-background: var(--card-color);
|
||||
--btn-color: var(--color-ink-inverted);
|
||||
}
|
||||
}
|
||||
|
||||
.card-field__description {
|
||||
lexxy-toolbar {
|
||||
border-block-start: 1px solid var(--color-ink-light);
|
||||
margin-block-start: var(--block-space-half) !important;
|
||||
}
|
||||
|
||||
& ~ .btn.btn--reversed {
|
||||
--btn-background: var(--card-color);
|
||||
--btn-color: var(--color-ink-inverted);
|
||||
}
|
||||
|
||||
& ~ .btn {
|
||||
--btn-border-color: var(--card-color);
|
||||
--btn-color: var(--card-color);
|
||||
}
|
||||
}
|
||||
|
||||
.card__stages {
|
||||
@@ -253,6 +256,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
.card__assignees-trigger {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
|
||||
.btn {
|
||||
box-shadow: 0 0 0 var(--focus-ring-size) var(--focus-ring-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card__meta-text {
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
@@ -498,5 +516,9 @@
|
||||
margin-inline: auto;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
code {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,11 @@
|
||||
}
|
||||
|
||||
.drag-and-drop__hover-container {
|
||||
background-color: var(--color-selected-light);
|
||||
outline: 2px dashed var(--color-selected-dark);
|
||||
--dnd-bg-color: var(--color-selected-light);
|
||||
--dnd-border-color: var(--color-selected-dark);
|
||||
|
||||
background-color: var(--dnd-bg-color);
|
||||
outline: 2px dashed var(--dnd-border-color);
|
||||
outline-offset: -2px;
|
||||
transition: background-color 200ms;
|
||||
z-index: 1;
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
"menu menu menu"
|
||||
"actions-start title actions-end";
|
||||
max-inline-size: 100dvw;
|
||||
padding: calc(var(--block-space-half) + env(safe-area-inset-top)) var(--main-padding);
|
||||
padding-block: calc(var(--block-space-half) + env(safe-area-inset-top)) var(--block-space-half);
|
||||
padding-inline: var(--main-padding);
|
||||
|
||||
/* Change the grid size depending on how many buttons are present */
|
||||
&:has(.header__actions > *:nth-child(1)) { --header-button-count: 1; }
|
||||
@@ -78,4 +79,16 @@
|
||||
--left-offset: var(--inline-space);
|
||||
}
|
||||
}
|
||||
|
||||
.header__logo {
|
||||
color: var(--color-ink);
|
||||
inline-size: auto;
|
||||
margin-block-start: 0.1em;
|
||||
|
||||
img {
|
||||
block-size: auto;
|
||||
inline-size: 1.15em;
|
||||
margin-inline-end: 0.8ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
.icon--clipboard { --svg: url("clipboard.svg "); }
|
||||
.icon--close { --svg: url("close.svg "); }
|
||||
.icon--close-circle { --svg: url("close-circle.svg "); }
|
||||
.icon--collapse { --svg: url("collapse.svg "); }
|
||||
.icon--board { --svg: url("board.svg "); }
|
||||
.icon--board-add { --svg: url("board-add.svg "); }
|
||||
.icon--column-left { --svg: url("column-left.svg "); }
|
||||
@@ -53,7 +54,9 @@
|
||||
.icon--everyone { --svg: url("everyone.svg "); }
|
||||
.icon--expand { --svg: url("expand.svg "); }
|
||||
.icon--gear { --svg: url("gear.svg "); }
|
||||
.icon--grid { --svg: url("grid.svg "); }
|
||||
.icon--filter { --svg: url("filter.svg "); }
|
||||
.icon--fizzy { --svg: url("fizzy.svg"); }
|
||||
.icon--globe { --svg: url("globe.svg "); }
|
||||
.icon--golden-ticket { --svg: url("golden-ticket.svg "); }
|
||||
.icon--home { --svg: url("home.svg "); }
|
||||
@@ -64,6 +67,7 @@
|
||||
.icon--lock { --svg: url("lock.svg "); }
|
||||
.icon--logout { --svg: url("logout.svg "); }
|
||||
.icon--marker { --svg: url("marker.svg "); }
|
||||
.icon--maximize { --svg: url("maximize.svg "); }
|
||||
.icon--menu { --svg: url("menu.svg "); }
|
||||
.icon--menu-dots-horizontal { --svg: url("menu-dots-horizontal.svg "); }
|
||||
.icon--menu-dots-vertical { --svg: url("menu-dots-vertical.svg "); }
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
@layer components {
|
||||
.boxcar-header-logo {
|
||||
color: var(--color-ink);
|
||||
inline-size: auto;
|
||||
margin-block-start: 0.1em;
|
||||
/* Trigger
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
img {
|
||||
block-size: auto;
|
||||
inline-size: 1.15em;
|
||||
margin-inline-end: 0.8ch;
|
||||
}
|
||||
}
|
||||
|
||||
.input:is(.boxcar-menu-trigger) {
|
||||
.nav__trigger {
|
||||
--input-background: var(--color-canvas);
|
||||
--input-border-color: transparent;
|
||||
--input-padding: 0.3em 2em 0.3em 0.75em;
|
||||
@@ -58,37 +49,45 @@
|
||||
}
|
||||
}
|
||||
|
||||
.boxcar-menu:is(.panel) {
|
||||
/* Dialog
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
.nav__menu.popup {
|
||||
--panel-border-color: var(--color-selected-dark);
|
||||
--panel-border-radius: 1.4em;
|
||||
--panel-padding: var(--block-space) var(--block-space) 0 var(--block-space);
|
||||
--panel-size: 45ch;
|
||||
--popup-display: grid;
|
||||
--nav-section-gap: 2px;
|
||||
|
||||
align-items: stretch;
|
||||
block-size: auto;
|
||||
box-shadow: 0 0 0 1px oklch(var(--lch-blue-medium) / 5%),
|
||||
0 0.2em 0.2em oklch(var(--lch-blue-medium) / 5%),
|
||||
0 0.4em 0.4em oklch(var(--lch-blue-medium) / 5%),
|
||||
0 0.8em 0.8em oklch(var(--lch-blue-medium) / 5%);
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
gap: var(--nav-section-gap);
|
||||
overflow: hidden;
|
||||
scrollbar-gutter: stable both-edges;
|
||||
|
||||
/* Safari doesn't position the sticky footer properly during the
|
||||
* transition, so we need to delay the footer entrance. */
|
||||
.popup__footer {
|
||||
animation: safari-sticky-footer-hack 0s;
|
||||
animation-delay: var(--dialog-duration);
|
||||
animation-fill-mode: both;
|
||||
inset-inline: var(--block-space);
|
||||
}
|
||||
}
|
||||
|
||||
.boxcar-menu__close {
|
||||
.nav__scroll-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nav-section-gap);
|
||||
margin-inline: calc(-1 * var(--block-space)); /* space for scrollbar */
|
||||
overflow: auto;
|
||||
padding-block-end: var(--nav-section-gap);
|
||||
padding-inline: var(--block-space);
|
||||
}
|
||||
|
||||
.nav__close {
|
||||
@media (any-hover: hover) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.boxcar-menu__hotkeys {
|
||||
.nav__hotkeys {
|
||||
--gap: 8px;
|
||||
|
||||
align-items: center;
|
||||
@@ -152,4 +151,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav__footer {
|
||||
background-color: var(--color-canvas);
|
||||
border-block-start: 1px solid var(--color-ink-lighter);
|
||||
font-size: var(--text-small);
|
||||
line-height: 1.6;
|
||||
margin-block-start: calc(-1 * var(--nav-section-gap));
|
||||
padding: 1.5ch;
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,22 @@
|
||||
color: var(--color-ink);
|
||||
inline-size: var(--panel-size, 60ch);
|
||||
max-inline-size: 100%;
|
||||
padding: var(--panel-padding, calc(var(--block-space) * 2));
|
||||
padding: var(--panel-padding, var(--block-space));
|
||||
|
||||
@media (min-width: 640px) {
|
||||
--panel-size: 100%;
|
||||
|
||||
padding: var(--panel-padding, var(--block-space-double));
|
||||
}
|
||||
}
|
||||
|
||||
.panel--centered {
|
||||
--panel-border-size: 0;
|
||||
--panel-size: 42ch;
|
||||
--panel-size: 100%;
|
||||
|
||||
@media (min-width: 640px) {
|
||||
--panel-size: 42ch;
|
||||
}
|
||||
|
||||
#main:has(&) {
|
||||
display: grid;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
--panel-size: auto;
|
||||
--popup-icon-size: 24px;
|
||||
--popup-item-padding-inline: 0.5rem;
|
||||
--popup-display: flex;
|
||||
|
||||
inset: 0 auto auto 50%;
|
||||
min-inline-size: min(25ch, calc(100dvw - var(--block-space-double)));
|
||||
@@ -14,39 +15,9 @@
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
z-index: var(--z-popup);
|
||||
opacity: 0;
|
||||
transform: scale(0.2) translateX(-50%);
|
||||
transform-origin: top left;
|
||||
transition: var(--dialog-duration) allow-discrete;
|
||||
transition-property: display, opacity, overlay, transform;
|
||||
|
||||
&::backdrop {
|
||||
background-color: var(--color-always-black);
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
transition: var(--dialog-duration) allow-discrete;
|
||||
transition-property: display, opacity, overlay;
|
||||
}
|
||||
|
||||
&[open] {
|
||||
display: flex;
|
||||
opacity: 1;
|
||||
transform: scale(1) translateX(-50%);
|
||||
|
||||
&::backdrop {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
@starting-style {
|
||||
&[open] {
|
||||
opacity: 0;
|
||||
transform: scale(0.2) translateX(-50%);
|
||||
}
|
||||
|
||||
&[open]::backdrop {
|
||||
opacity: 0;
|
||||
}
|
||||
display: var(--popup-display);
|
||||
}
|
||||
|
||||
&.orient-left {
|
||||
@@ -59,10 +30,6 @@
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
&:has(.popup__footer) {
|
||||
--panel-padding: var(--block-space) var(--block-space) 0 var(--block-space);
|
||||
}
|
||||
|
||||
form {
|
||||
display: contents;
|
||||
}
|
||||
@@ -86,11 +53,11 @@
|
||||
background: var(--color-canvas);
|
||||
font-size: var(--text-small);
|
||||
font-weight: bold;
|
||||
inset-block-start: 0;
|
||||
list-style: none;
|
||||
padding: var(--block-space-half) var(--inline-space-half);
|
||||
position: sticky;
|
||||
text-transform: uppercase;
|
||||
top: calc(-1 * var(--block-space));
|
||||
z-index: 1;
|
||||
|
||||
&:is(summary) {
|
||||
@@ -196,21 +163,42 @@
|
||||
}
|
||||
}
|
||||
|
||||
.popup__footer {
|
||||
background-color: var(--color-canvas);
|
||||
border-block-start: 1px solid var(--color-ink-lighter);
|
||||
font-size: var(--text-small);
|
||||
inset: auto 0 0;
|
||||
line-height: 1.6;
|
||||
margin-block-start: var(--block-space-half);
|
||||
padding: 1.5ch;
|
||||
position: sticky;
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
}
|
||||
/* Animated
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
@keyframes safari-sticky-footer-hack {
|
||||
0% { position: absolute; }
|
||||
100% { position: sticky; }
|
||||
.popup--animated {
|
||||
opacity: 0;
|
||||
transform: scale(0.2) translateX(-50%);
|
||||
transform-origin: top left;
|
||||
transition: var(--dialog-duration) allow-discrete;
|
||||
transition-property: display, opacity, overlay, transform;
|
||||
|
||||
&::backdrop {
|
||||
background-color: var(--color-always-black);
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
transition: var(--dialog-duration) allow-discrete;
|
||||
transition-property: display, opacity, overlay;
|
||||
}
|
||||
|
||||
&[open] {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateX(-50%);
|
||||
|
||||
&::backdrop {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
@starting-style {
|
||||
&[open] {
|
||||
opacity: 0;
|
||||
transform: scale(0.2) translateX(-50%);
|
||||
}
|
||||
|
||||
&[open]::backdrop {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
|
||||
.boxcar-menu,
|
||||
.boxcar-menu-trigger,
|
||||
.nav__menu,
|
||||
.nav__trigger,
|
||||
.header__actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
}
|
||||
|
||||
.reactions__trigger {
|
||||
--btn-border-color: var(--color-ink-lightest);
|
||||
background-color: var(--color-ink-lightest);
|
||||
|
||||
&:not(:hover) {
|
||||
@@ -38,10 +39,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
.reactions__trigger {
|
||||
--btn-border-color: var(--reaction-border-color);
|
||||
|
||||
img {
|
||||
block-size: 65%;
|
||||
inline-size: 65%;
|
||||
}
|
||||
}
|
||||
|
||||
.reactions__list {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--inline-space-half);
|
||||
|
||||
&:not(:has(.reaction)) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Single reaction
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
@layer components {
|
||||
.rich-text-content {
|
||||
--block-margin: 0;
|
||||
--block-margin: 0.5lh;
|
||||
|
||||
:where(h1, h2, h3, h4, h5, h6) {
|
||||
display: block;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.02ch;
|
||||
line-height: 1.1;
|
||||
margin-block: var(--block-margin);
|
||||
margin-block: 0 var(--block-margin);
|
||||
overflow-wrap: break-word;
|
||||
text-wrap: balance;
|
||||
}
|
||||
@@ -62,14 +62,17 @@
|
||||
}
|
||||
|
||||
:where(hr) {
|
||||
color: currentColor;
|
||||
block-size: 0;
|
||||
border-block-end: 1px solid currentColor;
|
||||
border: none;
|
||||
border-block-end: 2px solid currentColor;
|
||||
inline-size: 20%;
|
||||
margin: var(--block-margin) 0;
|
||||
margin: calc(var(--block-margin) * 2) 0;
|
||||
}
|
||||
|
||||
.horizontal-divider {
|
||||
margin-inline: 0;
|
||||
margin: var(--block-margin) 0;
|
||||
padding-block: var(--block-margin);
|
||||
}
|
||||
|
||||
.lexxy-content__strikethrough {
|
||||
@@ -372,6 +375,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
action-text-attachment[content-type^='application/vnd.actiontext.mention'] {
|
||||
img {
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
[data-lexical-cursor] {
|
||||
animation: blink 1s step-end infinite;
|
||||
block-size: 1lh;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@layer components {
|
||||
[data-controller^="tooltip"] {
|
||||
[data-controller~="tooltip"] {
|
||||
--tooltip-delay: 750ms;
|
||||
--tooltip-duration: 150ms;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
.txt-ink { color: var(--color-ink); }
|
||||
.txt-reversed { color: var(--color-ink-inverted); }
|
||||
.txt-negative { color: var(--color-negative); }
|
||||
.txt-subtle { color: var(--color-ink-medium); }
|
||||
.txt-subtle { color: var(--color-ink-dark); }
|
||||
.txt-alert { color: var(--color-marker); }
|
||||
.txt-undecorated { text-decoration: none; }
|
||||
.txt-underline { text-decoration: underline; }
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
class Account::EntropiesController < ApplicationController
|
||||
before_action :ensure_admin
|
||||
|
||||
def update
|
||||
Account.sole.entropy.update!(entropy_params)
|
||||
redirect_to account_settings_path, notice: "Account updated"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
class Account::JoinCodesController < ApplicationController
|
||||
before_action :set_join_code
|
||||
before_action :ensure_admin, only: %i[ update destroy ]
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
@@ -4,6 +4,7 @@ class ApplicationController < ActionController::Base
|
||||
include Authorization
|
||||
include CurrentRequest, CurrentTimezone, SetPlatform
|
||||
include TurboFlash, ViewTransitions
|
||||
include Saas
|
||||
|
||||
stale_when_importmap_changes
|
||||
allow_browser versions: :modern
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
class Boards::EntropiesController < ApplicationController
|
||||
include BoardScoped
|
||||
|
||||
before_action :ensure_permission_to_admin_board
|
||||
|
||||
def update
|
||||
@board.entropy.update!(entropy_params)
|
||||
end
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
class Boards::PublicationsController < ApplicationController
|
||||
include BoardScoped
|
||||
|
||||
before_action :ensure_permission_to_admin_board
|
||||
|
||||
def create
|
||||
@board.publish
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ class BoardsController < ApplicationController
|
||||
include FilterScoped
|
||||
|
||||
before_action :set_board, except: %i[ new create ]
|
||||
before_action :ensure_permission_to_change_accesses, only: %i[ update ]
|
||||
before_action :ensure_permission_to_admin_board, only: %i[ update ]
|
||||
|
||||
def show
|
||||
if @filter.used?(ignore_boards: true)
|
||||
@@ -48,20 +48,12 @@ class BoardsController < ApplicationController
|
||||
@board = Current.user.boards.find params[:id]
|
||||
end
|
||||
|
||||
def ensure_permission_to_change_accesses
|
||||
if trying_to_change_accesses? && !Current.user.can_administer_board?(@board)
|
||||
def ensure_permission_to_admin_board
|
||||
unless Current.user.can_administer_board?(@board)
|
||||
head :forbidden
|
||||
end
|
||||
end
|
||||
|
||||
def trying_to_change_accesses?
|
||||
all_access_changed? || grantees_changed?
|
||||
end
|
||||
|
||||
def all_access_changed?
|
||||
params[:board]&.key?(:all_access)
|
||||
end
|
||||
|
||||
def grantees_changed?
|
||||
params.key?(:user_ids)
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@ class Cards::AssignmentsController < ApplicationController
|
||||
|
||||
def new
|
||||
@users = @board.users.active.alphabetically
|
||||
fresh_when @users
|
||||
fresh_when etag: [ @users, @card.assignees ]
|
||||
end
|
||||
|
||||
def create
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class Cards::ColumnsController < ApplicationController
|
||||
def edit
|
||||
@card = Current.user.accessible_cards.find(params[:card_id])
|
||||
@columns = @card.board.columns
|
||||
@columns = @card.board.columns.sorted
|
||||
|
||||
fresh_when etag: [ @card, @columns ]
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@ class Cards::TaggingsController < ApplicationController
|
||||
|
||||
def new
|
||||
@tags = Tag.all.alphabetically
|
||||
fresh_when @tags
|
||||
fresh_when etag: [ @tags, @card.tags ]
|
||||
end
|
||||
|
||||
def create
|
||||
|
||||
@@ -9,4 +9,10 @@ module BoardScoped
|
||||
def set_board
|
||||
@board = Current.user.boards.find(params[:board_id])
|
||||
end
|
||||
|
||||
def ensure_permission_to_admin_board
|
||||
unless Current.user.can_administer_board?(@board)
|
||||
head :forbidden
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
module Saas
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
helper_method :signups_allowed?
|
||||
end
|
||||
|
||||
def signups_allowed?
|
||||
defined?(Signup) && defined?(saas)
|
||||
end
|
||||
end
|
||||
@@ -4,6 +4,8 @@ class JoinCodesController < ApplicationController
|
||||
before_action :set_join_code
|
||||
before_action :ensure_join_code_is_valid
|
||||
|
||||
layout "public"
|
||||
|
||||
def new
|
||||
@account_name = ApplicationRecord.with_tenant(tenant) { Account.sole.name }
|
||||
end
|
||||
@@ -14,7 +16,8 @@ class JoinCodesController < ApplicationController
|
||||
identity.memberships.find_or_create_by!(tenant: tenant) do |membership|
|
||||
membership.join_code = code
|
||||
end
|
||||
identity.send_magic_link
|
||||
magic_link = identity.send_magic_link
|
||||
flash[:magic_link_code] = magic_link&.code if Rails.env.development?
|
||||
end
|
||||
|
||||
session[:return_to_after_authenticating] = landing_url(script_name: "/#{tenant}")
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
class Memberships::EmailAddressesController < ApplicationController
|
||||
require_untenanted_access
|
||||
|
||||
layout "public"
|
||||
|
||||
before_action :set_membership
|
||||
rate_limit to: 5, within: 1.hour, only: :create
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
class My::MenusController < ApplicationController
|
||||
include FilterScoped
|
||||
|
||||
def show
|
||||
fresh_when etag: [ @user_filtering, Current.session ]
|
||||
@filters = Current.user.filters.all
|
||||
@boards = Current.user.boards.ordered_by_recently_accessed
|
||||
@tags = Tag.all.alphabetically
|
||||
@users = User.active.alphabetically
|
||||
|
||||
# fresh_when etag: [ @user_filtering, Current.session ]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
class SessionsController < ApplicationController
|
||||
# FIXME: Remove this before launch!
|
||||
SIGNUP_USERNAME = Rails.env.local? ? "testname" : Rails.application.credentials.account_signup_http_basic_auth.name
|
||||
SIGNUP_PASSWORD = Rails.env.local? ? "testpassword" : Rails.application.credentials.account_signup_http_basic_auth.password
|
||||
http_basic_authenticate_with name: SIGNUP_USERNAME, password: SIGNUP_PASSWORD, realm: "Fizzy Signup", only: :create, unless: -> { Identity.exists?(email_address: email_address) }
|
||||
|
||||
require_untenanted_access
|
||||
require_unauthenticated_access except: :destroy
|
||||
rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_path, alert: "Try again later." }
|
||||
@@ -9,8 +14,15 @@ class SessionsController < ApplicationController
|
||||
end
|
||||
|
||||
def create
|
||||
Identity.find_by_email_address(email_address)&.send_magic_link
|
||||
redirect_to session_magic_link_path
|
||||
if identity = Identity.find_by_email_address(email_address)
|
||||
magic_link = identity.send_magic_link
|
||||
flash[:magic_link_code] = magic_link&.code if Rails.env.development?
|
||||
redirect_to session_magic_link_path
|
||||
elsif signups_allowed?
|
||||
Signup.new(email_address: email_address).create_identity
|
||||
session[:return_to_after_authenticating] = saas.new_signup_membership_path
|
||||
redirect_to session_magic_link_path
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
||||
@@ -3,6 +3,8 @@ class Users::JoinsController < ApplicationController
|
||||
|
||||
before_action :set_join_code, :ensure_join_code_is_valid
|
||||
|
||||
layout "public"
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
module ApplicationHelper
|
||||
def page_title_tag
|
||||
tag.title @page_title || "Fizzy"
|
||||
account_name = if ApplicationRecord.current_tenant && Current.session&.identity&.memberships&.many?
|
||||
Account.sole&.name
|
||||
end
|
||||
tag.title [ @page_title, account_name, "Fizzy" ].compact.join(" | ")
|
||||
end
|
||||
|
||||
def icon_tag(name, **options)
|
||||
|
||||
@@ -19,6 +19,16 @@ module AvatarsHelper
|
||||
end
|
||||
end
|
||||
|
||||
def mail_avatar_tag(user, size: 48, **options)
|
||||
if user.avatar.attached?
|
||||
image_tag user_avatar_url(user), alt: user.name, class: "avatar", size: size, **options
|
||||
else
|
||||
tag.span class: "avatar", style: "background-color: #{avatar_background_color(user)};" do
|
||||
user.initials
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def avatar_preview_tag(user, hidden_for_screen_reader: false, **options)
|
||||
tag.span class: class_names("avatar", options.delete(:class)),
|
||||
aria: { hidden: hidden_for_screen_reader, label: user.name },
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
module BoardsHelper
|
||||
def link_back_to_board(board)
|
||||
link_to board, class: "btn borderless txt-medium",
|
||||
data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click click->turbo-navigation#backIfSamePath" } do
|
||||
tag.span ("←" + tag.strong(board.name, class: "overflow-ellipsis")).html_safe
|
||||
data: { controller: "hotkey", action: "keydown.left@document->hotkey#click click->turbo-navigation#backIfSamePath" } do
|
||||
tag.span(tag.strong("Back to #{board.name}", class: "overflow-ellipsis") + tag.kbd("←", class: "txt-x-small margin-inline-start hide-on-touch"), class: "flex align-center").html_safe
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module ClipboardHelper
|
||||
def button_to_copy_to_clipboard(url, &)
|
||||
tag.button class: "btn", data: {
|
||||
controller: "copy-to-clipboard", action: "copy-to-clipboard#copy",
|
||||
controller: "copy-to-clipboard tooltip", action: "copy-to-clipboard#copy",
|
||||
copy_to_clipboard_success_class: "btn--success", copy_to_clipboard_content_value: url
|
||||
}, &
|
||||
end
|
||||
|
||||
@@ -1,138 +1,22 @@
|
||||
module EventsHelper
|
||||
def event_columns(event_type, day_timeline)
|
||||
case event_type
|
||||
when "added"
|
||||
events = day_timeline.events.where(action: [ "card_published", "card_reopened" ])
|
||||
full_events_count = events.count
|
||||
{
|
||||
title: event_column_title("Added", full_events_count, day_timeline.day),
|
||||
index: 1,
|
||||
events: events.limit(100).load,
|
||||
full_events_count: full_events_count
|
||||
}
|
||||
when "closed"
|
||||
events = day_timeline.events.where(action: "card_closed")
|
||||
full_events_count = events.count
|
||||
{
|
||||
title: event_column_title("Closed", full_events_count, day_timeline.day),
|
||||
index: 3,
|
||||
events: events.limit(100).load,
|
||||
full_events_count: full_events_count
|
||||
}
|
||||
def event_action_icon(event)
|
||||
case event.action
|
||||
when "card_assigned"
|
||||
"assigned"
|
||||
when "card_unassigned"
|
||||
"minus"
|
||||
when "comment_created"
|
||||
"comment"
|
||||
when "card_title_changed"
|
||||
"rename"
|
||||
when "card_board_changed"
|
||||
"move"
|
||||
else
|
||||
events = day_timeline.events.where.not(action: [ "card_published", "card_closed", "card_reopened" ])
|
||||
full_events_count = events.count
|
||||
{
|
||||
title: event_column_title("Updated", full_events_count, day_timeline.day),
|
||||
index: 2,
|
||||
events: events.limit(100).load,
|
||||
full_events_count: full_events_count
|
||||
}
|
||||
"person"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def event_column_title(base_title, count, day)
|
||||
date_tag = local_datetime_tag day, style: :agoorweekday
|
||||
parts = [ base_title, date_tag ]
|
||||
parts << tag.span("(#{count})", class: "font-weight-normal") if count > 0
|
||||
safe_join(parts, " ")
|
||||
end
|
||||
|
||||
def event_column(event)
|
||||
case event.action
|
||||
when "card_closed"
|
||||
3
|
||||
when "card_published", "card_reopened"
|
||||
1
|
||||
else
|
||||
2
|
||||
end
|
||||
end
|
||||
|
||||
def event_cluster_tag(hour, col, &)
|
||||
row = 25 - hour
|
||||
tag.div class: "events__time-block", style: "grid-area: #{row}/#{col}", &
|
||||
end
|
||||
|
||||
def event_action_sentence(event)
|
||||
if event.action.comment_created?
|
||||
comment_event_action_sentence(event)
|
||||
else
|
||||
card_event_action_sentence(event)
|
||||
end
|
||||
end
|
||||
|
||||
def comment_event_action_sentence(event)
|
||||
"#{event_creator_name(event)} commented on #{event_card_title(event.eventable.card)}".html_safe
|
||||
end
|
||||
|
||||
def event_creator_name(event)
|
||||
tag.span data: { creator_id: event.creator.id } do
|
||||
safe_join([
|
||||
tag.span("You", data: { only_visible_to_you: true }),
|
||||
tag.span(h(event.creator.name), data: { only_visible_to_others: true })
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
def card_event_action_sentence(event)
|
||||
card = event.eventable
|
||||
|
||||
case event.action
|
||||
when "card_assigned"
|
||||
if event.assignees.include?(Current.user)
|
||||
"#{event_creator_name(event)} will handle #{event_card_title(card)}"
|
||||
else
|
||||
"#{event_creator_name(event)} assigned #{h event.assignees.pluck(:name).to_sentence} to #{event_card_title(card)}"
|
||||
end
|
||||
when "card_unassigned"
|
||||
"#{event_creator_name(event)} unassigned #{h(event.assignees.include?(Current.user) ? "yourself" : event.assignees.pluck(:name).to_sentence)} from #{event_card_title(card)}"
|
||||
when "card_published"
|
||||
"#{event_creator_name(event)} added #{event_card_title(card)}"
|
||||
when "card_closed"
|
||||
"#{event_creator_name(event)} moved #{event_card_title(card)} to “Done”"
|
||||
when "card_reopened"
|
||||
"#{event_creator_name(event)} reopened #{event_card_title(card)}"
|
||||
when "card_postponed"
|
||||
"#{event_creator_name(event)} moved #{event_card_title(card)} to “Not Now”"
|
||||
when "card_auto_postponed"
|
||||
"#{event_card_title(card)} was closed as “Not Now” due to inactivity"
|
||||
when "card_resumed"
|
||||
"#{event_creator_name(event)} resumed #{event_card_title(card)}"
|
||||
when "card_title_changed"
|
||||
"#{event_creator_name(event)} renamed #{event_card_title(card)} (was: “#{h event.particulars.dig('particulars', 'old_title')}”)"
|
||||
when "card_board_changed", "card_collection_changed"
|
||||
"#{event_creator_name(event)} moved #{event_card_title(card)} to “#{h (event.particulars.dig('particulars', 'new_board') || event.particulars.dig('particulars', 'new_collection'))}”"
|
||||
when "card_triaged"
|
||||
"#{event_creator_name(event)} moved #{event_card_title(card)} to “#{h event.particulars.dig('particulars', 'column')}”"
|
||||
when "card_sent_back_to_triage"
|
||||
"#{event_creator_name(event)} moved #{event_card_title(card)} back to “Maybe?”"
|
||||
end&.html_safe
|
||||
end
|
||||
|
||||
def event_card_title(card)
|
||||
tag.span card.title, class: "txt-underline"
|
||||
end
|
||||
|
||||
def event_due_date(event)
|
||||
event.particulars.dig("particulars", "due_date").to_date.strftime("%B %-d")
|
||||
end
|
||||
|
||||
def event_action_icon(event)
|
||||
case event.action
|
||||
when "card_assigned"
|
||||
"assigned"
|
||||
when "card_unassigned"
|
||||
"minus"
|
||||
when "comment_created"
|
||||
"comment"
|
||||
when "card_title_changed"
|
||||
"rename"
|
||||
when "card_board_changed"
|
||||
"move"
|
||||
else
|
||||
"person"
|
||||
end
|
||||
end
|
||||
def events_at_hour_container(column, hour, &block)
|
||||
tag.div class: "events__time-block", style: "grid-area: #{25 - hour}/#{column.index}", &block
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,4 +16,39 @@ module My::MenuHelper
|
||||
navigable_list_target: "input",
|
||||
action: "input->filter#filter" }
|
||||
end
|
||||
|
||||
def my_menu_board_item(board)
|
||||
my_menu_item("board", board) do
|
||||
link_to(tag.span(board.name, class: "overflow-ellipsis"), board, class: "popup__btn btn")
|
||||
end
|
||||
end
|
||||
|
||||
def my_menu_tag_item(the_tag)
|
||||
my_menu_item("tag", tag) do
|
||||
link_to(tag.span("#{the_tag.title} (#{the_tag.cards_count})", class: "overflow-ellipsis"), cards_path(tag_ids: [ the_tag ]), class: "popup__btn btn")
|
||||
end
|
||||
end
|
||||
|
||||
def my_menu_user_item(user)
|
||||
my_menu_item("person", user) do
|
||||
link_to(tag.span(user.name, class: "overflow-ellipsis"), user, class: "popup__btn btn")
|
||||
end
|
||||
end
|
||||
|
||||
def my_menu_filter_item(filter)
|
||||
my_menu_item("bookmark", filter) do
|
||||
link_to(cards_path(filter_id: filter.id), class: "popup__btn btn") do
|
||||
tag.div(class: "txt-tight-lines min-width txt-small overflow-ellipsis") do
|
||||
tag.div(tag.strong(filter.boards_label)) +
|
||||
tag.div(filter.summary, class: "txt-capitalize")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def my_menu_item(item, record)
|
||||
tag.li(class: "popup__item", data: { filter_target: "item", navigable_list_target: "item", id: "filter-#{item}-#{record.id}" }) do
|
||||
icon_tag(item, class: "popup__icon") + yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -28,7 +28,7 @@ module NotificationsHelper
|
||||
} do
|
||||
link_to(notification,
|
||||
class: [ "card card--notification", { "card--closed": notification.card.closed? }, { "unread": !notification.read? } ],
|
||||
data: { turbo_frame: "_top", badge_target: "unread", action: "badge#update" },
|
||||
data: { turbo_frame: "_top", badge_target: "unread", action: "badge#update dialog#close" },
|
||||
style: { "--card-color:": notification.card.color },
|
||||
&)
|
||||
end
|
||||
|
||||
@@ -6,6 +6,10 @@ export default class extends Controller {
|
||||
|
||||
async click() {
|
||||
await nextFrame()
|
||||
this.clickableTarget.click()
|
||||
this.#clickable.click()
|
||||
}
|
||||
|
||||
get #clickable() {
|
||||
return this.hasClickableTarget ? this.clickableTarget : this.element
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import { Controller } from "@hotwired/stimulus"
|
||||
import { nextFrame, debounce } from "helpers/timing_helpers";
|
||||
|
||||
export default class extends Controller {
|
||||
static classes = [ "collapsed", "noTransitions" ]
|
||||
static targets = [ "column", "button" ]
|
||||
static classes = [ "collapsed", "noTransitions", "titleNotVisible" ]
|
||||
static targets = [ "column", "button", "title" ]
|
||||
static values = {
|
||||
board: String
|
||||
}
|
||||
@@ -14,6 +14,14 @@ export default class extends Controller {
|
||||
|
||||
async connect() {
|
||||
await this.#restoreColumnsDisablingTransitions()
|
||||
this.#setupIntersectionObserver()
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this._intersectionObserver) {
|
||||
this._intersectionObserver.disconnect()
|
||||
this._intersectionObserver = null
|
||||
}
|
||||
}
|
||||
|
||||
toggle({ target }) {
|
||||
@@ -106,4 +114,23 @@ export default class extends Controller {
|
||||
#localStorageKeyFor(column) {
|
||||
return `expand-${this.boardValue}-${column.getAttribute("id")}`
|
||||
}
|
||||
|
||||
#setupIntersectionObserver() {
|
||||
if (typeof IntersectionObserver === "undefined") return
|
||||
if (this._intersectionObserver) this._intersectionObserver.disconnect()
|
||||
|
||||
this._intersectionObserver = new IntersectionObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
const title = entry.target
|
||||
const column = title.closest(".cards")
|
||||
|
||||
if (!column) return
|
||||
|
||||
const offscreen = entry.intersectionRatio === 0
|
||||
column.classList.toggle(this.titleNotVisibleClass, offscreen)
|
||||
})
|
||||
}, { threshold: [0] })
|
||||
|
||||
this.titleTargets.forEach(title => this._intersectionObserver.observe(title))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ export default class extends Controller {
|
||||
|
||||
limitHeightToViewport(this.dialogTarget, true)
|
||||
|
||||
this.#loadLazyFrames()
|
||||
this.dialogTarget.setAttribute("aria-hidden", "false")
|
||||
this.dispatch("show")
|
||||
}
|
||||
@@ -54,4 +55,8 @@ export default class extends Controller {
|
||||
event.stopPropagation()
|
||||
}
|
||||
}
|
||||
|
||||
#loadLazyFrames() {
|
||||
Array.from(this.dialogTarget.querySelectorAll("turbo-frame")).forEach(frame => { frame.loading = "eager" })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.element.addEventListener("dialog:show", this.handleDialogShow.bind(this))
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.element.removeEventListener("dialog:show", this.handleDialogShow.bind(this))
|
||||
}
|
||||
|
||||
handleDialogShow(event) {
|
||||
this.#dialogControllers.forEach(dialogController => {
|
||||
if (dialogController !== event.target) {
|
||||
const dialog = dialogController.querySelector("dialog")
|
||||
dialog.removeAttribute("open")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
get #dialogControllers() {
|
||||
return this.element.querySelectorAll('[data-controller~="dialog"]')
|
||||
}
|
||||
}
|
||||
@@ -80,6 +80,10 @@ export default class extends Controller {
|
||||
selectedItem.setAttribute(this.selectionAttributeValue, "true")
|
||||
this.currentItem = selectedItem
|
||||
await nextFrame()
|
||||
try {
|
||||
this.currentItem.scrollIntoView({ block: "nearest", inline: "nearest" })
|
||||
} catch (e) {}
|
||||
|
||||
if (this.focusOnSelectionValue) { this.currentItem.focus() }
|
||||
if (this.hasInputTarget && id) {
|
||||
this.inputTarget.setAttribute("aria-activedescendant", id)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class ApplicationMailer < ActionMailer::Base
|
||||
default from: "The Fizzy team <support@37signals.com>"
|
||||
default from: "Fizzy <support@fizzy.do>"
|
||||
|
||||
layout "mailer"
|
||||
append_view_path Rails.root.join("app/views/mailers")
|
||||
|
||||
@@ -3,7 +3,7 @@ class MagicLinkMailer < ApplicationMailer
|
||||
@magic_link = magic_link
|
||||
@identity = @magic_link.identity
|
||||
|
||||
mail to: @identity.email_address, subject: "Sign in to Fizzy"
|
||||
mail to: @identity.email_address, subject: "Your Fizzy verification code"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
class UserMailer < ApplicationMailer
|
||||
def email_change_confirmation(user:, email_address:, token:)
|
||||
@user = user
|
||||
@token = token
|
||||
|
||||
mail to: email_address, subject: "Confirm your new email address"
|
||||
end
|
||||
end
|
||||
@@ -31,49 +31,49 @@ class Account::Seeder
|
||||
playground = Board.create! name: "Playground", creator: creator, all_access: true
|
||||
|
||||
# Cards
|
||||
playground.cards.create! creator: creator, title: "Watch this: Fizzy orientation video", status: "published", description: <<~HTML
|
||||
playground.cards.create! creator: creator, title: "Finally, watch this Fizzy orientation video", status: "published", description: <<~HTML
|
||||
<p>There’s a whole lot more you can do in Fizzy. In the video below 37signals founder and CEO, Jason Fried, will walk you through the basics in just 8 minutes.</p>
|
||||
<p><br></p>
|
||||
<action-text-attachment url="https://videos.37signals.com/fizzy/assets/videos/fizzyorientation-4k.mp4" caption="Fizzy orientation" content-type="video/mp4" filename="fizzyorientation-4k.mp4"></action-text-attachment>
|
||||
HTML
|
||||
|
||||
playground.cards.create! creator: creator, title: "Grab the invite link to invite someone else", status: "published", description: <<~HTML
|
||||
playground.cards.create! creator: creator, title: "Now, grab the invite link to invite someone else", status: "published", description: <<~HTML
|
||||
<p>Open Fizzy menu, select “<b><strong>+ Add people</b></strong>”, then copy the invite link. You can give this link to someone else so they can make an login for themselves in your account.</p>
|
||||
<p><br></p>
|
||||
<action-text-attachment url="https://videos.37signals.com/fizzy/assets/images/invite-link.gif" alt="Demo of copying invite link" caption="Get a link to invite co-workers" content-type="image/*" filename="invite-link.gif" presentation="gallery"></action-text-attachment>
|
||||
HTML
|
||||
|
||||
playground.cards.create! creator: creator, title: "Head back home to check out activity", status: "published", description: <<~HTML
|
||||
playground.cards.create! creator: creator, title: "Then, head back home to check out activity", status: "published", description: <<~HTML
|
||||
<p>Hit “1” or pull down the Fizzy menu and select “Home”.</p>
|
||||
<p><br></p>
|
||||
<action-text-attachment url="https://videos.37signals.com/fizzy/assets/images/back-to-home.gif" alt="Demo of visiting Home" caption="Go back to Home for Latest Activity" content-type="image/*" filename="back-to-home.gif" presentation="gallery"></action-text-attachment>
|
||||
HTML
|
||||
|
||||
playground.cards.create! creator: creator, title: "Check out all cards assigned to you", status: "published", description: <<~HTML
|
||||
<p>Pull down the Fizzy menu at the top of the screen, and select “<b><strong>Assigned to me</b></strong>” or just hit “3” on your keyboard any time.</p>
|
||||
playground.cards.create! creator: creator, title: "Now,check out all cards assigned to you", status: "published", description: <<~HTML
|
||||
<p>Pull down the Fizzy menu at the top of the screen, and select “<b><strong>Assigned to me</b></strong>” or just hit “2” on your keyboard any time.</p>
|
||||
<p><br></p>
|
||||
<action-text-attachment url="https://videos.37signals.com/fizzy/assets/images/all-assigned.gif" alt="Demo of navigating to 'Assigned to Me'" caption="See all cards assigned to me" content-type="image/*" filename="all-assigned.gif" presentation="gallery"></action-text-attachment>
|
||||
HTML
|
||||
|
||||
playground.cards.create! creator: creator, title: "Open the Fizzy menu", status: "published", description: <<~HTML
|
||||
playground.cards.create! creator: creator, title: "Then, open the Fizzy menu", status: "published", description: <<~HTML
|
||||
<p>The Fizzy menu is how you get around the app. Click “<b><strong>Fizzy</b></strong>” at the top of the screen or hit the “J” key on your keyboard to pop it open.</p>
|
||||
<p><br></p>
|
||||
<action-text-attachment url="https://videos.37signals.com/fizzy/assets/images/open-menu.gif" alt="Demo of opening the main menu" caption="Open the Fizzy menu" content-type="image/*" filename="open-menu.gif" presentation="gallery"></action-text-attachment>
|
||||
HTML
|
||||
|
||||
playground.cards.create! creator: creator, title: "Assign this card to yourself", status: "published", description: <<~HTML
|
||||
playground.cards.create! creator: creator, title: "Next, assign this card to yourself", status: "published", description: <<~HTML
|
||||
<p>Click the little head with the + next to it, pick yourself.</p>
|
||||
<p><br></p>
|
||||
<action-text-attachment url="https://videos.37signals.com/fizzy/assets/images/assign-to-self.gif" alt="Demo of assigning a card" caption="Assign this to yourself" content-type="image/*" filename="assign-to-self.gif" presentation="gallery"></action-text-attachment>
|
||||
HTML
|
||||
|
||||
playground.cards.create! creator: creator, title: "Tag this card “Design” then move it to YES", status: "published", description: <<~HTML
|
||||
playground.cards.create! creator: creator, title: "Now, tag this card “Design” then move it to YES", status: "published", description: <<~HTML
|
||||
<p>Click the little Tag icon, type “design”, then “<b><strong>Create tag</b></strong>”. Then, move the card to the new “YES” column you created in the previous step.</p>
|
||||
<p><br></p>
|
||||
<action-text-attachment url="https://videos.37signals.com/fizzy/assets/images/tag-design.gif" alt="Demo of tagging a card" caption="Tag this #design" content-type="image/*" filename="tag-design.gif" presentation="gallery"></action-text-attachment>
|
||||
HTML
|
||||
|
||||
playground.cards.create! creator: creator, title: "Make two more columns", status: "published", description: <<~HTML
|
||||
playground.cards.create! creator: creator, title: "Next, make two more columns", status: "published", description: <<~HTML
|
||||
<ol>
|
||||
<li>Make one called "Yes"</li>
|
||||
<li>Make another called "Working on"</li>
|
||||
@@ -86,13 +86,13 @@ class Account::Seeder
|
||||
<action-text-attachment url="https://videos.37signals.com/fizzy/assets/images/make-columns.gif" alt="Demo of adding columns" caption="Make two more columns" content-type="image/*" filename="make-columns.gif" presentation="gallery"></action-text-attachment>
|
||||
HTML
|
||||
|
||||
playground.cards.create! creator: creator, title: "Move this card to NOT NOW", status: "published", description: <<~HTML
|
||||
playground.cards.create! creator: creator, title: "Second, move this card to NOT NOW", status: "published", description: <<~HTML
|
||||
<p>You can either select “NOT NOW” over in the sidebar, or you can go back out to the board view and drag this card into the “NOT NOW” column on the left side.</p>
|
||||
<p><br></p>
|
||||
<action-text-attachment url="https://videos.37signals.com/fizzy/assets/images/not-now.gif" alt="Demo of moving a card to Not Now" caption="Move to Not Now" content-type="image/*" filename="not-now.gif" presentation="gallery"></action-text-attachment>
|
||||
HTML
|
||||
|
||||
playground.cards.create! creator: creator, title: "Rename this card", status: "published", description: <<~HTML
|
||||
playground.cards.create! creator: creator, title: "First, rename this card", status: "published", description: <<~HTML
|
||||
<ol>
|
||||
<li>Click the title and you can rename the card, change the description, or add more information to the card.</li>
|
||||
<li>Then, hit "Mark as Done" at the bottom of the card.</li>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Board < ApplicationRecord
|
||||
include AutoPostponing, Accessible, Broadcastable, Cards, Entropic, Filterable, Publishable, Triageable
|
||||
include Accessible, AutoPostponing, Broadcastable, Cards, Entropic, Filterable, Publishable, Triageable
|
||||
|
||||
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ module Board::Accessible
|
||||
|
||||
mentions_for_user(user).destroy_all
|
||||
notifications_for_user(user).destroy_all
|
||||
watches_for(user).destroy_all
|
||||
end
|
||||
|
||||
def watchers
|
||||
@@ -89,4 +90,8 @@ module Board::Accessible
|
||||
(notifications.source_type = 'Event' AND events.eventable_type = 'Comment' AND event_comment_cards.board_id = ?)",
|
||||
id, id)
|
||||
end
|
||||
|
||||
def watches_for(user)
|
||||
Watch.where(card: cards, user: user)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class Card < ApplicationRecord
|
||||
include Assignable, Attachments, Broadcastable, Closeable, Colored, Entropic,
|
||||
Eventable, Golden, Mentions, Multistep, Pinnable, Postponable, Promptable,
|
||||
include Assignable, Attachments, Closeable, Colored, Entropic, Eventable,
|
||||
Golden, Mentions, Multistep, Pinnable, Postponable, Promptable,
|
||||
Readable, Searchable, Stallable, Statuses, Taggable, Triageable, Watchable
|
||||
|
||||
belongs_to :board, touch: true
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
module Card::Broadcastable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
broadcasts_refreshes
|
||||
end
|
||||
end
|
||||
@@ -7,5 +7,9 @@ module Card::Mentions
|
||||
def mentionable?
|
||||
published?
|
||||
end
|
||||
|
||||
def should_check_mentions?
|
||||
saved_change_to_status? && published?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,20 +19,6 @@ module Mentions
|
||||
|
||||
private
|
||||
def scan_mentionees
|
||||
mentionees_from_plain_text | mentionees_from_rich_text
|
||||
end
|
||||
|
||||
def mentionees_from_plain_text
|
||||
scan_mentioned_handles.filter_map do |mention|
|
||||
mentionable_users.find { |user| user.mentionable_handles.include?(mention) }
|
||||
end
|
||||
end
|
||||
|
||||
def scan_mentioned_handles
|
||||
mentionable_content.scan(/(?<!\w)@(\w+)/).flatten.uniq(&:downcase)
|
||||
end
|
||||
|
||||
def mentionees_from_rich_text
|
||||
mentionees_from_attachments & mentionable_users
|
||||
end
|
||||
|
||||
@@ -49,7 +35,7 @@ module Mentions
|
||||
end
|
||||
|
||||
def should_create_mentions?
|
||||
mentionable? && mentionable_content_changed?
|
||||
mentionable? && (mentionable_content_changed? || should_check_mentions?)
|
||||
end
|
||||
|
||||
def mentionable_content_changed?
|
||||
@@ -64,4 +50,8 @@ module Mentions
|
||||
def mentionable?
|
||||
true
|
||||
end
|
||||
|
||||
def should_check_mentions?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,6 +22,10 @@ class Event < ApplicationRecord
|
||||
eventable
|
||||
end
|
||||
|
||||
def description_for(user)
|
||||
Event::Description.new(self, user)
|
||||
end
|
||||
|
||||
private
|
||||
def dispatch_webhooks
|
||||
Event::WebhookDispatchJob.perform_later(self)
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
class Event::Description
|
||||
include ActionView::Helpers::TagHelper
|
||||
include ERB::Util
|
||||
|
||||
attr_reader :event, :user
|
||||
|
||||
def initialize(event, user)
|
||||
@event = event
|
||||
@user = user
|
||||
end
|
||||
|
||||
def to_html
|
||||
to_sentence(creator_tag, card_title_tag).html_safe
|
||||
end
|
||||
|
||||
def to_plain_text
|
||||
to_sentence(creator_name, card.title)
|
||||
end
|
||||
|
||||
private
|
||||
def to_sentence(creator, card_title)
|
||||
if event.action.comment_created?
|
||||
comment_sentence(creator, card_title)
|
||||
else
|
||||
action_sentence(creator, card_title)
|
||||
end
|
||||
end
|
||||
|
||||
def creator_tag
|
||||
tag.span data: { creator_id: event.creator.id } do
|
||||
tag.span("You", data: { only_visible_to_you: true }) +
|
||||
tag.span(event.creator.name, data: { only_visible_to_others: true })
|
||||
end
|
||||
end
|
||||
|
||||
def card_title_tag
|
||||
tag.span card.title, class: "txt-underline"
|
||||
end
|
||||
|
||||
def creator_name
|
||||
h(event.creator.name)
|
||||
end
|
||||
|
||||
def card
|
||||
@card ||= event.action.comment_created? ? event.eventable.card : event.eventable
|
||||
end
|
||||
|
||||
def comment_sentence(creator, card_title)
|
||||
"#{creator} commented on #{card_title}"
|
||||
end
|
||||
|
||||
def action_sentence(creator, card_title)
|
||||
case event.action
|
||||
when "card_assigned"
|
||||
assigned_sentence(creator, card_title)
|
||||
when "card_unassigned"
|
||||
unassigned_sentence(creator, card_title)
|
||||
when "card_published"
|
||||
"#{creator} added #{card_title}"
|
||||
when "card_closed"
|
||||
%(#{creator} moved #{card_title} to "Done")
|
||||
when "card_reopened"
|
||||
"#{creator} reopened #{card_title}"
|
||||
when "card_postponed"
|
||||
%(#{creator} moved #{card_title} to "Not Now")
|
||||
when "card_auto_postponed"
|
||||
%(#{card_title} was closed as "Not Now" due to inactivity)
|
||||
when "card_resumed"
|
||||
"#{creator} resumed #{card_title}"
|
||||
when "card_title_changed"
|
||||
renamed_sentence(creator, card_title)
|
||||
when "card_board_changed", "card_collection_changed"
|
||||
moved_sentence(creator, card_title)
|
||||
when "card_triaged"
|
||||
triaged_sentence(creator, card_title)
|
||||
when "card_sent_back_to_triage"
|
||||
%(#{creator} moved #{card_title} back to "Maybe?")
|
||||
end
|
||||
end
|
||||
|
||||
def assigned_sentence(creator, card_title)
|
||||
if event.assignees.include?(user)
|
||||
"#{creator} will handle #{card_title}"
|
||||
else
|
||||
"#{creator} assigned #{h event.assignees.pluck(:name).to_sentence} to #{card_title}"
|
||||
end
|
||||
end
|
||||
|
||||
def unassigned_sentence(creator, card_title)
|
||||
assignees_text = event.assignees.include?(user) ? "yourself" : event.assignees.pluck(:name).to_sentence
|
||||
"#{creator} unassigned #{h(assignees_text)} from #{card_title}"
|
||||
end
|
||||
|
||||
def renamed_sentence(creator, card_title)
|
||||
old_title = event.particulars.dig("particulars", "old_title")
|
||||
%(#{creator} renamed #{card_title} (was: "#{h old_title}"))
|
||||
end
|
||||
|
||||
def moved_sentence(creator, card_title)
|
||||
new_location = event.particulars.dig("particulars", "new_board") || event.particulars.dig("particulars", "new_collection")
|
||||
%(#{creator} moved #{card_title} to "#{h new_location}")
|
||||
end
|
||||
|
||||
def triaged_sentence(creator, card_title)
|
||||
column = event.particulars.dig("particulars", "column")
|
||||
%(#{creator} moved #{card_title} to "#{h column}")
|
||||
end
|
||||
end
|
||||
@@ -5,7 +5,7 @@ class Identity < UntenantedRecord
|
||||
has_many :magic_links, dependent: :destroy
|
||||
has_many :sessions, dependent: :destroy
|
||||
|
||||
normalizes :email_address, with: ->(value) { value.strip.downcase }
|
||||
normalizes :email_address, with: ->(value) { value.strip.downcase.presence }
|
||||
|
||||
def send_magic_link
|
||||
magic_links.create!.tap do |magic_link|
|
||||
|
||||
@@ -8,7 +8,7 @@ class Notifier::CardEventNotifier < Notifier
|
||||
when "card_assigned"
|
||||
source.assignees.excluding(creator)
|
||||
when "card_published"
|
||||
board.watchers.without(creator, *card.mentionees)
|
||||
board.watchers.without(creator, *card.mentionees).including(*card.assignees).uniq
|
||||
when "comment_created"
|
||||
card.watchers.without(creator, *source.eventable.mentionees)
|
||||
else
|
||||
|
||||
@@ -29,6 +29,18 @@ class User::DayTimeline
|
||||
day.yesterday.beginning_of_day
|
||||
end
|
||||
|
||||
def added_column
|
||||
@added_column ||= build_column("Added", 1, events.where(action: %w[card_published card_reopened]))
|
||||
end
|
||||
|
||||
def updated_column
|
||||
@updated_column ||= build_column("Updated", 2, events.where.not(action: %w[card_published card_closed card_reopened]))
|
||||
end
|
||||
|
||||
def closed_column
|
||||
@closed_column ||= build_column("Closed", 3, events.where(action: "card_closed"))
|
||||
end
|
||||
|
||||
def cache_key
|
||||
ActiveSupport::Cache.expand_cache_key [ user, filter, day.to_date, events ], "day-timeline"
|
||||
end
|
||||
@@ -70,6 +82,10 @@ class User::DayTimeline
|
||||
filtered_events.where(created_at: ...day.beginning_of_day).chronologically.last
|
||||
end
|
||||
|
||||
def build_column(base_title, index, events)
|
||||
Column.new(self, base_title, index, events)
|
||||
end
|
||||
|
||||
def window
|
||||
day.all_day
|
||||
end
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
class User::DayTimeline::Column
|
||||
include ActionView::Helpers::TagHelper, ActionView::Helpers::OutputSafetyHelper, TimeHelper
|
||||
|
||||
attr_reader :index
|
||||
|
||||
def initialize(day_timeline, base_title, index, events)
|
||||
@day_timeline = day_timeline
|
||||
@base_title = base_title
|
||||
@index = index
|
||||
@events = events
|
||||
end
|
||||
|
||||
def title
|
||||
date_tag = local_datetime_tag(day_timeline.day, style: :agoorweekday)
|
||||
parts = [ base_title, date_tag ]
|
||||
parts << tag.span("(#{full_events_count})", class: "font-weight-normal") if full_events_count > 0
|
||||
safe_join(parts, " ")
|
||||
end
|
||||
|
||||
def events_by_hour
|
||||
limited_events.group_by { it.created_at.hour }
|
||||
end
|
||||
|
||||
def has_more_events?
|
||||
limited_events.count < full_events_count
|
||||
end
|
||||
|
||||
def hidden_events_count
|
||||
full_events_count - limited_events.count
|
||||
end
|
||||
|
||||
private
|
||||
attr_reader :base_title, :day_timeline, :events
|
||||
|
||||
def limited_events
|
||||
@limited_events ||= events.limit(100).load
|
||||
end
|
||||
|
||||
def full_events_count
|
||||
@full_events_count ||= events.count
|
||||
end
|
||||
end
|
||||
@@ -18,7 +18,7 @@ module User::Role
|
||||
admin? || other == self
|
||||
end
|
||||
|
||||
def can_administer?(other = nil)
|
||||
def can_administer?(other)
|
||||
admin? && other != self
|
||||
end
|
||||
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to account_join_code_path, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">← <strong>Invite link</strong></span>
|
||||
<%= link_to account_join_code_path, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.left@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis flex align-center">
|
||||
<strong>Back to Invite link</strong>
|
||||
<kbd class="txt-x-small margin-inline-start hide-on-touch">←</kbd>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to account_settings_path, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">← <strong>Account Settings</strong></span>
|
||||
<%= link_to account_settings_path, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.left@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis flex align-center">
|
||||
<strong>Back to Account Settings</strong>
|
||||
<kbd class="txt-x-small margin-inline-start hide-on-touch">←</kbd>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -17,10 +20,13 @@
|
||||
<% url = join_url(code: @join_code.code, tenant: @join_code.tenant, script_name: nil) %>
|
||||
<div class="flex align-center gap-half">
|
||||
<input type="text" class="input flex-item-grow" value="<%= url %>" readonly>
|
||||
<%= button_to account_join_code_path, method: :delete, class: "btn btn--circle txt-small", data: {
|
||||
turbo_confirm: "Are you sure you want to generate a new link? The previous code will stop working." } do %>
|
||||
<%= icon_tag "refresh" %>
|
||||
<span class="for-screen-reader">Generate a new code</span>
|
||||
|
||||
<% if Current.user.admin? %>
|
||||
<%= button_to account_join_code_path, method: :delete, class: "btn btn--circle txt-small", data: {
|
||||
turbo_confirm: "Are you sure you want to generate a new link? The previous code will stop working." } do %>
|
||||
<%= icon_tag "refresh" %>
|
||||
<span class="for-screen-reader">Generate a new code</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -59,8 +65,10 @@
|
||||
This code has been used <%= @join_code.usage_count %>/<%= @join_code.usage_limit %> times
|
||||
(<%= @join_code.active? ? @join_code.usage_limit - @join_code.usage_count : "none" %> remaining)
|
||||
|
||||
<%= link_to edit_account_join_code_path, class: @join_code.active? ? "txt-link" : "txt-negative txt-underline" do %>
|
||||
<span>Change limit</span>
|
||||
<% if Current.user.admin? %>
|
||||
<%= link_to edit_account_join_code_path, class: @join_code.active? ? "txt-link" : "txt-negative txt-underline" do %>
|
||||
<span>Change limit</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
<p class="margin-none-block-start">Fizzy doesn’t let stale cards stick around forever. Cards automatically close as “Not Now” without activity for specific period of time. <em>This is the default, global setting — you can override it on each board.</em></p>
|
||||
</header>
|
||||
|
||||
<%= render "entropy/auto_close", model: account.entropy, url: account_entropy_path %>
|
||||
<%= render "entropy/auto_close", model: account.entropy, url: account_entropy_path, disabled: !Current.user.admin? %>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<%= form_with model: account, url: account_settings_path, method: :put, scope: :account, data: { controller: "form" }, class: "flex gap-half" do |form| %>
|
||||
<strong class="full-width"><%= form.text_field :name, required: true, class: "input input--transparent full-width txt-medium", placeholder: "Account name…", data: { action: "input->form#disableSubmitWhenInvalid" }, readonly: !Current.user.can_administer? %></strong>
|
||||
<strong class="full-width"><%= form.text_field :name, required: true, class: "input input--transparent full-width txt-medium", placeholder: "Account name…", data: { action: "input->form#disableSubmitWhenInvalid" }, readonly: !Current.user.admin? %></strong>
|
||||
|
||||
<% if Current.user.can_administer? %>
|
||||
<% if Current.user.admin? %>
|
||||
<%= form.button class: "btn btn--circle btn--link txt-medium", data: { form_target: "submit" }, disabled: form.object do %>
|
||||
<%= icon_tag "arrow-right" %>
|
||||
<span class="for-screen-reader">Save changes</span>
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<% @page_title = "Account Settings" %>
|
||||
|
||||
<% content_for :header do %>
|
||||
<h1 class="header__title"><%= @page_title %></h1>
|
||||
<h1 class="header__title">
|
||||
<%= @page_title %>
|
||||
<% unless Current.user.admin? %>
|
||||
<div class="txt-normal font-weight-normal">Only admins can change these settings</div>
|
||||
<% end %>
|
||||
</h1>
|
||||
<% end %>
|
||||
|
||||
<section class="settings margin-block-start-half">
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to @board, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">
|
||||
←
|
||||
<strong><%= @board.name %></strong>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= link_back_to_board(@board) %>
|
||||
</div>
|
||||
|
||||
<h1 class="header__title divider divider--fade full-width">
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to @board, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">
|
||||
←
|
||||
<strong><%= @board.name %></strong>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= link_back_to_board(@board) %>
|
||||
</div>
|
||||
|
||||
<h1 class="header__title divider divider--fade full-width">
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to @column.board, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">
|
||||
←
|
||||
<strong><%= @column.board.name %></strong>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= link_back_to_board(@column.board) %>
|
||||
</div>
|
||||
|
||||
<h1 class="header__title divider divider--fade full-width">
|
||||
@@ -16,7 +11,7 @@
|
||||
<% end %>
|
||||
|
||||
<section class="cards cards--grid">
|
||||
<%= turbo_frame_tag dom_id(@column, :cards) do %>
|
||||
<%= turbo_frame_tag @column, :cards do %>
|
||||
<% if @page.used? %>
|
||||
<%= with_automatic_pagination dom_id(@column, :cards), @page do %>
|
||||
<%= render "boards/columns/list", cards: @page.records, draggable: true %>
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to @board, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">
|
||||
←
|
||||
<strong><%= @board.name %></strong>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= link_back_to_board(@board) %>
|
||||
</div>
|
||||
|
||||
<h1 class="header__title divider divider--fade full-width">
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= link_to @board, class: "btn borderless txt-medium", data: { controller: "hotkey", action: "keydown.esc@document->hotkey#click" } do %>
|
||||
<span class="overflow-ellipsis">
|
||||
←
|
||||
<strong><%= @board.name %></strong>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= link_back_to_board(@board) %>
|
||||
</div>
|
||||
|
||||
<h1 class="header__title"><%= @page_title %></h1>
|
||||
<h1 class="header__title">
|
||||
<div><%= @page_title %></div>
|
||||
<% unless Current.user.can_administer_board?(@board) %>
|
||||
<div class="txt-normal font-weight-normal">Only admins can change these settings</div>
|
||||
<% end %>
|
||||
</h1>
|
||||
<% end %>
|
||||
|
||||
<section class="settings">
|
||||
@@ -19,10 +19,10 @@
|
||||
controller: "form boards-form",
|
||||
boards_form_self_removal_prompt_message_value: "Are you sure you want to remove yourself from this board? You won’t be able to get back in unless someone invites you.",
|
||||
action: "turbo:submit-start->boards-form#submitWithWarning" } do |form| %>
|
||||
<%= render "boards/edit/name", form: form %>
|
||||
<%= render "boards/edit/name", form: form, board: @board %>
|
||||
<%= render "boards/edit/users", board: @board, selected_users: @selected_users, unselected_users: @unselected_users, form: form %>
|
||||
|
||||
<button type="submit" id="log_in" class="btn btn--link center txt-normal">
|
||||
<button type="submit" id="log_in" class="btn btn--link center txt-normal" <%= "disabled" unless Current.user.can_administer_board?(@board) %>>
|
||||
<span>Save changes</span>
|
||||
</button>
|
||||
|
||||
@@ -33,6 +33,6 @@
|
||||
<div class="settings__panel panel shadow center">
|
||||
<%= render "boards/edit/auto_close", board: @board %>
|
||||
<%= render "boards/edit/publication", board: @board %>
|
||||
<%= render "boards/edit/delete", board: @board %>
|
||||
<%= render "boards/edit/delete", board: @board if Current.user.can_administer_board?(@board) %>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
<div class="margin-block-end">
|
||||
<h2 class="divider txt-large">Auto close</h2>
|
||||
<p class="margin-none-block-start">Fizzy doesn’t let stale cards stick around forever. Cards automatically close as “Not now” if no one updates, comments, or moves a card for…</p>
|
||||
<%= render "entropy/auto_close", model: board, url: board_entropy_path(board) %>
|
||||
<%= render "entropy/auto_close", model: board, url: board_entropy_path(board), disabled: !Current.user.can_administer_board?(@board) %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
<% name = label.parameterize %>
|
||||
|
||||
<fieldset class="knob" data-controller="knob" data-knob-target="field" style="--knob-options: <%= knob_options.length %>; --knob-index: <%= default_index %>;">
|
||||
|
||||
<div class="position-relative" role="radiogroup" aria-labelledby="<%= name %>">
|
||||
<% knob_options.each_with_index do |value, i| %>
|
||||
<label class="knob__option" style="--i: <%= i %>">
|
||||
<input
|
||||
<%= "checked" if value == knob_options[default_index] %>
|
||||
data-action="change->knob#optionChanged"
|
||||
data-index="<%= i %>"
|
||||
data-knob-target="option"
|
||||
name="<%= name %>"
|
||||
type="radio"
|
||||
value="<%= value %>"
|
||||
/>
|
||||
<span><%= value %></span>
|
||||
</label>
|
||||
<% end %>
|
||||
<input
|
||||
aria-hidden
|
||||
class="knob__slider"
|
||||
data-action="input->knob#sliderChanged"
|
||||
data-knob-target="slider"
|
||||
max="<%= knob_options.length - 1 %>"
|
||||
min="0"
|
||||
type="range"
|
||||
value="<%= default_index %>"
|
||||
/>
|
||||
|
||||
<div class="knob__knob" aria-hidden></div>
|
||||
</div>
|
||||
|
||||
<div id="<%= name %>" class="knob__label">Days</div>
|
||||
</fieldset>
|
||||
@@ -2,6 +2,7 @@
|
||||
<label class="flex-item-grow">
|
||||
<strong><%= form.text_field :name, name: "board[name]", class: "input full-width txt-medium",
|
||||
required: true, autofocus: false, placeholder: "Board name…",
|
||||
data: { action: "keydown.enter->form#submit:prevent, keydown.esc->form#cancel" } %></strong>
|
||||
data: { action: "keydown.enter->form#submit:prevent, keydown.esc->form#cancel" },
|
||||
readonly: !Current.user.can_administer_board?(board) %></strong>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<span class="txt-large"><%= icon_tag "lock" %></span>
|
||||
<label class="switch flex align-center justify-between">
|
||||
<%= form_with url: board_publication_path(board), method: :delete, data: { controller: "form" } do |form| %>
|
||||
<%= form.check_box :published, class: "switch__input", checked: true, data: { action: "change->form#submit" } %>
|
||||
<%= form.check_box :published, class: "switch__input", checked: true, data: { action: "change->form#submit" }, disabled: !Current.user.can_administer_board?(@board) %>
|
||||
<span class="switch__btn round"></span>
|
||||
<span class="for-screen-reader">Turn off the public link</span>
|
||||
<% end %>
|
||||
@@ -32,7 +32,8 @@
|
||||
<%= form_with model: board, class: "txt-align-start", data: { controller: "form", turbo_frame: "_top" } do |form| %>
|
||||
<%= form.rich_textarea :public_description, class: "rich-text-content txt-small",
|
||||
placeholder: "Add a public note about this board…",
|
||||
data: { action: "keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel:stop" } %>
|
||||
data: { action: "keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel:stop" },
|
||||
readonly: !Current.user.can_administer_board?(@board) %>
|
||||
<%= form.button "Save changes", type: :submit, class: "btn txt-small" %>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -43,7 +44,7 @@
|
||||
<span class="txt-large"><%= icon_tag "lock" %></span>
|
||||
<label class="switch flex align-center justify-between">
|
||||
<%= form_with url: board_publication_path(board), method: :post, data: { controller: "form" } do |form| %>
|
||||
<%= form.check_box :published, class: "switch__input", checked: false, data: { action: "change->form#submit" } %>
|
||||
<%= form.check_box :published, class: "switch__input", checked: false, data: { action: "change->form#submit" }, disabled: !Current.user.can_administer_board?(@board) %>
|
||||
<span class="switch__btn round"></span>
|
||||
<span class="for-screen-reader">Turn on the public link</span>
|
||||
<% end %>
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
data-drag-and-drop-target="container"
|
||||
data-drag-and-strum-target="container"
|
||||
data-collapsible-columns-target="column"
|
||||
data-column-name="Done"
|
||||
data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle"
|
||||
data-drag-and-drop-url="<%= columns_card_drops_closure_path("__id__") %>"
|
||||
>
|
||||
|
||||
<%= render "boards/show/expander", title: "Done", count: board.cards.closed.count, column_id: "closed-cards" do %>
|
||||
<%= render "boards/show/menu/expand", column_path: board_columns_closed_path(board) %>
|
||||
<% end %>
|
||||
|
||||
<%= column_frame_tag :closed_column, src: board_columns_closed_path(board) %>
|
||||
<div class="cards__transition-container">
|
||||
<header class="cards__header">
|
||||
<%= render "boards/show/expander", title: "Done", count: board.cards.closed.count, column_id: "closed-cards" %>
|
||||
<%= render "boards/show/menu/maximize", column_path: board_columns_closed_path(board) %>
|
||||
</header>
|
||||
<%= column_frame_tag :closed_column, src: board_columns_closed_path(board) %>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -4,17 +4,20 @@
|
||||
data-drag-and-strum-target="container"
|
||||
data-collapsible-columns-target="column"
|
||||
data-controller="clicker"
|
||||
data-column-name="<%= column.name %>"
|
||||
data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle turbo:before-stream-render@document->collapsible-columns#restoreState"
|
||||
data-drag-and-drop-url="<%= columns_card_drops_column_path("__id__", column_id: column.id) %>"
|
||||
>
|
||||
<%= render "boards/show/expander", title: column.name, count: column.cards.active.count, column_id: dom_id(column) do %>
|
||||
<%= render "boards/show/menu/column", column: column %>
|
||||
<% end %>
|
||||
<div class="cards__transition-container">
|
||||
<header class="cards__header">
|
||||
<%= render "boards/show/menu/column", column: column %>
|
||||
<%= render "boards/show/expander", title: column.name, count: column.cards.active.count, column_id: dom_id(column) %>
|
||||
|
||||
<%= link_to board_column_path(column.board, column), class: "cards__expander-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %>
|
||||
<%= icon_tag "expand", class: "translucent" %>
|
||||
<span class="for-screen-reader">Expand column</span>
|
||||
<% end %>
|
||||
|
||||
<%= column_frame_tag dom_id(column, :cards), src: board_column_path(column.board, column) %>
|
||||
<%= link_to board_column_path(column.board, column), class: "cards__maximize-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %>
|
||||
<%= icon_tag "grid", class: "translucent" %>
|
||||
<span class="for-screen-reader">Maximize column</span>
|
||||
<% end %>
|
||||
</header>
|
||||
<%= column_frame_tag dom_id(column, :cards), src: board_column_path(column.board, column) %>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
<%= tag.div data: {
|
||||
controller: "drag-and-drop drag-and-strum",
|
||||
<%= tag.div class: "card-columns", data: {
|
||||
controller: "collapsible-columns drag-and-drop drag-and-strum",
|
||||
drag_and_drop_dragged_item_class: "drag-and-drop__dragged-item",
|
||||
drag_and_drop_hover_container_class: "drag-and-drop__hover-container",
|
||||
collapsible_columns_board_value: board.id,
|
||||
collapsible_columns_collapsed_class: "is-collapsed",
|
||||
collapsible_columns_no_transitions_class: "no-transitions",
|
||||
collapsible_columns_title_not_visible_class: "is-off-screen",
|
||||
action: "
|
||||
dragstart->drag-and-drop#dragStart
|
||||
dragover->drag-and-drop#dragOver
|
||||
dragenter->drag-and-strum#dragEnter
|
||||
drop->drag-and-drop#drop
|
||||
dragend->drag-and-drop#dragEnd" } do %>
|
||||
<div class="card-columns" data-controller="collapsible-columns" data-collapsible-columns-board-value="<%= board.id %>" data-collapsible-columns-collapsed-class="is-collapsed" data-collapsible-columns-no-transitions-class="no-transitions">
|
||||
<div class="card-columns__left">
|
||||
<%= render "boards/show/not_now", board: board %>
|
||||
</div>
|
||||
<div class="card-columns__left">
|
||||
<%= render "boards/show/not_now", board: board %>
|
||||
</div>
|
||||
|
||||
<%= render "boards/show/stream", board: board, page: page %>
|
||||
<%= render "boards/show/stream", board: board, page: page %>
|
||||
|
||||
<div class="card-columns__right">
|
||||
<%= render partial: "boards/show/column", collection: board.columns.sorted, cached: ->(column){ [ column, column.leftmost?, column.rightmost? ] } %>
|
||||
<%= render "boards/show/closed", board: board %>
|
||||
<div class="card-columns__right">
|
||||
<%= render partial: "boards/show/column", collection: board.columns.sorted, cached: ->(column){ [ column, column.leftmost?, column.rightmost? ] } %>
|
||||
<%= render "boards/show/closed", board: board %>
|
||||
|
||||
<%= render "boards/show/menu/columns", board: board %>
|
||||
</div>
|
||||
<%= render "boards/show/menu/columns", board: board %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<button class="cards__expander btn btn--plain" data-collapsible-columns-target="button" data-action="click->collapsible-columns#toggle"
|
||||
style="--card-count: <%= [ count, 20 ].min %>" aria-controls="<%= column_id %>" aria-expanded="false">
|
||||
<span class="cards__expander-count" data-drag-and-drop-counter="true"><%= count > 99 ? "99+" : count %></span>
|
||||
<h2 class="cards__expander-title"><%= title %></h2>
|
||||
<h2 class="cards__expander-title" data-collapsible-columns-target="title">
|
||||
<%= title %>
|
||||
<%= icon_tag "collapse" %>
|
||||
</h2>
|
||||
</button>
|
||||
<%= yield if block_given? %>
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
data-collapsible-columns-target="column"
|
||||
data-drag-and-drop-target="container"
|
||||
data-drag-and-strum-target="container"
|
||||
data-column-name="Not Now"
|
||||
data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle"
|
||||
data-drag-and-drop-url="<%= columns_card_drops_not_now_path("__id__") %>"
|
||||
>
|
||||
|
||||
<%= render "boards/show/expander", title: "Not Now", count: board.cards.postponed.count, column_id: "not-now" do %>
|
||||
<%= render "boards/show/menu/expand", column_path: board_columns_not_now_path(board) %>
|
||||
<% end %>
|
||||
|
||||
<%= column_frame_tag :not_now_column, src: board_columns_not_now_path(board) %>
|
||||
<div class="cards__transition-container">
|
||||
<header class="cards__header">
|
||||
<%= render "boards/show/expander", title: "Not Now", count: board.cards.postponed.count, column_id: "not-now" %>
|
||||
<%= render "boards/show/menu/maximize", column_path: board_columns_not_now_path(board) %>
|
||||
</header>
|
||||
<%= column_frame_tag :not_now_column, src: board_columns_not_now_path(board) %>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
<section id="the-stream" class="cards cards--considering"
|
||||
data-drag-and-drop-target="container"
|
||||
data-column-name="Maybe?"
|
||||
data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle"
|
||||
data-drag-and-drop-url="<%= columns_card_drops_stream_path("__id__") %>">
|
||||
<header class="cards__expander">
|
||||
<h2 class="cards__expander-title position-relative">Maybe?</h2>
|
||||
<%= link_to board_columns_stream_path(board), class: "cards__expander-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %>
|
||||
<%= icon_tag "expand", class: "translucent" %>
|
||||
<span class="for-screen-reader">Expand column</span>
|
||||
<div class="cards__transition-container">
|
||||
<header class="cards__header">
|
||||
<div class="cards__expander">
|
||||
<h2 class="cards__expander-title" data-collapsible-columns-target="title">Maybe?</h2>
|
||||
</div>
|
||||
<%= link_to board_columns_stream_path(board), class: "cards__maximize-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %>
|
||||
<%= icon_tag "grid", class: "translucent" %>
|
||||
<span class="for-screen-reader">Expand column</span>
|
||||
<% end %>
|
||||
</header>
|
||||
<%= render "columns/show/add_card_button", board: board %>
|
||||
<% if page.used? %>
|
||||
<%= with_automatic_pagination "the-stream", @page do %>
|
||||
<%= render "boards/columns/list", cards: page.records, draggable: true %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</header>
|
||||
|
||||
<%= render "columns/show/add_card_button", board: board %>
|
||||
|
||||
<% if page.used? %>
|
||||
<%= with_automatic_pagination "the-stream", @page do %>
|
||||
<%= render "boards/columns/list", cards: page.records, draggable: true %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div id="<%= dom_id(column, :menu) %>" class="cards__expander-menu" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<div id="<%= dom_id(column, :menu) %>" class="cards__menu" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="btn btn--circle txt-x-small borderless" data-action="click->dialog#open">
|
||||
<%= icon_tag "menu-dots-horizontal", class: "translucent" %>
|
||||
<span class="for-screen-reader">Column options</span>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<div class="position-relative cards__new-column" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside" data-turbo-permanent>
|
||||
<button class="btn btn--circle txt-x-small borderless" data-action="click->dialog#open:stop" aria-label="Add a new column, etc.">
|
||||
<div class="cards__new-column position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside" data-turbo-permanent>
|
||||
<button class="btn btn--circle txt-x-small borderless" data-controller="tooltip" data-action="click->dialog#open:stop">
|
||||
<%= icon_tag "add", class: "translucent" %>
|
||||
<span class="for-screen-reader">Add a column</span>
|
||||
</button>
|
||||
<dialog class="popup panel flex-column gap-half fill-white shadow txt-small margin-block-double" data-dialog-target="dialog">
|
||||
<%= render "boards/show/menu/column_form", board: board, column: Column.new, label: "Add column" %>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<%= link_to column_path, class: "cards__expander-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %>
|
||||
<%= icon_tag "expand", class: "translucent" %>
|
||||
<%= link_to column_path, class: "cards__maximize-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %>
|
||||
<%= icon_tag "grid", class: "translucent" %>
|
||||
<span class="for-screen-reader">Expand column</span>
|
||||
<% end %>
|
||||
@@ -5,7 +5,11 @@
|
||||
dialog_target: "dialog",
|
||||
navigable_list_focus_on_selection_value: false,
|
||||
navigable_list_actionable_items_value: true } do %>
|
||||
<strong class="popup__title">Assign this to…</strong>
|
||||
|
||||
<div class="flex align-start justify-space-between">
|
||||
<strong class="popup__title">Assign this to…</strong>
|
||||
<kbd class="txt-xx-small hide-on-touch">a</kbd>
|
||||
</div>
|
||||
|
||||
<% if @users.many? %>
|
||||
<%= text_field_tag :search, nil, placeholder: "Filter…", class: "input input--transparent txt-small margin-block-half", autofocus: true,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<%= turbo_frame_tag dom_id(@card, :columns) do %>
|
||||
<%= turbo_frame_tag @card, :columns do %>
|
||||
<div class="card__stages" role="radiogroup">
|
||||
<legend class="for-screen-reader">Choose a column for this card</legend>
|
||||
|
||||
|
||||
@@ -13,9 +13,16 @@
|
||||
data: { local_save_target: "input", action: "lexxy:change->form#disableSubmitWhenInvalid lexxy:change->local-save#save turbo:morph-element->local-save#restoreContent" } do %>
|
||||
<%= general_prompts(@card.board) %>
|
||||
<% end %>
|
||||
<%= form.button class: "comment__submit btn btn--reversed flex-item-justify-start", data: { form_target: "submit" }, disabled: true do %>
|
||||
<span>Post this comment</span>
|
||||
<% end %>
|
||||
|
||||
<span class="flex-inline align-center gap">
|
||||
<%= form.button class: "comment__submit btn btn--reversed",
|
||||
data: { form_target: "submit" }, disabled: true do %>
|
||||
<span>Post</span>
|
||||
<% end %>
|
||||
<span class="hide-on-touch txt-subtle">
|
||||
<kbd class="txt-x-small"><%= hotkey_label([ "ctrl", "enter" ]) %></kbd>
|
||||
</span>
|
||||
</span>
|
||||
<span data-form-target="cancel" hidden></span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<%= turbo_frame_tag comment, :new_reaction do %>
|
||||
<%= link_to new_card_comment_reaction_path(comment.card, comment), role: "button",
|
||||
class: "reactions__trigger btn btn--circle borderless", action: "soft-keyboard#open",
|
||||
class: "reactions__trigger btn btn--circle", action: "soft-keyboard#open",
|
||||
data: { turbo_frame: dom_id(comment, :new_reaction), action: "dialog#close" } do %>
|
||||
<%= image_tag "boost-color.svg", aria: { hidden: true } %>
|
||||
<span class="for-screen-reader">Add your own reaction</span>
|
||||
|
||||
@@ -7,8 +7,11 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<%= button_to card_closure_path(card), class: "btn borderless" do %>
|
||||
<%= button_to card_closure_path(card), class: "btn borderless",
|
||||
data: { controller: "hotkey", form_target: "submit", action: "keydown.d@document->hotkey#click" },
|
||||
form: { data: { controller: "form" } } do %>
|
||||
<span class="overflow-ellipsis">Mark as Done</span>
|
||||
<kbd class="txt-x-small hide-on-touch">d</kbd>
|
||||
<% end %>
|
||||
|
||||
<div id="<%= dom_id(card, :closure_notice) %>">
|
||||
|
||||
@@ -4,9 +4,16 @@
|
||||
<%= link_to card.title, edit_card_path(card), class: "card__title-link" %>
|
||||
</h1>
|
||||
|
||||
<div class="card__description rich-text-content" data-controller="syntax-highlight retarget-links">
|
||||
<%= card.description %>
|
||||
</div>
|
||||
<% unless card.description.blank? %>
|
||||
<div class="card__description rich-text-content" data-controller="syntax-highlight retarget-links">
|
||||
<%= card.description %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= link_to edit_card_path(card), class: "btn fit-content txt-small", data: { controller: "hotkey", action: "keydown.e@document->hotkey#click" } do %>
|
||||
<span>Edit</span>
|
||||
<kbd class="txt-x-small hide-on-touch">e</kbd>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= form_with model: card, id: "card_form", data: { controller: "autoresize auto-save" } do |form| %>
|
||||
@@ -19,7 +26,7 @@
|
||||
<% end %>
|
||||
</h1>
|
||||
|
||||
<%= form.rich_textarea :description, class: "card-field__description rich-text-content",
|
||||
<%= form.rich_textarea :description, class: "card__description rich-text-content",
|
||||
placeholder: "Add some notes, context, pictures, or video about this…",
|
||||
data: { action: "lexxy:change->auto-save#change focusout->auto-save#submit" } do %>
|
||||
<%= general_prompts(card.board) %>
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
<div class="card-perma__notch card-perma__notch--bottom">
|
||||
<%= button_to "Create card", card_publish_path(card), name: "creation_type", value: "add", class: "btn",
|
||||
form: { data: { controller: "form", action: "keydown.ctrl+enter@document->form#submit:prevent keydown.meta+enter@document->form#submit:prevent" } } %>
|
||||
<%= button_to card_publish_path(card), name: "creation_type", value: "add", class: "btn",
|
||||
form: { data: { controller: "form" } },
|
||||
data: { form_target: "submit", controller: "clicker", action: "keydown.ctrl+enter@document->clicker#click keydown.meta+enter@document->clicker#click" } do %>
|
||||
<span>Create card</span>
|
||||
<% end %>
|
||||
|
||||
<%= button_to "Create and add another", card_publish_path(card), method: :post, class: "btn btn--reversed", name: "creation_type", value: "add_another",
|
||||
title: "Create and add another (ctrl+shift+enter)", form: { data: { turbo: false } }, data: { controller: "hotkey", action: "keydown.ctrl+shift+enter@document->hotkey#click" } %>
|
||||
<%= button_to card_publish_path(card), method: :post, class: "btn btn--reversed", name: "creation_type", value: "add_another",
|
||||
title: "Create and add another (ctrl+shift+enter)", form: { data: { controller: "form" } },
|
||||
data: { form_target: "submit", controller: "clicker", action: "keydown.ctrl+shift+enter@document->clicker#click keydown.meta+shift+enter@document->clicker#click" } do %>
|
||||
<span>Create and add another</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||