From f3db9e763061ce88e768adb954d25d7fa73f5374 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:54:23 -0500 Subject: [PATCH 1/4] Add search filters feature flag --- js/constants.js | 29 ++++++++++++++++++++++++++++- js/index.js | 5 +++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/js/constants.js b/js/constants.js index 1275ea6d..e5d0e355 100644 --- a/js/constants.js +++ b/js/constants.js @@ -147,6 +147,7 @@ const DEFAULT_FLAGS = Object.freeze({ FEATURE_WATCH_HISTORY_V2: true, FEATURE_PUBLISH_NIP71: false, FEATURE_HASHTAG_PREFERENCES: false, + FEATURE_SEARCH_FILTERS: false, FEATURE_TRUST_SEEDS: true, // Rollback: disable to drop baseline trust seeds without code changes. FEATURE_TRUSTED_HIDE_CONTROLS: true, FEATURE_IMPROVED_COMMENT_FETCHING: true, // Default true for comment persistence fixes @@ -166,6 +167,7 @@ const runtimeFlags = (() => { FEATURE_WATCH_HISTORY_V2: DEFAULT_FLAGS.FEATURE_WATCH_HISTORY_V2, FEATURE_PUBLISH_NIP71: DEFAULT_FLAGS.FEATURE_PUBLISH_NIP71, FEATURE_HASHTAG_PREFERENCES: DEFAULT_FLAGS.FEATURE_HASHTAG_PREFERENCES, + FEATURE_SEARCH_FILTERS: DEFAULT_FLAGS.FEATURE_SEARCH_FILTERS, FEATURE_TRUST_SEEDS: DEFAULT_FLAGS.FEATURE_TRUST_SEEDS, FEATURE_TRUSTED_HIDE_CONTROLS: DEFAULT_FLAGS.FEATURE_TRUSTED_HIDE_CONTROLS, FEATURE_IMPROVED_COMMENT_FETCHING: @@ -200,6 +202,11 @@ export let FEATURE_HASHTAG_PREFERENCES = coerceBoolean( DEFAULT_FLAGS.FEATURE_HASHTAG_PREFERENCES ); +export let FEATURE_SEARCH_FILTERS = coerceBoolean( + runtimeFlags.FEATURE_SEARCH_FILTERS, + DEFAULT_FLAGS.FEATURE_SEARCH_FILTERS +); + export let FEATURE_TRUST_SEEDS = coerceBoolean( runtimeFlags.FEATURE_TRUST_SEEDS, DEFAULT_FLAGS.FEATURE_TRUST_SEEDS @@ -282,6 +289,20 @@ Object.defineProperty(runtimeFlags, "FEATURE_HASHTAG_PREFERENCES", { }, }); +Object.defineProperty(runtimeFlags, "FEATURE_SEARCH_FILTERS", { + configurable: true, + enumerable: true, + get() { + return FEATURE_SEARCH_FILTERS; + }, + set(next) { + FEATURE_SEARCH_FILTERS = coerceBoolean( + next, + DEFAULT_FLAGS.FEATURE_SEARCH_FILTERS + ); + }, +}); + Object.defineProperty(runtimeFlags, "FEATURE_TRUST_SEEDS", { configurable: true, enumerable: true, @@ -370,6 +391,7 @@ runtimeFlags.URL_FIRST_ENABLED = URL_FIRST_ENABLED; runtimeFlags.FEATURE_WATCH_HISTORY_V2 = FEATURE_WATCH_HISTORY_V2; runtimeFlags.FEATURE_PUBLISH_NIP71 = FEATURE_PUBLISH_NIP71; runtimeFlags.FEATURE_HASHTAG_PREFERENCES = FEATURE_HASHTAG_PREFERENCES; +runtimeFlags.FEATURE_SEARCH_FILTERS = FEATURE_SEARCH_FILTERS; runtimeFlags.FEATURE_TRUST_SEEDS = FEATURE_TRUST_SEEDS; runtimeFlags.FEATURE_TRUSTED_HIDE_CONTROLS = FEATURE_TRUSTED_HIDE_CONTROLS; runtimeFlags.FEATURE_IMPROVED_COMMENT_FETCHING = @@ -412,10 +434,16 @@ export function setHashtagPreferencesEnabled(next) { return FEATURE_HASHTAG_PREFERENCES; } +export function setSearchFiltersEnabled(next) { + runtimeFlags.FEATURE_SEARCH_FILTERS = next; + return FEATURE_SEARCH_FILTERS; +} + export function resetRuntimeFlags() { setUrlFirstEnabled(DEFAULT_FLAGS.URL_FIRST_ENABLED); setWatchHistoryV2Enabled(DEFAULT_FLAGS.FEATURE_WATCH_HISTORY_V2); setHashtagPreferencesEnabled(DEFAULT_FLAGS.FEATURE_HASHTAG_PREFERENCES); + setSearchFiltersEnabled(DEFAULT_FLAGS.FEATURE_SEARCH_FILTERS); setTrustSeedsEnabled(DEFAULT_FLAGS.FEATURE_TRUST_SEEDS); setTrustedHideControlsEnabled(DEFAULT_FLAGS.FEATURE_TRUSTED_HIDE_CONTROLS); setImprovedCommentFetchingEnabled( @@ -450,4 +478,3 @@ export function getTrustedMuteHideThreshold() { export function getTrustedSpamHideThreshold() { return TRUSTED_SPAM_HIDE_THRESHOLD; } - diff --git a/js/index.js b/js/index.js index 18aa6d1a..25ecdd81 100644 --- a/js/index.js +++ b/js/index.js @@ -42,6 +42,7 @@ import { LOCKDOWN_TITLE, LOCKDOWN_MESSAGE, } from "./config.js"; +import { FEATURE_SEARCH_FILTERS } from "./constants.js"; import { createHashChangeHandler } from "./hashChangeHandler.js"; import AuthService from "./services/authService.js"; import getAuthProvider, { @@ -1213,6 +1214,10 @@ async function bootstrapInterface() { setHashView(buildSearchHashFromState(nextState)); }; searchFilterButtons.forEach((button) => { + if (!FEATURE_SEARCH_FILTERS) { + button.classList.add("hidden"); + return; + } attachSearchFiltersPopover(button, { getState: getSearchFilterState, onApply: applySearchFilters, From 861576db897a01faf3a9253902ed93b29622653e Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Fri, 23 Jan 2026 14:05:26 -0500 Subject: [PATCH 2/4] Gate advanced search filters behind toggle --- css/tailwind.source.css | 6 + js/index.js | 5 - js/ui/components/SearchFiltersPopover.js | 441 ++++++++++++++--------- 3 files changed, 270 insertions(+), 182 deletions(-) diff --git a/css/tailwind.source.css b/css/tailwind.source.css index 05eb23dc..df2a6e1d 100644 --- a/css/tailwind.source.css +++ b/css/tailwind.source.css @@ -490,6 +490,12 @@ @apply border-accent-strong bg-accent-strong text-white; } + @media (min-width: theme("screens.md")) { + .search-filters-panel.is-advanced-open { + width: 40rem; + } + } + [data-popover-arrow="true"] { left: var(--popover-arrow-left, auto); right: var(--popover-arrow-right, auto); diff --git a/js/index.js b/js/index.js index 25ecdd81..18aa6d1a 100644 --- a/js/index.js +++ b/js/index.js @@ -42,7 +42,6 @@ import { LOCKDOWN_TITLE, LOCKDOWN_MESSAGE, } from "./config.js"; -import { FEATURE_SEARCH_FILTERS } from "./constants.js"; import { createHashChangeHandler } from "./hashChangeHandler.js"; import AuthService from "./services/authService.js"; import getAuthProvider, { @@ -1214,10 +1213,6 @@ async function bootstrapInterface() { setHashView(buildSearchHashFromState(nextState)); }; searchFilterButtons.forEach((button) => { - if (!FEATURE_SEARCH_FILTERS) { - button.classList.add("hidden"); - return; - } attachSearchFiltersPopover(button, { getState: getSearchFilterState, onApply: applySearchFilters, diff --git a/js/ui/components/SearchFiltersPopover.js b/js/ui/components/SearchFiltersPopover.js index d923ce9d..120ce0db 100644 --- a/js/ui/components/SearchFiltersPopover.js +++ b/js/ui/components/SearchFiltersPopover.js @@ -1,5 +1,6 @@ import createPopover from "../overlay/popoverEngine.js"; import { DEFAULT_FILTERS } from "../../search/searchFilters.js"; +import { FEATURE_SEARCH_FILTERS } from "../../constants.js"; const FOCUSABLE_SELECTOR = "a[href], button:not([disabled]), input:not([disabled]):not([type=\"hidden\"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])"; @@ -239,9 +240,10 @@ export function attachSearchFiltersPopover(triggerElement, options = {}) { const render = ({ container, close }) => { const headingId = buildFilterId("search-filters-heading"); + const advancedPanelId = buildFilterId("search-filters-advanced"); const panel = createElement(doc, "div", { className: - "popover-panel w-80 max-w-full space-y-5 md:w-96", + "popover-panel search-filters-panel w-80 max-w-full space-y-5 md:w-96", attrs: { "aria-labelledby": headingId }, }); panel.dataset.popoverVariant = "filters"; @@ -398,136 +400,203 @@ export function attachSearchFiltersPopover(triggerElement, options = {}) { ); panel.appendChild(authorWrapper); - const tagSection = createElement(doc, "div", { - className: "bv-stack bv-stack--tight", - }); - tagSection.appendChild( - createElement(doc, "p", { - className: "text-xs font-semibold uppercase tracking-wide text-muted", - text: "Tags", - }), - ); - const tagInput = createElement(doc, "input", { - className: "input w-full", - attrs: { type: "text", placeholder: "Add tag or keyword" }, - }); - const tagList = createElement(doc, "div", { - className: "flex flex-wrap gap-2", - }); - enableArrowNavigation(tagList); - const tagChips = new Map(); - ["nostr", "music", "live"].forEach((tag) => { - const chip = createElement(doc, "button", { - className: "search-filter-chip focus-ring", - text: `#${tag}`, - attrs: { type: "button", "aria-pressed": "true" }, - }); - chip.dataset.state = "on"; - chip.addEventListener("click", () => toggleChip(chip)); - tagChips.set(tag, chip); - tagList.appendChild(chip); - }); - tagSection.append(tagInput, tagList); - panel.appendChild(tagSection); + let advancedToggleButton = null; + let advancedChevron = null; + let advancedContent = null; + let setAdvancedOpen = null; + let tagSection = null; + let tagInput = null; + let tagChips = null; + let durationSection = null; + let durationChecks = null; + let hasSection = null; + let hasChips = null; + let toggleSection = null; + let nsfwToggle = null; + let followedToggle = null; - const durationSection = createElement(doc, "div", { - className: "bv-stack bv-stack--tight", - }); - durationSection.appendChild( - createElement(doc, "p", { - className: "text-xs font-semibold uppercase tracking-wide text-muted", - text: "Duration", - }), - ); - const durationGrid = createElement(doc, "div", { - className: "grid gap-2", - }); - const durationChecks = {}; - [ - { id: "duration-short", label: "Short (under 5 min)" }, - { id: "duration-medium", label: "Medium (5-20 min)" }, - { id: "duration-long", label: "Long (20+ min)" }, - { id: "duration-live", label: "Live sessions" }, - ].forEach(({ id, label }) => { - const row = createElement(doc, "label", { - className: "flex items-center gap-3 text-sm text-text", - attrs: { for: id }, - }); - const checkbox = createElement(doc, "input", { - className: "checkbox", - attrs: { id, type: "checkbox" }, - }); - durationChecks[id] = checkbox; - row.append(checkbox, createElement(doc, "span", { text: label })); - durationGrid.appendChild(row); - }); - durationSection.appendChild(durationGrid); - panel.appendChild(durationSection); - - const hasSection = createElement(doc, "div", { - className: "bv-stack bv-stack--tight", - }); - hasSection.appendChild( - createElement(doc, "p", { - className: "text-xs font-semibold uppercase tracking-wide text-muted", - text: "Has", - }), - ); - const hasChipRow = createElement(doc, "div", { - className: "flex flex-wrap gap-2", - }); - enableArrowNavigation(hasChipRow); - const hasChips = new Map(); - [ - { key: "magnet", label: "Magnet" }, - { key: "url", label: "URL" }, - { key: "transcript", label: "Transcript" }, - ].forEach(({ key, label }) => { - const chip = createElement(doc, "button", { - className: "search-filter-chip focus-ring", - text: label, - attrs: { type: "button", "aria-pressed": "false" }, - }); - chip.dataset.state = "off"; - chip.addEventListener("click", () => toggleChip(chip)); - hasChips.set(key, chip); - hasChipRow.appendChild(chip); - }); - hasSection.appendChild(hasChipRow); - panel.appendChild(hasSection); - - const toggleSection = createElement(doc, "div", { - className: "grid gap-3", - }); - const toggleRow = (label, id) => { - const row = createElement(doc, "div", { - className: "flex items-center justify-between gap-3", - }); - const text = createElement(doc, "div", { + if (FEATURE_SEARCH_FILTERS) { + const advancedSection = createElement(doc, "div", { className: "bv-stack bv-stack--tight", }); - text.append( + advancedToggleButton = createElement(doc, "button", { + className: + "btn-ghost flex w-full items-center justify-between text-sm font-medium", + attrs: { + type: "button", + "aria-expanded": "false", + "aria-controls": advancedPanelId, + }, + }); + advancedChevron = createElement(doc, "span", { + className: "text-muted", + text: "▾", + attrs: { "aria-hidden": "true" }, + }); + advancedToggleButton.append( + createElement(doc, "span", { text: "Advanced filters" }), + advancedChevron, + ); + advancedContent = createElement(doc, "div", { + className: "search-filters-advanced hidden grid gap-5 md:grid-cols-2", + attrs: { id: advancedPanelId }, + }); + + setAdvancedOpen = (isOpen) => { + if (!advancedContent || !advancedToggleButton) { + return; + } + advancedContent.classList.toggle("hidden", !isOpen); + advancedToggleButton.setAttribute( + "aria-expanded", + isOpen ? "true" : "false", + ); + if (advancedChevron) { + advancedChevron.textContent = isOpen ? "▴" : "▾"; + } + panel.classList.toggle("is-advanced-open", isOpen); + }; + + advancedToggleButton.addEventListener("click", () => { + const isExpanded = + advancedToggleButton.getAttribute("aria-expanded") === "true"; + setAdvancedOpen(!isExpanded); + }); + + tagSection = createElement(doc, "div", { + className: "bv-stack bv-stack--tight md:col-span-2", + }); + tagSection.appendChild( createElement(doc, "p", { - className: "text-sm font-medium text-text", - text: label, - }), - createElement(doc, "p", { - className: "text-xs text-muted", - text: `Toggle ${label.toLowerCase()}`, + className: "text-xs font-semibold uppercase tracking-wide text-muted", + text: "Tags", }), ); - const toggle = createElement(doc, "button", { - className: "switch", - attrs: { type: "button", role: "switch", "aria-checked": "false", id }, + tagInput = createElement(doc, "input", { + className: "input w-full", + attrs: { type: "text", placeholder: "Add tag or keyword" }, }); - toggle.addEventListener("click", () => toggleSwitch(toggle)); - row.append(text, toggle); - return row; - }; - const nsfwToggle = toggleRow("NSFW content", "search-filters-nsfw"); - const followedToggle = toggleRow("Followed only", "search-filters-followed"); - toggleSection.append(nsfwToggle, followedToggle); - panel.appendChild(toggleSection); + const tagList = createElement(doc, "div", { + className: "flex flex-wrap gap-2", + }); + enableArrowNavigation(tagList); + tagChips = new Map(); + ["nostr", "music", "live"].forEach((tag) => { + const chip = createElement(doc, "button", { + className: "search-filter-chip focus-ring", + text: `#${tag}`, + attrs: { type: "button", "aria-pressed": "true" }, + }); + chip.dataset.state = "on"; + chip.addEventListener("click", () => toggleChip(chip)); + tagChips.set(tag, chip); + tagList.appendChild(chip); + }); + tagSection.append(tagInput, tagList); + advancedContent.appendChild(tagSection); + + durationSection = createElement(doc, "div", { + className: "bv-stack bv-stack--tight", + }); + durationSection.appendChild( + createElement(doc, "p", { + className: "text-xs font-semibold uppercase tracking-wide text-muted", + text: "Duration", + }), + ); + const durationGrid = createElement(doc, "div", { + className: "grid gap-2", + }); + durationChecks = {}; + [ + { id: "duration-short", label: "Short (under 5 min)" }, + { id: "duration-medium", label: "Medium (5-20 min)" }, + { id: "duration-long", label: "Long (20+ min)" }, + { id: "duration-live", label: "Live sessions" }, + ].forEach(({ id, label }) => { + const row = createElement(doc, "label", { + className: "flex items-center gap-3 text-sm text-text", + attrs: { for: id }, + }); + const checkbox = createElement(doc, "input", { + className: "checkbox", + attrs: { id, type: "checkbox" }, + }); + durationChecks[id] = checkbox; + row.append(checkbox, createElement(doc, "span", { text: label })); + durationGrid.appendChild(row); + }); + durationSection.appendChild(durationGrid); + advancedContent.appendChild(durationSection); + + hasSection = createElement(doc, "div", { + className: "bv-stack bv-stack--tight", + }); + hasSection.appendChild( + createElement(doc, "p", { + className: "text-xs font-semibold uppercase tracking-wide text-muted", + text: "Has", + }), + ); + const hasChipRow = createElement(doc, "div", { + className: "flex flex-wrap gap-2", + }); + enableArrowNavigation(hasChipRow); + hasChips = new Map(); + [ + { key: "magnet", label: "Magnet" }, + { key: "url", label: "URL" }, + { key: "transcript", label: "Transcript" }, + ].forEach(({ key, label }) => { + const chip = createElement(doc, "button", { + className: "search-filter-chip focus-ring", + text: label, + attrs: { type: "button", "aria-pressed": "false" }, + }); + chip.dataset.state = "off"; + chip.addEventListener("click", () => toggleChip(chip)); + hasChips.set(key, chip); + hasChipRow.appendChild(chip); + }); + hasSection.appendChild(hasChipRow); + advancedContent.appendChild(hasSection); + + toggleSection = createElement(doc, "div", { + className: "grid gap-3 md:col-span-2", + }); + const toggleRow = (label, id) => { + const row = createElement(doc, "div", { + className: "flex items-center justify-between gap-3", + }); + const text = createElement(doc, "div", { + className: "bv-stack bv-stack--tight", + }); + text.append( + createElement(doc, "p", { + className: "text-sm font-medium text-text", + text: label, + }), + createElement(doc, "p", { + className: "text-xs text-muted", + text: `Toggle ${label.toLowerCase()}`, + }), + ); + const toggle = createElement(doc, "button", { + className: "switch", + attrs: { type: "button", role: "switch", "aria-checked": "false", id }, + }); + toggle.addEventListener("click", () => toggleSwitch(toggle)); + row.append(text, toggle); + return row; + }; + nsfwToggle = toggleRow("NSFW content", "search-filters-nsfw"); + followedToggle = toggleRow("Followed only", "search-filters-followed"); + toggleSection.append(nsfwToggle, followedToggle); + advancedContent.appendChild(toggleSection); + + advancedSection.append(advancedToggleButton, advancedContent); + panel.appendChild(advancedSection); + } const footer = createElement(doc, "div", { className: "flex items-center justify-between gap-3 border-t border-border/60 pt-4", @@ -555,37 +624,41 @@ export function attachSearchFiltersPopover(triggerElement, options = {}) { button.dataset.state = isAllTime ? "on" : "off"; }); authorInput.value = safeFilters.authorPubkeys?.join(", ") || ""; - tagInput.value = ""; - tagChips.forEach((chip, tag) => { - const isActive = safeFilters.tags?.includes(tag); - chip.setAttribute("aria-pressed", isActive ? "true" : "false"); - chip.dataset.state = isActive ? "on" : "off"; - }); - const extraTags = (safeFilters.tags || []).filter( - (tag) => !tagChips.has(tag), - ); - if (extraTags.length) { - tagInput.value = extraTags.join(", "); + if (tagInput && tagChips) { + tagInput.value = ""; + tagChips.forEach((chip, tag) => { + const isActive = safeFilters.tags?.includes(tag); + chip.setAttribute("aria-pressed", isActive ? "true" : "false"); + chip.dataset.state = isActive ? "on" : "off"; + }); + const extraTags = (safeFilters.tags || []).filter( + (tag) => !tagChips.has(tag), + ); + if (extraTags.length) { + tagInput.value = extraTags.join(", "); + } } - Object.values(durationChecks).forEach((checkbox) => { - checkbox.checked = false; - }); - const minSeconds = safeFilters.duration?.minSeconds ?? null; - const maxSeconds = safeFilters.duration?.maxSeconds ?? null; - if (minSeconds === null && maxSeconds === 300) { - durationChecks["duration-short"].checked = true; - } else if (minSeconds === 300 && maxSeconds === 1200) { - durationChecks["duration-medium"].checked = true; - } else if (minSeconds === 1200 && maxSeconds === null) { - durationChecks["duration-long"].checked = true; + if (durationChecks) { + Object.values(durationChecks).forEach((checkbox) => { + checkbox.checked = false; + }); + const minSeconds = safeFilters.duration?.minSeconds ?? null; + const maxSeconds = safeFilters.duration?.maxSeconds ?? null; + if (minSeconds === null && maxSeconds === 300) { + durationChecks["duration-short"].checked = true; + } else if (minSeconds === 300 && maxSeconds === 1200) { + durationChecks["duration-medium"].checked = true; + } else if (minSeconds === 1200 && maxSeconds === null) { + durationChecks["duration-long"].checked = true; + } } - const nsfwSwitch = nsfwToggle.querySelector(".switch"); + const nsfwSwitch = nsfwToggle?.querySelector(".switch"); if (nsfwSwitch) { const isOn = safeFilters.nsfw === "true" || safeFilters.nsfw === "only"; nsfwSwitch.setAttribute("aria-checked", isOn ? "true" : "false"); nsfwSwitch.classList.toggle("is-on", isOn); } - const followedSwitch = followedToggle.querySelector(".switch"); + const followedSwitch = followedToggle?.querySelector(".switch"); if (followedSwitch) { followedSwitch.setAttribute("aria-checked", "false"); followedSwitch.classList.remove("is-on"); @@ -593,6 +666,16 @@ export function attachSearchFiltersPopover(triggerElement, options = {}) { if (sortSelect) { sortSelect.selectedIndex = 0; } + if (setAdvancedOpen) { + const hasAdvanced = + (safeFilters.tags && safeFilters.tags.length > 0) || + Number.isFinite(safeFilters.duration?.minSeconds) || + Number.isFinite(safeFilters.duration?.maxSeconds) || + safeFilters.hasMagnet === true || + safeFilters.hasUrl === true || + (safeFilters.nsfw && safeFilters.nsfw !== "any"); + setAdvancedOpen(hasAdvanced); + } }; const buildFiltersFromControls = () => { @@ -608,37 +691,41 @@ export function attachSearchFiltersPopover(triggerElement, options = {}) { .map((value) => value.trim()) .filter(Boolean); filters.authorPubkeys = authorValues; - const selectedTags = new Set(); - tagChips.forEach((chip, tag) => { - if (chip.getAttribute("aria-pressed") === "true") { - selectedTags.add(tag); + if (tagChips && tagInput) { + const selectedTags = new Set(); + tagChips.forEach((chip, tag) => { + if (chip.getAttribute("aria-pressed") === "true") { + selectedTags.add(tag); + } + }); + normalizeTagValue(tagInput.value).forEach((tag) => + selectedTags.add(tag), + ); + filters.tags = Array.from(selectedTags); + } + if (durationChecks) { + const ranges = []; + if (durationChecks["duration-short"].checked) { + ranges.push({ min: null, max: 300 }); + } + if (durationChecks["duration-medium"].checked) { + ranges.push({ min: 300, max: 1200 }); + } + if (durationChecks["duration-long"].checked) { + ranges.push({ min: 1200, max: null }); + } + if (ranges.length) { + const mins = ranges + .map((range) => range.min) + .filter((value) => value !== null); + const maxes = ranges + .map((range) => range.max) + .filter((value) => value !== null); + filters.duration.minSeconds = mins.length ? Math.min(...mins) : null; + filters.duration.maxSeconds = maxes.length ? Math.max(...maxes) : null; } - }); - normalizeTagValue(tagInput.value).forEach((tag) => - selectedTags.add(tag), - ); - filters.tags = Array.from(selectedTags); - const ranges = []; - if (durationChecks["duration-short"].checked) { - ranges.push({ min: null, max: 300 }); } - if (durationChecks["duration-medium"].checked) { - ranges.push({ min: 300, max: 1200 }); - } - if (durationChecks["duration-long"].checked) { - ranges.push({ min: 1200, max: null }); - } - if (ranges.length) { - const mins = ranges - .map((range) => range.min) - .filter((value) => value !== null); - const maxes = ranges - .map((range) => range.max) - .filter((value) => value !== null); - filters.duration.minSeconds = mins.length ? Math.min(...mins) : null; - filters.duration.maxSeconds = maxes.length ? Math.max(...maxes) : null; - } - const nsfwSwitch = nsfwToggle.querySelector(".switch"); + const nsfwSwitch = nsfwToggle?.querySelector(".switch"); if (nsfwSwitch?.getAttribute("aria-checked") === "true") { filters.nsfw = "true"; } From d639b712dd5fd0e94becd63da86011ab327ec3ef Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Fri, 23 Jan 2026 14:10:48 -0500 Subject: [PATCH 3/4] Restore search filter popover imports --- js/ui/components/SearchFiltersPopover.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/js/ui/components/SearchFiltersPopover.js b/js/ui/components/SearchFiltersPopover.js index 120ce0db..5ad2a29e 100644 --- a/js/ui/components/SearchFiltersPopover.js +++ b/js/ui/components/SearchFiltersPopover.js @@ -1,5 +1,9 @@ import createPopover from "../overlay/popoverEngine.js"; -import { DEFAULT_FILTERS } from "../../search/searchFilters.js"; +import { + DEFAULT_FILTERS, + parseFilterQuery, +} from "../../search/searchFilters.js"; +import { subscribeSearchFacetCounts } from "../../search/searchFacetState.js"; import { FEATURE_SEARCH_FILTERS } from "../../constants.js"; const FOCUSABLE_SELECTOR = From 954a1ae4e69f487a8ecddacc342fafdb6c41e96f Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Fri, 23 Jan 2026 14:16:43 -0500 Subject: [PATCH 4/4] Enable search filters flag in dev mode --- js/constants.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/constants.js b/js/constants.js index e5d0e355..b91faf40 100644 --- a/js/constants.js +++ b/js/constants.js @@ -4,6 +4,7 @@ import { DEFAULT_TRUST_SEED_NPUBS as CONFIG_DEFAULT_TRUST_SEED_NPUBS, DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD as CONFIG_DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD, DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD as CONFIG_DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD, + IS_DEV_MODE, } from "./config.js"; function coerceNonNegativeInteger(value, fallback) { @@ -147,7 +148,7 @@ const DEFAULT_FLAGS = Object.freeze({ FEATURE_WATCH_HISTORY_V2: true, FEATURE_PUBLISH_NIP71: false, FEATURE_HASHTAG_PREFERENCES: false, - FEATURE_SEARCH_FILTERS: false, + FEATURE_SEARCH_FILTERS: Boolean(IS_DEV_MODE), FEATURE_TRUST_SEEDS: true, // Rollback: disable to drop baseline trust seeds without code changes. FEATURE_TRUSTED_HIDE_CONTROLS: true, FEATURE_IMPROVED_COMMENT_FETCHING: true, // Default true for comment persistence fixes