You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

functions.ts 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. // @ts-expect-error
  2. import aliases from 'react-emoji-render/data/aliases';
  3. // eslint-disable-next-line lines-around-comment
  4. // @ts-expect-error
  5. import emojiAsciiAliases from 'react-emoji-render/data/asciiAliases';
  6. import { IReduxState } from '../app/types';
  7. import { getLocalizedDateFormatter } from '../base/i18n/dateUtil';
  8. import i18next from '../base/i18n/i18next';
  9. import { getParticipantById } from '../base/participants/functions';
  10. import { escapeRegexp } from '../base/util/helpers';
  11. import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL, TIMESTAMP_FORMAT } from './constants';
  12. import { IMessage } from './types';
  13. /**
  14. * An ASCII emoticon regexp array to find and replace old-style ASCII
  15. * emoticons (such as :O) with the new Unicode representation, so that
  16. * devices and browsers that support them can render these natively
  17. * without a 3rd party component.
  18. *
  19. * NOTE: this is currently only used on mobile, but it can be used
  20. * on web too once we drop support for browsers that don't support
  21. * unicode emoji rendering.
  22. */
  23. const ASCII_EMOTICON_REGEXP_ARRAY: Array<[RegExp, string]> = [];
  24. /**
  25. * An emoji regexp array to find and replace alias emoticons
  26. * (such as :smiley:) with the new Unicode representation, so that
  27. * devices and browsers that support them can render these natively
  28. * without a 3rd party component.
  29. *
  30. * NOTE: this is currently only used on mobile, but it can be used
  31. * on web too once we drop support for browsers that don't support
  32. * unicode emoji rendering.
  33. */
  34. const SLACK_EMOJI_REGEXP_ARRAY: Array<[RegExp, string]> = [];
  35. (function() {
  36. for (const [ key, value ] of Object.entries(aliases)) {
  37. // Add ASCII emoticons
  38. const asciiEmoticons = emojiAsciiAliases[key];
  39. if (asciiEmoticons) {
  40. const asciiEscapedValues = asciiEmoticons.map((v: string) => escapeRegexp(v));
  41. const asciiRegexp = `(${asciiEscapedValues.join('|')})`;
  42. // Escape urls
  43. const formattedAsciiRegexp = key === 'confused'
  44. ? `(?=(${asciiRegexp}))(:(?!//).)`
  45. : asciiRegexp;
  46. ASCII_EMOTICON_REGEXP_ARRAY.push([ new RegExp(formattedAsciiRegexp, 'g'), value as string ]);
  47. }
  48. // Add slack-type emojis
  49. const emojiRegexp = `\\B(${escapeRegexp(`:${key}:`)})\\B`;
  50. SLACK_EMOJI_REGEXP_ARRAY.push([ new RegExp(emojiRegexp, 'g'), value as string ]);
  51. }
  52. })();
  53. /**
  54. * Replaces ASCII and other non-unicode emoticons with unicode emojis to let the emojis be rendered
  55. * by the platform native renderer.
  56. *
  57. * @param {string} message - The message to parse and replace.
  58. * @returns {string}
  59. */
  60. export function replaceNonUnicodeEmojis(message: string) {
  61. let replacedMessage = message;
  62. for (const [ regexp, replaceValue ] of SLACK_EMOJI_REGEXP_ARRAY) {
  63. replacedMessage = replacedMessage.replace(regexp, replaceValue);
  64. }
  65. for (const [ regexp, replaceValue ] of ASCII_EMOTICON_REGEXP_ARRAY) {
  66. replacedMessage = replacedMessage.replace(regexp, replaceValue);
  67. }
  68. return replacedMessage;
  69. }
  70. /**
  71. * Selector for calculating the number of unread chat messages.
  72. *
  73. * @param {IReduxState} state - The redux state.
  74. * @returns {number} The number of unread messages.
  75. */
  76. export function getUnreadCount(state: IReduxState) {
  77. const { lastReadMessage, messages } = state['features/chat'];
  78. const messagesCount = messages.length;
  79. if (!messagesCount) {
  80. return 0;
  81. }
  82. let reactionMessages = 0;
  83. let lastReadIndex;
  84. if (navigator.product === 'ReactNative') {
  85. // React native stores the messages in a reversed order.
  86. lastReadIndex = messages.indexOf(<IMessage>lastReadMessage);
  87. for (let i = 0; i < lastReadIndex; i++) {
  88. if (messages[i].isReaction) {
  89. reactionMessages++;
  90. }
  91. }
  92. return lastReadIndex - reactionMessages;
  93. }
  94. lastReadIndex = messages.lastIndexOf(<IMessage>lastReadMessage);
  95. for (let i = lastReadIndex + 1; i < messagesCount; i++) {
  96. if (messages[i].isReaction) {
  97. reactionMessages++;
  98. }
  99. }
  100. return messagesCount - (lastReadIndex + 1) - reactionMessages;
  101. }
  102. /**
  103. * Get whether the chat smileys are disabled or not.
  104. *
  105. * @param {IReduxState} state - The redux state.
  106. * @returns {boolean} The disabled flag.
  107. */
  108. export function areSmileysDisabled(state: IReduxState) {
  109. const disableChatSmileys = state['features/base/config']?.disableChatSmileys === true;
  110. return disableChatSmileys;
  111. }
  112. /**
  113. * Returns the timestamp to display for the message.
  114. *
  115. * @param {IMessage} message - The message from which to get the timestamp.
  116. * @returns {string}
  117. */
  118. export function getFormattedTimestamp(message: IMessage) {
  119. return getLocalizedDateFormatter(new Date(message.timestamp))
  120. .format(TIMESTAMP_FORMAT);
  121. }
  122. /**
  123. * Generates the message text to be rendered in the component.
  124. *
  125. * @param {IMessage} message - The message from which to get the text.
  126. * @returns {string}
  127. */
  128. export function getMessageText(message: IMessage) {
  129. return message.messageType === MESSAGE_TYPE_ERROR
  130. ? i18next.t('chat.error', {
  131. error: message.message
  132. })
  133. : message.message;
  134. }
  135. /**
  136. * Returns whether a message can be replied to.
  137. *
  138. * @param {IReduxState} state - The redux state.
  139. * @param {IMessage} message - The message to be checked.
  140. * @returns {boolean}
  141. */
  142. export function getCanReplyToMessage(state: IReduxState, message: IMessage) {
  143. const { knocking } = state['features/lobby'];
  144. const participant = getParticipantById(state, message.participantId);
  145. return Boolean(participant)
  146. && (message.privateMessage || (message.lobbyChat && !knocking))
  147. && message.messageType !== MESSAGE_TYPE_LOCAL;
  148. }
  149. /**
  150. * Returns the message that is displayed as a notice for private messages.
  151. *
  152. * @param {IMessage} message - The message to be checked.
  153. * @returns {string}
  154. */
  155. export function getPrivateNoticeMessage(message: IMessage) {
  156. return i18next.t('chat.privateNotice', {
  157. recipient: message.messageType === MESSAGE_TYPE_LOCAL ? message.recipient : i18next.t('chat.you')
  158. });
  159. }