Merge branch 'main' into closing-soon-badge
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
SECRETS=$(kamal secrets fetch --adapter 1password --account basecamp --from Deploy/Fizzy Deployments/BASECAMP_REGISTRY_PASSWORD Production/SECRET_KEY_BASE)
|
||||
|
||||
GITHUB_TOKEN=$(gh config get -h github.com oauth_token)
|
||||
BASECAMP_REGISTRY_PASSWORD=$(kamal secrets extract BASECAMP_REGISTRY_PASSWORD $SECRETS)
|
||||
SECRET_KEY_BASE=$(kamal secrets extract SECRET_KEY_BASE $SECRETS)
|
||||
@@ -38,6 +38,10 @@ RUN bundle exec bootsnap precompile app/ lib/
|
||||
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
|
||||
|
||||
|
||||
# Fetch beamer library
|
||||
FROM basecamp/beamer:latest AS beamer
|
||||
|
||||
|
||||
# Final stage for app image
|
||||
FROM base
|
||||
|
||||
@@ -49,6 +53,7 @@ RUN apt-get update -qq && \
|
||||
# Copy built artifacts: gems, application
|
||||
COPY --from=build /usr/local/bundle /usr/local/bundle
|
||||
COPY --from=build /rails /rails
|
||||
COPY --from=beamer /home/beamer/bin/beamer.so /rails/bin/lib/beamer.so
|
||||
|
||||
# Run and own only the runtime files as a non-root user for security
|
||||
RUN useradd rails --create-home --shell /bin/bash && \
|
||||
|
||||
+31
-31
@@ -1,6 +1,6 @@
|
||||
GIT
|
||||
remote: https://github.com/basecamp/active_record-tenanted
|
||||
revision: 8758240be33a6f6f7e7f56486a2e78008c193030
|
||||
revision: 612f5e8b7f77b7076a7eb21521920630161461aa
|
||||
specs:
|
||||
active_record-tenanted (0.1.0)
|
||||
activerecord (>= 8.1.alpha)
|
||||
@@ -18,7 +18,7 @@ GIT
|
||||
|
||||
GIT
|
||||
remote: https://github.com/rails/rails.git
|
||||
revision: 32d96235810e268eb5335a7837b13cda3f8b653d
|
||||
revision: bc7f77b32d7f3f26db654cae506c26a959852506
|
||||
branch: main
|
||||
specs:
|
||||
actioncable (8.1.0.alpha)
|
||||
@@ -145,13 +145,13 @@ GEM
|
||||
xpath (~> 3.2)
|
||||
chunky_png (1.4.0)
|
||||
concurrent-ruby (1.3.5)
|
||||
connection_pool (2.5.0)
|
||||
connection_pool (2.5.3)
|
||||
crass (1.0.6)
|
||||
date (3.4.1)
|
||||
debug (1.10.0)
|
||||
irb (~> 1.10)
|
||||
reline (>= 0.3.8)
|
||||
dotenv (3.1.7)
|
||||
dotenv (3.1.8)
|
||||
drb (2.2.1)
|
||||
ed25519 (1.3.0)
|
||||
erubi (1.13.1)
|
||||
@@ -179,7 +179,7 @@ GEM
|
||||
jbuilder (2.13.0)
|
||||
actionview (>= 5.0.0)
|
||||
activesupport (>= 5.0.0)
|
||||
json (2.10.2)
|
||||
json (2.11.3)
|
||||
kamal (2.5.3)
|
||||
activesupport (>= 7.0)
|
||||
base64 (~> 0.2)
|
||||
@@ -208,7 +208,7 @@ GEM
|
||||
mini_portile2 (2.8.8)
|
||||
minitest (5.25.5)
|
||||
msgpack (1.8.0)
|
||||
net-imap (0.5.6)
|
||||
net-imap (0.5.7)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
@@ -223,28 +223,28 @@ GEM
|
||||
net-protocol
|
||||
net-ssh (7.3.0)
|
||||
nio4r (2.7.4)
|
||||
nokogiri (1.18.7)
|
||||
nokogiri (1.18.8)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-aarch64-linux-gnu)
|
||||
nokogiri (1.18.8-aarch64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-aarch64-linux-musl)
|
||||
nokogiri (1.18.8-aarch64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-arm-linux-gnu)
|
||||
nokogiri (1.18.8-arm-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-arm-linux-musl)
|
||||
nokogiri (1.18.8-arm-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-arm64-darwin)
|
||||
nokogiri (1.18.8-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-x86_64-darwin)
|
||||
nokogiri (1.18.8-x86_64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-x86_64-linux-gnu)
|
||||
nokogiri (1.18.8-x86_64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-x86_64-linux-musl)
|
||||
nokogiri (1.18.8-x86_64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
ostruct (0.6.1)
|
||||
parallel (1.26.3)
|
||||
parser (3.3.7.4)
|
||||
parallel (1.27.0)
|
||||
parser (3.3.8.0)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
pp (0.6.2)
|
||||
@@ -264,7 +264,7 @@ GEM
|
||||
nio4r (~> 2.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.8.1)
|
||||
rack (3.1.12)
|
||||
rack (3.1.13)
|
||||
rack-contrib (2.5.0)
|
||||
rack (< 4)
|
||||
rack-session (2.1.0)
|
||||
@@ -290,12 +290,12 @@ GEM
|
||||
reline (0.6.1)
|
||||
io-console (~> 0.5)
|
||||
rexml (3.4.1)
|
||||
rouge (4.5.1)
|
||||
rqrcode (2.2.0)
|
||||
rouge (4.5.2)
|
||||
rqrcode (3.1.0)
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 1.0)
|
||||
rqrcode_core (1.2.0)
|
||||
rubocop (1.75.2)
|
||||
rqrcode_core (~> 2.0)
|
||||
rqrcode_core (2.0.0)
|
||||
rubocop (1.75.4)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.1.0)
|
||||
@@ -306,7 +306,7 @@ GEM
|
||||
rubocop-ast (>= 1.44.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 4.0)
|
||||
rubocop-ast (1.44.0)
|
||||
rubocop-ast (1.44.1)
|
||||
parser (>= 3.3.7.2)
|
||||
prism (~> 1.4)
|
||||
rubocop-performance (1.25.0)
|
||||
@@ -347,7 +347,7 @@ GEM
|
||||
activejob (>= 7.2)
|
||||
activerecord (>= 7.2)
|
||||
railties (>= 7.2)
|
||||
solid_queue (1.1.4)
|
||||
solid_queue (1.1.5)
|
||||
activejob (>= 7.1)
|
||||
activerecord (>= 7.1)
|
||||
concurrent-ruby (>= 1.3.1)
|
||||
@@ -373,13 +373,13 @@ GEM
|
||||
ostruct
|
||||
stimulus-rails (1.3.4)
|
||||
railties (>= 6.0.0)
|
||||
stringio (3.1.6)
|
||||
stringio (3.1.7)
|
||||
thor (1.3.2)
|
||||
thruster (0.1.12)
|
||||
thruster (0.1.12-aarch64-linux)
|
||||
thruster (0.1.12-arm64-darwin)
|
||||
thruster (0.1.12-x86_64-darwin)
|
||||
thruster (0.1.12-x86_64-linux)
|
||||
thruster (0.1.13)
|
||||
thruster (0.1.13-aarch64-linux)
|
||||
thruster (0.1.13-arm64-darwin)
|
||||
thruster (0.1.13-x86_64-darwin)
|
||||
thruster (0.1.13-x86_64-linux)
|
||||
timeout (0.4.3)
|
||||
turbo-rails (2.0.13)
|
||||
actionpack (>= 7.1.0)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m23.1 12.9h-3v3c0 .5-.4.9-.9.9s-.9-.4-.9-.9v-3h-3c-.5 0-.9-.4-.9-.9s.4-.9.9-.9h3v-3c0-.5.4-.9.9-.9s.9.4.9.9v3s3 0 3 0c.5 0 .9.4.9.9s-.4.9-.9.9zm-7.6 8.2h-15c-.3 0-.5-.2-.5-.5.1-2.1 1.1-2.6 3.2-3.4 2-.7 2.6-1.5 2.8-3.3-.3-.2-.6-.5-.9-.8-.5-.6-.8-1.4-.9-2.1-.5 0-.9-1.1-.9-1.8s.3-1 .5-1 0 0 .1 0c-.1-.5-.2-1-.2-1.4 0-2.6 1.1-4.1 4.3-4.1s1.7.4 2 .9c.2-.1.4-.2.6-.2.8 0 1.8 1 1.8 3.3 0 .5 0 1-.2 1.4h.1c.2 0 .5.2.5 1s-.4 1.8-.9 1.8c0 .8-.4 1.5-.9 2.1-.3.3-.6.5-.9.8.1 1.8.8 2.5 2.8 3.3 2.1.8 3 1.3 3.2 3.4 0 .3-.2.5-.5.5z" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 615 B |
@@ -11,8 +11,8 @@
|
||||
|
||||
/* Text */
|
||||
--text-xx-small: 0.55rem;
|
||||
--text-x-small: 0.7rem;
|
||||
--text-small: 0.8rem;
|
||||
--text-x-small: 0.75rem;
|
||||
--text-small: 0.85rem;
|
||||
--text-normal: 1rem;
|
||||
--text-medium: 1.1rem;
|
||||
--text-large: 1.5rem;
|
||||
@@ -20,78 +20,253 @@
|
||||
--text-xx-large: 2.5rem;
|
||||
|
||||
/* Borders */
|
||||
--border: 1px solid var(--color-subtle);
|
||||
--border: 1px solid var(--color-ink-lighter);
|
||||
|
||||
/* Shadows */
|
||||
--shadow: 0 0 0 1px oklch(var(--lch-always-black) / 0.02),
|
||||
0 .2em 1.6em -0.8em oklch(var(--lch-always-black) / 0.2),
|
||||
0 .4em 2.4em -1em oklch(var(--lch-always-black) / 0.3),
|
||||
0 .4em .8em -1.2em oklch(var(--lch-always-black) / 0.4),
|
||||
0 .8em 1.2em -1.6em oklch(var(--lch-always-black) / 0.5),
|
||||
0 1.2em 1.6em -2em oklch(var(--lch-always-black) / 0.6);
|
||||
--shadow: 0 0 0 1px oklch(var(--lch-black) / 0.02),
|
||||
0 .2em 1.6em -0.8em oklch(var(--lch-black) / 0.2),
|
||||
0 .4em 2.4em -1em oklch(var(--lch-black) / 0.3),
|
||||
0 .4em .8em -1.2em oklch(var(--lch-black) / 0.4),
|
||||
0 .8em 1.2em -1.6em oklch(var(--lch-black) / 0.5),
|
||||
0 1.2em 1.6em -2em oklch(var(--lch-black) / 0.6);
|
||||
|
||||
/* Components */
|
||||
--btn-size: 2.65em;
|
||||
|
||||
/* Color values */
|
||||
--lch-black: 27.13% 0.1035 284.77;
|
||||
/* OKLCH colors: Fixed */
|
||||
--lch-black: 0% 0 0;
|
||||
--lch-white: 100% 0 0;
|
||||
--lch-gray-light: 96% 0.005 96;
|
||||
--lch-gray: 92% 0.005 96;
|
||||
--lch-gray-dark: 75% 0.005 96;
|
||||
--lch-blue: 54% 0.15 255;
|
||||
--lch-blue-light: 95% 0.03 255;
|
||||
--lch-blue-dark: 80% 0.08 255;
|
||||
--lch-golden: 83% 0.1128 72.43;
|
||||
--lch-orange: 70% 0.2 44;
|
||||
--lch-red: 51% 0.2 31;
|
||||
--lch-green: 65.59% 0.234 142.49;
|
||||
--lch-green-light: 95% 0.03 142.49;
|
||||
--lch-yellow: 92.62% 0.1 91.5;
|
||||
--lch-always-black: 0% 0 0;
|
||||
--lch-always-white: 100% 0 0;
|
||||
--lch-light-blue: 84.94% 0.048 216.62;
|
||||
|
||||
/* Color abstractions */
|
||||
--color-negative: oklch(var(--lch-red));
|
||||
--color-positive: oklch(var(--lch-green));
|
||||
--color-positive-light: oklch(var(--lch-green-light));
|
||||
--color-bg: oklch(var(--lch-white));
|
||||
--color-ink: oklch(var(--lch-black));
|
||||
--color-ink-reversed: oklch(var(--lch-white));
|
||||
--color-link: oklch(var(--lch-blue));
|
||||
--color-subtle-light: oklch(var(--lch-gray-light));
|
||||
--color-subtle: oklch(var(--lch-gray));
|
||||
--color-subtle-dark: oklch(var(--lch-gray-dark));
|
||||
--color-selected: oklch(var(--lch-blue-light));
|
||||
--color-selected-dark: oklch(var(--lch-blue-dark));
|
||||
--color-highlight: oklch(var(--lch-yellow));
|
||||
--color-marker: oklch(var(--lch-orange));
|
||||
--color-always-black: oklch(var(--lch-always-black));
|
||||
--color-always-white: oklch(var(--lch-always-white));
|
||||
--color-container: oklch(var(--lch-light-blue));
|
||||
--color-golden: oklch(var(--lch-golden));
|
||||
/* OKLCH colors: Light mode */
|
||||
--lch-canvas: var(--lch-white);
|
||||
--lch-ink-inverted: var(--lch-white);
|
||||
|
||||
--lch-ink-darkest: 26% 0.05 264;
|
||||
--lch-ink-darker: 40% 0.026 262;
|
||||
--lch-ink-dark: 57% 0.014 260;
|
||||
--lch-ink-medium: 70% 0.008 258;
|
||||
--lch-ink-light: 84% 0.005 256;
|
||||
--lch-ink-lighter: 92% 0.003 254;
|
||||
--lch-ink-lightest: 96% 0.002 252;
|
||||
|
||||
--lch-uncolor-darkest: 26% 0.018 40;
|
||||
--lch-uncolor-darker: 40% 0.03 50;
|
||||
--lch-uncolor-dark: 57% 0.04 60;
|
||||
--lch-uncolor-medium: 70% 0.044 70;
|
||||
--lch-uncolor-light: 84% 0.028 80;
|
||||
--lch-uncolor-lighter: 92% 0.014 90;
|
||||
--lch-uncolor-lightest: 96% 0.012 100;
|
||||
|
||||
--lch-red-darkest: 26% 0.105 34;
|
||||
--lch-red-darker: 40% 0.154 36;
|
||||
--lch-red-dark: 59% 0.19 38;
|
||||
--lch-red-medium: 70.4% 0.19 40;
|
||||
--lch-red-light: 84% 0.07 42;
|
||||
--lch-red-lighter: 92% 0.03 44;
|
||||
--lch-red-lightest: 96% 0.013 46;
|
||||
|
||||
--lch-yellow-darkest: 26% 0.0729 40;
|
||||
--lch-yellow-darker: 40% 0.12 50;
|
||||
--lch-yellow-dark: 58% 0.156 60;
|
||||
--lch-yellow-medium: 76% 0.17 70;
|
||||
--lch-yellow-light: 84% 0.12 80;
|
||||
--lch-yellow-lighter: 92% 0.076 90;
|
||||
--lch-yellow-lightest: 96% 0.034 100;
|
||||
|
||||
--lch-lime-darkest: 26% 0.064 109;
|
||||
--lch-lime-darker: 40% 0.101 110;
|
||||
--lch-lime-dark: 56.5% 0.142 111;
|
||||
--lch-lime-medium: 71% 0.158 112;
|
||||
--lch-lime-light: 84% 0.08 113;
|
||||
--lch-lime-lighter: 92% 0.046 114;
|
||||
--lch-lime-lightest: 96% 0.034 115;
|
||||
|
||||
--lch-green-darkest: 26% 0.071 149;
|
||||
--lch-green-darker: 40% 0.12 148;
|
||||
--lch-green-dark: 55% 0.162 147;
|
||||
--lch-green-medium: 70% 0.19 146;
|
||||
--lch-green-light: 84% 0.07 145;
|
||||
--lch-green-lighter: 92% 0.044 144;
|
||||
--lch-green-lightest: 96% 0.022 143;
|
||||
|
||||
--lch-aqua-darkest: 26% 0.059 214;
|
||||
--lch-aqua-darker: 40% 0.093 212;
|
||||
--lch-aqua-dark: 55.5% 0.122 210;
|
||||
--lch-aqua-medium: 70% 0.13 208;
|
||||
--lch-aqua-light: 84% 0.04 206;
|
||||
--lch-aqua-lighter: 92% 0.02 204;
|
||||
--lch-aqua-lightest: 96% 0.012 202;
|
||||
|
||||
--lch-blue-darkest: 26% 0.126 264;
|
||||
--lch-blue-darker: 40% 0.166 262;
|
||||
--lch-blue-dark: 57% 0.19 260;
|
||||
--lch-blue-medium: 70% 0.169 258;
|
||||
--lch-blue-light: 84% 0.06 256;
|
||||
--lch-blue-lighter: 92% 0.026 254;
|
||||
--lch-blue-lightest: 96% 0.016 252;
|
||||
|
||||
--lch-violet-darkest: 26% 0.148 292;
|
||||
--lch-violet-darker: 40% 0.2 290;
|
||||
--lch-violet-dark: 58% 0.216 288;
|
||||
--lch-violet-medium: 70% 0.178 286;
|
||||
--lch-violet-light: 84% 0.07 284;
|
||||
--lch-violet-lighter: 92% 0.03 282;
|
||||
--lch-violet-lightest: 96% 0.015 280;
|
||||
|
||||
--lch-purple-darkest: 26% 0.131 314;
|
||||
--lch-purple-darker: 40% 0.178 312;
|
||||
--lch-purple-dark: 58% 0.21 310;
|
||||
--lch-purple-medium: 70% 0.22 308;
|
||||
--lch-purple-light: 84% 0.07 306;
|
||||
--lch-purple-lighter: 92% 0.03 304;
|
||||
--lch-purple-lightest: 96% 0.019 302;
|
||||
|
||||
--lch-pink-darkest: 26% 0.12 348;
|
||||
--lch-pink-darker: 40% 0.16 346;
|
||||
--lch-pink-dark: 59% 0.188 344;
|
||||
--lch-pink-medium: 70% 0.198 342;
|
||||
--lch-pink-light: 84% 0.07 340;
|
||||
--lch-pink-lighter: 92% 0.03 338;
|
||||
--lch-pink-lightest: 96% 0.02 336;
|
||||
|
||||
/* Colors: Named */
|
||||
--color-black: oklch(var(--lch-black));
|
||||
--color-white: oklch(var(--lch-white));
|
||||
|
||||
--color-ink: oklch(var(--lch-ink-darkest));
|
||||
--color-ink-darkest: oklch(var(--lch-ink-darkest));
|
||||
--color-ink-darker: oklch(var(--lch-ink-darker));
|
||||
--color-ink-dark: oklch(var(--lch-ink-dark));
|
||||
--color-ink-medium: oklch(var(--lch-ink-medium));
|
||||
--color-ink-light: oklch(var(--lch-ink-light));
|
||||
--color-ink-lighter: oklch(var(--lch-ink-lighter));
|
||||
--color-ink-lightest: oklch(var(--lch-ink-lightest));
|
||||
|
||||
--color-ink-inverted: oklch(var(--lch-ink-inverted));
|
||||
|
||||
/* Colors: Abstractions */
|
||||
--color-canvas: oklch(var(--lch-canvas));
|
||||
--color-negative: oklch(var(--lch-red-dark));
|
||||
--color-positive: oklch(var(--lch-green-dark));
|
||||
--color-link: oklch(var(--lch-blue-dark));
|
||||
--color-selected: oklch(var(--lch-blue-lighter));
|
||||
--color-selected-dark: oklch(var(--lch-blue-light));
|
||||
--color-highlight: oklch(var(--lch-yellow-lighter));
|
||||
--color-marker: oklch(var(--lch-red-medium));
|
||||
--color-container: oklch(var(--lch-aqua-light));
|
||||
--color-terminal-bg: oklch(var(--lch-blue-darkest));
|
||||
--color-terminal-text: oklch(var(--lch-green-medium));
|
||||
|
||||
/* Colors: Cards */
|
||||
--color-card-1: oklch(var(--lch-ink-dark));
|
||||
--color-card-2: oklch(var(--lch-uncolor-dark));
|
||||
--color-card-3: oklch(var(--lch-yellow-dark));
|
||||
--color-card-4: oklch(var(--lch-lime-dark));
|
||||
--color-card-5: oklch(var(--lch-aqua-dark));
|
||||
--color-card-6: oklch(var(--lch-blue-dark));
|
||||
--color-card-7: oklch(var(--lch-violet-dark));
|
||||
--color-card-8: oklch(var(--lch-purple-dark));
|
||||
--color-card-9: oklch(var(--lch-pink-dark));
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
--lch-black: 100% 0 0;
|
||||
--lch-white: 0% 0 0;
|
||||
--lch-gray-light: 25.2% 0 0;
|
||||
--lch-gray: 30.12% 0 0;
|
||||
--lch-gray-dark: 44.95% 0 0;
|
||||
--lch-blue: 72.25% 0.16 248;
|
||||
--lch-blue-light: 28.11% 0.053 248;
|
||||
--lch-blue-dark: 42.25% 0.07 248;
|
||||
--lch-red: 73.8% 0.184 29.18;
|
||||
--lch-green: 75% 0.21 141.89;
|
||||
--lch-green-light: 28.11% 0.02 142.49;
|
||||
--lch-yellow: 40.9% 0.06 88.9;
|
||||
--lch-light-blue: 20.62% 0.048 216.62;
|
||||
--lch-canvas: 20% 0.0195 232.58;
|
||||
--lch-ink-inverted: var(--lch-black);
|
||||
|
||||
--shadow: 0 0 0 1px oklch(var(--lch-always-black) / 0.42),
|
||||
0 .2em 1.6em -0.8em oklch(var(--lch-always-black) / 0.6),
|
||||
0 .4em 2.4em -1em oklch(var(--lch-always-black) / 0.7),
|
||||
0 .4em .8em -1.2em oklch(var(--lch-always-black) / 0.8),
|
||||
0 .8em 1.2em -1.6em oklch(var(--lch-always-black) / 0.9),
|
||||
0 1.2em 1.6em -2em oklch(var(--lch-always-black) / 1);
|
||||
--lch-ink-darkest: 96.02% 0.0034 260;
|
||||
--lch-ink-darker: 86% 0.0061 260;
|
||||
--lch-ink-dark: 73.97% 0.009 260;
|
||||
--lch-ink-medium: 62% 0.0122 260;
|
||||
--lch-ink-light: 40% 0.0148 260;
|
||||
--lch-ink-lighter: 30% 0.0178 260;
|
||||
--lch-ink-lightest: 25% 0.0204 260;
|
||||
|
||||
--lch-uncolor-darkest: 96.09% 0.0076 100;
|
||||
--lch-uncolor-darker: 86% 0.021 90;
|
||||
--lch-uncolor-dark: 73.93% 0.041 80;
|
||||
--lch-uncolor-medium: 62% 0.0552 70;
|
||||
--lch-uncolor-light: 40% 0.0387 60;
|
||||
--lch-uncolor-lighter: 30% 0.012 50;
|
||||
--lch-uncolor-lightest: 25% 0.0017 40;
|
||||
|
||||
--lch-red-darkest: 95.85% 0.0218 46;
|
||||
--lch-red-darker: 86% 0.086 44;
|
||||
--lch-red-dark: 73.95% 0.139 42;
|
||||
--lch-red-medium: 62% 0.154 40;
|
||||
--lch-red-light: 40% 0.088 38;
|
||||
--lch-red-lighter: 30% 0.032 36;
|
||||
--lch-red-lightest: 25% 0.011 34;
|
||||
|
||||
--lch-yellow-darkest: 96% 0.056 100;
|
||||
--lch-yellow-darker: 86% 0.103 90;
|
||||
--lch-yellow-dark: 74.06% 0.136 80;
|
||||
--lch-yellow-medium: 62.1% 0.146 70;
|
||||
--lch-yellow-light: 40% 0.0736 60;
|
||||
--lch-yellow-lighter: 30% 0.026 50;
|
||||
--lch-yellow-lightest: 25% 0.01 40;
|
||||
|
||||
--lch-lime-darkest: 96.04% 0.066 115;
|
||||
--lch-lime-darker: 86% 0.098 114;
|
||||
--lch-lime-dark: 73.97% 0.121 113;
|
||||
--lch-lime-medium: 62% 0.128 112;
|
||||
--lch-lime-light: 40% 0.0637 111;
|
||||
--lch-lime-lighter: 30% 0.024 110;
|
||||
--lch-lime-lightest: 25% 0.012 109;
|
||||
|
||||
--lch-green-darkest: 96.12% 0.035 143;
|
||||
--lch-green-darker: 86% 0.082 144;
|
||||
--lch-green-dark: 73.99% 0.117 145;
|
||||
--lch-green-medium: 62% 0.1261 146;
|
||||
--lch-green-light: 40% 0.065 147;
|
||||
--lch-green-lighter: 30% 0.03 148;
|
||||
--lch-green-lightest: 25% 0.018 149;
|
||||
|
||||
--lch-aqua-darkest: 96.15% 0.0244 202;
|
||||
--lch-aqua-darker: 86% 0.06 204;
|
||||
--lch-aqua-dark: 73.92% 0.095 206;
|
||||
--lch-aqua-medium: 62% 0.106 208;
|
||||
--lch-aqua-light: 40% 0.0594 210;
|
||||
--lch-aqua-lighter: 30% 0.028 212;
|
||||
--lch-aqua-lightest: 25% 0.017 214;
|
||||
|
||||
--lch-blue-darkest: 95.93% 0.0217 252;
|
||||
--lch-blue-darker: 86% 0.068 254;
|
||||
--lch-blue-dark: 74% 0.1293 256;
|
||||
--lch-blue-medium: 62% 0.159 258;
|
||||
--lch-blue-light: 40% 0.094 260;
|
||||
--lch-blue-lighter: 30% 0.0452 262;
|
||||
--lch-blue-lightest: 25% 0.0318 264;
|
||||
|
||||
--lch-violet-darkest: 95.97% 0.019 280;
|
||||
--lch-violet-darker: 86% 0.068 282;
|
||||
--lch-violet-dark: 74.08% 0.142 284;
|
||||
--lch-violet-medium: 62% 0.184 286;
|
||||
--lch-violet-light: 40% 0.108 288;
|
||||
--lch-violet-lighter: 30% 0.048 290;
|
||||
--lch-violet-lightest: 25% 0.025 292;
|
||||
|
||||
--lch-purple-darkest: 95.99% 0.0217 302;
|
||||
--lch-purple-darker: 86% 0.068 304;
|
||||
--lch-purple-dark: 73.98% 0.141 306;
|
||||
--lch-purple-medium: 62% 0.177 308;
|
||||
--lch-purple-light: 40% 0.099 310;
|
||||
--lch-purple-lighter: 30% 0.04 312;
|
||||
--lch-purple-lightest: 25% 0.017 314;
|
||||
|
||||
--lch-pink-darkest: 95.84% 0.0308 336;
|
||||
--lch-pink-darker: 86% 0.074 338;
|
||||
--lch-pink-dark: 74.04% 0.1294 340;
|
||||
--lch-pink-medium: 62% 0.166 342;
|
||||
--lch-pink-light: 40% 0.085 344;
|
||||
--lch-pink-lighter: 30% 0.03 346;
|
||||
--lch-pink-lightest: 25% 0.011 348;
|
||||
|
||||
--color-container: oklch(var(--lch-aqua-lighter));
|
||||
|
||||
--shadow: 0 0 0 1px oklch(var(--lch-black) / 0.42),
|
||||
0 .2em 1.6em -0.8em oklch(var(--lch-black) / 0.6),
|
||||
0 .4em 2.4em -1em oklch(var(--lch-black) / 0.7),
|
||||
0 .4em .8em -1.2em oklch(var(--lch-black) / 0.8),
|
||||
0 .8em 1.2em -1.6em oklch(var(--lch-black) / 0.9),
|
||||
0 1.2em 1.6em -2em oklch(var(--lch-black) / 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
margin: 0;
|
||||
place-items: center;
|
||||
|
||||
img {
|
||||
img,
|
||||
.icon {
|
||||
aspect-ratio: 1;
|
||||
block-size: auto;
|
||||
border-radius: var(--avatar-border-radius);
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
--font-sans: system-ui;
|
||||
--font-serif: ui-serif, serif;
|
||||
--font-mono: ui-monospace, monospace;
|
||||
--hover-color: var(--color-subtle-dark);
|
||||
--hover-color: var(--color-ink-medium);
|
||||
--hover-size: 0.15rem;
|
||||
--hover-filter: brightness(1);
|
||||
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: none;
|
||||
background: var(--color-bg);
|
||||
background: var(--color-canvas);
|
||||
color: var(--color-ink);
|
||||
font-family: var(--font-sans);
|
||||
interpolate-size: allow-keywords;
|
||||
@@ -85,6 +85,19 @@
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
kbd {
|
||||
border: 1px solid;
|
||||
border-radius: .3em;
|
||||
box-shadow: 0 0.1em 0 currentColor;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-x-small);
|
||||
font-weight: 600;
|
||||
opacity: 0.7;
|
||||
padding: 0em 0.4em;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
video {
|
||||
max-inline-size: 100%;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
--btn-border-radius: 2em;
|
||||
|
||||
align-items: center;
|
||||
background-color: var(--btn-background, var(--color-bg));
|
||||
background-color: var(--btn-background, var(--color-canvas));
|
||||
border-radius: var(--btn-border-radius);
|
||||
border: var(--btn-border-size, 1px) solid var(--btn-border-color, var(--color-subtle-dark));
|
||||
border: var(--btn-border-size, 1px) solid var(--btn-border-color, var(--color-ink-light));
|
||||
color: var(--btn-color, var(--color-ink));
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
@@ -112,7 +112,7 @@
|
||||
|
||||
&:has(input:checked) {
|
||||
--btn-background: var(--color-ink);
|
||||
--btn-color: var(--color-ink-reversed);
|
||||
--btn-color: var(--color-ink-inverted);
|
||||
--outline-color: var(--color-ink);
|
||||
|
||||
.checked {
|
||||
@@ -137,13 +137,13 @@
|
||||
/* Variants */
|
||||
.btn--link {
|
||||
--btn-background: var(--color-link);
|
||||
--btn-color: var(--color-ink-reversed);
|
||||
--btn-color: var(--color-ink-inverted);
|
||||
--outline-color: var(--color-link);
|
||||
}
|
||||
|
||||
.btn--negative {
|
||||
--btn-background: var(--color-negative);
|
||||
--btn-color: var(--color-ink-reversed);
|
||||
--btn-color: var(--color-ink-inverted);
|
||||
--outline-color: var(--color-negative);
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@
|
||||
|
||||
.btn--positive {
|
||||
--btn-background: var(--color-positive);
|
||||
--btn-color: var(--color-ink-reversed);
|
||||
--btn-color: var(--color-ink-inverted);
|
||||
--outline-color: var(--color-positive);
|
||||
}
|
||||
|
||||
@@ -173,12 +173,12 @@
|
||||
|
||||
.btn--reversed {
|
||||
--btn-background: var(--color-ink);
|
||||
--btn-color: var(--color-bg);
|
||||
--btn-color: var(--color-canvas);
|
||||
--outline-color: var(--color-ink);
|
||||
}
|
||||
|
||||
:is(.btn--link, .btn--negative, .btn--positive, .btn--reversed) {
|
||||
--btn-border-color: var(--color-bg);
|
||||
--btn-border-color: var(--color-canvas);
|
||||
}
|
||||
|
||||
.btn--success {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
}
|
||||
|
||||
.card-columns {
|
||||
border-block-start: 1px solid var(--color-subtle);
|
||||
border-block-start: 1px solid var(--color-ink-lighter);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 50%);
|
||||
margin: auto;
|
||||
@@ -77,14 +77,10 @@
|
||||
--text-xx-large: 1.6em;
|
||||
--text-small: 1.1em;
|
||||
|
||||
background-color: var(--color-bg);
|
||||
box-shadow: 0 0 0 1px var(--color-subtle);
|
||||
background-color: var(--color-canvas);
|
||||
box-shadow: 0 0 0 1px var(--color-ink-lighter);
|
||||
line-height: 1.2;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
box-shadow: 0 0 0 1px var(--color-subtle-dark);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
block-size: 2.75em;
|
||||
inline-size: 2.75em;
|
||||
@@ -92,10 +88,11 @@
|
||||
}
|
||||
|
||||
.card--drafted {
|
||||
--card-color: var(--color-subtle-dark) !important;
|
||||
--card-color: var(--color-ink-medium) !important;
|
||||
|
||||
border-color: var(--card-color) !important;
|
||||
border-style: dashed !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.card__background {
|
||||
@@ -124,7 +121,7 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
.cards--closed {
|
||||
border-block-start: 1px solid var(--color-subtle);
|
||||
border-block-start: 1px solid var(--color-ink-lighter);
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
.card-perma {
|
||||
--actions-block-inset: 1.5rem;
|
||||
--actions-inline-inset: 4rem;
|
||||
--color-container: color-mix(in srgb, var(--card-color) 33%, var(--color-bg));
|
||||
--color-container: color-mix(in srgb, var(--card-color) 33%, var(--color-canvas));
|
||||
--padding-inline: calc(var(--block-space-double) + var(--block-space));
|
||||
--padding-block: var(--block-space-double) calc(var(--block-space-double) + var(--block-space-half));
|
||||
--padding-block: calc(var(--block-space-double) + var(--block-space-half));
|
||||
|
||||
align-items: start;
|
||||
column-gap: var(--inline-space);
|
||||
@@ -15,7 +15,7 @@
|
||||
"qw notch-top we"
|
||||
"actions-left card actions-right"
|
||||
"er notch-bottom rt";
|
||||
grid-template-columns: 48px minmax(0, 960px) 48px;
|
||||
grid-template-columns: 48px minmax(0, 1120px) 48px;
|
||||
inline-size: fit-content;
|
||||
max-inline-size: 100%;
|
||||
margin-inline: auto;
|
||||
@@ -31,6 +31,15 @@
|
||||
|
||||
border: none;
|
||||
}
|
||||
|
||||
.card__meta,
|
||||
.card__stages {
|
||||
font-size: var(--text-small);
|
||||
}
|
||||
|
||||
.card__stages {
|
||||
max-inline-size: 32ch;
|
||||
}
|
||||
}
|
||||
|
||||
/* Children
|
||||
@@ -60,12 +69,7 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
.card-perma__notch {
|
||||
--card-notch-padding: 0.5ch;
|
||||
--border-radius: 1em;
|
||||
|
||||
align-items: center;
|
||||
background-color: var(--color-bg);
|
||||
border-radius: 99rem;
|
||||
color: color-mix(in srgb, var(--card-color) 40%, var(--color-ink));
|
||||
display: inline-flex;
|
||||
inline-size: auto;
|
||||
@@ -92,23 +96,22 @@
|
||||
|
||||
&:has(input:checked) {
|
||||
--btn-background: var(--card-color);
|
||||
--btn-color: var(--color-ink-reversed);
|
||||
--btn-color: var(--color-ink-inverted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-perma__notch--bottom {
|
||||
grid-area: notch-bottom;
|
||||
padding: calc(var(--block-space) * 0.75) calc(var(--block-space) * 0.66);
|
||||
translate: 0 -50%;
|
||||
translate: 0 -25%;
|
||||
|
||||
&:has([open]) {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.btn:not(.popup__item, .btn--plain) {
|
||||
.btn:not(.popup__item, .btn--plain, .btn--reversed) {
|
||||
--btn-background: var(--card-color);
|
||||
--btn-color: var(--color-ink-reversed);
|
||||
--btn-color: var(--color-ink-inverted);
|
||||
}
|
||||
|
||||
.btn--plain {
|
||||
@@ -116,6 +119,11 @@
|
||||
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.btn--reversed {
|
||||
--btn-background: var(--color-canvas);
|
||||
--btn-color: var(--card-color);
|
||||
}
|
||||
}
|
||||
|
||||
.card-perma__closure-message {
|
||||
@@ -125,29 +133,4 @@
|
||||
padding-left: 1ch;
|
||||
}
|
||||
}
|
||||
|
||||
/* Variants
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
.card-perma--pointing {
|
||||
--arrow-size: 20em;
|
||||
--aspect-ratio: 7;
|
||||
|
||||
position: relative;
|
||||
margin-block-end: calc(var(--arrow-size) / var(--aspect-ratio));
|
||||
|
||||
&::after {
|
||||
aspect-ratio: var(--aspect-ratio);
|
||||
background-color: var(--color-container);
|
||||
clip-path: polygon(50% 100%, 100% 0, 0 0);
|
||||
inline-size: var(--arrow-size);
|
||||
content: "";
|
||||
display: block;
|
||||
inset-block-start: 99%;
|
||||
inset-inline-start: 50%;
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
--border-size: 0;
|
||||
|
||||
aspect-ratio: var(--card-aspect-ratio, auto);
|
||||
background-color: color-mix(in srgb, var(--card-color) 4%, var(--color-bg));
|
||||
background-color: color-mix(in srgb, var(--card-color) 4%, var(--color-canvas));
|
||||
border-radius: 0.5em;
|
||||
box-shadow: var(--shadow);
|
||||
color: color-mix(in srgb, var(--card-color) 40%, var(--color-ink));
|
||||
@@ -22,21 +22,27 @@
|
||||
.avatar {
|
||||
--avatar-size: 2.75em;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
--border-color: var(--color-subtle-dark);
|
||||
}
|
||||
}
|
||||
|
||||
.card--golden.card--doing {
|
||||
--border-color: var(--color-bg) !important;
|
||||
--border-color: var(--color-canvas) !important;
|
||||
--lch-golden: var(--lch-yellow-light);
|
||||
|
||||
background-color: var(--color-bg);
|
||||
background-image: linear-gradient(90deg, oklch(var(--lch-golden) / 0.5) 0%, oklch(var(--lch-golden) / 0.1) 50%, oklch(var(--lch-golden) / 0.75) 100%);
|
||||
background-color: var(--color-canvas);
|
||||
background-image: linear-gradient(60deg,
|
||||
oklch(var(--lch-golden) / 0.35) 0%,
|
||||
oklch(var(--lch-golden) / 0.15) 20%,
|
||||
oklch(var(--lch-golden) / 0) 60%,
|
||||
oklch(var(--lch-golden) / 0.5) 100%
|
||||
);
|
||||
box-shadow:
|
||||
0 0 0.5em 0.2em var(--color-golden),
|
||||
0 0 1em 0.4em var(--color-golden) !important;
|
||||
outline: 1px solid var(--color-golden);
|
||||
0 0 0.5em 0.2em oklch(var(--lch-golden) / 0.35),
|
||||
0 0 1em 0.4em oklch(var(--lch-golden) / 0.35) !important;
|
||||
outline: 1px solid oklch(var(--lch-golden) / 0.35);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
--lch-golden: var(--lch-blue-medium);
|
||||
}
|
||||
}
|
||||
|
||||
.card__background {
|
||||
@@ -99,18 +105,45 @@
|
||||
padding-block: var(--card-padding-block);
|
||||
}
|
||||
|
||||
.card__description {
|
||||
house-md-toolbar,
|
||||
.house-md-content {
|
||||
color: color-mix(in srgb, var(--card-color) 40%, var(--color-ink));
|
||||
}
|
||||
|
||||
.house-md-content {
|
||||
min-block-size: 3lh;
|
||||
padding: var(--block-space-half) var(--inline-space) 0 0;
|
||||
}
|
||||
|
||||
& ~ .btn {
|
||||
--btn-background: var(--card-color);
|
||||
--btn-color: var(--color-ink-inverted);
|
||||
}
|
||||
}
|
||||
|
||||
.card__content {
|
||||
flex: 2 1 auto;
|
||||
}
|
||||
|
||||
.card__title {
|
||||
--hover-size: 0;
|
||||
--input-border-radius: 0;
|
||||
--input-color: color-mix(in srgb, var(--card-color) 40%, var(--color-ink));
|
||||
--lines: 3;
|
||||
|
||||
flex: 2 1 75%;
|
||||
color: inherit;
|
||||
font-size: var(--text-xx-large);
|
||||
font-weight: 900;
|
||||
line-height: 1.1;
|
||||
line-height: 1.2;
|
||||
min-block-size: 3lh;
|
||||
text-wrap: balance;
|
||||
|
||||
.card__title-field {
|
||||
.card-perma__bg & {
|
||||
min-block-size: unset;
|
||||
}
|
||||
|
||||
.card-field__title {
|
||||
&:is(textarea)::placeholder {
|
||||
color: inherit;
|
||||
opacity: 0.5;
|
||||
@@ -118,23 +151,34 @@
|
||||
}
|
||||
|
||||
.card__title-link {
|
||||
--lines: 3;
|
||||
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.card__stages {
|
||||
display: flex;
|
||||
flex: 1 1 25%;
|
||||
flex: 0 1 auto;
|
||||
flex-direction: column;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
padding-block: var(--block-space);
|
||||
justify-self: end;
|
||||
max-inline-size: 20ch;
|
||||
padding-block: var(--block-space-half);
|
||||
}
|
||||
|
||||
.card--new-card-button:is(.btn) {
|
||||
--btn-padding: var(--block-space) var(--inline-space-double);
|
||||
--btn-border-size: 0;
|
||||
--btn-color: var(--color-link);
|
||||
--btn-icon-size: 1em;
|
||||
--card-padding-block: calc(var(--block-space) * 1.25);
|
||||
|
||||
box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-link) 20%, var(--color-canvas));
|
||||
color: var(--btn-color);
|
||||
font-weight: 700;
|
||||
|
||||
}
|
||||
|
||||
.card__move-button {
|
||||
--btn-background: color-mix(in srgb, var(--card-color) 40%, var(--color-ink));
|
||||
--btn-background: color-mix(in srgb, inherit 40%, var(--color-ink));
|
||||
|
||||
margin-block-start: calc(var(--block-space) * 1.2);
|
||||
margin-inline-end: calc(var(--inline-space) * -4);
|
||||
@@ -186,16 +230,25 @@
|
||||
|
||||
.card__closed {
|
||||
align-items: center;
|
||||
aspect-ratio: 7/4;
|
||||
aspect-ratio: 8/5;
|
||||
background-color: var(--color-canvas);
|
||||
border: var(--block-space-half) solid var(--card-color);
|
||||
border-radius: 0.2em;
|
||||
color: var(--card-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
inset: auto var(--block-space) var(--block-space-double) auto;
|
||||
justify-content: space-between;
|
||||
max-inline-size: 25ch;
|
||||
min-inline-size: 18ch;
|
||||
mix-blend-mode: multiply;
|
||||
padding: var(--block-space-half) calc(var(--inline-space) * 1.5);
|
||||
position: absolute;
|
||||
transform: rotate(-10deg);
|
||||
z-index: -1;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
mix-blend-mode: screen;
|
||||
}
|
||||
}
|
||||
|
||||
.card__closed-by {
|
||||
@@ -203,9 +256,11 @@
|
||||
}
|
||||
|
||||
.card__closed-title {
|
||||
color: var(--color-ink-reversed);
|
||||
font-size: var(--text-rel-large);
|
||||
color: var(--color-ink-inverted);
|
||||
font-size: 1.5em;
|
||||
font-weight: 900;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
text-shadow:
|
||||
-2px -2px 0 var(--card-color),
|
||||
2px -2px 0 var(--card-color),
|
||||
|
||||
@@ -2,34 +2,17 @@
|
||||
--panel-border-color: var(--card-color);
|
||||
--panel-border-radius: 2em;
|
||||
--panel-border-size: 2px;
|
||||
--panel-padding: 0.5em 0.7em;
|
||||
--panel-padding: 0.5em;
|
||||
--panel-size: auto;
|
||||
|
||||
inline-size: auto !important;
|
||||
inset: 0 auto auto 0;
|
||||
inset: 50% auto auto calc(-1 * var(--panel-padding));
|
||||
max-inline-size: var(--panel-size) !important;
|
||||
position: absolute;
|
||||
translate: 0 -50%;
|
||||
z-index: 2;
|
||||
|
||||
&[open] {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.color-picker__color {
|
||||
--btn-size: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.color-picker__button {
|
||||
font-size: 0.75em;
|
||||
|
||||
.cards & {
|
||||
display: none;
|
||||
}
|
||||
|
||||
+ .panel {
|
||||
--panel-size: auto;
|
||||
|
||||
inset: 0 0 auto auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
text-align: center;
|
||||
|
||||
@media (min-width: 160ch) {
|
||||
padding-inline: calc(var(--tray-size) + var(--inline-space) * 1.5);
|
||||
padding-inline: var(--tray-size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,11 +63,12 @@
|
||||
}
|
||||
|
||||
.comment__content {
|
||||
background-color: var(--color-subtle-light);
|
||||
background-color: var(--color-ink-lightest);
|
||||
border-radius: 0.2em;
|
||||
padding:
|
||||
var(--comment-padding-block)
|
||||
var(--comment-padding-inline);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.comment__edit {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
transform var(--speed);
|
||||
|
||||
&::backdrop {
|
||||
background-color: var(--color-always-black);
|
||||
background-color: var(--color-black);
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
transition:
|
||||
|
||||
@@ -11,13 +11,12 @@
|
||||
--column-gap: clamp(var(--inline-space), 3cqmin, calc(var(--inline-space) * 3));
|
||||
--grid-lines: 0.1rem;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 5fr 1fr;
|
||||
margin: auto;
|
||||
max-inline-size: min(105ch, 100%);
|
||||
}
|
||||
|
||||
.events__day-header {
|
||||
background-color: var(--color-bg);
|
||||
background-color: var(--color-canvas);
|
||||
block-size: var(--grid-lines);
|
||||
display: block;
|
||||
position: relative;
|
||||
@@ -25,7 +24,7 @@
|
||||
}
|
||||
|
||||
.events__day-header-content {
|
||||
background-color: var(--color-bg);
|
||||
background-color: var(--color-canvas);
|
||||
display: inline-flex;
|
||||
font-size: 0.8em;
|
||||
inset: calc(var(--grid-lines) - 1em) auto -1em;
|
||||
@@ -55,22 +54,13 @@
|
||||
|
||||
.events--none {
|
||||
background-color: var(--color-container);
|
||||
border-block-start: var(--grid-lines) solid var(--color-bg);
|
||||
border-block-start: var(--grid-lines) solid var(--color-canvas);
|
||||
padding-block: 3em;
|
||||
margin: auto var(--grid-lines) calc(var(--grid-lines) * -1) auto;
|
||||
}
|
||||
|
||||
.events__popup {
|
||||
.panel:is(&) {
|
||||
max-block-size: calc(100dvh - (2 * var(--block-space-double)));
|
||||
max-inline-size: calc(100dvw - (2 * var(--block-space-double)));
|
||||
}
|
||||
|
||||
#header:has(&) {
|
||||
max-inline-size: 100%;
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
}
|
||||
margin-block-start: var(--block-space-double);
|
||||
}
|
||||
|
||||
.event {
|
||||
@@ -93,6 +83,12 @@
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.event__collection {
|
||||
font-size: var(--text-x-small);
|
||||
font-weight: 500;
|
||||
opacity: 0.66;
|
||||
}
|
||||
|
||||
.event__grid-item {
|
||||
background-color: var(--color-container);
|
||||
block-size: 100%;
|
||||
|
||||
@@ -107,6 +107,6 @@
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
background-color: oklch(var(--lch-white) / 15%);
|
||||
background-color: oklch(var(--lch-canvas) / 15%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
block-size: 100%;
|
||||
border-block: 0;
|
||||
border-inline-end: 0;
|
||||
border-inline-start: 1px solid var(--color-subtle);
|
||||
border-inline-start: 1px solid var(--color-ink-lighter);
|
||||
content: "";
|
||||
display: inline-flex;
|
||||
inline-size: 0;
|
||||
@@ -107,3 +107,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter--active li {
|
||||
display: none;
|
||||
|
||||
&.selected {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
animation: appear-then-fade 3s 300ms both;
|
||||
background-color: var(--flash-background, var(--color-ink));
|
||||
border-radius: 4em;
|
||||
color: var(--flash-color, var(--color-ink-reversed));
|
||||
color: var(--flash-color, var(--color-ink-inverted));
|
||||
display: inline-flex;
|
||||
font-size: var(--font-size-medium);
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
@layer components {
|
||||
/* Editor */
|
||||
house-md {
|
||||
--placeholder: attr(placeholder);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
@@ -13,7 +15,7 @@
|
||||
/* Toolbar */
|
||||
house-md-toolbar {
|
||||
background-color: inherit;
|
||||
border-block-end: 1px solid var(--color-subtle-dark);
|
||||
border-block-end: 1px solid var(--color-ink-medium);
|
||||
border-radius: 0;
|
||||
color: currentColor;
|
||||
display: inline-flex;
|
||||
@@ -47,7 +49,7 @@
|
||||
}
|
||||
|
||||
&:is(:focus, :hover) {
|
||||
background-color: var(--color-subtle);
|
||||
background-color: var(--color-ink-lighter);
|
||||
}
|
||||
|
||||
&:is(:active) {
|
||||
@@ -58,7 +60,6 @@
|
||||
|
||||
/* Markdown Content */
|
||||
.house-md-content {
|
||||
caret-color: var(--color-link);
|
||||
flex-grow: 1;
|
||||
font-family: var(--font-sans);
|
||||
text-align: left;
|
||||
@@ -66,8 +67,8 @@
|
||||
padding: 1rem;
|
||||
|
||||
&.house-md-content-empty::before {
|
||||
content: attr(placeholder);
|
||||
color: var(--color-ink);
|
||||
content: var(--placeholder);
|
||||
color: currentColor;
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
@@ -93,7 +94,7 @@
|
||||
|
||||
&[status="failed"] {
|
||||
background-color: var(--color-negative);
|
||||
color: var(--color-ink-reversed);
|
||||
color: var(--color-ink-inverted);
|
||||
font-weight: bold;
|
||||
padding: 0.5em;
|
||||
}
|
||||
@@ -112,13 +113,13 @@
|
||||
cursor: pointer;
|
||||
|
||||
house-md-upload[status="failed"] & {
|
||||
color: var(--color-ink-reversed);
|
||||
color: var(--color-ink-inverted);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '×';
|
||||
color: var(--color-ink-reversed);
|
||||
color: var(--color-ink-inverted);
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
@@ -139,7 +140,7 @@
|
||||
.md-progress-bar {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background-color: var(--color-subtle);
|
||||
background-color: var(--color-ink-lighter);
|
||||
border-radius: 0.5em;
|
||||
block-size: 1ex;
|
||||
display: block;
|
||||
@@ -154,7 +155,7 @@
|
||||
|
||||
&::-webkit-progress-bar {
|
||||
border-radius: 0.5em;
|
||||
background-color: var(--color-subtle);
|
||||
background-color: var(--color-ink-lighter);
|
||||
}
|
||||
|
||||
&::-webkit-progress-inner-element {
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
.icon--password { --svg: url("password.svg "); }
|
||||
.icon--pencil { --svg: url("pencil.svg "); }
|
||||
.icon--person { --svg: url("person.svg "); }
|
||||
.icon--person-add { --svg: url("person-add.svg "); }
|
||||
.icon--picture-add { --svg: url("picture-add.svg "); }
|
||||
.icon--picture-double { --svg: url("picture-double.svg "); }
|
||||
.icon--picture-remove { --svg: url("picture-remove.svg "); }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
accent-color: var(--input-accent-color, var(--color-ink));
|
||||
background-color: var(--input-background, transparent);
|
||||
border-radius: var(--input-border-radius, 0.5em);
|
||||
border: var(--input-border-size, 1px) solid var(--input-border-color, var(--color-subtle-dark));
|
||||
border: var(--input-border-size, 1px) solid var(--input-border-color, var(--color-ink-medium));
|
||||
color: var(--input-color, var(--color-ink));
|
||||
font-size: max(16px, 1em);
|
||||
inline-size: 100%;
|
||||
@@ -94,6 +94,11 @@
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 19.5c-.7 0-1.3-.3-1.7-.8l-9.8-11.1c-.7-.8-.6-1.9.2-2.6.8-.6 1.9-.6 2.5.2l8.6 9.8c0 .1.2.1.4 0l8.6-9.8c.7-.8 1.8-.9 2.6-.2s.9 1.8.2 2.6l-9.8 11.1c-.4.5-1.1.8-1.7.8z' fill='%23fff'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
option {
|
||||
background-color: var(--color-canvas);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
}
|
||||
|
||||
.input--textarea {
|
||||
@@ -135,7 +140,7 @@
|
||||
&:has(:focus-visible) {
|
||||
.switch__btn {
|
||||
box-shadow:
|
||||
0 0 0 var(--outline-size) var(--color-bg),
|
||||
0 0 0 var(--outline-size) var(--color-canvas),
|
||||
0 0 0 calc(var(--outline-size) * 2) var(--color-ink);
|
||||
}
|
||||
}
|
||||
@@ -148,7 +153,7 @@
|
||||
}
|
||||
|
||||
.switch__btn {
|
||||
background-color: var(--color-subtle-dark);
|
||||
background-color: var(--color-ink-medium);
|
||||
border-radius: 2em;
|
||||
cursor: pointer;
|
||||
inset: 0;
|
||||
@@ -156,7 +161,7 @@
|
||||
transition: 150ms ease;
|
||||
|
||||
&::before {
|
||||
background-color: var(--color-ink-reversed);
|
||||
background-color: var(--color-ink-inverted);
|
||||
block-size: 1.35em;
|
||||
border-radius: 50%;
|
||||
content: "";
|
||||
@@ -168,7 +173,7 @@
|
||||
}
|
||||
|
||||
.switch__input:disabled + & {
|
||||
background-color: var(--color-subtle-dark) !important;
|
||||
background-color: var(--color-ink-medium) !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
}
|
||||
|
||||
a:not([class]) {
|
||||
background-color: var(--color-subtle-light);
|
||||
background-color: var(--color-ink-lightest);
|
||||
border-radius: 2em;
|
||||
color: var(--color-ink);
|
||||
font-weight: 500;
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
background-color: var(--color-subtle);
|
||||
background-color: var(--color-ink-lighter);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.lightbox {
|
||||
--backdrop-speed: 150ms;
|
||||
|
||||
background-color: oklch(var(--lch-white) / 0.66);
|
||||
background-color: oklch(var(--lch-canvas) / 0.66);
|
||||
block-size: 100dvh;
|
||||
border: 0;
|
||||
inline-size: 100dvw;
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
@layer components {
|
||||
.comments {
|
||||
.markdown-content {
|
||||
:where(h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6) {
|
||||
font-weight: 800;
|
||||
hyphens: auto;
|
||||
@@ -27,7 +27,7 @@
|
||||
}
|
||||
|
||||
:where(hr) {
|
||||
border-color: var(--color-subtle-dark);
|
||||
border-color: var(--color-ink-medium);
|
||||
border-style: var(--border-style, solid) none none;
|
||||
margin: 2lh auto;
|
||||
}
|
||||
@@ -37,8 +37,8 @@
|
||||
}
|
||||
|
||||
:is(pre, code, .pre, .code) {
|
||||
background-color: var(--color-subtle-light);
|
||||
border: 1px solid var(--color-subtle);
|
||||
background-color: var(--color-ink-lightest);
|
||||
border: 1px solid var(--color-ink-lighter);
|
||||
border-radius: 0.3em;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.85em;
|
||||
@@ -80,7 +80,7 @@
|
||||
}
|
||||
|
||||
:where(table, th, td) {
|
||||
border: 1px solid var(--color-subtle-dark);
|
||||
border: 1px solid var(--color-ink-medium);
|
||||
}
|
||||
|
||||
:where(table) {
|
||||
@@ -46,11 +46,11 @@
|
||||
padding: var(--padding);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
--border-color: var(--color-subtle-dark);
|
||||
--border-color: var(--color-ink-medium);
|
||||
}
|
||||
|
||||
.notificiations-list--read & {
|
||||
--border-color: var(--color-subtle);
|
||||
--border-color: var(--color-ink-lighter);
|
||||
|
||||
box-shadow: none !important;
|
||||
}
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||
.notification__unread_indicator {
|
||||
--btn-background: var(--color-marker);
|
||||
--btn-border-color: var(--color-bg);
|
||||
--btn-border-color: var(--color-canvas);
|
||||
--btn-icon-size: 0.7em;
|
||||
--btn-padding: 0;
|
||||
--btn-size: 1.2em;
|
||||
@@ -78,8 +78,8 @@
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
--btn-background: var(--color-subtle-dark);
|
||||
--btn-border-color: var(--color-subtle-dark);
|
||||
--btn-background: var(--color-ink-medium);
|
||||
--btn-border-color: var(--color-ink-medium);
|
||||
|
||||
scale: 1;
|
||||
|
||||
@@ -94,3 +94,17 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notifications__empty-message {
|
||||
.notificiations-list:has(.notification) & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.notificiations-list--read {
|
||||
display: none;
|
||||
|
||||
&:has(.notification) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
@layer components {
|
||||
.panel {
|
||||
background-color: var(--color-bg);
|
||||
border: var(--panel-border-size, 1px) solid var(--panel-border-color, var(--color-subtle));
|
||||
background-color: var(--color-canvas);
|
||||
border: var(--panel-border-size, 1px) solid var(--panel-border-color, var(--color-ink-lighter));
|
||||
border-radius: var(--panel-border-radius, 1em);
|
||||
color: var(--color-ink);
|
||||
inline-size: var(--panel-size, 60ch);
|
||||
max-inline-size: 100%;
|
||||
padding: var(--panel-padding, calc(var(--block-space) * 2));
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
--panel-border-color: var(--color-subtle-dark);
|
||||
.panels--two-up {
|
||||
--panel-size: auto;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--block-space-double);
|
||||
align-content: start;
|
||||
max-inline-size: min(100ch, 100%);
|
||||
margin: auto;
|
||||
|
||||
.panel {
|
||||
flex: 1 1 33%;
|
||||
min-inline-size: 36ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
--block-space-half: 0.5em;
|
||||
--text-xx-large: 2em;
|
||||
|
||||
border: 1px solid var(--color-bg);
|
||||
border: 1px solid var(--color-canvas);
|
||||
view-transition-name: unset !important;
|
||||
}
|
||||
|
||||
.card__collection {
|
||||
@@ -42,16 +43,18 @@
|
||||
padding-block-end: 0;
|
||||
}
|
||||
|
||||
.card__link {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.card__title {
|
||||
--lines: 1;
|
||||
|
||||
font-size: var(--text-small);
|
||||
font-weight: bold;
|
||||
min-block-size: 0;
|
||||
}
|
||||
|
||||
.card__title-link {
|
||||
--lines: 1;
|
||||
}
|
||||
|
||||
.card__stages {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@@ -1,41 +1,75 @@
|
||||
@layer components {
|
||||
.popup {
|
||||
--panel-border-radius: 0.5em;
|
||||
--panel-padding: 0.4em 0.5em 0.5em;
|
||||
--panel-padding: var(--block-space);
|
||||
--panel-size: auto;
|
||||
|
||||
inline-size: auto;
|
||||
inset: 0 auto auto 0;
|
||||
min-inline-size: 20ch;
|
||||
max-inline-size: 40ch;
|
||||
inset: 0 auto auto 50%;
|
||||
min-inline-size: min(25ch, 90vw);
|
||||
max-inline-size: min(50ch, 90vw);
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
z-index: 2;
|
||||
|
||||
&[open] {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#header:has(&) {
|
||||
max-inline-size: 100%;
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
|
||||
.popup {
|
||||
min-inline-size: 40ch;
|
||||
max-inline-size: 40ch;
|
||||
}
|
||||
}
|
||||
|
||||
.card & {
|
||||
min-inline-size: 25ch;
|
||||
max-inline-size: 25ch;
|
||||
}
|
||||
}
|
||||
|
||||
.popup__group {
|
||||
background: var(--color-ink-lightest);
|
||||
max-inline-size: 100%;
|
||||
padding-inline: var(--inline-space);
|
||||
|
||||
.btn {
|
||||
--btn-background: transparent;
|
||||
}
|
||||
|
||||
@media (any-hover: hover) {
|
||||
&:hover {
|
||||
background: var(--color-selected);
|
||||
}
|
||||
}
|
||||
|
||||
&:has(:focus-visible) {
|
||||
background: var(--color-selected-dark);
|
||||
|
||||
.popup__item {
|
||||
--outline-size: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup__item {
|
||||
--btn-background: var(--color-subtle-light);
|
||||
--btn-background: var(--color-ink-lightest);
|
||||
--btn-border-radius: 0.3em;
|
||||
--btn-border-size: 0;
|
||||
--btn-font-weight: 500;
|
||||
--btn-padding: var(--inline-space-half) var(--inline-space-half) var(--inline-space-half) var(--inline-space);
|
||||
--btn-padding: var(--inline-space-half) 0 var(--inline-space-half) var(--inline-space);
|
||||
--hover-size: 0;
|
||||
--outline-color: transparent;
|
||||
|
||||
flex: 1 1 auto;
|
||||
justify-content: start;
|
||||
|
||||
&::after {
|
||||
border-radius: var(--btn-border-radius);
|
||||
border: var(--btn-border-size) solid var(--btn-border-color, var(--color-subtle));
|
||||
content: "";
|
||||
display: block;
|
||||
inline-size: calc(100% - var(--btn-padding) * 2);
|
||||
block-size: calc(100% - var(--btn-padding) * 2);
|
||||
|
||||
}
|
||||
max-inline-size: 100%;
|
||||
text-align: start;
|
||||
|
||||
@media (any-hover: hover) {
|
||||
&:where(:not(:active):hover) {
|
||||
@@ -75,10 +109,67 @@
|
||||
}
|
||||
|
||||
.popup__list {
|
||||
max-block-size: 40dvh;
|
||||
overflow: auto;
|
||||
row-gap: 2px;
|
||||
}
|
||||
|
||||
.popup__new {
|
||||
--btn-icon-size: 1em;
|
||||
|
||||
.icon {
|
||||
margin-inline-end: 0.5em;
|
||||
}
|
||||
|
||||
padding-inline-start: var(--inline-space-half);
|
||||
}
|
||||
|
||||
.popup__title {
|
||||
font-weight: 800;
|
||||
}
|
||||
}
|
||||
|
||||
.popup--animated {
|
||||
--backdrop-speed: 150ms;
|
||||
--panel-size: max-content;
|
||||
--speed: 150ms;
|
||||
|
||||
opacity: 0;
|
||||
transform: scale(0.2) translateX(-50%);
|
||||
transform-origin: top left;
|
||||
transition:
|
||||
display var(--speed) allow-discrete,
|
||||
opacity var(--speed),
|
||||
overlay var(--speed) allow-discrete,
|
||||
transform var(--speed);
|
||||
|
||||
&::backdrop {
|
||||
background-color: var(--color-always-black);
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
transition:
|
||||
display var(--backdrop-speed) allow-discrete,
|
||||
opacity var(--backdrop-speed),
|
||||
overlay var(--backdrop-speed) allow-discrete;
|
||||
}
|
||||
|
||||
&[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@layer components {
|
||||
.reactions {
|
||||
--column-gap: 0.4ch;
|
||||
--reaction-border-color: var(--color-subtle);
|
||||
--reaction-border-color: var(--color-ink-lighter);
|
||||
--row-gap: 0;
|
||||
|
||||
margin-block-start: calc(var(--block-space-half) / 2);
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
@media (any-hover: hover) {
|
||||
&:where(:not(:active):hover) {
|
||||
--reaction-border-color: var(--color-subtle-dark);
|
||||
--reaction-border-color: var(--color-ink-medium);
|
||||
|
||||
box-shadow: 0 0 0 var(--hover-size) var(--hover-color);
|
||||
}
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
@media (any-hover: hover) {
|
||||
&:where(:not(:active):hover) {
|
||||
--reaction-border-color: var(--color-subtle-dark);
|
||||
--reaction-border-color: var(--color-ink-medium);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
}
|
||||
|
||||
.gr {
|
||||
color: var(--color-subtle-light);
|
||||
color: var(--color-ink-lightest);
|
||||
}
|
||||
|
||||
.gd {
|
||||
@@ -83,7 +83,7 @@
|
||||
}
|
||||
|
||||
.err {
|
||||
color: var(--color-ink-reversed);
|
||||
color: var(--color-ink-inverted);
|
||||
background-color: var(--markup-deleted);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,9 @@
|
||||
}
|
||||
|
||||
.tag-picker__button {
|
||||
font-size: 0.7em;
|
||||
margin-inline-start: 0.5em;
|
||||
font-size: 0.6em;
|
||||
margin-block-start: 0.1em;
|
||||
margin-inline-start: 0.2em;
|
||||
|
||||
+ .panel {
|
||||
--panel-size: 18ch;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
.terminal {
|
||||
--row-gap: 0.2lh;
|
||||
|
||||
background-color: var(--color-ink);
|
||||
color: var(--color-positive);
|
||||
background-color: var(--color-terminal-bg);
|
||||
color: var(--color-terminal-text);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-normal);
|
||||
inset: auto 0 0 0;
|
||||
@@ -12,29 +12,29 @@
|
||||
z-index: 1;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: var(--color-always-black);
|
||||
border-block-start: 1px solid var(--color-subtle-dark);
|
||||
background-color: var(--color-black);
|
||||
border-block-start: 1px solid var(--color-ink-lighter);
|
||||
}
|
||||
|
||||
.terminal__input {
|
||||
--input-border-color: transparent;
|
||||
--input-border-radius: 0.3em;
|
||||
--hover-color: transparent;
|
||||
--input-color: var(--color-positive);
|
||||
--input-color: var(--color-terminal-text);
|
||||
--outline-size: 0;
|
||||
--outline-color: transparent;
|
||||
|
||||
accent-color: var(--color-positive);
|
||||
caret-color: var(--color-positive);
|
||||
accent-color: var(--color-terminal-text);
|
||||
caret-color: var(--color-terminal-text);
|
||||
font-family: inherit;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-always-white);
|
||||
color: var(--color-white);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.terminal__link & {
|
||||
color: var(--color-always-white);
|
||||
color: var(--color-white);
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@
|
||||
.terminal__link {
|
||||
--hover-size: 0;
|
||||
|
||||
color: var(--color-positive);
|
||||
color: var(--color-terminal-text);
|
||||
}
|
||||
|
||||
.terminal__menu {
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
@layer components {
|
||||
.tray {
|
||||
--height: calc(2.48lh + var(--block-space));
|
||||
/* FIXME: Magic number */
|
||||
--height: calc(2.625lh + var(--block-space));
|
||||
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
@@ -61,11 +62,11 @@
|
||||
|
||||
.tray__actions {
|
||||
--border-style: dashed;
|
||||
--border-color: color-mix(in srgb, var(--color-always-white) 40%, var(--color-ink));
|
||||
--border-color: color-mix(in srgb, var(--color-white) 40%, var(--color-ink));
|
||||
--hover-size: 0;
|
||||
|
||||
align-items: center;
|
||||
color: var(--color-ink-reversed);
|
||||
color: var(--color-ink-inverted);
|
||||
display: flex;
|
||||
gap: var(--inline-space-half);
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.users-list {
|
||||
max-block-size: 40dvh;
|
||||
overflow: auto;
|
||||
}
|
||||
@@ -14,15 +14,16 @@
|
||||
.txt-align-end { text-align: end; }
|
||||
|
||||
.txt-ink { color: var(--color-ink); }
|
||||
.txt-reversed { color: var(--color-ink-reversed); }
|
||||
.txt-reversed { color: var(--color-ink-inverted); }
|
||||
.txt-negative { color: var(--color-negative); }
|
||||
.txt-subtle { color: var(--color-subtle-dark); }
|
||||
.txt-subtle { color: var(--color-ink-medium); }
|
||||
.txt-alert { color: var(--color-marker); }
|
||||
.txt-undecorated { text-decoration: none; }
|
||||
.txt-tight-lines { line-height: 1.2; }
|
||||
.txt-nowrap { white-space: nowrap; }
|
||||
.txt-break { word-break: break-word; }
|
||||
.txt-uppercase { text-transform: uppercase; }
|
||||
.txt-capitalize { text-transform: capitalize; }
|
||||
.txt-link { color: var(--color-link); text-decoration: underline; }
|
||||
|
||||
/* Flexbox and Grid */
|
||||
@@ -107,6 +108,7 @@
|
||||
.pad-inline-double { padding-inline: var(--inline-space-double); }
|
||||
|
||||
.unpad { padding: 0; }
|
||||
.unpad-block-end { padding-block-end: 0; }
|
||||
.unpad-inline { padding-inline: 0; }
|
||||
|
||||
/* Margins */
|
||||
@@ -146,10 +148,10 @@
|
||||
.position-sticky { position: sticky; inset: var(--inset, 0 auto auto auto); z-index: var(--z, 1); }
|
||||
|
||||
/* Fills */
|
||||
.fill { background-color: var(--color-bg); }
|
||||
.fill { background-color: var(--color-canvas); }
|
||||
.fill-black { background-color: var(--color-ink); }
|
||||
.fill-white { background-color: var(--color-ink-reversed); }
|
||||
.fill-shade { background-color: var(--color-subtle-light); }
|
||||
.fill-white { background-color: var(--color-ink-inverted); }
|
||||
.fill-shade { background-color: var(--color-ink-lightest); }
|
||||
.fill-selected { background-color: var(--color-selected); }
|
||||
.fill-highlight { background-color: var(--color-highlight); }
|
||||
.fill-transparent { background-color: transparent; }
|
||||
@@ -157,10 +159,10 @@
|
||||
.translucent { opacity: var(--opacity, 0.66); }
|
||||
|
||||
/* Borders */
|
||||
.border { border: var(--border-size, 1px) var(--border-style, solid) var(--border-color, var(--color-subtle)); }
|
||||
.border-block { border-block: var(--border-size, 1px) var(--border-style, solid) var(--border-color, var(--color-subtle)); }
|
||||
.border-bottom { border-block-end: var(--border-size, 1px) var(--border-style, solid) var(--border-color, var(--color-subtle)); }
|
||||
.border-top { border-block-start: var(--border-size, 1px) var(--border-style, solid) var(--border-color, var(--color-subtle)); }
|
||||
.border { border: var(--border-size, 1px) var(--border-style, solid) var(--border-color, var(--color-ink-lighter)); }
|
||||
.border-block { border-block: var(--border-size, 1px) var(--border-style, solid) var(--border-color, var(--color-ink-lighter)); }
|
||||
.border-bottom { border-block-end: var(--border-size, 1px) var(--border-style, solid) var(--border-color, var(--color-ink-lighter)); }
|
||||
.border-top { border-block-start: var(--border-size, 1px) var(--border-style, solid) var(--border-color, var(--color-ink-lighter)); }
|
||||
.borderless { border: 0; }
|
||||
|
||||
/* Border radius */
|
||||
|
||||
@@ -20,20 +20,53 @@
|
||||
.workflow-stage--current {
|
||||
--btn-background: var(--card-color);
|
||||
|
||||
color: var(--color-ink-reversed);
|
||||
color: var(--color-ink-inverted);
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
background-color: color(from var(--card-color) srgb r g b / 0.65);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ticket & {
|
||||
--btn-background: var(--color-ink);
|
||||
.workflow-preview {
|
||||
--border-size: 3px;
|
||||
|
||||
background: var(--card-color);
|
||||
border-radius: 0.2em;
|
||||
color: var(--color-ink-reversed);
|
||||
aspect-ratio: 1;
|
||||
flex: 1 1 30%;
|
||||
max-inline-size: 33%;
|
||||
|
||||
button {
|
||||
--hover-size: 0;
|
||||
|
||||
appearance: none;
|
||||
aspect-ratio: 1;
|
||||
background: transparent;
|
||||
block-size: 100%;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
inline-size: 100%;
|
||||
margin: 0;
|
||||
max-inline-size: 100%;
|
||||
outline: 0;
|
||||
padding: 1em;
|
||||
text-align: start;
|
||||
}
|
||||
}
|
||||
|
||||
.workflow-preview--selected {
|
||||
--border-color: var(--color-link);
|
||||
}
|
||||
|
||||
.workflow-preview__swatch {
|
||||
--btn-size: 1em;
|
||||
--btn-border-radius: 50%;
|
||||
--btn-border-size: 0;
|
||||
--hover-size: 0;
|
||||
|
||||
cursor: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,6 @@ class Cards::AssignmentsController < ApplicationController
|
||||
|
||||
def create
|
||||
@card.toggle_assignment @collection.users.active.find(params[:assignee_id])
|
||||
render_card_replacement
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,11 +3,11 @@ class Cards::ClosuresController < ApplicationController
|
||||
|
||||
def create
|
||||
@card.close(user: Current.user, reason: params[:reason])
|
||||
redirect_to @card
|
||||
render_card_replacement
|
||||
end
|
||||
|
||||
def destroy
|
||||
@card.reopen
|
||||
redirect_to @card
|
||||
render_card_replacement
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,7 +25,7 @@ class Cards::Comments::ReactionsController < ApplicationController
|
||||
|
||||
private
|
||||
def set_comment
|
||||
@comment = Comment.belonging_to_card(@card).find(params[:comment_id])
|
||||
@comment = @card.comments.find(params[:comment_id])
|
||||
end
|
||||
|
||||
def broadcast_create(reaction)
|
||||
|
||||
@@ -5,7 +5,7 @@ class Cards::CommentsController < ApplicationController
|
||||
before_action :ensure_creatorship, only: %i[ edit update destroy ]
|
||||
|
||||
def create
|
||||
@card.capture Comment.new(comment_params)
|
||||
@card.comments.create!(comment_params)
|
||||
end
|
||||
|
||||
def show
|
||||
@@ -25,7 +25,7 @@ class Cards::CommentsController < ApplicationController
|
||||
|
||||
private
|
||||
def set_comment
|
||||
@comment = Comment.belonging_to_card(@card).find(params[:id])
|
||||
@comment = @card.comments.find(params[:id])
|
||||
end
|
||||
|
||||
def ensure_creatorship
|
||||
|
||||
@@ -3,11 +3,11 @@ class Cards::EngagementsController < ApplicationController
|
||||
|
||||
def create
|
||||
@card.engage
|
||||
redirect_to @card
|
||||
render_card_replacement
|
||||
end
|
||||
|
||||
def destroy
|
||||
@card.reconsider
|
||||
redirect_to @card
|
||||
render_card_replacement
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,11 +3,11 @@ class Cards::GoldnessesController < ApplicationController
|
||||
|
||||
def create
|
||||
@card.gild
|
||||
redirect_to @card
|
||||
render_card_replacement
|
||||
end
|
||||
|
||||
def destroy
|
||||
@card.ungild
|
||||
redirect_to @card
|
||||
render_card_replacement
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,6 +3,6 @@ class Cards::ImagesController < ApplicationController
|
||||
|
||||
def destroy
|
||||
@card.image.purge_later
|
||||
redirect_to @card
|
||||
render_card_replacement
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,25 +5,29 @@ class Cards::PinsController < ApplicationController
|
||||
end
|
||||
|
||||
def create
|
||||
pin = @card.pin_by Current.user
|
||||
@pin = @card.pin_by Current.user
|
||||
|
||||
broadcast_my_new pin
|
||||
redirect_to card_pin_path(@card)
|
||||
broadcast_add_pin_to_tray
|
||||
render_pin_button_replacement
|
||||
end
|
||||
|
||||
def destroy
|
||||
pin = @card.unpin_by Current.user
|
||||
@pin = @card.unpin_by Current.user
|
||||
|
||||
broadcast_my_removed pin
|
||||
redirect_to card_pin_path(@card)
|
||||
broadcast_remove_pin_from_tray
|
||||
render_pin_button_replacement
|
||||
end
|
||||
|
||||
private
|
||||
def broadcast_my_new(pin)
|
||||
pin.card.broadcast_prepend_later_to [ Current.user, :pins ], target: "pins", partial: "cards/display/preview"
|
||||
def broadcast_add_pin_to_tray
|
||||
@pin.broadcast_prepend_to [ Current.user, :pins_tray ], target: "pins", partial: "my/pins/pin"
|
||||
end
|
||||
|
||||
def broadcast_my_removed(pin)
|
||||
pin.broadcast_remove_to [ Current.user, :pins ]
|
||||
def broadcast_remove_pin_from_tray
|
||||
@pin.broadcast_remove_to [ Current.user, :pins_tray ]
|
||||
end
|
||||
|
||||
def render_pin_button_replacement
|
||||
render turbo_stream: turbo_stream.replace([ @card, :pin_button ], partial: "cards/pins/pin_button", locals: { card: @card })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,12 +2,6 @@ class Cards::ReadingsController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
def create
|
||||
mark_card_notifications_read
|
||||
@notifications = Current.user.notifications.unread.ordered.limit(20)
|
||||
@notifications = @card.read_by(Current.user)
|
||||
end
|
||||
|
||||
private
|
||||
def mark_card_notifications_read
|
||||
Current.user.notifications.unread.where(card: @card).read_all
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
class Cards::StagingsController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
before_action :set_stage
|
||||
|
||||
def create
|
||||
if @stage
|
||||
@card.toggle_stage @stage
|
||||
else
|
||||
@card.update!(stage: nil)
|
||||
end
|
||||
def update
|
||||
@card.change_stage_to @collection.workflow.stages.find(params[:stage_id])
|
||||
render_card_replacement
|
||||
end
|
||||
|
||||
private
|
||||
def set_stage
|
||||
@stage = Workflow::Stage.find_by(id: params[:stage_id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,6 +7,7 @@ class Cards::TaggingsController < ApplicationController
|
||||
|
||||
def create
|
||||
@card.toggle_tag_with sanitized_tag_title_param
|
||||
render_card_replacement
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -25,16 +25,21 @@ class CardsController < ApplicationController
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
@card.update! card_params
|
||||
|
||||
if @card.published?
|
||||
render_card_replacement
|
||||
else
|
||||
redirect_to @card
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@card.destroy!
|
||||
redirect_to cards_path(collection_ids: [ @card.collection ]), notice: ("Card deleted" unless @card.creating?)
|
||||
end
|
||||
|
||||
def update
|
||||
@card.update! card_params
|
||||
redirect_to @card
|
||||
end
|
||||
|
||||
private
|
||||
def set_card
|
||||
@card = @collection.cards.find params[:id]
|
||||
@@ -49,6 +54,10 @@ class CardsController < ApplicationController
|
||||
end
|
||||
|
||||
def card_params
|
||||
params.expect(card: [ :status, :title, :color, :due_on, :image, :draft_comment, tag_ids: [] ])
|
||||
params.expect(card: [ :status, :title, :description, :image, tag_ids: [] ])
|
||||
end
|
||||
|
||||
def render_card_replacement
|
||||
render turbo_stream: turbo_stream.replace([ @card, :card_container ], partial: "cards/container", locals: { card: @card.reload })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,6 +10,6 @@ class Collections::WorkflowsController < ApplicationController
|
||||
|
||||
private
|
||||
def set_workflow
|
||||
@workflow = Workflow.find(params.expect(collection: [ :workflow_id ]).require(:workflow_id))
|
||||
@workflow = Workflow.find(params[:collection][:workflow_id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,7 +6,7 @@ class CollectionsController < ApplicationController
|
||||
end
|
||||
|
||||
def create
|
||||
@collection = Collection.create! collection_params
|
||||
@collection = Collection.create! collection_params.with_defaults(all_access: true)
|
||||
redirect_to cards_path(collection_ids: [ @collection ])
|
||||
end
|
||||
|
||||
@@ -19,7 +19,7 @@ class CollectionsController < ApplicationController
|
||||
@collection.update! collection_params
|
||||
@collection.accesses.revise granted: grantees, revoked: revokees
|
||||
|
||||
redirect_to cards_path(collection_ids: [ @collection ])
|
||||
redirect_to edit_collection_path(@collection), notice: "Collection updated"
|
||||
end
|
||||
|
||||
def destroy
|
||||
@@ -33,7 +33,7 @@ class CollectionsController < ApplicationController
|
||||
end
|
||||
|
||||
def collection_params
|
||||
params.expect(collection: [ :name, :all_access ]).with_defaults(all_access: true)
|
||||
params.expect(collection: [ :name, :all_access, :auto_close_period ])
|
||||
end
|
||||
|
||||
def grantees
|
||||
|
||||
@@ -13,4 +13,8 @@ module CardScoped
|
||||
def set_collection
|
||||
@collection = @card.collection
|
||||
end
|
||||
|
||||
def render_card_replacement
|
||||
render turbo_stream: turbo_stream.replace([ @card, :card_container ], partial: "cards/container", method: :morph, locals: { card: @card.reload })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
module CollectionFilterable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :set_collection_filter
|
||||
end
|
||||
|
||||
private
|
||||
def set_collection_filter
|
||||
params[:collection_ids] ||= cookies[:collection_filter]&.split(",") unless params[:clear_filter]
|
||||
end
|
||||
|
||||
def collection_filter
|
||||
params[:collection_ids].presence || Current.user.collection_ids
|
||||
end
|
||||
|
||||
def update_collection_filter
|
||||
if params[:clear_filter]
|
||||
cookies.delete(:collection_filter)
|
||||
elsif params[:collection_ids].present?
|
||||
cookies[:collection_filter] = params[:collection_ids].join(",")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,48 @@
|
||||
module DayTimelinesScoped
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include FilterScoped
|
||||
|
||||
before_action :restore_collections_filter_from_cookie
|
||||
before_action :set_day_timeline
|
||||
after_action :save_collections_filter_to_cookie
|
||||
end
|
||||
|
||||
private
|
||||
def restore_collections_filter_from_cookie
|
||||
if params[:clear_filter]
|
||||
delete_collections_filter_cookie
|
||||
else
|
||||
set_collections_filter_from_cookie
|
||||
end
|
||||
end
|
||||
|
||||
def delete_collections_filter_cookie
|
||||
cookies.delete(:collection_filter)
|
||||
end
|
||||
|
||||
def set_collections_filter_from_cookie
|
||||
if cookies[:collection_filter].present? && @filter.collections.blank?
|
||||
@filter.collection_ids = cookies[:collection_filter].split(",")
|
||||
end
|
||||
end
|
||||
|
||||
def set_day_timeline
|
||||
@day_timeline = Current.user.timeline_for(day, filter: @filter)
|
||||
end
|
||||
|
||||
def save_collections_filter_to_cookie
|
||||
cookies[:collection_filter] = @filter.collection_ids.join(",")
|
||||
end
|
||||
|
||||
def day
|
||||
if params[:day].present?
|
||||
Time.zone.parse(params[:day])
|
||||
else
|
||||
Time.current
|
||||
end
|
||||
rescue ArgumentError
|
||||
head :not_found
|
||||
end
|
||||
end
|
||||
@@ -1,40 +0,0 @@
|
||||
module EventsTimeline
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include CollectionFilterable
|
||||
|
||||
included do
|
||||
before_action :set_activity_day
|
||||
end
|
||||
|
||||
private
|
||||
def set_activity_day
|
||||
@activity_day = if params[:day].present?
|
||||
Time.zone.parse(params[:day])
|
||||
else
|
||||
Time.zone.now
|
||||
end
|
||||
rescue ArgumentError
|
||||
raise ActionController::RoutingError
|
||||
end
|
||||
|
||||
def events_for_activity_day
|
||||
user_events.where(created_at: @activity_day.all_day)
|
||||
.group_by { |event| [ event.created_at.hour, helpers.event_column(event) ] }
|
||||
.map { |hour_col, events| [ hour_col, events.uniq { |e| e.id } ] }
|
||||
end
|
||||
|
||||
def latest_event_before_activity_day
|
||||
user_events.where(created_at: ...@activity_day.beginning_of_day).chronologically.last
|
||||
end
|
||||
|
||||
def user_events
|
||||
Event.where(card: user_cards)
|
||||
end
|
||||
|
||||
def user_cards
|
||||
Current.user.accessible_cards
|
||||
.published_or_drafted_by(Current.user)
|
||||
.where(collection_id: collection_filter)
|
||||
end
|
||||
end
|
||||
@@ -1,8 +1,6 @@
|
||||
class Events::DaysController < ApplicationController
|
||||
include EventsTimeline
|
||||
include DayTimelinesScoped
|
||||
|
||||
def index
|
||||
@events = events_for_activity_day
|
||||
@next_day = latest_event_before_activity_day&.created_at
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
class EventsController < ApplicationController
|
||||
include EventsTimeline
|
||||
include DayTimelinesScoped
|
||||
|
||||
def index
|
||||
update_collection_filter
|
||||
|
||||
@collections = Current.user.collections.alphabetically
|
||||
@filters = Current.user.filters.all
|
||||
|
||||
@events = events_for_activity_day
|
||||
@next_day = latest_event_before_activity_day&.created_at
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
class Notifications::MarkAllReadsController < ApplicationController
|
||||
def create
|
||||
Current.user.notifications.unread.read_all
|
||||
|
||||
set_page_and_extract_portion_from Current.user.notifications.read.ordered if request.format.turbo_stream?
|
||||
|
||||
Turbo::StreamsChannel.broadcast_update_to(
|
||||
[ Current.user, :notifications ],
|
||||
target: "notifications",
|
||||
html: ""
|
||||
)
|
||||
|
||||
respond_to do |format|
|
||||
format.turbo_stream
|
||||
format.html
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
class Notifications::ReadingsController < ApplicationController
|
||||
def create
|
||||
@notification = Current.user.notifications.find(params[:id])
|
||||
@notification.read
|
||||
end
|
||||
|
||||
def create_all
|
||||
Current.user.notifications.unread.read_all
|
||||
redirect_to notifications_path
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,5 @@
|
||||
module Notifications
|
||||
class SettingsController < ApplicationController
|
||||
def show
|
||||
@collections = Current.user.collections.alphabetically
|
||||
end
|
||||
class Notifications::SettingsController < ApplicationController
|
||||
def show
|
||||
@collections = Current.user.collections.alphabetically
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Notifications::TraysController < ApplicationController
|
||||
def show
|
||||
@notifications = Current.user.notifications.unread.ordered.limit(20)
|
||||
@notifications = Current.user.notifications.unread.ordered.limit(100)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
class NotificationsController < ApplicationController
|
||||
def index
|
||||
@unread = Current.user.notifications.unread.ordered unless current_page_param
|
||||
set_page_and_extract_portion_from Current.user.notifications.read.ordered
|
||||
|
||||
if @page.first?
|
||||
@unread = Current.user.notifications.unread.ordered
|
||||
end
|
||||
end
|
||||
|
||||
def mark_read
|
||||
@notification = Current.user.notifications.find(params[:id])
|
||||
@notification.update!(read_at: Time.current)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_back fallback_location: notifications_path }
|
||||
format.turbo_stream
|
||||
format.turbo_stream if current_page_param # Allows read-all action to side step pagination
|
||||
format.html
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class TerminalsController < ApplicationController
|
||||
def show
|
||||
@events = Event.where(card: Current.user.accessible_cards, creator: Current.user).chronologically.reverse_order.limit(20)
|
||||
@events = Current.user.accessible_events.chronologically.reverse_order.limit(20)
|
||||
end
|
||||
|
||||
def edit
|
||||
|
||||
@@ -2,8 +2,8 @@ class UsersController < ApplicationController
|
||||
require_unauthenticated_access only: %i[ new create ]
|
||||
|
||||
before_action :set_user, only: %i[ show edit update destroy ]
|
||||
before_action :set_account_from_join_code, only: %i[ new create ]
|
||||
before_action :ensure_permission_to_administer_user, only: %i[ update destroy ]
|
||||
before_action :ensure_join_code_is_valid, only: %i[ new create ]
|
||||
before_action :ensure_permission_to_change_user, only: %i[ update destroy ]
|
||||
|
||||
def index
|
||||
@users = User.active
|
||||
@@ -36,16 +36,16 @@ class UsersController < ApplicationController
|
||||
end
|
||||
|
||||
private
|
||||
def set_account_from_join_code
|
||||
@account = Account.find_by_join_code!(params[:join_code])
|
||||
def ensure_join_code_is_valid
|
||||
head :forbidden unless Account.sole.join_code == params[:join_code]
|
||||
end
|
||||
|
||||
def set_user
|
||||
@user = User.active.find(params[:id])
|
||||
end
|
||||
|
||||
def ensure_permission_to_administer_user
|
||||
head :forbidden unless Current.user.can_administer?(@user)
|
||||
def ensure_permission_to_change_user
|
||||
head :forbidden unless Current.user.can_change?(@user)
|
||||
end
|
||||
|
||||
def user_params
|
||||
|
||||
@@ -11,8 +11,6 @@ class WorkflowsController < ApplicationController
|
||||
|
||||
def create
|
||||
@workflow = Workflow.create! workflow_params
|
||||
# FIXME: this should definitely change.
|
||||
[ "Triage", "In progress", "On Hold", "Review" ].each { |name| @workflow.stages.create! name: name }
|
||||
redirect_to workflows_path
|
||||
end
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module AccessesHelper
|
||||
def access_menu_tag(collection, &)
|
||||
tag.menu class: [ "flex flex-column gap margin-none pad txt-medium", { "toggler--toggled": collection.all_access? } ], data: {
|
||||
tag.menu class: [ "flex flex-column gap-half unpad margin-none txt-medium", { "toggler--toggled": collection.all_access? } ], data: {
|
||||
controller: "filter toggle-class",
|
||||
filter_active_class: "filter--active", filter_selected_class: "selected",
|
||||
toggle_class_toggle_class: "toggler--toggled" }, &
|
||||
|
||||
@@ -15,11 +15,12 @@ module CardsHelper
|
||||
**options
|
||||
end
|
||||
|
||||
def card_article_tag(card, id: dom_id(card, :ticket), **options, &block)
|
||||
def card_article_tag(card, id: dom_id(card, :article), **options, &block)
|
||||
classes = [
|
||||
options.delete(:class),
|
||||
("card--golden" if card.golden?),
|
||||
("card--doing" if card.doing?)
|
||||
("card--doing" if card.doing?),
|
||||
("card--drafted" if card.drafted?)
|
||||
].compact.join(" ")
|
||||
|
||||
tag.article \
|
||||
@@ -29,4 +30,22 @@ module CardsHelper
|
||||
**options,
|
||||
&block
|
||||
end
|
||||
|
||||
def button_to_delete_card(card)
|
||||
button_to collection_card_path(card.collection, card),
|
||||
method: :delete, class: "btn txt-negative borderless txt-small", data: { turbo_frame: "_top", turbo_confirm: "Are you sure you want to permanently delete this card?" } do
|
||||
concat(icon_tag("trash"))
|
||||
concat(tag.span("Delete this card"))
|
||||
end
|
||||
end
|
||||
|
||||
def card_title_tag(card)
|
||||
title = [
|
||||
card.title,
|
||||
"added by #{card.creator.name}",
|
||||
"in #{card.collection.name}"
|
||||
]
|
||||
title << "assigned to #{card.assignees.map(&:name).to_sentence}" if card.assignees.any?
|
||||
title.join(" ")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
module CollectionsHelper
|
||||
def collection_auto_close_options
|
||||
[
|
||||
[ "30 days", 30.days ],
|
||||
[ "60 days", 60.days ],
|
||||
[ "90 days", 90.days ],
|
||||
[ "6 months", 180.days ],
|
||||
[ "1 year", 365.days ],
|
||||
[ "Never", nil ]
|
||||
]
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
module CommentsHelper
|
||||
def new_comment_placeholder(card)
|
||||
if card.creator == Current.user && card.messages.comments.empty?
|
||||
if card.creator == Current.user && card.comments.empty?
|
||||
"Next, add some notes, context, pictures, or video about this…"
|
||||
else
|
||||
"Type your comment…"
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
module EventsHelper
|
||||
def event_day_title(day)
|
||||
case
|
||||
when day.today?
|
||||
"Today"
|
||||
when day.yesterday?
|
||||
"Yesterday"
|
||||
else
|
||||
day.strftime("%A, %B %e")
|
||||
end
|
||||
end
|
||||
|
||||
def event_column(event)
|
||||
case event.action
|
||||
when "closed"
|
||||
when "card_closed"
|
||||
3
|
||||
when "published"
|
||||
when "card_published"
|
||||
1
|
||||
else
|
||||
2
|
||||
@@ -33,7 +22,7 @@ module EventsHelper
|
||||
end
|
||||
end
|
||||
|
||||
def render_event_grid_cells(day, columns: 4, rows: 24)
|
||||
def render_event_grid_cells(columns: 4, rows: 24)
|
||||
safe_join((2..rows + 1).map do |row|
|
||||
(1..columns).map do |col|
|
||||
tag.div class: class_names("event__grid-item"), style: "grid-area: #{row}/#{col};"
|
||||
@@ -41,19 +30,11 @@ module EventsHelper
|
||||
end.flatten)
|
||||
end
|
||||
|
||||
def render_column_headers(day = Date.current)
|
||||
start_time = day.beginning_of_day
|
||||
end_time = day.end_of_day
|
||||
|
||||
accessible_events = Event.joins(card: :collection)
|
||||
.merge(Current.user.collections)
|
||||
.where(created_at: start_time..end_time)
|
||||
.where(cards: { collection_id: params[:collection_ids].presence || Current.user.collection_ids })
|
||||
|
||||
def render_column_headers(day_timeline)
|
||||
headers = {
|
||||
"Added" => accessible_events.where(action: "published").count,
|
||||
"Added" => day_timeline.events.where(action: "card_published").count,
|
||||
"Updated" => nil,
|
||||
"Closed" => accessible_events.where(action: "closed").count
|
||||
"Closed" => day_timeline.events.where(action: "card_closed").count
|
||||
}
|
||||
|
||||
headers.map do |header, count|
|
||||
@@ -63,47 +44,64 @@ module EventsHelper
|
||||
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 <span style='color: var(--card-color)'>#{ event.eventable.card.title }</span>".html_safe
|
||||
end
|
||||
|
||||
def event_creator_name(event)
|
||||
event.creator == Current.user ? "You" : event.creator.name
|
||||
end
|
||||
|
||||
def card_event_action_sentence(event)
|
||||
card = event.eventable
|
||||
title = card.title
|
||||
|
||||
case event.action
|
||||
when "assigned"
|
||||
when "card_assigned"
|
||||
if event.assignees.include?(Current.user)
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } will handle <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
|
||||
"#{ event_creator_name(event) } will handle <span style='color: var(--card-color)'>#{ title }</span>".html_safe
|
||||
else
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } assigned #{ event.assignees.pluck(:name).to_sentence } to <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
|
||||
"#{ event_creator_name(event) } assigned #{ event.assignees.pluck(:name).to_sentence } to <span style='color: var(--card-color)'>#{ title }</span>".html_safe
|
||||
end
|
||||
when "unassigned"
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } unassigned #{ event.assignees.include?(Current.user) ? "yourself" : event.assignees.pluck(:name).to_sentence } from <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
|
||||
when "commented"
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } commented on <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
|
||||
when "published"
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } added <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
|
||||
when "closed"
|
||||
"#{ event.creator == Current.user ? "You" : event.creator.name } closed <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
|
||||
when "staged"
|
||||
"#{event.creator == Current.user ? "You" : event.creator.name} moved <span style='color: var(--card-color)'>#{ event.card.title }</span> to the #{event.stage_name} stage".html_safe
|
||||
when "unstaged"
|
||||
"#{event.creator == Current.user ? "You" : event.creator.name} moved <span style='color: var(--card-color)'>#{ event.card.title }</span> out ofthe #{event.stage_name} stage".html_safe
|
||||
when "due_date_added"
|
||||
"#{event.creator == Current.user ? "You" : event.creator.name} set the date to #{event.particulars.dig('particulars', 'due_date').to_date.strftime('%B %-d')} on <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
|
||||
when "due_date_changed"
|
||||
"#{event.creator == Current.user ? "You" : event.creator.name} changed the date to #{event.particulars.dig('particulars', 'due_date').to_date.strftime('%B %-d')} on <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
|
||||
when "due_date_removed"
|
||||
"#{event.creator == Current.user ? "You" : event.creator.name} removed the date on <span style='color: var(--card-color)'>#{ event.card.title }</span>"
|
||||
when "title_changed"
|
||||
"#{event.creator == Current.user ? "You" : event.creator.name} renamed <span style='color: var(--card-color)'>#{ event.card.title }</span> (was: '#{event.particulars.dig('particulars', 'old_title')})'".html_safe
|
||||
when "card_unassigned"
|
||||
"#{ event_creator_name(event) } unassigned #{ event.assignees.include?(Current.user) ? "yourself" : event.assignees.pluck(:name).to_sentence } from <span style='color: var(--card-color)'>#{ title }</span>".html_safe
|
||||
when "card_published"
|
||||
"#{ event_creator_name(event) } added <span style='color: var(--card-color)'>#{ title }</span>".html_safe
|
||||
when "card_closed"
|
||||
"#{ event_creator_name(event) } closed <span style='color: var(--card-color)'>#{ title }</span>".html_safe
|
||||
when "card_staged"
|
||||
"#{event_creator_name(event)} moved <span style='color: var(--card-color)'>#{ title }</span> to the #{event.stage_name} stage".html_safe
|
||||
when "card_unstaged"
|
||||
"#{event_creator_name(event)} moved <span style='color: var(--card-color)'>#{ title }</span> out ofthe #{event.stage_name} stage".html_safe
|
||||
when "card_due_date_added"
|
||||
"#{event_creator_name(event)} set the date to #{event.particulars.dig('particulars', 'due_date').to_date.strftime('%B %-d')} on <span style='color: var(--card-color)'>#{ title }</span>".html_safe
|
||||
when "card_due_date_changed"
|
||||
"#{event_creator_name(event)} changed the date to #{event.particulars.dig('particulars', 'due_date').to_date.strftime('%B %-d')} on <span style='color: var(--card-color)'>#{ title }</span>".html_safe
|
||||
when "card_due_date_removed"
|
||||
"#{event_creator_name(event)} removed the date on <span style='color: var(--card-color)'>#{ title }</span>"
|
||||
when "card_title_changed"
|
||||
"#{event_creator_name(event)} renamed <span style='color: var(--card-color)'>#{ title }</span> (was: '#{event.particulars.dig('particulars', 'old_title')})'".html_safe
|
||||
end
|
||||
end
|
||||
|
||||
def event_action_icon(event)
|
||||
case event.action
|
||||
when "assigned"
|
||||
when "card_assigned"
|
||||
"assigned"
|
||||
when "staged"
|
||||
when "card_staged"
|
||||
"bolt"
|
||||
when "unstaged"
|
||||
when "card_unstaged"
|
||||
"bolt"
|
||||
when "commented"
|
||||
when "comment_created"
|
||||
"comment"
|
||||
when "title_changed"
|
||||
when "card_title_changed"
|
||||
"rename"
|
||||
else
|
||||
"person"
|
||||
|
||||
@@ -21,9 +21,19 @@ module FiltersHelper
|
||||
hidden_field_tag name, value, id: nil
|
||||
end
|
||||
|
||||
def filter_selected_collections_label(filter)
|
||||
selected_collections = if filter.collections.any?
|
||||
filter.collections.collect { "<strong>#{it.name}</strong>" }.uniq.sort.to_sentence
|
||||
else
|
||||
"all collections"
|
||||
end
|
||||
|
||||
"Activity in #{selected_collections}".html_safe
|
||||
end
|
||||
|
||||
def any_filters?(filter)
|
||||
filter.tags.any? || filter.assignees.any? || filter.creators.any? ||
|
||||
filter.stages.any? || filter.terms.any? ||
|
||||
filter.assignment_status.unassigned? || !filter.default_indexed_by?
|
||||
filter.stages.any? || filter.terms.any? ||
|
||||
filter.assignment_status.unassigned?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
module NotificationsHelper
|
||||
def notification_title(notification)
|
||||
if notification.resource.is_a? Comment
|
||||
"RE: " + notification.card.title
|
||||
elsif notification_event_action(notification) == "assigned"
|
||||
"Assigned to you: " + notification.card.title
|
||||
else
|
||||
notification.card.title
|
||||
def event_notification_title(event)
|
||||
case event_notification_action(event)
|
||||
when "comment_created" then "RE: " + event.eventable.card.title
|
||||
when "card_assigned" then "Assigned to you: " + event.eventable.title
|
||||
else event.eventable.title
|
||||
end
|
||||
end
|
||||
|
||||
def notification_body(notification)
|
||||
name = notification.creator.name
|
||||
def event_notification_body(event)
|
||||
name = event.creator.name
|
||||
|
||||
case notification_event_action(notification)
|
||||
when "closed" then "Closed by #{name}"
|
||||
when "published" then "Added by #{name}"
|
||||
case event_notification_action(event)
|
||||
when "card_closed" then "Closed by #{name}"
|
||||
when "card_published" then "Added by #{name}"
|
||||
when "comment_created" then comment_notification_body(event)
|
||||
else name
|
||||
end
|
||||
end
|
||||
@@ -22,7 +21,7 @@ module NotificationsHelper
|
||||
def notification_tag(notification, &)
|
||||
tag.div id: dom_id(notification), class: "notification tray__item border-radius txt-normal" do
|
||||
concat(
|
||||
link_to(notification.resource,
|
||||
link_to(notification,
|
||||
class: "notification__content border-radius shadow fill-white flex align-start txt-align-start gap flex-item-grow max-width border txt-ink",
|
||||
data: { action: "click->dialog#close", turbo_frame: "_top" },
|
||||
&)
|
||||
@@ -32,12 +31,12 @@ module NotificationsHelper
|
||||
end
|
||||
|
||||
def notification_mark_read_button(notification)
|
||||
button_to mark_read_notification_path(notification),
|
||||
class: "notification__unread_indicator btn borderless",
|
||||
title: "Mark as read",
|
||||
data: { turbo_frame: "_top" } do
|
||||
concat(image_tag("remove-med.svg", class: "unread_icon", size: 12, aria: { hidden: true }))
|
||||
concat(tag.span("Mark as read", class: "for-screen-reader"))
|
||||
button_to read_notification_path(notification),
|
||||
class: "notification__unread_indicator btn borderless",
|
||||
title: "Mark as read",
|
||||
data: { turbo_frame: "_top" } do
|
||||
concat(image_tag("remove-med.svg", class: "unread_icon", size: 12, aria: { hidden: true }))
|
||||
concat(tag.span("Mark as read", class: "for-screen-reader"))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -48,15 +47,16 @@ module NotificationsHelper
|
||||
end
|
||||
|
||||
private
|
||||
def notification_event_action(notification)
|
||||
if notification_is_for_initial_assignement?(notification)
|
||||
"assigned"
|
||||
def event_notification_action(event)
|
||||
if event.action.card_published? && event.eventable.assigned_to?(event.creator)
|
||||
"card_assigned"
|
||||
else
|
||||
notification.event.action
|
||||
event.action
|
||||
end
|
||||
end
|
||||
|
||||
def notification_is_for_initial_assignement?(notification)
|
||||
notification.event.action == "published" && notification.card.assigned_to?(notification.user)
|
||||
def comment_notification_body(event)
|
||||
comment = event.eventable
|
||||
"#{strip_tags(comment.body_html).blank? ? "#{event.creator.name} replied" : "#{event.creator.name}:" } #{strip_tags(comment.body_html).truncate(200)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,10 +2,4 @@ module UsersHelper
|
||||
def prepend_current_user_to(users_scope)
|
||||
users_scope.to_a.prepend(Current.user).uniq
|
||||
end
|
||||
|
||||
def familiar_name_for(user)
|
||||
names = user.name.split
|
||||
return user.name if names.length == 1
|
||||
"#{names.first} #{names[1..].map { |n| "#{n[0]}." }.join}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
module WorkflowsHelper
|
||||
def button_to_set_stage(card, stage)
|
||||
button_to card_stagings_path(card, stage_id: stage),
|
||||
method: :post, class: [ "btn justify-start workflow-stage txt-uppercase workflow-stage", { "workflow-stage--current": stage == card.stage } ],
|
||||
form_class: "flex align-center gap-half",
|
||||
data: { turbo_frame: "_top" } do
|
||||
tag.span class: "overflow-ellipsis" do
|
||||
stage.name
|
||||
end
|
||||
end
|
||||
button_to \
|
||||
tag.span(stage.name, class: "overflow-ellipsis"),
|
||||
card_staging_path(card, stage_id: stage),
|
||||
method: :put,
|
||||
class: [ "btn justify-start workflow-stage txt-uppercase workflow-stage", { "workflow-stage--current": stage == card.stage } ],
|
||||
form_class: "flex align-center gap-half",
|
||||
data: { turbo_frame: "_top" }
|
||||
end
|
||||
|
||||
def stage_color(stage)
|
||||
stage.color.presence || Colorable::DEFAULT_COLOR
|
||||
stage.color.presence || Card::DEFAULT_COLOR
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
import { nextFrame } from "helpers/timing_helpers"
|
||||
|
||||
export default class extends Controller {
|
||||
static classes = [ "play" ]
|
||||
static values = { playOnLoad: { type: Boolean, default: false } }
|
||||
|
||||
connect() {
|
||||
if (this.playOnLoadValue) {
|
||||
this.play()
|
||||
}
|
||||
}
|
||||
|
||||
async play() {
|
||||
await nextFrame()
|
||||
this.element.classList.remove(this.playClass)
|
||||
this.#forceReflow()
|
||||
this.element.classList.add(this.playClass)
|
||||
}
|
||||
|
||||
#forceReflow() {
|
||||
this.element.offsetWidth
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = [ "element" ]
|
||||
|
||||
connect() {
|
||||
this.observer = new IntersectionObserver(([entry]) => {
|
||||
if (entry.isIntersecting) this.elementTarget.focus()
|
||||
})
|
||||
this.observer.observe(this.elementTarget)
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.observer.disconnect()
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
const SIZES = [ "one", "two", "three", "four", "five" ]
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = [ "card" ]
|
||||
|
||||
connect() {
|
||||
this.resize()
|
||||
}
|
||||
|
||||
resize() {
|
||||
const [ min, max ] = this.#getScoreRange()
|
||||
|
||||
this.cardTargets.forEach(card => {
|
||||
const score = this.#currentCardScore(card)
|
||||
const idx = Math.round((score - min) / (max - min) * (SIZES.length - 1))
|
||||
|
||||
card.style.setProperty("--card-size", `var(--card-size-${SIZES[idx]})`)
|
||||
})
|
||||
}
|
||||
|
||||
#getScoreRange() {
|
||||
var min = 0, max = 1;
|
||||
|
||||
this.cardTargets.forEach(card => {
|
||||
const score = this.#currentCardScore(card)
|
||||
|
||||
min = Math.min(min, score)
|
||||
max = Math.max(max, score)
|
||||
})
|
||||
|
||||
return [ min, max ]
|
||||
}
|
||||
|
||||
#currentCardScore(el) {
|
||||
const score = el.dataset.activityScore
|
||||
const scoreAt = el.dataset.activityScoreAt
|
||||
const daysAgo = (Date.now() / 1000 - scoreAt) / (60 * 60 * 24)
|
||||
|
||||
return score / (2**daysAgo)
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export default class extends Controller {
|
||||
#triggerChangeEvent(newValue) {
|
||||
if (this.inputTarget.tagName === "HOUSE-MD") {
|
||||
this.inputTarget.dispatchEvent(new CustomEvent('house-md:change', {
|
||||
cards: true,
|
||||
bubbles: true,
|
||||
detail: {
|
||||
previousContent: '',
|
||||
newContent: newValue
|
||||
|
||||
@@ -2,7 +2,9 @@ import { Controller } from "@hotwired/stimulus"
|
||||
import { differenceInDays } from "helpers/date_helpers"
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = [ "time", "date", "datetime", "shortdate", "ago", "indays", "daysago" ]
|
||||
static targets = [ "time", "date", "datetime", "shortdate", "ago", "indays", "daysago", "agoorweekday" ]
|
||||
static values = { refreshInterval: Number }
|
||||
static classes = [ "local-time-value"]
|
||||
|
||||
#timer
|
||||
|
||||
@@ -13,7 +15,10 @@ export default class extends Controller {
|
||||
this.dateTimeFormatter = new Intl.DateTimeFormat(undefined, { timeStyle: "short", dateStyle: "short" })
|
||||
this.agoFormatter = new AgoFormatter()
|
||||
this.daysagoFormatter = new DaysAgoFormatter()
|
||||
this.datewithweekdayFormatter = new Intl.DateTimeFormat(undefined, { weekday: "long", month: "long", day: "numeric" })
|
||||
this.datewithweekdayFormatter = new Intl.DateTimeFormat(undefined, { weekday: "long", month: "long", day: "numeric" })
|
||||
this.indaysFormatter = new InDaysFormatter()
|
||||
this.agoorweekdayFormatter = new DaysAgoOrWeekdayFormatter()
|
||||
}
|
||||
|
||||
connect() {
|
||||
@@ -60,6 +65,10 @@ export default class extends Controller {
|
||||
this.#formatTime(this.daysagoFormatter, target)
|
||||
}
|
||||
|
||||
agoorweekdayTargetConnected(target) {
|
||||
this.#formatTime(this.agoorweekdayFormatter, target)
|
||||
}
|
||||
|
||||
#refreshRelativeTimes() {
|
||||
this.agoTargets.forEach(target => {
|
||||
this.#formatTime(this.agoFormatter, target)
|
||||
@@ -111,6 +120,18 @@ class DaysAgoFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
class DaysAgoOrWeekdayFormatter {
|
||||
format(date) {
|
||||
const days = differenceInDays(date, new Date())
|
||||
|
||||
if (days <= 1) {
|
||||
return new DaysAgoFormatter().format(date)
|
||||
} else {
|
||||
return new Intl.DateTimeFormat(undefined, { weekday: "long", month: "long", day: "numeric" }).format(date)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class InDaysFormatter {
|
||||
format(date) {
|
||||
const days = differenceInDays(new Date(), date)
|
||||
|
||||
@@ -12,7 +12,7 @@ export default class extends Controller {
|
||||
|
||||
for (const file of files) {
|
||||
const uploadEvent = new CustomEvent("house-md:before-upload", {
|
||||
cards: true,
|
||||
bubbles: true,
|
||||
detail: { file },
|
||||
cancelable: true
|
||||
})
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
import { nextEventNamed } from "helpers/timing_helpers"
|
||||
import { isTouchDevice } from "helpers/navigator_helpers"
|
||||
|
||||
export default class extends Controller {
|
||||
// Only load for touch devices
|
||||
static get shouldLoad() {
|
||||
return isTouchDevice()
|
||||
return "ontouchstart" in window && navigator.maxTouchPoints > 0
|
||||
}
|
||||
|
||||
// Use a fake input to trigger the soft keyboard on actions that load async content
|
||||
|
||||
@@ -6,4 +6,12 @@ export default class extends Controller {
|
||||
toggle() {
|
||||
this.element.classList.toggle(this.toggleClass)
|
||||
}
|
||||
|
||||
add() {
|
||||
this.element.classList.add(this.toggleClass)
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.element.classList.remove(this.toggleClass)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export function isTouchDevice() {
|
||||
return "ontouchstart" in window && navigator.maxTouchPoints > 0
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { Turbo } from "@hotwired/turbo-rails"
|
||||
|
||||
Turbo.StreamActions.set_css_variable = function() {
|
||||
const name = this.getAttribute("name")
|
||||
const value = this.getAttribute("value")
|
||||
|
||||
this.targetElements.forEach(element => element.style.setProperty(name, value))
|
||||
}
|
||||
@@ -1,2 +1 @@
|
||||
import "initializers/current"
|
||||
import "initializers/custom_stream_actions"
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
class Card::AutoCloseAllDueJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
Card.auto_close_all_due
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
class Card::AutoReconsiderAllStagnatedJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
Card.auto_reconsider_all_stagnated
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
class GenerateNotificationsJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(event)
|
||||
event.generate_notifications
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class Mention::CreateJob < ApplicationJob
|
||||
def perform(record, mentioner:)
|
||||
record.create_mentions(mentioner:)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class NotifyRecipientsJob < ApplicationJob
|
||||
def perform(notifiable)
|
||||
notifiable.notify_recipients
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,4 @@
|
||||
class RemoveAbandonedCreationsJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
Card.remove_abandoned_creations
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
#
|
||||
# This job backs up all the tenant databases using the SQLite Backup API, which should allow the
|
||||
# application to continue running against the database while it is backed up.
|
||||
#
|
||||
# ref: https://www.sqlite.org/c3ref/backup_finish.html
|
||||
#
|
||||
# It will keep N files around, like this:
|
||||
#
|
||||
# storage/tenants/development/honcho/db/
|
||||
# ├─ main.sqlite3
|
||||
# ├─ main.sqlite3.1
|
||||
# ├─ main.sqlite3.2
|
||||
# ├─ main.sqlite3.3
|
||||
# ├─ main.sqlite3.4
|
||||
# └─ main.sqlite3.5
|
||||
#
|
||||
class SQLiteBackupsJob < ApplicationJob
|
||||
DEFAULT_NUMBER_OF_BACKUPS = 5
|
||||
DEFAULT_STEP_PAGES = 1024
|
||||
|
||||
def perform(keep: DEFAULT_NUMBER_OF_BACKUPS, step: DEFAULT_STEP_PAGES)
|
||||
@failures = []
|
||||
|
||||
ApplicationRecord.with_each_tenant do |tenant|
|
||||
perform_file_rollover tenant, keep: keep
|
||||
perform_backup tenant, step: step
|
||||
end
|
||||
|
||||
if @failures.present?
|
||||
raise "SQLiteBackupsJob: failed to backup tenants: #{@failures.join(", ")}"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def perform_file_rollover(tenant, keep:)
|
||||
keep.downto(2) do |j|
|
||||
fresher = backup_path(tenant, j - 1)
|
||||
staler = backup_path(tenant, j)
|
||||
|
||||
if j == keep && File.exist?(staler)
|
||||
FileUtils.rm(staler)
|
||||
end
|
||||
|
||||
if File.exist?(fresher)
|
||||
# TODO: It may be worth benchmarking whether backing up into the previous backup is faster
|
||||
# than backing up into an empty file.
|
||||
FileUtils.mv(fresher, staler)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def perform_backup(tenant, step:)
|
||||
ApplicationRecord.with_connection do |conn|
|
||||
current_adapter = conn.raw_connection
|
||||
backup_db = backup_path(tenant, 1)
|
||||
backup_adapter = SQLite3::Database.new(backup_db)
|
||||
backup = SQLite3::Backup.new(backup_adapter, "main", current_adapter, "main")
|
||||
|
||||
pages = 0
|
||||
elapsed = ActiveSupport::Benchmark.realtime(:float_millisecond) do
|
||||
loop do
|
||||
status = backup.step(step)
|
||||
case status
|
||||
when SQLite3::Constants::ErrorCode::DONE
|
||||
break
|
||||
when SQLite3::Constants::ErrorCode::OK
|
||||
total = backup.pagecount
|
||||
progress = total - backup.remaining
|
||||
Rails.logger.debug { "SQLiteBackupsJob: #{tenant.inspect}: Wrote #{progress} of #{total} pages." }
|
||||
when SQLite3::Constants::ErrorCode::BUSY, SQLite3::Constants::ErrorCode::LOCKED
|
||||
Rails.logger.debug { "SQLiteBackupsJob: #{tenant.inspect}: Busy, retrying." }
|
||||
else
|
||||
Rails.logger.error "SQLiteBackupsJob: #{tenant.inspect}: Failed with status #{status}."
|
||||
@failures << tenant
|
||||
end
|
||||
end
|
||||
|
||||
pages = backup.pagecount
|
||||
backup.finish
|
||||
end
|
||||
|
||||
message = sprintf(
|
||||
"SQLiteBackupsJob: %{tenant}: Backup complete in %<elapsed>.1f ms. Wrote %{pages} pages to %{path}",
|
||||
tenant: tenant.inspect, path: backup_db.inspect, pages: pages, elapsed: elapsed
|
||||
)
|
||||
Rails.logger.info message
|
||||
end
|
||||
end
|
||||
|
||||
def backup_path(tenant, index)
|
||||
db_path(tenant) + ".#{index}"
|
||||
end
|
||||
|
||||
def db_path(tenant)
|
||||
db_config.database
|
||||
end
|
||||
|
||||
def db_config
|
||||
ApplicationRecord.connection_pool.db_config
|
||||
end
|
||||
end
|
||||
+10
-52
@@ -1,39 +1,27 @@
|
||||
class Card < ApplicationRecord
|
||||
include Assignable, Colored, DraftCommenting, Engageable, Eventable, Golden,
|
||||
Messages, Notifiable, Pinnable, Closeable, Scorable, Searchable, Staged,
|
||||
include Assignable, Colored, Engageable, Eventable, Golden,
|
||||
Mentions, Pinnable, Closeable, Readable, Searchable, Staged,
|
||||
Statuses, Taggable, Watchable
|
||||
|
||||
belongs_to :collection, touch: true
|
||||
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
||||
|
||||
has_many :notifications, dependent: :destroy
|
||||
|
||||
has_many :comments, dependent: :destroy
|
||||
has_one_attached :image, dependent: :purge_later
|
||||
|
||||
after_save :track_due_date_change, if: :saved_change_to_due_on?
|
||||
after_save :track_title_change, if: :saved_change_to_title?
|
||||
after_create :assign_initial_stage
|
||||
has_markdown :description
|
||||
|
||||
scope :reverse_chronologically, -> { order created_at: :desc, id: :desc }
|
||||
scope :chronologically, -> { order created_at: :asc, id: :asc }
|
||||
scope :latest, -> { order updated_at: :desc, id: :desc }
|
||||
scope :in_collection, ->(collection) { where collection: collection }
|
||||
|
||||
scope :indexed_by, ->(index) do
|
||||
case index
|
||||
when "most_active" then ordered_by_activity
|
||||
when "newest" then reverse_chronologically
|
||||
when "oldest" then chronologically
|
||||
when "latest" then latest
|
||||
when "stalled" then ordered_by_staleness
|
||||
when "closed" then closed
|
||||
end
|
||||
end
|
||||
|
||||
scope :by_engagement_status, ->(status) do
|
||||
case status.to_s
|
||||
when "considering" then considering
|
||||
when "doing" then doing.with_golden_first
|
||||
when "newest" then reverse_chronologically
|
||||
when "oldest" then chronologically
|
||||
when "latest" then latest
|
||||
when "stalled" then chronologically
|
||||
when "closed" then closed
|
||||
end
|
||||
end
|
||||
|
||||
@@ -42,36 +30,6 @@ class Card < ApplicationRecord
|
||||
end
|
||||
|
||||
def cache_key
|
||||
[ super, collection&.name ].compact.join("/")
|
||||
[ super, collection.name ].compact.join("/")
|
||||
end
|
||||
|
||||
private
|
||||
def track_due_date_change
|
||||
if due_on.present?
|
||||
if due_on_before_last_save.nil?
|
||||
track_event("due_date_added", particulars: { due_date: due_on })
|
||||
else
|
||||
track_event("due_date_changed", particulars: { due_date: due_on })
|
||||
end
|
||||
elsif due_on_before_last_save.present?
|
||||
track_event("due_date_removed")
|
||||
end
|
||||
end
|
||||
|
||||
def track_title_change
|
||||
if title_before_last_save.present?
|
||||
track_event("title_changed", particulars: {
|
||||
old_title: title_before_last_save,
|
||||
new_title: title
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def assign_initial_stage
|
||||
if workflow_stage = collection.workflow&.stages&.first
|
||||
self.stage = workflow_stage
|
||||
save! touch: false
|
||||
track_event :staged, stage_id: workflow_stage.id, stage_name: workflow_stage.name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,10 +8,13 @@ module Card::Closeable
|
||||
has_one :closure, dependent: :destroy
|
||||
|
||||
scope :closed, -> { joins(:closure) }
|
||||
scope :active, -> { where.missing(:closure) }
|
||||
scope :open, -> { where.missing(:closure) }
|
||||
|
||||
scope :recently_closed_first, -> { closed.order("closures.created_at": :desc) }
|
||||
scope :due_to_be_closed, -> { considering.where(last_active_at: ..AUTO_CLOSE_AFTER.ago) }
|
||||
scope :in_auto_closing_collection, -> { joins(:collection).merge(Collection.auto_closing) }
|
||||
scope :due_to_be_closed, -> { considering.in_auto_closing_collection.where("last_active_at <= DATETIME('now', '-' || auto_close_period || ' seconds')") }
|
||||
|
||||
delegate :auto_closing?, :auto_close_period, to: :collection
|
||||
end
|
||||
|
||||
class_methods do
|
||||
@@ -23,7 +26,7 @@ module Card::Closeable
|
||||
end
|
||||
|
||||
def auto_close_at
|
||||
last_active_at + AUTO_CLOSE_AFTER if last_active_at
|
||||
last_active_at + auto_close_period if auto_closing? && last_active_at
|
||||
end
|
||||
|
||||
def days_until_close
|
||||
@@ -38,7 +41,7 @@ module Card::Closeable
|
||||
closure.present?
|
||||
end
|
||||
|
||||
def active?
|
||||
def open?
|
||||
!closed?
|
||||
end
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
module Card::Colored
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
COLORS = %w[ #3b3633 #67695e #eb7a32 #bf7c2b #c09c6f #746b1e #2c6da8 #5d618f #663251 #ff63a8 ]
|
||||
DEFAULT_COLOR = "#2c6da8"
|
||||
|
||||
def color
|
||||
color_from_stage || Colorable::DEFAULT_COLOR
|
||||
color_from_stage || DEFAULT_COLOR
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
module Card::DraftCommenting
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
after_save :capture_draft_comment
|
||||
end
|
||||
|
||||
def draft_comment
|
||||
find_or_build_initial_comment.body.content
|
||||
end
|
||||
|
||||
def draft_comment=(body)
|
||||
if body.present?
|
||||
@draft_comment = body
|
||||
else
|
||||
messages.comments.destroy_all
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def find_or_build_initial_comment
|
||||
message = messages.comments.first || messages.new(messageable: Comment.new)
|
||||
message.comment
|
||||
end
|
||||
|
||||
def capture_draft_comment
|
||||
if @draft_comment.present?
|
||||
find_or_build_initial_comment.update! body: @draft_comment, creator: creator
|
||||
end
|
||||
|
||||
@draft_comment = nil
|
||||
end
|
||||
end
|
||||
@@ -6,17 +6,21 @@ module Card::Engageable
|
||||
included do
|
||||
has_one :engagement, dependent: :destroy, class_name: "Card::Engagement"
|
||||
|
||||
scope :doing, -> { published.active.joins(:engagement) }
|
||||
scope :considering, -> { published_or_drafted_by(Current.user).active.where.missing(:engagement) }
|
||||
scope :considering, -> { published_or_drafted_by(Current.user).open.where.missing(:engagement) }
|
||||
scope :doing, -> { published.open.joins(:engagement) }
|
||||
scope :stagnated, -> { doing.where(last_active_at: ..STAGNATED_AFTER.ago) }
|
||||
|
||||
scope :stagnated, -> { doing.where(last_active_at: ..STAGNATED_AFTER.ago) }
|
||||
scope :by_engagement_status, ->(status) do
|
||||
case status.to_s
|
||||
when "considering" then considering
|
||||
when "doing" then doing.with_golden_first
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def auto_reconsider_all_stagnated
|
||||
stagnated.find_each do |card|
|
||||
card.reconsider
|
||||
end
|
||||
stagnated.find_each(&:reconsider)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,11 +29,11 @@ module Card::Engageable
|
||||
end
|
||||
|
||||
def doing?
|
||||
active? && published? && engagement.present?
|
||||
open? && published? && engagement.present?
|
||||
end
|
||||
|
||||
def considering?
|
||||
active? && published? && !doing?
|
||||
open? && published? && engagement.blank?
|
||||
end
|
||||
|
||||
def engage
|
||||
|
||||
@@ -1,26 +1,33 @@
|
||||
module Card::Eventable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include ::Eventable
|
||||
|
||||
included do
|
||||
has_many :events, dependent: :destroy
|
||||
before_create { self.last_active_at = Time.current }
|
||||
|
||||
after_save :track_title_change, if: :saved_change_to_title?
|
||||
end
|
||||
|
||||
def touch_last_active_at
|
||||
touch :last_active_at
|
||||
end
|
||||
|
||||
def track_event(action, creator: Current.user, **particulars)
|
||||
if published?
|
||||
event = find_or_capture_event_summary.events.create! action: action, creator: creator, card: self, particulars: particulars
|
||||
event.generate_notifications_later
|
||||
def event_was_created(event)
|
||||
transaction do
|
||||
create_system_comment_for(event)
|
||||
touch(:last_active_at)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def find_or_capture_event_summary
|
||||
transaction do
|
||||
messages.last&.event_summary || capture(EventSummary.new).event_summary
|
||||
def should_track_event?
|
||||
published?
|
||||
end
|
||||
|
||||
def track_title_change
|
||||
if title_before_last_save.present?
|
||||
track_event "title_changed", particulars: { old_title: title_before_last_save, new_title: title }
|
||||
end
|
||||
end
|
||||
|
||||
def create_system_comment_for(event)
|
||||
SystemCommenter.new(self, event).comment
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
class Card::Eventable::SystemCommenter
|
||||
attr_reader :card, :event
|
||||
|
||||
def initialize(card, event)
|
||||
@card, @event = card, event
|
||||
end
|
||||
|
||||
def comment
|
||||
return unless comment_body.present?
|
||||
|
||||
if comment = find_replaceable_system_comment
|
||||
comment.update! body: comment_body
|
||||
else
|
||||
card.comments.create! creator: User.system, body: comment_body, created_at: event.created_at
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
RELATED_EVENTS = [
|
||||
%w[ card_assigned card_unassigned ]
|
||||
]
|
||||
|
||||
def comment_body
|
||||
case event.action
|
||||
when "card_assigned"
|
||||
"Assigned to #{card.assignees.pluck(:name).to_sentence}."
|
||||
when "card_unassigned"
|
||||
if card.assignees.empty?
|
||||
"Unassigned from #{event.assignees.pluck(:name).to_sentence}."
|
||||
else
|
||||
"Assigned to #{card.assignees.pluck(:name).to_sentence}."
|
||||
end
|
||||
when "card_staged"
|
||||
"#{event.creator.name} moved this to '#{event.stage_name}'."
|
||||
when "card_closed"
|
||||
"Closed by #{ event.creator.name }"
|
||||
when "card_title_changed"
|
||||
"#{event.creator.name} changed title from '#{event.particulars.dig('particulars', 'old_title')}' to '#{event.particulars.dig('particulars', 'new_title')}'."
|
||||
end
|
||||
end
|
||||
|
||||
def find_replaceable_system_comment
|
||||
previous_comment, previous_event = card.comments.last, card.events[-2]
|
||||
if previous_comment&.creator&.system? && replaceable_event?(previous_event, event)
|
||||
previous_comment
|
||||
end
|
||||
end
|
||||
|
||||
def replaceable_event?(candidate_event, replacement_event)
|
||||
candidate_event && replacement_event &&
|
||||
(candidate_event.action == replacement_event.action || related_events?(candidate_event, replacement_event))
|
||||
end
|
||||
|
||||
def related_events?(candidate_event, replacement_event)
|
||||
RELATED_EVENTS.find { |related_actions| related_actions.include?(candidate_event.action) && related_actions.include?(replacement_event.action) }
|
||||
end
|
||||
end
|
||||
@@ -2,10 +2,9 @@ module Card::Golden
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
scope :golden, -> { joins(:goldness) }
|
||||
|
||||
has_one :goldness, dependent: :destroy, class_name: "Card::Goldness"
|
||||
|
||||
scope :golden, -> { joins(:goldness) }
|
||||
scope :with_golden_first, -> { left_outer_joins(:goldness).prepend_order("card_goldnesses.id IS NULL") }
|
||||
end
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
module Card::Messages
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :messages, -> { chronologically }, dependent: :destroy
|
||||
end
|
||||
|
||||
def capture(messageable)
|
||||
messages.create! messageable: messageable
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user