mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-02-14 23:18:54 +00:00
Render group member labels in quotes
Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
@@ -1095,6 +1095,10 @@ $message-padding-horizontal: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.module-message__author--with-quote {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.module-message__text {
|
||||
@include mixins.font-body-1;
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ $color-white-alpha-12: rgba($color-white, 0.12);
|
||||
$color-white-alpha-16: rgba($color-white, 0.16);
|
||||
$color-white-alpha-20: rgba($color-white, 0.2);
|
||||
$color-white-alpha-30: rgba($color-white, 0.3);
|
||||
$color-white-alpha-36: rgba($color-white, 0.36);
|
||||
$color-white-alpha-40: rgba($color-white, 0.4);
|
||||
$color-white-alpha-50: rgba($color-white, 0.5);
|
||||
$color-white-alpha-55: rgba($color-white, 0.55);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
@use 'sass:map';
|
||||
@use 'sass:color';
|
||||
|
||||
@use '../variables';
|
||||
@use '../mixins';
|
||||
|
||||
button.module-contact-name {
|
||||
@@ -305,6 +306,18 @@ $contact-colors: (
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
&--label-pill--quote {
|
||||
margin-bottom: 2px;
|
||||
@include mixins.font-body-small;
|
||||
|
||||
color: variables.$color-gray-90;
|
||||
background-color: variables.$color-white-alpha-36;
|
||||
@include mixins.dark-theme() {
|
||||
color: variables.$color-gray-05;
|
||||
background-color: variables.$color-white-alpha-20;
|
||||
}
|
||||
}
|
||||
|
||||
&--label-pill--text {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@@ -185,6 +185,10 @@
|
||||
.module-quote__primary__author {
|
||||
@include mixins.font-body-2-bold;
|
||||
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
@@ -203,6 +207,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.module-quote__primary__author--with-label {
|
||||
margin-top: -3px;
|
||||
}
|
||||
|
||||
.module-quote__primary__text {
|
||||
@include mixins.font-body-1;
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { useFunEmojiLocalizer } from '../fun/useFunEmojiLocalizer.dom.js';
|
||||
import { FunStaticEmoji } from '../fun/FunEmoji.dom.js';
|
||||
import { missingEmojiPlaceholder } from '../../types/GroupMemberLabels.std.js';
|
||||
|
||||
import type { MemberLabelType } from '../../types/GroupMemberLabels.std.js';
|
||||
import type { ConversationType } from '../../state/ducks/conversations.preload.js';
|
||||
import type { ContactNameColorType } from '../../types/Colors.std.js';
|
||||
import type { FunStaticEmojiSize } from '../fun/FunEmoji.dom.js';
|
||||
@@ -113,7 +114,7 @@ export function ContactName({
|
||||
);
|
||||
}
|
||||
|
||||
export type Context = 'bubble' | 'list';
|
||||
export type Context = 'bubble' | 'list' | 'quote';
|
||||
|
||||
export function GroupMemberLabel({
|
||||
emojiSize = 12,
|
||||
@@ -123,7 +124,7 @@ export function GroupMemberLabel({
|
||||
module,
|
||||
}: {
|
||||
emojiSize?: FunStaticEmojiSize;
|
||||
contactLabel?: { labelString: string; labelEmoji: string | undefined };
|
||||
contactLabel?: MemberLabelType;
|
||||
contactNameColor?: ContactNameColorType;
|
||||
context: Context;
|
||||
module?: string;
|
||||
|
||||
@@ -127,6 +127,7 @@ import {
|
||||
import { useGroupedAndOrderedReactions } from '../../util/groupAndOrderReactions.dom.js';
|
||||
import type { AxoMenuBuilder } from '../../axo/AxoMenuBuilder.dom.js';
|
||||
import type { RenderAudioAttachmentProps } from '../../state/smart/renderAudioAttachment.preload.js';
|
||||
import type { MemberLabelType } from '../../types/GroupMemberLabels.std.js';
|
||||
|
||||
const { drop, take, unescape } = lodash;
|
||||
|
||||
@@ -247,7 +248,7 @@ export type PropsData = {
|
||||
id: string;
|
||||
renderingContext: RenderingContextType;
|
||||
contactNameColor?: ContactNameColorType;
|
||||
contactLabel?: { labelString: string; labelEmoji: string | undefined };
|
||||
contactLabel?: MemberLabelType;
|
||||
conversationColor: ConversationColorType;
|
||||
conversationTitle: string;
|
||||
customColor?: CustomColorType;
|
||||
@@ -305,6 +306,7 @@ export type PropsData = {
|
||||
authorPhoneNumber?: string;
|
||||
authorProfileName?: string;
|
||||
authorTitle: string;
|
||||
authorLabel?: MemberLabelType;
|
||||
authorName?: string;
|
||||
bodyRanges?: HydratedBodyRangesType;
|
||||
referencedMessageNotFound: boolean;
|
||||
@@ -2120,6 +2122,7 @@ export class Message extends React.PureComponent<Props, State> {
|
||||
payment={quote.payment}
|
||||
isIncoming={isIncoming}
|
||||
authorTitle={quote.authorTitle}
|
||||
authorLabel={quote.authorLabel}
|
||||
bodyRanges={quote.bodyRanges}
|
||||
conversationColor={conversationColor}
|
||||
conversationTitle={conversationTitle}
|
||||
|
||||
@@ -168,6 +168,7 @@ const defaultMessageProps: TimelineMessagesProps = {
|
||||
|
||||
const renderInMessage = ({
|
||||
authorTitle,
|
||||
authorLabel,
|
||||
conversationColor,
|
||||
isFromMe,
|
||||
rawAttachment,
|
||||
@@ -182,6 +183,7 @@ const renderInMessage = ({
|
||||
quote: {
|
||||
authorId: 'an-author',
|
||||
authorTitle,
|
||||
authorLabel,
|
||||
conversationColor,
|
||||
conversationTitle: getDefaultConversation().title,
|
||||
isFromMe,
|
||||
@@ -223,6 +225,16 @@ IncomingByAnotherAuthor.args = {
|
||||
isIncoming: true,
|
||||
};
|
||||
|
||||
export const IncomingByAnotherWithLabel = Template.bind({});
|
||||
IncomingByAnotherWithLabel.args = {
|
||||
authorTitle: getDefaultConversation().title,
|
||||
isIncoming: true,
|
||||
authorLabel: {
|
||||
labelEmoji: '1️⃣',
|
||||
labelString: 'First',
|
||||
},
|
||||
};
|
||||
|
||||
export const IncomingByMe = Template.bind({});
|
||||
IncomingByMe.args = {
|
||||
isFromMe: true,
|
||||
@@ -233,7 +245,14 @@ export function IncomingOutgoingColors(args: Props): React.JSX.Element {
|
||||
return (
|
||||
<>
|
||||
{ConversationColors.map(color =>
|
||||
renderInMessage({ ...args, conversationColor: color })
|
||||
renderInMessage({
|
||||
...args,
|
||||
conversationColor: color,
|
||||
authorLabel: {
|
||||
labelEmoji: '1️⃣',
|
||||
labelString: 'First',
|
||||
},
|
||||
})
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
// Copyright 2018 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import lodash from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import * as MIME from '../../types/MIME.std.js';
|
||||
import * as GoogleChrome from '../../util/GoogleChrome.std.js';
|
||||
|
||||
import { MessageBody } from './MessageBody.dom.js';
|
||||
import { ContactName, GroupMemberLabel } from './ContactName.dom.js';
|
||||
import { Emojify } from './Emojify.dom.js';
|
||||
import { TextAttachment } from '../TextAttachment.dom.js';
|
||||
import { getClassNamesFor } from '../../util/getClassNamesFor.std.js';
|
||||
import { getCustomColorStyle } from '../../util/getCustomColorStyle.dom.js';
|
||||
import { PaymentEventKind } from '../../types/Payment.std.js';
|
||||
import { getPaymentEventNotificationText } from '../../messages/payments.std.js';
|
||||
import { shouldTryToCopyFromQuotedMessage } from '../../messages/helpers.std.js';
|
||||
import { RenderLocation } from './MessageTextRenderer.dom.js';
|
||||
|
||||
import type {
|
||||
AttachmentType,
|
||||
ThumbnailType,
|
||||
@@ -20,17 +30,9 @@ import type {
|
||||
ConversationColorType,
|
||||
CustomColorType,
|
||||
} from '../../types/Colors.std.js';
|
||||
import { ContactName } from './ContactName.dom.js';
|
||||
import { Emojify } from './Emojify.dom.js';
|
||||
import { TextAttachment } from '../TextAttachment.dom.js';
|
||||
import { getClassNamesFor } from '../../util/getClassNamesFor.std.js';
|
||||
import { getCustomColorStyle } from '../../util/getCustomColorStyle.dom.js';
|
||||
import type { AnyPaymentEvent } from '../../types/Payment.std.js';
|
||||
import { PaymentEventKind } from '../../types/Payment.std.js';
|
||||
import { getPaymentEventNotificationText } from '../../messages/payments.std.js';
|
||||
import { shouldTryToCopyFromQuotedMessage } from '../../messages/helpers.std.js';
|
||||
import { RenderLocation } from './MessageTextRenderer.dom.js';
|
||||
import type { QuotedAttachmentType } from '../../model-types.d.ts';
|
||||
import type { MemberLabelType } from '../../types/GroupMemberLabels.std.js';
|
||||
|
||||
const { noop } = lodash;
|
||||
|
||||
@@ -44,6 +46,7 @@ export type QuotedAttachmentForUIType = Pick<
|
||||
|
||||
export type Props = {
|
||||
authorTitle: string;
|
||||
authorLabel?: MemberLabelType;
|
||||
conversationColor: ConversationColorType;
|
||||
conversationTitle: string;
|
||||
customColor?: CustomColorType;
|
||||
@@ -157,6 +160,7 @@ export function Quote(props: Props): React.JSX.Element | null {
|
||||
text,
|
||||
bodyRanges,
|
||||
authorTitle,
|
||||
authorLabel,
|
||||
conversationTitle,
|
||||
isFromMe,
|
||||
i18n,
|
||||
@@ -471,7 +475,15 @@ export function Quote(props: Props): React.JSX.Element | null {
|
||||
{title} · {i18n('icu:Quote__story')}
|
||||
</>
|
||||
) : (
|
||||
title
|
||||
<>
|
||||
{title}
|
||||
{authorLabel && !isFromMe ? (
|
||||
<>
|
||||
{' '}
|
||||
<GroupMemberLabel context="quote" contactLabel={authorLabel} />
|
||||
</>
|
||||
) : undefined}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -479,7 +491,8 @@ export function Quote(props: Props): React.JSX.Element | null {
|
||||
dir="auto"
|
||||
className={classNames(
|
||||
getClassName('__primary__author'),
|
||||
isIncoming ? getClassName('__primary__author--incoming') : null
|
||||
isIncoming ? getClassName('__primary__author--incoming') : null,
|
||||
authorLabel ? getClassName('__primary__author--with-label') : null
|
||||
)}
|
||||
>
|
||||
{author}
|
||||
|
||||
@@ -125,7 +125,7 @@ export function GroupMemberLabelEditor({
|
||||
setLabelEmoji(undefined);
|
||||
}
|
||||
|
||||
// Remove trailing/leading whitespace, replace all whitespace with basic space
|
||||
// Replace all whitespace with basic space
|
||||
setLabelString(value.replace(/\s/g, ' '));
|
||||
}}
|
||||
ref={undefined}
|
||||
|
||||
@@ -728,6 +728,16 @@ export const getPropsForQuote = (
|
||||
|
||||
const firstAttachment = quote.attachments && quote.attachments[0];
|
||||
const conversation = getConversation(message, conversationSelector);
|
||||
const authorMembership = conversation.memberships?.find(
|
||||
membership => membership.aci === contact.serviceId
|
||||
);
|
||||
const authorLabel =
|
||||
authorMembership && authorMembership.labelString
|
||||
? {
|
||||
labelEmoji: authorMembership.labelEmoji,
|
||||
labelString: authorMembership.labelString,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const { conversationColor, customColor } = getConversationColorAttributes(
|
||||
conversation,
|
||||
@@ -736,6 +746,7 @@ export const getPropsForQuote = (
|
||||
|
||||
return {
|
||||
authorId,
|
||||
authorLabel,
|
||||
authorName,
|
||||
authorPhoneNumber,
|
||||
authorProfileName,
|
||||
|
||||
@@ -17,6 +17,11 @@ export const EMOJI_OUTGOING_BYTE_LIMIT = 48;
|
||||
export const SERVER_STRING_BYTE_LIMIT = 512;
|
||||
export const SERVER_EMOJI_BYTE_LIMIT = 64;
|
||||
|
||||
export type MemberLabelType = {
|
||||
labelString: string;
|
||||
labelEmoji: string | undefined;
|
||||
};
|
||||
|
||||
export function getCanAddLabel(
|
||||
conversation: ConversationType,
|
||||
membership: MembershipType | undefined
|
||||
|
||||
Reference in New Issue
Block a user