fix: Don't lag when sending messages in big rooms
The old mentionMap was very inefficient to build and scaled badly with room member size. This resulted in noticable lag when sending any message in a large room, no matter if it contained a message or not. Now, the algorithm is severly optimized and mentions (and emotes) are only loaded when actually used.
This commit is contained in:
parent
aeea0669d5
commit
024e0de4b9
|
|
@ -566,6 +566,12 @@ class Room {
|
|||
Map<String, Map<String, String>> get emotePacks =>
|
||||
getImagePacksFlat(ImagePackUsage.emoticon);
|
||||
|
||||
/// returns the resolved mxid for a mention string, or null if none found
|
||||
String getMention(String mention) => getParticipants()
|
||||
.firstWhere((u) => u.mentionFragments.contains(mention),
|
||||
orElse: () => null)
|
||||
?.id;
|
||||
|
||||
/// Sends a normal text message to this room. Returns the event ID generated
|
||||
/// by the server for this message.
|
||||
Future<String> sendTextEvent(String message,
|
||||
|
|
@ -585,12 +591,9 @@ class Room {
|
|||
'body': message,
|
||||
};
|
||||
if (parseMarkdown) {
|
||||
final mentionMap = <String, String>{
|
||||
for (final user in getParticipants()) user.mention: user.id
|
||||
};
|
||||
final html = markdown(event['body'],
|
||||
emotePacks: getImagePacksFlat(ImagePackUsage.emoticon),
|
||||
mentionMap: mentionMap);
|
||||
getEmotePacks: () => getImagePacksFlat(ImagePackUsage.emoticon),
|
||||
getMention: getMention);
|
||||
// if the decoded html is the same as the body, there is no need in sending a formatted message
|
||||
if (HtmlUnescape().convert(html.replaceAll(RegExp(r'<br />\n?'), '\n')) !=
|
||||
event['body']) {
|
||||
|
|
|
|||
|
|
@ -207,6 +207,23 @@ class User extends Event {
|
|||
}
|
||||
return '$identifier#$ourHash';
|
||||
}
|
||||
|
||||
/// Get the mention fragments for this user.
|
||||
Set<String> get mentionFragments {
|
||||
if ((displayName?.isEmpty ?? true) ||
|
||||
{'[', ']', ':'}.any((c) => displayName.contains(c))) {
|
||||
return {};
|
||||
}
|
||||
var identifier = '@';
|
||||
// if we have non-word characters we need to surround with []
|
||||
if (!RegExp(r'^\w+$').hasMatch(displayName)) {
|
||||
identifier += '[$displayName]';
|
||||
} else {
|
||||
identifier += displayName;
|
||||
}
|
||||
final hash = _hash(id);
|
||||
return {identifier, '$identifier#$hash'};
|
||||
}
|
||||
}
|
||||
|
||||
String _hash(String s) {
|
||||
|
|
|
|||
|
|
@ -72,11 +72,13 @@ class SpoilerSyntax extends TagSyntax {
|
|||
}
|
||||
|
||||
class EmoteSyntax extends InlineSyntax {
|
||||
final Map<String, Map<String, String>> emotePacks;
|
||||
EmoteSyntax(this.emotePacks) : super(r':(?:([-\w]+)~)?([-\w]+):');
|
||||
final Map<String, Map<String, String>> Function() getEmotePacks;
|
||||
Map<String, Map<String, String>> emotePacks;
|
||||
EmoteSyntax(this.getEmotePacks) : super(r':(?:([-\w]+)~)?([-\w]+):');
|
||||
|
||||
@override
|
||||
bool onMatch(InlineParser parser, Match match) {
|
||||
emotePacks ??= getEmotePacks?.call() ?? <String, Map<String, String>>{};
|
||||
final pack = match[1] ?? '';
|
||||
final emote = match[2];
|
||||
String mxc;
|
||||
|
|
@ -182,29 +184,31 @@ class PillSyntax extends InlineSyntax {
|
|||
}
|
||||
|
||||
class MentionSyntax extends InlineSyntax {
|
||||
final Map<String, String> mentionMap;
|
||||
MentionSyntax(this.mentionMap) : super(r'(@(?:\[[^\]:]+\]|\w+)(?:#\w+)?)');
|
||||
final String Function(String) getMention;
|
||||
MentionSyntax(this.getMention) : super(r'(@(?:\[[^\]:]+\]|\w+)(?:#\w+)?)');
|
||||
|
||||
@override
|
||||
bool onMatch(InlineParser parser, Match match) {
|
||||
final mention = getMention?.call(match[1]);
|
||||
if ((match.start > 0 &&
|
||||
!RegExp(r'[\s.!?:;\(]').hasMatch(match.input[match.start - 1])) ||
|
||||
!mentionMap.containsKey(match[1])) {
|
||||
mention == null) {
|
||||
parser.addNode(Text(match[0]));
|
||||
return true;
|
||||
}
|
||||
final identifier = mentionMap[match[1]];
|
||||
final element = Element.text('a', htmlEscape.convert(match[1]));
|
||||
element.attributes['href'] =
|
||||
htmlAttrEscape.convert('https://matrix.to/#/$identifier');
|
||||
htmlAttrEscape.convert('https://matrix.to/#/$mention');
|
||||
parser.addNode(element);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
String markdown(String text,
|
||||
{Map<String, Map<String, String>> emotePacks,
|
||||
Map<String, String> mentionMap}) {
|
||||
String markdown(
|
||||
String text, {
|
||||
Map<String, Map<String, String>> Function() getEmotePacks,
|
||||
String Function(String) getMention,
|
||||
}) {
|
||||
var ret = markdownToHtml(
|
||||
text,
|
||||
extensionSet: ExtensionSet.commonMark,
|
||||
|
|
@ -215,9 +219,9 @@ String markdown(String text,
|
|||
StrikethroughSyntax(),
|
||||
LinebreakSyntax(),
|
||||
SpoilerSyntax(),
|
||||
EmoteSyntax(emotePacks ?? <String, Map<String, String>>{}),
|
||||
EmoteSyntax(getEmotePacks),
|
||||
PillSyntax(),
|
||||
MentionSyntax(mentionMap ?? <String, String>{}),
|
||||
MentionSyntax(getMention),
|
||||
InlineLatexSyntax(),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ void main() {
|
|||
'@[Fast Fox]#123': '@fastfox:example.org',
|
||||
'@[">]': '@blah:example.org',
|
||||
};
|
||||
final getMention = (mention) => mentionMap[mention];
|
||||
test('simple markdown', () {
|
||||
expect(markdown('hey *there* how are **you** doing?'),
|
||||
'hey <em>there</em> how are <strong>you</strong> doing?');
|
||||
|
|
@ -67,16 +68,18 @@ void main() {
|
|||
expect(markdown('foxies\ncute'), 'foxies<br />\ncute');
|
||||
});
|
||||
test('emotes', () {
|
||||
expect(markdown(':fox:', emotePacks: emotePacks),
|
||||
expect(markdown(':fox:', getEmotePacks: () => emotePacks),
|
||||
'<img data-mx-emoticon="" src="mxc://roomfox" alt=":fox:" title=":fox:" height="32" vertical-align="middle" />');
|
||||
expect(markdown(':user~fox:', emotePacks: emotePacks),
|
||||
expect(markdown(':user~fox:', getEmotePacks: () => emotePacks),
|
||||
'<img data-mx-emoticon="" src="mxc://userfox" alt=":fox:" title=":fox:" height="32" vertical-align="middle" />');
|
||||
expect(markdown(':raccoon:', emotePacks: emotePacks),
|
||||
expect(markdown(':raccoon:', getEmotePacks: () => emotePacks),
|
||||
'<img data-mx-emoticon="" src="mxc://raccoon" alt=":raccoon:" title=":raccoon:" height="32" vertical-align="middle" />');
|
||||
expect(markdown(':invalid:', emotePacks: emotePacks), ':invalid:');
|
||||
expect(markdown(':invalid:?!', emotePacks: emotePacks), ':invalid:?!');
|
||||
expect(
|
||||
markdown(':room~invalid:', emotePacks: emotePacks), ':room~invalid:');
|
||||
markdown(':invalid:', getEmotePacks: () => emotePacks), ':invalid:');
|
||||
expect(markdown(':invalid:?!', getEmotePacks: () => emotePacks),
|
||||
':invalid:?!');
|
||||
expect(markdown(':room~invalid:', getEmotePacks: () => emotePacks),
|
||||
':room~invalid:');
|
||||
});
|
||||
test('pills', () {
|
||||
expect(markdown('Hey @sorunome:sorunome.de!'),
|
||||
|
|
@ -95,17 +98,17 @@ void main() {
|
|||
'Hey <a href="https://matrix.to/#/@sorunome:[::1]">@sorunome:[::1]</a>!');
|
||||
});
|
||||
test('mentions', () {
|
||||
expect(markdown('Hey @Bob!', mentionMap: mentionMap),
|
||||
expect(markdown('Hey @Bob!', getMention: getMention),
|
||||
'Hey <a href="https://matrix.to/#/@bob:example.org">@Bob</a>!');
|
||||
expect(markdown('How is @[Bob Ross] doing?', mentionMap: mentionMap),
|
||||
expect(markdown('How is @[Bob Ross] doing?', getMention: getMention),
|
||||
'How is <a href="https://matrix.to/#/@bobross:example.org">@[Bob Ross]</a> doing?');
|
||||
expect(
|
||||
markdown('Hey @invalid!', mentionMap: mentionMap), 'Hey @invalid!');
|
||||
expect(markdown('Hey @Fox#123!', mentionMap: mentionMap),
|
||||
markdown('Hey @invalid!', getMention: getMention), 'Hey @invalid!');
|
||||
expect(markdown('Hey @Fox#123!', getMention: getMention),
|
||||
'Hey <a href="https://matrix.to/#/@fox:example.org">@Fox#123</a>!');
|
||||
expect(markdown('Hey @[Fast Fox]#123!', mentionMap: mentionMap),
|
||||
expect(markdown('Hey @[Fast Fox]#123!', getMention: getMention),
|
||||
'Hey <a href="https://matrix.to/#/@fastfox:example.org">@[Fast Fox]#123</a>!');
|
||||
expect(markdown('Hey @[">]!', mentionMap: mentionMap),
|
||||
expect(markdown('Hey @[">]!', getMention: getMention),
|
||||
'Hey <a href="https://matrix.to/#/@blah:example.org">@[">]</a>!');
|
||||
});
|
||||
test('latex', () {
|
||||
|
|
|
|||
|
|
@ -947,6 +947,12 @@ void main() {
|
|||
await room.removeSpaceChild('!1234:example.invalid');*/
|
||||
});
|
||||
|
||||
test('getMention', () async {
|
||||
expect(room.getMention('@invalid'), null);
|
||||
expect(room.getMention('@[Alice Margatroid]'), '@alice:example.org');
|
||||
expect(room.getMention('@[Alice Margatroid]#1754'), '@alice:example.org');
|
||||
});
|
||||
|
||||
test('logout', () async {
|
||||
await matrix.logout();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -159,6 +159,10 @@ void main() {
|
|||
expect(user1.mention, '@Bob#1745');
|
||||
user1.content['displayname'] = 'Alice M';
|
||||
});
|
||||
test('mentionFragments', () async {
|
||||
expect(user1.mentionFragments, {'@[Alice M]', '@[Alice M]#1745'});
|
||||
expect(user2.mentionFragments, {'@Bob', '@Bob#1542'});
|
||||
});
|
||||
test('dispose client', () async {
|
||||
await client.dispose(closeDatabase: true);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue