From 12b0b0af61f2c74e1f4c34575f2849b25a5b8861 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 7 Jun 2026 22:26:17 +0000 Subject: [PATCH] Remove the bespoke SelectableFilter widget FilterSelect fully replaces it: delete SelectableFilter and its _selectable_* helpers, the now-unused _get_filter_options, selectable_filter.js, and the .sf-* rules in input.css (rebuilt base.css). The three list views load search_select.js instead of selectable_filter.js. Drop the SelectableFilter export and refresh docs/comments that referenced it. https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS --- CLAUDE.md | 7 +- common/components/__init__.py | 2 - common/components/filters.py | 201 +-------------------------- common/criteria.py | 2 +- common/input.css | 45 ------ games/static/base.css | 165 ---------------------- games/static/js/search_select.js | 8 +- games/static/js/selectable_filter.js | 149 -------------------- games/views/game.py | 2 +- games/views/purchase.py | 2 +- games/views/session.py | 2 +- 11 files changed, 13 insertions(+), 572 deletions(-) delete mode 100644 games/static/js/selectable_filter.js diff --git a/CLAUDE.md b/CLAUDE.md index 169a022..d53b284 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -64,8 +64,8 @@ docs/ — Additional documentation - **`core.py`** — `Component(tag_name, attributes, children)`: the fundamental builder. `_render_element()` is `@lru_cache`-memoized (4096 entries, always active). Attribute values are always HTML-escaped; children are escaped unless they are `SafeText`. `randomid()` generates stable hash-based IDs. - **`primitives.py`** — Generic HTML: `A()`, `Button()` (with color/size/icon params), `ButtonGroup()`, `Div()`, `Span()`, `Label()`, `Input()`, `Icon()`, `Popover()`, `PopoverTruncated()`, `SearchField()`, `H1()`, `Modal()`, `SimpleTable()`, `TableRow()`, `TableTd()`, `TableHeader()`, `paginated_table_content()`, `AddForm()`, `Pill()`, `CsrfInput()`, `ModuleScript()` - **`domain.py`** — Domain-specific: `GameLink()`, `GameStatus()` (colored dot + label), `GameStatusSelector()` (Alpine.js PATCH dropdown), `SessionDeviceSelector()` (Alpine.js PATCH dropdown), `LinkedPurchase()`, `NameWithIcon()`, `PriceConverted()`, `PurchasePrice()` -- **`filters.py`** — Filter UI: `FilterBar()`, `SessionFilterBar()`, `PurchaseFilterBar()`, `SelectableFilter()` (clickable include/exclude chips) -- **`search_select.py`** — `SearchSelect()` + `SearchSelectOption`: search-as-you-type dropdown with removable pill selection, wired by `games/static/js/search_select.js` +- **`filters.py`** — Filter UI: `FilterBar()`, `SessionFilterBar()`, `PurchaseFilterBar()` (built from `FilterSelect` widgets) +- **`search_select.py`** — `SearchSelect()` (form combobox) + `FilterSelect()` (include/exclude filter combobox with pinned Any/None modifiers) + `SearchSelectOption`, all built on a shared `_combobox_shell`; wired by `games/static/js/search_select.js` **Filter system** (`games/filters.py` + `common/criteria.py`): Stash-inspired structured filtering. @@ -118,8 +118,7 @@ Only a small number of HTML templates remain (platform icon snippets and partial - **Tailwind CSS** — utility classes, compiled from `common/input.css` → `games/static/base.css` - **Custom JS** in `games/static/js/`: - `toast.js` — Alpine.js toast store (listens for `show-toast` HTMX event) - - `selectable_filter.js` — SelectableFilter widget interaction - - `search_select.js` — SearchSelect widget (search-as-you-type, pills) + - `search_select.js` — SearchSelect/FilterSelect widgets (search-as-you-type, pills, include/exclude filter mode) - `utils.js` — shared helpers (e.g., `fetchWithHtmxTriggers`) ### Deployment diff --git a/common/components/__init__.py b/common/components/__init__.py index 99b535f..cefdd5a 100644 --- a/common/components/__init__.py +++ b/common/components/__init__.py @@ -59,7 +59,6 @@ from common.components.domain import ( from common.components.filters import ( FilterBar, PurchaseFilterBar, - SelectableFilter, SessionFilterBar, ) @@ -109,6 +108,5 @@ __all__ = [ "_resolve_name_with_icon", "FilterBar", "PurchaseFilterBar", - "SelectableFilter", "SessionFilterBar", ] diff --git a/common/components/filters.py b/common/components/filters.py index e14fa87..f00ce3d 100644 --- a/common/components/filters.py +++ b/common/components/filters.py @@ -1,4 +1,4 @@ -"""Stash-style filter bars and the SelectableFilter widget.""" +"""Stash-style filter bars, built from FilterSelect widgets.""" from typing import NamedTuple @@ -12,7 +12,7 @@ from common.components.search_select import FilterSelect class FilterChoice(NamedTuple): - """Parsed state of a SelectableFilter widget from a filter JSON blob.""" + """Parsed include/exclude/modifier state of a filter field from filter JSON.""" selected: list[str] excluded: list[str] @@ -84,20 +84,6 @@ def _parse_bool(existing: dict, key: str) -> bool: return bool(field.get("value", False)) -def _get_filter_options(model_class, order_by="name") -> list[tuple[str, str]]: - """Return (value, label) pairs for a SelectableFilter from model rows. - - Uses values_list for efficiency (only fetches needed columns), - but unpacks each row into readable local variables. - """ - options: list[tuple[str, str]] = [] - for object_id, object_name in model_class.objects.order_by(order_by).values_list( - "id", order_by - ): - options.append((str(object_id), object_name)) - return options - - # ── FilterSelect adapters ──────────────────────────────────────────────────── # Each list filter is a FilterSelect. Enum fields pre-render their small, fixed # option set; model-backed fields fetch from a search endpoint and only resolve @@ -742,189 +728,6 @@ def FilterBar( return _filter_bar(fields, filter_json, preset_list_url, preset_save_url) -def _selectable_filter_tag( - value: str, label: str, *, excluded: bool = False -) -> SafeText: - """A selected (\u2713) or excluded (\u2717) value pill in the SelectableFilter.""" - checkmark = "\u2717" if excluded else "\u2713" - css = "sf-tag sf-excluded" if excluded else "sf-tag" - return Span( - attributes=[ - ("class", css), - ("data-value", value), - ("data-type", "exclude" if excluded else "include"), - ], - children=[ - Span( - attributes=[("class", "sf-tag-text")], - children=[f"{checkmark} {label}"], - ), - Component( - tag_name="button", - attributes=[ - ("type", "button"), - ("class", "sf-remove"), - ("aria-label", "Remove"), - ], - children=["\u00d7"], - ), - ], - ) - - -def _selectable_filter_modifier_tag(modifier: str, label: str) -> SafeText: - """An active modifier pill ((Any) / (None)) in the SelectableFilter.""" - return Span( - attributes=[ - ("class", "sf-modifier-tag active"), - ("data-modifier", modifier), - ], - children=[label], - ) - - -def _selectable_filter_modifier_option(modifier: str, label: str) -> SafeText: - """A modifier choice in the SelectableFilter dropdown list.""" - return Component( - tag_name="div", - attributes=[ - ("class", "sf-option sf-modifier-option"), - ("data-modifier", modifier), - ("data-label", label), - ], - children=[ - Span( - attributes=[("class", "sf-option-label")], - children=[label], - ), - ], - ) - - -def _selectable_filter_option(value: str, label: str) -> SafeText: - """An option row with include (+) and exclude (\u2212) buttons.""" - return Component( - tag_name="div", - attributes=[ - ("class", "sf-option"), - ("data-value", value), - ("data-label", label), - ], - children=[ - Span( - attributes=[("class", "sf-option-label")], - children=[label], - ), - Span( - attributes=[("class", "sf-option-buttons")], - children=[ - Component( - tag_name="button", - attributes=[ - ("type", "button"), - ("class", "sf-btn-include"), - ("data-action", "include"), - ("title", "Include"), - ], - children=["+"], - ), - Component( - tag_name="button", - attributes=[ - ("type", "button"), - ("class", "sf-btn-exclude"), - ("data-action", "exclude"), - ("title", "Exclude"), - ], - children=["\u2212"], - ), - ], - ), - ], - ) - - -def SelectableFilter( - field_name: str, - options: list[tuple[str, str]], - selected: list[str] | None = None, - excluded: list[str] | None = None, - modifier: str = "", - nullable: bool = True, -) -> "SafeText": - """Stash-style selectable filter with search, include/exclude, modifier tags.""" - selected = selected or [] - excluded = excluded or [] - - modifier_options = [("NOT_NULL", "(Any)")] - if nullable: - modifier_options.append(("IS_NULL", "(None)")) - - active_modifier_tag = "" - inactive_modifier_options: list[SafeText] = [] - for modifier_value, modifier_label in modifier_options: - if modifier == modifier_value: - active_modifier_tag = _selectable_filter_modifier_tag( - modifier_value, modifier_label - ) - else: - inactive_modifier_options.append( - _selectable_filter_modifier_option(modifier_value, modifier_label) - ) - - selected_tags: list[SafeText] = [] - for value in selected: - selected_tags.append( - _selectable_filter_tag(value, _find_label(options, value), excluded=False) - ) - for value in excluded: - selected_tags.append( - _selectable_filter_tag(value, _find_label(options, value), excluded=True) - ) - - option_rows: list[SafeText] = [] - for value, label in options: - option_rows.append(_selectable_filter_option(value, label)) - - selected_area_children: list[SafeText] = [] - if active_modifier_tag: - selected_area_children.append(active_modifier_tag) - selected_area_children.extend(selected_tags) - - options_area_children: list[SafeText] = [] - options_area_children.extend(inactive_modifier_options) - options_area_children.extend(option_rows) - - return Component( - tag_name="div", - attributes=[ - ("class", "sf-container"), - ("data-selectable-filter", field_name), - *([("data-modifier", modifier)] if modifier else []), - ], - children=[ - Component( - tag_name="div", - attributes=[("class", "sf-selected")], - children=selected_area_children, - ), - Component( - tag_name="input", - attributes=[ - ("type", "text"), - ("class", "sf-search"), - ("placeholder", "Search\u2026"), - ], - ), - Component( - tag_name="div", - attributes=[("class", "sf-options")], - children=options_area_children, - ), - ], - ) - - def _find_label(options: list[tuple[str, str]], value: str) -> str: for v, label in options: if str(v) == str(value): diff --git a/common/criteria.py b/common/criteria.py index 663fa74..ba4406b 100644 --- a/common/criteria.py +++ b/common/criteria.py @@ -299,7 +299,7 @@ class MultiCriterion(_Criterion): class ChoiceCriterion(_Criterion): """Filter on a choice/enum field with multi-select include/exclude. - Used by SelectableFilter widgets for status, ownership_type, etc. + Used by FilterSelect widgets for status, ownership_type, etc. Supports INCLUDES, EXCLUDES, EQUALS, IS_NULL, NOT_NULL modifiers. """ diff --git a/common/input.css b/common/input.css index ab07998..d299746 100644 --- a/common/input.css +++ b/common/input.css @@ -232,48 +232,3 @@ textarea:disabled { } } -/* SelectableFilter widget styling */ -.sf-container { - @apply border border-default-medium rounded-base bg-neutral-secondary-medium; -} -.sf-selected { - @apply flex flex-wrap gap-1 p-2 min-h-[2rem]; -} -.sf-tag { - @apply inline-flex items-center gap-1 px-2 py-0.5 text-sm rounded bg-brand/15 text-heading; -} -.sf-tag.sf-excluded { - @apply bg-red-500/15 text-red-600 line-through decoration-red-400; -} -.sf-remove { - @apply ml-1 text-body hover:text-heading font-bold cursor-pointer; -} -.sf-modifier-tag { - @apply inline-flex items-center px-2 py-0.5 text-sm rounded bg-amber-500/15 text-amber-600 cursor-pointer; -} -.sf-search { - @apply block w-full border-0 border-t border-default-medium bg-transparent text-sm text-heading p-2; - &:focus { - @apply ring-0 outline-hidden; - } -} -.sf-options { - @apply max-h-40 overflow-y-auto p-1 text-body; -} -.sf-option { - @apply flex items-center justify-between px-2 py-1 rounded text-sm hover:bg-neutral-secondary-strong cursor-pointer; -} -.sf-option-label { - @apply truncate; -} -.sf-option-buttons { - @apply flex gap-1 ml-2 shrink-0; -} -.sf-btn-include, -.sf-btn-exclude { - @apply w-5 h-5 flex items-center justify-center text-xs font-bold rounded border border-default-medium hover:bg-brand hover:text-white hover:border-brand; -} -.sf-modifier-option { - @apply px-2 py-1 text-sm text-body hover:bg-neutral-secondary-strong cursor-pointer; -} - diff --git a/games/static/base.css b/games/static/base.css index 21d46a8..e6ebb04 100644 --- a/games/static/base.css +++ b/games/static/base.css @@ -4429,171 +4429,6 @@ form input:disabled, select:disabled, textarea:disabled { padding: calc(var(--spacing) * 4); } } -.sf-container { - border-radius: var(--radius-base); - border-style: var(--tw-border-style); - border-width: 1px; - border-color: var(--color-default-medium); - background-color: var(--color-neutral-secondary-medium); -} -.sf-selected { - display: flex; - min-height: 2rem; - flex-wrap: wrap; - gap: calc(var(--spacing) * 1); - padding: calc(var(--spacing) * 2); -} -.sf-tag { - display: inline-flex; - align-items: center; - gap: calc(var(--spacing) * 1); - border-radius: var(--radius); - background-color: color-mix(in srgb, oklch(48.8% 0.243 264.376) 15%, transparent); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-brand) 15%, transparent); - } - padding-inline: calc(var(--spacing) * 2); - padding-block: calc(var(--spacing) * 0.5); - font-size: var(--text-sm); - line-height: var(--tw-leading, var(--text-sm--line-height)); - color: var(--color-heading); -} -.sf-tag.sf-excluded { - background-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 15%, transparent); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-red-500) 15%, transparent); - } - color: var(--color-red-600); - text-decoration-line: line-through; - text-decoration-color: var(--color-red-400); -} -.sf-remove { - margin-left: calc(var(--spacing) * 1); - cursor: pointer; - --tw-font-weight: var(--font-weight-bold); - font-weight: var(--font-weight-bold); - color: var(--color-body); - &:hover { - @media (hover: hover) { - color: var(--color-heading); - } - } -} -.sf-modifier-tag { - display: inline-flex; - cursor: pointer; - align-items: center; - border-radius: var(--radius); - background-color: color-mix(in srgb, oklch(76.9% 0.188 70.08) 15%, transparent); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-amber-500) 15%, transparent); - } - padding-inline: calc(var(--spacing) * 2); - padding-block: calc(var(--spacing) * 0.5); - font-size: var(--text-sm); - line-height: var(--tw-leading, var(--text-sm--line-height)); - color: var(--color-amber-600); -} -.sf-search { - display: block; - width: 100%; - border-style: var(--tw-border-style); - border-width: 0px; - border-top-style: var(--tw-border-style); - border-top-width: 1px; - border-color: var(--color-default-medium); - background-color: transparent; - padding: calc(var(--spacing) * 2); - font-size: var(--text-sm); - line-height: var(--tw-leading, var(--text-sm--line-height)); - color: var(--color-heading); - &:focus { - --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); - box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); - --tw-outline-style: none; - outline-style: none; - @media (forced-colors: active) { - outline: 2px solid transparent; - outline-offset: 2px; - } - } -} -.sf-options { - max-height: calc(var(--spacing) * 40); - overflow-y: auto; - padding: calc(var(--spacing) * 1); - color: var(--color-body); -} -.sf-option { - display: flex; - cursor: pointer; - align-items: center; - justify-content: space-between; - border-radius: var(--radius); - padding-inline: calc(var(--spacing) * 2); - padding-block: calc(var(--spacing) * 1); - font-size: var(--text-sm); - line-height: var(--tw-leading, var(--text-sm--line-height)); - &:hover { - @media (hover: hover) { - background-color: var(--color-neutral-secondary-strong); - } - } -} -.sf-option-label { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.sf-option-buttons { - margin-left: calc(var(--spacing) * 2); - display: flex; - flex-shrink: 0; - gap: calc(var(--spacing) * 1); -} -.sf-btn-include, .sf-btn-exclude { - display: flex; - height: calc(var(--spacing) * 5); - width: calc(var(--spacing) * 5); - align-items: center; - justify-content: center; - border-radius: var(--radius); - border-style: var(--tw-border-style); - border-width: 1px; - border-color: var(--color-default-medium); - font-size: var(--text-xs); - line-height: var(--tw-leading, var(--text-xs--line-height)); - --tw-font-weight: var(--font-weight-bold); - font-weight: var(--font-weight-bold); - &:hover { - @media (hover: hover) { - border-color: var(--color-brand); - } - } - &:hover { - @media (hover: hover) { - background-color: var(--color-brand); - } - } - &:hover { - @media (hover: hover) { - color: var(--color-white); - } - } -} -.sf-modifier-option { - cursor: pointer; - padding-inline: calc(var(--spacing) * 2); - padding-block: calc(var(--spacing) * 1); - font-size: var(--text-sm); - line-height: var(--tw-leading, var(--text-sm--line-height)); - color: var(--color-body); - &:hover { - @media (hover: hover) { - background-color: var(--color-neutral-secondary-strong); - } - } -} @layer base { input:where([type='text']),input:where(:not([type])),input:where([type='email']),input:where([type='url']),input:where([type='password']),input:where([type='number']),input:where([type='date']),input:where([type='datetime-local']),input:where([type='month']),input:where([type='search']),input:where([type='tel']),input:where([type='time']),input:where([type='week']),select:where([multiple]),textarea,select { appearance: none; diff --git a/games/static/js/search_select.js b/games/static/js/search_select.js index 813881f..3f823d5 100644 --- a/games/static/js/search_select.js +++ b/games/static/js/search_select.js @@ -12,8 +12,8 @@ * pills. Filter widgets have no hidden inputs; readSearchSelect serialises their * state into data-included / data-excluded / data-modifier for the filter bar. * - * Mirrors selectable_filter.js: initAll() on DOMContentLoaded + htmx:afterSwap, - * each widget guarded with el._ssInit. + * initAll() runs on DOMContentLoaded + htmx:afterSwap, each widget guarded with + * el._ssInit. * * The pill / option class strings below are kept byte-identical to the Python * Pill / SearchSelect / FilterSelect components so Tailwind generates the classes @@ -496,8 +496,8 @@ // Serialise each widget's current state onto data-* attributes for the caller. // Form widgets expose data-values (the submitted hidden-input values); filter - // widgets (parallel to readSelectableFilters) expose data-included / - // data-excluded / data-modifier for the filter bar to read. + // widgets expose data-included / data-excluded / data-modifier for the filter + // bar to read. window.readSearchSelect = function (form) { form.querySelectorAll("[data-search-select]").forEach(function (container) { var pills = container.querySelector("[data-ss-pills]"); diff --git a/games/static/js/selectable_filter.js b/games/static/js/selectable_filter.js deleted file mode 100644 index c6c8e52..0000000 --- a/games/static/js/selectable_filter.js +++ /dev/null @@ -1,149 +0,0 @@ -/** - * SelectableFilter widget — Stash-style choice filter with search, - * include/exclude buttons, and modifier tags (Any / None). - */ -(function () { - "use strict"; - - function initAll() { - document.querySelectorAll("[data-selectable-filter]").forEach(function (el) { - if (el._sfInit) return; - el._sfInit = true; - initWidget(el); - }); - } - - function initWidget(container) { - var search = container.querySelector(".sf-search"); - var options = container.querySelector(".sf-options"); - var selectedArea = container.querySelector(".sf-selected"); - - if (!search || !options || !selectedArea) return; - - // ── Search ── - search.addEventListener("input", function () { - var q = search.value.toLowerCase(); - options.querySelectorAll(".sf-option").forEach(function (item) { - var label = (item.getAttribute("data-label") || "").toLowerCase(); - item.style.display = label.indexOf(q) !== -1 ? "" : "none"; - }); - }); - - // ── Include / Exclude clicks ── - options.addEventListener("click", function (e) { - var btn = e.target.closest("button"); - if (btn) { - var action = btn.getAttribute("data-action"); - var itemEl = btn.closest(".sf-option"); - if (!itemEl) return; - var value = itemEl.getAttribute("data-value"); - var label = itemEl.getAttribute("data-label"); - if (!value) return; - if (action === "include") addTag(container, value, label, "include"); - else if (action === "exclude") addTag(container, value, label, "exclude"); - return; - } - - // Click on modifier option (not a button) - var modOption = e.target.closest(".sf-modifier-option"); - if (modOption) { - var modVal = modOption.getAttribute("data-modifier"); - setModifier(container, modVal); - } - }); - - // ── Remove selected tag ── - selectedArea.addEventListener("click", function (e) { - var removeBtn = e.target.closest(".sf-remove"); - if (removeBtn) { - removeBtn.closest(".sf-tag").remove(); - return; - } - - // Click on active modifier tag → deselect it - var modTag = e.target.closest(".sf-modifier-tag"); - if (modTag) { - clearModifier(container); - } - }); - } - - /** Add a tag to the selected area and clear modifier. */ - function addTag(container, value, label, type) { - clearModifier(container); - var selectedArea = container.querySelector(".sf-selected"); - - // Check if already present - var existing = selectedArea.querySelector('.sf-tag[data-value="' + value + '"]'); - if (existing) { - if (existing.getAttribute("data-type") !== type) { - existing.setAttribute("data-type", type); - existing.classList.toggle("sf-excluded", type === "exclude"); - var text = existing.querySelector(".sf-tag-text"); - if (text) text.textContent = (type === "exclude" ? "✗ " : "✓ ") + label; - } - return; - } - - var tag = document.createElement("span"); - tag.className = "sf-tag" + (type === "exclude" ? " sf-excluded" : ""); - tag.setAttribute("data-value", value); - tag.setAttribute("data-type", type); - tag.innerHTML = - '' + (type === "exclude" ? "✗ " : "✓ ") + label + "" + - ''; - selectedArea.appendChild(tag); - } - - /** Set a modifier (Any / None) — clears all tags. */ - function setModifier(container, modVal) { - var selectedArea = container.querySelector(".sf-selected"); - - // Clear all tags - selectedArea.querySelectorAll(".sf-tag").forEach(function (t) { t.remove(); }); - - // Clear existing modifier tag - selectedArea.querySelectorAll(".sf-modifier-tag").forEach(function (t) { t.remove(); }); - - // Add new modifier tag - var label = modVal === "NOT_NULL" ? "(Any)" : "(None)"; - var tag = document.createElement("span"); - tag.className = "sf-modifier-tag active"; - tag.setAttribute("data-modifier", modVal); - tag.textContent = label; - selectedArea.appendChild(tag); - - container.setAttribute("data-modifier", modVal); - } - - /** Clear any active modifier, removing the tag. */ - function clearModifier(container) { - var selectedArea = container.querySelector(".sf-selected"); - selectedArea.querySelectorAll(".sf-modifier-tag").forEach(function (t) { t.remove(); }); - container.removeAttribute("data-modifier"); - } - - // Read selections for form submission - window.readSelectableFilters = function (form) { - form.querySelectorAll("[data-selectable-filter]").forEach(function (container) { - var modifier = container.getAttribute("data-modifier"); - var modTag = container.querySelector(".sf-modifier-tag.active"); - if (modTag) modifier = modTag.getAttribute("data-modifier"); - - var included = []; - var excluded = []; - container.querySelectorAll(".sf-tag").forEach(function (tag) { - var val = tag.getAttribute("data-value"); - if (tag.getAttribute("data-type") === "exclude") excluded.push(val); - else included.push(val); - }); - - container.setAttribute("data-included", JSON.stringify(included)); - container.setAttribute("data-excluded", JSON.stringify(excluded)); - if (modifier) container.setAttribute("data-modifier", modifier); - }); - }; - - document.addEventListener("DOMContentLoaded", initAll); - document.addEventListener("htmx:afterSwap", initAll); -})(); diff --git a/games/views/game.py b/games/views/game.py index 06bdada..12ae14a 100644 --- a/games/views/game.py +++ b/games/views/game.py @@ -149,7 +149,7 @@ def list_games(request: HttpRequest, search_string: str = "") -> HttpResponse: content, title="Manage games", scripts=ModuleScript("range_slider.js") - + ModuleScript("selectable_filter.js") + + ModuleScript("search_select.js") + ModuleScript("filter_bar.js"), ) diff --git a/games/views/purchase.py b/games/views/purchase.py index 70309e5..9b292f9 100644 --- a/games/views/purchase.py +++ b/games/views/purchase.py @@ -142,7 +142,7 @@ def list_purchases(request: HttpRequest) -> HttpResponse: content, title="Manage purchases", scripts=ModuleScript("range_slider.js") - + ModuleScript("selectable_filter.js") + + ModuleScript("search_select.js") + ModuleScript("filter_bar.js"), ) diff --git a/games/views/session.py b/games/views/session.py index cc25005..becdaa3 100644 --- a/games/views/session.py +++ b/games/views/session.py @@ -182,7 +182,7 @@ def list_sessions(request: HttpRequest, search_string: str = "") -> HttpResponse content, title="Manage sessions", scripts=ModuleScript("range_slider.js") - + ModuleScript("selectable_filter.js") + + ModuleScript("search_select.js") + ModuleScript("filter_bar.js"), )