Skip to content

Commit e1b45be

Browse files
committed
Promote name matches to top position + add name match badge after the title.
1 parent 29bd04d commit e1b45be

File tree

9 files changed

+40
-50
lines changed

9 files changed

+40
-50
lines changed

app/lib/frontend/templates/admin.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ String renderAccountPackagesPage({
5858
searchForm: null,
5959
sdkLibraryHits: [],
6060
packageHits: packageHits,
61+
nameMatches: null,
6162
),
6263
if (nextPackage != null)
6364
d.div(

app/lib/frontend/templates/listing.dart

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@
55
import 'dart:math';
66

77
import 'package:_pub_shared/search/search_form.dart';
8-
import 'package:collection/collection.dart';
98

109
import '../../package/search_adapter.dart';
1110
import '../../search/search_service.dart';
12-
import '../../shared/urls.dart' as urls;
1311
import '../dom/dom.dart' as d;
1412

1513
import '_consts.dart';
@@ -29,6 +27,7 @@ d.Node packageList(SearchResultPage searchResultPage) {
2927
searchForm: searchResultPage.form,
3028
sdkLibraryHits: searchResultPage.sdkLibraryHits,
3129
packageHits: searchResultPage.packageHits,
30+
nameMatches: searchResultPage.nameMatches,
3231
);
3332
}
3433

@@ -50,7 +49,6 @@ String renderPkgIndexPage(
5049
title: topPackages,
5150
messageFromBackend: searchResultPage.errorMessage,
5251
),
53-
nameMatches: _nameMatches(searchForm, searchResultPage.nameMatches),
5452
packageList: packageList(searchResultPage),
5553
pagination: searchResultPage.hasHit ? paginationNode(links) : null,
5654
openSections: openSections,
@@ -123,27 +121,3 @@ class PageLinks {
123121
return min(fromSymmetry, max(currentPage!, fromCount));
124122
}
125123
}
126-
127-
d.Node? _nameMatches(SearchForm form, List<String>? matches) {
128-
if (matches == null || matches.isEmpty) {
129-
return null;
130-
}
131-
final singular = matches.length == 1;
132-
final isExactNameMatch = singular && form.parsedQuery.text == matches.single;
133-
final nameMatchLabel = isExactNameMatch
134-
? 'Exact package name match: '
135-
: 'Matching package ${singular ? 'name' : 'names'}: ';
136-
137-
return d.p(children: [
138-
d.text(nameMatchLabel),
139-
...matches.expandIndexed((i, name) {
140-
return [
141-
if (i > 0) d.text(', '),
142-
d.a(
143-
href: urls.pkgPageUrl(name),
144-
child: d.b(text: name),
145-
),
146-
];
147-
}),
148-
]);
149-
}

app/lib/frontend/templates/package_misc.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ final flutterFavoriteBadgeNode = packageBadgeNode(
2929
),
3030
);
3131

