Skip to content

Commit 4fe67a9

Browse files
authored
fix: combine token with following punctuation to prevent punctuation from causing newline (#1783)
1 parent cd0650f commit 4fe67a9

File tree

2 files changed

+85
-17
lines changed

2 files changed

+85
-17
lines changed

lib/pangea/events/utils/message_text_util.dart

+27-3
Original file line numberDiff line numberDiff line change
@@ -25,44 +25,68 @@ class MessageTextUtil {
2525
final List<TokenPosition> tokenPositions = [];
2626
int globalIndex = 0;
2727

28-
for (final token
29-
in pangeaMessageEvent.messageDisplayRepresentation!.tokens!) {
28+
final tokens = pangeaMessageEvent.messageDisplayRepresentation!.tokens!;
29+
int pointer = 0;
30+
while (pointer < tokens.length) {
31+
final token = tokens[pointer];
3032
final start = token.start;
3133
final end = token.end;
3234

3335
// Calculate the number of grapheme clusters up to the start and end positions
3436
final int startIndex = messageCharacters.take(start).length;
35-
final int endIndex = messageCharacters.take(end).length;
37+
int endIndex = messageCharacters.take(end).length;
3638

3739
final hideContent =
3840
messageAnalyticsEntry?.isTokenInHiddenWordActivity(token) ?? false;
3941

4042
final hasHiddenContent =
4143
messageAnalyticsEntry?.hasHiddenWordActivity ?? false;
4244

45+
// if this is white space, add position without token
4346
if (globalIndex < startIndex) {
4447
tokenPositions.add(
4548
TokenPosition(
4649
start: globalIndex,
4750
end: startIndex,
51+
tokenStart: globalIndex,
52+
tokenEnd: startIndex,
4853
hideContent: false,
4954
selected: (isSelected?.call(token) ?? false) && !hasHiddenContent,
5055
),
5156
);
5257
}
5358

59+
// group tokens with punctuation next to it so punctuation doesn't cause newline
60+
final List<PangeaToken> followingPunctTokens = [];
61+
int nextTokenPointer = pointer + 1;
62+
while (nextTokenPointer < tokens.length) {
63+
final nextToken = tokens[nextTokenPointer];
64+
if (nextToken.pos == 'PUNCT') {
65+
followingPunctTokens.add(nextToken);
66+
nextTokenPointer++;
67+
endIndex = messageCharacters.take(nextToken.end).length;
68+
continue;
69+
}
70+
break;
71+
}
72+
5473
tokenPositions.add(
5574
TokenPosition(
5675
start: startIndex,
5776
end: endIndex,
77+
tokenStart: startIndex,
78+
tokenEnd: messageCharacters.take(end).length,
5879
token: token,
5980
hideContent: hideContent,
6081
selected: (isSelected?.call(token) ?? false) &&
6182
!hideContent &&
6283
!hasHiddenContent,
6384
),
6485
);
86+
6587
globalIndex = endIndex;
88+
pointer = nextTokenPointer;
89+
continue;
6690
}
6791

6892
return tokenPositions;

lib/pangea/toolbar/widgets/message_token_text.dart

+58-14
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,27 @@ class MessageTokenText extends StatelessWidget {
7070
}
7171

7272
class TokenPosition {
73+
/// Start index of the full substring in the message
7374
final int start;
75+
76+
/// End index of the full substring in the message
7477
final int end;
78+
79+
/// Start index of the token in the message
80+
final int tokenStart;
81+
82+
/// End index of the token in the message
83+
final int tokenEnd;
84+
7585
final bool selected;
7686
final bool hideContent;
7787
final PangeaToken? token;
7888

7989
const TokenPosition({
8090
required this.start,
8191
required this.end,
92+
required this.tokenStart,
93+
required this.tokenEnd,
8294
required this.hideContent,
8395
required this.selected,
8496
this.token,
@@ -225,6 +237,19 @@ class MessageTextWidget extends StatelessWidget {
225237
),
226238
);
227239
}
240+
241+
// if the tokenPosition is a combination of the token and following punctuation
242+
// split them so that only the token itself is highlighted when clicked
243+
String firstSubstring = substring;
244+
String secondSubstring = '';
245+
246+
if (tokenPosition.end != tokenPosition.tokenEnd) {
247+
final splitIndex = (tokenPosition.end - tokenPosition.start) -
248+
(tokenPosition.end - tokenPosition.tokenEnd);
249+
firstSubstring = substring.substring(0, splitIndex);
250+
secondSubstring = substring.substring(splitIndex);
251+
}
252+
228253
return WidgetSpan(
229254
child: MouseRegion(
230255
cursor: SystemMouseCursors.click,
@@ -233,21 +258,40 @@ class MessageTextWidget extends StatelessWidget {
233258
? () => onClick?.call(tokenPosition)
234259
: null,
235260
child: RichText(
236-
text: LinkifySpan(
237-
text: substring,
238-
style: style.merge(
239-
TextStyle(
240-
backgroundColor: backgroundColor,
261+
text: TextSpan(
262+
children: [
263+
LinkifySpan(
264+
text: firstSubstring,
265+
style: style.merge(
266+
TextStyle(
267+
backgroundColor: backgroundColor,
268+
),
269+
),
270+
linkStyle: TextStyle(
271+
decoration: TextDecoration.underline,
272+
color:
273+
Theme.of(context).brightness == Brightness.light
274+
? Theme.of(context).colorScheme.primary
275+
: Theme.of(context).colorScheme.onPrimary,
276+
),
277+
onOpen: (url) =>
278+
UrlLauncher(context, url.url).launchUrl(),
241279
),
242-
),
243-
linkStyle: TextStyle(
244-
decoration: TextDecoration.underline,
245-
color: Theme.of(context).brightness == Brightness.light
246-
? Theme.of(context).colorScheme.primary
247-
: Theme.of(context).colorScheme.onPrimary,
248-
),
249-
onOpen: (url) =>
250-
UrlLauncher(context, url.url).launchUrl(),
280+
if (secondSubstring.isNotEmpty)
281+
LinkifySpan(
282+
text: secondSubstring,
283+
style: style,
284+
linkStyle: TextStyle(
285+
decoration: TextDecoration.underline,
286+
color: Theme.of(context).brightness ==
287+
Brightness.light
288+
? Theme.of(context).colorScheme.primary
289+
: Theme.of(context).colorScheme.onPrimary,
290+
),
291+
onOpen: (url) =>
292+
UrlLauncher(context, url.url).launchUrl(),
293+
),
294+
],
251295
),
252296
),
253297
),

0 commit comments

Comments
 (0)