95 lines
3.0 KiB
Dart
95 lines
3.0 KiB
Dart
/// Parsed page of Talk's `talk-message-current` search provider.
|
|
class SearchChatMessagesResponse {
|
|
final List<SearchChatMessagesEntry> entries;
|
|
|
|
/// Opaque pagination cursor; `null` when the provider has no further page.
|
|
final Object? cursor;
|
|
|
|
const SearchChatMessagesResponse({required this.entries, this.cursor});
|
|
|
|
factory SearchChatMessagesResponse.fromJson(
|
|
Map<String, dynamic> json, {
|
|
required String term,
|
|
}) {
|
|
final rawEntries = json['entries'];
|
|
final entries = rawEntries is List
|
|
? rawEntries
|
|
.whereType<Map<String, dynamic>>()
|
|
.map((e) => SearchChatMessagesEntry.fromJson(e, term: term))
|
|
.whereType<SearchChatMessagesEntry>()
|
|
.toList()
|
|
: const <SearchChatMessagesEntry>[];
|
|
final paginated = json['isPaginated'] == true;
|
|
final cursor = json['cursor'];
|
|
return SearchChatMessagesResponse(
|
|
entries: entries,
|
|
cursor: paginated && entries.isNotEmpty && cursor != null ? cursor : null,
|
|
);
|
|
}
|
|
}
|
|
|
|
class SearchChatMessagesEntry {
|
|
final int messageId;
|
|
final String? actorId;
|
|
final String? actorDisplayName;
|
|
|
|
/// Server-built excerpt of the message, placeholders already resolved.
|
|
final String excerpt;
|
|
final DateTime? sentAt;
|
|
|
|
const SearchChatMessagesEntry({
|
|
required this.messageId,
|
|
required this.excerpt,
|
|
this.actorId,
|
|
this.actorDisplayName,
|
|
this.sentAt,
|
|
});
|
|
|
|
/// Returns `null` for entries without a recognisable message id.
|
|
static SearchChatMessagesEntry? fromJson(
|
|
Map<String, dynamic> json, {
|
|
required String term,
|
|
}) {
|
|
final attributes = json['attributes'];
|
|
final attrs = attributes is Map ? attributes : const {};
|
|
final messageId =
|
|
_parseInt(attrs['messageId']) ??
|
|
_parseInt(
|
|
RegExp(
|
|
r'#message_(\d+)',
|
|
).firstMatch(json['resourceUrl']?.toString() ?? '')?.group(1),
|
|
);
|
|
if (messageId == null) return null;
|
|
|
|
// Title and subline have swapped roles across Talk versions (author vs.
|
|
// excerpt); the one containing the search term is the excerpt.
|
|
final title = json['title']?.toString() ?? '';
|
|
final subline = json['subline']?.toString() ?? '';
|
|
final q = term.trim().toLowerCase();
|
|
final titleIsExcerpt =
|
|
q.isNotEmpty &&
|
|
title.toLowerCase().contains(q) &&
|
|
!subline.toLowerCase().contains(q);
|
|
final excerpt = titleIsExcerpt ? title : subline;
|
|
final author = titleIsExcerpt ? subline : title;
|
|
|
|
final timestamp = _parseInt(attrs['timestamp']);
|
|
final actorId = attrs['actorId']?.toString();
|
|
return SearchChatMessagesEntry(
|
|
messageId: messageId,
|
|
excerpt: excerpt,
|
|
actorId: actorId == null || actorId.isEmpty ? null : actorId,
|
|
actorDisplayName: author.isEmpty ? null : author,
|
|
sentAt: timestamp == null
|
|
? null
|
|
: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000),
|
|
);
|
|
}
|
|
|
|
static int? _parseInt(Object? raw) => switch (raw) {
|
|
final int v => v,
|
|
final String v => int.tryParse(v),
|
|
_ => null,
|
|
};
|
|
}
|