32+
/// Renders the name match badge node, used for exact package name hits.
33+
final nameMatchBadgeNode = packageBadgeNode(label: 'Name match');
34+
3235
/// Renders the null-safe badge used by package listing and package page.
3336
d.Node nullSafeBadgeNode({String? title}) {
3437
return packageBadgeNode(

app/lib/frontend/templates/views/pkg/index.dart

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,12 @@ import '../../../static_files.dart';
1313
d.Node packageListingNode({
1414
required SearchForm searchForm,
1515
required d.Node listingInfo,
16-
required d.Node? nameMatches,
1716
required d.Node packageList,
1817
required d.Node? pagination,
1918
required Set<String>? openSections,
2019
}) {
2120
final innerContent = d.fragment([
2221
listingInfo,
23-
if (nameMatches != null)
24-
d.div(
25-
classes: ['listing-highlight-block'],
26-
child: nameMatches,
27-
),
2822
packageList,
2923
if (pagination != null) pagination,
3024
d.markdown('Check our help page for details on '

app/lib/frontend/templates/views/pkg/package_list.dart

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,19 @@ d.Node listOfPackagesNode({
2727
required SearchForm? searchForm,
2828
required List<SdkLibraryHit> sdkLibraryHits,
2929
required List<PackageView> packageHits,
30+
required List<String>? nameMatches,
3031
}) {
32+
final bestNameMatch =
33+
(nameMatches == null || nameMatches.isEmpty) ? null : nameMatches.first;
3134
return d.div(
3235
classes: ['packages'],
3336
children: [
3437
...sdkLibraryHits.map(_sdkLibraryItem),
35-
...packageHits.map((hit) => _packageItem(hit, searchForm: searchForm)),
38+
...packageHits.map((hit) => _packageItem(
39+
hit,
40+
searchForm: searchForm,
41+
isNameMatch: hit.name == bestNameMatch,
42+
)),
3643
imageCarousel(),
3744
],
3845
);
@@ -48,6 +55,7 @@ d.Node _sdkLibraryItem(SdkLibraryHit hit) {
4855
return _item(
4956
url: hit.url!,
5057
name: hit.library!,
58+
isNameMatch: false,
5159
newTimestamp: null,
5260
labeledScoresNode: null,
5361
description: hit.description ?? '',
@@ -71,6 +79,7 @@ d.Node _sdkLibraryItem(SdkLibraryHit hit) {
7179
d.Node _packageItem(
7280
PackageView view, {
7381
required SearchForm? searchForm,
82+
required bool isNameMatch,
7483
}) {
7584
final isFlutterFavorite = view.tags.contains(PackageTags.isFlutterFavorite);
7685
final isNullSafe = view.tags.contains(PackageVersionTags.isNullSafe);
@@ -177,6 +186,7 @@ d.Node _packageItem(
177186
screenshotDescriptions: screenshotDescriptions,
178187
url: urls.pkgPageUrl(view.name),
179188
name: view.name,
189+
isNameMatch: isNameMatch,
180190
newTimestamp: view.created,
181191
labeledScoresNode: labeledScoresNodeFromPackageView(view),
182192
description: view.ellipsizedDescription ?? '',
@@ -207,6 +217,7 @@ d.Node _item({
207217
List<d.Node> topics = const [],
208218
required String url,
209219
required String name,
220+
required bool isNameMatch,
210221
required DateTime? newTimestamp,
211222
required d.Node? labeledScoresNode,
212223
required String description,
@@ -230,6 +241,8 @@ d.Node _item({
230241
], children: [
231242
d.a(href: url, text: name),
232243
if (copyIcon != null) copyIcon,
244+
d.text(' '),
245+
if (isNameMatch) nameMatchBadgeNode,
233246
]),
234247
if (age != null && age.inDays <= 30)
235248
d.div(

app/lib/search/mem_index.dart

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ final _textSearchTimeout = Duration(milliseconds: 500);
2323
class InMemoryPackageIndex {
2424
final List<PackageDocument> _documents;
2525
final _documentsByName = <String, PackageDocument>{};
26+
final _nameToIndex = <String, int>{};
2627
late final PackageNameIndex _packageNameIndex;
2728
late final TokenIndex<String> _descrIndex;
2829
late final TokenIndex<String> _readmeIndex;
@@ -62,6 +63,7 @@ class InMemoryPackageIndex {
6263
for (var i = 0; i < _documents.length; i++) {
6364
final doc = _documents[i];
6465
_documentsByName[doc.package] = doc;
66+
_nameToIndex[doc.package] = i;
6567

6668
// transform tags into numberical IDs
6769
final tagIds = <int>[];
@@ -268,8 +270,11 @@ class InMemoryPackageIndex {
268270
/// it linearly into the [0.4-1.0] range, to allow better
269271
/// multiplication outcomes.
270272
packageScores.multiplyAllFromValues(_adjustedOverallScores);
271-
indexedHits = _rankWithValues(packageScores,
272-
requiredLengthThreshold: query.offset);
273+
indexedHits = _rankWithValues(
274+
packageScores,
275+
requiredLengthThreshold: query.offset,
276+
bestNameMatch: bestNameMatch,
277+
);
273278
break;
274279
case SearchOrder.text:
275280
indexedHits = _rankWithValues(packageScores,
@@ -471,11 +476,14 @@ class InMemoryPackageIndex {
471476
IndexedScore<String> score, {
472477
// if the item count is fewer than this threshold, an empty list will be returned
473478
int? requiredLengthThreshold,
479+
String? bestNameMatch,
474480
}) {
475481
final list = <IndexedPackageHit>[];
482+
final bestNameIndex =
483+
bestNameMatch == null ? null : _nameToIndex[bestNameMatch];
476484
for (var i = 0; i < score.length; i++) {
477485
final value = score.getValue(i);
478-
if (value <= 0.0) continue;
486+
if (value <= 0.0 && i != bestNameIndex) continue;
479487
list.add(IndexedPackageHit(
480488
i, PackageHit(package: score.keys[i], score: value)));
481489
}
@@ -484,6 +492,8 @@ class InMemoryPackageIndex {
484492
return [];
485493
}
486494
list.sort((a, b) {
495+
if (a.index == bestNameIndex) return -1;
496+
if (b.index == bestNameIndex) return 1;
487497
final scoreCompare = -a.hit.score!.compareTo(b.hit.score!);
488498
if (scoreCompare != 0) return scoreCompare;
489499
// if two packages got the same score, order by last updated

app/test/search/mem_index_test.dart

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -636,9 +636,9 @@ server.dart adds a small, prescriptive server (PicoServer) that can be configure
636636
'nameMatches': ['abc'],
637637
'sdkLibraryHits': [],
638638
'packageHits': [
639-
// `abc` is at its natural place
640-
{'package': 'def', 'score': closeTo(0.85, 0.01)},
639+
// `abc` is at the first position, score is kept
641640
{'package': 'abc', 'score': closeTo(0.70, 0.01)},
641+
{'package': 'def', 'score': closeTo(0.85, 0.01)},
642642
]
643643
});
644644
// exact name match with tags
@@ -651,9 +651,9 @@ server.dart adds a small, prescriptive server (PicoServer) that can be configure
651651
'nameMatches': ['abc'],
652652
'sdkLibraryHits': [],
653653
'packageHits': [
654-
// `abc` is at its natural place
655-
{'package': 'def', 'score': closeTo(0.85, 0.01)},
654+
// `abc` is at the first position, score is kept
656655
{'package': 'abc', 'score': closeTo(0.70, 0.01)},
656+
{'package': 'def', 'score': closeTo(0.85, 0.01)},
657657
]
658658
});
659659
// absent exact name match with tags
@@ -662,11 +662,12 @@ server.dart adds a small, prescriptive server (PicoServer) that can be configure
662662
.toJson(),
663663
{
664664
'timestamp': isNotEmpty,
665-
'totalCount': 1,
665+
'totalCount': 2,
666666
'nameMatches': ['abc'],
667667
'sdkLibraryHits': [],
668668
'packageHits': [
669-
// `abc` is not present in the package list
669+
// `abc` is at the first position, score is zero
670+
{'package': 'abc', 'score': 0.0},
670671
{'package': 'def', 'score': closeTo(0.85, 0.01)},
671672
]
672673
});

pkg/web_css/lib/src/_list.scss

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,6 @@
4646
}
4747
}
4848

49-
.listing-highlight-block {
50-
border-left: 0.25em solid var(--pub-markdown-alert-note);
51-
padding: .5rem 1rem;
52-
margin: 4px 0px;
53-
}
54-
5549
.sort-control {
5650
position: relative;
5751
cursor: pointer;

pkg/web_css/lib/src/_pkg.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@
511511
display: inline-block;
512512
height: 20px;
513513
width: 20px;
514-
margin-left: 12px;
514+
margin: 0px 12px;
515515

516516
.pkg-page-title-copy-icon {
517517
display: block;

0 commit comments

Comments
 (0)