Create text stories

This commit is contained in:
Josh Perez 2022-06-16 20:48:57 -04:00 committed by GitHub
parent 973b2264fe
commit d970d427f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 2433 additions and 1106 deletions

View file

@ -2,18 +2,21 @@
// SPDX-License-Identifier: AGPL-3.0-only
import Measure from 'react-measure';
import React, { useRef, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import classNames from 'classnames';
import type { LocalizerType, RenderTextCallbackType } from '../types/Util';
import type { TextAttachmentType } from '../types/Attachment';
import { AddNewLines } from './conversation/AddNewLines';
import { Emojify } from './conversation/Emojify';
import { StagedLinkPreview } from './conversation/StagedLinkPreview';
import { TextAttachmentStyleType } from '../types/Attachment';
import { count } from '../util/grapheme';
import { getDomain } from '../types/LinkPreview';
import { getFontNameByTextScript } from '../util/getFontNameByTextScript';
import {
COLOR_WHITE_INT,
getHexFromNumber,
getBackgroundColor,
} from '../util/getStoryBackground';
@ -27,7 +30,6 @@ const renderNewLines: RenderTextCallbackType = ({
const CHAR_LIMIT_TEXT_LARGE = 50;
const CHAR_LIMIT_TEXT_MEDIUM = 200;
const COLOR_WHITE_INT = 4294704123;
const FONT_SIZE_LARGE = 64;
const FONT_SIZE_MEDIUM = 42;
const FONT_SIZE_SMALL = 32;
@ -40,7 +42,9 @@ enum TextSize {
export type PropsType = {
i18n: LocalizerType;
isEditingText?: boolean;
isThumbnail?: boolean;
onChange?: (text: string) => unknown;
textAttachment: TextAttachmentType;
};
@ -84,9 +88,24 @@ function getFont(
return `${fontWeight}${fontSize}pt ${fontName}`;
}
function getTextStyles(
textContent: string,
textForegroundColor?: number | null,
textStyle?: TextAttachmentStyleType | null,
i18n?: LocalizerType
): { color: string; font: string; textAlign: 'left' | 'center' } {
return {
color: getHexFromNumber(textForegroundColor || COLOR_WHITE_INT),
font: getFont(textContent, getTextSize(textContent), textStyle, i18n),
textAlign: getTextSize(textContent) === TextSize.Small ? 'left' : 'center',
};
}
export const TextAttachment = ({
i18n,
isEditingText,
isThumbnail,
onChange,
textAttachment,
}: PropsType): JSX.Element | null => {
const linkPreview = useRef<HTMLDivElement | null>(null);
@ -94,6 +113,20 @@ export const TextAttachment = ({
number | undefined
>();
const textContent = textAttachment.text || '';
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]);
return (
<Measure bounds>
{({ contentRect, measureRef }) => (
@ -119,62 +152,72 @@ export const TextAttachment = ({
transform: `scale(${(contentRect.bounds?.height || 1) / 1280})`,
}}
>
{textAttachment.text && (
{(textContent || onChange) && (
<div
className="TextAttachment__text"
style={{
backgroundColor: textAttachment.textBackgroundColor
? getHexFromNumber(textAttachment.textBackgroundColor)
: 'none',
color: getHexFromNumber(
textAttachment.textForegroundColor || COLOR_WHITE_INT
),
font: getFont(
textAttachment.text,
getTextSize(textAttachment.text),
textAttachment.textStyle,
i18n
),
textAlign:
getTextSize(textAttachment.text) === TextSize.Small
? 'left'
: 'center',
: 'transparent',
}}
>
<div className="TextAttachment__text__container">
<Emojify
text={textAttachment.text}
renderNonEmoji={renderNewLines}
{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}
/>
</div>
) : (
<div
className="TextAttachment__text__container"
style={getTextStyles(
textContent,
textAttachment.textForegroundColor,
textAttachment.textStyle,
i18n
)}
>
<Emojify
text={textContent}
renderNonEmoji={renderNewLines}
/>
</div>
)}
</div>
)}
{textAttachment.preview && (
{textAttachment.preview && textAttachment.preview.url && (
<>
{linkPreviewOffsetTop &&
!isThumbnail &&
textAttachment.preview.url && (
<a
className="TextAttachment__preview__tooltip"
href={textAttachment.preview.url}
rel="noreferrer"
style={{
top: linkPreviewOffsetTop - 150,
}}
target="_blank"
>
<div>
<div>{i18n('TextAttachment__preview__link')}</div>
<div className="TextAttachment__preview__tooltip__url">
{textAttachment.preview.url}
</div>
{linkPreviewOffsetTop && !isThumbnail && (
<a
className="TextAttachment__preview__tooltip"
href={textAttachment.preview.url}
rel="noreferrer"
style={{
top: linkPreviewOffsetTop - 150,
}}
target="_blank"
>
<div>
<div>{i18n('TextAttachment__preview__link')}</div>
<div className="TextAttachment__preview__tooltip__url">
{textAttachment.preview.url}
</div>
<div className="TextAttachment__preview__tooltip__arrow" />
</a>
)}
</div>
<div className="TextAttachment__preview__tooltip__arrow" />
</a>
)}
<div
className={classNames('TextAttachment__preview', {
'TextAttachment__preview--large': Boolean(
className={classNames('TextAttachment__preview-container', {
'TextAttachment__preview-container--large': Boolean(
textAttachment.preview.title
),
})}
@ -186,17 +229,14 @@ export const TextAttachment = ({
setLinkPreviewOffsetTop(linkPreview?.current?.offsetTop)
}
>
<div className="TextAttachment__preview__image" />
<div className="TextAttachment__preview__title">
{textAttachment.preview.title && (
<div className="TextAttachment__preview__title__container">
{textAttachment.preview.title}
</div>
)}
<div className="TextAttachment__preview__url">
{getDomain(String(textAttachment.preview.url))}
</div>
</div>
<StagedLinkPreview
domain={getDomain(String(textAttachment.preview.url))}
i18n={i18n}
image={textAttachment.preview.image}
moduleClassName="TextAttachment__preview"
title={textAttachment.preview.title || undefined}
url={textAttachment.preview.url}
/>
</div>
</>
)}