From 00ea59881aacb269362489f53dba77c1ed632a3c Mon Sep 17 00:00:00 2001 From: Zach Leahan <54847203+ZacharyLeahan@users.noreply.github.com> Date: Tue, 27 May 2025 15:39:59 -0400 Subject: [PATCH 1/2] Integrate search rules with filter chips --- lib/queryrules.ini | 48 +++++++++--- lib/server.js | 51 ++++++++++++ src/components/Explorer.vue | 152 +++++++++++++++++++++++++++++++++--- 3 files changed, 228 insertions(+), 23 deletions(-) diff --git a/lib/queryrules.ini b/lib/queryrules.ini index 4c52a79..663a65c 100644 --- a/lib/queryrules.ini +++ b/lib/queryrules.ini @@ -30,22 +30,26 @@ Query = { "Life Cycle Flags": { "$in": ["Biennial"] } } [MonarchsCheck] Chip = Supports Monarchs Keywords = Monarch Butterfly, Monarchs, Monarch, Monarch Waystation -Query = { "Pollinator Flags": { "$in": ["Monarch Butterfly"] } } +Query = { "Pollinator Flags": { "$in": ["Monarchs", "Larval Host (Monarch)"] } } +Filters = { "Pollinator Flags": ["Monarchs"] } [SunExposure_PartShade] Chip = Part Shade Keywords = partial, Part Shade, Partial Shade, Filtered Light, Partial Sun, Part Sun Query = { "Sun Exposure Flags": { "$in": ["Part Shade"] } } +Filters = { "Sun Exposure Flags": ["Part Shade"] } [SunExposure_FullSun] -Chip = Full Sun -Keywords = Full Sun, All day sun, sun -Query = { "Sun Exposure Flags": { "$in": ["Full Sun"] } } +Chip = Sun +Keywords = Full Sun, Sun, Direct Sun +Query = { "Sun Exposure Flags": { "$in": ["Sun"] } } +Filters = { "Sun Exposure Flags": ["Sun"] } [SunExposure_Shade] Chip = Shade Keywords = Full Shade, Deep Shade, Shade, Shady location, Shade Tolerant Query = { "Sun Exposure Flags": { "$in": ["Shade"] } } +Filters = { "Sun Exposure Flags": ["Shade"] } [FallBlooming] Chip = Fall Blooming @@ -58,119 +62,139 @@ Keywords = Spring, Spring Flowers, Spring Bloom Query = { "Flowering Months": { "$regex": "mar|apr|may", "$options": "i" } } [SoilMoisture_Wet] -Chip = Wet Soil +Chip = Wet Keywords = Wet, Rain Garden, Puddles, Wet Feet, Wet Soil, Wet Dirt Query = { "Soil Moisture Flags": { "$in": ["Wet"] } } +Filters = { "Soil Moisture Flags": ["Wet"] } [SoilMoisture_Dry] -Chip = Dry Soil +Chip = Dry Keywords = Dry, Cracks in Soil, Dry Soil, Dry Dirt Query = { "Soil Moisture Flags": { "$in": ["Dry"] } } +Filters = { "Soil Moisture Flags": ["Dry"] } [SoilMoisture_Moist] -Chip = Moist Soil +Chip = Moist Keywords = Moist, Slightly Moist, Moist Soil, Slightly Wet Query = { "Soil Moisture Flags": { "$in": ["Moist"] } } +Filters = { "Soil Moisture Flags": ["Moist"] } [DroughtTolerance] Chip = Drought Tolerant Keywords = Drought Tolerant, Drought, tolerance of drought, droughts -Query = { "Plant Type Flags": { "$in": ["Drought Tolerant"] } } +Query = { "Drought Tolerance": { "$in": ["Medium", "High"] } } [EvergreenCheck] Chip = Evergreen Plants Keywords = Evergreen, Evergreen Trees, Trees that are evergreen -Query = { "Plant Type Flags": { "$in": ["Evergreen"] } } +Query = { "Leaf Retention": "Evergreen" } [Tree] Chip = Tree Keywords = Tree Query = { "Plant Type Flags": { "$in": ["Tree"] } } +Filters = { "Plant Type Flags": ["Tree"] } [Shrub] Chip = Shrub Keywords = Shrub, Bush Query = { "Plant Type Flags": { "$in": ["Shrub"] } } +Filters = { "Plant Type Flags": ["Shrub"] } [Vine] Chip = Vine Keywords = Vine, Climbing, Climber Query = { "Plant Type Flags": { "$in": ["Vine"] } } +Filters = { "Plant Type Flags": ["Vine"] } [Fern] Chip = Fern Keywords = Fern -Query = { "Plant Type Flags": { "$in": ["Fern"] } } +Query = { "$or": [{ "Common Name": { "$regex": "fern", "$options": "i" } }, { "Scientific Name": { "$regex": "fern", "$options": "i" } }] } +Filters = { "Plant Type Flags": ["Fern"] } [Grass] Chip = Grass Keywords = Grass, Grasses, Sedge, Sedges, Rush, Rushes -Query = { "Plant Type Flags": { "$in": ["Grass", "Sedge", "Rush"] } } +Query = { "Plant Family": "Poaceae" } [Hummingbirds] Chip = Attracts Hummingbirds Keywords = Hummingbird, Hummingbirds, Hummingbird-attracting Query = { "Pollinator Flags": { "$in": ["Hummingbirds"] } } +Filters = { "Pollinator Flags": ["Hummingbirds"] } [Butterflies] Chip = Attracts Butterflies Keywords = Butterfly, Butterflies Query = { "Pollinator Flags": { "$in": ["Butterflies"] } } +Filters = { "Pollinator Flags": ["Butterflies"] } [Bees] Chip = Attracts Bees Keywords = Bee, Bees, Bumblebee, Bumblebees, Honey Bee -Query = { "Pollinator Flags": { "$in": ["Bees"] } } +Query = { "Pollinator Flags": { "$in": ["Native Bees", "Honey Bees", "Bombus"] } } +Filters = { "Pollinator Flags": ["Bees"] } [RedFlower] Chip = Red Flowers Keywords = Red, Red Flower, Red Flowers, Red Blooms Query = { "Flower Color Flags": { "$in": ["Red"] } } +Filters = { "Flower Color Flags": ["Red"] } [BlueFlower] Chip = Blue Flowers Keywords = Blue, Blue Flower, Blue Flowers, Blue Blooms Query = { "Flower Color Flags": { "$in": ["Blue"] } } +Filters = { "Flower Color Flags": ["Blue"] } [YellowFlower] Chip = Yellow Flowers Keywords = Yellow, Yellow Flower, Yellow Flowers, Yellow Blooms Query = { "Flower Color Flags": { "$in": ["Yellow"] } } +Filters = { "Flower Color Flags": ["Yellow"] } [PurpleFlower] Chip = Purple Flowers Keywords = Purple, Purple Flower, Purple Flowers, Purple Blooms, Violet Query = { "Flower Color Flags": { "$in": ["Purple"] } } +Filters = { "Flower Color Flags": ["Purple"] } [WhiteFlower] Chip = White Flowers Keywords = White, White Flower, White Flowers, White Blooms Query = { "Flower Color Flags": { "$in": ["White"] } } +Filters = { "Flower Color Flags": ["White"] } [PinkFlower] Chip = Pink Flowers Keywords = Pink, Pink Flower, Pink Flowers, Pink Blooms Query = { "Flower Color Flags": { "$in": ["Pink"] } } +Filters = { "Flower Color Flags": ["Pink"] } [OrangeFlower] Chip = Orange Flowers Keywords = Orange, Orange Flower, Orange Flowers, Orange Blooms Query = { "Flower Color Flags": { "$in": ["Orange"] } } +Filters = { "Flower Color Flags": ["Orange"] } [GreenFlower] Chip = Green Flowers Keywords = Green, Green Flower, Green Flowers, Green Blooms Query = { "Flower Color Flags": { "$in": ["Green"] } } +Filters = { "Flower Color Flags": ["Green"] } [Superplant] Chip = Super Plant Keywords = Super, Super Plant, Superplant Query = { "Superplant": true } +Filters = { "Superplant": ["Super Plant"] } [Showy] Chip = Showy Keywords = Showy, Showy Flowers, Showy Blooms Query = { "Showy": true } +Filters = { "Showy": ["Showy"] } [FallColor] Chip = Fall Color diff --git a/lib/server.js b/lib/server.js index 5cd040f..6ce5d44 100644 --- a/lib/server.js +++ b/lib/server.js @@ -12,6 +12,34 @@ const version = fs.existsSync(versionFile) ? fs.readFileSync(versionFile, "utf8") : "0"; const axios = require("axios"); +const ini = require("ini"); + +// Load query rules from queryrules.ini at startup +const queryRulesPath = path.join(__dirname, "queryrules.ini"); +let queryRules = {}; +if (fs.existsSync(queryRulesPath)) { + const parsed = ini.parse(fs.readFileSync(queryRulesPath, "utf8")); + for (const [name, rule] of Object.entries(parsed)) { + try { + queryRules[name] = { + chip: rule.Chip, + keywords: String(rule.Keywords || "") + .split(/,\s*/) + .filter(Boolean), + query: JSON.parse(rule.Query), + }; + if (rule.Filters) { + try { + queryRules[name].filters = JSON.parse(rule.Filters); + } catch (e) { + console.error(`Failed to parse filters for rule ${name}:`, e); + } + } + } catch (e) { + console.error(`Failed to parse query rule ${name}:`, e); + } + } +} // Disabled for now because it causes confusion when we update the data // const cache = {}; @@ -59,11 +87,31 @@ module.exports = async function ({ plants, nurseries }) { }); return res.send(response.data); }); + + // Provide query rules to the frontend + app.get("/api/v1/queryrules", (req, res) => { + res.json(queryRules); + }); app.get("/api/v1/plants", async (req, res) => { try { const fetchResults = req.query.results !== "0"; const fetchTotal = req.query.total !== "0"; const query = {}; + const ruleChips = []; + const rulesParam = req.query.rules; + if (rulesParam) { + const names = Array.isArray(rulesParam) + ? rulesParam + : String(rulesParam).split(/,/); + for (const name of names) { + const rule = queryRules[name]; + if (rule) { + if (!query.$and) query.$and = []; + query.$and.push(rule.query); + ruleChips.push({ name, chip: rule.chip }); + } + } + } const sorts = { "Sort by Common Name (A-Z)": { "Common Name": 1, @@ -437,6 +485,9 @@ module.exports = async function ({ plants, nurseries }) { ); } } + if (ruleChips.length) { + response.ruleChips = ruleChips; + } // setCache(req, response); return res.send(response); } catch (e) { diff --git a/src/components/Explorer.vue b/src/components/Explorer.vue index da6e025..683caa2 100644 --- a/src/components/Explorer.vue +++ b/src/components/Explorer.vue @@ -862,6 +862,11 @@ export default { total: 0, q: "", activeSearch: "", + queryRules: {}, + appliedRules: [], + ruleChips: [], + ruleFiltersActive: {}, + applyingRuleFilters: false, sort: "Sort by Recommendation Score", filters, componentKey: 0, // Add a key for forcing re-renders @@ -913,7 +918,21 @@ export default { return extras; }, chips() { - return this.getChips(true); + const chips = this.getChips(true).filter((chip) => { + const active = this.ruleFiltersActive[chip.name]; + return !(active && active[chip.label]); + }); + if (this.ruleChips.length) { + for (const rc of this.ruleChips) { + chips.push({ + name: rc.name, + label: rc.chip, + key: `rule:${rc.name}`, + svg: 'Search', + }); + } + } + return chips; }, flags() { return this.getChips(false); @@ -990,6 +1009,9 @@ export default { }, filterValues: { async handler() { + if (this.applyingRuleFilters) { + return; + } if (this.isDesktop()) { this.submit(); } else { @@ -1024,6 +1046,15 @@ export default { // Pick a random hero image after hydration to avoid SSR hydration mismatch this.twoUpIndex = Math.floor(Math.random() * twoUpImageCredits.length); + // Fetch query rules used for keyword searches + try { + const resp = await fetch('/api/v1/queryrules'); + this.queryRules = await resp.json(); + } catch (e) { + console.error('Failed to fetch query rules', e); + this.queryRules = {}; + } + this.displayLocation = localStorage.getItem("displayLocation") || ""; this.zipCode = localStorage.getItem("zipCode") || ""; this.manualZip = localStorage.getItem("manualZip") === "true"; @@ -1176,6 +1207,87 @@ export default { this.manualZip = true; localStorage.setItem("manualZip", "true") }, + + detectRules() { + const matches = []; + if (!this.q || !this.queryRules) return matches; + const qLower = this.q.toLowerCase(); + for (const [name, rule] of Object.entries(this.queryRules)) { + for (const kw of rule.keywords) { + if (qLower.includes(kw.toLowerCase())) { + matches.push(name); + break; + } + } + } + return matches; + }, + + addRuleFilters(name) { + const rule = this.queryRules[name]; + if (!rule || !rule.filters) return; + this.applyingRuleFilters = true; + for (const [filterName, value] of Object.entries(rule.filters)) { + const filter = this.filters.find((f) => f.name === filterName); + if (!filter) continue; + const values = Array.isArray(value) ? value : [value]; + if (!this.filterValues[filterName]) { + this.$set(this.filterValues, filterName, filter.array ? [] : ""); + } + if (filter.array) { + for (const v of values) { + if (!this.filterValues[filterName].includes(v)) { + this.filterValues[filterName].push(v); + } + if (!this.ruleFiltersActive[filterName]) this.$set(this.ruleFiltersActive, filterName, {}); + if (!this.ruleFiltersActive[filterName][v]) this.$set(this.ruleFiltersActive[filterName], v, new Set()); + this.ruleFiltersActive[filterName][v].add(name); + } + } + } + this.applyingRuleFilters = false; + }, + + removeRuleFilters(name) { + const rule = this.queryRules[name]; + if (!rule || !rule.filters) return; + this.applyingRuleFilters = true; + for (const [filterName, value] of Object.entries(rule.filters)) { + const values = Array.isArray(value) ? value : [value]; + const arr = this.filterValues[filterName]; + if (!arr) continue; + for (const v of values) { + const stillNeeded = this.appliedRules.some((other) => { + if (other === name) return false; + const otherRule = this.queryRules[other]; + if (!otherRule || !otherRule.filters) return false; + const otherValues = otherRule.filters[filterName]; + if (!otherValues) return false; + const otherArr = Array.isArray(otherValues) ? otherValues : [otherValues]; + return otherArr.includes(v); + }); + if (!stillNeeded) { + const idx = arr.indexOf(v); + if (idx !== -1) arr.splice(idx, 1); + } + if (this.ruleFiltersActive[filterName] && this.ruleFiltersActive[filterName][v]) { + this.ruleFiltersActive[filterName][v].delete(name); + if (!this.ruleFiltersActive[filterName][v].size) { + this.$delete(this.ruleFiltersActive[filterName], v); + } + } + } + } + this.applyingRuleFilters = false; + }, + + updateRuleFilters(detected) { + const removed = this.appliedRules.filter((n) => !detected.includes(n)); + const added = detected.filter((n) => !this.appliedRules.includes(n)); + removed.forEach((name) => this.removeRuleFilters(name)); + added.forEach((name) => this.addRuleFilters(name)); + this.appliedRules = detected; + }, async getVendors() { if (!this.selected) return []; const data = { @@ -1330,6 +1442,8 @@ export default { clearTimeout(this.submitTimeout); this.submitTimeout = null; } + const detected = this.detectRules(); + this.updateRuleFilters(detected); this.submitTimeout = setTimeout(submit.bind(this), 50); // Reduced timeout for faster response function submit() { @@ -1370,19 +1484,23 @@ export default { this.updatingCounts = true; const doUpdate = async () => { try { - const params = { - ...this.filterValues, - q: this.q, - sort: this.sort, - }; + const params = { + ...this.filterValues, + ...(this.appliedRules.length ? {} : { q: this.q }), + sort: this.sort, + }; if (this.initializing) { resolve(); return; } + if (this.appliedRules.length) { + params.rules = this.appliedRules; + } const response = await fetch("/api/v1/plants?" + qs.stringify(params)); const data = await response.json(); this.filterCounts = data.counts; - this.activeSearch = this.q; + this.activeSearch = this.appliedRules.length ? "" : this.q; + this.ruleChips = data.ruleChips || []; } finally { this.updatingCounts = false; resolve(); @@ -1412,17 +1530,21 @@ export default { } : { ...this.filterValues, - q: this.q, + ...(this.appliedRules.length ? {} : { q: this.q }), sort: this.sort, page: this.page, }; - this.activeSearch = this.q; + if (this.appliedRules.length) { + params.rules = this.appliedRules; + } + this.activeSearch = this.appliedRules.length ? "" : this.q; if (this.initializing) { // Don't send a bogus query for min 0 max 0 delete params["Height (feet)"]; } const response = await fetch("/api/v1/plants?" + qs.stringify(params)); const data = await response.json(); + this.ruleChips = data.ruleChips || []; if (!this.favorites) { this.filterCounts = data.counts; for (const filter of this.filters) { @@ -1476,12 +1598,16 @@ export default { if (chip.name === "Search") { this.q = ""; } else { + if (chip.key && chip.key.startsWith('rule:')) { + this.removeRuleFilters(chip.name); + this.appliedRules = this.appliedRules.filter((n) => n !== chip.name); + } const filter = this.filters.find((filter) => filter.name === chip.name); - if (filter.array) { + if (filter && filter.array) { this.filterValues[chip.name] = this.filterValues[chip.name].filter( (value) => value !== chip.label ); - } else { + } else if (filter) { this.filterValues[chip.name] = filter.default; } } @@ -1492,6 +1618,10 @@ export default { this.filterValues[filter.name] = filter.default; } this.q = ""; + for (const name of this.appliedRules) { + this.removeRuleFilters(name); + } + this.appliedRules = []; this.submit(); }, toggleSort() { From d7e2ce9fc38b08883e1e54f78e7e04d76a15698b Mon Sep 17 00:00:00 2001 From: ZacharyLeahan <54847203+ZacharyLeahan@users.noreply.github.com> Date: Tue, 27 May 2025 19:14:36 -0400 Subject: [PATCH 2/2] Fix search functionality: 1) Improve rule detection for multiple keywords, 2) Only show chips when search is submitted, 3) Remove Filters property from Cactus rule --- lib/queryrules.ini | 122 +++++++++++++++++++++--------------- src/components/Explorer.vue | 38 ++++++++++- 2 files changed, 106 insertions(+), 54 deletions(-) diff --git a/lib/queryrules.ini b/lib/queryrules.ini index 663a65c..1d0723b 100644 --- a/lib/queryrules.ini +++ b/lib/queryrules.ini @@ -7,6 +7,70 @@ # Keywords = comma-separated list of search terms that trigger this rule # Query = MongoDB query to execute when this rule is matched +# More specific color/feature rules first +# Specific fruit color rules +[BlueFruit] +Chip = Blue Berries/Fruit +Keywords = Blue Berries, Blue Fruit +Query = { "Fruit Color": "Blue" } +Filters = { "Fruit Color": ["Blue"] } + +[RedFruit] +Chip = Red Berries/Fruit +Keywords = Red Berries, Red Fruit +Query = { "Fruit Color": "Red" } +Filters = { "Fruit Color": ["Red"] } + +# Specific flower color rules +[BlueFlower] +Chip = Blue Flowers +Keywords = Blue, Blue Flower, Blue Flowers, Blue Blooms +Query = { "Flower Color Flags": { "$in": ["Blue"] } } +Filters = { "Flower Color Flags": ["Blue"] } + +[RedFlower] +Chip = Red Flowers +Keywords = Red, Red Flower, Red Flowers, Red Blooms +Query = { "Flower Color Flags": { "$in": ["Red"] } } +Filters = { "Flower Color Flags": ["Red"] } + +[YellowFlower] +Chip = Yellow Flowers +Keywords = Yellow, Yellow Flower, Yellow Flowers, Yellow Blooms +Query = { "Flower Color Flags": { "$in": ["Yellow"] } } +Filters = { "Flower Color Flags": ["Yellow"] } + +[PurpleFlower] +Chip = Purple Flowers +Keywords = Purple, Purple Flower, Purple Flowers, Purple Blooms, Violet +Query = { "Flower Color Flags": { "$in": ["Purple"] } } +Filters = { "Flower Color Flags": ["Purple"] } + +[WhiteFlower] +Chip = White Flowers +Keywords = White, White Flower, White Flowers, White Blooms +Query = { "Flower Color Flags": { "$in": ["White"] } } +Filters = { "Flower Color Flags": ["White"] } + +[PinkFlower] +Chip = Pink Flowers +Keywords = Pink, Pink Flower, Pink Flowers, Pink Blooms +Query = { "Flower Color Flags": { "$in": ["Pink"] } } +Filters = { "Flower Color Flags": ["Pink"] } + +[OrangeFlower] +Chip = Orange Flowers +Keywords = Orange, Orange Flower, Orange Flowers, Orange Blooms +Query = { "Flower Color Flags": { "$in": ["Orange"] } } +Filters = { "Flower Color Flags": ["Orange"] } + +[GreenFlower] +Chip = Green Flowers +Keywords = Green, Green Flower, Green Flowers, Green Blooms +Query = { "Flower Color Flags": { "$in": ["Green"] } } +Filters = { "Flower Color Flags": ["Green"] } + +# Native status rules [NativeStatus_US] Chip = Native to the Continental US Keywords = Native, Native to USA, US Native, Lower 48, continental US @@ -136,53 +200,9 @@ Keywords = Bee, Bees, Bumblebee, Bumblebees, Honey Bee Query = { "Pollinator Flags": { "$in": ["Native Bees", "Honey Bees", "Bombus"] } } Filters = { "Pollinator Flags": ["Bees"] } -[RedFlower] -Chip = Red Flowers -Keywords = Red, Red Flower, Red Flowers, Red Blooms -Query = { "Flower Color Flags": { "$in": ["Red"] } } -Filters = { "Flower Color Flags": ["Red"] } - -[BlueFlower] -Chip = Blue Flowers -Keywords = Blue, Blue Flower, Blue Flowers, Blue Blooms -Query = { "Flower Color Flags": { "$in": ["Blue"] } } -Filters = { "Flower Color Flags": ["Blue"] } - -[YellowFlower] -Chip = Yellow Flowers -Keywords = Yellow, Yellow Flower, Yellow Flowers, Yellow Blooms -Query = { "Flower Color Flags": { "$in": ["Yellow"] } } -Filters = { "Flower Color Flags": ["Yellow"] } - -[PurpleFlower] -Chip = Purple Flowers -Keywords = Purple, Purple Flower, Purple Flowers, Purple Blooms, Violet -Query = { "Flower Color Flags": { "$in": ["Purple"] } } -Filters = { "Flower Color Flags": ["Purple"] } - -[WhiteFlower] -Chip = White Flowers -Keywords = White, White Flower, White Flowers, White Blooms -Query = { "Flower Color Flags": { "$in": ["White"] } } -Filters = { "Flower Color Flags": ["White"] } +# These flower color rules have been moved to the top of the file -[PinkFlower] -Chip = Pink Flowers -Keywords = Pink, Pink Flower, Pink Flowers, Pink Blooms -Query = { "Flower Color Flags": { "$in": ["Pink"] } } -Filters = { "Flower Color Flags": ["Pink"] } - -[OrangeFlower] -Chip = Orange Flowers -Keywords = Orange, Orange Flower, Orange Flowers, Orange Blooms -Query = { "Flower Color Flags": { "$in": ["Orange"] } } -Filters = { "Flower Color Flags": ["Orange"] } - -[GreenFlower] -Chip = Green Flowers -Keywords = Green, Green Flower, Green Flowers, Green Blooms -Query = { "Flower Color Flags": { "$in": ["Green"] } } -Filters = { "Flower Color Flags": ["Green"] } +# These flower color rules have been moved to the top of the file [Superplant] Chip = Super Plant @@ -251,15 +271,13 @@ Chip = Red Berries/Fruit Keywords = Red Berries, Red Fruit Query = { "Fruit Color": "Red" } -[BlueFruit] -Chip = Blue Berries/Fruit -Keywords = Blue Berries, Blue Fruit -Query = { "Fruit Color": "Blue" } + [AsterFamily] Chip = Aster Family -Keywords = Asteraceae, Aster Family, Daisy Family, Sunflower Family, Composite +Keywords = Asteraceae, Aster Family, Daisy Family, Sunflower Family, Composite, Aster Query = { "Plant Family": "Asteraceae" } +Filters = { "Plant Family": ["Asteraceae"] } [SilverFoliage] Chip = Silver/Gray Foliage @@ -269,4 +287,4 @@ Query = { "Foliage Color": "Gray-Green" } [Cactus] Chip = Cactus Keywords = Cactus, Cacti, Succulent, Desert Plant, Prickly, Spiny -Query = { "Plant Family": { "$regex": "Cactaceae", "$options": "i" } } +Query = { "Plant Family": "Cactaceae" } \ No newline at end of file diff --git a/src/components/Explorer.vue b/src/components/Explorer.vue index 683caa2..8c81b02 100644 --- a/src/components/Explorer.vue +++ b/src/components/Explorer.vue @@ -922,7 +922,8 @@ export default { const active = this.ruleFiltersActive[chip.name]; return !(active && active[chip.label]); }); - if (this.ruleChips.length) { + // Only show rule chips when they have been explicitly submitted + if (this.ruleChips.length && this.appliedRules.length) { for (const rc of this.ruleChips) { chips.push({ name: rc.name, @@ -1211,7 +1212,12 @@ export default { detectRules() { const matches = []; if (!this.q || !this.queryRules) return matches; + + // Split the query into individual words for better matching const qLower = this.q.toLowerCase(); + const queryWords = qLower.split(/\s+/); + + // First try to match exact phrases in the complete query for (const [name, rule] of Object.entries(this.queryRules)) { for (const kw of rule.keywords) { if (qLower.includes(kw.toLowerCase())) { @@ -1220,6 +1226,27 @@ export default { } } } + + // Then try to match individual words if they are exact matches to keywords + for (const word of queryWords) { + if (word.length < 3) continue; // Skip very short words + + for (const [name, rule] of Object.entries(this.queryRules)) { + // Skip if this rule is already matched + if (matches.includes(name)) continue; + + for (const kw of rule.keywords) { + // Only match if the word is exactly the same as the keyword (case insensitive) + // or if the keyword is a single word and matches exactly + const kwLower = kw.toLowerCase(); + if (word === kwLower || (kwLower.indexOf(' ') === -1 && word === kwLower)) { + matches.push(name); + break; + } + } + } + } + return matches; }, @@ -1442,8 +1469,12 @@ export default { clearTimeout(this.submitTimeout); this.submitTimeout = null; } + // Only apply rules when user explicitly submits const detected = this.detectRules(); this.updateRuleFilters(detected); + // Force a console log to debug the detected rules + console.log('Detected rules:', detected); + console.log('Applied rules:', this.appliedRules); this.submitTimeout = setTimeout(submit.bind(this), 50); // Reduced timeout for faster response function submit() { @@ -1530,12 +1561,15 @@ export default { } : { ...this.filterValues, - ...(this.appliedRules.length ? {} : { q: this.q }), + // Always include the search query for better debugging + q: this.q, sort: this.sort, page: this.page, }; if (this.appliedRules.length) { params.rules = this.appliedRules; + // Log the rules being sent to the server + console.log('Sending rules to server:', this.appliedRules); } this.activeSearch = this.appliedRules.length ? "" : this.q; if (this.initializing) {