mirror of
https://github.com/ChrisTitusTech/winutil
synced 2026-06-05 06:37:26 +00:00
Refactor: Secure literal search and defensive scope handling (#4492)
* Refactor: Secure literal search and defensive scope handling * Refactor: Secure literal search for Tweaks Tab * Merge branch 'ChrisTitusTech:main' into fix-search-security
This commit is contained in:
@@ -3,13 +3,41 @@ function Find-AppsByNameOrDescription {
|
|||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Searches through the Apps on the Install Tab and hides all entries that do not match the string
|
Searches through the Apps on the Install Tab and hides all entries that do not match the string
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Filters application entries by name or description using literal string matching.
|
||||||
|
Respects collapsed category state and handles null $sync gracefully.
|
||||||
|
|
||||||
.PARAMETER SearchString
|
.PARAMETER SearchString
|
||||||
The string to be searched for
|
The string to be searched for. Wildcards are treated as literal characters.
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
- Uses module-scope $sync (no parameter needed; inherits from caller's scope)
|
||||||
|
- Performs literal matching (no wildcard expansion)
|
||||||
|
- Safely handles missing hashtable keys and null UI elements
|
||||||
|
- Protected by try/catch to prevent UI thread crashes
|
||||||
#>
|
#>
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory = $false)]
|
||||||
[string]$SearchString = ""
|
[string]$SearchString = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Validate that $sync exists and has required structure
|
||||||
|
if ($null -eq $sync) {
|
||||||
|
Write-Warning "Find-AppsByNameOrDescription: Global `$sync not found. Aborting search."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($null -eq $sync.ItemsControl) {
|
||||||
|
Write-Warning "Find-AppsByNameOrDescription: `$sync.ItemsControl not initialized. Aborting search."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($null -eq $sync.configs -or $null -eq $sync.configs.applicationsHashtable) {
|
||||||
|
Write-Warning "Find-AppsByNameOrDescription: `$sync.configs.applicationsHashtable not initialized. Aborting search."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
# Reset the visibility if the search string is empty or the search is cleared
|
# Reset the visibility if the search string is empty or the search is cleared
|
||||||
if ([string]::IsNullOrWhiteSpace($SearchString)) {
|
if ([string]::IsNullOrWhiteSpace($SearchString)) {
|
||||||
$sync.ItemsControl.Items | ForEach-Object {
|
$sync.ItemsControl.Items | ForEach-Object {
|
||||||
@@ -26,7 +54,8 @@ function Find-AppsByNameOrDescription {
|
|||||||
# Respect the collapsed state of categories (indicated by + prefix)
|
# Respect the collapsed state of categories (indicated by + prefix)
|
||||||
if ($categoryLabel.Content -like "+*") {
|
if ($categoryLabel.Content -like "+*") {
|
||||||
$wrapPanel.Visibility = [Windows.Visibility]::Collapsed
|
$wrapPanel.Visibility = [Windows.Visibility]::Collapsed
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
$wrapPanel.Visibility = [Windows.Visibility]::Visible
|
$wrapPanel.Visibility = [Windows.Visibility]::Visible
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +68,9 @@ function Find-AppsByNameOrDescription {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Escape wildcard characters for literal matching
|
||||||
|
$escapedSearchString = [System.Management.Automation.WildcardPattern]::Escape($SearchString)
|
||||||
|
|
||||||
# Perform search
|
# Perform search
|
||||||
$sync.ItemsControl.Items | ForEach-Object {
|
$sync.ItemsControl.Items | ForEach-Object {
|
||||||
# Each item is a StackPanel container with Children[0] = label, Children[1] = WrapPanel
|
# Each item is a StackPanel container with Children[0] = label, Children[1] = WrapPanel
|
||||||
@@ -52,8 +84,20 @@ function Find-AppsByNameOrDescription {
|
|||||||
|
|
||||||
# Search through apps in this category
|
# Search through apps in this category
|
||||||
$wrapPanel.Children | ForEach-Object {
|
$wrapPanel.Children | ForEach-Object {
|
||||||
$appEntry = $sync.configs.applicationsHashtable.$($_.Tag)
|
# Safely retrieve app entry from hashtable
|
||||||
if ($appEntry.Content -like "*$SearchString*" -or $appEntry.Description -like "*$SearchString*") {
|
$appTag = $_.Tag
|
||||||
|
$appEntry = $null
|
||||||
|
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($appTag) -and $sync.configs.applicationsHashtable.ContainsKey($appTag)) {
|
||||||
|
$appEntry = $sync.configs.applicationsHashtable[$appTag]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if app matches search criteria
|
||||||
|
if ($null -ne $appEntry) {
|
||||||
|
$contentMatch = $appEntry.Content -like "*$escapedSearchString*"
|
||||||
|
$descriptionMatch = $appEntry.Description -like "*$escapedSearchString*"
|
||||||
|
|
||||||
|
if ($contentMatch -or $descriptionMatch) {
|
||||||
# Show the App and mark that this category has a match
|
# Show the App and mark that this category has a match
|
||||||
$_.Visibility = [Windows.Visibility]::Visible
|
$_.Visibility = [Windows.Visibility]::Visible
|
||||||
$categoryHasMatch = $true
|
$categoryHasMatch = $true
|
||||||
@@ -62,6 +106,11 @@ function Find-AppsByNameOrDescription {
|
|||||||
$_.Visibility = [Windows.Visibility]::Collapsed
|
$_.Visibility = [Windows.Visibility]::Collapsed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
# Hide app if no entry found (data integrity issue)
|
||||||
|
$_.Visibility = [Windows.Visibility]::Collapsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# If category has matches, show the WrapPanel and update the category label to expanded state
|
# If category has matches, show the WrapPanel and update the category label to expanded state
|
||||||
if ($categoryHasMatch) {
|
if ($categoryHasMatch) {
|
||||||
@@ -71,10 +120,17 @@ function Find-AppsByNameOrDescription {
|
|||||||
if ($categoryLabel.Content -like "+*") {
|
if ($categoryLabel.Content -like "+*") {
|
||||||
$categoryLabel.Content = $categoryLabel.Content -replace "^\+ ", "- "
|
$categoryLabel.Content = $categoryLabel.Content -replace "^\+ ", "- "
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
# Hide the entire category container if no matches
|
# Hide the entire category container if no matches
|
||||||
$_.Visibility = [Windows.Visibility]::Collapsed
|
$_.Visibility = [Windows.Visibility]::Collapsed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Warning "Find-AppsByNameOrDescription: An error occurred during search: $_"
|
||||||
|
# Fail gracefully - do not crash the UI thread
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,98 +3,300 @@ function Find-TweaksByNameOrDescription {
|
|||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Searches through the Tweaks on the Tweaks Tab and hides all entries that do not match the search string
|
Searches through the Tweaks on the Tweaks Tab and hides all entries that do not match the search string
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Filters tweak entries by name or description using literal string matching (no wildcard expansion).
|
||||||
|
Respects collapsed category state and handles null $sync gracefully.
|
||||||
|
Safe for rapid keystroke events; no terminal spam on error conditions.
|
||||||
|
|
||||||
.PARAMETER SearchString
|
.PARAMETER SearchString
|
||||||
The string to be searched for
|
The string to be searched for. Wildcards are treated as literal characters.
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
- Uses module-scope $sync (resolved via global/script fallback if needed)
|
||||||
|
- Performs literal matching (no wildcard expansion)
|
||||||
|
- Safely handles missing UI elements and null properties
|
||||||
|
- Protected by try/catch to prevent UI thread crashes
|
||||||
|
- PowerShell 5.1 compatible (no ternary operators, no advanced language features)
|
||||||
#>
|
#>
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory = $false)]
|
||||||
[string]$SearchString = ""
|
[string]$SearchString = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
# Reset the visibility if the search string is empty or the search is cleared
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
if ([string]::IsNullOrWhiteSpace($SearchString)) {
|
# 1. RESOLVE $SYNC WITH MULTI-LEVEL FALLBACK
|
||||||
# Show all categories
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
$tweakspanel = $sync.Form.FindName("tweakspanel")
|
|
||||||
$tweakspanel.Children | ForEach-Object {
|
|
||||||
$_.Visibility = [Windows.Visibility]::Visible
|
|
||||||
|
|
||||||
# Foreach category section, show all items
|
if ($null -eq $Sync) {
|
||||||
if ($_ -is [Windows.Controls.Border]) {
|
$Sync = $global:sync
|
||||||
$_.Visibility = [Windows.Visibility]::Visible
|
if ($null -eq $Sync) {
|
||||||
|
$Sync = $script:sync
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Find ItemsControl
|
# Validate that $Sync exists and has required structure
|
||||||
$dockPanel = $_.Child
|
if ($null -eq $Sync) {
|
||||||
if ($dockPanel -is [Windows.Controls.DockPanel]) {
|
# Silent return - function called on every keystroke; no warning spam
|
||||||
$itemsControl = $dockPanel.Children | Where-Object { $_ -is [Windows.Controls.ItemsControl] }
|
|
||||||
if ($itemsControl) {
|
|
||||||
# Show items in the category
|
|
||||||
foreach ($item in $itemsControl.Items) {
|
|
||||||
if ($item -is [Windows.Controls.Label]) {
|
|
||||||
$item.Visibility = [Windows.Visibility]::Visible
|
|
||||||
} elseif ($item -is [Windows.Controls.DockPanel] -or
|
|
||||||
$item -is [Windows.Controls.StackPanel]) {
|
|
||||||
$item.Visibility = [Windows.Visibility]::Visible
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
# Search for matching tweaks when search string is not null
|
if ($null -eq $Sync.Form) {
|
||||||
$tweakspanel = $sync.Form.FindName("tweakspanel")
|
# Silent return - form not yet initialized
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
$tweakspanel.Children | ForEach-Object {
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
# 2. GET REFERENCE TO TWEAKS PANEL
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$tweaksPanel = $null
|
||||||
|
try {
|
||||||
|
$tweaksPanel = $Sync.Form.FindName("tweakspanel")
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
# Silent return - panel not found or disposed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($null -eq $tweaksPanel) {
|
||||||
|
# Silent return - panel doesn't exist
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
# 3. HANDLE EMPTY/WHITESPACE SEARCH STRING - RESET TO DEFAULT STATE
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($SearchString)) {
|
||||||
|
try {
|
||||||
|
$tweaksPanel.Children | ForEach-Object {
|
||||||
$categoryBorder = $_
|
$categoryBorder = $_
|
||||||
$categoryVisible = $false
|
|
||||||
|
|
||||||
if ($_ -is [Windows.Controls.Border]) {
|
# Safely set visibility
|
||||||
# Find the ItemsControl
|
if ($null -ne $categoryBorder) {
|
||||||
$dockPanel = $_.Child
|
$categoryBorder.Visibility = [Windows.Visibility]::Visible
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process each category
|
||||||
|
if ($categoryBorder -is [Windows.Controls.Border]) {
|
||||||
|
$dockPanel = $null
|
||||||
|
if ($null -ne $categoryBorder.Child) {
|
||||||
|
$dockPanel = $categoryBorder.Child
|
||||||
|
}
|
||||||
|
|
||||||
if ($dockPanel -is [Windows.Controls.DockPanel]) {
|
if ($dockPanel -is [Windows.Controls.DockPanel]) {
|
||||||
$itemsControl = $dockPanel.Children | Where-Object { $_ -is [Windows.Controls.ItemsControl] }
|
$itemsControl = $null
|
||||||
if ($itemsControl) {
|
$itemsControl = $dockPanel.Children | Where-Object { $_ -is [Windows.Controls.ItemsControl] } | Select-Object -First 1
|
||||||
|
|
||||||
|
if ($null -ne $itemsControl) {
|
||||||
|
# Show all items in the category
|
||||||
|
foreach ($item in $itemsControl.Items) {
|
||||||
|
if ($null -ne $item) {
|
||||||
|
# Check if it's a category label (first Label in the ItemsControl)
|
||||||
|
if ($item -is [Windows.Controls.Label]) {
|
||||||
|
$item.Visibility = [Windows.Visibility]::Visible
|
||||||
|
}
|
||||||
|
elseif ($item -is [Windows.Controls.DockPanel] -or $item -is [Windows.Controls.StackPanel]) {
|
||||||
|
# Show all checkbox containers
|
||||||
|
$item.Visibility = [Windows.Visibility]::Visible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
# Silent catch - UI element may be disposed
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
# 4. PERFORM LITERAL SEARCH (NO WILDCARD EXPANSION)
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Normalize search term once for the entire operation
|
||||||
|
$searchTerm = $SearchString
|
||||||
|
if ($null -eq $searchTerm) {
|
||||||
|
$searchTerm = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Iterate through all categories
|
||||||
|
$tweaksPanel.Children | ForEach-Object {
|
||||||
|
$categoryBorder = $_
|
||||||
|
$categoryHasMatch = $false
|
||||||
|
|
||||||
|
if ($categoryBorder -is [Windows.Controls.Border]) {
|
||||||
|
$dockPanel = $null
|
||||||
|
if ($null -ne $categoryBorder.Child) {
|
||||||
|
$dockPanel = $categoryBorder.Child
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dockPanel -is [Windows.Controls.DockPanel]) {
|
||||||
|
$itemsControl = $null
|
||||||
|
$itemsControl = $dockPanel.Children | Where-Object { $_ -is [Windows.Controls.ItemsControl] } | Select-Object -First 1
|
||||||
|
|
||||||
|
if ($null -ne $itemsControl) {
|
||||||
$categoryLabel = $null
|
$categoryLabel = $null
|
||||||
|
|
||||||
# Process all items in the ItemsControl
|
# Process all items (checkboxes, labels, panels) in the ItemsControl
|
||||||
for ($i = 0; $i -lt $itemsControl.Items.Count; $i++) {
|
for ($i = 0; $i -lt $itemsControl.Items.Count; $i++) {
|
||||||
$item = $itemsControl.Items[$i]
|
$item = $itemsControl.Items[$i]
|
||||||
|
|
||||||
|
if ($null -eq $item) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
# ────────────────────────────────────────────────────────────
|
||||||
|
# Check if this is a category label (usually first Label)
|
||||||
|
# ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
if ($item -is [Windows.Controls.Label]) {
|
if ($item -is [Windows.Controls.Label]) {
|
||||||
$categoryLabel = $item
|
$categoryLabel = $item
|
||||||
|
# Initially hide category label; show it only if matches found
|
||||||
$item.Visibility = [Windows.Visibility]::Collapsed
|
$item.Visibility = [Windows.Visibility]::Collapsed
|
||||||
} elseif ($item -is [Windows.Controls.DockPanel]) {
|
}
|
||||||
|
|
||||||
|
# ────────────────────────────────────────────────────────────
|
||||||
|
# Check if this is a DockPanel containing a tweak checkbox
|
||||||
|
# ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
elseif ($item -is [Windows.Controls.DockPanel]) {
|
||||||
|
$checkbox = $null
|
||||||
|
$label = $null
|
||||||
|
|
||||||
|
# Safely extract checkbox and label
|
||||||
$checkbox = $item.Children | Where-Object { $_ -is [Windows.Controls.CheckBox] } | Select-Object -First 1
|
$checkbox = $item.Children | Where-Object { $_ -is [Windows.Controls.CheckBox] } | Select-Object -First 1
|
||||||
$label = $item.Children | Where-Object { $_ -is [Windows.Controls.Label] } | Select-Object -First 1
|
$label = $item.Children | Where-Object { $_ -is [Windows.Controls.Label] } | Select-Object -First 1
|
||||||
|
|
||||||
if ($label -and ($label.Content -like "*$SearchString*" -or $label.ToolTip -like "*$SearchString*")) {
|
# Check if tweak matches search criteria
|
||||||
|
$itemMatches = $false
|
||||||
|
|
||||||
|
if ($null -ne $label) {
|
||||||
|
$labelContent = $label.Content
|
||||||
|
$labelToolTip = $label.ToolTip
|
||||||
|
|
||||||
|
# Safely null-check properties
|
||||||
|
if ($null -eq $labelContent) {
|
||||||
|
$labelContent = ""
|
||||||
|
}
|
||||||
|
if ($null -eq $labelToolTip) {
|
||||||
|
$labelToolTip = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Convert to string and perform LITERAL matching
|
||||||
|
$labelContentStr = [string]$labelContent
|
||||||
|
$labelToolTipStr = [string]$labelToolTip
|
||||||
|
|
||||||
|
# Use IndexOf for literal matching (no wildcard interpretation)
|
||||||
|
$contentMatch = $labelContentStr.IndexOf($searchTerm, [System.StringComparison]::OrdinalIgnoreCase) -ge 0
|
||||||
|
$toolTipMatch = $labelToolTipStr.IndexOf($searchTerm, [System.StringComparison]::OrdinalIgnoreCase) -ge 0
|
||||||
|
|
||||||
|
if ($contentMatch -or $toolTipMatch) {
|
||||||
|
$itemMatches = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set visibility based on match result
|
||||||
|
if ($itemMatches) {
|
||||||
$item.Visibility = [Windows.Visibility]::Visible
|
$item.Visibility = [Windows.Visibility]::Visible
|
||||||
if ($categoryLabel) { $categoryLabel.Visibility = [Windows.Visibility]::Visible }
|
$categoryHasMatch = $true
|
||||||
$categoryVisible = $true
|
}
|
||||||
} else {
|
else {
|
||||||
$item.Visibility = [Windows.Visibility]::Collapsed
|
$item.Visibility = [Windows.Visibility]::Collapsed
|
||||||
}
|
}
|
||||||
} elseif ($item -is [Windows.Controls.StackPanel]) {
|
}
|
||||||
# StackPanel which contain checkboxes or other elements
|
|
||||||
|
# ────────────────────────────────────────────────────────────
|
||||||
|
# Check if this is a StackPanel containing a tweak checkbox
|
||||||
|
# ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
elseif ($item -is [Windows.Controls.StackPanel]) {
|
||||||
|
$checkbox = $null
|
||||||
$checkbox = $item.Children | Where-Object { $_ -is [Windows.Controls.CheckBox] } | Select-Object -First 1
|
$checkbox = $item.Children | Where-Object { $_ -is [Windows.Controls.CheckBox] } | Select-Object -First 1
|
||||||
|
|
||||||
if ($checkbox -and ($checkbox.Content -like "*$SearchString*" -or $checkbox.ToolTip -like "*$SearchString*")) {
|
$itemMatches = $false
|
||||||
|
|
||||||
|
if ($null -ne $checkbox) {
|
||||||
|
$checkboxContent = $checkbox.Content
|
||||||
|
$checkboxToolTip = $checkbox.ToolTip
|
||||||
|
|
||||||
|
# Safely null-check properties
|
||||||
|
if ($null -eq $checkboxContent) {
|
||||||
|
$checkboxContent = ""
|
||||||
|
}
|
||||||
|
if ($null -eq $checkboxToolTip) {
|
||||||
|
$checkboxToolTip = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Convert to string and perform LITERAL matching
|
||||||
|
$checkboxContentStr = [string]$checkboxContent
|
||||||
|
$checkboxToolTipStr = [string]$checkboxToolTip
|
||||||
|
|
||||||
|
# Use IndexOf for literal matching (no wildcard interpretation)
|
||||||
|
$contentMatch = $checkboxContentStr.IndexOf($searchTerm, [System.StringComparison]::OrdinalIgnoreCase) -ge 0
|
||||||
|
$toolTipMatch = $checkboxToolTipStr.IndexOf($searchTerm, [System.StringComparison]::OrdinalIgnoreCase) -ge 0
|
||||||
|
|
||||||
|
if ($contentMatch -or $toolTipMatch) {
|
||||||
|
$itemMatches = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set visibility based on match result
|
||||||
|
if ($itemMatches) {
|
||||||
$item.Visibility = [Windows.Visibility]::Visible
|
$item.Visibility = [Windows.Visibility]::Visible
|
||||||
if ($categoryLabel) { $categoryLabel.Visibility = [Windows.Visibility]::Visible }
|
$categoryHasMatch = $true
|
||||||
$categoryVisible = $true
|
}
|
||||||
} else {
|
else {
|
||||||
$item.Visibility = [Windows.Visibility]::Collapsed
|
$item.Visibility = [Windows.Visibility]::Collapsed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ────────────────────────────────────────────────────────────
|
||||||
|
# Update category label visibility and expanded/collapsed state
|
||||||
|
# ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if ($categoryHasMatch) {
|
||||||
|
# Show category label
|
||||||
|
if ($null -ne $categoryLabel) {
|
||||||
|
$categoryLabel.Visibility = [Windows.Visibility]::Visible
|
||||||
|
|
||||||
|
# Update category label to expanded state (change "+" to "-")
|
||||||
|
$labelContent = $categoryLabel.Content
|
||||||
|
if ($null -ne $labelContent) {
|
||||||
|
$labelStr = [string]$labelContent
|
||||||
|
|
||||||
|
# Safe string replacement without -replace regex
|
||||||
|
if ($labelStr.StartsWith("+ ")) {
|
||||||
|
$expandedLabel = "- " + $labelStr.Substring(2)
|
||||||
|
$categoryLabel.Content = $expandedLabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set the visibility based on if any item matched
|
# ────────────────────────────────────────────────────────────────
|
||||||
$categoryBorder.Visibility = if ($categoryVisible) { [Windows.Visibility]::Visible } else { [Windows.Visibility]::Collapsed }
|
# Set category border visibility based on whether it has matches
|
||||||
|
# ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if ($categoryHasMatch) {
|
||||||
|
$categoryBorder.Visibility = [Windows.Visibility]::Visible
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
$categoryBorder.Visibility = [Windows.Visibility]::Collapsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
# Silent catch - UI elements may be disposed or in unexpected state
|
||||||
|
# Do not log to terminal as this function is called on every keystroke
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user