mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-07-05 04:45:31 +01:00
Prevent forward of at-mentions, don't render in 1:1 conversations
Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
@@ -33,7 +33,6 @@ import { LinkPreviewSourceType } from '../types/LinkPreview.std.ts';
|
||||
import { ToastType } from '../types/Toast.dom.tsx';
|
||||
import type { ShowToastAction } from '../state/ducks/toast.preload.ts';
|
||||
import type { HydratedBodyRangesType } from '../types/BodyRange.std.ts';
|
||||
import { applyRangesToText } from '../types/BodyRange.std.ts';
|
||||
import { UserText } from './UserText.dom.tsx';
|
||||
import { Modal } from './Modal.dom.tsx';
|
||||
import { SizeObserver } from '../hooks/useSizeObserver.dom.tsx';
|
||||
@@ -146,28 +145,7 @@ export function ForwardMessagesModal({
|
||||
const previews = lonelyLinkPreview?.domain ? [lonelyLinkPreview] : [];
|
||||
doForwardMessages(conversationIds, [{ ...lonelyDraft, previews }]);
|
||||
} else {
|
||||
doForwardMessages(
|
||||
conversationIds,
|
||||
drafts.map(draft => {
|
||||
// We don't keep @mention bodyRanges in multi-forward scenarios
|
||||
const result = applyRangesToText(
|
||||
{
|
||||
body: draft.messageBody ?? '',
|
||||
bodyRanges: draft.bodyRanges ?? [],
|
||||
},
|
||||
{
|
||||
replaceMentions: true,
|
||||
replaceSpoilers: false,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
...draft,
|
||||
messageBody: result.body,
|
||||
bodyRanges: result.bodyRanges,
|
||||
};
|
||||
})
|
||||
);
|
||||
doForwardMessages(conversationIds, drafts);
|
||||
}
|
||||
}, [
|
||||
drafts,
|
||||
|
||||
@@ -16,6 +16,7 @@ import type { LocalizerType } from '../types/Util.std.ts';
|
||||
import { getClassNamesFor } from '../util/getClassNamesFor.std.ts';
|
||||
import { useRefMerger } from '../hooks/useRefMerger.std.ts';
|
||||
import { byteLength } from '../Bytes.std.ts';
|
||||
import { truncateString } from '../util/truncateString.std.ts';
|
||||
|
||||
export type PropsType = {
|
||||
autoFocus?: boolean;
|
||||
@@ -181,17 +182,32 @@ export const Input = forwardRef<
|
||||
|
||||
const pastedText = event.clipboardData.getData('Text');
|
||||
|
||||
const pastedLength = countLength(pastedText);
|
||||
const newLengthCount =
|
||||
countLength(textBeforeSelection) +
|
||||
countLength(pastedText) +
|
||||
pastedLength +
|
||||
countLength(textAfterSelection);
|
||||
const pastedBytes = countBytes(pastedText);
|
||||
const newByteCount =
|
||||
countBytes(textBeforeSelection) +
|
||||
countBytes(pastedText) +
|
||||
pastedBytes +
|
||||
countBytes(textAfterSelection);
|
||||
|
||||
if (newLengthCount > maxLengthCount || newByteCount > maxByteCount) {
|
||||
const lengthDelta = newLengthCount - maxLengthCount;
|
||||
const byteDelta = newByteCount - maxByteCount;
|
||||
if (lengthDelta > 0 || byteDelta > 0) {
|
||||
event.preventDefault();
|
||||
|
||||
const newPastedLength = pastedLength - lengthDelta;
|
||||
const newPastedBytes = pastedBytes - byteDelta;
|
||||
|
||||
const truncatedPaste = truncateString(pastedText, {
|
||||
byteLimit: newPastedBytes,
|
||||
graphemeLimit: newPastedLength,
|
||||
});
|
||||
|
||||
inputEl.value =
|
||||
textBeforeSelection + truncatedPaste + textAfterSelection;
|
||||
}
|
||||
|
||||
maybeSetLarge();
|
||||
|
||||
@@ -2099,9 +2099,13 @@ function setMessageToEdit(
|
||||
: undefined;
|
||||
}
|
||||
|
||||
const draftBodyRanges = processBodyRanges(message, {
|
||||
conversationSelector: getConversationSelector(getState()),
|
||||
});
|
||||
const draftBodyRanges = processBodyRanges(
|
||||
message,
|
||||
isGroup(conversation.attributes),
|
||||
{
|
||||
conversationSelector: getConversationSelector(getState()),
|
||||
}
|
||||
);
|
||||
conversation.set({
|
||||
draftEditMessage: {
|
||||
body: message.body,
|
||||
|
||||
@@ -56,7 +56,10 @@ import type {
|
||||
|
||||
import type { EmbeddedContactForUIType } from '../../types/EmbeddedContact.std.ts';
|
||||
import { embeddedContactSelector } from '../../types/EmbeddedContact.std.ts';
|
||||
import type { HydratedBodyRangesType } from '../../types/BodyRange.std.ts';
|
||||
import {
|
||||
BodyRange,
|
||||
type HydratedBodyRangesType,
|
||||
} from '../../types/BodyRange.std.ts';
|
||||
import { hydrateRanges } from '../../util/BodyRange.node.ts';
|
||||
import type { AssertProps, LocalizerType } from '../../types/Util.std.ts';
|
||||
import type { LinkPreviewForUIType } from '../../types/message/LinkPreviews.std.ts';
|
||||
@@ -356,13 +359,20 @@ export const getAttachmentsForMessage = (
|
||||
|
||||
export const processBodyRanges = (
|
||||
{ bodyRanges }: Pick<MessageWithUIFieldsType, 'bodyRanges'>,
|
||||
isGroup: boolean,
|
||||
options: { conversationSelector: GetConversationByIdType }
|
||||
): HydratedBodyRangesType | undefined => {
|
||||
if (!bodyRanges) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return hydrateRanges(bodyRanges, options.conversationSelector)?.sort(
|
||||
let toHydrate = bodyRanges;
|
||||
|
||||
if (!isGroup) {
|
||||
toHydrate = toHydrate.filter(range => !BodyRange.isMention(range));
|
||||
}
|
||||
|
||||
return hydrateRanges(toHydrate, options.conversationSelector)?.sort(
|
||||
(a, b) => b.start - a.start
|
||||
);
|
||||
};
|
||||
@@ -702,10 +712,12 @@ export const getPropsForQuote = (
|
||||
conversationSelector,
|
||||
ourConversationId,
|
||||
defaultConversationColor,
|
||||
isGroup,
|
||||
}: {
|
||||
conversationSelector: GetConversationByIdType;
|
||||
ourConversationId?: string;
|
||||
defaultConversationColor: DefaultConversationColorType;
|
||||
isGroup: boolean;
|
||||
}
|
||||
): PropsData['quote'] => {
|
||||
const { quote } = message;
|
||||
@@ -758,7 +770,7 @@ export const getPropsForQuote = (
|
||||
authorPhoneNumber,
|
||||
authorProfileName,
|
||||
authorTitle,
|
||||
bodyRanges: processBodyRanges(quote, { conversationSelector }),
|
||||
bodyRanges: processBodyRanges(quote, isGroup, { conversationSelector }),
|
||||
conversationColor,
|
||||
conversationTitle: conversation.title,
|
||||
customColor,
|
||||
@@ -869,11 +881,10 @@ export const getPropsForMessage = (
|
||||
item => item.wasTooBig
|
||||
);
|
||||
const attachments = getAttachmentsForMessage(message);
|
||||
const bodyRanges = processBodyRanges(message, options);
|
||||
const author = getAuthorForMessage(message, options);
|
||||
const previews = getPreviewsForMessage(message);
|
||||
const reactions = getReactionsForMessage(message, options);
|
||||
const quote = getPropsForQuote(message, options);
|
||||
|
||||
const storyReplyContext = getPropsForStoryReplyContext(message, options);
|
||||
const textAttachment = getTextAttachment(message);
|
||||
const payment = getPayment(message);
|
||||
@@ -901,6 +912,9 @@ export const getPropsForMessage = (
|
||||
|
||||
const conversation = getConversation(message, conversationSelector);
|
||||
const isGroup = conversation.type === 'group';
|
||||
const bodyRanges = processBodyRanges(message, isGroup, options);
|
||||
const quote = getPropsForQuote(message, { ...options, isGroup });
|
||||
|
||||
const isGroupTerminated = isGroup && conversation.terminated;
|
||||
const { sticker } = message;
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
|
||||
}) {
|
||||
const conversationSelector = useSelector(getConversationSelector);
|
||||
const conversation = conversationSelector(id);
|
||||
const isGroup = conversation.type === 'group';
|
||||
strictAssert(conversation, `Conversation id ${id} not found!`);
|
||||
|
||||
const i18n = useSelector(getIntl);
|
||||
@@ -189,6 +190,7 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
|
||||
conversationSelector,
|
||||
ourConversationId,
|
||||
defaultConversationColor,
|
||||
isGroup,
|
||||
})
|
||||
: undefined;
|
||||
}, [
|
||||
@@ -196,6 +198,7 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
|
||||
conversationSelector,
|
||||
ourConversationId,
|
||||
defaultConversationColor,
|
||||
isGroup,
|
||||
]);
|
||||
|
||||
const {
|
||||
|
||||
@@ -30,6 +30,7 @@ import type {
|
||||
MessageForwardDraft,
|
||||
} from '../../types/ForwardDraft.std.ts';
|
||||
import { getForwardMessagesProps } from '../selectors/globalModals.std.ts';
|
||||
import { applyRangesToText } from '../../types/BodyRange.std.ts';
|
||||
|
||||
const log = createLogger('ForwardMessagesModal');
|
||||
|
||||
@@ -76,7 +77,25 @@ function SmartForwardMessagesModalInner({
|
||||
|
||||
const [drafts, setDrafts] = useState<ReadonlyArray<MessageForwardDraft>>(
|
||||
() => {
|
||||
return forwardMessagesProps.messageDrafts;
|
||||
return forwardMessagesProps.messageDrafts.map(draft => {
|
||||
// We don't keep @mention bodyRanges when forwarding, so we turn them to text
|
||||
const result = applyRangesToText(
|
||||
{
|
||||
body: draft.messageBody ?? '',
|
||||
bodyRanges: draft.bodyRanges ?? [],
|
||||
},
|
||||
{
|
||||
replaceMentions: true,
|
||||
replaceSpoilers: false,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
...draft,
|
||||
messageBody: result.body,
|
||||
bodyRanges: result.bodyRanges,
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user