Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion app/lib/frontend/handlers/account.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
// BSD-style license that can be found in the LICENSE file.

import 'package:_pub_shared/data/account_api.dart';
import 'package:_pub_shared/search/search_form.dart';
import 'package:_pub_shared/search/tags.dart';
import 'package:clock/clock.dart';
import 'package:pub_dev/frontend/handlers/cache_control.dart';
import 'package:pub_dev/package/search_adapter.dart';
import 'package:shelf/shelf.dart' as shelf;

import '../../account/backend.dart';
Expand Down Expand Up @@ -311,7 +314,7 @@ Future<shelf.Response> accountPackagesPageHandler(shelf.Request request) async {
return htmlResponse(html);
}

/// Handles requests for GET my-liked-packages
/// Handles requests for GET /my-liked-packages
Future<shelf.Response> accountMyLikedPackagesPageHandler(
shelf.Request request,
) async {
Expand All @@ -323,11 +326,49 @@ Future<shelf.Response> accountMyLikedPackagesPageHandler(
final user = (await accountBackend.lookupUserById(
requestContext.authenticatedUserId!,
))!;

// Redirect in case of empty search query.
if (request.requestedUri.query == 'q=') {
return redirectResponse(request.requestedUri.path);
}

if (requestContext.experimentalFlags.useMyLikedSearch) {
// redirect to the search page when any search or pagination is present
final searchForm = SearchForm.parse(request.requestedUri.queryParameters);
if (searchForm.isNotEmpty) {
final redirectForm = searchForm.addRequiredTagIfAbsent(
AccountTag.isLikedByMe,
);
return redirectResponse(
redirectForm.toSearchLink(page: searchForm.currentPage),
);
}

final appliedSearchForm = SearchForm().toggleRequiredTag(
AccountTag.isLikedByMe,
);
final searchResult = await searchAdapter.search(
appliedSearchForm,
// Do not apply rate limit here.
rateLimitKey: null,
);
final html = renderMyLikedPackagesPage(
user: user,
userSessionData: requestContext.sessionData!,
likes: null,
searchForm: appliedSearchForm,
searchResult: searchResult,
);
return htmlResponse(html);
}

final likes = await likeBackend.listPackageLikes(user.userId);
final html = renderMyLikedPackagesPage(
user: user,
userSessionData: requestContext.sessionData!,
likes: likes,
searchForm: null,
searchResult: null,
);
return htmlResponse(html);
}
Expand Down
5 changes: 4 additions & 1 deletion app/lib/frontend/handlers/experimental.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ typedef PublicFlag = ({String name, String description});

const _publicFlags = <PublicFlag>{
(name: 'example', description: 'Short description'),
(
name: 'my-liked-search',
description: 'New "My liked packages" page and search.',
),
};

final _allFlags = <String>{
'dark-as-default',
'my-liked-search',
..._publicFlags.map((x) => x.name),
};

Expand Down
44 changes: 35 additions & 9 deletions app/lib/frontend/templates/admin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
// BSD-style license that can be found in the LICENSE file.

import 'package:_pub_shared/data/page_data.dart';
import 'package:_pub_shared/search/search_form.dart';
import 'package:pub_dev/frontend/templates/listing.dart';
import 'package:pub_dev/package/search_adapter.dart';

import '../../account/models.dart' show LikeData, User, SessionData;
import '../../audit/models.dart';
Expand Down Expand Up @@ -97,16 +100,38 @@ String renderAccountPackagesPage({
String renderMyLikedPackagesPage({
required User user,
required SessionData userSessionData,
required List<LikeData> likes,
required List<LikeData>? likes,
required SearchForm? searchForm,
required SearchResultPage? searchResult,
}) {
final resultCount = likes.isNotEmpty
? d.p(
text:
'You like ${likes.length} ${likes.length == 1 ? 'package' : 'packages'}.',
)
: d.p(text: 'You have not liked any packages yet.');

final tabContent = d.fragment([resultCount, likedPackageListNode(likes)]);
late d.Node tabContent;
if (likes != null) {
final resultCount = likes.isNotEmpty
? d.p(
text:
'You like ${likes.length} ${likes.length == 1 ? 'package' : 'packages'}.',
)
: d.p(text: 'You have not liked any packages yet.');

tabContent = d.fragment([resultCount, likedPackageListNode(likes)]);
} else {
final infoNode = listingInfo(
searchForm: searchForm!,
totalCount: searchResult!.totalCount,
title: 'My liked packages',
messageFromBackend: searchResult.errorMessage,
);
final listNode = packageList(searchResult);
final pagination = searchResult.hasHit
? paginationNode(PageLinks(searchForm, searchResult.totalCount))
: null;
tabContent = d.fragment([
infoNode,
listNode,
if (pagination != null) pagination,
]);
}

final content = renderDetailPage(
headerNode: _accountDetailHeader(user, userSessionData),
tabs: [
Expand All @@ -129,6 +154,7 @@ String renderMyLikedPackagesPage({
noIndex: true,
mainClasses: [wideHeaderDetailPageClassName],
pageData: PageData(sessionAware: true),
searchForm: searchForm,
);
}

Expand Down
12 changes: 8 additions & 4 deletions app/lib/frontend/templates/layout.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ enum PageType {

/// Whether to show a wide/tall search banner at the top of the page,
/// otherwise only show a top-navigation search input.
bool showSearchBanner(PageType type) =>
type != PageType.account &&
bool showSearchBanner(PageType type, SearchForm? searchForm) =>
(type != PageType.account || searchForm != null) &&
type != PageType.package &&
type != PageType.standalone;

Expand Down Expand Up @@ -99,7 +99,11 @@ String renderLayoutPage(
? null
: pageDataJsonCodec.encode(pageData.toJson()),
bodyClasses: bodyClasses,
siteHeader: siteHeaderNode(pageType: type, userSession: session),
siteHeader: siteHeaderNode(
pageType: type,
userSession: session,
searchForm: searchForm,
),
announcementBanner: announcementBannerHtml == null
? null
: d.unsafeRawHtml(announcementBannerHtml),
Expand All @@ -108,7 +112,7 @@ String renderLayoutPage(
: hex
.encode(sha1.convert(utf8.encode(announcementBannerHtml)).bytes)
.substring(0, 16),
searchBanner: showSearchBanner(type)
searchBanner: showSearchBanner(type, searchForm)
? _renderSearchBanner(type: type, searchForm: searchForm)
: null,
isLanding: type == PageType.landing,
Expand Down
10 changes: 8 additions & 2 deletions app/lib/frontend/templates/views/shared/site_header.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:_pub_shared/search/search_form.dart';

import '../../../../account/models.dart' show SessionData;
import '../../../../shared/urls.dart' as urls;
import '../../../dom/dom.dart' as d;
Expand All @@ -10,7 +12,11 @@ import '../../_consts.dart';
import '../../layout.dart' show PageType, showSearchBanner;

/// Creates the site header and navigation node.
d.Node siteHeaderNode({required PageType pageType, SessionData? userSession}) {
d.Node siteHeaderNode({
required PageType pageType,
required SessionData? userSession,
required SearchForm? searchForm,
}) {
return d.div(
classes: ['site-header'],
children: [
Expand All @@ -31,7 +37,7 @@ d.Node siteHeaderNode({required PageType pageType, SessionData? userSession}) {
),
d.div(classes: ['site-header-space']),
d.div(classes: ['site-header-mask']),
if (!showSearchBanner(pageType))
if (!showSearchBanner(pageType, searchForm))
d.div(
classes: ['site-header-search'],
child: d.form(
Expand Down
2 changes: 2 additions & 0 deletions app/test/frontend/templates_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,8 @@ void main() {
LikeData(package: 'super_package', created: liked1),
LikeData(package: 'another_package', created: liked2),
],
searchForm: null,
searchResult: null,
);
expectGoldenFile(
html,
Expand Down
5 changes: 5 additions & 0 deletions pkg/pub_integration/test/like_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ void main() {
await Future.delayed(Duration(milliseconds: 200));
expect(await getCountLabels(), ['1', '1', '']);

// checking /my-liked-packages - with the one liked package
await page.gotoOrigin('/my-liked-packages');
final info = await listingPageInfo(page);
expect(info.packageNames.toSet(), {'test_pkg'});

// checking search with my-liked packages - with the one liked package
await page.gotoOrigin('/packages?q=pkg+is:liked-by-me');
final info2 = await listingPageInfo(page);
Expand Down