Skip to content

content [nfc]: Pull out parseUserMention, give regexp less to do #1086

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 3, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 46 additions & 16 deletions lib/model/content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -706,17 +706,15 @@ class UserMentionNode extends InlineContainerNode {
const UserMentionNode({
super.debugHtmlNode,
required super.nodes,
// required this.mentionType,
// required this.isSilent,
});

// We don't currently seem to need this information in code. Instead,
// For the legacy design, we don't need this information in code; instead,
// the inner text already shows how to communicate it to the user
// (e.g., silent mentions' text lacks a leading "@"),
// and we show that text in the same style for all types of @-mention.
// If we need this information in the future, go ahead and add it here.
// final UserMentionType mentionType;
// final bool isSilent;
// We'll need these for implementing the post-2023 Zulip design, though.
// final UserMentionType mentionType; // TODO(#646)
// final bool isSilent; // TODO(#647)
}

sealed class EmojiNode extends InlineContentNode {
Expand Down Expand Up @@ -872,6 +870,41 @@ class _ZulipContentParser {
return descendant4.text.trim();
}

UserMentionNode? parseUserMention(dom.Element element) {
assert(_debugParserContext == _ParserContext.inline);
assert(element.localName == 'span');
final debugHtmlNode = kDebugMode ? element : null;

final classes = element.className.split(' ')..sort();
assert(classes.contains('user-mention')
|| classes.contains('user-group-mention'));
int i = 0;

if (i >= classes.length) return null;
if (classes[i] == 'silent') {
// A silent @-mention. We ignore this flag; see [UserMentionNode].
i++;
}

if (i >= classes.length) return null;
if (classes[i] == 'user-mention' || classes[i] == 'user-group-mention') {
// The class we already knew we'd find before we called this function.
// We ignore the distinction between these; see [UserMentionNode].
i++;
}

if (i != classes.length) {
// There was some class we didn't expect.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "class" -> "classes"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The singular is intentional: there's at least one class we didn't expect, namely the one at position i.

(Grammatically it'd be either "was some class" or "were some classes".)

return null;
}

// TODO assert UserMentionNode can't contain LinkNode;
// either a debug-mode check, or perhaps we can make expectations much
// tighter on a UserMentionNode's contents overall.
final nodes = parseInlineContentList(element.nodes);
return UserMentionNode(nodes: nodes, debugHtmlNode: debugHtmlNode);
}

/// The links found so far in the current block inline container.
///
/// Empty is represented as null.
Expand All @@ -884,12 +917,12 @@ class _ZulipContentParser {
return result;
}

static final _userMentionClassNameRegexp = () {
// This matches a class `user-mention` or `user-group-mention`,
// plus an optional class `silent`, appearing in either order.
const mentionClass = r"user(?:-group)?-mention";
return RegExp("^(?:$mentionClass(?: silent)?|silent $mentionClass)\$");
}();
/// Matches all className values that could be a UserMentionNode,
/// and no className values that could be any other type of node.
// Specifically, checks for `user-mention` or `user-group-mention`
// as a member of the list.
static final _userMentionClassNameRegexp = RegExp(
r"(^| )" r"user(?:-group)?-mention" r"( |$)");

static final _emojiClassNameRegexp = () {
const specificEmoji = r"emoji(?:-[0-9a-f]+)+";
Expand Down Expand Up @@ -944,10 +977,7 @@ class _ZulipContentParser {

if (localName == 'span'
&& _userMentionClassNameRegexp.hasMatch(className)) {
// TODO assert UserMentionNode can't contain LinkNode;
// either a debug-mode check, or perhaps we can make expectations much
// tighter on a UserMentionNode's contents overall.
return UserMentionNode(nodes: nodes(), debugHtmlNode: debugHtmlNode);
return parseUserMention(element) ?? unimplemented();
}

if (localName == 'span'
Expand Down