Merge "Replace mentions with gr-account-labels"
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
index bd01af9..da90b17 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
@@ -15,12 +15,15 @@
import {resolve} from '../../../models/dependency';
import {subscribe} from '../../lit/subscription-controller';
import {configModelToken} from '../../../models/config/config-model';
-import {CommentLinks} from '../../../api/rest-api';
+import {CommentLinks, EmailAddress} from '../../../api/rest-api';
import {
applyHtmlRewritesFromConfig,
applyLinkRewritesFromConfig,
linkifyNormalUrls,
} from '../../../utils/link-util';
+import '../gr-account-chip/gr-account-chip';
+import {KnownExperimentId} from '../../../services/flags/flags';
+import {getAppContext} from '../../../services/app-context';
/**
* This element optionally renders markdown and also applies some regex
@@ -37,6 +40,8 @@
@state()
private repoCommentLinks: CommentLinks = {};
+ private readonly flagsService = getAppContext().flagsService;
+
private readonly getConfigModel = resolve(this, configModelToken);
/**
@@ -91,6 +96,9 @@
li {
margin-left: var(--spacing-xl);
}
+ gr-account-chip {
+ display: inline;
+ }
.plaintext {
font: inherit;
white-space: var(--linked-text-white-space, pre-wrap);
@@ -200,6 +208,36 @@
return text;
}
+
+ override updated() {
+ // Look for @mentions and replace them with an account-label chip.
+ if (this.flagsService.isEnabled(KnownExperimentId.MENTION_USERS)) {
+ this.convertEmailsToAccountChips();
+ }
+ }
+
+ private convertEmailsToAccountChips() {
+ for (const emailLink of this.renderRoot.querySelectorAll(
+ 'a[href^="mailto"]'
+ )) {
+ const previous = emailLink.previousSibling;
+ // This Regexp matches the beginning of the MENTIONS_REGEX at the end of
+ // an element.
+ if (
+ previous?.nodeName === '#text' &&
+ previous?.textContent?.match(/(^|\s)@$/)
+ ) {
+ const accountChip = document.createElement('gr-account-chip');
+ accountChip.account = {
+ email: emailLink.textContent as EmailAddress,
+ };
+ accountChip.removable = false;
+ // Remove the trailing @ from the previous element.
+ previous.textContent = previous.textContent.slice(0, -1);
+ emailLink.parentNode?.replaceChild(accountChip, emailLink);
+ }
+ }
+ }
}
declare global {
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
index dcd13bd..f3c9d9c 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
@@ -15,9 +15,15 @@
import './gr-formatted-text';
import {GrFormattedText} from './gr-formatted-text';
import {createConfig} from '../../../test/test-data-generators';
-import {waitUntilObserved} from '../../../test/test-utils';
-import {CommentLinks} from '../../../api/rest-api';
+import {
+ queryAndAssert,
+ stubFlags,
+ waitUntilObserved,
+} from '../../../test/test-utils';
+import {CommentLinks, EmailAddress} from '../../../api/rest-api';
import {testResolver} from '../../../test/common-test-setup';
+import {KnownExperimentId} from '../../../services/flags/flags';
+import {GrAccountChip} from '../gr-account-chip/gr-account-chip';
suite('gr-formatted-text tests', () => {
let element: GrFormattedText;
@@ -235,6 +241,7 @@
`
);
});
+
test('renders multiline-code without linking or rewriting', async () => {
element.content = `\`\`\`\nmultiline code\n\`\`\`
\n\`\`\`\nmultiline code with plain link: google.com\n\`\`\`
@@ -281,6 +288,79 @@
);
});
+ test('does not handle @mentions if not enabled', async () => {
+ stubFlags('isEnabled')
+ .withArgs(KnownExperimentId.MENTION_USERS)
+ .returns(false);
+ element.content = '@someone@google.com';
+ await element.updateComplete;
+
+ assert.shadowDom.equal(
+ element,
+ /* HTML */ `
+ <marked-element>
+ <div slot="markdown-html">
+ <p>
+ @
+ <a href="mailto:someone@google.com"> someone@google.com </a>
+ </p>
+ </div>
+ </marked-element>
+ `
+ );
+ });
+
+ test('handles @mentions if enabled', async () => {
+ stubFlags('isEnabled')
+ .withArgs(KnownExperimentId.MENTION_USERS)
+ .returns(true);
+ element.content = '@someone@google.com';
+ await element.updateComplete;
+
+ assert.shadowDom.equal(
+ element,
+ /* HTML */ `
+ <marked-element>
+ <div slot="markdown-html">
+ <p>
+ <gr-account-chip></gr-account-chip>
+ </p>
+ </div>
+ </marked-element>
+ `
+ );
+ const accountChip = queryAndAssert<GrAccountChip>(
+ element,
+ 'gr-account-chip'
+ );
+ assert.equal(
+ accountChip.account?.email,
+ 'someone@google.com' as EmailAddress
+ );
+ });
+
+ test('does not handle @mentions that is part of a code block', async () => {
+ stubFlags('isEnabled')
+ .withArgs(KnownExperimentId.MENTION_USERS)
+ .returns(true);
+ element.content = '`@`someone@google.com';
+ await element.updateComplete;
+
+ assert.shadowDom.equal(
+ element,
+ /* HTML */ `
+ <marked-element>
+ <div slot="markdown-html">
+ <p>
+ <code>@</code>
+ <a href="mailto:someone@google.com"> someone@google.com </a>
+ </p>
+ </div>
+ </marked-element>
+ `
+ );
+ });
+
test('renders inline links into <a> tags', async () => {
element.content = '[myLink](https://www.google.com)';
await element.updateComplete;
diff --git a/polygerrit-ui/app/utils/account-util.ts b/polygerrit-ui/app/utils/account-util.ts
index 207152c..a7d0587 100644
--- a/polygerrit-ui/app/utils/account-util.ts
+++ b/polygerrit-ui/app/utils/account-util.ts
@@ -29,7 +29,7 @@
const SUGGESTIONS_LIMIT = 15;
// https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
export const MENTIONS_REGEX =
- /(?:^|\s)@([a-zA-Z0-9.!#$%&'*+=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)(?=\s+|$)/g;
+ /(?<=^|\s)@([a-zA-Z0-9.!#$%&'*+=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)(?=\s+|$)/g;
export function accountKey(account: AccountInfo): AccountId | EmailAddress {
if (account._account_id !== undefined) return account._account_id;