add uri normalization
This commit is contained in:
@@ -29,4 +29,14 @@ extension UriX on Uri {
|
|||||||
bool get isHttp => isScheme('http');
|
bool get isHttp => isScheme('http');
|
||||||
bool get isHttps => isScheme('https');
|
bool get isHttps => isScheme('https');
|
||||||
bool get isHttpOrHttps => isHttp || isHttps;
|
bool get isHttpOrHttps => isHttp || isHttps;
|
||||||
|
|
||||||
|
/// Removes a bare root path (`/`) when there is no query or fragment, so
|
||||||
|
/// that `https://example.com/` and `https://example.com` are treated as
|
||||||
|
/// equivalent.
|
||||||
|
Uri get normalized {
|
||||||
|
if (path == '/' && !hasQuery && !hasFragment) {
|
||||||
|
return replace(path: '');
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,7 +168,8 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
if (startedWithUrl) {
|
if (startedWithUrl) {
|
||||||
hasUserModifiedInput.value = text != initialSearchText;
|
hasUserModifiedInput.value = text != initialSearchText;
|
||||||
}
|
}
|
||||||
isUrlInput.value = text.isNotEmpty &&
|
isUrlInput.value =
|
||||||
|
text.isNotEmpty &&
|
||||||
classifyAddressBarInput(text) is NavigateInputClassification;
|
classifyAddressBarInput(text) is NavigateInputClassification;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -371,26 +372,18 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final emptyStateWidgets = <SearchModuleType, Widget>{
|
final emptyStateWidgets = <SearchModuleType, Widget>{
|
||||||
SearchModuleType.topSites: TopSitesSection(
|
SearchModuleType.topSites: TopSitesSection(onUriSelected: openUriInTab),
|
||||||
onUriSelected: openUriInTab,
|
|
||||||
),
|
|
||||||
SearchModuleType.recentArticles: RecentFeedArticlesSection(
|
SearchModuleType.recentArticles: RecentFeedArticlesSection(
|
||||||
onArticleSelected: (article) {
|
onArticleSelected: (article) {
|
||||||
unawaited(
|
FeedArticleRoute(articleId: article.id).pushReplacement(context);
|
||||||
FeedArticleRoute(articleId: article.id).push(context),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SearchModuleType.recentTabs: RecentTabsSection(
|
SearchModuleType.recentTabs: RecentTabsSection(
|
||||||
onTabSelected: (tabId) async {
|
onTabSelected: (tabId) async {
|
||||||
await ref
|
await ref.read(tabRepositoryProvider.notifier).selectTab(tabId);
|
||||||
.read(tabRepositoryProvider.notifier)
|
|
||||||
.selectTab(tabId);
|
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ref
|
ref.read(bottomSheetControllerProvider.notifier).requestDismiss();
|
||||||
.read(bottomSheetControllerProvider.notifier)
|
|
||||||
.requestDismiss();
|
|
||||||
const BrowserRoute().go(context);
|
const BrowserRoute().go(context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -436,9 +429,7 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
};
|
};
|
||||||
|
|
||||||
final searchWidgets = <SearchModuleType, Widget>{
|
final searchWidgets = <SearchModuleType, Widget>{
|
||||||
SearchModuleType.tabs: TabSearch(
|
SearchModuleType.tabs: TabSearch(searchTextListenable: sampledSearchText),
|
||||||
searchTextListenable: sampledSearchText,
|
|
||||||
),
|
|
||||||
SearchModuleType.bookmarks: BookmarkSearch(
|
SearchModuleType.bookmarks: BookmarkSearch(
|
||||||
searchTextListenable: sampledSearchText,
|
searchTextListenable: sampledSearchText,
|
||||||
onUriSelected: openUriInTab,
|
onUriSelected: openUriInTab,
|
||||||
@@ -671,9 +662,7 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
(!isUrlInput.value ||
|
(!isUrlInput.value ||
|
||||||
entry.type != SearchModuleType.articles))
|
entry.type != SearchModuleType.articles))
|
||||||
searchWidgets[entry.type]!,
|
searchWidgets[entry.type]!,
|
||||||
const _CustomizeSectionsButton(
|
const _CustomizeSectionsButton(group: SearchModuleGroup.search),
|
||||||
group: SearchModuleGroup.search,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:weblibre/extensions/uri.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/top_sites/data/database/daos/top_site.drift.dart';
|
import 'package:weblibre/features/geckoview/features/top_sites/data/database/daos/top_site.drift.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/top_sites/data/database/database.dart';
|
import 'package:weblibre/features/geckoview/features/top_sites/data/database/database.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/top_sites/data/database/definitions.drift.dart';
|
import 'package:weblibre/features/geckoview/features/top_sites/data/database/definitions.drift.dart';
|
||||||
@@ -42,7 +43,8 @@ class TopSiteDao extends DatabaseAccessor<TopSiteDatabase>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<TopSiteData?> getPersistedTopSiteByUrl(Uri url) {
|
Future<TopSiteData?> getPersistedTopSiteByUrl(Uri url) {
|
||||||
return (db.topSite.select()..where((t) => t.url.equalsValue(url)))
|
return (db.topSite.select()
|
||||||
|
..where((t) => t.url.equalsValue(url.normalized)))
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +65,7 @@ class TopSiteDao extends DatabaseAccessor<TopSiteDatabase>
|
|||||||
TopSiteCompanion.insert(
|
TopSiteCompanion.insert(
|
||||||
id: id,
|
id: id,
|
||||||
title: title,
|
title: title,
|
||||||
url: url,
|
url: url.normalized,
|
||||||
source: StoredTopSiteSource.pinned,
|
source: StoredTopSiteSource.pinned,
|
||||||
orderKey: orderKey,
|
orderKey: orderKey,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
@@ -83,7 +85,7 @@ class TopSiteDao extends DatabaseAccessor<TopSiteDatabase>
|
|||||||
required Uri url,
|
required Uri url,
|
||||||
}) {
|
}) {
|
||||||
return (db.topSite.update()..where((t) => t.id.equals(id))).write(
|
return (db.topSite.update()..where((t) => t.id.equals(id))).write(
|
||||||
TopSiteCompanion(title: Value(title), url: Value(url)),
|
TopSiteCompanion(title: Value(title), url: Value(url.normalized)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-4
@@ -22,6 +22,7 @@ import 'dart:async';
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:weblibre/core/uuid.dart';
|
import 'package:weblibre/core/uuid.dart';
|
||||||
|
import 'package:weblibre/extensions/uri.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/history/domain/repositories/history.dart';
|
import 'package:weblibre/features/geckoview/features/history/domain/repositories/history.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/top_sites/data/database/definitions.drift.dart';
|
import 'package:weblibre/features/geckoview/features/top_sites/data/database/definitions.drift.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/top_sites/data/entities/stored_top_site_source.dart';
|
import 'package:weblibre/features/geckoview/features/top_sites/data/entities/stored_top_site_source.dart';
|
||||||
@@ -55,7 +56,7 @@ class TopSiteRepository extends _$TopSiteRepository {
|
|||||||
TopSiteCompanion.insert(
|
TopSiteCompanion.insert(
|
||||||
id: uuid.v7(),
|
id: uuid.v7(),
|
||||||
title: seeds[i].title,
|
title: seeds[i].title,
|
||||||
url: seeds[i].url,
|
url: seeds[i].url.normalized,
|
||||||
source: StoredTopSiteSource.seeded,
|
source: StoredTopSiteSource.seeded,
|
||||||
orderKey: orderKey,
|
orderKey: orderKey,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
@@ -80,7 +81,9 @@ class TopSiteRepository extends _$TopSiteRepository {
|
|||||||
final remaining = targetCount - persisted.length;
|
final remaining = targetCount - persisted.length;
|
||||||
final historyItems = await _getHistoryItems(
|
final historyItems = await _getHistoryItems(
|
||||||
limit: remaining,
|
limit: remaining,
|
||||||
excludeUrls: persisted.map((s) => s.url.toString()).toSet(),
|
excludeUrls: persisted
|
||||||
|
.map((s) => s.url.normalized.toString())
|
||||||
|
.toSet(),
|
||||||
);
|
);
|
||||||
|
|
||||||
return [...persisted, ...historyItems];
|
return [...persisted, ...historyItems];
|
||||||
@@ -103,7 +106,9 @@ class TopSiteRepository extends _$TopSiteRepository {
|
|||||||
final remaining = targetCount - persistedItems.length;
|
final remaining = targetCount - persistedItems.length;
|
||||||
final historyItems = await _getHistoryItems(
|
final historyItems = await _getHistoryItems(
|
||||||
limit: remaining,
|
limit: remaining,
|
||||||
excludeUrls: persistedItems.map((s) => s.url.toString()).toSet(),
|
excludeUrls: persistedItems
|
||||||
|
.map((s) => s.url.normalized.toString())
|
||||||
|
.toSet(),
|
||||||
);
|
);
|
||||||
|
|
||||||
return [...persistedItems, ...historyItems];
|
return [...persistedItems, ...historyItems];
|
||||||
@@ -259,7 +264,7 @@ class TopSiteRepository extends _$TopSiteRepository {
|
|||||||
final uri = Uri.tryParse(site.url);
|
final uri = Uri.tryParse(site.url);
|
||||||
if (uri == null) continue;
|
if (uri == null) continue;
|
||||||
|
|
||||||
if (excludeUrls.contains(uri.toString())) continue;
|
if (excludeUrls.contains(uri.normalized.toString())) continue;
|
||||||
|
|
||||||
final title = (site.title?.trim().isNotEmpty == true)
|
final title = (site.title?.trim().isNotEmpty == true)
|
||||||
? site.title!.trim()
|
? site.title!.trim()
|
||||||
|
|||||||
Reference in New Issue
Block a user