Appearance
useCommonSearch Composable ​
File Information ​
- Path:
app/composables/core/common/useCommonSearch.ts - Purpose: Common search filter logic and state management
- Type: Core Composable
Overview ​
Turns the filter chips a user has applied (allFilterList, a list of { level1, level2, level3 }) into Typesense filter_by strings, keeps the filter list in the search store in sync, and serves recommended tags. It is the Nuxt 4 port of the admin filter mixin and is used by useSearch, useSearchBar, useFolderDetails and useCollageDetails.
The user-filter part of every filter_by sent from the search page, folder detail and collage detail is built here; the search page also gets its instance visibility clause from buildFiltersForCollection.
Features ​
Per-collection filter (getCollectionFilters / buildFiltersForCollection) ​
getCollectionFilters(tabFilter = "", includeLibrary = false) returns a map of collection name to filter_by:
digital_assetsis always present.dam_collectionsanddigital_assets_categoriesare added only whenincludeLibraryis true, there is notabFilter(asset-type filter) andhasAssetOnlyFilter(allFilterList)is false. Asset-only filters aretags,file-type,orientation,uploaded-by,collageand anycustom-field-*, because folders and collages do not carry those fields.
Each collection string is assembled in this order:
- The visibility clause uses
useAppDataStore().brand?.id. When no brand is resolved the clause is omitted entirely, so the request carries only the user filters. - Empty parts are dropped; if everything is empty the result is
''.
Example for a brand with id 123, one file-type chip (is, id <file_type_id>) and one tag chip (include-all-of, tag Marketing):
text
(instance_visibility:=123 && instance_visibility:!=0) && ((file_type:=<file_type_id>)) && ((tags:[\Marketing\]))Filter rules (buildFiltersNew) ​
Each chip becomes one clause wrapped in parentheses; clauses are joined with &&. Chips that produce an empty string are skipped. Rules are checked in this order:
level1 | level2 | Output |
|---|---|---|
custom-field-<id> | include-all-of / include-any-of | portal_custom_fields:= + backtick-quoted `<field_label>:<value>` per value, joined by && / || |
custom-field-<id> | exclude-all-of / exclude-any-of | same with :!= |
permission | is / is-not with public / other | instance_visibility:=<brand id> when (is and public) or (not is and not public), else instance_visibility:!=<brand id>; empty when no brand id |
orientation | is | orientation:=<value> |
orientation | other | orientation:!=N/A && orientation:!=<value> |
date-added / last-modified-date | see below | range on created_at / modified_at |
tags | include-all-of / include-any-of | tags:[\<tag_name>\] per tag, joined by && / || |
tags | exclude-all-of / exclude-any-of | tags:!=[<search_name>] per tag |
tags | contains | tags:*<term>*; empty term gives no clause |
file-type | is | file_type:=<id> per value joined by || |
file-type | not is | file_type:!=<id> per value joined by && |
| anything else | is / other | <level1 with - replaced by _>:=<level3> or :!= |
Custom fields:
- The field id is parsed from
custom-field-<id>; a non-numeric id yields no clause. - The label is looked up in
popular_custom_fieldsof the page-specific store data first (popularCollageSearchDataforcollage-page,popularFolderSearchDataforfolder-page, elsepopularSearchData), then inoptions.searchData.popular_custom_fields. Nofield_labelfound means no clause, which is whyuseSearchpreloads custom-field metadata before the first fetch when the URL already carries a custom-field filter. - Backticks and backslashes in label and value are escaped with
\. Object values use theirvalueproperty.
Dates (buildTimestampFilter) work in Unix seconds:
level2 | level3 slug | Output |
|---|---|---|
after | 30-mins-ago, 1-day-ago, 3-days-ago, 1-week-ago, 1-month-ago (30 days) | <field>:>=<now - offset> |
before | same | <field>:<=<now - offset> |
after / before / between | custom (label "<start> - <end>") | <field>:>=<start 00:00:00.001> && <field>:<=<end 23:59:59.999> (local time) |
between | a preset slug | <field>:<=<now - offset> |
Filter list maintenance ​
| Method | What it does |
|---|---|
updateFilterObj(payload) | Sets mainFilterObj from an array [level1, level2, level3] or an object. |
updateFilterListData({ old, new }) | Replaces the chip that matches old on all three levels (level3 compared with JSON.stringify). The new chip is kept when it has values, a slug, is tags+contains, or is an empty array (so an emptied chip stays visible). Calls onUpdateFilter unless the new value is an empty array. |
removeFilterOptn(chip) | Removes the matching chip, clears mainFilterObj if it was the same level1, writes the store, refetches and emits reset-brand-filters on search, folder detail and collage detail routes. |
updateFilterList(list) | Writes to the store method picked by options.flag: collage -> updateCollageAllFilterList, folder -> updateFolderAllFilterList, otherwise updateAllFilterList. |
removeNullFilter() | Drops tags and file-type chips with no values (except tags+contains), writes the store and refetches if anything changed. On the search route it always emits reset-brand-filters. |
checkReturnSearch(searchKey) | True when there is no search term and the only chip is an empty tags or file-type chip, so callers can skip a pointless request. |
resetFilter() | Clears all chips and mainFilterObj, refetches, then emits close-search-filter, reset-search-filter and reset-brand-filters. |
onUpdateFilter / initOrigIndexCache | Reorders the popular tag and file-type pools so selected items are pinned while unselected ones return to their original position (origIndexCache, CUTOFF = 49). |
Recommended tags ​
commonRecommendedTagsFn(tag)adds a tag to the existingtagschip (if not already present bytag_name) or creates a newtags/include-all-ofchip.getRecommendedTags(term, workspaceId, limit = RECOMMENDED_TAGS_LIMIT)callssearchApi.getSearchData(workspaceId, user.instance_id)and returns up tolimitunique tags (case-insensitive) whose name starts with any word of the term. Empty term or no data returns[]; errors are logged and return[].
Return Type ​
updateFilterObj, updateFilterListData, removeFilterOptn, updateFilterList, buildFiltersNew, buildTimestampFilter, buildTagFilter, removeNullFilter, checkReturnSearch, onUpdateFilter, initOrigIndexCache, getCollectionFilters, resetFilter, commonRecommendedTagsFn, getRecommendedTags, and the reactive origIndexCache.
Parameters ​
FilterBuilderOptions (app/types):
| Field | Type | Purpose |
|---|---|---|
mainFilterObj | Ref<MainFilterObj> | Chip currently being edited |
allFilterList | Ref<FilterItem[]> | Applied chips |
searchData | Ref<SearchData> | Popular tags, file types and custom fields |
fetchResults | () => Promise<void> | Called after list changes |
flag | string? | collage, folder or unset; picks the store list |
Usage Example ​
From useSearch.ts (search page, no flag):
typescript
const { getCollectionFilters, removeFilterOptn: originalRemoveFilterOptn, resetFilter: originalResetFilter } = useCommonSearch({
mainFilterObj,
allFilterList,
searchData,
fetchResults: async () => {
await fetchResults("filter");
},
});useFolderDetails.ts passes flag: "folder" with folderAllFilterList / folderMainFilterObj and calls buildFiltersNew directly instead of getCollectionFilters.
Related Composables ​
- See Composables Overview for all composables
Related Documentation ​
- Composables Overview - All composables
- Types - TypeScript type definitions
- Components - Components using this composable