aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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();
+ }
}