diff --git a/frontend/src/components/BlockPropertySections/TypographySection.ts b/frontend/src/components/BlockPropertySections/TypographySection.ts index 74c2d9642..4b65d9fb7 100644 --- a/frontend/src/components/BlockPropertySections/TypographySection.ts +++ b/frontend/src/components/BlockPropertySections/TypographySection.ts @@ -5,6 +5,7 @@ import OptionToggle from "@/components/Controls/OptionToggle.vue"; import StylePropertyControl from "@/components/Controls/StylePropertyControl.vue"; import userFonts from "@/data/userFonts"; import { UserFont } from "@/types/doctypes"; +import { filterOptions } from "@/utils/autocompleteOptions"; import blockController from "@/utils/blockController"; import { setFont as _setFont, fontListItems, getFontWeightOptions, loadFontList } from "@/utils/fontManager"; import { BOX_UNIT_OPTIONS } from "@/utils/unitOptions"; @@ -44,41 +45,23 @@ const typographySectionProperties = [ propertyKey: "fontFamily", getOptions: async (filterString: string) => { await loadFontList(); - const fontOptions = [] as { label: string; value: string }[]; - userFonts.data?.forEach((font: UserFont) => { - if (fontOptions.length >= 20) { - return; - } - const fontName = font.font_name as string; - if (fontName.toLowerCase().includes(filterString.toLowerCase()) || !filterString) { - fontOptions.push({ - label: fontName, - value: fontName, - }); - } - }); - if (fontOptions.length) { - fontOptions.unshift({ - label: "Custom", - value: "_separator_1", - }); - fontOptions.push({ - label: "Default", - value: "_separator_2", - }); - } - fontListItems.value.forEach((font) => { - if (fontOptions.length >= 20) { - return; - } - if (font.family.toLowerCase().includes(filterString.toLowerCase()) || !filterString) { - fontOptions.push({ - label: font.family, - value: font.family, - }); - } - }); - return fontOptions; + const toOption = (family: string) => ({ label: family, value: family }); + const userFontOptions = filterOptions( + (userFonts.data || []).map((font: UserFont) => toOption(font.font_name as string)), + filterString, + ); + const defaultFontOptions = filterOptions( + fontListItems.value.map((font) => toOption(font.family)), + filterString, + ); + + if (!userFontOptions.length) return defaultFontOptions; + return [ + { label: "Custom", value: "_separator_1" }, + ...userFontOptions, + { label: "Default", value: "_separator_2" }, + ...defaultFontOptions, + ]; }, actionButton: { component: FontUploader, diff --git a/frontend/src/components/DynamicValueDropdown.vue b/frontend/src/components/DynamicValueDropdown.vue index cdf560e1e..453b4fb9e 100644 --- a/frontend/src/components/DynamicValueDropdown.vue +++ b/frontend/src/components/DynamicValueDropdown.vue @@ -30,6 +30,7 @@ import Autocomplete from "@/components/Controls/Autocomplete.vue"; import MiddleTruncate from "@/components/MiddleTruncate.vue"; import usePageStore from "@/stores/pageStore"; +import { filterOptions } from "@/utils/autocompleteOptions"; import blockController from "@/utils/blockController"; import { getDataArray, getDefaultPropsList, getParentProps, getRepeaterScopedData } from "@/utils/helpers"; import { computed, ref, watch } from "vue"; @@ -100,12 +101,7 @@ const dynamicOptions = computed(() => { return options; }); -const getOptions = async (query: string) => { - const normalizedQuery = query.trim().toLowerCase(); - return dynamicOptions.value.filter((option) => { - return !normalizedQuery || option.label.toLowerCase().includes(normalizedQuery); - }); -}; +const getOptions = async (query: string) => filterOptions(dynamicOptions.value, query); const handleModelValueUpdate = (value: string | null) => { if (value == null) { diff --git a/frontend/src/components/Settings/AnalyticsFilters.vue b/frontend/src/components/Settings/AnalyticsFilters.vue index ff4cda140..085a955bc 100644 --- a/frontend/src/components/Settings/AnalyticsFilters.vue +++ b/frontend/src/components/Settings/AnalyticsFilters.vue @@ -25,6 +25,7 @@ import Autocomplete from "@/components/Controls/Autocomplete.vue"; import { webPages } from "@/data/webPage"; import { BuilderPage } from "@/types/doctypes"; +import { filterOptions } from "@/utils/autocompleteOptions"; import { DateRangePicker, Select } from "frappe-ui"; import { computed } from "vue"; @@ -101,14 +102,10 @@ const getRouteOptions = async (query: string) => { await webPages.fetch(); } - const queryLower = query?.toLowerCase() || ""; - - return (webPages.data ?? []) + const routeOptions = (webPages.data ?? []) .filter((page: BuilderPage) => page.route && !page.dynamic_route) - .map((page: BuilderPage) => ({ value: page.route as string, label: page.route as string })) - .filter( - (option: { value: string; label: string }) => - !queryLower || option.label.toLowerCase().includes(queryLower), - ); + .map((page: BuilderPage) => ({ value: page.route as string, label: page.route as string })); + + return filterOptions(routeOptions, query || ""); }; diff --git a/frontend/src/components/VisibilityInput.vue b/frontend/src/components/VisibilityInput.vue index 8bd5022ea..b409413fc 100644 --- a/frontend/src/components/VisibilityInput.vue +++ b/frontend/src/components/VisibilityInput.vue @@ -11,6 +11,7 @@ import InlineInput from "@/components/Controls/InlineInput.vue"; import useCanvasStore from "@/stores/canvasStore"; import usePageStore from "@/stores/pageStore"; +import { filterOptions } from "@/utils/autocompleteOptions"; import blockController from "@/utils/blockController"; import componentController from "@/utils/componentController"; import { getDataArray, getDefaultPropsList, getParentProps, getRepeaterScopedData } from "@/utils/helpers"; @@ -66,46 +67,19 @@ const defaultProps = computed(() => { } return Object.keys(getDefaultPropsList(currentBlock.value)); }); -const getOptions = async (query: string) => { - let options: { label: string; value: string }[] = []; +const visibilityOptions = computed(() => { + const toOptions = (props: string[], comesFrom: string) => + props.map((prop) => ({ label: prop, value: `${prop}--${comesFrom}` })); + const propsList = [...new Set([...ownProps.value, ...parentProps.value, ...defaultProps.value])]; - pageDataArray.value.map((prop) => { - if (query.trim() == "" || prop.toLowerCase().includes(query.toLowerCase())) { - options.push({ - label: prop, - value: `${prop}--dataScript`, - }); - } - }); - componentDataArray.value.map((prop) => { - if (query.trim() == "" || prop.toLowerCase().includes(query.toLowerCase())) { - options.push({ - label: prop, - value: `${prop}--componentData`, - }); - } - }); - const combinedProps = [...new Set([...ownProps.value, ...parentProps.value])]; - - combinedProps.map((prop) => { - if (query.trim() == "" || prop.toLowerCase().includes(query.toLowerCase())) { - options.push({ - label: prop, - value: `${prop}--props`, - }); - } - }); - defaultProps.value.map((prop) => { - if (query.trim() == "" || prop.toLowerCase().includes(query.toLowerCase())) { - options.push({ - label: prop, - value: `${prop}--props`, - }); - } - }); + return [ + ...toOptions(pageDataArray.value, "dataScript"), + ...toOptions(componentDataArray.value, "componentData"), + ...toOptions(propsList, "props"), + ]; +}); - return options; -}; +const getOptions = async (query: string) => filterOptions(visibilityOptions.value, query); const handleModelValueUpdate = (value: string | null) => { if (value == null) { diff --git a/frontend/src/utils/autocompleteOptions.ts b/frontend/src/utils/autocompleteOptions.ts new file mode 100644 index 000000000..8df5b5e30 --- /dev/null +++ b/frontend/src/utils/autocompleteOptions.ts @@ -0,0 +1,23 @@ +const MAX_OPTIONS = 20; + +// typing filters to matching options; an exact match (the current selection) +// shows the list windowed around it, with about a third of the window above it +function filterOptions