diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index 569ee3fd8..1f1c7dd68 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -92,29 +92,25 @@ display: none; } - .card__background { - .card:has(& img:not([src=""])) { - .card__content, - .card__meta, - .workflow-stage:not(.workflow-stage--current) { - opacity: 0; - transition: opacity 0.2s ease-in-out; + .card:has(.card__background img:not([src=""])) { + .card__content, + .card__meta, + .workflow-stage:not(.workflow-stage--current) { + opacity: 0; + transition: opacity 0.2s ease-in-out; - @media (any-hover: hover) { - .card:hover & { + @media (any-hover: hover) { + .card:hover & { opacity: 1; } - } } } + } - @media (any-hover: hover) { - .card:hover & { - img:not([src=""]) { - filter: blur(3px) brightness(1.2); - opacity: 0.2; - } - } + @media (any-hover: hover) { + .card:has(.card__background img:not([src=""])):hover .card__background img:not([src=""]) { + filter: blur(3px) brightness(1.2); + opacity: 0.2; } } diff --git a/app/assets/stylesheets/cards.css b/app/assets/stylesheets/cards.css index 1f2c4b84b..1efff8c97 100644 --- a/app/assets/stylesheets/cards.css +++ b/app/assets/stylesheets/cards.css @@ -308,11 +308,11 @@ position: absolute; rotate: 5deg; transform-origin: top right; + } - .card:has(&), - .card-perma:has(&) { - --card-color: var(--color-card-complete) !important; - } + .card:has(.card__closed), + .card-perma:has(.card__closed) { + --card-color: var(--color-card-complete) !important; } .card__closed-title { diff --git a/app/assets/stylesheets/filters.css b/app/assets/stylesheets/filters.css index 5b621bbf9..298c2744b 100644 --- a/app/assets/stylesheets/filters.css +++ b/app/assets/stylesheets/filters.css @@ -3,11 +3,14 @@ position: relative; view-transition-name: "filters"; z-index: var(--z-popup); + } - #header:has(&) { - position: relative; - z-index: var(--z-popup); - } + #header:has(.filters) { + position: relative; + z-index: var(--z-popup); + } + + .filters { .btn { --btn-border-color: var(--color-ink-medium); @@ -114,5 +117,17 @@ display: none; } } + + .filters:not(.filters--expanded) { + .quick-filter:not([data-filter-show=true]) { + display: none; + } + } + + .filters.filters--expanded { + .quick-filter { + display: block; + } + } } diff --git a/app/assets/stylesheets/header.css b/app/assets/stylesheets/header.css index 2d5bc40a9..d8b5a2a43 100644 --- a/app/assets/stylesheets/header.css +++ b/app/assets/stylesheets/header.css @@ -14,7 +14,6 @@ "actions-start title actions-end"; max-inline-size: 100dvw; padding: calc(var(--block-space-half) + env(safe-area-inset-top)) var(--main-padding); - view-transition-name: header; /* Change the grid size depending on how many buttons are present */ &:has(.header__actions > *:nth-child(1)) { --header-button-count: 1; } diff --git a/app/assets/stylesheets/inputs.css b/app/assets/stylesheets/inputs.css index 1a9afd70a..e0ca57536 100644 --- a/app/assets/stylesheets/inputs.css +++ b/app/assets/stylesheets/inputs.css @@ -60,13 +60,11 @@ cursor: pointer; opacity: 0; } + } - &:focus, - &:focus-visible { - .input--file:has(&) { - outline: 0.15rem solid var(--color-selected-dark); - } - } + &:has(input[type="file"]:focus), + &:has(input[type="file"]:focus-visible) { + outline: 0.15rem solid var(--color-selected-dark); } &:is(.avatar) { diff --git a/app/assets/stylesheets/popup.css b/app/assets/stylesheets/popup.css index e9c3bcbaf..186bfa53e 100644 --- a/app/assets/stylesheets/popup.css +++ b/app/assets/stylesheets/popup.css @@ -31,16 +31,16 @@ --panel-padding: var(--block-space) var(--block-space) 0 var(--block-space); } - #header:has(&) { - position: relative; - z-index: var(--z-nav); - } - .card & { inline-size: 260px; } } + #header:has(.popup) { + position: relative; + z-index: var(--z-nav); + } + .popup__footer { background-color: var(--color-canvas); border-block-start: 1px solid var(--color-ink-lighter); diff --git a/app/assets/stylesheets/rich-text-content.css b/app/assets/stylesheets/rich-text-content.css index b5ca20116..f1e0fc6d4 100644 --- a/app/assets/stylesheets/rich-text-content.css +++ b/app/assets/stylesheets/rich-text-content.css @@ -50,11 +50,12 @@ margin-inline: auto; max-block-size: 32rem; object-fit: contain; + } - /* Links should hug media contained within */ - a:has(&) { - display: inline-block; - } + /* Links should hug media contained within */ + a:has(img), + a:has(video) { + display: inline-block; } /* Avoid extra space due to empty paragraphs */ diff --git a/app/controllers/cards_controller.rb b/app/controllers/cards_controller.rb index 8c7af5074..7296e34d9 100644 --- a/app/controllers/cards_controller.rb +++ b/app/controllers/cards_controller.rb @@ -8,7 +8,7 @@ class CardsController < ApplicationController enable_collection_filtering only: :index - PAGE_SIZE = 50 + PAGE_SIZE = 25 def index @considering = page_and_filter_for @filter.with(engagement_status: "considering"), per_page: PAGE_SIZE @@ -17,7 +17,7 @@ class CardsController < ApplicationController @closed = page_and_filter_for_closed_cards @cache_key = [ @considering, @on_deck, @doing, @closed ].collect { it.page.records }.including([ Workflow.all ]) - fresh_when etag: @cache_key + fresh_when etag: [ @cache_key, @user_filtering ] end def create diff --git a/app/controllers/concerns/filter_scoped.rb b/app/controllers/concerns/filter_scoped.rb index 6f2dd87de..f191939b9 100644 --- a/app/controllers/concerns/filter_scoped.rb +++ b/app/controllers/concerns/filter_scoped.rb @@ -24,7 +24,11 @@ module FilterScoped end def set_user_filtering - @user_filtering = User::Filtering.new(Current.user, @filter, expanded: params[:expand_all]) + @user_filtering = User::Filtering.new(Current.user, @filter, expanded: expanded_param) + end + + def expanded_param + ActiveRecord::Type::Boolean.new.cast(params[:expand_all]) end def enable_collection_filtering diff --git a/app/controllers/my/menus_controller.rb b/app/controllers/my/menus_controller.rb new file mode 100644 index 000000000..a81705df3 --- /dev/null +++ b/app/controllers/my/menus_controller.rb @@ -0,0 +1,7 @@ +class My::MenusController < ApplicationController + include FilterScoped + + def show + fresh_when @user_filtering + end +end diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb index 99ece604c..53fa055b5 100644 --- a/app/helpers/time_helper.rb +++ b/app/helpers/time_helper.rb @@ -1,6 +1,6 @@ module TimeHelper def local_datetime_tag(datetime, style: :time, **attributes) # Render empty space to ensure it takes height until the local time is loaded via JS - tag.time " ".html_safe, **attributes, datetime: datetime.iso8601, data: { local_time_target: style, action: "turbo:morph-element->local-time#refreshTarget" } + tag.time " ".html_safe, **attributes, datetime: datetime.to_i, data: { local_time_target: style, action: "turbo:morph-element->local-time#refreshTarget" } end end diff --git a/app/javascript/controllers/form_controller.js b/app/javascript/controllers/form_controller.js index 64fc6fad6..eaf246fea 100644 --- a/app/javascript/controllers/form_controller.js +++ b/app/javascript/controllers/form_controller.js @@ -1,12 +1,30 @@ import { Controller } from "@hotwired/stimulus" +import { debounce } from "helpers/timing_helpers"; export default class extends Controller { static targets = [ "cancel" ] + static values = { + debounceTimeout: { type: Number, default: 300 } + } + + initialize() { + this.debouncedSubmit = debounce(this.debouncedSubmit.bind(this), this.debounceTimeoutValue) + } + submit() { this.element.requestSubmit() } + debouncedSubmit(event) { + this.submit(event) + } + + submitToTopTarget(event) { + this.element.setAttribute("data-turbo-frame", "_top") + this.submit() + } + cancel() { this.cancelTarget?.click() } diff --git a/app/javascript/controllers/local_time_controller.js b/app/javascript/controllers/local_time_controller.js index 69693a107..d38420332 100644 --- a/app/javascript/controllers/local_time_controller.js +++ b/app/javascript/controllers/local_time_controller.js @@ -1,5 +1,5 @@ import { Controller } from "@hotwired/stimulus" -import { differenceInDays } from "helpers/date_helpers" +import { differenceInDays, secondsToDate } from "helpers/date_helpers" const DEFAULT_LOCALE = "en-US" @@ -89,7 +89,7 @@ export default class extends Controller { } #formatTime(formatter, target) { - const dt = new Date(target.getAttribute("datetime")) + const dt = secondsToDate(parseInt(target.getAttribute("datetime"))) target.innerHTML = formatter.format(dt) target.title = this.datetimeFormatter.format(dt) } diff --git a/app/javascript/controllers/toggle_enable_controller.js b/app/javascript/controllers/toggle_enable_controller.js new file mode 100644 index 000000000..b65ea57d4 --- /dev/null +++ b/app/javascript/controllers/toggle_enable_controller.js @@ -0,0 +1,11 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = [ "element" ] + + toggle() { + this.elementTargets.forEach((element) => { + element.toggleAttribute("disabled") + }) + } +} diff --git a/app/javascript/helpers/date_helpers.js b/app/javascript/helpers/date_helpers.js index 529b2968e..b8d9855f4 100644 --- a/app/javascript/helpers/date_helpers.js +++ b/app/javascript/helpers/date_helpers.js @@ -9,3 +9,7 @@ export function signedDifferenceInDays(fromDate, toDate) { export function beginningOfDay(date) { return new Date(date.getFullYear(), date.getMonth(), date.getDate()) } + +export function secondsToDate(seconds) { + return new Date(seconds * 1000) +} diff --git a/app/models/event/summarizer.rb b/app/models/event/summarizer.rb index 5d5470d55..c46ff2c08 100644 --- a/app/models/event/summarizer.rb +++ b/app/models/event/summarizer.rb @@ -4,7 +4,7 @@ class Event::Summarizer attr_reader :events - LLM_MODEL = "chatgpt-4o-latest" + LLM_MODEL = "gpt-5-chat-latest" PROMPT = <<~PROMPT Help me make sense of the week’s activity in a news style format with bold headlines and short summaries. diff --git a/app/models/filter.rb b/app/models/filter.rb index 4748e54a4..6bef679e5 100644 --- a/app/models/filter.rb +++ b/app/models/filter.rb @@ -34,7 +34,7 @@ class Filter < ApplicationRecord result = result.closed_at_window(closure_window) if closure_window result = result.closed_by(closers) if closers.present? result = terms.reduce(result) do |result, term| - result.similar_to(term) + result.mentioning(term) end result diff --git a/app/models/filter/fields.rb b/app/models/filter/fields.rb index a261a23f4..af0966d2a 100644 --- a/app/models/filter/fields.rb +++ b/app/models/filter/fields.rb @@ -47,6 +47,10 @@ module Filter::Fields def terms Array(super) end + + def terms=(value) + super(Array(value).filter(&:present?)) + end end def with(**fields) diff --git a/app/models/user/filtering.rb b/app/models/user/filtering.rb index 1781e356d..f8186ed0e 100644 --- a/app/models/user/filtering.rb +++ b/app/models/user/filtering.rb @@ -47,28 +47,28 @@ class User::Filtering end def show_indexed_by? - expanded? || !filter.indexed_by.all? + !filter.indexed_by.all? end def show_sorted_by? - expanded? || !filter.sorted_by.latest? + !filter.sorted_by.latest? end def show_tags? return unless Tag.any? - expanded? || filter.tags.any? + filter.tags.any? end def show_assignees? - expanded? || filter.assignees.any? + filter.assignees.any? end def show_creators? - expanded? || filter.creators.any? + filter.creators.any? end def show_closers? - expanded? || filter.closers.any? + filter.closers.any? end def enable_collection_filtering(&block) diff --git a/app/views/cards/index.html.erb b/app/views/cards/index.html.erb index 34b6e01d0..59128e22e 100644 --- a/app/views/cards/index.html.erb +++ b/app/views/cards/index.html.erb @@ -1,4 +1,5 @@ <% @page_title = @user_filtering.selected_collections_label %> +<% turbo_exempts_page_from_cache %> <%= render "filters/broadcasts", filter: @filter %> diff --git a/app/views/cards/index/_add_card_button.html.erb b/app/views/cards/index/_add_card_button.html.erb index 1d5578caf..196af6bab 100644 --- a/app/views/cards/index/_add_card_button.html.erb +++ b/app/views/cards/index/_add_card_button.html.erb @@ -1,10 +1,10 @@ <% if collection = user_filtering.single_collection %> -