Add emoji button to text story creation
This commit is contained in:
parent
d6d53f9d18
commit
77f92b6cc3
7 changed files with 285 additions and 213 deletions
|
@ -316,4 +316,10 @@
|
|||
width: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
&__emoji-button,
|
||||
&__emoji-button::after {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,8 +55,13 @@ export default {
|
|||
onDistributionListCreated: { action: true },
|
||||
onHideMyStoriesFrom: { action: true },
|
||||
onSend: { action: true },
|
||||
onSetSkinTone: { action: true },
|
||||
onUseEmoji: { action: true },
|
||||
onViewersUpdated: { action: true },
|
||||
processAttachment: { action: true },
|
||||
recentEmojis: {
|
||||
defaultValue: [],
|
||||
},
|
||||
recentStickers: {
|
||||
defaultValue: [],
|
||||
},
|
||||
|
@ -65,6 +70,9 @@ export default {
|
|||
signalConnections: {
|
||||
defaultValue: Array.from(Array(42), getDefaultConversation),
|
||||
},
|
||||
skinTone: {
|
||||
defaultValue: 0,
|
||||
},
|
||||
toggleSignalConnectionsModal: { action: true },
|
||||
},
|
||||
} as Meta;
|
||||
|
|
|
@ -15,6 +15,7 @@ import type { Props as StickerButtonProps } from './stickers/StickerButton';
|
|||
import type { PropsType as SendStoryModalPropsType } from './SendStoryModal';
|
||||
import type { UUIDStringType } from '../types/UUID';
|
||||
import type { imageToBlurHash } from '../util/imageToBlurHash';
|
||||
import type { PropsType as TextStoryCreatorPropsType } from './TextStoryCreator';
|
||||
|
||||
import { TEXT_ATTACHMENT } from '../types/MIME';
|
||||
import { isVideoAttachment } from '../types/Attachment';
|
||||
|
@ -70,6 +71,10 @@ export type PropsType = {
|
|||
| 'toggleGroupsForStorySend'
|
||||
| 'mostRecentActiveStoryTimestampByGroupOrDistributionList'
|
||||
| 'toggleSignalConnectionsModal'
|
||||
> &
|
||||
Pick<
|
||||
TextStoryCreatorPropsType,
|
||||
'onUseEmoji' | 'skinTone' | 'onSetSkinTone' | 'recentEmojis'
|
||||
>;
|
||||
|
||||
export function StoryCreator({
|
||||
|
@ -87,7 +92,7 @@ export function StoryCreator({
|
|||
isSending,
|
||||
linkPreview,
|
||||
me,
|
||||
ourConversationId,
|
||||
mostRecentActiveStoryTimestampByGroupOrDistributionList,
|
||||
onClose,
|
||||
onDeleteList,
|
||||
onDistributionListCreated,
|
||||
|
@ -96,15 +101,19 @@ export function StoryCreator({
|
|||
onRepliesNReactionsChanged,
|
||||
onSelectedStoryList,
|
||||
onSend,
|
||||
onSetSkinTone,
|
||||
onUseEmoji,
|
||||
onViewersUpdated,
|
||||
ourConversationId,
|
||||
processAttachment,
|
||||
recentEmojis,
|
||||
recentStickers,
|
||||
renderCompositionTextArea,
|
||||
sendStoryModalOpenStateChanged,
|
||||
setMyStoriesToAllSignalConnections,
|
||||
signalConnections,
|
||||
skinTone,
|
||||
toggleGroupsForStorySend,
|
||||
mostRecentActiveStoryTimestampByGroupOrDistributionList,
|
||||
toggleSignalConnectionsModal,
|
||||
}: PropsType): JSX.Element {
|
||||
const [draftAttachment, setDraftAttachment] = useState<
|
||||
|
@ -236,6 +245,10 @@ export function StoryCreator({
|
|||
});
|
||||
setIsReadyToSend(true);
|
||||
}}
|
||||
onUseEmoji={onUseEmoji}
|
||||
onSetSkinTone={onSetSkinTone}
|
||||
recentEmojis={recentEmojis}
|
||||
skinTone={skinTone}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import Measure from 'react-measure';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { forwardRef, useEffect, useRef, useState } from 'react';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import classNames from 'classnames';
|
||||
|
||||
|
@ -21,6 +21,7 @@ import {
|
|||
getBackgroundColor,
|
||||
} from '../util/getStoryBackground';
|
||||
import { SECOND } from '../util/durations';
|
||||
import { useRefMerger } from '../hooks/useRefMerger';
|
||||
|
||||
const renderNewLines: RenderTextCallbackType = ({
|
||||
text: textWithNewLines,
|
||||
|
@ -105,207 +106,214 @@ function getTextStyles(
|
|||
};
|
||||
}
|
||||
|
||||
export function TextAttachment({
|
||||
disableLinkPreviewPopup,
|
||||
i18n,
|
||||
isEditingText,
|
||||
isThumbnail,
|
||||
onChange,
|
||||
onClick,
|
||||
onRemoveLinkPreview,
|
||||
textAttachment,
|
||||
}: PropsType): JSX.Element | null {
|
||||
const linkPreview = useRef<HTMLDivElement | null>(null);
|
||||
const [linkPreviewOffsetTop, setLinkPreviewOffsetTop] = useState<
|
||||
number | undefined
|
||||
>();
|
||||
export const TextAttachment = forwardRef<HTMLTextAreaElement, PropsType>(
|
||||
function TextAttachmentForwarded(
|
||||
{
|
||||
disableLinkPreviewPopup,
|
||||
i18n,
|
||||
isEditingText,
|
||||
isThumbnail,
|
||||
onChange,
|
||||
onClick,
|
||||
onRemoveLinkPreview,
|
||||
textAttachment,
|
||||
},
|
||||
forwardedTextEditorRef
|
||||
): JSX.Element | null {
|
||||
const linkPreview = useRef<HTMLDivElement | null>(null);
|
||||
const [linkPreviewOffsetTop, setLinkPreviewOffsetTop] = useState<
|
||||
number | undefined
|
||||
>();
|
||||
|
||||
const textContent = textAttachment.text || '';
|
||||
const textContent = textAttachment.text || '';
|
||||
const textEditorRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const refMerger = useRefMerger();
|
||||
|
||||
const textEditorRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const node = textEditorRef.current;
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.focus();
|
||||
node.setSelectionRange(node.value.length, node.value.length);
|
||||
}, [isEditingText]);
|
||||
|
||||
useEffect(() => {
|
||||
setLinkPreviewOffsetTop(undefined);
|
||||
}, [textAttachment.preview?.url]);
|
||||
|
||||
const [isHoveringOverTooltip, setIsHoveringOverTooltip] = useState(false);
|
||||
|
||||
function showTooltip() {
|
||||
if (disableLinkPreviewPopup) {
|
||||
return;
|
||||
}
|
||||
setIsHoveringOverTooltip(true);
|
||||
setLinkPreviewOffsetTop(linkPreview?.current?.offsetTop);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
if (!isHoveringOverTooltip) {
|
||||
setLinkPreviewOffsetTop(undefined);
|
||||
useEffect(() => {
|
||||
const node = textEditorRef?.current;
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
}, 5 * SECOND);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
node.focus();
|
||||
node.setSelectionRange(node.value.length, node.value.length);
|
||||
}, [isEditingText]);
|
||||
|
||||
useEffect(() => {
|
||||
setLinkPreviewOffsetTop(undefined);
|
||||
}, [textAttachment.preview?.url]);
|
||||
|
||||
const [isHoveringOverTooltip, setIsHoveringOverTooltip] = useState(false);
|
||||
|
||||
function showTooltip() {
|
||||
if (disableLinkPreviewPopup) {
|
||||
return;
|
||||
}
|
||||
setIsHoveringOverTooltip(true);
|
||||
setLinkPreviewOffsetTop(linkPreview?.current?.offsetTop);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
if (!isHoveringOverTooltip) {
|
||||
setLinkPreviewOffsetTop(undefined);
|
||||
}
|
||||
}, 5 * SECOND);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [isHoveringOverTooltip]);
|
||||
|
||||
const storyBackgroundColor = {
|
||||
background: getBackgroundColor(textAttachment),
|
||||
};
|
||||
}, [isHoveringOverTooltip]);
|
||||
|
||||
const storyBackgroundColor = {
|
||||
background: getBackgroundColor(textAttachment),
|
||||
};
|
||||
return (
|
||||
<Measure bounds>
|
||||
{({ contentRect, measureRef }) => {
|
||||
const scaleFactor = (contentRect.bounds?.height || 1) / 1280;
|
||||
|
||||
return (
|
||||
<Measure bounds>
|
||||
{({ contentRect, measureRef }) => {
|
||||
const scaleFactor = (contentRect.bounds?.height || 1) / 1280;
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||
<div
|
||||
className="TextAttachment"
|
||||
onClick={() => {
|
||||
if (linkPreviewOffsetTop) {
|
||||
setLinkPreviewOffsetTop(undefined);
|
||||
}
|
||||
onClick?.();
|
||||
}}
|
||||
onKeyUp={ev => {
|
||||
if (ev.key === 'Escape' && linkPreviewOffsetTop) {
|
||||
setLinkPreviewOffsetTop(undefined);
|
||||
}
|
||||
}}
|
||||
ref={measureRef}
|
||||
style={isThumbnail ? storyBackgroundColor : undefined}
|
||||
>
|
||||
{/*
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||
<div
|
||||
className="TextAttachment"
|
||||
onClick={() => {
|
||||
if (linkPreviewOffsetTop) {
|
||||
setLinkPreviewOffsetTop(undefined);
|
||||
}
|
||||
onClick?.();
|
||||
}}
|
||||
onKeyUp={ev => {
|
||||
if (ev.key === 'Escape' && linkPreviewOffsetTop) {
|
||||
setLinkPreviewOffsetTop(undefined);
|
||||
}
|
||||
}}
|
||||
ref={measureRef}
|
||||
style={isThumbnail ? storyBackgroundColor : undefined}
|
||||
>
|
||||
{/*
|
||||
The tooltip must be outside of the scaled area, as it should not scale with
|
||||
the story, but it must be positioned using the scaled offset
|
||||
*/}
|
||||
{textAttachment.preview &&
|
||||
textAttachment.preview.url &&
|
||||
linkPreviewOffsetTop &&
|
||||
!isThumbnail && (
|
||||
<a
|
||||
className="TextAttachment__preview__tooltip"
|
||||
href={textAttachment.preview.url}
|
||||
rel="noreferrer"
|
||||
style={{
|
||||
top: linkPreviewOffsetTop * scaleFactor - 89, // minus height of tooltip and some spacing
|
||||
}}
|
||||
target="_blank"
|
||||
>
|
||||
<div>
|
||||
<div className="TextAttachment__preview__tooltip__title">
|
||||
{i18n('TextAttachment__preview__link')}
|
||||
</div>
|
||||
<div className="TextAttachment__preview__tooltip__url">
|
||||
{textAttachment.preview.url}
|
||||
{textAttachment.preview &&
|
||||
textAttachment.preview.url &&
|
||||
linkPreviewOffsetTop &&
|
||||
!isThumbnail && (
|
||||
<a
|
||||
className="TextAttachment__preview__tooltip"
|
||||
href={textAttachment.preview.url}
|
||||
rel="noreferrer"
|
||||
style={{
|
||||
top: linkPreviewOffsetTop * scaleFactor - 89, // minus height of tooltip and some spacing
|
||||
}}
|
||||
target="_blank"
|
||||
>
|
||||
<div>
|
||||
<div className="TextAttachment__preview__tooltip__title">
|
||||
{i18n('TextAttachment__preview__link')}
|
||||
</div>
|
||||
<div className="TextAttachment__preview__tooltip__url">
|
||||
{textAttachment.preview.url}
|
||||
</div>
|
||||
</div>
|
||||
<div className="TextAttachment__preview__tooltip__arrow" />
|
||||
</a>
|
||||
)}
|
||||
<div
|
||||
className="TextAttachment__story"
|
||||
style={{
|
||||
...(isThumbnail ? {} : storyBackgroundColor),
|
||||
transform: `scale(${scaleFactor})`,
|
||||
}}
|
||||
>
|
||||
{(textContent || onChange) && (
|
||||
<div
|
||||
className={classNames('TextAttachment__text', {
|
||||
'TextAttachment__text--with-bg': Boolean(
|
||||
textAttachment.textBackgroundColor
|
||||
),
|
||||
})}
|
||||
style={{
|
||||
backgroundColor: textAttachment.textBackgroundColor
|
||||
? getHexFromNumber(textAttachment.textBackgroundColor)
|
||||
: 'transparent',
|
||||
}}
|
||||
>
|
||||
{onChange ? (
|
||||
<TextareaAutosize
|
||||
className="TextAttachment__text__container TextAttachment__text__textarea"
|
||||
disabled={!isEditingText}
|
||||
onChange={ev => onChange(ev.currentTarget.value)}
|
||||
placeholder={i18n('TextAttachment__placeholder')}
|
||||
ref={refMerger(forwardedTextEditorRef, textEditorRef)}
|
||||
style={getTextStyles(
|
||||
textContent,
|
||||
textAttachment.textForegroundColor,
|
||||
textAttachment.textStyle,
|
||||
i18n
|
||||
)}
|
||||
value={textContent}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="TextAttachment__text__container"
|
||||
style={getTextStyles(
|
||||
textContent,
|
||||
textAttachment.textForegroundColor,
|
||||
textAttachment.textStyle,
|
||||
i18n
|
||||
)}
|
||||
>
|
||||
<Emojify
|
||||
text={textContent}
|
||||
renderNonEmoji={renderNewLines}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="TextAttachment__preview__tooltip__arrow" />
|
||||
</a>
|
||||
)}
|
||||
<div
|
||||
className="TextAttachment__story"
|
||||
style={{
|
||||
...(isThumbnail ? {} : storyBackgroundColor),
|
||||
transform: `scale(${scaleFactor})`,
|
||||
}}
|
||||
>
|
||||
{(textContent || onChange) && (
|
||||
<div
|
||||
className={classNames('TextAttachment__text', {
|
||||
'TextAttachment__text--with-bg': Boolean(
|
||||
textAttachment.textBackgroundColor
|
||||
),
|
||||
})}
|
||||
style={{
|
||||
backgroundColor: textAttachment.textBackgroundColor
|
||||
? getHexFromNumber(textAttachment.textBackgroundColor)
|
||||
: 'transparent',
|
||||
}}
|
||||
>
|
||||
{onChange ? (
|
||||
<TextareaAutosize
|
||||
className="TextAttachment__text__container TextAttachment__text__textarea"
|
||||
disabled={!isEditingText}
|
||||
onChange={ev => onChange(ev.currentTarget.value)}
|
||||
placeholder={i18n('TextAttachment__placeholder')}
|
||||
ref={textEditorRef}
|
||||
style={getTextStyles(
|
||||
textContent,
|
||||
textAttachment.textForegroundColor,
|
||||
textAttachment.textStyle,
|
||||
i18n
|
||||
)}
|
||||
value={textContent}
|
||||
)}
|
||||
{textAttachment.preview && textAttachment.preview.url && (
|
||||
<div
|
||||
className={classNames('TextAttachment__preview-container', {
|
||||
'TextAttachment__preview-container--large': Boolean(
|
||||
textAttachment.preview.title
|
||||
),
|
||||
})}
|
||||
ref={linkPreview}
|
||||
onBlur={() => setIsHoveringOverTooltip(false)}
|
||||
onFocus={showTooltip}
|
||||
onMouseOut={() => setIsHoveringOverTooltip(false)}
|
||||
onMouseOver={showTooltip}
|
||||
>
|
||||
{onRemoveLinkPreview && (
|
||||
<div className="TextAttachment__preview__remove">
|
||||
<button
|
||||
aria-label={i18n(
|
||||
'Keyboard--remove-draft-link-preview'
|
||||
)}
|
||||
type="button"
|
||||
onClick={onRemoveLinkPreview}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<StoryLinkPreview
|
||||
{...textAttachment.preview}
|
||||
domain={getDomain(String(textAttachment.preview.url))}
|
||||
forceCompactMode={
|
||||
getTextSize(textContent) !== TextSize.Large
|
||||
}
|
||||
i18n={i18n}
|
||||
title={textAttachment.preview.title || undefined}
|
||||
url={textAttachment.preview.url}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="TextAttachment__text__container"
|
||||
style={getTextStyles(
|
||||
textContent,
|
||||
textAttachment.textForegroundColor,
|
||||
textAttachment.textStyle,
|
||||
i18n
|
||||
)}
|
||||
>
|
||||
<Emojify
|
||||
text={textContent}
|
||||
renderNonEmoji={renderNewLines}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{textAttachment.preview && textAttachment.preview.url && (
|
||||
<div
|
||||
className={classNames('TextAttachment__preview-container', {
|
||||
'TextAttachment__preview-container--large': Boolean(
|
||||
textAttachment.preview.title
|
||||
),
|
||||
})}
|
||||
ref={linkPreview}
|
||||
onBlur={() => setIsHoveringOverTooltip(false)}
|
||||
onFocus={showTooltip}
|
||||
onMouseOut={() => setIsHoveringOverTooltip(false)}
|
||||
onMouseOver={showTooltip}
|
||||
>
|
||||
{onRemoveLinkPreview && (
|
||||
<div className="TextAttachment__preview__remove">
|
||||
<button
|
||||
aria-label={i18n('Keyboard--remove-draft-link-preview')}
|
||||
type="button"
|
||||
onClick={onRemoveLinkPreview}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<StoryLinkPreview
|
||||
{...textAttachment.preview}
|
||||
domain={getDomain(String(textAttachment.preview.url))}
|
||||
forceCompactMode={
|
||||
getTextSize(textContent) !== TextSize.Large
|
||||
}
|
||||
i18n={i18n}
|
||||
title={textAttachment.preview.title || undefined}
|
||||
url={textAttachment.preview.url}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Measure>
|
||||
);
|
||||
}
|
||||
);
|
||||
}}
|
||||
</Measure>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -7,12 +7,15 @@ import classNames from 'classnames';
|
|||
import { get, has, noop } from 'lodash';
|
||||
import { usePopper } from 'react-popper';
|
||||
|
||||
import type { EmojiPickDataType } from './emoji/EmojiPicker';
|
||||
import type { LinkPreviewType } from '../types/message/LinkPreviews';
|
||||
import type { LocalizerType } from '../types/Util';
|
||||
import type { Props as EmojiButtonPropsType } from './emoji/EmojiButton';
|
||||
import type { TextAttachmentType } from '../types/Attachment';
|
||||
|
||||
import { Button, ButtonVariant } from './Button';
|
||||
import { ContextMenu } from './ContextMenu';
|
||||
import { EmojiButton } from './emoji/EmojiButton';
|
||||
import { LinkPreviewSourceType, findLinks } from '../types/LinkPreview';
|
||||
import type { MaybeGrabLinkPreviewOptionsType } from '../types/LinkPreview';
|
||||
import { Input } from './Input';
|
||||
|
@ -26,6 +29,7 @@ import {
|
|||
COLOR_WHITE_INT,
|
||||
getBackgroundColor,
|
||||
} from '../util/getStoryBackground';
|
||||
import { convertShortName } from './emoji/lib';
|
||||
import { objectMap } from '../util/objectMap';
|
||||
import { handleOutsideClick } from '../util/handleOutsideClick';
|
||||
import { ConfirmDiscardDialog } from './ConfirmDiscardDialog';
|
||||
|
@ -42,7 +46,8 @@ export type PropsType = {
|
|||
linkPreview?: LinkPreviewType;
|
||||
onClose: () => unknown;
|
||||
onDone: (textAttachment: TextAttachmentType) => unknown;
|
||||
};
|
||||
onUseEmoji: (_: EmojiPickDataType) => unknown;
|
||||
} & Pick<EmojiButtonPropsType, 'onSetSkinTone' | 'recentEmojis' | 'skinTone'>;
|
||||
|
||||
enum LinkPreviewApplied {
|
||||
None = 'None',
|
||||
|
@ -128,6 +133,10 @@ export function TextStoryCreator({
|
|||
linkPreview,
|
||||
onClose,
|
||||
onDone,
|
||||
onSetSkinTone,
|
||||
onUseEmoji,
|
||||
recentEmojis,
|
||||
skinTone,
|
||||
}: PropsType): JSX.Element {
|
||||
const [showConfirmDiscardModal, setShowConfirmDiscardModal] = useState(false);
|
||||
|
||||
|
@ -145,16 +154,6 @@ export function TextStoryCreator({
|
|||
const [sliderValue, setSliderValue] = useState<number>(100);
|
||||
const [text, setText] = useState<string>('');
|
||||
|
||||
const textEditorRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditingText) {
|
||||
textEditorRef.current?.focus();
|
||||
} else {
|
||||
textEditorRef.current?.blur();
|
||||
}
|
||||
}, [isEditingText]);
|
||||
|
||||
const [isColorPickerShowing, setIsColorPickerShowing] = useState(false);
|
||||
const [colorPickerPopperButtonRef, setColorPickerPopperButtonRef] =
|
||||
useState<HTMLButtonElement | null>(null);
|
||||
|
@ -328,6 +327,8 @@ export function TextStoryCreator({
|
|||
|
||||
const hasChanges = Boolean(text || hasLinkPreviewApplied);
|
||||
|
||||
const textEditorRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
return (
|
||||
<FocusTrap focusTrapOptions={{ allowOutsideClick: true }}>
|
||||
<div className="StoryCreator">
|
||||
|
@ -345,6 +346,7 @@ export function TextStoryCreator({
|
|||
onRemoveLinkPreview={() => {
|
||||
setLinkPreviewApplied(LinkPreviewApplied.None);
|
||||
}}
|
||||
ref={textEditorRef}
|
||||
textAttachment={textAttachment}
|
||||
/>
|
||||
</div>
|
||||
|
@ -428,6 +430,26 @@ export function TextStoryCreator({
|
|||
}}
|
||||
type="button"
|
||||
/>
|
||||
<EmojiButton
|
||||
className="StoryCreator__emoji-button"
|
||||
i18n={i18n}
|
||||
onPickEmoji={data => {
|
||||
onUseEmoji(data);
|
||||
const emoji = convertShortName(data.shortName, data.skinTone);
|
||||
const insertAt =
|
||||
textEditorRef.current?.selectionEnd ?? text.length;
|
||||
setText(
|
||||
originalText =>
|
||||
`${originalText.substr(
|
||||
0,
|
||||
insertAt
|
||||
)}${emoji}${originalText.substr(insertAt, text.length)}`
|
||||
);
|
||||
}}
|
||||
recentEmojis={recentEmojis}
|
||||
skinTone={skinTone}
|
||||
onSetSkinTone={onSetSkinTone}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="StoryCreator__toolbar--space" />
|
||||
|
|
|
@ -7,6 +7,7 @@ import { useSelector } from 'react-redux';
|
|||
import type { LocalizerType } from '../../types/Util';
|
||||
import type { StateType } from '../reducer';
|
||||
import { LinkPreviewSourceType } from '../../types/LinkPreview';
|
||||
import { SmartCompositionTextArea } from './CompositionTextArea';
|
||||
import { StoryCreator } from '../../components/StoryCreator';
|
||||
import {
|
||||
getAllSignalConnections,
|
||||
|
@ -22,18 +23,23 @@ import {
|
|||
getInstalledStickerPacks,
|
||||
getRecentStickers,
|
||||
} from '../selectors/stickers';
|
||||
import { getHasSetMyStoriesPrivacy } from '../selectors/items';
|
||||
import { getAddStoryData } from '../selectors/stories';
|
||||
import {
|
||||
getEmojiSkinTone,
|
||||
getHasSetMyStoriesPrivacy,
|
||||
} from '../selectors/items';
|
||||
import { getLinkPreview } from '../selectors/linkPreviews';
|
||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||
import { processAttachment } from '../../util/processAttachment';
|
||||
import { imageToBlurHash } from '../../util/imageToBlurHash';
|
||||
import { processAttachment } from '../../util/processAttachment';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
import { useActions as useEmojisActions } from '../ducks/emojis';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
import { useActions as useItemsActions } from '../ducks/items';
|
||||
import { useLinkPreviewActions } from '../ducks/linkPreviews';
|
||||
import { useRecentEmojis } from '../selectors/emojis';
|
||||
import { useStoriesActions } from '../ducks/stories';
|
||||
import { useStoryDistributionListsActions } from '../ducks/storyDistributionLists';
|
||||
import { SmartCompositionTextArea } from './CompositionTextArea';
|
||||
import { getAddStoryData } from '../selectors/stories';
|
||||
|
||||
export type PropsType = {
|
||||
file?: File;
|
||||
|
@ -81,6 +87,11 @@ export function SmartStoryCreator(): JSX.Element | null {
|
|||
const file = addStoryData?.type === 'Media' ? addStoryData.file : undefined;
|
||||
const isSending = addStoryData?.sending || false;
|
||||
|
||||
const recentEmojis = useRecentEmojis();
|
||||
const skinTone = useSelector<StateType, number>(getEmojiSkinTone);
|
||||
const { onSetSkinTone } = useItemsActions();
|
||||
const { onUseEmoji } = useEmojisActions();
|
||||
|
||||
return (
|
||||
<StoryCreator
|
||||
candidateConversations={candidateConversations}
|
||||
|
@ -97,7 +108,9 @@ export function SmartStoryCreator(): JSX.Element | null {
|
|||
isSending={isSending}
|
||||
linkPreview={linkPreviewForSource(LinkPreviewSourceType.StoryCreator)}
|
||||
me={me}
|
||||
ourConversationId={ourConversationId}
|
||||
mostRecentActiveStoryTimestampByGroupOrDistributionList={
|
||||
mostRecentActiveStoryTimestampByGroupOrDistributionList
|
||||
}
|
||||
onClose={() => setAddStoryData(undefined)}
|
||||
onDeleteList={deleteDistributionList}
|
||||
onDistributionListCreated={createDistributionList}
|
||||
|
@ -106,17 +119,19 @@ export function SmartStoryCreator(): JSX.Element | null {
|
|||
onRepliesNReactionsChanged={allowsRepliesChanged}
|
||||
onSelectedStoryList={verifyStoryListMembers}
|
||||
onSend={sendStoryMessage}
|
||||
onSetSkinTone={onSetSkinTone}
|
||||
onUseEmoji={onUseEmoji}
|
||||
onViewersUpdated={updateStoryViewers}
|
||||
ourConversationId={ourConversationId}
|
||||
processAttachment={processAttachment}
|
||||
recentEmojis={recentEmojis}
|
||||
recentStickers={recentStickers}
|
||||
renderCompositionTextArea={SmartCompositionTextArea}
|
||||
sendStoryModalOpenStateChanged={sendStoryModalOpenStateChanged}
|
||||
setMyStoriesToAllSignalConnections={setMyStoriesToAllSignalConnections}
|
||||
signalConnections={signalConnections}
|
||||
skinTone={skinTone}
|
||||
toggleGroupsForStorySend={toggleGroupsForStorySend}
|
||||
mostRecentActiveStoryTimestampByGroupOrDistributionList={
|
||||
mostRecentActiveStoryTimestampByGroupOrDistributionList
|
||||
}
|
||||
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -9093,7 +9093,7 @@
|
|||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/components/TextStoryCreator.tsx",
|
||||
"line": " const textEditorRef = useRef<HTMLInputElement | null>(null);",
|
||||
"line": " const textEditorRef = useRef<HTMLTextAreaElement | null>(null);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2022-06-16T23:23:32.306Z"
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue