format
This commit is contained in:
@@ -145,8 +145,10 @@ class BangUrlPattern {
|
|||||||
if (value == null) return null;
|
if (value == null) return null;
|
||||||
if (!value.startsWith(prefix)) return null;
|
if (!value.startsWith(prefix)) return null;
|
||||||
if (!value.endsWith(suffix)) return null;
|
if (!value.endsWith(suffix)) return null;
|
||||||
final captured =
|
final captured = value.substring(
|
||||||
value.substring(prefix.length, value.length - suffix.length);
|
prefix.length,
|
||||||
|
value.length - suffix.length,
|
||||||
|
);
|
||||||
return captured.isEmpty ? null : captured;
|
return captured.isEmpty ? null : captured;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,8 +157,10 @@ class BangUrlPattern {
|
|||||||
final segment = segments[pathIndex!];
|
final segment = segments[pathIndex!];
|
||||||
if (!segment.startsWith(prefix)) return null;
|
if (!segment.startsWith(prefix)) return null;
|
||||||
if (!segment.endsWith(suffix)) return null;
|
if (!segment.endsWith(suffix)) return null;
|
||||||
final captured =
|
final captured = segment.substring(
|
||||||
segment.substring(prefix.length, segment.length - suffix.length);
|
prefix.length,
|
||||||
|
segment.length - suffix.length,
|
||||||
|
);
|
||||||
return captured.isEmpty ? null : captured;
|
return captured.isEmpty ? null : captured;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,8 +242,9 @@ class BangUrlPattern {
|
|||||||
final placeholderValue = scopeComponents.queryParams[sentinelParam]!;
|
final placeholderValue = scopeComponents.queryParams[sentinelParam]!;
|
||||||
final sentinelStart = placeholderValue.indexOf(_sentinel);
|
final sentinelStart = placeholderValue.indexOf(_sentinel);
|
||||||
final prefix = placeholderValue.substring(0, sentinelStart);
|
final prefix = placeholderValue.substring(0, sentinelStart);
|
||||||
final suffix =
|
final suffix = placeholderValue.substring(
|
||||||
placeholderValue.substring(sentinelStart + _sentinel.length);
|
sentinelStart + _sentinel.length,
|
||||||
|
);
|
||||||
|
|
||||||
// The other params in the same scope become required constants.
|
// The other params in the same scope become required constants.
|
||||||
final required = <String, String>{
|
final required = <String, String>{
|
||||||
@@ -327,8 +332,9 @@ bool _requiredQueryParamsMatch(
|
|||||||
/// duplicate-keyed query params (multi-map semantics aren't worth the
|
/// duplicate-keyed query params (multi-map semantics aren't worth the
|
||||||
/// complexity for our use case).
|
/// complexity for our use case).
|
||||||
_Components? _componentsFromUri(Uri uri) {
|
_Components? _componentsFromUri(Uri uri) {
|
||||||
final segments =
|
final segments = uri.pathSegments
|
||||||
uri.pathSegments.where((s) => s.isNotEmpty).toList(growable: false);
|
.where((s) => s.isNotEmpty)
|
||||||
|
.toList(growable: false);
|
||||||
final all = uri.queryParametersAll;
|
final all = uri.queryParametersAll;
|
||||||
final params = <String, String>{};
|
final params = <String, String>{};
|
||||||
for (final entry in all.entries) {
|
for (final entry in all.entries) {
|
||||||
|
|||||||
@@ -64,14 +64,11 @@ class DesktopMode extends _$DesktopMode {
|
|||||||
// landing on a ruled host forces desktop on again, and leaving it reverts
|
// landing on a ruled host forces desktop on again, and leaving it reverts
|
||||||
// to the browser-wide default. Watching only the host keeps in-page
|
// to the browser-wide default. Watching only the host keeps in-page
|
||||||
// navigations (path/query changes) from clobbering a manual override.
|
// navigations (path/query changes) from clobbering a manual override.
|
||||||
ref.listen(
|
ref.listen(tabStateProvider(tabId), (previous, next) {
|
||||||
tabStateProvider(tabId),
|
if (previous?.url.host != next?.url.host) {
|
||||||
(previous, next) {
|
state = _resolveForHost(next?.url);
|
||||||
if (previous?.url.host != next?.url.host) {
|
}
|
||||||
state = _resolveForHost(next?.url);
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Seed the initial value from the per-site rule (falling back to the
|
// Seed the initial value from the per-site rule (falling back to the
|
||||||
// browser-wide default) so a newly opened tab's menu checkbox matches the
|
// browser-wide default) so a newly opened tab's menu checkbox matches the
|
||||||
|
|||||||
+12
-11
@@ -168,20 +168,21 @@ buildQuickTabSwitcherChipDecoration(
|
|||||||
null => null,
|
null => null,
|
||||||
},
|
},
|
||||||
side: (item, isSelected) => switch (item.color) {
|
side: (item, isSelected) => switch (item.color) {
|
||||||
final color? when isSelected => thickContainerSelectedBorder
|
final color? when isSelected =>
|
||||||
? BorderSide(
|
thickContainerSelectedBorder
|
||||||
color: ContainerColors.palette(
|
? BorderSide(
|
||||||
|
color: ContainerColors.palette(
|
||||||
|
context,
|
||||||
|
color,
|
||||||
|
useCustomColor: item.useCustomColor,
|
||||||
|
).outlineColor,
|
||||||
|
width: 2.0,
|
||||||
|
)
|
||||||
|
: ContainerColors.palette(
|
||||||
context,
|
context,
|
||||||
color,
|
color,
|
||||||
useCustomColor: item.useCustomColor,
|
useCustomColor: item.useCustomColor,
|
||||||
).outlineColor,
|
).selectedBorderSide,
|
||||||
width: 2.0,
|
|
||||||
)
|
|
||||||
: ContainerColors.palette(
|
|
||||||
context,
|
|
||||||
color,
|
|
||||||
useCustomColor: item.useCustomColor,
|
|
||||||
).selectedBorderSide,
|
|
||||||
final color? => ContainerColors.palette(
|
final color? => ContainerColors.palette(
|
||||||
context,
|
context,
|
||||||
color,
|
color,
|
||||||
|
|||||||
+13
-13
@@ -90,19 +90,19 @@ class GestureExclusionSection extends HookConsumerWidget {
|
|||||||
bool enabled,
|
bool enabled,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
await ref.read(gestureSettingsRepositoryProvider.notifier).updateSettings(
|
await ref.read(gestureSettingsRepositoryProvider.notifier).updateSettings((
|
||||||
(current) {
|
current,
|
||||||
final next = current.excludedSites.toList();
|
) {
|
||||||
if (enabled) {
|
final next = current.excludedSites.toList();
|
||||||
// Enabling gestures here => remove the host from the exclusion list.
|
if (enabled) {
|
||||||
next.remove(host);
|
// Enabling gestures here => remove the host from the exclusion list.
|
||||||
} else if (!next.contains(host)) {
|
next.remove(host);
|
||||||
// Disabling gestures here => add the host to the exclusion list.
|
} else if (!next.contains(host)) {
|
||||||
next.add(host);
|
// Disabling gestures here => add the host to the exclusion list.
|
||||||
}
|
next.add(host);
|
||||||
return current.copyWith.excludedSites(next);
|
}
|
||||||
},
|
return current.copyWith.excludedSites(next);
|
||||||
);
|
});
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
logger.e('Failed to toggle gesture exclusion', error: e, stackTrace: s);
|
logger.e('Failed to toggle gesture exclusion', error: e, stackTrace: s);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
|||||||
+2
-5
@@ -136,9 +136,7 @@ class _TabParentPickerSheet extends HookConsumerWidget {
|
|||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: const Icon(MdiIcons.fileTreeOutline),
|
leading: const Icon(MdiIcons.fileTreeOutline),
|
||||||
title: const Text('Make standalone'),
|
title: const Text('Make standalone'),
|
||||||
subtitle: const Text(
|
subtitle: const Text('Detach from current parent'),
|
||||||
'Detach from current parent',
|
|
||||||
),
|
|
||||||
enabled: movingTab.parentId != null,
|
enabled: movingTab.parentId != null,
|
||||||
onTap: () => Navigator.of(
|
onTap: () => Navigator.of(
|
||||||
context,
|
context,
|
||||||
@@ -173,8 +171,7 @@ class _TabParentPickerSheet extends HookConsumerWidget {
|
|||||||
// the one you're currently nested under".
|
// the one you're currently nested under".
|
||||||
return ListTabPreview(
|
return ListTabPreview(
|
||||||
tabId: candidate.id,
|
tabId: candidate.id,
|
||||||
isActive:
|
isActive: candidate.id == movingTab.parentId,
|
||||||
candidate.id == movingTab.parentId,
|
|
||||||
onTap: () => Navigator.of(
|
onTap: () => Navigator.of(
|
||||||
context,
|
context,
|
||||||
).pop(_ParentPickerSelected(candidate.id)),
|
).pop(_ParentPickerSelected(candidate.id)),
|
||||||
|
|||||||
+9
-13
@@ -296,8 +296,7 @@ class _TabTreesGrid extends HookConsumerWidget {
|
|||||||
|
|
||||||
final row = index ~/ 2;
|
final row = index ~/ 2;
|
||||||
final tabStart = row * itemSize.height;
|
final tabStart = row * itemSize.height;
|
||||||
final viewportDimension =
|
final viewportDimension = scrollController.position.viewportDimension;
|
||||||
scrollController.position.viewportDimension;
|
|
||||||
|
|
||||||
final targetOffset =
|
final targetOffset =
|
||||||
(tabStart - viewportDimension / 2 + itemSize.height / 2).clamp(
|
(tabStart - viewportDimension / 2 + itemSize.height / 2).clamp(
|
||||||
@@ -324,17 +323,14 @@ class _TabTreesGrid extends HookConsumerWidget {
|
|||||||
}, [filteredTabEntities, activeTab]);
|
}, [filteredTabEntities, activeTab]);
|
||||||
|
|
||||||
final tabs = useMemoized(() {
|
final tabs = useMemoized(() {
|
||||||
return filteredTabEntities.value
|
return filteredTabEntities.value.whereType<TabTreeEntity>().map((entity) {
|
||||||
.whereType<TabTreeEntity>()
|
return _TabTreePreview(
|
||||||
.map((entity) {
|
entity: entity,
|
||||||
return _TabTreePreview(
|
activeTabId: activeTab,
|
||||||
entity: entity,
|
onClose: onClose,
|
||||||
activeTabId: activeTab,
|
stackPadding: const Offset(8, 8),
|
||||||
onClose: onClose,
|
);
|
||||||
stackPadding: const Offset(8, 8),
|
}).toList();
|
||||||
);
|
|
||||||
})
|
|
||||||
.toList();
|
|
||||||
}, [filteredTabEntities, activeTab]);
|
}, [filteredTabEntities, activeTab]);
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
|
|||||||
+2
-6
@@ -143,9 +143,7 @@ class OpenSharedContent extends HookConsumerWidget {
|
|||||||
unawaited(
|
unawaited(
|
||||||
Future(() async {
|
Future(() async {
|
||||||
ContainerData? resolved;
|
ContainerData? resolved;
|
||||||
final containerRepo = ref.read(
|
final containerRepo = ref.read(containerRepositoryProvider.notifier);
|
||||||
containerRepositoryProvider.notifier,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Priority: explicit intent container (PWA shortcut) > site
|
// Priority: explicit intent container (PWA shortcut) > site
|
||||||
// assignment for the URL > mode default.
|
// assignment for the URL > mode default.
|
||||||
@@ -160,9 +158,7 @@ class OpenSharedContent extends HookConsumerWidget {
|
|||||||
final siteAssignedId = await containerRepo
|
final siteAssignedId = await containerRepo
|
||||||
.siteAssignedContainerId(Uri.parse(selectionUrlKey));
|
.siteAssignedContainerId(Uri.parse(selectionUrlKey));
|
||||||
if (siteAssignedId != null) {
|
if (siteAssignedId != null) {
|
||||||
resolved = await containerRepo.getContainerData(
|
resolved = await containerRepo.getContainerData(siteAssignedId);
|
||||||
siteAssignedId,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -631,8 +631,7 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
// Whether to surface an in-app close button so the page can be dismissed
|
// Whether to surface an in-app close button so the page can be dismissed
|
||||||
// without a system back button/gesture (opt-in, e.g. for e-ink devices).
|
// without a system back button/gesture (opt-in, e.g. for e-ink devices).
|
||||||
// Only meaningful when there is a route to pop back to.
|
// Only meaningful when there is a route to pop back to.
|
||||||
final showCloseButton =
|
final showCloseButton = context.canPop() && settings.showSearchCloseButton;
|
||||||
context.canPop() && settings.showSearchCloseButton;
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
|
|||||||
+1
-4
@@ -62,10 +62,7 @@ class ContainerDraftSuggestionsScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return math.max(
|
return math.max(
|
||||||
math.min(
|
math.min(calculatedCount, selectedContainer.value?.tabIds.length ?? 0),
|
||||||
calculatedCount,
|
|
||||||
selectedContainer.value?.tabIds.length ?? 0,
|
|
||||||
),
|
|
||||||
2,
|
2,
|
||||||
);
|
);
|
||||||
}, [screenWidth, selectedContainer.value?.tabIds.length]);
|
}, [screenWidth, selectedContainer.value?.tabIds.length]);
|
||||||
|
|||||||
+5
-9
@@ -77,10 +77,10 @@ class ColorPickerDialog extends HookWidget {
|
|||||||
child: const Text('Cancel'),
|
child: const Text('Cancel'),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop<ColorPickerResult?>(
|
onPressed: () => Navigator.pop<ColorPickerResult?>(context, (
|
||||||
context,
|
color: selectedColor.value,
|
||||||
(color: selectedColor.value, useCustomColor: useCustom.value),
|
useCustomColor: useCustom.value,
|
||||||
),
|
)),
|
||||||
child: const Text('Select'),
|
child: const Text('Select'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -191,11 +191,7 @@ class _CustomSwatch extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
final palette = isSelected
|
final palette = isSelected
|
||||||
? ContainerColors.palette(
|
? ContainerColors.palette(context, selectedColor, useCustomColor: true)
|
||||||
context,
|
|
||||||
selectedColor,
|
|
||||||
useCustomColor: true,
|
|
||||||
)
|
|
||||||
: null;
|
: null;
|
||||||
return InkResponse(
|
return InkResponse(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
|||||||
+3
-1
@@ -220,7 +220,9 @@ class _CooldownSection extends HookConsumerWidget {
|
|||||||
value: settings.intervalMs
|
value: settings.intervalMs
|
||||||
.clamp(minGestureIntervalMs, maxGestureIntervalMs)
|
.clamp(minGestureIntervalMs, maxGestureIntervalMs)
|
||||||
.toDouble(),
|
.toDouble(),
|
||||||
label: settings.intervalMs == 0 ? 'Off' : '${settings.intervalMs} ms',
|
label: settings.intervalMs == 0
|
||||||
|
? 'Off'
|
||||||
|
: '${settings.intervalMs} ms',
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
await ref
|
await ref
|
||||||
.read(gestureSettingsRepositoryProvider.notifier)
|
.read(gestureSettingsRepositoryProvider.notifier)
|
||||||
|
|||||||
+3
-2
@@ -141,8 +141,9 @@ class _SuggestAfterSection extends HookConsumerWidget {
|
|||||||
await ref
|
await ref
|
||||||
.read(gestureSettingsRepositoryProvider.notifier)
|
.read(gestureSettingsRepositoryProvider.notifier)
|
||||||
.updateSettings(
|
.updateSettings(
|
||||||
(current) =>
|
(current) => current.copyWith.minSuggestionStroke(
|
||||||
current.copyWith.minSuggestionStroke(value.round()),
|
value.round(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
|
|||||||
@@ -78,10 +78,7 @@ class _GestureActionPicker extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.fromLTRB(24, 0, 24, 8),
|
padding: const EdgeInsets.fromLTRB(24, 0, 24, 8),
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Text(
|
child: Text('Choose action', style: theme.textTheme.titleLarge),
|
||||||
'Choose action',
|
|
||||||
style: theme.textTheme.titleLarge,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|||||||
@@ -65,11 +65,7 @@ class GestureStrokeView extends StatelessWidget {
|
|||||||
if (showQualifiers) ...[
|
if (showQualifiers) ...[
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 6),
|
padding: const EdgeInsets.only(right: 6),
|
||||||
child: Icon(
|
child: Icon(stroke.startPosition.icon, size: 18, color: foreground),
|
||||||
stroke.startPosition.icon,
|
|
||||||
size: 18,
|
|
||||||
color: foreground,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 6),
|
padding: const EdgeInsets.only(right: 6),
|
||||||
|
|||||||
+5
-6
@@ -334,9 +334,7 @@ class _TabBarStackingModeSection extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
const ListTile(
|
const ListTile(
|
||||||
title: Text('Tab Stacking'),
|
title: Text('Tab Stacking'),
|
||||||
subtitle: Text(
|
subtitle: Text('How the quick tab switcher bar arranges its tabs'),
|
||||||
'How the quick tab switcher bar arranges its tabs',
|
|
||||||
),
|
|
||||||
leading: Icon(MdiIcons.folderSettings),
|
leading: Icon(MdiIcons.folderSettings),
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
),
|
),
|
||||||
@@ -505,8 +503,7 @@ class _QuickTabSwitcherTitleWidthTile extends HookConsumerWidget {
|
|||||||
onChangeEnd: enabled
|
onChangeEnd: enabled
|
||||||
? (value) async {
|
? (value) async {
|
||||||
final normalized =
|
final normalized =
|
||||||
(value / quickTabSwitcherTitleWidthStep)
|
(value / quickTabSwitcherTitleWidthStep).round() *
|
||||||
.round() *
|
|
||||||
quickTabSwitcherTitleWidthStep;
|
quickTabSwitcherTitleWidthStep;
|
||||||
sliderValue.value = normalized;
|
sliderValue.value = normalized;
|
||||||
await ref
|
await ref
|
||||||
@@ -683,7 +680,9 @@ class _QuickTabSwitcherHierarchyGlyphsTile extends HookConsumerWidget {
|
|||||||
)
|
)
|
||||||
.save(
|
.save(
|
||||||
(currentSettings) => currentSettings.copyWith
|
(currentSettings) => currentSettings.copyWith
|
||||||
.quickTabSwitcherHierarchyGlyphs(normalized),
|
.quickTabSwitcherHierarchyGlyphs(
|
||||||
|
normalized,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
|
|||||||
@@ -64,8 +64,7 @@ class TabBarPreviewHeaderDelegate extends SliverPersistentHeaderDelegate {
|
|||||||
_ => 1,
|
_ => 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
return height +
|
return height + BrowserTabBar.quickTabSwitcherHeight * quickTabSwitcherRows;
|
||||||
BrowserTabBar.quickTabSwitcherHeight * quickTabSwitcherRows;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double get _baseHeight =>
|
double get _baseHeight =>
|
||||||
|
|||||||
+7
-2
@@ -58,7 +58,9 @@ class WebSearchInfoboxCard extends HookConsumerWidget {
|
|||||||
|
|
||||||
void toggle() {
|
void toggle() {
|
||||||
ref
|
ref
|
||||||
.read(persistedBoolProvider(PersistedBoolKey.infoboxExpanded).notifier)
|
.read(
|
||||||
|
persistedBoolProvider(PersistedBoolKey.infoboxExpanded).notifier,
|
||||||
|
)
|
||||||
.toggle();
|
.toggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +349,10 @@ class _InfoboxLinks extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
foregroundColor: colorScheme.primary,
|
foregroundColor: colorScheme.primary,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -59,9 +59,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('path segment: captures search term', () {
|
test('path segment: captures search term', () {
|
||||||
final p = BangUrlPattern.parse(
|
final p = BangUrlPattern.parse('https://en.wikipedia.org/wiki/{{{s}}}');
|
||||||
'https://en.wikipedia.org/wiki/{{{s}}}',
|
|
||||||
);
|
|
||||||
expect(
|
expect(
|
||||||
p!.match(Uri.parse('https://en.wikipedia.org/wiki/Flutter')),
|
p!.match(Uri.parse('https://en.wikipedia.org/wiki/Flutter')),
|
||||||
'Flutter',
|
'Flutter',
|
||||||
@@ -69,9 +67,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('path segment: enforces non-placeholder segments', () {
|
test('path segment: enforces non-placeholder segments', () {
|
||||||
final p = BangUrlPattern.parse(
|
final p = BangUrlPattern.parse('https://www.ebay.com/sch/{{{s}}}/m.html');
|
||||||
'https://www.ebay.com/sch/{{{s}}}/m.html',
|
|
||||||
);
|
|
||||||
expect(
|
expect(
|
||||||
p!.match(Uri.parse('https://www.ebay.com/sch/toys/m.html')),
|
p!.match(Uri.parse('https://www.ebay.com/sch/toys/m.html')),
|
||||||
'toys',
|
'toys',
|
||||||
@@ -90,26 +86,17 @@ void main() {
|
|||||||
p!.match(Uri.parse('https://site.com/find/prefix-Foo-end')),
|
p!.match(Uri.parse('https://site.com/find/prefix-Foo-end')),
|
||||||
'Foo',
|
'Foo',
|
||||||
);
|
);
|
||||||
expect(
|
expect(p.match(Uri.parse('https://site.com/find/Foo-end')), isNull);
|
||||||
p.match(Uri.parse('https://site.com/find/Foo-end')),
|
|
||||||
isNull,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('host mismatch rejects', () {
|
test('host mismatch rejects', () {
|
||||||
final p = BangUrlPattern.parse('https://google.com/search?q={{{s}}}');
|
final p = BangUrlPattern.parse('https://google.com/search?q={{{s}}}');
|
||||||
expect(
|
expect(p!.match(Uri.parse('https://bing.com/search?q=foo')), isNull);
|
||||||
p!.match(Uri.parse('https://bing.com/search?q=foo')),
|
|
||||||
isNull,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('path length mismatch rejects', () {
|
test('path length mismatch rejects', () {
|
||||||
final p = BangUrlPattern.parse('https://x.com/a?q={{{s}}}');
|
final p = BangUrlPattern.parse('https://x.com/a?q={{{s}}}');
|
||||||
expect(
|
expect(p!.match(Uri.parse('https://x.com/a/b?q=foo')), isNull);
|
||||||
p!.match(Uri.parse('https://x.com/a/b?q=foo')),
|
|
||||||
isNull,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('empty captured query is rejected', () {
|
test('empty captured query is rejected', () {
|
||||||
@@ -152,7 +139,9 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('non-generic subdomains still must match', () {
|
test('non-generic subdomains still must match', () {
|
||||||
final p = BangUrlPattern.parse('https://cn.bing.com/dict/search?q={{{s}}}');
|
final p = BangUrlPattern.parse(
|
||||||
|
'https://cn.bing.com/dict/search?q={{{s}}}',
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
p!.match(Uri.parse('https://www.bing.com/dict/search?q=foo')),
|
p!.match(Uri.parse('https://www.bing.com/dict/search?q=foo')),
|
||||||
isNull,
|
isNull,
|
||||||
@@ -178,9 +167,7 @@ void main() {
|
|||||||
);
|
);
|
||||||
expect(p, isNotNull);
|
expect(p, isNotNull);
|
||||||
expect(
|
expect(
|
||||||
p!.match(
|
p!.match(Uri.parse('https://research.lensai.eu/#/s/search/quantum')),
|
||||||
Uri.parse('https://research.lensai.eu/#/s/search/quantum'),
|
|
||||||
),
|
|
||||||
'quantum',
|
'quantum',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -190,31 +177,21 @@ void main() {
|
|||||||
'https://research.lensai.eu/#/s/search/{{{s}}}',
|
'https://research.lensai.eu/#/s/search/{{{s}}}',
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
p!.match(
|
p!.match(Uri.parse('https://research.lensai.eu/#/s/answer/quantum')),
|
||||||
Uri.parse('https://research.lensai.eu/#/s/answer/quantum'),
|
|
||||||
),
|
|
||||||
isNull,
|
isNull,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('hash query: tolerates extra fragment params', () {
|
test('hash query: tolerates extra fragment params', () {
|
||||||
final p = BangUrlPattern.parse(
|
final p = BangUrlPattern.parse('https://site.example/#s={{{s}}}');
|
||||||
'https://site.example/#s={{{s}}}',
|
expect(p!.match(Uri.parse('https://site.example/#s=foo&page=2')), 'foo');
|
||||||
);
|
|
||||||
expect(
|
|
||||||
p!.match(Uri.parse('https://site.example/#s=foo&page=2')),
|
|
||||||
'foo',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fragment-required template rejects URLs without fragment', () {
|
test('fragment-required template rejects URLs without fragment', () {
|
||||||
final p = BangUrlPattern.parse(
|
final p = BangUrlPattern.parse(
|
||||||
'https://boards.4chan.org/g/catalog#s={{{s}}}',
|
'https://boards.4chan.org/g/catalog#s={{{s}}}',
|
||||||
);
|
);
|
||||||
expect(
|
expect(p!.match(Uri.parse('https://boards.4chan.org/g/catalog')), isNull);
|
||||||
p!.match(Uri.parse('https://boards.4chan.org/g/catalog')),
|
|
||||||
isNull,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -238,9 +215,7 @@ void main() {
|
|||||||
group('BangUrlPattern - tie-break helpers', () {
|
group('BangUrlPattern - tie-break helpers', () {
|
||||||
test('more required constants score higher', () {
|
test('more required constants score higher', () {
|
||||||
final plain = BangUrlPattern.parse('https://x.com/?q={{{s}}}')!;
|
final plain = BangUrlPattern.parse('https://x.com/?q={{{s}}}')!;
|
||||||
final imgs = BangUrlPattern.parse(
|
final imgs = BangUrlPattern.parse('https://x.com/?q={{{s}}}&tbm=isch')!;
|
||||||
'https://x.com/?q={{{s}}}&tbm=isch',
|
|
||||||
)!;
|
|
||||||
expect(imgs.constraintCount, greaterThan(plain.constraintCount));
|
expect(imgs.constraintCount, greaterThan(plain.constraintCount));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -60,8 +60,7 @@ class GeckoEventService extends GeckoStateEvents {
|
|||||||
ValueStream<bool> get engineReadyStateEvents => _engineStateSubject.stream;
|
ValueStream<bool> get engineReadyStateEvents => _engineStateSubject.stream;
|
||||||
ValueStream<List<String>> get tabListEvents => _tabListSubject.stream;
|
ValueStream<List<String>> get tabListEvents => _tabListSubject.stream;
|
||||||
ValueStream<String?> get selectedTabEvents => _selectedTabSubject.stream;
|
ValueStream<String?> get selectedTabEvents => _selectedTabSubject.stream;
|
||||||
ValueStream<bool> get restoreCompleteEvents =>
|
ValueStream<bool> get restoreCompleteEvents => _restoreCompleteSubject.stream;
|
||||||
_restoreCompleteSubject.stream;
|
|
||||||
|
|
||||||
Stream<TabContentState> get tabContentEvents => _tabContentSubject.stream;
|
Stream<TabContentState> get tabContentEvents => _tabContentSubject.stream;
|
||||||
Stream<HistoryEvent> get historyEvents => _historySubject.stream;
|
Stream<HistoryEvent> get historyEvents => _historySubject.stream;
|
||||||
|
|||||||
Reference in New Issue
Block a user