Skip to content

Commit

Permalink
Support for receiving formatted messages
Browse files Browse the repository at this point in the history
Co-authored-by: Alvaro Carrasco <alvaro@signal.org>
  • Loading branch information
scottnonnenberg-signal and alvaro-signal committed Apr 10, 2023
1 parent d34d187 commit d9d820e
Show file tree
Hide file tree
Showing 72 changed files with 3,422 additions and 859 deletions.
4 changes: 4 additions & 0 deletions _locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2349,6 +2349,10 @@
"messageformat": "Select",
"description": "Shown on the drop-down menu for an individual message, opens the conversation in select mode with the current message selected"
},
"icu:MessageTextRenderer--spoiler--label": {
"messageformat": "Spoiler",
"description": "Used as a label for screenreaders on 'spoiler' text, which is hidden by default"
},
"retrySend": {
"message": "Retry Send",
"description": "(deleted 03/29/2023) Shown on the drop-down menu for an individual message, but only if it is an outgoing message that failed to send"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@
"ts-loader": "4.1.0",
"ts-node": "8.3.0",
"typed-scss-modules": "4.1.1",
"typescript": "5.0.2",
"typescript": "4.9.5",
"webpack": "5.76.0",
"webpack-cli": "4.9.2",
"webpack-dev-server": "4.11.1"
Expand Down
21 changes: 16 additions & 5 deletions protos/SignalService.proto
Original file line number Diff line number Diff line change
Expand Up @@ -307,12 +307,22 @@ message DataMessage {
}

message BodyRange {
optional uint32 start = 1;
enum Style {
NONE = 0;
BOLD = 1;
ITALIC = 2;
SPOILER = 3;
STRIKETHROUGH = 4;
MONOSPACE = 5;
}

optional uint32 start = 1;
optional uint32 length = 2;

// oneof associatedValue {
optional string mentionUuid = 3;
//}

oneof associatedValue {
string mentionUuid = 3;
Style style = 4;
}
}

message GroupCallUpdate {
Expand Down Expand Up @@ -399,6 +409,7 @@ message StoryMessage {
TextAttachment textAttachment = 4;
}
optional bool allowsReplies = 5;
repeated BodyRange bodyRanges = 6;
}

message TextAttachment {
Expand Down
4 changes: 4 additions & 0 deletions stylesheets/_emoji.scss
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ img.emoji.max {
height: 56px;
}

img.emoji--invisible {
visibility: hidden;
}

// we need these, or we'll make conversation items too big in the left-nav
.conversations img.emoji.small {
width: 1em;
Expand Down
4 changes: 4 additions & 0 deletions stylesheets/components/MessageBody.scss
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
&--outgoing {
background-color: $color-black-alpha-40;
}

&--invisible {
visibility: hidden;
}
}

&__author {
Expand Down
112 changes: 112 additions & 0 deletions stylesheets/components/MessageTextRenderer.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

.MessageTextRenderer {
&__formatting {
&--bold {
font-weight: 600;
}
&--italic {
font-style: italic;
}
&--monospace {
font-family: monospace;
}
&--strikethrough {
text-decoration: line-through;
bdi {
text-decoration: line-through;
}
}
&--none {
text-decoration: none;
font-weight: 400;
bdi {
text-decoration: none;
}
}

// Note: only used in the left pane for search results, not in message bubbles
&--keywordHighlight {
font-weight: 600;

// To differentiate it from bold formatting, we increase the color contrast
@include light-theme {
color: $color-black; // vs color-gray-60 normally
}
@include dark-theme {
color: $color-white; // vs color-gray-25 normally
}
}

// Note: Spoiler must be last to override any other formatting applied to the section
&--spoiler {
user-select: none;
cursor: pointer;

// make child text invisible
color: transparent;

// fix outline
outline: none;

@include keyboard-mode {
&:focus {
box-shadow: 0 0 0px 1px $color-ultramarine;
}
}
}

&--spoiler--noninteractive {
cursor: default;
box-shadow: none;
}

// The simplest; always in dark mode
&--spoiler-StoryViewer {
background-color: $color-white;
}

// The left pane
&--spoiler-ConversationList,
&--spoiler-SearchResult {
@include light-theme {
background-color: $color-gray-60;
}
@include dark-theme {
background-color: $color-gray-25;
}
}

// The timeline
&--spoiler-Quote {
@include light-theme {
background-color: $color-gray-90;
}
@include dark-theme {
background-color: $color-gray-05;
}
}

&--spoiler-Timeline--incoming {
@include light-theme {
background-color: $color-gray-90;
}
@include dark-theme {
background-color: $color-gray-05;
}
}
&--spoiler-Timeline--outgoing {
@include light-theme {
background-color: rgba(255, 255, 255, 0.9);
}
@include dark-theme {
background-color: rgba(255, 255, 255, 0.9);
}
}

&--invisible {
visibility: hidden;
}
}
}
1 change: 1 addition & 0 deletions stylesheets/manifest.scss
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
@import './components/MediaQualitySelector.scss';
@import './components/MessageAudio.scss';
@import './components/MessageBody.scss';
@import './components/MessageTextRenderer.scss';
@import './components/MessageDetail.scss';
@import './components/MiniPlayer.scss';
@import './components/Modal.scss';
Expand Down
2 changes: 1 addition & 1 deletion ts/components/AnnouncementsOnlyGroupBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function AnnouncementsOnlyGroupBanner({
{groupAdmins.map(admin => (
<ConversationListItem
{...admin}
draftPreview=""
draftPreview={undefined}
i18n={i18n}
lastMessage={undefined}
lastUpdated={undefined}
Expand Down
17 changes: 9 additions & 8 deletions ts/components/CompositionArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { get } from 'lodash';
import classNames from 'classnames';
import type {
DraftBodyRangesType,
LocalizerType,
ThemeType,
} from '../types/Util';
import type { DraftBodyRangeMention } from '../types/BodyRange';
import type { LocalizerType, ThemeType } from '../types/Util';
import type { ErrorDialogAudioRecorderType } from '../types/AudioRecorder';
import { RecordingState } from '../types/AudioRecorder';
import type { imageToBlurHash } from '../util/imageToBlurHash';
Expand Down Expand Up @@ -123,7 +120,7 @@ export type OwnProps = Readonly<{
conversationId: string,
options: {
draftAttachments?: ReadonlyArray<AttachmentDraftType>;
mentions?: DraftBodyRangesType;
mentions?: ReadonlyArray<DraftBodyRangeMention>;
message?: string;
timestamp?: number;
voiceNoteAttachment?: InMemoryAttachmentDraftType;
Expand Down Expand Up @@ -233,8 +230,8 @@ export function CompositionArea({
shouldSendHighQualityAttachments,
// CompositionInput
clearQuotedMessage,
draftText,
draftBodyRanges,
draftText,
getPreferredBadge,
getQuotedMessage,
onEditorStateChange,
Expand Down Expand Up @@ -311,7 +308,11 @@ export function CompositionArea({
}, [inputApiRef, setLarge]);

const handleSubmit = useCallback(
(message: string, mentions: DraftBodyRangesType, timestamp: number) => {
(
message: string,
mentions: ReadonlyArray<DraftBodyRangeMention>,
timestamp: number
) => {
emojiButtonRef.current?.close();
sendMultiMediaMessage(conversationId, {
draftAttachments,
Expand Down
28 changes: 14 additions & 14 deletions ts/components/CompositionInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,8 @@ import { MentionCompletion } from '../quill/mentions/completion';
import { EmojiBlot, EmojiCompletion } from '../quill/emoji';
import type { EmojiPickDataType } from './emoji/EmojiPicker';
import { convertShortName } from './emoji/lib';
import type {
LocalizerType,
DraftBodyRangesType,
ThemeType,
} from '../types/Util';
import type { DraftBodyRangeMention } from '../types/BodyRange';
import type { LocalizerType, ThemeType } from '../types/Util';
import type { ConversationType } from '../state/ducks/conversations';
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
import { isValidUuid } from '../types/UUID';
Expand Down Expand Up @@ -64,7 +61,7 @@ export type InputApi = {
insertEmoji: (e: EmojiPickDataType) => void;
setContents: (
text: string,
draftBodyRanges?: DraftBodyRangesType,
draftBodyRanges?: ReadonlyArray<DraftBodyRangeMention>,
cursorToEnd?: boolean
) => void;
reset: () => void;
Expand All @@ -82,15 +79,15 @@ export type Props = Readonly<{
sendCounter: number;
skinTone?: EmojiPickDataType['skinTone'];
draftText?: string;
draftBodyRanges?: DraftBodyRangesType;
draftBodyRanges?: ReadonlyArray<DraftBodyRangeMention>;
moduleClassName?: string;
theme: ThemeType;
placeholder?: string;
sortedGroupMembers?: ReadonlyArray<ConversationType>;
scrollerRef?: React.RefObject<HTMLDivElement>;
onDirtyChange?(dirty: boolean): unknown;
onEditorStateChange?(options: {
bodyRanges: DraftBodyRangesType;
bodyRanges: ReadonlyArray<DraftBodyRangeMention>;
caretLocation?: number;
conversationId: string | undefined;
messageText: string;
Expand All @@ -100,7 +97,7 @@ export type Props = Readonly<{
onPickEmoji(o: EmojiPickDataType): unknown;
onSubmit(
message: string,
mentions: DraftBodyRangesType,
mentions: ReadonlyArray<DraftBodyRangeMention>,
timestamp: number
): unknown;
onScroll?: (ev: React.UIEvent<HTMLElement>) => void;
Expand Down Expand Up @@ -164,16 +161,19 @@ export function CompositionInput(props: Props): React.ReactElement {

const generateDelta = (
text: string,
bodyRanges: DraftBodyRangesType
mentions: ReadonlyArray<DraftBodyRangeMention>
): Delta => {
const initialOps = [{ insert: text }];
const opsWithMentions = insertMentionOps(initialOps, bodyRanges);
const opsWithMentions = insertMentionOps(initialOps, mentions);
const opsWithEmojis = insertEmojiOps(opsWithMentions);

return new Delta(opsWithEmojis);
};

const getTextAndMentions = (): [string, DraftBodyRangesType] => {
const getTextAndMentions = (): [
string,
ReadonlyArray<DraftBodyRangeMention>
] => {
const quill = quillRef.current;

if (quill === undefined) {
Expand Down Expand Up @@ -251,7 +251,7 @@ export function CompositionInput(props: Props): React.ReactElement {

const setContents = (
text: string,
bodyRanges?: DraftBodyRangesType,
mentions?: ReadonlyArray<DraftBodyRangeMention>,
cursorToEnd?: boolean
) => {
const quill = quillRef.current;
Expand All @@ -260,7 +260,7 @@ export function CompositionInput(props: Props): React.ReactElement {
return;
}

const delta = generateDelta(text || '', bodyRanges || []);
const delta = generateDelta(text || '', mentions || []);

canSendRef.current = true;
// We need to cast here because we use @types/quill@1.3.10 which has types
Expand Down
7 changes: 4 additions & 3 deletions ts/components/CompositionTextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { shouldNeverBeCalled } from '../util/shouldNeverBeCalled';
import type { InputApi } from './CompositionInput';
import { CompositionInput } from './CompositionInput';
import { EmojiButton } from './emoji/EmojiButton';
import type { DraftBodyRangesType, ThemeType } from '../types/Util';
import type { DraftBodyRangeMention } from '../types/BodyRange';
import type { ThemeType } from '../types/Util';
import type { Props as EmojiButtonProps } from './emoji/EmojiButton';
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
import * as grapheme from '../util/grapheme';
Expand All @@ -24,13 +25,13 @@ export type CompositionTextAreaProps = {
onPickEmoji: (e: EmojiPickDataType) => void;
onChange: (
messageText: string,
bodyRanges: DraftBodyRangesType,
draftBodyRanges: ReadonlyArray<DraftBodyRangeMention>,
caretLocation?: number | undefined
) => void;
onSetSkinTone: (tone: number) => void;
onSubmit: (
message: string,
mentions: DraftBodyRangesType,
draftBodyRanges: ReadonlyArray<DraftBodyRangeMention>,
timestamp: number
) => void;
onTextTooLong: () => void;
Expand Down
6 changes: 5 additions & 1 deletion ts/components/ConversationList.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,11 @@ ConversationTypingStatus.story = {
export const ConversationWithDraft = (): JSX.Element =>
renderConversation({
shouldShowDraft: true,
draftPreview: "I'm in the middle of typing this...",
draftPreview: {
text: "I'm in the middle of typing this...",
prefix: '🎤',
bodyRanges: [],
},
});

ConversationWithDraft.story = {
Expand Down
Loading

0 comments on commit d9d820e

Please sign in to comment.