diff --git a/app/discussion/client/lib/messageTypes/discussionMessage.js b/app/discussion/client/lib/messageTypes/discussionMessage.js index 5dc928a7f38e..b29af3cc9374 100644 --- a/app/discussion/client/lib/messageTypes/discussionMessage.js +++ b/app/discussion/client/lib/messageTypes/discussionMessage.js @@ -9,7 +9,6 @@ Meteor.startup(function() { message: 'discussion-created', data(message) { return { - // channelLink: `${ TAPi18n.__('discussion') }`, message: ` ${ message.msg }`, }; }, diff --git a/app/mentions/client/client.js b/app/mentions/client/client.js index 0d54957be17d..a227c6256865 100644 --- a/app/mentions/client/client.js +++ b/app/mentions/client/client.js @@ -1,20 +1,13 @@ import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../callbacks'; import { settings } from '../../settings'; -import Mentions from '../lib/Mentions'; +import { MentionsParser } from '../lib/MentionsParser'; -const MentionsClient = new Mentions({ - pattern() { - return settings.get('UTF8_Names_Validation'); - }, - useRealName() { - return settings.get('UI_Use_Real_Name'); - }, - me() { - const me = Meteor.user(); - return me && me.username; - }, +const instance = new MentionsParser({ + pattern: () => settings.get('UTF8_Names_Validation'), + useRealName: () => settings.get('UI_Use_Real_Name'), + me: () => Meteor.userId() && Meteor.user().username, }); -callbacks.add('renderMessage', (message) => MentionsClient.parse(message), callbacks.priority.MEDIUM, 'mentions-message'); -callbacks.add('renderMentions', (message) => MentionsClient.parse(message), callbacks.priority.MEDIUM, 'mentions-mentions'); +callbacks.add('renderMessage', (message) => instance.parse(message), callbacks.priority.MEDIUM, 'mentions-message'); +callbacks.add('renderMentions', (message) => instance.parse(message), callbacks.priority.MEDIUM, 'mentions-mentions'); diff --git a/app/mentions/client/index.js b/app/mentions/client/index.js index d99e4ed77352..c9353c892c3c 100644 --- a/app/mentions/client/index.js +++ b/app/mentions/client/index.js @@ -1 +1,2 @@ import './client'; +import './mentionLink.css'; diff --git a/app/mentions/client/mentionLink.css b/app/mentions/client/mentionLink.css new file mode 100644 index 000000000000..620238c7d210 --- /dev/null +++ b/app/mentions/client/mentionLink.css @@ -0,0 +1,27 @@ +.mention-link { + padding: 0 6px 2px; + + transition: border-radius 0.3s; + + color: var(--mention-link-text-color) !important; + + border-radius: var(--mention-link-radius); + + background-color: var(--mention-link-background) !important; + + font-weight: 700; + + &:hover { + border-radius: var(--border-radius); + } + + &--me { + color: var(--mention-link-me-text-color) !important; + background-color: var(--mention-link-me-background) !important; + } + + &--group { + color: var(--mention-link-group-text-color) !important; + background-color: var(--mention-link-group-background) !important; + } +} diff --git a/app/mentions/lib/Mentions.js b/app/mentions/lib/Mentions.js deleted file mode 100644 index 3e33d29f5e04..000000000000 --- a/app/mentions/lib/Mentions.js +++ /dev/null @@ -1,81 +0,0 @@ -/* -* Mentions is a named function that will process Mentions -* @param {Object} message - The message object -*/ -import s from 'underscore.string'; -export default class { - constructor({ pattern, useRealName, me }) { - this.pattern = pattern; - this.useRealName = useRealName; - this.me = me; - } - set me(m) { - this._me = m; - } - get me() { - return typeof this._me === 'function' ? this._me() : this._me; - } - set pattern(p) { - this._pattern = p; - } - get pattern() { - return typeof this._pattern === 'function' ? this._pattern() : this._pattern; - } - set useRealName(s) { - this._useRealName = s; - } - get useRealName() { - return typeof this._useRealName === 'function' ? this._useRealName() : this._useRealName; - } - get userMentionRegex() { - return new RegExp(`(^|\\s|
|
?)@(${ this.pattern }(@(${ this.pattern }))?)`, 'gm');
- }
- get channelMentionRegex() {
- return new RegExp(`(^|\\s|
)#(${ this.pattern }(@(${ this.pattern }))?)`, 'gm'); - } - replaceUsers(str, message, me) { - return str.replace(this.userMentionRegex, (match, prefix, username) => { - if (['all', 'here'].includes(username)) { - return `${ prefix }@${ username }`; - } - - const mentionObj = message.mentions && message.mentions.find((m) => m.username === username); - - if (message.temp == null && mentionObj == null) { - return match; - } - - const name = this.useRealName && mentionObj && s.escapeHTML(mentionObj.name); - - return `${ prefix }${ name || `@${ username }` }`; - }); - } - replaceChannels(str, message) { - // since apostrophe escaped contains # we need to unescape it - return str.replace(/'/g, '\'').replace(this.channelMentionRegex, (match, prefix, name) => { - if (!message.temp && !(message.channels && message.channels.find((c) => c.name === name))) { - return match; - } - - const channel = message.channels && message.channels.find((c) => c.name === name); - const roomNameorId = channel ? channel._id : name; - return `${ prefix }${ `#${ name }` }`; - }); - } - getUserMentions(str) { - return (str.match(this.userMentionRegex) || []).map((match) => match.trim()); - } - getChannelMentions(str) { - return (str.match(this.channelMentionRegex) || []).map((match) => match.trim()); - } - parse(message) { - let msg = (message && message.html) || ''; - if (!msg.trim()) { - return message; - } - msg = this.replaceUsers(msg, message, this.me); - msg = this.replaceChannels(msg, message, this.me); - message.html = msg; - return message; - } -} diff --git a/app/mentions/lib/MentionsParser.js b/app/mentions/lib/MentionsParser.js new file mode 100644 index 000000000000..270e3cd90411 --- /dev/null +++ b/app/mentions/lib/MentionsParser.js @@ -0,0 +1,102 @@ +import s from 'underscore.string'; + +export class MentionsParser { + constructor({ pattern, useRealName, me }) { + this.pattern = pattern; + this.useRealName = useRealName; + this.me = me; + } + + set me(m) { + this._me = m; + } + + get me() { + return typeof this._me === 'function' ? this._me() : this._me; + } + + set pattern(p) { + this._pattern = p; + } + + get pattern() { + return typeof this._pattern === 'function' ? this._pattern() : this._pattern; + } + + set useRealName(s) { + this._useRealName = s; + } + + get useRealName() { + return typeof this._useRealName === 'function' ? this._useRealName() : this._useRealName; + } + + get userMentionRegex() { + return new RegExp(`(^|\\s|
|
?)@(${ this.pattern }(@(${ this.pattern }))?)`, 'gm');
+ }
+
+ get channelMentionRegex() {
+ return new RegExp(`(^|\\s|
)#(${ this.pattern }(@(${ this.pattern }))?)`, 'gm');
+ }
+
+ replaceUsers = (msg, { mentions, temp }, me) => msg
+ .replace(this.userMentionRegex, (match, prefix, mention) => {
+ const isGroupMention = ['all', 'here'].includes(mention);
+ const className = [
+ 'mention-link',
+ 'mention-link--user',
+ mention === 'all' && 'mention-link--all',
+ mention === 'here' && 'mention-link--here',
+ mention === me && 'mention-link--me',
+ isGroupMention && 'mention-link--group',
+ ].filter(Boolean).join(' ');
+
+ if (isGroupMention) {
+ return `${ prefix }${ mention }`;
+ }
+
+ const label = temp ?
+ mention && s.escapeHTML(mention) :
+ (mentions || [])
+ .filter(({ username }) => username === mention)
+ .map(({ name, username }) => (this.useRealName ? name : username))
+ .map((label) => label && s.escapeHTML(label))[0];
+
+ if (!label) {
+ return match;
+ }
+
+ return `${ prefix }${ label }`;
+ })
+
+ replaceChannels = (msg, { temp, channels }) => msg
+ .replace(/'/g, '\'')
+ .replace(this.channelMentionRegex, (match, prefix, mention) => {
+ if (!temp && !(channels && channels.find((c) => c.name === mention))) {
+ return match;
+ }
+
+ const channel = channels && channels.find(({ name }) => name === mention);
+ const reference = channel ? channel._id : mention;
+ return `${ prefix }${ `#${ mention }` }`;
+ })
+
+ getUserMentions(str) {
+ return (str.match(this.userMentionRegex) || []).map((match) => match.trim());
+ }
+
+ getChannelMentions(str) {
+ return (str.match(this.channelMentionRegex) || []).map((match) => match.trim());
+ }
+
+ parse(message) {
+ let msg = (message && message.html) || '';
+ if (!msg.trim()) {
+ return message;
+ }
+ msg = this.replaceUsers(msg, message, this.me);
+ msg = this.replaceChannels(msg, message, this.me);
+ message.html = msg;
+ return message;
+ }
+}
diff --git a/app/mentions/server/Mentions.js b/app/mentions/server/Mentions.js
index 58172776165d..0d03dfdcc01c 100644
--- a/app/mentions/server/Mentions.js
+++ b/app/mentions/server/Mentions.js
@@ -2,9 +2,9 @@
* Mentions is a named function that will process Mentions
* @param {Object} message - The message object
*/
-import Mentions from '../lib/Mentions';
+import { MentionsParser } from '../lib/MentionsParser';
-export default class MentionsServer extends Mentions {
+export default class MentionsServer extends MentionsParser {
constructor(args) {
super(args);
this.messageMaxAll = args.messageMaxAll;
diff --git a/app/mentions/tests/client.tests.js b/app/mentions/tests/client.tests.js
index 39dcb8bac872..32f27f8241ca 100644
--- a/app/mentions/tests/client.tests.js
+++ b/app/mentions/tests/client.tests.js
@@ -1,58 +1,70 @@
/* eslint-env mocha */
import 'babel-polyfill';
import assert from 'assert';
+import { MentionsParser } from '../lib/MentionsParser';
-import Mentions from '../lib/Mentions';
-let mention;
+let mentionsParser;
beforeEach(function functionName() {
- mention = new Mentions({
+ mentionsParser = new MentionsParser({
pattern: '[0-9a-zA-Z-_.]+',
me: () => 'me',
});
});
+
describe('Mention', function() {
describe('get pattern', () => {
const regexp = '[0-9a-zA-Z-_.]+';
- beforeEach(() => mention.pattern = () => regexp);
+ beforeEach(() => mentionsParser.pattern = () => regexp);
+
describe('by function', function functionName() {
it(`should be equal to ${ regexp }`, () => {
- assert.equal(regexp, mention.pattern);
+ assert.equal(regexp, mentionsParser.pattern);
});
});
+
describe('by const', function functionName() {
it(`should be equal to ${ regexp }`, () => {
- assert.equal(regexp, mention.pattern);
+ assert.equal(regexp, mentionsParser.pattern);
});
});
});
+
describe('get useRealName', () => {
- beforeEach(() => mention.useRealName = () => true);
+ beforeEach(() => mentionsParser.useRealName = () => true);
+
describe('by function', function functionName() {
it('should be true', () => {
- assert.equal(true, mention.useRealName);
+ assert.equal(true, mentionsParser.useRealName);
});
});
+
describe('by const', function functionName() {
it('should be true', () => {
- assert.equal(true, mention.useRealName);
+ assert.equal(true, mentionsParser.useRealName);
});
});
});
+
describe('get me', () => {
const me = 'me';
+
describe('by function', function functionName() {
- beforeEach(() => mention.me = () => me);
+ beforeEach(() => mentionsParser.me = () => me);
+
it(`should be equal to ${ me }`, () => {
- assert.equal(me, mention.me);
+ assert.equal(me, mentionsParser.me);
});
});
+
describe('by const', function functionName() {
- beforeEach(() => mention.me = me);
+ beforeEach(() => mentionsParser.me = me);
+
it(`should be equal to ${ me }`, () => {
- assert.equal(me, mention.me);
+ assert.equal(me, mentionsParser.me);
});
});
});
+
describe('getUserMentions', function functionName() {
describe('for simple text, no mentions', () => {
const result = [];
@@ -62,10 +74,11 @@ describe('Mention', function() {
]
.forEach((text) => {
it(`should return "${ JSON.stringify(result) }" from "${ text }"`, () => {
- assert.deepEqual(result, mention.getUserMentions(text));
+ assert.deepEqual(result, mentionsParser.getUserMentions(text));
});
});
});
+
describe('for one user', () => {
const result = ['@rocket.cat'];
[
@@ -79,19 +92,23 @@ describe('Mention', function() {
]
.forEach((text) => {
it(`should return "${ JSON.stringify(result) }" from "${ text }"`, () => {
- assert.deepEqual(result, mention.getUserMentions(text));
+ assert.deepEqual(result, mentionsParser.getUserMentions(text));
});
});
+
it.skip('should return without the "." from "@rocket.cat."', () => {
- assert.deepEqual(result, mention.getUserMentions('@rocket.cat.'));
+ assert.deepEqual(result, mentionsParser.getUserMentions('@rocket.cat.'));
});
+
it.skip('should return without the "_" from "@rocket.cat_"', () => {
- assert.deepEqual(result, mention.getUserMentions('@rocket.cat_'));
+ assert.deepEqual(result, mentionsParser.getUserMentions('@rocket.cat_'));
});
- it.skip('should return without the "-" from "@rocket.cat."', () => {
- assert.deepEqual(result, mention.getUserMentions('@rocket.cat-'));
+
+ it.skip('should return without the "-" from "@rocket.cat-"', () => {
+ assert.deepEqual(result, mentionsParser.getUserMentions('@rocket.cat-'));
});
});
+
describe('for two users', () => {
const result = ['@rocket.cat', '@all'];
[
@@ -103,7 +120,7 @@ describe('Mention', function() {
]
.forEach((text) => {
it(`should return "${ JSON.stringify(result) }" from "${ text }"`, () => {
- assert.deepEqual(result, mention.getUserMentions(text));
+ assert.deepEqual(result, mentionsParser.getUserMentions(text));
});
});
});
@@ -118,10 +135,11 @@ describe('Mention', function() {
]
.forEach((text) => {
it(`should return "${ JSON.stringify(result) }" from "${ text }"`, () => {
- assert.deepEqual(result, mention.getChannelMentions(text));
+ assert.deepEqual(result, mentionsParser.getChannelMentions(text));
});
});
});
+
describe('for one channel', () => {
const result = ['#general'];
[
@@ -132,19 +150,23 @@ describe('Mention', function() {
'hello #general, how are you?',
].forEach((text) => {
it(`should return "${ JSON.stringify(result) }" from "${ text }"`, () => {
- assert.deepEqual(result, mention.getChannelMentions(text));
+ assert.deepEqual(result, mentionsParser.getChannelMentions(text));
});
});
+
it.skip('should return without the "." from "#general."', () => {
- assert.deepEqual(result, mention.getUserMentions('#general.'));
+ assert.deepEqual(result, mentionsParser.getUserMentions('#general.'));
});
+
it.skip('should return without the "_" from "#general_"', () => {
- assert.deepEqual(result, mention.getUserMentions('#general_'));
+ assert.deepEqual(result, mentionsParser.getUserMentions('#general_'));
});
+
it.skip('should return without the "-" from "#general."', () => {
- assert.deepEqual(result, mention.getUserMentions('#general-'));
+ assert.deepEqual(result, mentionsParser.getUserMentions('#general-'));
});
});
+
describe('for two channels', () => {
const result = ['#general', '#other'];
[
@@ -155,124 +177,137 @@ describe('Mention', function() {
'hello #general #other, how are you?',
].forEach((text) => {
it(`should return "${ JSON.stringify(result) }" from "${ text }"`, () => {
- assert.deepEqual(result, mention.getChannelMentions(text));
+ assert.deepEqual(result, mentionsParser.getChannelMentions(text));
});
});
});
+
describe('for url with fragments', () => {
const result = [];
[
'http://localhost/#general',
].forEach((text) => {
it(`should return nothing from "${ text }"`, () => {
- assert.deepEqual(result, mention.getChannelMentions(text));
+ assert.deepEqual(result, mentionsParser.getChannelMentions(text));
});
});
});
+
describe('for messages with url and channels', () => {
const result = ['#general'];
[
'http://localhost/#general #general',
].forEach((text) => {
it(`should return "${ JSON.stringify(result) }" from "${ text }"`, () => {
- assert.deepEqual(result, mention.getChannelMentions(text));
+ assert.deepEqual(result, mentionsParser.getChannelMentions(text));
});
});
});
});
-
});
+
const message = {
mentions: [{ username: 'rocket.cat', name: 'Rocket.Cat' }, { username: 'admin', name: 'Admin' }, { username: 'me', name: 'Me' }, { username: 'specialchars', name: '' }],
channels: [{ name: 'general', _id: '42' }, { name: 'rocket.cat', _id: '169' }],
};
+
describe('replace methods', function() {
describe('replaceUsers', () => {
it('should render for @all', () => {
- const result = mention.replaceUsers('@all', message, 'me');
- assert.equal('@all', result);
+ const result = mentionsParser.replaceUsers('@all', message, 'me');
+ assert.equal('all', result);
});
- const str2 = '@rocket.cat';
- it(`should render for ${ str2 }`, () => {
- const result = mention.replaceUsers('@rocket.cat', message, 'me');
- assert.equal(result, `${ str2 }`);
+
+ const str2 = 'rocket.cat';
+
+ it(`should render for "@${ str2 }"`, () => {
+ const result = mentionsParser.replaceUsers(`@${ str2 }`, message, 'me');
+ assert.equal(result, `${ str2 }`);
});
it(`should render for "hello ${ str2 }"`, () => {
- const result = mention.replaceUsers(`hello ${ str2 }`, message, 'me');
- assert.equal(result, `hello ${ str2 }`);
+ const result = mentionsParser.replaceUsers(`hello @${ str2 }`, message, 'me');
+ assert.equal(result, `hello ${ str2 }`);
});
+
it('should render for unknow/private user "hello @unknow"', () => {
- const result = mention.replaceUsers('hello @unknow', message, 'me');
+ const result = mentionsParser.replaceUsers('hello @unknow', message, 'me');
assert.equal(result, 'hello @unknow');
});
+
it('should render for me', () => {
- const result = mention.replaceUsers('hello @me', message, 'me');
- assert.equal(result, 'hello @me');
+ const result = mentionsParser.replaceUsers('hello @me', message, 'me');
+ assert.equal(result, 'hello me');
});
});
describe('replaceUsers (RealNames)', () => {
beforeEach(() => {
- mention.useRealName = () => true;
+ mentionsParser.useRealName = () => true;
});
+
it('should render for @all', () => {
- const result = mention.replaceUsers('@all', message, 'me');
- assert.equal('@all', result);
+ const result = mentionsParser.replaceUsers('@all', message, 'me');
+ assert.equal(result, 'all');
});
- const str2 = '@rocket.cat';
+ const str2 = 'rocket.cat';
const str2Name = 'Rocket.Cat';
- it(`should render for ${ str2 }`, () => {
- const result = mention.replaceUsers('@rocket.cat', message, 'me');
- assert.equal(result, `${ str2Name }`);
+
+ it(`should render for "@${ str2 }"`, () => {
+ const result = mentionsParser.replaceUsers(`@${ str2 }`, message, 'me');
+ assert.equal(result, `${ str2Name }`);
});
- it(`should render for "hello ${ str2 }"`, () => {
- const result = mention.replaceUsers(`hello ${ str2 }`, message, 'me');
- assert.equal(result, `hello ${ str2Name }`);
+
+ it(`should render for "hello @${ str2 }"`, () => {
+ const result = mentionsParser.replaceUsers(`hello @${ str2 }`, message, 'me');
+ assert.equal(result, `hello ${ str2Name }`);
});
- const specialchars = '@specialchars';
+ const specialchars = 'specialchars';
const specialcharsName = '<img onerror=alert(hello)>';
- it(`should escape special characters in "hello ${ specialchars }"`, () => {
- const result = mention.replaceUsers(`hello ${ specialchars }`, message, 'me');
- assert.equal(result, `hello ${ specialcharsName }`);
- });
- it(`should render for "hello
${ str2 }
"`, () => {
- const result = mention.replaceUsers(`hello
${ str2 }
`, message, 'me');
- const replaced = str2.replace('@', '');
- const expected = `hello
${ str2Name }
`;
+ it(`should escape special characters in "hello @${ specialchars }"`, () => {
+ const result = mentionsParser.replaceUsers(`hello @${ specialchars }`, message, 'me');
+ assert.equal(result, `hello ${ specialcharsName }`);
+ });
- assert.equal(result, expected);
+ it(`should render for "hello
@${ str2 }
"`, () => {
+ const result = mentionsParser.replaceUsers(`hello
@${ str2 }
`, message, 'me');
+ assert.equal(result, `hello
${ str2Name }
`);
});
it('should render for unknow/private user "hello @unknow"', () => {
- const result = mention.replaceUsers('hello @unknow', message, 'me');
+ const result = mentionsParser.replaceUsers('hello @unknow', message, 'me');
assert.equal(result, 'hello @unknow');
});
+
it('should render for me', () => {
- const result = mention.replaceUsers('hello @me', message, 'me');
- assert.equal(result, 'hello Me');
+ const result = mentionsParser.replaceUsers('hello @me', message, 'me');
+ assert.equal(result, 'hello Me');
});
});
describe('replaceChannels', () => {
it('should render for #general', () => {
- const result = mention.replaceChannels('#general', message);
- assert.equal('#general', result);
+ const result = mentionsParser.replaceChannels('#general', message);
+ assert.equal('#general', result);
});
+
const str2 = '#rocket.cat';
+
it(`should render for ${ str2 }`, () => {
- const result = mention.replaceChannels(str2, message);
- assert.equal(result, `${ str2 }`);
+ const result = mentionsParser.replaceChannels(str2, message);
+ assert.equal(result, `${ str2 }`);
});
+
it(`should render for "hello ${ str2 }"`, () => {
- const result = mention.replaceChannels(`hello ${ str2 }`, message);
- assert.equal(result, `hello ${ str2 }`);
+ const result = mentionsParser.replaceChannels(`hello ${ str2 }`, message);
+ assert.equal(result, `hello ${ str2 }`);
});
+
it('should render for unknow/private channel "hello #unknow"', () => {
- const result = mention.replaceChannels('hello #unknow', message);
+ const result = mentionsParser.replaceChannels('hello #unknow', message);
assert.equal(result, 'hello #unknow');
});
});
@@ -280,49 +315,56 @@ describe('replace methods', function() {
describe('parse all', () => {
it('should render for #general', () => {
message.html = '#general';
- const result = mention.parse(message, 'me');
- assert.equal('#general', result.html);
+ const result = mentionsParser.parse(message, 'me');
+ assert.equal(result.html, '#general');
});
+
it('should render for "#general and @rocket.cat', () => {
message.html = '#general and @rocket.cat';
- const result = mention.parse(message, 'me');
- assert.equal('#general and @rocket.cat', result.html);
+ const result = mentionsParser.parse(message, 'me');
+ assert.equal(result.html, '#general and rocket.cat');
});
+
it('should render for "', () => {
message.html = '';
- const result = mention.parse(message, 'me');
- assert.equal('', result.html);
+ const result = mentionsParser.parse(message, 'me');
+ assert.equal(result.html, '');
});
+
it('should render for "simple text', () => {
message.html = 'simple text';
- const result = mention.parse(message, 'me');
- assert.equal('simple text', result.html);
+ const result = mentionsParser.parse(message, 'me');
+ assert.equal(result.html, 'simple text');
});
});
describe('parse all (RealNames)', () => {
beforeEach(() => {
- mention.useRealName = () => true;
+ mentionsParser.useRealName = () => true;
});
+
it('should render for #general', () => {
message.html = '#general';
- const result = mention.parse(message, 'me');
- assert.equal('#general', result.html);
+ const result = mentionsParser.parse(message, 'me');
+ assert.equal(result.html, '#general');
});
+
it('should render for "#general and @rocket.cat', () => {
message.html = '#general and @rocket.cat';
- const result = mention.parse(message, 'me');
- assert.equal('#general and Rocket.Cat', result.html);
+ const result = mentionsParser.parse(message, 'me');
+ assert.equal(result.html, '#general and Rocket.Cat');
});
+
it('should render for "', () => {
message.html = '';
- const result = mention.parse(message, 'me');
- assert.equal('', result.html);
+ const result = mentionsParser.parse(message, 'me');
+ assert.equal(result.html, '');
});
+
it('should render for "simple text', () => {
message.html = 'simple text';
- const result = mention.parse(message, 'me');
- assert.equal('simple text', result.html);
+ const result = mentionsParser.parse(message, 'me');
+ assert.equal(result.html, 'simple text');
});
});
});
diff --git a/app/theme/client/imports/components/badge.css b/app/theme/client/imports/components/badge.css
index a7332ce81dc5..fc90a36ac152 100644
--- a/app/theme/client/imports/components/badge.css
+++ b/app/theme/client/imports/components/badge.css
@@ -1,18 +1,17 @@
.badge {
-
display: flex;
- min-width: 20px;
-
- padding: 2px 6px;
+ min-width: 18px;
+ min-height: 18px;
+ padding: 2px 5px;
color: var(--badge-text-color);
-
border-radius: var(--badge-radius);
-
background-color: var(--badge-background);
font-size: var(--badge-text-size);
+ line-height: 1;
+ align-items: center;
justify-content: center;
&--unread {
@@ -22,4 +21,16 @@
background-color: var(--badge-unread-background);
}
+
+ &--dm {
+ background-color: var(--badge-dm-background);
+ }
+
+ &--user-mentions {
+ background-color: var(--badge-user-mentions-background);
+ }
+
+ &--group-mentions {
+ background-color: var(--badge-group-mentions-background);
+ }
}
diff --git a/app/theme/client/imports/components/sidebar/sidebar-item.css b/app/theme/client/imports/components/sidebar/sidebar-item.css
index f5647d554ca4..31c515d3f3bd 100644
--- a/app/theme/client/imports/components/sidebar/sidebar-item.css
+++ b/app/theme/client/imports/components/sidebar/sidebar-item.css
@@ -323,11 +323,6 @@
fill: var(--color-white);
}
}
-
- & .mention-link {
- color: inherit;
- background: transparent;
- }
}
.flex-nav .sidebar-item__message {
diff --git a/app/theme/client/imports/general/base_old.css b/app/theme/client/imports/general/base_old.css
index 437dab356b90..b659b89c135a 100644
--- a/app/theme/client/imports/general/base_old.css
+++ b/app/theme/client/imports/general/base_old.css
@@ -2377,14 +2377,6 @@ rc-old select,
font-size: 1em;
}
-.rc-old .reply-preview .mention-link.mention-link-all {
- color: #ffffff;
-}
-
-.rc-old .reply-preview .mention-link.mention-link-me {
- color: #ffffff;
-}
-
.rc-old .message-popup.popup-with-reply-preview {
border-radius: 5px 5px 0 0;
}
@@ -2616,6 +2608,14 @@ rc-old select,
height: 2px;
border-radius: 2px;
+
+ &--me {
+ background-color: var(--mention-link-me-background);
+ }
+
+ &--group {
+ background-color: var(--mention-link-group-background);
+ }
}
}
@@ -4466,15 +4466,6 @@ rc-old select,
height: 100%;
}
-.rc-old .mention-link {
-
- padding: 0 6px 2px;
-
- border-radius: 10px;
-
- font-weight: bold;
-}
-
.rc-old .highlight-text {
padding: 2px;
diff --git a/app/theme/client/imports/general/variables.css b/app/theme/client/imports/general/variables.css
index 46df876897b8..902aad513931 100644
--- a/app/theme/client/imports/general/variables.css
+++ b/app/theme/client/imports/general/variables.css
@@ -256,7 +256,21 @@
--badge-radius: 12px;
--badge-text-size: 0.75rem;
--badge-background: var(--rc-color-primary-dark);
- --badge-unread-background: var(--rc-color-button-primary);
+ --badge-unread-background: var(--rc-color-primary-dark);
+ --badge-dm-background: var(--rc-color-primary-dark);
+ --badge-user-mentions-background: var(--rc-color-button-primary);
+ --badge-group-mentions-background: var(--rc-color-primary-dark);
+
+ /*
+ * Mention link
+ */
+ --mention-link-radius: 10px;
+ --mention-link-background: rgba(29, 116, 245, 0.2);
+ --mention-link-text-color: var(--rc-color-button-primary);
+ --mention-link-me-background: var(--rc-color-button-primary);
+ --mention-link-me-text-color: var(--color-white);
+ --mention-link-group-background: var(--rc-color-primary-dark);
+ --mention-link-group-text-color: var(--color-white);
/*
* Message box
diff --git a/app/ui-sidenav/client/sidebarItem.html b/app/ui-sidenav/client/sidebarItem.html
index c36ac3016c12..09b277f81d84 100644
--- a/app/ui-sidenav/client/sidebarItem.html
+++ b/app/ui-sidenav/client/sidebarItem.html
@@ -57,7 +57,7 @@
{{/if}}
{{/if}}
{{#if unread}}
- {{#if userMentions}}@ {{/if}}{{unread}}
+ {{unread}}
{{/if}}
diff --git a/app/ui-sidenav/client/sidebarItem.js b/app/ui-sidenav/client/sidebarItem.js
index b76e8c85a9ce..e039e187fb8a 100644
--- a/app/ui-sidenav/client/sidebarItem.js
+++ b/app/ui-sidenav/client/sidebarItem.js
@@ -35,6 +35,16 @@ Template.sidebarItem.helpers({
showUnread() {
return this.unread > 0 || (!this.hideUnreadStatus && this.alert);
},
+ badgeClass() {
+ const { t, unread, userMentions, groupMentions } = this;
+ return [
+ 'badge',
+ unread && 'badge--unread',
+ unread && t === 'd' && 'badge--dm',
+ userMentions && 'badge--user-mentions',
+ groupMentions && 'badge--group-mentions',
+ ].filter(Boolean).join(' ');
+ },
});
function setLastMessageTs(instance, ts) {
diff --git a/app/ui-utils/client/lib/RoomManager.js b/app/ui-utils/client/lib/RoomManager.js
index 68957d0f73a2..5e1a8015180f 100644
--- a/app/ui-utils/client/lib/RoomManager.js
+++ b/app/ui-utils/client/lib/RoomManager.js
@@ -231,15 +231,22 @@ export const RoomManager = new function() {
}
const [ticksBar] = dom.getElementsByClassName('ticks-bar');
- const messagesBox = $('.messages-box', dom);
- const scrollTop = messagesBox.find('> .wrapper').scrollTop() - 50;
- const totalHeight = messagesBox.find(' > .wrapper > ul').height() + 40;
-
- ticksBar.innerHTML = Array.from(messagesBox[0].getElementsByClassName('mention-link-me')).map((item) => {
- const topOffset = item.getBoundingClientRect().top + scrollTop;
- const percent = (100 / totalHeight) * topOffset;
- return `