74 lines
2.8 KiB
Dart
74 lines
2.8 KiB
Dart
/// The addresses of the client's own pages, under the `mews:` scheme.
|
|
/// One spelling of the scheme, shared by the navigator and the page builders.
|
|
library;
|
|
|
|
/// Internal addresses come in two shapes. A page address is a navigation and
|
|
/// joins the walk history; an action address under `do/` changes something and
|
|
/// leaves the reader where they are.
|
|
///
|
|
/// View state (sort, search, tag filter) rides in the query string rather than
|
|
/// in a signal, because swipe previews are built from the address alone and
|
|
/// going back should undo a sort.
|
|
abstract final class MewsUrls {
|
|
static const scheme = 'mews';
|
|
|
|
static const hub = 'mews:hub';
|
|
static const bookmarks = 'mews:bookmarks';
|
|
static const subscriptions = 'mews:subscriptions';
|
|
static const history = 'mews:history';
|
|
static const settings = 'mews:settings';
|
|
|
|
/// Whether [url] belongs to the internal scheme.
|
|
static bool isInternal(String url) => Uri.tryParse(url)?.scheme == scheme;
|
|
|
|
/// Whether [url] changes something instead of going somewhere, e.g.
|
|
/// mews:do/set-theme?value=dark.
|
|
static bool isAction(String url) {
|
|
final uri = Uri.tryParse(url);
|
|
if (uri == null || uri.scheme != scheme) return false;
|
|
final segments = uri.pathSegments;
|
|
return segments.length == 2 && segments.first == 'do';
|
|
}
|
|
|
|
/// The verb of an action address, or null when [url] is not one.
|
|
static String? verbOf(String url) =>
|
|
isAction(url) ? Uri.parse(url).pathSegments[1] : null;
|
|
|
|
/// A single spelling for every internal address, so the walk history entry
|
|
/// and the built page's url always compare equal. Bare `mews:` is the hub.
|
|
static String canonical(String url) {
|
|
if (!isInternal(url)) return url;
|
|
final uri = Uri.parse(url);
|
|
if (uri.path.isEmpty) return hub;
|
|
return uri.removeFragment().toString();
|
|
}
|
|
|
|
/// An action address carrying [args] as its query string.
|
|
static String action(String verb, [Map<String, String> args = const {}]) =>
|
|
Uri(
|
|
scheme: scheme,
|
|
path: 'do/$verb',
|
|
queryParameters: args.isEmpty ? null : args,
|
|
).toString();
|
|
|
|
/// [base] with [changes] merged over its query string. An empty or null
|
|
/// value drops the parameter, so clearing a search box returns the whole
|
|
/// list. Merging rather than replacing keeps the other parameters, which
|
|
/// `Uri.replace(queryParameters:)` would discard.
|
|
static String page(Uri base, [Map<String, String?> changes = const {}]) {
|
|
final params = {...base.queryParameters};
|
|
for (final change in changes.entries) {
|
|
final value = change.value;
|
|
if (value == null || value.isEmpty) {
|
|
params.remove(change.key);
|
|
} else {
|
|
params[change.key] = value;
|
|
}
|
|
}
|
|
return Uri(
|
|
scheme: scheme,
|
|
path: base.path,
|
|
queryParameters: params.isEmpty ? null : params,
|
|
).toString();
|
|
}
|
|
}
|