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 `

`; - }).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 `
`; + }) + .join(''); } }; Cls.initClass(); diff --git a/private/client/imports/general/variables.css b/private/client/imports/general/variables.css index 2778680b26ed..0c7ad35acd3a 100644 --- a/private/client/imports/general/variables.css +++ b/private/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 @@ -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); diff --git a/private/server/colors.less b/private/server/colors.less index 6948da0f0ff8..269cac743c77 100755 --- a/private/server/colors.less +++ b/private/server/colors.less @@ -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; }