[IMPROVE] New sidebar item badges, mention links, and ticks (#14030)

Closes #14024
pull/14057/head
Tasso Evangelista 6 years ago committed by Guilherme Gazzo
parent 2e79252ce1
commit 0a9678464b
  1. 1
      app/discussion/client/lib/messageTypes/discussionMessage.js
  2. 21
      app/mentions/client/client.js
  3. 1
      app/mentions/client/index.js
  4. 27
      app/mentions/client/mentionLink.css
  5. 81
      app/mentions/lib/Mentions.js
  6. 102
      app/mentions/lib/MentionsParser.js
  7. 4
      app/mentions/server/Mentions.js
  8. 216
      app/mentions/tests/client.tests.js
  9. 23
      app/theme/client/imports/components/badge.css
  10. 5
      app/theme/client/imports/components/sidebar/sidebar-item.css
  11. 25
      app/theme/client/imports/general/base_old.css
  12. 16
      app/theme/client/imports/general/variables.css
  13. 2
      app/ui-sidenav/client/sidebarItem.html
  14. 10
      app/ui-sidenav/client/sidebarItem.js
  15. 25
      app/ui-utils/client/lib/RoomManager.js
  16. 18
      private/client/imports/general/variables.css
  17. 37
      private/server/colors.less

@ -9,7 +9,6 @@ Meteor.startup(function() {
message: 'discussion-created',
data(message) {
return {
// channelLink: `<a class="mention-link" data-channel= ${ message.channels[0]._id } title="">${ TAPi18n.__('discussion') }</a>`,
message: `<svg class="rc-icon" aria-hidden="true"><use xlink:href="#icon-discussion"></use></svg> ${ message.msg }`,
};
},

@ -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');

@ -1 +1,2 @@
import './client';
import './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;
}
}

@ -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|<p>|<br> ?)@(${ this.pattern }(@(${ this.pattern }))?)`, 'gm');
}
get channelMentionRegex() {
return new RegExp(`(^|\\s|<p>)#(${ this.pattern }(@(${ this.pattern }))?)`, 'gm');
}
replaceUsers(str, message, me) {
return str.replace(this.userMentionRegex, (match, prefix, username) => {
if (['all', 'here'].includes(username)) {
return `${ prefix }<a class="mention-link mention-link-me mention-link-all">@${ username }</a>`;
}
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 }<a class="mention-link ${ username === me ? 'mention-link-me' : '' }" data-username="${ username }" title="${ name ? username : '' }">${ name || `@${ username }` }</a>`;
});
}
replaceChannels(str, message) {
// since apostrophe escaped contains # we need to unescape it
return str.replace(/&#39;/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 }<a class="mention-link" data-channel="${ roomNameorId }">${ `#${ name }` }</a>`;
});
}
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;
}
}

@ -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|<p>|<br> ?)@(${ this.pattern }(@(${ this.pattern }))?)`, 'gm');
}
get channelMentionRegex() {
return new RegExp(`(^|\\s|<p>)#(${ 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 }<a class="${ className }">${ mention }</a>`;
}
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 }<a class="${ className }" data-username="${ mention }" title="${ this.useRealName ? mention : label }">${ label }</a>`;
})
replaceChannels = (msg, { temp, channels }) => msg
.replace(/&#39;/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 }<a class="mention-link mention-link--room" data-channel="${ reference }">${ `#${ mention }` }</a>`;
})
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;
}
}

@ -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;

@ -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: '<img onerror=alert(hello)>' }],
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('<a class="mention-link mention-link-me mention-link-all">@all</a>', result);
const result = mentionsParser.replaceUsers('@all', message, 'me');
assert.equal('<a class="mention-link mention-link--user mention-link--all mention-link--group">all</a>', result);
});
const str2 = '@rocket.cat';
it(`should render for ${ str2 }`, () => {
const result = mention.replaceUsers('@rocket.cat', message, 'me');
assert.equal(result, `<a class="mention-link " data-username="${ str2.replace('@', '') }" title="">${ str2 }</a>`);
const str2 = 'rocket.cat';
it(`should render for "@${ str2 }"`, () => {
const result = mentionsParser.replaceUsers(`@${ str2 }`, message, 'me');
assert.equal(result, `<a class="mention-link mention-link--user" data-username="${ str2 }" title="${ str2 }">${ str2 }</a>`);
});
it(`should render for "hello ${ str2 }"`, () => {
const result = mention.replaceUsers(`hello ${ str2 }`, message, 'me');
assert.equal(result, `hello <a class="mention-link " data-username="${ str2.replace('@', '') }" title="">${ str2 }</a>`);
const result = mentionsParser.replaceUsers(`hello @${ str2 }`, message, 'me');
assert.equal(result, `hello <a class="mention-link mention-link--user" data-username="${ str2 }" title="${ str2 }">${ str2 }</a>`);
});
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 <a class="mention-link mention-link-me" data-username="me" title="">@me</a>');
const result = mentionsParser.replaceUsers('hello @me', message, 'me');
assert.equal(result, 'hello <a class="mention-link mention-link--user mention-link--me" data-username="me" title="me">me</a>');
});
});
describe('replaceUsers (RealNames)', () => {
beforeEach(() => {
mention.useRealName = () => true;
mentionsParser.useRealName = () => true;
});
it('should render for @all', () => {
const result = mention.replaceUsers('@all', message, 'me');
assert.equal('<a class="mention-link mention-link-me mention-link-all">@all</a>', result);
const result = mentionsParser.replaceUsers('@all', message, 'me');
assert.equal(result, '<a class="mention-link mention-link--user mention-link--all mention-link--group">all</a>');
});
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, `<a class="mention-link " data-username="${ str2.replace('@', '') }" title="${ str2.replace('@', '') }">${ str2Name }</a>`);
it(`should render for "@${ str2 }"`, () => {
const result = mentionsParser.replaceUsers(`@${ str2 }`, message, 'me');
assert.equal(result, `<a class="mention-link mention-link--user" data-username="${ str2 }" title="${ str2 }">${ str2Name }</a>`);
});
it(`should render for "hello ${ str2 }"`, () => {
const result = mention.replaceUsers(`hello ${ str2 }`, message, 'me');
assert.equal(result, `hello <a class="mention-link " data-username="${ str2.replace('@', '') }" title="${ str2.replace('@', '') }">${ str2Name }</a>`);
it(`should render for "hello @${ str2 }"`, () => {
const result = mentionsParser.replaceUsers(`hello @${ str2 }`, message, 'me');
assert.equal(result, `hello <a class="mention-link mention-link--user" data-username="${ str2 }" title="${ str2 }">${ str2Name }</a>`);
});
const specialchars = '@specialchars';
const specialchars = 'specialchars';
const specialcharsName = '&lt;img onerror=alert(hello)&gt;';
it(`should escape special characters in "hello ${ specialchars }"`, () => {
const result = mention.replaceUsers(`hello ${ specialchars }`, message, 'me');
assert.equal(result, `hello <a class="mention-link " data-username="${ specialchars.replace('@', '') }" title="${ specialchars.replace('@', '') }">${ specialcharsName }</a>`);
});
it(`should render for "hello<br>${ str2 } <br>"`, () => {
const result = mention.replaceUsers(`hello<br>${ str2 } <br>`, message, 'me');
const replaced = str2.replace('@', '');
const expected = `hello<br><a class="mention-link " data-username="${ replaced }" title="${ replaced }">${ str2Name }</a> <br>`;
it(`should escape special characters in "hello @${ specialchars }"`, () => {
const result = mentionsParser.replaceUsers(`hello @${ specialchars }`, message, 'me');
assert.equal(result, `hello <a class="mention-link mention-link--user" data-username="${ specialchars }" title="${ specialchars }">${ specialcharsName }</a>`);
});
assert.equal(result, expected);
it(`should render for "hello<br>@${ str2 } <br>"`, () => {
const result = mentionsParser.replaceUsers(`hello<br>@${ str2 } <br>`, message, 'me');
assert.equal(result, `hello<br><a class="mention-link mention-link--user" data-username="${ str2 }" title="${ str2 }">${ str2Name }</a> <br>`);
});
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 <a class="mention-link mention-link-me" data-username="me" title="me">Me</a>');
const result = mentionsParser.replaceUsers('hello @me', message, 'me');
assert.equal(result, 'hello <a class="mention-link mention-link--user mention-link--me" data-username="me" title="me">Me</a>');
});
});
describe('replaceChannels', () => {
it('should render for #general', () => {
const result = mention.replaceChannels('#general', message);
assert.equal('<a class="mention-link" data-channel="42">#general</a>', result);
const result = mentionsParser.replaceChannels('#general', message);
assert.equal('<a class="mention-link mention-link--room" data-channel="42">#general</a>', result);
});
const str2 = '#rocket.cat';
it(`should render for ${ str2 }`, () => {
const result = mention.replaceChannels(str2, message);
assert.equal(result, `<a class="mention-link" data-channel="169">${ str2 }</a>`);
const result = mentionsParser.replaceChannels(str2, message);
assert.equal(result, `<a class="mention-link mention-link--room" data-channel="169">${ str2 }</a>`);
});
it(`should render for "hello ${ str2 }"`, () => {
const result = mention.replaceChannels(`hello ${ str2 }`, message);
assert.equal(result, `hello <a class="mention-link" data-channel="169">${ str2 }</a>`);
const result = mentionsParser.replaceChannels(`hello ${ str2 }`, message);
assert.equal(result, `hello <a class="mention-link mention-link--room" data-channel="169">${ str2 }</a>`);
});
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('<a class="mention-link" data-channel="42">#general</a>', result.html);
const result = mentionsParser.parse(message, 'me');
assert.equal(result.html, '<a class="mention-link mention-link--room" data-channel="42">#general</a>');
});
it('should render for "#general and @rocket.cat', () => {
message.html = '#general and @rocket.cat';
const result = mention.parse(message, 'me');
assert.equal('<a class="mention-link" data-channel="42">#general</a> and <a class="mention-link " data-username="rocket.cat" title="">@rocket.cat</a>', result.html);
const result = mentionsParser.parse(message, 'me');
assert.equal(result.html, '<a class="mention-link mention-link--room" data-channel="42">#general</a> and <a class="mention-link mention-link--user" data-username="rocket.cat" title="rocket.cat">rocket.cat</a>');
});
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('<a class="mention-link" data-channel="42">#general</a>', result.html);
const result = mentionsParser.parse(message, 'me');
assert.equal(result.html, '<a class="mention-link mention-link--room" data-channel="42">#general</a>');
});
it('should render for "#general and @rocket.cat', () => {
message.html = '#general and @rocket.cat';
const result = mention.parse(message, 'me');
assert.equal('<a class="mention-link" data-channel="42">#general</a> and <a class="mention-link " data-username="rocket.cat" title="rocket.cat">Rocket.Cat</a>', result.html);
const result = mentionsParser.parse(message, 'me');
assert.equal(result.html, '<a class="mention-link mention-link--room" data-channel="42">#general</a> and <a class="mention-link mention-link--user" data-username="rocket.cat" title="rocket.cat">Rocket.Cat</a>');
});
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');
});
});
});

@ -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);
}
}

@ -293,11 +293,6 @@
fill: var(--color-white);
}
}
& .mention-link {
color: inherit;
background: transparent;
}
}
.flex-nav .sidebar-item__message {

@ -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;

@ -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

@ -59,7 +59,7 @@
{{/if}}
{{/if}}
{{#if unread}}
<span class="badge badge--unread">{{#if userMentions}}@ {{/if}}{{unread}}</span>
<span class="{{badgeClass}}">{{unread}}</span>
{{/if}}
</div>
</div>

@ -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) {

@ -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 `<div class="tick ${ item.classList.contains('mention-link-all') ? 'background-attention-color' : 'background-primary-action-color' }" style="top: ${ percent }%;"></div>`;
}).join('');
const [messagesBox] = dom.getElementsByClassName('messages-box');
const scrollTop = $('> .wrapper', messagesBox).scrollTop() - 50;
const totalHeight = $(' > .wrapper > ul', messagesBox).height() + 40;
ticksBar.innerHTML = Array.from(messagesBox.querySelectorAll('.mention-link--me, .mention-link--group'))
.map((mentionLink) => {
const topOffset = $(mentionLink).offset().top + scrollTop;
const percent = (100 / totalHeight) * topOffset;
const className = [
'tick',
mentionLink.classList.contains('mention-link--me') && 'tick--me',
mentionLink.classList.contains('mention-link--group') && 'tick--group',
].filter(Boolean).join(' ');
return `<div class="${ className }" style="top: ${ percent }%;"></div>`;
})
.join('');
}
};
Cls.initClass();

@ -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
@ -282,6 +296,8 @@
--header-padding: 16px;
--header-toggle-favorite-color: var(--color-gray-medium);
--header-toggle-favorite-star-color: var(--rc-color-alert-light);
--header-toggle-encryption-off-color: var(--color-gray-medium);
--header-toggle-encryption-on-color: var(--rc-color-alert-message-secondary);
--header-title-username-color-darker: var(--color-dark);
--header-title-font-size: var(--text-default-size);
--header-title-font-size--subtitle: var(--text-small-size);

@ -27,6 +27,29 @@
@transparent-lighter: rgba(255, 255, 255, 0.3);
@transparent-lightest: rgba(255, 255, 255, 0.6);
:root {
--legacy-default-action-color: @default-action-color;
--legacy-default-action-contrast: @default-action-contrast;
--legacy-primary-background-contrast: @primary-background-contrast;
--legacy-primary-action-contrast: @primary-action-contrast;
--legacy-secondary-background-contrast: @secondary-background-contrast;
--legacy-secondary-action-contrast: @secondary-action-contrast;
--legacy-selection-background: @selection-background;
--legacy-success-background: @success-background;
--legacy-success-border: @success-border;
--legacy-error-background: @error-background;
--legacy-error-border: @error-border;
--legacy-error-contrast: @error-contrast;
--legacy-pending-background: @pending-background;
--legacy-pending-border: @pending-border;
--legacy-transparent-darkest: @transparent-darkest;
--legacy-transparent-darker: @transparent-darker;
--legacy-transparent-dark: @transparent-dark;
--legacy-transparent-light: @transparent-light;
--legacy-transparent-lighter: @transparent-lighter;
--legacy-transparent-lightest: @transparent-lightest;
}
/** ----------------------------------------------------------------------------
* Mixins
*/
@ -573,20 +596,6 @@ input:-webkit-autofill {
}
}
.mention-link {
background-color: fade(@rc-color-button-primary, 20%);
&.mention-link-me {
background: @primary-action-color;
color: @primary-action-contrast;
}
&.mention-link-all {
background: @attention-color;
color: @primary-action-contrast;
}
}
.highlight-text {
background-color: @selection-background;
}

Loading…
Cancel
Save