aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShadowghost <Shadowghost@users.noreply.github.com>2026-01-18 11:30:41 -0500
committerBond_009 <bond.009@outlook.com>2026-01-18 11:30:41 -0500
commitc4ffc357a3d3658526f6fd879364145333eea6b0 (patch)
treeb63262687b50d40c3bc4b773f07518974dd45e42
parentafcaec0a894df038f8b88a517a01bace0d3c237c (diff)
Backport pull request #15983 from jellyfin/release-10.11.z
Prioritize better matches on search Original-merge: a518160a6ff471541b7daae6d54c8b896bb1f2e6 Merged-by: crobibero <cody@robibe.ro> Backported-by: Bond_009 <bond.009@outlook.com>
-rw-r--r--Jellyfin.Server.Implementations/Item/BaseItemRepository.cs29
-rw-r--r--Jellyfin.Server.Implementations/Item/OrderMapper.cs27
2 files changed, 45 insertions, 11 deletions
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
index 646a9c4836..a2f0e78c03 100644
--- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
+++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
@@ -1615,29 +1615,36 @@ public sealed class BaseItemRepository
IOrderedQueryable<BaseItemEntity>? orderedQuery = null;
+ // When searching, prioritize by match quality: exact match > prefix match > contains
+ if (hasSearch)
+ {
+ orderedQuery = query.OrderBy(OrderMapper.MapSearchRelevanceOrder(filter.SearchTerm!));
+ }
+
var firstOrdering = orderBy.FirstOrDefault();
if (firstOrdering != default)
{
var expression = OrderMapper.MapOrderByField(firstOrdering.OrderBy, filter, context);
- if (firstOrdering.SortOrder == SortOrder.Ascending)
+ if (orderedQuery is null)
{
- orderedQuery = query.OrderBy(expression);
+ // No search relevance ordering, start fresh
+ orderedQuery = firstOrdering.SortOrder == SortOrder.Ascending
+ ? query.OrderBy(expression)
+ : query.OrderByDescending(expression);
}
else
{
- orderedQuery = query.OrderByDescending(expression);
+ // Search relevance ordering already applied, chain with ThenBy
+ orderedQuery = firstOrdering.SortOrder == SortOrder.Ascending
+ ? orderedQuery.ThenBy(expression)
+ : orderedQuery.ThenByDescending(expression);
}
if (firstOrdering.OrderBy is ItemSortBy.Default or ItemSortBy.SortName)
{
- if (firstOrdering.SortOrder is SortOrder.Ascending)
- {
- orderedQuery = orderedQuery.ThenBy(e => e.Name);
- }
- else
- {
- orderedQuery = orderedQuery.ThenByDescending(e => e.Name);
- }
+ orderedQuery = firstOrdering.SortOrder is SortOrder.Ascending
+ ? orderedQuery.ThenBy(e => e.Name)
+ : orderedQuery.ThenByDescending(e => e.Name);
}
}
diff --git a/Jellyfin.Server.Implementations/Item/OrderMapper.cs b/Jellyfin.Server.Implementations/Item/OrderMapper.cs
index 192ee74996..1ae7cc6c4a 100644
--- a/Jellyfin.Server.Implementations/Item/OrderMapper.cs
+++ b/Jellyfin.Server.Implementations/Item/OrderMapper.cs
@@ -6,6 +6,7 @@ using System.Linq.Expressions;
using Jellyfin.Data.Enums;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities;
using Microsoft.EntityFrameworkCore;
@@ -68,4 +69,30 @@ public static class OrderMapper
_ => e => e.SortName
};
}
+
+ /// <summary>
+ /// Creates an expression to order search results by match quality.
+ /// Prioritizes: exact match (0) > prefix match with word boundary (1) > prefix match (2) > contains (3).
+ /// </summary>
+ /// <param name="searchTerm">The search term to match against.</param>
+ /// <returns>An expression that returns an integer representing match quality (lower is better).</returns>
+ public static Expression<Func<BaseItemEntity, int>> MapSearchRelevanceOrder(string searchTerm)
+ {
+ var cleanSearchTerm = GetCleanValue(searchTerm);
+ var searchPrefix = cleanSearchTerm + " ";
+ return e =>
+ e.CleanName == cleanSearchTerm ? 0 :
+ e.CleanName!.StartsWith(searchPrefix) ? 1 :
+ e.CleanName!.StartsWith(cleanSearchTerm) ? 2 : 3;
+ }
+
+ private static string GetCleanValue(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return value;
+ }
+
+ return value.RemoveDiacritics().ToLowerInvariant();
+ }
}