Skip to content
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

[PoC] Support creating mentions as part of annotation creation #6787

Closed
wants to merge 14 commits into from

Conversation

acelaya
Copy link
Contributor

@acelaya acelaya commented Feb 3, 2025

This PR is used to verify some assumptions, and check that we can proceed according to what's described in the engineering design document for phase 1 https://docs.google.com/document/d/16Iv-MPwQi_LIIN6Fk-aAaflZqWkSX5bQIWUEg_MUDBI/edit

The implementation here is very rough, and individual PRs will be extracted with the actual, properly tested implementation.

This PR has been tested in combination with hypothesis/h#9279, to ensure FE and BE integrate as expected.

highlighted-at-mention-2025-02-03_16.47.17.mp4
mentions-popover-2025-02-05_12.00.56.mp4

Logic

This PR covers:

  1. Replacing any occurrence of @something in the annotation text with the special mention tag (<a data-hyp-mention data-userid="acct:something@authority">@something</a>) right before sending it to the BE.
    We don't try to validate those are actual usernames client-side. Instead we let the BE validate them and "discard" those that are invalid.
    This will allow us to highlight incorrect mentions to end-users, or simply render them as plain-text if desired.
  2. Convert special mention tags to their plain-text representation when going back to editing an annotation.
  3. Render valid mentions as a link to the user profile, and invalid ones as a styled span.
  4. Display a popover when hovering over valid at-mentions. The content is temporary (display name or username), but we'll have to enhance it.

This PR assumes usernames are public and user-friendly. In future, we'll need to support mentioning via display name as well, for the LMS. In that case we will have to support an additional plain-text format for mentions that could be something like @[Display name], @<Display name>, @Display_name or something in those lines.

When converting that to the corresponding mention tag, we will have to get the userid somehow. Let's worry about that later.

TODO

  • Parse @username into special tag <a data-hyp-mention data-userid="acct:username@authority">@username</a>
  • Render valid mentions from server as links to the user profile, and invalid ones as plain-text or some fallback style
  • Parse special tag back into @username when entering annotation edit mode for an annotation with mentions
  • Show basic popover when hovering over a mention

Copy link

codecov bot commented Feb 3, 2025

Codecov Report

Attention: Patch coverage is 25.00000% with 51 lines in your changes missing coverage. Please review.

Project coverage is 98.94%. Comparing base (6a8e7ae) to head (1364a97).

Files with missing lines Patch % Lines
src/sidebar/helpers/mentions.ts 21.21% 26 Missing ⚠️
src/sidebar/components/MarkdownView.tsx 22.58% 24 Missing ⚠️
src/sidebar/services/annotations.ts 66.66% 1 Missing ⚠️

❌ Your patch status has failed because the patch coverage (25.00%) is below the target coverage (100.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6787      +/-   ##
==========================================
- Coverage   99.43%   98.94%   -0.49%     
==========================================
  Files         270      271       +1     
  Lines       10312    10379      +67     
  Branches     2474     2493      +19     
==========================================
+ Hits        10254    10270      +16     
- Misses         58      109      +51     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@acelaya acelaya force-pushed the mention-tags-poc branch 5 times, most recently from 6dc1f40 to 37cd560 Compare February 4, 2025 09:38
@@ -35,21 +76,49 @@ export default function MarkdownView({
});
}, [markdown]);

useEffect(() => {
const listenerCollection = new ListenerCollection();
const foundMentions = replaceMentionTags(content.current!, mentions);
Copy link
Contributor Author

@acelaya acelaya Feb 4, 2025

Choose a reason for hiding this comment

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

Similarly to how we replace links with embeds via a side effect, we replace mention tags with their rendered representation directly in the DOM.

Comment on lines 27 to 32
function mentionsToTags(text: string, authority: string): string {
return text.replace(/(?:^|\s)@(\w+)(?=\s|$)/g, (match, username) => {
const userid = `acct:${username}@${authority}`;
return ` <a data-hyp-mention data-userid="${userid}">@${username}</a>`;
});
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This converts plain-text mentions into the special tag, but assumes the mention is the username. We'll have to evolve this for display-name-based mentions.

src/types/api.ts Outdated
Comment on lines 162 to 164
userid: string;
username: string;
link: string;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This may not be entirely correct. For example, during my testing I was getting null links. We'll have to clarify this with the BE.

@acelaya acelaya requested a review from robertknight February 4, 2025 10:29
Copy link
Member

@robertknight robertknight left a comment

Choose a reason for hiding this comment

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

I would suggest to extract the functions related to extracting and replacing mentions into a module in helpers where they can be tested directly. Also I would default to use browser APIs for parsing and generating HTML unless we determine they can't be used for some reason. For listening for events, I suggested to try event delegation rather than adding listeners to each mention directly - unless you can see a reason we can't do that?

src/types/api.ts Outdated
export type Mention = UserInfo & {
userid: string;
username: string;
link: string;
Copy link
Member

Choose a reason for hiding this comment

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

What does link point to? The user's profile page? What about LMS users in future who don't have profile pages?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What does link point to? The user's profile page?

Yes, that is the intention

What about LMS users in future who don't have profile pages?

The field should be nullable. If it comes as null, then we would not render mentions as links, but something else.

@robertknight
Copy link
Member

For unresolvable mentions, I think it would be helpful to have a popover to explicitly indicate what the styling means:

invalid mention

The text of the popover could be something like "No user with username foobar exists".

@acelaya
Copy link
Contributor Author

acelaya commented Feb 4, 2025

Also I would default to use browser APIs for parsing and generating HTML unless we determine they can't be used for some reason.

Yep, this is a good suggestion, as using regular expressions proved to be problematic.

For some reason I've been working on the assumption that we would have to use regular expressions, so didn't really think on an alternative, but since we are dealing with HTML elements, using the browser APIs makes a lot of sense.

Copy link
Member

@robertknight robertknight left a comment

Choose a reason for hiding this comment

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

This generally looks good. I had a query about handling of null links when post-processing the rendered content for an annotation.

const listenerCollection = new ListenerCollection();
const foundMentions = renderMentionTags(content.current!, mentions);

listenerCollection.add(
Copy link
Member

Choose a reason for hiding this comment

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

Was there a reason not to use onMouseEnterCapture and onMouseLeaveCapture on the content div? (onMouseOver and onMouseOut could work as well with some other modifications I think).

Copy link
Contributor Author

@acelaya acelaya Feb 5, 2025

Choose a reason for hiding this comment

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

The main reason is that renderMentionTags needs to be called in a side effect, so we would have to track the result in a ref.

Defining the event handlers here simplified that, as it no longer needs to be referenced outside. But we can explore the other option.


if (mention) {
// If the mention exists in the list of mentions, render it as a link
mentionLink.setAttribute('href', mention.link);
Copy link
Member

Choose a reason for hiding this comment

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

I think you mentioned previously that link can be null? Is that correct? If so what should happen here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, that's correct. In that case this should be rendered as a different type of tag, and highlighted by color only, but not underlined.

@acelaya
Copy link
Contributor Author

acelaya commented Feb 5, 2025

This PoC has served its purpose. I'm closing it now and will start creating individual PRs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants